From e42129241681dde7adae7d20697e7b421682fbb4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 18:23:22 +0200 Subject: Adding upstream version 2.10.22. Signed-off-by: Daniel Baumann --- plug-ins/common/Makefile.am | 1857 +++++++ plug-ins/common/Makefile.in | 9373 ++++++++++++++++++++++++++++++++++ plug-ins/common/align-layers.c | 748 +++ plug-ins/common/animation-optimize.c | 1340 +++++ plug-ins/common/animation-play.c | 1759 +++++++ plug-ins/common/blinds.c | 730 +++ plug-ins/common/blur.c | 375 ++ plug-ins/common/border-average.c | 468 ++ plug-ins/common/busy-dialog.c | 309 ++ plug-ins/common/cartoon.c | 880 ++++ plug-ins/common/checkerboard.c | 525 ++ plug-ins/common/cml-explorer.c | 2569 ++++++++++ plug-ins/common/color-cube-analyze.c | 502 ++ plug-ins/common/color-enhance.c | 289 ++ plug-ins/common/colorify.c | 398 ++ plug-ins/common/colormap-remap.c | 752 +++ plug-ins/common/compose.c | 1402 +++++ plug-ins/common/contrast-retinex.c | 840 +++ plug-ins/common/crop-zealous.c | 326 ++ plug-ins/common/curve-bend.c | 3402 ++++++++++++ plug-ins/common/decompose.c | 973 ++++ plug-ins/common/depth-merge.c | 1050 ++++ plug-ins/common/despeckle.c | 901 ++++ plug-ins/common/destripe.c | 530 ++ plug-ins/common/edge-dog.c | 1029 ++++ plug-ins/common/emboss.c | 550 ++ plug-ins/common/file-aa.c | 412 ++ plug-ins/common/file-cel.c | 975 ++++ plug-ins/common/file-compressor.c | 1017 ++++ plug-ins/common/file-csource.c | 1045 ++++ plug-ins/common/file-desktop-link.c | 185 + plug-ins/common/file-dicom.c | 1643 ++++++ plug-ins/common/file-gbr.c | 367 ++ plug-ins/common/file-gegl.c | 481 ++ plug-ins/common/file-gif-load.c | 1252 +++++ plug-ins/common/file-gif-save.c | 2551 +++++++++ plug-ins/common/file-gih.c | 819 +++ plug-ins/common/file-glob.c | 450 ++ plug-ins/common/file-header.c | 439 ++ plug-ins/common/file-heif.c | 2076 ++++++++ plug-ins/common/file-html-table.c | 750 +++ plug-ins/common/file-jp2-load.c | 1344 +++++ plug-ins/common/file-mng.c | 1791 +++++++ plug-ins/common/file-pat.c | 320 ++ plug-ins/common/file-pcx.c | 1070 ++++ plug-ins/common/file-pdf-load.c | 1951 +++++++ plug-ins/common/file-pdf-save.c | 1791 +++++++ plug-ins/common/file-pix.c | 736 +++ plug-ins/common/file-png.c | 2650 ++++++++++ plug-ins/common/file-pnm.c | 1820 +++++++ plug-ins/common/file-ps.c | 3891 ++++++++++++++ plug-ins/common/file-psp.c | 2571 ++++++++++ plug-ins/common/file-raw-data.c | 2280 +++++++++ plug-ins/common/file-sunras.c | 1784 +++++++ plug-ins/common/file-svg.c | 914 ++++ plug-ins/common/file-tga.c | 1475 ++++++ plug-ins/common/file-wmf.c | 1049 ++++ plug-ins/common/file-xbm.c | 1445 ++++++ plug-ins/common/file-xmc.c | 2492 +++++++++ plug-ins/common/file-xpm.c | 881 ++++ plug-ins/common/file-xwd.c | 2587 ++++++++++ plug-ins/common/film.c | 1287 +++++ plug-ins/common/filter-pack.c | 2178 ++++++++ plug-ins/common/fractal-trace.c | 829 +++ plug-ins/common/gimprc.common | 89 + plug-ins/common/goat-exercise.c | 119 + plug-ins/common/gradient-map.c | 412 ++ plug-ins/common/grid.c | 1011 ++++ plug-ins/common/guillotine.c | 298 ++ plug-ins/common/hot.c | 782 +++ plug-ins/common/jigsaw.c | 2563 ++++++++++ plug-ins/common/mail.c | 830 +++ plug-ins/common/max-rgb.c | 373 ++ plug-ins/common/mkgen.pl | 234 + plug-ins/common/nl-filter.c | 1149 +++++ plug-ins/common/photocopy.c | 935 ++++ plug-ins/common/plugin-browser.c | 918 ++++ plug-ins/common/plugin-defs.pl | 91 + plug-ins/common/procedure-browser.c | 147 + plug-ins/common/qbist.c | 912 ++++ plug-ins/common/sample-colorize.c | 3113 +++++++++++ plug-ins/common/sharpen.c | 800 +++ plug-ins/common/smooth-palette.c | 499 ++ plug-ins/common/softglow.c | 713 +++ plug-ins/common/sparkle.c | 1189 +++++ plug-ins/common/sphere-designer.c | 3166 ++++++++++++ plug-ins/common/tile-small.c | 1130 ++++ plug-ins/common/tile.c | 505 ++ plug-ins/common/unit-editor.c | 700 +++ plug-ins/common/van-gogh-lic.c | 904 ++++ plug-ins/common/warp.c | 1793 +++++++ plug-ins/common/wavelet-decompose.c | 422 ++ plug-ins/common/web-browser.c | 214 + plug-ins/common/web-page.c | 551 ++ 94 files changed, 114037 insertions(+) create mode 100644 plug-ins/common/Makefile.am create mode 100644 plug-ins/common/Makefile.in create mode 100644 plug-ins/common/align-layers.c create mode 100644 plug-ins/common/animation-optimize.c create mode 100644 plug-ins/common/animation-play.c create mode 100644 plug-ins/common/blinds.c create mode 100644 plug-ins/common/blur.c create mode 100644 plug-ins/common/border-average.c create mode 100644 plug-ins/common/busy-dialog.c create mode 100644 plug-ins/common/cartoon.c create mode 100644 plug-ins/common/checkerboard.c create mode 100644 plug-ins/common/cml-explorer.c create mode 100644 plug-ins/common/color-cube-analyze.c create mode 100644 plug-ins/common/color-enhance.c create mode 100644 plug-ins/common/colorify.c create mode 100644 plug-ins/common/colormap-remap.c create mode 100644 plug-ins/common/compose.c create mode 100644 plug-ins/common/contrast-retinex.c create mode 100644 plug-ins/common/crop-zealous.c create mode 100644 plug-ins/common/curve-bend.c create mode 100644 plug-ins/common/decompose.c create mode 100644 plug-ins/common/depth-merge.c create mode 100644 plug-ins/common/despeckle.c create mode 100644 plug-ins/common/destripe.c create mode 100644 plug-ins/common/edge-dog.c create mode 100644 plug-ins/common/emboss.c create mode 100644 plug-ins/common/file-aa.c create mode 100644 plug-ins/common/file-cel.c create mode 100644 plug-ins/common/file-compressor.c create mode 100644 plug-ins/common/file-csource.c create mode 100644 plug-ins/common/file-desktop-link.c create mode 100644 plug-ins/common/file-dicom.c create mode 100644 plug-ins/common/file-gbr.c create mode 100644 plug-ins/common/file-gegl.c create mode 100644 plug-ins/common/file-gif-load.c create mode 100644 plug-ins/common/file-gif-save.c create mode 100644 plug-ins/common/file-gih.c create mode 100644 plug-ins/common/file-glob.c create mode 100644 plug-ins/common/file-header.c create mode 100644 plug-ins/common/file-heif.c create mode 100644 plug-ins/common/file-html-table.c create mode 100644 plug-ins/common/file-jp2-load.c create mode 100644 plug-ins/common/file-mng.c create mode 100644 plug-ins/common/file-pat.c create mode 100644 plug-ins/common/file-pcx.c create mode 100644 plug-ins/common/file-pdf-load.c create mode 100644 plug-ins/common/file-pdf-save.c create mode 100644 plug-ins/common/file-pix.c create mode 100644 plug-ins/common/file-png.c create mode 100644 plug-ins/common/file-pnm.c create mode 100644 plug-ins/common/file-ps.c create mode 100644 plug-ins/common/file-psp.c create mode 100644 plug-ins/common/file-raw-data.c create mode 100644 plug-ins/common/file-sunras.c create mode 100644 plug-ins/common/file-svg.c create mode 100644 plug-ins/common/file-tga.c create mode 100644 plug-ins/common/file-wmf.c create mode 100644 plug-ins/common/file-xbm.c create mode 100644 plug-ins/common/file-xmc.c create mode 100644 plug-ins/common/file-xpm.c create mode 100644 plug-ins/common/file-xwd.c create mode 100644 plug-ins/common/film.c create mode 100644 plug-ins/common/filter-pack.c create mode 100644 plug-ins/common/fractal-trace.c create mode 100644 plug-ins/common/gimprc.common create mode 100644 plug-ins/common/goat-exercise.c create mode 100644 plug-ins/common/gradient-map.c create mode 100644 plug-ins/common/grid.c create mode 100644 plug-ins/common/guillotine.c create mode 100644 plug-ins/common/hot.c create mode 100644 plug-ins/common/jigsaw.c create mode 100644 plug-ins/common/mail.c create mode 100644 plug-ins/common/max-rgb.c create mode 100755 plug-ins/common/mkgen.pl create mode 100644 plug-ins/common/nl-filter.c create mode 100644 plug-ins/common/photocopy.c create mode 100644 plug-ins/common/plugin-browser.c create mode 100644 plug-ins/common/plugin-defs.pl create mode 100644 plug-ins/common/procedure-browser.c create mode 100644 plug-ins/common/qbist.c create mode 100644 plug-ins/common/sample-colorize.c create mode 100644 plug-ins/common/sharpen.c create mode 100644 plug-ins/common/smooth-palette.c create mode 100644 plug-ins/common/softglow.c create mode 100644 plug-ins/common/sparkle.c create mode 100644 plug-ins/common/sphere-designer.c create mode 100644 plug-ins/common/tile-small.c create mode 100644 plug-ins/common/tile.c create mode 100644 plug-ins/common/unit-editor.c create mode 100644 plug-ins/common/van-gogh-lic.c create mode 100644 plug-ins/common/warp.c create mode 100644 plug-ins/common/wavelet-decompose.c create mode 100644 plug-ins/common/web-browser.c create mode 100644 plug-ins/common/web-page.c (limited to 'plug-ins/common') diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am new file mode 100644 index 0000000..7b14173 --- /dev/null +++ b/plug-ins/common/Makefile.am @@ -0,0 +1,1857 @@ + + +## --------------------------------------------------------- +## This file is autogenerated by mkgen.pl and plugin-defs.pl +## --------------------------------------------------------- + +## Modify those two files instead of this one; for most +## plug-ins you should only need to modify plugin-defs.pl. + +if OS_WIN32 +mwindows = -mwindows +else +libm = -lm +endif + +if PLATFORM_OSX +xobjective_c = "-xobjective-c" +framework_cocoa = -framework Cocoa +endif + +if HAVE_WINDRES +include $(top_srcdir)/build/windows/gimprc-plug-ins.rule +include gimprc.common +endif + +libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la $(libm) +libgimpmodule = $(top_builddir)/libgimpmodule/libgimpmodule-$(GIMP_API_VERSION).la +libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la +libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la + + +AM_LDFLAGS = $(mwindows) + +EXTRA_DIST = \ + mkgen.pl \ + plugin-defs.pl \ + gimprc.common + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(GEGL_CFLAGS) \ + -I$(includedir) + +align_layers_libexecdir = $(gimpplugindir)/plug-ins/align-layers +animation_optimize_libexecdir = $(gimpplugindir)/plug-ins/animation-optimize +animation_play_libexecdir = $(gimpplugindir)/plug-ins/animation-play +blinds_libexecdir = $(gimpplugindir)/plug-ins/blinds +blur_libexecdir = $(gimpplugindir)/plug-ins/blur +border_average_libexecdir = $(gimpplugindir)/plug-ins/border-average +busy_dialog_libexecdir = $(gimpplugindir)/plug-ins/busy-dialog +cartoon_libexecdir = $(gimpplugindir)/plug-ins/cartoon +checkerboard_libexecdir = $(gimpplugindir)/plug-ins/checkerboard +cml_explorer_libexecdir = $(gimpplugindir)/plug-ins/cml-explorer +color_cube_analyze_libexecdir = $(gimpplugindir)/plug-ins/color-cube-analyze +color_enhance_libexecdir = $(gimpplugindir)/plug-ins/color-enhance +colorify_libexecdir = $(gimpplugindir)/plug-ins/colorify +colormap_remap_libexecdir = $(gimpplugindir)/plug-ins/colormap-remap +compose_libexecdir = $(gimpplugindir)/plug-ins/compose +contrast_retinex_libexecdir = $(gimpplugindir)/plug-ins/contrast-retinex +crop_zealous_libexecdir = $(gimpplugindir)/plug-ins/crop-zealous +curve_bend_libexecdir = $(gimpplugindir)/plug-ins/curve-bend +decompose_libexecdir = $(gimpplugindir)/plug-ins/decompose +depth_merge_libexecdir = $(gimpplugindir)/plug-ins/depth-merge +despeckle_libexecdir = $(gimpplugindir)/plug-ins/despeckle +destripe_libexecdir = $(gimpplugindir)/plug-ins/destripe +edge_dog_libexecdir = $(gimpplugindir)/plug-ins/edge-dog +emboss_libexecdir = $(gimpplugindir)/plug-ins/emboss +file_aa_libexecdir = $(gimpplugindir)/plug-ins/file-aa +file_cel_libexecdir = $(gimpplugindir)/plug-ins/file-cel +file_compressor_libexecdir = $(gimpplugindir)/plug-ins/file-compressor +file_csource_libexecdir = $(gimpplugindir)/plug-ins/file-csource +file_desktop_link_libexecdir = $(gimpplugindir)/plug-ins/file-desktop-link +file_dicom_libexecdir = $(gimpplugindir)/plug-ins/file-dicom +file_gbr_libexecdir = $(gimpplugindir)/plug-ins/file-gbr +file_gegl_libexecdir = $(gimpplugindir)/plug-ins/file-gegl +file_gif_load_libexecdir = $(gimpplugindir)/plug-ins/file-gif-load +file_gif_save_libexecdir = $(gimpplugindir)/plug-ins/file-gif-save +file_gih_libexecdir = $(gimpplugindir)/plug-ins/file-gih +file_glob_libexecdir = $(gimpplugindir)/plug-ins/file-glob +file_header_libexecdir = $(gimpplugindir)/plug-ins/file-header +file_heif_libexecdir = $(gimpplugindir)/plug-ins/file-heif +file_html_table_libexecdir = $(gimpplugindir)/plug-ins/file-html-table +file_jp2_load_libexecdir = $(gimpplugindir)/plug-ins/file-jp2-load +file_mng_libexecdir = $(gimpplugindir)/plug-ins/file-mng +file_pat_libexecdir = $(gimpplugindir)/plug-ins/file-pat +file_pcx_libexecdir = $(gimpplugindir)/plug-ins/file-pcx +file_pdf_load_libexecdir = $(gimpplugindir)/plug-ins/file-pdf-load +file_pdf_save_libexecdir = $(gimpplugindir)/plug-ins/file-pdf-save +file_pix_libexecdir = $(gimpplugindir)/plug-ins/file-pix +file_png_libexecdir = $(gimpplugindir)/plug-ins/file-png +file_pnm_libexecdir = $(gimpplugindir)/plug-ins/file-pnm +file_ps_libexecdir = $(gimpplugindir)/plug-ins/file-ps +file_psp_libexecdir = $(gimpplugindir)/plug-ins/file-psp +file_raw_data_libexecdir = $(gimpplugindir)/plug-ins/file-raw-data +file_sunras_libexecdir = $(gimpplugindir)/plug-ins/file-sunras +file_svg_libexecdir = $(gimpplugindir)/plug-ins/file-svg +file_tga_libexecdir = $(gimpplugindir)/plug-ins/file-tga +file_wmf_libexecdir = $(gimpplugindir)/plug-ins/file-wmf +file_xbm_libexecdir = $(gimpplugindir)/plug-ins/file-xbm +file_xmc_libexecdir = $(gimpplugindir)/plug-ins/file-xmc +file_xpm_libexecdir = $(gimpplugindir)/plug-ins/file-xpm +file_xwd_libexecdir = $(gimpplugindir)/plug-ins/file-xwd +film_libexecdir = $(gimpplugindir)/plug-ins/film +filter_pack_libexecdir = $(gimpplugindir)/plug-ins/filter-pack +fractal_trace_libexecdir = $(gimpplugindir)/plug-ins/fractal-trace +goat_exercise_libexecdir = $(gimpplugindir)/plug-ins/goat-exercise +gradient_map_libexecdir = $(gimpplugindir)/plug-ins/gradient-map +grid_libexecdir = $(gimpplugindir)/plug-ins/grid +guillotine_libexecdir = $(gimpplugindir)/plug-ins/guillotine +hot_libexecdir = $(gimpplugindir)/plug-ins/hot +jigsaw_libexecdir = $(gimpplugindir)/plug-ins/jigsaw +mail_libexecdir = $(gimpplugindir)/plug-ins/mail +max_rgb_libexecdir = $(gimpplugindir)/plug-ins/max-rgb +nl_filter_libexecdir = $(gimpplugindir)/plug-ins/nl-filter +photocopy_libexecdir = $(gimpplugindir)/plug-ins/photocopy +plugin_browser_libexecdir = $(gimpplugindir)/plug-ins/plugin-browser +procedure_browser_libexecdir = $(gimpplugindir)/plug-ins/procedure-browser +qbist_libexecdir = $(gimpplugindir)/plug-ins/qbist +sample_colorize_libexecdir = $(gimpplugindir)/plug-ins/sample-colorize +sharpen_libexecdir = $(gimpplugindir)/plug-ins/sharpen +smooth_palette_libexecdir = $(gimpplugindir)/plug-ins/smooth-palette +softglow_libexecdir = $(gimpplugindir)/plug-ins/softglow +sparkle_libexecdir = $(gimpplugindir)/plug-ins/sparkle +sphere_designer_libexecdir = $(gimpplugindir)/plug-ins/sphere-designer +tile_libexecdir = $(gimpplugindir)/plug-ins/tile +tile_small_libexecdir = $(gimpplugindir)/plug-ins/tile-small +unit_editor_libexecdir = $(gimpplugindir)/plug-ins/unit-editor +van_gogh_lic_libexecdir = $(gimpplugindir)/plug-ins/van-gogh-lic +warp_libexecdir = $(gimpplugindir)/plug-ins/warp +wavelet_decompose_libexecdir = $(gimpplugindir)/plug-ins/wavelet-decompose +web_browser_libexecdir = $(gimpplugindir)/plug-ins/web-browser +web_page_libexecdir = $(gimpplugindir)/plug-ins/web-page + + +align_layers_libexec_PROGRAMS = align-layers +animation_optimize_libexec_PROGRAMS = animation-optimize +animation_play_libexec_PROGRAMS = animation-play +blinds_libexec_PROGRAMS = blinds +blur_libexec_PROGRAMS = blur +border_average_libexec_PROGRAMS = border-average +busy_dialog_libexec_PROGRAMS = busy-dialog +cartoon_libexec_PROGRAMS = cartoon +checkerboard_libexec_PROGRAMS = checkerboard +cml_explorer_libexec_PROGRAMS = cml-explorer +color_cube_analyze_libexec_PROGRAMS = color-cube-analyze +color_enhance_libexec_PROGRAMS = color-enhance +colorify_libexec_PROGRAMS = colorify +colormap_remap_libexec_PROGRAMS = colormap-remap +compose_libexec_PROGRAMS = compose +contrast_retinex_libexec_PROGRAMS = contrast-retinex +crop_zealous_libexec_PROGRAMS = crop-zealous +curve_bend_libexec_PROGRAMS = curve-bend +decompose_libexec_PROGRAMS = decompose +depth_merge_libexec_PROGRAMS = depth-merge +despeckle_libexec_PROGRAMS = despeckle +destripe_libexec_PROGRAMS = destripe +edge_dog_libexec_PROGRAMS = edge-dog +emboss_libexec_PROGRAMS = emboss +file_aa_libexec_PROGRAMS = $(FILE_AA) +file_cel_libexec_PROGRAMS = file-cel +file_compressor_libexec_PROGRAMS = file-compressor +file_csource_libexec_PROGRAMS = file-csource +file_desktop_link_libexec_PROGRAMS = file-desktop-link +file_dicom_libexec_PROGRAMS = file-dicom +file_gbr_libexec_PROGRAMS = file-gbr +file_gegl_libexec_PROGRAMS = file-gegl +file_gif_load_libexec_PROGRAMS = file-gif-load +file_gif_save_libexec_PROGRAMS = file-gif-save +file_gih_libexec_PROGRAMS = file-gih +file_glob_libexec_PROGRAMS = file-glob +file_header_libexec_PROGRAMS = file-header +file_heif_libexec_PROGRAMS = $(FILE_HEIF) +file_html_table_libexec_PROGRAMS = file-html-table +file_jp2_load_libexec_PROGRAMS = $(FILE_JP2_LOAD) +file_mng_libexec_PROGRAMS = $(FILE_MNG) +file_pat_libexec_PROGRAMS = file-pat +file_pcx_libexec_PROGRAMS = file-pcx +file_pdf_load_libexec_PROGRAMS = file-pdf-load +file_pdf_save_libexec_PROGRAMS = $(FILE_PDF_SAVE) +file_pix_libexec_PROGRAMS = file-pix +file_png_libexec_PROGRAMS = file-png +file_pnm_libexec_PROGRAMS = file-pnm +file_ps_libexec_PROGRAMS = $(FILE_PS) +file_psp_libexec_PROGRAMS = file-psp +file_raw_data_libexec_PROGRAMS = file-raw-data +file_sunras_libexec_PROGRAMS = file-sunras +file_svg_libexec_PROGRAMS = file-svg +file_tga_libexec_PROGRAMS = file-tga +file_wmf_libexec_PROGRAMS = $(FILE_WMF) +file_xbm_libexec_PROGRAMS = file-xbm +file_xmc_libexec_PROGRAMS = $(FILE_XMC) +file_xpm_libexec_PROGRAMS = $(FILE_XPM) +file_xwd_libexec_PROGRAMS = file-xwd +film_libexec_PROGRAMS = film +filter_pack_libexec_PROGRAMS = filter-pack +fractal_trace_libexec_PROGRAMS = fractal-trace +goat_exercise_libexec_PROGRAMS = goat-exercise +gradient_map_libexec_PROGRAMS = gradient-map +grid_libexec_PROGRAMS = grid +guillotine_libexec_PROGRAMS = guillotine +hot_libexec_PROGRAMS = hot +jigsaw_libexec_PROGRAMS = jigsaw +mail_libexec_PROGRAMS = $(MAIL) +max_rgb_libexec_PROGRAMS = max-rgb +nl_filter_libexec_PROGRAMS = nl-filter +photocopy_libexec_PROGRAMS = photocopy +plugin_browser_libexec_PROGRAMS = plugin-browser +procedure_browser_libexec_PROGRAMS = procedure-browser +qbist_libexec_PROGRAMS = qbist +sample_colorize_libexec_PROGRAMS = sample-colorize +sharpen_libexec_PROGRAMS = sharpen +smooth_palette_libexec_PROGRAMS = smooth-palette +softglow_libexec_PROGRAMS = softglow +sparkle_libexec_PROGRAMS = sparkle +sphere_designer_libexec_PROGRAMS = sphere-designer +tile_libexec_PROGRAMS = tile +tile_small_libexec_PROGRAMS = tile-small +unit_editor_libexec_PROGRAMS = unit-editor +van_gogh_lic_libexec_PROGRAMS = van-gogh-lic +warp_libexec_PROGRAMS = warp +wavelet_decompose_libexec_PROGRAMS = wavelet-decompose +web_browser_libexec_PROGRAMS = web-browser +web_page_libexec_PROGRAMS = $(WEB_PAGE) + + +EXTRA_PROGRAMS = \ + file-aa \ + file-heif \ + file-jp2-load \ + file-mng \ + file-pdf-save \ + file-ps \ + file-wmf \ + file-xmc \ + file-xpm \ + mail \ + web-page + +install-%: % + @$(NORMAL_INSTALL) + $(mkinstalldirs) $(DESTDIR)$(gimpplugindir)/plug-ins/$< + @p=$<; p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + || test -f $$p1 \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) $$p $(DESTDIR)$(gimpplugindir)/plug-ins/$$p/$$f"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) $$p $(DESTDIR)$(gimpplugindir)/plug-ins/$$p/$$f || exit 1; \ + else :; fi + +align_layers_SOURCES = \ + align-layers.c + +align_layers_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(align_layers_RC) + +animation_optimize_SOURCES = \ + animation-optimize.c + +animation_optimize_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(animation_optimize_RC) + +animation_play_SOURCES = \ + animation-play.c + +animation_play_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(animation_play_RC) + +blinds_SOURCES = \ + blinds.c + +blinds_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(blinds_RC) + +blur_SOURCES = \ + blur.c + +blur_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(blur_RC) + +border_average_SOURCES = \ + border-average.c + +border_average_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(border_average_RC) + +busy_dialog_SOURCES = \ + busy-dialog.c + +busy_dialog_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(busy_dialog_RC) + +cartoon_SOURCES = \ + cartoon.c + +cartoon_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(cartoon_RC) + +checkerboard_SOURCES = \ + checkerboard.c + +checkerboard_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(checkerboard_RC) + +cml_explorer_SOURCES = \ + cml-explorer.c + +cml_explorer_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(cml_explorer_RC) + +color_cube_analyze_SOURCES = \ + color-cube-analyze.c + +color_cube_analyze_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(color_cube_analyze_RC) + +color_enhance_SOURCES = \ + color-enhance.c + +color_enhance_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(color_enhance_RC) + +colorify_SOURCES = \ + colorify.c + +colorify_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(colorify_RC) + +colormap_remap_SOURCES = \ + colormap-remap.c + +colormap_remap_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(colormap_remap_RC) + +compose_SOURCES = \ + compose.c + +compose_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(compose_RC) + +contrast_retinex_SOURCES = \ + contrast-retinex.c + +contrast_retinex_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(contrast_retinex_RC) + +crop_zealous_SOURCES = \ + crop-zealous.c + +crop_zealous_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(crop_zealous_RC) + +curve_bend_SOURCES = \ + curve-bend.c + +curve_bend_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(curve_bend_RC) + +decompose_SOURCES = \ + decompose.c + +decompose_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(decompose_RC) + +depth_merge_SOURCES = \ + depth-merge.c + +depth_merge_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(depth_merge_RC) + +despeckle_SOURCES = \ + despeckle.c + +despeckle_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(despeckle_RC) + +destripe_SOURCES = \ + destripe.c + +destripe_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(destripe_RC) + +edge_dog_SOURCES = \ + edge-dog.c + +edge_dog_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(edge_dog_RC) + +emboss_SOURCES = \ + emboss.c + +emboss_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(emboss_RC) + +file_aa_SOURCES = \ + file-aa.c + +file_aa_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(AA_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_aa_RC) + +file_cel_SOURCES = \ + file-cel.c + +file_cel_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_cel_RC) + +file_compressor_CFLAGS = $(LZMA_CFLAGS) + +file_compressor_SOURCES = \ + file-compressor.c + +file_compressor_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GIO_LIBS) \ + $(LZMA_LIBS) \ + $(BZIP2_LIBS) \ + $(Z_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_compressor_RC) + +file_csource_SOURCES = \ + file-csource.c + +file_csource_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_csource_RC) + +file_desktop_link_SOURCES = \ + file-desktop-link.c + +file_desktop_link_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_desktop_link_RC) + +file_dicom_CFLAGS = -fno-strict-aliasing + +file_dicom_SOURCES = \ + file-dicom.c + +file_dicom_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_dicom_RC) + +file_gbr_SOURCES = \ + file-gbr.c + +file_gbr_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gbr_RC) + +file_gegl_SOURCES = \ + file-gegl.c + +file_gegl_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gegl_RC) + +file_gif_load_SOURCES = \ + file-gif-load.c + +file_gif_load_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gif_load_RC) + +file_gif_save_SOURCES = \ + file-gif-save.c + +file_gif_save_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gif_save_RC) + +file_gih_SOURCES = \ + file-gih.c + +file_gih_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gih_RC) + +file_glob_SOURCES = \ + file-glob.c + +file_glob_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_glob_RC) + +file_header_SOURCES = \ + file-header.c + +file_header_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_header_RC) + +file_heif_CFLAGS = $(LIBHEIF_CFLAGS) + +file_heif_SOURCES = \ + file-heif.c + +file_heif_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEXIV2_LIBS) \ + $(GEGL_LIBS) \ + $(LIBHEIF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(LCMS_LIBS) \ + $(file_heif_RC) + +file_html_table_SOURCES = \ + file-html-table.c + +file_html_table_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_html_table_RC) + +file_jp2_load_CFLAGS = $(OPENJPEG_CFLAGS) + +file_jp2_load_SOURCES = \ + file-jp2-load.c + +file_jp2_load_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(OPENJPEG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_jp2_load_RC) + +file_mng_CFLAGS = $(MNG_CFLAGS) + +file_mng_SOURCES = \ + file-mng.c + +file_mng_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(MNG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_mng_RC) + +file_pat_SOURCES = \ + file-pat.c + +file_pat_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pat_RC) + +file_pcx_SOURCES = \ + file-pcx.c + +file_pcx_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pcx_RC) + +file_pdf_load_CFLAGS = $(POPPLER_CFLAGS) + +file_pdf_load_SOURCES = \ + file-pdf-load.c + +file_pdf_load_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(POPPLER_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pdf_load_RC) + +file_pdf_save_CFLAGS = $(CAIRO_PDF_CFLAGS) + +file_pdf_save_SOURCES = \ + file-pdf-save.c + +file_pdf_save_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(CAIRO_PDF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pdf_save_RC) + +file_pix_SOURCES = \ + file-pix.c + +file_pix_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pix_RC) + +file_png_CFLAGS = $(PNG_CFLAGS) + +file_png_SOURCES = \ + file-png.c + +file_png_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(PNG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_png_RC) + +file_pnm_SOURCES = \ + file-pnm.c + +file_pnm_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pnm_RC) + +file_ps_SOURCES = \ + file-ps.c + +file_ps_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(GS_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_ps_RC) + +file_psp_SOURCES = \ + file-psp.c + +file_psp_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(Z_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_psp_RC) + +file_raw_data_SOURCES = \ + file-raw-data.c + +file_raw_data_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_raw_data_RC) + +file_sunras_SOURCES = \ + file-sunras.c + +file_sunras_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_sunras_RC) + +file_svg_CFLAGS = $(SVG_CFLAGS) + +file_svg_SOURCES = \ + file-svg.c + +file_svg_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(SVG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_svg_RC) + +file_tga_SOURCES = \ + file-tga.c + +file_tga_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_tga_RC) + +file_wmf_CFLAGS = $(WMF_CFLAGS) + +file_wmf_SOURCES = \ + file-wmf.c + +file_wmf_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(WMF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_wmf_RC) + +file_xbm_SOURCES = \ + file-xbm.c + +file_xbm_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xbm_RC) + +file_xmc_SOURCES = \ + file-xmc.c + +file_xmc_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(XMC_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xmc_RC) + +file_xpm_SOURCES = \ + file-xpm.c + +file_xpm_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(XPM_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xpm_RC) + +file_xwd_SOURCES = \ + file-xwd.c + +file_xwd_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xwd_RC) + +film_SOURCES = \ + film.c + +film_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(film_RC) + +filter_pack_SOURCES = \ + filter-pack.c + +filter_pack_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(filter_pack_RC) + +fractal_trace_SOURCES = \ + fractal-trace.c + +fractal_trace_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(fractal_trace_RC) + +goat_exercise_SOURCES = \ + goat-exercise.c + +goat_exercise_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(goat_exercise_RC) + +gradient_map_SOURCES = \ + gradient-map.c + +gradient_map_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(gradient_map_RC) + +grid_SOURCES = \ + grid.c + +grid_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(grid_RC) + +guillotine_SOURCES = \ + guillotine.c + +guillotine_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(guillotine_RC) + +hot_SOURCES = \ + hot.c + +hot_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(hot_RC) + +jigsaw_SOURCES = \ + jigsaw.c + +jigsaw_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(jigsaw_RC) + +mail_SOURCES = \ + mail.c + +mail_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(mail_RC) + +max_rgb_SOURCES = \ + max-rgb.c + +max_rgb_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(max_rgb_RC) + +nl_filter_SOURCES = \ + nl-filter.c + +nl_filter_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(nl_filter_RC) + +photocopy_SOURCES = \ + photocopy.c + +photocopy_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(photocopy_RC) + +plugin_browser_SOURCES = \ + plugin-browser.c + +plugin_browser_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(plugin_browser_RC) + +procedure_browser_SOURCES = \ + procedure-browser.c + +procedure_browser_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(procedure_browser_RC) + +qbist_SOURCES = \ + qbist.c + +qbist_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(qbist_RC) + +sample_colorize_SOURCES = \ + sample-colorize.c + +sample_colorize_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sample_colorize_RC) + +sharpen_SOURCES = \ + sharpen.c + +sharpen_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sharpen_RC) + +smooth_palette_SOURCES = \ + smooth-palette.c + +smooth_palette_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(smooth_palette_RC) + +softglow_SOURCES = \ + softglow.c + +softglow_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(softglow_RC) + +sparkle_SOURCES = \ + sparkle.c + +sparkle_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sparkle_RC) + +sphere_designer_SOURCES = \ + sphere-designer.c + +sphere_designer_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sphere_designer_RC) + +tile_SOURCES = \ + tile.c + +tile_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(tile_RC) + +tile_small_SOURCES = \ + tile-small.c + +tile_small_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(tile_small_RC) + +unit_editor_SOURCES = \ + unit-editor.c + +unit_editor_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(unit_editor_RC) + +van_gogh_lic_SOURCES = \ + van-gogh-lic.c + +van_gogh_lic_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(van_gogh_lic_RC) + +warp_SOURCES = \ + warp.c + +warp_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(warp_RC) + +wavelet_decompose_SOURCES = \ + wavelet-decompose.c + +wavelet_decompose_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(wavelet_decompose_RC) + +web_browser_LDFLAGS = $(framework_cocoa) + +web_browser_CPPFLAGS = $(AM_CPPFLAGS) $(xobjective_c) + +web_browser_SOURCES = \ + web-browser.c + +web_browser_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(web_browser_RC) + +web_page_CFLAGS = $(WEBKIT_CFLAGS) + +web_page_SOURCES = \ + web-page.c + +web_page_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(WEBKIT_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(web_page_RC) diff --git a/plug-ins/common/Makefile.in b/plug-ins/common/Makefile.in new file mode 100644 index 0000000..ea480ee --- /dev/null +++ b/plug-ins/common/Makefile.in @@ -0,0 +1,9373 @@ +# Makefile.in generated by automake 1.16.2 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Version resources for Microsoft Windows + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +align_layers_libexec_PROGRAMS = align-layers$(EXEEXT) +animation_optimize_libexec_PROGRAMS = animation-optimize$(EXEEXT) +animation_play_libexec_PROGRAMS = animation-play$(EXEEXT) +blinds_libexec_PROGRAMS = blinds$(EXEEXT) +blur_libexec_PROGRAMS = blur$(EXEEXT) +border_average_libexec_PROGRAMS = border-average$(EXEEXT) +busy_dialog_libexec_PROGRAMS = busy-dialog$(EXEEXT) +cartoon_libexec_PROGRAMS = cartoon$(EXEEXT) +checkerboard_libexec_PROGRAMS = checkerboard$(EXEEXT) +cml_explorer_libexec_PROGRAMS = cml-explorer$(EXEEXT) +color_cube_analyze_libexec_PROGRAMS = color-cube-analyze$(EXEEXT) +color_enhance_libexec_PROGRAMS = color-enhance$(EXEEXT) +colorify_libexec_PROGRAMS = colorify$(EXEEXT) +colormap_remap_libexec_PROGRAMS = colormap-remap$(EXEEXT) +compose_libexec_PROGRAMS = compose$(EXEEXT) +contrast_retinex_libexec_PROGRAMS = contrast-retinex$(EXEEXT) +crop_zealous_libexec_PROGRAMS = crop-zealous$(EXEEXT) +curve_bend_libexec_PROGRAMS = curve-bend$(EXEEXT) +decompose_libexec_PROGRAMS = decompose$(EXEEXT) +depth_merge_libexec_PROGRAMS = depth-merge$(EXEEXT) +despeckle_libexec_PROGRAMS = despeckle$(EXEEXT) +destripe_libexec_PROGRAMS = destripe$(EXEEXT) +edge_dog_libexec_PROGRAMS = edge-dog$(EXEEXT) +emboss_libexec_PROGRAMS = emboss$(EXEEXT) +file_cel_libexec_PROGRAMS = file-cel$(EXEEXT) +file_compressor_libexec_PROGRAMS = file-compressor$(EXEEXT) +file_csource_libexec_PROGRAMS = file-csource$(EXEEXT) +file_desktop_link_libexec_PROGRAMS = file-desktop-link$(EXEEXT) +file_dicom_libexec_PROGRAMS = file-dicom$(EXEEXT) +file_gbr_libexec_PROGRAMS = file-gbr$(EXEEXT) +file_gegl_libexec_PROGRAMS = file-gegl$(EXEEXT) +file_gif_load_libexec_PROGRAMS = file-gif-load$(EXEEXT) +file_gif_save_libexec_PROGRAMS = file-gif-save$(EXEEXT) +file_gih_libexec_PROGRAMS = file-gih$(EXEEXT) +file_glob_libexec_PROGRAMS = file-glob$(EXEEXT) +file_header_libexec_PROGRAMS = file-header$(EXEEXT) +file_html_table_libexec_PROGRAMS = file-html-table$(EXEEXT) +file_pat_libexec_PROGRAMS = file-pat$(EXEEXT) +file_pcx_libexec_PROGRAMS = file-pcx$(EXEEXT) +file_pdf_load_libexec_PROGRAMS = file-pdf-load$(EXEEXT) +file_pix_libexec_PROGRAMS = file-pix$(EXEEXT) +file_png_libexec_PROGRAMS = file-png$(EXEEXT) +file_pnm_libexec_PROGRAMS = file-pnm$(EXEEXT) +file_psp_libexec_PROGRAMS = file-psp$(EXEEXT) +file_raw_data_libexec_PROGRAMS = file-raw-data$(EXEEXT) +file_sunras_libexec_PROGRAMS = file-sunras$(EXEEXT) +file_svg_libexec_PROGRAMS = file-svg$(EXEEXT) +file_tga_libexec_PROGRAMS = file-tga$(EXEEXT) +file_xbm_libexec_PROGRAMS = file-xbm$(EXEEXT) +file_xwd_libexec_PROGRAMS = file-xwd$(EXEEXT) +film_libexec_PROGRAMS = film$(EXEEXT) +filter_pack_libexec_PROGRAMS = filter-pack$(EXEEXT) +fractal_trace_libexec_PROGRAMS = fractal-trace$(EXEEXT) +goat_exercise_libexec_PROGRAMS = goat-exercise$(EXEEXT) +gradient_map_libexec_PROGRAMS = gradient-map$(EXEEXT) +grid_libexec_PROGRAMS = grid$(EXEEXT) +guillotine_libexec_PROGRAMS = guillotine$(EXEEXT) +hot_libexec_PROGRAMS = hot$(EXEEXT) +jigsaw_libexec_PROGRAMS = jigsaw$(EXEEXT) +max_rgb_libexec_PROGRAMS = max-rgb$(EXEEXT) +nl_filter_libexec_PROGRAMS = nl-filter$(EXEEXT) +photocopy_libexec_PROGRAMS = photocopy$(EXEEXT) +plugin_browser_libexec_PROGRAMS = plugin-browser$(EXEEXT) +procedure_browser_libexec_PROGRAMS = procedure-browser$(EXEEXT) +qbist_libexec_PROGRAMS = qbist$(EXEEXT) +sample_colorize_libexec_PROGRAMS = sample-colorize$(EXEEXT) +sharpen_libexec_PROGRAMS = sharpen$(EXEEXT) +smooth_palette_libexec_PROGRAMS = smooth-palette$(EXEEXT) +softglow_libexec_PROGRAMS = softglow$(EXEEXT) +sparkle_libexec_PROGRAMS = sparkle$(EXEEXT) +sphere_designer_libexec_PROGRAMS = sphere-designer$(EXEEXT) +tile_libexec_PROGRAMS = tile$(EXEEXT) +tile_small_libexec_PROGRAMS = tile-small$(EXEEXT) +unit_editor_libexec_PROGRAMS = unit-editor$(EXEEXT) +van_gogh_lic_libexec_PROGRAMS = van-gogh-lic$(EXEEXT) +warp_libexec_PROGRAMS = warp$(EXEEXT) +wavelet_decompose_libexec_PROGRAMS = wavelet-decompose$(EXEEXT) +web_browser_libexec_PROGRAMS = web-browser$(EXEEXT) +EXTRA_PROGRAMS = file-aa$(EXEEXT) file-heif$(EXEEXT) \ + file-jp2-load$(EXEEXT) file-mng$(EXEEXT) \ + file-pdf-save$(EXEEXT) file-ps$(EXEEXT) file-wmf$(EXEEXT) \ + file-xmc$(EXEEXT) file-xpm$(EXEEXT) mail$(EXEEXT) \ + web-page$(EXEEXT) +subdir = plug-ins/common +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/gtk-doc.m4 \ + $(top_srcdir)/m4macros/intltool.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/m4macros/alsa.m4 \ + $(top_srcdir)/m4macros/ax_compare_version.m4 \ + $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \ + $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \ + $(top_srcdir)/m4macros/detectcflags.m4 \ + $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(align_layers_libexecdir)" \ + "$(DESTDIR)$(animation_optimize_libexecdir)" \ + "$(DESTDIR)$(animation_play_libexecdir)" \ + "$(DESTDIR)$(blinds_libexecdir)" \ + "$(DESTDIR)$(blur_libexecdir)" \ + "$(DESTDIR)$(border_average_libexecdir)" \ + "$(DESTDIR)$(busy_dialog_libexecdir)" \ + "$(DESTDIR)$(cartoon_libexecdir)" \ + "$(DESTDIR)$(checkerboard_libexecdir)" \ + "$(DESTDIR)$(cml_explorer_libexecdir)" \ + "$(DESTDIR)$(color_cube_analyze_libexecdir)" \ + "$(DESTDIR)$(color_enhance_libexecdir)" \ + "$(DESTDIR)$(colorify_libexecdir)" \ + "$(DESTDIR)$(colormap_remap_libexecdir)" \ + "$(DESTDIR)$(compose_libexecdir)" \ + "$(DESTDIR)$(contrast_retinex_libexecdir)" \ + "$(DESTDIR)$(crop_zealous_libexecdir)" \ + "$(DESTDIR)$(curve_bend_libexecdir)" \ + "$(DESTDIR)$(decompose_libexecdir)" \ + "$(DESTDIR)$(depth_merge_libexecdir)" \ + "$(DESTDIR)$(despeckle_libexecdir)" \ + "$(DESTDIR)$(destripe_libexecdir)" \ + "$(DESTDIR)$(edge_dog_libexecdir)" \ + "$(DESTDIR)$(emboss_libexecdir)" \ + "$(DESTDIR)$(file_aa_libexecdir)" \ + "$(DESTDIR)$(file_cel_libexecdir)" \ + "$(DESTDIR)$(file_compressor_libexecdir)" \ + "$(DESTDIR)$(file_csource_libexecdir)" \ + "$(DESTDIR)$(file_desktop_link_libexecdir)" \ + "$(DESTDIR)$(file_dicom_libexecdir)" \ + "$(DESTDIR)$(file_gbr_libexecdir)" \ + "$(DESTDIR)$(file_gegl_libexecdir)" \ + "$(DESTDIR)$(file_gif_load_libexecdir)" \ + "$(DESTDIR)$(file_gif_save_libexecdir)" \ + "$(DESTDIR)$(file_gih_libexecdir)" \ + "$(DESTDIR)$(file_glob_libexecdir)" \ + "$(DESTDIR)$(file_header_libexecdir)" \ + "$(DESTDIR)$(file_heif_libexecdir)" \ + "$(DESTDIR)$(file_html_table_libexecdir)" \ + "$(DESTDIR)$(file_jp2_load_libexecdir)" \ + "$(DESTDIR)$(file_mng_libexecdir)" \ + "$(DESTDIR)$(file_pat_libexecdir)" \ + "$(DESTDIR)$(file_pcx_libexecdir)" \ + "$(DESTDIR)$(file_pdf_load_libexecdir)" \ + "$(DESTDIR)$(file_pdf_save_libexecdir)" \ + "$(DESTDIR)$(file_pix_libexecdir)" \ + "$(DESTDIR)$(file_png_libexecdir)" \ + "$(DESTDIR)$(file_pnm_libexecdir)" \ + "$(DESTDIR)$(file_ps_libexecdir)" \ + "$(DESTDIR)$(file_psp_libexecdir)" \ + "$(DESTDIR)$(file_raw_data_libexecdir)" \ + "$(DESTDIR)$(file_sunras_libexecdir)" \ + "$(DESTDIR)$(file_svg_libexecdir)" \ + "$(DESTDIR)$(file_tga_libexecdir)" \ + "$(DESTDIR)$(file_wmf_libexecdir)" \ + "$(DESTDIR)$(file_xbm_libexecdir)" \ + "$(DESTDIR)$(file_xmc_libexecdir)" \ + "$(DESTDIR)$(file_xpm_libexecdir)" \ + "$(DESTDIR)$(file_xwd_libexecdir)" \ + "$(DESTDIR)$(film_libexecdir)" \ + "$(DESTDIR)$(filter_pack_libexecdir)" \ + "$(DESTDIR)$(fractal_trace_libexecdir)" \ + "$(DESTDIR)$(goat_exercise_libexecdir)" \ + "$(DESTDIR)$(gradient_map_libexecdir)" \ + "$(DESTDIR)$(grid_libexecdir)" \ + "$(DESTDIR)$(guillotine_libexecdir)" \ + "$(DESTDIR)$(hot_libexecdir)" "$(DESTDIR)$(jigsaw_libexecdir)" \ + "$(DESTDIR)$(mail_libexecdir)" \ + "$(DESTDIR)$(max_rgb_libexecdir)" \ + "$(DESTDIR)$(nl_filter_libexecdir)" \ + "$(DESTDIR)$(photocopy_libexecdir)" \ + "$(DESTDIR)$(plugin_browser_libexecdir)" \ + "$(DESTDIR)$(procedure_browser_libexecdir)" \ + "$(DESTDIR)$(qbist_libexecdir)" \ + "$(DESTDIR)$(sample_colorize_libexecdir)" \ + "$(DESTDIR)$(sharpen_libexecdir)" \ + "$(DESTDIR)$(smooth_palette_libexecdir)" \ + "$(DESTDIR)$(softglow_libexecdir)" \ + "$(DESTDIR)$(sparkle_libexecdir)" \ + "$(DESTDIR)$(sphere_designer_libexecdir)" \ + "$(DESTDIR)$(tile_libexecdir)" \ + "$(DESTDIR)$(tile_small_libexecdir)" \ + "$(DESTDIR)$(unit_editor_libexecdir)" \ + "$(DESTDIR)$(van_gogh_lic_libexecdir)" \ + "$(DESTDIR)$(warp_libexecdir)" \ + "$(DESTDIR)$(wavelet_decompose_libexecdir)" \ + "$(DESTDIR)$(web_browser_libexecdir)" \ + "$(DESTDIR)$(web_page_libexecdir)" +PROGRAMS = $(align_layers_libexec_PROGRAMS) \ + $(animation_optimize_libexec_PROGRAMS) \ + $(animation_play_libexec_PROGRAMS) $(blinds_libexec_PROGRAMS) \ + $(blur_libexec_PROGRAMS) $(border_average_libexec_PROGRAMS) \ + $(busy_dialog_libexec_PROGRAMS) $(cartoon_libexec_PROGRAMS) \ + $(checkerboard_libexec_PROGRAMS) \ + $(cml_explorer_libexec_PROGRAMS) \ + $(color_cube_analyze_libexec_PROGRAMS) \ + $(color_enhance_libexec_PROGRAMS) $(colorify_libexec_PROGRAMS) \ + $(colormap_remap_libexec_PROGRAMS) $(compose_libexec_PROGRAMS) \ + $(contrast_retinex_libexec_PROGRAMS) \ + $(crop_zealous_libexec_PROGRAMS) \ + $(curve_bend_libexec_PROGRAMS) $(decompose_libexec_PROGRAMS) \ + $(depth_merge_libexec_PROGRAMS) $(despeckle_libexec_PROGRAMS) \ + $(destripe_libexec_PROGRAMS) $(edge_dog_libexec_PROGRAMS) \ + $(emboss_libexec_PROGRAMS) $(file_aa_libexec_PROGRAMS) \ + $(file_cel_libexec_PROGRAMS) \ + $(file_compressor_libexec_PROGRAMS) \ + $(file_csource_libexec_PROGRAMS) \ + $(file_desktop_link_libexec_PROGRAMS) \ + $(file_dicom_libexec_PROGRAMS) $(file_gbr_libexec_PROGRAMS) \ + $(file_gegl_libexec_PROGRAMS) \ + $(file_gif_load_libexec_PROGRAMS) \ + $(file_gif_save_libexec_PROGRAMS) $(file_gih_libexec_PROGRAMS) \ + $(file_glob_libexec_PROGRAMS) $(file_header_libexec_PROGRAMS) \ + $(file_heif_libexec_PROGRAMS) \ + $(file_html_table_libexec_PROGRAMS) \ + $(file_jp2_load_libexec_PROGRAMS) $(file_mng_libexec_PROGRAMS) \ + $(file_pat_libexec_PROGRAMS) $(file_pcx_libexec_PROGRAMS) \ + $(file_pdf_load_libexec_PROGRAMS) \ + $(file_pdf_save_libexec_PROGRAMS) $(file_pix_libexec_PROGRAMS) \ + $(file_png_libexec_PROGRAMS) $(file_pnm_libexec_PROGRAMS) \ + $(file_ps_libexec_PROGRAMS) $(file_psp_libexec_PROGRAMS) \ + $(file_raw_data_libexec_PROGRAMS) \ + $(file_sunras_libexec_PROGRAMS) $(file_svg_libexec_PROGRAMS) \ + $(file_tga_libexec_PROGRAMS) $(file_wmf_libexec_PROGRAMS) \ + $(file_xbm_libexec_PROGRAMS) $(file_xmc_libexec_PROGRAMS) \ + $(file_xpm_libexec_PROGRAMS) $(file_xwd_libexec_PROGRAMS) \ + $(film_libexec_PROGRAMS) $(filter_pack_libexec_PROGRAMS) \ + $(fractal_trace_libexec_PROGRAMS) \ + $(goat_exercise_libexec_PROGRAMS) \ + $(gradient_map_libexec_PROGRAMS) $(grid_libexec_PROGRAMS) \ + $(guillotine_libexec_PROGRAMS) $(hot_libexec_PROGRAMS) \ + $(jigsaw_libexec_PROGRAMS) $(mail_libexec_PROGRAMS) \ + $(max_rgb_libexec_PROGRAMS) $(nl_filter_libexec_PROGRAMS) \ + $(photocopy_libexec_PROGRAMS) \ + $(plugin_browser_libexec_PROGRAMS) \ + $(procedure_browser_libexec_PROGRAMS) \ + $(qbist_libexec_PROGRAMS) $(sample_colorize_libexec_PROGRAMS) \ + $(sharpen_libexec_PROGRAMS) $(smooth_palette_libexec_PROGRAMS) \ + $(softglow_libexec_PROGRAMS) $(sparkle_libexec_PROGRAMS) \ + $(sphere_designer_libexec_PROGRAMS) $(tile_libexec_PROGRAMS) \ + $(tile_small_libexec_PROGRAMS) $(unit_editor_libexec_PROGRAMS) \ + $(van_gogh_lic_libexec_PROGRAMS) $(warp_libexec_PROGRAMS) \ + $(wavelet_decompose_libexec_PROGRAMS) \ + $(web_browser_libexec_PROGRAMS) $(web_page_libexec_PROGRAMS) +am_align_layers_OBJECTS = align-layers.$(OBJEXT) +align_layers_OBJECTS = $(am_align_layers_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la \ + $(am__DEPENDENCIES_1) +align_layers_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(align_layers_RC) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am_animation_optimize_OBJECTS = animation-optimize.$(OBJEXT) +animation_optimize_OBJECTS = $(am_animation_optimize_OBJECTS) +animation_optimize_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(animation_optimize_RC) +am_animation_play_OBJECTS = animation-play.$(OBJEXT) +animation_play_OBJECTS = $(am_animation_play_OBJECTS) +animation_play_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(animation_play_RC) +am_blinds_OBJECTS = blinds.$(OBJEXT) +blinds_OBJECTS = $(am_blinds_OBJECTS) +blinds_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(blinds_RC) +am_blur_OBJECTS = blur.$(OBJEXT) +blur_OBJECTS = $(am_blur_OBJECTS) +blur_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(blur_RC) +am_border_average_OBJECTS = border-average.$(OBJEXT) +border_average_OBJECTS = $(am_border_average_OBJECTS) +border_average_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(border_average_RC) +am_busy_dialog_OBJECTS = busy-dialog.$(OBJEXT) +busy_dialog_OBJECTS = $(am_busy_dialog_OBJECTS) +busy_dialog_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(busy_dialog_RC) +am_cartoon_OBJECTS = cartoon.$(OBJEXT) +cartoon_OBJECTS = $(am_cartoon_OBJECTS) +cartoon_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(cartoon_RC) +am_checkerboard_OBJECTS = checkerboard.$(OBJEXT) +checkerboard_OBJECTS = $(am_checkerboard_OBJECTS) +checkerboard_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(checkerboard_RC) +am_cml_explorer_OBJECTS = cml-explorer.$(OBJEXT) +cml_explorer_OBJECTS = $(am_cml_explorer_OBJECTS) +cml_explorer_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(cml_explorer_RC) +am_color_cube_analyze_OBJECTS = color-cube-analyze.$(OBJEXT) +color_cube_analyze_OBJECTS = $(am_color_cube_analyze_OBJECTS) +color_cube_analyze_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(color_cube_analyze_RC) +am_color_enhance_OBJECTS = color-enhance.$(OBJEXT) +color_enhance_OBJECTS = $(am_color_enhance_OBJECTS) +color_enhance_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(color_enhance_RC) +am_colorify_OBJECTS = colorify.$(OBJEXT) +colorify_OBJECTS = $(am_colorify_OBJECTS) +colorify_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(colorify_RC) +am_colormap_remap_OBJECTS = colormap-remap.$(OBJEXT) +colormap_remap_OBJECTS = $(am_colormap_remap_OBJECTS) +colormap_remap_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(colormap_remap_RC) +am_compose_OBJECTS = compose.$(OBJEXT) +compose_OBJECTS = $(am_compose_OBJECTS) +compose_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(compose_RC) +am_contrast_retinex_OBJECTS = contrast-retinex.$(OBJEXT) +contrast_retinex_OBJECTS = $(am_contrast_retinex_OBJECTS) +contrast_retinex_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(contrast_retinex_RC) +am_crop_zealous_OBJECTS = crop-zealous.$(OBJEXT) +crop_zealous_OBJECTS = $(am_crop_zealous_OBJECTS) +crop_zealous_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(crop_zealous_RC) +am_curve_bend_OBJECTS = curve-bend.$(OBJEXT) +curve_bend_OBJECTS = $(am_curve_bend_OBJECTS) +curve_bend_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(curve_bend_RC) +am_decompose_OBJECTS = decompose.$(OBJEXT) +decompose_OBJECTS = $(am_decompose_OBJECTS) +decompose_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(decompose_RC) +am_depth_merge_OBJECTS = depth-merge.$(OBJEXT) +depth_merge_OBJECTS = $(am_depth_merge_OBJECTS) +depth_merge_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(depth_merge_RC) +am_despeckle_OBJECTS = despeckle.$(OBJEXT) +despeckle_OBJECTS = $(am_despeckle_OBJECTS) +despeckle_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(despeckle_RC) +am_destripe_OBJECTS = destripe.$(OBJEXT) +destripe_OBJECTS = $(am_destripe_OBJECTS) +destripe_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(destripe_RC) +am_edge_dog_OBJECTS = edge-dog.$(OBJEXT) +edge_dog_OBJECTS = $(am_edge_dog_OBJECTS) +edge_dog_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(edge_dog_RC) +am_emboss_OBJECTS = emboss.$(OBJEXT) +emboss_OBJECTS = $(am_emboss_OBJECTS) +emboss_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(emboss_RC) +am_file_aa_OBJECTS = file-aa.$(OBJEXT) +file_aa_OBJECTS = $(am_file_aa_OBJECTS) +file_aa_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_aa_RC) +am_file_cel_OBJECTS = file-cel.$(OBJEXT) +file_cel_OBJECTS = $(am_file_cel_OBJECTS) +file_cel_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_cel_RC) +am_file_compressor_OBJECTS = \ + file_compressor-file-compressor.$(OBJEXT) +file_compressor_OBJECTS = $(am_file_compressor_OBJECTS) +file_compressor_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(file_compressor_RC) +file_compressor_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(file_compressor_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_file_csource_OBJECTS = file-csource.$(OBJEXT) +file_csource_OBJECTS = $(am_file_csource_OBJECTS) +file_csource_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_csource_RC) +am_file_desktop_link_OBJECTS = file-desktop-link.$(OBJEXT) +file_desktop_link_OBJECTS = $(am_file_desktop_link_OBJECTS) +file_desktop_link_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(file_desktop_link_RC) +am_file_dicom_OBJECTS = file_dicom-file-dicom.$(OBJEXT) +file_dicom_OBJECTS = $(am_file_dicom_OBJECTS) +file_dicom_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_dicom_RC) +file_dicom_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_dicom_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_gbr_OBJECTS = file-gbr.$(OBJEXT) +file_gbr_OBJECTS = $(am_file_gbr_OBJECTS) +file_gbr_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_gbr_RC) +am_file_gegl_OBJECTS = file-gegl.$(OBJEXT) +file_gegl_OBJECTS = $(am_file_gegl_OBJECTS) +file_gegl_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_gegl_RC) +am_file_gif_load_OBJECTS = file-gif-load.$(OBJEXT) +file_gif_load_OBJECTS = $(am_file_gif_load_OBJECTS) +file_gif_load_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_gif_load_RC) +am_file_gif_save_OBJECTS = file-gif-save.$(OBJEXT) +file_gif_save_OBJECTS = $(am_file_gif_save_OBJECTS) +file_gif_save_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(file_gif_save_RC) +am_file_gih_OBJECTS = file-gih.$(OBJEXT) +file_gih_OBJECTS = $(am_file_gih_OBJECTS) +file_gih_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_gih_RC) +am_file_glob_OBJECTS = file-glob.$(OBJEXT) +file_glob_OBJECTS = $(am_file_glob_OBJECTS) +file_glob_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_glob_RC) +am_file_header_OBJECTS = file-header.$(OBJEXT) +file_header_OBJECTS = $(am_file_header_OBJECTS) +file_header_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_header_RC) +am_file_heif_OBJECTS = file_heif-file-heif.$(OBJEXT) +file_heif_OBJECTS = $(am_file_heif_OBJECTS) +file_heif_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_heif_RC) +file_heif_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_heif_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_html_table_OBJECTS = file-html-table.$(OBJEXT) +file_html_table_OBJECTS = $(am_file_html_table_OBJECTS) +file_html_table_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(file_html_table_RC) +am_file_jp2_load_OBJECTS = file_jp2_load-file-jp2-load.$(OBJEXT) +file_jp2_load_OBJECTS = $(am_file_jp2_load_OBJECTS) +file_jp2_load_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_jp2_load_RC) +file_jp2_load_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_jp2_load_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_mng_OBJECTS = file_mng-file-mng.$(OBJEXT) +file_mng_OBJECTS = $(am_file_mng_OBJECTS) +file_mng_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_mng_RC) +file_mng_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_mng_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_pat_OBJECTS = file-pat.$(OBJEXT) +file_pat_OBJECTS = $(am_file_pat_OBJECTS) +file_pat_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_pat_RC) +am_file_pcx_OBJECTS = file-pcx.$(OBJEXT) +file_pcx_OBJECTS = $(am_file_pcx_OBJECTS) +file_pcx_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_pcx_RC) +am_file_pdf_load_OBJECTS = file_pdf_load-file-pdf-load.$(OBJEXT) +file_pdf_load_OBJECTS = $(am_file_pdf_load_OBJECTS) +file_pdf_load_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_pdf_load_RC) +file_pdf_load_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_pdf_load_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_pdf_save_OBJECTS = file_pdf_save-file-pdf-save.$(OBJEXT) +file_pdf_save_OBJECTS = $(am_file_pdf_save_OBJECTS) +file_pdf_save_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_pdf_save_RC) +file_pdf_save_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_pdf_save_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_pix_OBJECTS = file-pix.$(OBJEXT) +file_pix_OBJECTS = $(am_file_pix_OBJECTS) +file_pix_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_pix_RC) +am_file_png_OBJECTS = file_png-file-png.$(OBJEXT) +file_png_OBJECTS = $(am_file_png_OBJECTS) +file_png_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_png_RC) +file_png_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_png_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_pnm_OBJECTS = file-pnm.$(OBJEXT) +file_pnm_OBJECTS = $(am_file_pnm_OBJECTS) +file_pnm_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_pnm_RC) +am_file_ps_OBJECTS = file-ps.$(OBJEXT) +file_ps_OBJECTS = $(am_file_ps_OBJECTS) +file_ps_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_ps_RC) +am_file_psp_OBJECTS = file-psp.$(OBJEXT) +file_psp_OBJECTS = $(am_file_psp_OBJECTS) +file_psp_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_psp_RC) +am_file_raw_data_OBJECTS = file-raw-data.$(OBJEXT) +file_raw_data_OBJECTS = $(am_file_raw_data_OBJECTS) +file_raw_data_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(file_raw_data_RC) +am_file_sunras_OBJECTS = file-sunras.$(OBJEXT) +file_sunras_OBJECTS = $(am_file_sunras_OBJECTS) +file_sunras_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_sunras_RC) +am_file_svg_OBJECTS = file_svg-file-svg.$(OBJEXT) +file_svg_OBJECTS = $(am_file_svg_OBJECTS) +file_svg_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_svg_RC) +file_svg_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_svg_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_tga_OBJECTS = file-tga.$(OBJEXT) +file_tga_OBJECTS = $(am_file_tga_OBJECTS) +file_tga_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_tga_RC) +am_file_wmf_OBJECTS = file_wmf-file-wmf.$(OBJEXT) +file_wmf_OBJECTS = $(am_file_wmf_OBJECTS) +file_wmf_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_wmf_RC) +file_wmf_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_wmf_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_file_xbm_OBJECTS = file-xbm.$(OBJEXT) +file_xbm_OBJECTS = $(am_file_xbm_OBJECTS) +file_xbm_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_xbm_RC) +am_file_xmc_OBJECTS = file-xmc.$(OBJEXT) +file_xmc_OBJECTS = $(am_file_xmc_OBJECTS) +file_xmc_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_xmc_RC) +am_file_xpm_OBJECTS = file-xpm.$(OBJEXT) +file_xpm_OBJECTS = $(am_file_xpm_OBJECTS) +file_xpm_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_xpm_RC) +am_file_xwd_OBJECTS = file-xwd.$(OBJEXT) +file_xwd_OBJECTS = $(am_file_xwd_OBJECTS) +file_xwd_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(file_xwd_RC) +am_film_OBJECTS = film.$(OBJEXT) +film_OBJECTS = $(am_film_OBJECTS) +film_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(film_RC) +am_filter_pack_OBJECTS = filter-pack.$(OBJEXT) +filter_pack_OBJECTS = $(am_filter_pack_OBJECTS) +filter_pack_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(filter_pack_RC) +am_fractal_trace_OBJECTS = fractal-trace.$(OBJEXT) +fractal_trace_OBJECTS = $(am_fractal_trace_OBJECTS) +fractal_trace_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(fractal_trace_RC) +am_goat_exercise_OBJECTS = goat-exercise.$(OBJEXT) +goat_exercise_OBJECTS = $(am_goat_exercise_OBJECTS) +goat_exercise_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(goat_exercise_RC) +am_gradient_map_OBJECTS = gradient-map.$(OBJEXT) +gradient_map_OBJECTS = $(am_gradient_map_OBJECTS) +gradient_map_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(gradient_map_RC) +am_grid_OBJECTS = grid.$(OBJEXT) +grid_OBJECTS = $(am_grid_OBJECTS) +grid_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(grid_RC) +am_guillotine_OBJECTS = guillotine.$(OBJEXT) +guillotine_OBJECTS = $(am_guillotine_OBJECTS) +guillotine_DEPENDENCIES = $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(guillotine_RC) +am_hot_OBJECTS = hot.$(OBJEXT) +hot_OBJECTS = $(am_hot_OBJECTS) +hot_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(hot_RC) +am_jigsaw_OBJECTS = jigsaw.$(OBJEXT) +jigsaw_OBJECTS = $(am_jigsaw_OBJECTS) +jigsaw_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(jigsaw_RC) +am_mail_OBJECTS = mail.$(OBJEXT) +mail_OBJECTS = $(am_mail_OBJECTS) +mail_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(mail_RC) +am_max_rgb_OBJECTS = max-rgb.$(OBJEXT) +max_rgb_OBJECTS = $(am_max_rgb_OBJECTS) +max_rgb_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(max_rgb_RC) +am_nl_filter_OBJECTS = nl-filter.$(OBJEXT) +nl_filter_OBJECTS = $(am_nl_filter_OBJECTS) +nl_filter_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(nl_filter_RC) +am_photocopy_OBJECTS = photocopy.$(OBJEXT) +photocopy_OBJECTS = $(am_photocopy_OBJECTS) +photocopy_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(photocopy_RC) +am_plugin_browser_OBJECTS = plugin-browser.$(OBJEXT) +plugin_browser_OBJECTS = $(am_plugin_browser_OBJECTS) +plugin_browser_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(plugin_browser_RC) +am_procedure_browser_OBJECTS = procedure-browser.$(OBJEXT) +procedure_browser_OBJECTS = $(am_procedure_browser_OBJECTS) +procedure_browser_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(procedure_browser_RC) +am_qbist_OBJECTS = qbist.$(OBJEXT) +qbist_OBJECTS = $(am_qbist_OBJECTS) +qbist_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(qbist_RC) +am_sample_colorize_OBJECTS = sample-colorize.$(OBJEXT) +sample_colorize_OBJECTS = $(am_sample_colorize_OBJECTS) +sample_colorize_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(sample_colorize_RC) +am_sharpen_OBJECTS = sharpen.$(OBJEXT) +sharpen_OBJECTS = $(am_sharpen_OBJECTS) +sharpen_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(sharpen_RC) +am_smooth_palette_OBJECTS = smooth-palette.$(OBJEXT) +smooth_palette_OBJECTS = $(am_smooth_palette_OBJECTS) +smooth_palette_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(smooth_palette_RC) +am_softglow_OBJECTS = softglow.$(OBJEXT) +softglow_OBJECTS = $(am_softglow_OBJECTS) +softglow_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(softglow_RC) +am_sparkle_OBJECTS = sparkle.$(OBJEXT) +sparkle_OBJECTS = $(am_sparkle_OBJECTS) +sparkle_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(sparkle_RC) +am_sphere_designer_OBJECTS = sphere-designer.$(OBJEXT) +sphere_designer_OBJECTS = $(am_sphere_designer_OBJECTS) +sphere_designer_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(sphere_designer_RC) +am_tile_OBJECTS = tile.$(OBJEXT) +tile_OBJECTS = $(am_tile_OBJECTS) +tile_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(tile_RC) +am_tile_small_OBJECTS = tile-small.$(OBJEXT) +tile_small_OBJECTS = $(am_tile_small_OBJECTS) +tile_small_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(tile_small_RC) +am_unit_editor_OBJECTS = unit-editor.$(OBJEXT) +unit_editor_OBJECTS = $(am_unit_editor_OBJECTS) +unit_editor_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(unit_editor_RC) +am_van_gogh_lic_OBJECTS = van-gogh-lic.$(OBJEXT) +van_gogh_lic_OBJECTS = $(am_van_gogh_lic_OBJECTS) +van_gogh_lic_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(van_gogh_lic_RC) +am_warp_OBJECTS = warp.$(OBJEXT) +warp_OBJECTS = $(am_warp_OBJECTS) +warp_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) $(libgimpmodule) \ + $(libgimp) $(am__DEPENDENCIES_2) $(libgimpconfig) \ + $(libgimpcolor) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(warp_RC) +am_wavelet_decompose_OBJECTS = wavelet-decompose.$(OBJEXT) +wavelet_decompose_OBJECTS = $(am_wavelet_decompose_OBJECTS) +wavelet_decompose_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(wavelet_decompose_RC) +am_web_browser_OBJECTS = web_browser-web-browser.$(OBJEXT) +web_browser_OBJECTS = $(am_web_browser_OBJECTS) +web_browser_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(web_browser_RC) +web_browser_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(web_browser_LDFLAGS) $(LDFLAGS) -o $@ +am_web_page_OBJECTS = web_page-web-page.$(OBJEXT) +web_page_OBJECTS = $(am_web_page_OBJECTS) +web_page_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpmodule) $(libgimp) $(am__DEPENDENCIES_2) \ + $(libgimpconfig) $(libgimpcolor) $(libgimpbase) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(web_page_RC) +web_page_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(web_page_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/align-layers.Po \ + ./$(DEPDIR)/animation-optimize.Po \ + ./$(DEPDIR)/animation-play.Po ./$(DEPDIR)/blinds.Po \ + ./$(DEPDIR)/blur.Po ./$(DEPDIR)/border-average.Po \ + ./$(DEPDIR)/busy-dialog.Po ./$(DEPDIR)/cartoon.Po \ + ./$(DEPDIR)/checkerboard.Po ./$(DEPDIR)/cml-explorer.Po \ + ./$(DEPDIR)/color-cube-analyze.Po ./$(DEPDIR)/color-enhance.Po \ + ./$(DEPDIR)/colorify.Po ./$(DEPDIR)/colormap-remap.Po \ + ./$(DEPDIR)/compose.Po ./$(DEPDIR)/contrast-retinex.Po \ + ./$(DEPDIR)/crop-zealous.Po ./$(DEPDIR)/curve-bend.Po \ + ./$(DEPDIR)/decompose.Po ./$(DEPDIR)/depth-merge.Po \ + ./$(DEPDIR)/despeckle.Po ./$(DEPDIR)/destripe.Po \ + ./$(DEPDIR)/edge-dog.Po ./$(DEPDIR)/emboss.Po \ + ./$(DEPDIR)/file-aa.Po ./$(DEPDIR)/file-cel.Po \ + ./$(DEPDIR)/file-csource.Po ./$(DEPDIR)/file-desktop-link.Po \ + ./$(DEPDIR)/file-gbr.Po ./$(DEPDIR)/file-gegl.Po \ + ./$(DEPDIR)/file-gif-load.Po ./$(DEPDIR)/file-gif-save.Po \ + ./$(DEPDIR)/file-gih.Po ./$(DEPDIR)/file-glob.Po \ + ./$(DEPDIR)/file-header.Po ./$(DEPDIR)/file-html-table.Po \ + ./$(DEPDIR)/file-pat.Po ./$(DEPDIR)/file-pcx.Po \ + ./$(DEPDIR)/file-pix.Po ./$(DEPDIR)/file-pnm.Po \ + ./$(DEPDIR)/file-ps.Po ./$(DEPDIR)/file-psp.Po \ + ./$(DEPDIR)/file-raw-data.Po ./$(DEPDIR)/file-sunras.Po \ + ./$(DEPDIR)/file-tga.Po ./$(DEPDIR)/file-xbm.Po \ + ./$(DEPDIR)/file-xmc.Po ./$(DEPDIR)/file-xpm.Po \ + ./$(DEPDIR)/file-xwd.Po \ + ./$(DEPDIR)/file_compressor-file-compressor.Po \ + ./$(DEPDIR)/file_dicom-file-dicom.Po \ + ./$(DEPDIR)/file_heif-file-heif.Po \ + ./$(DEPDIR)/file_jp2_load-file-jp2-load.Po \ + ./$(DEPDIR)/file_mng-file-mng.Po \ + ./$(DEPDIR)/file_pdf_load-file-pdf-load.Po \ + ./$(DEPDIR)/file_pdf_save-file-pdf-save.Po \ + ./$(DEPDIR)/file_png-file-png.Po \ + ./$(DEPDIR)/file_svg-file-svg.Po \ + ./$(DEPDIR)/file_wmf-file-wmf.Po ./$(DEPDIR)/film.Po \ + ./$(DEPDIR)/filter-pack.Po ./$(DEPDIR)/fractal-trace.Po \ + ./$(DEPDIR)/goat-exercise.Po ./$(DEPDIR)/gradient-map.Po \ + ./$(DEPDIR)/grid.Po ./$(DEPDIR)/guillotine.Po \ + ./$(DEPDIR)/hot.Po ./$(DEPDIR)/jigsaw.Po ./$(DEPDIR)/mail.Po \ + ./$(DEPDIR)/max-rgb.Po ./$(DEPDIR)/nl-filter.Po \ + ./$(DEPDIR)/photocopy.Po ./$(DEPDIR)/plugin-browser.Po \ + ./$(DEPDIR)/procedure-browser.Po ./$(DEPDIR)/qbist.Po \ + ./$(DEPDIR)/sample-colorize.Po ./$(DEPDIR)/sharpen.Po \ + ./$(DEPDIR)/smooth-palette.Po ./$(DEPDIR)/softglow.Po \ + ./$(DEPDIR)/sparkle.Po ./$(DEPDIR)/sphere-designer.Po \ + ./$(DEPDIR)/tile-small.Po ./$(DEPDIR)/tile.Po \ + ./$(DEPDIR)/unit-editor.Po ./$(DEPDIR)/van-gogh-lic.Po \ + ./$(DEPDIR)/warp.Po ./$(DEPDIR)/wavelet-decompose.Po \ + ./$(DEPDIR)/web_browser-web-browser.Po \ + ./$(DEPDIR)/web_page-web-page.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(align_layers_SOURCES) $(animation_optimize_SOURCES) \ + $(animation_play_SOURCES) $(blinds_SOURCES) $(blur_SOURCES) \ + $(border_average_SOURCES) $(busy_dialog_SOURCES) \ + $(cartoon_SOURCES) $(checkerboard_SOURCES) \ + $(cml_explorer_SOURCES) $(color_cube_analyze_SOURCES) \ + $(color_enhance_SOURCES) $(colorify_SOURCES) \ + $(colormap_remap_SOURCES) $(compose_SOURCES) \ + $(contrast_retinex_SOURCES) $(crop_zealous_SOURCES) \ + $(curve_bend_SOURCES) $(decompose_SOURCES) \ + $(depth_merge_SOURCES) $(despeckle_SOURCES) \ + $(destripe_SOURCES) $(edge_dog_SOURCES) $(emboss_SOURCES) \ + $(file_aa_SOURCES) $(file_cel_SOURCES) \ + $(file_compressor_SOURCES) $(file_csource_SOURCES) \ + $(file_desktop_link_SOURCES) $(file_dicom_SOURCES) \ + $(file_gbr_SOURCES) $(file_gegl_SOURCES) \ + $(file_gif_load_SOURCES) $(file_gif_save_SOURCES) \ + $(file_gih_SOURCES) $(file_glob_SOURCES) \ + $(file_header_SOURCES) $(file_heif_SOURCES) \ + $(file_html_table_SOURCES) $(file_jp2_load_SOURCES) \ + $(file_mng_SOURCES) $(file_pat_SOURCES) $(file_pcx_SOURCES) \ + $(file_pdf_load_SOURCES) $(file_pdf_save_SOURCES) \ + $(file_pix_SOURCES) $(file_png_SOURCES) $(file_pnm_SOURCES) \ + $(file_ps_SOURCES) $(file_psp_SOURCES) \ + $(file_raw_data_SOURCES) $(file_sunras_SOURCES) \ + $(file_svg_SOURCES) $(file_tga_SOURCES) $(file_wmf_SOURCES) \ + $(file_xbm_SOURCES) $(file_xmc_SOURCES) $(file_xpm_SOURCES) \ + $(file_xwd_SOURCES) $(film_SOURCES) $(filter_pack_SOURCES) \ + $(fractal_trace_SOURCES) $(goat_exercise_SOURCES) \ + $(gradient_map_SOURCES) $(grid_SOURCES) $(guillotine_SOURCES) \ + $(hot_SOURCES) $(jigsaw_SOURCES) $(mail_SOURCES) \ + $(max_rgb_SOURCES) $(nl_filter_SOURCES) $(photocopy_SOURCES) \ + $(plugin_browser_SOURCES) $(procedure_browser_SOURCES) \ + $(qbist_SOURCES) $(sample_colorize_SOURCES) $(sharpen_SOURCES) \ + $(smooth_palette_SOURCES) $(softglow_SOURCES) \ + $(sparkle_SOURCES) $(sphere_designer_SOURCES) $(tile_SOURCES) \ + $(tile_small_SOURCES) $(unit_editor_SOURCES) \ + $(van_gogh_lic_SOURCES) $(warp_SOURCES) \ + $(wavelet_decompose_SOURCES) $(web_browser_SOURCES) \ + $(web_page_SOURCES) +DIST_SOURCES = $(align_layers_SOURCES) $(animation_optimize_SOURCES) \ + $(animation_play_SOURCES) $(blinds_SOURCES) $(blur_SOURCES) \ + $(border_average_SOURCES) $(busy_dialog_SOURCES) \ + $(cartoon_SOURCES) $(checkerboard_SOURCES) \ + $(cml_explorer_SOURCES) $(color_cube_analyze_SOURCES) \ + $(color_enhance_SOURCES) $(colorify_SOURCES) \ + $(colormap_remap_SOURCES) $(compose_SOURCES) \ + $(contrast_retinex_SOURCES) $(crop_zealous_SOURCES) \ + $(curve_bend_SOURCES) $(decompose_SOURCES) \ + $(depth_merge_SOURCES) $(despeckle_SOURCES) \ + $(destripe_SOURCES) $(edge_dog_SOURCES) $(emboss_SOURCES) \ + $(file_aa_SOURCES) $(file_cel_SOURCES) \ + $(file_compressor_SOURCES) $(file_csource_SOURCES) \ + $(file_desktop_link_SOURCES) $(file_dicom_SOURCES) \ + $(file_gbr_SOURCES) $(file_gegl_SOURCES) \ + $(file_gif_load_SOURCES) $(file_gif_save_SOURCES) \ + $(file_gih_SOURCES) $(file_glob_SOURCES) \ + $(file_header_SOURCES) $(file_heif_SOURCES) \ + $(file_html_table_SOURCES) $(file_jp2_load_SOURCES) \ + $(file_mng_SOURCES) $(file_pat_SOURCES) $(file_pcx_SOURCES) \ + $(file_pdf_load_SOURCES) $(file_pdf_save_SOURCES) \ + $(file_pix_SOURCES) $(file_png_SOURCES) $(file_pnm_SOURCES) \ + $(file_ps_SOURCES) $(file_psp_SOURCES) \ + $(file_raw_data_SOURCES) $(file_sunras_SOURCES) \ + $(file_svg_SOURCES) $(file_tga_SOURCES) $(file_wmf_SOURCES) \ + $(file_xbm_SOURCES) $(file_xmc_SOURCES) $(file_xpm_SOURCES) \ + $(file_xwd_SOURCES) $(film_SOURCES) $(filter_pack_SOURCES) \ + $(fractal_trace_SOURCES) $(goat_exercise_SOURCES) \ + $(gradient_map_SOURCES) $(grid_SOURCES) $(guillotine_SOURCES) \ + $(hot_SOURCES) $(jigsaw_SOURCES) $(mail_SOURCES) \ + $(max_rgb_SOURCES) $(nl_filter_SOURCES) $(photocopy_SOURCES) \ + $(plugin_browser_SOURCES) $(procedure_browser_SOURCES) \ + $(qbist_SOURCES) $(sample_colorize_SOURCES) $(sharpen_SOURCES) \ + $(smooth_palette_SOURCES) $(softglow_SOURCES) \ + $(sparkle_SOURCES) $(sphere_designer_SOURCES) $(tile_SOURCES) \ + $(tile_small_SOURCES) $(unit_editor_SOURCES) \ + $(van_gogh_lic_SOURCES) $(warp_SOURCES) \ + $(wavelet_decompose_SOURCES) $(web_browser_SOURCES) \ + $(web_page_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/gimprc.common \ + $(top_srcdir)/build/windows/gimprc-plug-ins.rule \ + $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +AA_LIBS = @AA_LIBS@ +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +ALL_LINGUAS = @ALL_LINGUAS@ +ALSA_CFLAGS = @ALSA_CFLAGS@ +ALSA_LIBS = @ALSA_LIBS@ +ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPSTREAM_UTIL = @APPSTREAM_UTIL@ +AR = @AR@ +AS = @AS@ +ATK_CFLAGS = @ATK_CFLAGS@ +ATK_LIBS = @ATK_LIBS@ +ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BABL_CFLAGS = @BABL_CFLAGS@ +BABL_LIBS = @BABL_LIBS@ +BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@ +BUG_REPORT_URL = @BUG_REPORT_URL@ +BUILD_EXEEXT = @BUILD_EXEEXT@ +BUILD_OBJEXT = @BUILD_OBJEXT@ +BZIP2_LIBS = @BZIP2_LIBS@ +CAIRO_CFLAGS = @CAIRO_CFLAGS@ +CAIRO_LIBS = @CAIRO_LIBS@ +CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@ +CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@ +CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@ +CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CC_VERSION = @CC_VERSION@ +CFLAGS = @CFLAGS@ +CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@ +CPP_FOR_BUILD = @CPP_FOR_BUILD@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DESKTOP_DATADIR = @DESKTOP_DATADIR@ +DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@ +DLLTOOL = @DLLTOOL@ +DOC_SHOOTER = @DOC_SHOOTER@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILE_AA = @FILE_AA@ +FILE_EXR = @FILE_EXR@ +FILE_HEIF = @FILE_HEIF@ +FILE_JP2_LOAD = @FILE_JP2_LOAD@ +FILE_MNG = @FILE_MNG@ +FILE_PDF_SAVE = @FILE_PDF_SAVE@ +FILE_PS = @FILE_PS@ +FILE_WMF = @FILE_WMF@ +FILE_XMC = @FILE_XMC@ +FILE_XPM = @FILE_XPM@ +FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@ +FONTCONFIG_LIBS = @FONTCONFIG_LIBS@ +FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@ +FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@ +FREETYPE_CFLAGS = @FREETYPE_CFLAGS@ +FREETYPE_LIBS = @FREETYPE_LIBS@ +GDBUS_CODEGEN = @GDBUS_CODEGEN@ +GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@ +GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@ +GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@ +GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@ +GEGL = @GEGL@ +GEGL_CFLAGS = @GEGL_CFLAGS@ +GEGL_LIBS = @GEGL_LIBS@ +GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@ +GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GEXIV2_CFLAGS = @GEXIV2_CFLAGS@ +GEXIV2_LIBS = @GEXIV2_LIBS@ +GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@ +GIMP_API_VERSION = @GIMP_API_VERSION@ +GIMP_APP_VERSION = @GIMP_APP_VERSION@ +GIMP_BINARY_AGE = @GIMP_BINARY_AGE@ +GIMP_COMMAND = @GIMP_COMMAND@ +GIMP_DATA_VERSION = @GIMP_DATA_VERSION@ +GIMP_FULL_NAME = @GIMP_FULL_NAME@ +GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@ +GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@ +GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@ +GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@ +GIMP_MKENUMS = @GIMP_MKENUMS@ +GIMP_MODULES = @GIMP_MODULES@ +GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@ +GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@ +GIMP_PLUGINS = @GIMP_PLUGINS@ +GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@ +GIMP_REAL_VERSION = @GIMP_REAL_VERSION@ +GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@ +GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@ +GIMP_UNSTABLE = @GIMP_UNSTABLE@ +GIMP_USER_VERSION = @GIMP_USER_VERSION@ +GIMP_VERSION = @GIMP_VERSION@ +GIO_CFLAGS = @GIO_CFLAGS@ +GIO_LIBS = @GIO_LIBS@ +GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@ +GIO_UNIX_LIBS = @GIO_UNIX_LIBS@ +GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@ +GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@ +GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@ +GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GREP = @GREP@ +GS_LIBS = @GS_LIBS@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@ +GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@ +GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@ +GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@ +HARFBUZZ_LIBS = @HARFBUZZ_LIBS@ +HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@ +HAVE_CXX14 = @HAVE_CXX14@ +HAVE_FINITE = @HAVE_FINITE@ +HAVE_ISFINITE = @HAVE_ISFINITE@ +HAVE_VFORK = @HAVE_VFORK@ +HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@ +ISO_CODES_LOCATION = @ISO_CODES_LOCATION@ +JPEG_LIBS = @JPEG_LIBS@ +JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@ +JSON_GLIB_LIBS = @JSON_GLIB_LIBS@ +LCMS_CFLAGS = @LCMS_CFLAGS@ +LCMS_LIBS = @LCMS_LIBS@ +LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ +LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@ +LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@ +LIBHEIF_LIBS = @LIBHEIF_LIBS@ +LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@ +LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@ +LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@ +LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@ +LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@ +LIBOBJS = @LIBOBJS@ +LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LT_VERSION_INFO = @LT_VERSION_INFO@ +LZMA_CFLAGS = @LZMA_CFLAGS@ +LZMA_LIBS = @LZMA_LIBS@ +MAIL = @MAIL@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@ +MIME_INFO_LIBS = @MIME_INFO_LIBS@ +MIME_TYPES = @MIME_TYPES@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@ +MNG_CFLAGS = @MNG_CFLAGS@ +MNG_LIBS = @MNG_LIBS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +MSGMERGE = @MSGMERGE@ +MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@ +MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@ +NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@ +NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENEXR_CFLAGS = @OPENEXR_CFLAGS@ +OPENEXR_LIBS = @OPENEXR_LIBS@ +OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@ +OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@ +OPENJPEG_LIBS = @OPENJPEG_LIBS@ +OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@ +PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@ +PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@ +PATHSEP = @PATHSEP@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@ +PERL_VERSION = @PERL_VERSION@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PNG_CFLAGS = @PNG_CFLAGS@ +PNG_LIBS = @PNG_LIBS@ +POFILES = @POFILES@ +POPPLER_CFLAGS = @POPPLER_CFLAGS@ +POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@ +POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@ +POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@ +POPPLER_LIBS = @POPPLER_LIBS@ +POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +PYBIN_PATH = @PYBIN_PATH@ +PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@ +PYCAIRO_LIBS = @PYCAIRO_LIBS@ +PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@ +PYGTK_CFLAGS = @PYGTK_CFLAGS@ +PYGTK_CODEGEN = @PYGTK_CODEGEN@ +PYGTK_DEFSDIR = @PYGTK_DEFSDIR@ +PYGTK_LIBS = @PYGTK_LIBS@ +PYLINK_LIBS = @PYLINK_LIBS@ +PYTHON = @PYTHON@ +PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_INCLUDES = @PYTHON_INCLUDES@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@ +RT_LIBS = @RT_LIBS@ +SCREENSHOT_LIBS = @SCREENSHOT_LIBS@ +SED = @SED@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SOCKET_LIBS = @SOCKET_LIBS@ +SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@ +SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@ +SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@ +STRIP = @STRIP@ +SVG_CFLAGS = @SVG_CFLAGS@ +SVG_LIBS = @SVG_LIBS@ +SYMPREFIX = @SYMPREFIX@ +TIFF_LIBS = @TIFF_LIBS@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WEBKIT_CFLAGS = @WEBKIT_CFLAGS@ +WEBKIT_LIBS = @WEBKIT_LIBS@ +WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@ +WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@ +WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@ +WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@ +WEBPMUX_LIBS = @WEBPMUX_LIBS@ +WEBP_CFLAGS = @WEBP_CFLAGS@ +WEBP_LIBS = @WEBP_LIBS@ +WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@ +WEB_PAGE = @WEB_PAGE@ +WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@ +WINDRES = @WINDRES@ +WMF_CFLAGS = @WMF_CFLAGS@ +WMF_CONFIG = @WMF_CONFIG@ +WMF_LIBS = @WMF_LIBS@ +WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@ +XDG_EMAIL = @XDG_EMAIL@ +XFIXES_CFLAGS = @XFIXES_CFLAGS@ +XFIXES_LIBS = @XFIXES_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@ +XMC_CFLAGS = @XMC_CFLAGS@ +XMC_LIBS = @XMC_LIBS@ +XMKMF = @XMKMF@ +XMLLINT = @XMLLINT@ +XMU_LIBS = @XMU_LIBS@ +XPM_LIBS = @XPM_LIBS@ +XSLTPROC = @XSLTPROC@ +XVFB_RUN = @XVFB_RUN@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +Z_LIBS = @Z_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +gimpdatadir = @gimpdatadir@ +gimpdir = @gimpdir@ +gimplocaledir = @gimplocaledir@ +gimpplugindir = @gimpplugindir@ +gimpsysconfdir = @gimpsysconfdir@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +manpage_gimpdir = @manpage_gimpdir@ +mkdir_p = @mkdir_p@ +ms_librarian = @ms_librarian@ +mypaint_brushes_dir = @mypaint_brushes_dir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +@OS_WIN32_TRUE@mwindows = -mwindows +@OS_WIN32_FALSE@libm = -lm +@PLATFORM_OSX_TRUE@xobjective_c = "-xobjective-c" +@PLATFORM_OSX_TRUE@framework_cocoa = -framework Cocoa +@HAVE_WINDRES_TRUE@GIMPPLUGINRC = $(top_builddir)/build/windows/gimp-plug-ins.rc +@HAVE_WINDRES_TRUE@align_layers_RC = align-layers.rc.o +@HAVE_WINDRES_TRUE@animation_optimize_RC = animation-optimize.rc.o +@HAVE_WINDRES_TRUE@animation_play_RC = animation-play.rc.o +@HAVE_WINDRES_TRUE@blinds_RC = blinds.rc.o +@HAVE_WINDRES_TRUE@blur_RC = blur.rc.o +@HAVE_WINDRES_TRUE@border_average_RC = border-average.rc.o +@HAVE_WINDRES_TRUE@busy_dialog_RC = busy-dialog.rc.o +@HAVE_WINDRES_TRUE@cartoon_RC = cartoon.rc.o +@HAVE_WINDRES_TRUE@checkerboard_RC = checkerboard.rc.o +@HAVE_WINDRES_TRUE@cml_explorer_RC = cml-explorer.rc.o +@HAVE_WINDRES_TRUE@color_cube_analyze_RC = color-cube-analyze.rc.o +@HAVE_WINDRES_TRUE@color_enhance_RC = color-enhance.rc.o +@HAVE_WINDRES_TRUE@colorify_RC = colorify.rc.o +@HAVE_WINDRES_TRUE@colormap_remap_RC = colormap-remap.rc.o +@HAVE_WINDRES_TRUE@compose_RC = compose.rc.o +@HAVE_WINDRES_TRUE@contrast_retinex_RC = contrast-retinex.rc.o +@HAVE_WINDRES_TRUE@crop_zealous_RC = crop-zealous.rc.o +@HAVE_WINDRES_TRUE@curve_bend_RC = curve-bend.rc.o +@HAVE_WINDRES_TRUE@decompose_RC = decompose.rc.o +@HAVE_WINDRES_TRUE@depth_merge_RC = depth-merge.rc.o +@HAVE_WINDRES_TRUE@despeckle_RC = despeckle.rc.o +@HAVE_WINDRES_TRUE@destripe_RC = destripe.rc.o +@HAVE_WINDRES_TRUE@edge_dog_RC = edge-dog.rc.o +@HAVE_WINDRES_TRUE@emboss_RC = emboss.rc.o +@HAVE_WINDRES_TRUE@file_aa_RC = file-aa.rc.o +@HAVE_WINDRES_TRUE@file_cel_RC = file-cel.rc.o +@HAVE_WINDRES_TRUE@file_compressor_RC = file-compressor.rc.o +@HAVE_WINDRES_TRUE@file_csource_RC = file-csource.rc.o +@HAVE_WINDRES_TRUE@file_desktop_link_RC = file-desktop-link.rc.o +@HAVE_WINDRES_TRUE@file_dicom_RC = file-dicom.rc.o +@HAVE_WINDRES_TRUE@file_gbr_RC = file-gbr.rc.o +@HAVE_WINDRES_TRUE@file_gegl_RC = file-gegl.rc.o +@HAVE_WINDRES_TRUE@file_gif_load_RC = file-gif-load.rc.o +@HAVE_WINDRES_TRUE@file_gif_save_RC = file-gif-save.rc.o +@HAVE_WINDRES_TRUE@file_gih_RC = file-gih.rc.o +@HAVE_WINDRES_TRUE@file_glob_RC = file-glob.rc.o +@HAVE_WINDRES_TRUE@file_header_RC = file-header.rc.o +@HAVE_WINDRES_TRUE@file_heif_RC = file-heif.rc.o +@HAVE_WINDRES_TRUE@file_html_table_RC = file-html-table.rc.o +@HAVE_WINDRES_TRUE@file_jp2_load_RC = file-jp2-load.rc.o +@HAVE_WINDRES_TRUE@file_mng_RC = file-mng.rc.o +@HAVE_WINDRES_TRUE@file_pat_RC = file-pat.rc.o +@HAVE_WINDRES_TRUE@file_pcx_RC = file-pcx.rc.o +@HAVE_WINDRES_TRUE@file_pdf_load_RC = file-pdf-load.rc.o +@HAVE_WINDRES_TRUE@file_pdf_save_RC = file-pdf-save.rc.o +@HAVE_WINDRES_TRUE@file_pix_RC = file-pix.rc.o +@HAVE_WINDRES_TRUE@file_png_RC = file-png.rc.o +@HAVE_WINDRES_TRUE@file_pnm_RC = file-pnm.rc.o +@HAVE_WINDRES_TRUE@file_ps_RC = file-ps.rc.o +@HAVE_WINDRES_TRUE@file_psp_RC = file-psp.rc.o +@HAVE_WINDRES_TRUE@file_raw_data_RC = file-raw-data.rc.o +@HAVE_WINDRES_TRUE@file_sunras_RC = file-sunras.rc.o +@HAVE_WINDRES_TRUE@file_svg_RC = file-svg.rc.o +@HAVE_WINDRES_TRUE@file_tga_RC = file-tga.rc.o +@HAVE_WINDRES_TRUE@file_wmf_RC = file-wmf.rc.o +@HAVE_WINDRES_TRUE@file_xbm_RC = file-xbm.rc.o +@HAVE_WINDRES_TRUE@file_xmc_RC = file-xmc.rc.o +@HAVE_WINDRES_TRUE@file_xpm_RC = file-xpm.rc.o +@HAVE_WINDRES_TRUE@file_xwd_RC = file-xwd.rc.o +@HAVE_WINDRES_TRUE@film_RC = film.rc.o +@HAVE_WINDRES_TRUE@filter_pack_RC = filter-pack.rc.o +@HAVE_WINDRES_TRUE@fractal_trace_RC = fractal-trace.rc.o +@HAVE_WINDRES_TRUE@goat_exercise_RC = goat-exercise.rc.o +@HAVE_WINDRES_TRUE@gradient_map_RC = gradient-map.rc.o +@HAVE_WINDRES_TRUE@grid_RC = grid.rc.o +@HAVE_WINDRES_TRUE@guillotine_RC = guillotine.rc.o +@HAVE_WINDRES_TRUE@hot_RC = hot.rc.o +@HAVE_WINDRES_TRUE@jigsaw_RC = jigsaw.rc.o +@HAVE_WINDRES_TRUE@mail_RC = mail.rc.o +@HAVE_WINDRES_TRUE@max_rgb_RC = max-rgb.rc.o +@HAVE_WINDRES_TRUE@nl_filter_RC = nl-filter.rc.o +@HAVE_WINDRES_TRUE@photocopy_RC = photocopy.rc.o +@HAVE_WINDRES_TRUE@plugin_browser_RC = plugin-browser.rc.o +@HAVE_WINDRES_TRUE@procedure_browser_RC = procedure-browser.rc.o +@HAVE_WINDRES_TRUE@qbist_RC = qbist.rc.o +@HAVE_WINDRES_TRUE@sample_colorize_RC = sample-colorize.rc.o +@HAVE_WINDRES_TRUE@sharpen_RC = sharpen.rc.o +@HAVE_WINDRES_TRUE@smooth_palette_RC = smooth-palette.rc.o +@HAVE_WINDRES_TRUE@softglow_RC = softglow.rc.o +@HAVE_WINDRES_TRUE@sparkle_RC = sparkle.rc.o +@HAVE_WINDRES_TRUE@sphere_designer_RC = sphere-designer.rc.o +@HAVE_WINDRES_TRUE@tile_RC = tile.rc.o +@HAVE_WINDRES_TRUE@tile_small_RC = tile-small.rc.o +@HAVE_WINDRES_TRUE@unit_editor_RC = unit-editor.rc.o +@HAVE_WINDRES_TRUE@van_gogh_lic_RC = van-gogh-lic.rc.o +@HAVE_WINDRES_TRUE@warp_RC = warp.rc.o +@HAVE_WINDRES_TRUE@wavelet_decompose_RC = wavelet-decompose.rc.o +@HAVE_WINDRES_TRUE@web_browser_RC = web-browser.rc.o +@HAVE_WINDRES_TRUE@web_page_RC = web-page.rc.o +libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la $(libm) +libgimpmodule = $(top_builddir)/libgimpmodule/libgimpmodule-$(GIMP_API_VERSION).la +libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la +libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +AM_LDFLAGS = $(mwindows) +EXTRA_DIST = \ + mkgen.pl \ + plugin-defs.pl \ + gimprc.common + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(GEGL_CFLAGS) \ + -I$(includedir) + +align_layers_libexecdir = $(gimpplugindir)/plug-ins/align-layers +animation_optimize_libexecdir = $(gimpplugindir)/plug-ins/animation-optimize +animation_play_libexecdir = $(gimpplugindir)/plug-ins/animation-play +blinds_libexecdir = $(gimpplugindir)/plug-ins/blinds +blur_libexecdir = $(gimpplugindir)/plug-ins/blur +border_average_libexecdir = $(gimpplugindir)/plug-ins/border-average +busy_dialog_libexecdir = $(gimpplugindir)/plug-ins/busy-dialog +cartoon_libexecdir = $(gimpplugindir)/plug-ins/cartoon +checkerboard_libexecdir = $(gimpplugindir)/plug-ins/checkerboard +cml_explorer_libexecdir = $(gimpplugindir)/plug-ins/cml-explorer +color_cube_analyze_libexecdir = $(gimpplugindir)/plug-ins/color-cube-analyze +color_enhance_libexecdir = $(gimpplugindir)/plug-ins/color-enhance +colorify_libexecdir = $(gimpplugindir)/plug-ins/colorify +colormap_remap_libexecdir = $(gimpplugindir)/plug-ins/colormap-remap +compose_libexecdir = $(gimpplugindir)/plug-ins/compose +contrast_retinex_libexecdir = $(gimpplugindir)/plug-ins/contrast-retinex +crop_zealous_libexecdir = $(gimpplugindir)/plug-ins/crop-zealous +curve_bend_libexecdir = $(gimpplugindir)/plug-ins/curve-bend +decompose_libexecdir = $(gimpplugindir)/plug-ins/decompose +depth_merge_libexecdir = $(gimpplugindir)/plug-ins/depth-merge +despeckle_libexecdir = $(gimpplugindir)/plug-ins/despeckle +destripe_libexecdir = $(gimpplugindir)/plug-ins/destripe +edge_dog_libexecdir = $(gimpplugindir)/plug-ins/edge-dog +emboss_libexecdir = $(gimpplugindir)/plug-ins/emboss +file_aa_libexecdir = $(gimpplugindir)/plug-ins/file-aa +file_cel_libexecdir = $(gimpplugindir)/plug-ins/file-cel +file_compressor_libexecdir = $(gimpplugindir)/plug-ins/file-compressor +file_csource_libexecdir = $(gimpplugindir)/plug-ins/file-csource +file_desktop_link_libexecdir = $(gimpplugindir)/plug-ins/file-desktop-link +file_dicom_libexecdir = $(gimpplugindir)/plug-ins/file-dicom +file_gbr_libexecdir = $(gimpplugindir)/plug-ins/file-gbr +file_gegl_libexecdir = $(gimpplugindir)/plug-ins/file-gegl +file_gif_load_libexecdir = $(gimpplugindir)/plug-ins/file-gif-load +file_gif_save_libexecdir = $(gimpplugindir)/plug-ins/file-gif-save +file_gih_libexecdir = $(gimpplugindir)/plug-ins/file-gih +file_glob_libexecdir = $(gimpplugindir)/plug-ins/file-glob +file_header_libexecdir = $(gimpplugindir)/plug-ins/file-header +file_heif_libexecdir = $(gimpplugindir)/plug-ins/file-heif +file_html_table_libexecdir = $(gimpplugindir)/plug-ins/file-html-table +file_jp2_load_libexecdir = $(gimpplugindir)/plug-ins/file-jp2-load +file_mng_libexecdir = $(gimpplugindir)/plug-ins/file-mng +file_pat_libexecdir = $(gimpplugindir)/plug-ins/file-pat +file_pcx_libexecdir = $(gimpplugindir)/plug-ins/file-pcx +file_pdf_load_libexecdir = $(gimpplugindir)/plug-ins/file-pdf-load +file_pdf_save_libexecdir = $(gimpplugindir)/plug-ins/file-pdf-save +file_pix_libexecdir = $(gimpplugindir)/plug-ins/file-pix +file_png_libexecdir = $(gimpplugindir)/plug-ins/file-png +file_pnm_libexecdir = $(gimpplugindir)/plug-ins/file-pnm +file_ps_libexecdir = $(gimpplugindir)/plug-ins/file-ps +file_psp_libexecdir = $(gimpplugindir)/plug-ins/file-psp +file_raw_data_libexecdir = $(gimpplugindir)/plug-ins/file-raw-data +file_sunras_libexecdir = $(gimpplugindir)/plug-ins/file-sunras +file_svg_libexecdir = $(gimpplugindir)/plug-ins/file-svg +file_tga_libexecdir = $(gimpplugindir)/plug-ins/file-tga +file_wmf_libexecdir = $(gimpplugindir)/plug-ins/file-wmf +file_xbm_libexecdir = $(gimpplugindir)/plug-ins/file-xbm +file_xmc_libexecdir = $(gimpplugindir)/plug-ins/file-xmc +file_xpm_libexecdir = $(gimpplugindir)/plug-ins/file-xpm +file_xwd_libexecdir = $(gimpplugindir)/plug-ins/file-xwd +film_libexecdir = $(gimpplugindir)/plug-ins/film +filter_pack_libexecdir = $(gimpplugindir)/plug-ins/filter-pack +fractal_trace_libexecdir = $(gimpplugindir)/plug-ins/fractal-trace +goat_exercise_libexecdir = $(gimpplugindir)/plug-ins/goat-exercise +gradient_map_libexecdir = $(gimpplugindir)/plug-ins/gradient-map +grid_libexecdir = $(gimpplugindir)/plug-ins/grid +guillotine_libexecdir = $(gimpplugindir)/plug-ins/guillotine +hot_libexecdir = $(gimpplugindir)/plug-ins/hot +jigsaw_libexecdir = $(gimpplugindir)/plug-ins/jigsaw +mail_libexecdir = $(gimpplugindir)/plug-ins/mail +max_rgb_libexecdir = $(gimpplugindir)/plug-ins/max-rgb +nl_filter_libexecdir = $(gimpplugindir)/plug-ins/nl-filter +photocopy_libexecdir = $(gimpplugindir)/plug-ins/photocopy +plugin_browser_libexecdir = $(gimpplugindir)/plug-ins/plugin-browser +procedure_browser_libexecdir = $(gimpplugindir)/plug-ins/procedure-browser +qbist_libexecdir = $(gimpplugindir)/plug-ins/qbist +sample_colorize_libexecdir = $(gimpplugindir)/plug-ins/sample-colorize +sharpen_libexecdir = $(gimpplugindir)/plug-ins/sharpen +smooth_palette_libexecdir = $(gimpplugindir)/plug-ins/smooth-palette +softglow_libexecdir = $(gimpplugindir)/plug-ins/softglow +sparkle_libexecdir = $(gimpplugindir)/plug-ins/sparkle +sphere_designer_libexecdir = $(gimpplugindir)/plug-ins/sphere-designer +tile_libexecdir = $(gimpplugindir)/plug-ins/tile +tile_small_libexecdir = $(gimpplugindir)/plug-ins/tile-small +unit_editor_libexecdir = $(gimpplugindir)/plug-ins/unit-editor +van_gogh_lic_libexecdir = $(gimpplugindir)/plug-ins/van-gogh-lic +warp_libexecdir = $(gimpplugindir)/plug-ins/warp +wavelet_decompose_libexecdir = $(gimpplugindir)/plug-ins/wavelet-decompose +web_browser_libexecdir = $(gimpplugindir)/plug-ins/web-browser +web_page_libexecdir = $(gimpplugindir)/plug-ins/web-page +file_aa_libexec_PROGRAMS = $(FILE_AA) +file_heif_libexec_PROGRAMS = $(FILE_HEIF) +file_jp2_load_libexec_PROGRAMS = $(FILE_JP2_LOAD) +file_mng_libexec_PROGRAMS = $(FILE_MNG) +file_pdf_save_libexec_PROGRAMS = $(FILE_PDF_SAVE) +file_ps_libexec_PROGRAMS = $(FILE_PS) +file_wmf_libexec_PROGRAMS = $(FILE_WMF) +file_xmc_libexec_PROGRAMS = $(FILE_XMC) +file_xpm_libexec_PROGRAMS = $(FILE_XPM) +mail_libexec_PROGRAMS = $(MAIL) +web_page_libexec_PROGRAMS = $(WEB_PAGE) +align_layers_SOURCES = \ + align-layers.c + +align_layers_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(align_layers_RC) + +animation_optimize_SOURCES = \ + animation-optimize.c + +animation_optimize_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(animation_optimize_RC) + +animation_play_SOURCES = \ + animation-play.c + +animation_play_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(animation_play_RC) + +blinds_SOURCES = \ + blinds.c + +blinds_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(blinds_RC) + +blur_SOURCES = \ + blur.c + +blur_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(blur_RC) + +border_average_SOURCES = \ + border-average.c + +border_average_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(border_average_RC) + +busy_dialog_SOURCES = \ + busy-dialog.c + +busy_dialog_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(busy_dialog_RC) + +cartoon_SOURCES = \ + cartoon.c + +cartoon_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(cartoon_RC) + +checkerboard_SOURCES = \ + checkerboard.c + +checkerboard_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(checkerboard_RC) + +cml_explorer_SOURCES = \ + cml-explorer.c + +cml_explorer_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(cml_explorer_RC) + +color_cube_analyze_SOURCES = \ + color-cube-analyze.c + +color_cube_analyze_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(color_cube_analyze_RC) + +color_enhance_SOURCES = \ + color-enhance.c + +color_enhance_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(color_enhance_RC) + +colorify_SOURCES = \ + colorify.c + +colorify_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(colorify_RC) + +colormap_remap_SOURCES = \ + colormap-remap.c + +colormap_remap_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(colormap_remap_RC) + +compose_SOURCES = \ + compose.c + +compose_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(compose_RC) + +contrast_retinex_SOURCES = \ + contrast-retinex.c + +contrast_retinex_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(contrast_retinex_RC) + +crop_zealous_SOURCES = \ + crop-zealous.c + +crop_zealous_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(crop_zealous_RC) + +curve_bend_SOURCES = \ + curve-bend.c + +curve_bend_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(curve_bend_RC) + +decompose_SOURCES = \ + decompose.c + +decompose_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(decompose_RC) + +depth_merge_SOURCES = \ + depth-merge.c + +depth_merge_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(depth_merge_RC) + +despeckle_SOURCES = \ + despeckle.c + +despeckle_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(despeckle_RC) + +destripe_SOURCES = \ + destripe.c + +destripe_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(destripe_RC) + +edge_dog_SOURCES = \ + edge-dog.c + +edge_dog_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(edge_dog_RC) + +emboss_SOURCES = \ + emboss.c + +emboss_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(emboss_RC) + +file_aa_SOURCES = \ + file-aa.c + +file_aa_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(AA_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_aa_RC) + +file_cel_SOURCES = \ + file-cel.c + +file_cel_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_cel_RC) + +file_compressor_CFLAGS = $(LZMA_CFLAGS) +file_compressor_SOURCES = \ + file-compressor.c + +file_compressor_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GIO_LIBS) \ + $(LZMA_LIBS) \ + $(BZIP2_LIBS) \ + $(Z_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_compressor_RC) + +file_csource_SOURCES = \ + file-csource.c + +file_csource_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_csource_RC) + +file_desktop_link_SOURCES = \ + file-desktop-link.c + +file_desktop_link_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_desktop_link_RC) + +file_dicom_CFLAGS = -fno-strict-aliasing +file_dicom_SOURCES = \ + file-dicom.c + +file_dicom_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_dicom_RC) + +file_gbr_SOURCES = \ + file-gbr.c + +file_gbr_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gbr_RC) + +file_gegl_SOURCES = \ + file-gegl.c + +file_gegl_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gegl_RC) + +file_gif_load_SOURCES = \ + file-gif-load.c + +file_gif_load_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gif_load_RC) + +file_gif_save_SOURCES = \ + file-gif-save.c + +file_gif_save_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gif_save_RC) + +file_gih_SOURCES = \ + file-gih.c + +file_gih_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_gih_RC) + +file_glob_SOURCES = \ + file-glob.c + +file_glob_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_glob_RC) + +file_header_SOURCES = \ + file-header.c + +file_header_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_header_RC) + +file_heif_CFLAGS = $(LIBHEIF_CFLAGS) +file_heif_SOURCES = \ + file-heif.c + +file_heif_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEXIV2_LIBS) \ + $(GEGL_LIBS) \ + $(LIBHEIF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(LCMS_LIBS) \ + $(file_heif_RC) + +file_html_table_SOURCES = \ + file-html-table.c + +file_html_table_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_html_table_RC) + +file_jp2_load_CFLAGS = $(OPENJPEG_CFLAGS) +file_jp2_load_SOURCES = \ + file-jp2-load.c + +file_jp2_load_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(OPENJPEG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_jp2_load_RC) + +file_mng_CFLAGS = $(MNG_CFLAGS) +file_mng_SOURCES = \ + file-mng.c + +file_mng_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(MNG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_mng_RC) + +file_pat_SOURCES = \ + file-pat.c + +file_pat_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pat_RC) + +file_pcx_SOURCES = \ + file-pcx.c + +file_pcx_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pcx_RC) + +file_pdf_load_CFLAGS = $(POPPLER_CFLAGS) +file_pdf_load_SOURCES = \ + file-pdf-load.c + +file_pdf_load_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(POPPLER_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pdf_load_RC) + +file_pdf_save_CFLAGS = $(CAIRO_PDF_CFLAGS) +file_pdf_save_SOURCES = \ + file-pdf-save.c + +file_pdf_save_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(CAIRO_PDF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pdf_save_RC) + +file_pix_SOURCES = \ + file-pix.c + +file_pix_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pix_RC) + +file_png_CFLAGS = $(PNG_CFLAGS) +file_png_SOURCES = \ + file-png.c + +file_png_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(PNG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_png_RC) + +file_pnm_SOURCES = \ + file-pnm.c + +file_pnm_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_pnm_RC) + +file_ps_SOURCES = \ + file-ps.c + +file_ps_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(GS_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_ps_RC) + +file_psp_SOURCES = \ + file-psp.c + +file_psp_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(Z_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_psp_RC) + +file_raw_data_SOURCES = \ + file-raw-data.c + +file_raw_data_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_raw_data_RC) + +file_sunras_SOURCES = \ + file-sunras.c + +file_sunras_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_sunras_RC) + +file_svg_CFLAGS = $(SVG_CFLAGS) +file_svg_SOURCES = \ + file-svg.c + +file_svg_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(SVG_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_svg_RC) + +file_tga_SOURCES = \ + file-tga.c + +file_tga_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_tga_RC) + +file_wmf_CFLAGS = $(WMF_CFLAGS) +file_wmf_SOURCES = \ + file-wmf.c + +file_wmf_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(WMF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_wmf_RC) + +file_xbm_SOURCES = \ + file-xbm.c + +file_xbm_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xbm_RC) + +file_xmc_SOURCES = \ + file-xmc.c + +file_xmc_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(XMC_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xmc_RC) + +file_xpm_SOURCES = \ + file-xpm.c + +file_xpm_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(XPM_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xpm_RC) + +file_xwd_SOURCES = \ + file-xwd.c + +file_xwd_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_xwd_RC) + +film_SOURCES = \ + film.c + +film_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(film_RC) + +filter_pack_SOURCES = \ + filter-pack.c + +filter_pack_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(filter_pack_RC) + +fractal_trace_SOURCES = \ + fractal-trace.c + +fractal_trace_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(fractal_trace_RC) + +goat_exercise_SOURCES = \ + goat-exercise.c + +goat_exercise_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(goat_exercise_RC) + +gradient_map_SOURCES = \ + gradient-map.c + +gradient_map_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(gradient_map_RC) + +grid_SOURCES = \ + grid.c + +grid_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(grid_RC) + +guillotine_SOURCES = \ + guillotine.c + +guillotine_LDADD = \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(CAIRO_LIBS) \ + $(GDK_PIXBUF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(guillotine_RC) + +hot_SOURCES = \ + hot.c + +hot_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(hot_RC) + +jigsaw_SOURCES = \ + jigsaw.c + +jigsaw_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(jigsaw_RC) + +mail_SOURCES = \ + mail.c + +mail_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(mail_RC) + +max_rgb_SOURCES = \ + max-rgb.c + +max_rgb_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(max_rgb_RC) + +nl_filter_SOURCES = \ + nl-filter.c + +nl_filter_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(nl_filter_RC) + +photocopy_SOURCES = \ + photocopy.c + +photocopy_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(photocopy_RC) + +plugin_browser_SOURCES = \ + plugin-browser.c + +plugin_browser_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(plugin_browser_RC) + +procedure_browser_SOURCES = \ + procedure-browser.c + +procedure_browser_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(procedure_browser_RC) + +qbist_SOURCES = \ + qbist.c + +qbist_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(qbist_RC) + +sample_colorize_SOURCES = \ + sample-colorize.c + +sample_colorize_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sample_colorize_RC) + +sharpen_SOURCES = \ + sharpen.c + +sharpen_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sharpen_RC) + +smooth_palette_SOURCES = \ + smooth-palette.c + +smooth_palette_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(smooth_palette_RC) + +softglow_SOURCES = \ + softglow.c + +softglow_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(softglow_RC) + +sparkle_SOURCES = \ + sparkle.c + +sparkle_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sparkle_RC) + +sphere_designer_SOURCES = \ + sphere-designer.c + +sphere_designer_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(sphere_designer_RC) + +tile_SOURCES = \ + tile.c + +tile_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(tile_RC) + +tile_small_SOURCES = \ + tile-small.c + +tile_small_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(tile_small_RC) + +unit_editor_SOURCES = \ + unit-editor.c + +unit_editor_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(unit_editor_RC) + +van_gogh_lic_SOURCES = \ + van-gogh-lic.c + +van_gogh_lic_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(van_gogh_lic_RC) + +warp_SOURCES = \ + warp.c + +warp_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(warp_RC) + +wavelet_decompose_SOURCES = \ + wavelet-decompose.c + +wavelet_decompose_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(wavelet_decompose_RC) + +web_browser_LDFLAGS = $(framework_cocoa) +web_browser_CPPFLAGS = $(AM_CPPFLAGS) $(xobjective_c) +web_browser_SOURCES = \ + web-browser.c + +web_browser_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(web_browser_RC) + +web_page_CFLAGS = $(WEBKIT_CFLAGS) +web_page_SOURCES = \ + web-page.c + +web_page_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(WEBKIT_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(web_page_RC) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/build/windows/gimprc-plug-ins.rule $(srcdir)/gimprc.common $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu plug-ins/common/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu plug-ins/common/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/build/windows/gimprc-plug-ins.rule $(srcdir)/gimprc.common $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-align_layers_libexecPROGRAMS: $(align_layers_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(align_layers_libexec_PROGRAMS)'; test -n "$(align_layers_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(align_layers_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(align_layers_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(align_layers_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(align_layers_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-align_layers_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(align_layers_libexec_PROGRAMS)'; test -n "$(align_layers_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(align_layers_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(align_layers_libexecdir)" && rm -f $$files + +clean-align_layers_libexecPROGRAMS: + @list='$(align_layers_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-animation_optimize_libexecPROGRAMS: $(animation_optimize_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(animation_optimize_libexec_PROGRAMS)'; test -n "$(animation_optimize_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(animation_optimize_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(animation_optimize_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(animation_optimize_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(animation_optimize_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-animation_optimize_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(animation_optimize_libexec_PROGRAMS)'; test -n "$(animation_optimize_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(animation_optimize_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(animation_optimize_libexecdir)" && rm -f $$files + +clean-animation_optimize_libexecPROGRAMS: + @list='$(animation_optimize_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-animation_play_libexecPROGRAMS: $(animation_play_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(animation_play_libexec_PROGRAMS)'; test -n "$(animation_play_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(animation_play_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(animation_play_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(animation_play_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(animation_play_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-animation_play_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(animation_play_libexec_PROGRAMS)'; test -n "$(animation_play_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(animation_play_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(animation_play_libexecdir)" && rm -f $$files + +clean-animation_play_libexecPROGRAMS: + @list='$(animation_play_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-blinds_libexecPROGRAMS: $(blinds_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(blinds_libexec_PROGRAMS)'; test -n "$(blinds_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(blinds_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(blinds_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(blinds_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(blinds_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-blinds_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(blinds_libexec_PROGRAMS)'; test -n "$(blinds_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(blinds_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(blinds_libexecdir)" && rm -f $$files + +clean-blinds_libexecPROGRAMS: + @list='$(blinds_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-blur_libexecPROGRAMS: $(blur_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(blur_libexec_PROGRAMS)'; test -n "$(blur_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(blur_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(blur_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(blur_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(blur_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-blur_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(blur_libexec_PROGRAMS)'; test -n "$(blur_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(blur_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(blur_libexecdir)" && rm -f $$files + +clean-blur_libexecPROGRAMS: + @list='$(blur_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-border_average_libexecPROGRAMS: $(border_average_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(border_average_libexec_PROGRAMS)'; test -n "$(border_average_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(border_average_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(border_average_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(border_average_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(border_average_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-border_average_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(border_average_libexec_PROGRAMS)'; test -n "$(border_average_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(border_average_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(border_average_libexecdir)" && rm -f $$files + +clean-border_average_libexecPROGRAMS: + @list='$(border_average_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-busy_dialog_libexecPROGRAMS: $(busy_dialog_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(busy_dialog_libexec_PROGRAMS)'; test -n "$(busy_dialog_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(busy_dialog_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(busy_dialog_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(busy_dialog_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(busy_dialog_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-busy_dialog_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(busy_dialog_libexec_PROGRAMS)'; test -n "$(busy_dialog_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(busy_dialog_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(busy_dialog_libexecdir)" && rm -f $$files + +clean-busy_dialog_libexecPROGRAMS: + @list='$(busy_dialog_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-cartoon_libexecPROGRAMS: $(cartoon_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(cartoon_libexec_PROGRAMS)'; test -n "$(cartoon_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(cartoon_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(cartoon_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(cartoon_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(cartoon_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-cartoon_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(cartoon_libexec_PROGRAMS)'; test -n "$(cartoon_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(cartoon_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(cartoon_libexecdir)" && rm -f $$files + +clean-cartoon_libexecPROGRAMS: + @list='$(cartoon_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-checkerboard_libexecPROGRAMS: $(checkerboard_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(checkerboard_libexec_PROGRAMS)'; test -n "$(checkerboard_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(checkerboard_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(checkerboard_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(checkerboard_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(checkerboard_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-checkerboard_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(checkerboard_libexec_PROGRAMS)'; test -n "$(checkerboard_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(checkerboard_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(checkerboard_libexecdir)" && rm -f $$files + +clean-checkerboard_libexecPROGRAMS: + @list='$(checkerboard_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-cml_explorer_libexecPROGRAMS: $(cml_explorer_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(cml_explorer_libexec_PROGRAMS)'; test -n "$(cml_explorer_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(cml_explorer_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(cml_explorer_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(cml_explorer_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(cml_explorer_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-cml_explorer_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(cml_explorer_libexec_PROGRAMS)'; test -n "$(cml_explorer_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(cml_explorer_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(cml_explorer_libexecdir)" && rm -f $$files + +clean-cml_explorer_libexecPROGRAMS: + @list='$(cml_explorer_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-color_cube_analyze_libexecPROGRAMS: $(color_cube_analyze_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(color_cube_analyze_libexec_PROGRAMS)'; test -n "$(color_cube_analyze_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(color_cube_analyze_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(color_cube_analyze_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(color_cube_analyze_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(color_cube_analyze_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-color_cube_analyze_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(color_cube_analyze_libexec_PROGRAMS)'; test -n "$(color_cube_analyze_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(color_cube_analyze_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(color_cube_analyze_libexecdir)" && rm -f $$files + +clean-color_cube_analyze_libexecPROGRAMS: + @list='$(color_cube_analyze_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-color_enhance_libexecPROGRAMS: $(color_enhance_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(color_enhance_libexec_PROGRAMS)'; test -n "$(color_enhance_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(color_enhance_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(color_enhance_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(color_enhance_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(color_enhance_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-color_enhance_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(color_enhance_libexec_PROGRAMS)'; test -n "$(color_enhance_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(color_enhance_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(color_enhance_libexecdir)" && rm -f $$files + +clean-color_enhance_libexecPROGRAMS: + @list='$(color_enhance_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-colorify_libexecPROGRAMS: $(colorify_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(colorify_libexec_PROGRAMS)'; test -n "$(colorify_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(colorify_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(colorify_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(colorify_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(colorify_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-colorify_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(colorify_libexec_PROGRAMS)'; test -n "$(colorify_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(colorify_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(colorify_libexecdir)" && rm -f $$files + +clean-colorify_libexecPROGRAMS: + @list='$(colorify_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-colormap_remap_libexecPROGRAMS: $(colormap_remap_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(colormap_remap_libexec_PROGRAMS)'; test -n "$(colormap_remap_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(colormap_remap_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(colormap_remap_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(colormap_remap_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(colormap_remap_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-colormap_remap_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(colormap_remap_libexec_PROGRAMS)'; test -n "$(colormap_remap_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(colormap_remap_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(colormap_remap_libexecdir)" && rm -f $$files + +clean-colormap_remap_libexecPROGRAMS: + @list='$(colormap_remap_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-compose_libexecPROGRAMS: $(compose_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(compose_libexec_PROGRAMS)'; test -n "$(compose_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(compose_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(compose_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(compose_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(compose_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-compose_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(compose_libexec_PROGRAMS)'; test -n "$(compose_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(compose_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(compose_libexecdir)" && rm -f $$files + +clean-compose_libexecPROGRAMS: + @list='$(compose_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-contrast_retinex_libexecPROGRAMS: $(contrast_retinex_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(contrast_retinex_libexec_PROGRAMS)'; test -n "$(contrast_retinex_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(contrast_retinex_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(contrast_retinex_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(contrast_retinex_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(contrast_retinex_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-contrast_retinex_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(contrast_retinex_libexec_PROGRAMS)'; test -n "$(contrast_retinex_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(contrast_retinex_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(contrast_retinex_libexecdir)" && rm -f $$files + +clean-contrast_retinex_libexecPROGRAMS: + @list='$(contrast_retinex_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-crop_zealous_libexecPROGRAMS: $(crop_zealous_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(crop_zealous_libexec_PROGRAMS)'; test -n "$(crop_zealous_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(crop_zealous_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(crop_zealous_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(crop_zealous_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(crop_zealous_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-crop_zealous_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(crop_zealous_libexec_PROGRAMS)'; test -n "$(crop_zealous_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(crop_zealous_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(crop_zealous_libexecdir)" && rm -f $$files + +clean-crop_zealous_libexecPROGRAMS: + @list='$(crop_zealous_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-curve_bend_libexecPROGRAMS: $(curve_bend_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(curve_bend_libexec_PROGRAMS)'; test -n "$(curve_bend_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(curve_bend_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(curve_bend_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(curve_bend_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(curve_bend_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-curve_bend_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(curve_bend_libexec_PROGRAMS)'; test -n "$(curve_bend_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(curve_bend_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(curve_bend_libexecdir)" && rm -f $$files + +clean-curve_bend_libexecPROGRAMS: + @list='$(curve_bend_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-decompose_libexecPROGRAMS: $(decompose_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(decompose_libexec_PROGRAMS)'; test -n "$(decompose_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(decompose_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(decompose_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(decompose_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(decompose_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-decompose_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(decompose_libexec_PROGRAMS)'; test -n "$(decompose_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(decompose_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(decompose_libexecdir)" && rm -f $$files + +clean-decompose_libexecPROGRAMS: + @list='$(decompose_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-depth_merge_libexecPROGRAMS: $(depth_merge_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(depth_merge_libexec_PROGRAMS)'; test -n "$(depth_merge_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(depth_merge_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(depth_merge_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(depth_merge_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(depth_merge_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-depth_merge_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(depth_merge_libexec_PROGRAMS)'; test -n "$(depth_merge_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(depth_merge_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(depth_merge_libexecdir)" && rm -f $$files + +clean-depth_merge_libexecPROGRAMS: + @list='$(depth_merge_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-despeckle_libexecPROGRAMS: $(despeckle_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(despeckle_libexec_PROGRAMS)'; test -n "$(despeckle_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(despeckle_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(despeckle_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(despeckle_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(despeckle_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-despeckle_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(despeckle_libexec_PROGRAMS)'; test -n "$(despeckle_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(despeckle_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(despeckle_libexecdir)" && rm -f $$files + +clean-despeckle_libexecPROGRAMS: + @list='$(despeckle_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-destripe_libexecPROGRAMS: $(destripe_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(destripe_libexec_PROGRAMS)'; test -n "$(destripe_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(destripe_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(destripe_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(destripe_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(destripe_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-destripe_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(destripe_libexec_PROGRAMS)'; test -n "$(destripe_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(destripe_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(destripe_libexecdir)" && rm -f $$files + +clean-destripe_libexecPROGRAMS: + @list='$(destripe_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-edge_dog_libexecPROGRAMS: $(edge_dog_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(edge_dog_libexec_PROGRAMS)'; test -n "$(edge_dog_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(edge_dog_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(edge_dog_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(edge_dog_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(edge_dog_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-edge_dog_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(edge_dog_libexec_PROGRAMS)'; test -n "$(edge_dog_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(edge_dog_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(edge_dog_libexecdir)" && rm -f $$files + +clean-edge_dog_libexecPROGRAMS: + @list='$(edge_dog_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-emboss_libexecPROGRAMS: $(emboss_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(emboss_libexec_PROGRAMS)'; test -n "$(emboss_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(emboss_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(emboss_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(emboss_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(emboss_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-emboss_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(emboss_libexec_PROGRAMS)'; test -n "$(emboss_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(emboss_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(emboss_libexecdir)" && rm -f $$files + +clean-emboss_libexecPROGRAMS: + @list='$(emboss_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_aa_libexecPROGRAMS: $(file_aa_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_aa_libexec_PROGRAMS)'; test -n "$(file_aa_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_aa_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_aa_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_aa_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_aa_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_aa_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_aa_libexec_PROGRAMS)'; test -n "$(file_aa_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_aa_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_aa_libexecdir)" && rm -f $$files + +clean-file_aa_libexecPROGRAMS: + @list='$(file_aa_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_cel_libexecPROGRAMS: $(file_cel_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_cel_libexec_PROGRAMS)'; test -n "$(file_cel_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_cel_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_cel_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_cel_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_cel_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_cel_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_cel_libexec_PROGRAMS)'; test -n "$(file_cel_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_cel_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_cel_libexecdir)" && rm -f $$files + +clean-file_cel_libexecPROGRAMS: + @list='$(file_cel_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_compressor_libexecPROGRAMS: $(file_compressor_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_compressor_libexec_PROGRAMS)'; test -n "$(file_compressor_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_compressor_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_compressor_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_compressor_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_compressor_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_compressor_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_compressor_libexec_PROGRAMS)'; test -n "$(file_compressor_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_compressor_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_compressor_libexecdir)" && rm -f $$files + +clean-file_compressor_libexecPROGRAMS: + @list='$(file_compressor_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_csource_libexecPROGRAMS: $(file_csource_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_csource_libexec_PROGRAMS)'; test -n "$(file_csource_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_csource_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_csource_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_csource_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_csource_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_csource_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_csource_libexec_PROGRAMS)'; test -n "$(file_csource_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_csource_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_csource_libexecdir)" && rm -f $$files + +clean-file_csource_libexecPROGRAMS: + @list='$(file_csource_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_desktop_link_libexecPROGRAMS: $(file_desktop_link_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_desktop_link_libexec_PROGRAMS)'; test -n "$(file_desktop_link_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_desktop_link_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_desktop_link_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_desktop_link_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_desktop_link_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_desktop_link_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_desktop_link_libexec_PROGRAMS)'; test -n "$(file_desktop_link_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_desktop_link_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_desktop_link_libexecdir)" && rm -f $$files + +clean-file_desktop_link_libexecPROGRAMS: + @list='$(file_desktop_link_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_dicom_libexecPROGRAMS: $(file_dicom_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_dicom_libexec_PROGRAMS)'; test -n "$(file_dicom_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_dicom_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_dicom_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_dicom_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_dicom_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_dicom_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_dicom_libexec_PROGRAMS)'; test -n "$(file_dicom_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_dicom_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_dicom_libexecdir)" && rm -f $$files + +clean-file_dicom_libexecPROGRAMS: + @list='$(file_dicom_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_gbr_libexecPROGRAMS: $(file_gbr_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_gbr_libexec_PROGRAMS)'; test -n "$(file_gbr_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_gbr_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_gbr_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_gbr_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_gbr_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_gbr_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_gbr_libexec_PROGRAMS)'; test -n "$(file_gbr_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_gbr_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_gbr_libexecdir)" && rm -f $$files + +clean-file_gbr_libexecPROGRAMS: + @list='$(file_gbr_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_gegl_libexecPROGRAMS: $(file_gegl_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_gegl_libexec_PROGRAMS)'; test -n "$(file_gegl_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_gegl_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_gegl_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_gegl_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_gegl_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_gegl_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_gegl_libexec_PROGRAMS)'; test -n "$(file_gegl_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_gegl_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_gegl_libexecdir)" && rm -f $$files + +clean-file_gegl_libexecPROGRAMS: + @list='$(file_gegl_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_gif_load_libexecPROGRAMS: $(file_gif_load_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_gif_load_libexec_PROGRAMS)'; test -n "$(file_gif_load_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_gif_load_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_gif_load_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_gif_load_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_gif_load_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_gif_load_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_gif_load_libexec_PROGRAMS)'; test -n "$(file_gif_load_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_gif_load_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_gif_load_libexecdir)" && rm -f $$files + +clean-file_gif_load_libexecPROGRAMS: + @list='$(file_gif_load_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_gif_save_libexecPROGRAMS: $(file_gif_save_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_gif_save_libexec_PROGRAMS)'; test -n "$(file_gif_save_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_gif_save_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_gif_save_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_gif_save_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_gif_save_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_gif_save_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_gif_save_libexec_PROGRAMS)'; test -n "$(file_gif_save_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_gif_save_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_gif_save_libexecdir)" && rm -f $$files + +clean-file_gif_save_libexecPROGRAMS: + @list='$(file_gif_save_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_gih_libexecPROGRAMS: $(file_gih_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_gih_libexec_PROGRAMS)'; test -n "$(file_gih_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_gih_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_gih_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_gih_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_gih_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_gih_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_gih_libexec_PROGRAMS)'; test -n "$(file_gih_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_gih_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_gih_libexecdir)" && rm -f $$files + +clean-file_gih_libexecPROGRAMS: + @list='$(file_gih_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_glob_libexecPROGRAMS: $(file_glob_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_glob_libexec_PROGRAMS)'; test -n "$(file_glob_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_glob_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_glob_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_glob_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_glob_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_glob_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_glob_libexec_PROGRAMS)'; test -n "$(file_glob_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_glob_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_glob_libexecdir)" && rm -f $$files + +clean-file_glob_libexecPROGRAMS: + @list='$(file_glob_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_header_libexecPROGRAMS: $(file_header_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_header_libexec_PROGRAMS)'; test -n "$(file_header_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_header_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_header_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_header_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_header_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_header_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_header_libexec_PROGRAMS)'; test -n "$(file_header_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_header_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_header_libexecdir)" && rm -f $$files + +clean-file_header_libexecPROGRAMS: + @list='$(file_header_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_heif_libexecPROGRAMS: $(file_heif_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_heif_libexec_PROGRAMS)'; test -n "$(file_heif_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_heif_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_heif_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_heif_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_heif_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_heif_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_heif_libexec_PROGRAMS)'; test -n "$(file_heif_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_heif_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_heif_libexecdir)" && rm -f $$files + +clean-file_heif_libexecPROGRAMS: + @list='$(file_heif_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_html_table_libexecPROGRAMS: $(file_html_table_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_html_table_libexec_PROGRAMS)'; test -n "$(file_html_table_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_html_table_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_html_table_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_html_table_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_html_table_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_html_table_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_html_table_libexec_PROGRAMS)'; test -n "$(file_html_table_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_html_table_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_html_table_libexecdir)" && rm -f $$files + +clean-file_html_table_libexecPROGRAMS: + @list='$(file_html_table_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_jp2_load_libexecPROGRAMS: $(file_jp2_load_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_jp2_load_libexec_PROGRAMS)'; test -n "$(file_jp2_load_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_jp2_load_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_jp2_load_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_jp2_load_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_jp2_load_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_jp2_load_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_jp2_load_libexec_PROGRAMS)'; test -n "$(file_jp2_load_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_jp2_load_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_jp2_load_libexecdir)" && rm -f $$files + +clean-file_jp2_load_libexecPROGRAMS: + @list='$(file_jp2_load_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_mng_libexecPROGRAMS: $(file_mng_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_mng_libexec_PROGRAMS)'; test -n "$(file_mng_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_mng_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_mng_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_mng_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_mng_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_mng_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_mng_libexec_PROGRAMS)'; test -n "$(file_mng_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_mng_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_mng_libexecdir)" && rm -f $$files + +clean-file_mng_libexecPROGRAMS: + @list='$(file_mng_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_pat_libexecPROGRAMS: $(file_pat_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_pat_libexec_PROGRAMS)'; test -n "$(file_pat_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_pat_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_pat_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_pat_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_pat_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_pat_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_pat_libexec_PROGRAMS)'; test -n "$(file_pat_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_pat_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_pat_libexecdir)" && rm -f $$files + +clean-file_pat_libexecPROGRAMS: + @list='$(file_pat_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_pcx_libexecPROGRAMS: $(file_pcx_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_pcx_libexec_PROGRAMS)'; test -n "$(file_pcx_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_pcx_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_pcx_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_pcx_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_pcx_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_pcx_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_pcx_libexec_PROGRAMS)'; test -n "$(file_pcx_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_pcx_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_pcx_libexecdir)" && rm -f $$files + +clean-file_pcx_libexecPROGRAMS: + @list='$(file_pcx_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_pdf_load_libexecPROGRAMS: $(file_pdf_load_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_pdf_load_libexec_PROGRAMS)'; test -n "$(file_pdf_load_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_pdf_load_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_pdf_load_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_pdf_load_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_pdf_load_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_pdf_load_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_pdf_load_libexec_PROGRAMS)'; test -n "$(file_pdf_load_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_pdf_load_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_pdf_load_libexecdir)" && rm -f $$files + +clean-file_pdf_load_libexecPROGRAMS: + @list='$(file_pdf_load_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_pdf_save_libexecPROGRAMS: $(file_pdf_save_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_pdf_save_libexec_PROGRAMS)'; test -n "$(file_pdf_save_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_pdf_save_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_pdf_save_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_pdf_save_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_pdf_save_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_pdf_save_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_pdf_save_libexec_PROGRAMS)'; test -n "$(file_pdf_save_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_pdf_save_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_pdf_save_libexecdir)" && rm -f $$files + +clean-file_pdf_save_libexecPROGRAMS: + @list='$(file_pdf_save_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_pix_libexecPROGRAMS: $(file_pix_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_pix_libexec_PROGRAMS)'; test -n "$(file_pix_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_pix_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_pix_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_pix_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_pix_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_pix_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_pix_libexec_PROGRAMS)'; test -n "$(file_pix_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_pix_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_pix_libexecdir)" && rm -f $$files + +clean-file_pix_libexecPROGRAMS: + @list='$(file_pix_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_png_libexecPROGRAMS: $(file_png_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_png_libexec_PROGRAMS)'; test -n "$(file_png_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_png_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_png_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_png_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_png_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_png_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_png_libexec_PROGRAMS)'; test -n "$(file_png_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_png_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_png_libexecdir)" && rm -f $$files + +clean-file_png_libexecPROGRAMS: + @list='$(file_png_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_pnm_libexecPROGRAMS: $(file_pnm_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_pnm_libexec_PROGRAMS)'; test -n "$(file_pnm_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_pnm_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_pnm_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_pnm_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_pnm_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_pnm_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_pnm_libexec_PROGRAMS)'; test -n "$(file_pnm_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_pnm_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_pnm_libexecdir)" && rm -f $$files + +clean-file_pnm_libexecPROGRAMS: + @list='$(file_pnm_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_ps_libexecPROGRAMS: $(file_ps_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_ps_libexec_PROGRAMS)'; test -n "$(file_ps_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_ps_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_ps_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_ps_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_ps_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_ps_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_ps_libexec_PROGRAMS)'; test -n "$(file_ps_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_ps_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_ps_libexecdir)" && rm -f $$files + +clean-file_ps_libexecPROGRAMS: + @list='$(file_ps_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_psp_libexecPROGRAMS: $(file_psp_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_psp_libexec_PROGRAMS)'; test -n "$(file_psp_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_psp_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_psp_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_psp_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_psp_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_psp_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_psp_libexec_PROGRAMS)'; test -n "$(file_psp_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_psp_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_psp_libexecdir)" && rm -f $$files + +clean-file_psp_libexecPROGRAMS: + @list='$(file_psp_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_raw_data_libexecPROGRAMS: $(file_raw_data_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_raw_data_libexec_PROGRAMS)'; test -n "$(file_raw_data_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_raw_data_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_raw_data_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_raw_data_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_raw_data_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_raw_data_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_raw_data_libexec_PROGRAMS)'; test -n "$(file_raw_data_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_raw_data_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_raw_data_libexecdir)" && rm -f $$files + +clean-file_raw_data_libexecPROGRAMS: + @list='$(file_raw_data_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_sunras_libexecPROGRAMS: $(file_sunras_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_sunras_libexec_PROGRAMS)'; test -n "$(file_sunras_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_sunras_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_sunras_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_sunras_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_sunras_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_sunras_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_sunras_libexec_PROGRAMS)'; test -n "$(file_sunras_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_sunras_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_sunras_libexecdir)" && rm -f $$files + +clean-file_sunras_libexecPROGRAMS: + @list='$(file_sunras_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_svg_libexecPROGRAMS: $(file_svg_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_svg_libexec_PROGRAMS)'; test -n "$(file_svg_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_svg_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_svg_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_svg_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_svg_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_svg_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_svg_libexec_PROGRAMS)'; test -n "$(file_svg_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_svg_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_svg_libexecdir)" && rm -f $$files + +clean-file_svg_libexecPROGRAMS: + @list='$(file_svg_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_tga_libexecPROGRAMS: $(file_tga_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_tga_libexec_PROGRAMS)'; test -n "$(file_tga_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_tga_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_tga_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_tga_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_tga_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_tga_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_tga_libexec_PROGRAMS)'; test -n "$(file_tga_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_tga_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_tga_libexecdir)" && rm -f $$files + +clean-file_tga_libexecPROGRAMS: + @list='$(file_tga_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_wmf_libexecPROGRAMS: $(file_wmf_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_wmf_libexec_PROGRAMS)'; test -n "$(file_wmf_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_wmf_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_wmf_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_wmf_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_wmf_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_wmf_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_wmf_libexec_PROGRAMS)'; test -n "$(file_wmf_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_wmf_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_wmf_libexecdir)" && rm -f $$files + +clean-file_wmf_libexecPROGRAMS: + @list='$(file_wmf_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_xbm_libexecPROGRAMS: $(file_xbm_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_xbm_libexec_PROGRAMS)'; test -n "$(file_xbm_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_xbm_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_xbm_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_xbm_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_xbm_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_xbm_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_xbm_libexec_PROGRAMS)'; test -n "$(file_xbm_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_xbm_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_xbm_libexecdir)" && rm -f $$files + +clean-file_xbm_libexecPROGRAMS: + @list='$(file_xbm_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_xmc_libexecPROGRAMS: $(file_xmc_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_xmc_libexec_PROGRAMS)'; test -n "$(file_xmc_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_xmc_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_xmc_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_xmc_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_xmc_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_xmc_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_xmc_libexec_PROGRAMS)'; test -n "$(file_xmc_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_xmc_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_xmc_libexecdir)" && rm -f $$files + +clean-file_xmc_libexecPROGRAMS: + @list='$(file_xmc_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_xpm_libexecPROGRAMS: $(file_xpm_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_xpm_libexec_PROGRAMS)'; test -n "$(file_xpm_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_xpm_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_xpm_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_xpm_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_xpm_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_xpm_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_xpm_libexec_PROGRAMS)'; test -n "$(file_xpm_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_xpm_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_xpm_libexecdir)" && rm -f $$files + +clean-file_xpm_libexecPROGRAMS: + @list='$(file_xpm_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-file_xwd_libexecPROGRAMS: $(file_xwd_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(file_xwd_libexec_PROGRAMS)'; test -n "$(file_xwd_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(file_xwd_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(file_xwd_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(file_xwd_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_xwd_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-file_xwd_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(file_xwd_libexec_PROGRAMS)'; test -n "$(file_xwd_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(file_xwd_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(file_xwd_libexecdir)" && rm -f $$files + +clean-file_xwd_libexecPROGRAMS: + @list='$(file_xwd_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-film_libexecPROGRAMS: $(film_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(film_libexec_PROGRAMS)'; test -n "$(film_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(film_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(film_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(film_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(film_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-film_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(film_libexec_PROGRAMS)'; test -n "$(film_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(film_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(film_libexecdir)" && rm -f $$files + +clean-film_libexecPROGRAMS: + @list='$(film_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-filter_pack_libexecPROGRAMS: $(filter_pack_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(filter_pack_libexec_PROGRAMS)'; test -n "$(filter_pack_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(filter_pack_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(filter_pack_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(filter_pack_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(filter_pack_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-filter_pack_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(filter_pack_libexec_PROGRAMS)'; test -n "$(filter_pack_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(filter_pack_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(filter_pack_libexecdir)" && rm -f $$files + +clean-filter_pack_libexecPROGRAMS: + @list='$(filter_pack_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-fractal_trace_libexecPROGRAMS: $(fractal_trace_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(fractal_trace_libexec_PROGRAMS)'; test -n "$(fractal_trace_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(fractal_trace_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(fractal_trace_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(fractal_trace_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(fractal_trace_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-fractal_trace_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(fractal_trace_libexec_PROGRAMS)'; test -n "$(fractal_trace_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(fractal_trace_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(fractal_trace_libexecdir)" && rm -f $$files + +clean-fractal_trace_libexecPROGRAMS: + @list='$(fractal_trace_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-goat_exercise_libexecPROGRAMS: $(goat_exercise_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(goat_exercise_libexec_PROGRAMS)'; test -n "$(goat_exercise_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(goat_exercise_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(goat_exercise_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(goat_exercise_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(goat_exercise_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-goat_exercise_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(goat_exercise_libexec_PROGRAMS)'; test -n "$(goat_exercise_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(goat_exercise_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(goat_exercise_libexecdir)" && rm -f $$files + +clean-goat_exercise_libexecPROGRAMS: + @list='$(goat_exercise_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-gradient_map_libexecPROGRAMS: $(gradient_map_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(gradient_map_libexec_PROGRAMS)'; test -n "$(gradient_map_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(gradient_map_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(gradient_map_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(gradient_map_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(gradient_map_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-gradient_map_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(gradient_map_libexec_PROGRAMS)'; test -n "$(gradient_map_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(gradient_map_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(gradient_map_libexecdir)" && rm -f $$files + +clean-gradient_map_libexecPROGRAMS: + @list='$(gradient_map_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-grid_libexecPROGRAMS: $(grid_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(grid_libexec_PROGRAMS)'; test -n "$(grid_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(grid_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(grid_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(grid_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(grid_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-grid_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(grid_libexec_PROGRAMS)'; test -n "$(grid_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(grid_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(grid_libexecdir)" && rm -f $$files + +clean-grid_libexecPROGRAMS: + @list='$(grid_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-guillotine_libexecPROGRAMS: $(guillotine_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(guillotine_libexec_PROGRAMS)'; test -n "$(guillotine_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(guillotine_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(guillotine_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(guillotine_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(guillotine_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-guillotine_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(guillotine_libexec_PROGRAMS)'; test -n "$(guillotine_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(guillotine_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(guillotine_libexecdir)" && rm -f $$files + +clean-guillotine_libexecPROGRAMS: + @list='$(guillotine_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-hot_libexecPROGRAMS: $(hot_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(hot_libexec_PROGRAMS)'; test -n "$(hot_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(hot_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(hot_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(hot_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(hot_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-hot_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(hot_libexec_PROGRAMS)'; test -n "$(hot_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(hot_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(hot_libexecdir)" && rm -f $$files + +clean-hot_libexecPROGRAMS: + @list='$(hot_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-jigsaw_libexecPROGRAMS: $(jigsaw_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(jigsaw_libexec_PROGRAMS)'; test -n "$(jigsaw_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(jigsaw_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(jigsaw_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(jigsaw_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(jigsaw_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-jigsaw_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(jigsaw_libexec_PROGRAMS)'; test -n "$(jigsaw_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(jigsaw_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(jigsaw_libexecdir)" && rm -f $$files + +clean-jigsaw_libexecPROGRAMS: + @list='$(jigsaw_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-mail_libexecPROGRAMS: $(mail_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(mail_libexec_PROGRAMS)'; test -n "$(mail_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(mail_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(mail_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(mail_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(mail_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-mail_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(mail_libexec_PROGRAMS)'; test -n "$(mail_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(mail_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(mail_libexecdir)" && rm -f $$files + +clean-mail_libexecPROGRAMS: + @list='$(mail_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-max_rgb_libexecPROGRAMS: $(max_rgb_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(max_rgb_libexec_PROGRAMS)'; test -n "$(max_rgb_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(max_rgb_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(max_rgb_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(max_rgb_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(max_rgb_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-max_rgb_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(max_rgb_libexec_PROGRAMS)'; test -n "$(max_rgb_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(max_rgb_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(max_rgb_libexecdir)" && rm -f $$files + +clean-max_rgb_libexecPROGRAMS: + @list='$(max_rgb_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-nl_filter_libexecPROGRAMS: $(nl_filter_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(nl_filter_libexec_PROGRAMS)'; test -n "$(nl_filter_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(nl_filter_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(nl_filter_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(nl_filter_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(nl_filter_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-nl_filter_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(nl_filter_libexec_PROGRAMS)'; test -n "$(nl_filter_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(nl_filter_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(nl_filter_libexecdir)" && rm -f $$files + +clean-nl_filter_libexecPROGRAMS: + @list='$(nl_filter_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-photocopy_libexecPROGRAMS: $(photocopy_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(photocopy_libexec_PROGRAMS)'; test -n "$(photocopy_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(photocopy_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(photocopy_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(photocopy_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(photocopy_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-photocopy_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(photocopy_libexec_PROGRAMS)'; test -n "$(photocopy_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(photocopy_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(photocopy_libexecdir)" && rm -f $$files + +clean-photocopy_libexecPROGRAMS: + @list='$(photocopy_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-plugin_browser_libexecPROGRAMS: $(plugin_browser_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(plugin_browser_libexec_PROGRAMS)'; test -n "$(plugin_browser_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(plugin_browser_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(plugin_browser_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(plugin_browser_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(plugin_browser_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-plugin_browser_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(plugin_browser_libexec_PROGRAMS)'; test -n "$(plugin_browser_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(plugin_browser_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(plugin_browser_libexecdir)" && rm -f $$files + +clean-plugin_browser_libexecPROGRAMS: + @list='$(plugin_browser_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-procedure_browser_libexecPROGRAMS: $(procedure_browser_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(procedure_browser_libexec_PROGRAMS)'; test -n "$(procedure_browser_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(procedure_browser_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(procedure_browser_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(procedure_browser_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(procedure_browser_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-procedure_browser_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(procedure_browser_libexec_PROGRAMS)'; test -n "$(procedure_browser_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(procedure_browser_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(procedure_browser_libexecdir)" && rm -f $$files + +clean-procedure_browser_libexecPROGRAMS: + @list='$(procedure_browser_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-qbist_libexecPROGRAMS: $(qbist_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(qbist_libexec_PROGRAMS)'; test -n "$(qbist_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(qbist_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(qbist_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(qbist_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(qbist_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-qbist_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(qbist_libexec_PROGRAMS)'; test -n "$(qbist_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(qbist_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(qbist_libexecdir)" && rm -f $$files + +clean-qbist_libexecPROGRAMS: + @list='$(qbist_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-sample_colorize_libexecPROGRAMS: $(sample_colorize_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sample_colorize_libexec_PROGRAMS)'; test -n "$(sample_colorize_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sample_colorize_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sample_colorize_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sample_colorize_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sample_colorize_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sample_colorize_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sample_colorize_libexec_PROGRAMS)'; test -n "$(sample_colorize_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sample_colorize_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sample_colorize_libexecdir)" && rm -f $$files + +clean-sample_colorize_libexecPROGRAMS: + @list='$(sample_colorize_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-sharpen_libexecPROGRAMS: $(sharpen_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sharpen_libexec_PROGRAMS)'; test -n "$(sharpen_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sharpen_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sharpen_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sharpen_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sharpen_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sharpen_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sharpen_libexec_PROGRAMS)'; test -n "$(sharpen_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sharpen_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sharpen_libexecdir)" && rm -f $$files + +clean-sharpen_libexecPROGRAMS: + @list='$(sharpen_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-smooth_palette_libexecPROGRAMS: $(smooth_palette_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(smooth_palette_libexec_PROGRAMS)'; test -n "$(smooth_palette_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(smooth_palette_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(smooth_palette_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(smooth_palette_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(smooth_palette_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-smooth_palette_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(smooth_palette_libexec_PROGRAMS)'; test -n "$(smooth_palette_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(smooth_palette_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(smooth_palette_libexecdir)" && rm -f $$files + +clean-smooth_palette_libexecPROGRAMS: + @list='$(smooth_palette_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-softglow_libexecPROGRAMS: $(softglow_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(softglow_libexec_PROGRAMS)'; test -n "$(softglow_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(softglow_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(softglow_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(softglow_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(softglow_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-softglow_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(softglow_libexec_PROGRAMS)'; test -n "$(softglow_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(softglow_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(softglow_libexecdir)" && rm -f $$files + +clean-softglow_libexecPROGRAMS: + @list='$(softglow_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-sparkle_libexecPROGRAMS: $(sparkle_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sparkle_libexec_PROGRAMS)'; test -n "$(sparkle_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sparkle_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sparkle_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sparkle_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sparkle_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sparkle_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sparkle_libexec_PROGRAMS)'; test -n "$(sparkle_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sparkle_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sparkle_libexecdir)" && rm -f $$files + +clean-sparkle_libexecPROGRAMS: + @list='$(sparkle_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-sphere_designer_libexecPROGRAMS: $(sphere_designer_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sphere_designer_libexec_PROGRAMS)'; test -n "$(sphere_designer_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sphere_designer_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sphere_designer_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sphere_designer_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sphere_designer_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sphere_designer_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sphere_designer_libexec_PROGRAMS)'; test -n "$(sphere_designer_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sphere_designer_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sphere_designer_libexecdir)" && rm -f $$files + +clean-sphere_designer_libexecPROGRAMS: + @list='$(sphere_designer_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-tile_libexecPROGRAMS: $(tile_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(tile_libexec_PROGRAMS)'; test -n "$(tile_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(tile_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(tile_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(tile_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(tile_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-tile_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(tile_libexec_PROGRAMS)'; test -n "$(tile_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(tile_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(tile_libexecdir)" && rm -f $$files + +clean-tile_libexecPROGRAMS: + @list='$(tile_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-tile_small_libexecPROGRAMS: $(tile_small_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(tile_small_libexec_PROGRAMS)'; test -n "$(tile_small_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(tile_small_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(tile_small_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(tile_small_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(tile_small_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-tile_small_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(tile_small_libexec_PROGRAMS)'; test -n "$(tile_small_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(tile_small_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(tile_small_libexecdir)" && rm -f $$files + +clean-tile_small_libexecPROGRAMS: + @list='$(tile_small_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-unit_editor_libexecPROGRAMS: $(unit_editor_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(unit_editor_libexec_PROGRAMS)'; test -n "$(unit_editor_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(unit_editor_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(unit_editor_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(unit_editor_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(unit_editor_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-unit_editor_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(unit_editor_libexec_PROGRAMS)'; test -n "$(unit_editor_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(unit_editor_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(unit_editor_libexecdir)" && rm -f $$files + +clean-unit_editor_libexecPROGRAMS: + @list='$(unit_editor_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-van_gogh_lic_libexecPROGRAMS: $(van_gogh_lic_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(van_gogh_lic_libexec_PROGRAMS)'; test -n "$(van_gogh_lic_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(van_gogh_lic_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(van_gogh_lic_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(van_gogh_lic_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(van_gogh_lic_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-van_gogh_lic_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(van_gogh_lic_libexec_PROGRAMS)'; test -n "$(van_gogh_lic_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(van_gogh_lic_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(van_gogh_lic_libexecdir)" && rm -f $$files + +clean-van_gogh_lic_libexecPROGRAMS: + @list='$(van_gogh_lic_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-warp_libexecPROGRAMS: $(warp_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(warp_libexec_PROGRAMS)'; test -n "$(warp_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(warp_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(warp_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(warp_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(warp_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-warp_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(warp_libexec_PROGRAMS)'; test -n "$(warp_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(warp_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(warp_libexecdir)" && rm -f $$files + +clean-warp_libexecPROGRAMS: + @list='$(warp_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-wavelet_decompose_libexecPROGRAMS: $(wavelet_decompose_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(wavelet_decompose_libexec_PROGRAMS)'; test -n "$(wavelet_decompose_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(wavelet_decompose_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(wavelet_decompose_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(wavelet_decompose_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(wavelet_decompose_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-wavelet_decompose_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(wavelet_decompose_libexec_PROGRAMS)'; test -n "$(wavelet_decompose_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(wavelet_decompose_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(wavelet_decompose_libexecdir)" && rm -f $$files + +clean-wavelet_decompose_libexecPROGRAMS: + @list='$(wavelet_decompose_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-web_browser_libexecPROGRAMS: $(web_browser_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(web_browser_libexec_PROGRAMS)'; test -n "$(web_browser_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(web_browser_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(web_browser_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(web_browser_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(web_browser_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-web_browser_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(web_browser_libexec_PROGRAMS)'; test -n "$(web_browser_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(web_browser_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(web_browser_libexecdir)" && rm -f $$files + +clean-web_browser_libexecPROGRAMS: + @list='$(web_browser_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-web_page_libexecPROGRAMS: $(web_page_libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(web_page_libexec_PROGRAMS)'; test -n "$(web_page_libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(web_page_libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(web_page_libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(web_page_libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(web_page_libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-web_page_libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(web_page_libexec_PROGRAMS)'; test -n "$(web_page_libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(web_page_libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(web_page_libexecdir)" && rm -f $$files + +clean-web_page_libexecPROGRAMS: + @list='$(web_page_libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +align-layers$(EXEEXT): $(align_layers_OBJECTS) $(align_layers_DEPENDENCIES) $(EXTRA_align_layers_DEPENDENCIES) + @rm -f align-layers$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(align_layers_OBJECTS) $(align_layers_LDADD) $(LIBS) + +animation-optimize$(EXEEXT): $(animation_optimize_OBJECTS) $(animation_optimize_DEPENDENCIES) $(EXTRA_animation_optimize_DEPENDENCIES) + @rm -f animation-optimize$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(animation_optimize_OBJECTS) $(animation_optimize_LDADD) $(LIBS) + +animation-play$(EXEEXT): $(animation_play_OBJECTS) $(animation_play_DEPENDENCIES) $(EXTRA_animation_play_DEPENDENCIES) + @rm -f animation-play$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(animation_play_OBJECTS) $(animation_play_LDADD) $(LIBS) + +blinds$(EXEEXT): $(blinds_OBJECTS) $(blinds_DEPENDENCIES) $(EXTRA_blinds_DEPENDENCIES) + @rm -f blinds$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(blinds_OBJECTS) $(blinds_LDADD) $(LIBS) + +blur$(EXEEXT): $(blur_OBJECTS) $(blur_DEPENDENCIES) $(EXTRA_blur_DEPENDENCIES) + @rm -f blur$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(blur_OBJECTS) $(blur_LDADD) $(LIBS) + +border-average$(EXEEXT): $(border_average_OBJECTS) $(border_average_DEPENDENCIES) $(EXTRA_border_average_DEPENDENCIES) + @rm -f border-average$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(border_average_OBJECTS) $(border_average_LDADD) $(LIBS) + +busy-dialog$(EXEEXT): $(busy_dialog_OBJECTS) $(busy_dialog_DEPENDENCIES) $(EXTRA_busy_dialog_DEPENDENCIES) + @rm -f busy-dialog$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(busy_dialog_OBJECTS) $(busy_dialog_LDADD) $(LIBS) + +cartoon$(EXEEXT): $(cartoon_OBJECTS) $(cartoon_DEPENDENCIES) $(EXTRA_cartoon_DEPENDENCIES) + @rm -f cartoon$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(cartoon_OBJECTS) $(cartoon_LDADD) $(LIBS) + +checkerboard$(EXEEXT): $(checkerboard_OBJECTS) $(checkerboard_DEPENDENCIES) $(EXTRA_checkerboard_DEPENDENCIES) + @rm -f checkerboard$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(checkerboard_OBJECTS) $(checkerboard_LDADD) $(LIBS) + +cml-explorer$(EXEEXT): $(cml_explorer_OBJECTS) $(cml_explorer_DEPENDENCIES) $(EXTRA_cml_explorer_DEPENDENCIES) + @rm -f cml-explorer$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(cml_explorer_OBJECTS) $(cml_explorer_LDADD) $(LIBS) + +color-cube-analyze$(EXEEXT): $(color_cube_analyze_OBJECTS) $(color_cube_analyze_DEPENDENCIES) $(EXTRA_color_cube_analyze_DEPENDENCIES) + @rm -f color-cube-analyze$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(color_cube_analyze_OBJECTS) $(color_cube_analyze_LDADD) $(LIBS) + +color-enhance$(EXEEXT): $(color_enhance_OBJECTS) $(color_enhance_DEPENDENCIES) $(EXTRA_color_enhance_DEPENDENCIES) + @rm -f color-enhance$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(color_enhance_OBJECTS) $(color_enhance_LDADD) $(LIBS) + +colorify$(EXEEXT): $(colorify_OBJECTS) $(colorify_DEPENDENCIES) $(EXTRA_colorify_DEPENDENCIES) + @rm -f colorify$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(colorify_OBJECTS) $(colorify_LDADD) $(LIBS) + +colormap-remap$(EXEEXT): $(colormap_remap_OBJECTS) $(colormap_remap_DEPENDENCIES) $(EXTRA_colormap_remap_DEPENDENCIES) + @rm -f colormap-remap$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(colormap_remap_OBJECTS) $(colormap_remap_LDADD) $(LIBS) + +compose$(EXEEXT): $(compose_OBJECTS) $(compose_DEPENDENCIES) $(EXTRA_compose_DEPENDENCIES) + @rm -f compose$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(compose_OBJECTS) $(compose_LDADD) $(LIBS) + +contrast-retinex$(EXEEXT): $(contrast_retinex_OBJECTS) $(contrast_retinex_DEPENDENCIES) $(EXTRA_contrast_retinex_DEPENDENCIES) + @rm -f contrast-retinex$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(contrast_retinex_OBJECTS) $(contrast_retinex_LDADD) $(LIBS) + +crop-zealous$(EXEEXT): $(crop_zealous_OBJECTS) $(crop_zealous_DEPENDENCIES) $(EXTRA_crop_zealous_DEPENDENCIES) + @rm -f crop-zealous$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(crop_zealous_OBJECTS) $(crop_zealous_LDADD) $(LIBS) + +curve-bend$(EXEEXT): $(curve_bend_OBJECTS) $(curve_bend_DEPENDENCIES) $(EXTRA_curve_bend_DEPENDENCIES) + @rm -f curve-bend$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(curve_bend_OBJECTS) $(curve_bend_LDADD) $(LIBS) + +decompose$(EXEEXT): $(decompose_OBJECTS) $(decompose_DEPENDENCIES) $(EXTRA_decompose_DEPENDENCIES) + @rm -f decompose$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(decompose_OBJECTS) $(decompose_LDADD) $(LIBS) + +depth-merge$(EXEEXT): $(depth_merge_OBJECTS) $(depth_merge_DEPENDENCIES) $(EXTRA_depth_merge_DEPENDENCIES) + @rm -f depth-merge$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(depth_merge_OBJECTS) $(depth_merge_LDADD) $(LIBS) + +despeckle$(EXEEXT): $(despeckle_OBJECTS) $(despeckle_DEPENDENCIES) $(EXTRA_despeckle_DEPENDENCIES) + @rm -f despeckle$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(despeckle_OBJECTS) $(despeckle_LDADD) $(LIBS) + +destripe$(EXEEXT): $(destripe_OBJECTS) $(destripe_DEPENDENCIES) $(EXTRA_destripe_DEPENDENCIES) + @rm -f destripe$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(destripe_OBJECTS) $(destripe_LDADD) $(LIBS) + +edge-dog$(EXEEXT): $(edge_dog_OBJECTS) $(edge_dog_DEPENDENCIES) $(EXTRA_edge_dog_DEPENDENCIES) + @rm -f edge-dog$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(edge_dog_OBJECTS) $(edge_dog_LDADD) $(LIBS) + +emboss$(EXEEXT): $(emboss_OBJECTS) $(emboss_DEPENDENCIES) $(EXTRA_emboss_DEPENDENCIES) + @rm -f emboss$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(emboss_OBJECTS) $(emboss_LDADD) $(LIBS) + +file-aa$(EXEEXT): $(file_aa_OBJECTS) $(file_aa_DEPENDENCIES) $(EXTRA_file_aa_DEPENDENCIES) + @rm -f file-aa$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_aa_OBJECTS) $(file_aa_LDADD) $(LIBS) + +file-cel$(EXEEXT): $(file_cel_OBJECTS) $(file_cel_DEPENDENCIES) $(EXTRA_file_cel_DEPENDENCIES) + @rm -f file-cel$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_cel_OBJECTS) $(file_cel_LDADD) $(LIBS) + +file-compressor$(EXEEXT): $(file_compressor_OBJECTS) $(file_compressor_DEPENDENCIES) $(EXTRA_file_compressor_DEPENDENCIES) + @rm -f file-compressor$(EXEEXT) + $(AM_V_CCLD)$(file_compressor_LINK) $(file_compressor_OBJECTS) $(file_compressor_LDADD) $(LIBS) + +file-csource$(EXEEXT): $(file_csource_OBJECTS) $(file_csource_DEPENDENCIES) $(EXTRA_file_csource_DEPENDENCIES) + @rm -f file-csource$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_csource_OBJECTS) $(file_csource_LDADD) $(LIBS) + +file-desktop-link$(EXEEXT): $(file_desktop_link_OBJECTS) $(file_desktop_link_DEPENDENCIES) $(EXTRA_file_desktop_link_DEPENDENCIES) + @rm -f file-desktop-link$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_desktop_link_OBJECTS) $(file_desktop_link_LDADD) $(LIBS) + +file-dicom$(EXEEXT): $(file_dicom_OBJECTS) $(file_dicom_DEPENDENCIES) $(EXTRA_file_dicom_DEPENDENCIES) + @rm -f file-dicom$(EXEEXT) + $(AM_V_CCLD)$(file_dicom_LINK) $(file_dicom_OBJECTS) $(file_dicom_LDADD) $(LIBS) + +file-gbr$(EXEEXT): $(file_gbr_OBJECTS) $(file_gbr_DEPENDENCIES) $(EXTRA_file_gbr_DEPENDENCIES) + @rm -f file-gbr$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_gbr_OBJECTS) $(file_gbr_LDADD) $(LIBS) + +file-gegl$(EXEEXT): $(file_gegl_OBJECTS) $(file_gegl_DEPENDENCIES) $(EXTRA_file_gegl_DEPENDENCIES) + @rm -f file-gegl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_gegl_OBJECTS) $(file_gegl_LDADD) $(LIBS) + +file-gif-load$(EXEEXT): $(file_gif_load_OBJECTS) $(file_gif_load_DEPENDENCIES) $(EXTRA_file_gif_load_DEPENDENCIES) + @rm -f file-gif-load$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_gif_load_OBJECTS) $(file_gif_load_LDADD) $(LIBS) + +file-gif-save$(EXEEXT): $(file_gif_save_OBJECTS) $(file_gif_save_DEPENDENCIES) $(EXTRA_file_gif_save_DEPENDENCIES) + @rm -f file-gif-save$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_gif_save_OBJECTS) $(file_gif_save_LDADD) $(LIBS) + +file-gih$(EXEEXT): $(file_gih_OBJECTS) $(file_gih_DEPENDENCIES) $(EXTRA_file_gih_DEPENDENCIES) + @rm -f file-gih$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_gih_OBJECTS) $(file_gih_LDADD) $(LIBS) + +file-glob$(EXEEXT): $(file_glob_OBJECTS) $(file_glob_DEPENDENCIES) $(EXTRA_file_glob_DEPENDENCIES) + @rm -f file-glob$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_glob_OBJECTS) $(file_glob_LDADD) $(LIBS) + +file-header$(EXEEXT): $(file_header_OBJECTS) $(file_header_DEPENDENCIES) $(EXTRA_file_header_DEPENDENCIES) + @rm -f file-header$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_header_OBJECTS) $(file_header_LDADD) $(LIBS) + +file-heif$(EXEEXT): $(file_heif_OBJECTS) $(file_heif_DEPENDENCIES) $(EXTRA_file_heif_DEPENDENCIES) + @rm -f file-heif$(EXEEXT) + $(AM_V_CCLD)$(file_heif_LINK) $(file_heif_OBJECTS) $(file_heif_LDADD) $(LIBS) + +file-html-table$(EXEEXT): $(file_html_table_OBJECTS) $(file_html_table_DEPENDENCIES) $(EXTRA_file_html_table_DEPENDENCIES) + @rm -f file-html-table$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_html_table_OBJECTS) $(file_html_table_LDADD) $(LIBS) + +file-jp2-load$(EXEEXT): $(file_jp2_load_OBJECTS) $(file_jp2_load_DEPENDENCIES) $(EXTRA_file_jp2_load_DEPENDENCIES) + @rm -f file-jp2-load$(EXEEXT) + $(AM_V_CCLD)$(file_jp2_load_LINK) $(file_jp2_load_OBJECTS) $(file_jp2_load_LDADD) $(LIBS) + +file-mng$(EXEEXT): $(file_mng_OBJECTS) $(file_mng_DEPENDENCIES) $(EXTRA_file_mng_DEPENDENCIES) + @rm -f file-mng$(EXEEXT) + $(AM_V_CCLD)$(file_mng_LINK) $(file_mng_OBJECTS) $(file_mng_LDADD) $(LIBS) + +file-pat$(EXEEXT): $(file_pat_OBJECTS) $(file_pat_DEPENDENCIES) $(EXTRA_file_pat_DEPENDENCIES) + @rm -f file-pat$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_pat_OBJECTS) $(file_pat_LDADD) $(LIBS) + +file-pcx$(EXEEXT): $(file_pcx_OBJECTS) $(file_pcx_DEPENDENCIES) $(EXTRA_file_pcx_DEPENDENCIES) + @rm -f file-pcx$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_pcx_OBJECTS) $(file_pcx_LDADD) $(LIBS) + +file-pdf-load$(EXEEXT): $(file_pdf_load_OBJECTS) $(file_pdf_load_DEPENDENCIES) $(EXTRA_file_pdf_load_DEPENDENCIES) + @rm -f file-pdf-load$(EXEEXT) + $(AM_V_CCLD)$(file_pdf_load_LINK) $(file_pdf_load_OBJECTS) $(file_pdf_load_LDADD) $(LIBS) + +file-pdf-save$(EXEEXT): $(file_pdf_save_OBJECTS) $(file_pdf_save_DEPENDENCIES) $(EXTRA_file_pdf_save_DEPENDENCIES) + @rm -f file-pdf-save$(EXEEXT) + $(AM_V_CCLD)$(file_pdf_save_LINK) $(file_pdf_save_OBJECTS) $(file_pdf_save_LDADD) $(LIBS) + +file-pix$(EXEEXT): $(file_pix_OBJECTS) $(file_pix_DEPENDENCIES) $(EXTRA_file_pix_DEPENDENCIES) + @rm -f file-pix$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_pix_OBJECTS) $(file_pix_LDADD) $(LIBS) + +file-png$(EXEEXT): $(file_png_OBJECTS) $(file_png_DEPENDENCIES) $(EXTRA_file_png_DEPENDENCIES) + @rm -f file-png$(EXEEXT) + $(AM_V_CCLD)$(file_png_LINK) $(file_png_OBJECTS) $(file_png_LDADD) $(LIBS) + +file-pnm$(EXEEXT): $(file_pnm_OBJECTS) $(file_pnm_DEPENDENCIES) $(EXTRA_file_pnm_DEPENDENCIES) + @rm -f file-pnm$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_pnm_OBJECTS) $(file_pnm_LDADD) $(LIBS) + +file-ps$(EXEEXT): $(file_ps_OBJECTS) $(file_ps_DEPENDENCIES) $(EXTRA_file_ps_DEPENDENCIES) + @rm -f file-ps$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_ps_OBJECTS) $(file_ps_LDADD) $(LIBS) + +file-psp$(EXEEXT): $(file_psp_OBJECTS) $(file_psp_DEPENDENCIES) $(EXTRA_file_psp_DEPENDENCIES) + @rm -f file-psp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_psp_OBJECTS) $(file_psp_LDADD) $(LIBS) + +file-raw-data$(EXEEXT): $(file_raw_data_OBJECTS) $(file_raw_data_DEPENDENCIES) $(EXTRA_file_raw_data_DEPENDENCIES) + @rm -f file-raw-data$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_raw_data_OBJECTS) $(file_raw_data_LDADD) $(LIBS) + +file-sunras$(EXEEXT): $(file_sunras_OBJECTS) $(file_sunras_DEPENDENCIES) $(EXTRA_file_sunras_DEPENDENCIES) + @rm -f file-sunras$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_sunras_OBJECTS) $(file_sunras_LDADD) $(LIBS) + +file-svg$(EXEEXT): $(file_svg_OBJECTS) $(file_svg_DEPENDENCIES) $(EXTRA_file_svg_DEPENDENCIES) + @rm -f file-svg$(EXEEXT) + $(AM_V_CCLD)$(file_svg_LINK) $(file_svg_OBJECTS) $(file_svg_LDADD) $(LIBS) + +file-tga$(EXEEXT): $(file_tga_OBJECTS) $(file_tga_DEPENDENCIES) $(EXTRA_file_tga_DEPENDENCIES) + @rm -f file-tga$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_tga_OBJECTS) $(file_tga_LDADD) $(LIBS) + +file-wmf$(EXEEXT): $(file_wmf_OBJECTS) $(file_wmf_DEPENDENCIES) $(EXTRA_file_wmf_DEPENDENCIES) + @rm -f file-wmf$(EXEEXT) + $(AM_V_CCLD)$(file_wmf_LINK) $(file_wmf_OBJECTS) $(file_wmf_LDADD) $(LIBS) + +file-xbm$(EXEEXT): $(file_xbm_OBJECTS) $(file_xbm_DEPENDENCIES) $(EXTRA_file_xbm_DEPENDENCIES) + @rm -f file-xbm$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_xbm_OBJECTS) $(file_xbm_LDADD) $(LIBS) + +file-xmc$(EXEEXT): $(file_xmc_OBJECTS) $(file_xmc_DEPENDENCIES) $(EXTRA_file_xmc_DEPENDENCIES) + @rm -f file-xmc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_xmc_OBJECTS) $(file_xmc_LDADD) $(LIBS) + +file-xpm$(EXEEXT): $(file_xpm_OBJECTS) $(file_xpm_DEPENDENCIES) $(EXTRA_file_xpm_DEPENDENCIES) + @rm -f file-xpm$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_xpm_OBJECTS) $(file_xpm_LDADD) $(LIBS) + +file-xwd$(EXEEXT): $(file_xwd_OBJECTS) $(file_xwd_DEPENDENCIES) $(EXTRA_file_xwd_DEPENDENCIES) + @rm -f file-xwd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_xwd_OBJECTS) $(file_xwd_LDADD) $(LIBS) + +film$(EXEEXT): $(film_OBJECTS) $(film_DEPENDENCIES) $(EXTRA_film_DEPENDENCIES) + @rm -f film$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(film_OBJECTS) $(film_LDADD) $(LIBS) + +filter-pack$(EXEEXT): $(filter_pack_OBJECTS) $(filter_pack_DEPENDENCIES) $(EXTRA_filter_pack_DEPENDENCIES) + @rm -f filter-pack$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(filter_pack_OBJECTS) $(filter_pack_LDADD) $(LIBS) + +fractal-trace$(EXEEXT): $(fractal_trace_OBJECTS) $(fractal_trace_DEPENDENCIES) $(EXTRA_fractal_trace_DEPENDENCIES) + @rm -f fractal-trace$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(fractal_trace_OBJECTS) $(fractal_trace_LDADD) $(LIBS) + +goat-exercise$(EXEEXT): $(goat_exercise_OBJECTS) $(goat_exercise_DEPENDENCIES) $(EXTRA_goat_exercise_DEPENDENCIES) + @rm -f goat-exercise$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(goat_exercise_OBJECTS) $(goat_exercise_LDADD) $(LIBS) + +gradient-map$(EXEEXT): $(gradient_map_OBJECTS) $(gradient_map_DEPENDENCIES) $(EXTRA_gradient_map_DEPENDENCIES) + @rm -f gradient-map$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(gradient_map_OBJECTS) $(gradient_map_LDADD) $(LIBS) + +grid$(EXEEXT): $(grid_OBJECTS) $(grid_DEPENDENCIES) $(EXTRA_grid_DEPENDENCIES) + @rm -f grid$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(grid_OBJECTS) $(grid_LDADD) $(LIBS) + +guillotine$(EXEEXT): $(guillotine_OBJECTS) $(guillotine_DEPENDENCIES) $(EXTRA_guillotine_DEPENDENCIES) + @rm -f guillotine$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(guillotine_OBJECTS) $(guillotine_LDADD) $(LIBS) + +hot$(EXEEXT): $(hot_OBJECTS) $(hot_DEPENDENCIES) $(EXTRA_hot_DEPENDENCIES) + @rm -f hot$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(hot_OBJECTS) $(hot_LDADD) $(LIBS) + +jigsaw$(EXEEXT): $(jigsaw_OBJECTS) $(jigsaw_DEPENDENCIES) $(EXTRA_jigsaw_DEPENDENCIES) + @rm -f jigsaw$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(jigsaw_OBJECTS) $(jigsaw_LDADD) $(LIBS) + +mail$(EXEEXT): $(mail_OBJECTS) $(mail_DEPENDENCIES) $(EXTRA_mail_DEPENDENCIES) + @rm -f mail$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(mail_OBJECTS) $(mail_LDADD) $(LIBS) + +max-rgb$(EXEEXT): $(max_rgb_OBJECTS) $(max_rgb_DEPENDENCIES) $(EXTRA_max_rgb_DEPENDENCIES) + @rm -f max-rgb$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(max_rgb_OBJECTS) $(max_rgb_LDADD) $(LIBS) + +nl-filter$(EXEEXT): $(nl_filter_OBJECTS) $(nl_filter_DEPENDENCIES) $(EXTRA_nl_filter_DEPENDENCIES) + @rm -f nl-filter$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(nl_filter_OBJECTS) $(nl_filter_LDADD) $(LIBS) + +photocopy$(EXEEXT): $(photocopy_OBJECTS) $(photocopy_DEPENDENCIES) $(EXTRA_photocopy_DEPENDENCIES) + @rm -f photocopy$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(photocopy_OBJECTS) $(photocopy_LDADD) $(LIBS) + +plugin-browser$(EXEEXT): $(plugin_browser_OBJECTS) $(plugin_browser_DEPENDENCIES) $(EXTRA_plugin_browser_DEPENDENCIES) + @rm -f plugin-browser$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(plugin_browser_OBJECTS) $(plugin_browser_LDADD) $(LIBS) + +procedure-browser$(EXEEXT): $(procedure_browser_OBJECTS) $(procedure_browser_DEPENDENCIES) $(EXTRA_procedure_browser_DEPENDENCIES) + @rm -f procedure-browser$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(procedure_browser_OBJECTS) $(procedure_browser_LDADD) $(LIBS) + +qbist$(EXEEXT): $(qbist_OBJECTS) $(qbist_DEPENDENCIES) $(EXTRA_qbist_DEPENDENCIES) + @rm -f qbist$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(qbist_OBJECTS) $(qbist_LDADD) $(LIBS) + +sample-colorize$(EXEEXT): $(sample_colorize_OBJECTS) $(sample_colorize_DEPENDENCIES) $(EXTRA_sample_colorize_DEPENDENCIES) + @rm -f sample-colorize$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sample_colorize_OBJECTS) $(sample_colorize_LDADD) $(LIBS) + +sharpen$(EXEEXT): $(sharpen_OBJECTS) $(sharpen_DEPENDENCIES) $(EXTRA_sharpen_DEPENDENCIES) + @rm -f sharpen$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sharpen_OBJECTS) $(sharpen_LDADD) $(LIBS) + +smooth-palette$(EXEEXT): $(smooth_palette_OBJECTS) $(smooth_palette_DEPENDENCIES) $(EXTRA_smooth_palette_DEPENDENCIES) + @rm -f smooth-palette$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(smooth_palette_OBJECTS) $(smooth_palette_LDADD) $(LIBS) + +softglow$(EXEEXT): $(softglow_OBJECTS) $(softglow_DEPENDENCIES) $(EXTRA_softglow_DEPENDENCIES) + @rm -f softglow$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(softglow_OBJECTS) $(softglow_LDADD) $(LIBS) + +sparkle$(EXEEXT): $(sparkle_OBJECTS) $(sparkle_DEPENDENCIES) $(EXTRA_sparkle_DEPENDENCIES) + @rm -f sparkle$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sparkle_OBJECTS) $(sparkle_LDADD) $(LIBS) + +sphere-designer$(EXEEXT): $(sphere_designer_OBJECTS) $(sphere_designer_DEPENDENCIES) $(EXTRA_sphere_designer_DEPENDENCIES) + @rm -f sphere-designer$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sphere_designer_OBJECTS) $(sphere_designer_LDADD) $(LIBS) + +tile$(EXEEXT): $(tile_OBJECTS) $(tile_DEPENDENCIES) $(EXTRA_tile_DEPENDENCIES) + @rm -f tile$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tile_OBJECTS) $(tile_LDADD) $(LIBS) + +tile-small$(EXEEXT): $(tile_small_OBJECTS) $(tile_small_DEPENDENCIES) $(EXTRA_tile_small_DEPENDENCIES) + @rm -f tile-small$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tile_small_OBJECTS) $(tile_small_LDADD) $(LIBS) + +unit-editor$(EXEEXT): $(unit_editor_OBJECTS) $(unit_editor_DEPENDENCIES) $(EXTRA_unit_editor_DEPENDENCIES) + @rm -f unit-editor$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_editor_OBJECTS) $(unit_editor_LDADD) $(LIBS) + +van-gogh-lic$(EXEEXT): $(van_gogh_lic_OBJECTS) $(van_gogh_lic_DEPENDENCIES) $(EXTRA_van_gogh_lic_DEPENDENCIES) + @rm -f van-gogh-lic$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(van_gogh_lic_OBJECTS) $(van_gogh_lic_LDADD) $(LIBS) + +warp$(EXEEXT): $(warp_OBJECTS) $(warp_DEPENDENCIES) $(EXTRA_warp_DEPENDENCIES) + @rm -f warp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(warp_OBJECTS) $(warp_LDADD) $(LIBS) + +wavelet-decompose$(EXEEXT): $(wavelet_decompose_OBJECTS) $(wavelet_decompose_DEPENDENCIES) $(EXTRA_wavelet_decompose_DEPENDENCIES) + @rm -f wavelet-decompose$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(wavelet_decompose_OBJECTS) $(wavelet_decompose_LDADD) $(LIBS) + +web-browser$(EXEEXT): $(web_browser_OBJECTS) $(web_browser_DEPENDENCIES) $(EXTRA_web_browser_DEPENDENCIES) + @rm -f web-browser$(EXEEXT) + $(AM_V_CCLD)$(web_browser_LINK) $(web_browser_OBJECTS) $(web_browser_LDADD) $(LIBS) + +web-page$(EXEEXT): $(web_page_OBJECTS) $(web_page_DEPENDENCIES) $(EXTRA_web_page_DEPENDENCIES) + @rm -f web-page$(EXEEXT) + $(AM_V_CCLD)$(web_page_LINK) $(web_page_OBJECTS) $(web_page_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/align-layers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/animation-optimize.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/animation-play.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blinds.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blur.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/border-average.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/busy-dialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cartoon.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/checkerboard.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cml-explorer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color-cube-analyze.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color-enhance.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colorify.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colormap-remap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compose.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/contrast-retinex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crop-zealous.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/curve-bend.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/decompose.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/depth-merge.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/despeckle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/destripe.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edge-dog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/emboss.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-aa.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-cel.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-csource.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-desktop-link.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-gbr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-gegl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-gif-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-gif-save.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-gih.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-glob.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-header.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-html-table.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-pat.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-pcx.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-pix.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-pnm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-ps.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-psp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-raw-data.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-sunras.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-tga.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-xbm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-xmc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-xpm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-xwd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_compressor-file-compressor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_dicom-file-dicom.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_heif-file-heif.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_jp2_load-file-jp2-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_mng-file-mng.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_pdf_load-file-pdf-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_pdf_save-file-pdf-save.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_png-file-png.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_svg-file-svg.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_wmf-file-wmf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/film.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filter-pack.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fractal-trace.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/goat-exercise.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gradient-map.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/guillotine.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hot.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jigsaw.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/max-rgb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nl-filter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/photocopy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plugin-browser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procedure-browser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/qbist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sample-colorize.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sharpen.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smooth-palette.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/softglow.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sparkle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sphere-designer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tile-small.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unit-editor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/van-gogh-lic.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/warp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wavelet-decompose.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_browser-web-browser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_page-web-page.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +file_compressor-file-compressor.o: file-compressor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_compressor_CFLAGS) $(CFLAGS) -MT file_compressor-file-compressor.o -MD -MP -MF $(DEPDIR)/file_compressor-file-compressor.Tpo -c -o file_compressor-file-compressor.o `test -f 'file-compressor.c' || echo '$(srcdir)/'`file-compressor.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_compressor-file-compressor.Tpo $(DEPDIR)/file_compressor-file-compressor.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-compressor.c' object='file_compressor-file-compressor.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_compressor_CFLAGS) $(CFLAGS) -c -o file_compressor-file-compressor.o `test -f 'file-compressor.c' || echo '$(srcdir)/'`file-compressor.c + +file_compressor-file-compressor.obj: file-compressor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_compressor_CFLAGS) $(CFLAGS) -MT file_compressor-file-compressor.obj -MD -MP -MF $(DEPDIR)/file_compressor-file-compressor.Tpo -c -o file_compressor-file-compressor.obj `if test -f 'file-compressor.c'; then $(CYGPATH_W) 'file-compressor.c'; else $(CYGPATH_W) '$(srcdir)/file-compressor.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_compressor-file-compressor.Tpo $(DEPDIR)/file_compressor-file-compressor.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-compressor.c' object='file_compressor-file-compressor.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_compressor_CFLAGS) $(CFLAGS) -c -o file_compressor-file-compressor.obj `if test -f 'file-compressor.c'; then $(CYGPATH_W) 'file-compressor.c'; else $(CYGPATH_W) '$(srcdir)/file-compressor.c'; fi` + +file_dicom-file-dicom.o: file-dicom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_dicom_CFLAGS) $(CFLAGS) -MT file_dicom-file-dicom.o -MD -MP -MF $(DEPDIR)/file_dicom-file-dicom.Tpo -c -o file_dicom-file-dicom.o `test -f 'file-dicom.c' || echo '$(srcdir)/'`file-dicom.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_dicom-file-dicom.Tpo $(DEPDIR)/file_dicom-file-dicom.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-dicom.c' object='file_dicom-file-dicom.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_dicom_CFLAGS) $(CFLAGS) -c -o file_dicom-file-dicom.o `test -f 'file-dicom.c' || echo '$(srcdir)/'`file-dicom.c + +file_dicom-file-dicom.obj: file-dicom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_dicom_CFLAGS) $(CFLAGS) -MT file_dicom-file-dicom.obj -MD -MP -MF $(DEPDIR)/file_dicom-file-dicom.Tpo -c -o file_dicom-file-dicom.obj `if test -f 'file-dicom.c'; then $(CYGPATH_W) 'file-dicom.c'; else $(CYGPATH_W) '$(srcdir)/file-dicom.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_dicom-file-dicom.Tpo $(DEPDIR)/file_dicom-file-dicom.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-dicom.c' object='file_dicom-file-dicom.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_dicom_CFLAGS) $(CFLAGS) -c -o file_dicom-file-dicom.obj `if test -f 'file-dicom.c'; then $(CYGPATH_W) 'file-dicom.c'; else $(CYGPATH_W) '$(srcdir)/file-dicom.c'; fi` + +file_heif-file-heif.o: file-heif.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_heif_CFLAGS) $(CFLAGS) -MT file_heif-file-heif.o -MD -MP -MF $(DEPDIR)/file_heif-file-heif.Tpo -c -o file_heif-file-heif.o `test -f 'file-heif.c' || echo '$(srcdir)/'`file-heif.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_heif-file-heif.Tpo $(DEPDIR)/file_heif-file-heif.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-heif.c' object='file_heif-file-heif.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_heif_CFLAGS) $(CFLAGS) -c -o file_heif-file-heif.o `test -f 'file-heif.c' || echo '$(srcdir)/'`file-heif.c + +file_heif-file-heif.obj: file-heif.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_heif_CFLAGS) $(CFLAGS) -MT file_heif-file-heif.obj -MD -MP -MF $(DEPDIR)/file_heif-file-heif.Tpo -c -o file_heif-file-heif.obj `if test -f 'file-heif.c'; then $(CYGPATH_W) 'file-heif.c'; else $(CYGPATH_W) '$(srcdir)/file-heif.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_heif-file-heif.Tpo $(DEPDIR)/file_heif-file-heif.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-heif.c' object='file_heif-file-heif.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_heif_CFLAGS) $(CFLAGS) -c -o file_heif-file-heif.obj `if test -f 'file-heif.c'; then $(CYGPATH_W) 'file-heif.c'; else $(CYGPATH_W) '$(srcdir)/file-heif.c'; fi` + +file_jp2_load-file-jp2-load.o: file-jp2-load.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_jp2_load_CFLAGS) $(CFLAGS) -MT file_jp2_load-file-jp2-load.o -MD -MP -MF $(DEPDIR)/file_jp2_load-file-jp2-load.Tpo -c -o file_jp2_load-file-jp2-load.o `test -f 'file-jp2-load.c' || echo '$(srcdir)/'`file-jp2-load.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_jp2_load-file-jp2-load.Tpo $(DEPDIR)/file_jp2_load-file-jp2-load.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-jp2-load.c' object='file_jp2_load-file-jp2-load.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_jp2_load_CFLAGS) $(CFLAGS) -c -o file_jp2_load-file-jp2-load.o `test -f 'file-jp2-load.c' || echo '$(srcdir)/'`file-jp2-load.c + +file_jp2_load-file-jp2-load.obj: file-jp2-load.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_jp2_load_CFLAGS) $(CFLAGS) -MT file_jp2_load-file-jp2-load.obj -MD -MP -MF $(DEPDIR)/file_jp2_load-file-jp2-load.Tpo -c -o file_jp2_load-file-jp2-load.obj `if test -f 'file-jp2-load.c'; then $(CYGPATH_W) 'file-jp2-load.c'; else $(CYGPATH_W) '$(srcdir)/file-jp2-load.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_jp2_load-file-jp2-load.Tpo $(DEPDIR)/file_jp2_load-file-jp2-load.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-jp2-load.c' object='file_jp2_load-file-jp2-load.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_jp2_load_CFLAGS) $(CFLAGS) -c -o file_jp2_load-file-jp2-load.obj `if test -f 'file-jp2-load.c'; then $(CYGPATH_W) 'file-jp2-load.c'; else $(CYGPATH_W) '$(srcdir)/file-jp2-load.c'; fi` + +file_mng-file-mng.o: file-mng.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_mng_CFLAGS) $(CFLAGS) -MT file_mng-file-mng.o -MD -MP -MF $(DEPDIR)/file_mng-file-mng.Tpo -c -o file_mng-file-mng.o `test -f 'file-mng.c' || echo '$(srcdir)/'`file-mng.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_mng-file-mng.Tpo $(DEPDIR)/file_mng-file-mng.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-mng.c' object='file_mng-file-mng.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_mng_CFLAGS) $(CFLAGS) -c -o file_mng-file-mng.o `test -f 'file-mng.c' || echo '$(srcdir)/'`file-mng.c + +file_mng-file-mng.obj: file-mng.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_mng_CFLAGS) $(CFLAGS) -MT file_mng-file-mng.obj -MD -MP -MF $(DEPDIR)/file_mng-file-mng.Tpo -c -o file_mng-file-mng.obj `if test -f 'file-mng.c'; then $(CYGPATH_W) 'file-mng.c'; else $(CYGPATH_W) '$(srcdir)/file-mng.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_mng-file-mng.Tpo $(DEPDIR)/file_mng-file-mng.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-mng.c' object='file_mng-file-mng.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_mng_CFLAGS) $(CFLAGS) -c -o file_mng-file-mng.obj `if test -f 'file-mng.c'; then $(CYGPATH_W) 'file-mng.c'; else $(CYGPATH_W) '$(srcdir)/file-mng.c'; fi` + +file_pdf_load-file-pdf-load.o: file-pdf-load.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_load_CFLAGS) $(CFLAGS) -MT file_pdf_load-file-pdf-load.o -MD -MP -MF $(DEPDIR)/file_pdf_load-file-pdf-load.Tpo -c -o file_pdf_load-file-pdf-load.o `test -f 'file-pdf-load.c' || echo '$(srcdir)/'`file-pdf-load.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_pdf_load-file-pdf-load.Tpo $(DEPDIR)/file_pdf_load-file-pdf-load.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-pdf-load.c' object='file_pdf_load-file-pdf-load.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_load_CFLAGS) $(CFLAGS) -c -o file_pdf_load-file-pdf-load.o `test -f 'file-pdf-load.c' || echo '$(srcdir)/'`file-pdf-load.c + +file_pdf_load-file-pdf-load.obj: file-pdf-load.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_load_CFLAGS) $(CFLAGS) -MT file_pdf_load-file-pdf-load.obj -MD -MP -MF $(DEPDIR)/file_pdf_load-file-pdf-load.Tpo -c -o file_pdf_load-file-pdf-load.obj `if test -f 'file-pdf-load.c'; then $(CYGPATH_W) 'file-pdf-load.c'; else $(CYGPATH_W) '$(srcdir)/file-pdf-load.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_pdf_load-file-pdf-load.Tpo $(DEPDIR)/file_pdf_load-file-pdf-load.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-pdf-load.c' object='file_pdf_load-file-pdf-load.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_load_CFLAGS) $(CFLAGS) -c -o file_pdf_load-file-pdf-load.obj `if test -f 'file-pdf-load.c'; then $(CYGPATH_W) 'file-pdf-load.c'; else $(CYGPATH_W) '$(srcdir)/file-pdf-load.c'; fi` + +file_pdf_save-file-pdf-save.o: file-pdf-save.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_save_CFLAGS) $(CFLAGS) -MT file_pdf_save-file-pdf-save.o -MD -MP -MF $(DEPDIR)/file_pdf_save-file-pdf-save.Tpo -c -o file_pdf_save-file-pdf-save.o `test -f 'file-pdf-save.c' || echo '$(srcdir)/'`file-pdf-save.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_pdf_save-file-pdf-save.Tpo $(DEPDIR)/file_pdf_save-file-pdf-save.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-pdf-save.c' object='file_pdf_save-file-pdf-save.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_save_CFLAGS) $(CFLAGS) -c -o file_pdf_save-file-pdf-save.o `test -f 'file-pdf-save.c' || echo '$(srcdir)/'`file-pdf-save.c + +file_pdf_save-file-pdf-save.obj: file-pdf-save.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_save_CFLAGS) $(CFLAGS) -MT file_pdf_save-file-pdf-save.obj -MD -MP -MF $(DEPDIR)/file_pdf_save-file-pdf-save.Tpo -c -o file_pdf_save-file-pdf-save.obj `if test -f 'file-pdf-save.c'; then $(CYGPATH_W) 'file-pdf-save.c'; else $(CYGPATH_W) '$(srcdir)/file-pdf-save.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_pdf_save-file-pdf-save.Tpo $(DEPDIR)/file_pdf_save-file-pdf-save.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-pdf-save.c' object='file_pdf_save-file-pdf-save.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_pdf_save_CFLAGS) $(CFLAGS) -c -o file_pdf_save-file-pdf-save.obj `if test -f 'file-pdf-save.c'; then $(CYGPATH_W) 'file-pdf-save.c'; else $(CYGPATH_W) '$(srcdir)/file-pdf-save.c'; fi` + +file_png-file-png.o: file-png.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_png_CFLAGS) $(CFLAGS) -MT file_png-file-png.o -MD -MP -MF $(DEPDIR)/file_png-file-png.Tpo -c -o file_png-file-png.o `test -f 'file-png.c' || echo '$(srcdir)/'`file-png.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_png-file-png.Tpo $(DEPDIR)/file_png-file-png.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-png.c' object='file_png-file-png.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_png_CFLAGS) $(CFLAGS) -c -o file_png-file-png.o `test -f 'file-png.c' || echo '$(srcdir)/'`file-png.c + +file_png-file-png.obj: file-png.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_png_CFLAGS) $(CFLAGS) -MT file_png-file-png.obj -MD -MP -MF $(DEPDIR)/file_png-file-png.Tpo -c -o file_png-file-png.obj `if test -f 'file-png.c'; then $(CYGPATH_W) 'file-png.c'; else $(CYGPATH_W) '$(srcdir)/file-png.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_png-file-png.Tpo $(DEPDIR)/file_png-file-png.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-png.c' object='file_png-file-png.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_png_CFLAGS) $(CFLAGS) -c -o file_png-file-png.obj `if test -f 'file-png.c'; then $(CYGPATH_W) 'file-png.c'; else $(CYGPATH_W) '$(srcdir)/file-png.c'; fi` + +file_svg-file-svg.o: file-svg.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_svg_CFLAGS) $(CFLAGS) -MT file_svg-file-svg.o -MD -MP -MF $(DEPDIR)/file_svg-file-svg.Tpo -c -o file_svg-file-svg.o `test -f 'file-svg.c' || echo '$(srcdir)/'`file-svg.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_svg-file-svg.Tpo $(DEPDIR)/file_svg-file-svg.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-svg.c' object='file_svg-file-svg.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_svg_CFLAGS) $(CFLAGS) -c -o file_svg-file-svg.o `test -f 'file-svg.c' || echo '$(srcdir)/'`file-svg.c + +file_svg-file-svg.obj: file-svg.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_svg_CFLAGS) $(CFLAGS) -MT file_svg-file-svg.obj -MD -MP -MF $(DEPDIR)/file_svg-file-svg.Tpo -c -o file_svg-file-svg.obj `if test -f 'file-svg.c'; then $(CYGPATH_W) 'file-svg.c'; else $(CYGPATH_W) '$(srcdir)/file-svg.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_svg-file-svg.Tpo $(DEPDIR)/file_svg-file-svg.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-svg.c' object='file_svg-file-svg.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_svg_CFLAGS) $(CFLAGS) -c -o file_svg-file-svg.obj `if test -f 'file-svg.c'; then $(CYGPATH_W) 'file-svg.c'; else $(CYGPATH_W) '$(srcdir)/file-svg.c'; fi` + +file_wmf-file-wmf.o: file-wmf.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_wmf_CFLAGS) $(CFLAGS) -MT file_wmf-file-wmf.o -MD -MP -MF $(DEPDIR)/file_wmf-file-wmf.Tpo -c -o file_wmf-file-wmf.o `test -f 'file-wmf.c' || echo '$(srcdir)/'`file-wmf.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_wmf-file-wmf.Tpo $(DEPDIR)/file_wmf-file-wmf.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-wmf.c' object='file_wmf-file-wmf.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_wmf_CFLAGS) $(CFLAGS) -c -o file_wmf-file-wmf.o `test -f 'file-wmf.c' || echo '$(srcdir)/'`file-wmf.c + +file_wmf-file-wmf.obj: file-wmf.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_wmf_CFLAGS) $(CFLAGS) -MT file_wmf-file-wmf.obj -MD -MP -MF $(DEPDIR)/file_wmf-file-wmf.Tpo -c -o file_wmf-file-wmf.obj `if test -f 'file-wmf.c'; then $(CYGPATH_W) 'file-wmf.c'; else $(CYGPATH_W) '$(srcdir)/file-wmf.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_wmf-file-wmf.Tpo $(DEPDIR)/file_wmf-file-wmf.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-wmf.c' object='file_wmf-file-wmf.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_wmf_CFLAGS) $(CFLAGS) -c -o file_wmf-file-wmf.obj `if test -f 'file-wmf.c'; then $(CYGPATH_W) 'file-wmf.c'; else $(CYGPATH_W) '$(srcdir)/file-wmf.c'; fi` + +web_browser-web-browser.o: web-browser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(web_browser_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT web_browser-web-browser.o -MD -MP -MF $(DEPDIR)/web_browser-web-browser.Tpo -c -o web_browser-web-browser.o `test -f 'web-browser.c' || echo '$(srcdir)/'`web-browser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/web_browser-web-browser.Tpo $(DEPDIR)/web_browser-web-browser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='web-browser.c' object='web_browser-web-browser.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(web_browser_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o web_browser-web-browser.o `test -f 'web-browser.c' || echo '$(srcdir)/'`web-browser.c + +web_browser-web-browser.obj: web-browser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(web_browser_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT web_browser-web-browser.obj -MD -MP -MF $(DEPDIR)/web_browser-web-browser.Tpo -c -o web_browser-web-browser.obj `if test -f 'web-browser.c'; then $(CYGPATH_W) 'web-browser.c'; else $(CYGPATH_W) '$(srcdir)/web-browser.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/web_browser-web-browser.Tpo $(DEPDIR)/web_browser-web-browser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='web-browser.c' object='web_browser-web-browser.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(web_browser_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o web_browser-web-browser.obj `if test -f 'web-browser.c'; then $(CYGPATH_W) 'web-browser.c'; else $(CYGPATH_W) '$(srcdir)/web-browser.c'; fi` + +web_page-web-page.o: web-page.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(web_page_CFLAGS) $(CFLAGS) -MT web_page-web-page.o -MD -MP -MF $(DEPDIR)/web_page-web-page.Tpo -c -o web_page-web-page.o `test -f 'web-page.c' || echo '$(srcdir)/'`web-page.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/web_page-web-page.Tpo $(DEPDIR)/web_page-web-page.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='web-page.c' object='web_page-web-page.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(web_page_CFLAGS) $(CFLAGS) -c -o web_page-web-page.o `test -f 'web-page.c' || echo '$(srcdir)/'`web-page.c + +web_page-web-page.obj: web-page.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(web_page_CFLAGS) $(CFLAGS) -MT web_page-web-page.obj -MD -MP -MF $(DEPDIR)/web_page-web-page.Tpo -c -o web_page-web-page.obj `if test -f 'web-page.c'; then $(CYGPATH_W) 'web-page.c'; else $(CYGPATH_W) '$(srcdir)/web-page.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/web_page-web-page.Tpo $(DEPDIR)/web_page-web-page.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='web-page.c' object='web_page-web-page.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(web_page_CFLAGS) $(CFLAGS) -c -o web_page-web-page.obj `if test -f 'web-page.c'; then $(CYGPATH_W) 'web-page.c'; else $(CYGPATH_W) '$(srcdir)/web-page.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(align_layers_libexecdir)" "$(DESTDIR)$(animation_optimize_libexecdir)" "$(DESTDIR)$(animation_play_libexecdir)" "$(DESTDIR)$(blinds_libexecdir)" "$(DESTDIR)$(blur_libexecdir)" "$(DESTDIR)$(border_average_libexecdir)" "$(DESTDIR)$(busy_dialog_libexecdir)" "$(DESTDIR)$(cartoon_libexecdir)" "$(DESTDIR)$(checkerboard_libexecdir)" "$(DESTDIR)$(cml_explorer_libexecdir)" "$(DESTDIR)$(color_cube_analyze_libexecdir)" "$(DESTDIR)$(color_enhance_libexecdir)" "$(DESTDIR)$(colorify_libexecdir)" "$(DESTDIR)$(colormap_remap_libexecdir)" "$(DESTDIR)$(compose_libexecdir)" "$(DESTDIR)$(contrast_retinex_libexecdir)" "$(DESTDIR)$(crop_zealous_libexecdir)" "$(DESTDIR)$(curve_bend_libexecdir)" "$(DESTDIR)$(decompose_libexecdir)" "$(DESTDIR)$(depth_merge_libexecdir)" "$(DESTDIR)$(despeckle_libexecdir)" "$(DESTDIR)$(destripe_libexecdir)" "$(DESTDIR)$(edge_dog_libexecdir)" "$(DESTDIR)$(emboss_libexecdir)" "$(DESTDIR)$(file_aa_libexecdir)" "$(DESTDIR)$(file_cel_libexecdir)" "$(DESTDIR)$(file_compressor_libexecdir)" "$(DESTDIR)$(file_csource_libexecdir)" "$(DESTDIR)$(file_desktop_link_libexecdir)" "$(DESTDIR)$(file_dicom_libexecdir)" "$(DESTDIR)$(file_gbr_libexecdir)" "$(DESTDIR)$(file_gegl_libexecdir)" "$(DESTDIR)$(file_gif_load_libexecdir)" "$(DESTDIR)$(file_gif_save_libexecdir)" "$(DESTDIR)$(file_gih_libexecdir)" "$(DESTDIR)$(file_glob_libexecdir)" "$(DESTDIR)$(file_header_libexecdir)" "$(DESTDIR)$(file_heif_libexecdir)" "$(DESTDIR)$(file_html_table_libexecdir)" "$(DESTDIR)$(file_jp2_load_libexecdir)" "$(DESTDIR)$(file_mng_libexecdir)" "$(DESTDIR)$(file_pat_libexecdir)" "$(DESTDIR)$(file_pcx_libexecdir)" "$(DESTDIR)$(file_pdf_load_libexecdir)" "$(DESTDIR)$(file_pdf_save_libexecdir)" "$(DESTDIR)$(file_pix_libexecdir)" "$(DESTDIR)$(file_png_libexecdir)" "$(DESTDIR)$(file_pnm_libexecdir)" "$(DESTDIR)$(file_ps_libexecdir)" "$(DESTDIR)$(file_psp_libexecdir)" "$(DESTDIR)$(file_raw_data_libexecdir)" "$(DESTDIR)$(file_sunras_libexecdir)" "$(DESTDIR)$(file_svg_libexecdir)" "$(DESTDIR)$(file_tga_libexecdir)" "$(DESTDIR)$(file_wmf_libexecdir)" "$(DESTDIR)$(file_xbm_libexecdir)" "$(DESTDIR)$(file_xmc_libexecdir)" "$(DESTDIR)$(file_xpm_libexecdir)" "$(DESTDIR)$(file_xwd_libexecdir)" "$(DESTDIR)$(film_libexecdir)" "$(DESTDIR)$(filter_pack_libexecdir)" "$(DESTDIR)$(fractal_trace_libexecdir)" "$(DESTDIR)$(goat_exercise_libexecdir)" "$(DESTDIR)$(gradient_map_libexecdir)" "$(DESTDIR)$(grid_libexecdir)" "$(DESTDIR)$(guillotine_libexecdir)" "$(DESTDIR)$(hot_libexecdir)" "$(DESTDIR)$(jigsaw_libexecdir)" "$(DESTDIR)$(mail_libexecdir)" "$(DESTDIR)$(max_rgb_libexecdir)" "$(DESTDIR)$(nl_filter_libexecdir)" "$(DESTDIR)$(photocopy_libexecdir)" "$(DESTDIR)$(plugin_browser_libexecdir)" "$(DESTDIR)$(procedure_browser_libexecdir)" "$(DESTDIR)$(qbist_libexecdir)" "$(DESTDIR)$(sample_colorize_libexecdir)" "$(DESTDIR)$(sharpen_libexecdir)" "$(DESTDIR)$(smooth_palette_libexecdir)" "$(DESTDIR)$(softglow_libexecdir)" "$(DESTDIR)$(sparkle_libexecdir)" "$(DESTDIR)$(sphere_designer_libexecdir)" "$(DESTDIR)$(tile_libexecdir)" "$(DESTDIR)$(tile_small_libexecdir)" "$(DESTDIR)$(unit_editor_libexecdir)" "$(DESTDIR)$(van_gogh_lic_libexecdir)" "$(DESTDIR)$(warp_libexecdir)" "$(DESTDIR)$(wavelet_decompose_libexecdir)" "$(DESTDIR)$(web_browser_libexecdir)" "$(DESTDIR)$(web_page_libexecdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-align_layers_libexecPROGRAMS \ + clean-animation_optimize_libexecPROGRAMS \ + clean-animation_play_libexecPROGRAMS \ + clean-blinds_libexecPROGRAMS clean-blur_libexecPROGRAMS \ + clean-border_average_libexecPROGRAMS \ + clean-busy_dialog_libexecPROGRAMS \ + clean-cartoon_libexecPROGRAMS \ + clean-checkerboard_libexecPROGRAMS \ + clean-cml_explorer_libexecPROGRAMS \ + clean-color_cube_analyze_libexecPROGRAMS \ + clean-color_enhance_libexecPROGRAMS \ + clean-colorify_libexecPROGRAMS \ + clean-colormap_remap_libexecPROGRAMS \ + clean-compose_libexecPROGRAMS \ + clean-contrast_retinex_libexecPROGRAMS \ + clean-crop_zealous_libexecPROGRAMS \ + clean-curve_bend_libexecPROGRAMS \ + clean-decompose_libexecPROGRAMS \ + clean-depth_merge_libexecPROGRAMS \ + clean-despeckle_libexecPROGRAMS clean-destripe_libexecPROGRAMS \ + clean-edge_dog_libexecPROGRAMS clean-emboss_libexecPROGRAMS \ + clean-file_aa_libexecPROGRAMS clean-file_cel_libexecPROGRAMS \ + clean-file_compressor_libexecPROGRAMS \ + clean-file_csource_libexecPROGRAMS \ + clean-file_desktop_link_libexecPROGRAMS \ + clean-file_dicom_libexecPROGRAMS \ + clean-file_gbr_libexecPROGRAMS clean-file_gegl_libexecPROGRAMS \ + clean-file_gif_load_libexecPROGRAMS \ + clean-file_gif_save_libexecPROGRAMS \ + clean-file_gih_libexecPROGRAMS clean-file_glob_libexecPROGRAMS \ + clean-file_header_libexecPROGRAMS \ + clean-file_heif_libexecPROGRAMS \ + clean-file_html_table_libexecPROGRAMS \ + clean-file_jp2_load_libexecPROGRAMS \ + clean-file_mng_libexecPROGRAMS clean-file_pat_libexecPROGRAMS \ + clean-file_pcx_libexecPROGRAMS \ + clean-file_pdf_load_libexecPROGRAMS \ + clean-file_pdf_save_libexecPROGRAMS \ + clean-file_pix_libexecPROGRAMS clean-file_png_libexecPROGRAMS \ + clean-file_pnm_libexecPROGRAMS clean-file_ps_libexecPROGRAMS \ + clean-file_psp_libexecPROGRAMS \ + clean-file_raw_data_libexecPROGRAMS \ + clean-file_sunras_libexecPROGRAMS \ + clean-file_svg_libexecPROGRAMS clean-file_tga_libexecPROGRAMS \ + clean-file_wmf_libexecPROGRAMS clean-file_xbm_libexecPROGRAMS \ + clean-file_xmc_libexecPROGRAMS clean-file_xpm_libexecPROGRAMS \ + clean-file_xwd_libexecPROGRAMS clean-film_libexecPROGRAMS \ + clean-filter_pack_libexecPROGRAMS \ + clean-fractal_trace_libexecPROGRAMS clean-generic \ + clean-goat_exercise_libexecPROGRAMS \ + clean-gradient_map_libexecPROGRAMS clean-grid_libexecPROGRAMS \ + clean-guillotine_libexecPROGRAMS clean-hot_libexecPROGRAMS \ + clean-jigsaw_libexecPROGRAMS clean-libtool \ + clean-mail_libexecPROGRAMS clean-max_rgb_libexecPROGRAMS \ + clean-nl_filter_libexecPROGRAMS \ + clean-photocopy_libexecPROGRAMS \ + clean-plugin_browser_libexecPROGRAMS \ + clean-procedure_browser_libexecPROGRAMS \ + clean-qbist_libexecPROGRAMS \ + clean-sample_colorize_libexecPROGRAMS \ + clean-sharpen_libexecPROGRAMS \ + clean-smooth_palette_libexecPROGRAMS \ + clean-softglow_libexecPROGRAMS clean-sparkle_libexecPROGRAMS \ + clean-sphere_designer_libexecPROGRAMS \ + clean-tile_libexecPROGRAMS clean-tile_small_libexecPROGRAMS \ + clean-unit_editor_libexecPROGRAMS \ + clean-van_gogh_lic_libexecPROGRAMS clean-warp_libexecPROGRAMS \ + clean-wavelet_decompose_libexecPROGRAMS \ + clean-web_browser_libexecPROGRAMS \ + clean-web_page_libexecPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/align-layers.Po + -rm -f ./$(DEPDIR)/animation-optimize.Po + -rm -f ./$(DEPDIR)/animation-play.Po + -rm -f ./$(DEPDIR)/blinds.Po + -rm -f ./$(DEPDIR)/blur.Po + -rm -f ./$(DEPDIR)/border-average.Po + -rm -f ./$(DEPDIR)/busy-dialog.Po + -rm -f ./$(DEPDIR)/cartoon.Po + -rm -f ./$(DEPDIR)/checkerboard.Po + -rm -f ./$(DEPDIR)/cml-explorer.Po + -rm -f ./$(DEPDIR)/color-cube-analyze.Po + -rm -f ./$(DEPDIR)/color-enhance.Po + -rm -f ./$(DEPDIR)/colorify.Po + -rm -f ./$(DEPDIR)/colormap-remap.Po + -rm -f ./$(DEPDIR)/compose.Po + -rm -f ./$(DEPDIR)/contrast-retinex.Po + -rm -f ./$(DEPDIR)/crop-zealous.Po + -rm -f ./$(DEPDIR)/curve-bend.Po + -rm -f ./$(DEPDIR)/decompose.Po + -rm -f ./$(DEPDIR)/depth-merge.Po + -rm -f ./$(DEPDIR)/despeckle.Po + -rm -f ./$(DEPDIR)/destripe.Po + -rm -f ./$(DEPDIR)/edge-dog.Po + -rm -f ./$(DEPDIR)/emboss.Po + -rm -f ./$(DEPDIR)/file-aa.Po + -rm -f ./$(DEPDIR)/file-cel.Po + -rm -f ./$(DEPDIR)/file-csource.Po + -rm -f ./$(DEPDIR)/file-desktop-link.Po + -rm -f ./$(DEPDIR)/file-gbr.Po + -rm -f ./$(DEPDIR)/file-gegl.Po + -rm -f ./$(DEPDIR)/file-gif-load.Po + -rm -f ./$(DEPDIR)/file-gif-save.Po + -rm -f ./$(DEPDIR)/file-gih.Po + -rm -f ./$(DEPDIR)/file-glob.Po + -rm -f ./$(DEPDIR)/file-header.Po + -rm -f ./$(DEPDIR)/file-html-table.Po + -rm -f ./$(DEPDIR)/file-pat.Po + -rm -f ./$(DEPDIR)/file-pcx.Po + -rm -f ./$(DEPDIR)/file-pix.Po + -rm -f ./$(DEPDIR)/file-pnm.Po + -rm -f ./$(DEPDIR)/file-ps.Po + -rm -f ./$(DEPDIR)/file-psp.Po + -rm -f ./$(DEPDIR)/file-raw-data.Po + -rm -f ./$(DEPDIR)/file-sunras.Po + -rm -f ./$(DEPDIR)/file-tga.Po + -rm -f ./$(DEPDIR)/file-xbm.Po + -rm -f ./$(DEPDIR)/file-xmc.Po + -rm -f ./$(DEPDIR)/file-xpm.Po + -rm -f ./$(DEPDIR)/file-xwd.Po + -rm -f ./$(DEPDIR)/file_compressor-file-compressor.Po + -rm -f ./$(DEPDIR)/file_dicom-file-dicom.Po + -rm -f ./$(DEPDIR)/file_heif-file-heif.Po + -rm -f ./$(DEPDIR)/file_jp2_load-file-jp2-load.Po + -rm -f ./$(DEPDIR)/file_mng-file-mng.Po + -rm -f ./$(DEPDIR)/file_pdf_load-file-pdf-load.Po + -rm -f ./$(DEPDIR)/file_pdf_save-file-pdf-save.Po + -rm -f ./$(DEPDIR)/file_png-file-png.Po + -rm -f ./$(DEPDIR)/file_svg-file-svg.Po + -rm -f ./$(DEPDIR)/file_wmf-file-wmf.Po + -rm -f ./$(DEPDIR)/film.Po + -rm -f ./$(DEPDIR)/filter-pack.Po + -rm -f ./$(DEPDIR)/fractal-trace.Po + -rm -f ./$(DEPDIR)/goat-exercise.Po + -rm -f ./$(DEPDIR)/gradient-map.Po + -rm -f ./$(DEPDIR)/grid.Po + -rm -f ./$(DEPDIR)/guillotine.Po + -rm -f ./$(DEPDIR)/hot.Po + -rm -f ./$(DEPDIR)/jigsaw.Po + -rm -f ./$(DEPDIR)/mail.Po + -rm -f ./$(DEPDIR)/max-rgb.Po + -rm -f ./$(DEPDIR)/nl-filter.Po + -rm -f ./$(DEPDIR)/photocopy.Po + -rm -f ./$(DEPDIR)/plugin-browser.Po + -rm -f ./$(DEPDIR)/procedure-browser.Po + -rm -f ./$(DEPDIR)/qbist.Po + -rm -f ./$(DEPDIR)/sample-colorize.Po + -rm -f ./$(DEPDIR)/sharpen.Po + -rm -f ./$(DEPDIR)/smooth-palette.Po + -rm -f ./$(DEPDIR)/softglow.Po + -rm -f ./$(DEPDIR)/sparkle.Po + -rm -f ./$(DEPDIR)/sphere-designer.Po + -rm -f ./$(DEPDIR)/tile-small.Po + -rm -f ./$(DEPDIR)/tile.Po + -rm -f ./$(DEPDIR)/unit-editor.Po + -rm -f ./$(DEPDIR)/van-gogh-lic.Po + -rm -f ./$(DEPDIR)/warp.Po + -rm -f ./$(DEPDIR)/wavelet-decompose.Po + -rm -f ./$(DEPDIR)/web_browser-web-browser.Po + -rm -f ./$(DEPDIR)/web_page-web-page.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-align_layers_libexecPROGRAMS \ + install-animation_optimize_libexecPROGRAMS \ + install-animation_play_libexecPROGRAMS \ + install-blinds_libexecPROGRAMS install-blur_libexecPROGRAMS \ + install-border_average_libexecPROGRAMS \ + install-busy_dialog_libexecPROGRAMS \ + install-cartoon_libexecPROGRAMS \ + install-checkerboard_libexecPROGRAMS \ + install-cml_explorer_libexecPROGRAMS \ + install-color_cube_analyze_libexecPROGRAMS \ + install-color_enhance_libexecPROGRAMS \ + install-colorify_libexecPROGRAMS \ + install-colormap_remap_libexecPROGRAMS \ + install-compose_libexecPROGRAMS \ + install-contrast_retinex_libexecPROGRAMS \ + install-crop_zealous_libexecPROGRAMS \ + install-curve_bend_libexecPROGRAMS \ + install-decompose_libexecPROGRAMS \ + install-depth_merge_libexecPROGRAMS \ + install-despeckle_libexecPROGRAMS \ + install-destripe_libexecPROGRAMS \ + install-edge_dog_libexecPROGRAMS \ + install-emboss_libexecPROGRAMS install-file_aa_libexecPROGRAMS \ + install-file_cel_libexecPROGRAMS \ + install-file_compressor_libexecPROGRAMS \ + install-file_csource_libexecPROGRAMS \ + install-file_desktop_link_libexecPROGRAMS \ + install-file_dicom_libexecPROGRAMS \ + install-file_gbr_libexecPROGRAMS \ + install-file_gegl_libexecPROGRAMS \ + install-file_gif_load_libexecPROGRAMS \ + install-file_gif_save_libexecPROGRAMS \ + install-file_gih_libexecPROGRAMS \ + install-file_glob_libexecPROGRAMS \ + install-file_header_libexecPROGRAMS \ + install-file_heif_libexecPROGRAMS \ + install-file_html_table_libexecPROGRAMS \ + install-file_jp2_load_libexecPROGRAMS \ + install-file_mng_libexecPROGRAMS \ + install-file_pat_libexecPROGRAMS \ + install-file_pcx_libexecPROGRAMS \ + install-file_pdf_load_libexecPROGRAMS \ + install-file_pdf_save_libexecPROGRAMS \ + install-file_pix_libexecPROGRAMS \ + install-file_png_libexecPROGRAMS \ + install-file_pnm_libexecPROGRAMS \ + install-file_ps_libexecPROGRAMS \ + install-file_psp_libexecPROGRAMS \ + install-file_raw_data_libexecPROGRAMS \ + install-file_sunras_libexecPROGRAMS \ + install-file_svg_libexecPROGRAMS \ + install-file_tga_libexecPROGRAMS \ + install-file_wmf_libexecPROGRAMS \ + install-file_xbm_libexecPROGRAMS \ + install-file_xmc_libexecPROGRAMS \ + install-file_xpm_libexecPROGRAMS \ + install-file_xwd_libexecPROGRAMS install-film_libexecPROGRAMS \ + install-filter_pack_libexecPROGRAMS \ + install-fractal_trace_libexecPROGRAMS \ + install-goat_exercise_libexecPROGRAMS \ + install-gradient_map_libexecPROGRAMS \ + install-grid_libexecPROGRAMS \ + install-guillotine_libexecPROGRAMS install-hot_libexecPROGRAMS \ + install-jigsaw_libexecPROGRAMS install-mail_libexecPROGRAMS \ + install-max_rgb_libexecPROGRAMS \ + install-nl_filter_libexecPROGRAMS \ + install-photocopy_libexecPROGRAMS \ + install-plugin_browser_libexecPROGRAMS \ + install-procedure_browser_libexecPROGRAMS \ + install-qbist_libexecPROGRAMS \ + install-sample_colorize_libexecPROGRAMS \ + install-sharpen_libexecPROGRAMS \ + install-smooth_palette_libexecPROGRAMS \ + install-softglow_libexecPROGRAMS \ + install-sparkle_libexecPROGRAMS \ + install-sphere_designer_libexecPROGRAMS \ + install-tile_libexecPROGRAMS \ + install-tile_small_libexecPROGRAMS \ + install-unit_editor_libexecPROGRAMS \ + install-van_gogh_lic_libexecPROGRAMS \ + install-warp_libexecPROGRAMS \ + install-wavelet_decompose_libexecPROGRAMS \ + install-web_browser_libexecPROGRAMS \ + install-web_page_libexecPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/align-layers.Po + -rm -f ./$(DEPDIR)/animation-optimize.Po + -rm -f ./$(DEPDIR)/animation-play.Po + -rm -f ./$(DEPDIR)/blinds.Po + -rm -f ./$(DEPDIR)/blur.Po + -rm -f ./$(DEPDIR)/border-average.Po + -rm -f ./$(DEPDIR)/busy-dialog.Po + -rm -f ./$(DEPDIR)/cartoon.Po + -rm -f ./$(DEPDIR)/checkerboard.Po + -rm -f ./$(DEPDIR)/cml-explorer.Po + -rm -f ./$(DEPDIR)/color-cube-analyze.Po + -rm -f ./$(DEPDIR)/color-enhance.Po + -rm -f ./$(DEPDIR)/colorify.Po + -rm -f ./$(DEPDIR)/colormap-remap.Po + -rm -f ./$(DEPDIR)/compose.Po + -rm -f ./$(DEPDIR)/contrast-retinex.Po + -rm -f ./$(DEPDIR)/crop-zealous.Po + -rm -f ./$(DEPDIR)/curve-bend.Po + -rm -f ./$(DEPDIR)/decompose.Po + -rm -f ./$(DEPDIR)/depth-merge.Po + -rm -f ./$(DEPDIR)/despeckle.Po + -rm -f ./$(DEPDIR)/destripe.Po + -rm -f ./$(DEPDIR)/edge-dog.Po + -rm -f ./$(DEPDIR)/emboss.Po + -rm -f ./$(DEPDIR)/file-aa.Po + -rm -f ./$(DEPDIR)/file-cel.Po + -rm -f ./$(DEPDIR)/file-csource.Po + -rm -f ./$(DEPDIR)/file-desktop-link.Po + -rm -f ./$(DEPDIR)/file-gbr.Po + -rm -f ./$(DEPDIR)/file-gegl.Po + -rm -f ./$(DEPDIR)/file-gif-load.Po + -rm -f ./$(DEPDIR)/file-gif-save.Po + -rm -f ./$(DEPDIR)/file-gih.Po + -rm -f ./$(DEPDIR)/file-glob.Po + -rm -f ./$(DEPDIR)/file-header.Po + -rm -f ./$(DEPDIR)/file-html-table.Po + -rm -f ./$(DEPDIR)/file-pat.Po + -rm -f ./$(DEPDIR)/file-pcx.Po + -rm -f ./$(DEPDIR)/file-pix.Po + -rm -f ./$(DEPDIR)/file-pnm.Po + -rm -f ./$(DEPDIR)/file-ps.Po + -rm -f ./$(DEPDIR)/file-psp.Po + -rm -f ./$(DEPDIR)/file-raw-data.Po + -rm -f ./$(DEPDIR)/file-sunras.Po + -rm -f ./$(DEPDIR)/file-tga.Po + -rm -f ./$(DEPDIR)/file-xbm.Po + -rm -f ./$(DEPDIR)/file-xmc.Po + -rm -f ./$(DEPDIR)/file-xpm.Po + -rm -f ./$(DEPDIR)/file-xwd.Po + -rm -f ./$(DEPDIR)/file_compressor-file-compressor.Po + -rm -f ./$(DEPDIR)/file_dicom-file-dicom.Po + -rm -f ./$(DEPDIR)/file_heif-file-heif.Po + -rm -f ./$(DEPDIR)/file_jp2_load-file-jp2-load.Po + -rm -f ./$(DEPDIR)/file_mng-file-mng.Po + -rm -f ./$(DEPDIR)/file_pdf_load-file-pdf-load.Po + -rm -f ./$(DEPDIR)/file_pdf_save-file-pdf-save.Po + -rm -f ./$(DEPDIR)/file_png-file-png.Po + -rm -f ./$(DEPDIR)/file_svg-file-svg.Po + -rm -f ./$(DEPDIR)/file_wmf-file-wmf.Po + -rm -f ./$(DEPDIR)/film.Po + -rm -f ./$(DEPDIR)/filter-pack.Po + -rm -f ./$(DEPDIR)/fractal-trace.Po + -rm -f ./$(DEPDIR)/goat-exercise.Po + -rm -f ./$(DEPDIR)/gradient-map.Po + -rm -f ./$(DEPDIR)/grid.Po + -rm -f ./$(DEPDIR)/guillotine.Po + -rm -f ./$(DEPDIR)/hot.Po + -rm -f ./$(DEPDIR)/jigsaw.Po + -rm -f ./$(DEPDIR)/mail.Po + -rm -f ./$(DEPDIR)/max-rgb.Po + -rm -f ./$(DEPDIR)/nl-filter.Po + -rm -f ./$(DEPDIR)/photocopy.Po + -rm -f ./$(DEPDIR)/plugin-browser.Po + -rm -f ./$(DEPDIR)/procedure-browser.Po + -rm -f ./$(DEPDIR)/qbist.Po + -rm -f ./$(DEPDIR)/sample-colorize.Po + -rm -f ./$(DEPDIR)/sharpen.Po + -rm -f ./$(DEPDIR)/smooth-palette.Po + -rm -f ./$(DEPDIR)/softglow.Po + -rm -f ./$(DEPDIR)/sparkle.Po + -rm -f ./$(DEPDIR)/sphere-designer.Po + -rm -f ./$(DEPDIR)/tile-small.Po + -rm -f ./$(DEPDIR)/tile.Po + -rm -f ./$(DEPDIR)/unit-editor.Po + -rm -f ./$(DEPDIR)/van-gogh-lic.Po + -rm -f ./$(DEPDIR)/warp.Po + -rm -f ./$(DEPDIR)/wavelet-decompose.Po + -rm -f ./$(DEPDIR)/web_browser-web-browser.Po + -rm -f ./$(DEPDIR)/web_page-web-page.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-align_layers_libexecPROGRAMS \ + uninstall-animation_optimize_libexecPROGRAMS \ + uninstall-animation_play_libexecPROGRAMS \ + uninstall-blinds_libexecPROGRAMS \ + uninstall-blur_libexecPROGRAMS \ + uninstall-border_average_libexecPROGRAMS \ + uninstall-busy_dialog_libexecPROGRAMS \ + uninstall-cartoon_libexecPROGRAMS \ + uninstall-checkerboard_libexecPROGRAMS \ + uninstall-cml_explorer_libexecPROGRAMS \ + uninstall-color_cube_analyze_libexecPROGRAMS \ + uninstall-color_enhance_libexecPROGRAMS \ + uninstall-colorify_libexecPROGRAMS \ + uninstall-colormap_remap_libexecPROGRAMS \ + uninstall-compose_libexecPROGRAMS \ + uninstall-contrast_retinex_libexecPROGRAMS \ + uninstall-crop_zealous_libexecPROGRAMS \ + uninstall-curve_bend_libexecPROGRAMS \ + uninstall-decompose_libexecPROGRAMS \ + uninstall-depth_merge_libexecPROGRAMS \ + uninstall-despeckle_libexecPROGRAMS \ + uninstall-destripe_libexecPROGRAMS \ + uninstall-edge_dog_libexecPROGRAMS \ + uninstall-emboss_libexecPROGRAMS \ + uninstall-file_aa_libexecPROGRAMS \ + uninstall-file_cel_libexecPROGRAMS \ + uninstall-file_compressor_libexecPROGRAMS \ + uninstall-file_csource_libexecPROGRAMS \ + uninstall-file_desktop_link_libexecPROGRAMS \ + uninstall-file_dicom_libexecPROGRAMS \ + uninstall-file_gbr_libexecPROGRAMS \ + uninstall-file_gegl_libexecPROGRAMS \ + uninstall-file_gif_load_libexecPROGRAMS \ + uninstall-file_gif_save_libexecPROGRAMS \ + uninstall-file_gih_libexecPROGRAMS \ + uninstall-file_glob_libexecPROGRAMS \ + uninstall-file_header_libexecPROGRAMS \ + uninstall-file_heif_libexecPROGRAMS \ + uninstall-file_html_table_libexecPROGRAMS \ + uninstall-file_jp2_load_libexecPROGRAMS \ + uninstall-file_mng_libexecPROGRAMS \ + uninstall-file_pat_libexecPROGRAMS \ + uninstall-file_pcx_libexecPROGRAMS \ + uninstall-file_pdf_load_libexecPROGRAMS \ + uninstall-file_pdf_save_libexecPROGRAMS \ + uninstall-file_pix_libexecPROGRAMS \ + uninstall-file_png_libexecPROGRAMS \ + uninstall-file_pnm_libexecPROGRAMS \ + uninstall-file_ps_libexecPROGRAMS \ + uninstall-file_psp_libexecPROGRAMS \ + uninstall-file_raw_data_libexecPROGRAMS \ + uninstall-file_sunras_libexecPROGRAMS \ + uninstall-file_svg_libexecPROGRAMS \ + uninstall-file_tga_libexecPROGRAMS \ + uninstall-file_wmf_libexecPROGRAMS \ + uninstall-file_xbm_libexecPROGRAMS \ + uninstall-file_xmc_libexecPROGRAMS \ + uninstall-file_xpm_libexecPROGRAMS \ + uninstall-file_xwd_libexecPROGRAMS \ + uninstall-film_libexecPROGRAMS \ + uninstall-filter_pack_libexecPROGRAMS \ + uninstall-fractal_trace_libexecPROGRAMS \ + uninstall-goat_exercise_libexecPROGRAMS \ + uninstall-gradient_map_libexecPROGRAMS \ + uninstall-grid_libexecPROGRAMS \ + uninstall-guillotine_libexecPROGRAMS \ + uninstall-hot_libexecPROGRAMS uninstall-jigsaw_libexecPROGRAMS \ + uninstall-mail_libexecPROGRAMS \ + uninstall-max_rgb_libexecPROGRAMS \ + uninstall-nl_filter_libexecPROGRAMS \ + uninstall-photocopy_libexecPROGRAMS \ + uninstall-plugin_browser_libexecPROGRAMS \ + uninstall-procedure_browser_libexecPROGRAMS \ + uninstall-qbist_libexecPROGRAMS \ + uninstall-sample_colorize_libexecPROGRAMS \ + uninstall-sharpen_libexecPROGRAMS \ + uninstall-smooth_palette_libexecPROGRAMS \ + uninstall-softglow_libexecPROGRAMS \ + uninstall-sparkle_libexecPROGRAMS \ + uninstall-sphere_designer_libexecPROGRAMS \ + uninstall-tile_libexecPROGRAMS \ + uninstall-tile_small_libexecPROGRAMS \ + uninstall-unit_editor_libexecPROGRAMS \ + uninstall-van_gogh_lic_libexecPROGRAMS \ + uninstall-warp_libexecPROGRAMS \ + uninstall-wavelet_decompose_libexecPROGRAMS \ + uninstall-web_browser_libexecPROGRAMS \ + uninstall-web_page_libexecPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-align_layers_libexecPROGRAMS \ + clean-animation_optimize_libexecPROGRAMS \ + clean-animation_play_libexecPROGRAMS \ + clean-blinds_libexecPROGRAMS clean-blur_libexecPROGRAMS \ + clean-border_average_libexecPROGRAMS \ + clean-busy_dialog_libexecPROGRAMS \ + clean-cartoon_libexecPROGRAMS \ + clean-checkerboard_libexecPROGRAMS \ + clean-cml_explorer_libexecPROGRAMS \ + clean-color_cube_analyze_libexecPROGRAMS \ + clean-color_enhance_libexecPROGRAMS \ + clean-colorify_libexecPROGRAMS \ + clean-colormap_remap_libexecPROGRAMS \ + clean-compose_libexecPROGRAMS \ + clean-contrast_retinex_libexecPROGRAMS \ + clean-crop_zealous_libexecPROGRAMS \ + clean-curve_bend_libexecPROGRAMS \ + clean-decompose_libexecPROGRAMS \ + clean-depth_merge_libexecPROGRAMS \ + clean-despeckle_libexecPROGRAMS clean-destripe_libexecPROGRAMS \ + clean-edge_dog_libexecPROGRAMS clean-emboss_libexecPROGRAMS \ + clean-file_aa_libexecPROGRAMS clean-file_cel_libexecPROGRAMS \ + clean-file_compressor_libexecPROGRAMS \ + clean-file_csource_libexecPROGRAMS \ + clean-file_desktop_link_libexecPROGRAMS \ + clean-file_dicom_libexecPROGRAMS \ + clean-file_gbr_libexecPROGRAMS clean-file_gegl_libexecPROGRAMS \ + clean-file_gif_load_libexecPROGRAMS \ + clean-file_gif_save_libexecPROGRAMS \ + clean-file_gih_libexecPROGRAMS clean-file_glob_libexecPROGRAMS \ + clean-file_header_libexecPROGRAMS \ + clean-file_heif_libexecPROGRAMS \ + clean-file_html_table_libexecPROGRAMS \ + clean-file_jp2_load_libexecPROGRAMS \ + clean-file_mng_libexecPROGRAMS clean-file_pat_libexecPROGRAMS \ + clean-file_pcx_libexecPROGRAMS \ + clean-file_pdf_load_libexecPROGRAMS \ + clean-file_pdf_save_libexecPROGRAMS \ + clean-file_pix_libexecPROGRAMS clean-file_png_libexecPROGRAMS \ + clean-file_pnm_libexecPROGRAMS clean-file_ps_libexecPROGRAMS \ + clean-file_psp_libexecPROGRAMS \ + clean-file_raw_data_libexecPROGRAMS \ + clean-file_sunras_libexecPROGRAMS \ + clean-file_svg_libexecPROGRAMS clean-file_tga_libexecPROGRAMS \ + clean-file_wmf_libexecPROGRAMS clean-file_xbm_libexecPROGRAMS \ + clean-file_xmc_libexecPROGRAMS clean-file_xpm_libexecPROGRAMS \ + clean-file_xwd_libexecPROGRAMS clean-film_libexecPROGRAMS \ + clean-filter_pack_libexecPROGRAMS \ + clean-fractal_trace_libexecPROGRAMS clean-generic \ + clean-goat_exercise_libexecPROGRAMS \ + clean-gradient_map_libexecPROGRAMS clean-grid_libexecPROGRAMS \ + clean-guillotine_libexecPROGRAMS clean-hot_libexecPROGRAMS \ + clean-jigsaw_libexecPROGRAMS clean-libtool \ + clean-mail_libexecPROGRAMS clean-max_rgb_libexecPROGRAMS \ + clean-nl_filter_libexecPROGRAMS \ + clean-photocopy_libexecPROGRAMS \ + clean-plugin_browser_libexecPROGRAMS \ + clean-procedure_browser_libexecPROGRAMS \ + clean-qbist_libexecPROGRAMS \ + clean-sample_colorize_libexecPROGRAMS \ + clean-sharpen_libexecPROGRAMS \ + clean-smooth_palette_libexecPROGRAMS \ + clean-softglow_libexecPROGRAMS clean-sparkle_libexecPROGRAMS \ + clean-sphere_designer_libexecPROGRAMS \ + clean-tile_libexecPROGRAMS clean-tile_small_libexecPROGRAMS \ + clean-unit_editor_libexecPROGRAMS \ + clean-van_gogh_lic_libexecPROGRAMS clean-warp_libexecPROGRAMS \ + clean-wavelet_decompose_libexecPROGRAMS \ + clean-web_browser_libexecPROGRAMS \ + clean-web_page_libexecPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install \ + install-align_layers_libexecPROGRAMS install-am \ + install-animation_optimize_libexecPROGRAMS \ + install-animation_play_libexecPROGRAMS \ + install-blinds_libexecPROGRAMS install-blur_libexecPROGRAMS \ + install-border_average_libexecPROGRAMS \ + install-busy_dialog_libexecPROGRAMS \ + install-cartoon_libexecPROGRAMS \ + install-checkerboard_libexecPROGRAMS \ + install-cml_explorer_libexecPROGRAMS \ + install-color_cube_analyze_libexecPROGRAMS \ + install-color_enhance_libexecPROGRAMS \ + install-colorify_libexecPROGRAMS \ + install-colormap_remap_libexecPROGRAMS \ + install-compose_libexecPROGRAMS \ + install-contrast_retinex_libexecPROGRAMS \ + install-crop_zealous_libexecPROGRAMS \ + install-curve_bend_libexecPROGRAMS install-data \ + install-data-am install-decompose_libexecPROGRAMS \ + install-depth_merge_libexecPROGRAMS \ + install-despeckle_libexecPROGRAMS \ + install-destripe_libexecPROGRAMS install-dvi install-dvi-am \ + install-edge_dog_libexecPROGRAMS \ + install-emboss_libexecPROGRAMS install-exec install-exec-am \ + install-file_aa_libexecPROGRAMS \ + install-file_cel_libexecPROGRAMS \ + install-file_compressor_libexecPROGRAMS \ + install-file_csource_libexecPROGRAMS \ + install-file_desktop_link_libexecPROGRAMS \ + install-file_dicom_libexecPROGRAMS \ + install-file_gbr_libexecPROGRAMS \ + install-file_gegl_libexecPROGRAMS \ + install-file_gif_load_libexecPROGRAMS \ + install-file_gif_save_libexecPROGRAMS \ + install-file_gih_libexecPROGRAMS \ + install-file_glob_libexecPROGRAMS \ + install-file_header_libexecPROGRAMS \ + install-file_heif_libexecPROGRAMS \ + install-file_html_table_libexecPROGRAMS \ + install-file_jp2_load_libexecPROGRAMS \ + install-file_mng_libexecPROGRAMS \ + install-file_pat_libexecPROGRAMS \ + install-file_pcx_libexecPROGRAMS \ + install-file_pdf_load_libexecPROGRAMS \ + install-file_pdf_save_libexecPROGRAMS \ + install-file_pix_libexecPROGRAMS \ + install-file_png_libexecPROGRAMS \ + install-file_pnm_libexecPROGRAMS \ + install-file_ps_libexecPROGRAMS \ + install-file_psp_libexecPROGRAMS \ + install-file_raw_data_libexecPROGRAMS \ + install-file_sunras_libexecPROGRAMS \ + install-file_svg_libexecPROGRAMS \ + install-file_tga_libexecPROGRAMS \ + install-file_wmf_libexecPROGRAMS \ + install-file_xbm_libexecPROGRAMS \ + install-file_xmc_libexecPROGRAMS \ + install-file_xpm_libexecPROGRAMS \ + install-file_xwd_libexecPROGRAMS install-film_libexecPROGRAMS \ + install-filter_pack_libexecPROGRAMS \ + install-fractal_trace_libexecPROGRAMS \ + install-goat_exercise_libexecPROGRAMS \ + install-gradient_map_libexecPROGRAMS \ + install-grid_libexecPROGRAMS \ + install-guillotine_libexecPROGRAMS install-hot_libexecPROGRAMS \ + install-html install-html-am install-info install-info-am \ + install-jigsaw_libexecPROGRAMS install-mail_libexecPROGRAMS \ + install-man install-max_rgb_libexecPROGRAMS \ + install-nl_filter_libexecPROGRAMS install-pdf install-pdf-am \ + install-photocopy_libexecPROGRAMS \ + install-plugin_browser_libexecPROGRAMS \ + install-procedure_browser_libexecPROGRAMS install-ps \ + install-ps-am install-qbist_libexecPROGRAMS \ + install-sample_colorize_libexecPROGRAMS \ + install-sharpen_libexecPROGRAMS \ + install-smooth_palette_libexecPROGRAMS \ + install-softglow_libexecPROGRAMS \ + install-sparkle_libexecPROGRAMS \ + install-sphere_designer_libexecPROGRAMS install-strip \ + install-tile_libexecPROGRAMS \ + install-tile_small_libexecPROGRAMS \ + install-unit_editor_libexecPROGRAMS \ + install-van_gogh_lic_libexecPROGRAMS \ + install-warp_libexecPROGRAMS \ + install-wavelet_decompose_libexecPROGRAMS \ + install-web_browser_libexecPROGRAMS \ + install-web_page_libexecPROGRAMS installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-align_layers_libexecPROGRAMS uninstall-am \ + uninstall-animation_optimize_libexecPROGRAMS \ + uninstall-animation_play_libexecPROGRAMS \ + uninstall-blinds_libexecPROGRAMS \ + uninstall-blur_libexecPROGRAMS \ + uninstall-border_average_libexecPROGRAMS \ + uninstall-busy_dialog_libexecPROGRAMS \ + uninstall-cartoon_libexecPROGRAMS \ + uninstall-checkerboard_libexecPROGRAMS \ + uninstall-cml_explorer_libexecPROGRAMS \ + uninstall-color_cube_analyze_libexecPROGRAMS \ + uninstall-color_enhance_libexecPROGRAMS \ + uninstall-colorify_libexecPROGRAMS \ + uninstall-colormap_remap_libexecPROGRAMS \ + uninstall-compose_libexecPROGRAMS \ + uninstall-contrast_retinex_libexecPROGRAMS \ + uninstall-crop_zealous_libexecPROGRAMS \ + uninstall-curve_bend_libexecPROGRAMS \ + uninstall-decompose_libexecPROGRAMS \ + uninstall-depth_merge_libexecPROGRAMS \ + uninstall-despeckle_libexecPROGRAMS \ + uninstall-destripe_libexecPROGRAMS \ + uninstall-edge_dog_libexecPROGRAMS \ + uninstall-emboss_libexecPROGRAMS \ + uninstall-file_aa_libexecPROGRAMS \ + uninstall-file_cel_libexecPROGRAMS \ + uninstall-file_compressor_libexecPROGRAMS \ + uninstall-file_csource_libexecPROGRAMS \ + uninstall-file_desktop_link_libexecPROGRAMS \ + uninstall-file_dicom_libexecPROGRAMS \ + uninstall-file_gbr_libexecPROGRAMS \ + uninstall-file_gegl_libexecPROGRAMS \ + uninstall-file_gif_load_libexecPROGRAMS \ + uninstall-file_gif_save_libexecPROGRAMS \ + uninstall-file_gih_libexecPROGRAMS \ + uninstall-file_glob_libexecPROGRAMS \ + uninstall-file_header_libexecPROGRAMS \ + uninstall-file_heif_libexecPROGRAMS \ + uninstall-file_html_table_libexecPROGRAMS \ + uninstall-file_jp2_load_libexecPROGRAMS \ + uninstall-file_mng_libexecPROGRAMS \ + uninstall-file_pat_libexecPROGRAMS \ + uninstall-file_pcx_libexecPROGRAMS \ + uninstall-file_pdf_load_libexecPROGRAMS \ + uninstall-file_pdf_save_libexecPROGRAMS \ + uninstall-file_pix_libexecPROGRAMS \ + uninstall-file_png_libexecPROGRAMS \ + uninstall-file_pnm_libexecPROGRAMS \ + uninstall-file_ps_libexecPROGRAMS \ + uninstall-file_psp_libexecPROGRAMS \ + uninstall-file_raw_data_libexecPROGRAMS \ + uninstall-file_sunras_libexecPROGRAMS \ + uninstall-file_svg_libexecPROGRAMS \ + uninstall-file_tga_libexecPROGRAMS \ + uninstall-file_wmf_libexecPROGRAMS \ + uninstall-file_xbm_libexecPROGRAMS \ + uninstall-file_xmc_libexecPROGRAMS \ + uninstall-file_xpm_libexecPROGRAMS \ + uninstall-file_xwd_libexecPROGRAMS \ + uninstall-film_libexecPROGRAMS \ + uninstall-filter_pack_libexecPROGRAMS \ + uninstall-fractal_trace_libexecPROGRAMS \ + uninstall-goat_exercise_libexecPROGRAMS \ + uninstall-gradient_map_libexecPROGRAMS \ + uninstall-grid_libexecPROGRAMS \ + uninstall-guillotine_libexecPROGRAMS \ + uninstall-hot_libexecPROGRAMS uninstall-jigsaw_libexecPROGRAMS \ + uninstall-mail_libexecPROGRAMS \ + uninstall-max_rgb_libexecPROGRAMS \ + uninstall-nl_filter_libexecPROGRAMS \ + uninstall-photocopy_libexecPROGRAMS \ + uninstall-plugin_browser_libexecPROGRAMS \ + uninstall-procedure_browser_libexecPROGRAMS \ + uninstall-qbist_libexecPROGRAMS \ + uninstall-sample_colorize_libexecPROGRAMS \ + uninstall-sharpen_libexecPROGRAMS \ + uninstall-smooth_palette_libexecPROGRAMS \ + uninstall-softglow_libexecPROGRAMS \ + uninstall-sparkle_libexecPROGRAMS \ + uninstall-sphere_designer_libexecPROGRAMS \ + uninstall-tile_libexecPROGRAMS \ + uninstall-tile_small_libexecPROGRAMS \ + uninstall-unit_editor_libexecPROGRAMS \ + uninstall-van_gogh_lic_libexecPROGRAMS \ + uninstall-warp_libexecPROGRAMS \ + uninstall-wavelet_decompose_libexecPROGRAMS \ + uninstall-web_browser_libexecPROGRAMS \ + uninstall-web_page_libexecPROGRAMS + +.PRECIOUS: Makefile + + +# `windres` seems a very stupid tool and it breaks with double shlashes +# in parameter paths. Strengthen the rule a little. +@HAVE_WINDRES_TRUE@%.rc.o: +@HAVE_WINDRES_TRUE@ $(WINDRES) --define ORIGINALFILENAME_STR="$*$(EXEEXT)" \ +@HAVE_WINDRES_TRUE@ --define INTERNALNAME_STR="$*" \ +@HAVE_WINDRES_TRUE@ --define TOP_SRCDIR="`echo $(top_srcdir) | sed 's*//*/*'`" \ +@HAVE_WINDRES_TRUE@ -I"`echo $(top_srcdir)/app | sed 's%/\+%/%'`" \ +@HAVE_WINDRES_TRUE@ -I"`echo $(top_builddir)/app | sed 's%/\+%/%'`"\ +@HAVE_WINDRES_TRUE@ -I"`echo $(top_builddir) | sed 's%/\+%/%'`"\ +@HAVE_WINDRES_TRUE@ $(GIMPPLUGINRC) $@ + +install-%: % + @$(NORMAL_INSTALL) + $(mkinstalldirs) $(DESTDIR)$(gimpplugindir)/plug-ins/$< + @p=$<; p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + || test -f $$p1 \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) $$p $(DESTDIR)$(gimpplugindir)/plug-ins/$$p/$$f"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) $$p $(DESTDIR)$(gimpplugindir)/plug-ins/$$p/$$f || exit 1; \ + else :; fi + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/plug-ins/common/align-layers.c b/plug-ins/common/align-layers.c new file mode 100644 index 0000000..9cddc3b --- /dev/null +++ b/plug-ins/common/align-layers.c @@ -0,0 +1,748 @@ +/* align_layers.c + * Author: Shuji Narazaki + * Version: 0.26 + * + * Copyright (C) 1997-1998 Shuji Narazaki + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#define PLUG_IN_PROC "plug-in-align-layers" +#define PLUG_IN_BINARY "align-layers" +#define PLUG_IN_ROLE "gimp-align-layers" +#define SCALE_WIDTH 150 + +enum +{ + H_NONE, + H_COLLECT, + LEFT2RIGHT, + RIGHT2LEFT, + SNAP2HGRID +}; + +enum +{ + H_BASE_LEFT, + H_BASE_CENTER, + H_BASE_RIGHT +}; + +enum +{ + V_NONE, + V_COLLECT, + TOP2BOTTOM, + BOTTOM2TOP, + SNAP2VGRID +}; + +enum +{ + V_BASE_TOP, + V_BASE_CENTER, + V_BASE_BOTTOM +}; + + +typedef struct +{ + gint step_x; + gint step_y; + gint base_x; + gint base_y; +} AlignData; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +/* Main function */ +static GimpPDBStatusType align_layers (gint32 image_id); + +/* Helpers and internal functions */ +static gint align_layers_count_visibles_layers (gint *layers, + gint length); +static gint align_layers_find_last_layer (gint *layers, + gint layers_num, + gboolean *found); +static gint align_layers_spread_visibles_layers (gint *layers, + gint layers_num, + gint *layers_array); +static gint * align_layers_spread_image (gint32 image_id, + gint *layer_num); +static gint align_layers_find_background (gint32 image_id); +static AlignData align_layers_gather_data (gint *layers, + gint layer_num, + gint background); +static void align_layers_perform_alignment (gint *layers, + gint layer_num, + AlignData data); +static void align_layers_get_align_offsets (gint32 drawable_id, + gint *x, + gint *y); +static gint align_layers_dialog (void); + + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +/* dialog variables */ +typedef struct +{ + gint h_style; + gint h_base; + gint v_style; + gint v_base; + gboolean ignore_bottom; + gboolean base_is_bottom_layer; + gint grid_size; +} ValueType; + +static ValueType VALS = +{ + H_NONE, + H_BASE_LEFT, + V_NONE, + V_BASE_TOP, + TRUE, + FALSE, + 10 +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args [] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"}, + { GIMP_PDB_IMAGE, "image", "Input image"}, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (not used)"}, + { GIMP_PDB_INT32, "link-after-alignment", "Link the visible layers after alignment { TRUE, FALSE }"}, + { GIMP_PDB_INT32, "use-bottom", "use the bottom layer as the base of alignment { TRUE, FALSE }"} + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Align all visible layers of the image"), + "Align visible layers", + "Shuji Narazaki ", + "Shuji Narazaki", + "1997", + N_("Align Visi_ble Layers..."), + "RGB*,GRAY*,INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Image/Arrange"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status = GIMP_PDB_EXECUTION_ERROR; + GimpRunMode run_mode; + gint image_id, layer_num; + gint *layers; + + run_mode = param[0].data.d_int32; + image_id = param[1].data.d_int32; + + INIT_I18N (); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + switch ( run_mode ) + { + case GIMP_RUN_INTERACTIVE: + layers = gimp_image_get_layers (image_id, &layer_num); + layer_num = align_layers_count_visibles_layers (layers, + layer_num); + g_free (layers); + if (layer_num < 2) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = _("There are not enough layers to align."); + return; + } + gimp_get_data (PLUG_IN_PROC, &VALS); + VALS.grid_size = MAX (VALS.grid_size, 1); + if (! align_layers_dialog ()) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &VALS); + break; + } + + status = align_layers (image_id); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + if (run_mode == GIMP_RUN_INTERACTIVE && status == GIMP_PDB_SUCCESS) + gimp_set_data (PLUG_IN_PROC, &VALS, sizeof (ValueType)); + + values[0].data.d_status = status; +} + +/* + * Main function + */ +static GimpPDBStatusType +align_layers (gint32 image_id) +{ + gint layer_num = 0; + gint *layers = NULL; + gint background = 0; + AlignData data; + + layers = align_layers_spread_image (image_id, &layer_num); + if (layer_num < 2) + { + g_free (layers); + return GIMP_PDB_EXECUTION_ERROR; + } + + background = align_layers_find_background (image_id); + + /* If we want to ignore the bottom layer and if it's visible */ + if (VALS.ignore_bottom && background == layers[layer_num - 1]) + { + layer_num--; + } + + data = align_layers_gather_data (layers, + layer_num, + background); + + gimp_image_undo_group_start (image_id); + + align_layers_perform_alignment (layers, + layer_num, + data); + + gimp_image_undo_group_end (image_id); + + g_free (layers); + + return GIMP_PDB_SUCCESS; +} + +/* + * Find the bottommost layer, visible or not + * The image must contain at least one layer. + */ +static gint +align_layers_find_last_layer (gint *layers, + gint layers_num, + gboolean *found) +{ + gint i; + + for (i = layers_num - 1; i >= 0; i--) + { + gint item = layers[i]; + + if (gimp_item_is_group (item)) + { + gint *children; + gint children_num; + gint last_layer; + + children = gimp_item_get_children (item, &children_num); + last_layer = align_layers_find_last_layer (children, + children_num, + found); + g_free (children); + if (*found) + return last_layer; + } + else if (gimp_item_is_layer (item)) + { + *found = TRUE; + return item; + } + } + + /* should never happen */ + return -1; +} + +/* + * Return the bottom layer. + */ +static gint +align_layers_find_background (gint32 image_id) +{ + gint *layers; + gint layers_num; + gint background; + gboolean found = FALSE; + + layers = gimp_image_get_layers (image_id, &layers_num); + background = align_layers_find_last_layer (layers, + layers_num, + &found); + g_free (layers); + + return background; +} + +/* + * Fill layers_array with all visible layers. + * layers_array needs to be allocated before the call + */ +static gint +align_layers_spread_visibles_layers (gint *layers, + gint layers_num, + gint *layers_array) +{ + gint i; + gint index = 0; + + for (i = 0; i < layers_num; i++) + { + gint item = layers[i]; + + if (gimp_item_get_visible (item)) + { + if (gimp_item_is_group (item)) + { + gint *children; + gint children_num; + + children = gimp_item_get_children (item, &children_num); + index += align_layers_spread_visibles_layers (children, + children_num, + &(layers_array[index])); + g_free (children); + } + else if (gimp_item_is_layer (item)) + { + layers_array[index] = item; + index++; + } + } + } + + return index; +} + +/* + * Return a contiguous array of all visible layers + */ +static gint * +align_layers_spread_image (gint32 image_id, + gint *layer_num) +{ + gint *layers; + gint *layers_array; + gint layer_num_loc; + + layers = gimp_image_get_layers (image_id, &layer_num_loc); + *layer_num = align_layers_count_visibles_layers (layers, + layer_num_loc); + + layers_array = g_malloc (sizeof (gint) * *layer_num); + + align_layers_spread_visibles_layers (layers, + layer_num_loc, + layers_array); + g_free (layers); + + return layers_array; +} + +static gint +align_layers_count_visibles_layers (gint *layers, + gint length) +{ + gint i; + gint count = 0; + + for (i = 0; i 1) + { + data.step_x = (max_x - min_x) / (layer_num - 1); + data.step_y = (max_y - min_y) / (layer_num - 1); + } + + if ( (VALS.h_style == LEFT2RIGHT) || (VALS.h_style == RIGHT2LEFT)) + data.base_x = min_x; + + if ( (VALS.v_style == TOP2BOTTOM) || (VALS.v_style == BOTTOM2TOP)) + data.base_y = min_y; + + return data; +} + +/* + * Modifies position of each visible layers + * according to data. + */ +static void +align_layers_perform_alignment (gint *layers, + gint layer_num, + AlignData data) +{ + gint index; + + for (index = 0; index < layer_num; index++) + { + gint x = 0; + gint y = 0; + gint orig_x; + gint orig_y; + gint offset_x; + gint offset_y; + + gimp_drawable_offsets (layers[index], &orig_x, &orig_y); + + align_layers_get_align_offsets (layers[index], + &offset_x, + &offset_y); + switch (VALS.h_style) + { + case H_NONE: + x = orig_x; + break; + case H_COLLECT: + x = data.base_x - offset_x; + break; + case LEFT2RIGHT: + x = (data.base_x + index * data.step_x) - offset_x; + break; + case RIGHT2LEFT: + x = (data.base_x + (layer_num - index - 1) * data.step_x) - offset_x; + break; + case SNAP2HGRID: + x = VALS.grid_size + * (int) ((orig_x + offset_x + VALS.grid_size /2) / VALS.grid_size) + - offset_x; + break; + } + + switch (VALS.v_style) + { + case V_NONE: + y = orig_y; + break; + case V_COLLECT: + y = data.base_y - offset_y; + break; + case TOP2BOTTOM: + y = (data.base_y + index * data.step_y) - offset_y; + break; + case BOTTOM2TOP: + y = (data.base_y + (layer_num - index - 1) * data.step_y) - offset_y; + break; + case SNAP2VGRID: + y = VALS.grid_size + * (int) ((orig_y + offset_y + VALS.grid_size / 2) / VALS.grid_size) + - offset_y; + break; + } + + gimp_layer_set_offsets (layers[index], x, y); + } +} + +static void +align_layers_get_align_offsets (gint32 drawable_id, + gint *x, + gint *y) +{ + gint width = gimp_drawable_width (drawable_id); + gint height = gimp_drawable_height (drawable_id); + + switch (VALS.h_base) + { + case H_BASE_LEFT: + *x = 0; + break; + case H_BASE_CENTER: + *x = width / 2; + break; + case H_BASE_RIGHT: + *x = width; + break; + default: + *x = 0; + break; + } + + switch (VALS.v_base) + { + case V_BASE_TOP: + *y = 0; + break; + case V_BASE_CENTER: + *y = height / 2; + break; + case V_BASE_BOTTOM: + *y = height; + break; + default: + *y = 0; + break; + } +} + +static int +align_layers_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *table; + GtkWidget *combo; + GtkWidget *toggle; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Align Visible Layers"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + table = gtk_table_new (7, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + table, FALSE, FALSE, 0); + gtk_widget_show (table); + + combo = gimp_int_combo_box_new (C_("align-style", "None"), H_NONE, + _("Collect"), H_COLLECT, + _("Fill (left to right)"), LEFT2RIGHT, + _("Fill (right to left)"), RIGHT2LEFT, + _("Snap to grid"), SNAP2HGRID, + NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), VALS.h_style); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &VALS.h_style); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Horizontal style:"), 0.0, 0.5, + combo, 2, FALSE); + + + combo = gimp_int_combo_box_new (_("Left edge"), H_BASE_LEFT, + _("Center"), H_BASE_CENTER, + _("Right edge"), H_BASE_RIGHT, + NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), VALS.h_base); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &VALS.h_base); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Ho_rizontal base:"), 0.0, 0.5, + combo, 2, FALSE); + + combo = gimp_int_combo_box_new (C_("align-style", "None"), V_NONE, + _("Collect"), V_COLLECT, + _("Fill (top to bottom)"), TOP2BOTTOM, + _("Fill (bottom to top)"), BOTTOM2TOP, + _("Snap to grid"), SNAP2VGRID, + NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), VALS.v_style); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &VALS.v_style); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_Vertical style:"), 0.0, 0.5, + combo, 2, FALSE); + + combo = gimp_int_combo_box_new (_("Top edge"), V_BASE_TOP, + _("Center"), V_BASE_CENTER, + _("Bottom edge"), V_BASE_BOTTOM, + NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), VALS.v_base); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &VALS.v_base); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("Ver_tical base:"), 0.0, 0.5, + combo, 2, FALSE); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 4, + _("_Grid size:"), SCALE_WIDTH, 0, + VALS.grid_size, 1, 200, 1, 10, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &VALS.grid_size); + + toggle = gtk_check_button_new_with_mnemonic + (_("_Ignore the bottom layer even if visible")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), VALS.ignore_bottom); + gtk_table_attach_defaults (GTK_TABLE (table), toggle, 0, 3, 5, 6); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &VALS.ignore_bottom); + + toggle = gtk_check_button_new_with_mnemonic + (_("_Use the (invisible) bottom layer as the base")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + VALS.base_is_bottom_layer); + gtk_table_attach_defaults (GTK_TABLE (table), toggle, 0, 3, 6, 7); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &VALS.base_is_bottom_layer); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/animation-optimize.c b/plug-ins/common/animation-optimize.c new file mode 100644 index 0000000..e110c13 --- /dev/null +++ b/plug-ins/common/animation-optimize.c @@ -0,0 +1,1340 @@ +/* + * Animation Optimizer plug-in version 1.1.2 + * + * (c) Adam D. Moss, 1997-2003 + * adam@gimp.org + * adam@foxbox.org + * + * GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* +#define EXPERIMENTAL_BACKDROP_CODE +*/ + + +#include "config.h" + +#include + +#include + +#include "libgimp/stdplugins-intl.h" + + +#define OPTIMIZE_PROC "plug-in-animationoptimize" +#define OPTIMIZE_DIFF_PROC "plug-in-animationoptimize-diff" +#define UNOPTIMIZE_PROC "plug-in-animationunoptimize" +#define REMOVE_BACKDROP_PROC "plug-in-animation-remove-backdrop" +#define FIND_BACKDROP_PROC "plug-in-animation-find-backdrop" + + +typedef enum +{ + DISPOSE_UNDEFINED = 0x00, + DISPOSE_COMBINE = 0x01, + DISPOSE_REPLACE = 0x02 +} DisposeType; + + +typedef enum +{ + OPOPTIMIZE = 0L, + OPUNOPTIMIZE = 1L, + OPFOREGROUND = 2L, + OPBACKGROUND = 3L +} operatingMode; + + +/* Declare local functions. */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 do_optimizations (GimpRunMode run_mode, + gboolean diff_only); + +/* tag util functions*/ +static gint parse_ms_tag (const gchar *str); +static DisposeType parse_disposal_tag (const gchar *str); +static DisposeType get_frame_disposal (guint whichframe); +static guint32 get_frame_duration (guint whichframe); +static void remove_disposal_tag (gchar *dest, + gchar *src); +static void remove_ms_tag (gchar *dest, + gchar *src); +static gboolean is_disposal_tag (const gchar *str, + DisposeType *disposal, + gint *taglength); +static gboolean is_ms_tag (const gchar *str, + gint *duration, + gint *taglength); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +/* Global widgets'n'stuff */ +static guint width, height; +static gint32 image_id; +static gint32 new_image_id; +static gint32 total_frames; +static gint32 *layers; +static GimpImageBaseType imagetype; +static GimpImageType drawabletype_alpha; +static guchar pixelstep; +static guchar *palette; +static gint ncolors; +static operatingMode opmode; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" } + }; + static const GimpParamDef return_args[] = + { + { GIMP_PDB_IMAGE, "result", "Resulting image" } + }; + + gimp_install_procedure (OPTIMIZE_PROC, + N_("Modify image to reduce size when saved as GIF animation"), + "This procedure applies various optimizations to" + " a GIMP layer-based animation in an attempt to" + " reduce the final file size. If a frame of the" + " animation can use the 'combine' mode, this" + " procedure attempts to maximize the number of" + " ajdacent pixels having the same color, which" + " improves the compression for some image formats" + " such as GIF or MNG.", + "Adam D. Moss ", + "Adam D. Moss ", + "1997-2003", + N_("Optimize (for _GIF)"), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_args), + args, return_args); + + gimp_install_procedure (OPTIMIZE_DIFF_PROC, + N_("Reduce file size where combining layers is possible"), + "This procedure applies various optimizations to" + " a GIMP layer-based animation in an attempt to" + " reduce the final file size. If a frame of the" + " animation can use the 'combine' mode, this" + " procedure uses a simple difference between the" + " frames.", + "Adam D. Moss ", + "Adam D. Moss ", + "1997-2001", + N_("_Optimize (Difference)"), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_args), + args, return_args); + + gimp_install_procedure (UNOPTIMIZE_PROC, + N_("Remove optimization to make editing easier"), + "This procedure 'simplifies' a GIMP layer-based" + " animation that has been optimized for animation. " + "This makes editing the animation much easier.", + "Adam D. Moss ", + "Adam D. Moss ", + "1997-2001", + N_("_Unoptimize"), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_args), + args, return_args); + + gimp_plugin_menu_register (OPTIMIZE_PROC, "/Filters/Animation"); + gimp_plugin_menu_register (OPTIMIZE_DIFF_PROC, "/Filters/Animation"); + gimp_plugin_menu_register (UNOPTIMIZE_PROC, "/Filters/Animation"); + +#ifdef EXPERIMENTAL_BACKDROP_CODE + gimp_install_procedure (REMOVE_BACKDROP_PROC, + "This procedure attempts to remove the backdrop" + " from a GIMP layer-based animation, leaving" + " the foreground animation over transparency.", + "", + "Adam D. Moss ", + "Adam D. Moss ", + "2001", + N_("_Remove Backdrop"), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_args), + args, return_args); + + gimp_install_procedure (FIND_BACKDROP_PROC, + "This procedure attempts to remove the foreground" + " from a GIMP layer-based animation, leaving" + " a one-layered image containing only the" + " constant backdrop image.", + "", + "Adam D. Moss ", + "Adam D. Moss ", + "2001", + N_("_Find Backdrop"), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_args), + args, return_args); + + gimp_plugin_menu_register (REMOVE_BACKDROP_PROC, "/Filters/Animation"); + gimp_plugin_menu_register (FIND_BACKDROP_PROC, "/Filters/Animation"); +#endif +} + +static void +run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gboolean diff_only = FALSE; + + *nreturn_vals = 2; + *return_vals = values; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3) + { + status = GIMP_PDB_CALLING_ERROR; + } + + /* Check the procedure name we were called with, to decide + what needs to be done. */ + if (strcmp (name, OPTIMIZE_PROC) == 0) + opmode = OPOPTIMIZE; + else if (strcmp (name, OPTIMIZE_DIFF_PROC) == 0) + { + opmode = OPOPTIMIZE; + diff_only = TRUE; + } + else if (strcmp (name, UNOPTIMIZE_PROC) == 0) + opmode = OPUNOPTIMIZE; + else if (strcmp (name, FIND_BACKDROP_PROC) == 0) + opmode = OPBACKGROUND; + else if (strcmp (name, REMOVE_BACKDROP_PROC) == 0) + opmode = OPFOREGROUND; + else + g_error("GAH!!!"); + + if (status == GIMP_PDB_SUCCESS) + { + image_id = param[1].data.d_image; + + new_image_id = do_optimizations (run_mode, diff_only); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush(); + } + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = new_image_id; +} + + + +/* Rendering Functions */ + +static void +total_alpha (guchar *imdata, + guint32 numpix, + guchar bytespp) +{ + /* Set image to total-transparency w/black + */ + + memset (imdata, 0, numpix * bytespp); +} + +static const Babl * +get_format (gint32 drawable_ID) +{ + if (gimp_drawable_is_rgb (drawable_ID)) + { + if (gimp_drawable_has_alpha (drawable_ID)) + return babl_format ("R'G'B'A u8"); + else + return babl_format ("R'G'B' u8"); + } + else if (gimp_drawable_is_gray (drawable_ID)) + { + if (gimp_drawable_has_alpha (drawable_ID)) + return babl_format ("Y'A u8"); + else + return babl_format ("Y' u8"); + } + + return gimp_drawable_get_format (drawable_ID); +} + +static void +compose_row (gint frame_num, + DisposeType dispose, + gint row_num, + guchar *dest, + gint dest_width, + gint32 drawable_ID, + gboolean cleanup) +{ + static guchar *line_buf = NULL; + GeglBuffer *src_buffer; + const Babl *format; + guchar *srcptr; + gint rawx, rawy, rawbpp, rawwidth, rawheight; + gint i; + gboolean has_alpha; + + if (cleanup) + { + if (line_buf) + { + g_free (line_buf); + line_buf = NULL; + } + + return; + } + + if (dispose == DISPOSE_REPLACE) + { + total_alpha (dest, dest_width, pixelstep); + } + + gimp_drawable_offsets (drawable_ID, &rawx, &rawy); + + rawwidth = gimp_drawable_width (drawable_ID); + rawheight = gimp_drawable_height (drawable_ID); + + /* this frame has nothing to give us for this row; return */ + if (row_num >= rawheight + rawy || + row_num < rawy) + return; + + format = get_format (drawable_ID); + + has_alpha = gimp_drawable_has_alpha (drawable_ID); + rawbpp = babl_format_get_bytes_per_pixel (format); + + if (line_buf) + { + g_free (line_buf); + line_buf = NULL; + } + line_buf = g_malloc (rawwidth * rawbpp); + + /* Initialise and fetch the raw new frame row */ + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, row_num - rawy, + rawwidth, 1), 1.0, + format, line_buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + g_object_unref (src_buffer); + + /* render... */ + + srcptr = line_buf; + + for (i=rawx; i=0 && i= 128) + { + for (j=0; j best_count) + { + best_count = count[j][i]; + best_r = red[j][i]; + best_g = green[j][i]; + best_b = blue[j][i]; + } + } + + back_frame[width * pixelstep * row +i*pixelstep + 0] = best_r; + if (pixelstep == 4) + { + back_frame[width * pixelstep * row +i*pixelstep + 1] = + best_g; + back_frame[width * pixelstep * row +i*pixelstep + 2] = + best_b; + } + back_frame[width * pixelstep * row +i*pixelstep +pixelstep-1] = + (best_count == 0) ? 0 : 255; + + if (best_count == 0) + g_warning("yayyyy!"); + } + /* memcpy(&back_frame[width * pixelstep * row], + these_rows[0], + width * pixelstep);*/ + } + + for (this_frame_num=0; this_frame_numrbox_right) rbox_right=xit; + if (yitrbox_bottom) rbox_bottom=yit; + } + if (keep_pix) + { + if (xitbbox_right) bbox_right=xit; + if (yitbbox_bottom) bbox_bottom=yit; + } + else + { + /* pixel didn't change this frame - make + * it transparent in our optimized buffer! + */ + opti_frame[yit*width*pixelstep + xit*pixelstep + + pixelstep-1] = 0; + } + } /* xit */ + } /* yit */ + + if (!can_combine) + { + bbox_left = rbox_left; + bbox_top = rbox_top; + bbox_right = rbox_right; + bbox_bottom = rbox_bottom; + } + + bbox_right++; + bbox_bottom++; + + if (can_combine && !diff_only) + { + /* Try to optimize the pixel data for RLE or LZW compression + * by making some transparent pixels non-transparent if they + * would have the same color as the adjacent pixels. This + * gives a better compression if the algorithm compresses + * the image line by line. + * See: http://bugzilla.gnome.org/show_bug.cgi?id=66367 + * It may not be very efficient to add two additional passes + * over the pixels, but this hopefully makes the code easier + * to maintain and less error-prone. + */ + for (yit = bbox_top; yit < bbox_bottom; yit++) + { + /* Compare with previous pixels from left to right */ + for (xit = bbox_left + 1; xit < bbox_right; xit++) + { + if (!(opti_frame[yit*width*pixelstep + + xit*pixelstep + + pixelstep-1]&128) + && (opti_frame[yit*width*pixelstep + + (xit-1)*pixelstep + + pixelstep-1]&128) + && (last_frame[yit*width*pixelstep + + xit*pixelstep + + pixelstep-1]&128)) + { + for (byteit=0; byteit= bbox_left; xit--) + { + if (!(opti_frame[yit*width*pixelstep + + xit*pixelstep + + pixelstep-1]&128) + && (opti_frame[yit*width*pixelstep + + (xit+1)*pixelstep + + pixelstep-1]&128) + && (last_frame[yit*width*pixelstep + + xit*pixelstep + + pixelstep-1]&128)) + { + for (byteit=0; byteit=length) || (!g_ascii_isdigit (str[offset]))) + return 0; + + do + { + sum *= 10; + sum += str[offset] - '0'; + offset++; + } + while ((offset. + */ + +/* + * TODO: + * pdb interface - should we bother? + * + * speedups (caching? most bottlenecks seem to be in pixelrgns) + * -> do pixelrgns properly! + */ + +#include "config.h" + +#include + +#include +#undef GDK_DISABLE_DEPRECATED +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-animationplay" +#define PLUG_IN_BINARY "animation-play" +#define PLUG_IN_ROLE "gimp-animation-play" +#define DITHERTYPE GDK_RGB_DITHER_NORMAL + + +typedef enum +{ + DISPOSE_COMBINE = 0x00, + DISPOSE_REPLACE = 0x01 +} DisposeType; + +typedef struct +{ + gint duration_index; + DisposeType default_frame_disposal; + guint32 default_frame_duration; +} +AnimationSettings; + +/* for shaping */ +typedef struct +{ + gint x, y; +} CursorOffset; + +/* Declare local functions. */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void initialize (void); +static void build_dialog (gchar *imagename); +static void refresh_dialog (gchar *imagename); + +static void da_size_callback (GtkWidget *widget, + GtkAllocation *allocation, void *data); +static void sda_size_callback (GtkWidget *widget, + GtkAllocation *allocation, void *data); + +static void window_destroy (GtkWidget *widget); +static void play_callback (GtkToggleAction *action); +static void step_back_callback (GtkAction *action); +static void step_callback (GtkAction *action); +static void refresh_callback (GtkAction *action); +static void rewind_callback (GtkAction *action); +static void speed_up_callback (GtkAction *action); +static void speed_down_callback (GtkAction *action); +static void speed_reset_callback (GtkAction *action); +static void framecombo_changed (GtkWidget *combo, + gpointer data); +static void speedcombo_changed (GtkWidget *combo, + gpointer data); +static void fpscombo_changed (GtkWidget *combo, + gpointer data); +static void zoomcombo_activated (GtkEntry *combo, + gpointer data); +static void zoomcombo_changed (GtkWidget *combo, + gpointer data); +static gboolean repaint_sda (GtkWidget *darea, + GdkEventExpose *event, + gpointer data); +static gboolean repaint_da (GtkWidget *darea, + GdkEventExpose *event, + gpointer data); + +static void init_frames (void); +static void render_frame (gint32 whichframe); +static void show_frame (void); +static void total_alpha_preview (void); +static void update_alpha_preview (void); +static void update_combobox (void); +static gdouble get_duration_factor (gint index); +static gint get_fps (gint index); +static gdouble get_scale (gint index); +static void update_scale (gdouble scale); + + +/* tag util functions*/ +static gint parse_ms_tag (const gchar *str); +static DisposeType parse_disposal_tag (const gchar *str); +static gboolean is_disposal_tag (const gchar *str, + DisposeType *disposal, + gint *taglength); +static gboolean is_ms_tag (const gchar *str, + gint *duration, + gint *taglength); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +/* Global widgets'n'stuff */ +static GtkWidget *window = NULL; +static GdkWindow *root_win = NULL; +static GtkUIManager *ui_manager = NULL; +static GtkWidget *progress; +static GtkWidget *speedcombo = NULL; +static GtkWidget *fpscombo = NULL; +static GtkWidget *zoomcombo = NULL; +static GtkWidget *frame_disposal_combo = NULL; + +static gint32 image_id; +static guint width = -1, + height = -1; +static gint32 *layers = NULL; +static gint32 total_layers = 0; + +static GtkWidget *drawing_area = NULL; +static guchar *drawing_area_data = NULL; +static guint drawing_area_width = -1, + drawing_area_height = -1; +static guchar *preview_alpha1_data = NULL; +static guchar *preview_alpha2_data = NULL; + +static GtkWidget *shape_window = NULL; +static GtkWidget *shape_drawing_area = NULL; +static guchar *shape_drawing_area_data = NULL; +static guint shape_drawing_area_width = -1, + shape_drawing_area_height = -1; +static gchar *shape_preview_mask = NULL; + + +static gint32 total_frames = 0; +static gint32 *frames = NULL; +static guchar *rawframe = NULL; +static guint32 *frame_durations = NULL; +static guint frame_number = 0; + +static gboolean playing = FALSE; +static guint timer = 0; +static gboolean detached = FALSE; +static gdouble scale, shape_scale; + +/* Default settings. */ +static AnimationSettings settings = +{ + 3, + DISPOSE_COMBINE, + 100 /* ms */ +}; + +static gint32 frames_image_id = 0; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Preview a GIMP layer-based animation"), + "", + "Adam D. Moss ", + "Adam D. Moss ", + "1997, 1998...", + N_("_Playback..."), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Animation"); + gimp_plugin_icon_register (PLUG_IN_PROC, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) "media-playback-start"); +} + +static void +run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + run_mode = param[0].data.d_int32; + + if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3) + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_get_data (PLUG_IN_PROC, &settings); + image_id = param[1].data.d_image; + + initialize (); + gtk_main (); + gimp_set_data (PLUG_IN_PROC, &settings, sizeof (settings)); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + gimp_image_delete (frames_image_id); + gegl_exit (); +} + +static void +reshape_from_bitmap (const gchar *bitmap) +{ + static gchar *prev_bitmap = NULL; + static guint prev_width = -1; + static guint prev_height = -1; + + if ((!prev_bitmap) || + prev_width != shape_drawing_area_width || prev_height != shape_drawing_area_height || + (memcmp (prev_bitmap, bitmap, (shape_drawing_area_width * shape_drawing_area_height) / 8 + shape_drawing_area_height))) + { + GdkBitmap *shape_mask; + + shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (shape_window), + bitmap, + shape_drawing_area_width, shape_drawing_area_height); + gtk_widget_shape_combine_mask (shape_window, shape_mask, 0, 0); + g_object_unref (shape_mask); + + if (!prev_bitmap || prev_width != shape_drawing_area_width || prev_height != shape_drawing_area_height) + { + g_free(prev_bitmap); + prev_bitmap = g_malloc ((shape_drawing_area_width * shape_drawing_area_height) / 8 + shape_drawing_area_height); + prev_width = shape_drawing_area_width; + prev_height = shape_drawing_area_height; + } + + memcpy (prev_bitmap, bitmap, (shape_drawing_area_width * shape_drawing_area_height) / 8 + shape_drawing_area_height); + } +} + +static gboolean +popup_menu (GtkWidget *widget, + GdkEventButton *event) +{ + GtkWidget *menu = gtk_ui_manager_get_widget (ui_manager, "/anim-play-popup"); + + gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget)); + gtk_menu_popup (GTK_MENU (menu), + NULL, NULL, NULL, NULL, + event ? event->button : 0, + event ? event->time : gtk_get_current_event_time ()); + + return TRUE; +} + +static gboolean +button_press (GtkWidget *widget, + GdkEventButton *event) +{ + if (gdk_event_triggers_context_menu ((GdkEvent *) event)) + return popup_menu (widget, event); + + return FALSE; +} + +/* + * Update the actual drawing area metrics, which may be different as requested, + * because there is no full control of the WM. + * data is always NULL. */ +static void +da_size_callback (GtkWidget *widget, + GtkAllocation *allocation, void *data) +{ + if (allocation->width == drawing_area_width && allocation->height == drawing_area_height) + return; + + drawing_area_width = allocation->width; + drawing_area_height = allocation->height; + scale = MIN ((gdouble) drawing_area_width / (gdouble) width, (gdouble) drawing_area_height / (gdouble) height); + + g_free (drawing_area_data); + drawing_area_data = g_malloc (drawing_area_width * drawing_area_height * 3); + + update_alpha_preview (); + + if (! detached) + { + /* Update the zoom information. */ + GtkEntry *zoomcombo_text_child; + + zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo))); + if (zoomcombo_text_child) + { + char* new_entry_text = g_strdup_printf (_("%.1f %%"), scale * 100.0); + + gtk_entry_set_text (zoomcombo_text_child, new_entry_text); + g_free (new_entry_text); + } + + /* Update the rawframe. */ + g_free (rawframe); + rawframe = g_malloc ((unsigned long) drawing_area_width * drawing_area_height * 4); + + /* As we re-allocated the drawn data, let's render it again. */ + if (frame_number < total_frames) + render_frame (frame_number); + } + else + { + /* Set "alpha grid" background. */ + total_alpha_preview (); + repaint_da(drawing_area, NULL, NULL); + } +} + +/* + * Update the actual shape drawing area metrics, which may be different as requested, + * They *should* be the same as the drawing area, but the safe way is to make sure + * and process it separately. + * data is always NULL. */ +static void +sda_size_callback (GtkWidget *widget, + GtkAllocation *allocation, void *data) +{ + if (allocation->width == shape_drawing_area_width && allocation->height == shape_drawing_area_height) + return; + + shape_drawing_area_width = allocation->width; + shape_drawing_area_height = allocation->height; + shape_scale = MIN ((gdouble) shape_drawing_area_width / (gdouble) width, (gdouble) shape_drawing_area_height / (gdouble) height); + + g_free (shape_drawing_area_data); + g_free (shape_preview_mask); + + shape_drawing_area_data = g_malloc (shape_drawing_area_width * shape_drawing_area_height * 3); + shape_preview_mask = g_malloc ((shape_drawing_area_width * shape_drawing_area_height) / 8 + 1 + shape_drawing_area_height); + + if (detached) + { + /* Update the zoom information. */ + GtkEntry *zoomcombo_text_child; + + zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo))); + if (zoomcombo_text_child) + { + char* new_entry_text = g_strdup_printf (_("%.1f %%"), shape_scale * 100.0); + + gtk_entry_set_text (zoomcombo_text_child, new_entry_text); + g_free (new_entry_text); + } + + /* Update the rawframe. */ + g_free (rawframe); + rawframe = g_malloc ((unsigned long) shape_drawing_area_width * shape_drawing_area_height * 4); + + if (frame_number < total_frames) + render_frame (frame_number); + } +} + +static gboolean +shape_pressed (GtkWidget *widget, + GdkEventButton *event) +{ + if (button_press (widget, event)) + return TRUE; + + /* ignore double and triple click */ + if (event->type == GDK_BUTTON_PRESS) + { + CursorOffset *p = g_object_get_data (G_OBJECT(widget), "cursor-offset"); + + if (!p) + return FALSE; + + p->x = (gint) event->x; + p->y = (gint) event->y; + + gtk_grab_add (widget); + gdk_pointer_grab (gtk_widget_get_window (widget), TRUE, + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK, + NULL, NULL, 0); + gdk_window_raise (gtk_widget_get_window (widget)); + } + + return FALSE; +} + +static gboolean +shape_released (GtkWidget *widget) +{ + gtk_grab_remove (widget); + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), 0); + gdk_flush (); + + return FALSE; +} + +static gboolean +shape_motion (GtkWidget *widget, + GdkEventMotion *event) +{ + GdkModifierType mask; + gint xp, yp; + + gdk_window_get_pointer (root_win, &xp, &yp, &mask); + + /* if a button is still held by the time we process this event... */ + if (mask & GDK_BUTTON1_MASK) + { + CursorOffset *p = g_object_get_data (G_OBJECT (widget), "cursor-offset"); + + if (!p) + return FALSE; + + gtk_window_move (GTK_WINDOW (widget), xp - p->x, yp - p->y); + } + else /* the user has released all buttons */ + { + shape_released (widget); + } + + return FALSE; +} + +static gboolean +repaint_da (GtkWidget *darea, + GdkEventExpose *event, + gpointer data) +{ + GtkStyle *style = gtk_widget_get_style (darea); + + gdk_draw_rgb_image (gtk_widget_get_window (darea), + style->white_gc, + (gint) ((drawing_area_width - scale * width) / 2), + (gint) ((drawing_area_height - scale * height) / 2), + drawing_area_width, drawing_area_height, + (total_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE, + drawing_area_data, drawing_area_width * 3); + + return TRUE; +} + +static gboolean +repaint_sda (GtkWidget *darea, + GdkEventExpose *event, + gpointer data) +{ + GtkStyle *style = gtk_widget_get_style (darea); + + gdk_draw_rgb_image (gtk_widget_get_window (darea), + style->white_gc, + (gint) ((shape_drawing_area_width - shape_scale * width) / 2), + (gint) ((shape_drawing_area_height - shape_scale * height) / 2), + shape_drawing_area_width, shape_drawing_area_height, + (total_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE, + shape_drawing_area_data, shape_drawing_area_width * 3); + + return TRUE; +} + +static void +close_callback (GtkAction *action, + gpointer data) +{ + gtk_widget_destroy (GTK_WIDGET (data)); +} + +static void +help_callback (GtkAction *action, + gpointer data) +{ + gimp_standard_help_func (PLUG_IN_PROC, data); +} + + +static void +detach_callback (GtkToggleAction *action) +{ + gboolean active = gtk_toggle_action_get_active (action); + + if (active == detached) + { + g_warning ("detached state and toggle action got out of sync"); + return; + } + + detached = active; + + if (detached) + { + gint x, y; + + /* Create a total-alpha buffer merely for the not-shaped + drawing area to now display. */ + + gtk_window_set_screen (GTK_WINDOW (shape_window), + gtk_widget_get_screen (drawing_area)); + + gtk_widget_show (shape_window); + + if (!gtk_widget_get_realized (drawing_area)) + gtk_widget_realize (drawing_area); + if (!gtk_widget_get_realized (shape_drawing_area)) + gtk_widget_realize (shape_drawing_area); + + gdk_window_get_origin (gtk_widget_get_window (drawing_area), &x, &y); + + gtk_window_move (GTK_WINDOW (shape_window), x + 6, y + 6); + + gdk_window_set_back_pixmap (gtk_widget_get_window (shape_drawing_area), NULL, TRUE); + + + /* Set "alpha grid" background. */ + total_alpha_preview (); + repaint_da(drawing_area, NULL, NULL); + } + else + gtk_widget_hide (shape_window); + + render_frame (frame_number); +} + +static GtkUIManager * +ui_manager_new (GtkWidget *window) +{ + static GtkActionEntry actions[] = + { + { "step-back", "media-skip-backward", + N_("Step _back"), "d", N_("Step back to previous frame"), + G_CALLBACK (step_back_callback) }, + + { "step", "media-skip-forward", + N_("_Step"), "f", N_("Step to next frame"), + G_CALLBACK (step_callback) }, + + { "rewind", "media-seek-backward", + NULL, NULL, N_("Rewind the animation"), + G_CALLBACK (rewind_callback) }, + + { "refresh", GIMP_ICON_VIEW_REFRESH, + NULL, "R", N_("Reload the image"), + G_CALLBACK (refresh_callback) }, + + { "help", "help-browser", + NULL, NULL, NULL, + G_CALLBACK (help_callback) }, + + { "close", "window-close", + NULL, "W", NULL, + G_CALLBACK (close_callback) + }, + { + "quit", "application-quit", + NULL, "Q", NULL, + G_CALLBACK (close_callback) + }, + { + "speed-up", NULL, + N_("Faster"), "L", N_("Increase the speed of the animation"), + G_CALLBACK (speed_up_callback) + }, + { + "speed-down", NULL, + N_("Slower"), "J", N_("Decrease the speed of the animation"), + G_CALLBACK (speed_down_callback) + }, + { + "speed-reset", NULL, + N_("Reset speed"), "K", N_("Reset the speed of the animation"), + G_CALLBACK (speed_reset_callback) + } + }; + + static GtkToggleActionEntry toggle_actions[] = + { + { "play", "media-playback-start", + NULL, "space", N_("Start playback"), + G_CALLBACK (play_callback), FALSE }, + + { "detach", GIMP_ICON_DETACH, + N_("Detach"), NULL, + N_("Detach the animation from the dialog window"), + G_CALLBACK (detach_callback), FALSE } + }; + + GtkUIManager *ui_manager = gtk_ui_manager_new (); + GtkActionGroup *group = gtk_action_group_new ("Actions"); + GError *error = NULL; + + gtk_action_group_set_translation_domain (group, NULL); + + gtk_action_group_add_actions (group, + actions, + G_N_ELEMENTS (actions), + window); + gtk_action_group_add_toggle_actions (group, + toggle_actions, + G_N_ELEMENTS (toggle_actions), + NULL); + + gtk_window_add_accel_group (GTK_WINDOW (window), + gtk_ui_manager_get_accel_group (ui_manager)); + gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager)); + + gtk_ui_manager_insert_action_group (ui_manager, group, -1); + g_object_unref (group); + + gtk_ui_manager_add_ui_from_string (ui_manager, + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "", + -1, &error); + + if (error) + { + g_warning ("error parsing ui: %s", error->message); + g_clear_error (&error); + } + + gtk_ui_manager_add_ui_from_string (ui_manager, + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "", + -1, &error); + + if (error) + { + g_warning ("error parsing ui: %s", error->message); + g_clear_error (&error); + } + + return ui_manager; +} + +static void +refresh_dialog (gchar *imagename) +{ + gchar *name; + GdkScreen *screen; + guint screen_width, screen_height; + gint window_width, window_height; + + /* Image Name */ + name = g_strconcat (_("Animation Playback:"), " ", imagename, NULL); + gtk_window_set_title (GTK_WINDOW (window), name); + g_free (name); + + /* Update GUI size. */ + screen = gtk_widget_get_screen (window); + screen_height = gdk_screen_get_height (screen); + screen_width = gdk_screen_get_width (screen); + gtk_window_get_size (GTK_WINDOW (window), &window_width, &window_height); + + /* if the *window* size is bigger than the screen size, + * diminish the drawing area by as much, then compute the corresponding scale. */ + if (window_width + 50 > screen_width || window_height + 50 > screen_height) + { + guint expected_drawing_area_width = MAX (1, width - window_width + screen_width); + guint expected_drawing_area_height = MAX (1, height - window_height + screen_height); + gdouble expected_scale = MIN ((gdouble) expected_drawing_area_width / (gdouble) width, + (gdouble) expected_drawing_area_height / (gdouble) height); + update_scale (expected_scale); + + /* There is unfortunately no good way to know the size of the decorations, taskbars, etc. + * So we take a wild guess by making the window slightly smaller to fit into any case. */ + gtk_window_set_default_size (GTK_WINDOW (window), + MIN (expected_drawing_area_width + 20, screen_width - 60), + MIN (expected_drawing_area_height + 90, screen_height - 60)); + + gtk_window_reshow_with_initial_size (GTK_WINDOW (window)); + } +} + +static void +build_dialog (gchar *imagename) +{ + GtkWidget *toolbar; + GtkWidget *frame; + GtkWidget *viewport; + GtkWidget *main_vbox; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *abox; + GtkToolItem *item; + GtkAction *action; + GdkCursor *cursor; + gint index; + gchar *text; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_role (GTK_WINDOW (window), "animation-playback"); + + g_signal_connect (window, "destroy", + G_CALLBACK (window_destroy), + NULL); + g_signal_connect (window, "popup-menu", + G_CALLBACK (popup_menu), + NULL); + + gimp_help_connect (window, gimp_standard_help_func, PLUG_IN_PROC, NULL); + + ui_manager = ui_manager_new (window); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (window), main_vbox); + gtk_widget_show (main_vbox); + + toolbar = gtk_ui_manager_get_widget (ui_manager, "/anim-play-toolbar"); + gtk_box_pack_start (GTK_BOX (main_vbox), toolbar, FALSE, FALSE, 0); + gtk_widget_show (toolbar); + + item = + GTK_TOOL_ITEM (gtk_ui_manager_get_widget (ui_manager, + "/anim-play-toolbar/space")); + gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (item), FALSE); + gtk_tool_item_set_expand (item, TRUE); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + /* Alignment for the scrolling window, which can be resized by the user. */ + abox = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_box_pack_start (GTK_BOX (vbox), abox, TRUE, TRUE, 0); + gtk_widget_show (abox); + + frame = gtk_scrolled_window_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (abox), frame); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (frame), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_show (frame); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (frame), viewport); + gtk_widget_show (viewport); + + /* I add the drawing area inside an alignment box to prevent it from being resized. */ + abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (viewport), abox); + gtk_widget_show (abox); + + /* Build a drawing area, with a default size same as the image */ + drawing_area = gtk_drawing_area_new (); + gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK); + gtk_container_add (GTK_CONTAINER (abox), drawing_area); + gtk_widget_show (drawing_area); + + g_signal_connect (drawing_area, "size-allocate", + G_CALLBACK(da_size_callback), + NULL); + g_signal_connect (drawing_area, "button-press-event", + G_CALLBACK (button_press), + NULL); + + /* Lower option bar. */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* Progress bar. */ + + progress = gtk_progress_bar_new (); + gtk_box_pack_end (GTK_BOX (hbox), progress, TRUE, TRUE, 0); + gtk_widget_show (progress); + + /* Zoom */ + zoomcombo = gtk_combo_box_text_new_with_entry (); + gtk_box_pack_end (GTK_BOX (hbox), zoomcombo, FALSE, FALSE, 0); + gtk_widget_show (zoomcombo); + for (index = 0; index < 5; index++) + { + /* list is given in "fps" - frames per second */ + text = g_strdup_printf (_("%.1f %%"), get_scale (index) * 100.0); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (zoomcombo), text); + g_free (text); + } + + gtk_combo_box_set_active (GTK_COMBO_BOX (zoomcombo), 2); /* 1.0 by default. */ + + g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo))), + "activate", + G_CALLBACK (zoomcombo_activated), + NULL); + g_signal_connect (zoomcombo, "changed", + G_CALLBACK (zoomcombo_changed), + NULL); + + gimp_help_set_help_data (zoomcombo, _("Zoom"), NULL); + + /* fps combo */ + fpscombo = gtk_combo_box_text_new (); + gtk_box_pack_end (GTK_BOX (hbox), fpscombo, FALSE, FALSE, 0); + gtk_widget_show (fpscombo); + + for (index = 0; index < 9; index++) + { + /* list is given in "fps" - frames per second */ + text = g_strdup_printf (_("%d fps"), get_fps (index)); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (fpscombo), text); + g_free (text); + if (settings.default_frame_duration == 1000 / get_fps(index)) + gtk_combo_box_set_active (GTK_COMBO_BOX (fpscombo), index); + } + + g_signal_connect (fpscombo, "changed", + G_CALLBACK (fpscombo_changed), + NULL); + + gimp_help_set_help_data (fpscombo, _("Default framerate"), NULL); + + /* Speed Combo */ + speedcombo = gtk_combo_box_text_new (); + gtk_box_pack_end (GTK_BOX (hbox), speedcombo, FALSE, FALSE, 0); + gtk_widget_show (speedcombo); + + for (index = 0; index < 7; index++) + { + text = g_strdup_printf ("%g\303\227", (100 / get_duration_factor (index)) / 100); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (speedcombo), text); + g_free (text); + } + + gtk_combo_box_set_active (GTK_COMBO_BOX (speedcombo), settings.duration_index); + + g_signal_connect (speedcombo, "changed", + G_CALLBACK (speedcombo_changed), + NULL); + + gimp_help_set_help_data (speedcombo, _("Playback speed"), NULL); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-reset"); + gtk_action_set_sensitive (action, FALSE); + + /* Set up the frame disposal combo. */ + frame_disposal_combo = gtk_combo_box_text_new (); + + /* 2 styles of default frame disposals: cumulative layers and one frame per layer. */ + text = g_strdup (_("Cumulative layers (combine)")); + gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (frame_disposal_combo), DISPOSE_COMBINE, text); + g_free (text); + + text = g_strdup (_("One frame per layer (replace)")); + gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (frame_disposal_combo), DISPOSE_REPLACE, text); + g_free (text); + + gtk_combo_box_set_active (GTK_COMBO_BOX (frame_disposal_combo), settings.default_frame_disposal); + + g_signal_connect (frame_disposal_combo, "changed", + G_CALLBACK (framecombo_changed), + NULL); + + gtk_box_pack_end (GTK_BOX (hbox), frame_disposal_combo, FALSE, FALSE, 0); + gtk_widget_show (frame_disposal_combo); + + gtk_window_set_resizable (GTK_WINDOW (window), TRUE); + gtk_window_set_default_size (GTK_WINDOW (window), width + 20, height + 90); + gtk_widget_show (window); + + /* shape_drawing_area for detached feature. */ + shape_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_resizable (GTK_WINDOW (shape_window), FALSE); + + shape_drawing_area = gtk_drawing_area_new (); + gtk_container_add (GTK_CONTAINER (shape_window), shape_drawing_area); + gtk_widget_show (shape_drawing_area); + gtk_widget_add_events (shape_drawing_area, GDK_BUTTON_PRESS_MASK); + gtk_widget_realize (shape_drawing_area); + + gdk_window_set_back_pixmap (gtk_widget_get_window (shape_window), NULL, FALSE); + + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (shape_window), + GDK_HAND2); + gdk_window_set_cursor (gtk_widget_get_window (shape_window), cursor); + gdk_cursor_unref (cursor); + + g_signal_connect(shape_drawing_area, "size-allocate", + G_CALLBACK(sda_size_callback), + NULL); + g_signal_connect (shape_window, "button-press-event", + G_CALLBACK (shape_pressed), + NULL); + g_signal_connect (shape_window, "button-release-event", + G_CALLBACK (shape_released), + NULL); + g_signal_connect (shape_window, "motion-notify-event", + G_CALLBACK (shape_motion), + NULL); + + g_object_set_data (G_OBJECT (shape_window), + "cursor-offset", g_new0 (CursorOffset, 1)); + + g_signal_connect (drawing_area, "expose-event", + G_CALLBACK (repaint_da), + NULL); + + g_signal_connect (shape_drawing_area, "expose-event", + G_CALLBACK (repaint_sda), + NULL); + + /* We request a minimum size *after* having connecting the + * size-allocate signal for correct initialization. */ + gtk_widget_set_size_request (drawing_area, width, height); + gtk_widget_set_size_request (shape_drawing_area, width, height); + + root_win = gdk_get_default_root_window (); +} + +static void +init_frames (void) +{ + /* Frames are associated to an unused image. */ + gint i; + gint32 new_frame, previous_frame, new_layer; + gboolean animated; + GtkAction *action; + gint duration = 0; + DisposeType disposal = settings.default_frame_disposal; + gchar *layer_name; + + total_frames = total_layers; + + /* Cleanup before re-generation. */ + if (frames) + { + gimp_image_delete (frames_image_id); + g_free (frames); + g_free (frame_durations); + } + frames = g_try_malloc0_n (total_frames, sizeof (gint32)); + frame_durations = g_try_malloc0_n (total_frames, sizeof (guint32)); + if (! frames || ! frame_durations) + { + gimp_message (_("Memory could not be allocated to the frame container.")); + gtk_main_quit (); + gimp_quit (); + return; + } + /* We only use RGB images for display because indexed images would somehow + render terrible colors. Layers from other types will be automatically + converted. */ + frames_image_id = gimp_image_new (width, height, GIMP_RGB); + /* Save processing time and memory by not saving history and merged frames. */ + gimp_image_undo_disable (frames_image_id); + + for (i = 0; i < total_frames; i++) + { + layer_name = gimp_item_get_name (layers[total_layers - (i + 1)]); + if (layer_name) + { + duration = parse_ms_tag (layer_name); + disposal = parse_disposal_tag (layer_name); + g_free (layer_name); + } + + if (i > 0 && disposal != DISPOSE_REPLACE) + { + previous_frame = gimp_layer_copy (frames[i - 1]); + gimp_image_insert_layer (frames_image_id, previous_frame, 0, -1); + gimp_item_set_visible (previous_frame, TRUE); + } + new_layer = gimp_layer_new_from_drawable (layers[total_layers - (i + 1)], frames_image_id); + gimp_image_insert_layer (frames_image_id, new_layer, 0, -1); + gimp_item_set_visible (new_layer, TRUE); + new_frame = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE); + frames[i] = new_frame; + gimp_item_set_visible (new_frame, FALSE); + + if (duration <= 0) + duration = settings.default_frame_duration; + frame_durations[i] = (guint32) duration; + } + + /* Update the UI. */ + animated = total_frames >= 2; + action = gtk_ui_manager_get_action (ui_manager, + "/ui/anim-play-toolbar/play"); + gtk_action_set_sensitive (action, animated); + + action = gtk_ui_manager_get_action (ui_manager, + "/ui/anim-play-toolbar/step-back"); + gtk_action_set_sensitive (action, animated); + + action = gtk_ui_manager_get_action (ui_manager, + "/ui/anim-play-toolbar/step"); + gtk_action_set_sensitive (action, animated); + + action = gtk_ui_manager_get_action (ui_manager, + "/ui/anim-play-toolbar/rewind"); + gtk_action_set_sensitive (action, animated); + + /* Keep the same frame number, unless it is now invalid. */ + if (frame_number >= total_frames) + frame_number = 0; +} + +static void +initialize (void) +{ + /* Freeing existing data after a refresh. */ + g_free (layers); + + /* Catch the case when the user has closed the image in the meantime. */ + if (! gimp_image_is_valid (image_id)) + { + gimp_message (_("Invalid image. Did you close it?")); + gtk_main_quit (); + return; + } + + width = gimp_image_width (image_id); + height = gimp_image_height (image_id); + layers = gimp_image_get_layers (image_id, &total_layers); + + if (!window) + build_dialog (gimp_image_get_name (image_id)); + refresh_dialog (gimp_image_get_name (image_id)); + + init_frames (); + render_frame (frame_number); + show_frame (); +} + +/* Rendering Functions */ + +static void +render_frame (gint32 whichframe) +{ + GeglBuffer *buffer; + gint i, j, k; + guchar *srcptr; + guchar *destptr; + GtkWidget *da; + guint drawing_width, drawing_height; + gdouble drawing_scale; + guchar *preview_data; + + g_assert (whichframe < total_frames); + + if (detached) + { + da = shape_drawing_area; + preview_data = shape_drawing_area_data; + drawing_width = shape_drawing_area_width; + drawing_height = shape_drawing_area_height; + drawing_scale = shape_scale; + } + else + { + da = drawing_area; + preview_data = drawing_area_data; + drawing_width = drawing_area_width; + drawing_height = drawing_area_height; + drawing_scale = scale; + + /* Set "alpha grid" background. */ + total_alpha_preview (); + } + + buffer = gimp_drawable_get_buffer (frames[whichframe]); + + /* Fetch and scale the whole raw new frame */ + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, drawing_width, drawing_height), + drawing_scale, babl_format ("R'G'B'A u8"), + rawframe, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP); + + /* Number of pixels. */ + i = drawing_width * drawing_height; + destptr = preview_data; + srcptr = rawframe; + while (i--) + { + if (! (srcptr[3] & 128)) + { + srcptr += 4; + destptr += 3; + continue; + } + + *(destptr++) = *(srcptr++); + *(destptr++) = *(srcptr++); + *(destptr++) = *(srcptr++); + + srcptr++; + } + + /* calculate the shape mask */ + if (detached) + { + memset (shape_preview_mask, 0, (drawing_width * drawing_height) / 8 + drawing_height); + srcptr = rawframe + 3; + + for (j = 0; j < drawing_height; j++) + { + k = j * ((7 + drawing_width) / 8); + + for (i = 0; i < drawing_width; i++) + { + if ((*srcptr) & 128) + shape_preview_mask[k + i/8] |= (1 << (i&7)); + + srcptr += 4; + } + } + reshape_from_bitmap (shape_preview_mask); + } + + /* Display the preview buffer. */ + if (gtk_widget_get_realized (da)) + gdk_draw_rgb_image (gtk_widget_get_window (da), + (gtk_widget_get_style (da))->white_gc, + (gint) ((drawing_width - drawing_scale * width) / 2), + (gint) ((drawing_height - drawing_scale * height) / 2), + drawing_width, drawing_height, + (total_frames == 1 ? + GDK_RGB_DITHER_MAX : DITHERTYPE), + preview_data, drawing_width * 3); + + /* clean up */ + g_object_unref (buffer); +} + +static void +show_frame (void) +{ + gchar *text; + + /* update the dialog's progress bar */ + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), + ((gfloat) frame_number / + (gfloat) (total_frames - 0.999))); + + text = g_strdup_printf (_("Frame %d of %d"), frame_number + 1, total_frames); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), text); + g_free (text); +} + +static void +update_alpha_preview (void) +{ + gint i; + + g_free (preview_alpha1_data); + g_free (preview_alpha2_data); + + preview_alpha1_data = g_malloc (drawing_area_width * 3); + preview_alpha2_data = g_malloc (drawing_area_width * 3); + + for (i = 0; i < drawing_area_width; i++) + { + if (i & 8) + { + preview_alpha1_data[i*3 + 0] = + preview_alpha1_data[i*3 + 1] = + preview_alpha1_data[i*3 + 2] = 102; + preview_alpha2_data[i*3 + 0] = + preview_alpha2_data[i*3 + 1] = + preview_alpha2_data[i*3 + 2] = 154; + } + else + { + preview_alpha1_data[i*3 + 0] = + preview_alpha1_data[i*3 + 1] = + preview_alpha1_data[i*3 + 2] = 154; + preview_alpha2_data[i*3 + 0] = + preview_alpha2_data[i*3 + 1] = + preview_alpha2_data[i*3 + 2] = 102; + } + } +} + +static void +total_alpha_preview (void) +{ + gint i; + + for (i = 0; i < drawing_area_height; i++) + { + if (i & 8) + memcpy (&drawing_area_data[i * 3 * drawing_area_width], preview_alpha1_data, 3 * drawing_area_width); + else + memcpy (&drawing_area_data[i * 3 * drawing_area_width], preview_alpha2_data, 3 * drawing_area_width); + } +} + +/* Util. */ + +static void +remove_timer (void) +{ + if (timer) + { + g_source_remove (timer); + timer = 0; + } +} + +static void +do_back_step (void) +{ + if (frame_number == 0) + frame_number = total_frames - 1; + else + frame_number = (frame_number - 1) % total_frames; + render_frame (frame_number); +} + +static void +do_step (void) +{ + frame_number = (frame_number + 1) % total_frames; + render_frame (frame_number); +} + + +/* Callbacks */ + +static void +window_destroy (GtkWidget *widget) +{ + if (playing) + remove_timer (); + + if (shape_window) + gtk_widget_destroy (GTK_WIDGET (shape_window)); + + gtk_main_quit (); +} + + +static gint +advance_frame_callback (gpointer data) +{ + gdouble duration; + + remove_timer(); + + duration = frame_durations[(frame_number + 1) % total_frames]; + + timer = g_timeout_add (duration * get_duration_factor (settings.duration_index), + advance_frame_callback, NULL); + + do_step (); + show_frame (); + + return FALSE; +} + + +static void +play_callback (GtkToggleAction *action) +{ + if (playing) + remove_timer (); + + playing = gtk_toggle_action_get_active (action); + + if (playing) + { + timer = g_timeout_add ((gdouble) frame_durations[frame_number] * + get_duration_factor (settings.duration_index), + advance_frame_callback, NULL); + + gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-pause"); + } + else + { + gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-start"); + } + + g_object_set (action, + "tooltip", playing ? _("Stop playback") : _("Start playback"), + NULL); +} + +static gdouble +get_duration_factor (gint index) +{ + switch (index) + { + case 0: + return 0.125; + case 1: + return 0.25; + case 2: + return 0.5; + case 3: + return 1.0; + case 4: + return 2.0; + case 5: + return 4.0; + case 6: + return 8.0; + default: + return 1.0; + } +} + +static gint +get_fps (gint index) +{ + switch (index) + { + case 0: + return 10; + case 1: + return 12; + case 2: + return 15; + case 3: + return 24; + case 4: + return 25; + case 5: + return 30; + case 6: + return 50; + case 7: + return 60; + case 8: + return 72; + default: + return 10; + } +} + +static gdouble +get_scale (gint index) +{ + switch (index) + { + case 0: + return 0.51; + case 1: + return 1.0; + case 2: + return 1.25; + case 3: + return 1.5; + case 4: + return 2.0; + default: + { + /* likely -1 returned if there is no active item from the list. + * Try a text conversion, locale-aware in such a case, assuming people write in percent. */ + gchar* active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (zoomcombo)); + gdouble zoom = g_strtod (active_text, NULL); + + /* Negative scales are inconsistent. And we want to avoid huge scaling. */ + if (zoom > 400.0) + zoom = 400.0; + else if (zoom <= 50.0) + /* FIXME: scales under 0.5 are broken. See bug 690265. */ + zoom = 50.1; + g_free (active_text); + return zoom / 100.0; + } + } +} + +static void +step_back_callback (GtkAction *action) +{ + if (playing) + gtk_action_activate (gtk_ui_manager_get_action (ui_manager, + "/anim-play-toolbar/play")); + do_back_step(); + show_frame(); +} + +static void +step_callback (GtkAction *action) +{ + if (playing) + gtk_action_activate (gtk_ui_manager_get_action (ui_manager, + "/anim-play-toolbar/play")); + do_step(); + show_frame(); +} + +static void +refresh_callback (GtkAction *action) +{ + initialize (); +} + +static void +rewind_callback (GtkAction *action) +{ + if (playing) + gtk_action_activate (gtk_ui_manager_get_action (ui_manager, + "/anim-play-toolbar/play")); + frame_number = 0; + render_frame (frame_number); + show_frame (); +} + +static void +speed_up_callback (GtkAction *action) +{ + if (settings.duration_index > 0) + --settings.duration_index; + + gtk_action_set_sensitive (action, settings.duration_index > 0); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-reset"); + gtk_action_set_sensitive (action, settings.duration_index != 3); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-down"); + gtk_action_set_sensitive (action, TRUE); + + update_combobox (); +} + +static void +speed_down_callback (GtkAction *action) +{ + if (settings.duration_index < 6) + ++settings.duration_index; + + gtk_action_set_sensitive (action, settings.duration_index < 6); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-reset"); + gtk_action_set_sensitive (action, settings.duration_index != 3); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-up"); + gtk_action_set_sensitive (action, TRUE); + + update_combobox (); +} + +static void +speed_reset_callback (GtkAction *action) +{ + settings.duration_index = 3; + + gtk_action_set_sensitive (action, FALSE); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-down"); + gtk_action_set_sensitive (action, TRUE); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-up"); + gtk_action_set_sensitive (action, TRUE); + + update_combobox (); +} + +static void +framecombo_changed (GtkWidget *combo, gpointer data) +{ + settings.default_frame_disposal = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); + init_frames (); + render_frame (frame_number); +} + +static void +speedcombo_changed (GtkWidget *combo, gpointer data) +{ + GtkAction * action; + + settings.duration_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-reset"); + gtk_action_set_sensitive (action, settings.duration_index != 3); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-down"); + gtk_action_set_sensitive (action, settings.duration_index < 6); + + action = gtk_ui_manager_get_action (ui_manager, + "/anim-play-popup/speed-up"); + gtk_action_set_sensitive (action, settings.duration_index > 0); +} + +static void +update_scale (gdouble scale) +{ + guint expected_drawing_area_width; + guint expected_drawing_area_height; + + /* FIXME: scales under 0.5 are broken. See bug 690265. */ + if (scale <= 0.5) + scale = 0.501; + + expected_drawing_area_width = width * scale; + expected_drawing_area_height = height * scale; + + gtk_widget_set_size_request (drawing_area, expected_drawing_area_width, expected_drawing_area_height); + gtk_widget_set_size_request (shape_drawing_area, expected_drawing_area_width, expected_drawing_area_height); + /* I force the shape window to a smaller size if we scale down. */ + if (detached) + { + gint x, y; + + gdk_window_get_origin (gtk_widget_get_window (shape_window), &x, &y); + gtk_window_reshow_with_initial_size (GTK_WINDOW (shape_window)); + gtk_window_move (GTK_WINDOW (shape_window), x, y); + } +} + +/* + * Callback emitted when the user hits the Enter key of the zoom combo. + */ +static void +zoomcombo_activated (GtkEntry *combo, gpointer data) +{ + update_scale (get_scale (-1)); +} + +/* + * Callback emitted when the user selects a zoom in the dropdown, + * or edits the text entry. + * We don't want to process manual edits because it greedily emits + * signals after each character deleted or added. + */ +static void +zoomcombo_changed (GtkWidget *combo, gpointer data) +{ + gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); + + /* If no index, user is probably editing by hand. We wait for him to click "Enter". */ + if (index != -1) + update_scale (get_scale (index)); +} + +static void +fpscombo_changed (GtkWidget *combo, gpointer data) +{ + settings.default_frame_duration = 1000 / get_fps (gtk_combo_box_get_active (GTK_COMBO_BOX (combo))); +} + +static void +update_combobox (void) +{ + gtk_combo_box_set_active (GTK_COMBO_BOX (speedcombo), settings.duration_index); +} + +/* tag util. */ + +static gboolean +is_ms_tag (const gchar *str, + gint *duration, + gint *taglength) +{ + gint sum = 0; + gint offset; + gint length; + + length = strlen(str); + + if (str[0] != '(') + return FALSE; + + offset = 1; + + /* eat any spaces between open-parenthesis and number */ + while ((offset < length) && (str[offset] == ' ')) + offset++; + + if ((offset>=length) || (!g_ascii_isdigit (str[offset]))) + return FALSE; + + do + { + sum *= 10; + sum += str[offset] - '0'; + offset++; + } + while ((offset. + * + * A fair proprotion of this code was taken from the Whirl plug-in + * which was copyrighted by Federico Mena Quintero (as below). + * + * Whirl plug-in --- distort an image into a whirlpool + * Copyright (C) 1997 Federico Mena Quintero + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +/***** Magic numbers *****/ + +#define PLUG_IN_PROC "plug-in-blinds" +#define PLUG_IN_BINARY "blinds" +#define PLUG_IN_ROLE "gimp-blinds" + +#define SCALE_WIDTH 150 + +#define MAX_FANS 100 + +/* Variables set in dialog box */ +typedef struct data +{ + gint angledsp; + gint numsegs; + GimpOrientationType orientation; + gboolean bg_trans; +} BlindVals; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean blinds_dialog (gint32 drawable_id); + +static void dialog_update_preview (gpointer drawable_id, + GimpPreview *preview); +static void apply_blinds (gint32 drawable_id); + + +/* Array to hold each size of fans. And no there are not each the + * same size (rounding errors...) + */ + +static gint fanwidths[MAX_FANS]; + +/* Values when first invoked */ +static BlindVals bvals = +{ + 30, + 3, + GIMP_ORIENTATION_HORIZONTAL, + FALSE +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "angle-dsp", "Angle of Displacement" }, + { GIMP_PDB_INT32, "num-segments", "Number of segments in blinds" }, + { GIMP_PDB_INT32, "orientation", "The orientation { ORIENTATION-HORIZONTAL (0), ORIENTATION-VERTICAL (1) }" }, + { GIMP_PDB_INT32, "bg-transparent", "Background transparent { FALSE, TRUE }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Simulate an image painted on window blinds"), + "More here later", + "Andy Thomas", + "Andy Thomas", + "1997", + N_("_Blinds..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + gint32 drawable_id; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + run_mode = param[0].data.d_int32; + drawable_id = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &bvals); + if (! blinds_dialog (drawable_id)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 7) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + bvals.angledsp = param[3].data.d_int32; + bvals.numsegs = param[4].data.d_int32; + bvals.orientation = param[5].data.d_int32; + bvals.bg_trans = param[6].data.d_int32; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &bvals); + break; + + default: + break; + } + + if (gimp_drawable_is_rgb (drawable_id) || + gimp_drawable_is_gray (drawable_id)) + { + gimp_progress_init (_("Adding blinds")); + + apply_blinds (drawable_id); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &bvals, sizeof (BlindVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + values[0].data.d_status = status; +} + + +static gboolean +blinds_dialog (gint32 drawable_id) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *table; + GtkObject *size_data; + GtkWidget *toggle; + GtkWidget *horizontal; + GtkWidget *vertical; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Blinds"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_aspect_preview_new_from_drawable_id (drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (dialog_update_preview), + GINT_TO_POINTER (drawable_id)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + frame = + gimp_int_radio_group_new (TRUE, _("Orientation"), + G_CALLBACK (gimp_radio_button_update), + &bvals.orientation, bvals.orientation, + + _("_Horizontal"), GIMP_ORIENTATION_HORIZONTAL, + &horizontal, + + _("_Vertical"), GIMP_ORIENTATION_VERTICAL, + &vertical, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + g_signal_connect_swapped (horizontal, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (vertical, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + frame = gimp_frame_new (_("Background")); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + toggle = gtk_check_button_new_with_mnemonic (_("_Transparent")); + gtk_container_add (GTK_CONTAINER (frame), toggle); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &bvals.bg_trans); + g_signal_connect_swapped (toggle, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), bvals.bg_trans); + + if (! gimp_drawable_has_alpha (drawable_id)) + { + gtk_widget_set_sensitive (toggle, FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE); + } + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 2); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + size_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Displacement:"), SCALE_WIDTH, 0, + bvals.angledsp, 1, 90, 1, 15, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (size_data, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &bvals.angledsp); + g_signal_connect_swapped (size_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + size_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Number of segments:"), SCALE_WIDTH, 0, + bvals.numsegs, 1, MAX_FANS, 1, 2, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (size_data, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &bvals.numsegs); + g_signal_connect_swapped (size_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +blindsapply (guchar *srow, + guchar *drow, + gint width, + gint bpp, + guchar *bg) +{ + guchar *src; + guchar *dst; + gint i,j,k; + gdouble ang; + gint available; + + /* Make the row 'shrink' around points along its length */ + /* The bvals.numsegs determines how many segments to slip it in to */ + /* The angle is the conceptual 'rotation' of each of these segments */ + + /* Note the row is considered to be made up of a two dim array actual + * pixel locations and the RGB color at these locations. + */ + + /* In the process copy the src row to the destination row */ + + /* Fill in with background color ? */ + for (i = 0 ; i < width ; i++) + { + dst = &drow[i*bpp]; + + for (j = 0 ; j < bpp; j++) + { + dst[j] = bg[j]; + } + } + + /* Apply it */ + + available = width; + for (i = 0; i < bvals.numsegs; i++) + { + /* Width of segs are variable */ + fanwidths[i] = available / (bvals.numsegs - i); + available -= fanwidths[i]; + } + + /* do center points first - just for fun...*/ + available = 0; + for (k = 1; k <= bvals.numsegs; k++) + { + int point; + + point = available + fanwidths[k - 1] / 2; + + available += fanwidths[k - 1]; + + src = &srow[point * bpp]; + dst = &drow[point * bpp]; + + /* Copy pixels across */ + for (j = 0 ; j < bpp; j++) + { + dst[j] = src[j]; + } + } + + /* Disp for each point */ + ang = (bvals.angledsp * 2 * G_PI) / 360; /* Angle in rads */ + ang = (1 - fabs (cos (ang))); + + available = 0; + for (k = 0 ; k < bvals.numsegs; k++) + { + int dx; /* Amount to move by */ + int fw; + + for (i = 0 ; i < (fanwidths[k]/2) ; i++) + { + /* Copy pixels across of left half of fan */ + fw = fanwidths[k] / 2; + dx = (int) (ang * ((double) (fw - (double)(i % fw)))); + + src = &srow[(available + i) * bpp]; + dst = &drow[(available + i + dx) * bpp]; + + for (j = 0; j < bpp; j++) + { + dst[j] = src[j]; + } + + /* Right side */ + j = i + 1; + src = &srow[(available + fanwidths[k] - j + - (fanwidths[k] % 2)) * bpp]; + dst = &drow[(available + fanwidths[k] - j + - (fanwidths[k] % 2) - dx) * bpp]; + + for (j = 0; j < bpp; j++) + { + dst[j] = src[j]; + } + } + + available += fanwidths[k]; + } +} + +static void +dialog_update_preview (gpointer drawable_ptr, + GimpPreview *preview) +{ + gint32 drawable_id = GPOINTER_TO_INT (drawable_ptr); + gint y; + guchar *p, *buffer, *cache; + GimpRGB background; + guchar bg[4]; + gint width, height, bpp; + + gimp_preview_get_size (preview, &width, &height); + cache = gimp_drawable_get_thumbnail_data (drawable_id, + &width, &height, &bpp); + p = cache; + + gimp_context_get_background (&background); + + if (bvals.bg_trans) + gimp_rgb_set_alpha (&background, 0.0); + + if (gimp_drawable_is_gray (drawable_id)) + { + bg[0] = gimp_rgb_luminance_uchar (&background); + gimp_rgba_get_uchar (&background, NULL, NULL, NULL, bg + 3); + } + else + { + gimp_rgba_get_uchar (&background, bg, bg + 1, bg + 2, bg + 3); + } + + buffer = g_new (guchar, width * height * bpp); + + if (bvals.orientation == GIMP_ORIENTATION_VERTICAL) + { + for (y = 0; y < height; y++) + { + blindsapply (p, + buffer + y * width * bpp, + width, + bpp, bg); + p += width * bpp; + } + } + else + { + /* Horizontal blinds */ + /* Apply the blinds algo to a single column - + * this act as a transfomation matrix for the + * rows. Make row 0 invalid so we can find it again! + */ + gint i; + guchar *sr = g_new (guchar, height * 4); + guchar *dr = g_new0 (guchar, height * 4); + guchar dummybg[4] = {0, 0, 0, 0}; + + /* Fill in with background color ? */ + for (i = 0 ; i < width ; i++) + { + gint j; + gint bd = bpp; + guchar *dst; + dst = &buffer[i * bd]; + + for (j = 0 ; j < bd; j++) + { + dst[j] = bg[j]; + } + } + + for ( y = 0 ; y < height; y++) + { + sr[y] = y+1; + } + + /* Bit of a fiddle since blindsapply really works on an image + * row not a set of bytes. - preview can't be > 255 + * or must make dr sr int rows. + */ + blindsapply (sr, dr, height, 1, dummybg); + + for (y = 0; y < height; y++) + { + if (dr[y] == 0) + { + /* Draw background line */ + p = buffer; + } + else + { + /* Draw line from src */ + p = cache + + (width * bpp * (dr[y] - 1)); + } + memcpy (buffer + y * width * bpp, + p, + width * bpp); + } + g_free (sr); + g_free (dr); + } + + gimp_preview_draw_buffer (preview, buffer, width * bpp); + + g_free (buffer); + g_free (cache); +} + +/* STEP tells us how many rows/columns to gulp down in one go... */ +/* Note all the "4" literals around here are to do with the depths + * of the images. Makes it easier to deal with for my small brain. + */ + +#define STEP 40 + +static void +apply_blinds (gint32 drawable_id) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + guchar *src_rows, *des_rows; + gint bytes; + gint x, y; + GimpRGB background; + guchar bg[4]; + gint sel_x1, sel_y1; + gint sel_width, sel_height; + + gimp_context_get_background (&background); + + if (bvals.bg_trans) + gimp_rgb_set_alpha (&background, 0.0); + + gimp_rgba_get_uchar (&background, bg, bg + 1, bg + 2, bg + 3); + + if (! gimp_drawable_mask_intersect (drawable_id, + &sel_x1, &sel_y1, + &sel_width, &sel_height)) + return; + + if (gimp_drawable_has_alpha (drawable_id)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + bytes = babl_format_get_bytes_per_pixel (format); + + src_buffer = gimp_drawable_get_buffer (drawable_id); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + src_rows = g_new (guchar, MAX (sel_width, sel_height) * bytes * STEP); + des_rows = g_new (guchar, MAX (sel_width, sel_height) * bytes * STEP); + + if (bvals.orientation == GIMP_ORIENTATION_VERTICAL) + { + for (y = 0; y < sel_height; y += STEP) + { + gint rr; + gint step; + + if ((y + STEP) > sel_height) + step = sel_height - y; + else + step = STEP; + + gegl_buffer_get (src_buffer, + GEGL_RECTANGLE (sel_x1, sel_y1 + y, + sel_width, step), 1.0, + format, src_rows, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + /* OK I could make this better */ + for (rr = 0; rr < STEP; rr++) + blindsapply (src_rows + (sel_width * rr * bytes), + des_rows + (sel_width * rr * bytes), + sel_width, bytes, bg); + + gegl_buffer_set (dest_buffer, + GEGL_RECTANGLE (sel_x1, sel_y1 + y, + sel_width, step), 0, + format, des_rows, + GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) y / (double) sel_height); + } + } + else + { + /* Horizontal blinds */ + /* Apply the blinds algo to a single column - + * this act as a transfomation matrix for the + * rows. Make row 0 invalid so we can find it again! + */ + gint i; + gint *sr = g_new (gint, sel_height * bytes); + gint *dr = g_new (gint, sel_height * bytes); + guchar *dst = g_new (guchar, STEP * bytes); + guchar dummybg[4]; + + memset (dummybg, 0, 4); + memset (dr, 0, sel_height * bytes); /* all dr rows are background rows */ + for (y = 0; y < sel_height; y++) + { + sr[y] = y+1; + } + + /* Hmmm. does this work portably? */ + /* This "swaps" the integers around that are held in in the + * sr & dr arrays. + */ + blindsapply ((guchar *) sr, (guchar *) dr, + sel_height, sizeof (gint), dummybg); + + /* Fill in with background color ? */ + for (i = 0 ; i < STEP ; i++) + { + int j; + guchar *bgdst; + bgdst = &dst[i * bytes]; + + for (j = 0 ; j < bytes; j++) + { + bgdst[j] = bg[j]; + } + } + + for (x = 0; x < sel_width; x += STEP) + { + int rr; + int step; + guchar *p; + + if((x + STEP) > sel_width) + step = sel_width - x; + else + step = STEP; + + gegl_buffer_get (src_buffer, + GEGL_RECTANGLE (sel_x1 + x, sel_y1, + step, sel_height), 1.0, + format, src_rows, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + /* OK I could make this better */ + for (rr = 0; rr < sel_height; rr++) + { + if(dr[rr] == 0) + { + /* Draw background line */ + p = dst; + } + else + { + /* Draw line from src */ + p = src_rows + (step * bytes * (dr[rr] - 1)); + } + memcpy (des_rows + (rr * step * bytes), p, + step * bytes); + } + + gegl_buffer_set (dest_buffer, + GEGL_RECTANGLE (sel_x1 + x, sel_y1, + step, sel_height), 0, + format, des_rows, + GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) x / (double) sel_width); + } + + g_free (dst); + g_free (sr); + g_free (dr); + } + + g_free (src_rows); + g_free (des_rows); + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, + sel_x1, sel_y1, sel_width, sel_height); +} diff --git a/plug-ins/common/blur.c b/plug-ins/common/blur.c new file mode 100644 index 0000000..82aa86c --- /dev/null +++ b/plug-ins/common/blur.c @@ -0,0 +1,375 @@ +/**************************************************************************** + * This is a plugin for GIMP v 0.99.8 or later. Documentation is + * available at http://www.rru.com/~meo/gimp/ . + * + * Copyright (C) 1997-98 Miles O'Neal http://www.rru.com/~meo/ + * Blur code Copyright (C) 1995 Spencer Kimball and Peter Mattis + * GUI based on GTK code from: + * alienmap (Copyright (C) 1996, 1997 Daniel Cotting) + * plasma (Copyright (C) 1996 Stephen Norris), + * oilify (Copyright (C) 1996 Torsten Martinsen), + * ripple (Copyright (C) 1997 Brian Degenhardt) and + * whirl (Copyright (C) 1997 Federico Mena Quintero). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + ****************************************************************************/ + +/**************************************************************************** + * Blur: + * + * blur version 2.1 (10 June 2004 WES) + * history + * 2.1 - 10 June 2004 WES + * removed dialog along with randomization and repeat options + * 2.0 - 1 May 1998 MEO + * based on randomize 1.7 + * + * Please send any patches or suggestions to the author: meo@rru.com . + * + * Blur applies a 3x3 blurring convolution kernel to the specified drawable. + * + * For each pixel in the selection or image, + * whether to change the pixel is decided by picking a + * random number, weighted by the user's "randomization" percentage. + * If the random number is in range, the pixel is modified. For + * blurring, an average is determined from the current and adjacent + * pixels. *(Except for the random factor, the blur code came + * straight from the original S&P blur plug-in.)* + * + * This works only with RGB and grayscale images. + * + ****************************************************************************/ + +#include "config.h" + +#include + +#include + +#include "libgimp/stdplugins-intl.h" + + +/********************************* + * + * PLUGIN-SPECIFIC CONSTANTS + * + ********************************/ + +#define PLUG_IN_PROC "plug-in-blur" + +/********************************* + * + * LOCAL FUNCTIONS + * + ********************************/ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static void blur (GimpDrawable *drawable); + +static inline void blur_prepare_row (GimpPixelRgn *pixel_rgn, + guchar *data, + gint x, + gint y, + gint w); + +/************************************ Guts ***********************************/ + +MAIN () + +/********************************* + * + * query() - query_proc + * + * called by GIMP to learn about this plug-in + * + ********************************/ + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Simple blur, fast but not very strong"), + "This plug-in blurs the specified drawable, using " + "a 3x3 blur. Indexed images are not supported.", + "Miles O'Neal ", + "Miles O'Neal, Spencer Kimball, Peter Mattis, " + "Torsten Martinsen, Brian Degenhardt, " + "Federico Mena Quintero, Stephen Norris, " + "Daniel Cotting", + "1995-1998", + N_("_Blur"), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + GimpDrawable *drawable; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + static GimpParam values[1]; + + INIT_I18N (); + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + *nreturn_vals = 1; + *return_vals = values; + + if (strcmp (name, PLUG_IN_PROC) != 0 || nparams < 3) + { + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + return; + } + + run_mode = param[0].data.d_int32; + drawable = gimp_drawable_get (param[2].data.d_drawable); + + /* + * Make sure the drawable type is appropriate. + */ + if (gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id)) + { + gimp_progress_init (_("Blurring")); + gimp_tile_cache_ntiles (2 * (drawable->width / gimp_tile_width () + 1)); + + blur (drawable); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + { + gimp_displays_flush (); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + values[0].data.d_status = status; + gimp_drawable_detach (drawable); +} + + +/********************************* + * + * blur_prepare_row() + * + * Get a row of pixels. If the requested row + * is off the edge, clone the edge row. + * + ********************************/ + +static inline void +blur_prepare_row (GimpPixelRgn *pixel_rgn, + guchar *data, + gint x, + gint y, + gint w) +{ + gint b; + + y = CLAMP (y, 0, pixel_rgn->h - 1); + + gimp_pixel_rgn_get_row (pixel_rgn, data, x, y, w); + + /* + * Fill in edge pixels + */ + for (b = 0; b < pixel_rgn->bpp; b++) + data[-(gint)pixel_rgn->bpp + b] = data[b]; + + for (b = 0; b < pixel_rgn->bpp; b++) + data[w * pixel_rgn->bpp + b] = data[(w - 1) * pixel_rgn->bpp + b]; +} + +/********************************* + * + * blur() + * + * Actually mess with the image. + * + ********************************/ + +static void +blur (GimpDrawable *drawable) +{ + GimpPixelRgn srcPR, destPR; + gint width, height; + gint bytes; + guchar *dest, *d; + guchar *prev_row, *pr; + guchar *cur_row, *cr; + guchar *next_row, *nr; + guchar *tmp; + gint row, col; + gint x1, y1, x2, y2; + gint ind; + gboolean has_alpha; + + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x1, &y1, &width, &height)) + return; + + x2 = x1 + width; + y2 = y1 + height; + + /* + * Get the size of the input image. (This will/must be the same + * as the size of the output image. Also get alpha info. + */ + width = drawable->width; + height = drawable->height; + bytes = drawable->bpp; + has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + /* + * allocate row buffers + */ + prev_row = g_new (guchar, (x2 - x1 + 2) * bytes); + cur_row = g_new (guchar, (x2 - x1 + 2) * bytes); + next_row = g_new (guchar, (x2 - x1 + 2) * bytes); + dest = g_new (guchar, (x2 - x1) * bytes); + + /* + * initialize the pixel regions + */ + gimp_pixel_rgn_init (&srcPR, drawable, 0, 0, width, height, FALSE, FALSE); + gimp_pixel_rgn_init (&destPR, drawable, 0, 0, width, height, TRUE, TRUE); + + pr = prev_row + bytes; + cr = cur_row + bytes; + nr = next_row + bytes; + + /* + * prepare the first row and previous row + */ + blur_prepare_row (&srcPR, pr, x1, y1 - 1, (x2 - x1)); + blur_prepare_row (&srcPR, cr, x1, y1, (x2 - x1)); + + /* + * loop through the rows, applying the selected convolution + */ + for (row = y1; row < y2; row++) + { + /* prepare the next row */ + blur_prepare_row (&srcPR, nr, x1, row + 1, (x2 - x1)); + + d = dest; + ind = 0; + for (col = 0; col < (x2 - x1) * bytes; col++) + { + ind++; + if (ind == bytes || !has_alpha) + { + /* + * If no alpha channel, + * or if there is one and this is it... + */ + *d++ = ((gint) pr[col - bytes] + (gint) pr[col] + + (gint) pr[col + bytes] + + (gint) cr[col - bytes] + (gint) cr[col] + + (gint) cr[col + bytes] + + (gint) nr[col - bytes] + (gint) nr[col] + + (gint) nr[col + bytes] + 4) / 9; + ind = 0; + } + else + { + /* + * otherwise we have an alpha channel, + * but this is a color channel + */ + *d++ = ROUND( + ((gdouble) (pr[col - bytes] * pr[col - ind]) + + (gdouble) (pr[col] * pr[col + bytes - ind]) + + (gdouble) (pr[col + bytes] * pr[col + 2*bytes - ind]) + + (gdouble) (cr[col - bytes] * cr[col - ind]) + + (gdouble) (cr[col] * cr[col + bytes - ind]) + + (gdouble) (cr[col + bytes] * cr[col + 2*bytes - ind]) + + (gdouble) (nr[col - bytes] * nr[col - ind]) + + (gdouble) (nr[col] * nr[col + bytes - ind]) + + (gdouble) (nr[col + bytes] * nr[col + 2*bytes - ind])) + / ((gdouble) pr[col - ind] + + (gdouble) pr[col + bytes - ind] + + (gdouble) pr[col + 2*bytes - ind] + + (gdouble) cr[col - ind] + + (gdouble) cr[col + bytes - ind] + + (gdouble) cr[col + 2*bytes - ind] + + (gdouble) nr[col - ind] + + (gdouble) nr[col + bytes - ind] + + (gdouble) nr[col + 2*bytes - ind])); + } + } + + /* + * Save the modified row, shuffle the row pointers, and every + * so often, update the progress meter. + */ + gimp_pixel_rgn_set_row (&destPR, dest, x1, row, (x2 - x1)); + + tmp = pr; + pr = cr; + cr = nr; + nr = tmp; + + if ((row % 32) == 0) + gimp_progress_update ((gdouble) row / (gdouble) (y2 - y1)); + } + + gimp_progress_update (1.0); + + /* + * update the blurred region + */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1), (y2 - y1)); + /* + * clean up after ourselves. + */ + g_free (prev_row); + g_free (cur_row); + g_free (next_row); + g_free (dest); +} diff --git a/plug-ins/common/border-average.c b/plug-ins/common/border-average.c new file mode 100644 index 0000000..70b7d53 --- /dev/null +++ b/plug-ins/common/border-average.c @@ -0,0 +1,468 @@ +/* borderaverage 0.01 - image processing plug-in for GIMP. + * + * Copyright (C) 1998 Philipp Klaus (webmaster@access.ch) + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-borderaverage" +#define PLUG_IN_BINARY "border-average" +#define PLUG_IN_ROLE "gimp-border-average" + + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + + +static void borderaverage (GeglBuffer *buffer, + gint32 drawable_id, + GimpRGB *result); + +static gboolean borderaverage_dialog (gint32 image_ID, + gint32 drawable_id); + +static void add_new_color (const guchar *buffer, + gint *cube, + gint bucket_expo); + +static void thickness_callback (GtkWidget *widget, + gpointer data); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init */ + NULL, /* quit */ + query, /* query */ + run, /* run */ +}; + +static gint borderaverage_thickness = 3; +static gint borderaverage_bucket_exponent = 4; + +struct borderaverage_data +{ + gint thickness; + gint bucket_exponent; +} + +static borderaverage_data = +{ + 3, + 4 +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "thickness", "Border size to take in count" }, + { GIMP_PDB_INT32, "bucket-exponent", "Bits for bucket size (default=4: 16 Levels)" }, + }; + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_COLOR, "borderaverage", "The average color of the specified border." }, + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Set foreground to the average color of the image border"), + "", + "Philipp Klaus", + "Internet Access AG", + "1998", + N_("_Border Average..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Colors/Info"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[3]; + gint32 image_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRGB result_color = { 0.0, }; + GimpRunMode run_mode; + gint32 drawable_id; + GeglBuffer *buffer; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + image_ID = param[1].data.d_int32; + drawable_id = param[2].data.d_drawable; + + buffer = gimp_drawable_get_buffer (drawable_id); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &borderaverage_data); + borderaverage_thickness = borderaverage_data.thickness; + borderaverage_bucket_exponent = borderaverage_data.bucket_exponent; + if (! borderaverage_dialog (image_ID, drawable_id)) + status = GIMP_PDB_EXECUTION_ERROR; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + if (status == GIMP_PDB_SUCCESS) + { + borderaverage_thickness = param[3].data.d_int32; + borderaverage_bucket_exponent = param[4].data.d_int32; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &borderaverage_data); + borderaverage_thickness = borderaverage_data.thickness; + borderaverage_bucket_exponent = borderaverage_data.bucket_exponent; + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Make sure that the drawable is RGB color */ + if (gimp_drawable_is_rgb (drawable_id)) + { + gimp_progress_init ( _("Border Average")); + borderaverage (buffer, drawable_id, &result_color); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + { + gimp_context_set_foreground (&result_color); + } + if (run_mode == GIMP_RUN_INTERACTIVE) + { + borderaverage_data.thickness = borderaverage_thickness; + borderaverage_data.bucket_exponent = borderaverage_bucket_exponent; + gimp_set_data (PLUG_IN_PROC, + &borderaverage_data, sizeof (borderaverage_data)); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + *nreturn_vals = 3; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + values[1].type = GIMP_PDB_COLOR; + values[1].data.d_color = result_color; + + g_object_unref (buffer); +} + + +static void +borderaverage (GeglBuffer *buffer, + gint32 drawable_id, + GimpRGB *result) +{ + gint x, y, width, height; + gint max; + guchar r, g, b; + gint bucket_num, bucket_expo, bucket_rexpo; + gint *cube; + gint i, j, k; + GeglRectangle border[4]; + + if (! gimp_drawable_mask_intersect (drawable_id, &x, &y, &width, &height)) + { + gimp_rgba_set_uchar (result, 0, 0, 0, 255); + return; + } + + /* allocate and clear the cube before */ + bucket_expo = borderaverage_bucket_exponent; + bucket_rexpo = 8 - bucket_expo; + cube = g_new (gint, 1 << (bucket_rexpo * 3)); + bucket_num = 1 << bucket_rexpo; + + for (i = 0; i < bucket_num; i++) + { + for (j = 0; j < bucket_num; j++) + { + for (k = 0; k < bucket_num; k++) + { + cube[(i << (bucket_rexpo << 1)) + (j << bucket_rexpo) + k] = 0; + } + } + } + + /* Top */ + border[0].x = x; + border[0].y = y; + border[0].width = width; + border[0].height = borderaverage_thickness; + + /* Bottom */ + border[1].x = x; + border[1].y = y + height - borderaverage_thickness; + border[1].width = width; + border[1].height = borderaverage_thickness; + + /* Left */ + border[2].x = x; + border[2].y = y + borderaverage_thickness; + border[2].width = borderaverage_thickness; + border[2].height = height - 2 * borderaverage_thickness; + + /* Right */ + border[3].x = x + width - borderaverage_thickness; + border[3].y = y + borderaverage_thickness; + border[3].width = borderaverage_thickness; + border[3].height = height - 2 * borderaverage_thickness; + + /* Fill the cube */ + for (i = 0; i < 4; i++) + { + if (border[i].width > 0 && border[i].height > 0) + { + GeglBufferIterator *gi; + + gi = gegl_buffer_iterator_new (buffer, &border[i], 0, babl_format ("R'G'B' u8"), + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (gi)) + { + guint k; + guchar *data; + + data = (guchar*) gi->items[0].data; + + for (k = 0; k < gi->length; k++) + { + add_new_color (data + k * 3, + cube, + bucket_expo); + } + } + } + } + + max = 0; r = 0; g = 0; b = 0; + + /* get max of cube */ + for (i = 0; i < bucket_num; i++) + { + for (j = 0; j < bucket_num; j++) + { + for (k = 0; k < bucket_num; k++) + { + if (cube[(i << (bucket_rexpo << 1)) + + (j << bucket_rexpo) + k] > max) + { + max = cube[(i << (bucket_rexpo << 1)) + + (j << bucket_rexpo) + k]; + r = (i<> bucket_expo; + g = buffer[1] >> bucket_expo; + b = buffer[2] >> bucket_expo; + cube[(r << (bucket_rexpo << 1)) + (g << bucket_rexpo) + b]++; +} + +static gboolean +borderaverage_dialog (gint32 image_ID, + gint32 drawable_id) +{ + GtkWidget *dialog; + GtkWidget *frame; + GtkWidget *main_vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *size_entry; + GimpUnit unit; + GtkWidget *combo; + GtkSizeGroup *group; + gboolean run; + gdouble xres, yres; + GeglBuffer *buffer = NULL; + + const gchar *labels[] = + { "1", "2", "4", "8", "16", "32", "64", "128", "256" }; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Border Average"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + frame = gimp_frame_new (_("Border Size")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("_Thickness:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (group, label); + g_object_unref (group); + + /* Get the image resolution and unit */ + gimp_image_get_resolution (image_ID, &xres, &yres); + unit = gimp_image_get_unit (image_ID); + + size_entry = gimp_size_entry_new (1, unit, "%a", TRUE, TRUE, FALSE, 4, + GIMP_SIZE_ENTRY_UPDATE_SIZE); + gtk_box_pack_start (GTK_BOX (hbox), size_entry, FALSE, FALSE, 0); + + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (size_entry), GIMP_UNIT_PIXEL); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (size_entry), 0, xres, TRUE); + + /* set the size (in pixels) that will be treated as 0% and 100% */ + buffer = gimp_drawable_get_buffer (drawable_id); + if (buffer) + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (size_entry), 0, 0.0, + MIN (gegl_buffer_get_width (buffer), + gegl_buffer_get_height (buffer))); + + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (size_entry), 0, + 1.0, 256.0); + gtk_table_set_col_spacing (GTK_TABLE (size_entry), 0, 4); + gtk_table_set_col_spacing (GTK_TABLE (size_entry), 2, 12); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (size_entry), 0, + (gdouble) borderaverage_thickness); + g_signal_connect (size_entry, "value-changed", + G_CALLBACK (thickness_callback), + NULL); + gtk_widget_show (size_entry); + + frame = gimp_frame_new (_("Number of Colors")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("_Bucket size:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gtk_size_group_add_widget (group, label); + + combo = gimp_int_combo_box_new_array (G_N_ELEMENTS (labels), labels); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + borderaverage_bucket_exponent); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &borderaverage_bucket_exponent); + + gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0); + gtk_widget_show (combo); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +thickness_callback (GtkWidget *widget, + gpointer data) +{ + borderaverage_thickness = + gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0); +} diff --git a/plug-ins/common/busy-dialog.c b/plug-ins/common/busy-dialog.c new file mode 100644 index 0000000..ac16792 --- /dev/null +++ b/plug-ins/common/busy-dialog.c @@ -0,0 +1,309 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * busy-dialog.c + * Copyright (C) 2018 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-busy-dialog" +#define PLUG_IN_BINARY "busy-dialog" +#define PLUG_IN_ROLE "gimp-busy-dialog" + + +typedef struct +{ + GIOChannel *read_channel; + GIOChannel *write_channel; +} Context; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static GimpPDBStatusType busy_dialog (gint read_fd, + gint write_fd, + const gchar *message, + gboolean cancelable); + +static gboolean busy_dialog_read_channel_notify (GIOChannel *source, + GIOCondition condition, + Context *context); + +static gboolean busy_dialog_delete_event (GtkDialog *dialog, + GdkEvent *event, + Context *context); +static void busy_dialog_response (GtkDialog *dialog, + gint response_id, + Context *context); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef args [] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" }, + { GIMP_PDB_INT32, "read-fd", "The read file descriptor" }, + { GIMP_PDB_INT32, "write-fd", "The write file descriptor" }, + { GIMP_PDB_STRING, "message", "The message" }, + { GIMP_PDB_INT32, "cancelable", "Whether the dialog is cancelable (TRUE or FALSE)" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + "Show a dialog while waiting for an operation to finish", + "Used by GIMP to display a dialog, containing a " + "spinner and a custom message, while waiting for an " + "ongoing operation to finish. Optionally, the dialog " + "may provide a \"Cancel\" button, which can be used " + "to cancel the operation.", + "Ell", + "Ell", + "2018", + NULL, + "", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint n_params, + const GimpParam *params, + gint *n_return_vals, + GimpParam **return_vals) +{ + GimpRunMode run_mode = params[0].data.d_int32; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + static GimpParam values[1]; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + *n_return_vals = 1; + *return_vals = values; + + INIT_I18N (); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_NONINTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + if (n_params != 5) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + status = busy_dialog (params[1].data.d_int32, + params[2].data.d_int32, + params[3].data.d_string, + params[4].data.d_int32); + } + break; + + default: + status = GIMP_PDB_CALLING_ERROR; + break; + } + + values[0].data.d_status = status; +} + +static GimpPDBStatusType +busy_dialog (gint read_fd, + gint write_fd, + const gchar *message, + gboolean cancelable) +{ + Context context; + GtkWidget *window; + GtkWidget *content_area; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *box; + +#ifdef G_OS_WIN32 + context.read_channel = g_io_channel_win32_new_fd (read_fd); + context.write_channel = g_io_channel_win32_new_fd (write_fd); +#else + context.read_channel = g_io_channel_unix_new (read_fd); + context.write_channel = g_io_channel_unix_new (write_fd); +#endif + + g_io_channel_set_close_on_unref (context.read_channel, TRUE); + g_io_channel_set_close_on_unref (context.write_channel, TRUE); + + /* triggered when the operation is finished in the main app, and we should + * quit. + */ + g_io_add_watch (context.read_channel, G_IO_IN | G_IO_ERR | G_IO_HUP, + (GIOFunc) busy_dialog_read_channel_notify, + &context); + + /* call gtk_init() before gimp_ui_init(), to avoid DESKTOP_STARTUP_ID from + * taking effect -- we want the dialog to be prominently displayed above + * other plug-in windows. + */ + gtk_init (NULL, NULL); + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + /* the main window */ + if (! cancelable) + { + window = g_object_new (GTK_TYPE_WINDOW, + "title", _("Please Wait"), + "skip-taskbar-hint", TRUE, + "deletable", FALSE, + "resizable", FALSE, + "role", "gimp-busy-dialog", + "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG, + "window-position", GTK_WIN_POS_CENTER, + NULL); + + g_signal_connect (window, "delete-event", G_CALLBACK (gtk_true), NULL); + + content_area = window; + } + else + { + window = g_object_new (GTK_TYPE_DIALOG, + "title", _("Please Wait"), + "skip-taskbar-hint", TRUE, + "resizable", FALSE, + "role", "gimp-busy-dialog", + "window-position", GTK_WIN_POS_CENTER, + NULL); + + gtk_dialog_add_button (GTK_DIALOG (window), + _("_Cancel"), GTK_RESPONSE_CANCEL); + + g_signal_connect (window, "delete-event", + G_CALLBACK (busy_dialog_delete_event), + &context); + + g_signal_connect (window, "response", + G_CALLBACK (busy_dialog_response), + &context); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (window)); + } + + /* the main vbox */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 16); + gtk_container_add (GTK_CONTAINER (content_area), vbox); + gtk_widget_show (vbox); + + /* the title label */ + label = gtk_label_new (_("Please wait for the operation to complete")); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* the busy box */ + box = gimp_busy_box_new (message); + gtk_container_set_border_width (GTK_CONTAINER (box), 8); + gtk_box_pack_start (GTK_BOX (vbox), box, TRUE, TRUE, 0); + gtk_widget_show (box); + + gtk_window_present (GTK_WINDOW (window)); + + gtk_main (); + + gtk_widget_destroy (window); + + g_clear_pointer (&context.read_channel, g_io_channel_unref); + g_clear_pointer (&context.write_channel, g_io_channel_unref); + + return GIMP_PDB_SUCCESS; +} + +static gboolean +busy_dialog_read_channel_notify (GIOChannel *source, + GIOCondition condition, + Context *context) +{ + gtk_main_quit (); + + return FALSE; +} + +static gboolean +busy_dialog_delete_event (GtkDialog *dialog, + GdkEvent *event, + Context *context) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL); + + return TRUE; +} + +static void +busy_dialog_response (GtkDialog *dialog, + gint response_id, + Context *context) +{ + switch (response_id) + { + case GTK_RESPONSE_CANCEL: + { + GtkWidget *button; + + gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_CANCEL, FALSE); + + button = gtk_dialog_get_widget_for_response (dialog, + GTK_RESPONSE_CANCEL); + gtk_button_set_label (GTK_BUTTON (button), _("Canceling...")); + + /* signal the cancellation request to the main app */ + g_clear_pointer (&context->write_channel, g_io_channel_unref); + } + break; + + default: + break; + } +} diff --git a/plug-ins/common/cartoon.c b/plug-ins/common/cartoon.c new file mode 100644 index 0000000..beebe7d --- /dev/null +++ b/plug-ins/common/cartoon.c @@ -0,0 +1,880 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Cartoon filter for GIMP for BIPS + * -Spencer Kimball + * + * This filter propagates dark values in an image based on + * each pixel's relative darkness to a neighboring average + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Some useful macros */ + +#define PLUG_IN_PROC "plug-in-cartoon" +#define PLUG_IN_BINARY "cartoon" +#define PLUG_IN_ROLE "gimp-cartoon" +#define TILE_CACHE_SIZE 48 + +typedef struct +{ + gdouble mask_radius; + gdouble threshold; + gdouble pct_black; +} CartoonVals; + + +/* + * Function prototypes. + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void cartoon (GimpDrawable *drawable, + GimpPreview *preview); +static gboolean cartoon_dialog (GimpDrawable *drawable); + +static gdouble compute_ramp (guchar *dest1, + guchar *dest2, + gint length, + gdouble pct_black); + +/* + * Gaussian blur helper functions + */ +static void find_constants (gdouble n_p[], + gdouble n_m[], + gdouble d_p[], + gdouble d_m[], + gdouble bd_p[], + gdouble bd_m[], + gdouble std_dev); +static void transfer_pixels (gdouble *src1, + gdouble *src2, + guchar *dest, + gint jump, + gint bytes, + gint width); + + +/***** Local vars *****/ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init */ + NULL, /* quit */ + query, /* query */ + run, /* run */ +}; + +static CartoonVals cvals = +{ + 7.0, /* mask_radius */ + 1.0, /* threshold */ + 0.2 /* pct_black */ +}; + + +/***** Functions *****/ + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_FLOAT, "mask-radius", "Cartoon mask radius (radius of pixel neighborhood)" }, + { GIMP_PDB_FLOAT, "pct-black", "Percentage of darkened pixels to set to black (0.0 - 1.0)" } + }; + + gchar *help_string = + "Propagates dark values in an image based on " + "each pixel's relative darkness to a neighboring average. The idea behind " + "this filter is to give the look of a black felt pen drawing subsequently " + "shaded with color. This is achieved by darkening areas of the image which " + "are measured to be darker than a neighborhood average. In this way, " + "sufficiently large shifts in intensity are darkened to black. The rate " + "at which they are darkened to black is determined by the second pct_black " + "parameter. The mask_radius parameter controls the size of the pixel " + "neighborhood over which the average intensity is computed and then " + "compared to each pixel in the neighborhood to decide whether or not to " + "darken it to black. Large values for mask_radius result in very thick " + "black areas bordering the shaded regions of color and much less detail " + "for black areas everywhere including inside regions of color. Small " + "values result in more subtle pen strokes and detail everywhere. Small " + "values for the pct_black make the blend from the color regions to the " + "black border lines smoother and the lines themselves thinner and less " + "noticeable; larger values achieve the opposite effect."; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Simulate a cartoon by enhancing edges"), + help_string, + "Spencer Kimball", + "Bit Specialists, Inc.", + "2001", + N_("Ca_rtoon (legacy)..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Artistic"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpDrawable *drawable; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + run_mode = param[0].data.d_int32; + + /* Get the specified drawable */ + drawable = gimp_drawable_get (param[2].data.d_drawable); + + /* set the tile cache size */ + gimp_tile_cache_ntiles (TILE_CACHE_SIZE); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + INIT_I18N(); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &cvals); + + /* First acquire information with a dialog */ + if (! cartoon_dialog (drawable)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + cvals.mask_radius = param[3].data.d_float; + cvals.pct_black = param[4].data.d_float; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &cvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Make sure that the drawable is RGB or GRAY color */ + if (gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id)) + { + gimp_progress_init ("Cartoon"); + + + cartoon (drawable, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &cvals, sizeof (CartoonVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = _("Cannot operate on indexed color images."); + } + } + + values[0].data.d_status = status; + + gimp_drawable_detach (drawable); +} + +/* + * Cartoon algorithm + * ----------------- + * Mask radius = radius of pixel neighborhood for intensity comparison + * Threshold = relative intensity difference which will result in darkening + * Ramp = amount of relative intensity difference before total black + * Blur radius = mask radius / 3.0 + * + * Algorithm: + * For each pixel, calculate pixel intensity value to be: avg (blur radius) + * relative diff = pixel intensity / avg (mask radius) + * If relative diff < Threshold + * intensity mult = (Ramp - MIN (Ramp, (Threshold - relative diff))) / Ramp + * pixel intensity *= intensity mult + */ +static void +cartoon (GimpDrawable *drawable, + GimpPreview *preview) +{ + GimpPixelRgn src_rgn, dest_rgn; + GimpPixelRgn *pr; + gint x, y, width, height; + gint bytes; + gboolean has_alpha; + guchar *dest1; + guchar *dest2; + guchar *src; + guchar *src1, *sp_p1, *sp_m1; + guchar *src2, *sp_p2, *sp_m2; + gdouble n_p1[5], n_m1[5]; + gdouble n_p2[5], n_m2[5]; + gdouble d_p1[5], d_m1[5]; + gdouble d_p2[5], d_m2[5]; + gdouble bd_p1[5], bd_m1[5]; + gdouble bd_p2[5], bd_m2[5]; + gdouble *val_p1, *val_m1, *vp1, *vm1; + gdouble *val_p2, *val_m2, *vp2, *vm2; + gint i, j; + gint row, col, b; + gint terms; + gint progress, max_progress; + gint initial_p1[4]; + gint initial_p2[4]; + gint initial_m1[4]; + gint initial_m2[4]; + gdouble radius; + gdouble std_dev1; + gdouble std_dev2; + gdouble ramp; + guchar *preview_buffer = NULL; + + if (preview) + { + gimp_preview_get_position (preview, &x, &y); + gimp_preview_get_size (preview, &width, &height); + } + else if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x, &y, &width, &height)) + { + return; + } + + bytes = drawable->bpp; + has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + val_p1 = g_new (gdouble, MAX (width, height) * bytes); + val_p2 = g_new (gdouble, MAX (width, height) * bytes); + val_m1 = g_new (gdouble, MAX (width, height) * bytes); + val_m2 = g_new (gdouble, MAX (width, height) * bytes); + + src = g_new (guchar, MAX (width, height) * bytes); + dest1 = g_new0 (guchar, width * height); + dest2 = g_new0 (guchar, width * height); + + gimp_pixel_rgn_init (&src_rgn, drawable, + 0, 0, drawable->width, drawable->height, FALSE, FALSE); + + progress = 0; + max_progress = width * height * 2; + + /* Calculate the standard deviations */ + radius = 1.0; /* blur radius */ + radius = fabs (radius) + 1.0; + std_dev1 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0))); + + radius = cvals.mask_radius; + radius = fabs (radius) + 1.0; + std_dev2 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0))); + + /* derive the constants for calculating the gaussian from the std dev */ + find_constants (n_p1, n_m1, d_p1, d_m1, bd_p1, bd_m1, std_dev1); + find_constants (n_p2, n_m2, d_p2, d_m2, bd_p2, bd_m2, std_dev2); + + /* First the vertical pass */ + for (col = 0; col < width; col++) + { + memset (val_p1, 0, height * bytes * sizeof (gdouble)); + memset (val_p2, 0, height * bytes * sizeof (gdouble)); + memset (val_m1, 0, height * bytes * sizeof (gdouble)); + memset (val_m2, 0, height * bytes * sizeof (gdouble)); + + gimp_pixel_rgn_get_col (&src_rgn, src, col + x, y, height); + + src1 = src; + sp_p1 = src1; + sp_m1 = src1 + (height - 1) * bytes; + vp1 = val_p1; + vp2 = val_p2; + vm1 = val_m1 + (height - 1) * bytes; + vm2 = val_m2 + (height - 1) * bytes; + + /* Set up the first vals */ + for (i = 0; i < bytes; i++) + { + initial_p1[i] = sp_p1[i]; + initial_m1[i] = sp_m1[i]; + } + + for (row = 0; row < height; row++) + { + gdouble *vpptr1, *vmptr1; + gdouble *vpptr2, *vmptr2; + + terms = (row < 4) ? row : 4; + + for (b = 0; b < bytes; b++) + { + vpptr1 = vp1 + b; vmptr1 = vm1 + b; + vpptr2 = vp2 + b; vmptr2 = vm2 + b; + + for (i = 0; i <= terms; i++) + { + *vpptr1 += n_p1[i] * sp_p1[(-i * bytes) + b] - + d_p1[i] * vp1[(-i * bytes) + b]; + *vmptr1 += n_m1[i] * sp_m1[(i * bytes) + b] - + d_m1[i] * vm1[(i * bytes) + b]; + + *vpptr2 += n_p2[i] * sp_p1[(-i * bytes) + b] - + d_p2[i] * vp2[(-i * bytes) + b]; + *vmptr2 += n_m2[i] * sp_m1[(i * bytes) + b] - + d_m2[i] * vm2[(i * bytes) + b]; + } + + for (j = i; j <= 4; j++) + { + *vpptr1 += (n_p1[j] - bd_p1[j]) * initial_p1[b]; + *vmptr1 += (n_m1[j] - bd_m1[j]) * initial_m1[b]; + + *vpptr2 += (n_p2[j] - bd_p2[j]) * initial_p1[b]; + *vmptr2 += (n_m2[j] - bd_m2[j]) * initial_m1[b]; + } + } + + sp_p1 += bytes; + sp_m1 -= bytes; + vp1 += bytes; + vp2 += bytes; + vm1 -= bytes; + vm2 -= bytes; + } + + transfer_pixels (val_p1, val_m1, dest1 + col, width, bytes, height); + transfer_pixels (val_p2, val_m2, dest2 + col, width, bytes, height); + + if (!preview) + { + progress += height; + if ((col % 5) == 0) + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + for (row = 0; row < height; row++) + { + memset (val_p1, 0, width * sizeof (gdouble)); + memset (val_p2, 0, width * sizeof (gdouble)); + memset (val_m1, 0, width * sizeof (gdouble)); + memset (val_m2, 0, width * sizeof (gdouble)); + + src1 = dest1 + row * width; + src2 = dest2 + row * width; + + sp_p1 = src1; + sp_p2 = src2; + sp_m1 = src1 + width - 1; + sp_m2 = src2 + width - 1; + vp1 = val_p1; + vp2 = val_p2; + vm1 = val_m1 + width - 1; + vm2 = val_m2 + width - 1; + + /* Set up the first vals */ + initial_p1[0] = sp_p1[0]; + initial_p2[0] = sp_p2[0]; + initial_m1[0] = sp_m1[0]; + initial_m2[0] = sp_m2[0]; + + for (col = 0; col < width; col++) + { + gdouble *vpptr1, *vmptr1; + gdouble *vpptr2, *vmptr2; + + terms = (col < 4) ? col : 4; + + vpptr1 = vp1; vmptr1 = vm1; + vpptr2 = vp2; vmptr2 = vm2; + + for (i = 0; i <= terms; i++) + { + *vpptr1 += n_p1[i] * sp_p1[-i] - d_p1[i] * vp1[-i]; + *vmptr1 += n_m1[i] * sp_m1[i] - d_m1[i] * vm1[i]; + + *vpptr2 += n_p2[i] * sp_p2[-i] - d_p2[i] * vp2[-i]; + *vmptr2 += n_m2[i] * sp_m2[i] - d_m2[i] * vm2[i]; + } + + for (j = i; j <= 4; j++) + { + *vpptr1 += (n_p1[j] - bd_p1[j]) * initial_p1[0]; + *vmptr1 += (n_m1[j] - bd_m1[j]) * initial_m1[0]; + + *vpptr2 += (n_p2[j] - bd_p2[j]) * initial_p2[0]; + *vmptr2 += (n_m2[j] - bd_m2[j]) * initial_m2[0]; + } + + sp_p1 ++; + sp_p2 ++; + sp_m1 --; + sp_m2 --; + vp1 ++; + vp2 ++; + vm1 --; + vm2 --; + } + + transfer_pixels (val_p1, val_m1, dest1 + row * width, 1, 1, width); + transfer_pixels (val_p2, val_m2, dest2 + row * width, 1, 1, width); + + if (!preview) + { + progress += width; + if ((row % 5) == 0) + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + /* Compute the ramp value which sets 'pct_black' % of the darkened pixels black */ + ramp = compute_ramp (dest1, dest2, width * height, cvals.pct_black); + + /* Initialize the pixel regions. */ + gimp_pixel_rgn_init (&src_rgn, drawable, x, y, width, height, FALSE, FALSE); + + if (preview) + { + pr = gimp_pixel_rgns_register (1, &src_rgn); + preview_buffer = g_new (guchar, width * height * bytes); + } + else + { + gimp_pixel_rgn_init (&dest_rgn, drawable, + x, y, width, height, TRUE, TRUE); + pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn); + } + + while (pr) + { + guchar *src_ptr = src_rgn.data; + guchar *dest_ptr; + guchar *blur_ptr = dest1 + (src_rgn.y - y) * width + (src_rgn.x - x); + guchar *avg_ptr = dest2 + (src_rgn.y - y) * width + (src_rgn.x - x); + gdouble diff; + gdouble mult = 0.0; + gdouble lightness; + + if (preview) + dest_ptr = + preview_buffer + + ((src_rgn.y - y) * width + (src_rgn.x - x)) * bytes; + else + dest_ptr = dest_rgn.data; + + for (row = 0; row < src_rgn.h; row++) + { + for (col = 0; col < src_rgn.w; col++) + { + if (avg_ptr[col] != 0) + { + diff = (gdouble) blur_ptr[col] / (gdouble) avg_ptr[col]; + if (diff < cvals.threshold) + { + if (ramp == 0.0) + mult = 0.0; + else + mult = (ramp - MIN (ramp, (cvals.threshold - diff))) / ramp; + } + else + mult = 1.0; + } + + lightness = CLAMP (blur_ptr[col] * mult, 0, 255); + + if (bytes < 3) + { + dest_ptr[col * bytes] = (guchar) lightness; + if (has_alpha) + dest_ptr[col * bytes + 1] = src_ptr[col * src_rgn.bpp + 1]; + } + else + { + /* Convert to HLS, set lightness and convert back */ + gint r, g, b; + + r = src_ptr[col * src_rgn.bpp + 0]; + g = src_ptr[col * src_rgn.bpp + 1]; + b = src_ptr[col * src_rgn.bpp + 2]; + + gimp_rgb_to_hsl_int (&r, &g, &b); + b = lightness; + gimp_hsl_to_rgb_int (&r, &g, &b); + + dest_ptr[col * bytes + 0] = r; + dest_ptr[col * bytes + 1] = g; + dest_ptr[col * bytes + 2] = b; + + if (has_alpha) + dest_ptr[col * bytes + 3] = src_ptr[col * src_rgn.bpp + 3]; + } + } + + src_ptr += src_rgn.rowstride; + if (preview) + dest_ptr += width * bytes; + else + dest_ptr += dest_rgn.rowstride; + blur_ptr += width; + avg_ptr += width; + } + + if (!preview) + { + progress += src_rgn.w * src_rgn.h; + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + + pr = gimp_pixel_rgns_process (pr); + } + + if (preview) + { + gimp_preview_draw_buffer (preview, preview_buffer, width * bytes); + g_free (preview_buffer); + } + else + { + gimp_progress_update (1.0); + /* merge the shadow, update the drawable */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x, y, width, height); + } + + /* free up buffers */ + g_free (val_p1); + g_free (val_p2); + g_free (val_m1); + g_free (val_m2); + g_free (src); + g_free (dest1); + g_free (dest2); +} + +static gdouble +compute_ramp (guchar *dest1, + guchar *dest2, + gint length, + gdouble pct_black) +{ + gint hist[100]; + gdouble diff; + gint count; + gint i; + gint sum; + + memset (hist, 0, sizeof (int) * 100); + count = 0; + + for (i = 0; i < length; i++) + { + if (*dest2 != 0) + { + diff = (gdouble) *dest1 / (gdouble) *dest2; + if (diff < 1.0) + { + hist[(int) (diff * 100)] += 1; + count += 1; + } + } + + dest1++; + dest2++; + } + + if (pct_black == 0.0 || count == 0) + return 1.0; + + sum = 0; + for (i = 0; i < 100; i++) + { + sum += hist[i]; + if (((gdouble) sum / (gdouble) count) > pct_black) + return (1.0 - (gdouble) i / 100.0); + } + + return 0.0; +} + + +/* + * Gaussian blur helper functions + */ + +static void +transfer_pixels (gdouble *src1, + gdouble *src2, + guchar *dest, + gint jump, + gint bytes, + gint width) +{ + gint i, b; + gdouble sum[4]; + + for(i = 0; i < width; i++) + { + for (b = 0; b < bytes; b++) + { + sum[b] = src1[b] + src2[b]; + if (sum[b] > 255) sum[b] = 255; + else if(sum[b] < 0) sum[b] = 0; + } + + /* Convert to lightness if RGB */ + if (bytes > 2) + *dest = (guchar) gimp_rgb_to_l_int (sum[0], sum[1], sum[2]); + else + *dest = (guchar) sum[0]; + + src1 += bytes; + src2 += bytes; + dest += jump; + } +} + +static void +find_constants (gdouble n_p[], + gdouble n_m[], + gdouble d_p[], + gdouble d_m[], + gdouble bd_p[], + gdouble bd_m[], + gdouble std_dev) +{ + gint i; + gdouble constants [8]; + gdouble div; + + /* The constants used in the implementation of a casual sequence + * using a 4th order approximation of the gaussian operator + */ + + div = sqrt (2 * G_PI) * std_dev; + + constants [0] = -1.783 / std_dev; + constants [1] = -1.723 / std_dev; + constants [2] = 0.6318 / std_dev; + constants [3] = 1.997 / std_dev; + constants [4] = 1.6803 / div; + constants [5] = 3.735 / div; + constants [6] = -0.6803 / div; + constants [7] = -0.2598 / div; + + n_p [0] = constants[4] + constants[6]; + n_p [1] = exp (constants[1]) * + (constants[7] * sin (constants[3]) - + (constants[6] + 2 * constants[4]) * cos (constants[3])) + + exp (constants[0]) * + (constants[5] * sin (constants[2]) - + (2 * constants[6] + constants[4]) * cos (constants[2])); + n_p [2] = 2 * exp (constants[0] + constants[1]) * + ((constants[4] + constants[6]) * cos (constants[3]) * cos (constants[2]) - + constants[5] * cos (constants[3]) * sin (constants[2]) - + constants[7] * cos (constants[2]) * sin (constants[3])) + + constants[6] * exp (2 * constants[0]) + + constants[4] * exp (2 * constants[1]); + n_p [3] = exp (constants[1] + 2 * constants[0]) * + (constants[7] * sin (constants[3]) - constants[6] * cos (constants[3])) + + exp (constants[0] + 2 * constants[1]) * + (constants[5] * sin (constants[2]) - constants[4] * cos (constants[2])); + n_p [4] = 0.0; + + d_p [0] = 0.0; + d_p [1] = -2 * exp (constants[1]) * cos (constants[3]) - + 2 * exp (constants[0]) * cos (constants[2]); + d_p [2] = 4 * cos (constants[3]) * cos (constants[2]) * exp (constants[0] + constants[1]) + + exp (2 * constants[1]) + exp (2 * constants[0]); + d_p [3] = -2 * cos (constants[2]) * exp (constants[0] + 2 * constants[1]) - + 2 * cos (constants[3]) * exp (constants[1] + 2 * constants[0]); + d_p [4] = exp (2 * constants[0] + 2 * constants[1]); + +#ifndef ORIGINAL_READABLE_CODE + memcpy(d_m, d_p, 5 * sizeof(gdouble)); +#else + for (i = 0; i <= 4; i++) + d_m [i] = d_p [i]; +#endif + + n_m[0] = 0.0; + for (i = 1; i <= 4; i++) + n_m [i] = n_p[i] - d_p[i] * n_p[0]; + + { + gdouble sum_n_p, sum_n_m, sum_d; + gdouble a, b; + + sum_n_p = 0.0; + sum_n_m = 0.0; + sum_d = 0.0; + + for (i = 0; i <= 4; i++) + { + sum_n_p += n_p[i]; + sum_n_m += n_m[i]; + sum_d += d_p[i]; + } + +#ifndef ORIGINAL_READABLE_CODE + sum_d++; + a = sum_n_p / sum_d; + b = sum_n_m / sum_d; +#else + a = sum_n_p / (1 + sum_d); + b = sum_n_m / (1 + sum_d); +#endif + + for (i = 0; i <= 4; i++) + { + bd_p[i] = d_p[i] * a; + bd_m[i] = d_m[i] * b; + } + } +} + +/*******************************************************/ +/* Dialog */ +/*******************************************************/ + +static gboolean +cartoon_dialog (GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *table; + GtkObject *scale_data; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Cartoon"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (cartoon), + drawable); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* Label, scale, entry for cvals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Mask radius:"), 100, 5, + cvals.mask_radius, 1.0, 50.0, 1, 5.0, 2, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &cvals.mask_radius); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* Label, scale, entry for cvals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Percent black:"), 50, 5, + cvals.pct_black, 0.0, 1.0, 0.01, 0.1, 3, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &cvals.pct_black); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/checkerboard.c b/plug-ins/common/checkerboard.c new file mode 100644 index 0000000..47fcd96 --- /dev/null +++ b/plug-ins/common/checkerboard.c @@ -0,0 +1,525 @@ +/* + * This is a plug-in for GIMP. + * + * Copyright (C) 1997 Brent Burton & the Edward Blevins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-checkerboard" +#define PLUG_IN_BINARY "checkerboard" +#define PLUG_IN_ROLE "gimp-checkerboard" +#define SPIN_BUTTON_WIDTH 8 + + +/* Variables set in dialog box */ +typedef struct data +{ + gboolean mode; + gint size; +} CheckVals; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void do_checkerboard_pattern (gint32 drawable_ID, + GimpPreview *preview); +static void do_checkerboard_preview (gpointer drawable_ID, + GimpPreview *preview); +static gint inblock (gint pos, + gint size); + +static gboolean checkerboard_dialog (gint32 image_ID, + gint32 drawable_ID); +static void check_size_update_callback (GtkWidget *widget); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static CheckVals cvals = +{ + FALSE, /* mode */ + 10 /* size */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "check-mode", "Check mode { REGULAR (0), PSYCHOBILY (1) }" }, + { GIMP_PDB_INT32, "check-size", "Size of the checks" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Create a checkerboard pattern"), + "More here later", + "Brent Burton & the Edward Blevins", + "Brent Burton & the Edward Blevins", + "1997", + N_("_Checkerboard (legacy)..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Render/Pattern"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + gint32 image_ID; + gint32 drawable_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + run_mode = param[0].data.d_int32; + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &cvals); + if (! checkerboard_dialog (image_ID, drawable_ID)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + if (status == GIMP_PDB_SUCCESS) + { + cvals.mode = param[3].data.d_int32; + cvals.size = param[4].data.d_int32; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &cvals); + break; + + default: + break; + } + + if (gimp_drawable_is_rgb (drawable_ID) || + gimp_drawable_is_gray (drawable_ID)) + { + do_checkerboard_pattern (drawable_ID, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &cvals, sizeof (CheckVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + values[0].data.d_status = status; +} + +typedef struct +{ + guchar fg[4]; + guchar bg[4]; +} CheckerboardParam_t; + +static void +checkerboard_func (gint x, + gint y, + guchar *dest, + gint bpp, + gpointer data) +{ + CheckerboardParam_t *param = (CheckerboardParam_t*) data; + + gint val, xp, yp; + gint b; + + if (cvals.mode) + { + /* Psychobilly Mode */ + val = (inblock (x, cvals.size) != inblock (y, cvals.size)); + } + else + { + /* Normal, regular checkerboard mode. + * Determine base factor (even or odd) of block + * this x/y position is in. + */ + xp = x / cvals.size; + yp = y / cvals.size; + + /* if both even or odd, color sqr */ + val = ( (xp & 1) != (yp & 1) ); + } + + for (b = 0; b < bpp; b++) + dest[b] = val ? param->fg[b] : param->bg[b]; +} + +static void +do_checkerboard_pattern (gint32 drawable_ID, + GimpPreview *preview) +{ + CheckerboardParam_t param; + GimpRGB fg, bg; + const Babl *format; + gint bpp; + + gimp_context_get_background (&bg); + gimp_context_get_foreground (&fg); + + if (gimp_drawable_is_gray (drawable_ID)) + { + param.bg[0] = gimp_rgb_luminance_uchar (&bg); + gimp_rgba_get_uchar (&bg, NULL, NULL, NULL, param.bg + 1); + + param.fg[0] = gimp_rgb_luminance_uchar (&fg); + gimp_rgba_get_uchar (&fg, NULL, NULL, NULL, param.fg + 3); + + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + } + else + { + gimp_rgba_get_uchar (&bg, + param.bg, param.bg + 1, param.bg + 2, param.bg + 1); + + gimp_rgba_get_uchar (&fg, + param.fg, param.fg + 1, param.fg + 2, param.fg + 3); + + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + } + + bpp = babl_format_get_bytes_per_pixel (format); + + if (cvals.size < 1) + { + /* make size 1 to prevent division by zero */ + cvals.size = 1; + } + + if (preview) + { + gint x1, y1; + gint width, height; + gint i; + guchar *buffer; + + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &width, &height); + bpp = gimp_drawable_bpp (drawable_ID); + buffer = g_new (guchar, width * height * bpp); + + for (i = 0; i < width * height; i++) + { + checkerboard_func (x1 + i % width, + y1 + i / width, + buffer + i * bpp, + bpp, ¶m); + } + + gimp_preview_draw_buffer (preview, buffer, width * bpp); + g_free (buffer); + } + else + { + GeglBuffer *buffer; + GeglBufferIterator *iter; + gint x, y, w, h; + gint progress_total; + gint progress_done = 0; + + if (! gimp_drawable_mask_intersect (drawable_ID, &x, &y, &w, &h)) + return; + + progress_total = w * h; + + gimp_progress_init (_("Checkerboard")); + + buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + iter = gegl_buffer_iterator_new (buffer, + GEGL_RECTANGLE (x, y, w, h), 0, + format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (iter)) + { + GeglRectangle roi = iter->items[0].roi; + guchar *dest = iter->items[0].data; + guchar *d; + gint y1, x1; + + d = dest; + + for (y1 = 0; y1 < roi.height; y1++) + { + for (x1 = 0; x1 < roi.width; x1++) + { + checkerboard_func (roi.x + x1, + roi.y + y1, + d + x1 * bpp, + bpp, ¶m); + } + + d += roi.width * bpp; + } + + progress_done += roi.width * roi.height; + gimp_progress_update ((gdouble) progress_done / + (gdouble) progress_total); + } + + g_object_unref (buffer); + + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, x, y, w, h); + } +} + +static void +do_checkerboard_preview (gpointer drawable_ID, + GimpPreview *preview) +{ + do_checkerboard_pattern (GPOINTER_TO_INT (drawable_ID), preview); +} + +static gint +inblock (gint pos, + gint size) +{ + static gint *in = NULL; /* initialized first time */ + static gint len = -1; + + /* avoid a FP exception */ + if (size == 1) + size = 2; + + if (in && len != size * size) + { + g_free (in); + in = NULL; + } + len = size * size; + + /* Initialize the array; since we'll be called thousands of + * times with the same size value, precompute the array. + */ + if (in == NULL) + { + gint cell = 1; /* cell value */ + gint i, j, k; + + in = g_new (gint, len); + + /* i is absolute index into in[] + * j is current number of blocks to fill in with a 1 or 0. + * k is just counter for the j cells. + */ + i = 0; + for (j = 1; j <= size; j++) + { /* first half */ + for (k = 0; k < j; k++) + { + in[i++] = cell; + } + cell = !cell; + } + for (j = size - 1; j >= 1; j--) + { /* second half */ + for (k = 0; k < j; k++) + { + in[i++] = cell; + } + cell = !cell; + } + } + + /* place pos within 0..(len-1) grid and return the value. */ + return in[pos % (len - 1)]; +} + +static gboolean +checkerboard_dialog (gint32 image_ID, + gint32 drawable_ID) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *preview; + GtkWidget *toggle; + GtkWidget *size_entry; + gint size, width, height; + GimpUnit unit; + gdouble xres; + gdouble yres; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Checkerboard"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable_ID); + gtk_box_pack_start (GTK_BOX (vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (do_checkerboard_preview), + GINT_TO_POINTER (drawable_ID)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* Get the image resolution and unit */ + gimp_image_get_resolution (image_ID, &xres, &yres); + unit = gimp_image_get_unit (image_ID); + + width = gimp_drawable_width (drawable_ID); + height = gimp_drawable_height (drawable_ID); + size = MIN (width, height); + + size_entry = gimp_size_entry_new (1, unit, "%a", + TRUE, TRUE, FALSE, SPIN_BUTTON_WIDTH, + GIMP_SIZE_ENTRY_UPDATE_SIZE); + gtk_table_set_col_spacing (GTK_TABLE (size_entry), 0, 4); + gtk_table_set_col_spacing (GTK_TABLE (size_entry), 1, 4); + gtk_box_pack_start (GTK_BOX (hbox), size_entry, FALSE, FALSE, 0); + gtk_widget_show (size_entry); + + /* set the unit back to pixels, since most times we will want pixels */ + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (size_entry), GIMP_UNIT_PIXEL); + + /* set the resolution to the image resolution */ + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (size_entry), 0, xres, TRUE); + + /* set the size (in pixels) that will be treated as 0% and 100% */ + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (size_entry), 0, 0.0, size); + + /* set upper and lower limits (in pixels) */ + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (size_entry), 0, + 1.0, size); + + /* initialize the values */ + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (size_entry), 0, cvals.size); + + /* attach labels */ + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (size_entry), + _("_Size:"), 1, 0, 0.0); + + g_signal_connect (size_entry, "value-changed", + G_CALLBACK (check_size_update_callback), + &cvals.size); + g_signal_connect_swapped (size_entry, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + toggle = gtk_check_button_new_with_mnemonic (_("_Psychobilly")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), cvals.mode); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &cvals.mode); + g_signal_connect_swapped (toggle, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +check_size_update_callback (GtkWidget *widget) +{ + cvals.size = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0); +} diff --git a/plug-ins/common/cml-explorer.c b/plug-ins/common/cml-explorer.c new file mode 100644 index 0000000..b09fc66 --- /dev/null +++ b/plug-ins/common/cml-explorer.c @@ -0,0 +1,2569 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * CML_explorer.c + * Time-stamp: <2000-02-13 18:18:37 yasuhiro> + * Copyright (C) 1997 Shuji Narazaki + * Version: 1.0.11 + * URL: http://www.inetq.or.jp/~narazaki/TheGIMP/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Comment: + * CML is the abbreviation of Coupled-Map Lattice that is a model of + * complex systems, proposed by a physicist[1,2]. + * + * Similar models are summaried as follows: + * + * Value Time Space + * Coupled-Map Lattice cont. discrete discrete + * Celluar Automata discrete discrete discrete + * Differential Eq. cont. cont. cont. + * + * (But this program uses a parameter: hold-rate to avoid very fast changes. + * Thus time is rather continuous than discrete. + * Yes, this change to model changes the output completely.) + * + * References: + * 1. Kunihiko Kaneko, Period-doubling of kind-antikink patterns, + * quasi-periodicity in antiferro-like structures and spatial + * intermittency in coupled map lattices -- Toward a prelude to a + * "field theory of chaos", Prog. Theor. Phys. 72 (1984) 480. + * + * 2. Kunihiko Kaneko ed., Theory and Applications of Coupled Map + * Lattices (Wiley, 1993). + * + * About Parameter File: + * I assume that the possible longest line in CMP parameter file is 1023. + * Please read CML_save_to_file_callback if you want know details of syntax. + * + * Format version 1.0 starts with: + * ; This is a parameter file for CML_explorer + * ; File format version: 1.0 + * ; + * Hue + * + * The old format for CML_explorer included in gimp-0.99.[89] is: + * ; CML parameter file (version: 1.0) + * ; Hue + * + * (This file format is interpreted as format version 0.99 now.) + * + * Thanks: + * This version contains patches from: + * Tim Mooney + * Sean P Cier + * David Mosberger-Tang + * Michael Sweet + * + */ +#include "config.h" + +#include +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#define PARAM_FILE_FORMAT_VERSION 1.0 +#define PLUG_IN_PROC "plug-in-cml-explorer" +#define PLUG_IN_BINARY "cml-explorer" +#define PLUG_IN_ROLE "gimp-cml-explorer" +#define VALS CML_explorer_vals +#define PROGRESS_UPDATE_NUM 100 +#define CML_LINE_SIZE 1024 +#define TILE_CACHE_SIZE 32 +#define SCALE_WIDTH 130 +#define PREVIEW_WIDTH 64 +#define PREVIEW_HEIGHT 220 + +#define CANNONIZE(p, x) (255*(((p).range_h - (p).range_l)*(x) + (p).range_l)) +#define HCANNONIZE(p, x) (254*(((p).range_h - (p).range_l)*(x) + (p).range_l)) +#define POS_IN_TORUS(i,size) ((i < 0) ? size + i : ((size <= i) ? i - size : i)) + +typedef struct _WidgetEntry WidgetEntry; + +struct _WidgetEntry +{ + GtkWidget *widget; + gpointer value; + void (*updater) (WidgetEntry *); +}; + +enum +{ + CML_KEEP_VALUES, + CML_KEEP_FIRST, + CML_FILL, + CML_LOGIST, + CML_LOGIST_STEP, + CML_POWER, + CML_POWER_STEP, + CML_REV_POWER, + CML_REV_POWER_STEP, + CML_DELTA, + CML_DELTA_STEP, + CML_SIN_CURVE, + CML_SIN_CURVE_STEP, + CML_NUM_VALUES +}; + +static const gchar *function_names[CML_NUM_VALUES] = +{ + N_("Keep image's values"), + N_("Keep the first value"), + N_("Fill with parameter k"), + N_("k{x(1-x)}^p"), + N_("k{x(1-x)}^p stepped"), + N_("kx^p"), + N_("kx^p stepped"), + N_("k(1-x^p)"), + N_("k(1-x^p) stepped"), + N_("Delta function"), + N_("Delta function stepped"), + N_("sin^p-based function"), + N_("sin^p, stepped") +}; + +enum +{ + COMP_NONE, + COMP_MAX_LINEAR, + COMP_MAX_LINEAR_P1, + COMP_MAX_LINEAR_M1, + COMP_MIN_LINEAR, + COMP_MIN_LINEAR_P1, + COMP_MIN_LINEAR_M1, + COMP_MAX_LINEAR_P1L, + COMP_MAX_LINEAR_P1U, + COMP_MAX_LINEAR_M1L, + COMP_MAX_LINEAR_M1U, + COMP_MIN_LINEAR_P1L, + COMP_MIN_LINEAR_P1U, + COMP_MIN_LINEAR_M1L, + COMP_MIN_LINEAR_M1U, + COMP_NUM_VALUES +}; + +static const gchar *composition_names[COMP_NUM_VALUES] = +{ + NC_("cml-composition", "None"), + N_("Max (x, -)"), + N_("Max (x+d, -)"), + N_("Max (x-d, -)"), + N_("Min (x, -)"), + N_("Min (x+d, -)"), + N_("Min (x-d, -)"), + N_("Max (x+d, -), (x < 0.5)"), + N_("Max (x+d, -), (0.5 < x)"), + N_("Max (x-d, -), (x < 0.5)"), + N_("Max (x-d, -), (0.5 < x)"), + N_("Min (x+d, -), (x < 0.5)"), + N_("Min (x+d, -), (0.5 < x)"), + N_("Min (x-d, -), (x < 0.5)"), + N_("Min (x-d, -), (0.5 < x)") +}; + +enum +{ + STANDARD, + AVERAGE, + ANTILOG, + RAND_POWER0, + RAND_POWER1, + RAND_POWER2, + MULTIPLY_RANDOM0, + MULTIPLY_RANDOM1, + MULTIPLY_GRADIENT, + RAND_AND_P, + ARRANGE_NUM_VALUES +}; + +static const gchar *arrange_names[ARRANGE_NUM_VALUES] = +{ + N_("Standard"), + N_("Use average value"), + N_("Use reverse value"), + N_("With random power (0,10)"), + N_("With random power (0,1)"), + N_("With gradient power (0,1)"), + N_("Multiply rand. value (0,1)"), + N_("Multiply rand. value (0,2)"), + N_("Multiply gradient (0,1)"), + N_("With p and random (0,1)"), +}; + +enum +{ + CML_INITIAL_RANDOM_INDEPENDENT = 6, + CML_INITIAL_RANDOM_SHARED, + CML_INITIAL_RANDOM_FROM_SEED, + CML_INITIAL_RANDOM_FROM_SEED_SHARED, + CML_INITIAL_NUM_VALUES +}; + +static const gchar *initial_value_names[CML_INITIAL_NUM_VALUES] = +{ + N_("All black"), + N_("All gray"), + N_("All white"), + N_("The first row of the image"), + N_("Continuous gradient"), + N_("Continuous grad. w/o gap"), + N_("Random, ch. independent"), + N_("Random shared"), + N_("Randoms from seed"), + N_("Randoms from seed (shared)") +}; + +#define CML_PARAM_NUM 15 + +typedef struct +{ + gint function; + gint composition; + gint arrange; + gint cyclic_range; + gdouble mod_rate; /* diff / old-value */ + gdouble env_sensitivity; /* self-diff : env-diff */ + gint diffusion_dist; + gdouble ch_sensitivity; + gint range_num; + gdouble power; + gdouble parameter_k; + gdouble range_l; + gdouble range_h; + gdouble mutation_rate; + gdouble mutation_dist; +} CML_PARAM; + +typedef struct +{ + CML_PARAM hue; + CML_PARAM sat; + CML_PARAM val; + gint initial_value; + gint scale; + gint start_offset; + gint seed; + gchar last_file_name[256]; +} ValueType; + +static ValueType VALS = +{ + /* function composition arra + cyc chng sens diff cor n pow k (l,h) rnd dist */ + { + CML_SIN_CURVE, COMP_NONE, STANDARD, + 1, 0.5, 0.7, 2, 0.0, 1, 1.0, 1.0, 0, 1, 0.0, 0.1 + }, + { + CML_FILL, COMP_NONE, STANDARD, + 0, 0.6, 0.1, 2, 0.0, 1, 1.4, 0.9, 0, 0.9, 0.0, 0.1 + }, + { + CML_FILL, COMP_NONE, STANDARD, + 0, 0.5, 0.2, 2, 0.0, 1, 2.0, 1.0, 0, 0.9, 0.0, 0.1 + }, + 6, /* random value 1 */ + 1, /* scale */ + 0, /* start_offset */ + 0, /* seed */ + "" /* last filename */ +}; + +static CML_PARAM *channel_params[] = +{ + &VALS.hue, + &VALS.sat, + &VALS.val +}; + +static const gchar *channel_names[] = +{ + N_("Hue"), + N_("Saturation"), + N_("Value") +}; + +static const gchar *load_channel_names[] = +{ + N_("(None)"), + N_("Hue"), + N_("Saturation"), + N_("Value") +}; + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static GimpPDBStatusType CML_main_function (gboolean preview_p); +static void CML_compute_next_step (gint size, + gdouble **h, + gdouble **s, + gdouble **v, + gdouble **hn, + gdouble **sn, + gdouble **vn, + gdouble **haux, + gdouble **saux, + gdouble **vaux); +static gdouble CML_next_value (gdouble *vec, + gint pos, + gint size, + gdouble c1, + gdouble c2, + CML_PARAM *param, + gdouble aux); +static gdouble logistic_function (CML_PARAM *param, + gdouble x, + gdouble power); + + +static gint CML_explorer_dialog (void); +static GtkWidget * CML_dialog_channel_panel_new (CML_PARAM *param, + gint channel_id); +static GtkWidget * CML_dialog_advanced_panel_new (void); + +static void CML_explorer_toggle_entry_init (WidgetEntry *widget_entry, + GtkWidget *widget, + gpointer value_ptr); + +static void CML_explorer_int_entry_init (WidgetEntry *widget_entry, + GtkObject *object, + gpointer value_ptr); + +static void CML_explorer_double_entry_init (WidgetEntry *widget_entry, + GtkObject *object, + gpointer value_ptr); + +static void CML_explorer_menu_update (GtkWidget *widget, + gpointer data); +static void CML_initial_value_menu_update (GtkWidget *widget, + gpointer data); +static void CML_explorer_menu_entry_init (WidgetEntry *widget_entry, + GtkWidget *widget, + gpointer value_ptr); + +static void preview_update (void); +static void function_graph_new (GtkWidget *widget, + gpointer *data); +static void CML_set_or_randomize_seed_callback (GtkWidget *widget, + gpointer data); +static void CML_copy_parameters_callback (GtkWidget *widget, + gpointer data); +static void CML_initial_value_sensitives_update (void); + +static void CML_save_to_file_callback (GtkWidget *widget, + gpointer data); +static void CML_save_to_file_response (GtkWidget *dialog, + gint response_id, + gpointer data); + +static void CML_preview_update_callback (GtkWidget *widget, + gpointer data); +static void CML_load_from_file_callback (GtkWidget *widget, + gpointer data); +static gboolean CML_load_parameter_file (const gchar *filename, + gboolean interactive_mode); +static void CML_load_from_file_response (GtkWidget *dialog, + gint response_id, + gpointer data); +static gint parse_line_to_gint (FILE *file, + gboolean *flag); +static gdouble parse_line_to_gdouble (FILE *file, + gboolean *flag); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static GtkWidget *preview; +static WidgetEntry widget_pointers[4][CML_PARAM_NUM]; + +static guchar *img; +static gint img_stride; +static cairo_surface_t *buffer; + +typedef struct +{ + GtkWidget *widget; + gint logic; +} CML_sensitive_widget_table; + +#define RANDOM_SENSITIVES_NUM 5 +#define GRAPHSIZE 256 + +static CML_sensitive_widget_table random_sensitives[RANDOM_SENSITIVES_NUM] = +{ + { NULL, 0 }, + { NULL, 0 }, + { NULL, 0 }, + { NULL, 0 }, + { NULL, 0 } +}; + +static GRand *gr; +static gint drawable_id = 0; +static gint copy_source = 0; +static gint copy_destination = 0; +static gint selective_load_source = 0; +static gint selective_load_destination = 0; +static gboolean CML_preview_defer = FALSE; + +static gdouble *mem_chank0 = NULL; +static gint mem_chank0_size = 0; +static guchar *mem_chank1 = NULL; +static gint mem_chank1_size = 0; +static guchar *mem_chank2 = NULL; +static gint mem_chank2_size = 0; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args [] = + { + { GIMP_PDB_INT32, "ru-_mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (not used)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_STRING, "parameter-filename", "The name of parameter file. CML_explorer makes an image with its settings." } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Create abstract Coupled-Map Lattice patterns"), + "Make an image of Coupled-Map Lattice (CML). CML is " + "a kind of Cellula Automata on continuous (value) " + "domain. In GIMP_RUN_NONINTERACTIVE, the name of a " + "parameter file is passed as the 4th arg. You can " + "control CML_explorer via parameter file.", + /* Or do you want to call me with over 50 args? */ + "Shuji Narazaki (narazaki@InetQ.or.jp); " + "http://www.inetq.or.jp/~narazaki/TheGIMP/", + "Shuji Narazaki", + "1997", + N_("CML _Explorer..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Render/Pattern"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_EXECUTION_ERROR; + GimpRunMode run_mode; + + run_mode = param[0].data.d_int32; + drawable_id = param[2].data.d_drawable; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &VALS); + if (! CML_explorer_dialog ()) + return; + break; + case GIMP_RUN_NONINTERACTIVE: + { + gchar *filename = param[3].data.d_string; + + if (! CML_load_parameter_file (filename, FALSE)) + return; + break; + } + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &VALS); + break; + } + + status = CML_main_function (FALSE); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush(); + if (run_mode == GIMP_RUN_INTERACTIVE && status == GIMP_PDB_SUCCESS) + gimp_set_data (PLUG_IN_PROC, &VALS, sizeof (ValueType)); + + g_free (mem_chank0); + g_free (mem_chank1); + g_free (mem_chank2); + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; +} + +static GimpPDBStatusType +CML_main_function (gboolean preview_p) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *src_format; + const Babl *dest_format; + guchar *dest_buf = NULL; + guchar *src_buf = NULL; + gint x, y; + gint dx, dy; + gboolean dest_has_alpha = FALSE; + gboolean dest_is_gray = FALSE; + gboolean src_has_alpha = FALSE; + gboolean src_is_gray = FALSE; + gint total, processed = 0; + gint keep_height = 1; + gint cell_num, width_by_pixel, height_by_pixel; + gint index; + gint src_bpp, src_bpl; + gint dest_bpp, dest_bpl; + gdouble *hues, *sats, *vals; + gdouble *newh, *news, *newv; + gdouble *haux, *saux, *vaux; + + if (! gimp_drawable_mask_intersect (drawable_id, + &x, &y, + &width_by_pixel, &height_by_pixel)) + return GIMP_PDB_SUCCESS; + + src_has_alpha = dest_has_alpha = gimp_drawable_has_alpha (drawable_id); + src_is_gray = dest_is_gray = gimp_drawable_is_gray (drawable_id); + + if (src_is_gray) + { + if (src_has_alpha) + src_format = babl_format ("Y'A u8"); + else + src_format = babl_format ("Y' u8"); + } + else + { + if (src_has_alpha) + src_format = babl_format ("R'G'B'A u8"); + else + src_format = babl_format ("R'G'B' u8"); + } + + dest_format = src_format; + + src_bpp = dest_bpp = babl_format_get_bytes_per_pixel (src_format); + + if (preview_p) + { + dest_format = babl_format ("R'G'B' u8"); + + dest_has_alpha = FALSE; + dest_bpp = 3; + + if (width_by_pixel > PREVIEW_WIDTH) /* preview < drawable (selection) */ + width_by_pixel = PREVIEW_WIDTH; + if (height_by_pixel > PREVIEW_HEIGHT) + height_by_pixel = PREVIEW_HEIGHT; + } + + dest_bpl = width_by_pixel * dest_bpp; + src_bpl = width_by_pixel * src_bpp; + cell_num = (width_by_pixel - 1)/ VALS.scale + 1; + total = height_by_pixel * width_by_pixel; + + if (total < 1) + return GIMP_PDB_EXECUTION_ERROR; + + keep_height = VALS.scale; + + /* configure reusable memories */ + if (mem_chank0_size < 9 * cell_num * sizeof (gdouble)) + { + g_free (mem_chank0); + mem_chank0_size = 9 * cell_num * sizeof (gdouble); + mem_chank0 = (gdouble *) g_malloc (mem_chank0_size); + } + + hues = mem_chank0; + sats = mem_chank0 + cell_num; + vals = mem_chank0 + 2 * cell_num; + newh = mem_chank0 + 3 * cell_num; + news = mem_chank0 + 4 * cell_num; + newv = mem_chank0 + 5 * cell_num; + haux = mem_chank0 + 6 * cell_num; + saux = mem_chank0 + 7 * cell_num; + vaux = mem_chank0 + 8 * cell_num; + + if (mem_chank1_size < src_bpl * keep_height) + { + g_free (mem_chank1); + mem_chank1_size = src_bpl * keep_height; + mem_chank1 = (guchar *) g_malloc (mem_chank1_size); + } + src_buf = mem_chank1; + + if (mem_chank2_size < dest_bpl * keep_height) + { + g_free (mem_chank2); + mem_chank2_size = dest_bpl * keep_height; + mem_chank2 = (guchar *) g_malloc (mem_chank2_size); + } + dest_buf = mem_chank2; + + if (! preview_p) + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + src_buffer = gimp_drawable_get_buffer (drawable_id); + + gr = g_rand_new (); + if (VALS.initial_value == CML_INITIAL_RANDOM_FROM_SEED) + g_rand_set_seed (gr, VALS.seed); + + for (index = 0; index < cell_num; index++) + { + switch (VALS.hue.arrange) + { + case RAND_POWER0: + haux [index] = g_rand_double_range (gr, 0, 10); + break; + case RAND_POWER2: + case MULTIPLY_GRADIENT: + haux [index] = (gdouble) abs ((index % 511) - 255) / (gdouble) 256; + break; + case RAND_POWER1: + case MULTIPLY_RANDOM0: + haux [index] = g_rand_double (gr); + break; + case MULTIPLY_RANDOM1: + haux [index] = g_rand_double_range (gr, 0, 2); + break; + case RAND_AND_P: + haux [index] = ((index % (2 * VALS.hue.diffusion_dist) == 0) ? + g_rand_double (gr) : VALS.hue.power); + break; + default: + haux [index] = VALS.hue.power; + break; + } + + switch (VALS.sat.arrange) + { + case RAND_POWER0: + saux [index] = g_rand_double_range (gr, 0, 10); + break; + case RAND_POWER2: + case MULTIPLY_GRADIENT: + saux [index] = (gdouble) abs ((index % 511) - 255) / (gdouble) 256; + break; + case RAND_POWER1: + case MULTIPLY_RANDOM0: + saux [index] = g_rand_double (gr); + break; + case MULTIPLY_RANDOM1: + saux [index] = g_rand_double_range (gr, 0, 2); + break; + case RAND_AND_P: + saux [index] = ((index % (2 * VALS.sat.diffusion_dist) == 0) ? + g_rand_double (gr) : VALS.sat.power); + break; + default: + saux [index] = VALS.sat.power; + break; + } + + switch (VALS.val.arrange) + { + case RAND_POWER0: + vaux [index] = g_rand_double_range (gr, 0, 10); + break; + case RAND_POWER2: + case MULTIPLY_GRADIENT: + vaux [index] = (gdouble) abs ((index % 511) - 255) / (gdouble) 256; + break; + case RAND_POWER1: + case MULTIPLY_RANDOM0: + vaux [index] = g_rand_double (gr); + break; + case MULTIPLY_RANDOM1: + vaux [index] = g_rand_double_range (gr, 0, 2); + break; + case RAND_AND_P: + vaux [index] = ((index % (2 * VALS.val.diffusion_dist) == 0) ? + g_rand_double (gr) : VALS.val.power); + break; + default: + vaux [index] = VALS.val.power; + break; + } + + switch (VALS.initial_value) + { + case 0: + case 1: + case 2: + hues[index] = sats[index] = vals[index] = 0.5 * (VALS.initial_value); + break; + case 3: /* use the values of the image (drawable) */ + break; /* copy from the drawable after this loop */ + case 4: /* grandient 1 */ + hues[index] = sats[index] = vals[index] + = (gdouble) (index % 256) / (gdouble) 256; + break; /* gradinet 2 */ + case 5: + hues[index] = sats[index] = vals[index] + = (gdouble) abs ((index % 511) - 255) / (gdouble) 256; + break; + case CML_INITIAL_RANDOM_INDEPENDENT: + case CML_INITIAL_RANDOM_FROM_SEED: + hues[index] = g_rand_double (gr); + sats[index] = g_rand_double (gr); + vals[index] = g_rand_double (gr); + break; + case CML_INITIAL_RANDOM_SHARED: + case CML_INITIAL_RANDOM_FROM_SEED_SHARED: + hues[index] = sats[index] = vals[index] = g_rand_double (gr); + break; + } + } + + if (VALS.initial_value == 3) + { + int index; + + for (index = 0; + index < MIN (cell_num, width_by_pixel / VALS.scale); + index++) + { + guchar buffer[4]; + int rgbi[3]; + int i; + + gegl_buffer_sample (src_buffer, x + (index * VALS.scale), y, NULL, + buffer, src_format, + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); + + for (i = 0; i < 3; i++) rgbi[i] = buffer[i]; + gimp_rgb_to_hsv_int (rgbi, rgbi + 1, rgbi + 2); + hues[index] = (gdouble) rgbi[0] / (gdouble) 255; + sats[index] = (gdouble) rgbi[1] / (gdouble) 255; + vals[index] = (gdouble) rgbi[2] / (gdouble) 255; + } + } + + if (! preview_p) + gimp_progress_init (_("CML Explorer: evoluting")); + + /* rolling start */ + for (index = 0; index < VALS.start_offset; index++) + CML_compute_next_step (cell_num, &hues, &sats, &vals, &newh, &news, &newv, + &haux, &saux, &vaux); + + /* rendering */ + for (dy = 0; dy < height_by_pixel; dy += VALS.scale) + { + gint r, g, b, h, s, v; + gint offset_x, offset_y, dest_offset; + + if (height_by_pixel < dy + keep_height) + keep_height = height_by_pixel - dy; + + if ((VALS.hue.function == CML_KEEP_VALUES) || + (VALS.sat.function == CML_KEEP_VALUES) || + (VALS.val.function == CML_KEEP_VALUES)) + { + gegl_buffer_get (src_buffer, + GEGL_RECTANGLE (x, y + dy, + width_by_pixel, keep_height), 1.0, + src_format, src_buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + CML_compute_next_step (cell_num, + &hues, &sats, &vals, + &newh, &news, &newv, + &haux, &saux, &vaux); + + for (dx = 0; dx < cell_num; dx++) + { + h = r = HCANNONIZE (VALS.hue, hues[dx]); + s = g = CANNONIZE (VALS.sat, sats[dx]); + v = b = CANNONIZE (VALS.val, vals[dx]); + + if (! dest_is_gray) + gimp_hsv_to_rgb_int (&r, &g, &b); + + /* render destination */ + for (offset_y = 0; + (offset_y < VALS.scale) && (dy + offset_y < height_by_pixel); + offset_y++) + for (offset_x = 0; + (offset_x < VALS.scale) && (dx * VALS.scale + offset_x < width_by_pixel); + offset_x++) + { + if ((VALS.hue.function == CML_KEEP_VALUES) || + (VALS.sat.function == CML_KEEP_VALUES) || + (VALS.val.function == CML_KEEP_VALUES)) + { + int rgbi[3]; + int i; + + for (i = 0; i < src_bpp; i++) + rgbi[i] = src_buf[offset_y * src_bpl + + (dx * VALS.scale + offset_x) * src_bpp + i]; + if (src_is_gray && (VALS.val.function == CML_KEEP_VALUES)) + { + b = rgbi[0]; + } + else + { + gimp_rgb_to_hsv_int (rgbi, rgbi + 1, rgbi + 2); + + r = (VALS.hue.function == CML_KEEP_VALUES) ? rgbi[0] : h; + g = (VALS.sat.function == CML_KEEP_VALUES) ? rgbi[1] : s; + b = (VALS.val.function == CML_KEEP_VALUES) ? rgbi[2] : v; + gimp_hsv_to_rgb_int (&r, &g, &b); + } + } + + dest_offset = (offset_y * dest_bpl + + (dx * VALS.scale + offset_x) * dest_bpp); + + if (dest_is_gray) + { + dest_buf[dest_offset++] = b; + if (preview_p) + { + dest_buf[dest_offset++] = b; + dest_buf[dest_offset++] = b; + } + } + else + { + dest_buf[dest_offset++] = r; + dest_buf[dest_offset++] = g; + dest_buf[dest_offset++] = b; + } + if (dest_has_alpha) + dest_buf[dest_offset] = 255; + + if ((!preview_p) && + (++processed % (total / PROGRESS_UPDATE_NUM + 1)) == 0) + gimp_progress_update ((gdouble) processed / (gdouble) total); + } + } + + if (preview_p) + { + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), + 0, dy, + width_by_pixel, keep_height, + GIMP_RGB_IMAGE, + dest_buf, + dest_bpl); + } + else + { + gegl_buffer_set (dest_buffer, + GEGL_RECTANGLE (x, y + dy, + width_by_pixel, keep_height), 0, + dest_format, dest_buf, + GEGL_AUTO_ROWSTRIDE); + } + } + + g_object_unref (src_buffer); + + if (preview_p) + { + gtk_widget_queue_draw (preview); + } + else + { + gimp_progress_update (1.0); + + g_object_unref (dest_buffer); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, + x, y, width_by_pixel, height_by_pixel); + } + + g_rand_free (gr); + + return GIMP_PDB_SUCCESS; +} + +static void +CML_compute_next_step (gint size, + gdouble **h, + gdouble **s, + gdouble **v, + gdouble **hn, + gdouble **sn, + gdouble **vn, + gdouble **haux, + gdouble **saux, + gdouble **vaux) +{ + gint index; + + for (index = 0; index < size; index++) + (*hn)[index] = CML_next_value (*h, index, size, + (*s)[POS_IN_TORUS (index, size)], + (*v)[POS_IN_TORUS (index, size)], + &VALS.hue, + (*haux)[POS_IN_TORUS (index , size)]); + for (index = 0; index < size; index++) + (*sn)[index] = CML_next_value (*s, index, size, + (*v)[POS_IN_TORUS (index , size)], + (*h)[POS_IN_TORUS (index , size)], + &VALS.sat, + (*saux)[POS_IN_TORUS (index , size)]); + for (index = 0; index < size; index++) + (*vn)[index] = CML_next_value (*v, index, size, + (*h)[POS_IN_TORUS (index , size)], + (*s)[POS_IN_TORUS (index , size)], + &VALS.val, + (*vaux)[POS_IN_TORUS (index , size)]); + +#define GD_SWAP(x, y) { gdouble *tmp = *x; *x = *y; *y = tmp; } + GD_SWAP (h, hn); + GD_SWAP (s, sn); + GD_SWAP (v, vn); +#undef SWAP +} + +#define LOGISTICS(x) logistic_function (param, x, power) +#define ENV_FACTOR(x) (param->env_sensitivity * LOGISTICS (x)) +#define CHN_FACTOR(x) (param->ch_sensitivity * LOGISTICS (x)) + +static gdouble +CML_next_value (gdouble *vec, + gint pos, + gint size, + gdouble c1, + gdouble c2, + CML_PARAM *param, + gdouble power) +{ + gdouble val = vec[pos]; + gdouble diff = 0; + gdouble self_diff = 0; + gdouble by_env = 0; + gdouble self_mod_rate = 0; + gdouble hold_rate = 1 - param->mod_rate; + gdouble env_factor = 0; + gint index; + + self_mod_rate = (1 - param->env_sensitivity - param->ch_sensitivity); + + switch (param->arrange) + { + case ANTILOG: + self_diff = self_mod_rate * LOGISTICS (1 - vec[pos]); + for (index = 1; index <= param->diffusion_dist / 2; index++) + env_factor += ENV_FACTOR (1 - vec[POS_IN_TORUS (pos + index, size)]) + + ENV_FACTOR (1 - vec[POS_IN_TORUS (pos - index, size)]); + if ((param->diffusion_dist % 2) == 1) + env_factor += (ENV_FACTOR (1 - vec[POS_IN_TORUS (pos + index, size)]) + + ENV_FACTOR (1 - vec[POS_IN_TORUS (pos - index, size)])) / 2; + env_factor /= (gdouble) param->diffusion_dist; + by_env = env_factor + (CHN_FACTOR (1 - c1) + CHN_FACTOR (1 - c2)) / 2; + diff = param->mod_rate * (self_diff + by_env); + val = hold_rate * vec[pos] + diff; + break; + case AVERAGE: + self_diff = self_mod_rate * LOGISTICS (vec[pos]); + for (index = 1; index <= param->diffusion_dist / 2; index++) + env_factor += vec[POS_IN_TORUS (pos + index, size)] + vec[POS_IN_TORUS (pos - index, size)]; + if ((param->diffusion_dist % 2) == 1) + env_factor += (vec[POS_IN_TORUS (pos + index, size)] + vec[POS_IN_TORUS (pos - index, size)]) / 2; + env_factor /= (gdouble) param->diffusion_dist; + by_env = ENV_FACTOR (env_factor) + (CHN_FACTOR (c1) + CHN_FACTOR (c2)) / 2; + diff = param->mod_rate * (self_diff + by_env); + val = hold_rate * vec[pos] + diff; + break; + case MULTIPLY_RANDOM0: + case MULTIPLY_RANDOM1: + case MULTIPLY_GRADIENT: + { + gdouble tmp; + + tmp = power; + power = param->power; + self_diff = self_mod_rate * LOGISTICS (vec[pos]); + for (index = 1; index <= param->diffusion_dist / 2; index++) + env_factor += ENV_FACTOR (vec[POS_IN_TORUS (pos + index, size)]) + + ENV_FACTOR (vec[POS_IN_TORUS (pos - index, size)]); + if ((param->diffusion_dist % 2) == 1) + env_factor += (ENV_FACTOR (vec[POS_IN_TORUS (pos + index, size)]) + + ENV_FACTOR (vec[POS_IN_TORUS (pos - index, size)])) / 2; + env_factor /= (gdouble) param->diffusion_dist; + by_env = (env_factor + CHN_FACTOR (c1) + CHN_FACTOR (c2)) / 2; + diff = pow (param->mod_rate * (self_diff + by_env), tmp); + val = hold_rate * vec[pos] + diff; + break; + } + case STANDARD: + case RAND_POWER0: + case RAND_POWER1: + case RAND_POWER2: + case RAND_AND_P: + default: + self_diff = self_mod_rate * LOGISTICS (vec[pos]); + + for (index = 1; index <= param->diffusion_dist / 2; index++) + env_factor += ENV_FACTOR (vec[POS_IN_TORUS (pos + index, size)]) + + ENV_FACTOR (vec[POS_IN_TORUS (pos - index, size)]); + if ((param->diffusion_dist % 2) == 1) + env_factor += (ENV_FACTOR (vec[POS_IN_TORUS (pos + index, size)]) + + ENV_FACTOR (vec[POS_IN_TORUS (pos - index, size)])) / 2; + env_factor /= (gdouble) param->diffusion_dist; + by_env = env_factor + (CHN_FACTOR (c1) + CHN_FACTOR (c2)) / 2; + diff = param->mod_rate * (self_diff + by_env); + val = hold_rate * vec[pos] + diff; + break; + } + /* finalize */ + if (g_rand_double (gr) < param->mutation_rate) + { + val += ((g_rand_double (gr) < 0.5) ? -1.0 : 1.0) * param->mutation_dist * g_rand_double (gr); + } + if (param->cyclic_range) + { + if (1.0 < val) + val = val - (int) val; + else if (val < 0.0) + val = val - floor (val); + } + else + /* The range of val should be [0,1], not [0,1). + Cannonization shuold be done in color mapping phase. */ + val = CLAMP (val, 0.0, 1); + + return val; +} +#undef LOGISTICS +#undef ENV_FACTOR +#undef CHN_FACTOR + + +static gdouble +logistic_function (CML_PARAM *param, + gdouble x, + gdouble power) +{ + gdouble x1 = x; + gdouble result = 0; + gint n = param->range_num; + gint step; + + step = (int) (x * (gdouble) n); + x1 = (x - ((gdouble) step / (gdouble) n)) * n; + switch (param->function) + { + case CML_KEEP_VALUES: + case CML_KEEP_FIRST: + result = x; + return result; + break; + case CML_FILL: + result = CLAMP (param->parameter_k, 0.0, 1.0); + return result; + break; + case CML_LOGIST: + result = param->parameter_k * pow (4 * x1 * (1.0 - x1), power); + break; + case CML_LOGIST_STEP: + result = param->parameter_k * pow (4 * x1 * (1.0 - x1), power); + result = (result + step) / (gdouble) n; + break; + case CML_POWER: + result = param->parameter_k * pow (x1, power); + break; + case CML_POWER_STEP: + result = param->parameter_k * pow (x1, power); + result = (result + step) / (gdouble) n; + break; + case CML_REV_POWER: + result = param->parameter_k * (1 - pow (x1, power)); + break; + case CML_REV_POWER_STEP: + result = param->parameter_k * (1 - pow (x1, power)); + result = (result + step) / (gdouble) n; + break; + case CML_DELTA: + result = param->parameter_k * 2 * ((x1 < 0.5) ? x1 : (1.0 - x1)); + break; + case CML_DELTA_STEP: + result = param->parameter_k * 2 * ((x1 < 0.5) ? x1 : (1.0 - x1)); + result = (result + step) / (gdouble) n; + break; + case CML_SIN_CURVE: + if (1.0 < power) + result = 0.5 * (sin (G_PI * ABS (x1 - 0.5) / power) / sin (G_PI * 0.5 / power) + 1); + else + result = 0.5 * (pow (sin (G_PI * ABS (x1 - 0.5)), power) + 1); + if (x1 < 0.5) result = 1 - result; + break; + case CML_SIN_CURVE_STEP: + if (1.0 < power) + result = 0.5 * (sin (G_PI * ABS (x1 - 0.5) / power) / sin (G_PI * 0.5 / power) + 1); + else + result = 0.5 * (pow (sin (G_PI * ABS (x1 - 0.5)), power) + 1); + if (x1 < 0.5) result = 1 - result; + result = (result + step) / (gdouble) n; + break; + } + switch (param->composition) + { + case COMP_NONE: + break; + case COMP_MAX_LINEAR: + result = MAX ((gdouble) x, (gdouble) result); + break; + case COMP_MAX_LINEAR_P1: + result = MAX ((gdouble) x + (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MAX_LINEAR_P1L: + if (x < 0.5) + result = MAX ((gdouble) x + (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MAX_LINEAR_P1U: + if (0.5 < x) + result = MAX ((gdouble) x + (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MAX_LINEAR_M1: + result = MAX ((gdouble) x - (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MAX_LINEAR_M1L: + if (x < 0.5) + result = MAX ((gdouble) x - (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MAX_LINEAR_M1U: + if (0.5 < x) + result = MAX ((gdouble) x - (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MIN_LINEAR: + result = MIN ((gdouble) x, (gdouble) result); + break; + case COMP_MIN_LINEAR_P1: + result = MIN ((gdouble) x + (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MIN_LINEAR_P1L: + if (x < 0.5) + result = MIN ((gdouble) x + (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MIN_LINEAR_P1U: + if (0.5 < x) + result = MIN ((gdouble) x + (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MIN_LINEAR_M1: + result = MIN ((gdouble) x - (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MIN_LINEAR_M1L: + if (x < 0.5) + result = MIN ((gdouble) x - (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + case COMP_MIN_LINEAR_M1U: + if (0.5 < x) + result = MIN ((gdouble) x - (gdouble) 1 / (gdouble) 256, (gdouble) result); + break; + } + return result; +} + +/* dialog stuff */ +static gint +CML_explorer_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *abox; + GtkWidget *bbox; + GtkWidget *button; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Coupled-Map-Lattice Explorer"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + CML_preview_defer = TRUE; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (vbox), abox, FALSE, FALSE, 0); + gtk_widget_show (abox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (abox), frame); + gtk_widget_show (frame); + + preview = gimp_preview_area_new (); + gtk_widget_set_size_request (preview, + PREVIEW_WIDTH, PREVIEW_HEIGHT); + gtk_container_add (GTK_CONTAINER (frame), preview); + gtk_widget_show (preview); + + bbox = gtk_button_box_new (GTK_ORIENTATION_VERTICAL); + gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); + gtk_widget_show (bbox); + + button = gtk_button_new_with_label (_("New Seed")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (CML_preview_update_callback), + &VALS); + + random_sensitives[0].widget = button; + random_sensitives[0].logic = TRUE; + + button = gtk_button_new_with_label (_("Fix Seed")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (CML_set_or_randomize_seed_callback), + &VALS); + + random_sensitives[1].widget = button; + random_sensitives[1].logic = TRUE; + + button = gtk_button_new_with_label (_("Random Seed")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (CML_set_or_randomize_seed_callback), + &VALS); + + random_sensitives[2].widget = button; + random_sensitives[2].logic = FALSE; + + bbox = gtk_button_box_new (GTK_ORIENTATION_VERTICAL); + gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); + gtk_widget_show (bbox); + + button = gtk_button_new_with_mnemonic (_("_Open")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (CML_load_from_file_callback), + &VALS); + + button = gtk_button_new_with_mnemonic (_("_Save")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (CML_save_to_file_callback), + &VALS); + + { + GtkWidget *notebook; + GtkWidget *page; + + notebook = gtk_notebook_new (); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); + gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0); + gtk_widget_show (notebook); + + page = CML_dialog_channel_panel_new (&VALS.hue, 0); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, + gtk_label_new_with_mnemonic (_("_Hue"))); + + page = CML_dialog_channel_panel_new (&VALS.sat, 1); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, + gtk_label_new_with_mnemonic (_("Sat_uration"))); + + page = CML_dialog_channel_panel_new (&VALS.val, 2); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, + gtk_label_new_with_mnemonic (_("_Value"))); + + page = CML_dialog_advanced_panel_new (); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, + gtk_label_new_with_mnemonic (_("_Advanced"))); + + { + GtkSizeGroup *group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + GtkWidget *table; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *frame; + GtkWidget *vbox; + GtkObject *adj; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_widget_show (vbox); + + frame = gimp_frame_new (_("Channel Independent Parameters")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + combo = gimp_int_combo_box_new_array (CML_INITIAL_NUM_VALUES, + initial_value_names); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + VALS.initial_value); + + g_signal_connect (combo, "changed", + G_CALLBACK (CML_initial_value_menu_update), + &VALS.initial_value); + + CML_explorer_menu_entry_init (&widget_pointers[3][0], + combo, &VALS.initial_value); + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Initial value:"), 0.0, 0.5, + combo, 2, FALSE); + gtk_size_group_add_widget (group, label); + g_object_unref (group); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("Zoom scale:"), SCALE_WIDTH, 3, + VALS.scale, 1, 10, 1, 2, 0, + TRUE, 0, 0, + NULL, NULL); + gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (adj)); + CML_explorer_int_entry_init (&widget_pointers[3][1], + adj, &VALS.scale); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("Start offset:"), SCALE_WIDTH, 3, + VALS.start_offset, 0, 100, 1, 10, 0, + TRUE, 0, 0, + NULL, NULL); + gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (adj)); + CML_explorer_int_entry_init (&widget_pointers[3][2], + adj, &VALS.start_offset); + + frame = + gimp_frame_new (_("Seed of Random (only for \"From Seed\" Modes)")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("Seed:"), SCALE_WIDTH, 0, + VALS.seed, 0, (guint32) -1, 1, 10, 0, + TRUE, 0, 0, + NULL, NULL); + gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (adj)); + CML_explorer_int_entry_init (&widget_pointers[3][3], + adj, &VALS.seed); + + random_sensitives[3].widget = table; + random_sensitives[3].logic = FALSE; + + button = + gtk_button_new_with_label + (_("Switch to \"From seed\" With the Last Seed")); + gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 3, 1, 2); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (CML_set_or_randomize_seed_callback), + &VALS); + + random_sensitives[4].widget = button; + random_sensitives[4].logic = TRUE; + + gimp_help_set_help_data (button, + _("\"Fix seed\" button is an alias of me.\n" + "The same seed produces the same image, " + "if (1) the widths of images are same " + "(this is the reason why image on drawable " + "is different from preview), and (2) all " + "mutation rates equal to zero."), NULL); + + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, + gtk_label_new_with_mnemonic (_("O_thers"))); + } + + { + GtkSizeGroup *group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + GtkWidget *table; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *vbox; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_widget_show (vbox); + + frame = gimp_frame_new (_("Copy Settings")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + combo = gimp_int_combo_box_new_array (G_N_ELEMENTS (channel_names), + channel_names); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), copy_source); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + ©_source); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Source channel:"), 0.0, 0.5, + combo, 1, FALSE); + gtk_size_group_add_widget (group, label); + g_object_unref (group); + + combo = gimp_int_combo_box_new_array (G_N_ELEMENTS (channel_names), + channel_names); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + copy_destination); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + ©_destination); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Destination channel:"), 0.0, 0.5, + combo, 1, FALSE); + gtk_size_group_add_widget (group, label); + + button = gtk_button_new_with_label (_("Copy Parameters")); + gtk_table_attach (GTK_TABLE (table), button, 0, 2, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (CML_copy_parameters_callback), + &VALS); + + frame = gimp_frame_new (_("Selective Load Settings")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + combo = gimp_int_combo_box_new_array (G_N_ELEMENTS (load_channel_names), + load_channel_names); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + selective_load_source); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &selective_load_source); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Source channel in file:"), + 0.0, 0.5, + combo, 1, FALSE); + gtk_size_group_add_widget (group, label); + + combo = gimp_int_combo_box_new_array (G_N_ELEMENTS (load_channel_names), + load_channel_names); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + selective_load_destination); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &selective_load_destination); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Destination channel:"), + 0.0, 0.5, + combo, 1, FALSE); + gtk_size_group_add_widget (group, label); + + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, + gtk_label_new_with_mnemonic (_("_Misc Ops."))); + } + } + + CML_initial_value_sensitives_update (); + + gtk_widget_show (dialog); + + img_stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, GRAPHSIZE); + img = g_malloc0 (img_stride * GRAPHSIZE); + + buffer = cairo_image_surface_create_for_data (img, CAIRO_FORMAT_RGB24, + GRAPHSIZE, + GRAPHSIZE, + img_stride); + + /* Displaying preview might takes a long time. Thus, first, dialog itself + * should be shown before making preview in it. + */ + CML_preview_defer = FALSE; + preview_update (); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + g_free (img); + cairo_surface_destroy (buffer); + + return run; +} + +static GtkWidget * +CML_dialog_channel_panel_new (CML_PARAM *param, + gint channel_id) +{ + GtkWidget *table; + GtkWidget *combo; + GtkWidget *toggle; + GtkWidget *button; + GtkObject *adj; + gpointer *chank; + gint index = 0; + + table = gtk_table_new (13, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_widget_show (table); + + combo = gimp_int_combo_box_new_array (CML_NUM_VALUES, function_names); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), param->function); + + g_signal_connect (combo, "changed", + G_CALLBACK (CML_explorer_menu_update), + ¶m->function); + + CML_explorer_menu_entry_init (&widget_pointers[channel_id][index], + combo, ¶m->function); + gimp_table_attach_aligned (GTK_TABLE (table), 0, index, + _("Function type:"), 0.0, 0.5, + combo, 2, FALSE); + index++; + + combo = gimp_int_combo_box_new_array (COMP_NUM_VALUES, composition_names); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + param->composition); + + g_signal_connect (combo, "changed", + G_CALLBACK (CML_explorer_menu_update), + ¶m->composition); + + CML_explorer_menu_entry_init (&widget_pointers[channel_id][index], + combo, ¶m->composition); + gimp_table_attach_aligned (GTK_TABLE (table), 0, index, + _("Composition:"), 0.0, 0.5, + combo, 2, FALSE); + index++; + + combo = gimp_int_combo_box_new_array (ARRANGE_NUM_VALUES, arrange_names); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), param->arrange); + + g_signal_connect (combo, "changed", + G_CALLBACK (CML_explorer_menu_update), + ¶m->arrange); + + CML_explorer_menu_entry_init (&widget_pointers[channel_id][index], + combo, ¶m->arrange); + gimp_table_attach_aligned (GTK_TABLE (table), 0, index, + _("Misc arrange:"), 0.0, 0.5, + combo, 2, FALSE); + index++; + + toggle = gtk_check_button_new_with_label (_("Use cyclic range")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + param->cyclic_range); + gtk_table_attach_defaults (GTK_TABLE (table), toggle, 0, 3, index, index + 1); + CML_explorer_toggle_entry_init (&widget_pointers[channel_id][index], + toggle, ¶m->cyclic_range); + gtk_widget_show (toggle); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Mod. rate:"), SCALE_WIDTH, 5, + param->mod_rate, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->mod_rate); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Env. sensitivity:"), SCALE_WIDTH, 5, + param->env_sensitivity, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->env_sensitivity); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Diffusion dist.:"), SCALE_WIDTH, 5, + param->diffusion_dist, 2, 10, 1, 2, 0, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_int_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->diffusion_dist); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("# of subranges:"), SCALE_WIDTH, 5, + param->range_num, 1, 10, 1, 2, 0, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_int_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->range_num); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("P(ower factor):"), SCALE_WIDTH, 5, + param->power, 0.0, 10.0, 0.1, 1.0, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->power); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Parameter k:"), SCALE_WIDTH, 5, + param->parameter_k, 0.0, 10.0, 0.1, 1.0, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->parameter_k); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Range low:"), SCALE_WIDTH, 5, + param->range_l, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->range_l); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Range high:"), SCALE_WIDTH, 5, + param->range_h, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index], + adj, ¶m->range_h); + index++; + + chank = g_new (gpointer, 2); + chank[0] = GINT_TO_POINTER (channel_id); + chank[1] = param; + + button = gtk_button_new_with_label (_("Plot a Graph of the Settings")); + gtk_table_attach_defaults (GTK_TABLE (table), button, + 0, 3, index, index + 1); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (function_graph_new), + chank); + return table; +} + +static GtkWidget * +CML_dialog_advanced_panel_new (void) +{ + GtkWidget *vbox; + GtkWidget *subframe; + GtkWidget *table; + GtkObject *adj; + + gint index = 0; + gint widget_offset = 12; + gint channel_id; + CML_PARAM *param; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_widget_show (vbox); + + for (channel_id = 0; channel_id < 3; channel_id++) + { + param = (CML_PARAM *)&VALS + channel_id; + + subframe = gimp_frame_new (gettext (channel_names[channel_id])); + gtk_box_pack_start (GTK_BOX (vbox), subframe, FALSE, FALSE, 0); + gtk_widget_show (subframe); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (subframe), table); + gtk_widget_show (table); + + index = 0; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Ch. sensitivity:"), SCALE_WIDTH, 0, + param->ch_sensitivity, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index + + widget_offset], + adj, ¶m->ch_sensitivity); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Mutation rate:"), SCALE_WIDTH, 0, + param->mutation_rate, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index + + widget_offset], + adj, ¶m->mutation_rate); + index++; + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, index, + _("Mutation dist.:"), SCALE_WIDTH, 0, + param->mutation_dist, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + CML_explorer_double_entry_init (&widget_pointers[channel_id][index + + widget_offset], + adj, ¶m->mutation_dist); + } + return vbox; +} + +static void +preview_update (void) +{ + if (! CML_preview_defer) + CML_main_function (TRUE); +} + +static gboolean +function_graph_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer *data) +{ + GtkStyle *style = gtk_widget_get_style (widget); + gint x, y; + gint rgbi[3]; + gint channel_id = GPOINTER_TO_INT (data[0]); + CML_PARAM *param = data[1]; + cairo_t *cr; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_set_line_width (cr, 1.0); + + cairo_surface_flush (buffer); + + for (x = 0; x < GRAPHSIZE; x++) + { + /* hue */ + rgbi[0] = rgbi[1] = rgbi[2] = 127; + if ((0 <= channel_id) && (channel_id <= 2)) + rgbi[channel_id] = CANNONIZE ((*param), ((gdouble) x / (gdouble) 255)); + gimp_hsv_to_rgb_int (rgbi, rgbi+1, rgbi+2); + for (y = 0; y < GRAPHSIZE; y++) + { + GIMP_CAIRO_RGB24_SET_PIXEL((img+(y*img_stride+x*4)), + rgbi[0], + rgbi[1], + rgbi[2]); + } + } + + cairo_surface_mark_dirty (buffer); + + cairo_set_source_surface (cr, buffer, 0.0, 0.0); + + cairo_paint (cr); + cairo_translate (cr, 0.5, 0.5); + + cairo_move_to (cr, 0, 255); + cairo_line_to (cr, 255, 0); + gdk_cairo_set_source_color (cr, &style->white); + cairo_stroke (cr); + + y = 255 * CLAMP (logistic_function (param, 0, param->power), + 0, 1.0); + cairo_move_to (cr, 0, 255-y); + for (x = 0; x < GRAPHSIZE; x++) + { + /* curve */ + y = 255 * CLAMP (logistic_function (param, x/(gdouble)255, param->power), + 0, 1.0); + cairo_line_to (cr, x, 255-y); + } + + gdk_cairo_set_source_color (cr, &style->black); + cairo_stroke (cr); + cairo_destroy (cr); + + return TRUE; +} + +static void +function_graph_new (GtkWidget *widget, + gpointer *data) +{ + GtkWidget *dialog; + GtkWidget *frame; + GtkWidget *preview; + + dialog = gimp_dialog_new (_("Graph of the Current Settings"), PLUG_IN_ROLE, + gtk_widget_get_toplevel (widget), 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_set_border_width (GTK_CONTAINER (frame), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + preview = gtk_drawing_area_new (); + gtk_widget_set_size_request (preview, GRAPHSIZE, GRAPHSIZE); + gtk_container_add (GTK_CONTAINER (frame), preview); + gtk_widget_show (preview); + g_signal_connect (preview, "expose-event", + G_CALLBACK (function_graph_expose), data); + + gtk_widget_show (dialog); + + gimp_dialog_run (GIMP_DIALOG (dialog)); + + gtk_widget_destroy (dialog); +} + +static void +CML_set_or_randomize_seed_callback (GtkWidget *widget, + gpointer data) +{ + CML_preview_defer = TRUE; + + switch (VALS.initial_value) + { + case CML_INITIAL_RANDOM_INDEPENDENT: + VALS.initial_value = CML_INITIAL_RANDOM_FROM_SEED; + break; + case CML_INITIAL_RANDOM_SHARED: + VALS.initial_value = CML_INITIAL_RANDOM_FROM_SEED_SHARED; + break; + case CML_INITIAL_RANDOM_FROM_SEED: + VALS.initial_value = CML_INITIAL_RANDOM_INDEPENDENT; + break; + case CML_INITIAL_RANDOM_FROM_SEED_SHARED: + VALS.initial_value = CML_INITIAL_RANDOM_SHARED; + break; + default: + break; + } + if (widget_pointers[3][3].widget && widget_pointers[3][3].updater) + (widget_pointers[3][3].updater) (widget_pointers[3]+3); + if (widget_pointers[3][0].widget && widget_pointers[3][0].updater) + (widget_pointers[3][0].updater) (widget_pointers[3]); + + CML_initial_value_sensitives_update (); + + CML_preview_defer = FALSE; +} + +static void +CML_copy_parameters_callback (GtkWidget *widget, + gpointer data) +{ + gint index; + WidgetEntry *widgets; + + if (copy_source == copy_destination) + { + g_message (_("Warning: the source and the destination are the same channel.")); + return; + } + *channel_params[copy_destination] = *channel_params[copy_source]; + CML_preview_defer = TRUE; + widgets = widget_pointers[copy_destination]; + + for (index = 0; index < CML_PARAM_NUM; index++) + if (widgets[index].widget && widgets[index].updater) + (widgets[index].updater) (widgets + index); + + CML_preview_defer = FALSE; + preview_update (); +} + +static void +CML_initial_value_sensitives_update (void) +{ + gint i = 0; + gint flag1, flag2; + + flag1 = (CML_INITIAL_RANDOM_INDEPENDENT <= VALS.initial_value) + & (VALS.initial_value <= CML_INITIAL_RANDOM_FROM_SEED_SHARED); + flag2 = (CML_INITIAL_RANDOM_INDEPENDENT <= VALS.initial_value) + & (VALS.initial_value <= CML_INITIAL_RANDOM_SHARED); + + for (; i < G_N_ELEMENTS (random_sensitives) ; i++) + if (random_sensitives[i].widget) + gtk_widget_set_sensitive (random_sensitives[i].widget, + flag1 & (random_sensitives[i].logic == flag2)); +} + +static void +CML_preview_update_callback (GtkWidget *widget, + gpointer data) +{ + WidgetEntry seed_widget = widget_pointers[3][3]; + + preview_update (); + + CML_preview_defer = TRUE; + + if (seed_widget.widget && seed_widget.updater) + seed_widget.updater (&seed_widget); + + CML_preview_defer = FALSE; +} + +/* parameter file saving functions */ + +static void +CML_save_to_file_callback (GtkWidget *widget, + gpointer data) +{ + static GtkWidget *dialog = NULL; + + if (! dialog) + { + dialog = + gtk_file_chooser_dialog_new (_("Save CML Explorer Parameters"), + GTK_WINDOW (gtk_widget_get_toplevel (widget)), + GTK_FILE_CHOOSER_ACTION_SAVE, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), + TRUE); + + g_signal_connect (dialog, "response", + G_CALLBACK (CML_save_to_file_response), + NULL); + g_signal_connect (dialog, "delete-event", + G_CALLBACK (gtk_true), + NULL); + } + + if (strlen (VALS.last_file_name)) + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), + VALS.last_file_name); + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +CML_save_to_file_response (GtkWidget *dialog, + gint response_id, + gpointer data) +{ + gchar *filename; + FILE *file; + gint channel_id; + + if (response_id != GTK_RESPONSE_OK) + { + gtk_widget_hide (dialog); + return; + } + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + if (! filename) + return; + + file = g_fopen (filename, "wb"); + + if (! file) + { + g_message (_("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + g_free (filename); + return; + } + + fprintf (file, "; This is a parameter file for CML_explorer\n"); + fprintf (file, "; File format version: %1.1f\n", PARAM_FILE_FORMAT_VERSION); + fprintf (file, ";\n"); + + for (channel_id = 0; channel_id < 3; channel_id++) + { + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + + CML_PARAM param = *(CML_PARAM *)(channel_params[channel_id]); + + fprintf (file, "\t%s\n", channel_names[channel_id]); + fprintf (file, "Function_type : %d (%s)\n", + param.function, function_names[param.function]); + fprintf (file, "Compostion_type : %d (%s)\n", + param.composition, composition_names[param.composition]); + fprintf (file, "Arrange : %d (%s)\n", + param.arrange, arrange_names[param.arrange]); + fprintf (file, "Cyclic_range : %d (%s)\n", + param.cyclic_range, (param.cyclic_range ? "TRUE" : "FALSE")); + fprintf (file, "Mod. rate : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.mod_rate)); + fprintf (file, "Env_sensitivtiy : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.env_sensitivity)); + fprintf (file, "Diffusion dist. : %d\n", param.diffusion_dist); + fprintf (file, "Ch. sensitivity : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.ch_sensitivity)); + fprintf (file, "Num. of Subranges: %d\n", param.range_num); + fprintf (file, "Power_factor : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.power)); + fprintf (file, "Parameter_k : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.parameter_k)); + fprintf (file, "Range_low : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.range_l)); + fprintf (file, "Range_high : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.range_h)); + fprintf (file, "Mutation_rate : %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.mutation_rate)); + fprintf (file, "Mutation_distance: %s\n", + g_ascii_dtostr (buf, sizeof (buf), param.mutation_dist)); + } + + fprintf (file, "\n"); + fprintf (file, "Initial value : %d (%s)\n", + VALS.initial_value, initial_value_names[VALS.initial_value]); + fprintf (file, "Zoom scale : %d\n", VALS.scale); + fprintf (file, "Start offset : %d\n", VALS.start_offset); + fprintf (file, "Random seed : %d\n", VALS.seed); + fclose(file); + + g_message (_("Parameters were saved to '%s'"), + gimp_filename_to_utf8 (filename)); + + strncpy (VALS.last_file_name, filename, + sizeof (VALS.last_file_name) - 1); + + g_free (filename); + + gtk_widget_hide (dialog); +} + +/* parameter file loading functions */ + +static void +CML_load_from_file_callback (GtkWidget *widget, + gpointer data) +{ + static GtkWidget *dialog = NULL; + + if (! dialog) + { + dialog = + gtk_file_chooser_dialog_new (_("Load CML Explorer Parameters"), + GTK_WINDOW (gtk_widget_get_toplevel (widget)), + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + g_signal_connect (dialog, "response", + G_CALLBACK (CML_load_from_file_response), + NULL); + g_signal_connect (dialog, "delete-event", + G_CALLBACK (gtk_true), + NULL); + } + + if (strlen (VALS.last_file_name) > 0) + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), + VALS.last_file_name); + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +CML_load_from_file_response (GtkWidget *dialog, + gint response_id, + gpointer data) +{ + if (response_id == GTK_RESPONSE_OK) + { + gchar *filename; + gint channel_id; + gboolean flag = TRUE; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + gtk_widget_set_sensitive (dialog, FALSE); + flag = CML_load_parameter_file (filename, TRUE); + + g_free (filename); + + if (flag) + { + WidgetEntry *widgets; + gint index; + + CML_preview_defer = TRUE; + + for (channel_id = 0; channel_id < 3; channel_id++) + { + widgets = widget_pointers[channel_id]; + for (index = 0; index < CML_PARAM_NUM; index++) + if (widgets[index].widget && widgets[index].updater) + (widgets[index].updater) (widgets + index); + } + /* channel independent parameters */ + widgets = widget_pointers[3]; + for (index = 0; index < 4; index++) + if (widgets[index].widget && widgets[index].updater) + (widgets[index].updater) (widgets + index); + + CML_preview_defer = FALSE; + + preview_update (); + } + } + + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static gboolean +CML_load_parameter_file (const gchar *filename, + gboolean interactive_mode) +{ + FILE *file; + gint channel_id; + gboolean flag = TRUE; + CML_PARAM ch[3]; + gint initial_value = 0; + gint scale = 1; + gint start_offset = 0; + gint seed = 0; + gint old2new_function_id[] = { 3, 4, 5, 6, 7, 9, 10, 11, 1, 2 }; + + file = g_fopen (filename, "rb"); + + if (!file) + { + g_message (_("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + else + { + gchar line[CML_LINE_SIZE]; + gdouble version = 0.99; + + version = parse_line_to_gdouble (file, &flag); /* old format returns 1 */ + if (version == 1.0) + version = 0.99; + else if (! flag) + { + flag = TRUE; + version = parse_line_to_gdouble (file, &flag); /* maybe new format */ + if (flag) + fgets (line, CML_LINE_SIZE - 1, file); /* one more comment line */ + } + if (version == 0) + { + if (interactive_mode) + gimp_message (_("Error: it's not CML parameter file.")); + fclose(file); + return FALSE; + } + if (interactive_mode) + { + if (version < PARAM_FILE_FORMAT_VERSION) + g_message (_("Warning: '%s' is an old format file."), + gimp_filename_to_utf8 (filename)); + + if (PARAM_FILE_FORMAT_VERSION < version) + g_message (_("Warning: '%s' is a parameter file for a newer " + "version of CML Explorer."), + gimp_filename_to_utf8 (filename)); + } + for (channel_id = 0; flag && (channel_id < 3); channel_id++) + { + /* patched by Tim Mooney */ + if (fgets (line, CML_LINE_SIZE - 1, file) == NULL) /* skip channel name */ + { + flag = FALSE; + break; + } + ch[channel_id].function = parse_line_to_gint (file, &flag); + if (version < 1.0) + ch[channel_id].function = old2new_function_id [ch[channel_id].function]; + if (1.0 <= version) + ch[channel_id].composition = parse_line_to_gint (file, &flag); + else + ch[channel_id].composition = COMP_NONE; + ch[channel_id].arrange = parse_line_to_gint (file, &flag); + ch[channel_id].cyclic_range = parse_line_to_gint (file, &flag); + ch[channel_id].mod_rate = parse_line_to_gdouble (file, &flag); + ch[channel_id].env_sensitivity = parse_line_to_gdouble (file, &flag); + if (1.0 <= version) + ch[channel_id].diffusion_dist = parse_line_to_gint (file, &flag); + else + ch[channel_id].diffusion_dist = 2; + ch[channel_id].ch_sensitivity = parse_line_to_gdouble (file, &flag); + ch[channel_id].range_num = parse_line_to_gint (file, &flag); + ch[channel_id].power = parse_line_to_gdouble (file, &flag); + ch[channel_id].parameter_k = parse_line_to_gdouble (file, &flag); + ch[channel_id].range_l = parse_line_to_gdouble (file, &flag); + ch[channel_id].range_h = parse_line_to_gdouble (file, &flag); + ch[channel_id].mutation_rate = parse_line_to_gdouble (file, &flag); + ch[channel_id].mutation_dist = parse_line_to_gdouble (file, &flag); + } + if (flag) + { + gint dummy; + + if (fgets (line, CML_LINE_SIZE - 1, file) == NULL) /* skip a line */ + dummy = 1; + else + { + initial_value = parse_line_to_gint (file, &dummy); + scale = parse_line_to_gint (file, &dummy); + start_offset = parse_line_to_gint (file, &dummy); + seed = parse_line_to_gint (file, &dummy); + } + if (! dummy) + { + initial_value = 0; + scale = 1; + start_offset = 0; + seed = 0; + } + } + fclose (file); + } + + if (! flag) + { + if (interactive_mode) + gimp_message (_("Error: failed to load parameters")); + } + else + { + if ((selective_load_source == 0) || (selective_load_destination == 0)) + { + VALS.hue = ch[0]; + VALS.sat = ch[1]; + VALS.val = ch[2]; + + VALS.initial_value = initial_value; + VALS.scale = scale; + VALS.start_offset = start_offset; + VALS.seed = seed; + } + else + { + memcpy ((CML_PARAM *)&VALS + (selective_load_destination - 1), + (void *)&ch[selective_load_source - 1], + sizeof (CML_PARAM)); + } + + strncpy (VALS.last_file_name, filename, + sizeof (VALS.last_file_name) - 1); + } + return flag; +} + +static gint +parse_line_to_gint (FILE *file, + gboolean *flag) +{ + gchar line[CML_LINE_SIZE]; + gchar *str; + + if (! *flag) + return 0; + if (fgets (line, CML_LINE_SIZE - 1, file) == NULL) + { + *flag = FALSE; /* set FALSE if fail to parse */ + return 0; + } + str = &line[0]; + while (*str != ':') + if (*str == '\000') + { + *flag = FALSE; + return 0; + } + else + { + str++; + } + + return atoi (str + 1); +} + +static gdouble +parse_line_to_gdouble (FILE *file, + gboolean *flag) +{ + gchar line[CML_LINE_SIZE]; + gchar *str; + + if (! *flag) + return 0.0; + + if (fgets (line, CML_LINE_SIZE - 1, file) == NULL) + { + *flag = FALSE; /* set FALSE if fail to parse */ + return 0.0; + } + str = &line[0]; + while (*str != ':') + if (*str == '\000') + { + *flag = FALSE; + return 0.0; + } + else + { + str++; + } + + return g_ascii_strtod (str + 1, NULL); +} + + +/* toggle button functions */ + +static void +CML_explorer_toggle_update (GtkWidget *widget, + gpointer data) +{ + gimp_toggle_button_update (widget, data); + + preview_update (); +} + +static void +CML_explorer_toggle_entry_change_value (WidgetEntry *widget_entry) +{ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget_entry->widget), + *(gint *) (widget_entry->value)); +} + +static void +CML_explorer_toggle_entry_init (WidgetEntry *widget_entry, + GtkWidget *widget, + gpointer value_ptr) +{ + g_signal_connect (widget, "toggled", + G_CALLBACK (CML_explorer_toggle_update), + value_ptr); + + widget_entry->widget = widget; + widget_entry->value = value_ptr; + widget_entry->updater = CML_explorer_toggle_entry_change_value; +} + +/* int adjustment functions */ + +static void +CML_explorer_int_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gimp_int_adjustment_update (adjustment, data); + + preview_update (); +} + +static void +CML_explorer_int_entry_change_value (WidgetEntry *widget_entry) +{ + GtkAdjustment *adjustment = (GtkAdjustment *) (widget_entry->widget); + + gtk_adjustment_set_value (adjustment, *(gint *) (widget_entry->value)); +} + +static void +CML_explorer_int_entry_init (WidgetEntry *widget_entry, + GtkObject *adjustment, + gpointer value_ptr) +{ + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (CML_explorer_int_adjustment_update), + value_ptr); + + widget_entry->widget = (GtkWidget *) adjustment; + widget_entry->value = value_ptr; + widget_entry->updater = CML_explorer_int_entry_change_value; +} + +/* double adjustment functions */ + +static void +CML_explorer_double_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gimp_double_adjustment_update (adjustment, data); + + preview_update (); +} + +static void +CML_explorer_double_entry_change_value (WidgetEntry *widget_entry) +{ + GtkAdjustment *adjustment = (GtkAdjustment *) (widget_entry->widget); + + gtk_adjustment_set_value (adjustment, *(gdouble *) (widget_entry->value)); +} + +static void +CML_explorer_double_entry_init (WidgetEntry *widget_entry, + GtkObject *adjustment, + gpointer value_ptr) +{ + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (CML_explorer_double_adjustment_update), + value_ptr); + + widget_entry->widget = (GtkWidget *) adjustment; + widget_entry->value = value_ptr; + widget_entry->updater = CML_explorer_double_entry_change_value; +} + +/* menu functions */ + +static void +CML_explorer_menu_update (GtkWidget *widget, + gpointer data) +{ + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), (gint *) data); + + preview_update (); +} + +static void +CML_initial_value_menu_update (GtkWidget *widget, + gpointer data) +{ + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), (gint *) data); + + CML_initial_value_sensitives_update (); + preview_update (); +} + +static void +CML_explorer_menu_entry_change_value (WidgetEntry *widget_entry) +{ + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (widget_entry->widget), + *(gint *) (widget_entry->value)); +} + +static void +CML_explorer_menu_entry_init (WidgetEntry *widget_entry, + GtkWidget *widget, + gpointer value_ptr) +{ + widget_entry->widget = widget; + widget_entry->value = value_ptr; + widget_entry->updater = CML_explorer_menu_entry_change_value; +} diff --git a/plug-ins/common/color-cube-analyze.c b/plug-ins/common/color-cube-analyze.c new file mode 100644 index 0000000..c58c177 --- /dev/null +++ b/plug-ins/common/color-cube-analyze.c @@ -0,0 +1,502 @@ +/* + * This is a plug-in for GIMP. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* + * Analyze colorcube. + * + * Author: robert@experimental.net + */ + +#include "config.h" + +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-ccanalyze" +#define PLUG_IN_BINARY "color-cube-analyze" +#define PLUG_IN_ROLE "gimp-color-cube-analyze" + +/* size of histogram image */ +#define PREWIDTH 256 +#define PREHEIGHT 150 + +/* lets prototype */ +static void query (void); +static void run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void doDialog (void); +static void analyze (GimpDrawable *drawable); + +static void histogram (guchar r, + guchar g, + guchar b, + gdouble a); +static void fillPreview (GtkWidget *preview); +static void insertcolor (guchar r, + guchar g, + guchar b, + gdouble a); + +static void doLabel (GtkWidget *table, + const char *format, + ...) G_GNUC_PRINTF (2, 3); + +/* some global variables */ +static gint width, height, bpp; +static gdouble hist_red[256], hist_green[256], hist_blue[256]; +static gdouble maxred = 0.0, maxgreen = 0.0, maxblue = 0.0; +static gint uniques = 0; +static gint32 imageID; + +/* lets declare what we want to do */ +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +/* run program */ +MAIN () + +/* tell GIMP who we are */ +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_INT32, "num-colors", "Number of colors in the image" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Analyze the set of colors in the image"), + "Analyze colorcube and print some information about " + "the current image (also displays a color-histogram)", + "robert@experimental.net", + "robert@experimental.net", + "June 20th, 1997", + N_("Colorcube A_nalysis..."), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Colors/Info"); +} + +/* main function */ +static void +run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpDrawable *drawable; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + + *nreturn_vals = 2; + *return_vals = values; + + if (run_mode == GIMP_RUN_NONINTERACTIVE) + { + if (n_params != 3) + status = GIMP_PDB_CALLING_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + drawable = gimp_drawable_get (param[2].data.d_drawable); + imageID = param[1].data.d_image; + + if (gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id) || + gimp_drawable_is_indexed (drawable->drawable_id)) + { + memset (hist_red, 0, sizeof (hist_red)); + memset (hist_green, 0, sizeof (hist_green)); + memset (hist_blue, 0, sizeof (hist_blue)); + + gimp_tile_cache_ntiles (2 * + (drawable->width / gimp_tile_width () + 1)); + + analyze (drawable); + + /* show dialog after we analyzed image */ + if (run_mode != GIMP_RUN_NONINTERACTIVE) + doDialog (); + } + else + status = GIMP_PDB_EXECUTION_ERROR; + + gimp_drawable_detach (drawable); + } + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + values[1].type = GIMP_PDB_INT32; + values[1].data.d_int32 = uniques; +} + +/* do the analyzing */ +static void +analyze (GimpDrawable *drawable) +{ + GimpPixelRgn srcPR; + guchar *src_row, *cmap; + gint x, y, numcol; + gint x1, y1, x2, y2, w, h; + guchar r, g, b; + gint a; + guchar idx; + gboolean gray; + gboolean has_alpha; + gboolean has_sel; + guchar *sel; + GimpPixelRgn selPR; + gint ofsx, ofsy; + GimpDrawable *selDrawable; + + gimp_progress_init (_("Colorcube Analysis")); + + if (! gimp_drawable_mask_intersect (drawable->drawable_id, &x1, &y1, &w, &h)) + return; + + x2 = x1 + w; + y2 = y1 + h; + + /* + * Get the size of the input image (this will/must be the same + * as the size of the output image). + */ + width = drawable->width; + height = drawable->height; + bpp = drawable->bpp; + + has_sel = !gimp_selection_is_empty (imageID); + gimp_drawable_offsets (drawable->drawable_id, &ofsx, &ofsy); + + /* initialize the pixel region */ + gimp_pixel_rgn_init (&srcPR, drawable, 0, 0, width, height, FALSE, FALSE); + + cmap = gimp_image_get_colormap (imageID, &numcol); + gray = (gimp_drawable_is_gray (drawable->drawable_id) || + gimp_item_is_channel (drawable->drawable_id)); + has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + selDrawable = gimp_drawable_get (gimp_image_get_selection (imageID)); + gimp_pixel_rgn_init (&selPR, + selDrawable, + 0, 0, width, height, FALSE, FALSE); + + /* allocate row buffer */ + src_row = g_new (guchar, (x2 - x1) * bpp); + sel = g_new (guchar, x2 - x1); + + for (y = y1; y < y2; y++) + { + gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y, (x2 - x1)); + if (has_sel) + gimp_pixel_rgn_get_row (&selPR, sel, x1 + ofsx, y + ofsy, (x2 - x1)); + + for (x = 0; x < w; x++) + { + /* Start with full opacity. */ + a = 255; + + /* + * If the image is indexed, fetch RGB values + * from colormap. + */ + if (cmap) + { + idx = src_row[x * bpp]; + + r = cmap[idx * 3]; + g = cmap[idx * 3 + 1]; + b = cmap[idx * 3 + 2]; + if (has_alpha) + a = src_row[x * bpp + 1]; + } + else if (gray) + { + r = g = b = src_row[x * bpp]; + if (has_alpha) + a = src_row[x * bpp + 1]; + } + else + { + r = src_row[x * bpp]; + g = src_row[x * bpp + 1]; + b = src_row[x * bpp + 2]; + if (has_alpha) + a = src_row[x * bpp + 3]; + } + + if (has_sel) + a *= sel[x]; + else + a *= 255; + + if (a != 0) + insertcolor (r, g, b, (gdouble) a * (1.0 / (255.0 * 255.0))); + } + + /* tell the user what we're doing */ + if ((y % 10) == 0) + gimp_progress_update ((gdouble) y / (gdouble) (y2 - y1)); + } + + gimp_progress_update (1.0); + + /* clean up */ + gimp_drawable_detach (selDrawable); + g_free (src_row); + g_free (sel); +} + +static void +insertcolor (guchar r, + guchar g, + guchar b, + gdouble a) +{ + static GHashTable *hash_table; + guint key; + + if (!hash_table) + hash_table = g_hash_table_new (g_direct_hash, g_direct_equal); + + histogram (r, g, b, a); + + key = r + 256 * (g + 256 * b); + if (g_hash_table_lookup (hash_table, GINT_TO_POINTER (key))) + { + return; + } + + g_hash_table_insert (hash_table, GINT_TO_POINTER (key), + GINT_TO_POINTER (1)); + + uniques++; +} + +/* + * Update RGB count, and keep track of maximum values (which aren't used + * anywhere as of yet, but they might be useful sometime). + */ +static void +histogram (guchar r, + guchar g, + guchar b, + gdouble a) +{ + hist_red[r] += a; + hist_green[g] += a; + hist_blue[b] += a; + + if (hist_red[r] > maxred) + maxred = hist_red[r]; + + if (hist_green[g] > maxgreen) + maxgreen = hist_green[g]; + + if (hist_blue[b] > maxblue) + maxblue = hist_blue[b]; +} + +/* show our results */ +static void +doDialog (void) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *preview; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Colorcube Analysis"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, TRUE, TRUE, 0); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + + /* use preview for histogram window */ + preview = gimp_preview_area_new (); + gtk_widget_set_size_request (preview, PREWIDTH, PREHEIGHT); + gtk_container_add (GTK_CONTAINER (frame), preview); + + /* output results */ + doLabel (vbox, _("Image dimensions: %d × %d"), width, height); + + if (uniques == 0) + doLabel (vbox, _("No colors")); + else if (uniques == 1) + doLabel (vbox, _("Only one unique color")); + else + doLabel (vbox, _("Number of unique colors: %d"), uniques); + + /* show stuff */ + gtk_widget_show_all (dialog); + + fillPreview (preview); + + gimp_dialog_run (GIMP_DIALOG (dialog)); + + gtk_widget_destroy (dialog); +} + +/* shortcut */ +static void +doLabel (GtkWidget *vbox, + const gchar *format, + ...) +{ + GtkWidget *label; + gchar *text; + va_list args; + + va_start (args, format); + text = g_strdup_vprintf (format, args); + va_end (args); + + label = gtk_label_new (text); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + g_free (text); +} + +/* fill our preview image with the color-histogram */ +static void +fillPreview (GtkWidget *preview) +{ + guchar *image, *column, *pixel; + gint x, y, rowstride; + gdouble histcount, val; + + rowstride = PREWIDTH * 3; + + image = g_new0 (guchar, PREWIDTH * rowstride); + + for (x = 0, column = image; x < PREWIDTH; x++, column += 3) + { + /* + * For every channel, calculate a logarithmic value, scale it, + * and build a one-pixel bar. + * ... in the respective channel, preserving the other ones. --hb + */ + histcount = hist_red[x] > 1.0 ? hist_red[x] : 1.0; + + val = log (histcount) * (PREHEIGHT / 12); + + if (val > PREHEIGHT) + val = PREHEIGHT; + + y = PREHEIGHT - 1; + pixel = column + (y * rowstride); + for (; y > (PREHEIGHT - val); y--) + { + pixel[0] = 255; + pixel -= rowstride; + } + + histcount = hist_green[x] > 1.0 ? hist_green[x] : 1.0; + + val = log (histcount) * (PREHEIGHT / 12); + + if (val > PREHEIGHT) + val = PREHEIGHT; + + y = PREHEIGHT - 1; + pixel = column + (y * rowstride); + for (; y > (PREHEIGHT - val); y--) + { + pixel[1] = 255; + pixel -= rowstride; + } + + histcount = hist_blue[x] > 1.0 ? hist_blue[x] : 1.0; + + val = log (histcount) * (PREHEIGHT / 12); + + if (val > PREHEIGHT) + val = PREHEIGHT; + + y = PREHEIGHT - 1; + pixel = column + (y * rowstride); + for (; y > (PREHEIGHT - val); y--) + { + pixel[2] = 255; + pixel -= rowstride; + } + } + + /* move our data into the preview image */ + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), + 0, 0, PREWIDTH, PREHEIGHT, + GIMP_RGB_IMAGE, + image, + 3 * PREWIDTH); + + g_free (image); +} diff --git a/plug-ins/common/color-enhance.c b/plug-ins/common/color-enhance.c new file mode 100644 index 0000000..66fc29f --- /dev/null +++ b/plug-ins/common/color-enhance.c @@ -0,0 +1,289 @@ +/* Color Enhance 0.10 --- image filter plug-in for GIMP + * + * Copyright (C) 1999 Martin Weber + * Copyright (C) 1996 Federico Mena Quintero + * + * You can contact me at martweb@gmx.net + * You can contact the original GIMP authors at gimp@xcf.berkeley.edu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-color-enhance" + + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void Color_Enhance (GimpDrawable *drawable); +static void indexed_Color_Enhance (gint32 image_ID); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Stretch color saturation to cover maximum possible range"), + "This simple plug-in does an automatic saturation " + "stretch. For each channel in the image, it finds " + "the minimum and maximum values... it uses those " + "values to stretch the individual histograms to the " + "full range. For some images it may do just what " + "you want; for others it may not work that well. " + "This version differs from Contrast Autostretch in " + "that it works in HSV space, and preserves hue.", + "Martin Weber", + "Martin Weber", + "1997", + N_("_Color Enhance (legacy)"), + "RGB*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Colors/Auto"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpDrawable *drawable; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gint32 image_ID; + + INIT_I18N(); + + run_mode = param[0].data.d_int32; + + /* Get the specified drawable */ + drawable = gimp_drawable_get (param[2].data.d_drawable); + image_ID = param[1].data.d_image; + + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id)) + { + gimp_progress_init (_("Color Enhance")); + gimp_tile_cache_ntiles (2 * (drawable->width / gimp_tile_width () + 1)); + Color_Enhance (drawable); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + else if (gimp_drawable_is_indexed (drawable->drawable_id)) + { + indexed_Color_Enhance (image_ID); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + gimp_drawable_detach (drawable); +} + +static gdouble +get_v (const guchar *src) +{ + gdouble h, z, v; + gint c, m, y; + gint k; + guchar map[3]; + + c = 255 - src[0]; + m = 255 - src[1]; + y = 255 - src[2]; + + k = c; + if (m < k) k = m; + if (y < k) k = y; + + map[0] = c - k; + map[1] = m - k; + map[2] = y - k; + + gimp_rgb_to_hsv4(map, &h, &z, &v); + + return v; +} + +static void +enhance_it (const guchar *src, guchar *dest, gdouble vlo, gdouble vhi) +{ + gdouble h, z, v; + gint c, m, y; + gint k; + guchar map[3]; + + c = 255 - src[0]; + m = 255 - src[1]; + y = 255 - src[2]; + + k = c; + if (m < k) k = m; + if (y < k) k = y; + + map[0] = c - k; + map[1] = m - k; + map[2] = y - k; + + gimp_rgb_to_hsv4 (map, &h, &z, &v); + + if (vhi != vlo) + v = (v - vlo) / (vhi - vlo); + + gimp_hsv_to_rgb4 (map, h, z, v); + + c = map[0]; + m = map[1]; + y = map[2]; + + c += k; + if (c > 255) c = 255; + m += k; + if (m > 255) m = 255; + y += k; + if (y > 255) y = 255; + + dest[0] = 255 - c; + dest[1] = 255 - m; + dest[2] = 255 - y; +} + +static void +indexed_Color_Enhance (gint32 image_ID) +{ + guchar *cmap; + gint ncols,i; + gdouble vhi = 0.0, vlo = 1.0; + + cmap = gimp_image_get_colormap (image_ID, &ncols); + + if (!cmap) + { + g_message ("colormap was NULL! Quitting."); + gimp_quit(); + } + + for (i = 0; i < ncols; i++) + { + gdouble v = get_v (&cmap[3 * i]); + + if (v > vhi) vhi = v; + if (v < vlo) vlo = v; + } + + for (i = 0; i < ncols; i++) + { + enhance_it (&cmap[3 * i], &cmap[3 * i], vlo, vhi); + } + + gimp_image_set_colormap (image_ID, cmap, ncols); +} + +typedef struct +{ + gdouble vhi; + gdouble vlo; + gboolean has_alpha; +} ColorEnhanceParam_t; + +static void +find_vhi_vlo (const guchar *src, + gint bpp, + gpointer data) +{ + ColorEnhanceParam_t *param = (ColorEnhanceParam_t*) data; + + if (!param->has_alpha || src[3]) + { + gdouble v = get_v (src); + + if (v > param->vhi) param->vhi = v; + if (v < param->vlo) param->vlo = v; + } +} + +static void +color_enhance_func (const guchar *src, + guchar *dest, + gint bpp, + gpointer data) +{ + ColorEnhanceParam_t *param = (ColorEnhanceParam_t*) data; + + enhance_it (src, dest, param->vlo, param->vhi); + + if (param->has_alpha) + dest[3] = src[3]; +} + +static void +Color_Enhance (GimpDrawable *drawable) +{ + ColorEnhanceParam_t param; + + param.has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + param.vhi = 0.0; + param.vlo = 1.0; + + gimp_rgn_iterate1 (drawable, 0 /* unused */, find_vhi_vlo, ¶m); + gimp_rgn_iterate2 (drawable, 0 /* unused */, color_enhance_func, ¶m); +} diff --git a/plug-ins/common/colorify.c b/plug-ins/common/colorify.c new file mode 100644 index 0000000..5bd0a88 --- /dev/null +++ b/plug-ins/common/colorify.c @@ -0,0 +1,398 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Colorify. Changes the pixel's luminosity to a specified color + * Copyright (C) 1997 Francisco Bustamante + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-colorify" +#define PLUG_IN_BINARY "colorify" +#define PLUG_IN_ROLE "gimp-colorify" +#define PLUG_IN_VERSION "1.1" + +#define COLOR_SIZE 30 + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void colorify (GimpDrawable *drawable, + GimpPreview *preview); +static gboolean colorify_dialog (GimpDrawable *drawable); +static void predefined_color_callback (GtkWidget *widget, + gpointer data); + +typedef struct +{ + GimpRGB color; +} ColorifyVals; + +static ColorifyVals cvals = +{ + { 1.0, 1.0, 1.0, 1.0 } +}; + +static GimpRGB button_color[] = +{ + { 1.0, 0.0, 0.0, 1.0 }, + { 1.0, 1.0, 0.0, 1.0 }, + { 0.0, 1.0, 0.0, 1.0 }, + { 0.0, 1.0, 1.0, 1.0 }, + { 0.0, 0.0, 1.0, 1.0 }, + { 1.0, 0.0, 1.0, 1.0 }, + { 1.0, 1.0, 1.0, 1.0 }, +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run, +}; + +static GtkWidget *custom_color_button = NULL; + +static gint lum_red_lookup[256]; +static gint lum_green_lookup[256]; +static gint lum_blue_lookup[256]; +static gint final_red_lookup[256]; +static gint final_green_lookup[256]; +static gint final_blue_lookup[256]; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_COLOR, "color", "Color to apply" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Replace all colors with shades of a specified color"), + "Makes an average of the RGB channels and uses it " + "to set the color", + "Francisco Bustamante", + "Francisco Bustamante", + PLUG_IN_VERSION, + N_("Colorif_y..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + GimpPDBStatusType status; + static GimpParam values[1]; + GimpDrawable *drawable; + GimpRunMode run_mode; + + INIT_I18N (); + + status = GIMP_PDB_SUCCESS; + run_mode = param[0].data.d_int32; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + *nreturn_vals = 1; + *return_vals = values; + + drawable = gimp_drawable_get (param[2].data.d_drawable); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &cvals); + if (!colorify_dialog (drawable)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 4) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + cvals.color = param[3].data.d_color; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &cvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_progress_init (_("Colorifying")); + + colorify (drawable, NULL); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &cvals, sizeof (ColorifyVals)); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + + gimp_drawable_detach (drawable); + + values[0].data.d_status = status; +} + +static void +colorify_func (const guchar *src, + guchar *dest, + gint bpp, + gpointer data) +{ + gint lum; + + lum = lum_red_lookup[src[0]] + + lum_green_lookup[src[1]] + + lum_blue_lookup[src[2]]; + + dest[0] = final_red_lookup[lum]; + dest[1] = final_green_lookup[lum]; + dest[2] = final_blue_lookup[lum]; + + if (bpp == 4) + dest[3] = src[3]; +} + +static void +colorify (GimpDrawable *drawable, + GimpPreview *preview) +{ + gint i; + + for (i = 0; i < 256; i ++) + { + lum_red_lookup[i] = i * GIMP_RGB_LUMINANCE_RED; + lum_green_lookup[i] = i * GIMP_RGB_LUMINANCE_GREEN; + lum_blue_lookup[i] = i * GIMP_RGB_LUMINANCE_BLUE; + final_red_lookup[i] = i * cvals.color.r; + final_green_lookup[i] = i * cvals.color.g; + final_blue_lookup[i] = i * cvals.color.b; + } + + if (preview) + { + gint width, height, bytes; + guchar *src; + + src = gimp_zoom_preview_get_source (GIMP_ZOOM_PREVIEW (preview), + &width, &height, &bytes); + for (i = 0; i < width * height; i++) + colorify_func (src + i * bytes, src + i * bytes, bytes, NULL); + + gimp_preview_draw_buffer (preview, src, width * bytes); + g_free (src); + } + else + { + GimpPixelRgn srcPR, destPR; + gint x1, y1, x2, y2; + gpointer pr; + gint total_area; + gint area_so_far; + gint count; + + gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); + + total_area = (x2 - x1) * (y2 - y1); + area_so_far = 0; + + if (total_area <= 0) + return; + + /* Initialize the pixel regions. */ + gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, (x2 - x1), (y2 - y1), + FALSE, FALSE); + gimp_pixel_rgn_init (&destPR, drawable, x1, y1, (x2 - x1), (y2 - y1), + TRUE, TRUE); + + for (pr = gimp_pixel_rgns_register (2, &srcPR, &destPR), count = 0; + pr != NULL; + pr = gimp_pixel_rgns_process (pr), count++) + { + const guchar *src = srcPR.data; + guchar *dest = destPR.data; + gint row; + + for (row = 0; row < srcPR.h; row++) + { + const guchar *s = src; + guchar *d = dest; + gint pixels = srcPR.w; + + while (pixels--) + { + colorify_func (s, d, srcPR.bpp, NULL); + + s += srcPR.bpp; + d += destPR.bpp; + } + + src += srcPR.rowstride; + dest += destPR.rowstride; + } + + area_so_far += srcPR.w * srcPR.h; + + if ((count % 16) == 0) + gimp_progress_update ((gdouble) area_so_far / (gdouble) total_area); + } + + /* update the processed region */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1), (y2 - y1)); + } +} + +static gboolean +colorify_dialog (GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *label; + GtkWidget *button; + GtkWidget *table; + GtkWidget *color_area; + gint i; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Colorify"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_zoom_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (colorify), + drawable); + + table = gtk_table_new (2, 7, TRUE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + label = gtk_label_new (_("Custom color:")); + gtk_table_attach (GTK_TABLE (table), label, 4, 6, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + custom_color_button = gimp_color_button_new (_("Colorify Custom Color"), + COLOR_SIZE, COLOR_SIZE, + &cvals.color, + GIMP_COLOR_AREA_FLAT); + g_signal_connect (custom_color_button, "color-changed", + G_CALLBACK (gimp_color_button_get_color), + &cvals.color); + g_signal_connect_swapped (custom_color_button, "color-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_table_attach (GTK_TABLE (table), custom_color_button, 6, 7, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (custom_color_button); + + for (i = 0; i < 7; i++) + { + button = gtk_button_new (); + color_area = gimp_color_area_new (&button_color[i], + GIMP_COLOR_AREA_FLAT, + GDK_BUTTON2_MASK); + gtk_widget_set_size_request (GTK_WIDGET (color_area), + COLOR_SIZE, COLOR_SIZE); + gtk_container_add (GTK_CONTAINER (button), color_area); + g_signal_connect (button, "clicked", + G_CALLBACK (predefined_color_callback), + &button_color[i]); + gtk_widget_show (color_area); + + gtk_table_attach (GTK_TABLE (table), button, i, i + 1, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (button); + } + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +predefined_color_callback (GtkWidget *widget, + gpointer data) +{ + gimp_color_button_set_color (GIMP_COLOR_BUTTON (custom_color_button), + (GimpRGB *) data); +} diff --git a/plug-ins/common/colormap-remap.c b/plug-ins/common/colormap-remap.c new file mode 100644 index 0000000..6980ec8 --- /dev/null +++ b/plug-ins/common/colormap-remap.c @@ -0,0 +1,752 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Colormap remapping plug-in + * Copyright (C) 2006 Mukund Sivaraman + * + * This plug-in takes the colormap and lets you move colors from one index + * to another while keeping the original image visually unmodified. + * + * Such functionality is useful for creating graphics files for applications + * which expect certain indices to contain some specific colors. + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC_REMAP "plug-in-colormap-remap" +#define PLUG_IN_PROC_SWAP "plug-in-colormap-swap" +#define PLUG_IN_BINARY "colormap-remap" +#define PLUG_IN_ROLE "gimp-colormap-remap" + + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean remap (gint32 image_ID, + gint num_colors, + guchar *map); + +static gboolean remap_dialog (gint32 image_ID, + guchar *map); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef remap_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "num-colors", "Length of 'map' argument " + "(should be equal to colormap size)" }, + { GIMP_PDB_INT8ARRAY, "map", "Remap array for the colormap" } + }; + + static const GimpParamDef swap_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT8, "index1", "First index in the colormap" }, + { GIMP_PDB_INT8, "index2", "Second (other) index in the colormap" } + }; + + gimp_install_procedure (PLUG_IN_PROC_REMAP, + N_("Rearrange the colormap"), + "This procedure takes an indexed image and lets you " + "alter the positions of colors in the colormap " + "without visually changing the image.", + "Mukund Sivaraman ", + "Mukund Sivaraman ", + "June 2006", + N_("R_earrange Colormap..."), + "INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (remap_args), 0, + remap_args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC_REMAP, "/Colors/Map/Colormap"); + gimp_plugin_menu_register (PLUG_IN_PROC_REMAP, ""); + gimp_plugin_icon_register (PLUG_IN_PROC_REMAP, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) GIMP_ICON_COLORMAP); + + gimp_install_procedure (PLUG_IN_PROC_SWAP, + N_("Swap two colors in the colormap"), + "This procedure takes an indexed image and lets you " + "swap the positions of two colors in the colormap " + "without visually changing the image.", + "Mukund Sivaraman ", + "Mukund Sivaraman ", + "June 2006", + N_("_Swap Colors"), + "INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (swap_args), 0, + swap_args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + gint32 image_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + guchar map[256]; + gint i; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + image_ID = param[1].data.d_image; + + for (i = 0; i < 256; i++) + map[i] = i; + + if (strcmp (name, PLUG_IN_PROC_REMAP) == 0) + { + /* Make sure that the image is indexed */ + if (gimp_image_base_type (image_ID) != GIMP_INDEXED) + status = GIMP_PDB_EXECUTION_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + gint n_cols; + + g_free (gimp_image_get_colormap (image_ID, &n_cols)); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + if (! remap_dialog (image_ID, map)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + if (n_cols != param[3].data.d_int32) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + for (i = 0; i < n_cols; i++) + map[i] = param[4].data.d_int8array[i]; + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC_REMAP, map); + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (! remap (image_ID, n_cols, map)) + status = GIMP_PDB_EXECUTION_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC_REMAP, map, sizeof (map)); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + } + } + } + else if (strcmp (name, PLUG_IN_PROC_SWAP) == 0) + { + /* Make sure that the image is indexed */ + if (gimp_image_base_type (image_ID) != GIMP_INDEXED) + status = GIMP_PDB_EXECUTION_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + if (run_mode == GIMP_RUN_NONINTERACTIVE && nparams == 5) + { + guchar index1 = param[3].data.d_int8; + guchar index2 = param[4].data.d_int8; + gint n_cols; + + g_free (gimp_image_get_colormap (image_ID, &n_cols)); + + if (index1 >= n_cols || index2 >= n_cols) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + guchar tmp; + + tmp = map[index1]; + map[index1] = map[index2]; + map[index2] = tmp; + + if (! remap (image_ID, n_cols, map)) + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].data.d_status = status; +} + + +static gboolean +remap (gint32 image_ID, + gint num_colors, + guchar *map) +{ + guchar *cmap; + guchar *new_cmap; + guchar *new_cmap_i; + gint ncols; + gint num_layers; + gint32 *layers; + glong pixels = 0; + glong processed = 0; + guchar pixel_map[256]; + gboolean valid[256]; + gint i; + + cmap = gimp_image_get_colormap (image_ID, &ncols); + + g_return_val_if_fail (cmap != NULL, FALSE); + g_return_val_if_fail (ncols > 0, FALSE); + + if (num_colors != ncols) + { + g_message (_("Invalid remap array was passed to remap function")); + return FALSE; + } + + for (i = 0; i < ncols; i++) + valid[i] = FALSE; + + for (i = 0; i < ncols; i++) + { + if (map[i] >= ncols) + { + g_message (_("Invalid remap array was passed to remap function")); + return FALSE; + } + + valid[map[i]] = TRUE; + pixel_map[map[i]] = i; + } + + for (i = 0; i < ncols; i++) + if (valid[i] == FALSE) + { + g_message (_("Invalid remap array was passed to remap function")); + return FALSE; + } + + new_cmap = g_new (guchar, ncols * 3); + + new_cmap_i = new_cmap; + + for (i = 0; i < ncols; i++) + { + gint j = map[i] * 3; + + *new_cmap_i++ = cmap[j]; + *new_cmap_i++ = cmap[j + 1]; + *new_cmap_i++ = cmap[j + 2]; + } + + gimp_image_undo_group_start (image_ID); + + gimp_image_set_colormap (image_ID, new_cmap, ncols); + + g_free (cmap); + g_free (new_cmap); + + gimp_progress_init (_("Rearranging the colormap")); + + /* There is no needs to process the layers recursively, because + * indexed images cannot have layer groups. + */ + layers = gimp_image_get_layers (image_ID, &num_layers); + + for (i = 0; i < num_layers; i++) + pixels += + gimp_drawable_width (layers[i]) * gimp_drawable_height (layers[i]); + + for (i = 0; i < num_layers; i++) + { + GeglBuffer *buffer; + GeglBuffer *shadow; + const Babl *format; + GeglBufferIterator *iter; + GeglRectangle *src_roi; + GeglRectangle *dest_roi; + gint width, height, bpp; + gint update = 0; + + buffer = gimp_drawable_get_buffer (layers[i]); + shadow = gimp_drawable_get_shadow_buffer (layers[i]); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + format = gegl_buffer_get_format (buffer); + bpp = babl_format_get_bytes_per_pixel (format); + + iter = gegl_buffer_iterator_new (buffer, + GEGL_RECTANGLE (0, 0, width, height), 0, + format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2); + src_roi = &iter->items[0].roi; + + gegl_buffer_iterator_add (iter, shadow, + GEGL_RECTANGLE (0, 0, width, height), 0, + format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE); + dest_roi = &iter->items[1].roi; + + while (gegl_buffer_iterator_next (iter)) + { + const guchar *src_row = iter->items[0].data; + guchar *dest_row = iter->items[1].data; + gint y; + + for (y = 0; y < src_roi->height; y++) + { + const guchar *src = src_row; + guchar *dest = dest_row; + gint x; + + if (bpp == 1) + { + for (x = 0; x < src_roi->width; x++) + *dest++ = pixel_map[*src++]; + } + else + { + for (x = 0; x < src_roi->width; x++) + { + *dest++ = pixel_map[*src++]; + *dest++ = *src++; + } + } + + src_row += src_roi->width * bpp; + dest_row += dest_roi->width * bpp; + } + + processed += src_roi->width * src_roi->height; + update %= 16; + + if (update == 0) + gimp_progress_update ((gdouble) processed / pixels); + + update++; + } + + g_object_unref (buffer); + g_object_unref (shadow); + + gimp_drawable_merge_shadow (layers[i], TRUE); + gimp_drawable_update (layers[i], 0, 0, width, height); + } + + g_free (layers); + + gimp_progress_update (1.0); + + gimp_image_undo_group_end (image_ID); + + return TRUE; +} + + +/* dialog */ + +#define RESPONSE_RESET 1 + +enum +{ + COLOR_INDEX, + COLOR_INDEX_TEXT, + COLOR_RGB, + COLOR_H, + COLOR_S, + COLOR_V, + NUM_COLS +}; + +static GtkUIManager *remap_ui = NULL; +static gboolean remap_run = FALSE; +static gint reverse_order[256]; + + +static void +remap_sort (GtkTreeSortable *store, + gint column, + GtkSortType order) +{ + gtk_tree_sortable_set_sort_column_id (store, column, order); + gtk_tree_sortable_set_sort_column_id (store, + GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 0); +} + +static void +remap_sort_callback (GtkAction *action, + GtkTreeSortable *store) +{ + const gchar *name = gtk_action_get_name (action); + gint column = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID; + + g_return_if_fail (g_str_has_prefix (name, "sort-")); + + if (strncmp (name + 5, "hue", 3) == 0) + column = COLOR_H; + else if (strncmp (name + 5, "sat", 3) == 0) + column = COLOR_S; + else if (strncmp (name + 5, "val", 3) == 0) + column = COLOR_V; + + remap_sort (store, column, GTK_SORT_ASCENDING); +} + +static void +remap_reset_callback (GtkAction *action, + GtkTreeSortable *store) +{ + remap_sort (store, COLOR_INDEX, GTK_SORT_ASCENDING); +} + +static void +remap_reverse_callback (GtkAction *action, + GtkListStore *store) +{ + gtk_list_store_reorder (store, reverse_order); +} + +static GtkUIManager * +remap_ui_manager_new (GtkWidget *window, + GtkListStore *store) +{ + static const GtkActionEntry actions[] = + { + { + "sort-hue", NULL, N_("Sort on Hue"), NULL, NULL, + G_CALLBACK (remap_sort_callback) + }, + { + "sort-sat", NULL, N_("Sort on Saturation"), NULL, NULL, + G_CALLBACK (remap_sort_callback) + }, + { + "sort-val", NULL, N_("Sort on Value"), NULL, NULL, + G_CALLBACK (remap_sort_callback) + }, + { + "reverse", NULL, N_("Reverse Order"), NULL, NULL, + G_CALLBACK (remap_reverse_callback) + }, + { + "reset", GIMP_ICON_RESET, N_("Reset Order"), NULL, NULL, + G_CALLBACK (remap_reset_callback) + }, + }; + + GtkUIManager *ui_manager = gtk_ui_manager_new (); + GtkActionGroup *group = gtk_action_group_new ("Actions"); + GError *error = NULL; + + gtk_action_group_set_translation_domain (group, NULL); + gtk_action_group_add_actions (group, actions, G_N_ELEMENTS (actions), store); + + gtk_ui_manager_insert_action_group (ui_manager, group, -1); + g_object_unref (group); + + gtk_ui_manager_add_ui_from_string (ui_manager, + "" + " " + " " + " " + " " + " " + " " + " " + " " + "", + -1, &error); + if (error) + { + g_warning ("error parsing ui: %s", error->message); + g_clear_error (&error); + } + + return ui_manager; +} + +static gboolean +remap_popup_menu (GtkWidget *widget, + GdkEventButton *event) +{ + GtkWidget *menu = gtk_ui_manager_get_widget (remap_ui, "/remap-popup"); + + gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget)); + gtk_menu_popup (GTK_MENU (menu), + NULL, NULL, NULL, NULL, + event ? event->button : 0, + event ? event->time : gtk_get_current_event_time ()); + + return TRUE; +} + +static gboolean +remap_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + if (gdk_event_triggers_context_menu ((GdkEvent *) event)) + return remap_popup_menu (widget, event); + + return FALSE; +} + +static void +remap_response (GtkWidget *dialog, + gint response_id, + GtkTreeSortable *store) +{ + switch (response_id) + { + case RESPONSE_RESET: + remap_reset_callback (NULL, store); + break; + + case GTK_RESPONSE_OK: + remap_run = TRUE; + /* fallthrough */ + + default: + gtk_main_quit (); + break; + } +} + +static gboolean +remap_dialog (gint32 image_ID, + guchar *map) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *box; + GtkWidget *iconview; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeIter iter; + guchar *cmap; + gint ncols, i; + gboolean valid; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Rearrange Colormap"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC_REMAP, + + _("_Reset"), RESPONSE_RESET, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + RESPONSE_RESET, + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, TRUE, TRUE, 0); + + cmap = gimp_image_get_colormap (image_ID, &ncols); + + g_return_val_if_fail ((ncols > 0) && (ncols <= 256), FALSE); + + store = gtk_list_store_new (NUM_COLS, + G_TYPE_INT, G_TYPE_STRING, GIMP_TYPE_RGB, + G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE); + + for (i = 0; i < ncols; i++) + { + GimpRGB rgb; + GimpHSV hsv; + gint index = map[i]; + gchar *text = g_strdup_printf ("%d", index); + + gimp_rgb_set_uchar (&rgb, + cmap[index * 3], + cmap[index * 3 + 1], + cmap[index * 3 + 2]); + gimp_rgb_to_hsv (&rgb, &hsv); + + reverse_order[i] = ncols - i - 1; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLOR_INDEX, index, + COLOR_INDEX_TEXT, text, + COLOR_RGB, &rgb, + COLOR_H, hsv.h, + COLOR_S, hsv.s, + COLOR_V, hsv.v, + -1); + g_free (text); + } + + g_free (cmap); + + remap_ui = remap_ui_manager_new (dialog, store); + + iconview = gtk_icon_view_new_with_model (GTK_TREE_MODEL (store)); + g_object_unref (store); + + gtk_box_pack_start (GTK_BOX (vbox), iconview, TRUE, TRUE, 0); + + gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (iconview), + GTK_SELECTION_SINGLE); + gtk_icon_view_set_orientation (GTK_ICON_VIEW (iconview), + GTK_ORIENTATION_VERTICAL); + gtk_icon_view_set_columns (GTK_ICON_VIEW (iconview), 16); + gtk_icon_view_set_row_spacing (GTK_ICON_VIEW (iconview), 0); + gtk_icon_view_set_column_spacing (GTK_ICON_VIEW (iconview), 0); + gtk_icon_view_set_reorderable (GTK_ICON_VIEW (iconview), TRUE); + + renderer = gimp_cell_renderer_color_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (iconview), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (iconview), renderer, + "color", COLOR_RGB, + NULL); + g_object_set (renderer, + "width", 24, + NULL); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (iconview), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (iconview), renderer, + "text", COLOR_INDEX_TEXT, + NULL); + g_object_set (renderer, + "size-points", 6.0, + "xalign", 0.5, + "ypad", 0, + NULL); + + g_signal_connect (iconview, "popup-menu", + G_CALLBACK (remap_popup_menu), + NULL); + + g_signal_connect (iconview, "button-press-event", + G_CALLBACK (remap_button_press), + NULL); + + box = gimp_hint_box_new (_("Drag and drop colors to rearrange the colormap. " + "The numbers shown are the original indices. " + "Right-click for a menu with sort options.")); + + gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 0); + gtk_widget_show (box); + + g_signal_connect (dialog, "response", + G_CALLBACK (remap_response), + store); + + gtk_widget_show_all (dialog); + + gtk_main (); + + i = 0; + + for (valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + valid; + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)) + { + gint index; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + COLOR_INDEX, &index, + -1); + map[i++] = index; + } + + gtk_widget_destroy (dialog); + + return remap_run; +} diff --git a/plug-ins/common/compose.c b/plug-ins/common/compose.c new file mode 100644 index 0000000..2f70579 --- /dev/null +++ b/plug-ins/common/compose.c @@ -0,0 +1,1402 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Compose plug-in (C) 1997,1999 Peter Kirchgessner + * e-mail: peter@kirchgessner.net, WWW: http://www.kirchgessner.net + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This plug-in composes RGB-images from several types of channels + */ + +/* Lab colorspace support originally written by Alexey Dyachenko, + * merged into the official plug-in by Sven Neumann. + * + * Support for channels empty or filled with a single mask value + * added by Sylvain FORET. + */ + +/* + * All redundant _256 versions of YCbCr* are here only for compatibility . + * They can be dropped for GIMP 3.0 + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define COMPOSE_PROC "plug-in-compose" +#define DRAWABLE_COMPOSE_PROC "plug-in-drawable-compose" +#define RECOMPOSE_PROC "plug-in-recompose" +#define PLUG_IN_BINARY "compose" +#define PLUG_IN_ROLE "gimp-compose" + +/* Maximum number of images to compose */ +#define MAX_COMPOSE_IMAGES 4 + +typedef struct +{ + union + { + gint32 ID; /* Image ID of input images or drawable */ + guchar val; /* Mask value to compose with */ + } comp; + gboolean is_ID; +} ComposeInput; + +/* Description of a component */ +typedef struct +{ + const gchar *babl_name; + const gchar *name; + const gchar *icon; + const float range_min; /* val min of the component */ + const float range_max; /* val max of the component */ + const gboolean is_perceptual; /* Take the componenent from an Y' or Y buffer */ +} COMPONENT_DSC; + +/* Description of a composition */ +typedef struct +{ + const gchar *babl_model; + const gchar *compose_type; /* Type of composition ("RGB", "RGBA",...) */ + gint num_images; /* Number of input images needed */ + /* Channel information */ + const COMPONENT_DSC components[MAX_COMPOSE_IMAGES]; + const gchar *filename; /* Name of new image */ + +} COMPOSE_DSC; + + +/* Declare local functions + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void cpn_affine_transform (GeglBuffer *buffer, + gdouble min, + gdouble max); + +static void fill_buffer_from_components (GeglBuffer *temp[MAX_COMPOSE_IMAGES], + GeglBuffer *dst, + gint num_cpn, + ComposeInput *inputs, + gdouble mask_vals[MAX_COMPOSE_IMAGES]); + +static void perform_composition (COMPOSE_DSC curr_compose_dsc, + GeglBuffer *buffer_src[MAX_COMPOSE_IMAGES], + GeglBuffer *buffer_dst, + ComposeInput *inputs, + gint num_images); + +static gint32 compose (const gchar *compose_type, + ComposeInput *inputs, + gboolean compose_by_drawable); + +static gint32 create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageType gdtype, + GimpPrecision precision, + gint32 *layer_ID, + GeglBuffer **drawable); + +static gboolean compose_dialog (const gchar *compose_type, + gint32 drawable_ID); + +static gboolean check_gray (gint32 image_id, + gint32 drawable_id, + gpointer data); + +static void combo_callback (GimpIntComboBox *cbox, + gpointer data); + +static void scale_callback (GtkAdjustment *adj, + ComposeInput *input); + +static void check_response (GtkWidget *dialog, + gint response, + gpointer data); + +static void type_combo_callback (GimpIntComboBox *combo, + gpointer data); + + + +/* Decompositions availables. + * All the following values have to be kept in sync with those of decompose.c + */ + +#define CPN_RGBA_R { "R", N_("_Red:"), GIMP_ICON_CHANNEL_RED, 0.0, 1.0, FALSE} +#define CPN_RGBA_G { "G", N_("_Green:"), GIMP_ICON_CHANNEL_GREEN, 0.0, 1.0, FALSE} +#define CPN_RGBA_B { "B", N_("_Blue:"), GIMP_ICON_CHANNEL_BLUE, 0.0, 1.0, FALSE} +#define CPN_RGBA_A { "A", N_("_Alpha:"), GIMP_ICON_CHANNEL_ALPHA, 0.0, 1.0, TRUE} + +#define CPN_HSV_H { "hue", N_("_Hue:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSV_S { "saturation", N_("_Saturation:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSV_V { "value", N_("_Value:"), NULL, 0.0, 1.0, TRUE} + +#define CPN_HSL_H { "hue", N_("_Hue:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSL_S { "saturation", N_("_Saturation:"), NULL, 0.0, 1.0, TRUE} +#define CPN_HSL_L { "lightness", N_("_Lightness:"), NULL, 0.0, 1.0, TRUE} + +#define CPN_CMYK_C { "Cyan", N_("_Cyan:"), NULL, 0.0, 1.0, TRUE} +#define CPN_CMYK_M { "Magenta", N_("_Magenta:"), NULL, 0.0, 1.0, TRUE} +#define CPN_CMYK_Y { "Yellow", N_("_Yellow:"), NULL, 0.0, 1.0, TRUE} +#define CPN_CMYK_K { "Key", N_("_Black:"), NULL, 0.0, 1.0, TRUE} + +#define CPN_LAB_L { "CIE L", N_("_L:"), NULL, 0.0, 100.0, TRUE} +#define CPN_LAB_A { "CIE a", N_("_A:"), NULL, -127.5, 127.5, TRUE} +#define CPN_LAB_B { "CIE b", N_("_B:"), NULL, -127.5, 127.5, TRUE} + +#define CPN_LCH_L { "CIE L", N_("_L"), NULL, 0.0, 100.0, TRUE} +#define CPN_LCH_C { "CIE C(ab)", N_("_C"), NULL, 0.0, 200.0, TRUE} +#define CPN_LCH_H { "CIE H(ab)", N_("_H"), NULL, 0.0, 360.0, TRUE} + +#define CPN_YCBCR_Y { "Y'", N_("_Luma y470:"), NULL, 0.0, 1.0, TRUE } +#define CPN_YCBCR_CB { "Cb", N_("_Blueness cb470:"), NULL, -0.5, 0.5, TRUE } +#define CPN_YCBCR_CR { "Cr", N_("_Redness cr470:"), NULL, -0.5, 0.5, TRUE } + +#define CPN_YCBCR709_Y { "Y'", N_("_Luma y709:"), NULL, 0.0, 1.0, TRUE } +#define CPN_YCBCR709_CB { "Cb", N_("_Blueness cb709:"), NULL, -0.5, 0.5, TRUE } +#define CPN_YCBCR709_CR { "Cr", N_("_Redness cr709:"), NULL, -0.5, 0.5, TRUE } + + +static COMPOSE_DSC compose_dsc[] = +{ + { "RGB", + N_("RGB"), 3, + { CPN_RGBA_R, + CPN_RGBA_G, + CPN_RGBA_B }, + "rgb-compose" }, + + { "RGBA", + N_("RGBA"), 4, + { CPN_RGBA_R, + CPN_RGBA_G, + CPN_RGBA_B, + CPN_RGBA_A }, + "rgba-compose" }, + + { "HSV", + N_("HSV"), 3, + { CPN_HSV_H, + CPN_HSV_S, + CPN_HSV_V }, + "hsv-compose" }, + + { "HSL", + N_("HSL"), 3, + { CPN_HSL_H, + CPN_HSL_S, + CPN_HSL_L }, + "hsl-compose" }, + + { "CMYK", + N_("CMYK"), 4, + { CPN_CMYK_C, + CPN_CMYK_M, + CPN_CMYK_Y, + CPN_CMYK_K }, + "cmyk-compose" }, + + { "CIE Lab", + N_("LAB"), 3, + { CPN_LAB_L, + CPN_LAB_A, + CPN_LAB_B }, + "lab-compose" }, + + { "CIE LCH(ab)", + N_("LCH"), 3, + { CPN_LCH_L, + CPN_LCH_C, + CPN_LCH_H }, + "lch-compose" }, + + { "Y'CbCr", + N_("YCbCr_ITU_R470"), 3, + { CPN_YCBCR_Y, + CPN_YCBCR_CB, + CPN_YCBCR_CR }, + "ycbcr470-compose" }, + + { "Y'CbCr709", + N_("YCbCr_ITU_R709"), 3, + { CPN_YCBCR709_Y, + CPN_YCBCR709_CB, + CPN_YCBCR709_CR }, + "ycbcr709-compose" }, + + { "Y'CbCr", + N_("YCbCr_ITU_R470_256"), 3, + { CPN_YCBCR_Y, + CPN_YCBCR_CB, + CPN_YCBCR_CR }, + "ycbcr470F-compose" }, + + { "Y'CbCr709", + N_("YCbCr_ITU_R709_256"), 3, + { CPN_YCBCR709_Y, + CPN_YCBCR709_CB, + CPN_YCBCR709_CR }, + "ycbcr709F-compose" } +}; + + +typedef struct +{ + ComposeInput inputs[MAX_COMPOSE_IMAGES]; /* Image IDs or mask value of input */ + gchar compose_type[32]; /* type of composition */ + gboolean do_recompose; + gint32 source_layer_ID; /* for recomposing */ +} ComposeVals; + +/* Dialog structure */ +typedef struct +{ + gint width, height; /* Size of selected image */ + + GtkWidget *channel_label[MAX_COMPOSE_IMAGES]; /* The labels to change */ + GtkWidget *channel_icon[MAX_COMPOSE_IMAGES]; /* The icons */ + GtkWidget *channel_menu[MAX_COMPOSE_IMAGES]; /* The menus */ + GtkWidget *color_scales[MAX_COMPOSE_IMAGES]; /* The values color scales */ + GtkWidget *color_spins[MAX_COMPOSE_IMAGES]; /* The values spin buttons */ + + ComposeInput selected[MAX_COMPOSE_IMAGES]; /* Image Ids or mask values from menus */ + + gint compose_idx; /* Compose type */ +} ComposeInterface; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static ComposeVals composevals = +{ + {{{ 0 }}}, /* Image IDs of images to compose or mask values */ + "rgb", /* Type of composition */ + FALSE, /* Do recompose */ + -1 /* source layer ID */ +}; + +static ComposeInterface composeint = +{ + 0, 0, /* width, height */ + { NULL }, /* Label Widgets */ + { NULL }, /* Icon Widgets */ + { NULL }, /* Menu Widgets */ + { NULL }, /* Color Scale Widgets */ + { NULL }, /* Color Spin Widgets */ + {{{ 0 }}}, /* Image Ids or mask values from menus */ + 0 /* Compose type */ +}; + + +MAIN () + + +static void +query (void) +{ + static GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image1", "First input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (not used)" }, + { GIMP_PDB_IMAGE, "image2", "Second input image" }, + { GIMP_PDB_IMAGE, "image3", "Third input image" }, + { GIMP_PDB_IMAGE, "image4", "Fourth input image" }, + { GIMP_PDB_STRING, "compose-type", NULL } + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "new_image", "Output image" } + }; + + static GimpParamDef drw_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image1", "First input image (not used)" }, + { GIMP_PDB_DRAWABLE, "drawable1", "First input drawable" }, + { GIMP_PDB_DRAWABLE, "drawable2", "Second input drawable" }, + { GIMP_PDB_DRAWABLE, "drawable3", "Third input drawable" }, + { GIMP_PDB_DRAWABLE, "drawable4", "Fourth input drawable" }, + { GIMP_PDB_STRING, "compose-type", NULL } + }; + + static const GimpParamDef drw_return_vals[] = + { + { GIMP_PDB_IMAGE, "new_image", "Output image" } + }; + + static const GimpParamDef recompose_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Image to recompose from" }, + { GIMP_PDB_DRAWABLE, "drawable", "Not used" }, + }; + + GString *type_desc; + gint i; + + type_desc = g_string_new ("What to compose: "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, compose_dsc[0].compose_type); + g_string_append_c (type_desc, '"'); + + for (i = 1; i < G_N_ELEMENTS (compose_dsc); i++) + { + g_string_append (type_desc, ", "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, compose_dsc[i].compose_type); + g_string_append_c (type_desc, '"'); + } + + args[6].description = type_desc->str; + drw_args[6].description = type_desc->str; + + gimp_install_procedure (COMPOSE_PROC, + N_("Create an image using multiple gray images as color channels"), + "This function creates a new image from " + "multiple gray images", + "Peter Kirchgessner", + "Peter Kirchgessner (peter@kirchgessner.net)", + "1997", + N_("C_ompose..."), + "GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (COMPOSE_PROC, "/Colors/Components"); + + gimp_install_procedure (DRAWABLE_COMPOSE_PROC, + "Compose an image from multiple drawables of gray images", + "This function creates a new image from " + "multiple drawables of gray images", + "Peter Kirchgessner", + "Peter Kirchgessner (peter@kirchgessner.net)", + "1998", + NULL, /* It is not available in interactive mode */ + "GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (drw_args), + G_N_ELEMENTS (drw_return_vals), + drw_args, drw_return_vals); + + gimp_install_procedure (RECOMPOSE_PROC, + N_("Recompose an image that was previously decomposed"), + "This function recombines the grayscale layers produced " + "by Decompose into a single RGB or RGBA layer, and " + "replaces the originally decomposed layer with the " + "result.", + "Bill Skaggs", + "Bill Skaggs", + "2004", + N_("R_ecompose"), + "GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (recompose_args), 0, + recompose_args, NULL); + + gimp_plugin_menu_register (RECOMPOSE_PROC, "/Colors/Components"); + + g_string_free (type_desc, TRUE); +} + + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gint32 image_ID; + gint32 drawable_ID = -1; + gint compose_by_drawable; + gint i; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + compose_by_drawable = (strcmp (name, DRAWABLE_COMPOSE_PROC) == 0); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_int32 = -1; + + if (strcmp (name, RECOMPOSE_PROC) == 0) + { + GimpParasite *parasite = gimp_image_get_parasite (param[1].data.d_image, + "decompose-data"); + + if (! parasite) + { + g_message (_("You can only run 'Recompose' if the active image " + "was originally produced by 'Decompose'.")); + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + gint nret; + + nret = sscanf (gimp_parasite_data (parasite), + "source=%d type=%31s %d %d %d %d", + &composevals.source_layer_ID, + composevals.compose_type, + &composevals.inputs[0].comp.ID, + &composevals.inputs[1].comp.ID, + &composevals.inputs[2].comp.ID, + &composevals.inputs[3].comp.ID); + + gimp_parasite_free (parasite); + + for (i = 0; i < MAX_COMPOSE_IMAGES; i++) + composevals.inputs[i].is_ID = TRUE; + + if (nret < 5) + { + g_message (_("Error scanning 'decompose-data' parasite: " + "too few layers found")); + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + composevals.do_recompose = TRUE; + compose_by_drawable = TRUE; + } + } + } + else + { + composevals.do_recompose = FALSE; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (name, &composevals); + + compose_by_drawable = TRUE; + + /* The dialog is now drawable based. Get a drawable-ID of the image */ + if (strcmp (name, COMPOSE_PROC) == 0) + { + gint32 *layer_list; + gint nlayers; + + layer_list = gimp_image_get_layers (param[1].data.d_int32, + &nlayers); + if ((layer_list == NULL) || (nlayers <= 0)) + { + g_message (_("Could not get layers for image %d"), + (gint) param[1].data.d_int32); + return; + } + + drawable_ID = layer_list[0]; + g_free (layer_list); + } + else + { + drawable_ID = param[2].data.d_int32; + } + + /* First acquire information with a dialog */ + if (! compose_dialog (composevals.compose_type, drawable_ID)) + return; + + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams < 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + composevals.inputs[0].comp.ID = (compose_by_drawable ? + param[2].data.d_int32 : + param[1].data.d_int32); + composevals.inputs[1].comp.ID = param[3].data.d_int32; + composevals.inputs[2].comp.ID = param[4].data.d_int32; + composevals.inputs[3].comp.ID = param[5].data.d_int32; + + strncpy (composevals.compose_type, param[6].data.d_string, + sizeof (composevals.compose_type)); + composevals.compose_type[sizeof (composevals.compose_type)-1] = '\0'; + + for (i = 0; i < MAX_COMPOSE_IMAGES; i++) + { + if (composevals.inputs[i].comp.ID == -1) + { + composevals.inputs[i].is_ID = FALSE; + composevals.inputs[i].comp.val = 0; + } + else + { + composevals.inputs[i].is_ID = TRUE; + } + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (name, &composevals); + + compose_by_drawable = TRUE; + break; + + default: + break; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_progress_init (_("Composing")); + + image_ID = compose (composevals.compose_type, + composevals.inputs, + compose_by_drawable); + + if (image_ID < 0) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + values[1].data.d_int32 = image_ID; + + if (composevals.do_recompose) + { + gimp_displays_flush (); + } + else + { + gimp_image_undo_enable (image_ID); + gimp_image_clean_all (image_ID); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_display_new (image_ID); + } + } + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (name, &composevals, sizeof (ComposeVals)); + } + + *nreturn_vals = composevals.do_recompose ? 1 : 2; + + values[0].data.d_status = status; +} + +static void +cpn_affine_transform (GeglBuffer *buffer, + gdouble min, + gdouble max) +{ + GeglBufferIterator *gi; + const gdouble scale = max - min; + const gdouble offset = min; + + /* We want to scale values linearly, regardless of the format of the buffer */ + gegl_buffer_set_format (buffer, babl_format ("Y double")); + + gi = gegl_buffer_iterator_new (buffer, NULL, 0, NULL, + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (gi)) + { + gdouble *data = gi->items[0].data; + guint k; + + for (k = 0; k < gi->length; k++) + { + data[k] = data[k] * scale + offset; + } + } +} + +static void +fill_buffer_from_components (GeglBuffer *temp[MAX_COMPOSE_IMAGES], + GeglBuffer *dst, + gint num_cpn, + ComposeInput *inputs, + gdouble mask_vals[MAX_COMPOSE_IMAGES]) +{ + GeglBufferIterator *gi; + gint j; + + gi = gegl_buffer_iterator_new (dst, NULL, 0, NULL, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 10); + + for (j = 0; j < num_cpn; j++) + { + if (inputs[j].is_ID) + gegl_buffer_iterator_add (gi, temp[j], NULL, 0, NULL, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE); + } + + while (gegl_buffer_iterator_next (gi)) + { + gdouble *src_data[MAX_COMPOSE_IMAGES]; + gdouble *dst_data = (gdouble*) gi->items[0].data; + gulong k, count; + + count = 1; + for (j = 0; j < num_cpn; j++) + if (inputs[j].is_ID) + src_data[j] = (gdouble*) gi->items[count++].data; + + for (k = 0; k < gi->length; k++) + { + gulong pos = k * num_cpn; + + for (j = 0; j < num_cpn; j++) + { + if (inputs[j].is_ID) + dst_data[pos+j] = src_data[j][k]; + else + dst_data[pos+j] = mask_vals[j]; + } + } + } +} + +static void +perform_composition (COMPOSE_DSC curr_compose_dsc, + GeglBuffer *buffer_src[MAX_COMPOSE_IMAGES], + GeglBuffer *buffer_dst, + ComposeInput *inputs, + gint num_images) +{ + const Babl *dst_format; + GeglBuffer *temp[MAX_COMPOSE_IMAGES]; + GeglBuffer *dst_temp; + const GeglRectangle *extent = NULL; + + const COMPONENT_DSC *components; + gdouble mask_vals[MAX_COMPOSE_IMAGES]; + gint i; + + components = curr_compose_dsc.components; + + /* Get all individual components in gray buffers */ + for (i = 0; i < num_images; i++) + { + COMPONENT_DSC cpn_dsc = components[i]; + const Babl *gray_format; + + if (cpn_dsc.is_perceptual) + gray_format = babl_format ("Y' double"); + else + gray_format = babl_format ("Y double"); + + if (! inputs[i].is_ID) + { + const Babl *fish = babl_fish (babl_format ("Y' u8"), gray_format); + + babl_process (fish, &inputs[i].comp.val, &mask_vals[i], 1); + + mask_vals[i] = mask_vals[i] * (cpn_dsc.range_max - cpn_dsc.range_min) + cpn_dsc.range_min; + } + else + { + extent = gegl_buffer_get_extent (buffer_src[i]); + + temp[i] = gegl_buffer_new (extent, gray_format); + + gegl_buffer_copy (buffer_src[i], NULL, GEGL_ABYSS_NONE, temp[i], NULL); + + if (cpn_dsc.range_min != 0.0 || cpn_dsc.range_max != 1.0) + cpn_affine_transform (temp[i], cpn_dsc.range_min, cpn_dsc.range_max); + } + + gimp_progress_update ((gdouble) i / (gdouble) (num_images + 1.0)); + } + + dst_format = babl_format_new (babl_model (curr_compose_dsc.babl_model), + babl_type ("double"), + babl_component (components[0].babl_name), + num_images > 1 ? babl_component (components[1].babl_name) : NULL, + num_images > 2 ? babl_component (components[2].babl_name) : NULL, + num_images > 3 ? babl_component (components[3].babl_name) : NULL, + NULL); + + /* extent is not NULL because there is at least one drawable */ + dst_temp = gegl_buffer_new (extent, dst_format); + + /* Gather all individual components in the dst_format buffer */ + fill_buffer_from_components (temp, dst_temp, num_images, inputs, mask_vals); + + gimp_progress_update ((gdouble) num_images / (gdouble) (num_images + 1.0)); + + /* Copy back to the format GIMP wants (and perform the conversion in itself) */ + gegl_buffer_copy (dst_temp, NULL, GEGL_ABYSS_NONE, buffer_dst, NULL); + + for (i = 0; i< num_images; i++) + if( inputs[i].is_ID) + g_object_unref (temp[i]); + + g_object_unref (dst_temp); +} + +/* Compose an image from several gray-images */ +static gint32 +compose (const gchar *compose_type, + ComposeInput *inputs, + gboolean compose_by_drawable) +{ + gint width, height; + gint num_images, compose_idx; + gint i, j; + gint num_layers; + gint32 layer_ID_dst, image_ID_dst; + gint first_ID; + GimpImageType gdtype_dst; + GeglBuffer *buffer_src[MAX_COMPOSE_IMAGES]; + GeglBuffer *buffer_dst; + GimpPrecision precision; + + /* Search type of composing */ + compose_idx = -1; + for (j = 0; j < G_N_ELEMENTS (compose_dsc); j++) + { + if (g_ascii_strcasecmp (compose_type, compose_dsc[j].compose_type) == 0) + { + compose_idx = j; + break; + } + } + + if (compose_idx < 0) + return -1; + + num_images = compose_dsc[compose_idx].num_images; + + /* Check that at least one image or one drawable is provided */ + first_ID = -1; + for (i = 0; i < num_images; i++) + { + if (inputs[i].is_ID) + { + first_ID = i; + break; + } + } + + if (-1 == first_ID) + { + g_message (_("At least one image is needed to compose")); + return -1; + } + + /* Check image sizes */ + if (compose_by_drawable) + { + gint32 first_image = gimp_item_get_image (inputs[first_ID].comp.ID); + + if (! gimp_item_is_valid (inputs[first_ID].comp.ID)) + { + g_message (_("Specified layer %d not found"), + inputs[first_ID].comp.ID); + return -1; + } + + width = gimp_drawable_width (inputs[first_ID].comp.ID); + height = gimp_drawable_height (inputs[first_ID].comp.ID); + + precision = gimp_image_get_precision (first_image); + + for (j = first_ID + 1; j < num_images; j++) + { + if (inputs[j].is_ID) + { + if (! gimp_item_is_valid (inputs[j].comp.ID)) + { + g_message (_("Specified layer %d not found"), + inputs[j].comp.ID); + return -1; + } + + if ((width != gimp_drawable_width (inputs[j].comp.ID)) || + (height != gimp_drawable_height (inputs[j].comp.ID))) + { + g_message (_("Drawables have different size")); + return -1; + } + } + } + + for (j = 0; j < num_images; j++) + { + if (inputs[j].is_ID) + buffer_src[j] = gimp_drawable_get_buffer (inputs[j].comp.ID); + else + buffer_src[j] = NULL; + } + } + else /* Compose by image ID */ + { + width = gimp_image_width (inputs[first_ID].comp.ID); + height = gimp_image_height (inputs[first_ID].comp.ID); + + precision = gimp_image_get_precision (inputs[first_ID].comp.ID); + + for (j = first_ID + 1; j < num_images; j++) + { + if (inputs[j].is_ID) + { + if ((width != gimp_image_width (inputs[j].comp.ID)) || + (height != gimp_image_height (inputs[j].comp.ID))) + { + g_message (_("Images have different size")); + return -1; + } + } + } + + /* Get first layer/drawable for all input images */ + for (j = 0; j < num_images; j++) + { + if (inputs[j].is_ID) + { + gint32 *layers; + + /* Get first layer of image */ + layers = gimp_image_get_layers (inputs[j].comp.ID, &num_layers); + + if (! layers || (num_layers <= 0)) + { + g_message (_("Error in getting layer IDs")); + return -1; + } + + /* Get drawable for layer */ + buffer_src[j] = gimp_drawable_get_buffer (layers[0]); + g_free (layers); + } + } + } + + /* Unless recomposing, create new image */ + if (composevals.do_recompose) + { + layer_ID_dst = composevals.source_layer_ID; + + if (! gimp_item_is_valid (layer_ID_dst)) + { + g_message (_("Unable to recompose, source layer not found")); + return -1; + } + + image_ID_dst = gimp_item_get_image (layer_ID_dst); + + buffer_dst = gimp_drawable_get_shadow_buffer (layer_ID_dst); + } + else + { + gdtype_dst = ((babl_model (compose_dsc[compose_idx].babl_model) == babl_model ("RGBA")) ? + GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE); + + image_ID_dst = create_new_image (compose_dsc[compose_idx].filename, + width, height, gdtype_dst, precision, + &layer_ID_dst, &buffer_dst); + } + + if (! compose_by_drawable) + { + gdouble xres, yres; + + gimp_image_get_resolution (inputs[first_ID].comp.ID, &xres, &yres); + gimp_image_set_resolution (image_ID_dst, xres, yres); + } + + perform_composition (compose_dsc[compose_idx], + buffer_src, + buffer_dst, + inputs, + num_images); + + gimp_progress_update (1.0); + + for (j = 0; j < num_images; j++) + { + if (inputs[j].is_ID) + g_object_unref (buffer_src[j]); + } + + g_object_unref (buffer_dst); + + if (composevals.do_recompose) + gimp_drawable_merge_shadow (layer_ID_dst, TRUE); + + gimp_drawable_update (layer_ID_dst, 0, 0, + gimp_drawable_width (layer_ID_dst), + gimp_drawable_height (layer_ID_dst)); + + return image_ID_dst; +} + + +/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */ +static gint32 +create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageType gdtype, + GimpPrecision precision, + gint32 *layer_ID, + GeglBuffer **buffer) +{ + gint32 image_ID; + GimpImageBaseType gitype; + + if ((gdtype == GIMP_GRAY_IMAGE) || (gdtype == GIMP_GRAYA_IMAGE)) + gitype = GIMP_GRAY; + else if ((gdtype == GIMP_INDEXED_IMAGE) || (gdtype == GIMP_INDEXEDA_IMAGE)) + gitype = GIMP_INDEXED; + else + gitype = GIMP_RGB; + + image_ID = gimp_image_new_with_precision (width, height, gitype, precision); + + gimp_image_undo_disable (image_ID); + gimp_image_set_filename (image_ID, filename); + + *layer_ID = gimp_layer_new (image_ID, _("Background"), width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, *layer_ID, -1, 0); + + *buffer = gimp_drawable_get_buffer (*layer_ID); + + return image_ID; +} + + +static gboolean +compose_dialog (const gchar *compose_type, + gint32 drawable_ID) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *table; + GtkSizeGroup *size_group; + gint32 *layer_list; + gint nlayers; + gint j; + gboolean run; + + /* Check default compose type */ + composeint.compose_idx = 0; + for (j = 0; j < G_N_ELEMENTS (compose_dsc); j++) + { + if (g_ascii_strcasecmp (compose_type, compose_dsc[j].compose_type) == 0) + { + composeint.compose_idx = j; + break; + } + } + + /* Save original image width/height */ + composeint.width = gimp_drawable_width (drawable_ID); + composeint.height = gimp_drawable_height (drawable_ID); + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + layer_list = gimp_image_get_layers (gimp_item_get_image (drawable_ID), + &nlayers); + + dialog = gimp_dialog_new (_("Compose"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, COMPOSE_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_signal_connect (dialog, "response", + G_CALLBACK (check_response), + NULL); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + /* Compose type combo */ + + frame = gimp_frame_new (_("Compose Channels")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_widget_show (hbox); + + size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + label = gtk_label_new_with_mnemonic (_("Color _model:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gtk_size_group_add_widget (size_group, label); + g_object_unref (size_group); + + combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + for (j = 0; j < G_N_ELEMENTS (compose_dsc); j++) + { + gchar *label = g_strdup (gettext (compose_dsc[j].compose_type)); + gchar *l; + + for (l = label; *l; l++) + if (*l == '-' || *l == '_') + *l = ' '; + + gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (combo), + GIMP_INT_STORE_LABEL, label, + GIMP_INT_STORE_VALUE, j, + -1); + g_free (label); + } + + gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + /* Channel representation table */ + + frame = gimp_frame_new (_("Channel Representations")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + table = gtk_table_new (MAX_COMPOSE_IMAGES, 4, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + for (j = 0; j < MAX_COMPOSE_IMAGES; j++) + { + GtkWidget *image; + GtkWidget *label; + GtkWidget *combo; + GtkObject *scale; + GtkTreeIter iter; + GtkTreeModel *model; + GdkPixbuf *ico; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_table_attach (GTK_TABLE (table), hbox, 0, 1, j, j + 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + gtk_size_group_add_widget (size_group, hbox); + + composeint.channel_icon[j] = image = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + composeint.channel_label[j] = label = gtk_label_new_with_mnemonic (""); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + if (composeint.compose_idx >= 0 && + nlayers >= compose_dsc[composeint.compose_idx].num_images && + j < nlayers) + { + composeint.selected[j].comp.ID = layer_list[j]; + } + else + { + composeint.selected[j].comp.ID = drawable_ID; + } + + composeint.selected[j].is_ID = TRUE; + + combo = gimp_drawable_combo_box_new (check_gray, NULL); + composeint.channel_menu[j] = combo; + + ico = gtk_widget_render_icon (dialog, + GIMP_ICON_CHANNEL_GRAY, + GTK_ICON_SIZE_BUTTON, NULL); + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + GIMP_INT_STORE_VALUE, -1, + GIMP_INT_STORE_LABEL, _("Mask value"), + GIMP_INT_STORE_PIXBUF, ico, + -1); + g_object_unref (ico); + gtk_table_attach (GTK_TABLE (table), combo, 1, 2, j, j + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + scale = gimp_color_scale_entry_new (GTK_TABLE (table), 2, j, NULL, + 100, 4, + 255.0, 0.0, 255.0, 1.0, 10.0, 0, + NULL, NULL); + composeint.color_scales[j] = GIMP_SCALE_ENTRY_SCALE (scale); + composeint.color_spins[j] = GIMP_SCALE_ENTRY_SPINBUTTON (scale); + + gtk_widget_set_sensitive (composeint.color_scales[j], FALSE); + gtk_widget_set_sensitive (composeint.color_spins[j], FALSE); + + g_signal_connect (scale, "value-changed", + G_CALLBACK (scale_callback), + &composeint.selected[j]); + + /* This has to be connected last otherwise it will emit before + * combo_callback has any scale and spinbutton to work with + */ + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + composeint.selected[j].comp.ID, + G_CALLBACK (combo_callback), + GINT_TO_POINTER (j)); + } + + g_free (layer_list); + + /* Calls the combo callback and sets icons, labels and sensitivity */ + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + composeint.compose_idx, + G_CALLBACK (type_combo_callback), + NULL); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + if (run) + { + gint j; + + for (j = 0; j < MAX_COMPOSE_IMAGES; j++) + { + composevals.inputs[j].is_ID = composeint.selected[j].is_ID; + + if (composevals.inputs[j].is_ID) + composevals.inputs[j].comp.ID = composeint.selected[j].comp.ID; + else + composevals.inputs[j].comp.val = composeint.selected[j].comp.val; + } + + strcpy (composevals.compose_type, + compose_dsc[composeint.compose_idx].compose_type); + } + + return run; +} + +/* Compose interface functions */ + +static gboolean +check_gray (gint32 image_id, + gint32 drawable_id, + gpointer data) + +{ + return ((gimp_image_base_type (image_id) == GIMP_GRAY) && + (gimp_image_width (image_id) == composeint.width) && + (gimp_image_height (image_id) == composeint.height)); +} + +static void +check_response (GtkWidget *dialog, + gint response, + gpointer data) +{ + switch (response) + { + case GTK_RESPONSE_OK: + { + gint i; + gint nb = 0; + gboolean has_image = FALSE; + + nb = compose_dsc[composeint.compose_idx].num_images; + + for (i = 0; i < nb; i++) + { + if (composeint.selected[i].is_ID) + { + has_image = TRUE; + break; + } + } + + if (! has_image) + { + GtkWidget *d; + + g_signal_stop_emission_by_name (dialog, "response"); + d = gtk_message_dialog_new (GTK_WINDOW (dialog), + GTK_DIALOG_DESTROY_WITH_PARENT | + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("At least one image is needed to compose")); + + gtk_dialog_run (GTK_DIALOG (d)); + gtk_widget_destroy (d); + } + } + break; + + default: + break; + } +} + +static void +combo_callback (GimpIntComboBox *widget, + gpointer data) +{ + gint id; + gint n; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &id); + n = GPOINTER_TO_INT (data); + + if (id == -1) + { + gtk_widget_set_sensitive (composeint.color_scales[n], TRUE); + gtk_widget_set_sensitive (composeint.color_spins[n], TRUE); + + composeint.selected[n].is_ID = FALSE; + composeint.selected[n].comp.val = + gtk_range_get_value (GTK_RANGE (composeint.color_scales[n])); + } + else + { + gtk_widget_set_sensitive (composeint.color_scales[n], FALSE); + gtk_widget_set_sensitive (composeint.color_spins[n], FALSE); + + composeint.selected[n].is_ID = TRUE; + composeint.selected[n].comp.ID = id; + } +} + +static void +scale_callback (GtkAdjustment *adj, + ComposeInput *input) +{ + input->comp.val = gtk_adjustment_get_value (adj); +} + +static void +type_combo_callback (GimpIntComboBox *combo, + gpointer data) +{ + if (gimp_int_combo_box_get_active (combo, &composeint.compose_idx)) + { + gboolean combo4; + gboolean scale4; + gint compose_idx; + gint j; + + compose_idx = composeint.compose_idx; + + for (j = 0; j < MAX_COMPOSE_IMAGES; j++) + { + GtkWidget *label = composeint.channel_label[j]; + GtkWidget *image = composeint.channel_icon[j]; + const gchar *text = compose_dsc[compose_idx].components[j].name; + const gchar *icon = compose_dsc[compose_idx].components[j].icon; + + gtk_label_set_text_with_mnemonic (GTK_LABEL (label), + text ? gettext (text) : ""); + + if (icon) + { + gtk_image_set_from_icon_name (GTK_IMAGE (image), + icon, GTK_ICON_SIZE_BUTTON); + gtk_widget_show (image); + } + else + { + gtk_image_clear (GTK_IMAGE (image)); + gtk_widget_hide (image); + } + } + + /* Set sensitivity of last menu */ + combo4 = (compose_dsc[compose_idx].num_images == 4); + gtk_widget_set_sensitive (composeint.channel_menu[3], combo4); + + scale4 = combo4 && !composeint.selected[3].is_ID; + gtk_widget_set_sensitive (composeint.color_scales[3], scale4); + gtk_widget_set_sensitive (composeint.color_spins[3], scale4); + } +} diff --git a/plug-ins/common/contrast-retinex.c b/plug-ins/common/contrast-retinex.c new file mode 100644 index 0000000..f48e5c9 --- /dev/null +++ b/plug-ins/common/contrast-retinex.c @@ -0,0 +1,840 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimp/gimp.h" +#include "libgimp/gimpui.h" + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-retinex" +#define PLUG_IN_BINARY "contrast-retinex" +#define PLUG_IN_ROLE "gimp-contrast-retinex" +#define MAX_RETINEX_SCALES 8 +#define MIN_GAUSSIAN_SCALE 16 +#define MAX_GAUSSIAN_SCALE 250 +#define SCALE_WIDTH 150 +#define ENTRY_WIDTH 4 + + +typedef struct +{ + gint scale; + gint nscales; + gint scales_mode; + gfloat cvar; +} RetinexParams; + +typedef enum +{ + filter_uniform, + filter_low, + filter_high +} FilterMode; + +/* + Definit comment sont repartis les + differents filtres en fonction de + l'echelle (~= ecart type de la gaussienne) + */ +#define RETINEX_UNIFORM 0 +#define RETINEX_LOW 1 +#define RETINEX_HIGH 2 + +static gfloat RetinexScales[MAX_RETINEX_SCALES]; + +typedef struct +{ + gint N; + gfloat sigma; + gdouble B; + gdouble b[4]; +} gauss3_coefs; + + +/* + * Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +/* Gimp */ +static gboolean retinex_dialog (gint32 drawable_ID); +static void retinex (gint32 drawable_ID, + GimpPreview *preview); +static void retinex_preview (gpointer drawable_ID, + GimpPreview *preview); + +static void retinex_scales_distribution (gfloat *scales, + gint nscales, + gint mode, + gint s); + +static void compute_mean_var (gfloat *src, + gfloat *mean, + gfloat *var, + gint size, + gint bytes); +/* + * Gauss + */ +static void compute_coefs3 (gauss3_coefs *c, + gfloat sigma); + +static void gausssmooth (gfloat *in, + gfloat *out, + gint size, + gint rowtride, + gauss3_coefs *c); + +/* + * MSRCR = MultiScale Retinex with Color Restoration + */ +static void MSRCR (guchar *src, + gint width, + gint height, + gint bytes, + gboolean preview_mode); + + +/* + * Private variables. + */ +static RetinexParams rvals = +{ + 240, /* Scale */ + 3, /* Scales */ + RETINEX_UNIFORM, /* Echelles reparties uniformement */ + 1.2 /* A voir */ +}; + +static GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "scale", "Biggest scale value" }, + { GIMP_PDB_INT32, "nscales", "Number of scales" }, + { GIMP_PDB_INT32, "scales-mode", "Retinex distribution through scales" }, + { GIMP_PDB_FLOAT, "cvar", "Variance value" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Enhance contrast using the Retinex method"), + "The Retinex Image Enhancement Algorithm is an " + "automatic image enhancement method that enhances " + "a digital image in terms of dynamic range " + "compression, color independence from the spectral " + "distribution of the scene illuminant, and " + "color/lightness rendition.", + "Fabien Pelisson", + "Fabien Pelisson", + "2003", + N_("Retine_x..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Colors/Tone Mapping"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + gint32 drawable_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint x, y, width, height; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + run_mode = param[0].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + if (! gimp_drawable_mask_intersect (drawable_ID, + &x, &y, &width, &height) || + width < MIN_GAUSSIAN_SCALE || + height < MIN_GAUSSIAN_SCALE) + { + status = GIMP_PDB_EXECUTION_ERROR; + values[0].data.d_status = status; + return; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &rvals); + + /* First acquire information with a dialog */ + if (! retinex_dialog (drawable_ID)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + rvals.scale = (param[3].data.d_int32); + rvals.nscales = (param[4].data.d_int32); + rvals.scales_mode = (param[5].data.d_int32); + rvals.cvar = (param[6].data.d_float); + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &rvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS && + (gimp_drawable_is_rgb (drawable_ID))) + { + gimp_progress_init (_("Retinex")); + + retinex (drawable_ID, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &rvals, sizeof (RetinexParams)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + values[0].data.d_status = status; +} + + +static gboolean +retinex_dialog (gint32 drawable_ID) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *table; + GtkWidget *combo; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Retinex Image Enhancement"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_zoom_preview_new_from_drawable_id (drawable_ID); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (retinex_preview), + GINT_TO_POINTER (drawable_ID)); + + table = gtk_table_new (4, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + combo = gimp_int_combo_box_new (_("Uniform"), filter_uniform, + _("Low"), filter_low, + _("High"), filter_high, + NULL); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), rvals.scales_mode, + G_CALLBACK (gimp_int_combo_box_get_active), + &rvals.scales_mode); + g_signal_connect_swapped (combo, "changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Level:"), 0.0, 0.5, + combo, 2, FALSE); + gtk_widget_show (combo); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Scale:"), SCALE_WIDTH, ENTRY_WIDTH, + rvals.scale, + MIN_GAUSSIAN_SCALE, MAX_GAUSSIAN_SCALE, 1, 1, 0, + TRUE, 0, 0, NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &rvals.scale); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("Scale _division:"), SCALE_WIDTH, ENTRY_WIDTH, + rvals.nscales, + 0, MAX_RETINEX_SCALES, 1, 1, 0, + TRUE, 0, 0, NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &rvals.nscales); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 3, + _("Dy_namic:"), SCALE_WIDTH, ENTRY_WIDTH, + rvals.cvar, 0, 4, 0.1, 0.1, 1, + TRUE, 0, 0, NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_float_adjustment_update), + &rvals.cvar); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +/* + * Applies the algorithm + */ +static void +retinex (gint32 drawable_ID, + GimpPreview *preview) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + guchar *src = NULL; + guchar *psrc = NULL; + gint x, y, width, height; + gint size, bytes; + + /* + * Get the size of the current image or its selection. + */ + if (preview) + { + src = gimp_zoom_preview_get_source (GIMP_ZOOM_PREVIEW (preview), + &width, &height, &bytes); + } + else + { + if (! gimp_drawable_mask_intersect (drawable_ID, + &x, &y, &width, &height)) + return; + + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + bytes = babl_format_get_bytes_per_pixel (format); + + /* Allocate memory */ + size = width * height * bytes; + src = g_try_malloc (sizeof (guchar) * size); + + if (src == NULL) + { + g_warning ("Failed to allocate memory"); + return; + } + + memset (src, 0, sizeof (guchar) * size); + + /* Fill allocated memory with pixel data */ + src_buffer = gimp_drawable_get_buffer (drawable_ID); + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x, y, width, height), 1.0, + format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + /* + Algorithm for Multi-scale Retinex with color Restoration (MSRCR). + */ + psrc = src; + MSRCR (psrc, width, height, bytes, preview != NULL); + + if (preview) + { + gimp_preview_draw_buffer (preview, psrc, width * bytes); + } + else + { + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x, y, width, height), 0, + format, psrc, + GEGL_AUTO_ROWSTRIDE); + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, x, y, width, height); + } + + g_free (src); +} + +static void +retinex_preview (gpointer drawable_ID, + GimpPreview *preview) +{ + retinex (GPOINTER_TO_INT (drawable_ID), preview); +} + +/* + * calculate scale values for desired distribution. + */ +static void +retinex_scales_distribution (gfloat *scales, + gint nscales, + gint mode, + gint s) +{ + if (nscales == 1) + { /* For one filter we choose the median scale */ + scales[0] = (gint) s / 2; + } + else if (nscales == 2) + { /* For two filters we choose the median and maximum scale */ + scales[0] = (gint) s / 2; + scales[1] = (gint) s; + } + else + { + gfloat size_step = (gfloat) s / (gfloat) nscales; + gint i; + + switch(mode) + { + case RETINEX_UNIFORM: + for(i = 0; i < nscales; ++i) + scales[i] = 2. + (gfloat) i * size_step; + break; + + case RETINEX_LOW: + size_step = (gfloat) log(s - 2.0) / (gfloat) nscales; + for (i = 0; i < nscales; ++i) + scales[i] = 2. + pow (10, (i * size_step) / log (10)); + break; + + case RETINEX_HIGH: + size_step = (gfloat) log(s - 2.0) / (gfloat) nscales; + for (i = 0; i < nscales; ++i) + scales[i] = s - pow (10, (i * size_step) / log (10)); + break; + + default: + break; + } + } +} + +/* + * Calculate the coefficients for the recursive filter algorithm + * Fast Computation of gaussian blurring. + */ +static void +compute_coefs3 (gauss3_coefs *c, gfloat sigma) +{ + /* + * Papers: "Recursive Implementation of the gaussian filter.", + * Ian T. Young , Lucas J. Van Vliet, Signal Processing 44, Elsevier 1995. + * formula: 11b computation of q + * 8c computation of b0..b1 + * 10 alpha is normalization constant B + */ + gfloat q, q2, q3; + + if (sigma >= 2.5) + { + q = 0.98711 * sigma - 0.96330; + } + else if ((sigma >= 0.5) && (sigma < 2.5)) + { + q = 3.97156 - 4.14554 * (gfloat) sqrt ((double) 1 - 0.26891 * sigma); + } + else + { + q = 0.1147705018520355224609375; + } + + q2 = q * q; + q3 = q * q2; + c->b[0] = (1.57825+(2.44413*q)+(1.4281 *q2)+(0.422205*q3)); + c->b[1] = ( (2.44413*q)+(2.85619*q2)+(1.26661 *q3)); + c->b[2] = ( -((1.4281*q2)+(1.26661 *q3))); + c->b[3] = ( (0.422205*q3)); + c->B = 1.0-((c->b[1]+c->b[2]+c->b[3])/c->b[0]); + c->sigma = sigma; + c->N = 3; + +/* + g_printerr ("q %f\n", q); + g_printerr ("q2 %f\n", q2); + g_printerr ("q3 %f\n", q3); + g_printerr ("c->b[0] %f\n", c->b[0]); + g_printerr ("c->b[1] %f\n", c->b[1]); + g_printerr ("c->b[2] %f\n", c->b[2]); + g_printerr ("c->b[3] %f\n", c->b[3]); + g_printerr ("c->B %f\n", c->B); + g_printerr ("c->sigma %f\n", c->sigma); + g_printerr ("c->N %d\n", c->N); +*/ +} + +static void +gausssmooth (gfloat *in, gfloat *out, gint size, gint rowstride, gauss3_coefs *c) +{ + /* + * Papers: "Recursive Implementation of the gaussian filter.", + * Ian T. Young , Lucas J. Van Vliet, Signal Processing 44, Elsevier 1995. + * formula: 9a forward filter + * 9b backward filter + * fig7 algorithm + */ + gint i,n, bufsize; + gfloat *w1,*w2; + + /* forward pass */ + bufsize = size+3; + size -= 1; + w1 = (gfloat *) g_try_malloc (bufsize * sizeof (gfloat)); + w2 = (gfloat *) g_try_malloc (bufsize * sizeof (gfloat)); + w1[0] = in[0]; + w1[1] = in[0]; + w1[2] = in[0]; + for ( i = 0 , n=3; i <= size ; i++, n++) + { + w1[n] = (gfloat)(c->B*in[i*rowstride] + + ((c->b[1]*w1[n-1] + + c->b[2]*w1[n-2] + + c->b[3]*w1[n-3] ) / c->b[0])); + } + + /* backward pass */ + w2[size+1]= w1[size+3]; + w2[size+2]= w1[size+3]; + w2[size+3]= w1[size+3]; + for (i = size, n = i; i >= 0; i--, n--) + { + w2[n]= out[i * rowstride] = (gfloat)(c->B*w1[n+3] + + ((c->b[1]*w2[n+1] + + c->b[2]*w2[n+2] + + c->b[3]*w2[n+3] ) / c->b[0])); + } + + g_free (w1); + g_free (w2); +} + +/* + * This function is the heart of the algo. + * (a) Filterings at several scales and sumarize the results. + * (b) Calculation of the final values. + */ +static void +MSRCR (guchar *src, gint width, gint height, gint bytes, gboolean preview_mode) +{ + + gint scale,row,col; + gint i,j; + gint size; + gint channel; + guchar *psrc = NULL; /* backup pointer for src buffer */ + gfloat *dst = NULL; /* float buffer for algorithm */ + gfloat *pdst = NULL; /* backup pointer for float buffer */ + gfloat *in, *out; + gint channelsize; /* Float memory cache for one channel */ + gfloat weight; + gauss3_coefs coef; + gfloat mean, var; + gfloat mini, range, maxi; + gfloat alpha; + gfloat gain; + gfloat offset; + gdouble max_preview = 0.0; + + if (!preview_mode) + { + gimp_progress_init (_("Retinex: filtering")); + max_preview = 3 * rvals.nscales; + } + + /* Allocate all the memory needed for algorithm*/ + size = width * height * bytes; + dst = g_try_malloc (size * sizeof (gfloat)); + if (dst == NULL) + { + g_warning ("Failed to allocate memory"); + return; + } + memset (dst, 0, size * sizeof (gfloat)); + + channelsize = (width * height); + in = (gfloat *) g_try_malloc (channelsize * sizeof (gfloat)); + if (in == NULL) + { + g_free (dst); + g_warning ("Failed to allocate memory"); + return; /* do some clever stuff */ + } + + out = (gfloat *) g_try_malloc (channelsize * sizeof (gfloat)); + if (out == NULL) + { + g_free (in); + g_free (dst); + g_warning ("Failed to allocate memory"); + return; /* do some clever stuff */ + } + + + /* + Calculate the scales of filtering according to the + number of filter and their distribution. + */ + + retinex_scales_distribution (RetinexScales, + rvals.nscales, rvals.scales_mode, rvals.scale); + + /* + Filtering according to the various scales. + Summerize the results of the various filters according to a + specific weight(here equivalent for all). + */ + weight = 1./ (gfloat) rvals.nscales; + + /* + The recursive filtering algorithm needs different coefficients according + to the selected scale (~ = standard deviation of Gaussian). + */ + for (channel = 0; channel < 3; channel++) + { + gint pos; + + for (i = 0, pos = channel; i < channelsize ; i++, pos += bytes) + { + /* 0-255 => 1-256 */ + in[i] = (gfloat)(src[pos] + 1.0); + } + for (scale = 0; scale < rvals.nscales; scale++) + { + compute_coefs3 (&coef, RetinexScales[scale]); + /* + * Filtering (smoothing) Gaussian recursive. + * + * Filter rows first + */ + for (row=0 ;row < height; row++) + { + pos = row * width; + gausssmooth (in + pos, out + pos, width, 1, &coef); + } + + memcpy(in, out, channelsize * sizeof(gfloat)); + memset(out, 0 , channelsize * sizeof(gfloat)); + + /* + * Filtering (smoothing) Gaussian recursive. + * + * Second columns + */ + for (col=0; col < width; col++) + { + pos = col; + gausssmooth(in + pos, out + pos, height, width, &coef); + } + + /* + Summarize the filtered values. + In fact one calculates a ratio between the original values and the filtered values. + */ + for (i = 0, pos = channel; i < channelsize; i++, pos += bytes) + { + dst[pos] += weight * (log (src[pos] + 1.) - log (out[i])); + } + + if (!preview_mode) + gimp_progress_update ((channel * rvals.nscales + scale) / + max_preview); + } + } + g_free(in); + g_free(out); + + /* + Final calculation with original value and cumulated filter values. + The parameters gain, alpha and offset are constants. + */ + /* Ci(x,y)=log[a Ii(x,y)]-log[ Ei=1-s Ii(x,y)] */ + + alpha = 128.; + gain = 1.; + offset = 0.; + + for (i = 0; i < size; i += bytes) + { + gfloat logl; + + psrc = src+i; + pdst = dst+i; + + logl = log((gfloat)psrc[0] + (gfloat)psrc[1] + (gfloat)psrc[2] + 3.); + + pdst[0] = gain * ((log(alpha * (psrc[0]+1.)) - logl) * pdst[0]) + offset; + pdst[1] = gain * ((log(alpha * (psrc[1]+1.)) - logl) * pdst[1]) + offset; + pdst[2] = gain * ((log(alpha * (psrc[2]+1.)) - logl) * pdst[2]) + offset; + } + +/* if (!preview_mode) + gimp_progress_update ((2.0 + (rvals.nscales * 3)) / + ((rvals.nscales * 3) + 3));*/ + + /* + Adapt the dynamics of the colors according to the statistics of the first and second order. + The use of the variance makes it possible to control the degree of saturation of the colors. + */ + pdst = dst; + + compute_mean_var (pdst, &mean, &var, size, bytes); + mini = mean - rvals.cvar*var; + maxi = mean + rvals.cvar*var; + range = maxi - mini; + + if (!range) + range = 1.0; + + for (i = 0; i < size; i+= bytes) + { + psrc = src + i; + pdst = dst + i; + + for (j = 0 ; j < 3 ; j++) + { + gfloat c = 255 * ( pdst[j] - mini ) / range; + + psrc[j] = (guchar) CLAMP (c, 0, 255); + } + } + + g_free (dst); +} + +/* + * Calculate the average and variance in one go. + */ +static void +compute_mean_var (gfloat *src, gfloat *mean, gfloat *var, gint size, gint bytes) +{ + gfloat vsquared; + gint i,j; + gfloat *psrc; + + vsquared = 0; + *mean = 0; + for (i = 0; i < size; i+= bytes) + { + psrc = src+i; + for (j = 0 ; j < 3 ; j++) + { + *mean += psrc[j]; + vsquared += psrc[j] * psrc[j]; + } + } + + *mean /= (gfloat) size; /* mean */ + vsquared /= (gfloat) size; /* mean (x^2) */ + *var = ( vsquared - (*mean * *mean) ); + *var = sqrt(*var); /* var */ +} diff --git a/plug-ins/common/crop-zealous.c b/plug-ins/common/crop-zealous.c new file mode 100644 index 0000000..6efc34b --- /dev/null +++ b/plug-ins/common/crop-zealous.c @@ -0,0 +1,326 @@ +/* + * ZealousCrop plug-in version 1.00 + * by Adam D. Moss + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimp/stdplugins-intl.h" + +#define PLUG_IN_PROC "plug-in-zealouscrop" + +#define EPSILON (1e-5) +#define FLOAT_IS_ZERO(value) (value > -EPSILON && value < EPSILON) +#define FLOAT_EQUAL(v1, v2) ((v1 - v2) > -EPSILON && (v1 - v2) < EPSILON) + +/* Declare local functions. */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static inline gboolean colors_equal (const gfloat *col1, + const gfloat *col2, + gint components, + gboolean has_alpha); +static void do_zcrop (gint32 drawable_id, + gint32 image_id); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Autocrop unused space from edges and middle"), + "", + "Adam D. Moss", + "Adam D. Moss", + "1997", + N_("_Zealous Crop"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Image/Crop"); +} + +static void +run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 drawable_id; + gint32 image_id; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + run_mode = param[0].data.d_int32; + + if (run_mode == GIMP_RUN_NONINTERACTIVE) + { + if (n_params != 3) + { + status = GIMP_PDB_CALLING_ERROR; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Get the specified drawable */ + image_id = param[1].data.d_int32; + drawable_id = param[2].data.d_int32; + + /* Make sure that the drawable is gray or RGB or indexed */ + if (gimp_drawable_is_rgb (drawable_id) || + gimp_drawable_is_gray (drawable_id) || + gimp_drawable_is_indexed (drawable_id)) + { + gimp_progress_init (_("Zealous cropping")); + + do_zcrop (drawable_id, image_id); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + gegl_exit (); +} + +static inline gboolean +colors_equal (const gfloat *col1, + const gfloat *col2, + gint components, + gboolean has_alpha) +{ + if (has_alpha && + FLOAT_IS_ZERO (col1[components - 1]) && + FLOAT_IS_ZERO (col2[components - 1])) + { + return TRUE; + } + else + { + gint b; + + for (b = 0; b < components; b++) + { + if (! FLOAT_EQUAL (col1[b], col2[b])) + return FALSE; + } + + return TRUE; + } +} + +static void +do_zcrop (gint32 drawable_id, + gint32 image_id) +{ + GeglBuffer *drawable_buffer; + GeglBuffer *shadow_buffer; + gfloat *linear_buf; + const Babl *format; + + gint x, y, width, height; + gint components; + gint8 *killrows; + gint8 *killcols; + gint32 livingrows, livingcols, destrow, destcol; + gint32 selection_copy_id; + gboolean has_alpha; + + drawable_buffer = gimp_drawable_get_buffer (drawable_id); + shadow_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + width = gegl_buffer_get_width (drawable_buffer); + height = gegl_buffer_get_height (drawable_buffer); + has_alpha = gimp_drawable_has_alpha (drawable_id); + + if (has_alpha) + format = babl_format ("R'G'B'A float"); + else + format = babl_format ("R'G'B' float"); + + components = babl_format_get_n_components (format); + + killrows = g_new (gint8, height); + killcols = g_new (gint8, width); + + linear_buf = g_new (gfloat, (width > height ? width : height) * components); + + /* search which rows to remove */ + + livingrows = 0; + for (y = 0; y < height; y++) + { + gegl_buffer_get (drawable_buffer, GEGL_RECTANGLE (0, y, width, 1), + 1.0, format, linear_buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + killrows[y] = TRUE; + + for (x = components; x < width * components; x += components) + { + if (! colors_equal (linear_buf, &linear_buf[x], components, has_alpha)) + { + livingrows++; + killrows[y] = FALSE; + break; + } + } + } + + gimp_progress_update (0.25); + + /* search which columns to remove */ + + livingcols = 0; + for (x = 0; x < width; x++) + { + gegl_buffer_get (drawable_buffer, GEGL_RECTANGLE (x, 0, 1, height), + 1.0, format, linear_buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + killcols[x] = TRUE; + + for (y = components; y < height * components; y += components) + { + if (! colors_equal (linear_buf, &linear_buf[y], components, has_alpha)) + { + livingcols++; + killcols[x] = FALSE; + break; + } + } + } + + gimp_progress_update (0.5); + + if ((livingcols == 0 || livingrows == 0) || + (livingcols == width && livingrows == height)) + { + g_message (_("Nothing to crop.")); + + g_object_unref (shadow_buffer); + g_object_unref (drawable_buffer); + + g_free (linear_buf); + g_free (killrows); + g_free (killcols); + return; + } + + /* restitute living rows */ + + destrow = 0; + for (y = 0; y < height; y++) + { + if (!killrows[y]) + { + gegl_buffer_copy (drawable_buffer, + GEGL_RECTANGLE (0, y, width, 1), + GEGL_ABYSS_NONE, + shadow_buffer, + GEGL_RECTANGLE (0, destrow, width, 1)); + + destrow++; + } + } + + gimp_progress_update (0.75); + + /* restitute living columns */ + + destcol = 0; + for (x = 0; x < width; x++) + { + if (!killcols[x]) + { + gegl_buffer_copy (shadow_buffer, + GEGL_RECTANGLE (x, 0, 1, height), + GEGL_ABYSS_NONE, + shadow_buffer, + GEGL_RECTANGLE (destcol, 0, 1, height)); + + destcol++; + } + } + + gimp_progress_update (1.00); + + gimp_image_undo_group_start (image_id); + + selection_copy_id = gimp_selection_save (image_id); + gimp_selection_none (image_id); + + gegl_buffer_flush (shadow_buffer); + gimp_drawable_merge_shadow (drawable_id, TRUE); + gegl_buffer_flush (drawable_buffer); + + gimp_image_select_item (image_id, GIMP_CHANNEL_OP_REPLACE, selection_copy_id); + gimp_image_remove_channel (image_id, selection_copy_id); + + gimp_image_crop (image_id, livingcols, livingrows, 0, 0); + + gimp_image_undo_group_end (image_id); + + g_object_unref (shadow_buffer); + g_object_unref (drawable_buffer); + + g_free (linear_buf); + g_free (killrows); + g_free (killcols); +} diff --git a/plug-ins/common/curve-bend.c b/plug-ins/common/curve-bend.c new file mode 100644 index 0000000..dfa9ff7 --- /dev/null +++ b/plug-ins/common/curve-bend.c @@ -0,0 +1,3402 @@ +/* curve_bend plugin for GIMP */ + +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Revision history + * (2004/02/08) v1.3.18 hof: #133244 exit with execution error if there is + * an empty selection + * (2003/08/24) v1.3.18 hof: #119937 show busy cursor when recalculating + * preview + * (2002/09/xx) v1.1.18 mitch and gsr: clean interface + * (2000/02/16) v1.1.17b hof: added spinbuttons for rotate entry + * (2000/02/16) v1.1.17 hof: undo bugfix (#6012) + * don't call gimp_undo_push_group_end + * after gimp_displays_flush + * (1999/09/13) v1.01 hof: PDB-calls updated for gimp 1.1.9 + * (1999/05/10) v1.0 hof: first public release + * (1999/04/23) v0.0 hof: coding started, + * splines and dialog parts are similar to curves.c + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Defines */ +#define PLUG_IN_PROC "plug-in-curve-bend" +#define PLUG_IN_BINARY "curve-bend" +#define PLUG_IN_ROLE "gimp-curve-bend" +#define PLUG_IN_VERSION "v1.3.18 (2003/08/26)" +#define PLUG_IN_IMAGE_TYPES "RGB*, GRAY*" +#define PLUG_IN_AUTHOR "Wolfgang Hofer (hof@hotbot.com)" +#define PLUG_IN_COPYRIGHT "Wolfgang Hofer" + +#define PLUG_IN_ITER_NAME "plug_in_curve_bend_Iterator" +#define PLUG_IN_DATA_ITER_FROM "plug_in_curve_bend_ITER_FROM" +#define PLUG_IN_DATA_ITER_TO "plug_in_curve_bend_ITER_TO" + +#define KEY_POINTFILE "POINTFILE_CURVE_BEND" +#define KEY_POINTS "POINTS" +#define KEY_VAL_Y "VAL_Y" + +#define MIDDLE 127 + +#define MIX_CHANNEL(a, b, m) (((a * m) + (b * (255 - m))) / 255) + +#define UP_GRAPH 0x1 +#define UP_PREVIEW_EXPOSE 0x4 +#define UP_PREVIEW 0x8 +#define UP_DRAW 0x10 +#define UP_ALL 0xFF + +#define GRAPH_WIDTH 256 +#define GRAPH_HEIGHT 256 +#define PREVIEW_SIZE_X 256 +#define PREVIEW_SIZE_Y 256 +#define PV_IMG_WIDTH 128 +#define PV_IMG_HEIGHT 128 +#define RADIUS 3 +#define MIN_DISTANCE 8 +#define PREVIEW_BPP 4 + +#define SMOOTH 0 +#define GFREE 1 + +#define GRAPH_MASK GDK_EXPOSURE_MASK | \ + GDK_POINTER_MOTION_MASK | \ + GDK_POINTER_MOTION_HINT_MASK | \ + GDK_ENTER_NOTIFY_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_RELEASE_MASK | \ + GDK_BUTTON1_MOTION_MASK + + +#define OUTLINE_UPPER 0 +#define OUTLINE_LOWER 1 + +typedef struct _BenderValues BenderValues; +struct _BenderValues +{ + guchar curve[2][256]; /* for curve_type freehand mode 0 <= curve <= 255 */ + gdouble points[2][17][2]; /* for curve_type smooth mode 0.0 <= points <= 1.0 */ + + gint curve_type; + + gint smoothing; + gint antialias; + gint work_on_copy; + gdouble rotation; + + gint32 total_steps; + gdouble current_step; +}; + +typedef struct _Curves Curves; + +struct _Curves +{ + int x, y; /* coords for last mouse click */ +}; + +typedef struct _BenderDialog BenderDialog; + +struct _BenderDialog +{ + GtkWidget *shell; + GtkWidget *outline_menu; + GtkWidget *pv_widget; + GtkWidget *graph; + GtkAdjustment *rotate_data; + GdkPixmap *pixmap; + GtkWidget *filechooser; + + GdkCursor *cursor_busy; + + gint32 drawable_id; + int color; + int outline; + gint preview; + + int grab_point; + int last; + int leftmost; + int rightmost; + int curve_type; + gdouble points[2][17][2]; /* 0.0 <= points <= 1.0 */ + guchar curve[2][256]; /* 0 <= curve <= 255 */ + gint32 *curve_ptr[2]; /* 0 <= curve_ptr <= src_drawable_width + * both arrays are allocated dynamic, + * depending on drawable width + */ + gint32 min2[2]; + gint32 max2[2]; + gint32 zero2[2]; + + gboolean show_progress; + gboolean smoothing; + gboolean antialias; + gboolean work_on_copy; + gdouble rotation; + + gint32 preview_image_id; + gint32 preview_layer_id1; + gint32 preview_layer_id2; + + BenderValues *bval_from; + BenderValues *bval_to; + BenderValues *bval_curr; + + gboolean run; +}; + +/* points Coords: + * + * 1.0 +----+----+----+----+ + * | . | | | + * +--.-+--.-+----+----+ + * . | . | | + * 0.5 +----+----+-.--+----+ + * | | | . . + * +----+----+----+-.-.+ + * | | | | | + * 0.0 +----+----+----+----+ + * 0.0 0.5 1.0 + * + * curve Coords: + * + * OUTLINE_UPPER OUTLINE_LOWER + * + * 255 +----+----+----+----+ 255 +----+----+----+----+ + * | . | | | --- max | . | | | --- max + * +--.-+--.-+----+----+ +--.-+--.-+----+----+ + * . | . | | zero ___ . | . | | + * +----+----+-.--+----+ +----+----+-.--+----+ + * | | | . . --- zero | | | . . + * +----+----+----+-.-.+ ___ min +----+----+----+-.-.+ ___ min + * | | | | | | | | | | + * 0 +----+----+----+----+ 0 +----+----+----+----+ + * 0 255 0 255 + */ + +typedef double CRMatrix[4][4]; + +typedef struct +{ + guint32 drawable_id; + gint width; + gint height; + GeglBuffer *buffer; + const Babl *format; + gint x1; + gint y1; + gint x2; + gint y2; + gint index_alpha; /* 0 == no alpha, 1 == GREYA, 3 == RGBA */ + gint bpp; + gint tile_width; + gint tile_height; +} t_GDRW; + +typedef struct +{ + gint32 y; + guchar color[4]; +} t_Last; + + +/* curves action functions */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static BenderDialog * bender_new_dialog (gint32 drawable_id); +static void bender_update (BenderDialog *, + int); +static void bender_plot_curve (BenderDialog *, + int, + int, + int, + int, + gint32, + gint32, + gint); +static void bender_calculate_curve (BenderDialog *, + gint32, + gint32, + gint); +static void bender_rotate_adj_callback (GtkAdjustment *, + gpointer); +static void bender_border_callback (GtkWidget *, + gpointer); +static void bender_type_callback (GtkWidget *, + gpointer); +static void bender_reset_callback (GtkWidget *, + gpointer); +static void bender_copy_callback (GtkWidget *, + gpointer); +static void bender_copy_inv_callback (GtkWidget *, + gpointer); +static void bender_swap_callback (GtkWidget *, + gpointer); +static void bender_response (GtkWidget *, + gint, + BenderDialog *); +static void bender_smoothing_callback (GtkWidget *, + gpointer); +static void bender_antialias_callback (GtkWidget *, + gpointer); +static void bender_work_on_copy_callback (GtkWidget *, + gpointer); +static void bender_preview_update (GtkWidget *, + gpointer); +static void bender_preview_update_once (GtkWidget *, + gpointer); +static void bender_load_callback (GtkWidget *, + BenderDialog *); +static void bender_save_callback (GtkWidget *, + BenderDialog *); +static gint bender_graph_events (GtkWidget *, + GdkEvent *, + BenderDialog *); +static void bender_CR_compose (CRMatrix, + CRMatrix, + CRMatrix); +static void bender_init_min_max (BenderDialog *, + gint32); +static BenderDialog * do_dialog (gint32 drawable_id); +static void p_init_gdrw (t_GDRW *gdrw, + gint32 drawable_id, + int shadow); +static void p_end_gdrw (t_GDRW *gdrw); +static gint32 p_main_bend (BenderDialog *cd, + guint32, + gint); +static gint32 p_create_pv_image (gint32 src_drawable_id, + gint32 *layer_id); +static void p_render_preview (BenderDialog *cd, + gint32 layer_id); +static void p_get_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *pixel); +static void p_put_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *pixel); +static void p_put_mix_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *color, + gint32 nb_curvy, + gint32 nb2_curvy, + gint32 curvy); +static void p_stretch_curves (BenderDialog *cd, + gint32 xmax, + gint32 ymax); +static void p_cd_to_bval (BenderDialog *cd, + BenderValues *bval); +static void p_cd_from_bval (BenderDialog *cd, + BenderValues *bval); +static void p_store_values (BenderDialog *cd); +static void p_retrieve_values (BenderDialog *cd); +static void p_bender_calculate_iter_curve (BenderDialog *cd, + gint32 xmax, + gint32 ymax); +static void p_delta_gdouble (double *val, + double val_from, + double val_to, + gint32 total_steps, + gdouble current_step); +static void p_delta_gint32 (gint32 *val, + gint32 val_from, + gint32 val_to, + gint32 total_steps, + gdouble current_step); +static void p_copy_points (BenderDialog *cd, + int outline, + int xy, + int argc, + gdouble *floatarray); +static void p_copy_yval (BenderDialog *cd, + int outline, + int argc, + guint8 *int8array); +static int p_save_pointfile (BenderDialog *cd, + const gchar *filename); + + +/* Global Variables */ +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +static CRMatrix CR_basis = +{ + { -0.5, 1.5, -1.5, 0.5 }, + { 1.0, -2.5, 2.0, -0.5 }, + { -0.5, 0.0, 0.5, 0.0 }, + { 0.0, 1.0, 0.0, 0.0 }, +}; + +static int gb_debug = FALSE; + +/* Functions */ +/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX PDB_STUFF XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ + +#ifdef ROTATE_OPTIMIZE + +/* ============================================================================ + * p_pdb_procedure_available + * if requested procedure is available in the PDB return the number of args + * (0 upto n) that are needed to call the procedure. + * if not available return -1 + * ============================================================================ + */ + +static gint +p_pdb_procedure_available (const gchar *proc_name) +{ + gint nparams; + gint nreturn_vals; + GimpPDBProcType proc_type; + gchar *proc_blurb; + gchar *proc_help; + gchar *proc_author; + gchar *proc_copyright; + gchar *proc_date; + GimpParamDef *params; + GimpParamDef *return_vals; + gint rc; + + rc = 0; + + /* Query the gimp application's procedural database + * regarding a particular procedure. + */ + if (gimp_procedural_db_proc_info (proc_name, + &proc_blurb, + &proc_help, + &proc_author, + &proc_copyright, + &proc_date, + &proc_type, + &nparams, &nreturn_vals, + ¶ms, &return_vals)) + { + /* procedure found in PDB */ + return nparams; + } + + g_printerr ("Warning: Procedure %s not found.\n", proc_name); + return -1; +} + +#endif /* ROTATE_OPTIMIZE */ + +static gint +p_gimp_rotate (gint32 image_id, + gint32 drawable_id, + gint32 interpolation, + gdouble angle_deg) +{ + gdouble angle_rad; + gint rc; + +#ifdef ROTATE_OPTIMIZE + static gchar *rotate_proc = "plug-in-rotate"; + GimpParam *return_vals; + gint nreturn_vals; + gint32 angle_step; + gint nparams; + + if (angle_deg == 90.0) { angle_step = 1; } + else if(angle_deg == 180.0) { angle_step = 2; } + else if(angle_deg == 270.0) { angle_step = 3; } + else { angle_step = 0; } + + if (angle_step != 0) + { + nparams = p_pdb_procedure_available (rotate_proc); + if (nparams == 5) + { + /* use faster rotate plugin on multiples of 90 degrees */ + return_vals = gimp_run_procedure (rotate_proc, + &nreturn_vals, + GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE, + GIMP_PDB_IMAGE, image_id, + GIMP_PDB_DRAWABLE, drawable_id, + GIMP_PDB_INT32, angle_step, + GIMP_PDB_INT32, FALSE, /* don't rotate the whole image */ + GIMP_PDB_END); + + if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS) + { + return 0; + } + } + } +#endif /* ROTATE_OPTIMIZE */ + + angle_rad = (angle_deg * G_PI) / 180.0; + + gimp_context_push (); + if (! interpolation) + gimp_context_set_interpolation (GIMP_INTERPOLATION_NONE); + gimp_context_set_transform_resize (GIMP_TRANSFORM_RESIZE_ADJUST); + rc = gimp_item_transform_rotate (drawable_id, + angle_rad, + TRUE /*auto_center*/, + -1.0 /*center_x*/, + -1.0 /*center_y*/); + gimp_context_pop (); + + if (rc == -1) + g_printerr ("Error: gimp_drawable_transform_rotate_default call failed\n"); + + return rc; +} + +/* ============================================================================ + * p_if_selection_float_it + * ============================================================================ + */ + +static gint32 +p_if_selection_float_it (gint32 image_id, + gint32 layer_id) +{ + if (! gimp_layer_is_floating_sel (layer_id)) + { + gint32 sel_channel_id; + gint32 x1, x2, y1, y2; + gint32 non_empty; + + /* check and see if we have a selection mask */ + sel_channel_id = gimp_image_get_selection (image_id); + + gimp_selection_bounds (image_id, &non_empty, &x1, &y1, &x2, &y2); + + if (non_empty && sel_channel_id >= 0) + { + /* selection is TRUE, make a layer (floating selection) from + the selection */ + if (gimp_edit_copy (layer_id)) + { + layer_id = gimp_edit_paste (layer_id, FALSE); + } + else + { + return -1; + } + } + } + + return layer_id; +} + +/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX END_PDB_STUFF XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ + +/* + * M M AAAAA IIIIII N N + * M M M M A A II NN N + * M M M AAAAAAA II N N N + * M M A A II N NN + * M M A A IIIIII N N + */ + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"}, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (must be a layer without layermask)"}, + { GIMP_PDB_FLOAT, "rotation", "Direction {angle 0 to 360 degree } of the bend effect"}, + { GIMP_PDB_INT32, "smoothing", "Smoothing { TRUE, FALSE }"}, + { GIMP_PDB_INT32, "antialias", "Antialias { TRUE, FALSE }"}, + { GIMP_PDB_INT32, "work-on-copy", "{ TRUE, FALSE } TRUE: copy the drawable and bend the copy"}, + { GIMP_PDB_INT32, "curve-type", " { 0, 1 } 0 == smooth (use 17 points), 1 == freehand (use 256 val_y) "}, + { GIMP_PDB_INT32, "argc-upper-point-x", "{2 <= argc <= 17} "}, + { GIMP_PDB_FLOATARRAY, "upper-point-x", "array of 17 x point_koords { 0.0 <= x <= 1.0 or -1 for unused point }"}, + { GIMP_PDB_INT32, "argc-upper-point-y", "{2 <= argc <= 17} "}, + { GIMP_PDB_FLOATARRAY, "upper-point-y", "array of 17 y point_koords { 0.0 <= y <= 1.0 or -1 for unused point }"}, + { GIMP_PDB_INT32, "argc-lower_point-x", "{2 <= argc <= 17} "}, + { GIMP_PDB_FLOATARRAY, "lower-point-x", "array of 17 x point_koords { 0.0 <= x <= 1.0 or -1 for unused point }"}, + { GIMP_PDB_INT32, "argc-lower-point-y", "{2 <= argc <= 17} "}, + { GIMP_PDB_FLOATARRAY, "lower_point_y", "array of 17 y point_koords { 0.0 <= y <= 1.0 or -1 for unused point }"}, + { GIMP_PDB_INT32, "argc-upper-val-y", "{ 256 } "}, + { GIMP_PDB_INT8ARRAY, "upper-val-y", "array of 256 y freehand koord { 0 <= y <= 255 }"}, + { GIMP_PDB_INT32, "argc-lower-val-y", "{ 256 } "}, + { GIMP_PDB_INT8ARRAY, "lower-val-y", "array of 256 y freehand koord { 0 <= y <= 255 }"} + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_LAYER, "bent-layer", "the handled layer" } + }; + + static const GimpParamDef args_iter[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_INT32, "total-steps", "total number of steps (# of layers-1 to apply the related plug-in)" }, + { GIMP_PDB_FLOAT, "current-step", "current (for linear iterations this is the layerstack position, otherwise some value in between)" }, + { GIMP_PDB_INT32, "len-struct", "length of stored data structure with id is equal to the plug_in proc_name" }, + }; + + /* the actual installation of the bend plugin */ + gimp_install_procedure (PLUG_IN_PROC, + N_("Bend the image using two control curves"), + "This plug-in does bend the active layer " + "If there is a current selection it is copied to " + "floating selection and the curve_bend distortion " + "is done on the floating selection. If " + "work_on_copy parameter is TRUE, the curve_bend " + "distortion is done on a copy of the active layer " + "(or floating selection). The upper and lower edges " + "are bent in shape of 2 spline curves. both (upper " + "and lower) curves are determined by upto 17 points " + "or by 256 Y-Values if curve_type == 1 (freehand " + "mode) If rotation is not 0, the layer is rotated " + "before and rotated back after the bend operation. " + "This enables bending in other directions than " + "vertical. bending usually changes the size of " + "the handled layer. this plug-in sets the offsets " + "of the handled layer to keep its center at the " + "same position", + PLUG_IN_AUTHOR, + PLUG_IN_COPYRIGHT, + PLUG_IN_VERSION, + N_("_Curve Bend..."), + PLUG_IN_IMAGE_TYPES, + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, + return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Distorts"); + + /* the installation of the Iterator procedure for the bend plugin */ + gimp_install_procedure (PLUG_IN_ITER_NAME, + "This procedure calculates the modified values " + "for one iterationstep for the call of " + "plug_in_curve_bend", + "", + PLUG_IN_AUTHOR, + PLUG_IN_COPYRIGHT, + PLUG_IN_VERSION, + NULL, /* do not appear in menus */ + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (args_iter), 0, + args_iter, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + const gchar *env; + BenderDialog *cd; + gint32 active_drawable_id = -1; + gint32 image_id = -1; + gint32 layer_id = -1; + GError *error = NULL; + + /* Get the runmode from the in-parameters */ + GimpRunMode run_mode = param[0].data.d_int32; + + /* status variable, use it to check for errors in invocation usually only + during non-interactive calling */ + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + /*always return at least the status to the caller. */ + static GimpParam values[2]; + + INIT_I18N (); + gegl_init (NULL, NULL); + + cd = NULL; + + env = g_getenv ("BEND_DEBUG"); + if (env != NULL) + { + if ((*env != 'n') && (*env != 'N')) gb_debug = 1; + } + + if (gb_debug) g_printerr ("\n\nDEBUG: run %s\n", name); + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + values[1].type = GIMP_PDB_LAYER; + values[1].data.d_int32 = -1; + + *nreturn_vals = 2; + *return_vals = values; + + if (strcmp (name, PLUG_IN_ITER_NAME) == 0) + { + gint32 len_struct; + gint32 total_steps; + gdouble current_step; + BenderValues bval; /* current values while iterating */ + BenderValues bval_from, bval_to; /* start and end values */ + + /* Iterator procedure for animated calls is usually called from + * "plug_in_gap_layers_run_animfilter" + * (always run noninteractive) + */ + if ((run_mode == GIMP_RUN_NONINTERACTIVE) && (nparams == 4)) + { + total_steps = param[1].data.d_int32; + current_step = param[2].data.d_float; + len_struct = param[3].data.d_int32; + + if (len_struct == sizeof (bval)) + { + /* get _FROM and _TO data, + * This data was stored by plug_in_gap_layers_run_animfilter + */ + gimp_get_data (PLUG_IN_DATA_ITER_FROM, &bval_from); + gimp_get_data (PLUG_IN_DATA_ITER_TO, &bval_to); + bval = bval_from; + + p_delta_gdouble (&bval.rotation, bval_from.rotation, + bval_to.rotation, total_steps, current_step); + /* note: iteration of curve and points arrays would not + * give useful results. (there might be different + * number of points in the from/to bender values ) + * the iteration is done later, (see + * p_bender_calculate_iter_curve) when the curve + * is calculated. + */ + + bval.total_steps = total_steps; + bval.current_step = current_step; + + gimp_set_data (PLUG_IN_PROC, &bval, sizeof (bval)); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].data.d_status = status; + return; + } + + /* get image and drawable */ + image_id = param[1].data.d_int32; + layer_id = param[2].data.d_drawable; + + if (! gimp_item_is_layer (layer_id)) + { + g_set_error (&error, 0, 0, "%s", + _("Can operate on layers only " + "(but was called on channel or mask).")); + status = GIMP_PDB_EXECUTION_ERROR; + } + + /* check for layermask */ + if (gimp_layer_get_mask (layer_id) > 0) + { + g_set_error (&error, 0, 0, "%s", + _("Cannot operate on layers with masks.")); + status = GIMP_PDB_EXECUTION_ERROR; + } + + /* if there is a selection, make it the floating selection layer */ + active_drawable_id = p_if_selection_float_it (image_id, layer_id); + if (active_drawable_id < 0) + { + /* could not float the selection because selection rectangle + * is completely empty return GIMP_PDB_EXECUTION_ERROR + */ + g_set_error (&error, 0, 0, "%s", + _("Cannot operate on empty selections.")); + status = GIMP_PDB_EXECUTION_ERROR; + } + + /* how are we running today? */ + if (status == GIMP_PDB_SUCCESS) + { + /* how are we running today? */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data from a previous run */ + /* gimp_get_data (PLUG_IN_PROC, &g_bndvals); */ + + /* Get information from the dialog */ + cd = do_dialog (active_drawable_id); + cd->show_progress = TRUE; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* check to see if invoked with the correct number of parameters */ + if (nparams >= 20) + { + cd = g_new (BenderDialog, 1); + cd->run = TRUE; + cd->show_progress = TRUE; + cd->drawable_id = active_drawable_id; + + cd->rotation = param[3].data.d_float; + cd->smoothing = param[4].data.d_int32 != 0; + cd->antialias = param[5].data.d_int32 != 0; + cd->work_on_copy = param[6].data.d_int32 != 0; + cd->curve_type = param[7].data.d_int32 != 0; + + p_copy_points (cd, OUTLINE_UPPER, 0, + param[8].data.d_int32, + param[9].data.d_floatarray); + p_copy_points (cd, OUTLINE_UPPER, 1, + param[10].data.d_int32, + param[11].data.d_floatarray); + p_copy_points (cd, OUTLINE_LOWER, 0, + param[12].data.d_int32, + param[13].data.d_floatarray); + p_copy_points (cd, OUTLINE_LOWER, 1, + param[14].data.d_int32, + param[15].data.d_floatarray); + + p_copy_yval (cd, OUTLINE_UPPER, + param[16].data.d_int32, + param[17].data.d_int8array); + p_copy_yval (cd, OUTLINE_LOWER, + param[18].data.d_int32, + param[19].data.d_int8array); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + cd = g_new (BenderDialog, 1); + cd->run = TRUE; + cd->show_progress = TRUE; + cd->drawable_id = active_drawable_id; + p_retrieve_values (cd); /* Possibly retrieve data from a previous run */ + break; + + default: + break; + } + } + + if (! cd) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Run the main function */ + + if (cd->run) + { + gint32 bent_layer_id; + + gimp_image_undo_group_start (image_id); + + bent_layer_id = p_main_bend (cd, cd->drawable_id, + cd->work_on_copy); + + gimp_image_undo_group_end (image_id); + + /* Store variable states for next run */ + if (run_mode == GIMP_RUN_INTERACTIVE) + { + p_store_values (cd); + } + + /* return the id of handled layer */ + values[1].data.d_int32 = bent_layer_id; + } + else + { + status = GIMP_PDB_CANCEL; + } + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static int +p_save_pointfile (BenderDialog *cd, + const gchar *filename) +{ + FILE *fp; + gint j; + + fp = g_fopen(filename, "w+b"); + if (! fp) + { + g_message (_("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + fprintf (fp, "%s\n", KEY_POINTFILE); + fprintf (fp, "VERSION 1.0\n\n"); + + fprintf (fp, "# points for upper and lower smooth curve (0.0 <= pt <= 1.0)\n"); + fprintf (fp, "# there are upto 17 points where unused points are set to -1\n"); + fprintf (fp, "# UPPERX UPPERY LOWERX LOWERY\n"); + fprintf (fp, "\n"); + + for(j = 0; j < 17; j++) + { + fprintf (fp, "%s %+.6f %+.6f %+.6f %+.6f\n", KEY_POINTS, + (float)cd->points[OUTLINE_UPPER][j][0], + (float)cd->points[OUTLINE_UPPER][j][1], + (float)cd->points[OUTLINE_LOWER][j][0], + (float)cd->points[OUTLINE_LOWER][j][1] ); + } + + fprintf (fp, "\n"); + fprintf (fp, "# y values for upper/lower freehand curve (0 <= y <= 255) \n"); + fprintf (fp, "# there must be exactly 256 y values \n"); + fprintf (fp, "# UPPER_Y LOWER_Y\n"); + fprintf (fp, "\n"); + + for (j = 0; j < 256; j++) + { + fprintf (fp, "%s %3d %3d\n", KEY_VAL_Y, + (int)cd->curve[OUTLINE_UPPER][j], + (int)cd->curve[OUTLINE_LOWER][j]); + } + + fclose (fp); + + return 0; +} + +static int +p_load_pointfile (BenderDialog *cd, + const gchar *filename) +{ + gint pi, ci, n, len; + FILE *fp; + char buff[2000]; + float fux, fuy, flx, fly; + gint iuy, ily ; + + fp = g_fopen(filename, "rb"); + if (! fp) + { + g_message (_("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + pi = 0; + ci = 0; + + if (! fgets (buff, 2000 - 1, fp)) + { + g_message (_("Error while reading '%s': %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + fclose (fp); + return -1; + } + + if (strncmp (buff, KEY_POINTFILE, strlen(KEY_POINTFILE)) == 0) + { + while (NULL != fgets (buff, 2000-1, fp)) + { + len = strlen(KEY_POINTS); + + if (strncmp (buff, KEY_POINTS, len) == 0) + { + n = sscanf (&buff[len], + "%f %f %f %f", &fux, &fuy, &flx, &fly); + + if ((n == 4) && (pi < 17)) + { + cd->points[OUTLINE_UPPER][pi][0] = fux; + cd->points[OUTLINE_UPPER][pi][1] = fuy; + cd->points[OUTLINE_LOWER][pi][0] = flx; + cd->points[OUTLINE_LOWER][pi][1] = fly; + pi++; + } + else + { + g_printf("warning: BAD points[%d] in file %s are ignored\n", pi, filename); + } + } + + len = strlen (KEY_VAL_Y); + + if (strncmp (buff, KEY_VAL_Y, len) == 0) + { + n = sscanf (&buff[len], "%d %d", &iuy, &ily); + + if ((n == 2) && (ci < 256)) + { + cd->curve[OUTLINE_UPPER][ci] = iuy; + cd->curve[OUTLINE_LOWER][ci] = ily; + ci++; + } + else + { + g_printf("warning: BAD y_vals[%d] in file %s are ignored\n", ci, filename); + } + } + } + } + + fclose (fp); + + return 0; +} + +static void +p_cd_to_bval (BenderDialog *cd, + BenderValues *bval) +{ + gint i, j; + + for (i = 0; i < 2; i++) + { + for (j = 0; j < 256; j++) + { + bval->curve[i][j] = cd->curve[i][j]; + } + + for (j = 0; j < 17; j++) + { + bval->points[i][j][0] = cd->points[i][j][0]; /* x */ + bval->points[i][j][1] = cd->points[i][j][1]; /* y */ + } + } + + bval->curve_type = cd->curve_type; + bval->smoothing = cd->smoothing; + bval->antialias = cd->antialias; + bval->work_on_copy = cd->work_on_copy; + bval->rotation = cd->rotation; + + bval->total_steps = 0; + bval->current_step = 0.0; +} + +static void +p_cd_from_bval (BenderDialog *cd, + BenderValues *bval) +{ + gint i, j; + + for (i = 0; i < 2; i++) + { + for (j = 0; j < 256; j++) + { + cd->curve[i][j] = bval->curve[i][j]; + } + + for (j = 0; j < 17; j++) + { + cd->points[i][j][0] = bval->points[i][j][0]; /* x */ + cd->points[i][j][1] = bval->points[i][j][1]; /* y */ + } + } + + cd->curve_type = bval->curve_type; + cd->smoothing = bval->smoothing; + cd->antialias = bval->antialias; + cd->work_on_copy = bval->work_on_copy; + cd->rotation = bval->rotation; +} + +static void +p_store_values (BenderDialog *cd) +{ + BenderValues bval; + + p_cd_to_bval (cd, &bval); + gimp_set_data (PLUG_IN_PROC, &bval, sizeof (bval)); +} + +static void +p_retrieve_values (BenderDialog *cd) +{ + BenderValues bval; + + bval.total_steps = 0; + bval.current_step = -444.4; /* init with an invalid dummy value */ + + gimp_get_data (PLUG_IN_PROC, &bval); + + if (bval.total_steps == 0) + { + cd->bval_from = NULL; + cd->bval_to = NULL; + if(bval.current_step != -444.4) + { + /* last_value data was retrieved (and dummy value was overwritten) */ + p_cd_from_bval(cd, &bval); + } + } + else + { + cd->bval_from = g_new (BenderValues, 1); + cd->bval_to = g_new (BenderValues, 1); + cd->bval_curr = g_new (BenderValues, 1); + *cd->bval_curr = bval; + + /* it seems that we are called from GAP with "Varying Values" */ + gimp_get_data(PLUG_IN_DATA_ITER_FROM, cd->bval_from); + gimp_get_data(PLUG_IN_DATA_ITER_TO, cd->bval_to); + *cd->bval_curr = bval; + p_cd_from_bval(cd, cd->bval_curr); + cd->work_on_copy = FALSE; + } +} + +static void +p_delta_gdouble (double *val, + double val_from, + double val_to, + gint32 total_steps, + gdouble current_step) +{ + double delta; + + if (total_steps < 1) + return; + + delta = ((double)(val_to - val_from) / (double)total_steps) * ((double)total_steps - current_step); + *val = val_from + delta; +} + +static void +p_delta_gint32 (gint32 *val, + gint32 val_from, + gint32 val_to, + gint32 total_steps, + gdouble current_step) +{ + double delta; + + if (total_steps < 1) + return; + + delta = ((double)(val_to - val_from) / (double)total_steps) * ((double)total_steps - current_step); + *val = val_from + delta; +} + +static void +p_copy_points (BenderDialog *cd, + int outline, + int xy, + int argc, + gdouble *floatarray) +{ + int j; + + for (j = 0; j < 17; j++) + { + cd->points[outline][j][xy] = -1; + } + + for (j = 0; j < argc; j++) + { + cd->points[outline][j][xy] = floatarray[j]; + } +} + +static void +p_copy_yval (BenderDialog *cd, + int outline, + int argc, + guint8 *int8array) +{ + int j; + guchar fill; + + fill = MIDDLE; + + for (j = 0; j < 256; j++) + { + if (j < argc) + { + fill = cd->curve[outline][j] = int8array[j]; + } + else + { + cd->curve[outline][j] = fill; + } + } +} + +/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ +/* curves machinery */ +/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ + +static BenderDialog * +do_dialog (gint32 drawable_id) +{ + BenderDialog *cd; + + /* Init GTK */ + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + /* The curve_bend dialog */ + cd = bender_new_dialog (drawable_id); + + /* create temporary image (with a small copy of drawable) for the preview */ + cd->preview_image_id = p_create_pv_image (drawable_id, + &cd->preview_layer_id1); + cd->preview_layer_id2 = -1; + + if (! gtk_widget_get_visible (cd->shell)) + gtk_widget_show (cd->shell); + + bender_update (cd, UP_GRAPH | UP_DRAW | UP_PREVIEW_EXPOSE); + + gtk_main (); + gdk_flush (); + + gimp_image_delete(cd->preview_image_id); + cd->preview_image_id = -1; + cd->preview_layer_id1 = -1; + cd->preview_layer_id2 = -1; + + return cd; +} + +/**************************/ +/* Select Curves dialog */ +/**************************/ + +static BenderDialog * +bender_new_dialog (gint32 drawable_id) +{ + BenderDialog *cd; + GtkWidget *main_hbox; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *vbox2; + GtkWidget *abox; + GtkWidget *frame; + GtkWidget *upper, *lower; + GtkWidget *smooth, *freew; + GtkWidget *toggle; + GtkWidget *button; + GtkWidget *spinbutton; + GtkWidget *label; + GdkDisplay *display; + gint i, j; + + cd = g_new (BenderDialog, 1); + + cd->preview = FALSE; + cd->curve_type = SMOOTH; + cd->pixmap = NULL; + cd->filechooser = NULL; + cd->outline = OUTLINE_UPPER; + cd->show_progress = FALSE; + cd->smoothing = TRUE; + cd->antialias = TRUE; + cd->work_on_copy = FALSE; + cd->rotation = 0.0; /* vertical bend */ + + cd->drawable_id = drawable_id; + + cd->color = gimp_drawable_is_rgb (drawable_id); + + cd->run = FALSE; + cd->bval_from = NULL; + cd->bval_to = NULL; + cd->bval_curr = NULL; + + for (i = 0; i < 2; i++) + for (j = 0; j < 256; j++) + cd->curve[i][j] = MIDDLE; + + cd->grab_point = -1; + for (i = 0; i < 2; i++) + { + for (j = 0; j < 17; j++) + { + cd->points[i][j][0] = -1; + cd->points[i][j][1] = -1; + } + cd->points[i][0][0] = 0.0; /* x */ + cd->points[i][0][1] = 0.5; /* y */ + cd->points[i][16][0] = 1.0; /* x */ + cd->points[i][16][1] = 0.5; /* y */ + } + + p_retrieve_values(cd); /* Possibly retrieve data from a previous run */ + + /* The shell and main vbox */ + cd->shell = gimp_dialog_new (_("Curve Bend"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (cd->shell), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (cd->shell)); + + g_signal_connect (cd->shell, "response", + G_CALLBACK (bender_response), + cd); + + /* busy cursor */ + display = gtk_widget_get_display (cd->shell); + cd->cursor_busy = gdk_cursor_new_for_display (display, GDK_WATCH); + + /* The main hbox */ + main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (cd->shell))), + main_hbox, TRUE, TRUE, 0); + gtk_widget_show (main_hbox); + + /* Left side column */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (main_hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + /* Preview area, top of column */ + frame = gimp_frame_new (_("Preview")); + gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox2); + gtk_widget_show (vbox2); + + abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (vbox2), abox, FALSE, FALSE, 0); + gtk_widget_show (abox); + + /* The range drawing area */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (abox), frame); + gtk_widget_show (frame); + + cd->pv_widget = gimp_preview_area_new (); + gtk_widget_set_size_request (cd->pv_widget, + PREVIEW_SIZE_X, PREVIEW_SIZE_Y); + gtk_container_add (GTK_CONTAINER (frame), cd->pv_widget); + gtk_widget_show (cd->pv_widget); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_end (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* The preview button */ + button = gtk_button_new_with_mnemonic (_("_Preview Once")); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (bender_preview_update_once), + cd); + + /* The preview toggle */ + toggle = gtk_check_button_new_with_mnemonic (_("Automatic pre_view")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), cd->preview); + gtk_box_pack_start (GTK_BOX (hbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (bender_preview_update), + cd); + + /* Options area, bottom of column */ + frame = gimp_frame_new (_("Options")); + gtk_box_pack_end (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + /* Render Options */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* Rotate spinbutton */ + label = gtk_label_new_with_mnemonic (_("Rotat_e:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + cd->rotate_data = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0.0, 360.0, 1, 45, 0)); + gtk_adjustment_set_value (cd->rotate_data, cd->rotation); + + spinbutton = gimp_spin_button_new (cd->rotate_data, 0.5, 1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + + g_signal_connect (cd->rotate_data, "value-changed", + G_CALLBACK (bender_rotate_adj_callback), + cd); + + /* The smoothing toggle */ + toggle = gtk_check_button_new_with_mnemonic (_("Smoo_thing")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), cd->smoothing); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (bender_smoothing_callback), + cd); + + /* The antialiasing toggle */ + toggle = gtk_check_button_new_with_mnemonic (_("_Antialiasing")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), cd->antialias); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (bender_antialias_callback), + cd); + + /* The work_on_copy toggle */ + toggle = gtk_check_button_new_with_mnemonic (_("Work on cop_y")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), cd->work_on_copy); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (bender_work_on_copy_callback), + cd); + + /* The curves graph */ + frame = gimp_frame_new (_("Modify Curves")); + gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (vbox), abox, FALSE, FALSE, 0); + gtk_widget_show (abox); + + cd->graph = gtk_drawing_area_new (); + gtk_widget_set_size_request (cd->graph, + GRAPH_WIDTH + RADIUS * 2, + GRAPH_HEIGHT + RADIUS * 2); + gtk_widget_set_events (cd->graph, GRAPH_MASK); + gtk_container_add (GTK_CONTAINER (abox), cd->graph); + gtk_widget_show (cd->graph); + + g_signal_connect (cd->graph, "event", + G_CALLBACK (bender_graph_events), + cd); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + frame = gimp_int_radio_group_new (TRUE, _("Curve for Border"), + G_CALLBACK (bender_border_callback), + &cd->outline, cd->outline, + + C_("curve-border", "_Upper"), OUTLINE_UPPER, &upper, + C_("curve-border", "_Lower"), OUTLINE_LOWER, &lower, + + NULL); + + g_object_set_data (G_OBJECT (upper), "cd", cd); + g_object_set_data (G_OBJECT (lower), "cd", cd); + + gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + frame = gimp_int_radio_group_new (TRUE, _("Curve Type"), + G_CALLBACK (bender_type_callback), + &cd->curve_type, cd->curve_type, + + _("Smoot_h"), SMOOTH, &smooth, + _("_Free"), GFREE, &freew, + + NULL); + g_object_set_data (G_OBJECT (smooth), "cd", cd); + g_object_set_data (G_OBJECT (freew), "cd", cd); + + gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + /* hbox for curve options */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* The Copy button */ + button = gtk_button_new_with_mnemonic (_("_Copy")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + gimp_help_set_help_data (button, + _("Copy the active curve to the other border"), NULL); + + g_signal_connect (button, "clicked", + G_CALLBACK (bender_copy_callback), + cd); + + /* The CopyInv button */ + button = gtk_button_new_with_mnemonic (_("_Mirror")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + gimp_help_set_help_data (button, + _("Mirror the active curve to the other border"), + NULL); + + g_signal_connect (button, "clicked", + G_CALLBACK (bender_copy_inv_callback), + cd); + + /* The Swap button */ + button = gtk_button_new_with_mnemonic (_("S_wap")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + gimp_help_set_help_data (button, + _("Swap the two curves"), NULL); + + g_signal_connect (button, "clicked", + G_CALLBACK (bender_swap_callback), + cd); + + /* The Reset button */ + button = gtk_button_new_with_mnemonic (_("_Reset")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + gimp_help_set_help_data (button, + _("Reset the active curve"), NULL); + + g_signal_connect (button, "clicked", + G_CALLBACK (bender_reset_callback), + cd); + + /* hbox for curve load and save */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* The Load button */ + button = gtk_button_new_with_mnemonic (_("_Open")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + gimp_help_set_help_data (button, + _("Load the curves from a file"), NULL); + + g_signal_connect (button, "clicked", + G_CALLBACK (bender_load_callback), + cd); + + /* The Save button */ + button = gtk_button_new_with_mnemonic (_("_Save")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + gimp_help_set_help_data (button, + _("Save the curves to a file"), NULL); + + g_signal_connect (button, "clicked", + G_CALLBACK (bender_save_callback), + cd); + + gtk_widget_show (main_hbox); + + return cd; +} + +static void +bender_update (BenderDialog *cd, + int update) +{ + GtkStyle *graph_style = gtk_widget_get_style (cd->graph); + gint i; + gint other; + + if (update & UP_PREVIEW) + { + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (cd->shell)), + cd->cursor_busy); + gdk_flush (); + + if (cd->preview_layer_id2 >= 0) + gimp_image_remove_layer(cd->preview_image_id, cd->preview_layer_id2); + + cd->preview_layer_id2 = p_main_bend(cd, cd->preview_layer_id1, TRUE /* work_on_copy*/ ); + p_render_preview(cd, cd->preview_layer_id2); + + if (update & UP_DRAW) + gtk_widget_queue_draw (cd->pv_widget); + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (cd->shell)), + NULL); + } + if (update & UP_PREVIEW_EXPOSE) + { + /* on expose just redraw cd->preview_layer_id2 + * that holds the bent version of the preview (if there is one) + */ + if (cd->preview_layer_id2 < 0) + cd->preview_layer_id2 = p_main_bend(cd, cd->preview_layer_id1, TRUE /* work_on_copy*/ ); + p_render_preview(cd, cd->preview_layer_id2); + + if (update & UP_DRAW) + gtk_widget_queue_draw (cd->pv_widget); + } + if ((update & UP_GRAPH) && (update & UP_DRAW) && cd->pixmap != NULL) + { + cairo_t *cr; + + cr = gdk_cairo_create (gtk_widget_get_window (cd->graph)); + + cairo_set_line_width (cr, 1.0); + cairo_translate (cr, 0.5, 0.5); + + /* Clear the background */ + gdk_cairo_set_source_color (cr, &graph_style->bg[GTK_STATE_NORMAL]); + cairo_paint (cr); + + /* Draw the grid lines */ + for (i = 0; i < 5; i++) + { + cairo_move_to (cr, RADIUS, i * (GRAPH_HEIGHT / 4) + RADIUS); + cairo_line_to (cr, GRAPH_WIDTH + RADIUS, i * (GRAPH_HEIGHT / 4) + RADIUS); + + cairo_move_to (cr, i * (GRAPH_WIDTH / 4) + RADIUS, RADIUS); + cairo_line_to (cr, i * (GRAPH_WIDTH / 4) + RADIUS, GRAPH_HEIGHT + RADIUS); + } + + gdk_cairo_set_source_color (cr, &graph_style->dark[GTK_STATE_NORMAL]); + cairo_stroke (cr); + + /* Draw the other curve */ + other = (cd->outline == 0) ? 1 : 0; + + cairo_move_to (cr, RADIUS, 255 - cd->curve[other][0] + RADIUS); + + for (i = 1; i < 256; i++) + { + cairo_line_to (cr, i + RADIUS, 255 - cd->curve[other][i] + RADIUS); + } + + gdk_cairo_set_source_color (cr, &graph_style->dark[GTK_STATE_NORMAL]); + cairo_stroke (cr); + + /* Draw the active curve */ + cairo_move_to (cr, RADIUS, 255 - cd->curve[cd->outline][0] + RADIUS); + + for (i = 1; i < 256; i++) + { + cairo_line_to (cr, i + RADIUS, 255 - cd->curve[cd->outline][i] + RADIUS); + } + + /* Draw the points */ + if (cd->curve_type == SMOOTH) + { + for (i = 0; i < 17; i++) + { + if (cd->points[cd->outline][i][0] != -1) + { + cairo_new_sub_path (cr); + cairo_arc (cr, + (cd->points[cd->outline][i][0] * 255.0) + RADIUS, + 255 - (cd->points[cd->outline][i][1] * 255.0) + RADIUS, + RADIUS, + 0, 2 * G_PI); + } + } + } + + gdk_cairo_set_source_color (cr, &graph_style->black); + cairo_stroke (cr); + + cairo_destroy (cr); + } +} + +static void +bender_plot_curve (BenderDialog *cd, + int p1, + int p2, + int p3, + int p4, + gint32 xmax, + gint32 ymax, + gint fix255) +{ + CRMatrix geometry; + CRMatrix tmp1, tmp2; + CRMatrix deltas; + double x, dx, dx2, dx3; + double y, dy, dy2, dy3; + double d, d2, d3; + int lastx, lasty; + gint32 newx, newy; + gint32 ntimes; + gint32 i; + + /* construct the geometry matrix from the segment */ + for (i = 0; i < 4; i++) + { + geometry[i][2] = 0; + geometry[i][3] = 0; + } + + geometry[0][0] = (cd->points[cd->outline][p1][0] * xmax); + geometry[1][0] = (cd->points[cd->outline][p2][0] * xmax); + geometry[2][0] = (cd->points[cd->outline][p3][0] * xmax); + geometry[3][0] = (cd->points[cd->outline][p4][0] * xmax); + + geometry[0][1] = (cd->points[cd->outline][p1][1] * ymax); + geometry[1][1] = (cd->points[cd->outline][p2][1] * ymax); + geometry[2][1] = (cd->points[cd->outline][p3][1] * ymax); + geometry[3][1] = (cd->points[cd->outline][p4][1] * ymax); + + /* subdivide the curve ntimes (1000) times */ + ntimes = 4 * xmax; + /* ntimes can be adjusted to give a finer or coarser curve */ + d = 1.0 / ntimes; + d2 = d * d; + d3 = d * d * d; + + /* construct a temporary matrix for determining the forward differencing deltas */ + tmp2[0][0] = 0; tmp2[0][1] = 0; tmp2[0][2] = 0; tmp2[0][3] = 1; + tmp2[1][0] = d3; tmp2[1][1] = d2; tmp2[1][2] = d; tmp2[1][3] = 0; + tmp2[2][0] = 6*d3; tmp2[2][1] = 2*d2; tmp2[2][2] = 0; tmp2[2][3] = 0; + tmp2[3][0] = 6*d3; tmp2[3][1] = 0; tmp2[3][2] = 0; tmp2[3][3] = 0; + + /* compose the basis and geometry matrices */ + bender_CR_compose (CR_basis, geometry, tmp1); + + /* compose the above results to get the deltas matrix */ + bender_CR_compose (tmp2, tmp1, deltas); + + /* extract the x deltas */ + x = deltas[0][0]; + dx = deltas[1][0]; + dx2 = deltas[2][0]; + dx3 = deltas[3][0]; + + /* extract the y deltas */ + y = deltas[0][1]; + dy = deltas[1][1]; + dy2 = deltas[2][1]; + dy3 = deltas[3][1]; + + lastx = CLAMP (x, 0, xmax); + lasty = CLAMP (y, 0, ymax); + + + if (fix255) + { + cd->curve[cd->outline][lastx] = lasty; + } + else + { + cd->curve_ptr[cd->outline][lastx] = lasty; + if(gb_debug) g_printf("bender_plot_curve xmax:%d ymax:%d\n", + (int)xmax, (int)ymax); + } + + /* loop over the curve */ + for (i = 0; i < ntimes; i++) + { + /* increment the x values */ + x += dx; + dx += dx2; + dx2 += dx3; + + /* increment the y values */ + y += dy; + dy += dy2; + dy2 += dy3; + + newx = CLAMP ((ROUND (x)), 0, xmax); + newy = CLAMP ((ROUND (y)), 0, ymax); + + /* if this point is different than the last one...then draw it */ + if ((lastx != newx) || (lasty != newy)) + { + if (fix255) + { + /* use fixed array size (for the curve graph) */ + cd->curve[cd->outline][newx] = newy; + } + else + { + /* use dynamic allocated curve_ptr (for the real curve) */ + cd->curve_ptr[cd->outline][newx] = newy; + + if(gb_debug) g_printf("outline: %d cX: %d cY: %d\n", + (int)cd->outline, (int)newx, (int)newy); + } + } + + lastx = newx; + lasty = newy; + } +} + +static void +bender_calculate_curve (BenderDialog *cd, + gint32 xmax, + gint32 ymax, + gint fix255) +{ + int i; + int points[17]; + int num_pts; + int p1, p2, p3, p4; + int xmid; + int yfirst, ylast; + + switch (cd->curve_type) + { + case GFREE: + break; + + case SMOOTH: + /* cycle through the curves */ + num_pts = 0; + for (i = 0; i < 17; i++) + if (cd->points[cd->outline][i][0] != -1) + points[num_pts++] = i; + + xmid = xmax / 2; + /* Initialize boundary curve points */ + if (num_pts != 0) + { + if (fix255) + { + for (i = 0; i < (cd->points[cd->outline][points[0]][0] * 255); i++) + cd->curve[cd->outline][i] = + (cd->points[cd->outline][points[0]][1] * 255); + + for (i = (cd->points[cd->outline][points[num_pts - 1]][0] * 255); i < 256; i++) + cd->curve[cd->outline][i] = + (cd->points[cd->outline][points[num_pts - 1]][1] * 255); + } + else + { + yfirst = cd->points[cd->outline][points[0]][1] * ymax; + ylast = cd->points[cd->outline][points[num_pts - 1]][1] * ymax; + + for (i = 0; i < xmid; i++) + { + cd->curve_ptr[cd->outline][i] = yfirst; + } + + for (i = xmid; i <= xmax; i++) + { + cd->curve_ptr[cd->outline][i] = ylast; + } + } + } + + for (i = 0; i < num_pts - 1; i++) + { + p1 = (i == 0) ? points[i] : points[(i - 1)]; + p2 = points[i]; + p3 = points[(i + 1)]; + p4 = (i == (num_pts - 2)) ? points[(num_pts - 1)] : points[(i + 2)]; + + bender_plot_curve (cd, p1, p2, p3, p4, xmax, ymax, fix255); + } + break; + } +} + +static void +bender_rotate_adj_callback (GtkAdjustment *adjustment, + gpointer client_data) +{ + BenderDialog *cd = client_data; + + if (gtk_adjustment_get_value (adjustment) != cd->rotation) + { + cd->rotation = gtk_adjustment_get_value (adjustment); + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); + } +} + +static void +bender_border_callback (GtkWidget *widget, + gpointer data) +{ + BenderDialog *cd; + + gimp_radio_button_update (widget, data); + cd = g_object_get_data (G_OBJECT (widget), "cd"); + bender_update (cd, UP_GRAPH | UP_DRAW); +} + +static void +bender_type_callback (GtkWidget *widget, + gpointer data) +{ + BenderDialog *cd; + + gimp_radio_button_update (widget, data); + + cd = g_object_get_data (G_OBJECT (widget), "cd"); + if (! cd) + return; + + if (cd->curve_type == SMOOTH) + { + gint i; + + /* pick representative points from the curve and make them control points */ + for (i = 0; i <= 8; i++) + { + gint index = CLAMP ((i * 32), 0, 255); + cd->points[cd->outline][i * 2][0] = (gdouble)index / 255.0; + cd->points[cd->outline][i * 2][1] = (gdouble)cd->curve[cd->outline][index] / 255.0; + } + + bender_calculate_curve (cd, 255, 255, TRUE); + bender_update (cd, UP_GRAPH | UP_DRAW); + + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); + } + else + { + bender_update (cd, UP_GRAPH | UP_DRAW); + } +} + +static void +bender_reset_callback (GtkWidget *widget, + gpointer client_data) +{ + BenderDialog *cd = (BenderDialog *) client_data; + gint i; + + /* Initialize the values */ + for (i = 0; i < 256; i++) + cd->curve[cd->outline][i] = MIDDLE; + + cd->grab_point = -1; + for (i = 0; i < 17; i++) + { + cd->points[cd->outline][i][0] = -1; + cd->points[cd->outline][i][1] = -1; + } + cd->points[cd->outline][0][0] = 0.0; /* x */ + cd->points[cd->outline][0][1] = 0.5; /* y */ + cd->points[cd->outline][16][0] = 1.0; /* x */ + cd->points[cd->outline][16][1] = 0.5; /* y */ + + bender_update (cd, UP_GRAPH | UP_DRAW); + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + +static void +bender_copy_callback (GtkWidget *widget, + gpointer client_data) +{ + BenderDialog *cd = (BenderDialog *) client_data; + int i; + int other; + + other = (cd->outline) ? 0 : 1; + + for (i = 0; i < 17; i++) + { + cd->points[other][i][0] = cd->points[cd->outline][i][0]; + cd->points[other][i][1] = cd->points[cd->outline][i][1]; + } + + for (i= 0; i < 256; i++) + { + cd->curve[other][i] = cd->curve[cd->outline][i]; + } + + bender_update (cd, UP_GRAPH | UP_DRAW); + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + +static void +bender_copy_inv_callback (GtkWidget *widget, + gpointer client_data) +{ + BenderDialog *cd = (BenderDialog*) client_data; + int i; + int other; + + other = (cd->outline) ? 0 : 1; + + for (i = 0; i < 17; i++) + { + cd->points[other][i][0] = cd->points[cd->outline][i][0]; /* x */ + cd->points[other][i][1] = 1.0 - cd->points[cd->outline][i][1]; /* y */ + } + + for (i= 0; i < 256; i++) + { + cd->curve[other][i] = 255 - cd->curve[cd->outline][i]; + } + + bender_update (cd, UP_GRAPH | UP_DRAW); + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + + +static void +bender_swap_callback (GtkWidget *widget, + gpointer client_data) +{ +#define SWAP_VALUE(a, b, h) { h=a; a=b; b=h; } + BenderDialog *cd = (BenderDialog*) client_data; + int i; + int other; + gdouble hd; + guchar hu; + + other = (cd->outline) ? 0 : 1; + + for (i = 0; i < 17; i++) + { + SWAP_VALUE(cd->points[other][i][0], cd->points[cd->outline][i][0], hd); /* x */ + SWAP_VALUE(cd->points[other][i][1], cd->points[cd->outline][i][1], hd); /* y */ + } + + for (i= 0; i < 256; i++) + { + SWAP_VALUE(cd->curve[other][i], cd->curve[cd->outline][i], hu); + } + + bender_update (cd, UP_GRAPH | UP_DRAW); + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + +static void +bender_response (GtkWidget *widget, + gint response_id, + BenderDialog *cd) +{ + if (response_id == GTK_RESPONSE_OK) + cd->run = TRUE; + + gtk_widget_destroy (GTK_WIDGET (cd->shell)); + gtk_main_quit (); +} + +static void +bender_preview_update (GtkWidget *widget, + gpointer data) +{ + BenderDialog *cd = (BenderDialog*) data; + + cd->preview = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if(cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + +static void +bender_preview_update_once (GtkWidget *widget, + gpointer data) +{ + BenderDialog *cd = (BenderDialog*) data; + + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + +static void +p_points_save_to_file_response (GtkWidget *dialog, + gint response_id, + BenderDialog *cd) +{ + if (response_id == GTK_RESPONSE_OK) + { + gchar *filename; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + p_save_pointfile (cd, filename); + + g_free (filename); + } + + gtk_widget_destroy (dialog); +} + +static void +p_points_load_from_file_response (GtkWidget *dialog, + gint response_id, + BenderDialog *cd) +{ + if (response_id == GTK_RESPONSE_OK) + { + gchar *filename; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + p_load_pointfile (cd, filename); + bender_update (cd, UP_ALL); + + g_free (filename); + } + + gtk_widget_destroy (dialog); +} + +static void +bender_load_callback (GtkWidget *w, + BenderDialog *cd) +{ + if (! cd->filechooser) + { + cd->filechooser = + gtk_file_chooser_dialog_new (_("Load Curve Points from File"), + GTK_WINDOW (gtk_widget_get_toplevel (w)), + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (cd->filechooser), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_dialog_set_default_response (GTK_DIALOG (cd->filechooser), + GTK_RESPONSE_OK); + + g_signal_connect (cd->filechooser, "response", + G_CALLBACK (p_points_load_from_file_response), + cd); + g_signal_connect (cd->filechooser, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &cd->filechooser); + } + + gtk_window_present (GTK_WINDOW (cd->filechooser)); +} + +static void +bender_save_callback (GtkWidget *w, + BenderDialog *cd) +{ + if (! cd->filechooser) + { + cd->filechooser = + gtk_file_chooser_dialog_new (_("Save Curve Points to File"), + GTK_WINDOW (gtk_widget_get_toplevel (w)), + GTK_FILE_CHOOSER_ACTION_SAVE, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_OK, + + NULL); + + g_signal_connect (cd->filechooser, "response", + G_CALLBACK (p_points_save_to_file_response), + cd); + g_signal_connect (cd->filechooser, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &cd->filechooser); + + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (cd->filechooser), + "curve_bend.points"); + } + + gtk_window_present (GTK_WINDOW (cd->filechooser)); +} + +static void +bender_smoothing_callback (GtkWidget *w, + gpointer data) +{ + BenderDialog *cd = (BenderDialog*) data; + + cd->smoothing = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)); + + if(cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + +static void +bender_antialias_callback (GtkWidget *w, + gpointer data) +{ + BenderDialog *cd = (BenderDialog*) data; + + cd->antialias = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)); + + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); +} + +static void +bender_work_on_copy_callback (GtkWidget *w, + gpointer data) +{ + BenderDialog *cd = (BenderDialog*) data; + + cd->work_on_copy = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)); +} + +static gboolean +bender_graph_events (GtkWidget *widget, + GdkEvent *event, + BenderDialog *cd) +{ + static GdkCursorType cursor_type = GDK_TOP_LEFT_ARROW; + GdkCursorType new_type; + GdkEventMotion *mevent; + int i; + int tx, ty; + int x, y; + int closest_point; + int distance; + int x1, x2, y1, y2; + + new_type = GDK_X_CURSOR; + closest_point = 0; + + /* get the pointer position */ + gdk_window_get_pointer (gtk_widget_get_window (cd->graph), &tx, &ty, NULL); + x = CLAMP ((tx - RADIUS), 0, 255); + y = CLAMP ((ty - RADIUS), 0, 255); + + distance = G_MAXINT; + for (i = 0; i < 17; i++) + { + if (cd->points[cd->outline][i][0] != -1) + if (abs ((int) (x - (cd->points[cd->outline][i][0] * 255.0))) < distance) + { + distance = abs ((int) (x - (cd->points[cd->outline][i][0] * 255.0))); + closest_point = i; + } + } + if (distance > MIN_DISTANCE) + closest_point = (x + 8) / 16; + + switch (event->type) + { + case GDK_EXPOSE: + if (cd->pixmap == NULL) + cd->pixmap = gdk_pixmap_new (gtk_widget_get_window (cd->graph), + GRAPH_WIDTH + RADIUS * 2, + GRAPH_HEIGHT + RADIUS * 2, -1); + + bender_update (cd, UP_GRAPH | UP_DRAW); + break; + + case GDK_BUTTON_PRESS: + new_type = GDK_TCROSS; + + switch (cd->curve_type) + { + case SMOOTH: + /* determine the leftmost and rightmost points */ + cd->leftmost = -1; + for (i = closest_point - 1; i >= 0; i--) + if (cd->points[cd->outline][i][0] != -1) + { + cd->leftmost = (cd->points[cd->outline][i][0] * 255.0); + break; + } + cd->rightmost = 256; + for (i = closest_point + 1; i < 17; i++) + if (cd->points[cd->outline][i][0] != -1) + { + cd->rightmost = (cd->points[cd->outline][i][0] * 255.0); + break; + } + + cd->grab_point = closest_point; + cd->points[cd->outline][cd->grab_point][0] = (gdouble)x / 255.0; + cd->points[cd->outline][cd->grab_point][1] = (gdouble)(255 - y) / 255.0; + + bender_calculate_curve (cd, 255, 255, TRUE); + break; + + case GFREE: + cd->curve[cd->outline][x] = 255 - y; + cd->grab_point = x; + cd->last = y; + break; + } + + bender_update (cd, UP_GRAPH | UP_DRAW); + break; + + case GDK_BUTTON_RELEASE: + new_type = GDK_FLEUR; + cd->grab_point = -1; + + if (cd->preview) + bender_update (cd, UP_PREVIEW | UP_DRAW); + break; + + case GDK_MOTION_NOTIFY: + mevent = (GdkEventMotion *) event; + + if (mevent->is_hint) + { + mevent->x = tx; + mevent->y = ty; + } + + switch (cd->curve_type) + { + case SMOOTH: + /* If no point is grabbed... */ + if (cd->grab_point == -1) + { + if (cd->points[cd->outline][closest_point][0] != -1) + new_type = GDK_FLEUR; + else + new_type = GDK_TCROSS; + } + /* Else, drag the grabbed point */ + else + { + new_type = GDK_TCROSS; + + cd->points[cd->outline][cd->grab_point][0] = -1; + + if (x > cd->leftmost && x < cd->rightmost) + { + closest_point = (x + 8) / 16; + if (cd->points[cd->outline][closest_point][0] == -1) + cd->grab_point = closest_point; + cd->points[cd->outline][cd->grab_point][0] = (gdouble)x / 255.0; + cd->points[cd->outline][cd->grab_point][1] = (gdouble)(255 - y) / 255.0; + } + + bender_calculate_curve (cd, 255, 255, TRUE); + bender_update (cd, UP_GRAPH | UP_DRAW); + } + break; + + case GFREE: + if (cd->grab_point != -1) + { + if (cd->grab_point > x) + { + x1 = x; + x2 = cd->grab_point; + y1 = y; + y2 = cd->last; + } + else + { + x1 = cd->grab_point; + x2 = x; + y1 = cd->last; + y2 = y; + } + + if (x2 != x1) + for (i = x1; i <= x2; i++) + cd->curve[cd->outline][i] = 255 - (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1)); + else + cd->curve[cd->outline][x] = 255 - y; + + cd->grab_point = x; + cd->last = y; + + bender_update (cd, UP_GRAPH | UP_DRAW); + } + + if (mevent->state & GDK_BUTTON1_MASK) + new_type = GDK_TCROSS; + else + new_type = GDK_PENCIL; + break; + } + + if (new_type != cursor_type) + { + cursor_type = new_type; + /* change_win_cursor (gtk_widget_get_window (cd->graph), cursor_type); */ + } + break; + + default: + break; + } + + return FALSE; +} + +static void +bender_CR_compose (CRMatrix a, + CRMatrix b, + CRMatrix ab) +{ + gint i, j; + + for (i = 0; i < 4; i++) + { + for (j = 0; j < 4; j++) + { + ab[i][j] = (a[i][0] * b[0][j] + + a[i][1] * b[1][j] + + a[i][2] * b[2][j] + + a[i][3] * b[3][j]); + } + } +} + +static void +p_render_preview (BenderDialog *cd, + gint32 layer_id) +{ + guchar pixel[4]; + guchar *buf, *ptr; + gint x, y; + gint ofx, ofy; + gint width, height; + t_GDRW l_gdrw; + t_GDRW *gdrw; + + width = gimp_drawable_width (layer_id); + height = gimp_drawable_height (layer_id); + + ptr = buf = g_new (guchar, PREVIEW_BPP * PREVIEW_SIZE_X * PREVIEW_SIZE_Y); + gdrw = &l_gdrw; + p_init_gdrw(gdrw, layer_id, FALSE); + + /* offsets to set bend layer to preview center */ + ofx = (width / 2) - (PREVIEW_SIZE_X / 2); + ofy = (height / 2) - (PREVIEW_SIZE_Y / 2); + + /* render preview */ + for (y = 0; y < PREVIEW_SIZE_Y; y++) + { + for (x = 0; x < PREVIEW_SIZE_X; x++) + { + p_get_pixel (gdrw, x + ofx, y + ofy, &pixel[0]); + + if (cd->color) + { + ptr[0] = pixel[0]; + ptr[1] = pixel[1]; + ptr[2] = pixel[2]; + } + else + { + ptr[0] = pixel[0]; + ptr[1] = pixel[0]; + ptr[2] = pixel[0]; + } + + ptr[3] = pixel[gdrw->index_alpha]; + + ptr += PREVIEW_BPP; + } + } + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (cd->pv_widget), + 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, + GIMP_RGBA_IMAGE, + buf, + PREVIEW_BPP * PREVIEW_SIZE_X); + g_free (buf); + + p_end_gdrw(gdrw); +} + +/* ===================================================== */ +/* curve_bend worker procedures */ +/* ===================================================== */ + +static void +p_stretch_curves (BenderDialog *cd, + gint32 xmax, + gint32 ymax) +{ + gint32 x1, x2; + gdouble ya, yb; + gdouble rest; + int outline; + + for (outline = 0; outline < 2; outline++) + { + for(x1 = 0; x1 <= xmax; x1++) + { + x2 = (x1 * 255) / xmax; + + if ((xmax <= 255) && (x2 < 255)) + { + cd->curve_ptr[outline][x1] = + ROUND ((cd->curve[outline][x2] * ymax) / 255); + } + else + { + /* interpolate */ + rest = (((gdouble)x1 * 255.0) / (gdouble)xmax) - x2; + ya = cd->curve[outline][x2]; /* y of this point */ + yb = cd->curve[outline][x2 +1]; /* y of next point */ + + cd->curve_ptr[outline][x1] = + ROUND (((ya + ((yb -ya) * rest)) * ymax) / 255); + } + } + } +} + +static void +bender_init_min_max (BenderDialog *cd, + gint32 xmax) +{ + int i, j; + + for (i = 0; i < 2; i++) + { + cd->min2[i] = 65000; + cd->max2[i] = 0; + + for (j = 0; j <= xmax; j++) + { + if(cd->curve_ptr[i][j] > cd->max2[i]) + { + cd->max2[i] = cd->curve_ptr[i][j]; + } + + if(cd->curve_ptr[i][j] < cd->min2[i]) + { + cd->min2[i] = cd->curve_ptr[i][j]; + } + } + } + + /* for UPPER outline : y-zero line is assumed at the min leftmost or + * rightmost point + */ + cd->zero2[OUTLINE_UPPER] = MIN (cd->curve_ptr[OUTLINE_UPPER][0], + cd->curve_ptr[OUTLINE_UPPER][xmax]); + + /* for LOWER outline : y-zero line is assumed at the min leftmost or + * rightmost point + */ + cd->zero2[OUTLINE_LOWER] = MAX (cd->curve_ptr[OUTLINE_LOWER][0], + cd->curve_ptr[OUTLINE_LOWER][xmax]); +} + +static gint32 +p_curve_get_dy (BenderDialog *cd, + gint32 x, + gint32 drawable_width, + gint32 total_steps, + gdouble current_step) +{ + /* get y values of both upper and lower curve, + * and return the iterated value in between + */ + gdouble y1, y2; + gdouble delta; + + y1 = cd->zero2[OUTLINE_UPPER] - cd->curve_ptr[OUTLINE_UPPER][x]; + y2 = cd->zero2[OUTLINE_LOWER] - cd->curve_ptr[OUTLINE_LOWER][x]; + + delta = ((double)(y2 - y1) / (double)(total_steps - 1)) * current_step; + + return SIGNED_ROUND (y1 + delta); +} + +static gint32 +p_upper_curve_extend (BenderDialog *cd, + gint32 drawable_width, + gint32 drawable_height) +{ + gint32 y1, y2; + + y1 = cd->max2[OUTLINE_UPPER] - cd->zero2[OUTLINE_UPPER]; + y2 = (cd->max2[OUTLINE_LOWER] - cd->zero2[OUTLINE_LOWER]) - drawable_height; + + return MAX (y1, y2); +} + +static gint32 +p_lower_curve_extend (BenderDialog *cd, + gint32 drawable_width, + gint32 drawable_height) +{ + gint32 y1, y2; + + y1 = cd->zero2[OUTLINE_LOWER] - cd->min2[OUTLINE_LOWER]; + y2 = (cd->zero2[OUTLINE_UPPER] - cd->min2[OUTLINE_UPPER]) - drawable_height; + + return MAX (y1, y2); +} + +static void +p_end_gdrw (t_GDRW *gdrw) +{ + g_object_unref (gdrw->buffer); +} + +static void +p_init_gdrw (t_GDRW *gdrw, + gint32 drawable_id, + int shadow) +{ + gint w, h; + + gdrw->drawable_id = drawable_id; + + if (shadow) + gdrw->buffer = gimp_drawable_get_shadow_buffer (drawable_id); + else + gdrw->buffer = gimp_drawable_get_buffer (drawable_id); + + gdrw->width = gimp_drawable_width (gdrw->drawable_id); + gdrw->height = gimp_drawable_height (gdrw->drawable_id); + + gdrw->tile_width = gimp_tile_width (); + gdrw->tile_height = gimp_tile_height (); + + if (! gimp_drawable_mask_intersect (gdrw->drawable_id, + &gdrw->x1, &gdrw->y1, &w, &h)) + { + w = 0; + h = 0; + } + + gdrw->x2 = gdrw->x1 + w; + gdrw->y2 = gdrw->y1 + h; + + if (gimp_drawable_has_alpha (drawable_id)) + gdrw->format = babl_format ("R'G'B'A u8"); + else + gdrw->format = babl_format ("R'G'B' u8"); + + gdrw->bpp = babl_format_get_bytes_per_pixel (gdrw->format); + + if (gimp_drawable_has_alpha (drawable_id)) + { + /* index of the alpha channelbyte {1|3} */ + gdrw->index_alpha = gdrw->bpp - 1; + } + else + { + gdrw->index_alpha = 0; /* there is no alpha channel */ + } +} + +/* get pixel value + * return light transparent black gray pixel if out of bounds + * (should occur in the previews only) + */ +static void +p_get_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *pixel) +{ + pixel[1] = 255; + pixel[3] = 255; /* simulate full visible alpha channel */ + + gegl_buffer_sample (gdrw->buffer, x, y, NULL, pixel, gdrw->format, + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); +} + +static void +p_put_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *pixel) +{ + gegl_buffer_set (gdrw->buffer, GEGL_RECTANGLE (x, y, 1, 1), 0, + gdrw->format, pixel, GEGL_AUTO_ROWSTRIDE); +} + +static void +p_put_mix_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *color, + gint32 nb_curvy, + gint32 nb2_curvy, + gint32 curvy) +{ + guchar pixel[4]; + guchar mixmask; + gint idx; + gint diff; + + mixmask = 255 - 96; + diff = abs(nb_curvy - curvy); + + if (diff == 0) + { + mixmask = 255 - 48; + diff = abs(nb2_curvy - curvy); + + if (diff == 0) + { + /* last 2 neighbours were not shifted against current pixel, do not mix */ + p_put_pixel(gdrw, x, y, color); + return; + } + } + + /* get left neighbour pixel */ + p_get_pixel(gdrw, x-1, y, &pixel[0]); + + if (pixel[gdrw->index_alpha] < 10) + { + /* neighbour is (nearly or full) transparent, do not mix */ + p_put_pixel(gdrw, x, y, color); + return; + } + + for (idx = 0; idx < gdrw->index_alpha ; idx++) + { + /* mix in left neighbour color */ + pixel[idx] = MIX_CHANNEL(color[idx], pixel[idx], mixmask); + } + + pixel[gdrw->index_alpha] = color[gdrw->index_alpha]; + p_put_pixel(gdrw, x, y, &pixel[0]); +} + +/* ============================================================================ + * p_create_pv_image + * ============================================================================ + */ +static gint32 +p_create_pv_image (gint32 src_drawable_id, + gint32 *layer_id) +{ + gint32 new_image_id; + guint new_width; + guint new_height; + GimpImageType type; + guint x, y; + double scale; + guchar pixel[4]; + t_GDRW src_gdrw; + t_GDRW dst_gdrw; + gint src_width; + gint src_height; + + src_width = gimp_drawable_width (src_drawable_id); + src_height = gimp_drawable_height (src_drawable_id); + + new_image_id = gimp_image_new (PREVIEW_SIZE_X, PREVIEW_SIZE_Y, + gimp_image_base_type (gimp_item_get_image (src_drawable_id))); + gimp_image_undo_disable (new_image_id); + + type = gimp_drawable_type (src_drawable_id); + if (src_height > src_width) + { + new_height = PV_IMG_HEIGHT; + new_width = (src_width * new_height) / src_height; + scale = (float) src_height / PV_IMG_HEIGHT; + } + else + { + new_width = PV_IMG_WIDTH; + new_height = (src_height * new_width) / src_width; + scale = (float) src_width / PV_IMG_WIDTH; + } + + *layer_id = gimp_layer_new(new_image_id, "preview_original", + new_width, new_height, + type, + 100.0, /* opacity */ + 0); /* mode NORMAL */ + if (! gimp_drawable_has_alpha(*layer_id)) + { + /* always add alpha channel */ + gimp_layer_add_alpha(*layer_id); + } + + gimp_image_insert_layer(new_image_id, *layer_id, -1, 0); + + p_init_gdrw (&src_gdrw, src_drawable_id, FALSE); + p_init_gdrw (&dst_gdrw, *layer_id, FALSE); + + for (y = 0; y < new_height; y++) + { + for (x = 0; x < new_width; x++) + { + p_get_pixel(&src_gdrw, x * scale, y * scale, &pixel[0]); + p_put_pixel(&dst_gdrw, x, y, &pixel[0]); + } + } + + p_end_gdrw (&src_gdrw); + p_end_gdrw (&dst_gdrw); + + return new_image_id; +} + +/* ============================================================================ + * p_add_layer + * ============================================================================ + */ +static gint32 +p_add_layer (gint width, + gint height, + gint32 src_drawable_id) +{ + GimpImageType type; + gint32 new_layer_id; + char *name; + char *name2; + gdouble opacity; + GimpLayerMode mode; + gint visible; + gint32 image_id; + gint stack_position; + + image_id = gimp_item_get_image (src_drawable_id); + stack_position = 0; /* TODO: should be same as src_layer */ + + /* copy type, name, opacity and mode from src_drawable */ + type = gimp_drawable_type (src_drawable_id); + visible = gimp_item_get_visible (src_drawable_id); + + name2 = gimp_item_get_name (src_drawable_id); + name = g_strdup_printf ("%s_b", name2); + g_free (name2); + + mode = gimp_layer_get_mode (src_drawable_id); + opacity = gimp_layer_get_opacity (src_drawable_id); /* full opacity */ + + new_layer_id = gimp_layer_new (image_id, name, + width, height, + type, + opacity, + mode); + + g_free (name); + if (!gimp_drawable_has_alpha (new_layer_id)) + { + /* always add alpha channel */ + gimp_layer_add_alpha (new_layer_id); + } + + /* add the copied layer to the temp. working image */ + gimp_image_insert_layer (image_id, new_layer_id, -1, stack_position); + + /* copy visibility state */ + gimp_item_set_visible (new_layer_id, visible); + + return new_layer_id; +} + +/* ============================================================================ + * p_bender_calculate_iter_curve + * ============================================================================ + */ + +static void +p_bender_calculate_iter_curve (BenderDialog *cd, + gint32 xmax, + gint32 ymax) +{ + gint x; + gint outline; + BenderDialog *cd_from; + BenderDialog *cd_to; + + outline = cd->outline; + + if ((cd->bval_from == NULL) || + (cd->bval_to == NULL) || + (cd->bval_curr == NULL)) + { + if(gb_debug) g_printf("p_bender_calculate_iter_curve NORMAL1\n"); + if (cd->curve_type == SMOOTH) + { + cd->outline = OUTLINE_UPPER; + bender_calculate_curve (cd, xmax, ymax, FALSE); + cd->outline = OUTLINE_LOWER; + bender_calculate_curve (cd, xmax, ymax, FALSE); + } + else + { + p_stretch_curves(cd, xmax, ymax); + } + } + else + { + /* compose curves by iterating between FROM/TO values */ + if(gb_debug) g_printf ("p_bender_calculate_iter_curve ITERmode 1\n"); + + /* init FROM curves */ + cd_from = g_new (BenderDialog, 1); + p_cd_from_bval (cd_from, cd->bval_from); + cd_from->curve_ptr[OUTLINE_UPPER] = g_new (gint32, 1+xmax); + cd_from->curve_ptr[OUTLINE_LOWER] = g_new (gint32, 1+xmax); + + /* init TO curves */ + cd_to = g_new (BenderDialog, 1); + p_cd_from_bval (cd_to, cd->bval_to); + cd_to->curve_ptr[OUTLINE_UPPER] = g_new (gint32, 1+xmax); + cd_to->curve_ptr[OUTLINE_LOWER] = g_new (gint32, 1+xmax); + + if (cd_from->curve_type == SMOOTH) + { + /* calculate FROM curves */ + cd_from->outline = OUTLINE_UPPER; + bender_calculate_curve (cd_from, xmax, ymax, FALSE); + cd_from->outline = OUTLINE_LOWER; + bender_calculate_curve (cd_from, xmax, ymax, FALSE); + } + else + { + p_stretch_curves (cd_from, xmax, ymax); + } + + if (cd_to->curve_type == SMOOTH) + { + /* calculate TO curves */ + cd_to->outline = OUTLINE_UPPER; + bender_calculate_curve (cd_to, xmax, ymax, FALSE); + cd_to->outline = OUTLINE_LOWER; + bender_calculate_curve (cd_to, xmax, ymax, FALSE); + } + else + { + p_stretch_curves (cd_to, xmax, ymax); + } + + /* MIX Y-koords of the curves according to current iteration step */ + for (x = 0; x <= xmax; x++) + { + p_delta_gint32 (&cd->curve_ptr[OUTLINE_UPPER][x], + cd_from->curve_ptr[OUTLINE_UPPER][x], + cd_to->curve_ptr[OUTLINE_UPPER][x], + cd->bval_curr->total_steps, + cd->bval_curr->current_step); + + p_delta_gint32 (&cd->curve_ptr[OUTLINE_LOWER][x], + cd_from->curve_ptr[OUTLINE_LOWER][x], + cd_to->curve_ptr[OUTLINE_LOWER][x], + cd->bval_curr->total_steps, + cd->bval_curr->current_step); + } + + g_free (cd_from->curve_ptr[OUTLINE_UPPER]); + g_free (cd_from->curve_ptr[OUTLINE_LOWER]); + + g_free (cd_from); + g_free (cd_to); + } + + cd->outline = outline; +} + +/* ============================================================================ + * p_vertical_bend + * ============================================================================ + */ + +static void +p_vertical_bend (BenderDialog *cd, + t_GDRW *src_gdrw, + t_GDRW *dst_gdrw) +{ + gint32 row, col; + gint32 first_row, first_col, last_row, last_col; + gint32 x, y; + gint32 x2, y2; + gint32 curvy, nb_curvy, nb2_curvy; + gint32 desty, othery; + gint32 miny, maxy; + gint32 sign, dy, diff; + gint32 topshift; + float progress_step; + float progress_max; + float progress; + + t_Last *last_arr; + t_Last *first_arr; + guchar color[4]; + guchar mixcolor[4]; + gint alias_dir; + guchar mixmask; + + topshift = p_upper_curve_extend (cd, + src_gdrw->width, + src_gdrw->height); + diff = curvy = nb_curvy = nb2_curvy= miny = maxy = 0; + + /* allocate array of last values (one element foreach x coordinate) */ + last_arr = g_new (t_Last, src_gdrw->x2); + first_arr = g_new (t_Last, src_gdrw->x2); + + /* ------------------------------------------------ + * foreach pixel in the SAMPLE_drawable: + * ------------------------------------------------ + * the inner loops (x/y) are designed to process + * all pixels of one tile in the sample drawable, the outer loops (row/col) do step + * to the next tiles. (this was done to reduce tile swapping) + */ + + first_row = src_gdrw->y1 / src_gdrw->tile_height; + last_row = (src_gdrw->y2 / src_gdrw->tile_height); + first_col = src_gdrw->x1 / src_gdrw->tile_width; + last_col = (src_gdrw->x2 / src_gdrw->tile_width); + + /* init progress */ + progress_max = (1 + last_row - first_row) * (1 + last_col - first_col); + progress_step = 1.0 / progress_max; + progress = 0.0; + if (cd->show_progress) + gimp_progress_init ( _("Curve Bend")); + + for (row = first_row; row <= last_row; row++) + { + for (col = first_col; col <= last_col; col++) + { + if (col == first_col) + x = src_gdrw->x1; + else + x = col * src_gdrw->tile_width; + if (col == last_col) + x2 = src_gdrw->x2; + else + x2 = (col +1) * src_gdrw->tile_width; + + if (cd->show_progress) + gimp_progress_update (progress += progress_step); + + for( ; x < x2; x++) + { + if (row == first_row) + y = src_gdrw->y1; + else + y = row * src_gdrw->tile_height; + + if (row == last_row) + y2 = src_gdrw->y2; + else + y2 = (row +1) * src_gdrw->tile_height ; + + for( ; y < y2; y++) + { + /* ---------- copy SRC_PIXEL to curve position ------ */ + + p_get_pixel (src_gdrw, x, y, color); + + curvy = p_curve_get_dy (cd, x, + (gint32)src_gdrw->width, + (gint32)src_gdrw->height, + (gdouble)y); + desty = y + topshift + curvy; + + /* ----------- SMOOTHING ------------------ */ + if (cd->smoothing && (x > 0)) + { + nb_curvy = p_curve_get_dy (cd, x -1, + (gint32)src_gdrw->width, + (gint32)src_gdrw->height, + (gdouble)y); + if ((nb_curvy == curvy) && (x > 1)) + { + nb2_curvy = p_curve_get_dy (cd, x -2, + (gint32)src_gdrw->width, + (gint32)src_gdrw->height, + (gdouble)y); + } + else + { + nb2_curvy = nb_curvy; + } + + p_put_mix_pixel (dst_gdrw, x, desty, color, nb_curvy, nb2_curvy, curvy); + } + else + { + p_put_pixel (dst_gdrw, x, desty, color); + } + + /* ----------- render ANTIALIAS ------------------ */ + + if (cd->antialias) + { + othery = desty; + + if (y == src_gdrw->y1) /* Upper outline */ + { + first_arr[x].y = curvy; + memcpy (first_arr[x].color, color, + dst_gdrw->bpp); + + if (x > 0) + { + memcpy (mixcolor, first_arr[x-1].color, + dst_gdrw->bpp); + + diff = abs(first_arr[x - 1].y - curvy) +1; + miny = MIN(first_arr[x - 1].y, curvy) -1; + maxy = MAX(first_arr[x - 1].y, curvy) +1; + + othery = (src_gdrw->y2 -1) + + topshift + + p_curve_get_dy(cd, x, + (gint32)src_gdrw->width, + (gint32)src_gdrw->height, + (gdouble)(src_gdrw->y2 -1)); + } + } + + if (y == src_gdrw->y2 - 1) /* Lower outline */ + { + if (x > 0) + { + memcpy (mixcolor, last_arr[x-1].color, + dst_gdrw->bpp); + + diff = abs (last_arr[x - 1].y - curvy) +1; + maxy = MAX (last_arr[x - 1].y, curvy) +1; + miny = MIN (last_arr[x - 1].y, curvy) -1; + } + + othery = (src_gdrw->y1) + + topshift + + p_curve_get_dy(cd, x, + (gint32)src_gdrw->width, + (gint32)src_gdrw->height, + (gdouble)(src_gdrw->y1)); + } + + if(desty < othery) { alias_dir = 1; } /* fade to transp. with descending dy */ + else if(desty > othery) { alias_dir = -1; } /* fade to transp. with ascending dy */ + else { alias_dir = 0; } /* no antialias at curve crossing point(s) */ + + if (alias_dir != 0) + { + guchar alpha_lo = 20; + + if (gimp_drawable_has_alpha (src_gdrw->drawable_id)) + { + alpha_lo = MIN (20, mixcolor[src_gdrw->index_alpha]); + } + + for (dy = 0; dy < diff; dy++) + { + /* iterate for fading alpha channel */ + mixmask = 255 * ((gdouble)(dy + 1) / (gdouble) (diff+1)); + mixcolor[dst_gdrw->index_alpha] = MIX_CHANNEL(color[dst_gdrw->index_alpha], alpha_lo, mixmask); + + if(alias_dir > 0) + { + p_put_pixel (dst_gdrw, x -1, y + topshift + miny + dy, mixcolor); + } + else + { + p_put_pixel (dst_gdrw, x -1, y + topshift + (maxy - dy), mixcolor); + } + + } + } + } + + /* ------------------ FILL HOLES ------------------ */ + + if (y == src_gdrw->y1) + { + diff = 0; + sign = 1; + } + else + { + diff = last_arr[x].y - curvy; + if (diff < 0) + { + diff = 0 - diff; + sign = -1; + } + else + { + sign = 1; + } + + memcpy (mixcolor, color, dst_gdrw->bpp); + } + + for (dy = 1; dy <= diff; dy++) + { + /* y differs more than 1 pixel from last y in the + * destination drawable. So we have to fill the empty + * space between using a mixed color + */ + + if (cd->smoothing) + { + /* smoothing is on, so we are using a mixed color */ + gulong alpha1 = last_arr[x].color[3]; + gulong alpha2 = color[3]; + gulong alpha; + + mixmask = 255 * ((gdouble)(dy) / (gdouble)(diff+1)); + alpha = alpha1 * mixmask + alpha2 * (255 - mixmask); + mixcolor[3] = alpha/255; + if (mixcolor[3]) + { + mixcolor[0] = (alpha1 * mixmask * last_arr[x].color[0] + + alpha2 * (255 - mixmask) * color[0])/alpha; + mixcolor[1] = (alpha1 * mixmask * last_arr[x].color[1] + + alpha2 * (255 - mixmask) * color[1])/alpha; + mixcolor[2] = (alpha1 * mixmask * last_arr[x].color[2] + + alpha2 * (255 - mixmask) * color[2])/alpha; + /*mixcolor[2] = MIX_CHANNEL(last_arr[x].color[2], color[2], mixmask);*/ + } + } + else + { + /* smoothing is off, so we are using this color or + the last color */ + if (dy < diff / 2) + { + memcpy (mixcolor, color, + dst_gdrw->bpp); + } + else + { + memcpy (mixcolor, last_arr[x].color, + dst_gdrw->bpp); + } + } + + if (cd->smoothing) + { + p_put_mix_pixel (dst_gdrw, x, + desty + (dy * sign), + mixcolor, + nb_curvy, nb2_curvy, curvy ); + } + else + { + p_put_pixel (dst_gdrw, x, + desty + (dy * sign), mixcolor); + } + } + + /* store y and color */ + last_arr[x].y = curvy; + memcpy (last_arr[x].color, color, dst_gdrw->bpp); + } + } + } + } + + gimp_progress_update (1.0); + + g_free (last_arr); + g_free (first_arr); +} + +/* ============================================================================ + * p_main_bend + * ============================================================================ + */ + +static gint32 +p_main_bend (BenderDialog *cd, + guint32 original_drawable_id, + gint work_on_copy) +{ + t_GDRW src_gdrw; + t_GDRW dst_gdrw; + gint32 src_drawable_id; + gint32 dst_drawable_id; + gint src_width; + gint src_height; + gint32 dst_height; + gint32 image_id; + gint32 tmp_layer_id; + gint32 interpolation; + gint offset_x, offset_y; + gint center_x, center_y; + gint32 xmax, ymax; + + interpolation = cd->smoothing; + image_id = gimp_item_get_image (original_drawable_id); + gimp_drawable_offsets(original_drawable_id, &offset_x, &offset_y); + + center_x = offset_x + (gimp_drawable_width (original_drawable_id) / 2 ); + center_y = offset_y + (gimp_drawable_height (original_drawable_id) / 2 ); + + /* always copy original_drawable to a tmp src_layer */ + tmp_layer_id = gimp_layer_copy(original_drawable_id); + /* set layer invisible and dummyname and + * add at top of the image while working + * (for the case of undo GIMP must know, + * that the layer was part of the image) + */ + gimp_image_insert_layer (image_id, tmp_layer_id, -1, 0); + gimp_item_set_visible (tmp_layer_id, FALSE); + gimp_item_set_name (tmp_layer_id, "curve_bend_dummylayer"); + + if(gb_debug) g_printf("p_main_bend tmp_layer_id %d\n", (int)tmp_layer_id); + + if (cd->rotation != 0.0) + { + if(gb_debug) g_printf("p_main_bend rotate: %f\n", (float)cd->rotation); + p_gimp_rotate(image_id, tmp_layer_id, interpolation, cd->rotation); + } + + src_drawable_id = tmp_layer_id; + + src_width = gimp_drawable_width (tmp_layer_id); + src_height = gimp_drawable_height (tmp_layer_id); + + xmax = ymax = src_width -1; + cd->curve_ptr[OUTLINE_UPPER] = g_new (gint32, 1+xmax); + cd->curve_ptr[OUTLINE_LOWER] = g_new (gint32, 1+xmax); + + p_bender_calculate_iter_curve(cd, xmax, ymax); + bender_init_min_max(cd, xmax); + + dst_height = src_height + + p_upper_curve_extend(cd, src_width, src_height) + + p_lower_curve_extend(cd, src_width, src_height); + + if(gb_debug) g_printf("p_main_bend: dst_height:%d\n", dst_height); + + if (work_on_copy) + { + dst_drawable_id = p_add_layer (src_width, dst_height, + src_drawable_id); + if (gb_debug) g_printf("p_main_bend: DONE add layer\n"); + } + else + { + /* work on the original */ + gimp_layer_resize (original_drawable_id, + src_width, + dst_height, + offset_x, offset_y); + if (gb_debug) g_printf("p_main_bend: DONE layer resize\n"); + if (! gimp_drawable_has_alpha (original_drawable_id)) + { + /* always add alpha channel */ + gimp_layer_add_alpha (original_drawable_id); + } + + dst_drawable_id = original_drawable_id; + } + + gimp_drawable_fill (dst_drawable_id, GIMP_FILL_TRANSPARENT); + + p_init_gdrw (&src_gdrw, src_drawable_id, FALSE); + p_init_gdrw (&dst_gdrw, dst_drawable_id, FALSE); + + p_vertical_bend (cd, &src_gdrw, &dst_gdrw); + + if(gb_debug) g_printf("p_main_bend: DONE vertical bend\n"); + + p_end_gdrw (&src_gdrw); + p_end_gdrw (&dst_gdrw); + + if (cd->rotation != 0.0) + { + p_gimp_rotate (image_id, dst_drawable_id, + interpolation, (gdouble)(360.0 - cd->rotation)); + + /* TODO: here we should crop dst_drawable to cut off full transparent borderpixels */ + } + + /* set offsets of the resulting new layer + *(center == center of original_drawable) + */ + offset_x = center_x - (gimp_drawable_width (dst_drawable_id) / 2 ); + offset_y = center_y - (gimp_drawable_height (dst_drawable_id) / 2 ); + gimp_layer_set_offsets (dst_drawable_id, offset_x, offset_y); + + /* delete the temp layer */ + gimp_image_remove_layer (image_id, tmp_layer_id); + + g_free (cd->curve_ptr[OUTLINE_UPPER]); + g_free (cd->curve_ptr[OUTLINE_LOWER]); + + if (gb_debug) g_printf("p_main_bend: DONE bend main\n"); + + return dst_drawable_id; +} diff --git a/plug-ins/common/decompose.c b/plug-ins/common/decompose.c new file mode 100644 index 0000000..b96c317 --- /dev/null +++ b/plug-ins/common/decompose.c @@ -0,0 +1,973 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Decompose plug-in (C) 1997 Peter Kirchgessner + * e-mail: peter@kirchgessner.net, WWW: http://www.kirchgessner.net + * + * Copyright 2013 Martijn van Beers + * Copyright 2013 Téo Mazars + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Lab colorspace support originally written by Alexey Dyachenko, + * merged into the officical plug-in by Sven Neumann. + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#define PLUG_IN_PROC "plug-in-decompose" +#define PLUG_IN_PROC_REG "plug-in-decompose-registered" +#define PLUG_IN_BINARY "decompose" +#define PLUG_IN_ROLE "gimp-decompose" + + +/* Description of a component */ +typedef struct +{ + const gchar *babl_name; /* channel's babl_component name */ + const gchar *channel_name; /* name of channel to extract */ + + const gdouble range_min; /* min and max */ + const gdouble range_max; + const gboolean perceptual_channel; /* "correct" the channel in Y' space */ + +} Component; + + +/* Maximum number of images/layers generated by an extraction */ +#define MAX_EXTRACT_IMAGES 4 + +/* Description of an extraction */ +typedef struct +{ + const gchar *type; /* What to extract */ + const gchar *model; /* the babl_model string to use */ + const gboolean dialog; /* Set to TRUE if you want + * this extract function in the dialog */ + const gint num_images; /* Number of images to create */ + + const gboolean clamp; /* clamping values in [0.0, 1.0] */ + + /* the babl_component names of the channels */ + const Component component[MAX_EXTRACT_IMAGES]; + +} Extract; + +typedef struct +{ + gchar extract_type[32]; + gboolean as_layers; + gboolean use_registration; +} DecomposeVals; + + +/* Declare local functions + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 decompose (gint32 image_id, + gint32 drawable_ID, + const gchar *extract_type, + gint32 *image_ID_dst, + gint32 *num_layers, + gint32 *layer_ID_dst); +static gint32 create_new_image (const gchar *filename, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type, + GimpPrecision precision, + gdouble xres, + gdouble yres, + gint32 *layer_ID); +static gint32 create_new_layer (gint32 image_ID, + gint position, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type); +static void transfer_registration_color (GeglBuffer *src, + GeglBuffer **dst, + gint count); +static void cpn_affine_transform_clamp (GeglBuffer *buffer, + gdouble min, + gdouble max, + gboolean clamp); +static void copy_n_components (GeglBuffer *src, + GeglBuffer **dst, + Extract ext); +static void copy_one_component (GeglBuffer *src, + GeglBuffer *dst, + const char *model, + const Component component, + gboolean clamp); +static gboolean decompose_dialog (void); +static gchar * generate_filename (guint32 image_ID, + guint colorspace, + guint channel); + + +#define CPN_RGBA_R { "R", N_("red"), 0.0, 1.0, FALSE } +#define CPN_RGBA_G { "G", N_("green"), 0.0, 1.0, FALSE } +#define CPN_RGBA_B { "B", N_("blue"), 0.0, 1.0, FALSE } +#define CPN_RGBA_A { "A", N_("alpha"), 0.0, 1.0, TRUE } + +#define CPN_HSV_H { "hue", N_("hue"), 0.0, 1.0, TRUE } +#define CPN_HSV_S { "saturation", N_("saturation"), 0.0, 1.0, TRUE } +#define CPN_HSV_V { "value", N_("value"), 0.0, 1.0, TRUE } + +#define CPN_HSL_H { "hue", N_("hue"), 0.0, 1.0, TRUE } +#define CPN_HSL_S { "saturation", N_("saturation"), 0.0, 1.0, TRUE } +#define CPN_HSL_L { "lightness", N_("lightness"), 0.0, 1.0, TRUE } + +#define CPN_CMYK_C { "Cyan", N_("cyan"), 0.0, 1.0, TRUE } +#define CPN_CMYK_M { "Magenta", N_("magenta"), 0.0, 1.0, TRUE } +#define CPN_CMYK_Y { "Yellow", N_("yellow"), 0.0, 1.0, TRUE } +#define CPN_CMYK_K { "Key", N_("black"), 0.0, 1.0, TRUE } + +#define CPN_LAB_L { "CIE L", N_("L"), 0.0, 100.0, TRUE } +#define CPN_LAB_A { "CIE a", N_("A"), -127.5, 127.5, TRUE } +#define CPN_LAB_B { "CIE b", N_("B"), -127.5, 127.5, TRUE } + +#define CPN_LCH_L { "CIE L", N_("L"), 0.0, 100.0, TRUE } +#define CPN_LCH_C { "CIE C(ab)", N_("C"), 0.0, 200.0, TRUE } +#define CPN_LCH_H { "CIE H(ab)", N_("H"), 0.0, 360.0, TRUE } + +#define CPN_YCBCR_Y { "Y'", N_("luma-y470"), 0.0, 1.0, TRUE } +#define CPN_YCBCR_CB { "Cb", N_("blueness-cb470"), -0.5, 0.5, TRUE } +#define CPN_YCBCR_CR { "Cr", N_("redness-cr470"), -0.5, 0.5, TRUE } + +#define CPN_YCBCR709_Y { "Y'", N_("luma-y709"), 0.0, 1.0, TRUE } +#define CPN_YCBCR709_CB { "Cb", N_("blueness-cb709"), -0.5, 0.5, TRUE } +#define CPN_YCBCR709_CR { "Cr", N_("redness-cr709"), -0.5, 0.5, TRUE } + + +static const Extract extract[] = +{ + { N_("RGB"), "RGB", TRUE, 3, FALSE, { CPN_RGBA_R, CPN_RGBA_G, CPN_RGBA_B } }, + { N_("RGBA"), "RGBA", TRUE, 4, FALSE, { CPN_RGBA_R, CPN_RGBA_G, CPN_RGBA_B, CPN_RGBA_A } }, + + { N_("Red"), "RGB", FALSE, 1, FALSE, { CPN_RGBA_R } }, + { N_("Green"), "RGB", FALSE, 1, FALSE, { CPN_RGBA_G } }, + { N_("Blue"), "RGB", FALSE, 1, FALSE, { CPN_RGBA_B } }, + { N_("Alpha"), "RGBA", TRUE , 1, FALSE, { CPN_RGBA_A } }, + + { N_("HSV"), "HSV", TRUE, 3, FALSE, { CPN_HSV_H, CPN_HSV_S, CPN_HSV_V } }, + { N_("Hue"), "HSV", FALSE, 1, FALSE, { CPN_HSV_H } }, + { N_("Saturation"), "HSV", FALSE, 1, FALSE, { CPN_HSV_S } }, + { N_("Value"), "HSV", FALSE, 1, FALSE, { CPN_HSV_V } }, + + { N_("HSL"), "HSL", TRUE, 3, FALSE, { CPN_HSL_H, CPN_HSL_S, CPN_HSL_L } }, + { N_("Hue (HSL)"), "HSL", FALSE, 1, FALSE, { CPN_HSL_H } }, + { N_("Saturation (HSL)"), "HSL", FALSE, 1, FALSE, { CPN_HSL_S } }, + { N_("Lightness"), "HSL", FALSE, 1, FALSE, { CPN_HSL_L } }, + + { N_("CMYK"), "CMYK", TRUE, 4, FALSE, { CPN_CMYK_C, CPN_CMYK_M, CPN_CMYK_Y, CPN_CMYK_K } }, + { N_("Cyan"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_C } }, + { N_("Magenta"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_M } }, + { N_("Yellow"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_Y } }, + { N_("Black"), "CMYK", FALSE, 1, FALSE, { CPN_CMYK_K } }, + + { N_("LAB"), "CIE Lab", TRUE, 3, FALSE, { CPN_LAB_L, CPN_LAB_A, CPN_LAB_B } }, + + { N_("LCH"), "CIE LCH(ab)", TRUE, 3, FALSE, { CPN_LCH_L, CPN_LCH_C, CPN_LCH_H } }, + + { N_("YCbCr_ITU_R470"), "Y'CbCr", TRUE, 3, FALSE, { CPN_YCBCR_Y, CPN_YCBCR_CB, CPN_YCBCR_CR} }, + { N_("YCbCr_ITU_R470_256"), "Y'CbCr", TRUE, 3, TRUE, { CPN_YCBCR_Y, CPN_YCBCR_CB, CPN_YCBCR_CR} }, + + { N_("YCbCr_ITU_R709"), "Y'CbCr709", TRUE, 3, FALSE, { CPN_YCBCR709_Y, CPN_YCBCR709_CB, CPN_YCBCR709_CR} }, + { N_("YCbCr_ITU_R709_256"), "Y'CbCr709", TRUE, 3, TRUE, { CPN_YCBCR709_Y, CPN_YCBCR709_CB, CPN_YCBCR709_CR} } +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static DecomposeVals decovals = +{ + "rgb", /* Decompose type */ + TRUE, /* Decompose to Layers */ + FALSE /* use registration color */ +}; + + +MAIN () + + +static void +query (void) +{ + static GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_STRING, "decompose-type", NULL }, + { GIMP_PDB_INT32, "layers-mode", "Create channels as layers in a single image" } + }; + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "new-image", "Output gray image" }, + { GIMP_PDB_IMAGE, "new-image", "Output gray image (N/A for single channel extract)" }, + { GIMP_PDB_IMAGE, "new-image", "Output gray image (N/A for single channel extract)" }, + { GIMP_PDB_IMAGE, "new-image", "Output gray image (N/A for single channel extract)" } + }; + + GString *type_desc; + gint i; + + type_desc = g_string_new ("What to decompose: "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, extract[0].type); + g_string_append_c (type_desc, '"'); + + for (i = 1; i < G_N_ELEMENTS (extract); i++) + { + g_string_append (type_desc, ", "); + g_string_append_c (type_desc, '"'); + g_string_append (type_desc, extract[i].type); + g_string_append_c (type_desc, '"'); + } + + args[3].description = type_desc->str; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Decompose an image into separate colorspace components"), + "This function creates new gray images with " + "different channel information in each of them", + "Peter Kirchgessner", + "Peter Kirchgessner", + "1997", + N_("_Decompose..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_install_procedure (PLUG_IN_PROC_REG, + N_("Decompose an image into separate colorspace components"), + "This function creates new gray images with " + "different channel information in each of them. " + "Pixels in the foreground color will appear black " + "in all output images. This can be used for " + "things like crop marks that have to show up on " + "all channels.", + "Peter Kirchgessner", + "Peter Kirchgessner, Clarence Risher", + "1997", + N_("_Decompose..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC_REG, "/Colors/Components"); + + g_string_free (type_desc, TRUE); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[MAX_EXTRACT_IMAGES + 1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gint32 num_images; + gint32 image_ID_extract[MAX_EXTRACT_IMAGES]; + gint32 layer_ID_extract[MAX_EXTRACT_IMAGES]; + gint j; + gint32 layer; + gint32 num_layers; + gint32 image_ID; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + image_ID = param[1].data.d_image; + layer = param[2].data.d_drawable; + + *nreturn_vals = MAX_EXTRACT_IMAGES + 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + for (j = 0; j < MAX_EXTRACT_IMAGES; j++) + { + values[j+1].type = GIMP_PDB_IMAGE; + values[j+1].data.d_int32 = -1; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &decovals); + + /* First acquire information with a dialog */ + if (! decompose_dialog ()) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 4 && nparams != 5 && nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + strncpy (decovals.extract_type, param[3].data.d_string, + sizeof (decovals.extract_type)); + decovals.extract_type[sizeof (decovals.extract_type) - 1] = '\0'; + + decovals.as_layers = nparams > 4 ? param[4].data.d_int32 : FALSE; + decovals.use_registration = (strcmp (name, PLUG_IN_PROC_REG) == 0); + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &decovals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_progress_init (_("Decomposing")); + + num_images = decompose (image_ID, layer, + decovals.extract_type, + image_ID_extract, + &num_layers, + layer_ID_extract); + + if (num_images <= 0) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + /* create decompose-data parasite */ + GString *data = g_string_new (""); + + g_string_printf (data, "source=%d type=%s ", + layer, decovals.extract_type); + + for (j = 0; j < num_layers; j++) + g_string_append_printf (data, "%d ", layer_ID_extract[j]); + + for (j = 0; j < num_images; j++) + { + GimpParasite *parasite; + + values[j+1].data.d_int32 = image_ID_extract[j]; + + gimp_image_undo_enable (image_ID_extract[j]); + gimp_image_clean_all (image_ID_extract[j]); + + parasite = gimp_parasite_new ("decompose-data", + 0, data->len + 1, data->str); + gimp_image_attach_parasite (image_ID_extract[j], parasite); + gimp_parasite_free (parasite); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_display_new (image_ID_extract[j]); + } + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &decovals, sizeof (DecomposeVals)); + } + + gimp_progress_end (); + } + + values[0].data.d_status = status; +} + + +/* Decompose an image. It returns the number of new (gray) images. + * The image IDs for the new images are returned in image_ID_dst. + * On failure, -1 is returned. + */ +static gint32 +decompose (gint32 image_ID, + gint32 drawable_ID, + const gchar *extract_type, + gint32 *image_ID_dst, + gint32 *nlayers, + gint32 *layer_ID_dst) +{ + const gchar *layername; + gint j, extract_idx; + gint height, width, num_layers; + GeglBuffer *src_buffer; + GeglBuffer *dst_buffer[MAX_EXTRACT_IMAGES]; + GimpPrecision precision; + gboolean requirements = FALSE; + gboolean decomp_has_alpha = FALSE; + + extract_idx = -1; /* Search extract type */ + for (j = 0; j < G_N_ELEMENTS (extract); j++) + { + if (g_ascii_strcasecmp (extract_type, extract[j].type) == 0) + { + extract_idx = j; + break; + } + } + if (extract_idx < 0) + return -1; + + num_layers = extract[extract_idx].num_images; + + /* Sanity checks */ + src_buffer = gimp_drawable_get_buffer (drawable_ID); + precision = gimp_image_get_precision (image_ID); + + for (j = 0; j < num_layers; j++) + { + /* FIXME: Not 100% reliable */ + decomp_has_alpha |= ! g_strcmp0 ("alpha", extract[extract_idx].component[j].babl_name); + decomp_has_alpha |= ! g_strcmp0 ("A", extract[extract_idx].component[j].babl_name); + } + + requirements |= (gimp_drawable_is_rgb (drawable_ID)); + requirements |= (gimp_drawable_is_indexed (drawable_ID)); + requirements |= (gimp_drawable_is_gray (drawable_ID) + && gimp_drawable_has_alpha (drawable_ID) + && (num_layers <= 2) + && decomp_has_alpha); + requirements &= (!decomp_has_alpha || gimp_drawable_has_alpha (drawable_ID)); + + if (!requirements) + { + g_message (_("Image not suitable for this decomposition")); + return -1; + } + + width = gegl_buffer_get_width (src_buffer); + height = gegl_buffer_get_height (src_buffer); + + /* Create all new gray images */ + for (j = 0; j < num_layers; j++) + { + gchar *filename; + gdouble xres, yres; + + filename = generate_filename (image_ID, extract_idx, j); + gimp_image_get_resolution (image_ID, &xres, &yres); + + if (decovals.as_layers) + { + layername = gettext (extract[extract_idx].component[j].channel_name); + + if (j == 0) + image_ID_dst[j] = create_new_image (filename, layername, + width, height, GIMP_GRAY, precision, + xres, yres, + layer_ID_dst + j); + else + layer_ID_dst[j] = create_new_layer (image_ID_dst[0], j, layername, + width, height, GIMP_GRAY); + } + else + { + image_ID_dst[j] = create_new_image (filename, NULL, + width, height, GIMP_GRAY, precision, + xres, yres, + layer_ID_dst + j); + } + + g_free (filename); + + dst_buffer[j] = gimp_drawable_get_buffer (layer_ID_dst[j]); + } + + copy_n_components (src_buffer, dst_buffer, + extract[extract_idx]); + + if (decovals.use_registration) + transfer_registration_color (src_buffer, dst_buffer, num_layers); + + gimp_progress_update (1.0); + + g_object_unref (src_buffer); + + for (j = 0; j < num_layers; j++) + { + g_object_unref (dst_buffer[j]); + } + + *nlayers = num_layers; + + return (decovals.as_layers ? 1 : num_layers); +} + + +/* Create an image. Returns layer_ID and image_ID */ +static gint32 +create_new_image (const gchar *filename, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type, + GimpPrecision precision, + gdouble xres, + gdouble yres, + gint32 *layer_ID) +{ + gint32 image_ID; + + image_ID = gimp_image_new_with_precision (width, height, type, precision); + + gimp_image_undo_disable (image_ID); + gimp_image_set_filename (image_ID, filename); + gimp_image_set_resolution (image_ID, xres, yres); + + *layer_ID = create_new_layer (image_ID, 0, + layername, width, height, type); + + return image_ID; +} + + +static gint32 +create_new_layer (gint32 image_ID, + gint position, + const gchar *layername, + guint width, + guint height, + GimpImageBaseType type) +{ + gint32 layer_ID; + GimpImageType gdtype = GIMP_RGB_IMAGE; + + switch (type) + { + case GIMP_RGB: + gdtype = GIMP_RGB_IMAGE; + break; + case GIMP_GRAY: + gdtype = GIMP_GRAY_IMAGE; + break; + case GIMP_INDEXED: + gdtype = GIMP_INDEXED_IMAGE; + break; + } + + if (! layername) + layername = _("Background"); + + layer_ID = gimp_layer_new (image_ID, layername, width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, position); + + return layer_ID; +} + +/* Registration Color function */ + +static void +transfer_registration_color (GeglBuffer *src, + GeglBuffer **dst, + gint count) +{ + GimpRGB color, test; + GeglBufferIterator *gi; + const Babl *src_format; + const Babl *dst_format; + gint src_bpp; + gint dst_bpp; + gint i; + gdouble white; + + gimp_context_get_foreground (&color); + white = 1.0; + + src_format = gegl_buffer_get_format (src); + src_bpp = babl_format_get_bytes_per_pixel (src_format); + + dst_format = gegl_buffer_get_format (dst[0]); + dst_bpp = babl_format_get_bytes_per_pixel (dst_format); + + gi = gegl_buffer_iterator_new (src, NULL, 0, NULL, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 10); + + for (i = 0; i < count; i++) + { + gegl_buffer_iterator_add (gi, dst[i], NULL, 0, NULL, + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE); + } + + while (gegl_buffer_iterator_next (gi)) + { + gpointer src_data; + gpointer dst_data[MAX_EXTRACT_IMAGES]; + gint j, k; + + src_data = gi->items[0].data; + for (j = 0; j < count; j++) + dst_data[j] = gi->items[j + 1].data; + + for (k = 0; k < gi->length; k++) + { + gulong pos = k * src_bpp; + + gimp_rgba_set_pixel (&test, src_format, ((guchar *)src_data) + pos); + + if (gimp_rgb_distance (&test, &color) < 1e-6) + { + for (j = 0; j < count; j++) + { + gpointer data = dst_data[j]; + + babl_process (babl_fish (babl_format ("Y double"), dst_format), + &white, (guchar *)data + (k * dst_bpp), 1); + } + } + } + } +} + +static void +cpn_affine_transform_clamp (GeglBuffer *buffer, + gdouble min, + gdouble max, + gboolean clamp) +{ + GeglBufferIterator *gi; + gdouble scale = 1.0 / (max - min); + gdouble offset = - min; + + /* We want to scale values linearly, regardless of the format of the buffer */ + gegl_buffer_set_format (buffer, babl_format ("Y double")); + + gi = gegl_buffer_iterator_new (buffer, NULL, 0, NULL, + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (gi)) + { + guint k; + double *data; + + data = (double*) gi->items[0].data; + + if (clamp) + { + for (k = 0; k < gi->length; k++) + { + data[k] = CLAMP ((data[k] + offset) * scale, 0.0, 1.0); + } + } + else + { + for (k = 0; k < gi->length; k++) + { + data[k] = (data[k] + offset) * scale; + } + } + } +} + +static void +copy_n_components (GeglBuffer *src, + GeglBuffer **dst, + Extract ext) +{ + gint i; + + for (i = 0; i < ext.num_images; i++) + { + gimp_progress_update ((gdouble) i / (gdouble) ext.num_images); + + copy_one_component (src, dst[i], ext.model, ext.component[i], ext.clamp); + } +} + +static void +copy_one_component (GeglBuffer *src, + GeglBuffer *dst, + const gchar *model, + const Component component, + gboolean clamp) +{ + const Babl *component_format; + const Babl *dst_format; + GeglBuffer *temp; + const GeglRectangle *extent; + + /* We are working in linear double precision */ + component_format = babl_format_new (babl_model (model), + babl_type ("double"), + babl_component (component.babl_name), + NULL); + + /* We need to enforce linearity here + * If the output is "Y'", the output of temp is already ok + * If the output is "Y" , it will enforce gamma-decoding. + * A bit tricky and suboptimal... + */ + if (component.perceptual_channel) + dst_format = babl_format ("Y' double"); + else + dst_format = babl_format ("Y double"); + + extent = gegl_buffer_get_extent (src); + temp = gegl_buffer_new (extent, dst_format); + + /* we want to copy the component as is */ + gegl_buffer_set_format (temp, component_format); + gegl_buffer_copy (src, NULL, GEGL_ABYSS_NONE, temp, NULL); + + if (component.range_min != 0.0 || + component.range_max != 1.0 || + clamp) + { + cpn_affine_transform_clamp (temp, + component.range_min, component.range_max, + clamp); + } + + /* This is our new "Y(') double" component buffer */ + gegl_buffer_set_format (temp, NULL); + + /* Now we let babl convert it back to the format that dst needs */ + gegl_buffer_copy (temp, NULL, GEGL_ABYSS_NONE, dst, NULL); + + g_object_unref (temp); +} + +static gboolean +decompose_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *toggle; + gint j; + gint extract_idx; + gboolean run; + + extract_idx = 0; + for (j = 0; j < G_N_ELEMENTS (extract); j++) + { + if (extract[j].dialog && + g_ascii_strcasecmp (decovals.extract_type, extract[j].type) == 0) + { + extract_idx = j; + break; + } + } + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Decompose"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + frame = gimp_frame_new (_("Extract Channels")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("Color _model:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + for (j = 0; j < G_N_ELEMENTS (extract); j++) + { + if (extract[j].dialog) + { + gchar *label = g_strdup (gettext (extract[j].type)); + gchar *l; + + for (l = label; *l; l++) + if (*l == '-' || *l == '_') + *l = ' '; + + gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (combo), + GIMP_INT_STORE_LABEL, label, + GIMP_INT_STORE_VALUE, j, + -1); + g_free (label); + } + } + + gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + extract_idx, + G_CALLBACK (gimp_int_combo_box_get_active), + &extract_idx); + + toggle = gtk_check_button_new_with_mnemonic (_("_Decompose to layers")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + decovals.as_layers); + gtk_box_pack_start (GTK_BOX (main_vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &decovals.as_layers); + + toggle = + gtk_check_button_new_with_mnemonic (_("_Foreground as registration color")); + gimp_help_set_help_data (toggle, _("Pixels in the foreground color will " + "appear black in all output images. " + "This can be used for things like crop " + "marks that have to show up on all " + "channels."), PLUG_IN_PROC); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + decovals.use_registration); + gtk_box_pack_start (GTK_BOX (main_vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &decovals.use_registration); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + if (run) + strncpy (decovals.extract_type, extract[extract_idx].type, + sizeof decovals.extract_type - 1); + + return run; +} + +/* Build a filename like -. */ +gchar * +generate_filename (guint32 image_ID, + guint colorspace, + guint channel) +{ + /* Build a filename like -. */ + gchar *fname; + gchar *filename; + gchar *extension; + + fname = gimp_image_get_filename (image_ID); + + if (fname) + { + extension = fname + strlen (fname) - 1; + + while (extension >= fname) + { + if (*extension == '.') + break; + extension--; + } + + if (extension >= fname) + { + *(extension++) = '\0'; + + if (decovals.as_layers) + filename = g_strdup_printf ("%s-%s.%s", fname, + gettext (extract[colorspace].type), + extension); + else + filename = g_strdup_printf ("%s-%s.%s", fname, + gettext (extract[colorspace].component[channel].channel_name), + extension); + } + else + { + if (decovals.as_layers) + filename = g_strdup_printf ("%s-%s", fname, + gettext (extract[colorspace].type)); + else + filename = g_strdup_printf ("%s-%s", fname, + gettext (extract[colorspace].component[channel].channel_name)); + } + } + else + { + filename = g_strdup (gettext (extract[colorspace].component[channel].channel_name)); + } + + g_free (fname); + + return filename; +} diff --git a/plug-ins/common/depth-merge.c b/plug-ins/common/depth-merge.c new file mode 100644 index 0000000..ff65693 --- /dev/null +++ b/plug-ins/common/depth-merge.c @@ -0,0 +1,1050 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Depth Merge -- Combine two image layers via corresponding depth maps + * Copyright (C) 1997, 1998 Sean Cier (scier@PostHorizon.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Version 1.0.0: (14 August 1998) + * Math optimizations, miscellaneous speedups + * + * Version 0.1: (6 July 1997) + * Initial Release + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define DEBUG + +#ifndef LERP +#define LERP(frac,a,b) ((frac)*(b) + (1-(frac))*(a)) +#endif + +#define MUL255(i) ((i)*256 - (i)) +#define DIV255(i) (((i) + (i)/256 + 1) / 256) + +#define PLUG_IN_PROC "plug-in-depth-merge" +#define PLUG_IN_VERSION "August 1998" +#define PLUG_IN_BINARY "depth-merge" +#define PLUG_IN_ROLE "gimp-depth-merge" + +#define PREVIEW_SIZE 256 + +/* ----- DepthMerge ----- */ + +struct _DepthMerge; + +typedef struct _DepthMergeInterface +{ + gboolean active; + + GtkWidget *dialog; + + GtkWidget *preview; + gint previewWidth; + gint previewHeight; + + guchar *previewSource1; + guchar *previewSource2; + guchar *previewDepthMap1; + guchar *previewDepthMap2; +} DepthMergeInterface; + +typedef struct _DepthMergeParams +{ + gint32 result; + gint32 source1; + gint32 source2; + gint32 depthMap1; + gint32 depthMap2; + gfloat overlap; + gfloat offset; + gfloat scale1; + gfloat scale2; +} DepthMergeParams; + +typedef struct _DepthMerge +{ + DepthMergeInterface *interface; + DepthMergeParams params; + + gint32 resultDrawable_id; + gint32 source1Drawable_id; + gint32 source2Drawable_id; + gint32 depthMap1Drawable_id; + gint32 depthMap2Drawable_id; + gint selectionX; + gint selectionY; + gint selectionWidth; + gint selectionHeight; + gint resultHasAlpha; +} DepthMerge; + +static void DepthMerge_initParams (DepthMerge *dm); +static gboolean DepthMerge_construct (DepthMerge *dm); +static void DepthMerge_destroy (DepthMerge *dm); +static gint32 DepthMerge_execute (DepthMerge *dm); +static void DepthMerge_executeRegion (DepthMerge *dm, + guchar *source1Row, + guchar *source2Row, + guchar *depthMap1Row, + guchar *depthMap2Row, + guchar *resultRow, + gint length); +static gboolean DepthMerge_dialog (DepthMerge *dm); +static void DepthMerge_buildPreviewSourceImage (DepthMerge *dm); +static void DepthMerge_updatePreview (DepthMerge *dm); + + +static gboolean dm_constraint (gint32 imageId, + gint32 drawableId, + gpointer data); + +static void dialogSource1ChangedCallback (GtkWidget *widget, DepthMerge *dm); +static void dialogSource2ChangedCallback (GtkWidget *widget, DepthMerge *dm); +static void dialogDepthMap1ChangedCallback (GtkWidget *widget, DepthMerge *dm); +static void dialogDepthMap2ChangedCallback (GtkWidget *widget, DepthMerge *dm); + +static void dialogValueScaleUpdateCallback (GtkAdjustment *adjustment, + gpointer data); + +static void util_fillReducedBuffer (guchar *dest, + const Babl *dest_format, + gint destWidth, + gint destHeight, + gint32 sourceDrawable_id, + gint x0, + gint y0, + gint sourceWidth, + gint sourceHeight); + + +/* ----- plug-in entry points ----- */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "result", "Result" }, + { GIMP_PDB_DRAWABLE, "source1", "Source 1" }, + { GIMP_PDB_DRAWABLE, "source2", "Source 2" }, + { GIMP_PDB_DRAWABLE, "depthMap1", "Depth map 1" }, + { GIMP_PDB_DRAWABLE, "depthMap2", "Depth map 2" }, + { GIMP_PDB_FLOAT, "overlap", "Overlap" }, + { GIMP_PDB_FLOAT, "offset", "Depth relative offset" }, + { GIMP_PDB_FLOAT, "scale1", "Depth relative scale 1" }, + { GIMP_PDB_FLOAT, "scale2", "Depth relative scale 2" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Combine two images using depth maps (z-buffers)"), + "Taking as input two full-color, full-alpha " + "images and two corresponding grayscale depth " + "maps, this plug-in combines the images based " + "on which is closer (has a lower depth map value) " + "at each point.", + "Sean Cier", + "Sean Cier", + PLUG_IN_VERSION, + N_("_Depth Merge..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Combine"); +} + +static void +run (const gchar *name, + gint numParams, + const GimpParam *param, + gint *numReturnVals, + GimpParam **returnVals) +{ + static GimpParam values[1]; + GimpRunMode runMode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + DepthMerge dm; + + INIT_I18N (); + gegl_init (NULL, NULL); + + runMode = param[0].data.d_int32; + + *numReturnVals = 1; + *returnVals = values; + + switch (runMode) + { + case GIMP_RUN_INTERACTIVE: + DepthMerge_initParams (&dm); + gimp_get_data (PLUG_IN_PROC, &(dm.params)); + dm.params.result = param[2].data.d_drawable; + if (!DepthMerge_construct (&dm)) + return; + + if (!DepthMerge_dialog (&dm)) + { + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_SUCCESS; + return; + } + break; + + case GIMP_RUN_NONINTERACTIVE: + DepthMerge_initParams (&dm); + if (numParams != 11) + status = GIMP_PDB_CALLING_ERROR; + else + { + dm.params.result = param[ 2].data.d_drawable; + dm.params.source1 = param[ 3].data.d_drawable; + dm.params.source2 = param[ 4].data.d_drawable; + dm.params.depthMap1 = param[ 5].data.d_drawable; + dm.params.depthMap2 = param[ 6].data.d_drawable; + dm.params.overlap = param[ 7].data.d_float; + dm.params.offset = param[ 8].data.d_float; + dm.params.scale1 = param[ 9].data.d_float; + dm.params.scale2 = param[10].data.d_float; + } + if (!DepthMerge_construct (&dm)) + return; + break; + + case GIMP_RUN_WITH_LAST_VALS: + DepthMerge_initParams (&dm); + gimp_get_data (PLUG_IN_PROC, &(dm.params)); + if (!DepthMerge_construct (&dm)) + return; + break; + + default: + status = GIMP_PDB_CALLING_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (!DepthMerge_execute (&dm)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + if (runMode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + if (runMode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, + &(dm.params), sizeof (DepthMergeParams)); + } + } + + DepthMerge_destroy (&dm); + + values[0].data.d_status = status; +} + +/* ----- DepthMerge ----- */ + +static void +DepthMerge_initParams (DepthMerge *dm) +{ + dm->params.result = -1; + dm->params.source1 = -1; + dm->params.source2 = -1; + dm->params.depthMap1 = -1; + dm->params.depthMap2 = -1; + dm->params.overlap = 0; + dm->params.offset = 0; + dm->params.scale1 = 1; + dm->params.scale2 = 1; +} + +static gboolean +DepthMerge_construct (DepthMerge *dm) +{ + dm->interface = NULL; + + dm->resultDrawable_id = dm->params.result; + + if (! gimp_drawable_mask_intersect (dm->resultDrawable_id, + &(dm->selectionX), &(dm->selectionY), + &(dm->selectionWidth), + &(dm->selectionHeight))) + { + return FALSE; + } + + dm->resultHasAlpha = gimp_drawable_has_alpha (dm->resultDrawable_id); + + dm->source1Drawable_id = dm->params.source1; + dm->source2Drawable_id = dm->params.source2; + dm->depthMap1Drawable_id = dm->params.depthMap1; + dm->depthMap2Drawable_id = dm->params.depthMap2; + + dm->params.overlap = CLAMP (dm->params.overlap, 0, 2); + dm->params.offset = CLAMP (dm->params.offset, -1, 1); + dm->params.scale1 = CLAMP (dm->params.scale1, -1, 1); + dm->params.scale2 = CLAMP (dm->params.scale2, -1, 1); + + return TRUE; +} + +static void +DepthMerge_destroy (DepthMerge *dm) +{ + if (dm->interface != NULL) + { + g_free (dm->interface->previewSource1); + g_free (dm->interface->previewSource2); + g_free (dm->interface->previewDepthMap1); + g_free (dm->interface->previewDepthMap2); + g_free (dm->interface); + } +} + +static gint32 +DepthMerge_execute (DepthMerge *dm) +{ + int x, y; + GeglBuffer *source1_buffer = NULL; + GeglBuffer *source2_buffer = NULL; + GeglBuffer *depthMap1_buffer = NULL; + GeglBuffer *depthMap2_buffer = NULL; + GeglBuffer *result_buffer; + guchar *source1Row, *source2Row; + guchar *depthMap1Row, *depthMap2Row; + guchar *resultRow; + guchar *tempRow; + + gimp_progress_init (_("Depth-merging")); + + resultRow = g_new (guchar, dm->selectionWidth * 4); + source1Row = g_new (guchar, dm->selectionWidth * 4); + source2Row = g_new (guchar, dm->selectionWidth * 4); + depthMap1Row = g_new (guchar, dm->selectionWidth ); + depthMap2Row = g_new (guchar, dm->selectionWidth ); + tempRow = g_new (guchar, dm->selectionWidth * 4); + + if (dm->source1Drawable_id > 0) + { + source1_buffer = gimp_drawable_get_buffer (dm->source1Drawable_id); + } + else + { + for (x = 0; x < dm->selectionWidth; x++) + { + source1Row[4 * x ] = 0; + source1Row[4 * x + 1] = 0; + source1Row[4 * x + 2] = 0; + source1Row[4 * x + 3] = 255; + } + } + + if (dm->source2Drawable_id > 0) + { + source2_buffer = gimp_drawable_get_buffer (dm->source2Drawable_id); + } + else + { + for (x = 0; x < dm->selectionWidth; x++) + { + source2Row[4 * x ] = 0; + source2Row[4 * x + 1] = 0; + source2Row[4 * x + 2] = 0; + source2Row[4 * x + 3] = 255; + } + } + + if (dm->depthMap1Drawable_id > 0) + { + depthMap1_buffer = gimp_drawable_get_buffer (dm->depthMap1Drawable_id); + } + else + { + for (x = 0; x < dm->selectionWidth; x++) + { + depthMap1Row[x] = 0; + } + } + + if (dm->depthMap2Drawable_id > 0) + { + depthMap2_buffer = gimp_drawable_get_buffer (dm->depthMap2Drawable_id); + } + else + { + for (x = 0; x < dm->selectionWidth; x++) + { + depthMap2Row[x] = 0; + } + } + + result_buffer = gimp_drawable_get_shadow_buffer (dm->resultDrawable_id); + + for (y = dm->selectionY; y < (dm->selectionY + dm->selectionHeight); y++) + { + if (dm->source1Drawable_id > 0) + { + gegl_buffer_get (source1_buffer, + GEGL_RECTANGLE (dm->selectionX, y, + dm->selectionWidth, 1), 1.0, + babl_format ("R'G'B'A u8"), source1Row, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + if (dm->source2Drawable_id > 0) + { + gegl_buffer_get (source2_buffer, + GEGL_RECTANGLE (dm->selectionX, y, + dm->selectionWidth, 1), 1.0, + babl_format ("R'G'B'A u8"), source2Row, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + if (dm->depthMap1Drawable_id > 0) + { + gegl_buffer_get (depthMap1_buffer, + GEGL_RECTANGLE (dm->selectionX, y, + dm->selectionWidth, 1), 1.0, + babl_format ("Y' u8"), depthMap1Row, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + if (dm->depthMap2Drawable_id > 0) + { + gegl_buffer_get (depthMap2_buffer, + GEGL_RECTANGLE (dm->selectionX, y, + dm->selectionWidth, 1), 1.0, + babl_format ("Y' u8"), depthMap2Row, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + DepthMerge_executeRegion (dm, + source1Row, source2Row, depthMap1Row, depthMap2Row, + resultRow, + dm->selectionWidth); + + gegl_buffer_set (result_buffer, + GEGL_RECTANGLE (dm->selectionX, y, + dm->selectionWidth, 1), 0, + babl_format ("R'G'B'A u8"), resultRow, + GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double)(y-dm->selectionY) / + (double)(dm->selectionHeight - 1)); + } + + g_free (resultRow); + g_free (source1Row); + g_free (source2Row); + g_free (depthMap1Row); + g_free (depthMap2Row); + g_free (tempRow); + + gimp_progress_update (1.0); + + if (source1_buffer) + g_object_unref (source1_buffer); + + if (source2_buffer) + g_object_unref (source2_buffer); + + if (depthMap1_buffer) + g_object_unref (depthMap1_buffer); + + if (depthMap2_buffer) + g_object_unref (depthMap2_buffer); + + g_object_unref (result_buffer); + + gimp_drawable_merge_shadow (dm->resultDrawable_id, TRUE); + gimp_drawable_update (dm->resultDrawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + return TRUE; +} + +static void +DepthMerge_executeRegion (DepthMerge *dm, + guchar *source1Row, + guchar *source2Row, + guchar *depthMap1Row, + guchar *depthMap2Row, + guchar *resultRow, + gint length) +{ + gfloat scale1, scale2, offset255, invOverlap255; + gfloat frac, depth1, depth2; + gushort c1[4], c2[4]; + gushort cR1[4] = { 0, 0, 0, 0 }, cR2[4] = { 0, 0, 0, 0 }; + gushort cR[4], temp; + gint i, tempInt; + + invOverlap255 = 1.0 / (MAX (dm->params.overlap, 0.001) * 255); + offset255 = dm->params.offset * 255; + scale1 = dm->params.scale1; + scale2 = dm->params.scale2; + + for (i = 0; i < length; i++) + { + depth1 = (gfloat) depthMap1Row[i]; + depth2 = (gfloat) depthMap2Row[i]; + + frac = (depth2 * scale2 - (depth1 * scale1 + offset255)) * invOverlap255; + frac = 0.5 * (frac + 1.0); + frac = CLAMP(frac, 0.0, 1.0); + + /* c1 -> color corresponding to source1 */ + c1[0] = source1Row[4 * i ]; + c1[1] = source1Row[4 * i + 1]; + c1[2] = source1Row[4 * i + 2]; + c1[3] = source1Row[4 * i + 3]; + + /* c2 -> color corresponding to source2 */ + c2[0] = source2Row[4 * i ]; + c2[1] = source2Row[4 * i + 1]; + c2[2] = source2Row[4 * i + 2]; + c2[3] = source2Row[4 * i + 3]; + + if (frac != 0) + { + /* cR1 -> result if c1 is completely on top */ + cR1[0] = c1[3] * c1[0] + (255 - c1[3]) * c2[0]; + cR1[1] = c1[3] * c1[1] + (255 - c1[3]) * c2[1]; + cR1[2] = c1[3] * c1[2] + (255 - c1[3]) * c2[2]; + cR1[3] = MUL255 (c1[3]) + (255 - c1[3]) * c2[3]; + } + + if (frac != 1) + { + /* cR2 -> result if c2 is completely on top */ + cR2[0] = c2[3] * c2[0] + (255 - c2[3]) * c1[0]; + cR2[1] = c2[3] * c2[1] + (255 - c2[3]) * c1[1]; + cR2[2] = c2[3] * c2[2] + (255 - c2[3]) * c1[2]; + cR2[3] = MUL255 (c2[3]) + (255 - c2[3]) * c1[3]; + } + + if (frac == 1) + { + cR[0] = cR1[0]; + cR[1] = cR1[1]; + cR[2] = cR1[2]; + cR[3] = cR1[3]; + } + else if (frac == 0) + { + cR[0] = cR2[0]; + cR[1] = cR2[1]; + cR[2] = cR2[2]; + cR[3] = cR2[3]; + } + else + { + tempInt = LERP (frac, cR2[0], cR1[0]); + cR[0] = CLAMP (tempInt,0,255 * 255); + tempInt = LERP (frac, cR2[1], cR1[1]); + cR[1] = CLAMP (tempInt,0,255 * 255); + tempInt = LERP (frac, cR2[2], cR1[2]); + cR[2] = CLAMP (tempInt,0,255 * 255); + tempInt = LERP (frac, cR2[3], cR1[3]); + cR[3] = CLAMP (tempInt,0,255 * 255); + } + + temp = DIV255 (cR[0]); resultRow[4 * i ] = MIN (temp, 255); + temp = DIV255 (cR[1]); resultRow[4 * i + 1] = MIN (temp, 255); + temp = DIV255 (cR[2]); resultRow[4 * i + 2] = MIN (temp, 255); + temp = DIV255 (cR[3]); resultRow[4 * i + 3] = MIN (temp, 255); + } +} + +static gboolean +DepthMerge_dialog (DepthMerge *dm) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *combo; + GtkObject *adj; + gboolean run; + + dm->interface = g_new0 (DepthMergeInterface, 1); + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dm->interface->dialog = + dialog = gimp_dialog_new (_("Depth Merge"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + /* Preview */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + dm->interface->previewWidth = MIN (dm->selectionWidth, PREVIEW_SIZE); + dm->interface->previewHeight = MIN (dm->selectionHeight, PREVIEW_SIZE); + dm->interface->preview = gimp_preview_area_new (); + gtk_widget_set_size_request (dm->interface->preview, + dm->interface->previewWidth, + dm->interface->previewHeight); + gtk_container_add (GTK_CONTAINER (frame), dm->interface->preview); + gtk_widget_show (dm->interface->preview); + + DepthMerge_buildPreviewSourceImage (dm); + + /* Source and Depth Map selection */ + table = gtk_table_new (8, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 1, 12); + gtk_table_set_row_spacing (GTK_TABLE (table), 3, 12); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + label = gtk_label_new (_("Source 1:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + combo = gimp_drawable_combo_box_new (dm_constraint, dm); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.source1, + G_CALLBACK (dialogSource1ChangedCallback), + dm); + + gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + label = gtk_label_new(_("Depth map:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + combo = gimp_drawable_combo_box_new (dm_constraint, dm); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.depthMap1, + G_CALLBACK (dialogDepthMap1ChangedCallback), + dm); + + gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + label = gtk_label_new (_("Source 2:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + combo = gimp_drawable_combo_box_new (dm_constraint, dm); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.source2, + G_CALLBACK (dialogSource2ChangedCallback), + dm); + + gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 2, 3, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + label = gtk_label_new (_("Depth map:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + combo = gimp_drawable_combo_box_new (dm_constraint, dm); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dm->params.depthMap2, + G_CALLBACK (dialogDepthMap2ChangedCallback), + dm); + + gtk_table_attach (GTK_TABLE (table), combo, 1, 3, 3, 4, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + /* Numeric parameters */ + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 4, + _("O_verlap:"), 0, 6, + dm->params.overlap, 0, 2, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialogValueScaleUpdateCallback), + &(dm->params.overlap)); + g_object_set_data (G_OBJECT (adj), "dm", dm); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 5, + _("O_ffset:"), 0, 6, + dm->params.offset, -1, 1, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialogValueScaleUpdateCallback), + &(dm->params.offset)); + g_object_set_data (G_OBJECT (adj), "dm", dm); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 6, + _("Sc_ale 1:"), 0, 6, + dm->params.scale1, -1, 1, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialogValueScaleUpdateCallback), + &(dm->params.scale1)); + g_object_set_data (G_OBJECT (adj), "dm", dm); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 7, + _("Sca_le 2:"), 0, 6, + dm->params.scale2, -1, 1, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialogValueScaleUpdateCallback), + &(dm->params.scale2)); + g_object_set_data (G_OBJECT (adj), "dm", dm); + + dm->interface->active = TRUE; + + gtk_widget_show (dm->interface->dialog); + DepthMerge_updatePreview (dm); + + run = (gimp_dialog_run (GIMP_DIALOG (dm->interface->dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dm->interface->dialog); + dm->interface->dialog = NULL; + + return run; +} + +static void +DepthMerge_buildPreviewSourceImage (DepthMerge *dm) +{ + dm->interface->previewSource1 = + g_new (guchar, dm->interface->previewWidth * + dm->interface->previewHeight * 4); + util_fillReducedBuffer (dm->interface->previewSource1, + babl_format ("R'G'B'A u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->source1Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + dm->interface->previewSource2 = + g_new (guchar, dm->interface->previewWidth * + dm->interface->previewHeight * 4); + util_fillReducedBuffer (dm->interface->previewSource2, + babl_format ("R'G'B'A u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->source2Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + dm->interface->previewDepthMap1 = + g_new (guchar, dm->interface->previewWidth * + dm->interface->previewHeight * 1); + util_fillReducedBuffer (dm->interface->previewDepthMap1, + babl_format ("Y' u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->depthMap1Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + dm->interface->previewDepthMap2 = + g_new (guchar, dm->interface->previewWidth * + dm->interface->previewHeight * 1); + util_fillReducedBuffer (dm->interface->previewDepthMap2, + babl_format ("Y' u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->depthMap2Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); +} + +static void +DepthMerge_updatePreview (DepthMerge *dm) +{ + gint y; + guchar *source1Row, *source2Row; + guchar *depthMap1Row, *depthMap2Row; + guchar *resultRGBA; + + if (!dm->interface->active) + return; + + resultRGBA = g_new (guchar, 4 * dm->interface->previewWidth * + dm->interface->previewHeight); + + for (y = 0; y < dm->interface->previewHeight; y++) + { + source1Row = + &(dm->interface->previewSource1[ y * dm->interface->previewWidth * 4]); + source2Row = + &(dm->interface->previewSource2[ y * dm->interface->previewWidth * 4]); + depthMap1Row = + &(dm->interface->previewDepthMap1[y * dm->interface->previewWidth ]); + depthMap2Row = + &(dm->interface->previewDepthMap2[y * dm->interface->previewWidth ]); + + DepthMerge_executeRegion(dm, + source1Row, source2Row, depthMap1Row, depthMap2Row, + resultRGBA + 4 * y * dm->interface->previewWidth, + dm->interface->previewWidth); + } + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (dm->interface->preview), + 0, 0, + dm->interface->previewWidth, + dm->interface->previewHeight, + GIMP_RGBA_IMAGE, + resultRGBA, + dm->interface->previewWidth * 4); + g_free(resultRGBA); +} + +/* ----- Callbacks ----- */ + +static gboolean +dm_constraint (gint32 imageId, + gint32 drawableId, + gpointer data) +{ + DepthMerge *dm = (DepthMerge *)data; + + return ((drawableId == -1) || + ((gimp_drawable_width (drawableId) == + gimp_drawable_width (dm->params.result)) && + (gimp_drawable_height (drawableId) == + gimp_drawable_height (dm->params.result)) && + ((gimp_drawable_is_rgb (drawableId) && + (gimp_drawable_is_rgb (dm->params.result))) || + gimp_drawable_is_gray (drawableId)))); +} + +static void +dialogSource1ChangedCallback (GtkWidget *widget, + DepthMerge *dm) +{ + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), + &dm->params.source1); + + dm->source1Drawable_id = dm->params.source1; + + util_fillReducedBuffer (dm->interface->previewSource1, + babl_format ("R'G'B'A u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->source1Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + DepthMerge_updatePreview (dm); +} + +static void +dialogSource2ChangedCallback (GtkWidget *widget, + DepthMerge *dm) +{ + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), + &dm->params.source2); + + dm->source2Drawable_id = dm->params.source2; + + util_fillReducedBuffer (dm->interface->previewSource2, + babl_format ("R'G'B'A u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->source2Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + DepthMerge_updatePreview (dm); +} + +static void +dialogDepthMap1ChangedCallback (GtkWidget *widget, + DepthMerge *dm) +{ + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), + &dm->params.depthMap1); + + dm->depthMap1Drawable_id = dm->params.depthMap1; + + util_fillReducedBuffer (dm->interface->previewDepthMap1, + babl_format ("Y' u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->depthMap1Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + DepthMerge_updatePreview (dm); +} + +static void +dialogDepthMap2ChangedCallback (GtkWidget *widget, + DepthMerge *dm) +{ + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), + &dm->params.depthMap2); + + dm->depthMap2Drawable_id = dm->params.depthMap2;; + + util_fillReducedBuffer (dm->interface->previewDepthMap2, + babl_format ("Y' u8"), + dm->interface->previewWidth, + dm->interface->previewHeight, + dm->depthMap2Drawable_id, + dm->selectionX, dm->selectionY, + dm->selectionWidth, dm->selectionHeight); + + DepthMerge_updatePreview (dm); +} + +static void +dialogValueScaleUpdateCallback (GtkAdjustment *adjustment, + gpointer data) +{ + DepthMerge *dm = g_object_get_data (G_OBJECT (adjustment), "dm"); + + gimp_float_adjustment_update (adjustment, data); + + DepthMerge_updatePreview (dm); +} + +/* ----- Utility routines ----- */ + +static void +util_fillReducedBuffer (guchar *dest, + const Babl *dest_format, + gint destWidth, + gint destHeight, + gint32 sourceDrawable_id, + gint x0, + gint y0, + gint sourceWidth, + gint sourceHeight) +{ + GeglBuffer *buffer; + guchar *sourceBuffer, *reducedRowBuffer; + guchar *sourceBufferPos, *reducedRowBufferPos; + guchar *sourceBufferRow; + gint x, y, i, yPrime; + gint destBPP; + gint *sourceRowOffsetLookup; + + destBPP = babl_format_get_bytes_per_pixel (dest_format); + + if ((sourceDrawable_id < 1) || (sourceWidth == 0) || (sourceHeight == 0)) + { + for (x = 0; x < destWidth * destHeight * destBPP; x++) + dest[x] = 0; + + return; + } + + sourceBuffer = g_new (guchar, sourceWidth * sourceHeight * destBPP); + reducedRowBuffer = g_new (guchar, destWidth * destBPP); + sourceRowOffsetLookup = g_new (int, destWidth); + + buffer = gimp_drawable_get_buffer (sourceDrawable_id); + + for (x = 0; x < destWidth; x++) + sourceRowOffsetLookup[x] = (x * (sourceWidth - 1) / (destWidth - 1)) * destBPP; + + gegl_buffer_get (buffer, + GEGL_RECTANGLE (x0, y0, sourceWidth, sourceHeight), 1.0, + dest_format, sourceBuffer, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (y = 0; y < destHeight; y++) + { + yPrime = y * (sourceHeight - 1) / (destHeight - 1); + sourceBufferRow = &(sourceBuffer[yPrime * sourceWidth * destBPP]); + reducedRowBufferPos = reducedRowBuffer; + + for (x = 0; x < destWidth; x++) + { + sourceBufferPos = sourceBufferRow + sourceRowOffsetLookup[x]; + for (i = 0; i < destBPP; i++) + reducedRowBufferPos[i] = sourceBufferPos[i]; + reducedRowBufferPos += destBPP; + } + + memcpy (&(dest[y * destWidth * destBPP]), reducedRowBuffer, + destWidth * destBPP); + } + + g_object_unref (buffer); + + g_free (sourceBuffer); + g_free (reducedRowBuffer); + g_free (sourceRowOffsetLookup); +} diff --git a/plug-ins/common/despeckle.c b/plug-ins/common/despeckle.c new file mode 100644 index 0000000..8113343 --- /dev/null +++ b/plug-ins/common/despeckle.c @@ -0,0 +1,901 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Despeckle (adaptive median) filter + * + * Copyright 1997-1998 Michael Sweet (mike@easysw.com) + * optimized in 2010 by Przemyslaw Zych (kermidt.zed@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* + * Constants... + */ + +#define PLUG_IN_PROC "plug-in-despeckle" +#define PLUG_IN_BINARY "despeckle" +#define PLUG_IN_ROLE "gimp-despeckle" +#define PLUG_IN_VERSION "May 2010" +#define SCALE_WIDTH 100 +#define ENTRY_WIDTH 3 +#define MAX_RADIUS 30 + +#define FILTER_ADAPTIVE 0x01 +#define FILTER_RECURSIVE 0x02 + +#define despeckle_radius (despeckle_vals[0]) /* diameter of filter */ +#define filter_type (despeckle_vals[1]) /* filter type */ +#define black_level (despeckle_vals[2]) /* Black level */ +#define white_level (despeckle_vals[3]) /* White level */ + +/* List that stores pixels falling in to the same luma bucket */ +#define MAX_LIST_ELEMS SQR(2 * MAX_RADIUS + 1) + +typedef struct +{ + const guchar *elems[MAX_LIST_ELEMS]; + gint start; + gint count; +} PixelsList; + +typedef struct +{ + gint elems[256]; /* Number of pixels that fall into each luma bucket */ + PixelsList origs[256]; /* Original pixels */ + gint xmin; + gint ymin; + gint xmax; + gint ymax; /* Source rect */ +} DespeckleHistogram; + +/* Number of pixels in actual histogram falling into each category */ +static gint hist0; /* Less than min threshold */ +static gint hist255; /* More than max threshold */ +static gint histrest; /* From min to max */ + +static DespeckleHistogram histogram; + + +/* + * Local functions... + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void despeckle (void); +static void despeckle_median (guchar *src, + guchar *dst, + gint width, + gint height, + gint bpp, + gint radius, + gboolean preview); + +static gboolean despeckle_dialog (void); + +static void dialog_adaptive_callback (GtkWidget *widget, + gpointer data); +static void dialog_recursive_callback (GtkWidget *widget, + gpointer data); + +static void preview_update (GtkWidget *preview); + +/* + * Globals... + */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init */ + NULL, /* quit */ + query, /* query */ + run /* run */ +}; + +static GtkWidget *preview; /* Preview widget */ +static gint32 drawable_ID = -1; /* Current drawable */ + + +static gint despeckle_vals[4] = +{ + 3, /* Default value for the diameter */ + FILTER_ADAPTIVE, /* Default value for the filter type */ + 7, /* Default value for the black level */ + 248 /* Default value for the white level */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "radius", "Filter box radius (default = 3)" }, + { GIMP_PDB_INT32, "type", "Filter type { MEDIAN (0), ADAPTIVE (1), RECURSIVE-MEDIAN (2), RECURSIVE-ADAPTIVE (3) }" }, + { GIMP_PDB_INT32, "black", "Black level (-1 to 255)" }, + { GIMP_PDB_INT32, "white", "White level (0 to 256)" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Remove speckle noise from the image"), + "This plug-in selectively performs a median or " + "adaptive box filter on an image.", + "Michael Sweet ", + "Copyright 1997-1998 by Michael Sweet", + PLUG_IN_VERSION, + N_("Des_peckle..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Enhance"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + static GimpParam values[1]; + + INIT_I18N (); + gegl_init (NULL, NULL); + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + *nreturn_vals = 1; + *return_vals = values; + + run_mode = param[0].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE : + gimp_get_data (PLUG_IN_PROC, &despeckle_radius); + + if (gimp_drawable_is_rgb (drawable_ID) || + gimp_drawable_is_gray (drawable_ID)) + { + if (! despeckle_dialog ()) + return; + } + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams < 4 || nparams > 9) + { + status = GIMP_PDB_CALLING_ERROR; + } + else if (nparams == 4) + { + despeckle_radius = param[3].data.d_int32; + filter_type = FILTER_ADAPTIVE; + black_level = 7; + white_level = 248; + } + else if (nparams == 5) + { + despeckle_radius = param[3].data.d_int32; + filter_type = param[4].data.d_int32; + black_level = 7; + white_level = 248; + } + else if (nparams == 6) + { + despeckle_radius = param[3].data.d_int32; + filter_type = param[4].data.d_int32; + black_level = param[5].data.d_int32; + white_level = 248; + } + else + { + despeckle_radius = param[3].data.d_int32; + filter_type = param[4].data.d_int32; + black_level = param[5].data.d_int32; + white_level = param[6].data.d_int32; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, despeckle_vals); + break; + + default: + status = GIMP_PDB_CALLING_ERROR; + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (gimp_drawable_is_rgb (drawable_ID) || + gimp_drawable_is_gray (drawable_ID)) + { + despeckle (); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, + despeckle_vals, sizeof (despeckle_vals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + values[0].data.d_status = status; +} + +static inline guchar +pixel_luminance (const guchar *p, + gint bpp) +{ + switch (bpp) + { + case 1: + case 2: + return p[0]; + + case 3: + case 4: + return GIMP_RGB_LUMINANCE (p[0], p[1], p[2]); + + default: + return 0; /* should not be reached */ + } +} + +static inline void +pixel_copy (guchar *dest, + const guchar *src, + gint bpp) +{ + switch (bpp) + { + case 4: + *dest++ = *src++; + case 3: + *dest++ = *src++; + case 2: + *dest++ = *src++; + case 1: + *dest++ = *src++; + } +} + +/* + * 'despeckle()' - Despeckle an image using a median filter. + * + * A median filter basically collects pixel values in a region around the + * target pixel, sorts them, and uses the median value. This code uses a + * circular row buffer to improve performance. + * + * The adaptive filter is based on the median filter but analyzes the histogram + * of the region around the target pixel and adjusts the despeckle diameter + * accordingly. + */ + +static void +despeckle (void) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + guchar *src; + guchar *dst; + gint img_bpp; + gint x, y; + gint width, height; + + if (! gimp_drawable_mask_intersect (drawable_ID, + &x, &y, &width, &height)) + return; + + if (gimp_drawable_is_rgb (drawable_ID)) + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + } + else + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + } + + img_bpp = babl_format_get_bytes_per_pixel (format); + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + src = g_new (guchar, width * height * img_bpp); + dst = g_new (guchar, width * height * img_bpp); + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x, y, width, height), 1.0, + format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + despeckle_median (src, dst, width, height, img_bpp, despeckle_radius, FALSE); + + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x, y, width, height), 0, + format, dst, + GEGL_AUTO_ROWSTRIDE); + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, x, y, width, height); + + g_free (dst); + g_free (src); +} + +static gboolean +despeckle_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *frame; + GtkWidget *button; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Despeckle"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable_ID); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect (preview, "invalidated", + G_CALLBACK (preview_update), + NULL); + + frame = gimp_frame_new (_("Median")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + button = gtk_check_button_new_with_mnemonic (_("_Adaptive")); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + filter_type & FILTER_ADAPTIVE); + gtk_widget_show (button); + + g_signal_connect (button, "toggled", + G_CALLBACK (dialog_adaptive_callback), + NULL); + + button = gtk_check_button_new_with_mnemonic (_("R_ecursive")); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + filter_type & FILTER_RECURSIVE); + gtk_widget_show (button); + + g_signal_connect (button, "toggled", + G_CALLBACK (dialog_recursive_callback), + NULL); + + table = gtk_table_new (4, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* + * Box size (diameter) control... + */ + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Radius:"), SCALE_WIDTH, ENTRY_WIDTH, + despeckle_radius, 1, MAX_RADIUS, 1, 5, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &despeckle_radius); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* + * Black level control... + */ + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Black level:"), SCALE_WIDTH, ENTRY_WIDTH, + black_level, -1, 255, 1, 8, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &black_level); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* + * White level control... + */ + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("_White level:"), SCALE_WIDTH, ENTRY_WIDTH, + white_level, 0, 256, 1, 8, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &white_level); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +preview_update (GtkWidget *widget) +{ + GimpPreview *preview = GIMP_PREVIEW (widget); + GeglBuffer *src_buffer; + const Babl *format; + guchar *dst; + guchar *src; + gint img_bpp; + gint x1,y1; + gint width, height; + + preview = GIMP_PREVIEW (widget); + + if (gimp_drawable_is_rgb (drawable_ID)) + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + } + else + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + } + + img_bpp = babl_format_get_bytes_per_pixel (format); + + width = preview->width; + height = preview->height; + + gimp_preview_get_position (preview, &x1, &y1); + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + + dst = g_new (guchar, width * height * img_bpp); + src = g_new (guchar, width * height * img_bpp); + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y1, width, height), 1.0, + format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + despeckle_median (src, dst, width, height, img_bpp, despeckle_radius, TRUE); + + gimp_preview_draw_buffer (preview, dst, width * img_bpp); + + g_object_unref (src_buffer); + + g_free (src); + g_free (dst); +} + +static void +dialog_adaptive_callback (GtkWidget *widget, + gpointer data) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + filter_type |= FILTER_ADAPTIVE; + else + filter_type &= ~FILTER_ADAPTIVE; + + gimp_preview_invalidate (GIMP_PREVIEW (preview)); +} + +static void +dialog_recursive_callback (GtkWidget *widget, + gpointer data) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + filter_type |= FILTER_RECURSIVE; + else + filter_type &= ~FILTER_RECURSIVE; + + gimp_preview_invalidate (GIMP_PREVIEW (preview)); +} + + +static inline void +list_add_elem (PixelsList *list, + const guchar *elem) +{ + const gint pos = list->start + list->count++; + + list->elems[pos >= MAX_LIST_ELEMS ? pos - MAX_LIST_ELEMS : pos] = elem; +} + +static inline void +list_del_elem (PixelsList* list) +{ + list->count--; + list->start++; + + if (list->start >= MAX_LIST_ELEMS) + list->start = 0; +} + +static inline const guchar * +list_get_random_elem (PixelsList *list) +{ + const gint pos = list->start + rand () % list->count; + + if (pos >= MAX_LIST_ELEMS) + return list->elems[pos - MAX_LIST_ELEMS]; + + return list->elems[pos]; +} + +static inline void +histogram_add (DespeckleHistogram *hist, + guchar val, + const guchar *orig) +{ + hist->elems[val]++; + list_add_elem (&hist->origs[val], orig); +} + +static inline void +histogram_remove (DespeckleHistogram *hist, + guchar val) +{ + hist->elems[val]--; + list_del_elem (&hist->origs[val]); +} + +static inline void +histogram_clean (DespeckleHistogram *hist) +{ + gint i; + + for (i = 0; i < 256; i++) + { + hist->elems[i] = 0; + hist->origs[i].count = 0; + } +} + +static inline const guchar * +histogram_get_median (DespeckleHistogram *hist, + const guchar *_default) +{ + gint count = histrest; + gint i; + gint sum = 0; + + if (! count) + return _default; + + count = (count + 1) / 2; + + i = 0; + while ((sum += hist->elems[i]) < count) + i++; + + return list_get_random_elem (&hist->origs[i]); +} + +static inline void +add_val (DespeckleHistogram *hist, + const guchar *src, + gint width, + gint bpp, + gint x, + gint y) +{ + const gint pos = (x + (y * width)) * bpp; + const gint value = pixel_luminance (src + pos, bpp); + + if (value > black_level && value < white_level) + { + histogram_add (hist, value, src + pos); + histrest++; + } + else + { + if (value <= black_level) + hist0++; + + if (value >= white_level) + hist255++; + } +} + +static inline void +del_val (DespeckleHistogram *hist, + const guchar *src, + gint width, + gint bpp, + gint x, + gint y) +{ + const gint pos = (x + (y * width)) * bpp; + const gint value = pixel_luminance (src + pos, bpp); + + if (value > black_level && value < white_level) + { + histogram_remove (hist, value); + histrest--; + } + else + { + if (value <= black_level) + hist0--; + + if (value >= white_level) + hist255--; + } +} + +static inline void +add_vals (DespeckleHistogram *hist, + const guchar *src, + gint width, + gint bpp, + gint xmin, + gint ymin, + gint xmax, + gint ymax) +{ + gint x; + gint y; + + if (xmin > xmax) + return; + + for (y = ymin; y <= ymax; y++) + { + for (x = xmin; x <= xmax; x++) + { + add_val (hist, src, width, bpp, x, y); + } + } +} + +static inline void +del_vals (DespeckleHistogram *hist, + const guchar *src, + gint width, + gint bpp, + gint xmin, + gint ymin, + gint xmax, + gint ymax) +{ + gint x; + gint y; + + if (xmin > xmax) + return; + + for (y = ymin; y <= ymax; y++) + { + for (x = xmin; x <= xmax; x++) + { + del_val (hist, src, width, bpp, x, y); + } + } +} + +static inline void +update_histogram (DespeckleHistogram *hist, + const guchar *src, + gint width, + gint bpp, + gint xmin, + gint ymin, + gint xmax, + gint ymax) +{ + /* assuming that radious of the box can change no more than one + pixel in each call */ + /* assuming that box is moving either right or down */ + + del_vals (hist, + src, width, bpp, hist->xmin, hist->ymin, xmin - 1, hist->ymax); + del_vals (hist, src, width, bpp, xmin, hist->ymin, xmax, ymin - 1); + del_vals (hist, src, width, bpp, xmin, ymax + 1, xmax, hist->ymax); + + add_vals (hist, src, width, bpp, hist->xmax + 1, ymin, xmax, ymax); + add_vals (hist, src, width, bpp, xmin, ymin, hist->xmax, hist->ymin - 1); + add_vals (hist, + src, width, bpp, hist->xmin, hist->ymax + 1, hist->xmax, ymax); + + hist->xmin = xmin; + hist->ymin = ymin; + hist->xmax = xmax; + hist->ymax = ymax; +} + +static void +despeckle_median (guchar *src, + guchar *dst, + gint width, + gint height, + gint bpp, + gint radius, + gboolean preview) +{ + guint progress; + guint max_progress; + gint x, y; + gint adapt_radius; + gint pos; + gint ymin; + gint ymax; + gint xmin; + gint xmax; + + memset (&histogram, 0, sizeof(histogram)); + progress = 0; + max_progress = width * height; + + if (! preview) + gimp_progress_init (_("Despeckle")); + + adapt_radius = radius; + for (y = 0; y < height; y++) + { + x = 0; + ymin = MAX (0, y - adapt_radius); + ymax = MIN (height - 1, y + adapt_radius); + xmin = MAX (0, x - adapt_radius); + xmax = MIN (width - 1, x + adapt_radius); + hist0 = 0; + histrest = 0; + hist255 = 0; + histogram_clean (&histogram); + histogram.xmin = xmin; + histogram.ymin = ymin; + histogram.xmax = xmax; + histogram.ymax = ymax; + add_vals (&histogram, + src, width, bpp, + histogram.xmin, histogram.ymin, + histogram.xmax, histogram.ymax); + + for (x = 0; x < width; x++) + { + const guchar *pixel; + + ymin = MAX (0, y - adapt_radius); /* update ymin, ymax when adapt_radius changed (FILTER_ADAPTIVE) */ + ymax = MIN (height - 1, y + adapt_radius); + xmin = MAX (0, x - adapt_radius); + xmax = MIN (width - 1, x + adapt_radius); + + update_histogram (&histogram, + src, width, bpp, xmin, ymin, xmax, ymax); + + pos = (x + (y * width)) * bpp; + pixel = histogram_get_median (&histogram, src + pos); + + if (filter_type & FILTER_RECURSIVE) + { + del_val (&histogram, src, width, bpp, x, y); + pixel_copy (src + pos, pixel, bpp); + add_val (&histogram, src, width, bpp, x, y); + } + + pixel_copy (dst + pos, pixel, bpp); + + /* + * Check the histogram and adjust the diameter accordingly... + */ + if (filter_type & FILTER_ADAPTIVE) + { + if (hist0 >= adapt_radius || hist255 >= adapt_radius) + { + if (adapt_radius < radius) + adapt_radius++; + } + else if (adapt_radius > 1) + { + adapt_radius--; + } + } + } + + progress += width; + + if (! preview && y % 32 == 0) + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + + if (! preview) + gimp_progress_update (1.0); +} diff --git a/plug-ins/common/destripe.c b/plug-ins/common/destripe.c new file mode 100644 index 0000000..d9994a6 --- /dev/null +++ b/plug-ins/common/destripe.c @@ -0,0 +1,530 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Destripe filter + * + * Copyright 1997 Marc Lehmann, heavily modified from a filter by + * Michael Sweet. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* + * Constants... + */ + +#define PLUG_IN_PROC "plug-in-destripe" +#define PLUG_IN_BINARY "destripe" +#define PLUG_IN_ROLE "gimp-destripe" +#define PLUG_IN_VERSION "0.2" +#define SCALE_WIDTH 140 +#define MAX_AVG 100 + + +/* + * Local functions... + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void destripe (gint32 drawable_ID, + GimpPreview *preview); +static void destripe_preview (gpointer drawable_ID, + GimpPreview *preview); + +static gboolean destripe_dialog (gint32 drawable_ID); + +/* + * Globals... + */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +typedef struct +{ + gboolean histogram; + gint avg_width; + gboolean preview; +} DestripeValues; + +static DestripeValues vals = +{ + FALSE, /* histogram */ + 36, /* average width */ + TRUE /* preview */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "avg-width", "Averaging filter width (default = 36)" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Remove vertical stripe artifacts from the image"), + "This plug-in tries to remove vertical stripes from " + "an image.", + "Marc Lehmann ", + "Marc Lehmann ", + PLUG_IN_VERSION, + N_("Des_tripe..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Enhance"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; /* Return values */ + GimpPDBStatusType status; /* Return status */ + GimpRunMode run_mode; /* Current run mode */ + gint32 drawable_ID; + + INIT_I18N (); + gegl_init (NULL, NULL); + + status = GIMP_PDB_SUCCESS; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + *nreturn_vals = 1; + *return_vals = values; + + run_mode = param[0].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* + * Possibly retrieve data... + */ + gimp_get_data (PLUG_IN_PROC, &vals); + + /* + * Get information from the dialog... + */ + if (! destripe_dialog (drawable_ID)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* + * Make sure all the arguments are present... + */ + if (nparams != 4) + status = GIMP_PDB_CALLING_ERROR; + else + vals.avg_width = param[3].data.d_int32; + break; + + case GIMP_RUN_WITH_LAST_VALS : + /* + * Possibly retrieve data... + */ + gimp_get_data (PLUG_IN_PROC, &vals); + break; + + default : + status = GIMP_PDB_CALLING_ERROR; + break; + }; + + /* + * Destripe the image... + */ + + if (status == GIMP_PDB_SUCCESS) + { + if ((gimp_drawable_is_rgb (drawable_ID) || + gimp_drawable_is_gray (drawable_ID))) + { + /* + * Run! + */ + destripe (drawable_ID, NULL); + + /* + * If run mode is interactive, flush displays... + */ + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + /* + * Store data... + */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &vals, sizeof (vals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + }; + + /* + * Reset the current run status... + */ + values[0].data.d_status = status; +} + +static void +destripe (gint32 drawable_ID, + GimpPreview *preview) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + guchar *src_rows; /* image data */ + gdouble progress, progress_inc; + gint x1, x2, y1; + gint width, height; + gint bpp; + glong *hist, *corr; /* "histogram" data */ + gint tile_width = gimp_tile_width (); + gint i, x, y, ox, cols; + + progress = 0.0; + progress_inc = 0.0; + + if (preview) + { + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &width, &height); + } + else + { + gimp_progress_init (_("Destriping")); + + if (! gimp_drawable_mask_intersect (drawable_ID, + &x1, &y1, &width, &height)) + { + return; + } + + progress = 0; + progress_inc = 0.5 * tile_width / width; + } + + x2 = x1 + width; + + if (gimp_drawable_is_rgb (drawable_ID)) + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + } + else + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + } + + bpp = babl_format_get_bytes_per_pixel (format); + + /* + * Setup for filter... + */ + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + hist = g_new (long, width * bpp); + corr = g_new (long, width * bpp); + src_rows = g_new (guchar, tile_width * height * bpp); + + memset (hist, 0, width * bpp * sizeof (long)); + + /* + * collect "histogram" data. + */ + + for (ox = x1; ox < x2; ox += tile_width) + { + guchar *rows = src_rows; + + cols = x2 - ox; + if (cols > tile_width) + cols = tile_width; + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (ox, y1, cols, height), 1.0, + format, rows, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (y = 0; y < height; y++) + { + long *h = hist + (ox - x1) * bpp; + guchar *row_end = rows + cols * bpp; + + while (rows < row_end) + *h++ += *rows++; + } + + if (! preview) + gimp_progress_update (progress += progress_inc); + } + + /* + * average out histogram + */ + + { + gint extend = (vals.avg_width / 2) * bpp; + + for (i = 0; i < MIN (3, bpp); i++) + { + long *h = hist - extend + i; + long *c = corr - extend + i; + long sum = 0; + gint cnt = 0; + + for (x = -extend; x < width * bpp; x += bpp) + { + if (x + extend < width * bpp) + { + sum += h[ extend]; cnt++; + } + + if (x - extend >= 0) + { + sum -= h[-extend]; cnt--; + } + + if (x >= 0) + { + if (*h) + *c = ((sum / cnt - *h) << 10) / *h; + else + *c = G_MAXINT; + } + + h += bpp; + c += bpp; + } + } + } + + /* + * remove stripes. + */ + + for (ox = x1; ox < x2; ox += tile_width) + { + guchar *rows = src_rows; + + cols = x2 - ox; + if (cols > tile_width) + cols = tile_width; + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (ox, y1, cols, height), 1.0, + format, rows, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (! preview) + gimp_progress_update (progress += progress_inc); + + for (y = 0; y < height; y++) + { + long *c = corr + (ox - x1) * bpp; + guchar *row_end = rows + cols * bpp; + + if (vals.histogram) + { + while (rows < row_end) + { + *rows = MIN (255, MAX (0, 128 + (*rows * *c >> 10))); + c++; rows++; + } + } + else + { + while (rows < row_end) + { + *rows = MIN (255, MAX (0, *rows + (*rows * *c >> 10) )); + c++; rows++; + } + } + } + + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (ox, y1, cols, height), 0, + format, src_rows, + GEGL_AUTO_ROWSTRIDE); + + if (! preview) + gimp_progress_update (progress += progress_inc); + } + + g_free (src_rows); + + g_object_unref (src_buffer); + + if (preview) + { + guchar *buffer = g_new (guchar, width * height * bpp); + + gegl_buffer_get (dest_buffer, GEGL_RECTANGLE (x1, y1, width, height), 1.0, + format, buffer, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + gimp_preview_draw_buffer (GIMP_PREVIEW (preview), + buffer, width * bpp); + + g_free (buffer); + g_object_unref (dest_buffer); + } + else + { + g_object_unref (dest_buffer); + + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, + x1, y1, width, height); + } + + g_free (hist); + g_free (corr); +} + +static void +destripe_preview (gpointer drawable_ID, + GimpPreview *preview) +{ + destripe (GPOINTER_TO_INT (drawable_ID), preview); +} + + +static gboolean +destripe_dialog (gint32 drawable_ID) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *table; + GtkWidget *button; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Destripe"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable_ID); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (destripe_preview), + GINT_TO_POINTER (drawable_ID)); + + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Width:"), SCALE_WIDTH, 0, + vals.avg_width, 2, MAX_AVG, 1, 10, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &vals.avg_width); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + button = gtk_check_button_new_with_mnemonic (_("Create _histogram")); + gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), vals.histogram); + gtk_widget_show (button); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &vals.histogram); + g_signal_connect_swapped (button, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/edge-dog.c b/plug-ins/common/edge-dog.c new file mode 100644 index 0000000..cb6701f --- /dev/null +++ b/plug-ins/common/edge-dog.c @@ -0,0 +1,1029 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Gimp plug-in dog.c: (C) 2004 William Skaggs + * + * Edge detection using the "Difference of Gaussians" method. + * Finds edges by doing two Gaussian blurs with different radius, and + * subtracting the results. Blurring is done using code taken from + * gauss_rle.c (as of Gimp 2.1, incorporated into gauss.c). + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-dog" +#define PLUG_IN_BINARY "edge-dog" +#define PLUG_IN_ROLE "gimp-edge-dog" + + +typedef struct +{ + gdouble inner; + gdouble outer; + gboolean normalize; + gboolean invert; +} DoGValues; + + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint dog_dialog (gint32 image_ID, + GimpDrawable *drawable); + +static void gauss_rle (GimpDrawable *drawable, + gdouble radius, + gint pass, + gboolean show_progress); + +static void compute_difference (GimpDrawable *drawable, + GimpDrawable *drawable1, + GimpDrawable *drawable2, + guchar *maxval); + +static void normalize_invert (GimpDrawable *drawable, + gboolean normalize, + guint maxval, + gboolean invert); + +static void dog (gint32 image_ID, + GimpDrawable *drawable, + gdouble inner, + gdouble outer, + gboolean show_progress); + +static void preview_update_preview (GimpPreview *preview, + GimpDrawable *drawable); +static void change_radius_callback (GtkWidget *widget, + gpointer data); + + + +/* + * Gaussian blur helper functions + */ +static gint * make_curve (gdouble sigma, + gint *length); +static void run_length_encode (guchar *src, + gint *dest, + gint bytes, + gint width); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static DoGValues dogvals = +{ + 3.0, /* inner radius */ + 1.0, /* outer radius */ + TRUE, /* normalize */ + TRUE /* invert */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_FLOAT, "inner", "Radius of inner gaussian blur (in pixels, > 0.0)" }, + { GIMP_PDB_FLOAT, "outer", "Radius of outer gaussian blur (in pixels, > 0.0)" }, + { GIMP_PDB_INT32, "normalize", "Normalize { TRUE, FALSE }" }, + { GIMP_PDB_INT32, "invert", "Invert { TRUE, FALSE }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Edge detection with control of edge thickness"), + "Applies two Gaussian blurs to the drawable, and " + "subtracts the results. This is robust and widely " + "used method for detecting edges.", + "Spencer Kimball, Peter Mattis, Sven Neumann, William Skaggs", + "Spencer Kimball, Peter Mattis, Sven Neumann, William Skaggs", + "1995-2004", + N_("_Difference of Gaussians (legacy)..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Edge-Detect"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + gint32 image_ID; + GimpDrawable *drawable; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + if (! gimp_item_is_layer (param[2].data.d_drawable)) + { + g_set_error (&error, 0, 0, "%s", + _("Can operate on layers only " + "(but was called on channel or mask).")); + status = GIMP_PDB_EXECUTION_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Get the specified image and drawable */ + image_ID = param[1].data.d_image; + drawable = gimp_drawable_get (param[2].data.d_drawable); + + /* set the tile cache size so that the gaussian blur works well */ + gimp_tile_cache_ntiles (2 * + (MAX (drawable->width, drawable->height) / + gimp_tile_width () + 1)); + + if (strcmp (name, PLUG_IN_PROC) == 0) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &dogvals); + + /* First acquire information with a dialog */ + if (! dog_dialog (image_ID, drawable)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 7) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + dogvals.inner = param[3].data.d_float; + dogvals.outer = param[4].data.d_float; + dogvals.normalize = param[5].data.d_int32; + dogvals.invert = param[6].data.d_int32; + + if (dogvals.inner <= 0.0 || dogvals.outer <= 0.0) + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &dogvals); + break; + + default: + break; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id)) + { + gimp_progress_init (_("DoG Edge Detect")); + + /* run the Difference of Gaussians */ + gimp_image_undo_group_start (image_ID); + + dog (image_ID, drawable, dogvals.inner, dogvals.outer, TRUE); + + gimp_image_undo_group_end (image_ID); + + gimp_progress_update (1.0); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &dogvals, sizeof (DoGValues)); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + *nreturn_vals = 2; + + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = _("Cannot operate on indexed color images."); + } + + gimp_drawable_detach (drawable); + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gint +dog_dialog (gint32 image_ID, + GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *frame; + GtkWidget *button; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *coord; + GimpUnit unit; + gdouble xres; + gdouble yres; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("DoG Edge Detect"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, FALSE, FALSE, 0); + gtk_widget_show (preview); + + g_signal_connect (preview, "invalidated", + G_CALLBACK (preview_update_preview), + drawable); + + frame = gimp_frame_new (_("Smoothing Parameters")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + /* Get the image resolution and unit */ + gimp_image_get_resolution (image_ID, &xres, &yres); + unit = gimp_image_get_unit (image_ID); + + coord = gimp_coordinates_new (unit, "%a", TRUE, FALSE, -1, + GIMP_SIZE_ENTRY_UPDATE_SIZE, + + FALSE, + TRUE, + + _("_Radius 1:"), dogvals.inner, xres, + 0, 8 * MAX (drawable->width, drawable->height), + 0, 0, + + _("R_adius 2:"), dogvals.outer, yres, + 0, 8 * MAX (drawable->width, drawable->height), + 0, 0); + + gtk_container_add (GTK_CONTAINER (frame), coord); + gtk_widget_show (coord); + + gimp_size_entry_set_pixel_digits (GIMP_SIZE_ENTRY (coord), 1); + g_signal_connect (coord, "value-changed", + G_CALLBACK (change_radius_callback), + preview); + + button = gtk_check_button_new_with_mnemonic (_("_Normalize")); + gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), dogvals.normalize); + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &dogvals.normalize); + g_signal_connect_swapped (button, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + gtk_widget_show (button); + + button = gtk_check_button_new_with_mnemonic (_("_Invert")); + gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), dogvals.invert); + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &dogvals.invert); + g_signal_connect_swapped (button, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + gtk_widget_show (button); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + dogvals.inner = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (coord), 0); + dogvals.outer = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (coord), 1); + } + + gtk_widget_destroy (dialog); + + + return run; +} + + +/* Convert from separated to premultiplied alpha, on a single scan line. */ +static void +multiply_alpha (guchar *buf, + gint width, + gint bytes) +{ + gint i, j; + gdouble alpha; + + for (i = 0; i < width * bytes; i += bytes) + { + alpha = buf[i + bytes - 1] * (1.0 / 255.0); + + for (j = 0; j < bytes - 1; j++) + buf[i + j] *= alpha; + } +} + +/* Convert from premultiplied to separated alpha, on a single scan + line. */ +static void +separate_alpha (guchar *buf, + gint width, + gint bytes) +{ + gint i, j; + guchar alpha; + gdouble recip_alpha; + gint new_val; + + for (i = 0; i < width * bytes; i += bytes) + { + alpha = buf[i + bytes - 1]; + + if (alpha != 0 && alpha != 255) + { + recip_alpha = 255.0 / alpha; + + for (j = 0; j < bytes - 1; j++) + { + new_val = buf[i + j] * recip_alpha; + buf[i + j] = MIN (255, new_val); + } + } + } +} + +static void +dog (gint32 image_ID, + GimpDrawable *drawable, + gdouble inner, + gdouble outer, + gboolean show_progress) +{ + GimpDrawable *drawable1; + GimpDrawable *drawable2; + gint32 drawable_id = drawable->drawable_id; + gint32 layer1; + gint32 layer2; + gint width, height; + gint x1, y1; + guchar maxval = 255; + + if (! gimp_drawable_mask_intersect (drawable_id, &x1, &y1, &width, &height)) + return; + + gimp_drawable_flush (drawable); + + layer1 = gimp_layer_copy (drawable_id); + gimp_item_set_visible (layer1, FALSE); + gimp_item_set_name (layer1, "dog_scratch_layer1"); + gimp_image_insert_layer (image_ID, layer1, + gimp_item_get_parent (drawable_id), 0); + + layer2 = gimp_layer_copy (drawable_id); + gimp_item_set_visible (layer2, FALSE); + gimp_item_set_name (layer2, "dog_scratch_layer2"); + gimp_image_insert_layer (image_ID, layer2, + gimp_item_get_parent (drawable_id), 0); + + drawable1 = gimp_drawable_get (layer1); + drawable2 = gimp_drawable_get (layer2); + + gauss_rle (drawable1, inner, 0, show_progress); + gauss_rle (drawable2, outer, 1, show_progress); + + compute_difference (drawable, drawable1, drawable2, &maxval); + + gimp_drawable_detach (drawable1); + gimp_drawable_detach (drawable2); + + gimp_image_remove_layer (image_ID, layer1); + gimp_image_remove_layer (image_ID, layer2); + + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, x1, y1, width, height); + + if (dogvals.normalize || dogvals.invert) + /* gimp_invert doesn't work properly with previews due to shadow handling + * so reimplement it here - see Bug 557380 + */ + { + normalize_invert (drawable, dogvals.normalize, maxval, dogvals.invert); + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, x1, y1, width, height); + } +} + + +static void +compute_difference (GimpDrawable *drawable, + GimpDrawable *drawable1, + GimpDrawable *drawable2, + guchar *maxval) +{ + GimpPixelRgn src1_rgn, src2_rgn, dest_rgn; + gint width, height; + gint bpp; + gpointer pr; + gint x, y, k; + gint x1, y1; + gboolean has_alpha; + + *maxval = 0; + + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x1, &y1, &width, &height)) + return; + + bpp = drawable->bpp; + has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + gimp_pixel_rgn_init (&src1_rgn, + drawable1, 0, 0, drawable1->width, drawable1->height, + FALSE, FALSE); + gimp_pixel_rgn_init (&src2_rgn, + drawable2, 0, 0, drawable1->width, drawable1->height, + FALSE, FALSE); + gimp_pixel_rgn_init (&dest_rgn, + drawable, 0, 0, drawable->width, drawable->height, + TRUE, TRUE); + + for (pr = gimp_pixel_rgns_register (3, &src1_rgn, &src2_rgn, &dest_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) + { + guchar *src1 = src1_rgn.data; + guchar *src2 = src2_rgn.data; + guchar *dest = dest_rgn.data; + gint row = src1_rgn.y - y1; + + for (y = 0; y < src1_rgn.h; y++, row++) + { + guchar *s1 = src1; + guchar *s2 = src2; + guchar *d = dest; + gint col = src1_rgn.x - x1; + + for (x = 0; x < src1_rgn.w; x++, col++) + { + + if (has_alpha) + { + for (k = 0; k < bpp-1; k++) + { + d[k] = CLAMP0255 (s1[k] - s2[k]); + *maxval = MAX (d[k], *maxval); + } + } + else + { + for (k = 0; k < bpp; k++) + { + d[k] = CLAMP0255 (s1[k] - s2[k]); + *maxval = MAX (d[k], *maxval); + } + } + + s1 += bpp; + s2 += bpp; + d += bpp; + } + + src1 += src1_rgn.rowstride; + src2 += src2_rgn.rowstride; + dest += dest_rgn.rowstride; + } + } +} + + +static void +normalize_invert (GimpDrawable *drawable, + gboolean normalize, + guint maxval, + gboolean invert) +{ + GimpPixelRgn src_rgn, dest_rgn; + gint bpp; + gpointer pr; + gint x, y, k; + gint x1, y1; + gint width, height; + gboolean has_alpha; + gdouble factor; + + if (normalize && maxval != 0) { + factor = 255.0 / maxval; + } + else + factor = 1.0; + + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x1, &y1, &width, &height)) + return; + + bpp = drawable->bpp; + has_alpha = gimp_drawable_has_alpha(drawable->drawable_id); + + gimp_pixel_rgn_init (&src_rgn, + drawable, 0, 0, drawable->width, drawable->height, + FALSE, FALSE); + gimp_pixel_rgn_init (&dest_rgn, + drawable, 0, 0, drawable->width, drawable->height, + TRUE, TRUE); + + for (pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) + { + guchar *src = src_rgn.data; + guchar *dest = dest_rgn.data; + gint row = src_rgn.y - y1; + + for (y = 0; y < src_rgn.h; y++, row++) + { + guchar *s = src; + guchar *d = dest; + gint col = src_rgn.x - x1; + + for (x = 0; x < src_rgn.w; x++, col++) + { + + if (has_alpha) + { + for (k = 0; k < bpp-1; k++) + { + d[k] = factor * s[k]; + if (invert) + d[k] = 255 - d[k]; + } + } + else + { + for (k = 0; k < bpp; k++) + { + d[k] = factor * s[k]; + if (invert) + d[k] = 255 - d[k]; + } + } + + s += bpp; + d += bpp; + } + + src += src_rgn.rowstride; + dest += dest_rgn.rowstride; + } + } +} + + +static void +gauss_rle (GimpDrawable *drawable, + gdouble radius, + gint pass, + gboolean show_progress) +{ + GimpPixelRgn src_rgn, dest_rgn; + gint width, height; + gint bytes; + gint has_alpha; + guchar *dest, *dp; + guchar *src, *sp; + gint *buf, *bb; + gint pixels; + gint total = 1; + gint x1, y1; + gint i, row, col, b; + gint start, end; + gdouble progress, max_progress; + gint *curve; + gint *sum = NULL; + gint val; + gint length; + gint initial_p, initial_m; + gdouble std_dev; + + if (radius <= 0.0) + return; + + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x1, &y1, &width, &height)) + return; + + bytes = drawable->bpp; + has_alpha = gimp_drawable_has_alpha(drawable->drawable_id); + + buf = g_new (gint, MAX (width, height) * 2); + + /* allocate buffers for source and destination pixels */ + src = g_new (guchar, MAX (width, height) * bytes); + dest = g_new (guchar, MAX (width, height) * bytes); + + gimp_pixel_rgn_init (&src_rgn, + drawable, 0, 0, drawable->width, drawable->height, + FALSE, FALSE); + gimp_pixel_rgn_init (&dest_rgn, + drawable, 0, 0, drawable->width, drawable->height, + TRUE, TRUE); + + progress = 0.0; + max_progress = 2 * width * height; + + /* First the vertical pass */ + radius = fabs (radius) + 1.0; + std_dev = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0))); + + curve = make_curve (std_dev, &length); + sum = g_new (gint, 2 * length + 1); + + sum[0] = 0; + + for (i = 1; i <= length*2; i++) + sum[i] = curve[i-length-1] + sum[i-1]; + sum += length; + + total = sum[length] - sum[-length]; + + for (col = 0; col < width; col++) + { + gimp_pixel_rgn_get_col (&src_rgn, src, col + x1, y1, height); + + if (has_alpha) + multiply_alpha (src, height, bytes); + + sp = src; + dp = dest; + + for (b = 0; b < bytes; b++) + { + initial_p = sp[b]; + initial_m = sp[(height-1) * bytes + b]; + + /* Determine a run-length encoded version of the row */ + run_length_encode (sp + b, buf, bytes, height); + + for (row = 0; row < height; row++) + { + start = (row < length) ? -row : -length; + end = (height <= (row + length) ? + (height - row - 1) : length); + + val = 0; + i = start; + bb = buf + (row + i) * 2; + + if (start != -length) + val += initial_p * (sum[start] - sum[-length]); + + while (i < end) + { + pixels = bb[0]; + i += pixels; + + if (i > end) + i = end; + + val += bb[1] * (sum[i] - sum[start]); + bb += (pixels * 2); + start = i; + } + + if (end != length) + val += initial_m * (sum[length] - sum[end]); + + dp[row * bytes + b] = val / total; + } + } + + if (has_alpha) + separate_alpha (dest, height, bytes); + + gimp_pixel_rgn_set_col (&dest_rgn, dest, col + x1, y1, height); + + if (show_progress) + { + progress += height; + + if ((col % 32) == 0) + gimp_progress_update (0.5 * (pass + (progress / max_progress))); + } + } + + /* prepare for the horizontal pass */ + gimp_pixel_rgn_init (&src_rgn, + drawable, 0, 0, drawable->width, drawable->height, + FALSE, TRUE); + + /* Now the horizontal pass */ + for (row = 0; row < height; row++) + { + gimp_pixel_rgn_get_row (&src_rgn, src, x1, row + y1, width); + if (has_alpha) + multiply_alpha (src, width, bytes); + + sp = src; + dp = dest; + + for (b = 0; b < bytes; b++) + { + initial_p = sp[b]; + initial_m = sp[(width-1) * bytes + b]; + + /* Determine a run-length encoded version of the row */ + run_length_encode (sp + b, buf, bytes, width); + + for (col = 0; col < width; col++) + { + start = (col < length) ? -col : -length; + end = (width <= (col + length)) ? (width - col - 1) : length; + + val = 0; + i = start; + bb = buf + (col + i) * 2; + + if (start != -length) + val += initial_p * (sum[start] - sum[-length]); + + while (i < end) + { + pixels = bb[0]; + i += pixels; + + if (i > end) + i = end; + + val += bb[1] * (sum[i] - sum[start]); + bb += (pixels * 2); + start = i; + } + + if (end != length) + val += initial_m * (sum[length] - sum[end]); + + dp[col * bytes + b] = val / total; + } + } + + if (has_alpha) + separate_alpha (dest, width, bytes); + + gimp_pixel_rgn_set_row (&dest_rgn, dest, x1, row + y1, width); + + if (show_progress) + { + progress += width; + + if ((row % 32) == 0) + gimp_progress_update (0.5 * (pass + (progress / max_progress))); + } + } + + /* merge the shadow, update the drawable */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x1, y1, width, height); + + /* free buffers */ + g_free (buf); + g_free (src); + g_free (dest); +} + +/* + * The equations: g(r) = exp (- r^2 / (2 * sigma^2)) + * r = sqrt (x^2 + y ^2) + */ + +static gint * +make_curve (gdouble sigma, + gint *length) +{ + gint *curve; + gdouble sigma2; + gdouble l; + gint temp; + gint i, n; + + sigma2 = 2 * sigma * sigma; + l = sqrt (-sigma2 * log (1.0 / 255.0)); + + n = ceil (l) * 2; + if ((n % 2) == 0) + n += 1; + + curve = g_new (gint, n); + + *length = n / 2; + curve += *length; + curve[0] = 255; + + for (i = 1; i <= *length; i++) + { + temp = (gint) (exp (- (i * i) / sigma2) * 255); + curve[-i] = temp; + curve[i] = temp; + } + + return curve; +} + +static void +run_length_encode (guchar *src, + gint *dest, + gint bytes, + gint width) +{ + gint start; + gint i; + gint j; + guchar last; + + last = *src; + src += bytes; + start = 0; + + for (i = 1; i < width; i++) + { + if (*src != last) + { + for (j = start; j < i; j++) + { + *dest++ = (i - j); + *dest++ = last; + } + start = i; + last = *src; + } + src += bytes; + } + + for (j = start; j < i; j++) + { + *dest++ = (i - j); + *dest++ = last; + } +} + +static void +preview_update_preview (GimpPreview *preview, + GimpDrawable *drawable) +{ + gint x1, y1; + gint width, height; + gint bpp; + guchar *buffer; + GimpPixelRgn src_rgn; + GimpPixelRgn preview_rgn; + gint32 image_id, src_image_id; + gint32 preview_id; + GimpDrawable *preview_drawable; + + bpp = gimp_drawable_bpp (drawable->drawable_id); + + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &width, &height); + + buffer = g_new (guchar, width * height * bpp); + + gimp_pixel_rgn_init (&src_rgn, drawable, + x1, y1, width, height, FALSE, FALSE); + gimp_pixel_rgn_get_rect (&src_rgn, buffer, + x1, y1, width, height); + + /* set up gimp drawable for rendering preview into */ + src_image_id = gimp_item_get_image (drawable->drawable_id); + image_id = gimp_image_new (width, height, + gimp_image_base_type (src_image_id)); + preview_id = gimp_layer_new (image_id, "preview", width, height, + gimp_drawable_type (drawable->drawable_id), + 100, + gimp_image_get_default_new_layer_mode (image_id)); + preview_drawable = gimp_drawable_get (preview_id); + gimp_image_insert_layer (image_id, preview_id, -1, 0); + gimp_layer_set_offsets (preview_id, 0, 0); + gimp_pixel_rgn_init (&preview_rgn, preview_drawable, + 0, 0, width, height, TRUE, TRUE); + gimp_pixel_rgn_set_rect (&preview_rgn, buffer, + 0, 0, width, height); + gimp_drawable_flush (preview_drawable); + gimp_drawable_merge_shadow (preview_id, TRUE); + gimp_drawable_update (preview_id, 0, 0, width, height); + + dog (image_id, preview_drawable, dogvals.inner, dogvals.outer, FALSE); + + gimp_pixel_rgn_get_rect (&preview_rgn, buffer, + 0, 0, width, height); + + gimp_preview_draw_buffer (preview, buffer, width * bpp); + + gimp_image_delete (image_id); + g_free (buffer); +} + +static void +change_radius_callback (GtkWidget *coord, + gpointer data) +{ + GimpPreview *preview = GIMP_PREVIEW (data); + + dogvals.inner = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (coord), 0); + dogvals.outer = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (coord), 1); + + gimp_preview_invalidate (preview); +} diff --git a/plug-ins/common/emboss.c b/plug-ins/common/emboss.c new file mode 100644 index 0000000..51ec60e --- /dev/null +++ b/plug-ins/common/emboss.c @@ -0,0 +1,550 @@ +/************************************************** + * file: emboss/emboss.c + * + * Copyright (c) 1997 Eric L. Hernes (erich@rrnet.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software withough specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-emboss" +#define PLUG_IN_BINARY "emboss" +#define PLUG_IN_ROLE "gimp-emboss" + + +enum +{ + FUNCTION_BUMPMAP = 0, + FUNCTION_EMBOSS = 1 +}; + +typedef struct +{ + gdouble azimuth; + gdouble elevation; + gint32 depth; + gint32 embossp; +} piArgs; + +static piArgs evals = +{ + 30.0, /* azimuth */ + 45.0, /* elevation */ + 20, /* depth */ + 1 /* emboss */ +}; + +struct embossFilter +{ + gdouble Lx; + gdouble Ly; + gdouble Lz; + gdouble Nz; + gdouble Nz2; + gdouble NzLz; + gdouble bg; +} static Filter; + +static void query (void); +static void run (const gchar *name, + gint nparam, + const GimpParam *param, + gint *nretvals, + GimpParam **retvals); + +static void emboss (GimpDrawable *drawable, + GimpPreview *preview); +static gboolean emboss_dialog (GimpDrawable *drawable); + +static void emboss_init (gdouble azimuth, + gdouble elevation, + gushort width45); +static void emboss_row (const guchar *src, + const guchar *texture, + guchar *dst, + guint width, + guint bypp, + gboolean alpha); + + +#define DtoR(d) ((d)*(G_PI/(gdouble)180)) + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init */ + NULL, /* quit */ + query, /* query */ + run, /* run */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "The Image" }, + { GIMP_PDB_DRAWABLE, "drawable", "The Drawable" }, + { GIMP_PDB_FLOAT, "azimuth", "The Light Angle (degrees)" }, + { GIMP_PDB_FLOAT, "elevation", "The Elevation Angle (degrees)" }, + { GIMP_PDB_INT32, "depth", "The Filter Width" }, + { GIMP_PDB_INT32, "emboss", "Emboss or Bumpmap" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Simulate an image created by embossing"), + "Emboss or Bumpmap the given drawable, specifying " + "the angle and elevation for the light source.", + "Eric L. Hernes, John Schlag", + "Eric L. Hernes", + "1997", + N_("_Emboss (legacy)..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Distorts"); +} + +static void +run (const gchar *name, + gint nparam, + const GimpParam *param, + gint *nretvals, + GimpParam **retvals) +{ + static GimpParam rvals[1]; + GimpDrawable *drawable; + + *nretvals = 1; + *retvals = rvals; + + INIT_I18N (); + + drawable = gimp_drawable_get (param[2].data.d_drawable); + gimp_tile_cache_ntiles (drawable->ntile_cols); + + + rvals[0].type = GIMP_PDB_STATUS; + rvals[0].data.d_status = GIMP_PDB_SUCCESS; + + switch (param[0].data.d_int32) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &evals); + + if (! emboss_dialog (drawable)) + { + rvals[0].data.d_status = GIMP_PDB_CANCEL; + } + else + { + gimp_set_data (PLUG_IN_PROC, &evals, sizeof (piArgs)); + } + + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparam != 7) + { + rvals[0].data.d_status = GIMP_PDB_CALLING_ERROR; + break; + } + + evals.azimuth = param[3].data.d_float; + evals.elevation = param[4].data.d_float; + evals.depth = param[5].data.d_int32; + evals.embossp = param[6].data.d_int32; + + emboss (drawable, NULL); + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &evals); + /* use this image and drawable, even with last args */ + emboss (drawable, NULL); + break; + } +} + +#define pixelScale 255.9 + +static void +emboss_init (gdouble azimuth, + gdouble elevation, + gushort width45) +{ + /* + * compute the light vector from the input parameters. + * normalize the length to pixelScale for fast shading calculation. + */ + Filter.Lx = cos (azimuth) * cos (elevation) * pixelScale; + Filter.Ly = sin (azimuth) * cos (elevation) * pixelScale; + Filter.Lz = sin (elevation) * pixelScale; + + /* + * constant z component of image surface normal - this depends on the + * image slope we wish to associate with an angle of 45 degrees, which + * depends on the width of the filter used to produce the source image. + */ + Filter.Nz = (6 * 255) / width45; + Filter.Nz2 = Filter.Nz * Filter.Nz; + Filter.NzLz = Filter.Nz * Filter.Lz; + + /* optimization for vertical normals: L.[0 0 1] */ + Filter.bg = Filter.Lz; +} + + +/* + * ANSI C code from the article + * "Fast Embossing Effects on Raster Image Data" + * by John Schlag, jfs@kerner.com + * in "Graphics Gems IV", Academic Press, 1994 + * + * + * Emboss - shade 24-bit pixels using a single distant light source. + * Normals are obtained by differentiating a monochrome 'bump' image. + * The unary case ('texture' == NULL) uses the shading result as output. + * The binary case multiples the optional 'texture' image by the shade. + * Images are in row major order with interleaved color components (rgbrgb...). + * E.g., component c of pixel x,y of 'dst' is dst[3*(y*width + x) + c]. + * + */ + +static void +emboss_row (const guchar *src, + const guchar *texture, + guchar *dst, + guint width, + guint bypp, + gboolean alpha) +{ + const guchar *s[3]; + gdouble M[3][3]; + gint x, bytes; + + /* mung pixels, avoiding edge pixels */ + s[0] = src; + s[1] = s[0] + (width * bypp); + s[2] = s[1] + (width * bypp); + dst += bypp; + + bytes = (alpha) ? bypp - 1 : bypp; + + if (texture) + texture += (width + 1) * bypp; + + for (x = 1; x < width - 1; x++) + { + gdouble a; + glong Nx, Ny, NdotL; + gint shade, b; + gint i, j; + + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + M[i][j] = 0.0; + + for (b = 0; b < bytes; b++) + { + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + { + if (alpha) + a = s[i][j * bypp + bytes] / 255.0; + else + a = 1.0; + + M[i][j] += a * s[i][j * bypp + b]; + } + } + + Nx = M[0][0] + M[1][0] + M[2][0] - M[0][2] - M[1][2] - M[2][2]; + Ny = M[2][0] + M[2][1] + M[2][2] - M[0][0] - M[0][1] - M[0][2]; + + /* shade with distant light source */ + if ( Nx == 0 && Ny == 0 ) + shade = Filter.bg; + else if ( (NdotL = Nx * Filter.Lx + Ny * Filter.Ly + Filter.NzLz) < 0 ) + shade = 0; + else + shade = NdotL / sqrt(Nx*Nx + Ny*Ny + Filter.Nz2); + + /* do something with the shading result */ + if (texture) + { + for (b = 0; b < bytes; b++) + *dst++ = (*texture++ * shade) >> 8; + + if (alpha) + { + *dst++ = s[1][bypp + bytes]; /* preserve the alpha */ + texture++; + } + } + else + { + for (b = 0; b < bytes; b++) + *dst++ = shade; + + if (alpha) + *dst++ = s[1][bypp + bytes]; /* preserve the alpha */ + } + + for (i = 0; i < 3; i++) + s[i] += bypp; + } + + if (texture) + texture += bypp; +} + +static void +emboss (GimpDrawable *drawable, + GimpPreview *preview) +{ + GimpPixelRgn src, dst; + gint p_update; + gint y; + gint x1, y1, x2, y2; + gint width, height; + gint bypp, rowsize; + gboolean has_alpha; + guchar *srcbuf, *dstbuf; + + if (preview) + { + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &width, &height); + x2 = x1 + width; + y2 = y1 + height; + } + else + { + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x1, &y1, &width, &height)) + return; + + /* expand the bounds a little */ + x1 = MAX (0, x1 - evals.depth); + y1 = MAX (0, y1 - evals.depth); + x2 = MIN (drawable->width, x1 + width + evals.depth); + y2 = MIN (drawable->height, y1 + height + evals.depth); + + width = x2 - x1; + height = y2 - y1; + } + + bypp = drawable->bpp; + p_update = MAX (1, height / 20); + rowsize = width * bypp; + has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + gimp_pixel_rgn_init (&src, drawable, + x1, y1, width, height, + FALSE, FALSE); + gimp_pixel_rgn_init (&dst, drawable, + x1, y1, width, height, + preview == NULL, TRUE); + + srcbuf = g_new0 (guchar, rowsize * 3); + dstbuf = g_new0 (guchar, rowsize); + + emboss_init (DtoR(evals.azimuth), DtoR(evals.elevation), evals.depth); + if (!preview) + gimp_progress_init (_("Emboss")); + + /* first row */ + gimp_pixel_rgn_get_rect (&src, srcbuf, x1, y1, width, 3); + memcpy (srcbuf, srcbuf + rowsize, rowsize); + emboss_row (srcbuf, evals.embossp ? NULL : srcbuf, + dstbuf, width, bypp, has_alpha); + gimp_pixel_rgn_set_row (&dst, dstbuf, 0, 0, width); + + /* middle rows */ + for (y = 0; y < height - 2; y++) + { + if (! preview && (y % p_update == 0)) + gimp_progress_update ((gdouble) y / (gdouble) height); + + gimp_pixel_rgn_get_rect (&src, srcbuf, x1, y1 + y, width, 3); + emboss_row (srcbuf, evals.embossp ? NULL : srcbuf, + dstbuf, width, bypp, has_alpha); + gimp_pixel_rgn_set_row (&dst, dstbuf, x1, y1 + y + 1, width); + } + + /* last row */ + gimp_pixel_rgn_get_rect (&src, srcbuf, x1, y2 - 3, width, 3); + memcpy (srcbuf + rowsize * 2, srcbuf + rowsize, rowsize); + emboss_row (srcbuf, evals.embossp ? NULL : srcbuf, + dstbuf, width, bypp, has_alpha); + gimp_pixel_rgn_set_row (&dst, dstbuf, x1, y2 - 1, width); + + if (preview) + { + gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview), + &dst); + } + else + { + gimp_progress_update (1.0); + + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x1, y1, width, height); + gimp_displays_flush (); + } + + g_free (srcbuf); + g_free (dstbuf); +} + +static gboolean +emboss_dialog (GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *radio1; + GtkWidget *radio2; + GtkWidget *frame; + GtkWidget *table; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Emboss"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (emboss), + drawable); + + frame = gimp_int_radio_group_new (TRUE, _("Function"), + G_CALLBACK (gimp_radio_button_update), + &evals.embossp, evals.embossp, + + _("_Bumpmap"), FUNCTION_BUMPMAP, &radio1, + _("_Emboss"), FUNCTION_EMBOSS, &radio2, + + NULL); + + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + g_signal_connect_swapped (radio1, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (radio2, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Azimuth:"), 100, 6, + evals.azimuth, 0.0, 360.0, 1.0, 10.0, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &evals.azimuth); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("E_levation:"), 100, 6, + evals.elevation, 0.0, 180.0, 1.0, 10.0, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &evals.elevation); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("_Depth:"), 100, 6, + evals.depth, 1.0, 100.0, 1.0, 5.0, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &evals.depth); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (table); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + if (run) + emboss (drawable, NULL); + + return run; +} diff --git a/plug-ins/common/file-aa.c b/plug-ins/common/file-aa.c new file mode 100644 index 0000000..abc6f9c --- /dev/null +++ b/plug-ins/common/file-aa.c @@ -0,0 +1,412 @@ +/** + * aa.c version 1.0 + * A plugin that uses libaa (ftp://ftp.ta.jcu.cz/pub/aa) to save images as + * ASCII. + * NOTE: This plugin *requires* aalib 1.2 or later. Earlier versions will + * not work. + * Code copied from all over the GIMP source. + * Tim Newsome + */ + +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-aa-save" +#define PLUG_IN_BINARY "file-aa" +#define PLUG_IN_ROLE "gimp-file-aa" + + +/* + * Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gboolean save_aa (gint32 drawable_ID, + gchar *filename, + gint output_type); +static void gimp2aa (gint32 drawable_ID, + aa_context *context); + +static gint aa_dialog (gint selected); + + +/* + * Some global variables. + */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + {GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"}, + {GIMP_PDB_IMAGE, "image", "Input image"}, + {GIMP_PDB_DRAWABLE, "drawable", "Drawable to save"}, + {GIMP_PDB_STRING, "filename", "The name of the file to save the image in"}, + {GIMP_PDB_STRING, "raw-filename", "The name entered"}, + {GIMP_PDB_STRING, "file-type", "File type to use"} + }; + + gimp_install_procedure (SAVE_PROC, + "Saves grayscale image in various text formats", + "This plug-in uses aalib to save grayscale image " + "as ascii art into a variety of text formats", + "Tim Newsome ", + "Tim Newsome ", + "1997", + N_("ASCII art"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "text/plain"); + gimp_register_save_handler (SAVE_PROC, "txt,ansi,text", ""); +} + +/** + * Searches aa_formats defined by aalib to find the index of the type + * specified by string. + * -1 means it wasn't found. + */ +static gint +get_type_from_string (const gchar *string) +{ + gint type = 0; + aa_format **p = (aa_format **) aa_formats; + + while (*p && strcmp ((*p)->formatname, string)) + { + p++; + type++; + } + + if (*p == NULL) + return -1; + + return type; +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint output_type = 0; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + run_mode = param[0].data.d_int32; + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "AA", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + if (! (gimp_drawable_is_rgb (drawable_ID) || + gimp_drawable_is_gray (drawable_ID))) + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (SAVE_PROC, &output_type); + output_type = aa_dialog (output_type); + if (output_type < 0) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + output_type = get_type_from_string (param[5].data.d_string); + if (output_type < 0) + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (SAVE_PROC, &output_type); + break; + + default: + break; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_aa (drawable_ID, param[3].data.d_string, output_type)) + { + gimp_set_data (SAVE_PROC, &output_type, sizeof (output_type)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + values[0].data.d_status = status; +} + +/** + * The actual save function. What it's all about. + * The image type has to be GRAY. + */ +static gboolean +save_aa (gint32 drawable_ID, + gchar *filename, + gint output_type) +{ + aa_savedata savedata; + aa_context *context; + aa_format format = *aa_formats[output_type]; + + format.width = gimp_drawable_width (drawable_ID) / 2; + format.height = gimp_drawable_height (drawable_ID) / 2; + + /* Get a libaa context which will save its output to filename. */ + savedata.name = filename; + savedata.format = &format; + + context = aa_init (&save_d, &aa_defparams, &savedata); + if (!context) + return FALSE; + + gimp2aa (drawable_ID, context); + aa_flush (context); + aa_close (context); + + return TRUE; +} + +static void +gimp2aa (gint32 drawable_ID, + aa_context *context) +{ + GeglBuffer *buffer; + const Babl *format; + aa_renderparams *renderparams; + gint width; + gint height; + gint x, y; + gint bpp; + guchar *buf; + guchar *p; + + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = aa_imgwidth (context); + height = aa_imgheight (context); + + switch (gimp_drawable_type (drawable_ID)) + { + case GIMP_GRAY_IMAGE: + format = babl_format ("Y' u8"); + break; + + case GIMP_GRAYA_IMAGE: + format = babl_format ("Y'A u8"); + break; + + case GIMP_RGB_IMAGE: + case GIMP_INDEXED_IMAGE: + format = babl_format ("R'G'B' u8"); + break; + + case GIMP_RGBA_IMAGE: + case GIMP_INDEXEDA_IMAGE: + format = babl_format ("R'G'B'A u8"); + break; + + default: + g_return_if_reached (); + break; + } + + bpp = babl_format_get_bytes_per_pixel (format); + + buf = g_new (guchar, width * bpp); + + for (y = 0; y < height; y++) + { + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, y, width, 1), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + switch (bpp) + { + case 1: /* GRAY */ + for (x = 0, p = buf; x < width; x++, p++) + aa_putpixel (context, x, y, *p); + break; + + case 2: /* GRAYA, blend over black */ + for (x = 0, p = buf; x < width; x++, p += 2) + aa_putpixel (context, x, y, (p[0] * (p[1] + 1)) >> 8); + break; + + case 3: /* RGB */ + for (x = 0, p = buf; x < width; x++, p += 3) + aa_putpixel (context, x, y, + GIMP_RGB_LUMINANCE (p[0], p[1], p[2]) + 0.5); + break; + + case 4: /* RGBA, blend over black */ + for (x = 0, p = buf; x < width; x++, p += 4) + aa_putpixel (context, x, y, + ((guchar) (GIMP_RGB_LUMINANCE (p[0], p[1], p[2]) + 0.5) + * (p[3] + 1)) >> 8); + break; + + default: + g_assert_not_reached (); + break; + } + } + + g_free (buf); + + g_object_unref (buffer); + + renderparams = aa_getrenderparams (); + renderparams->dither = AA_FLOYD_S; + + aa_render (context, renderparams, 0, 0, + aa_scrwidth (context), aa_scrheight (context)); +} + +static gint +aa_dialog (gint selected) +{ + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *combo; + gint i; + + /* Create the actual window. */ + dialog = gimp_export_dialog_new (_("Text"), PLUG_IN_BINARY, SAVE_PROC); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("_Format:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + for (i = 0; aa_formats[i]; i++) + gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (combo), + GIMP_INT_STORE_VALUE, i, + GIMP_INT_STORE_LABEL, aa_formats[i]->formatname, + -1); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), selected, + G_CALLBACK (gimp_int_combo_box_get_active), + &selected); + + gtk_widget_show (dialog); + + if (gimp_dialog_run (GIMP_DIALOG (dialog)) != GTK_RESPONSE_OK) + selected = -1; + + gtk_widget_destroy (dialog); + + return selected; +} diff --git a/plug-ins/common/file-cel.c b/plug-ins/common/file-cel.c new file mode 100644 index 0000000..95706c1 --- /dev/null +++ b/plug-ins/common/file-cel.c @@ -0,0 +1,975 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * cel.c -- KISS CEL file format plug-in + * (copyright) 1997,1998 Nick Lamb (njl195@zepler.org.uk) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-cel-load" +#define SAVE_PROC "file-cel-save" +#define PLUG_IN_BINARY "file-cel" +#define PLUG_IN_ROLE "gimp-file-cel" + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint load_palette (const gchar *file, + FILE *fp, + guchar palette[], + GError **error); +static gint32 load_image (const gchar *file, + GError **error); +static gboolean save_image (GFile *file, + gint32 image, + gint32 layer, + GError **error); +static void palette_dialog (const gchar *title); +static gboolean need_palette (const gchar *file, + GError **error); + + +/* Globals... */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static gchar *palette_file = NULL; +static gsize data_length = 0; + +/* Let GIMP library handle initialisation (and inquisitive users) */ + +MAIN () + +/* GIMP queries plug-in for parameters etc. */ + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "Filename to load image from" }, + { GIMP_PDB_STRING, "raw-filename", "Name entered" }, + { GIMP_PDB_STRING, "palette-filename", "Filename to load palette from" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "Filename to export image to" }, + { GIMP_PDB_STRING, "raw-filename", "Name entered" }, + { GIMP_PDB_STRING, "palette-filename", "Filename to save palette to" }, + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in KISS CEL file format", + "This plug-in loads individual KISS cell files.", + "Nick Lamb", + "Nick Lamb ", + "May 1998", + N_("KISS CEL"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_magic_load_handler (LOAD_PROC, + "cel", + "", + "0,string,KiSS\\040"); + + gimp_install_procedure (SAVE_PROC, + "Exports files in KISS CEL file format", + "This plug-in exports individual KISS cell files.", + "Nick Lamb", + "Nick Lamb ", + "May 1998", + N_("KISS CEL"), + "RGB*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "cel", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; /* Return values */ + GimpRunMode run_mode; + gint32 image_ID; + gint32 drawable_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + gint needs_palette = 0; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + /* Set up default return values */ + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + if (run_mode != GIMP_RUN_NONINTERACTIVE) + { + data_length = gimp_get_data_size (SAVE_PROC); + if (data_length > 0) + { + palette_file = g_malloc (data_length); + gimp_get_data (SAVE_PROC, palette_file); + } + else + { + palette_file = g_strdup ("*.kcf"); + data_length = strlen (palette_file) + 1; + } + } + + if (run_mode == GIMP_RUN_NONINTERACTIVE) + { + palette_file = param[3].data.d_string; + if (palette_file) + data_length = strlen (palette_file) + 1; + else + data_length = 0; + } + else if (run_mode == GIMP_RUN_INTERACTIVE) + { + /* Let user choose KCF palette (cancel ignores) */ + needs_palette = need_palette (param[1].data.d_string, &error); + + if (! error) + { + if (needs_palette) + palette_dialog (_("Load KISS Palette")); + + gimp_set_data (SAVE_PROC, palette_file, data_length); + } + } + + if (! error) + { + image = load_image (param[1].data.d_string, + &error); + + if (image != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "CEL", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_ALPHA | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + if (save_image (g_file_new_for_uri (param[3].data.d_string), + image_ID, drawable_ID, &error)) + { + if (data_length) + { + gimp_set_data (SAVE_PROC, palette_file, data_length); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +/* Peek into the file to determine whether we need a palette */ +static gboolean +need_palette (const gchar *file, + GError **error) +{ + FILE *fp; + guchar header[32]; + size_t n_read; + + fp = g_fopen (file, "rb"); + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (file), g_strerror (errno)); + return FALSE; + } + + n_read = fread (header, 32, 1, fp); + + fclose (fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("EOF or error while reading image header")); + return FALSE; + } + + return (header[5] < 32); +} + +/* Load CEL image into GIMP */ + +static gint32 +load_image (const gchar *file, + GError **error) +{ + FILE *fp; /* Read file pointer */ + guchar header[32], /* File header */ + file_mark, /* KiSS file type */ + bpp; /* Bits per pixel */ + gint height, width, /* Dimensions of image */ + offx, offy, /* Layer offsets */ + colors; /* Number of colors */ + + gint32 image, /* Image */ + layer; /* Layer */ + guchar *buf; /* Temporary buffer */ + guchar *line; /* Pixel data */ + GeglBuffer *buffer; /* Buffer for layer */ + + gint i, j, k; /* Counters */ + size_t n_read; /* Number of items read from file */ + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (file)); + + /* Open the file for reading */ + fp = g_fopen (file, "r"); + + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (file), g_strerror (errno)); + return -1; + } + + /* Get the image dimensions and create the image... */ + + n_read = fread (header, 4, 1, fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("EOF or error while reading image header")); + fclose (fp); + return -1; + } + + if (strncmp ((const gchar *) header, "KiSS", 4)) + { + colors= 16; + bpp = 4; + width = header[0] + (256 * header[1]); + height = header[2] + (256 * header[3]); + offx= 0; + offy= 0; + } + else + { /* New-style image file, read full header */ + n_read = fread (header, 28, 1, fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("EOF or error while reading image header")); + fclose (fp); + return -1; + } + + file_mark = header[0]; + if (file_mark != 0x20 && file_mark != 0x21) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("is not a CEL image file")); + fclose (fp); + return -1; + } + + bpp = header[1]; + switch (bpp) + { + case 4: + case 8: + case 32: + colors = (1 << bpp); + break; + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("illegal bpp value in image: %hhu"), bpp); + fclose (fp); + return -1; + } + + width = header[4] + (256 * header[5]); + height = header[6] + (256 * header[7]); + offx = header[8] + (256 * header[9]); + offy = header[10] + (256 * header[11]); + } + + if ((width == 0) || (height == 0) || (width + offx > GIMP_MAX_IMAGE_SIZE) || + (height + offy > GIMP_MAX_IMAGE_SIZE)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("illegal image dimensions: width: %d, horizontal offset: " + "%d, height: %d, vertical offset: %d"), + width, offx, height, offy); + fclose (fp); + return -1; + } + + if (bpp == 32) + image = gimp_image_new (width + offx, height + offy, GIMP_RGB); + else + image = gimp_image_new (width + offx, height + offy, GIMP_INDEXED); + + if (image == -1) + { + g_set_error (error, 0, 0, _("Can't create a new image")); + fclose (fp); + return -1; + } + + gimp_image_set_filename (image, file); + + /* Create an indexed-alpha layer to hold the image... */ + if (bpp == 32) + layer = gimp_layer_new (image, _("Background"), width, height, + GIMP_RGBA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image)); + else + layer = gimp_layer_new (image, _("Background"), width, height, + GIMP_INDEXEDA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image)); + gimp_image_insert_layer (image, layer, -1, 0); + gimp_layer_set_offsets (layer, offx, offy); + + /* Get the drawable and set the pixel region for our load... */ + + buffer = gimp_drawable_get_buffer (layer); + + /* Read the image in and give it to GIMP a line at a time */ + buf = g_new (guchar, width * 4); + line = g_new (guchar, (width + 1) * 4); + + for (i = 0; i < height && !feof(fp); ++i) + { + switch (bpp) + { + case 4: + n_read = fread (buf, (width + 1) / 2, 1, fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("EOF or error while reading image data")); + fclose (fp); + return -1; + } + + for (j = 0, k = 0; j < width * 2; j+= 4, ++k) + { + if (buf[k] / 16 == 0) + { + line[j] = 16; + line[j+ 1 ] = 0; + } + else + { + line[j] = (buf[k] / 16) - 1; + line[j + 1] = 255; + } + + if (buf[k] % 16 == 0) + { + line[j + 2] = 16; + line[j + 3] = 0; + } + else + { + line[j + 2] = (buf[k] % 16) - 1; + line[j + 3] = 255; + } + } + break; + + case 8: + n_read = fread (buf, width, 1, fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("EOF or error while reading image data")); + fclose (fp); + return -1; + } + + for (j = 0, k = 0; j < width * 2; j+= 2, ++k) + { + if (buf[k] == 0) + { + line[j] = 255; + line[j + 1] = 0; + } + else + { + line[j] = buf[k] - 1; + line[j + 1] = 255; + } + } + break; + + case 32: + n_read = fread (line, width * 4, 1, fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("EOF or error while reading image data")); + fclose (fp); + return -1; + } + + /* The CEL file order is BGR so we need to swap B and R + * to get the Gimp RGB order. + */ + for (j= 0; j < width; j++) + { + guint8 tmp = line[j*4]; + line[j*4] = line[j*4+2]; + line[j*4+2] = tmp; + } + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported bit depth (%d)!"), bpp); + fclose (fp); + return -1; + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i, width, 1), 0, + NULL, line, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((float) i / (float) height); + } + + /* Close image files, give back allocated memory */ + + fclose (fp); + g_free (buf); + g_free (line); + + if (bpp != 32) + { + /* Use palette from file or otherwise default grey palette */ + guchar palette[256 * 3]; + + /* Open the file for reading if user picked one */ + if (palette_file == NULL) + { + fp = NULL; + } + else + { + fp = g_fopen (palette_file, "r"); + + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (palette_file), + g_strerror (errno)); + return -1; + } + } + + if (fp != NULL) + { + colors = load_palette (palette_file, fp, palette, error); + fclose (fp); + if (colors < 0 || *error) + return -1; + } + else + { + for (i= 0; i < colors; ++i) + { + palette[i * 3] = palette[i * 3 + 1] = palette[i * 3 + 2]= i * 256 / colors; + } + } + + gimp_image_set_colormap (image, palette + 3, colors - 1); + } + + /* Now get everything redrawn and hand back the finished image */ + + g_object_unref (buffer); + + gimp_progress_update (1.0); + + return image; +} + +static gint +load_palette (const gchar *file, + FILE *fp, + guchar palette[], + GError **error) +{ + guchar header[32]; /* File header */ + guchar buffer[2]; + guchar file_mark, bpp; + gint i, colors = 0; + size_t n_read; + + n_read = fread (header, 4, 1, fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': EOF or error while reading palette header"), + gimp_filename_to_utf8 (file)); + return -1; + } + + if (!strncmp ((const gchar *) header, "KiSS", 4)) + { + n_read = fread (header+4, 28, 1, fp); + + if (n_read < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': EOF or error while reading palette header"), + gimp_filename_to_utf8 (file)); + return -1; + } + + file_mark = header[4]; + if (file_mark != 0x10) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': is not a KCF palette file"), + gimp_filename_to_utf8 (file)); + return -1; + } + + bpp = header[5]; + if (bpp != 12 && bpp != 24) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': illegal bpp value in palette: %hhu"), + gimp_filename_to_utf8 (file), bpp); + return -1; + } + + colors = header[8] + header[9] * 256; + if (colors != 16 && colors != 256) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': illegal number of colors: %u"), + gimp_filename_to_utf8 (file), colors); + return -1; + } + + switch (bpp) + { + case 12: + for (i = 0; i < colors; ++i) + { + n_read = fread (buffer, 1, 2, fp); + + if (n_read < 2) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': EOF or error while reading " + "palette data"), + gimp_filename_to_utf8 (file)); + return -1; + } + + palette[i*3]= buffer[0] & 0xf0; + palette[i*3+1]= (buffer[1] & 0x0f) * 16; + palette[i*3+2]= (buffer[0] & 0x0f) * 16; + } + break; + case 24: + n_read = fread (palette, colors, 3, fp); + + if (n_read < 3) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': EOF or error while reading palette data"), + gimp_filename_to_utf8 (file)); + return -1; + } + break; + default: + g_assert_not_reached (); + } + } + else + { + colors = 16; + fseek (fp, 0, SEEK_SET); + for (i= 0; i < colors; ++i) + { + n_read = fread (buffer, 1, 2, fp); + + if (n_read < 2) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s': EOF or error while reading palette data"), + gimp_filename_to_utf8 (file)); + return -1; + } + + palette[i*3] = buffer[0] & 0xf0; + palette[i*3+1] = (buffer[1] & 0x0f) * 16; + palette[i*3+2] = (buffer[0] & 0x0f) * 16; + } + } + + return colors; +} + +static gboolean +save_image (GFile *file, + gint32 image, + gint32 layer, + GError **error) +{ + GOutputStream *output; + GeglBuffer *buffer; + const Babl *format; + GCancellable *cancellable; + gint width; + gint height; + guchar header[32]; /* File header */ + gint bpp; /* Bit per pixel */ + gint colors; /* Number of colors */ + gint type; /* type of layer */ + gint offx, offy; /* Layer offsets */ + guchar *buf = NULL; /* Temporary buffer */ + guchar *line = NULL; /* Pixel data */ + gint i, j, k; /* Counters */ + + /* Check that this is an indexed image, fail otherwise */ + type = gimp_drawable_type (layer); + + if (type == GIMP_INDEXEDA_IMAGE) + { + bpp = 4; + format = NULL; + } + else + { + bpp = 32; + format = babl_format ("R'G'B'A u8"); + } + + /* Find out how offset this layer was */ + gimp_drawable_offsets (layer, &offx, &offy); + + buffer = gimp_drawable_get_buffer (layer); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_file_get_utf8_name (file)); + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (output) + { + GOutputStream *buffered; + + buffered = g_buffered_output_stream_new (output); + g_object_unref (output); + + output = buffered; + } + else + { + return FALSE; + } + + /* Headers */ + memset (header, 0, 32); + strcpy ((gchar *) header, "KiSS"); + header[4]= 0x20; + + /* Work out whether to save as 8bit or 4bit */ + if (bpp < 32) + { + g_free (gimp_image_get_colormap (image, &colors)); + + if (colors > 15) + { + header[5] = 8; + } + else + { + header[5] = 4; + } + } + else + { + header[5] = 32; + } + + /* Fill in the blanks ... */ + header[8] = width % 256; + header[9] = width / 256; + header[10] = height % 256; + header[11] = height / 256; + header[12] = offx % 256; + header[13] = offx / 256; + header[14] = offy % 256; + header[15] = offy / 256; + + if (! g_output_stream_write_all (output, header, 32, NULL, + NULL, error)) + goto fail; + + /* Arrange for memory etc. */ + buf = g_new (guchar, width * 4); + line = g_new (guchar, (width + 1) * 4); + + /* Get the image from GIMP one line at a time and write it out */ + for (i = 0; i < height; ++i) + { + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, 1), 1.0, + format, line, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + memset (buf, 0, width); + + if (bpp == 32) + { + for (j = 0; j < width; j++) + { + buf[4 * j] = line[4 * j + 2]; /* B */ + buf[4 * j + 1] = line[4 * j + 1]; /* G */ + buf[4 * j + 2] = line[4 * j + 0]; /* R */ + buf[4 * j + 3] = line[4 * j + 3]; /* Alpha */ + } + + if (! g_output_stream_write_all (output, buf, width * 4, NULL, + NULL, error)) + goto fail; + } + else if (colors > 16) + { + for (j = 0, k = 0; j < width * 2; j += 2, ++k) + { + if (line[j + 1] > 127) + { + buf[k]= line[j] + 1; + } + } + + if (! g_output_stream_write_all (output, buf, width, NULL, + NULL, error)) + goto fail; + } + else + { + for (j = 0, k = 0; j < width * 2; j+= 4, ++k) + { + buf[k] = 0; + + if (line[j + 1] > 127) + { + buf[k] += (line[j] + 1)<< 4; + } + + if (line[j + 3] > 127) + { + buf[k] += (line[j + 2] + 1); + } + } + + if (! g_output_stream_write_all (output, buf, width + 1 / 2, NULL, + NULL, error)) + goto fail; + } + + gimp_progress_update ((float) i / (float) height); + } + + if (! g_output_stream_close (output, NULL, error)) + goto fail; + + gimp_progress_update (1.0); + + g_free (buf); + g_free (line); + g_object_unref (buffer); + g_object_unref (output); + + return TRUE; + + fail: + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + + g_free (buf); + g_free (line); + g_object_unref (buffer); + g_object_unref (output); + + return FALSE; +} + +static void +palette_dialog (const gchar *title) +{ + GtkWidget *dialog; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gtk_file_chooser_dialog_new (title, NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), palette_file); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + gtk_widget_show (dialog); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) + { + g_free (palette_file); + palette_file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + data_length = strlen (palette_file) + 1; + } + + gtk_widget_destroy (dialog); +} diff --git a/plug-ins/common/file-compressor.c b/plug-ins/common/file-compressor.c new file mode 100644 index 0000000..a51da9b --- /dev/null +++ b/plug-ins/common/file-compressor.c @@ -0,0 +1,1017 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * Copyright (C) 1997 Daniel Risacher, magnus@alum.mit.edu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Minor changes to support file magic */ +/* 4 Oct 1997 -- Risacher */ + +/* compressor plug-in for GIMP */ +/* based on gz.c which in turn is */ +/* loosley based on url.c by */ +/* Josh MacDonald, jmacd@cs.berkeley.edu */ + +/* and, very loosely on hrz.c by */ +/* Albert Cahalan */ + +/* LZMA compression code is based on code by Lasse Collin which was + * placed in the public-domain. */ + +/* This is reads and writes compressed image files for GIMP + * + * It should work with file names of the form + * filename.foo.[gz|bz2] where foo is some already-recognized extension + * + * and it also works for names of the form + * filename.xcf[gz|bz2] - which is equivalent to + * filename.xcf.[gz|bz2] + * + * I added the xcfgz bit because having a default extension of xcf.gz + * can confuse the file selection dialog box somewhat, forcing the + * user to type sometimes when he/she otherwise wouldn't need to. + * + * I later decided I didn't like it because I don't like to bloat + * the file-extension namespace. But I left in the recognition + * feature/bug so if people want to use files named foo.xcfgz by + * default, they can just hack their pluginrc file. + * + * to do this hack, change : + * "xcf.gz,gz,xcfgz" + * to + * "xcfgz,gz,xcf.gz" + * + * + * -Dan Risacher, 0430 CDT, 26 May 1997 + */ + +#include "config.h" + +#include +#include + +#include + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#ifndef _O_BINARY +#define _O_BINARY 0 +#endif + +#include + +#include "libgimp/stdplugins-intl.h" + +#include +#include +#include + + +/* Author 1: Josh MacDonald (url.c) */ +/* Author 2: Daniel Risacher (gz.c) */ +/* Author 3: Michael Natterer (compressor.c) */ + +/* According to USAF Lt Steve Werhle, US DoD software development + * contracts average about $25 USD per source line of code (SLOC). By + * that metric, I figure this plug-in is worth about $10,000 USD */ +/* But you got it free. Magic of Gnu. */ + +typedef gboolean (*LoadFn) (const char *infile, + const char *outfile); +typedef gboolean (*SaveFn) (const char *infile, + const char *outfile); + +typedef struct _Compressor Compressor; + +struct _Compressor +{ + const gchar *file_type; + const gchar *mime_type; + const gchar *extensions; + const gchar *magic; + const gchar *xcf_extension; + const gchar *generic_extension; + + const gchar *load_proc; + const gchar *load_blurb; + const gchar *load_help; + LoadFn load_fn; + + const gchar *save_proc; + const gchar *save_blurb; + const gchar *save_help; + SaveFn save_fn; +}; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static GimpPDBStatusType save_image (const Compressor *compressor, + const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 run_mode, + GError **error); +static gint32 load_image (const Compressor *compressor, + const gchar *filename, + gint32 run_mode, + GimpPDBStatusType *status, + GError **error); + +static gboolean valid_file (const gchar *filename); +static const gchar * find_extension (const Compressor *compressor, + const gchar *filename); + +static gboolean gzip_load (const char *infile, + const char *outfile); +static gboolean gzip_save (const char *infile, + const char *outfile); + +static gboolean bzip2_load (const char *infile, + const char *outfile); +static gboolean bzip2_save (const char *infile, + const char *outfile); + +static gboolean xz_load (const char *infile, + const char *outfile); +static gboolean xz_save (const char *infile, + const char *outfile); +static goffset get_file_info (const gchar *filename); + + +static const Compressor compressors[] = +{ + { + N_("gzip archive"), + "application/x-gzip", + "xcf.gz,xcfgz", /* FIXME "xcf.gz,gz,xcfgz" */ + "0,string,\037\213", + ".xcfgz", + ".gz", + + "file-gz-load", + "loads files compressed with gzip", + "This procedure loads files in the gzip compressed format.", + gzip_load, + + "file-gz-save", + "saves files compressed with gzip", + "This procedure saves files in the gzip compressed format.", + gzip_save + }, + + { + N_("bzip archive"), + "application/x-bzip", + "xcf.bz2,xcfbz2", /* FIXME "xcf.bz2,bz2,xcfbz2" */ + "0,string,BZh", + ".xcfbz2", + ".bz2", + + "file-bz2-load", + "loads files compressed with bzip2", + "This procedure loads files in the bzip2 compressed format.", + bzip2_load, + + "file-bz2-save", + "saves files compressed with bzip2", + "This procedure saves files in the bzip2 compressed format.", + bzip2_save + }, + + { + N_("xz archive"), + "application/x-xz", + "xcf.xz,xcfxz", /* FIXME "xcf.xz,xz,xcfxz" */ + "0,string,\3757zXZ\x00", + ".xcfxz", + ".xz", + + "file-xz-load", + "loads files compressed with xz", + "This procedure loads files in the xz compressed format.", + xz_load, + + "file-xz-save", + "saves files compressed with xz", + "This procedure saves files in the xz compressed format.", + xz_save + } +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" }, + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to " + "save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + }; + + gint i; + + for (i = 0; i < G_N_ELEMENTS (compressors); i++) + { + const Compressor *compressor = &compressors[i]; + + gimp_install_procedure (compressor->load_proc, + compressor->load_blurb, + compressor->load_help, + "Daniel Risacher", + "Daniel Risacher, Spencer Kimball and Peter Mattis", + "1995-1997", + compressor->file_type, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (compressor->load_proc, + compressor->mime_type); + gimp_register_magic_load_handler (compressor->load_proc, + compressor->extensions, + "", + compressor->magic); + + gimp_install_procedure (compressor->save_proc, + compressor->save_blurb, + compressor->save_help, + "Daniel Risacher", + "Daniel Risacher, Spencer Kimball and Peter Mattis", + "1995-1997", + compressor->file_type, + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (compressor->save_proc, + compressor->mime_type); + gimp_register_save_handler (compressor->save_proc, + compressor->extensions, ""); + } +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + gint32 image_ID; + gint i; + + run_mode = param[0].data.d_int32; + + INIT_I18N(); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + /* We handle PDB errors by forwarding them to the caller in + * our return values. + */ + gimp_plugin_set_pdb_error_handler (GIMP_PDB_ERROR_HANDLER_PLUGIN); + + for (i = 0; i < G_N_ELEMENTS (compressors); i++) + { + const Compressor *compressor = &compressors[i]; + + if (! strcmp (name, compressor->load_proc)) + { + image_ID = load_image (compressor, + param[1].data.d_string, + param[0].data.d_int32, + &status, &error); + + if (image_ID != -1 && status == GIMP_PDB_SUCCESS) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + + break; + } + else if (! strcmp (name, compressor->save_proc)) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + break; + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + break; + case GIMP_RUN_WITH_LAST_VALS: + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + status = save_image (compressor, + param[3].data.d_string, + param[1].data.d_int32, + param[2].data.d_int32, + param[0].data.d_int32, + &error); + + break; + } + } + + if (i == G_N_ELEMENTS (compressors)) + status = GIMP_PDB_CALLING_ERROR; + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static GimpPDBStatusType +save_image (const Compressor *compressor, + const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 run_mode, + GError **error) +{ + const gchar *ext; + gchar *tmpname; + + ext = find_extension (compressor, filename); + + if (! ext) + { + g_message (_("No sensible file extension, saving as compressed XCF.")); + ext = ".xcf"; + } + + /* get a temp name with the right extension and save into it. */ + + tmpname = gimp_temp_name (ext + 1); + + if (! (gimp_file_save (run_mode, + image_ID, + drawable_ID, + tmpname, + tmpname) && valid_file (tmpname))) + { + g_unlink (tmpname); + g_free (tmpname); + + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", gimp_get_pdb_error ()); + + return GIMP_PDB_EXECUTION_ERROR; + } + + gimp_progress_init_printf (_("Compressing '%s'"), + gimp_filename_to_utf8 (filename)); + + if (!compressor->save_fn (tmpname, filename)) + { + g_unlink (tmpname); + g_free (tmpname); + + return GIMP_PDB_EXECUTION_ERROR; + } + + g_unlink (tmpname); + gimp_progress_update (1.0); + g_free (tmpname); + + /* ask the core to save a thumbnail for compressed XCF files */ + if (strcmp (ext, ".xcf") == 0) + gimp_file_save_thumbnail (image_ID, filename); + + return GIMP_PDB_SUCCESS; +} + +static gint32 +load_image (const Compressor *compressor, + const gchar *filename, + gint32 run_mode, + GimpPDBStatusType *status, + GError **error) +{ + gint32 image_ID; + const gchar *ext; + gchar *tmpname; + + ext = find_extension (compressor, filename); + + if (! ext) + { + g_message (_("No sensible file extension, " + "attempting to load with file magic.")); + ext = ".foo"; + } + + /* find a temp name */ + tmpname = gimp_temp_name (ext + 1); + + if (!compressor->load_fn (filename, tmpname)) + { + g_free (tmpname); + *status = GIMP_PDB_EXECUTION_ERROR; + return -1; + } + + /* now that we uncompressed it, load the temp file */ + + image_ID = gimp_file_load (run_mode, tmpname, tmpname); + + g_unlink (tmpname); + g_free (tmpname); + + if (image_ID != -1) + { + *status = GIMP_PDB_SUCCESS; + + gimp_image_set_filename (image_ID, filename); + } + else + { + /* Forward the return status of the underlining plug-in for the + * given format. + */ + *status = gimp_get_pdb_status (); + + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", gimp_get_pdb_error ()); + } + + return image_ID; +} + +static gboolean +valid_file (const gchar *filename) +{ + GStatBuf buf; + + return g_stat (filename, &buf) == 0 && buf.st_size > 0; +} + +static const gchar * +find_extension (const Compressor *compressor, + const gchar *filename) +{ + gchar *filename_copy; + gchar *ext; + + /* we never free this copy - aren't we evil! */ + filename_copy = g_strdup (filename); + + /* find the extension, boy! */ + ext = strrchr (filename_copy, '.'); + + while (TRUE) + { + if (!ext || ext[1] == '\0' || strchr (ext, G_DIR_SEPARATOR)) + { + return NULL; + } + + if (0 == g_ascii_strcasecmp (ext, compressor->xcf_extension)) + { + return ".xcf"; /* we've found it */ + } + if (0 != g_ascii_strcasecmp (ext, compressor->generic_extension)) + { + return ext; + } + else + { + /* we found ".gz" so strip it, loop back, and look again */ + *ext = '\0'; + ext = strrchr (filename_copy, '.'); + } + } +} + +static gboolean +gzip_load (const char *infile, + const char *outfile) +{ + gboolean ret; + int fd; + gzFile in; + FILE *out; + char buf[16384]; + int len; + + ret = FALSE; + in = NULL; + out = NULL; + + fd = g_open (infile, O_RDONLY | _O_BINARY, 0); + if (fd == -1) + goto out; + + in = gzdopen (fd, "rb"); + if (!in) + { + close (fd); + goto out; + } + + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + while (TRUE) + { + len = gzread (in, buf, sizeof buf); + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (fwrite(buf, 1, len, out) != len) + break; + } + + out: + /* There is no need to close(fd) as it is closed by gzclose(). */ + if (in) + if (gzclose (in) != Z_OK) + ret = FALSE; + + if (out) + fclose (out); + + return ret; +} + +static gboolean +gzip_save (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + int fd; + gzFile out; + char buf[16384]; + int len; + goffset tot = 0, file_size; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + fd = g_open (outfile, O_CREAT | O_WRONLY | O_TRUNC | _O_BINARY, 0664); + if (fd == -1) + goto out; + + out = gzdopen (fd, "wb"); + if (!out) + { + close (fd); + goto out; + } + + file_size = get_file_info (infile); + while (TRUE) + { + len = fread (buf, 1, sizeof buf, in); + if (ferror (in)) + break; + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (gzwrite (out, buf, len) != len) + break; + + gimp_progress_update ((tot += len) * 1.0 / file_size); + } + + out: + if (in) + fclose (in); + + /* There is no need to close(fd) as it is closed by gzclose(). */ + if (out) + if (gzclose (out) != Z_OK) + ret = FALSE; + + return ret; +} + +static gboolean +bzip2_load (const char *infile, + const char *outfile) +{ + gboolean ret; + int fd; + BZFILE *in; + FILE *out; + char buf[16384]; + int len; + + ret = FALSE; + in = NULL; + out = NULL; + + fd = g_open (infile, O_RDONLY | _O_BINARY, 0); + if (fd == -1) + goto out; + + in = BZ2_bzdopen (fd, "rb"); + if (!in) + { + close (fd); + goto out; + } + + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + while (TRUE) + { + len = BZ2_bzread (in, buf, sizeof buf); + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (fwrite(buf, 1, len, out) != len) + break; + } + + out: + /* TODO: Check this in the case of BZ2_bzclose(): */ + /* There is no need to close(fd) as it is closed by BZ2_bzclose(). */ + if (in) + BZ2_bzclose (in); + + if (out) + fclose (out); + + return ret; +} + +static gboolean +bzip2_save (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + int fd; + BZFILE *out; + char buf[16384]; + int len; + goffset tot = 0, file_size; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + fd = g_open (outfile, O_CREAT | O_WRONLY | O_TRUNC | _O_BINARY, 0664); + if (fd == -1) + goto out; + + out = BZ2_bzdopen (fd, "wb"); + if (!out) + { + close (fd); + goto out; + } + + file_size = get_file_info (infile); + while (TRUE) + { + len = fread (buf, 1, sizeof buf, in); + if (ferror (in)) + break; + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (BZ2_bzwrite (out, buf, len) != len) + break; + + gimp_progress_update ((tot += len) * 1.0 / file_size); + } + + out: + if (in) + fclose (in); + + /* TODO: Check this in the case of BZ2_bzclose(): */ + /* There is no need to close(fd) as it is closed by BZ2_bzclose(). */ + if (out) + BZ2_bzclose (out); + + return ret; +} + +static gboolean +xz_load (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + FILE *out; + lzma_stream strm = LZMA_STREAM_INIT; + lzma_action action; + guint8 inbuf[BUFSIZ]; + guint8 outbuf[BUFSIZ]; + lzma_ret status; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + if (lzma_stream_decoder (&strm, UINT64_MAX, 0) != LZMA_OK) + goto out; + + strm.next_in = NULL; + strm.avail_in = 0; + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + + action = LZMA_RUN; + status = LZMA_OK; + + while (status == LZMA_OK) + { + /* Fill the input buffer if it is empty. */ + if ((strm.avail_in == 0) && (!feof(in))) + { + strm.next_in = inbuf; + strm.avail_in = fread (inbuf, 1, sizeof inbuf, in); + + if (ferror (in)) + goto out; + + /* Once the end of the input file has been reached, we need to + tell lzma_code() that no more input will be coming and that + it should finish the encoding. */ + if (feof (in)) + action = LZMA_FINISH; + } + + status = lzma_code (&strm, action); + + if ((strm.avail_out == 0) || (status == LZMA_STREAM_END)) + { + /* When lzma_code() has returned LZMA_STREAM_END, the output + buffer is likely to be only partially full. Calculate how + much new data there is to be written to the output file. */ + size_t write_size = sizeof outbuf - strm.avail_out; + + if (fwrite (outbuf, 1, write_size, out) != write_size) + goto out; + + /* Reset next_out and avail_out. */ + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + } + } + + if (status != LZMA_STREAM_END) + goto out; + + lzma_end (&strm); + ret = TRUE; + + out: + if (in) + fclose (in); + + if (out) + fclose (out); + + return ret; +} + +static gboolean +xz_save (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + FILE *out; + lzma_stream strm = LZMA_STREAM_INIT; + lzma_action action; + guint8 inbuf[BUFSIZ]; + guint8 outbuf[BUFSIZ]; + lzma_ret status; + goffset tot = 0, file_size; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + file_size = get_file_info (infile); + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + if (lzma_easy_encoder (&strm, + LZMA_PRESET_DEFAULT, + LZMA_CHECK_CRC64) != LZMA_OK) + goto out; + + strm.next_in = NULL; + strm.avail_in = 0; + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + + action = LZMA_RUN; + status = LZMA_OK; + + while (status == LZMA_OK) + { + /* Fill the input buffer if it is empty. */ + if ((strm.avail_in == 0) && (!feof(in))) + { + strm.next_in = inbuf; + strm.avail_in = fread (inbuf, 1, sizeof inbuf, in); + + if (ferror (in)) + goto out; + + /* Once the end of the input file has been reached, we need to + tell lzma_code() that no more input will be coming and that + it should finish the encoding. */ + if (feof (in)) + action = LZMA_FINISH; + + gimp_progress_update ((tot += strm.avail_in) * 1.0 / file_size); + } + + status = lzma_code (&strm, action); + + if ((strm.avail_out == 0) || (status == LZMA_STREAM_END)) + { + /* When lzma_code() has returned LZMA_STREAM_END, the output + buffer is likely to be only partially full. Calculate how + much new data there is to be written to the output file. */ + size_t write_size = sizeof outbuf - strm.avail_out; + + if (fwrite (outbuf, 1, write_size, out) != write_size) + goto out; + + /* Reset next_out and avail_out. */ + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + } + } + + if (status != LZMA_STREAM_END) + goto out; + + lzma_end (&strm); + ret = TRUE; + + out: + if (in) + fclose (in); + + if (out) + fclose (out); + + return ret; +} + +/* get file size from a filename */ +static goffset +get_file_info (const gchar *filename) +{ + GFile *file = g_file_new_for_path (filename); + GFileInfo *info; + goffset size = 1; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (info) + { + size = g_file_info_get_size (info); + + g_object_unref (info); + } + + g_object_unref (file); + + return size; +} diff --git a/plug-ins/common/file-csource.c b/plug-ins/common/file-csource.c new file mode 100644 index 0000000..2b79148 --- /dev/null +++ b/plug-ins/common/file-csource.c @@ -0,0 +1,1045 @@ +/* CSource - GIMP Plugin to dump image data in RGB(A) format for C source + * Copyright (C) 1999 Tim Janik + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This plugin is heavily based on the header plugin by Spencer Kimball and + * Peter Mattis. + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-csource-save" +#define PLUG_IN_BINARY "file-csource" +#define PLUG_IN_ROLE "gimp-file-csource" + + +typedef struct +{ + gchar *prefixed_name; + gchar *comment; + gboolean use_comment; + gboolean glib_types; + gboolean alpha; + gboolean rgb565; + gboolean use_macros; + gboolean use_rle; + gdouble opacity; +} Config; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean save_image (GFile *file, + Config *config, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static gboolean run_save_dialog (Config *config); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static Config config = +{ + "gimp_image", /* prefixed_name */ + NULL, /* comment */ + FALSE, /* use_comment */ + TRUE, /* glib_types */ + FALSE, /* alpha */ + FALSE, /* rgb565 */ + FALSE, /* use_macros */ + FALSE, /* use_rle */ + 100.0, /* opacity */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" } + }; + + gimp_install_procedure (SAVE_PROC, + "Dump image data in RGB(A) format for C source", + "CSource cannot be run non-interactively.", + "Tim Janik", + "Tim Janik", + "1999", + N_("C source code"), + "*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "text/x-csrc"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "c", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (run_mode == GIMP_RUN_INTERACTIVE && + strcmp (name, SAVE_PROC) == 0) + { + gint32 image_ID = param[1].data.d_int32; + gint32 drawable_ID = param[2].data.d_int32; + GimpParasite *parasite; + gchar *x; + + gimp_get_data (SAVE_PROC, &config); + + config.prefixed_name = "gimp_image"; + config.comment = NULL; + config.alpha = gimp_drawable_has_alpha (drawable_ID); + + parasite = gimp_image_get_parasite (image_ID, "gimp-comment"); + if (parasite) + { + config.comment = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + gimp_parasite_free (parasite); + } + x = config.comment; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "C Source", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + + if (run_save_dialog (&config)) + { + if (x != config.comment && + !(x && config.comment && strcmp (x, config.comment) == 0)) + { + if (!config.comment || !config.comment[0]) + { + gimp_image_detach_parasite (image_ID, "gimp-comment"); + } + else + { + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (config.comment) + 1, + config.comment); + gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + } + } + + if (! save_image (g_file_new_for_uri (param[3].data.d_string), + &config, image_ID, drawable_ID, &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + + if (error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + else + { + gimp_set_data (SAVE_PROC, &config, sizeof (config)); + } + } + else + { + status = GIMP_PDB_CANCEL; + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].data.d_status = status; +} + +static gboolean +diff2_rgb565 (guint8 *ip) +{ + return ip[0] != ip[2] || ip[1] != ip[3]; +} + +static gboolean +diff2_rgb (guint8 *ip) +{ + return ip[0] != ip[3] || ip[1] != ip[4] || ip[2] != ip[5]; +} + +static gboolean +diff2_rgba (guint8 *ip) +{ + return ip[0] != ip[4] || ip[1] != ip[5] || ip[2] != ip[6] || ip[3] != ip[7]; +} + +static guint8 * +rl_encode_rgbx (guint8 *bp, + guint8 *ip, + guint8 *limit, + guint bpp) +{ + gboolean (*diff2_pix) (guint8 *); + guint8 *ilimit = limit - bpp; + + switch (bpp) + { + case 2: diff2_pix = diff2_rgb565; break; + case 3: diff2_pix = diff2_rgb; break; + case 4: diff2_pix = diff2_rgba; break; + default: g_assert_not_reached (); + } + + while (ip < limit) + { + g_assert (ip < ilimit); /* paranoid */ + + if (diff2_pix (ip)) + { + guint8 *s_ip = ip; + guint l = 1; + + ip += bpp; + while (l < 127 && ip < ilimit && diff2_pix (ip)) + { ip += bpp; l += 1; } + if (ip == ilimit && l < 127) + { ip += bpp; l += 1; } + *(bp++) = l; + memcpy (bp, s_ip, l * bpp); + bp += l * bpp; + } + else + { + guint l = 2; + + ip += bpp; + while (l < 127 && ip < ilimit && !diff2_pix (ip)) + { ip += bpp; l += 1; } + *(bp++) = l | 128; + memcpy (bp, ip, bpp); + ip += bpp; + bp += bpp; + } + if (ip == ilimit) + { + *(bp++) = 1; + memcpy (bp, ip, bpp); + ip += bpp; + bp += bpp; + } + } + + return bp; +} + +static gboolean print (GOutputStream *stream, + GError **error, + const gchar *format, + ...) G_GNUC_PRINTF (3, 4); + +static gboolean +print (GOutputStream *stream, + GError **error, + const gchar *format, + ...) +{ + va_list args; + gboolean success; + + va_start (args, format); + success = g_output_stream_vprintf (stream, NULL, NULL, + error, format, args); + va_end (args); + + return success; +} + +static inline gboolean +save_rle_decoder (GOutputStream *output, + const gchar *macro_name, + const gchar *s_uint, + const gchar *s_uint_8, + guint bpp, + GError **error) +{ + return + print (output, error, + "#define %s_RUN_LENGTH_DECODE(image_buf, rle_data, size, bpp) do \\\n", + macro_name) && + print (output, error, + "{ %s __bpp; %s *__ip; const %s *__il, *__rd; \\\n", + s_uint, s_uint_8, s_uint_8) && + print (output, error, + " __bpp = (bpp); __ip = (image_buf); __il = __ip + (size) * __bpp; \\\n" + " __rd = (rle_data); if (__bpp > 3) { /* RGBA */ \\\n" + " while (__ip < __il) { %s __l = *(__rd++); \\\n", + s_uint) && + print (output, error, + " if (__l & 128) { __l = __l - 128; \\\n" + " do { memcpy (__ip, __rd, 4); __ip += 4; } while (--__l); __rd += 4; \\\n" + " } else { __l *= 4; memcpy (__ip, __rd, __l); \\\n" + " __ip += __l; __rd += __l; } } \\\n" + " } else if (__bpp == 3) { /* RGB */ \\\n" + " while (__ip < __il) { %s __l = *(__rd++); \\\n", + s_uint) && + print (output, error, + " if (__l & 128) { __l = __l - 128; \\\n" + " do { memcpy (__ip, __rd, 3); __ip += 3; } while (--__l); __rd += 3; \\\n" + " } else { __l *= 3; memcpy (__ip, __rd, __l); \\\n" + " __ip += __l; __rd += __l; } } \\\n" + " } else { /* RGB16 */ \\\n" + " while (__ip < __il) { %s __l = *(__rd++); \\\n", + s_uint) && + print (output, error, + " if (__l & 128) { __l = __l - 128; \\\n" + " do { memcpy (__ip, __rd, 2); __ip += 2; } while (--__l); __rd += 2; \\\n" + " } else { __l *= 2; memcpy (__ip, __rd, __l); \\\n" + " __ip += __l; __rd += __l; } } \\\n" + " } } while (0)\n"); +} + +static inline gboolean +save_uchar (GOutputStream *output, + guint *c, + guint8 d, + Config *config, + GError **error) +{ + static guint8 pad = 0; + + if (*c > 74) + { + if (! config->use_macros) + { + if (! print (output, error, "\"\n \"")) + return FALSE; + + *c = 3; + } + else + { + if (! print (output, error, "\"\n \"")) + return FALSE; + + *c = 2; + } + } + + if (d < 33 || (d >= 48 && d <= 57) || d > 126) + { + if (! print (output, error, "\\%03o", d)) + return FALSE; + + *c += 1 + 1 + (d > 7) + (d > 63); + pad = d < 64; + + return TRUE; + } + + if (d == '\\') + { + if (! print (output, error, "\\\\")) + return FALSE; + + *c += 2; + } + else if (d == '"') + { + if (! print (output, error, "\\\"")) + return FALSE; + + *c += 2; + } + else if (pad && d >= '0' && d <= '9') + { + if (! print (output, error, "\"\"%c", d)) + return FALSE; + + *c += 3; + } + else + { + if (! print (output, error, "%c", d)) + return FALSE; + + *c += 1; + } + + pad = 0; + + return TRUE; +} + +static gboolean +save_image (GFile *file, + Config *config, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GOutputStream *output; + GeglBuffer *buffer; + GCancellable *cancellable; + GimpImageType drawable_type = gimp_drawable_type (drawable_ID); + gchar *s_uint_8, *s_uint, *s_char, *s_null; + guint c; + gchar *macro_name; + guint8 *img_buffer, *img_buffer_end; + gchar *basename; + guint8 *data, *p; + gint width; + gint height; + gint x, y, pad, n_bytes, bpp; + const Babl *drawable_format; + gint drawable_bpp; + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (output) + { + GOutputStream *buffered; + + buffered = g_buffered_output_stream_new (output); + g_object_unref (output); + + output = buffered; + } + else + { + return FALSE; + } + + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + if (gimp_drawable_has_alpha (drawable_ID)) + drawable_format = babl_format ("R'G'B'A u8"); + else + drawable_format = babl_format ("R'G'B' u8"); + + drawable_bpp = babl_format_get_bytes_per_pixel (drawable_format); + + bpp = config->rgb565 ? 2 : (config->alpha ? 4 : 3); + n_bytes = width * height * bpp; + pad = width * drawable_bpp; + if (config->use_rle) + pad = MAX (pad, 130 + n_bytes / 127); + + data = g_new (guint8, pad + n_bytes); + p = data + pad; + + for (y = 0; y < height; y++) + { + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, y, width, 1), 1.0, + drawable_format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (bpp == 2) + { + for (x = 0; x < width; x++) + { + guint8 *d = data + x * drawable_bpp; + guint8 r, g, b; + gushort rgb16; + gdouble alpha = drawable_type == GIMP_RGBA_IMAGE ? d[3] : 0xff; + + alpha *= config->opacity / 25500.0; + r = (0.5 + alpha * (gdouble) d[0]); + g = (0.5 + alpha * (gdouble) d[1]); + b = (0.5 + alpha * (gdouble) d[2]); + r >>= 3; + g >>= 2; + b >>= 3; + rgb16 = (r << 11) + (g << 5) + b; + *(p++) = (guchar) rgb16; + *(p++) = (guchar) (rgb16 >> 8); + } + } + else if (config->alpha) + { + for (x = 0; x < width; x++) + { + guint8 *d = data + x * drawable_bpp; + gdouble alpha = drawable_type == GIMP_RGBA_IMAGE ? d[3] : 0xff; + + alpha *= config->opacity / 100.0; + *(p++) = d[0]; + *(p++) = d[1]; + *(p++) = d[2]; + *(p++) = alpha + 0.5; + } + } + else + { + for (x = 0; x < width; x++) + { + guint8 *d = data + x * drawable_bpp; + gdouble alpha = drawable_type == GIMP_RGBA_IMAGE ? d[3] : 0xff; + + alpha *= config->opacity / 25500.0; + *(p++) = 0.5 + alpha * (gdouble) d[0]; + *(p++) = 0.5 + alpha * (gdouble) d[1]; + *(p++) = 0.5 + alpha * (gdouble) d[2]; + } + } + } + + img_buffer = data + pad; + if (config->use_rle) + { + img_buffer_end = rl_encode_rgbx (data, img_buffer, + img_buffer + n_bytes, bpp); + img_buffer = data; + } + else + { + img_buffer_end = img_buffer + n_bytes; + } + + if (!config->use_macros && config->glib_types) + { + s_uint_8 = "guint8 "; + s_uint = "guint "; + s_char = "gchar "; + s_null = "NULL"; + } + else if (!config->use_macros) + { + s_uint_8 = "unsigned char"; + s_uint = "unsigned int "; + s_char = "char "; + s_null = "(char*) 0"; + } + else if (config->use_macros && config->glib_types) + { + s_uint_8 = "guint8"; + s_uint = "guint"; + s_char = "gchar"; + s_null = "NULL"; + } + else /* config->use_macros && !config->glib_types */ + { + s_uint_8 = "unsigned char"; + s_uint = "unsigned int"; + s_char = "char"; + s_null = "(char*) 0"; + } + + macro_name = g_ascii_strup (config->prefixed_name, -1); + + basename = g_file_get_basename (file); + + if (! print (output, error, + "/* GIMP %s C-Source image dump %s(%s) */\n\n", + config->alpha ? "RGBA" : "RGB", + config->use_rle ? "1-byte-run-length-encoded " : "", + basename)) + goto fail; + + g_free (basename); + + if (config->use_rle && !config->use_macros) + { + if (! save_rle_decoder (output, + macro_name, + config->glib_types ? "guint" : "unsigned int", + config->glib_types ? "guint8" : "unsigned char", + bpp, + error)) + goto fail; + } + + if (!config->use_macros) + { + if (! print (output, error, + "static const struct {\n" + " %s\t width;\n" + " %s\t height;\n" + " %s\t bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ \n", + s_uint, s_uint, s_uint)) + goto fail; + + if (config->use_comment) + { + if (! print (output, error, " %s\t*comment;\n", s_char)) + goto fail; + } + + if (! print (output, error, + " %s\t %spixel_data[", + s_uint_8, + config->use_rle ? "rle_" : "")) + goto fail; + + if (config->use_rle) + { + if (! print (output, error, + "%u + 1];\n", + (guint) (img_buffer_end - img_buffer))) + goto fail; + } + else + { + if (! print (output, error, + "%u * %u * %u + 1];\n", + width, + height, + bpp)) + goto fail; + } + + if (! print (output, error, "} %s = {\n", config->prefixed_name)) + goto fail; + + if (! print (output, error, + " %u, %u, %u,\n", + width, + height, + bpp)) + goto fail; + } + else /* use macros */ + { + if (! print (output, error, + "#define %s_WIDTH (%u)\n" + "#define %s_HEIGHT (%u)\n" + "#define %s_BYTES_PER_PIXEL (%u) /* 2:RGB16, 3:RGB, 4:RGBA */\n", + macro_name, width, + macro_name, height, + macro_name, bpp)) + { + goto fail; + } + } + + if (config->use_comment && !config->comment) + { + if (! config->use_macros) + { + if (! print (output, error, " %s,\n", s_null)) + goto fail; + } + else + { + if (! print (output, error, + "#define %s_COMMENT (%s)\n", + macro_name, s_null)) + goto fail; + } + } + else if (config->use_comment) + { + gchar *p = config->comment - 1; + + if (config->use_macros) + { + if (! print (output, error, "#define %s_COMMENT \\\n", macro_name)) + goto fail; + } + + if (! print (output, error, " \"")) + goto fail; + + while (*(++p)) + { + gboolean success = FALSE; + + if (*p == '\\') + success = print (output, error, "\\\\"); + else if (*p == '"') + success = print (output, error, "\\\""); + else if (*p == '\n' && p[1]) + success = print (output, error, + "\\n\"%s\n \"", + config->use_macros ? " \\" : ""); + else if (*p == '\n') + success = print (output, error, "\\n"); + else if (*p == '\r') + success = print (output, error, "\\r"); + else if (*p == '\b') + success = print (output, error, "\\b"); + else if (*p == '\f') + success = print (output, error, "\\f"); + else if (( *p >= 32 && *p <= 47 ) || (*p >= 58 && *p <= 126)) + success = print (output, error, "%c", *p); + else + success = print (output, error, "\\%03o", *p); + + if (! success) + goto fail; + } + + if (! config->use_macros) + { + if (! print (output, error, "\",\n")) + goto fail; + } + else + { + if (! print (output, error, "\"\n")) + goto fail; + } + } + + if (config->use_macros) + { + if (! print (output, error, + "#define %s_%sPIXEL_DATA ((%s*) %s_%spixel_data)\n", + macro_name, + config->use_rle ? "RLE_" : "", + s_uint_8, + macro_name, + config->use_rle ? "rle_" : "")) + goto fail; + + if (config->use_rle) + { + if (! save_rle_decoder (output, + macro_name, + s_uint, + s_uint_8, + bpp, + error)) + goto fail; + } + + if (! print (output, error, + "static const %s %s_%spixel_data[", + s_uint_8, + macro_name, + config->use_rle ? "rle_" : "")) + goto fail; + + if (config->use_rle) + { + if (! print (output, error, + "%u] =\n", + (guint) (img_buffer_end - img_buffer))) + goto fail; + } + else + { + if (! print (output, error, + "%u * %u * %u + 1] =\n", + width, + height, + bpp)) + goto fail; + } + + if (! print (output, error, "(\"")) + goto fail; + + c = 2; + } + else + { + if (! print (output, error, " \"")) + goto fail; + + c = 3; + } + + switch (drawable_type) + { + case GIMP_RGB_IMAGE: + case GIMP_RGBA_IMAGE: + do + { + if (! save_uchar (output, &c, *(img_buffer++), config, error)) + goto fail; + } + while (img_buffer < img_buffer_end); + break; + + default: + g_warning ("unhandled drawable type (%d)", drawable_type); + goto fail; + } + + if (! config->use_macros) + { + if (! print (output, error, "\",\n};\n\n")) + goto fail; + } + else + { + if (! print (output, error, "\");\n\n")) + goto fail; + } + + if (! g_output_stream_close (output, NULL, error)) + goto fail; + + g_object_unref (output); + g_object_unref (buffer); + + return TRUE; + + fail: + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + + g_object_unref (output); + g_object_unref (buffer); + + return FALSE; +} + +static void +rgb565_toggle_button_update (GtkWidget *toggle, + gpointer data) +{ + GtkWidget *widget; + gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)); + + gimp_toggle_button_update (toggle, data); + + widget = g_object_get_data (G_OBJECT (toggle), "set-insensitive-1"); + if (widget) + gtk_widget_set_sensitive (widget, ! active); +} + +static gboolean +run_save_dialog (Config *config) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *prefixed_name; + GtkWidget *centry; + GtkWidget *toggle; + GtkWidget *alpha_toggle; + GtkObject *adj; + gboolean run; + + dialog = gimp_export_dialog_new (_("C-Source"), PLUG_IN_BINARY, SAVE_PROC); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* Prefixed Name + */ + prefixed_name = gtk_entry_new (); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Prefixed name:"), 0.0, 0.5, + prefixed_name, 1, FALSE); + gtk_entry_set_text (GTK_ENTRY (prefixed_name), + config->prefixed_name ? config->prefixed_name : ""); + + /* Comment Entry + */ + centry = gtk_entry_new (); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Co_mment:"), 0.0, 0.5, + centry, 1, FALSE); + gtk_entry_set_text (GTK_ENTRY (centry), + config->comment ? config->comment : ""); + + /* Use Comment + */ + toggle = gtk_check_button_new_with_mnemonic (_("_Save comment to file")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + config->use_comment); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &config->use_comment); + + /* GLib types + */ + toggle = gtk_check_button_new_with_mnemonic (_("_Use GLib types (guint8*)")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + config->glib_types); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &config->glib_types); + + /* Use Macros + */ + toggle = + gtk_check_button_new_with_mnemonic (_("Us_e macros instead of struct")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + config->use_macros); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &config->use_macros); + + /* Use RLE + */ + toggle = + gtk_check_button_new_with_mnemonic (_("Use _1 byte Run-Length-Encoding")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + config->use_rle); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &config->use_rle); + + /* Alpha + */ + alpha_toggle = toggle = + gtk_check_button_new_with_mnemonic (_("Sa_ve alpha channel (RGBA/RGB)")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + config->alpha); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &config->alpha); + + /* RGB-565 + */ + toggle = gtk_check_button_new_with_mnemonic (_("Save as _RGB565 (16-bit)")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + + /* Alpha setting is not used with RGB-565 */ + g_object_set_data (G_OBJECT (toggle), "set-insensitive-1", alpha_toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (rgb565_toggle_button_update), + &config->rgb565); + gtk_widget_show (toggle); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + config->rgb565); + + /* Max Alpha Value + */ + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("Op_acity:"), 100, 0, + config->opacity, 0, 100, 1, 10, 1, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &config->opacity); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + config->prefixed_name = + g_strdup (gtk_entry_get_text (GTK_ENTRY (prefixed_name))); + config->comment = g_strdup (gtk_entry_get_text (GTK_ENTRY (centry))); + } + + gtk_widget_destroy (dialog); + + if (!config->prefixed_name || !config->prefixed_name[0]) + config->prefixed_name = "tmp"; + + if (config->comment && !config->comment[0]) + config->comment = NULL; + + return run; +} diff --git a/plug-ins/common/file-desktop-link.c b/plug-ins/common/file-desktop-link.c new file mode 100644 index 0000000..315adb5 --- /dev/null +++ b/plug-ins/common/file-desktop-link.c @@ -0,0 +1,185 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Desktop Entry Specification + * http://standards.freedesktop.org/desktop-entry-spec/latest/ + */ + +#include "config.h" + +#include + +#include + +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-desktop-link-load" +#define PLUG_IN_BINARY "file-desktop-link" +#define PLUG_IN_ROLE "gimp-file-desktop-link" + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GimpRunMode run_mode, + GError **error); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + gimp_install_procedure (LOAD_PROC, + "Follows a link to an image in a .desktop file", + "Opens a .desktop file and if it is a link, it " + "asks GIMP to open the file the link points to.", + "Sven Neumann", + "Sven Neumann", + "2006", + N_("Desktop Link"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_load_handler (LOAD_PROC, "desktop", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_EXECUTION_ERROR; + GError *error = NULL; + gint32 image_ID; + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, run_mode, &error); + + if (image_ID != -1) + { + status = GIMP_PDB_SUCCESS; + + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else if (error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].data.d_status = status; +} + +static gint32 +load_image (const gchar *filename, + GimpRunMode run_mode, + GError **load_error) +{ + GKeyFile *file = g_key_file_new (); + gchar *group = NULL; + gchar *value = NULL; + gint32 image_ID = -1; + GError *error = NULL; + + if (! g_key_file_load_from_file (file, filename, G_KEY_FILE_NONE, &error)) + goto out; + + group = g_key_file_get_start_group (file); + if (! group || strcmp (group, G_KEY_FILE_DESKTOP_GROUP) != 0) + goto out; + + value = g_key_file_get_value (file, + group, G_KEY_FILE_DESKTOP_KEY_TYPE, &error); + if (! value || strcmp (value, G_KEY_FILE_DESKTOP_TYPE_LINK) != 0) + goto out; + + g_free (value); + + value = g_key_file_get_value (file, + group, G_KEY_FILE_DESKTOP_KEY_URL, &error); + if (value) + image_ID = gimp_file_load (run_mode, value, value); + + out: + if (error) + { + g_set_error (load_error, error->domain, error->code, + _("Error loading desktop file '%s': %s"), + gimp_filename_to_utf8 (filename), error->message); + g_error_free (error); + } + + g_free (value); + g_free (group); + g_key_file_free (file); + + return image_ID; +} diff --git a/plug-ins/common/file-dicom.c b/plug-ins/common/file-dicom.c new file mode 100644 index 0000000..1ef8549 --- /dev/null +++ b/plug-ins/common/file-dicom.c @@ -0,0 +1,1643 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * PNM reading and writing code Copyright (C) 1996 Erik Nygren + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * The dicom reading and writing code was written from scratch + * by Dov Grobgeld. (dov.grobgeld@gmail.com). + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-dicom-load" +#define SAVE_PROC "file-dicom-save" +#define PLUG_IN_BINARY "file-dicom" +#define PLUG_IN_ROLE "gimp-file-dicom" + + +/* A lot of Dicom images are wrongly encoded. By guessing the endian + * we can get around this problem. + */ +#define GUESS_ENDIAN 1 + +/* Declare local data types */ +typedef struct _DicomInfo +{ + guint width, height; /* The size of the image */ + gint maxval; /* For 16 and 24 bit image files, the max + value which we need to normalize to */ + gint samples_per_pixel; /* Number of image planes (0 for pbm) */ + gint bpp; + gint bits_stored; + gint high_bit; + gboolean is_signed; +} DicomInfo; + +/* Local function prototypes */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 load_image (const gchar *filename, + GError **error); +static gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static void dicom_loader (guint8 *pix_buf, + DicomInfo *info, + GeglBuffer *buffer); +static void guess_and_set_endian2 (guint16 *buf16, + gint length); +static void toggle_endian2 (guint16 *buf16, + gint length); +static void add_tag_pointer (GByteArray *group_stream, + gint group, + gint element, + const gchar *value_rep, + const guint8 *data, + gint length); +static GSList * dicom_add_tags (FILE *DICOM, + GByteArray *group_stream, + GSList *elements); +static gboolean write_group_to_file (FILE *DICOM, + gint group, + GByteArray *group_stream); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save" }, + }; + + gimp_install_procedure (LOAD_PROC, + "loads files of the dicom file format", + "Load a file in the DICOM standard format." + "The standard is defined at " + "http://medical.nema.org/. The plug-in currently " + "only supports reading images with uncompressed " + "pixel sections.", + "Dov Grobgeld", + "Dov Grobgeld ", + "2003", + N_("DICOM image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-dcm"); + gimp_register_magic_load_handler (LOAD_PROC, + "dcm,dicom", + "", + "128,string,DICM" + ); + + gimp_install_procedure (SAVE_PROC, + "Save file in the DICOM file format", + "Save an image in the medical standard DICOM image " + "formats. The standard is defined at " + "http://medical.nema.org/. The file format is " + "defined in section 10 of the standard. The files " + "are saved uncompressed and the compulsory DICOM " + "tags are filled with default dummy values.", + "Dov Grobgeld", + "Dov Grobgeld ", + "2003", + N_("Digital Imaging and Communications in " + "Medicine image"), + "RGB, GRAY", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-dcm"); + gimp_register_save_handler (SAVE_PROC, "dcm,dicom", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + + if (error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + export = gimp_export_image (&image_ID, &drawable_ID, "DICOM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + break; + + case GIMP_RUN_WITH_LAST_VALS: + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (! save_image (param[3].data.d_string, image_ID, drawable_ID, + &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + + if (error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].data.d_status = status; +} + +/** + * add_parasites_to_image: + * @data: pointer to a GimpParasite to be attached to the image + * specified by @user_data. + * @user_data: pointer to the image_ID to which parasite @data should + * be added. + * + * Attaches parasite to image and also frees that parasite +**/ +static void +add_parasites_to_image (gpointer data, + gpointer user_data) +{ + GimpParasite *parasite = (GimpParasite *) data; + gint32 *image_ID = (gint32 *) user_data; + + gimp_image_attach_parasite (*image_ID, parasite); + gimp_parasite_free (parasite); +} + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + gint32 volatile image_ID = -1; + gint32 layer_ID; + GeglBuffer *buffer; + GSList *elements = NULL; + FILE *DICOM; + gchar buf[500]; /* buffer for random things like scanning */ + DicomInfo *dicominfo; + guint width = 0; + guint height = 0; + gint samples_per_pixel = 0; + gint bpp = 0; + gint bits_stored = 0; + gint high_bit = 0; + guint8 *pix_buf = NULL; + gboolean is_signed = FALSE; + guint8 in_sequence = 0; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + DICOM = g_fopen (filename, "rb"); + + if (! DICOM) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + /* allocate the necessary structures */ + dicominfo = g_new0 (DicomInfo, 1); + + /* Parse the file */ + fread (buf, 1, 128, DICOM); /* skip past buffer */ + + /* Check for unsupported formats */ + if (g_ascii_strncasecmp (buf, "PAPYRUS", 7) == 0) + { + g_message ("'%s' is a PAPYRUS DICOM file.\n" + "This plug-in does not support this type yet.", + gimp_filename_to_utf8 (filename)); + g_free (dicominfo); + fclose (DICOM); + return -1; + } + + fread (buf, 1, 4, DICOM); /* This should be dicom */ + if (g_ascii_strncasecmp (buf,"DICM",4) != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s' is not a DICOM file."), + gimp_filename_to_utf8 (filename)); + g_free (dicominfo); + fclose (DICOM); + return -1; + } + + while (!feof (DICOM)) + { + guint16 group_word; + guint16 element_word; + gchar value_rep[3]; + guint32 element_length; + guint16 ctx_us; + guint8 *value; + guint32 tag; + gboolean __attribute__((unused))do_toggle_endian = FALSE; + gboolean implicit_encoding = FALSE; + + if (fread (&group_word, 1, 2, DICOM) == 0) + break; + group_word = g_ntohs (GUINT16_SWAP_LE_BE (group_word)); + + fread (&element_word, 1, 2, DICOM); + element_word = g_ntohs (GUINT16_SWAP_LE_BE (element_word)); + + tag = (group_word << 16) | element_word; + fread(value_rep, 2, 1, DICOM); + value_rep[2] = 0; + + /* Check if the value rep looks valid. There probably is a + better way of checking this... + */ + if ((/* Always need lookup for implicit encoding */ + tag > 0x0002ffff && implicit_encoding) + /* This heuristics isn't used if we are doing implicit + encoding according to the value representation... */ + || ((value_rep[0] < 'A' || value_rep[0] > 'Z' + || value_rep[1] < 'A' || value_rep[1] > 'Z') + + /* I found this in one of Ednas images. It seems like a + bug... + */ + && !(value_rep[0] == ' ' && value_rep[1])) + ) + { + /* Look up type from the dictionary. At the time we don't + support this option... */ + gchar element_length_chars[4]; + + /* Store the bytes that were read */ + element_length_chars[0] = value_rep[0]; + element_length_chars[1] = value_rep[1]; + + /* Unknown value rep. It is not used right now anyhow */ + strcpy (value_rep, "??"); + + /* For implicit value_values the length is always four bytes, + so we need to read another two. */ + fread (&element_length_chars[2], 1, 2, DICOM); + + /* Now cast to integer and insert into element_length */ + element_length = + g_ntohl (GUINT32_SWAP_LE_BE (*((gint *) element_length_chars))); + } + /* Binary value reps are OB, OW, SQ or UN */ + else if (strncmp (value_rep, "OB", 2) == 0 + || strncmp (value_rep, "OW", 2) == 0 + || strncmp (value_rep, "SQ", 2) == 0 + || strncmp (value_rep, "UN", 2) == 0) + { + fread (&element_length, 1, 2, DICOM); /* skip two bytes */ + fread (&element_length, 1, 4, DICOM); + element_length = g_ntohl (GUINT32_SWAP_LE_BE (element_length)); + } + /* Short length */ + else + { + guint16 el16; + + fread (&el16, 1, 2, DICOM); + element_length = g_ntohs (GUINT16_SWAP_LE_BE (el16)); + } + + /* Sequence of items - just ignore the delimiters... */ + if (element_length == 0xffffffff) + { + in_sequence = 1; + continue; + } + /* End of Sequence tag */ + if (tag == 0xFFFEE0DD) + { + in_sequence = 0; + continue; + } + + /* Sequence of items item tag... Ignore as well */ + if (tag == 0xFFFEE000) + continue; + + /* Even for pixel data, we don't handle very large element + lengths */ + + if (element_length >= (G_MAXUINT - 6)) + { + g_message ("'%s' seems to have an incorrect value field length.", + gimp_filename_to_utf8 (filename)); + gimp_quit (); + } + + /* Read contents. Allocate a bit more to make room for casts to int + below. */ + value = g_new0 (guint8, element_length + 4); + fread (value, 1, element_length, DICOM); + + /* ignore everything inside of a sequence */ + if (in_sequence) + { + g_free (value); + continue; + } + /* Some special casts that are used below */ + ctx_us = *(guint16 *) value; + + /* Recognize some critical tags */ + if (group_word == 0x0002) + { + switch (element_word) + { + case 0x0010: /* transfer syntax id */ + if (strcmp("1.2.840.10008.1.2", (char*)value) == 0) + { + do_toggle_endian = FALSE; + implicit_encoding = TRUE; + } + else if (strcmp("1.2.840.10008.1.2.1", (char*)value) == 0) + do_toggle_endian = FALSE; + else if (strcmp("1.2.840.10008.1.2.2", (char*)value) == 0) + do_toggle_endian = TRUE; + break; + } + } + else if (group_word == 0x0028) + { + switch (element_word) + { + case 0x0002: /* samples per pixel */ + samples_per_pixel = ctx_us; + break; + case 0x0010: /* rows */ + height = ctx_us; + break; + case 0x0011: /* columns */ + width = ctx_us; + break; + case 0x0100: /* bits allocated */ + bpp = ctx_us; + break; + case 0x0101: /* bits stored */ + bits_stored = ctx_us; + break; + case 0x0102: /* high bit */ + high_bit = ctx_us; + break; + case 0x0103: /* is pixel representation signed? */ + is_signed = (ctx_us == 0) ? FALSE : TRUE; + break; + } + } + + /* Pixel data */ + if (group_word == 0x7fe0 && element_word == 0x0010) + { + pix_buf = value; + } + else + { + /* save this element to a parasite for later writing */ + GimpParasite *parasite; + gchar pname[255]; + + /* all elements are retrievable using gimp_get_parasite_list() */ + g_snprintf (pname, sizeof (pname), + "dcm/%04x-%04x-%s", group_word, element_word, value_rep); + if ((parasite = gimp_parasite_new (pname, + GIMP_PARASITE_PERSISTENT, + element_length, value))) + { + /* + * at this point, the image has not yet been created, so + * image_ID is not valid. keep the parasite around + * until we're able to attach it. + */ + + /* add to our list of parasites to be added (prepending + * for speed. we'll reverse it later) + */ + elements = g_slist_prepend (elements, parasite); + } + + g_free (value); + } + } + + if ((bpp != 8) && (bpp != 16)) + { + g_message ("'%s' has a bpp of %d which GIMP cannot handle.", + gimp_filename_to_utf8 (filename), bpp); + gimp_quit (); + } + + if ((width > GIMP_MAX_IMAGE_SIZE) || (height > GIMP_MAX_IMAGE_SIZE)) + { + g_message ("'%s' has a larger image size (%d x %d) than GIMP can handle.", + gimp_filename_to_utf8 (filename), width, height); + gimp_quit (); + } + + if (samples_per_pixel > 3) + { + g_message ("'%s' has samples per pixel of %d which GIMP cannot handle.", + gimp_filename_to_utf8 (filename), samples_per_pixel); + gimp_quit (); + } + + dicominfo->width = width; + dicominfo->height = height; + dicominfo->bpp = bpp; + + dicominfo->bits_stored = bits_stored; + dicominfo->high_bit = high_bit; + dicominfo->is_signed = is_signed; + dicominfo->samples_per_pixel = samples_per_pixel; + dicominfo->maxval = -1; /* External normalization factor - not used yet */ + + /* Create a new image of the proper size and associate the filename with it. + */ + image_ID = gimp_image_new (dicominfo->width, dicominfo->height, + (dicominfo->samples_per_pixel >= 3 ? + GIMP_RGB : GIMP_GRAY)); + gimp_image_set_filename (image_ID, filename); + + layer_ID = gimp_layer_new (image_ID, _("Background"), + dicominfo->width, dicominfo->height, + (dicominfo->samples_per_pixel >= 3 ? + GIMP_RGB_IMAGE : GIMP_GRAY_IMAGE), + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_ID); + +#if GUESS_ENDIAN + if (bpp == 16) + guess_and_set_endian2 ((guint16 *) pix_buf, width * height); +#endif + + dicom_loader (pix_buf, dicominfo, buffer); + + if (elements) + { + /* flip the parasites back around into the order they were + * created (read from the file) + */ + elements = g_slist_reverse (elements); + /* and add each one to the image */ + g_slist_foreach (elements, add_parasites_to_image, (gpointer) &image_ID); + g_slist_free (elements); + } + + g_free (pix_buf); + g_free (dicominfo); + + fclose (DICOM); + + g_object_unref (buffer); + + return image_ID; +} + +static void +dicom_loader (guint8 *pix_buffer, + DicomInfo *info, + GeglBuffer *buffer) +{ + guchar *data; + gint row_idx; + gint width = info->width; + gint height = info->height; + gint samples_per_pixel = info->samples_per_pixel; + guint16 *buf16 = (guint16 *) pix_buffer; + + if (info->bpp == 16) + { + gulong pix_idx; + guint shift = info->high_bit + 1 - info->bits_stored; + + /* Reorder the buffer; also shift the data so that the LSB + * of the pixel data is at the LSB of the 16-bit array entries + * (i.e., compensate for high_bit and bits_stored). + */ + for (pix_idx = 0; pix_idx < width * height * samples_per_pixel; pix_idx++) + buf16[pix_idx] = g_htons (buf16[pix_idx]) >> shift; + } + + data = g_malloc (gimp_tile_height () * width * samples_per_pixel); + + for (row_idx = 0; row_idx < height; ) + { + guchar *d = data; + gint start; + gint end; + gint scanlines; + gint i; + + start = row_idx; + end = row_idx + gimp_tile_height (); + end = MIN (end, height); + + scanlines = end - start; + + for (i = 0; i < scanlines; i++) + { + if (info->bpp == 16) + { + guint16 *row_start; + gint col_idx; + + row_start = buf16 + (row_idx + i) * width * samples_per_pixel; + + for (col_idx = 0; col_idx < width * samples_per_pixel; col_idx++) + { + /* Shift it by 8 bits, or less in case bits_stored + * is less than bpp. + */ + d[col_idx] = (guint8) (row_start[col_idx] >> + (info->bits_stored - 8)); + if (info->is_signed) + { + /* If the data is negative, make it 0. Otherwise, + * multiply the positive value by 2, so that the + * positive values span between 0 and 254. + */ + if (d[col_idx] > 127) + d[col_idx] = 0; + else + d[col_idx] <<= 1; + } + } + } + else if (info->bpp == 8) + { + guint8 *row_start; + gint col_idx; + + row_start = (pix_buffer + + (row_idx + i) * width * samples_per_pixel); + + for (col_idx = 0; col_idx < width * samples_per_pixel; col_idx++) + { + /* Shift it by 0 bits, or more in case bits_stored is + * less than bpp. + */ + d[col_idx] = row_start[col_idx] << (8 - info->bits_stored); + + if (info->is_signed) + { + /* If the data is negative, make it 0. Otherwise, + * multiply the positive value by 2, so that the + * positive values span between 0 and 254. + */ + if (d[col_idx] > 127) + d[col_idx] = 0; + else + d[col_idx] <<= 1; + } + } + } + + d += width * samples_per_pixel; + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, row_idx, width, scanlines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + row_idx += scanlines; + + gimp_progress_update ((gdouble) row_idx / (gdouble) height); + } + + g_free (data); + + gimp_progress_update (1.0); +} + + +/* Guess and set endian. Guesses the endian of a buffer by + * checking the maximum value of the first and the last byte + * in the words of the buffer. It assumes that the least + * significant byte has a larger maximum than the most + * significant byte. + */ +static void +guess_and_set_endian2 (guint16 *buf16, + int length) +{ + guint16 *p = buf16; + gint max_first = -1; + gint max_second = -1; + + while (p max_first) + max_first = *(guint8*)p; + if (((guint8*)p)[1] > max_second) + max_second = ((guint8*)p)[1]; + p++; + } + + if ( ((max_second > max_first) && (G_BYTE_ORDER == G_LITTLE_ENDIAN)) + || ((max_second < max_first) && (G_BYTE_ORDER == G_BIG_ENDIAN))) + toggle_endian2 (buf16, length); +} + +/* toggle_endian2 toggles the endian for a 16 bit entity. */ +static void +toggle_endian2 (guint16 *buf16, + gint length) +{ + guint16 *p = buf16; + + while (p < buf16 + length) + { + *p = ((*p & 0xff) << 8) | (*p >> 8); + p++; + } +} + +typedef struct +{ + guint16 group_word; + guint16 element_word; + gchar value_rep[3]; + guint32 element_length; + guint8 *value; + gboolean free; +} DICOMELEMENT; + +/** + * dicom_add_element: + * @elements: head of a GSList containing DICOMELEMENT structures. + * @group_word: Dicom Element group number for the tag to be added to + * @elements. + * @element_word: Dicom Element element number for the tag to be added + * to @elements. + * @value_rep: a string representing the Dicom VR for the new element. + * @value: a pointer to an integer containing the value for the + * element to be created. + * + * Creates a DICOMELEMENT object and inserts it into @elements. + * + * Return value: the new head of @elements +**/ +static GSList * +dicom_add_element (GSList *elements, + guint16 group_word, + guint16 element_word, + const gchar *value_rep, + guint32 element_length, + guint8 *value) +{ + DICOMELEMENT *element = g_slice_new0 (DICOMELEMENT); + + element->group_word = group_word; + element->element_word = element_word; + strncpy (element->value_rep, value_rep, sizeof (element->value_rep)); + element->element_length = element_length; + element->value = value; + + return g_slist_prepend (elements, element); +} + +static GSList * +dicom_add_element_copy (GSList *elements, + guint16 group_word, + guint16 element_word, + gchar *value_rep, + guint32 element_length, + const guint8 *value) +{ + elements = dicom_add_element (elements, + group_word, element_word, value_rep, + element_length, + g_memdup (value, element_length)); + + ((DICOMELEMENT *) elements->data)->free = TRUE; + + return elements; +} + +/** + * dicom_add_element_int: + * @elements: head of a GSList containing DICOMELEMENT structures. + + * @group_word: Dicom Element group number for the tag to be added to + * @elements. + * @element_word: Dicom Element element number for the tag to be added to + * @elements. + * @value_rep: a string representing the Dicom VR for the new element. + * @value: a pointer to an integer containing the value for the + * element to be created. + * + * Creates a DICOMELEMENT object from the passed integer pointer and + * adds it to @elements. Note: value should be the address of a + * guint16 for @value_rep == %US or guint32 for other values of + * @value_rep + * + * Return value: the new head of @elements + */ +static GSList * +dicom_add_element_int (GSList *elements, + guint16 group_word, + guint16 element_word, + gchar *value_rep, + guint8 *value) +{ + guint32 len; + + if (strcmp (value_rep, "US") == 0) + len = 2; + else + len = 4; + + return dicom_add_element (elements, + group_word, element_word, value_rep, + len, value); +} + +/** + * dicom_element_done: + * @data: pointer to a DICOMELEMENT structure which is to be destroyed. + * + * Destroys the DICOMELEMENT passed as @data +**/ +static void +dicom_element_done (gpointer data) +{ + if (data) + { + DICOMELEMENT *e = data; + + if (e->free) + g_free (e->value); + + g_slice_free (DICOMELEMENT, data); + } +} + +/** + * dicom_elements_destroy: + * @elements: head of a GSList containing DICOMELEMENT structures. + * + * Destroys the list of DICOMELEMENTs +**/ +static void +dicom_elements_destroy (GSList *elements) +{ + if (elements) + g_slist_free_full (elements, dicom_element_done); +} + +/** + * dicom_destroy_element: + * @elements: head of a GSList containing DICOMELEMENT structures. + * @ele: a DICOMELEMENT structure to be removed from @elements + * + * Removes the specified DICOMELEMENT from @elements and Destroys it + * + * Return value: the new head of @elements +**/ +static GSList * +dicom_destroy_element (GSList *elements, + DICOMELEMENT *ele) +{ + if (ele) + { + elements = g_slist_remove_all (elements, ele); + + if (ele->free) + g_free (ele->value); + + g_slice_free (DICOMELEMENT, ele); + } + + return elements; +} + +/** + * dicom_elements_compare: + * @a: pointer to a DICOMELEMENT structure. + * @b: pointer to a DICOMELEMENT structure. + * + * Determines the equality of @a and @b as strcmp + * + * Return value: an integer indicating the equality of @a and @b. +**/ +static gint +dicom_elements_compare (gconstpointer a, + gconstpointer b) +{ + DICOMELEMENT *e1 = (DICOMELEMENT *)a; + DICOMELEMENT *e2 = (DICOMELEMENT *)b; + + if (e1->group_word == e2->group_word) + { + if (e1->element_word == e2->element_word) + { + return 0; + } + else if (e1->element_word > e2->element_word) + { + return 1; + } + else + { + return -1; + } + } + else if (e1->group_word < e2->group_word) + { + return -1; + } + + return 1; +} + +/** + * dicom_element_find_by_num: + * @head: head of a GSList containing DICOMELEMENT structures. + * @group_word: Dicom Element group number for the tag to be found. + * @element_word: Dicom Element element number for the tag to be found. + * + * Retrieves the specified DICOMELEMENT from @head, if available. + * + * Return value: a DICOMELEMENT matching the specified group,element, + * or NULL if the specified element was not found. +**/ +static DICOMELEMENT * +dicom_element_find_by_num (GSList *head, + guint16 group_word, + guint16 element_word) +{ + DICOMELEMENT data = { group_word,element_word, "", 0, NULL}; + GSList *ele = g_slist_find_custom (head,&data,dicom_elements_compare); + return (ele ? ele->data : NULL); +} + +/** + * dicom_get_elements_list: + * @image_ID: the image_ID from which to read parasites in order to + * retrieve the dicom elements + * + * Reads all DICOMELEMENTs from the specified image's parasites. + * + * Return value: a GSList of all known dicom elements +**/ +static GSList * +dicom_get_elements_list (gint32 image_ID) +{ + GSList *elements = NULL; + GimpParasite *parasite; + gchar **parasites = NULL; + gint count = 0; + + parasites = gimp_image_get_parasite_list (image_ID, &count); + + if (parasites && count > 0) + { + gint i; + + for (i = 0; i < count; i++) + { + if (strncmp (parasites[i], "dcm", 3) == 0) + { + parasite = gimp_image_get_parasite (image_ID, parasites[i]); + + if (parasite) + { + gchar buf[1024]; + gchar *ptr1; + gchar *ptr2; + gchar value_rep[3] = ""; + guint16 group_word = 0; + guint16 element_word = 0; + + /* sacrificial buffer */ + strncpy (buf, parasites[i], sizeof (buf)); + + /* buf should now hold a string of the form + * dcm/XXXX-XXXX-AA where XXXX are Hex values for + * group and element respectively AA is the Value + * Representation of the element + * + * start off by jumping over the dcm/ to the first Hex blob + */ + ptr1 = strchr (buf, '/'); + + if (ptr1) + { + gchar t[15]; + + ptr1++; + ptr2 = strchr (ptr1,'-'); + + if (ptr2) + *ptr2 = '\0'; + + g_snprintf (t, sizeof (t), "0x%s", ptr1); + group_word = (guint16) g_ascii_strtoull (t, NULL, 16); + ptr1 = ptr2 + 1; + } + + /* now get the second Hex blob */ + if (ptr1) + { + gchar t[15]; + + ptr2 = strchr (ptr1, '-'); + + if (ptr2) + *ptr2 = '\0'; + + g_snprintf (t, sizeof (t), "0x%s", ptr1); + element_word = (guint16) g_ascii_strtoull (t, NULL, 16); + ptr1 = ptr2 + 1; + } + + /* and lastly, the VR */ + if (ptr1) + strncpy (value_rep, ptr1, sizeof (value_rep)); + + /* + * If all went according to plan, we should be able + * to add this element + */ + if (group_word > 0 && element_word > 0) + { + const guint8 *val = gimp_parasite_data (parasite); + const guint len = gimp_parasite_data_size (parasite); + + /* and add the dicom element, asking to have + it's value copied for later garbage collection */ + elements = dicom_add_element_copy (elements, + group_word, + element_word, + value_rep, len, val); + } + + gimp_parasite_free (parasite); + } + } + } + } + + /* cleanup the array of names */ + g_strfreev (parasites); + + return elements; +} + +/** + * dicom_remove_gimp_specified_elements: + * @elements: GSList to remove elements from + * @samples_per_pixel: samples per pixel of the image to be written. + * if set to %3 the planar configuration for color images + * will also be removed from @elements + * + * Removes certain DICOMELEMENTs from the elements list which are specific to the output of this plugin. + * + * Return value: the new head of @elements +**/ +static GSList * +dicom_remove_gimp_specified_elements (GSList *elements, + gint samples_per_pixel) +{ + DICOMELEMENT remove[] = { + /* Image presentation group */ + /* Samples per pixel */ + {0x0028, 0x0002, "", 0, NULL}, + /* Photometric interpretation */ + {0x0028, 0x0004, "", 0, NULL}, + /* rows */ + {0x0028, 0x0010, "", 0, NULL}, + /* columns */ + {0x0028, 0x0011, "", 0, NULL}, + /* Bits allocated */ + {0x0028, 0x0100, "", 0, NULL}, + /* Bits Stored */ + {0x0028, 0x0101, "", 0, NULL}, + /* High bit */ + {0x0028, 0x0102, "", 0, NULL}, + /* Pixel representation */ + {0x0028, 0x0103, "", 0, NULL}, + + {0,0,"",0,NULL} + }; + DICOMELEMENT *ele; + gint i; + + /* + * Remove all Dicom elements which will be set as part of the writing of the new file + */ + for (i=0; remove[i].group_word > 0;i++) + { + if ((ele = dicom_element_find_by_num (elements,remove[i].group_word,remove[i].element_word))) + { + elements = dicom_destroy_element (elements,ele); + } + } + /* special case - allow this to be overwritten if necessary */ + if (samples_per_pixel == 3) + { + /* Planar configuration for color images */ + if ((ele = dicom_element_find_by_num (elements,0x0028,0x0006))) + { + elements = dicom_destroy_element (elements,ele); + } + } + return elements; +} + +/** + * dicom_ensure_required_elements_present: + * @elements: GSList to remove elements from + * @today_string: string containing today's date in DICOM format. This + * is used to default any required Dicom elements of date + * type to today's date. + * + * Defaults DICOMELEMENTs to the values set by previous version of + * this plugin, but only if they do not already exist. + * + * Return value: the new head of @elements +**/ +static GSList * +dicom_ensure_required_elements_present (GSList *elements, + gchar *today_string) +{ + const DICOMELEMENT defaults[] = { + /* Meta element group */ + /* 0002, 0001 - File Meta Information Version */ + { 0x0002, 0x0001, "OB", 2, (guint8 *) "\0\1" }, + /* 0002, 0010 - Transfer syntax uid */ + { 0x0002, 0x0010, "UI", + strlen ("1.2.840.10008.1.2.1"), (guint8 *) "1.2.840.10008.1.2.1"}, + /* 0002, 0013 - Implementation version name */ + { 0x0002, 0x0013, "SH", + strlen ("GIMP Dicom Plugin 1.0"), (guint8 *) "GIMP Dicom Plugin 1.0" }, + /* Identifying group */ + /* ImageType */ + { 0x0008, 0x0008, "CS", + strlen ("ORIGINAL\\PRIMARY"), (guint8 *) "ORIGINAL\\PRIMARY" }, + { 0x0008, 0x0016, "UI", + strlen ("1.2.840.10008.5.1.4.1.1.7"), (guint8 *) "1.2.840.10008.5.1.4.1.1.7" }, + /* Study date */ + { 0x0008, 0x0020, "DA", + strlen (today_string), (guint8 *) today_string }, + /* Series date */ + { 0x0008, 0x0021, "DA", + strlen (today_string), (guint8 *) today_string }, + /* Acquisition date */ + { 0x0008, 0x0022, "DA", + strlen (today_string), (guint8 *) today_string }, + /* Content Date */ + { 0x0008, 0x0023, "DA", + strlen (today_string), (guint8 *) today_string}, + /* Content Time */ + { 0x0008, 0x0030, "TM", + strlen ("000000.000000"), (guint8 *) "000000.000000"}, + /* AccessionNumber */ + { 0x0008, 0x0050, "SH", strlen (""), (guint8 *) "" }, + /* Modality */ + { 0x0008, 0x0060, "CS", strlen ("MR"), (guint8 *) "MR" }, + /* ConversionType */ + { 0x0008, 0x0064, "CS", strlen ("WSD"), (guint8 *) "WSD" }, + /* ReferringPhysiciansName */ + { 0x0008, 0x0090, "PN", strlen (""), (guint8 *) "" }, + /* Patient group */ + /* Patient name */ + { 0x0010, 0x0010, "PN", + strlen ("DOE^WILBER"), (guint8 *) "DOE^WILBER" }, + /* Patient ID */ + { 0x0010, 0x0020, "LO", + strlen ("314159265"), (guint8 *) "314159265" }, + /* Patient Birth date */ + { 0x0010, 0x0030, "DA", + strlen (today_string), (guint8 *) today_string }, + /* Patient sex */ + { 0x0010, 0x0040, "CS", strlen (""), (guint8 *) "" /* unknown */ }, + /* Relationship group */ + /* StudyId */ + { 0x0020, 0x0010, "IS", strlen ("1"), (guint8 *) "1" }, + /* SeriesNumber */ + { 0x0020, 0x0011, "IS", strlen ("1"), (guint8 *) "1" }, + /* AcquisitionNumber */ + { 0x0020, 0x0012, "IS", strlen ("1"), (guint8 *) "1" }, + /* Instance number */ + { 0x0020, 0x0013, "IS", strlen ("1"), (guint8 *) "1" }, + + { 0, 0, "", 0, NULL } + }; + gint i; + + /* + * Make sure that all of the default elements have a value + */ + for (i=0; defaults[i].group_word > 0; i++) + { + if (dicom_element_find_by_num (elements, + defaults[i].group_word, + defaults[i].element_word) == NULL) + { + elements = dicom_add_element (elements, + defaults[i].group_word, + defaults[i].element_word, + defaults[i].value_rep, + defaults[i].element_length, + defaults[i].value); + } + } + + return elements; +} + +/* save_image() saves an image in the dicom format. The DICOM format + * requires a lot of tags to be set. Some of them have real uses, others + * must just be filled with dummy values. + */ +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + FILE *DICOM; + GimpImageType drawable_type; + GeglBuffer *buffer; + const Babl *format; + gint width; + gint height; + GByteArray *group_stream; + GSList *elements = NULL; + gint group; + GDate *date; + gchar today_string[16]; + gchar *photometric_interp; + gint samples_per_pixel; + gboolean retval = TRUE; + guint16 zero = 0; + guint16 seven = 7; + guint16 eight = 8; + guchar *src = NULL; + + drawable_type = gimp_drawable_type (drawable_ID); + + /* Make sure we're not saving an image with an alpha channel */ + if (gimp_drawable_has_alpha (drawable_ID)) + { + g_message (_("Cannot save images with alpha channel.")); + return FALSE; + } + + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + format = babl_format ("Y' u8"); + samples_per_pixel = 1; + photometric_interp = "MONOCHROME2"; + break; + + case GIMP_RGB_IMAGE: + format = babl_format ("R'G'B' u8"); + samples_per_pixel = 3; + photometric_interp = "RGB"; + break; + + default: + g_message (_("Cannot operate on unknown image types.")); + return FALSE; + } + + date = g_date_new (); + g_date_set_time_t (date, time (NULL)); + g_snprintf (today_string, sizeof (today_string), + "%04d%02d%02d", date->year, date->month, date->day); + g_date_free (date); + + /* Open the output file. */ + DICOM = g_fopen (filename, "wb"); + + if (!DICOM) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + /* Print dicom header */ + { + guint8 val = 0; + gint i; + + for (i = 0; i < 0x80; i++) + fwrite (&val, 1, 1, DICOM); + } + fprintf (DICOM, "DICM"); + + group_stream = g_byte_array_new (); + + elements = dicom_get_elements_list (image_ID); + if (0/*replaceElementsList*/) + { + /* to do */ + } + else if (1/*insist_on_basic_elements*/) + { + elements = dicom_ensure_required_elements_present (elements,today_string); + } + + /* + * Set value of custom elements + */ + elements = dicom_remove_gimp_specified_elements (elements,samples_per_pixel); + + /* Image presentation group */ + group = 0x0028; + /* Samples per pixel */ + elements = dicom_add_element_int (elements, group, 0x0002, "US", + (guint8 *) &samples_per_pixel); + /* Photometric interpretation */ + elements = dicom_add_element (elements, group, 0x0004, "CS", + strlen (photometric_interp), + (guint8 *) photometric_interp); + /* Planar configuration for color images */ + if (samples_per_pixel == 3) + elements = dicom_add_element_int (elements, group, 0x0006, "US", + (guint8 *) &zero); + /* rows */ + elements = dicom_add_element_int (elements, group, 0x0010, "US", + (guint8 *) &height); + /* columns */ + elements = dicom_add_element_int (elements, group, 0x0011, "US", + (guint8 *) &width); + /* Bits allocated */ + elements = dicom_add_element_int (elements, group, 0x0100, "US", + (guint8 *) &eight); + /* Bits Stored */ + elements = dicom_add_element_int (elements, group, 0x0101, "US", + (guint8 *) &eight); + /* High bit */ + elements = dicom_add_element_int (elements, group, 0x0102, "US", + (guint8 *) &seven); + /* Pixel representation */ + elements = dicom_add_element_int (elements, group, 0x0103, "US", + (guint8 *) &zero); + + /* Pixel data */ + group = 0x7fe0; + src = g_new (guchar, height * width * samples_per_pixel); + if (src) + { + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0, + format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + elements = dicom_add_element (elements, group, 0x0010, "OW", + width * height * samples_per_pixel, + (guint8 *) src); + + elements = dicom_add_tags (DICOM, group_stream, elements); + + g_free (src); + } + else + { + retval = FALSE; + } + + fclose (DICOM); + + dicom_elements_destroy (elements); + g_byte_array_free (group_stream, TRUE); + g_object_unref (buffer); + + return retval; +} + +/** + * dicom_print_tags: + * @data: pointer to a DICOMELEMENT structure which is to be written to file + * @user_data: structure containing state information and output parameters + * + * Writes the specified DICOMELEMENT to @user_data's group_stream member. + * Between groups, flushes the group_stream to @user_data's DICOM member. + */ +static void +dicom_print_tags(gpointer data, + gpointer user_data) +{ + struct { + FILE *DICOM; + GByteArray *group_stream; + gint last_group; + } *d = user_data; + DICOMELEMENT *e = (DICOMELEMENT *) data; + + if (d->last_group >= 0 && e->group_word != d->last_group) + { + write_group_to_file (d->DICOM, d->last_group, d->group_stream); + } + + add_tag_pointer (d->group_stream, + e->group_word, e->element_word, + e->value_rep,e->value, e->element_length); + d->last_group = e->group_word; +} + +/** + * dicom_add_tags: + * @DICOM: File pointer to which @elements should be written. + * @group_stream: byte array used for staging Dicom Element groups + * before flushing them to disk. + * @elements: GSList container the Dicom Element elements from + * + * Writes all Dicom tags in @elements to the file @DICOM + * + * Return value: the new head of @elements +**/ +static GSList * +dicom_add_tags (FILE *DICOM, + GByteArray *group_stream, + GSList *elements) +{ + struct { + FILE *DICOM; + GByteArray *group_stream; + gint last_group; + } data = { DICOM, group_stream, -1 }; + + elements = g_slist_sort (elements, dicom_elements_compare); + g_slist_foreach (elements, dicom_print_tags, &data); + /* make sure that the final group is written to the file */ + write_group_to_file (data.DICOM, data.last_group, data.group_stream); + + return elements; +} + +/* add_tag_pointer () adds to the group_stream one single value with its + * corresponding value_rep. Note that we use "explicit VR". + */ +static void +add_tag_pointer (GByteArray *group_stream, + gint group, + gint element, + const gchar *value_rep, + const guint8 *data, + gint length) +{ + gboolean is_long; + guint16 swapped16; + guint32 swapped32; + guint pad = 0; + + is_long = (strstr ("OB|OW|SQ|UN", value_rep) != NULL) || length > 65535; + + swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (group)); + g_byte_array_append (group_stream, (guint8 *) &swapped16, 2); + + swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (element)); + g_byte_array_append (group_stream, (guint8 *) &swapped16, 2); + + g_byte_array_append (group_stream, (const guchar *) value_rep, 2); + + if (length % 2 != 0) + { + /* the dicom standard requires all elements to be of even byte + * length. this element would be odd, so we must pad it before + * adding it + */ + pad = 1; + } + + if (is_long) + { + + g_byte_array_append (group_stream, (const guchar *) "\0\0", 2); + + swapped32 = g_ntohl (GUINT32_SWAP_LE_BE (length + pad)); + g_byte_array_append (group_stream, (guint8 *) &swapped32, 4); + } + else + { + swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (length + pad)); + g_byte_array_append (group_stream, (guint8 *) &swapped16, 2); + } + + g_byte_array_append (group_stream, data, length); + + if (pad) + { + /* add a padding byte to the stream + * + * From ftp://medical.nema.org/medical/dicom/2009/09_05pu3.pdf: + * + * Values with VRs constructed of character strings, except in + * the case of the VR UI, shall be padded with SPACE characters + * (20H, in the Default Character Repertoire) when necessary to + * achieve even length. Values with a VR of UI shall be padded + * with a single trailing NULL (00H) character when necessary + * to achieve even length. Values with a VR of OB shall be + * padded with a single trailing NULL byte value (00H) when + * necessary to achieve even length. + */ + if (strstr ("UI|OB", value_rep) != NULL) + { + g_byte_array_append (group_stream, (guint8 *) "\0", 1); + } + else + { + g_byte_array_append (group_stream, (guint8 *) " ", 1); + } + } +} + +/* Once a group has been built it has to be wrapped with a meta-group + * tag before it is written to the DICOM file. This is done by + * write_group_to_file. + */ +static gboolean +write_group_to_file (FILE *DICOM, + gint group, + GByteArray *group_stream) +{ + gboolean retval = TRUE; + guint16 swapped16; + guint32 swapped32; + + /* Add header to the group and output it */ + swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (group)); + + fwrite ((gchar *) &swapped16, 1, 2, DICOM); + fputc (0, DICOM); + fputc (0, DICOM); + fputc ('U', DICOM); + fputc ('L', DICOM); + + swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (4)); + fwrite ((gchar *) &swapped16, 1, 2, DICOM); + + swapped32 = g_ntohl (GUINT32_SWAP_LE_BE (group_stream->len)); + fwrite ((gchar *) &swapped32, 1, 4, DICOM); + + if (fwrite (group_stream->data, + 1, group_stream->len, DICOM) != group_stream->len) + retval = FALSE; + + g_byte_array_set_size (group_stream, 0); + + return retval; +} diff --git a/plug-ins/common/file-gbr.c b/plug-ins/common/file-gbr.c new file mode 100644 index 0000000..90c7b65 --- /dev/null +++ b/plug-ins/common/file-gbr.c @@ -0,0 +1,367 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * gbr plug-in version 1.00 + * Loads/exports version 2 GIMP .gbr files, by Tim Newsome + * Some bits stolen from the .99.7 source tree. + * + * Added in GBR version 1 support after learning that there wasn't a + * tool to read them. + * July 6, 1998 by Seth Burgess + * + * Dec 17, 2000 + * Load and save GIMP brushes in GRAY or RGBA. jtl + neo + * + * + * TODO: Give some better error reporting on not opening files/bad headers + * etc. + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-gbr-save" +#define PLUG_IN_BINARY "file-gbr" +#define PLUG_IN_ROLE "gimp-file-gbr" + + +typedef struct +{ + gchar description[256]; + gint spacing; +} BrushInfo; + + +/* local function prototypes */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean save_dialog (void); +static void entry_callback (GtkWidget *widget, + gpointer data); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +/* private variables */ + +static BrushInfo info = +{ + "GIMP Brush", + 10 +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "uri", "The URI of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-uri", "The URI of the file to export the image in" }, + { GIMP_PDB_INT32, "spacing", "Spacing of the brush" }, + { GIMP_PDB_STRING, "description", "Short description of the brush" } + }; + + gimp_install_procedure (SAVE_PROC, + "Exports files in the GIMP brush file format", + "Exports files in the GIMP brush file format", + "Tim Newsome, Jens Lautenbacher, Sven Neumann", + "Tim Newsome, Jens Lautenbacher, Sven Neumann", + "1997-2000", + N_("GIMP brush"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_plugin_icon_register (SAVE_PROC, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) GIMP_ICON_BRUSH); + gimp_register_file_handler_mime (SAVE_PROC, "image/x-gimp-gbr"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "gbr", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, SAVE_PROC) == 0) + { + GFile *file; + GimpParasite *parasite; + gint32 orig_image_ID; + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + file = g_file_new_for_uri (param[3].data.d_string); + + orig_image_ID = image_ID; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "GBR", + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &info); + + parasite = gimp_image_get_parasite (orig_image_ID, + "gimp-brush-name"); + if (parasite) + { + strncpy (info.description, + gimp_parasite_data (parasite), + MIN (sizeof (info.description), + gimp_parasite_data_size (parasite))); + info.description[sizeof (info.description) - 1] = '\0'; + + gimp_parasite_free (parasite); + } + else + { + gchar *name = g_path_get_basename (gimp_file_get_utf8_name (file)); + + if (g_str_has_suffix (name, ".gbr")) + name[strlen (name) - 4] = '\0'; + + if (strlen (name)) + { + strncpy (info.description, name, sizeof (info.description)); + info.description[sizeof (info.description) - 1] = '\0'; + } + + g_free (name); + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + info.spacing = (param[5].data.d_int32); + strncpy (info.description, param[6].data.d_string, + sizeof (info.description)); + info.description[sizeof (info.description) - 1] = '\0'; + } + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + GimpParam *save_retvals; + gint n_save_retvals; + + save_retvals = + gimp_run_procedure ("file-gbr-save-internal", + &n_save_retvals, + GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE, + GIMP_PDB_IMAGE, image_ID, + GIMP_PDB_DRAWABLE, drawable_ID, + GIMP_PDB_STRING, param[3].data.d_string, + GIMP_PDB_STRING, param[4].data.d_string, + GIMP_PDB_INT32, info.spacing, + GIMP_PDB_STRING, info.description, + GIMP_PDB_END); + + if (save_retvals[0].data.d_status == GIMP_PDB_SUCCESS) + { + gimp_set_data (SAVE_PROC, &info, sizeof (info)); + } + else + { + g_set_error (&error, 0, 0, + "Running procedure 'file-gbr-save-internal' " + "failed: %s", + gimp_get_pdb_error ()); + + status = GIMP_PDB_EXECUTION_ERROR; + } + + gimp_destroy_params (save_retvals, n_save_retvals); + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + if (strlen (info.description)) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new ("gimp-brush-name", + GIMP_PARASITE_PERSISTENT, + strlen (info.description) + 1, + info.description); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + } + else + { + gimp_image_detach_parasite (orig_image_ID, "gimp-brush-name"); + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *table; + GtkWidget *entry; + GtkWidget *spinbutton; + GtkAdjustment *adj; + gboolean run; + + dialog = gimp_export_dialog_new (_("Brush"), PLUG_IN_BINARY, SAVE_PROC); + + /* The main table */ + table = gtk_table_new (2, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + table, TRUE, TRUE, 0); + gtk_widget_show (table); + + entry = gtk_entry_new (); + gtk_entry_set_width_chars (GTK_ENTRY (entry), 20); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_entry_set_text (GTK_ENTRY (entry), info.description); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Description:"), 1.0, 0.5, + entry, 1, FALSE); + + g_signal_connect (entry, "changed", + G_CALLBACK (entry_callback), + info.description); + + adj = (GtkAdjustment *) gtk_adjustment_new (info.spacing, 1, 1000, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Spacing:"), 1.0, 0.5, + spinbutton, 1, TRUE); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &info.spacing); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +entry_callback (GtkWidget *widget, + gpointer data) +{ + strncpy (info.description, gtk_entry_get_text (GTK_ENTRY (widget)), + sizeof (info.description)); + info.description[sizeof (info.description) - 1] = '\0'; +} diff --git a/plug-ins/common/file-gegl.c b/plug-ins/common/file-gegl.c new file mode 100644 index 0000000..81943f5 --- /dev/null +++ b/plug-ins/common/file-gegl.c @@ -0,0 +1,481 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * file-gegl.c -- GEGL based file format plug-in + * Copyright (C) 2012 Simon Budig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_BINARY "file-gegl" + + +typedef struct _FileFormat FileFormat; + +struct _FileFormat +{ + const gchar *file_type; + const gchar *mime_type; + const gchar *extensions; + const gchar *magic; + + const gchar *load_proc; + const gchar *load_blurb; + const gchar *load_help; + + const gchar *save_proc; + const gchar *save_blurb; + const gchar *save_help; +}; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 load_image (const gchar *filename, + GError **error); +static gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + + +static const FileFormat file_formats[] = +{ + { + N_("Radiance RGBE"), + "image/vnd.radiance", + "hdr", + "0,string,?#", + + "file-load-rgbe", + "Load files in the RGBE file format", + "This procedure loads images in the RGBE format, using gegl:load", + + "file-save-rgbe", + "Saves files in the RGBE file format", + "This procedure exports images in the RGBE format, using gegl:save" + }, + { + N_("OpenEXR image"), + "image/x-exr", + "exr", + "0,lelong,20000630", + + /* no EXR loading (implemented in native GIMP plug-in) */ + NULL, NULL, NULL, + + "file-exr-save", + "Saves files in the OpenEXR file format", + "This procedure saves images in the OpenEXR format, using gegl:save" + } +}; + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load." }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" } + }; + + gint i; + + for (i = 0; i < G_N_ELEMENTS (file_formats); i++) + { + const FileFormat *format = &file_formats[i]; + + if (format->load_proc) + { + gimp_install_procedure (format->load_proc, + format->load_blurb, + format->load_help, + "Simon Budig", + "Simon Budig", + "2012", + format->file_type, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (format->load_proc, + format->mime_type); + gimp_register_magic_load_handler (format->load_proc, + format->extensions, + "", + format->magic); + } + + if (format->save_proc) + { + gimp_install_procedure (format->save_proc, + format->save_blurb, + format->save_help, + "Simon Budig", + "Simon Budig", + "2012", + format->file_type, + "*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (format->save_proc, + format->mime_type); + gimp_register_save_handler (format->save_proc, + format->extensions, ""); + } + } +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gint image_ID; + gint drawable_ID; + GError *error = NULL; + gint i; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + for (i = 0; i < G_N_ELEMENTS (file_formats); i++) + { + const FileFormat *format = &file_formats[i]; + + if (format->load_proc && !strcmp (name, format->load_proc)) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + break; + } + else if (format->save_proc && !strcmp (name, format->save_proc)) + { + GimpExportReturn export = GIMP_EXPORT_CANCEL; + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "GEGL", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + *nreturn_vals = 1; + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + if (! save_image (param[3].data.d_string, image_ID, drawable_ID, + &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + break; + } + } + + if (i == G_N_ELEMENTS (file_formats)) + status = GIMP_PDB_CALLING_ERROR; + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; + + gegl_exit (); +} + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + gint32 image_ID = -1; + gint32 layer_ID; + GimpImageType image_type; + GimpImageBaseType base_type; + GimpPrecision precision; + gint width; + gint height; + GeglNode *graph; + GeglNode *sink; + GeglNode *source; + GeglBuffer *src_buf = NULL; + GeglBuffer *dest_buf = NULL; + const Babl *format; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + graph = gegl_node_new (); + + source = gegl_node_new_child (graph, + "operation", "gegl:load", + "path", filename, + NULL); + sink = gegl_node_new_child (graph, + "operation", "gegl:buffer-sink", + "buffer", &src_buf, + NULL); + + gegl_node_connect_to (source, "output", + sink, "input"); + + gegl_node_process (sink); + + g_object_unref (graph); + + if (! src_buf) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not open '%s'"), + gimp_filename_to_utf8 (filename)); + return -1; + } + + gimp_progress_update (0.33); + + width = gegl_buffer_get_width (src_buf); + height = gegl_buffer_get_height (src_buf); + format = gegl_buffer_get_format (src_buf); + + if (babl_format_is_palette (format)) + { + base_type = GIMP_INDEXED; + + if (babl_format_has_alpha (format)) + image_type = GIMP_INDEXEDA_IMAGE; + else + image_type = GIMP_INDEXED_IMAGE; + + precision = GIMP_PRECISION_U8_GAMMA; + } + else + { + const Babl *model = babl_format_get_model (format); + const Babl *type = babl_format_get_type (format, 0); + gboolean linear = TRUE; + + if (model == babl_model ("Y") || + model == babl_model ("Y'") || + model == babl_model ("YA") || + model == babl_model ("Y'A")) + { + base_type = GIMP_GRAY; + + if (babl_format_has_alpha (format)) + image_type = GIMP_GRAYA_IMAGE; + else + image_type = GIMP_GRAY_IMAGE; + + if (model == babl_model ("Y'") || + model == babl_model ("Y'A")) + linear = FALSE; + } + else + { + base_type = GIMP_RGB; + + if (babl_format_has_alpha (format)) + image_type = GIMP_RGBA_IMAGE; + else + image_type = GIMP_RGB_IMAGE; + + if (model == babl_model ("R'G'B'") || + model == babl_model ("R'G'B'A")) + linear = FALSE; + } + + if (linear) + { + if (type == babl_type ("u8")) + precision = GIMP_PRECISION_U8_LINEAR; + else if (type == babl_type ("u16")) + precision = GIMP_PRECISION_U16_LINEAR; + else if (type == babl_type ("u32")) + precision = GIMP_PRECISION_U32_LINEAR; + else if (type == babl_type ("half")) + precision = GIMP_PRECISION_HALF_LINEAR; + else + precision = GIMP_PRECISION_FLOAT_LINEAR; + } + else + { + if (type == babl_type ("u8")) + precision = GIMP_PRECISION_U8_GAMMA; + else if (type == babl_type ("u16")) + precision = GIMP_PRECISION_U16_GAMMA; + else if (type == babl_type ("u32")) + precision = GIMP_PRECISION_U32_GAMMA; + else if (type == babl_type ("half")) + precision = GIMP_PRECISION_HALF_GAMMA; + else + precision = GIMP_PRECISION_FLOAT_GAMMA; + } + } + + + image_ID = gimp_image_new_with_precision (width, height, + base_type, precision); + gimp_image_set_filename (image_ID, filename); + + layer_ID = gimp_layer_new (image_ID, + _("Background"), + width, height, + image_type, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + dest_buf = gimp_drawable_get_buffer (layer_ID); + + gimp_progress_update (0.66); + + gegl_buffer_copy (src_buf, NULL, GEGL_ABYSS_NONE, dest_buf, NULL); + + g_object_unref (src_buf); + g_object_unref (dest_buf); + + gimp_progress_update (1.0); + + return image_ID; +} + +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglNode *graph; + GeglNode *source; + GeglNode *sink; + GeglBuffer *src_buf; + + src_buf = gimp_drawable_get_buffer (drawable_ID); + + graph = gegl_node_new (); + + source = gegl_node_new_child (graph, + "operation", "gegl:buffer-source", + "buffer", src_buf, + NULL); + sink = gegl_node_new_child (graph, + "operation", "gegl:save", + "path", filename, + NULL); + + gegl_node_connect_to (source, "output", + sink, "input"); + + gegl_node_process (sink); + + g_object_unref (graph); + g_object_unref (src_buf); + + return TRUE; +} diff --git a/plug-ins/common/file-gif-load.c b/plug-ins/common/file-gif-load.c new file mode 100644 index 0000000..e4702f4 --- /dev/null +++ b/plug-ins/common/file-gif-load.c @@ -0,0 +1,1252 @@ +/* GIF loading file filter for GIMP 2.x + * +-------------------------------------------------------------------+ + * | Copyright Adam D. Moss, Peter Mattis, Spencer Kimball | + * +-------------------------------------------------------------------+ + * Version 1.50.4 - 2003/06/03 + * Adam D. Moss - + */ + +/* Copyright notice for old GIF code from which this plugin was long ago */ +/* derived (David Koblas has kindly granted permission to relicense): */ +/* +-------------------------------------------------------------------+ */ +/* | Copyright 1990, 1991, 1993, David Koblas. (koblas@extra.com) | */ +/* +-------------------------------------------------------------------+ */ +/* Also... + * 'This filter uses code taken from the "giftopnm" and "ppmtogif" programs + * which are part of the "netpbm" package.' + */ +/* Additionally... + * "The Graphics Interchange Format(c) is the Copyright property of + * CompuServe Incorporated. GIF(sm) is a Service Mark property of + * CompuServe Incorporated." + */ + +/* + * REVISION HISTORY + * + * 2003/06/03 + * 1.50.04 - When initializing the LZW state, watch out for a completely + * bogus input_code_size [based on fix by Raphael Quinet] + * Also, fix a stupid old bug when clearing the code table between + * subimages. (Enables us to deal better with errors when the stream is + * corrupted pretty early in a subimage.) [adam] + * Minor-version-bump to distinguish between gimp1.2/1.4 branches. + * + * 2000/03/31 + * 1.00.03 - Just mildly more useful comments/messages concerning frame + * disposals. + * + * 1999/11/20 + * 1.00.02 - Fixed a couple of possible infinite loops where an + * error condition was not being checked. Also changed some g_message()s + * back to g_warning()s as they should be (don't get carried away with + * the user feedback fellahs, no-one wants to be told of every single + * corrupt byte and block in its own little window. :-( ). + * + * 1999/11/11 + * 1.00.01 - Fixed an uninitialized variable which has been around + * forever... thanks to jrb@redhat.com for noticing that there + * was a problem somewhere! + * + * 1999/03/20 + * 1.00.00 - GIF load-only code split from main GIF plugin. + * + * For previous revision information, please consult the comments + * in the 'gif' plugin. + */ + +/* + * TODO (more *'s means more important!) + * + * - PDB stuff for comments + * + * - Remove unused colormap entries for GRAYSCALE images. + */ + +#include "config.h" + +#include +#include + +#include + +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-gif-load" +#define LOAD_THUMB_PROC "file-gif-load-thumb" + + +/* uncomment the line below for a little debugging info */ +/* #define GIFDEBUG yesplease */ + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 load_image (const gchar *filename, + gboolean thumbnail, + GError **error); + + +static guchar used_cmap[3][256]; +static guchar highest_used_index; +static gboolean promote_to_rgb = FALSE; +static guchar gimp_cmap[768]; +static GimpParasite *comment_parasite = NULL; + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + static const GimpParamDef thumb_args[] = + { + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_INT32, "thumb-size", "Preferred thumbnail size" } + }; + static const GimpParamDef thumb_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" }, + { GIMP_PDB_INT32, "image-width", "Width of full-sized image" }, + { GIMP_PDB_INT32, "image-height", "Height of full-sized image" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files of Compuserve GIF file format", + "FIXME: write help for gif_load", + "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas", + "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas", + "1995-2006", + N_("GIF image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/gif"); + gimp_register_magic_load_handler (LOAD_PROC, + "gif", + "", + "0,string,GIF8"); + + gimp_install_procedure (LOAD_THUMB_PROC, + "Loads only the first frame of a GIF image, to be " + "used as a thumbnail", + "", + "Sven Neumann", + "Sven Neumann", + "2006", + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (thumb_args), + G_N_ELEMENTS (thumb_return_vals), + thumb_args, thumb_return_vals); + + gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[4]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + gint32 image_ID; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, FALSE, &error); + } + else if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + image_ID = load_image (param[0].data.d_string, TRUE, &error); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (image_ID != -1) + { + /* The GIF format only tells you how many bits per pixel + * are in the image, not the actual number of used indices (D'OH!) + * + * So if we're not careful, repeated load/save of a transparent GIF + * without intermediate indexed->RGB->indexed pumps up the number of + * bits used, as we add an index each time for the transparent + * color. Ouch. We either do some heavier analysis at save-time, + * or trim down the number of GIMP colors at load-time. We do the + * latter for now. + */ +#ifdef GIFDEBUG + g_print ("GIF: Highest used index is %d\n", highest_used_index); +#endif + if (! promote_to_rgb) + gimp_image_set_colormap (image_ID, + gimp_cmap, highest_used_index + 1); + + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + + if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + *nreturn_vals = 4; + values[2].type = GIMP_PDB_INT32; + values[2].data.d_int32 = gimp_image_width (image_ID); + values[3].type = GIMP_PDB_INT32; + values[3].data.d_int32 = gimp_image_height (image_ID); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + + if (error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + } + + values[0].data.d_status = status; +} + + +#define MAXCOLORMAPSIZE 256 + +#define CM_RED 0 +#define CM_GREEN 1 +#define CM_BLUE 2 + +#define MAX_LZW_BITS 12 + +#define INTERLACE 0x40 +#define LOCALCOLORMAP 0x80 +#define BitSet(byte, bit) (((byte) & (bit)) == (bit)) + +#define ReadOK(file,buffer,len) (fread(buffer, len, 1, file) != 0) +#define LM_to_uint(a,b) (((b)<<8)|(a)) + +#define GRAYSCALE 1 +#define COLOR 2 + +typedef guchar CMap[3][MAXCOLORMAPSIZE]; + +static struct +{ + guint Width; + guint Height; + CMap ColorMap; + guint BitPixel; + guint ColorResolution; + guint Background; + guint AspectRatio; + /* + ** + */ + gint GrayScale; +} GifScreen; + +static struct +{ + gint transparent; + gint delayTime; + gint inputFlag; + gint disposal; +} Gif89 = { -1, -1, -1, 0 }; + +static gboolean ReadColorMap (FILE *fd, + gint number, + CMap buffer, + gint *format); +static gint DoExtension (FILE *fd, + gint label); +static gint GetDataBlock (FILE *fd, + guchar *buf); +static gint GetCode (FILE *fd, + gint code_size, + gboolean flag); +static gint LZWReadByte (FILE *fd, + gint just_reset_LZW, + gint input_code_size); +static gboolean ReadImage (FILE *fd, + const gchar *filename, + gint len, + gint height, + CMap cmap, + gint ncols, + gint format, + gint interlace, + gint number, + guint leftpos, + guint toppos, + guint screenwidth, + guint screenheight, + gint32 *image_ID); + + +static gint32 +load_image (const gchar *filename, + gboolean thumbnail, + GError **error) +{ + FILE *fd; + guchar buf[16]; + guchar c; + CMap localColorMap; + gint grayScale; + gboolean useGlobalColormap; + gint bitPixel; + gint imageCount = 0; + gchar version[4]; + gint32 image_ID = -1; + gboolean status; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + fd = g_fopen (filename, "rb"); + + if (! fd) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + if (! ReadOK (fd, buf, 6)) + { + g_message ("Error reading magic number"); + fclose (fd); + return -1; + } + + if (strncmp ((gchar *) buf, "GIF", 3) != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", _("This is not a GIF file")); + fclose (fd); + return -1; + } + + strncpy (version, (gchar *) buf + 3, 3); + version[3] = '\0'; + + if ((strcmp (version, "87a") != 0) && (strcmp (version, "89a") != 0)) + { + g_message ("Bad version number, not '87a' or '89a'"); + fclose (fd); + return -1; + } + + if (! ReadOK (fd, buf, 7)) + { + g_message ("Failed to read screen descriptor"); + fclose (fd); + return -1; + } + + GifScreen.Width = LM_to_uint (buf[0], buf[1]); + GifScreen.Height = LM_to_uint (buf[2], buf[3]); + GifScreen.BitPixel = 2 << (buf[4] & 0x07); + GifScreen.ColorResolution = (((buf[4] & 0x70) >> 3) + 1); + GifScreen.Background = buf[5]; + GifScreen.AspectRatio = buf[6]; + + if (BitSet (buf[4], LOCALCOLORMAP)) + { + /* Global Colormap */ + if (! ReadColorMap (fd, GifScreen.BitPixel, GifScreen.ColorMap, + &GifScreen.GrayScale)) + { + g_message ("Error reading global colormap"); + fclose (fd); + return -1; + } + } + + if (GifScreen.AspectRatio != 0 && GifScreen.AspectRatio != 49) + { + g_message (_("Non-square pixels. Image might look squashed.")); + } + + + highest_used_index = 0; + + while (TRUE) + { + if (! ReadOK (fd, &c, 1)) + { + g_message ("EOF / read error on image data"); + fclose (fd); + return image_ID; /* will be -1 if failed on first image! */ + } + + if (c == ';') + { + /* GIF terminator */ + fclose (fd); + return image_ID; + } + + if (c == '!') + { + /* Extension */ + if (! ReadOK (fd, &c, 1)) + { + g_message ("EOF / read error on extension function code"); + fclose (fd); + return image_ID; /* will be -1 if failed on first image! */ + } + + DoExtension (fd, c); + continue; + } + + if (c != ',') + { + /* Not a valid start character */ + g_printerr ("GIF: bogus character 0x%02x, ignoring.\n", (int) c); + continue; + } + + ++imageCount; + + if (! ReadOK (fd, buf, 9)) + { + g_message ("Couldn't read left/top/width/height"); + fclose (fd); + return image_ID; /* will be -1 if failed on first image! */ + } + + useGlobalColormap = !BitSet (buf[8], LOCALCOLORMAP); + + bitPixel = 1 << ((buf[8] & 0x07) + 1); + + if (! useGlobalColormap) + { + if (! ReadColorMap (fd, bitPixel, localColorMap, &grayScale)) + { + g_message ("Error reading local colormap"); + fclose (fd); + return image_ID; /* will be -1 if failed on first image! */ + } + + status = ReadImage (fd, filename, LM_to_uint (buf[4], buf[5]), + LM_to_uint (buf[6], buf[7]), + localColorMap, bitPixel, + grayScale, + BitSet (buf[8], INTERLACE), imageCount, + (guint) LM_to_uint (buf[0], buf[1]), + (guint) LM_to_uint (buf[2], buf[3]), + GifScreen.Width, + GifScreen.Height, + &image_ID); + } + else + { + status = ReadImage (fd, filename, LM_to_uint (buf[4], buf[5]), + LM_to_uint (buf[6], buf[7]), + GifScreen.ColorMap, GifScreen.BitPixel, + GifScreen.GrayScale, + BitSet (buf[8], INTERLACE), imageCount, + (guint) LM_to_uint (buf[0], buf[1]), + (guint) LM_to_uint (buf[2], buf[3]), + GifScreen.Width, + GifScreen.Height, + &image_ID); + } + + if (!status) + { + break; + } + + if (comment_parasite != NULL) + { + if (! thumbnail) + gimp_image_attach_parasite (image_ID, comment_parasite); + + gimp_parasite_free (comment_parasite); + comment_parasite = NULL; + } + + /* If we are loading a thumbnail, we stop after the first frame. */ + if (thumbnail) + break; + } + + fclose (fd); + + return image_ID; +} + +static gboolean +ReadColorMap (FILE *fd, + gint number, + CMap buffer, + gint *format) +{ + guchar rgb[3]; + gint flag; + gint i; + + flag = TRUE; + + for (i = 0; i < number; ++i) + { + if (! ReadOK (fd, rgb, sizeof (rgb))) + return FALSE; + + buffer[CM_RED][i] = rgb[0]; + buffer[CM_GREEN][i] = rgb[1]; + buffer[CM_BLUE][i] = rgb[2]; + + flag &= (rgb[0] == rgb[1] && rgb[1] == rgb[2]); + } + + *format = (flag) ? GRAYSCALE : COLOR; + + return TRUE; +} + +static gint +DoExtension (FILE *fd, + gint label) +{ + static guchar buf[256]; +#ifdef GIFDEBUG + gchar *str; +#endif + + switch (label) + { + case 0x01: /* Plain Text Extension */ +#ifdef GIFDEBUG + str = "Plain Text Extension"; +#endif + +#ifdef notdef + if (GetDataBlock (fd, (guchar *) buf) == 0) + ; + + lpos = LM_to_uint (buf[0], buf[1]); + tpos = LM_to_uint (buf[2], buf[3]); + width = LM_to_uint (buf[4], buf[5]); + height = LM_to_uint (buf[6], buf[7]); + cellw = buf[8]; + cellh = buf[9]; + foreground = buf[10]; + background = buf[11]; + + while (GetDataBlock (fd, (guchar *) buf) > 0) + { + PPM_ASSIGN (image[ypos][xpos], + cmap[CM_RED][v], + cmap[CM_GREEN][v], + cmap[CM_BLUE][v]); + ++index; + } + + return FALSE; +#else + break; +#endif + + case 0xff: /* Application Extension */ +#ifdef GIFDEBUG + str = "Application Extension"; +#endif + break; + case 0xfe: /* Comment Extension */ +#ifdef GIFDEBUG + str = "Comment Extension"; +#endif + while (GetDataBlock (fd, (guchar *) buf) > 0) + { + gchar *comment = (gchar *) buf; + + if (! g_utf8_validate (comment, -1, NULL)) + continue; + + if (comment_parasite) + gimp_parasite_free (comment_parasite); + + comment_parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (comment) + 1, comment); + } + return TRUE; + break; + + case 0xf9: /* Graphic Control Extension */ +#ifdef GIFDEBUG + str = "Graphic Control Extension"; +#endif + (void) GetDataBlock (fd, (guchar *) buf); + Gif89.disposal = (buf[0] >> 2) & 0x7; + Gif89.inputFlag = (buf[0] >> 1) & 0x1; + Gif89.delayTime = LM_to_uint (buf[1], buf[2]); + if ((buf[0] & 0x1) != 0) + Gif89.transparent = buf[3]; + else + Gif89.transparent = -1; + + while (GetDataBlock (fd, (guchar *) buf) > 0); + + return FALSE; + break; + + default: +#ifdef GIFDEBUG + str = (gchar *)buf; +#endif + sprintf ((gchar *)buf, "UNKNOWN (0x%02x)", label); + break; + } + +#ifdef GIFDEBUG + g_print ("GIF: got a '%s'\n", str); +#endif + + while (GetDataBlock (fd, (guchar *) buf) > 0); + + return FALSE; +} + +static gint ZeroDataBlock = FALSE; + +static gint +GetDataBlock (FILE *fd, + guchar *buf) +{ + guchar count; + + if (! ReadOK (fd, &count, 1)) + { + g_message ("Error in getting DataBlock size"); + return -1; + } + + ZeroDataBlock = (count == 0); + + if ((count != 0) && (! ReadOK (fd, buf, count))) + { + g_message ("Error in reading DataBlock"); + return -1; + } + + return count; +} + +static gint +GetCode (FILE *fd, + gint code_size, + gboolean flag) +{ + static guchar buf[280]; + static gint curbit, lastbit, done, last_byte; + gint i, j, ret, count; + + if (flag) + { + curbit = 0; + lastbit = 0; + done = FALSE; + last_byte = 2; + return 0; + } + + while ((curbit + code_size) > lastbit) + { + if (done) + { + if (curbit >= lastbit) + g_message ("Ran off the end of my bits"); + + return -1; + } + + buf[0] = buf[last_byte - 2]; + buf[1] = buf[last_byte - 1]; + + count = GetDataBlock (fd, &buf[2]); + if (count < 0) + { + return -1; + } + else if (count == 0) + { + done = TRUE; + } + + last_byte = 2 + count; + curbit = (curbit - lastbit) + 16; + lastbit = (2 + count) * 8; + } + + ret = 0; + for (i = curbit, j = 0; j < code_size; ++i, ++j) + ret |= ((buf[i / 8] & (1 << (i % 8))) != 0) << j; + + curbit += code_size; + + return ret; +} + +static gint +LZWReadByte (FILE *fd, + gint just_reset_LZW, + gint input_code_size) +{ + static gint fresh = FALSE; + gint code, incode; + static gint code_size, set_code_size; + static gint max_code, max_code_size; + static gint firstcode, oldcode; + static gint clear_code, end_code; + static gint table[2][(1 << MAX_LZW_BITS)]; +#define STACK_SIZE ((1 << (MAX_LZW_BITS)) * 2) + static gint stack[STACK_SIZE], *sp; + gint i; + + if (just_reset_LZW) + { + if (input_code_size > MAX_LZW_BITS || input_code_size <= 1) + { + g_message ("Value out of range for code size (corrupted file?)"); + return -1; + } + + set_code_size = input_code_size; + code_size = set_code_size + 1; + clear_code = 1 << set_code_size; + end_code = clear_code + 1; + max_code_size = 2 * clear_code; + max_code = clear_code + 2; + + if (GetCode (fd, 0, TRUE) < 0) + { + return -1; + } + + fresh = TRUE; + + sp = stack; + + for (i = 0; i < clear_code; ++i) + { + table[0][i] = 0; + table[1][i] = i; + } + for (; i < (1 << MAX_LZW_BITS); ++i) + { + table[0][i] = 0; + table[1][i] = 0; + } + + return 0; + } + else if (fresh) + { + fresh = FALSE; + do + { + firstcode = oldcode = GetCode (fd, code_size, FALSE); + } + while (firstcode == clear_code); + + if (firstcode < 0) + { + return -1; + } + + return firstcode & 255; + } + + if (sp > stack) + return (*--sp) & 255; + + while ((code = GetCode (fd, code_size, FALSE)) >= 0) + { + if (code == clear_code) + { + for (i = 0; i < clear_code; ++i) + { + table[0][i] = 0; + table[1][i] = i; + } + for (; i < (1 << MAX_LZW_BITS); ++i) + { + table[0][i] = 0; + table[1][i] = 0; + } + + code_size = set_code_size + 1; + max_code_size = 2 * clear_code; + max_code = clear_code + 2; + sp = stack; + firstcode = oldcode = GetCode (fd, code_size, FALSE); + + if (firstcode < 0) + { + return -1; + } + + return firstcode & 255; + } + else if (code == end_code || code > max_code) + { + gint count; + guchar buf[260]; + + if (ZeroDataBlock) + return -2; + + while ((count = GetDataBlock (fd, buf)) > 0) + ; + + if (count != 0) + g_print ("GIF: missing EOD in data stream (common occurrence)"); + + return -2; + } + + incode = code; + + if (code == max_code) + { + if (sp < &(stack[STACK_SIZE])) + *sp++ = firstcode; + code = oldcode; + } + + while (code >= clear_code && sp < &(stack[STACK_SIZE])) + { + *sp++ = table[1][code]; + if (code == table[0][code]) + { + g_message ("Circular table entry. Corrupt file."); + gimp_quit (); + } + code = table[0][code]; + } + + if (sp < &(stack[STACK_SIZE])) + *sp++ = firstcode = table[1][code]; + + if ((code = max_code) < (1 << MAX_LZW_BITS)) + { + table[0][code] = oldcode; + table[1][code] = firstcode; + ++max_code; + if ((max_code >= max_code_size) && + (max_code_size < (1 << MAX_LZW_BITS))) + { + max_code_size *= 2; + ++code_size; + } + } + + oldcode = incode; + + if (sp > stack) + return (*--sp) & 255; + } + + if (code < 0) + { + return -1; + } + + return code & 255; +} + +static gboolean +ReadImage (FILE *fd, + const gchar *filename, + gint len, + gint height, + CMap cmap, + gint ncols, + gint format, + gint interlace, + gint number, + guint leftpos, + guint toppos, + guint screenwidth, + guint screenheight, + gint32 *image_ID) +{ + static gint frame_number = 1; + + gint32 layer_ID; + GeglBuffer *buffer; + guchar *dest, *temp; + guchar c; + gint xpos = 0, ypos = 0, pass = 0; + gint cur_progress, max_progress; + gint v; + gint i, j; + gchar *framename; + gchar *framename_ptr; + gboolean alpha_frame = FALSE; + static gint previous_disposal; + + /* Guard against bogus frame size */ + if (len < 1 || height < 1) + { + g_message ("Bogus frame dimensions"); + *image_ID = -1; + return FALSE; + } + + /* + ** Initialize the Compression routines + */ + if (! ReadOK (fd, &c, 1)) + { + g_message ("EOF / read error on image data"); + *image_ID = -1; + return FALSE; + } + + if (LZWReadByte (fd, TRUE, c) < 0) + { + g_message ("Error while reading"); + *image_ID = -1; + return FALSE; + } + + if (frame_number == 1) + { + /* Guard against bogus logical screen size values */ + if (screenwidth == 0) + screenwidth = len; + + if (screenheight == 0) + screenheight = height; + + *image_ID = gimp_image_new (screenwidth, screenheight, GIMP_INDEXED); + gimp_image_set_filename (*image_ID, filename); + + for (i = 0, j = 0; i < ncols; i++) + { + used_cmap[0][i] = gimp_cmap[j++] = cmap[0][i]; + used_cmap[1][i] = gimp_cmap[j++] = cmap[1][i]; + used_cmap[2][i] = gimp_cmap[j++] = cmap[2][i]; + } + + gimp_image_set_colormap (*image_ID, gimp_cmap, ncols); + + if (Gif89.delayTime < 0) + framename = g_strdup (_("Background")); + else + framename = g_strdup_printf (_("Background (%d%s)"), + 10 * Gif89.delayTime, "ms"); + + previous_disposal = Gif89.disposal; + + if (Gif89.transparent == -1) + { + layer_ID = gimp_layer_new (*image_ID, framename, + len, height, + GIMP_INDEXED_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (*image_ID)); + } + else + { + layer_ID = gimp_layer_new (*image_ID, framename, + len, height, + GIMP_INDEXEDA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (*image_ID)); + alpha_frame=TRUE; + } + + g_free (framename); + } + else /* NOT FIRST FRAME */ + { + gimp_progress_set_text_printf (_("Opening '%s' (frame %d)"), + gimp_filename_to_utf8 (filename), + frame_number); + gimp_progress_pulse (); + + /* If the colormap is now different, we have to promote to RGB! */ + if (! promote_to_rgb) + { + for (i = 0; i < ncols; i++) + { + if ((used_cmap[0][i] != cmap[0][i]) || + (used_cmap[1][i] != cmap[1][i]) || + (used_cmap[2][i] != cmap[2][i])) + { + /* Everything is RGB(A) from now on... sigh. */ + promote_to_rgb = TRUE; + + /* Promote everything we have so far into RGB(A) */ +#ifdef GIFDEBUG + g_print ("GIF: Promoting image to RGB...\n"); +#endif + gimp_image_convert_rgb (*image_ID); + + break; + } + } + } + + if (Gif89.delayTime < 0) + framename = g_strdup_printf (_("Frame %d"), frame_number); + else + framename = g_strdup_printf (_("Frame %d (%d%s)"), + frame_number, 10 * Gif89.delayTime, "ms"); + + switch (previous_disposal) + { + case 0x00: + break; /* 'don't care' */ + case 0x01: + framename_ptr = framename; + framename = g_strconcat (framename, " (combine)", NULL); + g_free (framename_ptr); + break; + case 0x02: + framename_ptr = framename; + framename = g_strconcat (framename, " (replace)", NULL); + g_free (framename_ptr); + break; + case 0x03: /* Rarely-used, and unhandled by many + loaders/players (including GIMP: we treat as + 'combine' mode). */ + framename_ptr = framename; + framename = g_strconcat (framename, " (combine) (!)", NULL); + g_free (framename_ptr); + break; + case 0x04: /* I've seen a composite of this type. stvo_online_banner2.gif */ + case 0x05: + case 0x06: /* I've seen a composite of this type. bn31.Gif */ + case 0x07: + framename_ptr = framename; + framename = g_strconcat (framename, " (unknown disposal)", NULL); + g_free (framename_ptr); + g_message (_("GIF: Undocumented GIF composite type %d is " + "not handled. Animation might not play or " + "re-save perfectly."), + previous_disposal); + break; + default: + g_message ("Disposal word got corrupted. Bug."); + break; + } + previous_disposal = Gif89.disposal; + + layer_ID = gimp_layer_new (*image_ID, framename, + len, height, + promote_to_rgb ? + GIMP_RGBA_IMAGE : GIMP_INDEXEDA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (*image_ID)); + alpha_frame = TRUE; + g_free (framename); + } + + frame_number++; + + gimp_image_insert_layer (*image_ID, layer_ID, -1, 0); + gimp_item_transform_translate (layer_ID, (gint) leftpos, (gint) toppos); + + cur_progress = 0; + max_progress = height; + + if (len > (G_MAXSIZE / height / (alpha_frame ? (promote_to_rgb ? 4 : 2) : 1))) + { + g_message ("'%s' has a larger image size than GIMP can handle.", + gimp_filename_to_utf8 (filename)); + *image_ID = -1; + return FALSE; + } + + if (alpha_frame) + dest = (guchar *) g_malloc ((gsize)len * (gsize)height * (promote_to_rgb ? 4 : 2)); + else + dest = (guchar *) g_malloc ((gsize)len * (gsize)height); + +#ifdef GIFDEBUG + g_print ("GIF: reading %d by %d%s GIF image, ncols=%d\n", + len, height, interlace ? " interlaced" : "", ncols); +#endif + + if (! alpha_frame && promote_to_rgb) + { + /* I don't see how one would easily construct a GIF in which + this could happen, but it's a mad mad world. */ + g_message ("Ouch! Can't handle non-alpha RGB frames.\n" + "Please file a bug report at " + "https://gitlab.gnome.org/GNOME/gimp/issues"); + gimp_quit (); + } + + while ((v = LZWReadByte (fd, FALSE, c)) >= 0) + { + if (alpha_frame) + { + if (((guchar) v > highest_used_index) && !(v == Gif89.transparent)) + highest_used_index = (guchar) v; + + if (promote_to_rgb) + { + temp = dest + ( (ypos * len) + xpos ) * 4; + *(temp ) = (guchar) cmap[0][v]; + *(temp+1) = (guchar) cmap[1][v]; + *(temp+2) = (guchar) cmap[2][v]; + *(temp+3) = (guchar) ((v == Gif89.transparent) ? 0 : 255); + } + else + { + temp = dest + ( (ypos * len) + xpos ) * 2; + *temp = (guchar) v; + *(temp+1) = (guchar) ((v == Gif89.transparent) ? 0 : 255); + } + } + else + { + if ((guchar) v > highest_used_index) + highest_used_index = (guchar) v; + + temp = dest + (ypos * len) + xpos; + *temp = (guchar) v; + } + + xpos++; + if (xpos == len) + { + xpos = 0; + if (interlace) + { + switch (pass) + { + case 0: + case 1: + ypos += 8; + break; + case 2: + ypos += 4; + break; + case 3: + ypos += 2; + break; + } + + if (ypos >= height) + { + pass++; + switch (pass) + { + case 1: + ypos = 4; + break; + case 2: + ypos = 2; + break; + case 3: + ypos = 1; + break; + default: + goto fini; + } + } + } + else + { + ypos++; + } + + if (frame_number == 1) + { + cur_progress++; + if ((cur_progress % 16) == 0) + gimp_progress_update ((gdouble) cur_progress / + (gdouble) max_progress); + } + } + + if (ypos >= height) + break; + } + + if (v < 0) + { + return FALSE; + } + + fini: + buffer = gimp_drawable_get_buffer (layer_ID); + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, len, height), 0, + NULL, dest, GEGL_AUTO_ROWSTRIDE); + + g_free (dest); + + g_object_unref (buffer); + + gimp_progress_update (1.0); + + if (LZWReadByte (fd, FALSE, c) >= 0) + { + g_print ("GIF: too much input data, ignoring extra...\n"); + return FALSE; + } + + return TRUE; +} diff --git a/plug-ins/common/file-gif-save.c b/plug-ins/common/file-gif-save.c new file mode 100644 index 0000000..cd849be --- /dev/null +++ b/plug-ins/common/file-gif-save.c @@ -0,0 +1,2551 @@ +/* GIF exporting file filter for GIMP + * + * Copyright + * - Adam D. Moss + * - Peter Mattis + * - Spencer Kimball + * + * Based around original GIF code by David Koblas. + * + * + * Version 4.1.0 - 2003-06-16 + * Adam D. Moss - + */ +/* + * This filter uses code taken from the "giftopnm" and "ppmtogif" programs + * which are part of the "netpbm" package. + */ +/* + * "The Graphics Interchange Format(c) is the Copyright property of + * CompuServe Incorporated. GIF(sm) is a Service Mark property of + * CompuServe Incorporated." + */ +/* Copyright notice for GIF code from which this plugin was long ago */ +/* derived (David Koblas has granted permission to relicense): */ +/* +-------------------------------------------------------------------+ */ +/* | Copyright 1990, 1991, 1993, David Koblas. (koblas@extra.com) | */ +/* +-------------------------------------------------------------------+ */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-gif-save" +#define SAVE2_PROC "file-gif-save2" +#define PLUG_IN_BINARY "file-gif-save" +#define PLUG_IN_ROLE "gimp-file-gif-save" + + +/* uncomment the line below for a little debugging info */ +/* #define GIFDEBUG yesplease */ + + +enum +{ + DISPOSE_STORE_VALUE_COLUMN, + DISPOSE_STORE_LABEL_COLUMN +}; + +enum +{ + DISPOSE_UNSPECIFIED, + DISPOSE_COMBINE, + DISPOSE_REPLACE +}; + +typedef struct +{ + gint interlace; + gint save_comment; + gint loop; + gint default_delay; + gint default_dispose; + gboolean always_use_default_delay; + gboolean always_use_default_dispose; + gboolean as_animation; +} GIFSaveVals; + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + GError **error); + +static GimpPDBStatusType sanity_check (GFile *file, + gint32 *image_ID, + GimpRunMode run_mode, + GError **error); +static gboolean bad_bounds_dialog (void); + +static gboolean save_dialog (gint32 image_ID); +static void comment_entry_callback (GtkTextBuffer *buffer); + + +static gboolean comment_was_edited = FALSE; +static gchar *globalcomment = NULL; +static gint Interlace; /* For compression code */ + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static GIFSaveVals gsvals = +{ + FALSE, /* interlace */ + TRUE, /* save comment */ + TRUE, /* loop infinitely */ + 100, /* default_delay between frames (100ms) */ + 0, /* default_dispose = "don't care" */ + FALSE, /* don't always use default_delay */ + FALSE, /* don't always use default_dispose */ + FALSE /* as_animation */ +}; + + +MAIN () + +#define COMMON_SAVE_ARGS \ + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, \ + { GIMP_PDB_IMAGE, "image", "Image to export" }, \ + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, \ + { GIMP_PDB_STRING, "uri", "The name of the URI to export the image in" }, \ + { GIMP_PDB_STRING, "raw-uri", "The name of the URI to export the image in" }, \ + { GIMP_PDB_INT32, "interlace", "Try to export as interlaced" }, \ + { GIMP_PDB_INT32, "loop", "(animated gif) loop infinitely" }, \ + { GIMP_PDB_INT32, "default-delay", "(animated gif) Default delay between frames in milliseconds" }, \ + { GIMP_PDB_INT32, "default-dispose", "(animated gif) Default disposal type (0=`don't care`, 1=combine, 2=replace)" } + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + COMMON_SAVE_ARGS + }; + + static const GimpParamDef save2_args[] = + { + COMMON_SAVE_ARGS, + { GIMP_PDB_INT32, "as-animation", "Export GIF as animation?" }, + { GIMP_PDB_INT32, "force-delay", "(animated gif) Use specified delay for all frames?" }, + { GIMP_PDB_INT32, "force-dispose", "(animated gif) Use specified disposal for all frames?" } + }; + + gimp_install_procedure (SAVE_PROC, + "exports files in Compuserve GIF file format", + "Export a file in Compuserve GIF format, with " + "possible animation, transparency, and comment. " + "To export an animation, operate on a multi-layer " + "file. The plug-in will interpret <50% alpha as " + "transparent. When run non-interactively, the " + "value for the comment is taken from the " + "'gimp-comment' parasite. ", + "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas", + "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas", + "1995-1997", + N_("GIF image"), + "INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (SAVE2_PROC, + "exports files in Compuserve GIF file format", + "Export a file in Compuserve GIF format, with " + "possible animation, transparency, and comment. " + "To export an animation, operate on a multi-layer " + "file and give the 'as-animation' parameter " + "as TRUE. The plug-in will interpret <50% " + "alpha as transparent. When run " + "non-interactively, the value for the comment " + "is taken from the 'gimp-comment' parasite. ", + "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas", + "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas", + "1995-1997", + N_("GIF image"), + "INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (save2_args), 0, + save2_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/gif"); + gimp_register_save_handler (SAVE_PROC, "gif", ""); + gimp_register_file_handler_uri (SAVE_PROC); + + gimp_register_file_handler_uri (SAVE2_PROC); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, SAVE_PROC) == 0 || + strcmp (name, SAVE2_PROC) == 0) + { + GFile *file; + gint32 image_ID; + gint32 orig_image_ID; + gint32 sanitized_image_ID = 0; + gint32 drawable_ID; + + image_ID = orig_image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + file = g_file_new_for_uri (param[3].data.d_string); + + if (run_mode == GIMP_RUN_INTERACTIVE || + run_mode == GIMP_RUN_WITH_LAST_VALS) + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + status = sanity_check (file, &image_ID, run_mode, &error); + + /* Get the export options */ + if (status == GIMP_PDB_SUCCESS) + { + /* If the sanity check succeeded, the image_ID will point to + * a duplicate image to delete later. + */ + sanitized_image_ID = image_ID; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &gsvals); + + /* First acquire information with a dialog */ + if (! save_dialog (image_ID)) + { + gimp_image_delete (sanitized_image_ID); + status = GIMP_PDB_CANCEL; + } + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 9 && nparams != 12) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + gsvals.interlace = (param[5].data.d_int32) ? TRUE : FALSE; + gsvals.save_comment = TRUE; /* no way to to specify that through the PDB */ + gsvals.loop = (param[6].data.d_int32) ? TRUE : FALSE; + gsvals.default_delay = param[7].data.d_int32; + gsvals.default_dispose = param[8].data.d_int32; + if (nparams == 12) + { + gsvals.as_animation = (param[9].data.d_int32) ? TRUE : FALSE; + gsvals.always_use_default_delay = (param[10].data.d_int32) ? TRUE : FALSE; + gsvals.always_use_default_dispose = (param[11].data.d_int32) ? TRUE : FALSE; + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &gsvals); + break; + + default: + break; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Create an exportable image based on the export options */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + { + GimpExportCapabilities capabilities = + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_ALPHA; + + if (gsvals.as_animation) + capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS; + + export = gimp_export_image (&image_ID, &drawable_ID, "GIF", + capabilities); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + if (sanitized_image_ID) + gimp_image_delete (sanitized_image_ID); + return; + } + } + break; + default: + break; + } + + /* Write the image to file */ + if (save_image (file, + image_ID, drawable_ID, orig_image_ID, + &error)) + { + /* Store psvals data */ + gimp_set_data (SAVE_PROC, &gsvals, sizeof (GIFSaveVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + gimp_image_delete (sanitized_image_ID); + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + g_object_unref (file); + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +/* ppmtogif.c - read a portable pixmap and produce a GIF file +** +** Based on GIFENCOD by David Rowley . A +** Lempel-Ziv compression based on "compress". +** +** Modified by Marcel Wijkstra +** +** +** Copyright (C) 1989 by Jef Poskanzer. +** +** Permission to use, copy, modify, and distribute this software and its +** documentation for any purpose and without fee is hereby granted, provided +** that the above copyright notice appear in all copies and that both that +** copyright notice and this permission notice appear in supporting +** documentation. This software is provided "as is" without express or +** implied warranty. +** +** The Graphics Interchange Format(c) is the Copyright property of +** CompuServe Incorporated. GIF(sm) is a Service Mark property of +** CompuServe Incorporated. +*/ + +#define MAXCOLORS 256 + +/* + * Pointer to function returning an int + */ +typedef gint (* ifunptr) (gint x, + gint y); + + +static gint find_unused_ia_color (const guchar *pixels, + gint numpixels, + gint num_indices, + gint *colors); + +static void special_flatten_indexed_alpha (guchar *pixels, + gint transparent, + gint numpixels); + +static gint colors_to_bpp (gint colors); +static gint bpp_to_colors (gint bpp); +static gint get_pixel (gint x, + gint y); +static gint gif_next_pixel (ifunptr getpixel); +static void bump_pixel (void); + +static gboolean gif_encode_header (GOutputStream *output, + gboolean gif89, + gint width, + gint height, + gint background, + gint bpp, + gint *red, + gint *green, + gint *blue, + ifunptr get_pixel, + GError **error); +static gboolean gif_encode_graphic_control_ext (GOutputStream *output, + gint disposal, + gint delay89, + gint n_frames, + gint width, + gint height, + gint transparent, + gint bpp, + ifunptr get_pixel, + GError **error); +static gboolean gif_encode_image_data (GOutputStream *output, + gint width, + gint height, + gint interlace, + gint bpp, + ifunptr get_pixel, + gint offset_x, + gint offset_y, + GError **error); +static gboolean gif_encode_close (GOutputStream *output, + GError **error); +static gboolean gif_encode_loop_ext (GOutputStream *output, + guint n_loops, + GError **error); +static gboolean gif_encode_comment_ext (GOutputStream *output, + const gchar *comment, + GError **error); + +static gint rowstride; +static guchar *pixels; +static gint cur_progress; +static gint max_progress; + +static gboolean compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error); +static gboolean no_compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error); +static gboolean rle_compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error); +static gboolean normal_compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error); + +static gboolean put_byte (GOutputStream *output, + guchar b, + GError **error); +static gboolean put_word (GOutputStream *output, + gint w, + GError **error); +static gboolean put_string (GOutputStream *output, + const gchar *s, + GError **error); +static gboolean output_code (GOutputStream *output, + gint code, + GError **error); +static gboolean cl_block (GOutputStream *output, + GError **error); +static void cl_hash (glong hsize); + +static void char_init (void); +static gboolean char_out (GOutputStream *output, + gint c, + GError **error); +static gboolean char_flush (GOutputStream *output, + GError **error); + + +static gint +find_unused_ia_color (const guchar *pixels, + gint numpixels, + gint num_indices, + gint *colors) +{ + gboolean ix_used[256]; + gint i; + +#ifdef GIFDEBUG + g_printerr ("GIF: fuiac: Image claims to use %d/%d indices - finding free " + "index...\n", *colors, num_indices); +#endif + + for (i = 0; i < 256; i++) + ix_used[i] = FALSE; + + for (i = 0; i < numpixels; i++) + { + if (pixels[i * 2 + 1]) + ix_used[pixels[i * 2]] = TRUE; + } + + for (i = num_indices - 1; i >= 0; i--) + { + if (! ix_used[i]) + { +#ifdef GIFDEBUG + g_printerr ("GIF: Found unused color index %d.\n", (int) i); +#endif + return i; + } + } + + /* Couldn't find an unused color index within the number of bits per + * pixel we wanted. Will have to increment the number of colors in + * the image and assign a transparent pixel there. + */ + if (*colors < 256) + { + (*colors)++; + + g_printerr ("GIF: 2nd pass " + "- Increasing bounds and using color index %d.\n", + *colors - 1); + return ((*colors) - 1); + } + + g_message (_("Couldn't simply reduce colors further. Exporting as opaque.")); + + return -1; +} + + +static void +special_flatten_indexed_alpha (guchar *pixels, + gint transparent, + gint numpixels) +{ + guint32 i; + + /* Each transparent pixel in the image is mapped to a uniform value + * for encoding, if image already has <=255 colors + */ + + if (transparent == -1) /* tough, no indices left for the trans. index */ + { + for (i = 0; i < numpixels; i++) + pixels[i] = pixels[i * 2]; + } + else /* make transparent */ + { + for (i = 0; i < numpixels; i++) + { + if (! (pixels[i * 2 + 1] & 128)) + { + pixels[i] = (guchar) transparent; + } + else + { + pixels[i] = pixels[i * 2]; + } + } + } +} + + +static gint +parse_ms_tag (const gchar *str) +{ + gint sum = 0; + gint offset = 0; + gint length; + + length = strlen (str); + + find_another_bra: + + while ((offset < length) && (str[offset] != '(')) + offset++; + + if (offset >= length) + return(-1); + + if (! g_ascii_isdigit (str[++offset])) + goto find_another_bra; + + do + { + sum *= 10; + sum += str[offset] - '0'; + offset++; + } + while ((offset < length) && (g_ascii_isdigit (str[offset]))); + + if (length - offset <= 2) + return(-3); + + if ((g_ascii_toupper (str[offset]) != 'M') || + (g_ascii_toupper (str[offset + 1]) != 'S')) + return -4; + + return sum; +} + + +static gint +parse_disposal_tag (const gchar *str) +{ + gint offset = 0; + gint length; + + length = strlen (str); + + while ((offset + 9) <= length) + { + if (strncmp (&str[offset], "(combine)", 9) == 0) + return 0x01; + + if (strncmp (&str[offset], "(replace)", 9) == 0) + return 0x02 ; + + offset++; + } + + return gsvals.default_dispose; +} + + +static GimpPDBStatusType +sanity_check (GFile *file, + gint32 *image_ID, + GimpRunMode run_mode, + GError **error) +{ + gint32 *layers; + gint nlayers; + gint image_width; + gint image_height; + gint i; + + image_width = gimp_image_width (*image_ID); + image_height = gimp_image_height (*image_ID); + + if (image_width > G_MAXUSHORT || image_height > G_MAXUSHORT) + { + g_set_error (error, 0, 0, + _("Unable to export '%s'. " + "The GIF file format does not support images that are " + "more than %d pixels wide or tall."), + gimp_file_get_utf8_name (file), G_MAXUSHORT); + + return GIMP_PDB_EXECUTION_ERROR; + } + + /*** Iterate through the layers to make sure they're all ***/ + /*** within the bounds of the image ***/ + + *image_ID = gimp_image_duplicate (*image_ID); + layers = gimp_image_get_layers (*image_ID, &nlayers); + + for (i = 0; i < nlayers; i++) + { + gint offset_x; + gint offset_y; + + gimp_drawable_offsets (layers[i], &offset_x, &offset_y); + + if (offset_x < 0 || + offset_y < 0 || + offset_x + gimp_drawable_width (layers[i]) > image_width || + offset_y + gimp_drawable_height (layers[i]) > image_height) + { + g_free (layers); + + /* Image has illegal bounds - ask the user what it wants to do */ + + /* Do the crop if we can't talk to the user, or if we asked + * the user and they said yes. + */ + if ((run_mode == GIMP_RUN_NONINTERACTIVE) || bad_bounds_dialog ()) + { + gimp_image_crop (*image_ID, image_width, image_height, 0, 0); + return GIMP_PDB_SUCCESS; + } + else + { + gimp_image_delete (*image_ID); + return GIMP_PDB_CANCEL; + } + } + } + + g_free (layers); + + return GIMP_PDB_SUCCESS; +} + + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + GError **error) +{ + GeglBuffer *buffer; + GimpImageType drawable_type; + const Babl *format = NULL; + GOutputStream *output; + gint Red[MAXCOLORS]; + gint Green[MAXCOLORS]; + gint Blue[MAXCOLORS]; + guchar *cmap; + guint rows, cols; + gint BitsPerPixel; + gint liberalBPP = 0; + gint useBPP = 0; + gint colors; + gint i; + gint transparent; + gint offset_x, offset_y; + + gint32 *layers; + gint nlayers; + + gboolean is_gif89 = FALSE; + + gint Delay89; + gint Disposal; + gchar *layer_name; + + GimpRGB background; + guchar bgred, bggreen, bgblue; + guchar bgindex = 0; + guint best_error = 0xFFFFFFFF; + + /* Save the comment back to the ImageID, if appropriate */ + if (globalcomment != NULL && comment_was_edited) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (globalcomment) + 1, + (gpointer) globalcomment); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + } + + /* The GIF spec says 7bit ASCII for the comment block. */ + if (gsvals.save_comment && globalcomment) + { + const gchar *c = globalcomment; + gint len; + + for (len = strlen (c); len; c++, len--) + { + if ((guchar) *c > 127) + { + g_message (_("The GIF format only supports comments in " + "7bit ASCII encoding. No comment is saved.")); + + g_free (globalcomment); + globalcomment = NULL; + + break; + } + } + } + + /* get a list of layers for this image_ID */ + layers = gimp_image_get_layers (image_ID, &nlayers); + + drawable_type = gimp_drawable_type (layers[0]); + + /* If the image has multiple layers (i.e. will be animated), a + * comment, or transparency, then it must be encoded as a GIF89a + * file, not a vanilla GIF87a. + */ + if (nlayers > 1) + is_gif89 = TRUE; + + if (gsvals.save_comment) + is_gif89 = TRUE; + + switch (drawable_type) + { + case GIMP_INDEXEDA_IMAGE: + is_gif89 = TRUE; + case GIMP_INDEXED_IMAGE: + cmap = gimp_image_get_colormap (image_ID, &colors); + + gimp_context_get_background (&background); + gimp_rgb_get_uchar (&background, &bgred, &bggreen, &bgblue); + + for (i = 0; i < colors; i++) + { + Red[i] = *cmap++; + Green[i] = *cmap++; + Blue[i] = *cmap++; + } + for ( ; i < 256; i++) + { + Red[i] = bgred; + Green[i] = bggreen; + Blue[i] = bgblue; + } + break; + case GIMP_GRAYA_IMAGE: + is_gif89 = TRUE; + case GIMP_GRAY_IMAGE: + colors = 256; /* FIXME: Not ideal. */ + for ( i = 0; i < 256; i++) + { + Red[i] = Green[i] = Blue[i] = i; + } + + if (drawable_type == GIMP_GRAYA_IMAGE) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + break; + + default: + g_message (_("Cannot export RGB color images. Convert to " + "indexed color or grayscale first.")); + return FALSE; + } + + + /* find earliest index in palette which is closest to the background + * color, and ATTEMPT to use that as the GIF's default background + * color. + */ + for (i = 255; i >= 0; --i) + { + guint local_error = 0; + + local_error += (Red[i] - bgred) * (Red[i] - bgred); + local_error += (Green[i] - bggreen) * (Green[i] - bggreen); + local_error += (Blue[i] - bgblue) * (Blue[i] - bgblue); + + if (local_error <= best_error) + { + bgindex = i; + best_error = local_error; + } + } + + + /* init the progress meter */ + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_file_get_utf8_name (file)); + + + /* open the destination file for writing */ + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (output) + { + GDataOutputStream *data_output; + + data_output = g_data_output_stream_new (output); + g_object_unref (output); + + g_data_output_stream_set_byte_order (data_output, + G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN); + + output = G_OUTPUT_STREAM (data_output); + } + else + { + return FALSE; + } + + + /* write the GIFheader */ + + if (colors < 256) + { + /* we keep track of how many bits we promised to have in + * liberalBPP, so that we don't accidentally come under this + * when doing clever transparency stuff where we can re-use + * wasted indices. + */ + liberalBPP = BitsPerPixel = + colors_to_bpp (colors + ((drawable_type==GIMP_INDEXEDA_IMAGE) ? 1 : 0)); + } + else + { + liberalBPP = BitsPerPixel = + colors_to_bpp (256); + + if (drawable_type == GIMP_INDEXEDA_IMAGE) + { + g_printerr ("GIF: Too many colors?\n"); + } + } + + cols = gimp_image_width (image_ID); + rows = gimp_image_height (image_ID); + Interlace = gsvals.interlace; + if (! gif_encode_header (output, is_gif89, cols, rows, bgindex, + BitsPerPixel, Red, Green, Blue, get_pixel, + error)) + return FALSE; + + + /* If the image has multiple layers it'll be made into an animated + * GIF, so write out the infinite-looping extension + */ + if ((nlayers > 1) && (gsvals.loop)) + if (! gif_encode_loop_ext (output, 0, error)) + return FALSE; + + /* Write comment extension - mustn't be written before the looping ext. */ + if (gsvals.save_comment && globalcomment) + { + if (! gif_encode_comment_ext (output, globalcomment, error)) + return FALSE; + } + + + /*** Now for each layer in the image, save an image in a compound GIF ***/ + /************************************************************************/ + + cur_progress = 0; + max_progress = nlayers * rows; + + for (i = nlayers - 1; i >= 0; i--, cur_progress = (nlayers - i) * rows) + { + drawable_type = gimp_drawable_type (layers[i]); + buffer = gimp_drawable_get_buffer (layers[i]); + gimp_drawable_offsets (layers[i], &offset_x, &offset_y); + cols = gimp_drawable_width (layers[i]); + rows = gimp_drawable_height (layers[i]); + rowstride = cols; + + pixels = g_new (guchar, (cols * rows * + (((drawable_type == GIMP_INDEXEDA_IMAGE) || + (drawable_type == GIMP_GRAYA_IMAGE)) ? 2 : 1))); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, cols, rows), 1.0, + format, pixels, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + /* sort out whether we need to do transparency jiggery-pokery */ + if ((drawable_type == GIMP_INDEXEDA_IMAGE) || + (drawable_type == GIMP_GRAYA_IMAGE)) + { + /* Try to find an entry which isn't actually used in the + * image, for a transparency index. + */ + + transparent = + find_unused_ia_color (pixels, + cols * rows, + bpp_to_colors (colors_to_bpp (colors)), + &colors); + + special_flatten_indexed_alpha (pixels, + transparent, + cols * rows); + } + else + { + transparent = -1; + } + + BitsPerPixel = colors_to_bpp (colors); + + if (BitsPerPixel != liberalBPP) + { + /* We were able to re-use an index within the existing + * bitspace, whereas the estimate in the header was + * pessimistic but still needs to be upheld... + */ +#ifdef GIFDEBUG + static gboolean onceonly = FALSE; + + if (! onceonly) + { + g_warning ("Promised %d bpp, pondered writing chunk with %d bpp!", + liberalBPP, BitsPerPixel); + onceonly = TRUE; + } +#endif + } + + useBPP = (BitsPerPixel > liberalBPP) ? BitsPerPixel : liberalBPP; + + if (is_gif89) + { + if (i > 0 && ! gsvals.always_use_default_dispose) + { + layer_name = gimp_item_get_name (layers[i - 1]); + Disposal = parse_disposal_tag (layer_name); + g_free (layer_name); + } + else + { + Disposal = gsvals.default_dispose; + } + + layer_name = gimp_item_get_name (layers[i]); + Delay89 = parse_ms_tag (layer_name); + g_free (layer_name); + + if (Delay89 < 0 || gsvals.always_use_default_delay) + Delay89 = (gsvals.default_delay + 5) / 10; + else + Delay89 = (Delay89 + 5) / 10; + + /* don't allow a CPU-sucking completely 0-delay looping anim */ + if ((nlayers > 1) && gsvals.loop && (Delay89 == 0)) + { + static gboolean onceonly = FALSE; + + if (! onceonly) + { + g_message (_("Delay inserted to prevent evil " + "CPU-sucking animation.")); + onceonly = TRUE; + } + + Delay89 = 1; + } + + if (! gif_encode_graphic_control_ext (output, + Disposal, Delay89, nlayers, + cols, rows, + transparent, + useBPP, + get_pixel, + error)) + return FALSE; + } + + if (! gif_encode_image_data (output, cols, rows, + (rows > 4) ? gsvals.interlace : 0, + useBPP, + get_pixel, + offset_x, offset_y, + error)) + return FALSE; + + gimp_progress_update (1.0); + + g_object_unref (buffer); + + g_free (pixels); + } + + g_free (layers); + + if (! gif_encode_close (output, error)) + return FALSE; + + return TRUE; +} + +static gboolean +bad_bounds_dialog (void) +{ + GtkWidget *dialog; + gboolean crop; + + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, + _("The image you are trying to export as a " + "GIF contains layers which extend beyond " + "the actual borders of the image.")); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("Cr_op"), GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("The GIF file format does not " + "allow this. You may choose " + "whether to crop all of the " + "layers to the image borders, " + "or cancel this export.")); + + gtk_widget_show (dialog); + + crop = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return crop; +} + +static GtkWidget * +file_gif_toggle_button_init (GtkBuilder *builder, + const gchar *name, + gboolean initial_value, + gboolean *value_pointer) +{ + GtkWidget *toggle = NULL; + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, name)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), initial_value); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + value_pointer); + + return toggle; +} + +static GtkWidget * +file_gif_spin_button_int_init (GtkBuilder *builder, + const gchar *name, + int initial_value, + int *value_pointer) +{ + GtkWidget *spin_button = NULL; + GtkAdjustment *adjustment = NULL; + + spin_button = GTK_WIDGET (gtk_builder_get_object (builder, name)); + + adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin_button)); + gtk_adjustment_set_value (adjustment, initial_value); + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + value_pointer); + + return spin_button; +} + +static void +file_gif_combo_box_int_update_value (GtkComboBox *combo, + gint *value) +{ + GtkTreeIter iter; + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) + { + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)), + &iter, + DISPOSE_STORE_VALUE_COLUMN, value, + -1); + } +} + +static GtkWidget * +file_gif_combo_box_int_init (GtkBuilder *builder, + const gchar *name, + int initial_value, + int *value_pointer, + const gchar *first_label, + gint first_value, + ...) +{ + GtkWidget *combo = NULL; + GtkListStore *store = NULL; + const gchar *label = NULL; + gint value = 0; + GtkTreeIter iter = { 0, }; + va_list values; + + combo = GTK_WIDGET (gtk_builder_get_object (builder, name)); + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo))); + + /* Populate */ + va_start (values, first_value); + for (label = first_label, value = first_value; + label; + label = va_arg (values, const gchar *), value = va_arg (values, gint)) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + DISPOSE_STORE_VALUE_COLUMN, value, + DISPOSE_STORE_LABEL_COLUMN, label, + -1); + } + va_end (values); + + /* Set initial value */ + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store), + &iter, + NULL, + initial_value); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); + + /* Arrange update of value */ + g_signal_connect (combo, "changed", + G_CALLBACK (file_gif_combo_box_int_update_value), + value_pointer); + + return combo; +} + +static gboolean +save_dialog (gint32 image_ID) +{ + GtkBuilder *builder = NULL; + gchar *ui_file = NULL; + GError *error = NULL; + GtkWidget *dialog; + GtkWidget *text_view; + GtkTextBuffer *text_buffer; + GtkWidget *toggle; + GtkWidget *frame; + GimpParasite *GIF2_CMNT; + gint32 nlayers; + gboolean animation_supported = FALSE; + gboolean run; + + g_free (gimp_image_get_layers (image_ID, &nlayers)); + animation_supported = nlayers > 1; + + dialog = gimp_export_dialog_new (_("GIF"), PLUG_IN_BINARY, SAVE_PROC); + + /* GtkBuilder init */ + builder = gtk_builder_new (); + ui_file = g_build_filename (gimp_data_directory (), + "ui/plug-ins/plug-in-file-gif.ui", + NULL); + if (! gtk_builder_add_from_file (builder, ui_file, &error)) + g_printerr (_("Error loading UI file '%s':\n%s"), + ui_file, error ? error->message : "???"); + g_free (ui_file); + + /* Main vbox */ + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + GTK_WIDGET (gtk_builder_get_object (builder, "main-vbox")), + TRUE, TRUE, 0); + + /* regular gif parameter settings */ + file_gif_toggle_button_init (builder, "interlace", + gsvals.interlace, &gsvals.interlace); + file_gif_toggle_button_init (builder, "save-comment", + gsvals.save_comment, &gsvals.save_comment); + file_gif_toggle_button_init (builder, "as-animation", + gsvals.as_animation, &gsvals.as_animation); + + text_view = GTK_WIDGET (gtk_builder_get_object (builder, "comment")); + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); + + if (globalcomment) + g_free (globalcomment); + + GIF2_CMNT = gimp_image_get_parasite (image_ID, "gimp-comment"); + if (GIF2_CMNT) + { + globalcomment = g_strndup (gimp_parasite_data (GIF2_CMNT), + gimp_parasite_data_size (GIF2_CMNT)); + gimp_parasite_free (GIF2_CMNT); + } + else + { + globalcomment = gimp_get_default_comment (); + } + + if (globalcomment) + gtk_text_buffer_set_text (text_buffer, globalcomment, -1); + + g_signal_connect (text_buffer, "changed", + G_CALLBACK (comment_entry_callback), + NULL); + + /* additional animated gif parameter settings */ + file_gif_toggle_button_init (builder, "loop-forever", + gsvals.loop, &gsvals.loop); + + /* default_delay entry field */ + file_gif_spin_button_int_init (builder, "delay-spin", + gsvals.default_delay, &gsvals.default_delay); + + /* Disposal selector */ + file_gif_combo_box_int_init (builder, "dispose-combo", + gsvals.default_dispose, &gsvals.default_dispose, + _("I don't care"), + DISPOSE_UNSPECIFIED, + _("Cumulative layers (combine)"), + DISPOSE_COMBINE, + _("One frame per layer (replace)"), + DISPOSE_REPLACE, + NULL); + + /* The "Always use default values" toggles */ + file_gif_toggle_button_init (builder, "use-default-delay", + gsvals.always_use_default_delay, + &gsvals.always_use_default_delay); + file_gif_toggle_button_init (builder, "use-default-dispose", + gsvals.always_use_default_dispose, + &gsvals.always_use_default_dispose); + + frame = GTK_WIDGET (gtk_builder_get_object (builder, "animation-frame")); + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "as-animation")); + if (! animation_supported) + { + gimp_help_set_help_data (toggle, + _("You can only export as animation when the " + "image has more than one layer. The image " + "you are trying to export only has one " + "layer."), + NULL); + /* Make sure the checkbox is not checked from session data. */ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE); + } + gtk_widget_set_sensitive (toggle, animation_supported); + + g_object_bind_property (toggle, "active", + frame, "sensitive", + G_BINDING_SYNC_CREATE); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static int +colors_to_bpp (gint colors) +{ + gint bpp; + + if (colors <= 2) + bpp = 1; + else if (colors <= 4) + bpp = 2; + else if (colors <= 8) + bpp = 3; + else if (colors <= 16) + bpp = 4; + else if (colors <= 32) + bpp = 5; + else if (colors <= 64) + bpp = 6; + else if (colors <= 128) + bpp = 7; + else if (colors <= 256) + bpp = 8; + else + { + g_warning ("GIF: colors_to_bpp - Eep! too many colors: %d\n", colors); + return 8; + } + + return bpp; +} + +static int +bpp_to_colors (gint bpp) +{ + gint colors; + + if (bpp > 8) + { + g_warning ("GIF: bpp_to_colors - Eep! bpp==%d !\n", bpp); + return 256; + } + + colors = 1 << bpp; + + return colors; +} + + + +static gint +get_pixel (gint x, + gint y) +{ + return *(pixels + (rowstride * (long) y) + (long) x); +} + + +/***************************************************************************** + * + * GIFENCODE.C - GIF Image compression interface + * + * GIFEncode( FName, GHeight, GWidth, GInterlace, Background, Transparent, + * BitsPerPixel, Red, Green, Blue, get_pixel ) + * + *****************************************************************************/ + +static gint Width, Height; +static gint curx, cury; +static glong CountDown; +static gint Pass = 0; + +/* + * Bump the 'curx' and 'cury' to point to the next pixel + */ +static void +bump_pixel (void) +{ + /* + * Bump the current X position + */ + curx++; + + /* + * If we are at the end of a scan line, set curx back to the beginning + * If we are interlaced, bump the cury to the appropriate spot, + * otherwise, just increment it. + */ + if (curx == Width) + { + cur_progress++; + + if ((cur_progress % 20) == 0) + gimp_progress_update ((gdouble) cur_progress / (gdouble) max_progress); + + curx = 0; + + if (! Interlace) + ++cury; + else + { + switch (Pass) + { + + case 0: + cury += 8; + if (cury >= Height) + { + Pass++; + cury = 4; + } + break; + + case 1: + cury += 8; + if (cury >= Height) + { + Pass++; + cury = 2; + } + break; + + case 2: + cury += 4; + if (cury >= Height) + { + Pass++; + cury = 1; + } + break; + + case 3: + cury += 2; + break; + } + } + } +} + +/* + * Return the next pixel from the image + */ +static gint +gif_next_pixel (ifunptr getpixel) +{ + gint r; + + if (CountDown == 0) + return EOF; + + --CountDown; + + r = (*getpixel) (curx, cury); + + bump_pixel (); + + return r; +} + +/* public */ + +static gboolean +gif_encode_header (GOutputStream *output, + gboolean gif89, + gint GWidth, + gint GHeight, + gint Background, + gint BitsPerPixel, + gint Red[], + gint Green[], + gint Blue[], + ifunptr get_pixel, + GError **error) +{ + gint B; + gint RWidth, RHeight; + gint Resolution; + gint ColorMapSize; + gint i; + + ColorMapSize = 1 << BitsPerPixel; + + RWidth = Width = GWidth; + RHeight = Height = GHeight; + + Resolution = BitsPerPixel; + + /* + * Calculate number of bits we are expecting + */ + CountDown = (long) Width *(long) Height; + + /* + * Indicate which pass we are on (if interlace) + */ + Pass = 0; + + /* + * Set up the current x and y position + */ + curx = cury = 0; + + /* + * Write the Magic header + */ + if (! put_string (output, gif89 ? "GIF89a" : "GIF87a", error)) + return FALSE; + + /* + * Write out the screen width and height + */ + if (! put_word (output, RWidth, error) || + ! put_word (output, RHeight, error)) + return FALSE; + + /* + * Indicate that there is a global color map + */ + B = 0x80; /* Yes, there is a color map */ + + /* + * OR in the resolution + */ + B |= (Resolution - 1) << 5; + + /* + * OR in the Bits per Pixel + */ + B |= (BitsPerPixel - 1); + + /* + * Write it out + */ + if (! put_byte (output, B, error)) + return FALSE; + + /* + * Write out the Background color + */ + if (! put_byte (output, Background, error)) + return FALSE; + + /* + * Byte of 0's (future expansion) + */ + if (! put_byte (output, 0, error)) + return FALSE; + + /* + * Write out the Global Color Map + */ + for (i = 0; i < ColorMapSize; i++) + { + if (! put_byte (output, Red[i], error) || + ! put_byte (output, Green[i], error) || + ! put_byte (output, Blue[i], error)) + return FALSE; + } + + return TRUE; +} + + +static gboolean +gif_encode_graphic_control_ext (GOutputStream *output, + int Disposal, + int Delay89, + int NumFramesInImage, + int GWidth, + int GHeight, + int Transparent, + int BitsPerPixel, + ifunptr get_pixel, + GError **error) +{ + Width = GWidth; + Height = GHeight; + + /* + * Calculate number of bits we are expecting + */ + CountDown = (long) Width *(long) Height; + + /* + * Indicate which pass we are on (if interlace) + */ + Pass = 0; + + /* + * Set up the current x and y position + */ + curx = cury = 0; + + /* + * Write out extension for transparent color index, if necessary. + */ + if ( (Transparent >= 0) || (NumFramesInImage > 1) ) + { + /* Extension Introducer - fixed. */ + if (! put_byte (output, '!', error)) + return FALSE; + + /* Graphic Control Label - fixed. */ + if (! put_byte (output, 0xf9, error)) + return FALSE; + + /* Block Size - fixed. */ + if (! put_byte (output, 4, error)) + return FALSE; + + /* Packed Fields - XXXdddut (d=disposal, u=userInput, t=transFlag) */ + /* s8421 */ + if (! put_byte (output, + ((Transparent >= 0) ? 0x01 : 0x00) /* TRANSPARENCY */ + + /* DISPOSAL */ + | ((NumFramesInImage > 1) ? (Disposal << 2) : 0x00 ), + /* 0x03 or 0x01 build frames cumulatively */ + /* 0x02 clears frame before drawing */ + /* 0x00 'don't care' */ + + error)) + return FALSE; + + if (! put_word (output, Delay89, error)) + return FALSE; + + if (! put_byte (output, Transparent, error) || + ! put_byte (output, 0, error)) + return FALSE; + } + + return TRUE; +} + + +static gboolean +gif_encode_image_data (GOutputStream *output, + int GWidth, + int GHeight, + int GInterlace, + int BitsPerPixel, + ifunptr get_pixel, + gint offset_x, + gint offset_y, + GError **error) +{ + gint LeftOfs, TopOfs; + gint InitCodeSize; + + Interlace = GInterlace; + + Width = GWidth; + Height = GHeight; + LeftOfs = (gint) offset_x; + TopOfs = (gint) offset_y; + + /* + * Calculate number of bits we are expecting + */ + CountDown = (long) Width * (long) Height; + + /* + * Indicate which pass we are on (if interlace) + */ + Pass = 0; + + /* + * The initial code size + */ + if (BitsPerPixel <= 1) + InitCodeSize = 2; + else + InitCodeSize = BitsPerPixel; + + /* + * Set up the current x and y position + */ + curx = cury = 0; + + /* + * Write an Image separator + */ + if (! put_byte (output, ',', error)) + return FALSE; + + /* + * Write the Image header + */ + + if (! put_word (output, LeftOfs, error) || + ! put_word (output, TopOfs, error) || + ! put_word (output, Width, error) || + ! put_word (output, Height, error)) + return FALSE; + + /* + * Write out whether or not the image is interlaced + */ + if (Interlace) + { + if (! put_byte (output, 0x40, error)) + return FALSE; + } + else + { + if (! put_byte (output, 0x00, error)) + return FALSE; + } + + /* + * Write out the initial code size + */ + if (! put_byte (output, InitCodeSize, error)) + return FALSE; + + /* + * Go and actually compress the data + */ + if (! compress (output, InitCodeSize + 1, get_pixel, error)) + return FALSE; + + /* + * Write out a Zero-length packet (to end the series) + */ + if (! put_byte (output, 0, error)) + return FALSE; + +#if 0 + /***************************/ + Interlace = GInterlace; + Width = GWidth; + Height = GHeight; + LeftOfs = TopOfs = 0; + + CountDown = (long) Width *(long) Height; + Pass = 0; + /* + * The initial code size + */ + if (BitsPerPixel <= 1) + InitCodeSize = 2; + else + InitCodeSize = BitsPerPixel; + /* + * Set up the current x and y position + */ + curx = cury = 0; +#endif + + return TRUE; +} + + +static gboolean +gif_encode_close (GOutputStream *output, + GError **error) +{ + /* + * Write the GIF file terminator + */ + if (! put_byte (output, ';', error)) + return FALSE; + + /* + * And close the file + */ + return g_output_stream_close (output, NULL, error); +} + + +static gboolean +gif_encode_loop_ext (GOutputStream *output, + guint num_loops, + GError **error) +{ + return (put_byte (output, 0x21, error) && + put_byte (output, 0xff, error) && + put_byte (output, 0x0b, error) && + put_string (output, "NETSCAPE2.0", error) && + put_byte (output, 0x03, error) && + put_byte (output, 0x01, error) && + put_word (output, num_loops, error) && + put_byte (output, 0x00, error)); + + /* NOTE: num_loops == 0 means 'loop infinitely' */ +} + + +static gboolean +gif_encode_comment_ext (GOutputStream *output, + const gchar *comment, + GError **error) +{ + if (!comment || !*comment) + return TRUE; + + if (strlen (comment) > 240) + { + g_printerr ("GIF: warning:" + "comment too large - comment block not written.\n"); + return TRUE; + } + + return (put_byte (output, 0x21, error) && + put_byte (output, 0xfe, error) && + put_byte (output, strlen (comment), error) && + put_string (output, comment, error) && + put_byte (output, 0x00, error)); +} + + +/* + * Write stuff to the GIF file + */ +static gboolean +put_byte (GOutputStream *output, + guchar b, + GError **error) +{ + return g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (output), + b, NULL, error); +} + +static gboolean +put_word (GOutputStream *output, + gint w, + GError **error) +{ + return g_data_output_stream_put_int16 (G_DATA_OUTPUT_STREAM (output), + w, NULL, error); +} + +static gboolean +put_string (GOutputStream *output, + const gchar *s, + GError **error) +{ + return g_data_output_stream_put_string (G_DATA_OUTPUT_STREAM (output), + s, NULL, error); +} + + +/*************************************************************************** + * + * GIFCOMPR.C - GIF Image compression routines + * + * Lempel-Ziv compression based on 'compress'. GIF modifications by + * David Rowley (mgardi@watdcsu.waterloo.edu) + * + ***************************************************************************/ + +/* + * General DEFINEs + */ + +#define GIF_BITS 12 + +#define HSIZE 5003 /* 80% occupancy */ + +/* + * GIF Image compression - modified 'compress' + * + * Based on: compress.c - File compression ala IEEE Computer, June 1984. + * + * By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + * Jim McKie (decvax!mcvax!jim) + * Steve Davies (decvax!vax135!petsd!peora!srd) + * Ken Turkowski (decvax!decwrl!turtlevax!ken) + * James A. Woods (decvax!ihnp4!ames!jaw) + * Joe Orost (decvax!vax135!petsd!joe) + * + */ + +static gint n_bits; /* number of bits/code */ +static gint maxbits = GIF_BITS; /* user settable max # bits/code */ +static gint maxcode; /* maximum code, given n_bits */ +static gint maxmaxcode = (gint) 1 << GIF_BITS; /* should NEVER generate this code */ +#ifdef COMPATIBLE /* But wrong! */ +#define MAXCODE(Mn_bits) ((gint) 1 << (Mn_bits) - 1) +#else /*COMPATIBLE */ +#define MAXCODE(Mn_bits) (((gint) 1 << (Mn_bits)) - 1) +#endif /*COMPATIBLE */ + +static glong htab[HSIZE]; +static gushort codetab[HSIZE]; +#define HashTabOf(i) htab[i] +#define CodeTabOf(i) codetab[i] + +static const gint hsize = HSIZE; /* the original reason for this being + variable was "for dynamic table sizing", + but since it was never actually changed + I made it const --Adam. */ + +static gint free_ent = 0; /* first unused entry */ + +/* + * block compression parameters -- after all codes are used up, + * and compression rate changes, start over. + */ +static gint clear_flg = 0; + +static gint offset; +static glong in_count = 1; /* length of input */ +static glong out_count = 0; /* # of codes output (for debugging) */ + +/* + * compress stdin to stdout + * + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / next character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. Here, the modular division first probe is gives way + * to a faster exclusive-or manipulation. Also do block compression with + * an adaptive reset, whereby the code table is cleared when the compression + * ratio decreases, but after the table fills. The variable-length output + * codes are re-sized at this point, and a special CLEAR code is generated + * for the decompressor. Late addition: construct the table according to + * file size for noticeable speed improvement on small files. Please direct + * questions about this implementation to ames!jaw. + */ + +static gint g_init_bits; + +static gint ClearCode; +static gint EOFCode; + +static gulong cur_accum; +static gint cur_bits; + +static gulong masks[] = +{ + 0x0000, 0x0001, 0x0003, 0x0007, + 0x000F, 0x001F, 0x003F, 0x007F, + 0x00FF, 0x01FF, 0x03FF, 0x07FF, + 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, + 0xFFFF +}; + + +static gboolean +compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error) +{ + if (FALSE) + return no_compress (output, init_bits, ReadValue, error); + else if (FALSE) + return rle_compress (output, init_bits, ReadValue, error); + else + return normal_compress (output, init_bits, ReadValue, error); +} + +static gboolean +no_compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error) +{ + glong fcode; + gint i /* = 0 */ ; + gint c; + gint ent; + gint hsize_reg; + gint hshift; + + /* + * Set up the globals: g_init_bits - initial number of bits + */ + g_init_bits = init_bits; + + cur_bits = 0; + cur_accum = 0; + + /* + * Set up the necessary values + */ + offset = 0; + out_count = 0; + clear_flg = 0; + in_count = 1; + + ClearCode = (1 << (init_bits - 1)); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + + /* Had some problems here... should be okay now. --Adam */ + n_bits = g_init_bits; + maxcode = MAXCODE (n_bits); + + + char_init(); + + ent = gif_next_pixel (ReadValue); + + hshift = 0; + for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L) + ++hshift; + hshift = 8 - hshift; /* set hash code range bound */ + + hsize_reg = hsize; + cl_hash ((glong) hsize_reg); /* clear hash table */ + + if (! output_code (output, (gint) ClearCode, error)) + return FALSE; + + while ((c = gif_next_pixel (ReadValue)) != EOF) + { + ++in_count; + + fcode = (long) (((long) c << maxbits) + ent); + i = (((gint) c << hshift) ^ ent); /* xor hashing */ + + if (! output_code (output, (gint) ent, error)) + return FALSE; + + ++out_count; + ent = c; + + if (free_ent < maxmaxcode) + { + CodeTabOf (i) = free_ent++; /* code -> hashtable */ + HashTabOf (i) = fcode; + } + else + { + if (! cl_block (output, error)) + return FALSE; + } + } + + /* + * Put out the final code. + */ + if (! output_code (output, (gint) ent, error)) + return FALSE; + + ++out_count; + + if (! output_code (output, (gint) EOFCode, error)) + return FALSE; + + return TRUE; +} + +static gboolean +rle_compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error) +{ + glong fcode; + gint i /* = 0 */ ; + gint c, last; + gint ent; + gint disp; + gint hsize_reg; + gint hshift; + + /* + * Set up the globals: g_init_bits - initial number of bits + */ + g_init_bits = init_bits; + + cur_bits = 0; + cur_accum = 0; + + /* + * Set up the necessary values + */ + offset = 0; + out_count = 0; + clear_flg = 0; + in_count = 1; + + ClearCode = (1 << (init_bits - 1)); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + + /* Had some problems here... should be okay now. --Adam */ + n_bits = g_init_bits; + maxcode = MAXCODE (n_bits); + + + char_init (); + + last = ent = gif_next_pixel (ReadValue); + + hshift = 0; + for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L) + ++hshift; + hshift = 8 - hshift; /* set hash code range bound */ + + hsize_reg = hsize; + cl_hash ((glong) hsize_reg); /* clear hash table */ + + if (! output_code (output, (gint) ClearCode, error)) + return FALSE; + + + while ((c = gif_next_pixel (ReadValue)) != EOF) + { + ++in_count; + + fcode = (long) (((long) c << maxbits) + ent); + i = (((gint) c << hshift) ^ ent); /* xor hashing */ + + + if (last == c) { + if (HashTabOf (i) == fcode) + { + ent = CodeTabOf (i); + continue; + } + else if ((long) HashTabOf (i) < 0) /* empty slot */ + goto nomatch; + disp = hsize_reg - i; /* secondary hash (after G. Knott) */ + if (i == 0) + disp = 1; + probe: + if ((i -= disp) < 0) + i += hsize_reg; + + if (HashTabOf (i) == fcode) + { + ent = CodeTabOf (i); + continue; + } + if ((long) HashTabOf (i) > 0) + goto probe; + } + nomatch: + if (! output_code (output, (gint) ent, error)) + return FALSE; + + ++out_count; + last = ent = c; + if (free_ent < maxmaxcode) + { + CodeTabOf (i) = free_ent++; /* code -> hashtable */ + HashTabOf (i) = fcode; + } + else + { + if (! cl_block (output, error)) + return FALSE; + } + } + + /* + * Put out the final code. + */ + if (! output_code (output, (gint) ent, error)) + return FALSE; + + ++out_count; + + if (! output_code (output, (gint) EOFCode, error)) + return FALSE; + + return TRUE; +} + +static gboolean +normal_compress (GOutputStream *output, + gint init_bits, + ifunptr ReadValue, + GError **error) +{ + glong fcode; + gint i /* = 0 */ ; + gint c; + gint ent; + gint disp; + gint hsize_reg; + gint hshift; + + /* + * Set up the globals: g_init_bits - initial number of bits + */ + g_init_bits = init_bits; + + cur_bits = 0; + cur_accum = 0; + + /* + * Set up the necessary values + */ + offset = 0; + out_count = 0; + clear_flg = 0; + in_count = 1; + + ClearCode = (1 << (init_bits - 1)); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + + /* Had some problems here... should be okay now. --Adam */ + n_bits = g_init_bits; + maxcode = MAXCODE (n_bits); + + + char_init(); + + ent = gif_next_pixel (ReadValue); + + hshift = 0; + for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L) + ++hshift; + hshift = 8 - hshift; /* set hash code range bound */ + + hsize_reg = hsize; + cl_hash ((glong) hsize_reg); /* clear hash table */ + + if (! output_code (output, (gint) ClearCode, error)) + return FALSE; + + + while ((c = gif_next_pixel (ReadValue)) != EOF) + { + ++in_count; + + fcode = (long) (((long) c << maxbits) + ent); + i = (((gint) c << hshift) ^ ent); /* xor hashing */ + + if (HashTabOf (i) == fcode) + { + ent = CodeTabOf (i); + continue; + } + else if ((long) HashTabOf (i) < 0) /* empty slot */ + goto nomatch; + disp = hsize_reg - i; /* secondary hash (after G. Knott) */ + if (i == 0) + disp = 1; + probe: + if ((i -= disp) < 0) + i += hsize_reg; + + if (HashTabOf (i) == fcode) + { + ent = CodeTabOf (i); + continue; + } + if ((long) HashTabOf (i) > 0) + goto probe; + nomatch: + if (! output_code (output, (gint) ent, error)) + return FALSE; + + ++out_count; + ent = c; + if (free_ent < maxmaxcode) + { + CodeTabOf (i) = free_ent++; /* code -> hashtable */ + HashTabOf (i) = fcode; + } + else + { + if (! cl_block (output, error)) + return FALSE; + } + } + + /* + * Put out the final code. + */ + if (! output_code (output, (gint) ent, error)) + return FALSE; + + ++out_count; + + if (! output_code (output, (gint) EOFCode, error)) + return FALSE; + + return TRUE; +} + + +/***************************************************************** + * TAG( output ) + * + * Output the given code. + * Inputs: + * code: A n_bits-bit integer. If == -1, then EOF. This assumes + * that n_bits =< (long)wordsize - 1. + * Outputs: + * Outputs code to the file. + * Assumptions: + * Chars are 8 bits long. + * Algorithm: + * Maintain a GIF_BITS character long buffer (so that 8 codes will + * fit in it exactly). Use the VAX insv instruction to insert each + * code in turn. When the buffer fills up empty it and start over. + */ + +static gboolean +output_code (GOutputStream *output, + gint code, + GError **error) +{ + cur_accum &= masks[cur_bits]; + + if (cur_bits > 0) + cur_accum |= ((long) code << cur_bits); + else + cur_accum = code; + + cur_bits += n_bits; + + while (cur_bits >= 8) + { + if (! char_out (output, (guchar) (cur_accum & 0xff), error)) + return FALSE; + + cur_accum >>= 8; + cur_bits -= 8; + } + + /* + * If the next entry is going to be too big for the code size, + * then increase it, if possible. + */ + if (free_ent > maxcode || clear_flg) + { + if (clear_flg) + { + maxcode = MAXCODE (n_bits = g_init_bits); + clear_flg = 0; + } + else + { + ++n_bits; + if (n_bits == maxbits) + maxcode = maxmaxcode; + else + maxcode = MAXCODE (n_bits); + } + } + + if (code == EOFCode) + { + /* + * At EOF, write the rest of the buffer. + */ + while (cur_bits > 0) + { + if (! char_out (output, (guchar) (cur_accum & 0xff), error)) + return FALSE; + + cur_accum >>= 8; + cur_bits -= 8; + } + + if (! char_flush (output, error)) + return FALSE; + } + + return TRUE; +} + +/* + * Clear out the hash table + */ +static gboolean +cl_block (GOutputStream *output, + GError **error) /* table clear for block compress */ +{ + cl_hash ((glong) hsize); + free_ent = ClearCode + 2; + clear_flg = 1; + + return output_code (output, (gint) ClearCode, error); +} + +static void +cl_hash (glong hsize) /* reset code table */ +{ + glong *htab_p = htab + hsize; + + long i; + long m1 = -1; + + i = hsize - 16; + do + { /* might use Sys V memset(3) here */ + *(htab_p - 16) = m1; + *(htab_p - 15) = m1; + *(htab_p - 14) = m1; + *(htab_p - 13) = m1; + *(htab_p - 12) = m1; + *(htab_p - 11) = m1; + *(htab_p - 10) = m1; + *(htab_p - 9) = m1; + *(htab_p - 8) = m1; + *(htab_p - 7) = m1; + *(htab_p - 6) = m1; + *(htab_p - 5) = m1; + *(htab_p - 4) = m1; + *(htab_p - 3) = m1; + *(htab_p - 2) = m1; + *(htab_p - 1) = m1; + htab_p -= 16; + } + while ((i -= 16) >= 0); + + for (i += 16; i > 0; --i) + *--htab_p = m1; +} + + +/****************************************************************************** + * GIF Specific routines + ******************************************************************************/ + +/* + * Number of characters so far in this 'packet' + */ +static int a_count; + +/* + * Set up the 'byte output' routine + */ +static void +char_init (void) +{ + a_count = 0; +} + +/* + * Define the storage for the packet accumulator + */ +static char accum[256]; + +/* + * Add a character to the end of the current packet, and if it is 254 + * characters, flush the packet to disk. + */ +static gboolean +char_out (GOutputStream *output, + gint c, + GError **error) +{ + accum[a_count++] = c; + + if (a_count >= 254) + return char_flush (output, error); + + return TRUE; +} + +/* + * Flush the packet to disk, and reset the accumulator + */ +static gboolean +char_flush (GOutputStream *output, + GError **error) +{ + if (a_count > 0) + { + if (! put_byte (output, a_count, error)) + return FALSE; + + if (! g_output_stream_write_all (output, accum, a_count, + NULL, NULL, error)) + return FALSE; + + a_count = 0; + } + + return TRUE; +} + + +/* Save interface functions */ + +static void +comment_entry_callback (GtkTextBuffer *buffer) +{ + GtkTextIter start_iter; + GtkTextIter end_iter; + gchar *text; + + gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter); + text = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE); + +#define MAX_COMMENT 240 + + if (strlen (text) > MAX_COMMENT) + { + /* translators: the %d is *always* 240 here */ + g_message (_("The default comment is limited to %d characters."), + MAX_COMMENT); + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, MAX_COMMENT - 1); + gtk_text_buffer_get_end_iter (buffer, &end_iter); + + /* this calls us recursivaly, but in the else branch + */ + gtk_text_buffer_delete (buffer, &start_iter, &end_iter); + } + else + { + g_free (globalcomment); + globalcomment = g_strdup (text); + comment_was_edited = TRUE; + } + + g_free (text); +} diff --git a/plug-ins/common/file-gih.c b/plug-ins/common/file-gih.c new file mode 100644 index 0000000..6e44f9a --- /dev/null +++ b/plug-ins/common/file-gih.c @@ -0,0 +1,819 @@ +/* Plug-in to load and export .gih (GIMP Brush Pipe) files. + * + * Copyright (C) 1999 Tor Lillqvist + * Copyright (C) 2000 Jens Lautenbacher, Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + /* Example of how to call file_gih_save from script-fu: + + (let ((ranks (cons-array 1 'byte))) + (aset ranks 0 12) + (file-gih-save 1 + img + drawable + "foo.gih" + "foo.gih" + 100 + "test brush" + 125 + 125 + 3 + 4 + 1 + ranks + 1 + '("random"))) + */ + + +#include "config.h" + +#include +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-gih-save" +#define PLUG_IN_BINARY "file-gih" +#define PLUG_IN_ROLE "gimp-file-gih" + + +/* Parameters applicable each time we export a gih, exported in the + * main gimp application between invocations of this plug-in. + */ +typedef struct +{ + gchar description[256]; + gint spacing; +} BrushInfo; + +typedef struct +{ + GimpOrientationType orientation; + gint32 image; + gint32 toplayer; + gint nguides; + gint32 *guides; + gint *value; + GtkWidget *count_label; /* Corresponding count adjustment, */ + gint *count; /* cols or rows */ + gint *other_count; /* and the other one */ + GtkAdjustment *ncells; + GtkAdjustment *rank0; + GtkWidget *warning_label; + GtkWidget *rank_entry[GIMP_PIXPIPE_MAXDIM]; + GtkWidget *mode_entry[GIMP_PIXPIPE_MAXDIM]; +} SizeAdjustmentData; + + +/* local function prototypes */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean gih_save_dialog (gint32 image_ID); + + +/* private variables */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static BrushInfo info = +{ + "GIMP Brush Pipe", + 20 +}; + +static gint num_layers = 0; +static GimpPixPipeParams gihparams = { 0, }; + +static const gchar * const selection_modes[] = { "incremental", + "angular", + "random", + "velocity", + "pressure", + "xtilt", + "ytilt" }; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef gih_save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "uri", "The URI of the file to export the brush pipe in" }, + { GIMP_PDB_STRING, "raw-uri", "The URI of the file to export the brush pipe in" }, + { GIMP_PDB_INT32, "spacing", "Spacing of the brush" }, + { GIMP_PDB_STRING, "description", "Short description of the brush pipe" }, + { GIMP_PDB_INT32, "cell-width", "Width of the brush cells" }, + { GIMP_PDB_INT32, "cell-height", "Width of the brush cells" }, + { GIMP_PDB_INT8, "display-cols", "Display column number" }, + { GIMP_PDB_INT8, "display-rows", "Display row number" }, + { GIMP_PDB_INT32, "dimension", "Dimension of the brush pipe" }, + /* The number of rank and sel args depend on the dimension */ + { GIMP_PDB_INT8ARRAY, "rank", "Ranks of the dimensions" }, + { GIMP_PDB_INT32, "dimension", "Dimension (again)" }, + { GIMP_PDB_STRINGARRAY, "sel", "Selection modes" } + }; + + gimp_install_procedure (SAVE_PROC, + "exports images in GIMP brush pipe format", + "This plug-in exports an image in the GIMP brush pipe " + "format. For a colored brush pipe, RGBA layers are " + "used, otherwise the layers should be grayscale " + "masks. The image can be multi-layered, and " + "additionally the layers can be divided into a " + "rectangular array of brushes.", + "Tor Lillqvist", + "Tor Lillqvist", + "1999", + N_("GIMP brush (animated)"), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (gih_save_args), 0, + gih_save_args, NULL); + + gimp_plugin_icon_register (SAVE_PROC, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) GIMP_ICON_BRUSH); + gimp_register_file_handler_mime (SAVE_PROC, "image/x-gimp-gih"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "gih", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + gint i; + + INIT_I18N(); + + run_mode = param[0].data.d_int32; + + *return_vals = values; + *nreturn_vals = 1; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, SAVE_PROC) == 0) + { + GFile *file; + GimpParasite *parasite; + gint32 orig_image_ID; + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + file = g_file_new_for_uri (param[3].data.d_string); + + orig_image_ID = image_ID; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "GIH", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_ALPHA | + GIMP_EXPORT_CAN_HANDLE_LAYERS); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &info); + + parasite = gimp_image_get_parasite (orig_image_ID, + "gimp-brush-pipe-name"); + if (parasite) + { + strncpy (info.description, + gimp_parasite_data (parasite), + MIN (sizeof (info.description), + gimp_parasite_data_size (parasite))); + info.description[sizeof (info.description) - 1] = '\0'; + + gimp_parasite_free (parasite); + } + else + { + gchar *name = g_path_get_basename (gimp_file_get_utf8_name (file)); + + if (g_str_has_suffix (name, ".gih")) + name[strlen (name) - 4] = '\0'; + + if (strlen (name)) + { + strncpy (info.description, name, sizeof (info.description)); + info.description[sizeof (info.description) - 1] = '\0'; + } + + g_free (name); + } + + parasite = gimp_image_get_parasite (orig_image_ID, + "gimp-brush-pipe-spacing"); + if (parasite) + { + info.spacing = atoi (gimp_parasite_data (parasite)); + gimp_parasite_free (parasite); + } + break; + + default: + break; + } + + g_free (gimp_image_get_layers (image_ID, &num_layers)); + + gimp_pixpipe_params_init (&gihparams); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gihparams.ncells = (num_layers * gihparams.rows * gihparams.cols); + + gihparams.cellwidth = gimp_image_width (image_ID) / gihparams.cols; + gihparams.cellheight = gimp_image_height (image_ID) / gihparams.rows; + + parasite = gimp_image_get_parasite (orig_image_ID, + "gimp-brush-pipe-parameters"); + if (parasite) + { + gimp_pixpipe_params_parse (gimp_parasite_data (parasite), + &gihparams); + gimp_parasite_free (parasite); + } + + /* Force default rank to same as number of cells if there is + * just one dim + */ + if (gihparams.dim == 1) + gihparams.rank[0] = gihparams.ncells; + + if (! gih_save_dialog (image_ID)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 15) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + info.spacing = param[5].data.d_int32; + strncpy (info.description, param[6].data.d_string, + sizeof (info.description)); + info.description[sizeof (info.description) - 1] = '\0'; + + gihparams.cellwidth = param[7].data.d_int32; + gihparams.cellheight = param[8].data.d_int32; + gihparams.cols = param[9].data.d_int8; + gihparams.rows = param[10].data.d_int8; + gihparams.dim = param[11].data.d_int32; + gihparams.ncells = 1; + + if (param[13].data.d_int32 != gihparams.dim) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + for (i = 0; i < gihparams.dim; i++) + { + gihparams.rank[i] = param[12].data.d_int8array[i]; + gihparams.selection[i] = g_strdup (param[14].data.d_stringarray[i]); + gihparams.ncells *= gihparams.rank[i]; + } + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + parasite = gimp_image_get_parasite (orig_image_ID, + "gimp-brush-pipe-parameters"); + if (parasite) + { + gimp_pixpipe_params_parse (gimp_parasite_data (parasite), + &gihparams); + gimp_parasite_free (parasite); + } + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + GimpParam *save_retvals; + gint n_save_retvals; + gchar spacing[8]; + gchar *paramstring; + + paramstring = gimp_pixpipe_params_build (&gihparams); + + save_retvals = + gimp_run_procedure ("file-gih-save-internal", + &n_save_retvals, + GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE, + GIMP_PDB_IMAGE, image_ID, + GIMP_PDB_DRAWABLE, drawable_ID, + GIMP_PDB_STRING, param[3].data.d_string, + GIMP_PDB_STRING, param[4].data.d_string, + GIMP_PDB_INT32, info.spacing, + GIMP_PDB_STRING, info.description, + GIMP_PDB_STRING, paramstring, + GIMP_PDB_END); + + if (save_retvals[0].data.d_status == GIMP_PDB_SUCCESS) + { + gimp_set_data (SAVE_PROC, &info, sizeof (info)); + + parasite = gimp_parasite_new ("gimp-brush-pipe-name", + GIMP_PARASITE_PERSISTENT, + strlen (info.description) + 1, + info.description); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + + g_snprintf (spacing, sizeof (spacing), "%d", + info.spacing); + + parasite = gimp_parasite_new ("gimp-brush-pipe-spacing", + GIMP_PARASITE_PERSISTENT, + strlen (spacing) + 1, spacing); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + + parasite = gimp_parasite_new ("gimp-brush-pipe-parameters", + GIMP_PARASITE_PERSISTENT, + strlen (paramstring) + 1, + paramstring); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + } + else + { + g_set_error (&error, 0, 0, + "Running procedure 'file-gih-save-internal' " + "failed: %s", + gimp_get_pdb_error ()); + + status = GIMP_PDB_EXECUTION_ERROR; + } + + g_free (paramstring); + } + + gimp_pixpipe_params_free (&gihparams); + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +/* save routines */ + +static void +size_adjustment_callback (GtkWidget *widget, + SizeAdjustmentData *adj) +{ + gint i; + gint size; + gint newn; + gchar buf[10]; + + for (i = 0; i < adj->nguides; i++) + gimp_image_delete_guide (adj->image, adj->guides[i]); + + g_free (adj->guides); + adj->guides = NULL; + gimp_displays_flush (); + + *(adj->value) = gtk_adjustment_get_value (GTK_ADJUSTMENT (widget)); + + if (adj->orientation == GIMP_ORIENTATION_VERTICAL) + { + size = gimp_image_width (adj->image); + newn = size / *(adj->value); + adj->nguides = newn - 1; + adj->guides = g_new (gint32, adj->nguides); + for (i = 0; i < adj->nguides; i++) + adj->guides[i] = gimp_image_add_vguide (adj->image, + *(adj->value) * (i+1)); + } + else + { + size = gimp_image_height (adj->image); + newn = size / *(adj->value); + adj->nguides = newn - 1; + adj->guides = g_new (gint32, adj->nguides); + for (i = 0; i < adj->nguides; i++) + adj->guides[i] = gimp_image_add_hguide (adj->image, + *(adj->value) * (i+1)); + } + gimp_displays_flush (); + g_snprintf (buf, sizeof (buf), "%2d", newn); + gtk_label_set_text (GTK_LABEL (adj->count_label), buf); + + *(adj->count) = newn; + + gtk_widget_set_visible (GTK_WIDGET (adj->warning_label), + newn * *(adj->value) != size); + + if (adj->ncells != NULL) + gtk_adjustment_set_value (GTK_ADJUSTMENT (adj->ncells), + *(adj->other_count) * *(adj->count)); + if (adj->rank0 != NULL) + gtk_adjustment_set_value (GTK_ADJUSTMENT (adj->rank0), + *(adj->other_count) * *(adj->count)); +} + +static void +entry_callback (GtkWidget *widget, + gpointer data) +{ + if (data == info.description) + { + strncpy (info.description, gtk_entry_get_text (GTK_ENTRY (widget)), + sizeof (info.description)); + info.description[sizeof (info.description) - 1] = 0; + } +} + +static void +cb_callback (GtkWidget *widget, + gpointer data) +{ + gint index; + + index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)); + + *((const gchar **) data) = selection_modes [index]; +} + +static void +dim_callback (GtkAdjustment *adjustment, + SizeAdjustmentData *data) +{ + gint i; + + gihparams.dim = RINT (gtk_adjustment_get_value (adjustment)); + + for (i = 0; i < GIMP_PIXPIPE_MAXDIM; i++) + { + gtk_widget_set_sensitive (data->rank_entry[i], i < gihparams.dim); + gtk_widget_set_sensitive (data->mode_entry[i], i < gihparams.dim); + } +} + +static gboolean +gih_save_dialog (gint32 image_ID) +{ + GtkWidget *dialog; + GtkWidget *table; + GtkWidget *dimtable; + GtkWidget *label; + GtkAdjustment *adjustment; + GtkWidget *spinbutton; + GtkWidget *entry; + GtkWidget *box; + GtkWidget *cb; + gint i; + gchar buffer[100]; + SizeAdjustmentData cellw_adjust; + SizeAdjustmentData cellh_adjust; + gint32 *layer_ID; + gint32 nlayers; + gboolean run; + + dialog = gimp_export_dialog_new (_("Brush Pipe"), PLUG_IN_BINARY, SAVE_PROC); + + /* The main table */ + table = gtk_table_new (8, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + table, TRUE, TRUE, 0); + gtk_widget_show (table); + + /* + * Description: ___________ + */ + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 200, -1); + gtk_entry_set_text (GTK_ENTRY (entry), info.description); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Description:"), 0.0, 0.5, + entry, 1, FALSE); + + g_signal_connect (entry, "changed", + G_CALLBACK (entry_callback), + info.description); + + /* + * Spacing: __ + */ + adjustment = (GtkAdjustment *) gtk_adjustment_new (info.spacing, + 1, 1000, 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Spacing (percent):"), 0.0, 0.5, + spinbutton, 1, TRUE); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &info.spacing); + + /* + * Cell size: __ x __ pixels + */ + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (gihparams.cellwidth, + 2, gimp_image_width (image_ID), 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + layer_ID = gimp_image_get_layers (image_ID, &nlayers); + cellw_adjust.orientation = GIMP_ORIENTATION_VERTICAL; + cellw_adjust.image = image_ID; + cellw_adjust.toplayer = layer_ID[nlayers-1]; + cellw_adjust.nguides = 0; + cellw_adjust.guides = NULL; + cellw_adjust.value = &gihparams.cellwidth; + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (size_adjustment_callback), + &cellw_adjust); + + label = gtk_label_new ("x"); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (gihparams.cellheight, + 2, gimp_image_height (image_ID), 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + cellh_adjust.orientation = GIMP_ORIENTATION_HORIZONTAL; + cellh_adjust.image = image_ID; + cellh_adjust.toplayer = layer_ID[nlayers-1]; + cellh_adjust.nguides = 0; + cellh_adjust.guides = NULL; + cellh_adjust.value = &gihparams.cellheight; + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (size_adjustment_callback), + &cellh_adjust); + + label = gtk_label_new ( _("Pixels")); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("Ce_ll size:"), 0.0, 0.5, + box, 1, FALSE); + + g_free (layer_ID); + + /* + * Number of cells: ___ + */ + adjustment = (GtkAdjustment *) + gtk_adjustment_new (gihparams.ncells, 1, 1000, 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("_Number of cells:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &gihparams.ncells); + + if (gihparams.dim == 1) + cellw_adjust.ncells = cellh_adjust.ncells = adjustment; + else + cellw_adjust.ncells = cellh_adjust.ncells = NULL; + + /* + * Display as: __ rows x __ cols + */ + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + + g_snprintf (buffer, sizeof (buffer), "%2d", gihparams.rows); + label = gtk_label_new (buffer); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + cellh_adjust.count_label = label; + cellh_adjust.count = &gihparams.rows; + cellh_adjust.other_count = &gihparams.cols; + gtk_widget_show (label); + + label = gtk_label_new (_(" Rows of ")); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + g_snprintf (buffer, sizeof (buffer), "%2d", gihparams.cols); + label = gtk_label_new (buffer); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + cellw_adjust.count_label = label; + cellw_adjust.count = &gihparams.cols; + cellw_adjust.other_count = &gihparams.rows; + gtk_widget_show (label); + + label = gtk_label_new (_(" Columns on each layer")); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + label = gtk_label_new (_(" (Width Mismatch!) ")); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + cellw_adjust.warning_label = label; + + label = gtk_label_new (_(" (Height Mismatch!) ")); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + cellh_adjust.warning_label = label; + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, + _("Display as:"), 0.0, 0.5, + box, 1, FALSE); + + /* + * Dimension: ___ + */ + adjustment = (GtkAdjustment *) + gtk_adjustment_new (gihparams.dim, 1, GIMP_PIXPIPE_MAXDIM, 1, 1, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 5, + _("Di_mension:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (dim_callback), + &cellw_adjust); + + /* + * Ranks / Selection: ______ ______ ______ ______ ______ + */ + + dimtable = gtk_table_new (2, GIMP_PIXPIPE_MAXDIM, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (dimtable), 4); + for (i = 0; i < GIMP_PIXPIPE_MAXDIM; i++) + { + gint j; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (gihparams.rank[i], 1, 100, 1, 1, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_table_attach (GTK_TABLE (dimtable), spinbutton, 0, 1, i, i + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + + gtk_widget_show (spinbutton); + + if (i >= gihparams.dim) + gtk_widget_set_sensitive (spinbutton, FALSE); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &gihparams.rank[i]); + + cellw_adjust.rank_entry[i] = cellh_adjust.rank_entry[i] = spinbutton; + + if (i == 0) + { + if (gihparams.dim == 1) + cellw_adjust.rank0 = cellh_adjust.rank0 = adjustment; + else + cellw_adjust.rank0 = cellh_adjust.rank0 = NULL; + } + + cb = gtk_combo_box_text_new (); + + for (j = 0; j < G_N_ELEMENTS (selection_modes); j++) + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cb), + selection_modes[j]); + gtk_combo_box_set_active (GTK_COMBO_BOX (cb), 2); /* random */ + + if (gihparams.selection[i]) + for (j = 0; j < G_N_ELEMENTS (selection_modes); j++) + if (!strcmp (gihparams.selection[i], selection_modes[j])) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (cb), j); + break; + } + + gtk_table_attach (GTK_TABLE (dimtable), cb, 1, 2, i, i + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + + gtk_widget_show (cb); + + if (i >= gihparams.dim) + gtk_widget_set_sensitive (cb, FALSE); + + g_signal_connect (GTK_COMBO_BOX (cb), "changed", + G_CALLBACK (cb_callback), + &gihparams.selection[i]); + + cellw_adjust.mode_entry[i] = cellh_adjust.mode_entry[i] = cb; + + + } + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 6, + _("Ranks:"), 0.0, 0.0, + dimtable, 1, FALSE); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + gint i; + + for (i = 0; i < GIMP_PIXPIPE_MAXDIM; i++) + gihparams.selection[i] = g_strdup (gihparams.selection[i]); + + /* Fix up bogus values */ + gihparams.ncells = MIN (gihparams.ncells, + num_layers * gihparams.rows * gihparams.cols); + } + + gtk_widget_destroy (dialog); + + for (i = 0; i < cellw_adjust.nguides; i++) + gimp_image_delete_guide (image_ID, cellw_adjust.guides[i]); + for (i = 0; i < cellh_adjust.nguides; i++) + gimp_image_delete_guide (image_ID, cellh_adjust.guides[i]); + + return run; +} diff --git a/plug-ins/common/file-glob.c b/plug-ins/common/file-glob.c new file mode 100644 index 0000000..6ce0e00 --- /dev/null +++ b/plug-ins/common/file-glob.c @@ -0,0 +1,450 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* The idea is taken from a plug-in written by George Hartz; the code isn't. + */ + +#include "config.h" + +#include + +#include "libgimp/gimp.h" + + +#define PLUG_IN_PROC "file-glob" + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean glob_match (const gchar *pattern, + gboolean filename_encoding, + gint *num_matches, + gchar ***matches); +static gboolean glob_fnmatch (const gchar *pattern, + const gchar *string); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run, +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef glob_args[] = + { + { GIMP_PDB_STRING, "pattern" , "The glob pattern (in UTF-8 encoding)" }, + { GIMP_PDB_INT32, "encoding", "Encoding of the returned names: " + "{ UTF-8 (0), filename encoding (1) }" } + }; + + static const GimpParamDef glob_return_vals[] = + { + { GIMP_PDB_INT32, "num-files", "The number of returned names" }, + { GIMP_PDB_STRINGARRAY, "files", "The list of matching names" } + }; + + + gimp_install_procedure (PLUG_IN_PROC, + "Returns a list of matching filenames", + "This can be useful in scripts and other plug-ins " + "(e.g., batch-conversion). See the glob(7) manpage " + "for more info. Note however that this isn't a " + "full-featured glob implementation. It only handles " + "simple patterns like \"/home/foo/bar/*.jpg\".", + "Sven Neumann", + "Sven Neumann", + "2004", + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (glob_args), + G_N_ELEMENTS (glob_return_vals), + glob_args, + glob_return_vals); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[3]; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + + if (strcmp (name, PLUG_IN_PROC) == 0 && nparams >= 1) + { + gchar **matches; + gint num_matches; + gboolean filename_encoding = FALSE; + + if (nparams > 1) + filename_encoding = param[0].data.d_int32 ? TRUE : FALSE; + + if (! glob_match (param[0].data.d_string, filename_encoding, + &num_matches, &matches)) + { + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + return; + } + + *nreturn_vals = 3; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_SUCCESS; + + values[1].type = GIMP_PDB_INT32; + values[1].data.d_int32 = num_matches; + + values[2].type = GIMP_PDB_STRINGARRAY; + values[2].data.d_stringarray = matches; + } +} + +static gboolean +glob_match (const gchar *pattern, + gboolean filename_encoding, + gint *num_matches, + gchar ***matches) +{ + GDir *dir; + GPtrArray *array; + const gchar *filename; + gchar *dirname; + gchar *tmp; + + g_return_val_if_fail (pattern != NULL, FALSE); + g_return_val_if_fail (num_matches != NULL, FALSE); + g_return_val_if_fail (matches != NULL, FALSE); + + *num_matches = 0; + *matches = NULL; + + /* This is not a complete glob() implementation but rather a very + * simplistic approach. However it works for the most common use + * case and is better than nothing. + */ + + tmp = g_filename_from_utf8 (pattern, -1, NULL, NULL, NULL); + if (! tmp) + return FALSE; + + dirname = g_path_get_dirname (tmp); + + dir = g_dir_open (dirname, 0, NULL); + g_free (tmp); + + if (! dir) + { + g_free (dirname); + return TRUE; + } + + /* check if the pattern has a directory part at all */ + tmp = g_path_get_basename (pattern); + if (strcmp (pattern, tmp) == 0) + { + g_free (dirname); + dirname = NULL; + } + g_free (tmp); + + array = g_ptr_array_new (); + + for (filename = g_dir_read_name (dir); + filename; + filename = g_dir_read_name (dir)) + { + gchar *path; + gchar *name; + + if (dirname) + path = g_build_filename (dirname, filename, NULL); + else + path = g_strdup (filename); + + name = g_filename_to_utf8 (path, -1, NULL, NULL, NULL); + + if (name && glob_fnmatch (pattern, name)) + { + if (filename_encoding) + { + g_ptr_array_add (array, path); + path = NULL; + } + else + { + g_ptr_array_add (array, name); + name = NULL; + } + } + + g_free (path); + g_free (name); + } + + g_dir_close (dir); + g_free (dirname); + + *num_matches = array->len; + *matches = (gchar **) g_ptr_array_free (array, FALSE); + + return TRUE; +} + + +/* + * The following code is borrowed from GTK+. + * + * GTK+ used to use a old version of GNU fnmatch() that was buggy + * in various ways and didn't handle UTF-8. The following is + * converted to UTF-8. To simplify the process of making it + * correct, this is special-cased to the combinations of flags + * that gtkfilesel.c uses. + * + * FNM_FILE_NAME - always set + * FNM_LEADING_DIR - never set + * FNM_NOESCAPE - set only on windows + * FNM_CASEFOLD - set only on windows + */ + +/* We need to make sure that all constants are defined + * to properly compile this file + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +static gunichar +get_char (const char **str) +{ + gunichar c = g_utf8_get_char (*str); + *str = g_utf8_next_char (*str); + +#ifdef G_PLATFORM_WIN32 + c = g_unichar_tolower (c); +#endif + + return c; +} + +#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN) +#define DO_ESCAPE 0 +#else +#define DO_ESCAPE 1 +#endif + +static gunichar +get_unescaped_char (const char **str, + gboolean *was_escaped) +{ + gunichar c = get_char (str); + + *was_escaped = DO_ESCAPE && c == '\\'; + if (*was_escaped) + c = get_char (str); + + return c; +} + +/* Match STRING against the filename pattern PATTERN, + * returning TRUE if it matches, FALSE otherwise. + */ +static gboolean +fnmatch_intern (const gchar *pattern, + const gchar *string, + gboolean component_start, + gboolean no_leading_period) +{ + const char *p = pattern, *n = string; + + while (*p) + { + const char *last_n = n; + + gunichar c = get_char (&p); + gunichar nc = get_char (&n); + + switch (c) + { + case '?': + if (nc == '\0') + return FALSE; + else if (nc == G_DIR_SEPARATOR) + return FALSE; + else if (nc == '.' && component_start && no_leading_period) + return FALSE; + break; + case '\\': + if (DO_ESCAPE) + c = get_char (&p); + if (nc != c) + return FALSE; + break; + case '*': + if (nc == '.' && component_start && no_leading_period) + return FALSE; + + { + const char *last_p = p; + + for (last_p = p, c = get_char (&p); + c == '?' || c == '*'; + last_p = p, c = get_char (&p)) + { + if (c == '?') + { + if (nc == '\0') + return FALSE; + else if (nc == G_DIR_SEPARATOR) + return FALSE; + else + { + last_n = n; nc = get_char (&n); + } + } + } + + /* If the pattern ends with wildcards, we have a + * guaranteed match unless there is a dir separator + * in the remainder of the string. + */ + if (c == '\0') + { + if (strchr (last_n, G_DIR_SEPARATOR) != NULL) + return FALSE; + else + return TRUE; + } + + if (DO_ESCAPE && c == '\\') + c = get_char (&p); + + for (p = last_p; nc != '\0';) + { + if ((c == '[' || nc == c) && + fnmatch_intern (p, last_n, + component_start, no_leading_period)) + return TRUE; + + component_start = (nc == G_DIR_SEPARATOR); + last_n = n; + nc = get_char (&n); + } + + return FALSE; + } + + case '[': + { + /* Nonzero if the sense of the character class is inverted. */ + gboolean not; + gboolean was_escaped; + + if (nc == '\0' || nc == G_DIR_SEPARATOR) + return FALSE; + + if (nc == '.' && component_start && no_leading_period) + return FALSE; + + not = (*p == '!' || *p == '^'); + if (not) + ++p; + + c = get_unescaped_char (&p, &was_escaped); + for (;;) + { + register gunichar cstart = c, cend = c; + if (c == '\0') + /* [ (unterminated) loses. */ + return FALSE; + + c = get_unescaped_char (&p, &was_escaped); + + if (!was_escaped && c == '-' && *p != ']') + { + cend = get_unescaped_char (&p, &was_escaped); + if (cend == '\0') + return FALSE; + + c = get_char (&p); + } + + if (nc >= cstart && nc <= cend) + goto matched; + + if (!was_escaped && c == ']') + break; + } + if (!not) + return FALSE; + break; + + matched:; + /* Skip the rest of the [...] that already matched. */ + /* XXX 1003.2d11 is unclear if was_escaped is right. */ + while (was_escaped || c != ']') + { + if (c == '\0') + /* [... (unterminated) loses. */ + return FALSE; + + c = get_unescaped_char (&p, &was_escaped); + } + if (not) + return FALSE; + } + break; + + default: + if (c != nc) + return FALSE; + } + + component_start = (nc == G_DIR_SEPARATOR); + } + + if (*n == '\0') + return TRUE; + + return FALSE; +} + +static gboolean +glob_fnmatch (const gchar *pattern, + const gchar *string) +{ + return fnmatch_intern (pattern, string, TRUE, TRUE); +} diff --git a/plug-ins/common/file-header.c b/plug-ins/common/file-header.c new file mode 100644 index 0000000..50e5e37 --- /dev/null +++ b/plug-ins/common/file-header.c @@ -0,0 +1,439 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-header-save" +#define PLUG_IN_BINARY "file-header" +#define PLUG_IN_ROLE "gimp-file-header" + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + +static gboolean print (GOutputStream *output, + GError **error, + const gchar *format, + ...) G_GNUC_PRINTF (3, 4); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" } + }; + + gimp_install_procedure (SAVE_PROC, + "saves files as C unsigned character array", + "FIXME: write help", + "Spencer Kimball & Peter Mattis", + "Spencer Kimball & Peter Mattis", + "1997", + N_("C source code header"), + "INDEXED, RGB", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "text/x-chdr"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "h", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, SAVE_PROC) == 0) + { + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "Header", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + if (! save_image (g_file_new_for_uri (param[3].data.d_string), + image_ID, drawable_ID, &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglBuffer *buffer; + const Babl *format; + GimpImageType drawable_type; + GOutputStream *output; + gint x, y, b, c; + const gchar *backslash = "\\\\"; + const gchar *quote = "\\\""; + const gchar *newline = "\"\n\t\""; + gchar buf[4]; + guchar *d = NULL; + guchar *data = NULL; + guchar *cmap; + GCancellable *cancellable; + gint colors; + gint width; + gint height; + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (output) + { + GOutputStream *buffered; + + buffered = g_buffered_output_stream_new (output); + g_object_unref (output); + + output = buffered; + } + else + { + return FALSE; + } + + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + drawable_type = gimp_drawable_type (drawable_ID); + + if (! print (output, error, + "/* GIMP header image file format (%s): %s */\n\n", + GIMP_RGB_IMAGE == drawable_type ? "RGB" : "INDEXED", + gimp_file_get_utf8_name (file)) || + ! print (output, error, + "static unsigned int width = %d;\n", width) || + ! print (output, error, + "static unsigned int height = %d;\n\n", height) || + ! print (output, error, + "/* Call this macro repeatedly. After each use, the pixel data can be extracted */\n\n")) + { + goto fail; + } + + switch (drawable_type) + { + case GIMP_RGB_IMAGE: + if (! print (output, error, + "#define HEADER_PIXEL(data,pixel) {\\\n" + "pixel[0] = (((data[0] - 33) << 2) | ((data[1] - 33) >> 4)); \\\n" + "pixel[1] = ((((data[1] - 33) & 0xF) << 4) | ((data[2] - 33) >> 2)); \\\n" + "pixel[2] = ((((data[2] - 33) & 0x3) << 6) | ((data[3] - 33))); \\\n" + "data += 4; \\\n}\n") || + ! print (output, error, + "static char *header_data =\n\t\"")) + { + goto fail; + } + + format = babl_format ("R'G'B' u8"); + + data = g_new (guchar, width * babl_format_get_bytes_per_pixel (format)); + + c = 0; + for (y = 0; y < height; y++) + { + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, y, width, 1), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (x = 0; x < width; x++) + { + d = data + x * babl_format_get_bytes_per_pixel (format); + + buf[0] = ((d[0] >> 2) & 0x3F) + 33; + buf[1] = ((((d[0] & 0x3) << 4) | (d[1] >> 4)) & 0x3F) + 33; + buf[2] = ((((d[1] & 0xF) << 2) | (d[2] >> 6)) & 0x3F) + 33; + buf[3] = (d[2] & 0x3F) + 33; + + for (b = 0; b < 4; b++) + { + if (buf[b] == '"') + { + if (! print (output, error, "%s", quote)) + goto fail; + } + else if (buf[b] == '\\') + { + if (! print (output, error, "%s", backslash)) + goto fail; + } + else + { + if (! print (output, error, "%c", buf[b])) + goto fail; + } + } + + c++; + if (c >= 16) + { + if (! print (output, error, "%s", newline)) + goto fail; + + c = 0; + } + } + } + + if (! print (output, error, "\";\n")) + goto fail; + break; + + case GIMP_INDEXED_IMAGE: + if (! print (output, error, + "#define HEADER_PIXEL(data,pixel) {\\\n" + "pixel[0] = header_data_cmap[(unsigned char)data[0]][0]; \\\n" + "pixel[1] = header_data_cmap[(unsigned char)data[0]][1]; \\\n" + "pixel[2] = header_data_cmap[(unsigned char)data[0]][2]; \\\n" + "data ++; }\n\n")) + { + goto fail; + } + + /* save colormap */ + cmap = gimp_image_get_colormap (image_ID, &colors); + + if (! print (output, error, + "static unsigned char header_data_cmap[256][3] = {") || + ! print (output, error, + "\n\t{%3d,%3d,%3d}", + (gint) cmap[0], (gint) cmap[1], (gint) cmap[2])) + { + goto fail; + } + + for (c = 1; c < colors; c++) + { + if (! print (output, error, + ",\n\t{%3d,%3d,%3d}", + (gint) cmap[3 * c], + (gint) cmap[3 * c + 1], + (gint) cmap[3 * c + 2])) + { + goto fail; + } + } + + /* fill the rest */ + for ( ; c < 256; c++) + { + if (! print (output, error, ",\n\t{255,255,255}")) + goto fail; + } + + /* close bracket */ + if (! print (output, error, "\n\t};\n")) + goto fail; + + g_free (cmap); + + /* save image */ + if (! print (output, error, "static unsigned char header_data[] = {\n\t")) + goto fail; + + data = g_new (guchar, width * 1); + + c = 0; + for (y = 0; y < height; y++) + { + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, y, width, 1), 1.0, + NULL, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (x = 0; x < width -1; x++) + { + d = data + x * 1; + + if (! print (output, error, "%d,", (gint) d[0])) + goto fail; + + c++; + if (c >= 16) + { + if (! print (output, error, "\n\t")) + goto fail; + + c = 0; + } + } + + if (y != height - 1) + { + if (! print (output, error, "%d,\n\t", (gint) d[1])) + goto fail; + } + else + { + if (! print (output, error, "%d\n\t", (gint) d[1])) + goto fail; + } + + c = 0; /* reset line counter */ + } + + if (! print (output, error, "};\n")) + goto fail; + break; + + default: + g_warning ("unhandled drawable type (%d)", drawable_type); + goto fail; + } + + if (! g_output_stream_close (output, NULL, error)) + goto fail; + + g_free (data); + g_object_unref (output); + g_object_unref (buffer); + + return TRUE; + + fail: + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + + g_free (data); + g_object_unref (output); + g_object_unref (buffer); + g_object_unref (cancellable); + + return FALSE; +} + +static gboolean +print (GOutputStream *output, + GError **error, + const gchar *format, + ...) +{ + va_list args; + gboolean success; + + va_start (args, format); + success = g_output_stream_vprintf (output, NULL, NULL, + error, format, args); + va_end (args); + + return success; +} diff --git a/plug-ins/common/file-heif.c b/plug-ins/common/file-heif.c new file mode 100644 index 0000000..945951e --- /dev/null +++ b/plug-ins/common/file-heif.c @@ -0,0 +1,2076 @@ +/* + * GIMP HEIF loader / write plugin. + * Copyright (c) 2018 struktur AG, Dirk Farin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-heif-load" +#define SAVE_PROC "file-heif-save" +#define SAVE_PROC_AV1 "file-heif-av1-save" +#define PLUG_IN_BINARY "file-heif" + + +typedef struct _SaveParams SaveParams; + +struct _SaveParams +{ + gint quality; + gboolean lossless; + gboolean save_profile; + gint save_bit_depth; +}; + + +/* local function prototypes */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (GFile *file, + gboolean interactive, + GError **error); +static gboolean save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + const SaveParams *params, + GError **error, + enum heif_compression_format compression); + +static gboolean load_dialog (struct heif_context *heif, + uint32_t *selected_image); +static gboolean save_dialog (SaveParams *params, + gint32 image_ID, + const gchar *procname); + + +GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "uri", "The URI of the file to load" }, + { GIMP_PDB_STRING, "raw-uri", "The URI of the file to load" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "uri", "The URI of the file to export the image to" }, + { GIMP_PDB_STRING, "raw-uri", "The UTI of the file to export the image to" }, + { GIMP_PDB_INT32, "quality", "Quality factor (range: 0-100. 0 = worst, 100 = best)" }, + { GIMP_PDB_INT32, "lossless", "Use lossless compression (0 = lossy, 1 = lossless)" } + }; + + gimp_install_procedure (LOAD_PROC, + _("Loads HEIF images"), + _("Load image stored in HEIF format (High " + "Efficiency Image File Format). Typical " + "suffices for HEIF files are .heif, .heic."), + "Dirk Farin ", + "Dirk Farin ", + "2018", + _("HEIF/HEIC"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_load_handler (LOAD_PROC, + "heic,heif" +#if LIBHEIF_HAVE_VERSION(1,8,0) + ",avif" +#endif + , ""); + gimp_register_file_handler_mime (LOAD_PROC, + "image/heif" +#if LIBHEIF_HAVE_VERSION(1,8,0) + ",image/avif" +#endif + ); + gimp_register_file_handler_uri (LOAD_PROC); + /* HEIF is an ISOBMFF format whose "brand" (the value after "ftyp") + * can be of various values. + * See also: https://gitlab.gnome.org/GNOME/gimp/issues/2209 + */ + gimp_register_magic_load_handler (LOAD_PROC, + "heif,heic" +#if LIBHEIF_HAVE_VERSION(1,8,0) + ",avif" +#endif + , "", + "4,string,ftypheic,4,string,ftypheix," + "4,string,ftyphevc,4,string,ftypheim," + "4,string,ftypheis,4,string,ftyphevm," + "4,string,ftyphevs,4,string,ftypmif1," + "4,string,ftypmsf1" +#if LIBHEIF_HAVE_VERSION(1,8,0) + ",4,string,ftypavif" +#endif + ); + + gimp_install_procedure (SAVE_PROC, + _("Exports HEIF images"), + _("Save image in HEIF format (High Efficiency " + "Image File Format)."), + "Dirk Farin ", + "Dirk Farin ", + "2018", + _("HEIF/HEIC"), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_save_handler (SAVE_PROC, "heic,heif", ""); + gimp_register_file_handler_mime (SAVE_PROC, "image/heif"); + gimp_register_file_handler_uri (SAVE_PROC); + +#if LIBHEIF_HAVE_VERSION(1,8,0) + gimp_install_procedure (SAVE_PROC_AV1, + "Exports AVIF images", + "Save image in AV1 Image File Format (AVIF)", + "Daniel Novomesky ", + "Daniel Novomesky ", + "2020", + "HEIF/AVIF", + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_save_handler (SAVE_PROC_AV1, "avif", ""); + gimp_register_file_handler_mime (SAVE_PROC_AV1, "image/avif"); + gimp_register_file_handler_uri (SAVE_PROC_AV1); +#endif +} + +#define LOAD_HEIF_CANCEL -2 + +static void +run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + if (strcmp (name, LOAD_PROC) == 0) + { + GFile *file; + gboolean interactive; + + if (n_params != 3) + status = GIMP_PDB_CALLING_ERROR; + + file = g_file_new_for_uri (param[1].data.d_string); + interactive = (run_mode == GIMP_RUN_INTERACTIVE); + + if (status == GIMP_PDB_SUCCESS) + { + gint32 image_ID; + + image_ID = load_image (file, interactive, &error); + + if (image_ID >= 0) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else if (image_ID == LOAD_HEIF_CANCEL) + { + status = GIMP_PDB_CANCEL; + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + } + + g_object_unref (file); + } + else if (strcmp (name, SAVE_PROC) == 0) + { + gint32 image_ID = param[1].data.d_int32; + gint32 drawable_ID = param[2].data.d_int32; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + SaveParams params; + GimpMetadata *metadata = NULL; + GimpMetadataSaveFlags metadata_flags; + + metadata = gimp_image_metadata_save_prepare (image_ID, + "image/heif", + &metadata_flags); + + params.lossless = FALSE; + params.quality = 50; + params.save_profile = (metadata_flags & GIMP_METADATA_SAVE_COLOR_PROFILE) != 0; + params.save_bit_depth = 8; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + export = gimp_export_image (&image_ID, &drawable_ID, "HEIF", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + g_clear_object (&metadata); + return; + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (SAVE_PROC, ¶ms); + + if (! save_dialog (¶ms, image_ID, name)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (SAVE_PROC, ¶ms); + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (n_params != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + params.quality = (param[5].data.d_int32); + params.lossless = (param[6].data.d_int32); + } + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + GFile *file = g_file_new_for_uri (param[3].data.d_string); + + if (save_image (file, image_ID, drawable_ID, + ¶ms, + &error, + heif_compression_HEVC)) + { + /* Exiv2 doesn't support HEIC format yet, following code doesn't work + if (metadata) + gimp_image_metadata_save_finish (image_ID, + "image/heif", + metadata, metadata_flags, + file, NULL); + */ + gimp_set_data (SAVE_PROC, ¶ms, sizeof (params)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + g_object_unref (file); + } + + g_clear_object (&metadata); + } +#if LIBHEIF_HAVE_VERSION(1,8,0) + else if (strcmp (name, SAVE_PROC_AV1) == 0) + { + gint32 image_ID = param[1].data.d_int32; + gint32 drawable_ID = param[2].data.d_int32; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + SaveParams params; + GimpMetadata *metadata = NULL; + GimpMetadataSaveFlags metadata_flags; + + metadata = gimp_image_metadata_save_prepare (image_ID, + "image/avif", + &metadata_flags); + + params.lossless = FALSE; + params.quality = 50; + params.save_profile = (metadata_flags & GIMP_METADATA_SAVE_COLOR_PROFILE) != 0; + params.save_bit_depth = 8; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + export = gimp_export_image (&image_ID, &drawable_ID, "AVIF", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + g_clear_object (&metadata); + return; + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (SAVE_PROC_AV1, ¶ms); + + if (! save_dialog (¶ms, image_ID, name)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (SAVE_PROC_AV1, ¶ms); + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (n_params != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + params.quality = (param[5].data.d_int32); + params.lossless = (param[6].data.d_int32); + } + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + GFile *file = g_file_new_for_uri (param[3].data.d_string); + + if (save_image (file, image_ID, drawable_ID, + ¶ms, + &error, + heif_compression_AV1)) + { + gimp_set_data (SAVE_PROC_AV1, ¶ms, sizeof (params)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + g_object_unref (file); + } + + g_clear_object (&metadata); + } +#endif + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static goffset +get_file_size (GFile *file, + GError **error) +{ + GFileInfo *info; + goffset size = 1; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, error); + if (info) + { + size = g_file_info_get_size (info); + + g_object_unref (info); + } + + return size; +} + +#if LIBHEIF_HAVE_VERSION(1,8,0) +static void +heifplugin_color_profile_set_tag (cmsHPROFILE profile, + cmsTagSignature sig, + const gchar *tag) +{ + cmsMLU *mlu; + + mlu = cmsMLUalloc (NULL, 1); + cmsMLUsetASCII (mlu, "en", "US", tag); + cmsWriteTag (profile, sig, mlu); + cmsMLUfree (mlu); +} + +static GimpColorProfile * +nclx_to_gimp_profile (const struct heif_color_profile_nclx *nclx) +{ + const gchar *primaries_name = ""; + const gchar *trc_name = ""; + cmsHPROFILE profile = NULL; + cmsCIExyY whitepoint; + cmsCIExyYTRIPLE primaries; + cmsToneCurve *curve[3]; + + cmsFloat64Number srgb_parameters[5] = + { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 }; + + cmsFloat64Number rec709_parameters[5] = + { 2.2, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 }; + + if (nclx == NULL) + { + return NULL; + } + + if (nclx->color_primaries == heif_color_primaries_unspecified) + { + return NULL; + } + + if (nclx->color_primaries == heif_color_primaries_ITU_R_BT_709_5) + { + if (nclx->transfer_characteristics == heif_transfer_characteristic_IEC_61966_2_1) + { + return gimp_color_profile_new_rgb_srgb(); + } + + if (nclx->transfer_characteristics == heif_transfer_characteristic_linear) + { + return gimp_color_profile_new_rgb_srgb_linear(); + } + } + + whitepoint.x = nclx->color_primary_white_x; + whitepoint.y = nclx->color_primary_white_y; + whitepoint.Y = 1.0f; + + primaries.Red.x = nclx->color_primary_red_x; + primaries.Red.y = nclx->color_primary_red_y; + primaries.Red.Y = 1.0f; + + primaries.Green.x = nclx->color_primary_green_x; + primaries.Green.y = nclx->color_primary_green_y; + primaries.Green.Y = 1.0f; + + primaries.Blue.x = nclx->color_primary_blue_x; + primaries.Blue.y = nclx->color_primary_blue_y; + primaries.Blue.Y = 1.0f; + + switch (nclx->color_primaries) + { + case heif_color_primaries_ITU_R_BT_709_5: + primaries_name = "BT.709"; + break; + case heif_color_primaries_ITU_R_BT_470_6_System_M: + primaries_name = "BT.470-6 System M"; + break; + case heif_color_primaries_ITU_R_BT_470_6_System_B_G: + primaries_name = "BT.470-6 System BG"; + break; + case heif_color_primaries_ITU_R_BT_601_6: + primaries_name = "BT.601"; + break; + case heif_color_primaries_SMPTE_240M: + primaries_name = "SMPTE 240M"; + break; + case 8: + primaries_name = "Generic film"; + break; + case 9: + primaries_name = "BT.2020"; + break; + case 10: + primaries_name = "XYZ"; + break; + case 11: + primaries_name = "SMPTE RP 431-2"; + break; + case 12: + primaries_name = "SMPTE EG 432-1 (DCI P3)"; + break; + case 22: + primaries_name = "EBU Tech. 3213-E"; + break; + default: + g_warning ("%s: Unsupported color_primaries value %d.", + G_STRFUNC, nclx->color_primaries); + return NULL; + break; + } + + switch (nclx->transfer_characteristics) + { + case heif_transfer_characteristic_ITU_R_BT_709_5: + curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve (NULL, 4, + rec709_parameters); + profile = cmsCreateRGBProfile (&whitepoint, &primaries, curve); + cmsFreeToneCurve (curve[0]); + trc_name = "Rec709 RGB"; + break; + case heif_transfer_characteristic_ITU_R_BT_470_6_System_M: + curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 2.2f); + profile = cmsCreateRGBProfile (&whitepoint, &primaries, curve); + cmsFreeToneCurve (curve[0]); + trc_name = "Gamma2.2 RGB"; + break; + case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G: + curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 2.8f); + profile = cmsCreateRGBProfile (&whitepoint, &primaries, curve); + cmsFreeToneCurve (curve[0]); + trc_name = "Gamma2.8 RGB"; + break; + case heif_transfer_characteristic_linear: + curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 1.0f); + profile = cmsCreateRGBProfile (&whitepoint, &primaries, curve); + cmsFreeToneCurve (curve[0]); + trc_name = "linear RGB"; + break; + case heif_transfer_characteristic_IEC_61966_2_1: + /* same as default */ + default: + curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve (NULL, 4, + srgb_parameters); + profile = cmsCreateRGBProfile (&whitepoint, &primaries, curve); + cmsFreeToneCurve (curve[0]); + trc_name = "sRGB-TRC RGB"; + break; + } + + if (profile) + { + GimpColorProfile *new_profile; + gchar *description = g_strdup_printf ("%s %s", primaries_name, trc_name); + + heifplugin_color_profile_set_tag (profile, cmsSigProfileDescriptionTag, + description); + heifplugin_color_profile_set_tag (profile, cmsSigDeviceMfgDescTag, + "GIMP"); + heifplugin_color_profile_set_tag (profile, cmsSigDeviceModelDescTag, + description); + heifplugin_color_profile_set_tag (profile, cmsSigCopyrightTag, + "Public Domain"); + + new_profile = gimp_color_profile_new_from_lcms_profile (profile, NULL); + + cmsCloseProfile (profile); + g_free (description); + return new_profile; + } + + return NULL; +} +#endif + +gint32 +load_image (GFile *file, + gboolean interactive, + GError **error) +{ + GInputStream *input; + goffset file_size; + guchar *file_buffer; + gsize bytes_read; + struct heif_context *ctx; + struct heif_error err; + struct heif_image_handle *handle = NULL; + struct heif_image *img = NULL; + GimpColorProfile *profile = NULL; + gint n_images; + heif_item_id primary; + heif_item_id selected_image; + gboolean has_alpha; + gint width; + gint height; + gint32 image_ID; + gint32 layer_ID; + GeglBuffer *buffer; + const Babl *format; + const guint8 *data; + gint stride; + gint bit_depth = 8; + enum heif_chroma chroma = heif_chroma_interleaved_RGB; + GimpPrecision precision; + gboolean load_linear; + const char *encoding; + + gimp_progress_init_printf (_("Opening '%s'"), + g_file_get_parse_name (file)); + + file_size = get_file_size (file, error); + if (file_size <= 0) + return -1; + + input = G_INPUT_STREAM (g_file_read (file, NULL, error)); + if (! input) + return -1; + + file_buffer = g_malloc (file_size); + + if (! g_input_stream_read_all (input, file_buffer, file_size, + &bytes_read, NULL, error) && + bytes_read == 0) + { + g_free (file_buffer); + g_object_unref (input); + return -1; + } + + gimp_progress_update (0.25); + + ctx = heif_context_alloc (); + if (!ctx) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "cannot allocate heif_context"); + g_free (file_buffer); + g_object_unref (input); + return -1; + } + + err = heif_context_read_from_memory (ctx, file_buffer, file_size, NULL); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_context_free (ctx); + g_free (file_buffer); + g_object_unref (input); + + return -1; + } + + g_free (file_buffer); + g_object_unref (input); + + gimp_progress_update (0.5); + + /* analyze image content + * Is there more than one image? Which image is the primary image? + */ + + n_images = heif_context_get_number_of_top_level_images (ctx); + if (n_images == 0) + { + g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: " + "Input file contains no readable images")); + heif_context_free (ctx); + + return -1; + } + + err = heif_context_get_primary_image_ID (ctx, &primary); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_context_free (ctx); + + return -1; + } + + /* if primary image is no top level image or not present (invalid + * file), just take the first image + */ + + if (! heif_context_is_top_level_image_ID (ctx, primary)) + { + gint n = heif_context_get_list_of_top_level_image_IDs (ctx, &primary, 1); + g_assert (n == 1); + } + + selected_image = primary; + + /* if there are several images in the file and we are running + * interactive, let the user choose a picture + */ + + if (interactive && n_images > 1) + { + if (! load_dialog (ctx, &selected_image)) + { + heif_context_free (ctx); + + return LOAD_HEIF_CANCEL; + } + } + + /* load the picture */ + + err = heif_context_get_image_handle (ctx, selected_image, &handle); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_context_free (ctx); + + return -1; + } + + has_alpha = heif_image_handle_has_alpha_channel (handle); + +#if LIBHEIF_HAVE_VERSION(1,8,0) + bit_depth = heif_image_handle_get_luma_bits_per_pixel (handle); + if (bit_depth < 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Input image has undefined bit-depth"); + heif_image_handle_release (handle); + heif_context_free (ctx); + + return -1; + } +#endif + + if (bit_depth == 8) + { + if (has_alpha) + { + chroma = heif_chroma_interleaved_RGBA; + } + else + { + chroma = heif_chroma_interleaved_RGB; + } + } + else /* high bit depth */ + { +#if LIBHEIF_HAVE_VERSION(1,8,0) +#if ( G_BYTE_ORDER == G_LITTLE_ENDIAN ) + if (has_alpha) + { + chroma = heif_chroma_interleaved_RRGGBBAA_LE; + } + else + { + chroma = heif_chroma_interleaved_RRGGBB_LE; + } +#else + if (has_alpha) + { + chroma = heif_chroma_interleaved_RRGGBBAA_BE; + } + else + { + chroma = heif_chroma_interleaved_RRGGBB_BE; + } +#endif +#endif + } + + err = heif_decode_image (handle, + &img, + heif_colorspace_RGB, + chroma, + NULL); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_image_handle_release (handle); + heif_context_free (ctx); + + return -1; + } + +#if LIBHEIF_HAVE_VERSION(1,4,0) + switch (heif_image_handle_get_color_profile_type (handle)) + { + case heif_color_profile_type_not_present: + break; + case heif_color_profile_type_rICC: + case heif_color_profile_type_prof: + /* I am unsure, but it looks like both these types represent an + * ICC color profile. XXX + */ + { + void *profile_data; + size_t profile_size; + + profile_size = heif_image_handle_get_raw_color_profile_size (handle); + profile_data = g_malloc0 (profile_size); + err = heif_image_handle_get_raw_color_profile (handle, profile_data); + + if (err.code) + g_warning ("%s: color profile loading failed and discarded.", + G_STRFUNC); + else + profile = gimp_color_profile_new_from_icc_profile ((guint8 *) profile_data, + profile_size, NULL); + + g_free (profile_data); + } + break; +#if LIBHEIF_HAVE_VERSION(1,8,0) + case heif_color_profile_type_nclx: + { + struct heif_color_profile_nclx *nclx = NULL; + + err = heif_image_handle_get_nclx_color_profile (handle, &nclx); + if (err.code) + { + g_warning ("%s: NCLX profile loading failed and discarded.", + G_STRFUNC); + } + else + { + profile = nclx_to_gimp_profile (nclx); + heif_nclx_color_profile_free (nclx); + } + } + break; +#endif + default: + g_warning ("%s: unknown color profile type has been discarded.", + G_STRFUNC); + break; + } +#endif /* LIBHEIF_HAVE_VERSION(1,4,0) */ + + gimp_progress_update (0.75); + + width = heif_image_get_width (img, heif_channel_interleaved); + height = heif_image_get_height (img, heif_channel_interleaved); + + /* create GIMP image and copy HEIF image into the GIMP image + * (converting it to RGB) + */ + + if (profile) + { + load_linear = gimp_color_profile_is_linear (profile); + } + else + { + load_linear = FALSE; + } + + if (load_linear) + { + if (bit_depth == 8) + { + precision = GIMP_PRECISION_U8_LINEAR; + encoding = has_alpha ? "RGBA u8" : "RGB u8"; + } + else + { + precision = GIMP_PRECISION_U16_LINEAR; + encoding = has_alpha ? "RGBA u16" : "RGB u16"; + } + } + else /* non-linear profiles */ + { + if (bit_depth == 8) + { + precision = GIMP_PRECISION_U8_GAMMA; + encoding = has_alpha ? "R'G'B'A u8" : "R'G'B' u8"; + } + else + { + precision = GIMP_PRECISION_U16_GAMMA; + encoding = has_alpha ? "R'G'B'A u16" : "R'G'B' u16"; + } + } + + image_ID = gimp_image_new_with_precision (width, height, GIMP_RGB, precision); + gimp_image_set_filename (image_ID, g_file_get_uri (file)); + + if (profile) + { + if (gimp_color_profile_is_rgb (profile)) + { + gimp_image_set_color_profile (image_ID, profile); + } + else if (gimp_color_profile_is_gray (profile)) + { + g_warning ("Gray ICC profile was not applied to the imported image."); + } + else + { + g_warning ("ICC profile was not applied to the imported image."); + } + } + + layer_ID = gimp_layer_new (image_ID, + _("image content"), + width, height, + has_alpha ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE, + 100.0, + gimp_image_get_default_new_layer_mode (image_ID)); + + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_ID); + + data = heif_image_get_plane_readonly (img, heif_channel_interleaved, + &stride); + + format = babl_format_with_space (encoding, + gegl_buffer_get_format (buffer)); + + if (bit_depth == 8) + { + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, 0, width, height), + 0, format, data, stride); + } + else /* high bit depth */ + { + uint16_t *data16; + const uint16_t *src16; + uint16_t *dest16; + gint x, y, rowentries; + int tmp_pixelval; + + if (has_alpha) + { + rowentries = width * 4; + } + else /* no alpha */ + { + rowentries = width * 3; + } + + data16 = g_malloc_n (height, rowentries * 2); + dest16 = data16; + + switch (bit_depth) + { + case 10: + for (y = 0; y < height; y++) + { + src16 = (const uint16_t *) (y * stride + data); + for (x = 0; x < rowentries; x++) + { + tmp_pixelval = (int) ( ( (float) (0x03ff & (*src16)) / 1023.0f) * 65535.0f + 0.5f); + *dest16 = CLAMP (tmp_pixelval, 0, 65535); + dest16++; + src16++; + } + } + break; + case 12: + for (y = 0; y < height; y++) + { + src16 = (const uint16_t *) (y * stride + data); + for (x = 0; x < rowentries; x++) + { + tmp_pixelval = (int) ( ( (float) (0x0fff & (*src16)) / 4095.0f) * 65535.0f + 0.5f); + *dest16 = CLAMP (tmp_pixelval, 0, 65535); + dest16++; + src16++; + } + } + break; + default: + for (y = 0; y < height; y++) + { + src16 = (const uint16_t *) (y * stride + data); + for (x = 0; x < rowentries; x++) + { + *dest16 = *src16; + dest16++; + src16++; + } + } + break; + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, 0, width, height), + 0, format, data16, GEGL_AUTO_ROWSTRIDE); + + g_free (data16); + } + + g_object_unref (buffer); + + { + size_t exif_data_size = 0; + uint8_t *exif_data = NULL; + size_t xmp_data_size = 0; + uint8_t *xmp_data = NULL; + gint n_metadata; + heif_item_id metadata_id; + + n_metadata = + heif_image_handle_get_list_of_metadata_block_IDs (handle, + "Exif", + &metadata_id, 1); + if (n_metadata > 0) + { + exif_data_size = heif_image_handle_get_metadata_size (handle, + metadata_id); + exif_data = g_alloca (exif_data_size); + + err = heif_image_handle_get_metadata (handle, metadata_id, exif_data); + if (err.code != 0) + { + exif_data = NULL; + exif_data_size = 0; + } + } + + n_metadata = + heif_image_handle_get_list_of_metadata_block_IDs (handle, + "mime", + &metadata_id, 1); + if (n_metadata > 0) + { + if (g_strcmp0 ( + heif_image_handle_get_metadata_content_type (handle, metadata_id), + "application/rdf+xml") == 0) + { + xmp_data_size = heif_image_handle_get_metadata_size (handle, + metadata_id); + xmp_data = g_alloca (xmp_data_size); + + err = heif_image_handle_get_metadata (handle, metadata_id, xmp_data); + if (err.code != 0) + { + xmp_data = NULL; + xmp_data_size = 0; + } + } + } + + if (exif_data || xmp_data) + { + GimpMetadata *metadata = gimp_metadata_new (); + GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_COMMENT | GIMP_METADATA_LOAD_RESOLUTION; + + if (exif_data) + { + const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 }; + const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 }; + GExiv2Metadata *exif_metadata = GEXIV2_METADATA (metadata); + const guint8 *tiffheader = exif_data; + glong new_exif_size = exif_data_size; + + while (new_exif_size >= 4) /*Searching for TIFF Header*/ + { + if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] && + tiffheader[2] == tiffHeaderBE[2] && tiffheader[2] == tiffHeaderBE[2]) + { + break; + } + if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] && + tiffheader[2] == tiffHeaderLE[2] && tiffheader[2] == tiffHeaderLE[2]) + { + break; + } + new_exif_size--; + tiffheader++; + } + + if (new_exif_size > 4) /* TIFF header + some data found*/ + { + if (! gexiv2_metadata_open_buf (exif_metadata, tiffheader, new_exif_size, error)) + { + g_printerr ("%s: Failed to set EXIF metadata: %s\n", G_STRFUNC, (*error)->message); + g_clear_error (error); + } + } + else + { + g_printerr ("%s: EXIF metadata not set\n", G_STRFUNC); + } + } + + + if (xmp_data) + { + if (!gimp_metadata_set_from_xmp (metadata, xmp_data, xmp_data_size, error)) + { + g_printerr ("%s: Failed to set XMP metadata: %s\n", G_STRFUNC, (*error)->message); + g_clear_error (error); + } + } + + gimp_image_metadata_load_finish (image_ID, "image/heif", + metadata, flags, + interactive); + } + } + + if (profile) + g_object_unref (profile); + + heif_image_handle_release (handle); + heif_context_free (ctx); + heif_image_release (img); + + gimp_progress_update (1.0); + + return image_ID; +} + +static struct heif_error +write_callback (struct heif_context *ctx, + const void *data, + size_t size, + void *userdata) +{ + GOutputStream *output = userdata; + GError *error = NULL; + struct heif_error heif_error; + + heif_error.code = heif_error_Ok; + heif_error.subcode = heif_suberror_Unspecified; + heif_error.message = ""; + + if (! g_output_stream_write_all (output, data, size, NULL, NULL, &error)) + { + heif_error.code = 99; /* hmm */ + heif_error.message = error->message; + } + + return heif_error; +} + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + const SaveParams *params, + GError **error, + enum heif_compression_format compression) +{ + struct heif_image *image = NULL; + struct heif_context *context = heif_context_alloc (); + struct heif_encoder *encoder = NULL; + struct heif_image_handle *handle = NULL; + struct heif_writer writer; + struct heif_error err; + GOutputStream *output; + GeglBuffer *buffer; + const gchar *encoding; + const Babl *format; + const Babl *space = NULL; + guint8 *data; + gint stride; + gint width; + gint height; + gboolean has_alpha; + gboolean out_linear = FALSE; + + if (!context) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "cannot allocate heif_context"); + return FALSE; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + g_file_get_parse_name (file)); + + width = gimp_drawable_width (drawable_ID); + height = gimp_drawable_height (drawable_ID); + + has_alpha = gimp_drawable_has_alpha (drawable_ID); + + switch (params->save_bit_depth) + { + case 8: + err = heif_image_create (width, height, + heif_colorspace_RGB, + has_alpha ? + heif_chroma_interleaved_RGBA : + heif_chroma_interleaved_RGB, + &image); + break; +#if LIBHEIF_HAVE_VERSION(1,8,0) + case 10: + case 12: +#if ( G_BYTE_ORDER == G_LITTLE_ENDIAN ) + err = heif_image_create (width, height, + heif_colorspace_RGB, + has_alpha ? + heif_chroma_interleaved_RRGGBBAA_LE : + heif_chroma_interleaved_RRGGBB_LE, + &image); +#else + err = heif_image_create (width, height, + heif_colorspace_RGB, + has_alpha ? + heif_chroma_interleaved_RRGGBBAA_BE : + heif_chroma_interleaved_RRGGBB_BE, + &image); +#endif + break; +#endif + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Unsupported bit depth: %d", + params->save_bit_depth); + heif_context_free (context); + return FALSE; + break; + } + + if (err.code != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Encoding HEIF image failed: %s"), + err.message); + heif_context_free (context); + return FALSE; + } + +#if LIBHEIF_HAVE_VERSION(1,4,0) + if (params->save_profile) + { + GimpColorProfile *profile = NULL; + const guint8 *icc_data; + gsize icc_length; + + profile = gimp_image_get_color_profile (image_ID); + if (profile && gimp_color_profile_is_linear (profile)) + out_linear = TRUE; + + if (! profile) + { + profile = gimp_image_get_effective_color_profile (image_ID); + + if (gimp_color_profile_is_linear (profile)) + { + if (gimp_image_get_precision (image_ID) != GIMP_PRECISION_U8_LINEAR) + { + /* If stored data was linear, let's convert the profile. */ + GimpColorProfile *saved_profile; + + saved_profile = gimp_color_profile_new_srgb_trc_from_color_profile (profile); + g_clear_object (&profile); + profile = saved_profile; + } + else + { + /* Keep linear profile as-is for 8-bit linear image. */ + out_linear = TRUE; + } + } + } + + icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length); + heif_image_set_raw_color_profile (image, "prof", icc_data, icc_length); + space = gimp_color_profile_get_space (profile, + GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC, + error); + if (error && *error) + { + /* Don't make this a hard failure yet output the error. */ + g_printerr ("%s: error getting the profile space: %s", + G_STRFUNC, (*error)->message); + g_clear_error (error); + } + + g_object_unref (profile); + } + else + { +#if LIBHEIF_HAVE_VERSION(1,8,0) + /* We save as sRGB */ + struct heif_color_profile_nclx nclx_profile; + + nclx_profile.version = 1; + nclx_profile.color_primaries = heif_color_primaries_ITU_R_BT_709_5; + nclx_profile.transfer_characteristics = heif_transfer_characteristic_IEC_61966_2_1; + nclx_profile.matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; + nclx_profile.full_range_flag = 1; + + heif_image_set_nclx_color_profile (image, &nclx_profile); + + space = babl_space ("sRGB"); + out_linear = FALSE; +#endif + } +#endif /* LIBHEIF_HAVE_VERSION(1,4,0) */ + + if (! space) + space = gimp_drawable_get_format (drawable_ID); + + if (params->save_bit_depth > 8) + { + uint16_t *data16; + const uint16_t *src16; + uint16_t *dest16; + gint x, y, rowentries; + int tmp_pixelval; + + if (has_alpha) + { + rowentries = width * 4; + + if (out_linear) + encoding = "RGBA u16"; + else + encoding = "R'G'B'A u16"; + } + else /* no alpha */ + { + rowentries = width * 3; + + if (out_linear) + encoding = "RGB u16"; + else + encoding = "R'G'B' u16"; + } + + data16 = g_malloc_n (height, rowentries * 2); + src16 = data16; + + format = babl_format_with_space (encoding, space); + + buffer = gimp_drawable_get_buffer (drawable_ID); + + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, 0, width, height), + 1.0, format, data16, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + g_object_unref (buffer); + + heif_image_add_plane (image, heif_channel_interleaved, + width, height, params->save_bit_depth); + + data = heif_image_get_plane (image, heif_channel_interleaved, &stride); + + switch (params->save_bit_depth) + { + case 10: + for (y = 0; y < height; y++) + { + dest16 = (uint16_t *) (y * stride + data); + for (x = 0; x < rowentries; x++) + { + tmp_pixelval = (int) ( ( (float) (*src16) / 65535.0f) * 1023.0f + 0.5f); + *dest16 = CLAMP (tmp_pixelval, 0, 1023); + dest16++; + src16++; + } + } + break; + case 12: + for (y = 0; y < height; y++) + { + dest16 = (uint16_t *) (y * stride + data); + for (x = 0; x < rowentries; x++) + { + tmp_pixelval = (int) ( ( (float) (*src16) / 65535.0f) * 4095.0f + 0.5f); + *dest16 = CLAMP (tmp_pixelval, 0, 4095); + dest16++; + src16++; + } + } + break; + default: + for (y = 0; y < height; y++) + { + dest16 = (uint16_t *) (y * stride + data); + for (x = 0; x < rowentries; x++) + { + *dest16 = *src16; + dest16++; + src16++; + } + } + break; + } + g_free (data16); + } + else /* save_bit_depth == 8 */ + { +#if LIBHEIF_HAVE_VERSION(1,8,0) + heif_image_add_plane (image, heif_channel_interleaved, + width, height, 8); +#else + /* old style settings */ + heif_image_add_plane (image, heif_channel_interleaved, + width, height, has_alpha ? 32 : 24); +#endif + + data = heif_image_get_plane (image, heif_channel_interleaved, &stride); + + buffer = gimp_drawable_get_buffer (drawable_ID); + + if (has_alpha) + { + if (out_linear) + encoding = "RGBA u8"; + else + encoding = "R'G'B'A u8"; + } + else + { + if (out_linear) + encoding = "RGB u8"; + else + encoding = "R'G'B' u8"; + } + format = babl_format_with_space (encoding, space); + + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, 0, width, height), + 1.0, format, data, stride, GEGL_ABYSS_NONE); + + g_object_unref (buffer); + } + + gimp_progress_update (0.33); + + /* encode to HEIF file */ + + err = heif_context_get_encoder_for_format (context, + compression, + &encoder); + + if (err.code != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Unable to find suitable HEIF encoder"); + heif_image_release (image); + heif_context_free (context); + return FALSE; + } + + heif_encoder_set_lossy_quality (encoder, params->quality); + heif_encoder_set_lossless (encoder, params->lossless); + /* heif_encoder_set_logging_level (encoder, logging_level); */ + + err = heif_context_encode_image (context, + image, + encoder, + NULL, + &handle); + if (err.code != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Encoding HEIF image failed: %s"), + err.message); + heif_encoder_release (encoder); + heif_image_release (image); + heif_context_free (context); + return FALSE; + } + + heif_image_handle_release (handle); + + gimp_progress_update (0.66); + + writer.writer_api_version = 1; + writer.write = write_callback; + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (! output) + { + heif_encoder_release (encoder); + heif_image_release (image); + heif_context_free (context); + return FALSE; + } + + err = heif_context_write (context, &writer, output); + + if (err.code != 0) + { + GCancellable *cancellable = g_cancellable_new (); + + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Writing HEIF image failed: %s"), + err.message); + + heif_encoder_release (encoder); + heif_image_release (image); + heif_context_free (context); + return FALSE; + } + + g_object_unref (output); + + heif_encoder_release (encoder); + heif_image_release (image); + heif_context_free (context); + + gimp_progress_update (1.0); + + return TRUE; +} + + +/* the load dialog */ + +#define MAX_THUMBNAIL_SIZE 320 + +typedef struct _HeifImage HeifImage; + +struct _HeifImage +{ + uint32_t ID; + gchar caption[100]; + struct heif_image *thumbnail; + gint width; + gint height; +}; + +static gboolean +load_thumbnails (struct heif_context *heif, + HeifImage *images) +{ + guint32 *IDs; + gint n_images; + gint i; + + n_images = heif_context_get_number_of_top_level_images (heif); + + /* get list of all (top level) image IDs */ + + IDs = g_alloca (n_images * sizeof (guint32)); + + heif_context_get_list_of_top_level_image_IDs (heif, IDs, n_images); + + + /* Load a thumbnail for each image. */ + + for (i = 0; i < n_images; i++) + { + struct heif_image_handle *handle = NULL; + struct heif_error err; + gint width; + gint height; + struct heif_image_handle *thumbnail_handle = NULL; + heif_item_id thumbnail_ID; + gint n_thumbnails; + struct heif_image *thumbnail_img = NULL; + gint thumbnail_width; + gint thumbnail_height; + + images[i].ID = IDs[i]; + images[i].caption[0] = 0; + images[i].thumbnail = NULL; + + /* get image handle */ + + err = heif_context_get_image_handle (heif, IDs[i], &handle); + if (err.code) + { + gimp_message (err.message); + continue; + } + + /* generate image caption */ + + width = heif_image_handle_get_width (handle); + height = heif_image_handle_get_height (handle); + + if (heif_image_handle_is_primary_image (handle)) + { + g_snprintf (images[i].caption, sizeof (images[i].caption), + "%dx%d (%s)", width, height, _("primary")); + } + else + { + g_snprintf (images[i].caption, sizeof (images[i].caption), + "%dx%d", width, height); + } + + /* get handle to thumbnail image + * + * if there is no thumbnail image, just the the image itself + * (will be scaled down later) + */ + + n_thumbnails = heif_image_handle_get_list_of_thumbnail_IDs (handle, + &thumbnail_ID, + 1); + + if (n_thumbnails > 0) + { + err = heif_image_handle_get_thumbnail (handle, thumbnail_ID, + &thumbnail_handle); + if (err.code) + { + gimp_message (err.message); + continue; + } + } + else + { + err = heif_context_get_image_handle (heif, IDs[i], &thumbnail_handle); + if (err.code) + { + gimp_message (err.message); + continue; + } + } + + /* decode the thumbnail image */ + + err = heif_decode_image (thumbnail_handle, + &thumbnail_img, + heif_colorspace_RGB, + heif_chroma_interleaved_RGB, + NULL); + if (err.code) + { + gimp_message (err.message); + continue; + } + + /* if thumbnail image size exceeds the maximum, scale it down */ + + thumbnail_width = heif_image_handle_get_width (thumbnail_handle); + thumbnail_height = heif_image_handle_get_height (thumbnail_handle); + + if (thumbnail_width > MAX_THUMBNAIL_SIZE || + thumbnail_height > MAX_THUMBNAIL_SIZE) + { + /* compute scaling factor to fit into a max sized box */ + + gfloat factor_h = thumbnail_width / (gfloat) MAX_THUMBNAIL_SIZE; + gfloat factor_v = thumbnail_height / (gfloat) MAX_THUMBNAIL_SIZE; + gint new_width, new_height; + struct heif_image *scaled_img = NULL; + + if (factor_v > factor_h) + { + new_height = MAX_THUMBNAIL_SIZE; + new_width = thumbnail_width / factor_v; + } + else + { + new_height = thumbnail_height / factor_h; + new_width = MAX_THUMBNAIL_SIZE; + } + + /* scale the image */ + + err = heif_image_scale_image (thumbnail_img, + &scaled_img, + new_width, new_height, + NULL); + if (err.code) + { + gimp_message (err.message); + continue; + } + + /* release the old image and only keep the scaled down version */ + + heif_image_release (thumbnail_img); + thumbnail_img = scaled_img; + + thumbnail_width = new_width; + thumbnail_height = new_height; + } + + heif_image_handle_release (thumbnail_handle); + heif_image_handle_release (handle); + + /* remember the HEIF thumbnail image (we need it for the GdkPixbuf) */ + + images[i].thumbnail = thumbnail_img; + + images[i].width = thumbnail_width; + images[i].height = thumbnail_height; + } + + return TRUE; +} + +static void +load_dialog_item_activated (GtkIconView *icon_view, + GtkTreePath *path, + GtkDialog *dialog) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_OK); +} + +static gboolean +load_dialog (struct heif_context *heif, + uint32_t *selected_image) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + HeifImage *heif_images; + GtkListStore *list_store; + GtkTreeIter iter; + GtkWidget *scrolled_window; + GtkWidget *icon_view; + GtkCellRenderer *renderer; + gint n_images; + gint i; + gint selected_idx = -1; + gboolean run = FALSE; + + n_images = heif_context_get_number_of_top_level_images (heif); + + heif_images = g_alloca (n_images * sizeof (HeifImage)); + + if (! load_thumbnails (heif, heif_images)) + return FALSE; + + dialog = gimp_dialog_new (_("Load HEIF Image"), PLUG_IN_BINARY, + NULL, 0, + gimp_standard_help_func, LOAD_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + + frame = gimp_frame_new (_("Select Image")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + /* prepare list store with all thumbnails and caption */ + + list_store = gtk_list_store_new (2, G_TYPE_STRING, GDK_TYPE_PIXBUF); + + for (i = 0; i < n_images; i++) + { + GdkPixbuf *pixbuf; + const guint8 *data; + gint stride; + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, 0, heif_images[i].caption, -1); + + data = heif_image_get_plane_readonly (heif_images[i].thumbnail, + heif_channel_interleaved, + &stride); + + pixbuf = gdk_pixbuf_new_from_data (data, + GDK_COLORSPACE_RGB, + FALSE, + 8, + heif_images[i].width, + heif_images[i].height, + stride, + NULL, + NULL); + + gtk_list_store_set (list_store, &iter, 1, pixbuf, -1); + } + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_widget_set_size_request (scrolled_window, + 2 * MAX_THUMBNAIL_SIZE, + 1.5 * MAX_THUMBNAIL_SIZE); + gtk_container_add (GTK_CONTAINER (frame), scrolled_window); + gtk_widget_show (scrolled_window); + + icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (list_store)); + gtk_container_add (GTK_CONTAINER (scrolled_window), icon_view); + gtk_widget_show (icon_view); + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), renderer, FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view), renderer, + "pixbuf", 1, + NULL); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), renderer, FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view), renderer, + "text", 0, + NULL); + g_object_set (renderer, + "alignment", PANGO_ALIGN_CENTER, + "wrap-mode", PANGO_WRAP_WORD_CHAR, + "xalign", 0.5, + "yalign", 0.0, + NULL); + + g_signal_connect (icon_view, "item-activated", + G_CALLBACK (load_dialog_item_activated), + dialog); + + /* pre-select the primary image */ + + for (i = 0; i < n_images; i++) + { + if (heif_images[i].ID == *selected_image) + { + selected_idx = i; + break; + } + } + + if (selected_idx != -1) + { + GtkTreePath *path = gtk_tree_path_new_from_indices (selected_idx, -1); + + gtk_icon_view_select_path (GTK_ICON_VIEW (icon_view), path); + gtk_tree_path_free (path); + } + + gtk_widget_show (main_vbox); + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + GList *selected_items = + gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view)); + + if (selected_items) + { + GtkTreePath *path = selected_items->data; + gint *indices = gtk_tree_path_get_indices (path); + + *selected_image = heif_images[indices[0]].ID; + + g_list_free_full (selected_items, + (GDestroyNotify) gtk_tree_path_free); + } + } + + gtk_widget_destroy (dialog); + + /* release thumbnail images */ + + for (i = 0 ; i < n_images; i++) + heif_image_release (heif_images[i].thumbnail); + + return run; +} + + +/* the save dialog */ + +static void +save_dialog_lossless_button_toggled (GtkToggleButton *source, + GtkWidget *hbox) +{ + gboolean lossless = gtk_toggle_button_get_active (source); + + gtk_widget_set_sensitive (hbox, ! lossless); +} + +gboolean +save_dialog (SaveParams *params, + gint32 image_ID, + const gchar *procname) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *lossless_button; + GtkWidget *frame; +#if LIBHEIF_HAVE_VERSION(1,4,0) + GtkWidget *profile_button; +#endif + GtkWidget *quality_slider; +#if LIBHEIF_HAVE_VERSION(1,8,0) + GtkWidget *table; + GtkWidget *label2; + GtkWidget *combo; +#endif + gboolean run = FALSE; + + dialog = gimp_export_dialog_new (g_strcmp0 (procname, SAVE_PROC_AV1) == 0? + "AVIF" : "HEIF", + PLUG_IN_BINARY, procname); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + main_vbox, TRUE, TRUE, 0); + + frame = gimp_frame_new (NULL); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + + lossless_button = gtk_check_button_new_with_mnemonic (_("Nearly _lossless (YUV420 format)")); + gtk_frame_set_label_widget (GTK_FRAME (frame), lossless_button); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + label = gtk_label_new_with_mnemonic (_("_Quality:")); + quality_slider = gtk_hscale_new_with_range (0, 100, 5); + gtk_scale_set_value_pos (GTK_SCALE (quality_slider), GTK_POS_RIGHT); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), quality_slider, TRUE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + gtk_range_set_value (GTK_RANGE (quality_slider), params->quality); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lossless_button), + params->lossless); + gtk_widget_set_sensitive (quality_slider, !params->lossless); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), quality_slider); + + g_signal_connect (lossless_button, "toggled", + G_CALLBACK (save_dialog_lossless_button_toggled), + hbox); + +#if LIBHEIF_HAVE_VERSION(1,8,0) + switch (gimp_image_get_precision (image_ID)) + { + case GIMP_PRECISION_U8_LINEAR: + case GIMP_PRECISION_U8_GAMMA: + /* image is 8bit depth */ + params->save_bit_depth = 8; + break; + default: + /* high bit depth */ + if (params->save_bit_depth < 12) + { + params->save_bit_depth = 10; + } + else if (params->save_bit_depth > 12) + { + params->save_bit_depth = 12; + } + break; + } + + table = gtk_table_new (1, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + label2 = gtk_label_new (_("Bit depth:")); + gtk_label_set_xalign (GTK_LABEL (label2), 0.0); + gtk_table_attach (GTK_TABLE (table), label2, 0, 1, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label2); + + combo = gimp_int_combo_box_new (_("8 bit/channel"), 8, + _("10 bit/channel (HDR)"), 10, + _("12 bit/channel (HDR)"), 12, + NULL); + gtk_table_attach (GTK_TABLE (table), combo, 1, 2, 0, 1, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (combo); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), params->save_bit_depth); +#endif + +#if LIBHEIF_HAVE_VERSION(1,4,0) + profile_button = gtk_check_button_new_with_mnemonic (_("Save color _profile")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (profile_button), + params->save_profile); + g_signal_connect (profile_button, "toggled", + G_CALLBACK (gimp_toggle_button_update), + ¶ms->save_profile); + gtk_box_pack_start (GTK_BOX (main_vbox), profile_button, FALSE, FALSE, 0); +#endif + + gtk_widget_show_all (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + params->quality = gtk_range_get_value (GTK_RANGE (quality_slider)); + params->lossless = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (lossless_button)); +#if LIBHEIF_HAVE_VERSION(1,8,0) + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), ¶ms->save_bit_depth); +#endif + } + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/file-html-table.c b/plug-ins/common/file-html-table.c new file mode 100644 index 0000000..0af9eb3 --- /dev/null +++ b/plug-ins/common/file-html-table.c @@ -0,0 +1,750 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GTM plug-in --- GIMP Table Magic + * Allows images to be exported as HTML tables with different colored cells. + * It doesn't have very much practical use other than being able to + * easily design a table by "painting" it in GIMP, or to make small HTML + * table images/icons. + * + * Copyright (C) 1997 Daniel Dunbar + * Email: ddunbar@diads.com + * WWW: http://millennium.diads.com/gimp/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Version 1.0: + * Once I first found out that it was possible to have pixel level control + * of HTML tables I instantly realized that it would be possible, however + * pointless, to save an image as a, albeit huge, HTML table. + * + * One night when I was feeling in an adventourously stupid programming mood + * I decided to write a program to do it. + * + * At first I just wrote a really ugly hack to do it, which I then planned + * on using once just to see how it worked, and then posting a URL and + * laughing about it on #gimp. As it turns out, tigert thought it actually + * had potential to be a useful plugin, so I started adding features and + * and making a nice UI. + * + * It's still not very useful, but I did manage to significantly improve my + * C programming skills in the process, so it was worth it. + * + * If you happen to find it useful I would appreciate any email about it. + * - Daniel Dunbar + * ddunbar@diads.com + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-gtm-save" +#define PLUG_IN_BINARY "file-html-table" +#define PLUG_IN_ROLE "gimp-file-html-table" + + +/* Typedefs */ + +typedef struct +{ + gchar captiontxt[256]; + gchar cellcontent[256]; + gchar clwidth[256]; + gchar clheight[256]; + gboolean fulldoc; + gboolean caption; + gint border; + gboolean spantags; + gboolean tdcomp; + gint cellpadding; + gint cellspacing; +} GTMValues; + + +/* Declare some local functions */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean save_image (GFile *file, + GeglBuffer *buffer, + GError **error); +static gboolean save_dialog (gint32 image_ID); + +static gboolean print (GOutputStream *output, + GError **error, + const gchar *format, + ...) G_GNUC_PRINTF (3, 0); +static gboolean color_comp (guchar *buffer, + guchar *buf2); +static void entry_changed_callback (GtkEntry *entry, + gchar *string); + + +/* Variables */ + +static GTMValues gtmvals = +{ + "Made with GIMP Table Magic", /* caption text */ + " ", /* cellcontent text */ + "", /* cell width text */ + "", /* cell height text */ + TRUE, /* fulldoc */ + FALSE, /* caption */ + 2, /* border */ + FALSE, /* spantags */ + FALSE, /* tdcomp */ + 4, /* cellpadding */ + 0 /* cellspacing */ +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" } + }; + + gimp_install_procedure (SAVE_PROC, + "GIMP Table Magic", + "Allows you to draw an HTML table in GIMP. See help for more info.", + "Daniel Dunbar", + "Daniel Dunbar", + "1998", + _("HTML table"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "text/html"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "html,htm", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + gimp_get_data (SAVE_PROC, >mvals); + + if (save_dialog (param[1].data.d_int32)) + { + GeglBuffer *buffer = gimp_drawable_get_buffer (param[2].data.d_int32); + + if (save_image (g_file_new_for_uri (param[3].data.d_string), + buffer, &error)) + { + gimp_set_data (SAVE_PROC, >mvals, sizeof (GTMValues)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + g_object_unref (buffer); + } + else + { + status = GIMP_PDB_CANCEL; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gboolean +save_image (GFile *file, + GeglBuffer *buffer, + GError **error) +{ + const Babl *format = babl_format ("R'G'B'A u8"); + GeglSampler *sampler; + GCancellable *cancellable; + GOutputStream *output; + gint row, col; + gint cols, rows; + gint x, y; + gint colcount, colspan, rowspan; + gint *palloc; + guchar *buf, *buf2; + gchar *width = NULL; + gchar *height = NULL; + + cols = gegl_buffer_get_width (buffer); + rows = gegl_buffer_get_height (buffer); + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_file_get_utf8_name (file)); + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (output) + { + GOutputStream *buffered; + + buffered = g_buffered_output_stream_new (output); + g_object_unref (output); + + output = buffered; + } + else + { + return FALSE; + } + + sampler = gegl_buffer_sampler_new (buffer, format, GEGL_SAMPLER_NEAREST); + + palloc = g_new (int, rows * cols); + + if (gtmvals.fulldoc) + { + if (! print (output, error, + "\n%s\n\n", + gimp_file_get_utf8_name (file)) || + ! print (output, error, "

%s

\n", + gimp_file_get_utf8_name (file))) + { + goto fail; + } + } + + if (! print (output, error, + "\n", + gtmvals.border, gtmvals.cellpadding, gtmvals.cellspacing)) + goto fail; + + if (gtmvals.caption) + { + if (! print (output, error, "\n", + gtmvals.captiontxt)) + goto fail; + } + + buf = g_newa (guchar, babl_format_get_bytes_per_pixel (format)); + buf2 = g_newa (guchar, babl_format_get_bytes_per_pixel (format)); + + if (strcmp (gtmvals.clwidth, "") != 0) + { + width = g_strdup_printf (" WIDTH=\"%s\"", gtmvals.clwidth); + } + + if (strcmp (gtmvals.clheight, "") != 0) + { + height = g_strdup_printf (" HEIGHT=\"%s\" ", gtmvals.clheight); + } + + if (! width) + width = g_strdup (" "); + + if (! height) + height = g_strdup (" "); + + /* Initialize array to hold ROWSPAN and COLSPAN cell allocation table */ + + for (row = 0; row < rows; row++) + for (col = 0; col < cols; col++) + palloc[cols * row + col] = 1; + + colspan = 0; + rowspan = 0; + + for (y = 0; y < rows; y++) + { + if (! print (output, error, " \n")) + goto fail; + + for (x = 0; x < cols; x++) + { + gegl_sampler_get (sampler, x, y, NULL, buf, GEGL_ABYSS_NONE); + + /* Determine ROWSPAN and COLSPAN */ + + if (gtmvals.spantags) + { + col = x; + row = y; + colcount = 0; + colspan = 0; + rowspan = 0; + + gegl_sampler_get (sampler, col, row, NULL, buf2, GEGL_ABYSS_NONE); + + while (color_comp (buf, buf2) && + palloc[cols * row + col] == 1 && + row < rows) + { + while (color_comp (buf, buf2) && + palloc[cols * row + col] == 1 && + col < cols) + { + colcount++; + col++; + + gegl_sampler_get (sampler, + col, row, NULL, buf2, GEGL_ABYSS_NONE); + } + + if (colcount != 0) + { + row++; + rowspan++; + } + + if (colcount < colspan || colspan == 0) + colspan = colcount; + + col = x; + colcount = 0; + + gegl_sampler_get (sampler, + col, row, NULL, buf2, GEGL_ABYSS_NONE); + } + + if (colspan > 1 || rowspan > 1) + { + for (row = 0; row < rowspan; row++) + for (col = 0; col < colspan; col++) + palloc[cols * (row + y) + (col + x)] = 0; + + palloc[cols * y + x] = 2; + } + } + + if (palloc[cols * y + x] == 1) + { + if (! print (output, error, + " ", + width, height, buf[0], buf[1], buf[2])) + goto fail; + } + + if (palloc[cols * y + x] == 2) + { + if (! print (output, error, + " \n", gtmvals.cellcontent)) + goto fail; + } + else + { + if (! print (output, error, + "\n %s\n \n", gtmvals.cellcontent)) + goto fail; + } + } + } + + if (! print (output, error, " \n")) + goto fail; + + gimp_progress_update ((double) y / (double) rows); + } + + if (gtmvals.fulldoc) + { + if (! print (output, error, "
%s
", + rowspan, colspan, width, height, + buf[0], buf[1], buf[2])) + goto fail; + } + + if (palloc[cols * y + x] != 0) + { + if (gtmvals.tdcomp) + { + if (! print (output, error, + "%s
\n")) + goto fail; + } + else + { + if (! print (output, error, "\n")) + goto fail; + } + + if (! g_output_stream_close (output, NULL, error)) + goto fail; + + g_object_unref (output); + g_object_unref (sampler); + g_free (width); + g_free (height); + g_free (palloc); + + gimp_progress_update (1.0); + + return TRUE; + + fail: + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + + g_object_unref (output); + g_object_unref (sampler); + g_free (width); + g_free (height); + g_free (palloc); + + return FALSE; +} + +static gint +save_dialog (gint32 image_ID) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *spinbutton; + GtkAdjustment *adj; + GtkWidget *entry; + GtkWidget *toggle; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_export_dialog_new (_("HTML table"), PLUG_IN_BINARY, SAVE_PROC); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + main_vbox, TRUE, TRUE, 0); + + if (gimp_image_width (image_ID) * gimp_image_height (image_ID) > 4096) + { + GtkWidget *eek; + GtkWidget *label; + GtkWidget *hbox; + + frame = gimp_frame_new (_("Warning")); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + eek = gtk_image_new_from_icon_name (GIMP_ICON_WILBER_EEK, + GTK_ICON_SIZE_DIALOG); + gtk_box_pack_start (GTK_BOX (hbox), eek, FALSE, FALSE, 0); + + label = gtk_label_new (_("You are about to create a huge\n" + "HTML file which will most likely\n" + "crash your browser.")); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + + gtk_widget_show_all (frame); + } + + /* HTML Page Options */ + frame = gimp_frame_new (_("HTML Page Options")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + toggle = gtk_check_button_new_with_mnemonic (_("_Generate full HTML document")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), gtmvals.fulldoc); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("If checked GTM will output a full HTML document " + "with , , etc. tags instead of just " + "the table html."), + NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + >mvals.fulldoc); + + gtk_widget_show (main_vbox); + gtk_widget_show (frame); + + /* HTML Table Creation Options */ + frame = gimp_frame_new (_("Table Creation Options")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + + table = gtk_table_new (4, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + + toggle = gtk_check_button_new_with_mnemonic (_("_Use cellspan")); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), gtmvals.spantags); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("If checked GTM will replace any rectangular " + "sections of identically colored blocks with one " + "large cell with ROWSPAN and COLSPAN values."), + NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + >mvals.spantags); + + toggle = gtk_check_button_new_with_mnemonic (_("Co_mpress TD tags")); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), gtmvals.tdcomp); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("Checking this tag will cause GTM to leave no " + "whitespace between the TD tags and the " + "cellcontent. This is only necessary for pixel " + "level positioning control."), + NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + >mvals.tdcomp); + + toggle = gtk_check_button_new_with_mnemonic (_("C_aption")); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), gtmvals.caption); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("Check if you would like to have the table " + "captioned."), + NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + >mvals.caption); + + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 200, -1); + gtk_entry_set_text (GTK_ENTRY (entry), gtmvals.captiontxt); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 2, 3, + GTK_FILL | GTK_EXPAND, 0, 0, 0); + gtk_widget_show (entry); + + gimp_help_set_help_data (entry, _("The text for the table caption."), NULL); + + g_signal_connect (entry, "changed", + G_CALLBACK (entry_changed_callback), + gtmvals.captiontxt); + + g_object_bind_property (toggle, "active", + entry, "sensitive", + G_BINDING_SYNC_CREATE); + + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 200, -1); + gtk_entry_set_text (GTK_ENTRY (entry), gtmvals.cellcontent); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("C_ell content:"), 0.0, 0.5, + entry, 1, FALSE); + gtk_widget_show (entry); + + gimp_help_set_help_data (entry, _("The text to go into each cell."), NULL); + + g_signal_connect (entry, "changed", + G_CALLBACK (entry_changed_callback), + gtmvals.cellcontent); + + gtk_widget_show (table); + gtk_widget_show (frame); + + /* HTML Table Options */ + frame = gimp_frame_new (_("Table Options")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + + table = gtk_table_new (5, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + + adj = (GtkAdjustment *) gtk_adjustment_new (gtmvals.border, + 0, 1000, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Border:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + gimp_help_set_help_data (spinbutton, + _("The number of pixels in the table border."), + NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + >mvals.border); + + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 60, -1); + gtk_entry_set_text (GTK_ENTRY (entry), gtmvals.clwidth); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Width:"), 0.0, 0.5, + entry, 1, TRUE); + + gimp_help_set_help_data (entry, + _("The width for each table cell. " + "Can be a number or a percent."), + NULL); + + g_signal_connect (entry, "changed", + G_CALLBACK (entry_changed_callback), + gtmvals.clwidth); + + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 60, -1); + gtk_entry_set_text (GTK_ENTRY (entry), gtmvals.clheight); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_Height:"), 0.0, 0.5, + entry, 1, TRUE); + + gimp_help_set_help_data (entry, + _("The height for each table cell. " + "Can be a number or a percent."), + NULL); + + g_signal_connect (entry, "changed", + G_CALLBACK (entry_changed_callback), + gtmvals.clheight); + + adj = (GtkAdjustment *) gtk_adjustment_new (gtmvals.cellpadding, + 0, 1000, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("Cell-_padding:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + gimp_help_set_help_data (spinbutton, + _("The amount of cell padding."), NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + >mvals.cellpadding); + + adj = (GtkAdjustment *) gtk_adjustment_new (gtmvals.cellspacing, + 0, 1000, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, + _("Cell-_spacing:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + gimp_help_set_help_data (spinbutton, + _("The amount of cell spacing."), NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + >mvals.cellspacing); + + gtk_widget_show (table); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static gboolean +print (GOutputStream *output, + GError **error, + const gchar *format, + ...) +{ + va_list args; + gboolean success; + + va_start (args, format); + success = g_output_stream_vprintf (output, NULL, NULL, + error, format, args); + va_end (args); + + return success; +} + +static gboolean +color_comp (guchar *buf, + guchar *buf2) +{ + return (buf[0] == buf2[0] && + buf[1] == buf2[1] && + buf[2] == buf2[2]); +} + +/* Export interface functions */ + +static void +entry_changed_callback (GtkEntry *entry, + gchar *string) +{ + strncpy (string, gtk_entry_get_text (entry), 255); +} diff --git a/plug-ins/common/file-jp2-load.c b/plug-ins/common/file-jp2-load.c new file mode 100644 index 0000000..9ab43b5 --- /dev/null +++ b/plug-ins/common/file-jp2-load.c @@ -0,0 +1,1344 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * file-jp2.c -- JPEG 2000 file format plug-in + * Copyright (C) 2009 Aurimas JuÅ¡ka + * Copyright (C) 2004 Florian Traverse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Portions of this plug-in code (color conversion, etc.) were imported + * from the OpenJPEG project covered under the following GNU GPL + * compatible license: + * + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium + * Copyright (c) 2002-2014, Professor Benoit Macq + * Copyright (c) 2001-2003, David Janssens + * Copyright (c) 2002-2003, Yannick Verschueren + * Copyright (c) 2003-2007, Francois-Olivier Devaux + * Copyright (c) 2003-2014, Antonin Descampe + * Copyright (c) 2005, Herve Drolon, FreeImage Team + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#include + +#include + +#ifdef G_OS_WIN32 +#include +#endif + +#ifndef _O_BINARY +#define _O_BINARY 0 +#endif + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#include + + +#define LOAD_JP2_PROC "file-jp2-load" +#define LOAD_J2K_PROC "file-j2k-load" +#define PLUG_IN_BINARY "file-jp2-load" +#define PLUG_IN_ROLE "gimp-file-jp2-load" + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 load_image (const gchar *filename, + OPJ_CODEC_FORMAT format, + OPJ_COLOR_SPACE color_space, + gboolean interactive, + gboolean *profile_loaded, + GError **error); + +static OPJ_COLOR_SPACE open_dialog (const gchar *filename, + OPJ_CODEC_FORMAT format, + gint num_components, + GError **error); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef jp2_load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load." }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + }; + + static const GimpParamDef j2k_load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load." }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + { GIMP_PDB_INT32, "colorspace", "Color space { UNKNOWN (0), GRAYSCALE (1), RGB (2), CMYK (3), YCbCr (4), xvYCC (5) }" }, + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + gimp_install_procedure (LOAD_JP2_PROC, + "Loads JPEG 2000 images.", + "The JPEG 2000 image loader.", + "Mukund Sivaraman", + "Mukund Sivaraman", + "2009", + N_("JPEG 2000 image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (jp2_load_args), + G_N_ELEMENTS (load_return_vals), + jp2_load_args, load_return_vals); + /* + * XXX: more complete magic number would be: + * "0,string,\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A" + * But the '\0' character makes problem in a 0-terminated string + * obviously, as well as some other space characters, it would seem. + * The below smaller version seems ok and not interfering with other + * formats. + */ + gimp_register_magic_load_handler (LOAD_JP2_PROC, + "jp2", + "", + "3,string,\x0CjP"); + gimp_register_file_handler_mime (LOAD_JP2_PROC, "image/jp2"); + + gimp_install_procedure (LOAD_J2K_PROC, + "Loads JPEG 2000 codestream.", + "Loads JPEG 2000 codestream. " + "If the color space is set to UNKNOWN (0), " + "we will try to guess, which is only possible " + "for few spaces (such as grayscale). Most " + "such calls will fail. You are rather " + "expected to know the color space of your data.", + "Jehan", + "Jehan", + "2009", + N_("JPEG 2000 codestream"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (j2k_load_args), + G_N_ELEMENTS (load_return_vals), + j2k_load_args, load_return_vals); + gimp_register_magic_load_handler (LOAD_J2K_PROC, + "j2k,j2c,jpc", + "", + "0,string,\xff\x4f\xff\x51\x00"); + gimp_register_file_handler_mime (LOAD_J2K_PROC, "image/x-jp2-codestream"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint image_ID; + gboolean profile_loaded = FALSE; + GError *error = NULL; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_JP2_PROC) == 0 || + strcmp (name, LOAD_J2K_PROC) == 0) + { + OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_UNKNOWN; + gboolean interactive; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + interactive = TRUE; + break; + + default: + if (strcmp (name, LOAD_J2K_PROC) == 0) + { + /* Order is not the same as OpenJPEG enum on purpose, + * since it's better to not rely on a given order or + * on enum values. + */ + switch (param[3].data.d_int32) + { + case 1: + color_space = OPJ_CLRSPC_GRAY; + break; + case 2: + color_space = OPJ_CLRSPC_SRGB; + break; + case 3: + color_space = OPJ_CLRSPC_CMYK; + break; + case 4: + color_space = OPJ_CLRSPC_SYCC; + break; + case 5: + color_space = OPJ_CLRSPC_EYCC; + break; + default: + /* Stays unknown. */ + break; + } + } + interactive = FALSE; + break; + } + + if (strcmp (name, LOAD_JP2_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, OPJ_CODEC_JP2, + color_space, interactive, &profile_loaded, + &error); + } + else /* strcmp (name, LOAD_J2K_PROC) == 0 */ + { + image_ID = load_image (param[1].data.d_string, OPJ_CODEC_J2K, + color_space, interactive, &profile_loaded, + &error); + } + + if (image_ID != -1) + { + GFile *file = g_file_new_for_path (param[1].data.d_string); + GimpMetadata *metadata; + + metadata = gimp_image_metadata_load_prepare (image_ID, "image/jp2", + file, NULL); + + if (metadata) + { + GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_ALL; + + if (profile_loaded) + flags &= ~GIMP_METADATA_LOAD_COLORSPACE; + + gimp_image_metadata_load_finish (image_ID, "image/jp2", + metadata, flags, + interactive); + + g_object_unref (metadata); + } + + g_object_unref (file); + + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else if (error) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + status = GIMP_PDB_CANCEL; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static void +sycc_to_rgb (int offset, + int upb, + int y, + int cb, + int cr, + int *out_r, + int *out_g, + int *out_b) +{ + int r, g, b; + + cb -= offset; + cr -= offset; + r = y + (int) (1.402 * (float) cr); + + if (r < 0) + r = 0; + else if (r > upb) + r = upb; + *out_r = r; + + g = y - (int) (0.344 * (float) cb + 0.714 * (float) cr); + if (g < 0) + g = 0; + else if (g > upb) + g = upb; + *out_g = g; + + b = y + (int) (1.772 * (float) cb); + if (b < 0) + b = 0; + else if (b > upb) + b = upb; + *out_b = b; +} + +static gboolean +sycc420_to_rgb (opj_image_t *img) +{ + int *d0, *d1, *d2, *r, *g, *b, *nr, *ng, *nb; + const int *y, *cb, *cr, *ny; + size_t maxw, maxh, max, offx, loopmaxw, offy, loopmaxh; + int offset, upb; + size_t i; + + upb = (int) img->comps[0].prec; + offset = 1 << (upb - 1); + upb = (1 << upb) - 1; + + maxw = (size_t) img->comps[0].w; + maxh = (size_t) img->comps[0].h; + max = maxw * maxh; + + y = img->comps[0].data; + cb = img->comps[1].data; + cr = img->comps[2].data; + + d0 = r = (int *) malloc (sizeof (int) * max); + d1 = g = (int *) malloc (sizeof (int) * max); + d2 = b = (int *) malloc (sizeof (int) * max); + + if (r == NULL || g == NULL || b == NULL) + { + g_warning ("malloc() failed in sycc420_to_rgb()"); + goto out; + } + + /* if img->x0 is odd, then first column shall use Cb/Cr = 0 */ + offx = img->x0 & 1U; + loopmaxw = maxw - offx; + /* if img->y0 is odd, then first line shall use Cb/Cr = 0 */ + offy = img->y0 & 1U; + loopmaxh = maxh - offy; + + if (offy > 0U) + { + size_t j; + + for (j = 0; j < maxw; ++j) + { + sycc_to_rgb (offset, upb, *y, 0, 0, r, g, b); + ++y; + ++r; + ++g; + ++b; + } + } + + for (i = 0U; i < (loopmaxh & ~(size_t) 1U); i += 2U) + { + size_t j; + + ny = y + maxw; + nr = r + maxw; + ng = g + maxw; + nb = b + maxw; + + if (offx > 0U) + { + sycc_to_rgb (offset, upb, *y, 0, 0, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + } + + for (j = 0; j < (loopmaxw & ~(size_t) 1U); j += 2U) + { + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + + sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + + ++cb; + ++cr; + } + + if (j < loopmaxw) + { + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); + ++ny; + ++nr; + ++ng; + ++nb; + + ++cb; + ++cr; + } + + y += maxw; + r += maxw; + g += maxw; + b += maxw; + } + + if (i < loopmaxh) + { + size_t j; + + for (j = 0U; j < (maxw & ~(size_t) 1U); j += 2U) + { + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + ++cb; + ++cr; + } + + if (j < maxw) + { + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + } + } + + free (img->comps[0].data); + img->comps[0].data = d0; + free (img->comps[1].data); + img->comps[1].data = d1; + free (img->comps[2].data); + img->comps[2].data = d2; + + img->comps[1].w = img->comps[2].w = img->comps[0].w; + img->comps[1].h = img->comps[2].h = img->comps[0].h; + img->comps[1].dx = img->comps[2].dx = img->comps[0].dx; + img->comps[1].dy = img->comps[2].dy = img->comps[0].dy; + img->color_space = OPJ_CLRSPC_SRGB; + + return TRUE; + + out: + free (r); + free (g); + free (b); + return FALSE; +} + +static gboolean +sycc422_to_rgb (opj_image_t *img) +{ + int *d0, *d1, *d2, *r, *g, *b; + const int *y, *cb, *cr; + size_t maxw, maxh, max, offx, loopmaxw; + int offset, upb; + size_t i; + + upb = (int) img->comps[0].prec; + offset = 1 <<(upb - 1); + upb = (1 << upb) - 1; + + maxw = (size_t) img->comps[0].w; + maxh = (size_t) img->comps[0].h; + max = maxw * maxh; + + y = img->comps[0].data; + cb = img->comps[1].data; + cr = img->comps[2].data; + + d0 = r = (int *) malloc (sizeof (int) * max); + d1 = g = (int *) malloc (sizeof (int) * max); + d2 = b = (int *) malloc (sizeof (int) * max); + + if (r == NULL || g == NULL || b == NULL) + { + g_warning ("malloc() failed in sycc422_to_rgb()"); + goto out; + } + + /* if img->x0 is odd, then first column shall use Cb/Cr = 0 */ + offx = img->x0 & 1U; + loopmaxw = maxw - offx; + + for (i = 0U; i < maxh; ++i) + { + size_t j; + + if (offx > 0U) + { + sycc_to_rgb (offset, upb, *y, 0, 0, r, g, b); + ++y; + ++r; + ++g; + ++b; + } + + for (j = 0U; j < (loopmaxw & ~(size_t) 1U); j += 2U) + { + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + ++cb; + ++cr; + } + + if (j < loopmaxw) + { + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++r; + ++g; + ++b; + ++cb; + ++cr; + } + } + + free (img->comps[0].data); + img->comps[0].data = d0; + free (img->comps[1].data); + img->comps[1].data = d1; + free (img->comps[2].data); + img->comps[2].data = d2; + + img->comps[1].w = img->comps[2].w = img->comps[0].w; + img->comps[1].h = img->comps[2].h = img->comps[0].h; + img->comps[1].dx = img->comps[2].dx = img->comps[0].dx; + img->comps[1].dy = img->comps[2].dy = img->comps[0].dy; + img->color_space = OPJ_CLRSPC_SRGB; + + return (TRUE); + + out: + free (r); + free (g); + free (b); + return (FALSE); +} + +static gboolean +sycc444_to_rgb (opj_image_t *img) +{ + int *d0, *d1, *d2, *r, *g, *b; + const int *y, *cb, *cr; + size_t maxw, maxh, max, i; + int offset, upb; + + upb = (int) img->comps[0].prec; + offset = 1 << (upb - 1); + upb = (1 << upb) - 1; + + maxw = (size_t) img->comps[0].w; + maxh = (size_t) img->comps[0].h; + max = maxw * maxh; + + y = img->comps[0].data; + cb = img->comps[1].data; + cr = img->comps[2].data; + + d0 = r = (int *) malloc(sizeof (int) * max); + d1 = g = (int *) malloc(sizeof (int) * max); + d2 = b = (int *) malloc(sizeof (int) * max); + + if (r == NULL || g == NULL || b == NULL) + { + g_warning ("malloc() failed in sycc444_to_rgb()"); + goto out; + } + + for (i = 0U; i < max; ++i) + { + sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); + ++y; + ++cb; + ++cr; + ++r; + ++g; + ++b; + } + + free (img->comps[0].data); + img->comps[0].data = d0; + free (img->comps[1].data); + img->comps[1].data = d1; + free (img->comps[2].data); + img->comps[2].data = d2; + + img->color_space = OPJ_CLRSPC_SRGB; + return TRUE; + + out: + free (r); + free (g); + free (b); + return FALSE; +} + +static gboolean +color_sycc_to_rgb (opj_image_t *img) +{ + if (img->numcomps < 3) + { + img->color_space = OPJ_CLRSPC_GRAY; + return TRUE; + } + else if ((img->comps[0].dx == 1) && + (img->comps[1].dx == 2) && + (img->comps[2].dx == 2) && + (img->comps[0].dy == 1) && + (img->comps[1].dy == 2) && + (img->comps[2].dy == 2)) + { + /* horizontal and vertical sub-sample */ + return sycc420_to_rgb (img); + } + else if ((img->comps[0].dx == 1) && + (img->comps[1].dx == 2) && + (img->comps[2].dx == 2) && + (img->comps[0].dy == 1) && + (img->comps[1].dy == 1) && + (img->comps[2].dy == 1)) + { + /* horizontal sub-sample only */ + return sycc422_to_rgb (img); + } + else if ((img->comps[0].dx == 1) && + (img->comps[1].dx == 1) && + (img->comps[2].dx == 1) && + (img->comps[0].dy == 1) && + (img->comps[1].dy == 1) && + (img->comps[2].dy == 1)) + { + /* no sub-sample */ + return sycc444_to_rgb (img); + } + else + { + g_warning ("Cannot convert in color_sycc_to_rgb()"); + return FALSE; + } +} + +static gboolean +color_cmyk_to_rgb (opj_image_t *image) +{ + float C, M, Y, K; + float sC, sM, sY, sK; + unsigned int w, h, max, i; + + w = image->comps[0].w; + h = image->comps[0].h; + + if ((image->numcomps < 4) || + (image->comps[0].dx != image->comps[1].dx) || + (image->comps[0].dx != image->comps[2].dx) || + (image->comps[0].dx != image->comps[3].dx) || + (image->comps[0].dy != image->comps[1].dy) || + (image->comps[0].dy != image->comps[2].dy) || + (image->comps[0].dy != image->comps[3].dy)) + { + g_warning ("Cannot convert in color_cmyk_to_rgb()"); + return FALSE; + } + + max = w * h; + + sC = 1.0f / (float) ((1 << image->comps[0].prec) - 1); + sM = 1.0f / (float) ((1 << image->comps[1].prec) - 1); + sY = 1.0f / (float) ((1 << image->comps[2].prec) - 1); + sK = 1.0f / (float) ((1 << image->comps[3].prec) - 1); + + for (i = 0; i < max; ++i) + { + /* CMYK values from 0 to 1 */ + C = (float) (image->comps[0].data[i]) * sC; + M = (float) (image->comps[1].data[i]) * sM; + Y = (float) (image->comps[2].data[i]) * sY; + K = (float) (image->comps[3].data[i]) * sK; + + /* Invert all CMYK values */ + C = 1.0f - C; + M = 1.0f - M; + Y = 1.0f - Y; + K = 1.0f - K; + + /* CMYK -> RGB : RGB results from 0 to 255 */ + image->comps[0].data[i] = (int) (255.0f * C * K); /* R */ + image->comps[1].data[i] = (int) (255.0f * M * K); /* G */ + image->comps[2].data[i] = (int) (255.0f * Y * K); /* B */ + } + + free (image->comps[3].data); + image->comps[3].data = NULL; + image->comps[0].prec = 8; + image->comps[1].prec = 8; + image->comps[2].prec = 8; + image->numcomps -= 1; + image->color_space = OPJ_CLRSPC_SRGB; + + for (i = 3; i < image->numcomps; ++i) + { + memcpy(&(image->comps[i]), &(image->comps[i + 1]), + sizeof (image->comps[i])); + } + + return TRUE; +} + +/* + * This code has been adopted from sjpx_openjpeg.c of ghostscript + */ +static gboolean +color_esycc_to_rgb (opj_image_t *image) +{ + int y, cb, cr, sign1, sign2, val; + unsigned int w, h, max, i; + int flip_value; + int max_value; + + flip_value = (1 << (image->comps[0].prec - 1)); + max_value = (1 << image->comps[0].prec) - 1; + + if ((image->numcomps < 3) || + (image->comps[0].dx != image->comps[1].dx) || + (image->comps[0].dx != image->comps[2].dx) || + (image->comps[0].dy != image->comps[1].dy) || + (image->comps[0].dy != image->comps[2].dy)) + { + g_warning ("Cannot convert in color_esycc_to_rgb()"); + return FALSE; + } + + w = image->comps[0].w; + h = image->comps[0].h; + + sign1 = (int)image->comps[1].sgnd; + sign2 = (int)image->comps[2].sgnd; + + max = w * h; + + for (i = 0; i < max; ++i) + { + + y = image->comps[0].data[i]; + cb = image->comps[1].data[i]; + cr = image->comps[2].data[i]; + + if (! sign1) + cb -= flip_value; + + if (! sign2) + cr -= flip_value; + + val = (int) ((float) y - (float) 0.0000368 * + (float) cb + (float) 1.40199 * (float) cr + (float) 0.5); + + if (val > max_value) + val = max_value; + else if (val < 0) + val = 0; + image->comps[0].data[i] = val; + + val = (int) ((float) 1.0003 * (float) y - (float) 0.344125 * + (float) cb - (float) 0.7141128 * (float) cr + (float) 0.5); + + if (val > max_value) + val = max_value; + else if(val < 0) + val = 0; + image->comps[1].data[i] = val; + + val = (int) ((float) 0.999823 * (float) y + (float) 1.77204 * + (float) cb - (float) 0.000008 * (float) cr + (float) 0.5); + + if (val > max_value) + val = max_value; + else if (val < 0) + val = 0; + image->comps[2].data[i] = val; + } + + image->color_space = OPJ_CLRSPC_SRGB; + return TRUE; +} + +/* + * get_valid_precision() converts given precision to standard precision + * of gimp i.e. 8, 16, 32 + * e.g 12-bit to 16-bit , 24-bit to 32-bit +*/ +static gint +get_valid_precision (gint precision_actual) +{ + if (precision_actual <= 8) + return 8; + else if (precision_actual <= 16) + return 16; + else + return 32; +} + +static GimpPrecision +get_image_precision (gint precision, + gboolean linear) +{ + switch (precision) + { + case 32: + if (linear) + return GIMP_PRECISION_U32_LINEAR; + return GIMP_PRECISION_U32_GAMMA; + case 16: + if (linear) + return GIMP_PRECISION_U16_LINEAR; + return GIMP_PRECISION_U16_GAMMA; + default: + if (linear) + return GIMP_PRECISION_U8_LINEAR; + return GIMP_PRECISION_U8_GAMMA; + } +} + +static OPJ_COLOR_SPACE +open_dialog (const gchar *filename, + OPJ_CODEC_FORMAT format, + gint num_components, + GError **error) +{ + const gchar *title; + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *table; + GtkWidget *combo = NULL; + OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_SRGB; + + if (format == OPJ_CODEC_J2K) + /* Not having color information is expected. */ + title = "Opening JPEG 2000 codestream"; + else + /* Unexpected, but let's be a bit flexible and ask. */ + title = "JPEG 2000 image with no color space"; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (title, PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, + (format == OPJ_CODEC_J2K) ? LOAD_J2K_PROC : LOAD_JP2_PROC, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + table = gtk_table_new (4, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_container_add (GTK_CONTAINER (main_vbox), table); + gtk_widget_show (table); + + if (num_components == 3) + { + /* Can be RGB, YUV and YCC. */ + combo = gimp_int_combo_box_new (_("sRGB"), OPJ_CLRSPC_SRGB, + _("YCbCr"), OPJ_CLRSPC_SYCC, + _("xvYCC"), OPJ_CLRSPC_EYCC, + NULL); + } + else if (num_components == 4) + { + /* Can be RGB, YUV and YCC with alpha or CMYK. */ + combo = gimp_int_combo_box_new (_("sRGB"), OPJ_CLRSPC_SRGB, + _("YCbCr"), OPJ_CLRSPC_SYCC, + _("xvYCC"), OPJ_CLRSPC_EYCC, + _("CMYK"), OPJ_CLRSPC_CMYK, + NULL); + } + else + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported JPEG 2000%s '%s' with %d components."), + (format == OPJ_CODEC_J2K) ? " codestream" : "", + gimp_filename_to_utf8 (filename), num_components); + color_space = OPJ_CLRSPC_UNKNOWN; + } + + if (combo) + { + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Color space:"), 0.0, 0.5, + combo, 2, FALSE); + gtk_widget_show (combo); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &color_space); + + /* By default, RGB is active. */ + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), OPJ_CLRSPC_SRGB); + + gtk_widget_show (dialog); + + if (gimp_dialog_run (GIMP_DIALOG (dialog)) != GTK_RESPONSE_OK) + { + /* Do not set an error here. The import was simply canceled. + * No error occurred. */ + color_space = OPJ_CLRSPC_UNKNOWN; + } + } + + gtk_widget_destroy (dialog); + + return color_space; +} + +static gint32 +load_image (const gchar *filename, + OPJ_CODEC_FORMAT format, + OPJ_COLOR_SPACE color_space, + gboolean interactive, + gboolean *profile_loaded, + GError **error) +{ + opj_stream_t *stream; + opj_codec_t *codec; + opj_dparameters_t parameters; + opj_image_t *image; + GimpColorProfile *profile; + gint32 image_ID; + gint32 layer_ID; + GimpImageType image_type; + GimpImageBaseType base_type; + gint width; + gint height; + gint num_components; + GeglBuffer *buffer; + gint i, j, k, it; + guchar *pixels; + const Babl *file_format; + gint bpp; + GimpPrecision image_precision; + gint precision_actual, precision_scaled; + gint temp; + gboolean linear; + unsigned char *c; + + stream = NULL; + codec = NULL; + image = NULL; + profile = NULL; + image_ID = -1; + linear = FALSE; + c = NULL; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + stream = opj_stream_create_default_file_stream (filename, OPJ_TRUE); + if (! stream) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not open '%s' for reading"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + codec = opj_create_decompress (format); + + opj_set_default_decoder_parameters (¶meters); + if (opj_setup_decoder (codec, ¶meters) != OPJ_TRUE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't set parameters on decoder for '%s'."), + gimp_filename_to_utf8 (filename)); + goto out; + } + + if (opj_read_header (stream, codec, &image) != OPJ_TRUE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't read JP2 header from '%s'."), + gimp_filename_to_utf8 (filename)); + goto out; + } + + if (opj_decode (codec, stream, image) != OPJ_TRUE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't decode JP2 image in '%s'."), + gimp_filename_to_utf8 (filename)); + goto out; + } + + if (opj_end_decompress (codec, stream) != OPJ_TRUE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't decompress JP2 image in '%s'."), + gimp_filename_to_utf8 (filename)); + goto out; + } + + if (image->icc_profile_buf) + { + if (image->icc_profile_len) + { + profile = gimp_color_profile_new_from_icc_profile (image->icc_profile_buf, + image->icc_profile_len, + error); + if (! profile) + goto out; + + *profile_loaded = TRUE; + + if (image->color_space == OPJ_CLRSPC_UNSPECIFIED || + image->color_space == OPJ_CLRSPC_UNKNOWN) + { + if (gimp_color_profile_is_rgb (profile)) + image->color_space = OPJ_CLRSPC_SRGB; + else if (gimp_color_profile_is_gray (profile)) + image->color_space = OPJ_CLRSPC_GRAY; + else if (gimp_color_profile_is_cmyk (profile)) + image->color_space = OPJ_CLRSPC_CMYK; + } + } + else + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't decode CIELAB JP2 image in '%s'."), + gimp_filename_to_utf8 (filename)); + goto out; + } + + free (image->icc_profile_buf); + image->icc_profile_buf = NULL; + image->icc_profile_len = 0; + } + + num_components = image->numcomps; + + if ((image->color_space == OPJ_CLRSPC_UNSPECIFIED || + image->color_space == OPJ_CLRSPC_UNKNOWN) && ! interactive) + image->color_space = color_space; + + if (image->color_space == OPJ_CLRSPC_UNSPECIFIED || + image->color_space == OPJ_CLRSPC_UNKNOWN) + { + /* Sometimes the color space is not set at this point, which + * sucks. This happens always with codestream images (.j2c or + * .j2k) which are meant to be embedded by other files. + * + * It might also happen with JP2 in case the header does not have + * color space and the ICC profile is absent (though this may mean + * that the JP2 is broken, but let's be flexible and allow manual + * fallback). + * Assuming RGB/RGBA space is bogus since this format can handle + * so much more. Therefore we instead pop-up a dialog asking one + * to specify the color space in interactive mode. + */ + if (num_components == 1 || num_components == 2) + { + /* Only possibility is gray. */ + image->color_space = OPJ_CLRSPC_GRAY; + } + else if (num_components == 5) + { + /* Can only be CMYK with Alpha. */ + image->color_space = OPJ_CLRSPC_CMYK; + } + else if (interactive) + { + image->color_space = open_dialog (filename, format, + num_components, error); + + if (image->color_space == OPJ_CLRSPC_UNKNOWN) + goto out; + } + else /* ! interactive */ + { + /* API call where color space was set to UNKNOWN. We don't + * want to guess or assume anything. It is much better to just + * fail. It is the responsibility of the developer to know its + * data when loading it in a script. + */ + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unknown color space in JP2 codestream '%s'."), + gimp_filename_to_utf8 (filename)); + goto out; + } + } + + if (image->color_space == OPJ_CLRSPC_SYCC) + { + if (! color_sycc_to_rgb (image)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't convert YCbCr JP2 image '%s' to RGB."), + gimp_filename_to_utf8 (filename)); + goto out; + } + } + else if ((image->color_space == OPJ_CLRSPC_CMYK)) + { + if (! color_cmyk_to_rgb (image)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't convert CMYK JP2 image in '%s' to RGB."), + gimp_filename_to_utf8 (filename)); + goto out; + } + } + else if (image->color_space == OPJ_CLRSPC_EYCC) + { + if (! color_esycc_to_rgb (image)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Couldn't convert xvYCC JP2 image in '%s' to RGB."), + gimp_filename_to_utf8 (filename)); + goto out; + } + } + + /* At this point, the image should be converted to Gray or RGB. */ + if (image->color_space == OPJ_CLRSPC_GRAY) + { + base_type = GIMP_GRAY; + image_type = GIMP_GRAY_IMAGE; + + if (num_components == 2) + image_type = GIMP_GRAYA_IMAGE; + } + else if (image->color_space == OPJ_CLRSPC_SRGB) + { + base_type = GIMP_RGB; + image_type = GIMP_RGB_IMAGE; + + if (num_components == 4) + image_type = GIMP_RGBA_IMAGE; + } + else + { + /* If not gray or RGB, this is an image we cannot handle. */ + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported color space in JP2 image '%s'."), + gimp_filename_to_utf8 (filename)); + goto out; + } + + width = image->comps[0].w; + height = image->comps[0].h; + + if (profile) + linear = gimp_color_profile_is_linear (profile); + + precision_actual = image->comps[0].prec; + + precision_scaled = get_valid_precision (precision_actual); + image_precision = get_image_precision (precision_scaled, linear); + + image_ID = gimp_image_new_with_precision (width, height, + base_type, image_precision); + + gimp_image_set_filename (image_ID, filename); + + if (profile) + gimp_image_set_color_profile (image_ID, profile); + + layer_ID = gimp_layer_new (image_ID, + _("Background"), + width, height, + image_type, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + file_format = gimp_drawable_get_format (layer_ID); + bpp = babl_format_get_bytes_per_pixel (file_format); + + buffer = gimp_drawable_get_buffer (layer_ID); + pixels = g_new0 (guchar, width * bpp); + + for (i = 0; i < height; i++) + { + for (j = 0; j < num_components; j++) + { + int shift = precision_scaled - precision_actual; + + for (k = 0; k < width; k++) + { + if (shift >= 0) + temp = image->comps[j].data[i * width + k] << shift; + else /* precision_actual > 32 */ + temp = image->comps[j].data[i * width + k] >> (- shift); + + c = (unsigned char *) &temp; + for (it = 0; it < (precision_scaled / 8); it++) + { + pixels[k * bpp + j * (precision_scaled / 8) + it] = c[it]; + } + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i, width, 1), 0, + file_format, pixels, GEGL_AUTO_ROWSTRIDE); + } + + g_free (pixels); + + g_object_unref (buffer); + gimp_progress_update (1.0); + + out: + if (profile) + g_object_unref (profile); + if (image) + opj_image_destroy (image); + if (codec) + opj_destroy_codec (codec); + if (stream) + opj_stream_destroy (stream); + + return image_ID; +} diff --git a/plug-ins/common/file-mng.c b/plug-ins/common/file-mng.c new file mode 100644 index 0000000..14c10fe --- /dev/null +++ b/plug-ins/common/file-mng.c @@ -0,0 +1,1791 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Multiple-image Network Graphics (MNG) plug-in + * + * Copyright (C) 2002 Mukund Sivaraman + * Portions are copyright of the authors of the file-gif-save, file-png-save + * and file-jpeg-save plug-ins' code. The exact ownership of these code + * fragments has not been determined. + * + * This work was sponsored by Xinit Systems Limited, UK. + * http://www.xinitsystems.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * -- + * + * For now, this MNG plug-in can only save images. It cannot load images. + * Save your working copy as .xcf and use this for "exporting" your images + * to MNG. Supports animation the same way as animated GIFs. Supports alpha + * transparency. Uses the libmng library (http://www.libmng.com/). + * The MIME content-type for MNG images is video/x-mng for now. Make sure + * your web-server is configured appropriately if you want to serve MNG + * images. + * + * Since libmng cannot write PNG, JNG and delta PNG chunks at this time + * (when this text was written), this plug-in uses libpng and jpeglib to + * create the data for the chunks. + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + + +/* libpng and jpeglib are currently used in this plug-in. */ + +#include +#include + + +/* Grrr. The grrr is because the following have to be defined + * by the application as well for some reason, although they + * were enabled when libmng was compiled. The authors of libmng + * must look into this. */ + +#if !defined(MNG_SUPPORT_FULL) +#define MNG_SUPPORT_FULL 1 +#endif + +#if !defined(MNG_SUPPORT_READ) +#define MNG_SUPPORT_READ 1 +#endif + +#if !defined(MNG_SUPPORT_WRITE) +#define MNG_SUPPORT_WRITE 1 +#endif + +#if !defined(MNG_SUPPORT_DISPLAY) +#define MNG_SUPPORT_DISPLAY 1 +#endif + +#if !defined(MNG_ACCESS_CHUNKS) +#define MNG_ACCESS_CHUNKS 1 +#endif + +#include + +#include "libgimp/gimp.h" +#include "libgimp/gimpui.h" + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-mng-save" +#define PLUG_IN_BINARY "file-mng" +#define PLUG_IN_ROLE "gimp-file-mng" +#define SCALE_WIDTH 125 + +enum +{ + CHUNKS_PNG_D, + CHUNKS_JNG_D, + CHUNKS_PNG, + CHUNKS_JNG +}; + +enum +{ + DISPOSE_COMBINE, + DISPOSE_REPLACE +}; + + +/* The contents of this struct remain static among multiple + * invocations of the plug-in. */ + +/* TODO: describe the members of the struct */ + +struct mng_data_t +{ + gint32 interlaced; + gint32 bkgd; + gint32 gama; + gint32 phys; + gint32 time; + gint32 default_chunks; + + gfloat quality; + gfloat smoothing; + + gint32 compression_level; + + gint32 loop; + gint32 default_delay; + gint32 default_dispose; +}; + +/* Values of the instance of the above struct when the plug-in is + * first invoked. */ + +static struct mng_data_t mng_data = +{ + FALSE, /* interlaced */ + FALSE, /* bkgd */ + FALSE, /* gama */ + TRUE, /* phys */ + TRUE, /* time */ + CHUNKS_PNG_D, /* default_chunks */ + + 0.75, /* quality */ + 0.0, /* smoothing */ + + 9, /* compression_level */ + + TRUE, /* loop */ + 100, /* default_delay */ + DISPOSE_COMBINE /* default_dispose */ +}; + + +/* These are not saved or restored. */ + +struct mng_globals_t +{ + gboolean has_trns; + png_bytep trans; + int num_trans; + gboolean has_plte; + png_colorp palette; + int num_palette; +}; + +static struct mng_globals_t mngg; + + +/* The output FILE pointer which is used by libmng; + * passed around as user data. */ +struct mnglib_userdata_t +{ + FILE *fp; +}; + + +/* + * Function prototypes + */ + +static mng_ptr MNG_DECL myalloc (mng_size_t size); +static void MNG_DECL myfree (mng_ptr ptr, + mng_size_t size); +static mng_bool MNG_DECL myopenstream (mng_handle handle); +static mng_bool MNG_DECL myclosestream (mng_handle handle); +static mng_bool MNG_DECL mywritedata (mng_handle handle, + mng_ptr buf, + mng_uint32 size, + mng_uint32 *written_size); + + +static gint32 parse_chunks_type_from_layer_name (const gchar *str); +static gint32 parse_disposal_type_from_layer_name (const gchar *str); +static gint32 parse_ms_tag_from_layer_name (const gchar *str); +static gint find_unused_ia_color (guchar *pixels, + gint numpixels, + gint *colors); +static gboolean ia_has_transparent_pixels (guchar *pixels, + gint numpixels); + +static gboolean respin_cmap (png_structp png_ptr, + png_infop png_info_ptr, + guchar *remap, + gint32 image_id, + GeglBuffer *buffer, + int *bit_depth); + +static gboolean mng_save_image (const gchar *filename, + gint32 image_id, + gint32 drawable_id, + gint32 original_image_id, + GError **error); +static gboolean mng_save_dialog (gint32 image_id); + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + + +/* + * Callbacks for libmng + */ + +static mng_ptr MNG_DECL +myalloc (mng_size_t size) +{ + gpointer ptr; + + ptr = g_try_malloc (size); + + if (ptr != NULL) + memset (ptr, 0, size); + + return ((mng_ptr) ptr); +} + +static void MNG_DECL +myfree (mng_ptr ptr, + mng_size_t size) +{ + g_free (ptr); +} + +static mng_bool MNG_DECL +myopenstream (mng_handle handle) +{ + return MNG_TRUE; +} + +static mng_bool MNG_DECL +myclosestream (mng_handle handle) +{ + return MNG_TRUE; +} + +static mng_bool MNG_DECL +mywritedata (mng_handle handle, + mng_ptr buf, + mng_uint32 size, + mng_uint32 *written_size) +{ + struct mnglib_userdata_t *userdata = + (struct mnglib_userdata_t *) mng_get_userdata (handle); + + + *written_size = (mng_uint32) fwrite ((void *) buf, 1, + (size_t) size, userdata->fp); + + return MNG_TRUE; +} + + +/* Parses which output chunk type to use for this layer + * from the layer name. */ + +static gint32 +parse_chunks_type_from_layer_name (const gchar *str) +{ + guint i; + + for (i = 0; (i + 5) <= strlen (str); i++) + { + if (g_ascii_strncasecmp (str + i, "(png)", 5) == 0) + return CHUNKS_PNG; + else if (g_ascii_strncasecmp (str + i, "(jng)", 5) == 0) + return CHUNKS_JNG; + } + + return mng_data.default_chunks; +} + + +/* Parses which disposal type to use for this layer + * from the layer name. */ + +static gint32 +parse_disposal_type_from_layer_name (const gchar *str) +{ + guint i; + + for (i = 0; (i + 9) <= strlen (str); i++) + { + if (g_ascii_strncasecmp (str + i, "(combine)", 9) == 0) + return DISPOSE_COMBINE; + else if (g_ascii_strncasecmp (str + i, "(replace)", 9) == 0) + return DISPOSE_REPLACE; + } + + return mng_data.default_dispose; +} + + +/* Parses the millisecond delay to use for this layer + * from the layer name. */ + +static gint32 +parse_ms_tag_from_layer_name (const gchar *str) +{ + guint offset = 0; + gint32 sum = 0; + guint length = strlen (str); + + while (TRUE) + { + while ((offset < length) && (str[offset] != '(')) + offset++; + + if (offset >= length) + return mng_data.default_delay; + + offset++; + + if (g_ascii_isdigit (str[offset])) + break; + } + + do + { + sum *= 10; + sum += str[offset] - '0'; + offset++; + } + while ((offset < length) && (g_ascii_isdigit (str[offset]))); + + if ((length - offset) <= 2) + return mng_data.default_delay; + + if ((g_ascii_toupper (str[offset]) != 'M') || + (g_ascii_toupper (str[offset + 1]) != 'S')) + return mng_data.default_delay; + + return sum; +} + + +/* Try to find a color in the palette which isn't actually + * used in the image, so that we can use it as the transparency + * index. Taken from png.c */ +static gint +find_unused_ia_color (guchar *pixels, + gint numpixels, + gint *colors) +{ + gint i; + gboolean ix_used[256]; + gboolean trans_used = FALSE; + + for (i = 0; i < *colors; i++) + { + ix_used[i] = FALSE; + } + + for (i = 0; i < numpixels; i++) + { + /* If alpha is over a threshold, the color index in the + * palette is taken. Otherwise, this pixel is transparent. */ + if (pixels[i * 2 + 1] > 127) + ix_used[pixels[i * 2]] = TRUE; + else + trans_used = TRUE; + } + + /* If there is no transparency, ignore alpha. */ + if (trans_used == FALSE) + return -1; + + for (i = 0; i < *colors; i++) + { + if (ix_used[i] == FALSE) + { + return i; + } + } + + /* Couldn't find an unused color index within the number of + bits per pixel we wanted. Will have to increment the number + of colors in the image and assign a transparent pixel there. */ + if ((*colors) < 256) + { + (*colors)++; + return ((*colors) - 1); + } + + return -1; +} + + +static gboolean +ia_has_transparent_pixels (guchar *pixels, + gint numpixels) +{ + while (numpixels --) + { + if (pixels [1] <= 127) + return TRUE; + pixels += 2; + } + return FALSE; +} + +static int +get_bit_depth_for_palette (int num_palette) +{ + if (num_palette <= 2) + return 1; + else if (num_palette <= 4) + return 2; + else if (num_palette <= 16) + return 4; + else + return 8; +} + +/* Spins the color map (palette) putting the transparent color at + * index 0 if there is space. If there isn't any space, warn the user + * and forget about transparency. Returns TRUE if the colormap has + * been changed and FALSE otherwise. + */ + +static gboolean +respin_cmap (png_structp pp, + png_infop info, + guchar *remap, + gint32 image_id, + GeglBuffer *buffer, + int *bit_depth) +{ + static guchar trans[] = { 0 }; + guchar *before; + guchar *pixels; + gint numpixels; + gint colors; + gint transparent; + gint cols, rows; + + before = gimp_image_get_colormap (image_id, &colors); + + /* Make sure there is something in the colormap */ + if (colors == 0) + { + before = g_newa (guchar, 3); + memset (before, 0, sizeof (guchar) * 3); + + colors = 1; + } + + cols = gegl_buffer_get_width (buffer); + rows = gegl_buffer_get_height (buffer); + numpixels = cols * rows; + + pixels = (guchar *) g_malloc (numpixels * 2); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, cols, rows), 1.0, + NULL, pixels, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (ia_has_transparent_pixels (pixels, numpixels)) + { + transparent = find_unused_ia_color (pixels, numpixels, &colors); + + if (transparent != -1) + { + static png_color palette[256] = { {0, 0, 0} }; + gint i; + + /* Set tRNS chunk values for writing later. */ + mngg.has_trns = TRUE; + mngg.trans = trans; + mngg.num_trans = 1; + + /* Transform all pixels with a value = transparent to + * 0 and vice versa to compensate for re-ordering in palette + * due to png_set_tRNS(). + */ + + remap[0] = transparent; + remap[transparent] = 0; + + /* Copy from index 0 to index transparent - 1 to index 1 to + * transparent of after, then from transparent+1 to colors-1 + * unchanged, and finally from index transparent to index 0. + */ + + for (i = 1; i < colors; i++) + { + palette[i].red = before[3 * remap[i]]; + palette[i].green = before[3 * remap[i] + 1]; + palette[i].blue = before[3 * remap[i] + 2]; + } + + /* Set PLTE chunk values for writing later. */ + mngg.has_plte = TRUE; + mngg.palette = palette; + mngg.num_palette = colors; + + *bit_depth = get_bit_depth_for_palette (colors); + + return TRUE; + } + else + { + g_message (_("Couldn't losslessly save transparency, " + "saving opacity instead.")); + } + } + + mngg.has_plte = TRUE; + mngg.palette = (png_colorp) before; + mngg.num_palette = colors; + *bit_depth = get_bit_depth_for_palette (colors); + + return FALSE; +} + +static mng_retcode +mng_putchunk_plte_wrapper (mng_handle handle, + gint numcolors, + const guchar *colormap) +{ + mng_palette8 palette; + + memset (palette, 0, sizeof palette); + if (0 < numcolors) + memcpy (palette, colormap, numcolors * sizeof palette[0]); + + return mng_putchunk_plte (handle, numcolors, palette); +} + +static mng_retcode +mng_putchunk_trns_wrapper (mng_handle handle, + gint n_alphas, + const guchar *buffer) +{ + const mng_bool mng_global = TRUE; + const mng_bool mng_empty = TRUE; + mng_uint8arr alphas; + + memset (alphas, 0, sizeof alphas); + if (buffer && 0 < n_alphas) + memcpy (alphas, buffer, n_alphas * sizeof alphas[0]); + + return mng_putchunk_trns (handle, + ! mng_empty, + ! mng_global, + MNG_COLORTYPE_INDEXED, + n_alphas, + alphas, + 0, 0, 0, 0, 0, alphas); +} + +static gboolean +mng_save_image (const gchar *filename, + gint32 image_id, + gint32 drawable_id, + gint32 original_image_id, + GError **error) +{ + gboolean ret = FALSE; + gint rows, cols; + volatile gint i; + time_t t; + struct tm *gmt; + + gint num_layers; + gint32 *layers; + + struct mnglib_userdata_t *userdata; + mng_handle handle; + guint32 mng_ticks_per_second; + guint32 mng_simplicity_profile; + + layers = gimp_image_get_layers (image_id, &num_layers); + + if (num_layers < 1) + return FALSE; + + if (num_layers > 1) + mng_ticks_per_second = 1000; + else + mng_ticks_per_second = 0; + + rows = gimp_image_height (image_id); + cols = gimp_image_width (image_id); + + mng_simplicity_profile = (MNG_SIMPLICITY_VALID | + MNG_SIMPLICITY_SIMPLEFEATURES | + MNG_SIMPLICITY_COMPLEXFEATURES); + + /* JNG and delta-PNG chunks exist */ + mng_simplicity_profile |= (MNG_SIMPLICITY_JNG | + MNG_SIMPLICITY_DELTAPNG); + + for (i = 0; i < num_layers; i++) + if (gimp_drawable_has_alpha (layers[i])) + { + /* internal transparency exists */ + mng_simplicity_profile |= MNG_SIMPLICITY_TRANSPARENCY; + + /* validity of following flags */ + mng_simplicity_profile |= 0x00000040; + + /* semi-transparency exists */ + mng_simplicity_profile |= 0x00000100; + + /* background transparency should happen */ + mng_simplicity_profile |= 0x00000080; + + break; + } + + userdata = g_new0 (struct mnglib_userdata_t, 1); + userdata->fp = g_fopen (filename, "wb"); + + if (NULL == userdata->fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + goto err; + } + + handle = mng_initialize ((mng_ptr) userdata, myalloc, myfree, NULL); + if (NULL == handle) + { + g_warning ("Unable to mng_initialize() in mng_save_image()"); + goto err2; + } + + if ((mng_setcb_openstream (handle, myopenstream) != MNG_NOERROR) || + (mng_setcb_closestream (handle, myclosestream) != MNG_NOERROR) || + (mng_setcb_writedata (handle, mywritedata) != MNG_NOERROR)) + { + g_warning ("Unable to setup callbacks in mng_save_image()"); + goto err3; + } + + if (mng_create (handle) != MNG_NOERROR) + { + g_warning ("Unable to mng_create() image in mng_save_image()"); + goto err3; + } + + if (mng_putchunk_mhdr (handle, cols, rows, mng_ticks_per_second, 1, + num_layers, mng_data.default_delay, + mng_simplicity_profile) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_mhdr() in mng_save_image()"); + goto err3; + } + + if ((num_layers > 1) && (mng_data.loop)) + { + gint32 ms = + parse_ms_tag_from_layer_name (gimp_item_get_name (layers[0])); + + if (mng_putchunk_term (handle, MNG_TERMACTION_REPEAT, + MNG_ITERACTION_LASTFRAME, + ms, 0x7fffffff) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_term() in mng_save_image()"); + goto err3; + } + } + else + { + gint32 ms = + parse_ms_tag_from_layer_name (gimp_item_get_name (layers[0])); + + if (mng_putchunk_term (handle, MNG_TERMACTION_LASTFRAME, + MNG_ITERACTION_LASTFRAME, + ms, 0x7fffffff) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_term() in mng_save_image()"); + goto err3; + } + } + + + /* For now, we hardwire a comment */ + + if (mng_putchunk_text (handle, + strlen (MNG_TEXT_TITLE), MNG_TEXT_TITLE, + 18, "Created using GIMP") != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_text() in mng_save_image()"); + goto err3; + } + +#if 0 + + /* how do we get this to work? */ + if (mng_data.bkgd) + { + GimpRGB bgcolor; + guchar red, green, blue; + + gimp_context_get_background (&bgcolor); + gimp_rgb_get_uchar (&bgcolor, &red, &green, &blue); + + if (mng_putchunk_back (handle, red, green, blue, + MNG_BACKGROUNDCOLOR_MANDATORY, + 0, MNG_BACKGROUNDIMAGE_NOTILE) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_back() in mng_save_image()"); + goto err3; + } + + if (mng_putchunk_bkgd (handle, MNG_FALSE, 2, 0, + gimp_rgb_luminance_uchar (&bgcolor), + red, green, blue) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_bkgd() in mng_save_image()"); + goto err3; + } + } + +#endif + + if (mng_data.gama) + { + if (mng_putchunk_gama (handle, MNG_FALSE, + (1.0 / 2.2 * 100000)) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_gama() in mng_save_image()"); + goto err3; + } + } + +#if 0 + + /* how do we get this to work? */ + if (mng_data.phys) + { + gimp_image_get_resolution(original_image_id, &xres, &yres); + + if (mng_putchunk_phyg (handle, MNG_FALSE, + (mng_uint32) (xres * 39.37), + (mng_uint32) (yres * 39.37), 1) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_phyg() in mng_save_image()"); + goto err3; + } + + if (mng_putchunk_phys (handle, MNG_FALSE, + (mng_uint32) (xres * 39.37), + (mng_uint32) (yres * 39.37), 1) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_phys() in mng_save_image()"); + goto err3; + } + } + +#endif + + if (mng_data.time) + { + t = time (NULL); + gmt = gmtime (&t); + + if (mng_putchunk_time (handle, gmt->tm_year + 1900, gmt->tm_mon + 1, + gmt->tm_mday, gmt->tm_hour, gmt->tm_min, + gmt->tm_sec) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_time() in mng_save_image()"); + goto err3; + } + } + + if (gimp_image_base_type (image_id) == GIMP_INDEXED) + { + guchar *palette; + gint numcolors; + + palette = gimp_image_get_colormap (image_id, &numcolors); + + if ((numcolors != 0) && + (mng_putchunk_plte_wrapper (handle, numcolors, + palette) != MNG_NOERROR)) + { + g_warning ("Unable to mng_putchunk_plte() in mng_save_image()"); + goto err3; + } + } + + for (i = (num_layers - 1); i >= 0; i--) + { + GimpImageType layer_drawable_type; + GeglBuffer *layer_buffer; + gint layer_offset_x, layer_offset_y; + gint layer_rows, layer_cols; + gchar *layer_name; + gint layer_chunks_type; + const Babl *layer_format; + volatile gint layer_bpp; + + guint8 __attribute__((unused))layer_mng_colortype; + guint8 __attribute__((unused))layer_mng_compression_type; + guint8 __attribute__((unused))layer_mng_interlace_type; + gboolean layer_has_unique_palette; + + gchar frame_mode; + int frame_delay; + gchar *temp_file_name; + png_structp pp; + png_infop info; + FILE *infile, *outfile; + int num_passes; + int tile_height; + guchar **layer_pixels, *layer_pixel; + int pass, j, k, begin, end, num; + guchar *fixed; + guchar layer_remap[256]; + int color_type; + int bit_depth; + + layer_name = gimp_item_get_name (layers[i]); + layer_chunks_type = parse_chunks_type_from_layer_name (layer_name); + layer_drawable_type = gimp_drawable_type (layers[i]); + + layer_buffer = gimp_drawable_get_buffer (layers[i]); + layer_cols = gegl_buffer_get_width (layer_buffer); + layer_rows = gegl_buffer_get_height (layer_buffer); + + gimp_drawable_offsets (layers[i], &layer_offset_x, &layer_offset_y); + layer_has_unique_palette = TRUE; + + for (j = 0; j < 256; j++) + layer_remap[j] = j; + + switch (layer_drawable_type) + { + case GIMP_RGB_IMAGE: + layer_format = babl_format ("R'G'B' u8"); + layer_mng_colortype = MNG_COLORTYPE_RGB; + break; + case GIMP_RGBA_IMAGE: + layer_format = babl_format ("R'G'B'A u8"); + layer_mng_colortype = MNG_COLORTYPE_RGBA; + break; + case GIMP_GRAY_IMAGE: + layer_format = babl_format ("Y' u8"); + layer_mng_colortype = MNG_COLORTYPE_GRAY; + break; + case GIMP_GRAYA_IMAGE: + layer_format = babl_format ("Y'A u8"); + layer_mng_colortype = MNG_COLORTYPE_GRAYA; + break; + case GIMP_INDEXED_IMAGE: + layer_format = gegl_buffer_get_format (layer_buffer); + layer_mng_colortype = MNG_COLORTYPE_INDEXED; + break; + case GIMP_INDEXEDA_IMAGE: + layer_format = gegl_buffer_get_format (layer_buffer); + layer_mng_colortype = MNG_COLORTYPE_INDEXED | MNG_COLORTYPE_GRAYA; + break; + default: + g_warning ("Unsupported GimpImageType in mng_save_image()"); + goto err3; + } + + layer_bpp = babl_format_get_bytes_per_pixel (layer_format); + + /* Delta PNG chunks are not yet supported */ + + /* if (i == (num_layers - 1)) */ + { + if (layer_chunks_type == CHUNKS_JNG_D) + layer_chunks_type = CHUNKS_JNG; + else if (layer_chunks_type == CHUNKS_PNG_D) + layer_chunks_type = CHUNKS_PNG; + } + + switch (layer_chunks_type) + { + case CHUNKS_PNG_D: + layer_mng_compression_type = MNG_COMPRESSION_DEFLATE; + if (mng_data.interlaced != 0) + layer_mng_interlace_type = MNG_INTERLACE_ADAM7; + else + layer_mng_interlace_type = MNG_INTERLACE_NONE; + break; + case CHUNKS_JNG_D: + layer_mng_compression_type = MNG_COMPRESSION_DEFLATE; + if (mng_data.interlaced != 0) + layer_mng_interlace_type = MNG_INTERLACE_ADAM7; + else + layer_mng_interlace_type = MNG_INTERLACE_NONE; + break; + case CHUNKS_PNG: + layer_mng_compression_type = MNG_COMPRESSION_DEFLATE; + if (mng_data.interlaced != 0) + layer_mng_interlace_type = MNG_INTERLACE_ADAM7; + else + layer_mng_interlace_type = MNG_INTERLACE_NONE; + break; + case CHUNKS_JNG: + layer_mng_compression_type = MNG_COMPRESSION_BASELINEJPEG; + if (mng_data.interlaced != 0) + layer_mng_interlace_type = MNG_INTERLACE_PROGRESSIVE; + else + layer_mng_interlace_type = MNG_INTERLACE_SEQUENTIAL; + break; + default: + g_warning ("Huh? Programmer stupidity error " + "with 'layer_chunks_type'"); + goto err3; + } + + if ((i == (num_layers - 1)) || + (parse_disposal_type_from_layer_name (layer_name) != DISPOSE_COMBINE)) + frame_mode = MNG_FRAMINGMODE_3; + else + frame_mode = MNG_FRAMINGMODE_1; + + frame_delay = parse_ms_tag_from_layer_name (layer_name); + + if (mng_putchunk_fram (handle, MNG_FALSE, frame_mode, 0, NULL, + MNG_CHANGEDELAY_DEFAULT, + MNG_CHANGETIMOUT_NO, + MNG_CHANGECLIPPING_DEFAULT, + MNG_CHANGESYNCID_NO, + frame_delay, 0, 0, + layer_offset_x, + layer_offset_x + layer_cols, + layer_offset_y, + layer_offset_y + layer_rows, + 0, NULL) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_fram() in mng_save_image()"); + goto err3; + } + + if ((layer_offset_x != 0) || (layer_offset_y != 0)) + { + if (mng_putchunk_defi (handle, 0, 0, 1, 1, layer_offset_x, + layer_offset_y, 1, layer_offset_x, + layer_offset_x + layer_cols, layer_offset_y, + layer_offset_y + layer_rows) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_defi() in mng_save_image()"); + goto err3; + } + } + + if ((temp_file_name = gimp_temp_name ("mng")) == NULL) + { + g_warning ("gimp_temp_name() failed in mng_save_image()"); + goto err3; + } + + if ((outfile = g_fopen (temp_file_name, "wb")) == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (temp_file_name), + g_strerror (errno)); + g_unlink (temp_file_name); + goto err3; + } + + pp = png_create_write_struct (PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (NULL == pp) + { + g_warning ("Unable to png_create_write_struct() in mng_save_image()"); + fclose (outfile); + g_unlink (temp_file_name); + goto err3; + } + + info = png_create_info_struct (pp); + if (NULL == info) + { + g_warning + ("Unable to png_create_info_struct() in mng_save_image()"); + png_destroy_write_struct (&pp, NULL); + fclose (outfile); + g_unlink (temp_file_name); + goto err3; + } + + if (setjmp (png_jmpbuf (pp)) != 0) + { + g_warning ("HRM saving PNG in mng_save_image()"); + png_destroy_write_struct (&pp, &info); + fclose (outfile); + g_unlink (temp_file_name); + goto err3; + } + + png_init_io (pp, outfile); + + bit_depth = 8; + + switch (layer_drawable_type) + { + case GIMP_RGB_IMAGE: + color_type = PNG_COLOR_TYPE_RGB; + break; + case GIMP_RGBA_IMAGE: + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + break; + case GIMP_GRAY_IMAGE: + color_type = PNG_COLOR_TYPE_GRAY; + break; + case GIMP_GRAYA_IMAGE: + color_type = PNG_COLOR_TYPE_GRAY_ALPHA; + break; + case GIMP_INDEXED_IMAGE: + color_type = PNG_COLOR_TYPE_PALETTE; + mngg.has_plte = TRUE; + mngg.palette = (png_colorp) + gimp_image_get_colormap (image_id, &mngg.num_palette); + bit_depth = get_bit_depth_for_palette (mngg.num_palette); + break; + case GIMP_INDEXEDA_IMAGE: + color_type = PNG_COLOR_TYPE_PALETTE; + layer_has_unique_palette = + respin_cmap (pp, info, layer_remap, + image_id, layer_buffer, + &bit_depth); + break; + default: + g_warning ("This can't be!\n"); + png_destroy_write_struct (&pp, &info); + fclose (outfile); + g_unlink (temp_file_name); + goto err3; + } + + /* Note: png_set_IHDR() must be called before any other + png_set_*() functions. */ + png_set_IHDR (pp, info, layer_cols, layer_rows, + bit_depth, + color_type, + mng_data.interlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + if (mngg.has_trns) + { + png_set_tRNS (pp, info, mngg.trans, mngg.num_trans, NULL); + } + + if (mngg.has_plte) + { + png_set_PLTE (pp, info, mngg.palette, mngg.num_palette); + } + + png_set_compression_level (pp, mng_data.compression_level); + + png_write_info (pp, info); + + if (mng_data.interlaced != 0) + num_passes = png_set_interlace_handling (pp); + else + num_passes = 1; + + if ((color_type == PNG_COLOR_TYPE_PALETTE) && + (bit_depth < 8)) + png_set_packing (pp); + + tile_height = gimp_tile_height (); + layer_pixel = g_new (guchar, tile_height * layer_cols * layer_bpp); + layer_pixels = g_new (guchar *, tile_height); + + for (j = 0; j < tile_height; j++) + layer_pixels[j] = layer_pixel + (layer_cols * layer_bpp * j); + + for (pass = 0; pass < num_passes; pass++) + { + for (begin = 0, end = tile_height; + begin < layer_rows; + begin += tile_height, end += tile_height) + { + if (end > layer_rows) + end = layer_rows; + + num = end - begin; + + gegl_buffer_get (layer_buffer, + GEGL_RECTANGLE (0, begin, layer_cols, num), 1.0, + layer_format, layer_pixel, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (png_get_valid (pp, info, PNG_INFO_tRNS)) + { + for (j = 0; j < num; j++) + { + fixed = layer_pixels[j]; + + for (k = 0; k < layer_cols; k++) + fixed[k] = (fixed[k * 2 + 1] > 127) ? + layer_remap[fixed[k * 2]] : 0; + } + } + else if (png_get_valid (pp, info, PNG_INFO_PLTE) + && (layer_bpp == 2)) + { + for (j = 0; j < num; j++) + { + fixed = layer_pixels[j]; + + for (k = 0; k < layer_cols; k++) + fixed[k] = fixed[k * 2]; + } + } + + png_write_rows (pp, layer_pixels, num); + } + } + + g_object_unref (layer_buffer); + + png_write_end (pp, info); + png_destroy_write_struct (&pp, &info); + + g_free (layer_pixels); + g_free (layer_pixel); + + fclose (outfile); + + infile = g_fopen (temp_file_name, "rb"); + if (NULL == infile) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (temp_file_name), + g_strerror (errno)); + g_unlink (temp_file_name); + goto err3; + } + + fseek (infile, 8L, SEEK_SET); + + while (!feof (infile)) + { + guchar chunksize_chars[4]; + gulong chunksize; + gchar chunkname[5]; + guchar *chunkbuffer; + glong chunkwidth; + glong chunkheight; + gchar chunkbitdepth; + gchar chunkcolortype; + gchar chunkcompression; + gchar chunkfilter; + gchar chunkinterlaced; + + + if (fread (chunksize_chars, 1, 4, infile) != 4) + break; + + if (fread (chunkname, 1, 4, infile) != 4) + break; + + chunkname[4] = 0; + + chunksize = ((chunksize_chars[0] << 24) | + (chunksize_chars[1] << 16) | + (chunksize_chars[2] << 8) | + (chunksize_chars[3])); + + chunkbuffer = NULL; + + if (chunksize > 0) + { + chunkbuffer = g_new (guchar, chunksize); + + if (fread (chunkbuffer, 1, chunksize, infile) != chunksize) + break; + } + + if (strncmp (chunkname, "IHDR", 4) == 0) + { + chunkwidth = ((chunkbuffer[0] << 24) | + (chunkbuffer[1] << 16) | + (chunkbuffer[2] << 8) | + (chunkbuffer[3])); + + chunkheight = ((chunkbuffer[4] << 24) | + (chunkbuffer[5] << 16) | + (chunkbuffer[6] << 8) | + (chunkbuffer[7])); + + chunkbitdepth = chunkbuffer[8]; + chunkcolortype = chunkbuffer[9]; + chunkcompression = chunkbuffer[10]; + chunkfilter = chunkbuffer[11]; + chunkinterlaced = chunkbuffer[12]; + + if (mng_putchunk_ihdr (handle, chunkwidth, chunkheight, + chunkbitdepth, chunkcolortype, + chunkcompression, chunkfilter, + chunkinterlaced) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_ihdr() " + "in mng_save_image()"); + fclose (infile); + goto err3; + } + } + else if (strncmp (chunkname, "IDAT", 4) == 0) + { + if (mng_putchunk_idat (handle, chunksize, + chunkbuffer) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_idat() " + "in mng_save_image()"); + fclose (infile); + goto err3; + } + } + else if (strncmp (chunkname, "IEND", 4) == 0) + { + if (mng_putchunk_iend (handle) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_iend() " + "in mng_save_image()"); + fclose (infile); + goto err3; + } + } + else if (strncmp (chunkname, "PLTE", 4) == 0) + { + /* If this frame's palette is the same as the global palette, + * write a 0-color palette chunk. + */ + if (mng_putchunk_plte_wrapper (handle, + (layer_has_unique_palette ? + (chunksize / 3) : 0), + chunkbuffer) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_plte() " + "in mng_save_image()"); + fclose (infile); + goto err3; + } + } + else if (strncmp (chunkname, "tRNS", 4) == 0) + { + if (mng_putchunk_trns_wrapper (handle, + chunksize, + chunkbuffer) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_trns() " + "in mng_save_image()"); + fclose (infile); + goto err3; + } + } + + if (chunksize > 0) + g_free (chunkbuffer); + + /* read 4 bytes after the chunk */ + + fread (chunkname, 1, 4, infile); + } + + fclose (infile); + g_unlink (temp_file_name); + } + + if (mng_putchunk_mend (handle) != MNG_NOERROR) + { + g_warning ("Unable to mng_putchunk_mend() in mng_save_image()"); + goto err3; + } + + if (mng_write (handle) != MNG_NOERROR) + { + g_warning ("Unable to mng_write() the image in mng_save_image()"); + goto err3; + } + + ret = TRUE; + + err3: + mng_cleanup (&handle); + err2: + fclose (userdata->fp); + err: + g_free (userdata); + + return ret; +} + + +/* The interactive dialog. */ + +static gboolean +mng_save_dialog (gint32 image_id) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *toggle; + GtkWidget *hbox; + GtkWidget *combo; + GtkWidget *label; + GtkWidget *scale; + GtkAdjustment *scale_adj; + GtkWidget *spinbutton; + GtkAdjustment *spinbutton_adj; + gint num_layers; + gboolean run; + + dialog = gimp_export_dialog_new (_("MNG"), PLUG_IN_BINARY, SAVE_PROC); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + main_vbox, TRUE, TRUE, 0); + + frame = gimp_frame_new (_("MNG Options")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + toggle = gtk_check_button_new_with_mnemonic (_("_Interlace")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &mng_data.interlaced); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + mng_data.interlaced); + + gtk_widget_show (toggle); + + toggle = gtk_check_button_new_with_mnemonic (_("Save _background color")); + gtk_widget_set_sensitive (toggle, FALSE); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &mng_data.bkgd); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.bkgd); + + gtk_widget_show (toggle); + + toggle = gtk_check_button_new_with_mnemonic (_("Save _gamma")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &mng_data.gama); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.gama); + + gtk_widget_show (toggle); + + toggle = gtk_check_button_new_with_label (_("Save resolution")); + gtk_widget_set_sensitive (toggle, FALSE); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &mng_data.phys); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.phys); + + gtk_widget_show (toggle); + + toggle = gtk_check_button_new_with_mnemonic (_("Save creation _time")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &mng_data.time); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.time); + + gtk_widget_show (toggle); + + table = gtk_table_new (2, 4, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + gimp_image_get_layers (image_id, &num_layers); + + if (num_layers == 1) + combo = gimp_int_combo_box_new (_("PNG"), CHUNKS_PNG_D, + _("JNG"), CHUNKS_JNG_D, + NULL); + else + combo = gimp_int_combo_box_new (_("PNG + delta PNG"), CHUNKS_PNG_D, + _("JNG + delta PNG"), CHUNKS_JNG_D, + _("All PNG"), CHUNKS_PNG, + _("All JNG"), CHUNKS_JNG, + NULL); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + mng_data.default_chunks); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &mng_data.default_chunks); + + gtk_widget_set_sensitive (combo, FALSE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Default chunks type:"), 0.0, 0.5, + combo, 1, FALSE); + + combo = gimp_int_combo_box_new (_("Combine"), DISPOSE_COMBINE, + _("Replace"), DISPOSE_REPLACE, + NULL); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + mng_data.default_dispose); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &mng_data.default_dispose); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Default _frame disposal:"), 0.0, 0.5, + combo, 1, FALSE); + + scale_adj = (GtkAdjustment *) + gtk_adjustment_new (mng_data.compression_level, + 0.0, 9.0, 1.0, 1.0, 0.0); + + scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, scale_adj); + gtk_widget_set_size_request (scale, SCALE_WIDTH, -1); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); + gtk_scale_set_digits (GTK_SCALE (scale), 0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_PNG compression level:"), 0.0, 0.9, + scale, 1, FALSE); + + g_signal_connect (scale_adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &mng_data.compression_level); + + gimp_help_set_help_data (scale, + _("Choose a high compression level " + "for small file size"), + NULL); + + scale_adj = (GtkAdjustment *) + gtk_adjustment_new (mng_data.quality, + 0.0, 1.0, 0.01, 0.01, 0.0); + + scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, scale_adj); + gtk_widget_set_size_request (scale, SCALE_WIDTH, -1); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); + gtk_scale_set_digits (GTK_SCALE (scale), 2); + gtk_widget_set_sensitive (scale, FALSE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("JPEG compression quality:"), 0.0, 0.9, + scale, 1, FALSE); + + g_signal_connect (scale_adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &mng_data.quality); + + scale_adj = (GtkAdjustment *) + gtk_adjustment_new (mng_data.smoothing, + 0.0, 1.0, 0.01, 0.01, 0.0); + + scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, scale_adj); + gtk_widget_set_size_request (scale, SCALE_WIDTH, -1); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); + gtk_scale_set_digits (GTK_SCALE (scale), 2); + gtk_widget_set_sensitive (scale, FALSE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, + _("JPEG smoothing factor:"), 0.0, 0.9, + scale, 1, FALSE); + + g_signal_connect (scale_adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &mng_data.smoothing); + + gtk_widget_show (vbox); + gtk_widget_show (frame); + + frame = gimp_frame_new (_("Animated MNG Options")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + toggle = gtk_check_button_new_with_mnemonic (_("_Loop")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &mng_data.loop); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + mng_data.loop); + + gtk_widget_show (toggle); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (_("Default frame delay:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + spinbutton_adj = (GtkAdjustment *) + gtk_adjustment_new (mng_data.default_delay, + 0, 65000, 10, 100, 0); + spinbutton = gimp_spin_button_new (spinbutton_adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + g_signal_connect (spinbutton_adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &mng_data.default_delay); + + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + + gtk_widget_show (spinbutton); + + label = gtk_label_new (_("milliseconds")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gtk_widget_show (hbox); + + gtk_widget_show (vbox); + gtk_widget_show (frame); + + if (num_layers <= 1) + { + gtk_widget_set_sensitive (frame, FALSE); + gimp_help_set_help_data (frame, + _("These options are only available when " + "the exported image has more than one " + "layer. The image you are exporting only has " + "one layer."), + NULL); + } + + gtk_widget_show (main_vbox); + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + + +/* GIMP calls these methods. */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" }, + + { GIMP_PDB_INT32, "interlace", "Use interlacing" }, + { GIMP_PDB_INT32, "compression", "PNG deflate compression level (0 - 9)" }, + { GIMP_PDB_FLOAT, "quality", "JPEG quality factor (0.00 - 1.00)" }, + { GIMP_PDB_FLOAT, "smoothing", "JPEG smoothing factor (0.00 - 1.00)" }, + { GIMP_PDB_INT32, "loop", "(ANIMATED MNG) Loop infinitely" }, + { GIMP_PDB_INT32, "default-delay", "(ANIMATED MNG) Default delay between frames in milliseconds" }, + { GIMP_PDB_INT32, "default-chunks", "(ANIMATED MNG) Default chunks type (0 = PNG + Delta PNG; 1 = JNG + Delta PNG; 2 = All PNG; 3 = All JNG)" }, + { GIMP_PDB_INT32, "default-dispose", "(ANIMATED MNG) Default dispose type (0 = combine; 1 = replace)" }, + { GIMP_PDB_INT32, "bkgd", "Write bKGD (background color) chunk" }, + { GIMP_PDB_INT32, "gama", "Write gAMA (gamma) chunk"}, + { GIMP_PDB_INT32, "phys", "Write pHYs (image resolution) chunk" }, + { GIMP_PDB_INT32, "time", "Write tIME (creation time) chunk" } + }; + + gimp_install_procedure (SAVE_PROC, + "Saves images in the MNG file format", + "This plug-in saves images in the Multiple-image " + "Network Graphics (MNG) format which can be used as " + "a replacement for animated GIFs, and more.", + "Mukund Sivaraman ", + "Mukund Sivaraman ", + "November 19, 2002", + N_("MNG animation"), + "RGB*,GRAY*,INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-mng"); + gimp_register_save_handler (SAVE_PROC, "mng", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_SUCCESS; + + if (strcmp (name, SAVE_PROC) == 0) + { + GimpRunMode run_mode = param[0].data.d_int32; + gint32 image_id = param[1].data.d_int32; + gint32 original_image_id = image_id; + gint32 drawable_id = param[2].data.d_int32; + GimpExportReturn export = GIMP_EXPORT_IGNORE; + + if (run_mode == GIMP_RUN_INTERACTIVE || + run_mode == GIMP_RUN_WITH_LAST_VALS) + { + gimp_procedural_db_get_data (SAVE_PROC, &mng_data); + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_id, &drawable_id, "MNG", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA | + GIMP_EXPORT_CAN_HANDLE_LAYERS); + } + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + } + else if (export == GIMP_EXPORT_IGNORE || export == GIMP_EXPORT_EXPORT) + { + if (run_mode == GIMP_RUN_INTERACTIVE) + { + if (mng_save_dialog (image_id) == 0) + values[0].data.d_status = GIMP_PDB_CANCEL; + } + else if (run_mode == GIMP_RUN_NONINTERACTIVE) + { + if (nparams != 17) + { + g_message ("Incorrect number of parameters " + "passed to file-mng-save()"); + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + } + else + { + mng_data.interlaced = param[5].data.d_int32; + mng_data.compression_level = param[6].data.d_int32; + mng_data.quality = param[7].data.d_float; + mng_data.smoothing = param[8].data.d_float; + mng_data.loop = param[9].data.d_int32; + mng_data.default_delay = param[10].data.d_int32; + mng_data.default_chunks = param[11].data.d_int32; + mng_data.default_dispose = param[12].data.d_int32; + mng_data.bkgd = param[13].data.d_int32; + mng_data.gama = param[14].data.d_int32; + mng_data.phys = param[15].data.d_int32; + mng_data.time = param[16].data.d_int32; + + if ((mng_data.compression_level < 0) + || (mng_data.compression_level > 9)) + { + g_warning ("Parameter 'compression_level' passed to " + "file-mng-save() must be in the range 0 - 9; " + "Clamping it to the default value of 6."); + mng_data.compression_level = 6; + } + + if ((mng_data.quality < ((float) 0)) + || (mng_data.quality > ((float) 1))) + { + g_warning ("Parameter 'quality' passed to " + "file-mng-save() must be in the range " + "0.00 - 1.00; Clamping it to the " + "default value of 0.75."); + mng_data.quality = 0.75; + } + + if ((mng_data.smoothing < ((float) 0)) + || (mng_data.smoothing > ((float) 1))) + { + g_warning ("Parameter 'smoothing' passed to " + "file-mng-save() must be in the " + "range 0.00 - 1.00; Clamping it to " + "the default value of 0.00."); + mng_data.smoothing = 0.0; + } + + if ((mng_data.default_chunks < 0) + || (mng_data.default_chunks > 3)) + { + g_warning ("Parameter 'default_chunks' passed to " + "file-mng-save() must be in the range 0 - 2."); + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + } + + if ((mng_data.default_dispose < 0) + || (mng_data.default_dispose > 1)) + { + g_warning ("Parameter 'default_dispose' passed to " + "file-mng-save() must be in the range 0 - 1."); + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + } + } + } + + if (values[0].data.d_status == GIMP_PDB_SUCCESS) + { + GError *error = NULL; + + if (mng_save_image (param[3].data.d_string, + image_id, drawable_id, + original_image_id, &error)) + { + gimp_set_data (SAVE_PROC, &mng_data, sizeof (mng_data)); + } + else + { + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_id); + } + + } + else + { + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + } +} diff --git a/plug-ins/common/file-pat.c b/plug-ins/common/file-pat.c new file mode 100644 index 0000000..3d1e43c --- /dev/null +++ b/plug-ins/common/file-pat.c @@ -0,0 +1,320 @@ +/* + * pat plug-in version 1.01 + * Loads/exports version 1 GIMP .pat files, by Tim Newsome + * + * GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-pat-save" +#define PLUG_IN_BINARY "file-pat" +#define PLUG_IN_ROLE "gimp-file-pat" + + +/* local function prototypes */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean save_dialog (void); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +/* private variables */ + +static gchar description[256] = "GIMP Pattern"; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "uri", "The URI of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-uri", "The URI of the file to export the image in" }, + { GIMP_PDB_STRING, "description", "Short description of the pattern" } + }; + + gimp_install_procedure (SAVE_PROC, + "Exports Gimp pattern file (.PAT)", + "New Gimp patterns can be created by exporting them " + "in the appropriate place with this plug-in.", + "Tim Newsome", + "Tim Newsome", + "1997", + N_("GIMP pattern"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_plugin_icon_register (SAVE_PROC, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) GIMP_ICON_PATTERN); + gimp_register_file_handler_mime (SAVE_PROC, "image/x-gimp-pat"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "pat", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, SAVE_PROC) == 0) + { + GFile *file; + GimpParasite *parasite; + gint32 orig_image_ID; + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + file = g_file_new_for_uri (param[3].data.d_string); + + orig_image_ID = image_ID; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "PAT", + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, description); + + parasite = gimp_image_get_parasite (orig_image_ID, + "gimp-pattern-name"); + if (parasite) + { + strncpy (description, + gimp_parasite_data (parasite), + MIN (sizeof (description), + gimp_parasite_data_size (parasite))); + description[sizeof (description) - 1] = '\0'; + + gimp_parasite_free (parasite); + } + else + { + gchar *name = g_path_get_basename (gimp_file_get_utf8_name (file)); + + if (g_str_has_suffix (name, ".pat")) + name[strlen (name) - 4] = '\0'; + + if (strlen (name)) + { + strncpy (description, name, sizeof (description)); + description[sizeof (description) - 1] = '\0'; + } + + g_free (name); + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + strncpy (description, param[5].data.d_string, + sizeof (description)); + description[sizeof (description) - 1] = '\0'; + } + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + GimpParam *save_retvals; + gint n_save_retvals; + + save_retvals = + gimp_run_procedure ("file-pat-save-internal", + &n_save_retvals, + GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE, + GIMP_PDB_IMAGE, image_ID, + GIMP_PDB_DRAWABLE, drawable_ID, + GIMP_PDB_STRING, param[3].data.d_string, + GIMP_PDB_STRING, param[4].data.d_string, + GIMP_PDB_STRING, description, + GIMP_PDB_END); + + + if (save_retvals[0].data.d_status == GIMP_PDB_SUCCESS) + { + gimp_set_data (SAVE_PROC, description, sizeof (description)); + } + else + { + g_set_error (&error, 0, 0, + "Running procedure 'file-pat-save-internal' " + "failed: %s", + gimp_get_pdb_error ()); + + status = GIMP_PDB_EXECUTION_ERROR; + } + + gimp_destroy_params (save_retvals, n_save_retvals); + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + if (strlen (description)) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new ("gimp-pattern-name", + GIMP_PARASITE_PERSISTENT, + strlen (description) + 1, + description); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + } + else + { + gimp_image_detach_parasite (orig_image_ID, "gimp-pattern-name"); + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *table; + GtkWidget *entry; + gboolean run; + + dialog = gimp_export_dialog_new (_("Pattern"), PLUG_IN_BINARY, SAVE_PROC); + + /* The main table */ + table = gtk_table_new (1, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + table, TRUE, TRUE, 0); + gtk_widget_show (table); + + entry = gtk_entry_new (); + gtk_entry_set_width_chars (GTK_ENTRY (entry), 20); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_entry_set_text (GTK_ENTRY (entry), description); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Description:"), 1.0, 0.5, + entry, 1, FALSE); + gtk_widget_show (entry); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + strncpy (description, gtk_entry_get_text (GTK_ENTRY (entry)), + sizeof (description)); + description[sizeof (description) - 1] = '\0'; + } + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/file-pcx.c b/plug-ins/common/file-pcx.c new file mode 100644 index 0000000..9d5d9d9 --- /dev/null +++ b/plug-ins/common/file-pcx.c @@ -0,0 +1,1070 @@ +/* + * pcx.c GIMP plug-in for loading & exporting PCX files + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This code is based in parts on code by Francisco Bustamante, but the + largest portion of the code has been rewritten and is now maintained + occasionally by Nick Lamb njl195@zepler.org.uk */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-pcx-load" +#define SAVE_PROC "file-pcx-save" +#define PLUG_IN_BINARY "file-pcx" +#define PLUG_IN_ROLE "gimp-file-pcx" + + +/* Declare local functions. */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); + +static void load_1 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes); +static void load_4 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes); +static void load_sub_8 (FILE *fp, + gint width, + gint height, + gint bpp, + gint plane, + guchar *buf, + guint16 bytes); +static void load_8 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes); +static void load_24 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes); +static void readline (FILE *fp, + guchar *buf, + gint bytes); + +static gint save_image (const gchar *filename, + gint32 image, + gint32 layer, + GError **error); +static void save_less_than_8 (FILE *fp, + gint width, + gint height, + const gint bpp, + const guchar *buf, + gboolean padding); +static void save_8 (FILE *fp, + gint width, + gint height, + const guchar *buf, + gboolean padding); +static void save_24 (FILE *fp, + gint width, + gint height, + const guchar *buf, + gboolean padding); +static void writeline (FILE *fp, + const guchar *buf, + gint bytes); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in Zsoft PCX file format", + "FIXME: write help for pcx_load", + "Francisco Bustamante & Nick Lamb", + "Nick Lamb ", + "January 1997", + N_("ZSoft PCX image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-pcx"); + gimp_register_magic_load_handler (LOAD_PROC, + "pcx,pcc", + "", + "0&,byte,10,2&,byte,1,3&,byte,>0,3,byte,<9"); + + gimp_install_procedure (SAVE_PROC, + "Exports files in ZSoft PCX file format", + "FIXME: write help for pcx_save", + "Francisco Bustamante & Nick Lamb", + "Nick Lamb ", + "January 1997", + N_("ZSoft PCX image"), + "INDEXED, RGB, GRAY", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-pcx"); + gimp_register_save_handler (SAVE_PROC, "pcx,pcc", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "PCX", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + break; + + case GIMP_RUN_WITH_LAST_VALS: + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (! save_image (param[3].data.d_string, image_ID, drawable_ID, + &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static struct +{ + guint8 manufacturer; + guint8 version; + guint8 compression; + guint8 bpp; + guint16 x1, y1; + guint16 x2, y2; + guint16 hdpi; + guint16 vdpi; + guint8 colormap[48]; + guint8 reserved; + guint8 planes; + guint16 bytesperline; + guint16 color; + guint8 filler[58]; +} pcx_header; + +static struct { + size_t size; + gpointer address; +} const pcx_header_buf_xlate[] = { + { 1, &pcx_header.manufacturer }, + { 1, &pcx_header.version }, + { 1, &pcx_header.compression }, + { 1, &pcx_header.bpp }, + { 2, &pcx_header.x1 }, + { 2, &pcx_header.y1 }, + { 2, &pcx_header.x2 }, + { 2, &pcx_header.y2 }, + { 2, &pcx_header.hdpi }, + { 2, &pcx_header.vdpi }, + { 48, &pcx_header.colormap }, + { 1, &pcx_header.reserved }, + { 1, &pcx_header.planes }, + { 2, &pcx_header.bytesperline }, + { 2, &pcx_header.color }, + { 58, &pcx_header.filler }, + { 0, NULL } +}; + +static void +pcx_header_from_buffer (guint8 *buf) +{ + gint i; + gint buf_offset = 0; + + for (i = 0; pcx_header_buf_xlate[i].size != 0; i++) + { + memmove (pcx_header_buf_xlate[i].address, buf + buf_offset, + pcx_header_buf_xlate[i].size); + buf_offset += pcx_header_buf_xlate[i].size; + } +} + +static void +pcx_header_to_buffer (guint8 *buf) +{ + gint i; + gint buf_offset = 0; + + for (i = 0; pcx_header_buf_xlate[i].size != 0; i++) + { + memmove (buf + buf_offset, pcx_header_buf_xlate[i].address, + pcx_header_buf_xlate[i].size); + buf_offset += pcx_header_buf_xlate[i].size; + } +} + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + FILE *fd; + GeglBuffer *buffer; + guint16 offset_x, offset_y, bytesperline; + gint32 width, height; + guint16 resolution_x, resolution_y; + gint32 image, layer; + guchar *dest, cmap[768]; + guint8 header_buf[128]; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + fd = g_fopen (filename, "rb"); + + if (! fd) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + if (fread (header_buf, 128, 1, fd) == 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not read header from '%s'"), + gimp_filename_to_utf8 (filename)); + fclose (fd); + return -1; + } + + pcx_header_from_buffer (header_buf); + + if (pcx_header.manufacturer != 10) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s' is not a PCX file"), + gimp_filename_to_utf8 (filename)); + fclose (fd); + return -1; + } + + offset_x = GUINT16_FROM_LE (pcx_header.x1); + offset_y = GUINT16_FROM_LE (pcx_header.y1); + width = GUINT16_FROM_LE (pcx_header.x2) - offset_x + 1; + height = GUINT16_FROM_LE (pcx_header.y2) - offset_y + 1; + bytesperline = GUINT16_FROM_LE (pcx_header.bytesperline); + resolution_x = GUINT16_FROM_LE (pcx_header.hdpi); + resolution_y = GUINT16_FROM_LE (pcx_header.vdpi); + + if ((width <= 0) || (width > GIMP_MAX_IMAGE_SIZE)) + { + g_message (_("Unsupported or invalid image width: %d"), width); + fclose (fd); + return -1; + } + if ((height <= 0) || (height > GIMP_MAX_IMAGE_SIZE)) + { + g_message (_("Unsupported or invalid image height: %d"), height); + fclose (fd); + return -1; + } + if (bytesperline < ((width * pcx_header.bpp + 7) / 8)) + { + g_message (_("Invalid number of bytes per line in PCX header")); + fclose (fd); + return -1; + } + if ((resolution_x < 1) || (resolution_x > GIMP_MAX_RESOLUTION) || + (resolution_y < 1) || (resolution_y > GIMP_MAX_RESOLUTION)) + { + g_message (_("Resolution out of bounds in XCX header, using 72x72")); + resolution_x = 72; + resolution_y = 72; + } + + /* Shield against potential buffer overflows in load_*() functions. */ + if (G_MAXSIZE / width / height < 3) + { + g_message (_("Image dimensions too large: width %d x height %d"), width, height); + fclose (fd); + return -1; + } + + if (pcx_header.planes == 3 && pcx_header.bpp == 8) + { + image= gimp_image_new (width, height, GIMP_RGB); + layer= gimp_layer_new (image, _("Background"), width, height, + GIMP_RGB_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image)); + } + else + { + image= gimp_image_new (width, height, GIMP_INDEXED); + layer= gimp_layer_new (image, _("Background"), width, height, + GIMP_INDEXED_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image)); + } + + gimp_image_set_filename (image, filename); + gimp_image_set_resolution (image, resolution_x, resolution_y); + + gimp_image_insert_layer (image, layer, -1, 0); + gimp_layer_set_offsets (layer, offset_x, offset_y); + + buffer = gimp_drawable_get_buffer (layer); + + if (pcx_header.planes == 1 && pcx_header.bpp == 1) + { + const guint8 *colormap = pcx_header.colormap; + dest = g_new (guchar, ((gsize) width) * height); + load_1 (fd, width, height, dest, bytesperline); + /* Monochrome does not mean necessarily B&W. Therefore we still + * want to check the header palette, even for just 2 colors. + * Hopefully the header palette will always be filled with + * meaningful colors and the creator software did not just assume + * B&W by being monochrome. + * Until now test samples showed that even when B&W the header + * palette was correctly filled with these 2 colors and we didn't + * find counter-examples. + * See bug 159947, comment 21 and 23. + */ + /* ... Actually, there *are* files out there with a zeroed 1-bit palette, + * which are supposed to be displayed as B&W (see issue #2997.) These + * files *might* be in the wrong (who knows...) but the fact is that + * other software, including older versions of GIMP, do display them + * "correctly", so let's follow suit: if the two palette colors are + * equal, use a B&W palette instead. + */ + if (! memcmp (colormap, colormap + 3, 3)) + { + static const guint8 bw_colormap[6] = { 0, 0, 0, + 255, 255, 255}; + colormap = bw_colormap; + } + gimp_image_set_colormap (image, colormap, 2); + } + else if (pcx_header.bpp == 1 && pcx_header.planes == 2) + { + dest = g_new (guchar, ((gsize) width) * height); + load_sub_8 (fd, width, height, 1, 2, dest, bytesperline); + gimp_image_set_colormap (image, pcx_header.colormap, 4); + } + else if (pcx_header.bpp == 2 && pcx_header.planes == 1) + { + dest = g_new (guchar, ((gsize) width) * height); + load_sub_8 (fd, width, height, 2, 1, dest, bytesperline); + gimp_image_set_colormap (image, pcx_header.colormap, 4); + } + else if (pcx_header.bpp == 1 && pcx_header.planes == 3) + { + dest = g_new (guchar, ((gsize) width) * height); + load_sub_8 (fd, width, height, 1, 3, dest, bytesperline); + gimp_image_set_colormap (image, pcx_header.colormap, 8); + } + else if (pcx_header.bpp == 1 && pcx_header.planes == 4) + { + dest = g_new (guchar, ((gsize) width) * height); + load_4 (fd, width, height, dest, bytesperline); + gimp_image_set_colormap (image, pcx_header.colormap, 16); + } + else if (pcx_header.bpp == 4 && pcx_header.planes == 1) + { + dest = g_new (guchar, ((gsize) width) * height); + load_sub_8 (fd, width, height, 4, 1, dest, bytesperline); + gimp_image_set_colormap (image, pcx_header.colormap, 16); + } + else if (pcx_header.bpp == 8 && pcx_header.planes == 1) + { + dest = g_new (guchar, ((gsize) width) * height); + load_8 (fd, width, height, dest, bytesperline); + fseek (fd, -768L, SEEK_END); + fread (cmap, 768, 1, fd); + gimp_image_set_colormap (image, cmap, 256); + } + else if (pcx_header.bpp == 8 && pcx_header.planes == 3) + { + dest = g_new (guchar, ((gsize) width) * height * 3); + load_24 (fd, width, height, dest, bytesperline); + } + else + { + g_message (_("Unusual PCX flavour, giving up")); + fclose (fd); + return -1; + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0, + NULL, dest, GEGL_AUTO_ROWSTRIDE); + + fclose (fd); + g_free (dest); + g_object_unref (buffer); + + gimp_progress_update (1.0); + + return image; +} + +static void +load_8 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes) +{ + gint row; + guchar *line = g_new (guchar, bytes); + + for (row = 0; row < height; buf += width, ++row) + { + readline (fp, line, bytes); + memcpy (buf, line, width); + gimp_progress_update ((double) row / (double) height); + } + + g_free (line); +} + +static void +load_24 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes) +{ + gint x, y, c; + guchar *line = g_new (guchar, bytes); + + for (y = 0; y < height; buf += width * 3, ++y) + { + for (c = 0; c < 3; ++c) + { + readline (fp, line, bytes); + for (x = 0; x < width; ++x) + { + buf[x * 3 + c] = line[x]; + } + } + gimp_progress_update ((double) y / (double) height); + } + + g_free (line); +} + +static void +load_1 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes) +{ + gint x, y; + guchar *line = g_new (guchar, bytes); + + for (y = 0; y < height; buf += width, ++y) + { + readline (fp, line, bytes); + for (x = 0; x < width; ++x) + { + if (line[x / 8] & (128 >> (x % 8))) + buf[x] = 1; + else + buf[x] = 0; + } + gimp_progress_update ((double) y / (double) height); + } + + g_free (line); +} + +static void +load_4 (FILE *fp, + gint width, + gint height, + guchar *buf, + guint16 bytes) +{ + gint x, y, c; + guchar *line = g_new (guchar, bytes); + + for (y = 0; y < height; buf += width, ++y) + { + for (x = 0; x < width; ++x) + buf[x] = 0; + for (c = 0; c < 4; ++c) + { + readline(fp, line, bytes); + for (x = 0; x < width; ++x) + { + if (line[x / 8] & (128 >> (x % 8))) + buf[x] += (1 << c); + } + } + gimp_progress_update ((double) y / (double) height); + } + + g_free (line); +} + +static void +load_sub_8 (FILE *fp, + gint width, + gint height, + gint bpp, + gint plane, + guchar *buf, + guint16 bytes) +{ + gint x, y, c, b; + guchar *line = g_new (guchar, bytes); + gint real_bpp = bpp - 1; + gint current_bit = 0; + + for (y = 0; y < height; buf += width, ++y) + { + for (x = 0; x < width; ++x) + buf[x] = 0; + for (c = 0; c < plane; ++c) + { + readline (fp, line, bytes); + for (x = 0; x < width; ++x) + { + for (b = 0; b < bpp; b++) + { + current_bit = bpp * x + b; + if (line[current_bit / 8] & (128 >> (current_bit % 8))) + buf[x] += (1 << (real_bpp - b + c)); + } + } + } + gimp_progress_update ((double) y / (double) height); + } + + g_free (line); +} + +static void +readline (FILE *fp, + guchar *buf, + gint bytes) +{ + static guchar count = 0, value = 0; + + if (pcx_header.compression) + { + while (bytes--) + { + if (count == 0) + { + value = fgetc (fp); + if (value < 0xc0) + { + count = 1; + } + else + { + count = value - 0xc0; + value = fgetc (fp); + } + } + count--; + *(buf++) = value; + } + } + else + { + fread (buf, bytes, 1, fp); + } +} + +static gint +save_image (const gchar *filename, + gint32 image, + gint32 layer, + GError **error) +{ + FILE *fp; + GeglBuffer *buffer; + const Babl *format; + GimpImageType drawable_type; + guchar *cmap= NULL; + guchar *pixels; + gint offset_x, offset_y; + guint width, height; + gdouble resolution_x, resolution_y; + gint colors, i; + guint8 header_buf[128]; + gboolean padding = FALSE; + + drawable_type = gimp_drawable_type (layer); + gimp_drawable_offsets (layer, &offset_x, &offset_y); + + buffer = gimp_drawable_get_buffer (layer); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_filename_to_utf8 (filename)); + + pcx_header.manufacturer = 0x0a; + pcx_header.version = 5; + pcx_header.compression = 1; + + switch (drawable_type) + { + case GIMP_INDEXED_IMAGE: + cmap = gimp_image_get_colormap (image, &colors); + if (colors > 16) + { + pcx_header.bpp = 8; + pcx_header.planes = 1; + pcx_header.bytesperline = width; + } + else if (colors > 2) + { + pcx_header.bpp = 4; + pcx_header.planes = 1; + pcx_header.bytesperline = (width + 1) / 2; + } + else + { + pcx_header.bpp = 1; + pcx_header.planes = 1; + pcx_header.bytesperline = (width + 7) / 8; + } + pcx_header.color = GUINT16_TO_LE (1); + format = NULL; + + /* Some references explain that 2bpp/1plane and 4bpp/1plane files + * would use the palette at EOF (not the one from the header) if + * we are in version 5 of PCX. Other sources affirm that even in + * version 5, EOF palette must be used only when there are more + * than 16 colors. We go with this second assumption. + * See bug 159947, comment 21 and 23. + */ + if (colors <= 16) + { + for (i = 0; i < (colors * 3); i++) + { + pcx_header.colormap[i] = cmap[i]; + } + } + + break; + + case GIMP_RGB_IMAGE: + pcx_header.bpp = 8; + pcx_header.planes = 3; + pcx_header.color = GUINT16_TO_LE (1); + pcx_header.bytesperline = width; + format = babl_format ("R'G'B' u8"); + break; + + case GIMP_GRAY_IMAGE: + pcx_header.bpp = 8; + pcx_header.planes = 1; + pcx_header.color = GUINT16_TO_LE (2); + pcx_header.bytesperline = width; + format = babl_format ("Y' u8"); + break; + + default: + g_message (_("Cannot export images with alpha channel.")); + return FALSE; + } + + /* Bytes per Line must be an even number, according to spec */ + if (pcx_header.bytesperline % 2 != 0) + { + pcx_header.bytesperline++; + padding = TRUE; + } + pcx_header.bytesperline = GUINT16_TO_LE (pcx_header.bytesperline); + + pixels = (guchar *) g_malloc (width * height * pcx_header.planes); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0, + format, pixels, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if ((offset_x < 0) || (offset_x > (1<<16))) + { + g_message (_("Invalid X offset: %d"), offset_x); + return FALSE; + } + + if ((offset_y < 0) || (offset_y > (1<<16))) + { + g_message (_("Invalid Y offset: %d"), offset_y); + return FALSE; + } + + if (offset_x + width - 1 > (1<<16)) + { + g_message (_("Right border out of bounds (must be < %d): %d"), (1<<16), + offset_x + width - 1); + return FALSE; + } + + if (offset_y + height - 1 > (1<<16)) + { + g_message (_("Bottom border out of bounds (must be < %d): %d"), (1<<16), + offset_y + height - 1); + return FALSE; + } + + if ((fp = g_fopen (filename, "wb")) == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + pcx_header.x1 = GUINT16_TO_LE ((guint16)offset_x); + pcx_header.y1 = GUINT16_TO_LE ((guint16)offset_y); + pcx_header.x2 = GUINT16_TO_LE ((guint16)(offset_x + width - 1)); + pcx_header.y2 = GUINT16_TO_LE ((guint16)(offset_y + height - 1)); + + gimp_image_get_resolution (image, &resolution_x, &resolution_y); + + pcx_header.hdpi = GUINT16_TO_LE (RINT (MAX (resolution_x, 1.0))); + pcx_header.vdpi = GUINT16_TO_LE (RINT (MAX (resolution_y, 1.0))); + pcx_header.reserved = 0; + + pcx_header_to_buffer (header_buf); + + fwrite (header_buf, 128, 1, fp); + + switch (drawable_type) + { + case GIMP_INDEXED_IMAGE: + if (colors > 16) + { + save_8 (fp, width, height, pixels, padding); + fputc (0x0c, fp); + fwrite (cmap, colors, 3, fp); + for (i = colors; i < 256; i++) + { + fputc (0, fp); + fputc (0, fp); + fputc (0, fp); + } + } + else /* Covers 1 and 4 bpp */ + { + save_less_than_8 (fp, width, height, pcx_header.bpp, pixels, padding); + } + break; + + case GIMP_RGB_IMAGE: + save_24 (fp, width, height, pixels, padding); + break; + + case GIMP_GRAY_IMAGE: + save_8 (fp, width, height, pixels, padding); + fputc (0x0c, fp); + for (i = 0; i < 256; i++) + { + fputc ((guchar) i, fp); + fputc ((guchar) i, fp); + fputc ((guchar) i, fp); + } + break; + + default: + return FALSE; + } + + g_object_unref (buffer); + g_free (pixels); + + if (fclose (fp) != 0) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Writing to file '%s' failed: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static void +save_less_than_8 (FILE *fp, + gint width, + gint height, + const gint bpp, + const guchar *buf, + gboolean padding) +{ + const gint bit_limit = (8 - bpp); + const gint buf_size = width * height; + const gint line_end = width - 1; + gint j = bit_limit; + gint count = 0; + guchar byte_to_write = 0x00; + guchar *line; + gint x; + + line = (guchar *) g_malloc (((width + 7) / 8) * bpp); + + for (x = 0; x < buf_size; x++) + { + byte_to_write |= (buf[x] << j); + j -= bpp; + + if (j < 0 || (x % width == line_end)) + { + line[count] = byte_to_write; + count++; + byte_to_write = 0x00; + j = bit_limit; + + if ((x % width == line_end)) + { + writeline (fp, line, count); + count = 0; + if (padding) + fputc ('\0', fp); + gimp_progress_update ((double) x / (double) buf_size); + } + } + } + g_free (line); +} + +static void +save_8 (FILE *fp, + gint width, + gint height, + const guchar *buf, + gboolean padding) +{ + int row; + + for (row = 0; row < height; ++row) + { + writeline (fp, buf, width); + buf += width; + if (padding) + fputc ('\0', fp); + gimp_progress_update ((double) row / (double) height); + } +} + +static void +save_24 (FILE *fp, + gint width, + gint height, + const guchar *buf, + gboolean padding) +{ + int x, y, c; + guchar *line; + + line = (guchar *) g_malloc (width); + + for (y = 0; y < height; ++y) + { + for (c = 0; c < 3; ++c) + { + for (x = 0; x < width; ++x) + { + line[x] = buf[(3*x) + c]; + } + writeline (fp, line, width); + if (padding) + fputc ('\0', fp); + } + buf += width * 3; + gimp_progress_update ((double) y / (double) height); + } + g_free (line); +} + +static void +writeline (FILE *fp, + const guchar *buf, + gint bytes) +{ + const guchar *finish = buf + bytes; + guchar value; + guchar count; + + while (buf < finish) + { + value = *(buf++); + count = 1; + + while (buf < finish && count < 63 && *buf == value) + { + count++; buf++; + } + + if (value < 0xc0 && count == 1) + { + fputc (value, fp); + } + else + { + fputc (0xc0 + count, fp); + fputc (value, fp); + } + } +} diff --git a/plug-ins/common/file-pdf-load.c b/plug-ins/common/file-pdf-load.c new file mode 100644 index 0000000..b2c12fa --- /dev/null +++ b/plug-ins/common/file-pdf-load.c @@ -0,0 +1,1951 @@ +/* GIMP - The GNU Image Manipulation Program + * + * file-pdf-load.c - PDF file loader + * + * Copyright (C) 2005 Nathan Summers + * + * Some code in render_page_to_surface() borrowed from + * poppler.git/glib/poppler-page.cc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#undef GTK_DISABLE_SINGLE_INCLUDES +#include +#define GTK_DISABLE_SINGLE_INCLUDES + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-pdf-load" +#define LOAD2_PROC "file-pdf-load2" +#define LOAD_THUMB_PROC "file-pdf-load-thumb" +#define PLUG_IN_BINARY "file-pdf-load" +#define PLUG_IN_ROLE "gimp-file-pdf-load" + +#define THUMBNAIL_SIZE 128 + +#define GIMP_PLUGIN_PDF_LOAD_ERROR gimp_plugin_pdf_load_error_quark () +static GQuark +gimp_plugin_pdf_load_error_quark (void) +{ + return g_quark_from_static_string ("gimp-plugin-pdf-load-error-quark"); +} + +/* Structs for the load dialog */ +typedef struct +{ + GimpPageSelectorTarget target; + gdouble resolution; + gboolean antialias; + gchar *PDF_password; +} PdfLoadVals; + +static PdfLoadVals loadvals = +{ + GIMP_PAGE_SELECTOR_TARGET_LAYERS, + 100.00, /* 100 dpi */ + TRUE, + NULL +}; + +typedef struct +{ + gint n_pages; + gint *pages; +} PdfSelectedPages; + +/* Declare local functions */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (PopplerDocument *doc, + const gchar *filename, + GimpRunMode run_mode, + GimpPageSelectorTarget target, + guint32 resolution, + gboolean antialias, + PdfSelectedPages *pages); + +static GimpPDBStatusType load_dialog (PopplerDocument *doc, + PdfSelectedPages *pages); + +static PopplerDocument * open_document (const gchar *filename, + const gchar *PDF_password, + GimpRunMode run_mode, + GError **error); + +static cairo_surface_t * get_thumb_surface (PopplerDocument *doc, + gint page, + gint preferred_size); + +static GdkPixbuf * get_thumb_pixbuf (PopplerDocument *doc, + gint page, + gint preferred_size); + +static gint32 layer_from_surface (gint32 image, + const gchar *layer_name, + gint position, + cairo_surface_t *surface, + gdouble progress_start, + gdouble progress_scale); + +/** + ** the following was formerly part of + ** gimpresolutionentry.h and gimpresolutionentry.c, + ** moved here because this is the only thing that uses + ** it, and it is undesirable to maintain all that api. + ** Most unused functions have been removed. + **/ +#define GIMP_TYPE_RESOLUTION_ENTRY (gimp_resolution_entry_get_type ()) +#define GIMP_RESOLUTION_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_RESOLUTION_ENTRY, GimpResolutionEntry)) +#define GIMP_RESOLUTION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_RESOLUTION_ENTRY, GimpResolutionEntryClass)) +#define GIMP_IS_RESOLUTION_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_RESOLUTION_ENTRY)) +#define GIMP_IS_RESOLUTION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_RESOLUTION_ENTRY)) +#define GIMP_RESOLUTION_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_RESOLUTION_ENTRY, GimpResolutionEntryClass)) + + +typedef struct _GimpResolutionEntry GimpResolutionEntry; +typedef struct _GimpResolutionEntryClass GimpResolutionEntryClass; + +typedef struct _GimpResolutionEntryField GimpResolutionEntryField; + +struct _GimpResolutionEntryField +{ + GimpResolutionEntry *gre; + GimpResolutionEntryField *corresponding; + + gboolean size; + + GtkWidget *label; + + guint changed_signal; + + GtkAdjustment *adjustment; + GtkWidget *spinbutton; + + gdouble phy_size; + + gdouble value; + gdouble min_value; + gdouble max_value; + + gint stop_recursion; +}; + + +struct _GimpResolutionEntry +{ + GtkTable parent_instance; + + GimpUnit size_unit; + GimpUnit unit; + + GtkWidget *unitmenu; + GtkWidget *chainbutton; + + GimpResolutionEntryField width; + GimpResolutionEntryField height; + GimpResolutionEntryField x; + GimpResolutionEntryField y; + +}; + +struct _GimpResolutionEntryClass +{ + GtkTableClass parent_class; + + void (* value_changed) (GimpResolutionEntry *gse); + void (* refval_changed) (GimpResolutionEntry *gse); + void (* unit_changed) (GimpResolutionEntry *gse); +}; + + +GType gimp_resolution_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_resolution_entry_new (const gchar *width_label, + gdouble width, + const gchar *height_label, + gdouble height, + GimpUnit size_unit, + const gchar *res_label, + gdouble initial_res, + GimpUnit initial_unit); + +GtkWidget * gimp_resolution_entry_attach_label (GimpResolutionEntry *gre, + const gchar *text, + gint row, + gint column, + gfloat alignment); + +gdouble gimp_resolution_entry_get_x_in_dpi (GimpResolutionEntry *gre); + +gdouble gimp_resolution_entry_get_y_in_dpi (GimpResolutionEntry *gre); + + +/* signal callback convenience functions */ +void gimp_resolution_entry_update_x_in_dpi (GimpResolutionEntry *gre, + gpointer data); + +void gimp_resolution_entry_update_y_in_dpi (GimpResolutionEntry *gre, + gpointer data); + + +enum +{ + WIDTH_CHANGED, + HEIGHT_CHANGED, + X_CHANGED, + Y_CHANGED, + UNIT_CHANGED, + LAST_SIGNAL +}; + +static void gimp_resolution_entry_class_init (GimpResolutionEntryClass *class); +static void gimp_resolution_entry_init (GimpResolutionEntry *gre); + +static void gimp_resolution_entry_update_value (GimpResolutionEntryField *gref, + gdouble value); +static void gimp_resolution_entry_value_callback (GtkWidget *widget, + gpointer data); +static void gimp_resolution_entry_update_unit (GimpResolutionEntry *gre, + GimpUnit unit); +static void gimp_resolution_entry_unit_callback (GtkWidget *widget, + GimpResolutionEntry *gre); + +static void gimp_resolution_entry_field_init (GimpResolutionEntry *gre, + GimpResolutionEntryField *gref, + GimpResolutionEntryField *corresponding, + guint changed_signal, + gdouble initial_val, + GimpUnit initial_unit, + gboolean size, + gint spinbutton_width); + +static void gimp_resolution_entry_format_label (GimpResolutionEntry *gre, + GtkWidget *label, + gdouble size); + +/** + ** end of gimpresolutionentry stuff + ** the actual code can be found at the end of this file + **/ +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load2_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + { GIMP_PDB_STRING, "pdf-password", "The password to decrypt the encrypted PDF file" }, + { GIMP_PDB_INT32, "n-pages", "Number of pages to load (0 for all)" }, + { GIMP_PDB_INT32ARRAY,"pages", "The pages to load in the expected order" }, + /* XXX: Nice to have API at some point, but needs work + { GIMP_PDB_INT32, "resolution", "Resolution to rasterize to (dpi)" }, + { GIMP_PDB_INT32, "antialiasing", "Use anti-aliasing" }, */ + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef thumb_args[] = + { + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_INT32, "thumb-size", "Preferred thumbnail size" } + }; + + static const GimpParamDef thumb_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Thumbnail image" }, + { GIMP_PDB_INT32, "image-width", "Width of full-sized image" }, + { GIMP_PDB_INT32, "image-height", "Height of full-sized image" }, + { GIMP_PDB_INT32, "image-type", "Image type" }, + { GIMP_PDB_INT32, "num-layers", "Number of pages" } + }; + + gimp_install_procedure (LOAD_PROC, + "Load file in PDF format", + "Loads files in Adobe's Portable Document Format. " + "PDF is designed to be easily processed by a variety " + "of different platforms, and is a distant cousin of " + "PostScript.\n" + "If the PDF document has multiple pages, only the first " + "page will be loaded. Call file_pdf_load2() to load " + "several pages as layers.", + "Nathan Summers", + "Nathan Summers", + "2005", + N_("Portable Document Format"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_install_procedure (LOAD2_PROC, + "Load file in PDF format", + "Loads files in Adobe's Portable Document Format. " + "PDF is designed to be easily processed by a variety " + "of different platforms, and is a distant cousin of " + "PostScript.\n" + "This procedure adds extra parameters to " + "file-pdf-load to open encrypted PDF and to allow " + "multiple page loading.", + "Nathan Summers, Lionel N.", + "Nathan Summers, Lionel N.", + "2005, 2017", + N_("Portable Document Format"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load2_args), + G_N_ELEMENTS (load_return_vals), + load2_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD2_PROC, "application/pdf"); + gimp_register_magic_load_handler (LOAD2_PROC, + "pdf", + "", + "0, string,%PDF-"); + + gimp_install_procedure (LOAD_THUMB_PROC, + "Loads a preview from a PDF file.", + "Loads a small preview of the first page of the PDF " + "format file. Uses the embedded thumbnail if " + "present.", + "Nathan Summers", + "Nathan Summers", + "2005", + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (thumb_args), + G_N_ELEMENTS (thumb_return_vals), + thumb_args, thumb_return_vals); + + gimp_register_thumbnail_loader (LOAD2_PROC, LOAD_THUMB_PROC); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[7]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID = -1; + PopplerDocument *doc = NULL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0 || strcmp (name, LOAD2_PROC) == 0) + { + PdfSelectedPages pages = { 0, NULL }; + GimpRunMode run_mode; + + run_mode = param[0].data.d_int32; + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve last settings */ + if (strcmp (name, LOAD_PROC) == 0) + { + gimp_get_data (LOAD_PROC, &loadvals); + } + else if (strcmp (name, LOAD2_PROC) == 0) + { + gimp_get_data (LOAD2_PROC, &loadvals); + } + gimp_ui_init (PLUG_IN_BINARY, FALSE); + doc = open_document (param[1].data.d_string, + loadvals.PDF_password, + run_mode, &error); + + if (!doc) + { + status = GIMP_PDB_EXECUTION_ERROR; + break; + } + + status = load_dialog (doc, &pages); + if (status == GIMP_PDB_SUCCESS) + { + if (strcmp (name, LOAD_PROC) == 0) + { + gimp_set_data (LOAD_PROC, &loadvals, sizeof(loadvals)); + } + else if (strcmp (name, LOAD2_PROC) == 0) + { + gimp_set_data (LOAD2_PROC, &loadvals, sizeof(loadvals)); + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* FIXME: implement last vals mode */ + status = GIMP_PDB_EXECUTION_ERROR; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (strcmp (name, LOAD_PROC) == 0) + { + doc = open_document (param[1].data.d_string, + NULL, run_mode, &error); + } + else if (strcmp (name, LOAD2_PROC) == 0) + { + doc = open_document (param[1].data.d_string, + param[3].data.d_string, + run_mode, &error); + } + + if (doc) + { + PopplerPage *test_page = poppler_document_get_page (doc, 0); + + if (test_page) + { + if (strcmp (name, LOAD2_PROC) != 0) + { + /* For retrocompatibility, file-pdf-load always + * just loads the first page. */ + pages.n_pages = 1; + pages.pages = g_new (gint, 1); + pages.pages[0] = 0; + + g_object_unref (test_page); + } + else + { + gint i; + gint doc_n_pages; + + doc_n_pages = poppler_document_get_n_pages (doc); + /* The number of imported pages may be bigger than + * the number of pages from the original document. + * Indeed it is possible to duplicate some pages + * by setting the same number several times in the + * "pages" argument. + * Not ceiling this value is *not* an error. + */ + pages.n_pages = param[4].data.d_int32; + if (pages.n_pages <= 0) + { + pages.n_pages = doc_n_pages; + pages.pages = g_new (gint, pages.n_pages); + for (i = 0; i < pages.n_pages; i++) + pages.pages[i] = i; + } + else + { + pages.pages = g_new (gint, pages.n_pages); + for (i = 0; i < pages.n_pages; i++) + { + if (param[5].data.d_int32array[i] >= doc_n_pages) + { + status = GIMP_PDB_EXECUTION_ERROR; + g_set_error (&error, GIMP_PLUGIN_PDF_LOAD_ERROR, 0, + /* TRANSLATORS: first argument is file name, + * second is out-of-range page number, third is + * number of pages. Specify order as in English if needed. + */ + ngettext ("PDF document '%1$s' has %3$d page. Page %2$d is out of range.", + "PDF document '%1$s' has %3$d pages. Page %2$d is out of range.", + doc_n_pages), + param[1].data.d_string, + param[5].data.d_int32array[i], + doc_n_pages); + break; + } + else + { + pages.pages[i] = param[5].data.d_int32array[i]; + } + } + } + g_object_unref (test_page); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + g_object_unref (doc); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + image_ID = load_image (doc, param[1].data.d_string, + run_mode, + loadvals.target, + loadvals.resolution, + loadvals.antialias, + &pages); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (doc) + g_object_unref (doc); + + g_free (pages.pages); + } + else if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + if (nparams < 2) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + gdouble width = 0; + gdouble height = 0; + gdouble scale; + gint32 image = -1; + gint num_pages = 0; + cairo_surface_t *surface = NULL; + + /* Possibly retrieve last settings */ + if (strcmp (name, LOAD_PROC) == 0) + { + gimp_get_data (LOAD_PROC, &loadvals); + } + else if (strcmp (name, LOAD2_PROC) == 0) + { + gimp_get_data (LOAD2_PROC, &loadvals); + } + + doc = open_document (param[0].data.d_string, + loadvals.PDF_password, + GIMP_RUN_NONINTERACTIVE, + &error); + + if (doc) + { + PopplerPage *page = poppler_document_get_page (doc, 0); + + if (page) + { + poppler_page_get_size (page, &width, &height); + + g_object_unref (page); + } + + num_pages = poppler_document_get_n_pages (doc); + + surface = get_thumb_surface (doc, 0, param[1].data.d_int32); + + g_object_unref (doc); + } + + if (surface) + { + image = gimp_image_new (cairo_image_surface_get_width (surface), + cairo_image_surface_get_height (surface), + GIMP_RGB); + + gimp_image_undo_disable (image); + + + layer_from_surface (image, "thumbnail", 0, surface, 0.0, 1.0); + cairo_surface_destroy (surface); + + gimp_image_undo_enable (image); + gimp_image_clean_all (image); + } + + scale = loadvals.resolution / gimp_unit_get_factor (GIMP_UNIT_POINT); + + width *= scale; + height *= scale; + + if (image != -1) + { + *nreturn_vals = 6; + + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image; + values[2].type = GIMP_PDB_INT32; + values[2].data.d_int32 = width; + values[3].type = GIMP_PDB_INT32; + values[3].data.d_int32 = height; + values[4].type = GIMP_PDB_INT32; + values[4].data.d_int32 = GIMP_RGB_IMAGE; + values[5].type = GIMP_PDB_INT32; + values[5].data.d_int32 = num_pages; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; + + gegl_exit (); +} + +static PopplerDocument* +open_document (const gchar *filename, + const gchar *PDF_password, + GimpRunMode run_mode, + GError **load_error) +{ + PopplerDocument *doc; + GFile *file; + GError *error = NULL; + + file = g_file_new_for_path (filename); + doc = poppler_document_new_from_gfile (file, PDF_password, NULL, &error); + + if (run_mode == GIMP_RUN_INTERACTIVE) + { + GtkWidget *label; + + label = gtk_label_new (_("PDF is password protected, please input the password:")); + while (error && + error->domain == POPPLER_ERROR && + error->code == POPPLER_ERROR_ENCRYPTED) + { + GtkWidget *vbox; + GtkWidget *dialog; + GtkWidget *entry; + gint run; + + dialog = gimp_dialog_new (_("Encrypted PDF"), PLUG_IN_ROLE, + NULL, 0, + NULL, NULL, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + NULL); + gimp_window_set_transient (GTK_WINDOW (dialog)); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, TRUE, TRUE, 0); + entry = gtk_entry_new (); + gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_container_add (GTK_CONTAINER (vbox), label); + gtk_container_add (GTK_CONTAINER (vbox), entry); + + gtk_widget_show_all (dialog); + + run = gimp_dialog_run (GIMP_DIALOG (dialog)); + if (run == GTK_RESPONSE_OK) + { + g_clear_error (&error); + doc = poppler_document_new_from_gfile (file, + gtk_entry_get_text (GTK_ENTRY (entry)), + NULL, &error); + } + label = gtk_label_new (_("Wrong password! Please input the right one:")); + gtk_widget_destroy (dialog); + if (run == GTK_RESPONSE_CANCEL || run == GTK_RESPONSE_DELETE_EVENT) + { + break; + } + } + gtk_widget_destroy (label); + } + g_object_unref (file); + + /* We can't g_mapped_file_unref(mapped_file) as apparently doc has + * references to data in there. No big deal, this is just a + * short-lived plug-in. + */ + if (! doc) + { + g_set_error (load_error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not load '%s': %s"), + gimp_filename_to_utf8 (filename), + error->message); + g_error_free (error); + return NULL; + } + + return doc; +} + +/* FIXME: Remove this someday when we depend fully on GTK+ >= 3 */ + +#if (!GTK_CHECK_VERSION (3, 0, 0)) + +static cairo_format_t +gdk_cairo_format_for_content (cairo_content_t content) +{ + switch (content) + { + case CAIRO_CONTENT_COLOR: + return CAIRO_FORMAT_RGB24; + case CAIRO_CONTENT_ALPHA: + return CAIRO_FORMAT_A8; + case CAIRO_CONTENT_COLOR_ALPHA: + default: + return CAIRO_FORMAT_ARGB32; + } +} + +static cairo_surface_t * +gdk_cairo_surface_coerce_to_image (cairo_surface_t *surface, + cairo_content_t content, + int src_x, + int src_y, + int width, + int height) +{ + cairo_surface_t *copy; + cairo_t *cr; + + copy = cairo_image_surface_create (gdk_cairo_format_for_content (content), + width, + height); + + cr = cairo_create (copy); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface (cr, surface, -src_x, -src_y); + cairo_paint (cr); + cairo_destroy (cr); + + return copy; +} + +static void +convert_alpha (guchar *dest_data, + int dest_stride, + guchar *src_data, + int src_stride, + int src_x, + int src_y, + int width, + int height) +{ + int x, y; + + src_data += src_stride * src_y + src_x * 4; + + for (y = 0; y < height; y++) { + guint32 *src = (guint32 *) src_data; + + for (x = 0; x < width; x++) { + guint alpha = src[x] >> 24; + + if (alpha == 0) + { + dest_data[x * 4 + 0] = 0; + dest_data[x * 4 + 1] = 0; + dest_data[x * 4 + 2] = 0; + } + else + { + dest_data[x * 4 + 0] = (((src[x] & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; + dest_data[x * 4 + 1] = (((src[x] & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; + dest_data[x * 4 + 2] = (((src[x] & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; + } + dest_data[x * 4 + 3] = alpha; + } + + src_data += src_stride; + dest_data += dest_stride; + } +} + +static void +convert_no_alpha (guchar *dest_data, + int dest_stride, + guchar *src_data, + int src_stride, + int src_x, + int src_y, + int width, + int height) +{ + int x, y; + + src_data += src_stride * src_y + src_x * 4; + + for (y = 0; y < height; y++) { + guint32 *src = (guint32 *) src_data; + + for (x = 0; x < width; x++) { + dest_data[x * 3 + 0] = src[x] >> 16; + dest_data[x * 3 + 1] = src[x] >> 8; + dest_data[x * 3 + 2] = src[x]; + } + + src_data += src_stride; + dest_data += dest_stride; + } +} + +/** + * gdk_pixbuf_get_from_surface: + * @surface: surface to copy from + * @src_x: Source X coordinate within @surface + * @src_y: Source Y coordinate within @surface + * @width: Width in pixels of region to get + * @height: Height in pixels of region to get + * + * Transfers image data from a #cairo_surface_t and converts it to an RGB(A) + * representation inside a #GdkPixbuf. This allows you to efficiently read + * individual pixels from cairo surfaces. For #GdkWindows, use + * gdk_pixbuf_get_from_window() instead. + * + * This function will create an RGB pixbuf with 8 bits per channel. + * The pixbuf will contain an alpha channel if the @surface contains one. + * + * Return value: (transfer full): A newly-created pixbuf with a reference + * count of 1, or %NULL on error + */ +static GdkPixbuf * +gdk_pixbuf_get_from_surface (cairo_surface_t *surface, + gint src_x, + gint src_y, + gint width, + gint height) +{ + cairo_content_t content; + GdkPixbuf *dest; + + /* General sanity checks */ + g_return_val_if_fail (surface != NULL, NULL); + g_return_val_if_fail (width > 0 && height > 0, NULL); + + content = cairo_surface_get_content (surface) | CAIRO_CONTENT_COLOR; + dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + !!(content & CAIRO_CONTENT_ALPHA), + 8, + width, height); + + surface = gdk_cairo_surface_coerce_to_image (surface, content, + src_x, src_y, + width, height); + cairo_surface_flush (surface); + if (cairo_surface_status (surface) || dest == NULL) + { + cairo_surface_destroy (surface); + return NULL; + } + + if (gdk_pixbuf_get_has_alpha (dest)) + convert_alpha (gdk_pixbuf_get_pixels (dest), + gdk_pixbuf_get_rowstride (dest), + cairo_image_surface_get_data (surface), + cairo_image_surface_get_stride (surface), + 0, 0, + width, height); + else + convert_no_alpha (gdk_pixbuf_get_pixels (dest), + gdk_pixbuf_get_rowstride (dest), + cairo_image_surface_get_data (surface), + cairo_image_surface_get_stride (surface), + 0, 0, + width, height); + + cairo_surface_destroy (surface); + return dest; +} + +#endif + +static gint32 +layer_from_surface (gint32 image, + const gchar *layer_name, + gint position, + cairo_surface_t *surface, + gdouble progress_start, + gdouble progress_scale) +{ + gint32 layer; + + layer = gimp_layer_new_from_surface (image, layer_name, surface, + progress_start, + progress_start + progress_scale); + gimp_image_insert_layer (image, layer, -1, position); + + return layer; +} + +static cairo_surface_t * +render_page_to_surface (PopplerPage *page, + int width, + int height, + double scale, + gboolean antialias) +{ + cairo_surface_t *surface; + cairo_t *cr; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + cr = cairo_create (surface); + + cairo_save (cr); + cairo_translate (cr, 0.0, 0.0); + + if (scale != 1.0) + cairo_scale (cr, scale, scale); + + if (! antialias) + { + cairo_font_options_t *options = cairo_font_options_create (); + + cairo_get_font_options (cr, options); + cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_NONE); + cairo_set_font_options (cr, options); + cairo_font_options_destroy (options); + + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + } + + poppler_page_render (page, cr); + cairo_restore (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER); + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_paint (cr); + + cairo_destroy (cr); + + return surface; +} + +#if 0 + +/* This is currently unused, but we'll have it here in case the military + wants it. */ + +static GdkPixbuf * +render_page_to_pixbuf (PopplerPage *page, + int width, + int height, + double scale) +{ + GdkPixbuf *pixbuf; + cairo_surface_t *surface; + + surface = render_page_to_surface (page, width, height, scale); + pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, + cairo_image_surface_get_width (surface), + cairo_image_surface_get_height (surface)); + cairo_surface_destroy (surface); + + return pixbuf; +} + +#endif + +static gint32 +load_image (PopplerDocument *doc, + const gchar *filename, + GimpRunMode run_mode, + GimpPageSelectorTarget target, + guint32 resolution, + gboolean antialias, + PdfSelectedPages *pages) +{ + gint32 image_ID = 0; + gint32 *images = NULL; + gint i; + gdouble scale; + gdouble doc_progress = 0; + + if (target == GIMP_PAGE_SELECTOR_TARGET_IMAGES) + images = g_new0 (gint32, pages->n_pages); + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + scale = resolution / gimp_unit_get_factor (GIMP_UNIT_POINT); + + /* read the file */ + + for (i = 0; i < pages->n_pages; i++) + { + PopplerPage *page; + gchar *page_label; + gdouble page_width; + gdouble page_height; + + cairo_surface_t *surface; + gint width; + gint height; + + page = poppler_document_get_page (doc, pages->pages[i]); + + poppler_page_get_size (page, &page_width, &page_height); + width = page_width * scale; + height = page_height * scale; + + g_object_get (G_OBJECT (page), "label", &page_label, NULL); + + if (! image_ID) + { + gchar *name; + + image_ID = gimp_image_new (width, height, GIMP_RGB); + gimp_image_undo_disable (image_ID); + + if (target == GIMP_PAGE_SELECTOR_TARGET_IMAGES) + name = g_strdup_printf (_("%s-%s"), filename, page_label); + else + name = g_strdup_printf (_("%s-pages"), filename); + + gimp_image_set_filename (image_ID, name); + g_free (name); + + gimp_image_set_resolution (image_ID, resolution, resolution); + } + + surface = render_page_to_surface (page, width, height, scale, antialias); + + layer_from_surface (image_ID, page_label, 0, surface, + doc_progress, 1.0 / pages->n_pages); + + g_free (page_label); + cairo_surface_destroy (surface); + + doc_progress = (double) (i + 1) / pages->n_pages; + gimp_progress_update (doc_progress); + + if (target == GIMP_PAGE_SELECTOR_TARGET_IMAGES) + { + images[i] = image_ID; + + gimp_image_undo_enable (image_ID); + gimp_image_clean_all (image_ID); + + image_ID = 0; + } + } + gimp_progress_update (1.0); + + if (image_ID) + { + gimp_image_undo_enable (image_ID); + gimp_image_clean_all (image_ID); + } + + if (target == GIMP_PAGE_SELECTOR_TARGET_IMAGES) + { + if (run_mode != GIMP_RUN_NONINTERACTIVE) + { + /* Display images in reverse order. The last will be + * displayed by GIMP itself + */ + for (i = pages->n_pages - 1; i > 0; i--) + gimp_display_new (images[i]); + } + + image_ID = images[0]; + + g_free (images); + } + + return image_ID; +} + +static cairo_surface_t * +get_thumb_surface (PopplerDocument *doc, + gint page_num, + gint preferred_size) +{ + PopplerPage *page; + cairo_surface_t *surface; + + page = poppler_document_get_page (doc, page_num); + + if (! page) + return NULL; + + surface = poppler_page_get_thumbnail (page); + + if (! surface) + { + gdouble width; + gdouble height; + gdouble scale; + + poppler_page_get_size (page, &width, &height); + + scale = (gdouble) preferred_size / MAX (width, height); + + width *= scale; + height *= scale; + + surface = render_page_to_surface (page, width, height, scale, TRUE); + } + + g_object_unref (page); + + return surface; +} + +static GdkPixbuf * +get_thumb_pixbuf (PopplerDocument *doc, + gint page_num, + gint preferred_size) +{ + cairo_surface_t *surface; + GdkPixbuf *pixbuf; + + surface = get_thumb_surface (doc, page_num, preferred_size); + pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, + cairo_image_surface_get_width (surface), + cairo_image_surface_get_height (surface)); + cairo_surface_destroy (surface); + + return pixbuf; +} + +typedef struct +{ + PopplerDocument *document; + GimpPageSelector *selector; + gboolean stop_thumbnailing; +} ThreadData; + +typedef struct +{ + GimpPageSelector *selector; + gint page_no; + GdkPixbuf *pixbuf; +} IdleData; + +static gboolean +idle_set_thumbnail (gpointer data) +{ + IdleData *idle_data = data; + + gimp_page_selector_set_page_thumbnail (idle_data->selector, + idle_data->page_no, + idle_data->pixbuf); + g_object_unref (idle_data->pixbuf); + g_free (idle_data); + + return FALSE; +} + +static gpointer +thumbnail_thread (gpointer data) +{ + ThreadData *thread_data = data; + gint n_pages; + gint i; + + n_pages = poppler_document_get_n_pages (thread_data->document); + + for (i = 0; i < n_pages; i++) + { + IdleData *idle_data = g_new0 (IdleData, 1); + + idle_data->selector = thread_data->selector; + idle_data->page_no = i; + + /* FIXME get preferred size from somewhere? */ + idle_data->pixbuf = get_thumb_pixbuf (thread_data->document, i, + THUMBNAIL_SIZE); + + g_idle_add (idle_set_thumbnail, idle_data); + + if (thread_data->stop_thumbnailing) + break; + } + + return NULL; +} + +static GimpPDBStatusType +load_dialog (PopplerDocument *doc, + PdfSelectedPages *pages) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *title; + GtkWidget *selector; + GtkWidget *resolution; + GtkWidget *antialias; + GtkWidget *hbox; + + ThreadData thread_data; + GThread *thread; + + gint i; + gint n_pages; + + gdouble width; + gdouble height; + + gboolean run; + + dialog = gimp_dialog_new (_("Import from PDF"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, LOAD_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Import"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + /* Title */ + title = gimp_prop_label_new (G_OBJECT (doc), "title"); + gtk_label_set_ellipsize (GTK_LABEL (title), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (vbox), title, FALSE, FALSE, 0); + gtk_widget_show (title); + + /* Page Selector */ + selector = gimp_page_selector_new (); + gtk_widget_set_size_request (selector, 380, 360); + gtk_box_pack_start (GTK_BOX (vbox), selector, TRUE, TRUE, 0); + gtk_widget_show (selector); + + n_pages = poppler_document_get_n_pages (doc); + + if (n_pages <= 0) + { + g_message (_("Error getting number of pages from the given PDF file.")); + return GIMP_PDB_EXECUTION_ERROR; + } + + gimp_page_selector_set_n_pages (GIMP_PAGE_SELECTOR (selector), n_pages); + gimp_page_selector_set_target (GIMP_PAGE_SELECTOR (selector), + loadvals.target); + + for (i = 0; i < n_pages; i++) + { + PopplerPage *page; + gchar *label; + + page = poppler_document_get_page (doc, i); + g_object_get (G_OBJECT (page), "label", &label, NULL); + + gimp_page_selector_set_page_label (GIMP_PAGE_SELECTOR (selector), i, + label); + + if (i == 0) + poppler_page_get_size (page, &width, &height); + + g_object_unref (page); + g_free (label); + } + /* Since selecting none will be equivalent to selecting all, this is + * only useful as a feedback for the default behavior of selecting all + * pages. */ + gimp_page_selector_select_all (GIMP_PAGE_SELECTOR (selector)); + + g_signal_connect_swapped (selector, "activate", + G_CALLBACK (gtk_window_activate_default), + dialog); + + thread_data.document = doc; + thread_data.selector = GIMP_PAGE_SELECTOR (selector); + thread_data.stop_thumbnailing = FALSE; + + thread = g_thread_new ("thumbnailer", thumbnail_thread, &thread_data); + + /* Resolution */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + resolution = gimp_resolution_entry_new (_("_Width (pixels):"), width, + _("_Height (pixels):"), height, + GIMP_UNIT_POINT, + _("_Resolution:"), + loadvals.resolution, GIMP_UNIT_INCH); + + gtk_box_pack_start (GTK_BOX (hbox), resolution, FALSE, FALSE, 0); + gtk_widget_show (resolution); + + g_signal_connect (resolution, "x-changed", + G_CALLBACK (gimp_resolution_entry_update_x_in_dpi), + &loadvals.resolution); + + /* Antialiasing*/ + antialias = gtk_check_button_new_with_mnemonic (_("Use _Anti-aliasing")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (antialias), loadvals.antialias); + gtk_box_pack_start (GTK_BOX (vbox), antialias, FALSE, FALSE, 0); + + g_signal_connect (antialias, "toggled", + G_CALLBACK (gimp_toggle_button_update), &loadvals.antialias); + gtk_widget_show (antialias); + + /* Setup done; display the dialog */ + gtk_widget_show (dialog); + + /* run the dialog */ + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + loadvals.target = + gimp_page_selector_get_target (GIMP_PAGE_SELECTOR (selector)); + + pages->pages = + gimp_page_selector_get_selected_pages (GIMP_PAGE_SELECTOR (selector), + &pages->n_pages); + + /* select all if none selected */ + if (pages->n_pages == 0) + { + gimp_page_selector_select_all (GIMP_PAGE_SELECTOR (selector)); + + pages->pages = + gimp_page_selector_get_selected_pages (GIMP_PAGE_SELECTOR (selector), + &pages->n_pages); + } + + /* cleanup */ + thread_data.stop_thumbnailing = TRUE; + g_thread_join (thread); + + return run ? GIMP_PDB_SUCCESS : GIMP_PDB_CANCEL; +} + + +/** + ** code for GimpResolutionEntry widget, formerly in libgimpwidgets + **/ + +static guint gimp_resolution_entry_signals[LAST_SIGNAL] = { 0 }; + +static GtkTableClass *parent_class = NULL; + + +GType +gimp_resolution_entry_get_type (void) +{ + static GType gre_type = 0; + + if (! gre_type) + { + const GTypeInfo gre_info = + { + sizeof (GimpResolutionEntryClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gimp_resolution_entry_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GimpResolutionEntry), + 0, /* n_preallocs */ + (GInstanceInitFunc) gimp_resolution_entry_init, + }; + + gre_type = g_type_register_static (GTK_TYPE_TABLE, + "GimpResolutionEntry", + &gre_info, 0); + } + + return gre_type; +} + +static void +gimp_resolution_entry_class_init (GimpResolutionEntryClass *klass) +{ + parent_class = g_type_class_peek_parent (klass); + + gimp_resolution_entry_signals[HEIGHT_CHANGED] = + g_signal_new ("height-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpResolutionEntryClass, value_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_resolution_entry_signals[WIDTH_CHANGED] = + g_signal_new ("width-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpResolutionEntryClass, value_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_resolution_entry_signals[X_CHANGED] = + g_signal_new ("x-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpResolutionEntryClass, value_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_resolution_entry_signals[Y_CHANGED] = + g_signal_new ("y-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpResolutionEntryClass, refval_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_resolution_entry_signals[UNIT_CHANGED] = + g_signal_new ("unit-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpResolutionEntryClass, unit_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->value_changed = NULL; + klass->refval_changed = NULL; + klass->unit_changed = NULL; +} + +static void +gimp_resolution_entry_init (GimpResolutionEntry *gre) +{ + gre->unitmenu = NULL; + gre->unit = GIMP_UNIT_INCH; + + gtk_table_set_col_spacings (GTK_TABLE (gre), 4); + gtk_table_set_row_spacings (GTK_TABLE (gre), 2); +} + +static void +gimp_resolution_entry_field_init (GimpResolutionEntry *gre, + GimpResolutionEntryField *gref, + GimpResolutionEntryField *corresponding, + guint changed_signal, + gdouble initial_val, + GimpUnit initial_unit, + gboolean size, + gint spinbutton_width) +{ + gint digits; + + g_return_if_fail (GIMP_IS_RESOLUTION_ENTRY (gre)); + + gref->gre = gre; + gref->corresponding = corresponding; + gref->changed_signal = gimp_resolution_entry_signals[changed_signal]; + + if (size) + { + gref->value = initial_val / + gimp_unit_get_factor (initial_unit) * + corresponding->value * + gimp_unit_get_factor (gre->unit); + + gref->phy_size = initial_val / + gimp_unit_get_factor (initial_unit); + } + else + { + gref->value = initial_val; + } + + gref->min_value = GIMP_MIN_RESOLUTION; + gref->max_value = GIMP_MAX_RESOLUTION; + gref->adjustment = NULL; + + gref->stop_recursion = 0; + + gref->size = size; + + if (size) + { + gref->label = g_object_new (GTK_TYPE_LABEL, + "xalign", 0.0, + "yalign", 0.5, + NULL); + gimp_label_set_attributes (GTK_LABEL (gref->label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + + gimp_resolution_entry_format_label (gre, gref->label, gref->phy_size); + } + + digits = size ? 0 : MIN (gimp_unit_get_digits (initial_unit), 5) + 1; + + gref->adjustment = (GtkAdjustment *) + gtk_adjustment_new (gref->value, + gref->min_value, + gref->max_value, + 1.0, 10.0, 0.0); + gref->spinbutton = gimp_spin_button_new (gref->adjustment, + 1.0, digits); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (gref->spinbutton), TRUE); + + if (spinbutton_width > 0) + { + if (spinbutton_width < 17) + gtk_entry_set_width_chars (GTK_ENTRY (gref->spinbutton), + spinbutton_width); + else + gtk_widget_set_size_request (gref->spinbutton, + spinbutton_width, -1); + } +} + +/** + * gimp_resolution_entry_new: + * @width_label: Optional label for the width control. + * @width: Width of the item, specified in terms of @size_unit. + * @height_label: Optional label for the height control. + * @height: Height of the item, specified in terms of @size_unit. + * @size_unit: Unit used to specify the width and height. + * @res_label: Optional label for the resolution entry. + * @initial_res: The initial resolution. + * @initial_unit: The initial unit. + * + * Creates a new #GimpResolutionEntry widget. + * + * The #GimpResolutionEntry is derived from #GtkTable and will have + * an empty border of one cell width on each side plus an empty column left + * of the #GimpUnitMenu to allow the caller to add labels or other widgets. + * + * A #GimpChainButton is displayed if independent is set to %TRUE. + * + * Returns: A pointer to the new #GimpResolutionEntry widget. + **/ +GtkWidget * +gimp_resolution_entry_new (const gchar *width_label, + gdouble width, + const gchar *height_label, + gdouble height, + GimpUnit size_unit, + const gchar *res_label, + gdouble initial_res, + GimpUnit initial_unit) +{ + GimpResolutionEntry *gre; + GtkTreeModel *model; + + gre = g_object_new (GIMP_TYPE_RESOLUTION_ENTRY, NULL); + + gre->unit = initial_unit; + + gtk_table_resize (GTK_TABLE (gre), 4, 4); + + gimp_resolution_entry_field_init (gre, &gre->x, + &gre->width, + X_CHANGED, + initial_res, initial_unit, + FALSE, 0); + + gtk_table_attach_defaults (GTK_TABLE (gre), gre->x.spinbutton, + 1, 2, + 3, 4); + + g_signal_connect (gre->x.adjustment, "value-changed", + G_CALLBACK (gimp_resolution_entry_value_callback), + &gre->x); + + gtk_widget_show (gre->x.spinbutton); + + gre->unitmenu = gimp_unit_combo_box_new (); + model = gtk_combo_box_get_model (GTK_COMBO_BOX (gre->unitmenu)); + gimp_unit_store_set_has_pixels (GIMP_UNIT_STORE (model), FALSE); + gimp_unit_store_set_has_percent (GIMP_UNIT_STORE (model), FALSE); + g_object_set (model, + "short-format", _("pixels/%a"), + "long-format", _("pixels/%a"), + NULL); + gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (gre->unitmenu), + initial_unit); + + gtk_table_attach (GTK_TABLE (gre), gre->unitmenu, + 3, 4, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + g_signal_connect (gre->unitmenu, "changed", + G_CALLBACK (gimp_resolution_entry_unit_callback), + gre); + gtk_widget_show (gre->unitmenu); + + gimp_resolution_entry_field_init (gre, &gre->width, + &gre->x, + WIDTH_CHANGED, + width, size_unit, + TRUE, 0); + + gtk_table_attach_defaults (GTK_TABLE (gre), gre->width.spinbutton, + 1, 2, + 1, 2); + + gtk_table_attach_defaults (GTK_TABLE (gre), gre->width.label, + 3, 4, + 1, 2); + + g_signal_connect (gre->width.adjustment, "value-changed", + G_CALLBACK (gimp_resolution_entry_value_callback), + &gre->width); + + gtk_widget_show (gre->width.spinbutton); + gtk_widget_show (gre->width.label); + + gimp_resolution_entry_field_init (gre, &gre->height, &gre->x, + HEIGHT_CHANGED, + height, size_unit, + TRUE, 0); + + gtk_table_attach_defaults (GTK_TABLE (gre), gre->height.spinbutton, + 1, 2, 2, 3); + + gtk_table_attach_defaults (GTK_TABLE (gre), gre->height.label, + 3, 4, 2, 3); + + g_signal_connect (gre->height.adjustment, "value-changed", + G_CALLBACK (gimp_resolution_entry_value_callback), + &gre->height); + + gtk_widget_show (gre->height.spinbutton); + gtk_widget_show (gre->height.label); + + if (width_label) + gimp_resolution_entry_attach_label (gre, width_label, 1, 0, 0.0); + + if (height_label) + gimp_resolution_entry_attach_label (gre, height_label, 2, 0, 0.0); + + if (res_label) + gimp_resolution_entry_attach_label (gre, res_label, 3, 0, 0.0); + + return GTK_WIDGET (gre); +} + +/** + * gimp_resolution_entry_attach_label: + * @gre: The #GimpResolutionEntry you want to add a label to. + * @text: The text of the label. + * @row: The row where the label will be attached. + * @column: The column where the label will be attached. + * @alignment: The horizontal alignment of the label. + * + * Attaches a #GtkLabel to the #GimpResolutionEntry (which is a #GtkTable). + * + * Returns: A pointer to the new #GtkLabel widget. + **/ +GtkWidget * +gimp_resolution_entry_attach_label (GimpResolutionEntry *gre, + const gchar *text, + gint row, + gint column, + gfloat alignment) +{ + GtkWidget *label; + + g_return_val_if_fail (GIMP_IS_RESOLUTION_ENTRY (gre), NULL); + g_return_val_if_fail (text != NULL, NULL); + + label = gtk_label_new_with_mnemonic (text); + + if (column == 0) + { + GList *children; + GList *list; + + children = gtk_container_get_children (GTK_CONTAINER (gre)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *child = list->data; + gint left_attach; + gint top_attach; + + gtk_container_child_get (GTK_CONTAINER (gre), child, + "left-attach", &left_attach, + "top-attach", &top_attach, + NULL); + + if (left_attach == 1 && top_attach == row) + { + gtk_label_set_mnemonic_widget (GTK_LABEL (label), child); + break; + } + } + + g_list_free (children); + } + + gtk_label_set_xalign (GTK_LABEL (label), alignment); + + gtk_table_attach (GTK_TABLE (gre), label, column, column+1, row, row+1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + return label; +} + +/** + * gimp_resolution_entry_get_x_in_dpi; + * @gre: The #GimpResolutionEntry you want to know the resolution of. + * + * Returns the X resolution of the #GimpResolutionEntry in pixels per inch. + **/ +gdouble +gimp_resolution_entry_get_x_in_dpi (GimpResolutionEntry *gre) +{ + g_return_val_if_fail (GIMP_IS_RESOLUTION_ENTRY (gre), 0); + + /* dots_in_one_unit * units_in_one_inch -> dpi */ + return gre->x.value * gimp_unit_get_factor (gre->unit); +} + +/** + * gimp_resolution_entry_get_y_in_dpi; + * @gre: The #GimpResolutionEntry you want to know the resolution of. + * + * Returns the Y resolution of the #GimpResolutionEntry in pixels per inch. + **/ +gdouble +gimp_resolution_entry_get_y_in_dpi (GimpResolutionEntry *gre) +{ + g_return_val_if_fail (GIMP_IS_RESOLUTION_ENTRY (gre), 0); + + return gre->y.value * gimp_unit_get_factor (gre->unit); +} + + +static void +gimp_resolution_entry_update_value (GimpResolutionEntryField *gref, + gdouble value) +{ + if (gref->stop_recursion > 0) + return; + + gref->value = value; + + gref->stop_recursion++; + + if (gref->size) + gimp_resolution_entry_update_value (gref->corresponding, + gref->value / + gref->phy_size / + gimp_unit_get_factor (gref->gre->unit)); + else + { + gdouble factor = gimp_unit_get_factor (gref->gre->unit); + + gimp_resolution_entry_update_value (&gref->gre->width, + gref->value * + gref->gre->width.phy_size * + factor); + + gimp_resolution_entry_update_value (&gref->gre->height, + gref->value * + gref->gre->height.phy_size * + factor); + } + + gtk_adjustment_set_value (gref->adjustment, value); + + gref->stop_recursion--; + + g_signal_emit (gref->gre, gref->changed_signal, 0); +} + +static void +gimp_resolution_entry_value_callback (GtkWidget *widget, + gpointer data) +{ + GimpResolutionEntryField *gref = (GimpResolutionEntryField *) data; + gdouble new_value; + + new_value = gtk_adjustment_get_value (GTK_ADJUSTMENT (widget)); + + if (gref->value != new_value) + gimp_resolution_entry_update_value (gref, new_value); +} + +static void +gimp_resolution_entry_update_unit (GimpResolutionEntry *gre, + GimpUnit unit) +{ + GimpUnit old_unit; + gint digits; + gdouble factor; + + old_unit = gre->unit; + gre->unit = unit; + + digits = (gimp_unit_get_digits (GIMP_UNIT_INCH) - + gimp_unit_get_digits (unit)); + + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gre->x.spinbutton), + MAX (3 + digits, 3)); + + factor = gimp_unit_get_factor (old_unit) / gimp_unit_get_factor (unit); + + gre->x.min_value *= factor; + gre->x.max_value *= factor; + gre->x.value *= factor; + + gtk_adjustment_set_value (GTK_ADJUSTMENT (gre->x.adjustment), + gre->x.value); + + gimp_resolution_entry_format_label (gre, + gre->width.label, gre->width.phy_size); + gimp_resolution_entry_format_label (gre, + gre->height.label, gre->height.phy_size); + + g_signal_emit (gre, gimp_resolution_entry_signals[UNIT_CHANGED], 0); +} + +static void +gimp_resolution_entry_unit_callback (GtkWidget *widget, + GimpResolutionEntry *gre) +{ + GimpUnit new_unit; + + new_unit = gimp_unit_combo_box_get_active (GIMP_UNIT_COMBO_BOX (widget)); + + if (gre->unit != new_unit) + gimp_resolution_entry_update_unit (gre, new_unit); +} + +/** + * gimp_resolution_entry_update_x_in_dpi: + * @gre: the #GimpResolutionEntry + * @data: a pointer to a gdouble + * + * Convenience function to set a double to the X resolution, suitable + * for use as a signal callback. + */ +void +gimp_resolution_entry_update_x_in_dpi (GimpResolutionEntry *gre, + gpointer data) +{ + gdouble *val; + + g_return_if_fail (gre != NULL); + g_return_if_fail (data != NULL); + g_return_if_fail (GIMP_IS_RESOLUTION_ENTRY (gre)); + + val = (gdouble *) data; + + *val = gimp_resolution_entry_get_x_in_dpi (gre); +} + +/** + * gimp_resolution_entry_update_y_in_dpi: + * @gre: the #GimpResolutionEntry + * @data: a pointer to a gdouble + * + * Convenience function to set a double to the Y resolution, suitable + * for use as a signal callback. + */ +void +gimp_resolution_entry_update_y_in_dpi (GimpResolutionEntry *gre, + gpointer data) +{ + gdouble *val; + + g_return_if_fail (gre != NULL); + g_return_if_fail (data != NULL); + g_return_if_fail (GIMP_IS_RESOLUTION_ENTRY (gre)); + + val = (gdouble *) data; + + *val = gimp_resolution_entry_get_y_in_dpi (gre); +} + +static void +gimp_resolution_entry_format_label (GimpResolutionEntry *gre, + GtkWidget *label, + gdouble size) +{ + gchar *format = g_strdup_printf ("%%.%df %%s", + gimp_unit_get_digits (gre->unit)); + gchar *text = g_strdup_printf (format, + size * gimp_unit_get_factor (gre->unit), + gimp_unit_get_plural (gre->unit)); + g_free (format); + + gtk_label_set_text (GTK_LABEL (label), text); + g_free (text); +} diff --git a/plug-ins/common/file-pdf-save.c b/plug-ins/common/file-pdf-save.c new file mode 100644 index 0000000..42c073d --- /dev/null +++ b/plug-ins/common/file-pdf-save.c @@ -0,0 +1,1791 @@ +/* GIMP - The GNU Image Manipulation Program + * + * file-pdf-save.c - PDF file exporter, based on the cairo PDF surface + * + * Copyright (C) 2010 Barak Itkin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* The PDF export plugin has 3 main procedures: + * 1. file-pdf-save + * This is the main procedure. It has 3 options for optimizations of + * the pdf file, and it can show a gui. This procedure works on a single + * image. + * 2. file-pdf-save-defaults + * This procedures is the one that will be invoked by gimp's file-save, + * when the pdf extension is chosen. If it's in RUN_INTERACTIVE, it will + * pop a user interface with more options, like file-pdf-save. If it's in + * RUN_NONINTERACTIVE, it will simply use the default values. Note that on + * RUN_WITH_LAST_VALS there will be no gui, however the values will be the + * ones that were used in the last interactive run (or the defaults if none + * are available. + * 3. file-pdf-save-multi + * This procedures is more advanced, and it allows the creation of multiple + * paged pdf files. It will be located in File/Create/Multiple page PDF... + * + * It was suggested that file-pdf-save-multi will be removed from the UI as it + * does not match the product vision (GIMP isn't a program for editing multiple + * paged documents). + */ + +/* Known Issues (except for the coding style issues): + * 1. Grayscale layers are inverted (although layer masks which are not grayscale, + * are not inverted) + * 2. Exporting some fonts doesn't work since gimp_text_layer_get_font Returns a + * font which is sometimes incompatiable with pango_font_description_from_string + * (gimp_text_layer_get_font sometimes returns suffixes such as "semi-expanded" to + * the font's name although the GIMP's font selection dialog shows the don'ts name + * normally - This should be checked again in GIMP 2.7) + * 3. Indexed layers can't be optimized yet (Since gimp_histogram won't work on + * indexed layers) + * 4. Rendering the pango layout requires multiplying the size in PANGO_SCALE. This + * means I'll need to do some hacking on the markup returned from GIMP. + * 5. When accessing the contents of layer groups is supported, we should do use it + * (since this plugin should preserve layers). + * + * Also, there are 2 things which we should warn the user about: + * 1. Cairo does not support bitmap masks for text. + * 2. Currently layer modes are ignored. We do support layers, including + * transparency and opacity, but layer modes are not supported. + */ + +/* Changelog + * + * April 29, 2009 | Barak Itkin + * First version of the plugin. This is only a proof of concept and not a full + * working plugin. + * + * May 6, 2009 Barak | Itkin + * Added new features and several bugfixes: + * - Added handling for image resolutions + * - fixed the behaviour of getting font sizes + * - Added various optimizations (solid rectangles instead of bitmaps, ignoring + * invisible layers, etc.) as a macro flag. + * - Added handling for layer masks, use CAIRO_FORMAT_A8 for grayscale drawables. + * - Indexed layers are now supported + * + * August 17, 2009 | Barak Itkin + * Most of the plugin was rewritten from scratch and it now has several new + * features: + * - Got rid of the optimization macros in the code. The gui now supports + * selecting which optimizations to apply. + * - Added a procedure to allow the creation of multiple paged PDF's + * - Registered the plugin on "/File/Create/PDF" + * + * August 21, 2009 | Barak Itkin + * Fixed a typo that prevented the plugin from compiling... + * A migration to the new GIMP 2.8 api, which includes: + * - Now using gimp_export_dialog_new + * - Using gimp_text_layer_get_hint_style (2.8) instead of the depreceated + * gimp_text_layer_get_hinting (2.6). + * + * August 24, 2010 | Barak Itkin + * More migrations to the new GIMP 2.8 api: + * - Now using the GimpItem api + * - Using gimp_text_layer_get_markup where possible + * - Fixed some compiler warnings + * Also merged the header and c file into one file, Updated some of the comments + * and documentation, and moved this into the main source repository. + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define SAVE_PROC "file-pdf-save" +#define SAVE2_PROC "file-pdf-save2" +#define SAVE_MULTI_PROC "file-pdf-save-multi" +#define PLUG_IN_BINARY "file-pdf-save" +#define PLUG_IN_ROLE "gimp-file-pdf-save" + +#define DATA_OPTIMIZE "file-pdf-data-optimize" +#define DATA_IMAGE_LIST "file-pdf-data-multi-page" + +/* Gimp will crash before you reach this limitation :D */ +#define MAX_PAGE_COUNT 350 +#define MAX_FILE_NAME_LENGTH 350 + +#define THUMB_WIDTH 90 +#define THUMB_HEIGHT 120 + +#define GIMP_PLUGIN_PDF_SAVE_ERROR gimp_plugin_pdf_save_error_quark () + +typedef enum +{ + GIMP_PLUGIN_PDF_SAVE_ERROR_FAILED +} GimpPluginPDFError; + +GQuark gimp_plugin_pdf_save_error_quark (void); + +typedef enum +{ + SA_RUN_MODE, + SA_IMAGE, + SA_DRAWABLE, + SA_FILENAME, + SA_RAW_FILENAME, + SA_VECTORIZE, + SA_IGNORE_HIDDEN, + SA_APPLY_MASKS, + SA_LAYERS_AS_PAGES, + SA_REVERSE_ORDER, + SA_ARG_COUNT +} SaveArgs; + +typedef enum +{ + SMA_RUN_MODE, + SMA_COUNT, + SMA_IMAGES, + SMA_VECTORIZE, + SMA_IGNORE_HIDDEN, + SMA_APPLY_MASKS, + SMA_FILENAME, + SMA_RAWFILENAME, + SMA_ARG_COUNT +} SaveMultiArgs; + +typedef struct +{ + gboolean vectorize; + gboolean ignore_hidden; + gboolean apply_masks; + gboolean layers_as_pages; + gboolean reverse_order; +} PdfOptimize; + +typedef struct +{ + gint32 images[MAX_PAGE_COUNT]; + guint32 image_count; + gchar file_name[MAX_FILE_NAME_LENGTH]; +} PdfMultiPage; + +typedef struct +{ + PdfOptimize optimize; + GArray *images; +} PdfMultiVals; + +enum +{ + THUMB, + PAGE_NUMBER, + IMAGE_NAME, + IMAGE_ID +}; + +typedef struct +{ + GdkPixbuf *thumb; + gint32 page_number; + gchar *image_name; +} Page; + + +static void query (void); + +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean init_vals (const gchar *name, + gint nparams, + const GimpParam *param, + gboolean *single, + gboolean *defaults, + GimpRunMode *run_mode); + +static void init_image_list_defaults (gint32 image); + +static void validate_image_list (void); + +static gboolean gui_single (void); + +static gboolean gui_multi (void); + +static void reverse_order_toggled (GtkToggleButton *reverse_order, + GtkButton *layers_as_pages); + +static void choose_file_call (GtkWidget *browse_button, + gpointer file_entry); + +static gboolean get_image_list (void); + +static GtkTreeModel * create_model (void); + +static void add_image_call (GtkWidget *widget, + gpointer img_combo); +static void del_image_call (GtkWidget *widget, + gpointer icon_view); +static void remove_call (GtkTreeModel *tree_model, + GtkTreePath *path, + gpointer user_data); +static void recount_pages (void); + +static cairo_surface_t * get_cairo_surface (gint32 drawable_ID, + gboolean as_mask, + GError **error); + +static GimpRGB get_layer_color (gint32 layer_ID, + gboolean *single); + +static void drawText (gint32 text_id, + gdouble opacity, + cairo_t *cr, + gdouble x_res, + gdouble y_res); + +static gboolean draw_layer (gint32 *layers, + gint n_layers, + gint j, + cairo_t *cr, + gdouble x_res, + gdouble y_res, + const gchar *name, + GError **error); + +static gboolean dnd_remove = TRUE; +static PdfMultiPage multi_page; + +static PdfOptimize optimize = +{ + TRUE, /* vectorize */ + TRUE, /* ignore_hidden */ + TRUE, /* apply_masks */ + FALSE, /* layers_as_pages */ + FALSE /* reverse_order */ +}; + +static GtkTreeModel *model; +static GtkWidget *file_choose; +static gchar *file_name; + +GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run +}; + +G_DEFINE_QUARK (gimp-plugin-pdf-save-error-quark, gimp_plugin_pdf_save_error) + +MAIN() + +static void +query (void) +{ + static GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "Run mode" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" }, + { GIMP_PDB_INT32, "vectorize", "Convert bitmaps to vector graphics where possible. TRUE or FALSE" }, + { GIMP_PDB_INT32, "ignore-hidden", "Omit hidden layers and layers with zero opacity. TRUE or FALSE" }, + { GIMP_PDB_INT32, "apply-masks", "Apply layer masks before saving. TRUE or FALSE (Keeping them will not change the output)" } + }; + + static GimpParamDef save2_args[] = + { + { GIMP_PDB_INT32, "run-mode", "Run mode" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" }, + { GIMP_PDB_INT32, "vectorize", "Convert bitmaps to vector graphics where possible. TRUE or FALSE" }, + { GIMP_PDB_INT32, "ignore-hidden", "Omit hidden layers and layers with zero opacity. TRUE or FALSE" }, + { GIMP_PDB_INT32, "apply-masks", "Apply layer masks before saving. TRUE or FALSE (Keeping them will not change the output)" }, + { GIMP_PDB_INT32, "layers-as-pages", "Layers as pages (bottom layers first). TRUE or FALSE" }, + { GIMP_PDB_INT32, "reverse-order", "Reverse the pages order (top layers first). TRUE or FALSE" } + }; + + static GimpParamDef save_multi_args[] = + { + { GIMP_PDB_INT32, "run-mode", "Run mode" }, + { GIMP_PDB_INT32, "count", "The amount of images entered (This will be the amount of pages). 1 <= count <= MAX_PAGE_COUNT" }, + { GIMP_PDB_INT32ARRAY, "images", "Input image for each page (An image can appear more than once)" }, + { GIMP_PDB_INT32, "vectorize", "Convert bitmaps to vector graphics where possible. TRUE or FALSE" }, + { GIMP_PDB_INT32, "ignore-hidden", "Omit hidden layers and layers with zero opacity. TRUE or FALSE" }, + { GIMP_PDB_INT32, "apply-masks", "Apply layer masks before saving. TRUE or FALSE (Keeping them will not change the output)" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" } + }; + + gimp_install_procedure (SAVE_PROC, + "Save files in PDF format", + "Saves files in Adobe's Portable Document Format. " + "PDF is designed to be easily processed by a variety " + "of different platforms, and is a distant cousin of " + "PostScript.", + "Barak Itkin", + "Copyright Barak Itkin", + "August 2009", + N_("Portable Document Format"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (SAVE2_PROC, + "Save files in PDF format", + "Saves files in Adobe's Portable Document Format. " + "PDF is designed to be easily processed by a variety " + "of different platforms, and is a distant cousin of " + "PostScript.\n" + "This procedure adds an extra parameter to " + "file-pdf-save to save layers as pages.", + "Barak Itkin, Lionel N., Jehan", + "Copyright Barak Itkin, Lionel N., Jehan", + "August 2009, 2017", + N_("Portable Document Format"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save2_args), 0, + save2_args, NULL); + + gimp_install_procedure (SAVE_MULTI_PROC, + "Save files in PDF format", + "Saves files in Adobe's Portable Document Format. " + "PDF is designed to be easily processed by a variety " + "of different platforms, and is a distant cousin of " + "PostScript.", + "Barak Itkin", + "Copyright Barak Itkin", + "August 2009", + N_("_Create multipage PDF..."), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_multi_args), 0, + save_multi_args, NULL); + +#if 0 + gimp_plugin_menu_register (SAVE_MULTI_PROC, + "/File/Create/PDF"); +#endif + + gimp_register_file_handler_mime (SAVE2_PROC, "application/pdf"); + gimp_register_save_handler (SAVE2_PROC, "pdf", ""); +} + +static cairo_status_t +write_func (void *fp, + const unsigned char *data, + unsigned int size) +{ + return fwrite (data, 1, size, fp) == size ? CAIRO_STATUS_SUCCESS + : CAIRO_STATUS_WRITE_ERROR; +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gboolean single_image; + gboolean defaults_proc; + cairo_surface_t *pdf_file; + cairo_t *cr; + GimpExportCapabilities capabilities; + FILE *fp; + gint i; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + /* Setting mandatory output values */ + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + /* Initializing all the settings */ + multi_page.image_count = 0; + + if (! init_vals (name, nparams, param, &single_image, + &defaults_proc, &run_mode)) + { + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + return; + } + + /* Starting the executions */ + if (run_mode == GIMP_RUN_INTERACTIVE) + { + if (single_image) + { + if (! gui_single ()) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + } + else if (! gui_multi ()) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + + if (file_name == NULL) + { + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + gimp_message (_("You must select a file to save!")); + return; + } + } + + fp = g_fopen (file_name, "wb"); + if (fp == NULL) + { + *nreturn_vals = 2; + + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + values[1].type = GIMP_PDB_STRING; + if (error == NULL) + { + g_set_error (&error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (file_name), g_strerror (errno)); + } + values[1].data.d_string = error->message; + return; + } + + pdf_file = cairo_pdf_surface_create_for_stream (write_func, fp, 1, 1); + + if (cairo_surface_status (pdf_file) != CAIRO_STATUS_SUCCESS) + { + g_message (_("An error occurred while creating the PDF file:\n" + "%s\n" + "Make sure you entered a valid filename and that the " + "selected location isn't read only!"), + cairo_status_to_string (cairo_surface_status (pdf_file))); + + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + return; + } + + cr = cairo_create (pdf_file); + + capabilities = (GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_ALPHA | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_LAYERS | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + /* This seems counter-intuitive, but not setting the mask capability + * will apply any layer mask upon gimp_export_image(). + */ + if (! optimize.apply_masks) + capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS; + + for (i = 0; i < multi_page.image_count; i++) + { + gint32 image_ID = multi_page.images[i]; + gint32 *layers; + gint32 n_layers; + gdouble x_res, y_res; + gdouble x_scale, y_scale; + gint32 temp; + gint j; + + temp = gimp_image_get_active_drawable (image_ID); + if (temp < 1) + continue; + + /* Save the state of the surface before any changes, so that + * settings from one page won't affect all the others + */ + cairo_save (cr); + + if (! (gimp_export_image (&image_ID, &temp, NULL, + capabilities) == GIMP_EXPORT_EXPORT)) + { + /* gimp_drawable_histogram() only works within the bounds of + * the selection, which is a problem (see issue #2431). + * Instead of saving the selection, unselecting to later + * reselect, let's just always work on a duplicate of the + * image. + */ + image_ID = gimp_image_duplicate (image_ID); + } + gimp_selection_none (image_ID); + + gimp_image_get_resolution (image_ID, &x_res, &y_res); + x_scale = 72.0 / x_res; + y_scale = 72.0 / y_res; + + cairo_pdf_surface_set_size (pdf_file, + gimp_image_width (image_ID) * x_scale, + gimp_image_height (image_ID) * y_scale); + + /* This way we set how many pixels are there in every inch. + * It's very important for PangoCairo + */ + cairo_surface_set_fallback_resolution (pdf_file, x_res, y_res); + + /* Cairo has a concept of user-space vs device-space units. + * From what I understand, by default the user-space unit is the + * typographical "point". Since we work mostly with pixels, not + * points, the following call simply scales the transformation + * matrix from points to pixels, relatively to the image + * resolution, knowing that 1 typographical point == 1/72 inch. + */ + cairo_scale (cr, x_scale, y_scale); + + layers = gimp_image_get_layers (image_ID, &n_layers); + + /* Fill image with background color - + * otherwise the output PDF will always show white for background, + * and may display artifacts at transparency boundaries + */ + if (gimp_drawable_has_alpha (layers[n_layers - 1])) + { + GimpRGB color; + + cairo_rectangle (cr, 0.0, 0.0, + gimp_image_width (image_ID), + gimp_image_height (image_ID)); + gimp_context_get_background (&color); + cairo_set_source_rgb (cr, + color.r, + color.g, + color.b); + cairo_fill (cr); + } + + /* Now, we should loop over the layers of each image */ + for (j = 0; j < n_layers; j++) + { + if (! draw_layer (layers, n_layers, j, cr, x_res, y_res, name, &error)) + { + *nreturn_vals = 2; + + /* free the resources */ + g_free (layers); + cairo_surface_destroy (pdf_file); + cairo_destroy (cr); + fclose (fp); + + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + return; + } + } + g_free (layers); + + /* We are done with this image - Show it! + * Unless that's a multi-page to avoid blank page at the end + */ + if (g_strcmp0 (name, SAVE2_PROC) != 0 || + ! optimize.layers_as_pages) + cairo_show_page (cr); + cairo_restore (cr); + + gimp_image_delete (image_ID); + } + + /* We are done with all the images - time to free the resources */ + cairo_surface_destroy (pdf_file); + cairo_destroy (cr); + + fclose (fp); + + /* Finally done, let's save the parameters */ + gimp_set_data (DATA_OPTIMIZE, &optimize, sizeof (optimize)); + + if (! single_image) + { + g_strlcpy (multi_page.file_name, file_name, MAX_FILE_NAME_LENGTH); + gimp_set_data (DATA_IMAGE_LIST, &multi_page, sizeof (multi_page)); + } +} + +/******************************************************/ +/* Beginning of parameter handling functions */ +/******************************************************/ + +/* A function that takes care of loading the basic parameters + */ +static gboolean +init_vals (const gchar *name, + gint nparams, + const GimpParam *param, + gboolean *single_image, + gboolean *defaults_proc, + GimpRunMode *run_mode) +{ + gboolean had_saved_list = FALSE; + gboolean single; + gboolean defaults = FALSE; + gint32 i; + gint32 image; + + if ((g_str_equal (name, SAVE_PROC) && nparams == SA_ARG_COUNT - 2) || + (g_str_equal (name, SAVE2_PROC) && nparams == SA_ARG_COUNT)) + { + single = TRUE; + *run_mode = param[SA_RUN_MODE].data.d_int32; + image = param[SA_IMAGE].data.d_int32; + file_name = param[SA_FILENAME].data.d_string; + + if (*run_mode == GIMP_RUN_NONINTERACTIVE) + { + optimize.apply_masks = param[SA_APPLY_MASKS].data.d_int32; + optimize.vectorize = param[SA_VECTORIZE].data.d_int32; + optimize.ignore_hidden = param[SA_IGNORE_HIDDEN].data.d_int32; + if (nparams == SA_ARG_COUNT) + { + optimize.layers_as_pages = param[SA_LAYERS_AS_PAGES].data.d_int32; + optimize.reverse_order = param[SA_REVERSE_ORDER].data.d_int32; + } + } + else + defaults = TRUE; + } + else if (g_str_equal (name, SAVE_MULTI_PROC)) + { + single = FALSE; + if (nparams != SMA_ARG_COUNT) + return FALSE; + + *run_mode = param[SMA_RUN_MODE].data.d_int32; + image = -1; + file_name = param[SMA_FILENAME].data.d_string; + + optimize.apply_masks = param[SMA_APPLY_MASKS].data.d_int32; + optimize.vectorize = param[SMA_VECTORIZE].data.d_int32; + optimize.ignore_hidden = param[SMA_IGNORE_HIDDEN].data.d_int32; + } + else + { + return FALSE; + } + + switch (*run_mode) + { + case GIMP_RUN_NONINTERACTIVE: + if (single) + { + init_image_list_defaults (image); + } + else + { + multi_page.image_count = param[SMA_COUNT].data.d_int32; + if (param[SMA_IMAGES].data.d_int32array != NULL) + for (i = 0; i < param[SMA_COUNT].data.d_int32; i++) + multi_page.images[i] = param[SMA_IMAGES].data.d_int32array[i]; + } + break; + + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (DATA_OPTIMIZE, &optimize); + had_saved_list = gimp_get_data (DATA_IMAGE_LIST, &multi_page); + + if (had_saved_list && (file_name == NULL || strlen (file_name) == 0)) + { + file_name = multi_page.file_name; + } + + if (single || ! had_saved_list ) + init_image_list_defaults (image); + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + if (! single) + { + had_saved_list = gimp_get_data (DATA_IMAGE_LIST, &multi_page); + if (had_saved_list) + { + file_name = multi_page.file_name; + } + } + else + { + init_image_list_defaults (image); + } + gimp_get_data (DATA_OPTIMIZE, &optimize); + break; + } + + *defaults_proc = defaults; + *single_image = single; + + validate_image_list (); + + return TRUE; +} + +/* A function that initializes the image list to default values */ +static void +init_image_list_defaults (gint32 image) +{ + if (image != -1) + { + multi_page.images[0] = image; + multi_page.image_count = 1; + } + else + { + multi_page.image_count = 0; + } +} + +/* A function that removes images that are no longer valid from the + * image list + */ +static void +validate_image_list (void) +{ + gint32 valid = 0; + guint32 i = 0; + + for (i = 0 ; i < MAX_PAGE_COUNT && i < multi_page.image_count ; i++) + { + if (gimp_image_is_valid (multi_page.images[i])) + { + multi_page.images[valid] = multi_page.images[i]; + valid++; + } + } + + multi_page.image_count = valid; +} + + +/******************************************************/ +/* Beginning of GUI functions */ +/******************************************************/ + +/* The main GUI function for saving single-paged PDFs */ + +static gboolean +gui_single (void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *vectorize_c; + GtkWidget *ignore_hidden_c; + GtkWidget *apply_c; + GtkWidget *layers_as_pages_c; + GtkWidget *reverse_order_c; + GtkWidget *frame; + gchar *text; + gboolean run; + gint32 n_layers; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + window = gimp_export_dialog_new ("PDF", PLUG_IN_ROLE, SAVE_PROC); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (window)), + vbox, TRUE, TRUE, 0); + + gtk_container_set_border_width (GTK_CONTAINER (window), 12); + + ignore_hidden_c = gtk_check_button_new_with_mnemonic (_("_Omit hidden layers and layers with zero opacity")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ignore_hidden_c), + optimize.ignore_hidden); + gtk_box_pack_end (GTK_BOX (vbox), ignore_hidden_c, TRUE, TRUE, 0); + + vectorize_c = gtk_check_button_new_with_mnemonic (_("Convert _bitmaps to vector graphics where possible")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (vectorize_c), + optimize.vectorize); + gtk_box_pack_end (GTK_BOX (vbox), vectorize_c, TRUE, TRUE, 0); + + apply_c = gtk_check_button_new_with_mnemonic (_("_Apply layer masks before saving")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_c), + optimize.apply_masks); + gtk_box_pack_end (GTK_BOX (vbox), apply_c, TRUE, TRUE, 0); + gimp_help_set_help_data (apply_c, _("Keeping the masks will not change the output"), NULL); + + /* Frame for multi-page from layers. */ + frame = gtk_frame_new (NULL); + gtk_box_pack_end (GTK_BOX (vbox), frame, TRUE, TRUE, 0); + + text = g_strdup_printf (_("_Layers as pages (%s)"), + optimize.reverse_order ? + _("top layers first") : _("bottom layers first")); + layers_as_pages_c = gtk_check_button_new_with_mnemonic (text); + g_free (text); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (layers_as_pages_c), + optimize.layers_as_pages); + gtk_frame_set_label_widget (GTK_FRAME (frame), layers_as_pages_c); + g_free (gimp_image_get_layers (multi_page.images[0], &n_layers)); + + reverse_order_c = gtk_check_button_new_with_mnemonic (_("_Reverse the pages order")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (reverse_order_c), + optimize.reverse_order); + gtk_container_add (GTK_CONTAINER (frame), reverse_order_c); + + if (n_layers <= 1) + { + gtk_widget_set_sensitive (layers_as_pages_c, FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (layers_as_pages_c), + FALSE); + } + + g_object_bind_property (layers_as_pages_c, "active", + reverse_order_c, "sensitive", + G_BINDING_SYNC_CREATE); + g_signal_connect (G_OBJECT (reverse_order_c), "toggled", + G_CALLBACK (reverse_order_toggled), + layers_as_pages_c); + + gtk_widget_show_all (window); + + run = gtk_dialog_run (GTK_DIALOG (window)) == GTK_RESPONSE_OK; + + optimize.ignore_hidden = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ignore_hidden_c)); + optimize.vectorize = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (vectorize_c)); + optimize.apply_masks = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_c)); + optimize.layers_as_pages = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (layers_as_pages_c)); + optimize.reverse_order = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (reverse_order_c)); + + gtk_widget_destroy (window); + + return run; +} + +/* The main GUI function for saving multi-paged PDFs */ + +static gboolean +gui_multi (void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *file_label; + GtkWidget *file_entry; + GtkWidget *file_browse; + GtkWidget *file_hbox; + GtkWidget *vectorize_c; + GtkWidget *ignore_hidden_c; + GtkWidget *apply_c; + GtkWidget *scroll; + GtkWidget *page_view; + GtkWidget *h_but_box; + GtkWidget *del; + GtkWidget *h_box; + GtkWidget *img_combo; + GtkWidget *add_image; + gboolean run; + const gchar *temp; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + window = gimp_export_dialog_new ("PDF", PLUG_IN_ROLE, SAVE_MULTI_PROC); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (window)), + vbox, TRUE, TRUE, 0); + + gtk_container_set_border_width (GTK_CONTAINER (window), 12); + + file_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + file_label = gtk_label_new (_("Save to:")); + file_entry = gtk_entry_new (); + if (file_name != NULL) + gtk_entry_set_text (GTK_ENTRY (file_entry), file_name); + file_browse = gtk_button_new_with_label (_("Browse...")); + file_choose = gtk_file_chooser_dialog_new (_("Multipage PDF export"), + GTK_WINDOW (window), + GTK_FILE_CHOOSER_ACTION_SAVE, + _("_Save"), GTK_RESPONSE_OK, + _("_Cancel"), GTK_RESPONSE_CANCEL, + NULL); + + gtk_box_pack_start (GTK_BOX (file_hbox), file_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (file_hbox), file_entry, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (file_hbox), file_browse, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (vbox), file_hbox, TRUE, TRUE, 0); + + page_view = gtk_icon_view_new (); + model = create_model (); + gtk_icon_view_set_model (GTK_ICON_VIEW (page_view), model); + gtk_icon_view_set_reorderable (GTK_ICON_VIEW (page_view), TRUE); + gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (page_view), + GTK_SELECTION_MULTIPLE); + + gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (page_view), THUMB); + gtk_icon_view_set_text_column (GTK_ICON_VIEW (page_view), PAGE_NUMBER); + gtk_icon_view_set_tooltip_column (GTK_ICON_VIEW (page_view), IMAGE_NAME); + + scroll = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (scroll, -1, 300); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_container_add (GTK_CONTAINER (scroll), page_view); + + gtk_box_pack_start (GTK_BOX (vbox), scroll, TRUE, TRUE, 0); + + h_but_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout (GTK_BUTTON_BOX (h_but_box), GTK_BUTTONBOX_START); + + del = gtk_button_new_with_label (_("Remove the selected pages")); + gtk_box_pack_start (GTK_BOX (h_but_box), del, TRUE, TRUE, 0); + + gtk_box_pack_start (GTK_BOX (vbox), h_but_box, FALSE, FALSE, 0); + + h_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + + img_combo = gimp_image_combo_box_new (NULL, NULL); + gtk_box_pack_start (GTK_BOX (h_box), img_combo, FALSE, FALSE, 0); + + add_image = gtk_button_new_with_label (_("Add this image")); + gtk_box_pack_start (GTK_BOX (h_box), add_image, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (vbox), h_box, FALSE, FALSE, 0); + + ignore_hidden_c = gtk_check_button_new_with_mnemonic (_("_Omit hidden layers and layers with zero opacity")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ignore_hidden_c), + optimize.ignore_hidden); + gtk_box_pack_end (GTK_BOX (vbox), ignore_hidden_c, FALSE, FALSE, 0); + + vectorize_c = gtk_check_button_new_with_mnemonic (_("Convert _bitmaps to vector graphics where possible")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (vectorize_c), + optimize.vectorize); + gtk_box_pack_end (GTK_BOX (vbox), vectorize_c, FALSE, FALSE, 0); + + apply_c = gtk_check_button_new_with_mnemonic (_("_Apply layer masks before saving")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_c), + optimize.apply_masks); + gtk_box_pack_end (GTK_BOX (vbox), apply_c, FALSE, FALSE, 0); + gimp_help_set_help_data (apply_c, _("Keeping the masks will not change the output"), NULL); + + gtk_widget_show_all (window); + + g_signal_connect (G_OBJECT (file_browse), "clicked", + G_CALLBACK (choose_file_call), + file_entry); + + g_signal_connect (G_OBJECT (add_image), "clicked", + G_CALLBACK (add_image_call), + img_combo); + + g_signal_connect (G_OBJECT (del), "clicked", + G_CALLBACK (del_image_call), + page_view); + + g_signal_connect (G_OBJECT (model), "row-deleted", + G_CALLBACK (remove_call), + NULL); + + run = gtk_dialog_run (GTK_DIALOG (window)) == GTK_RESPONSE_OK; + + run &= get_image_list (); + + temp = gtk_entry_get_text (GTK_ENTRY (file_entry)); + g_stpcpy (file_name, temp); + + optimize.ignore_hidden = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ignore_hidden_c)); + optimize.vectorize = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (vectorize_c)); + optimize.apply_masks = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_c)); + + gtk_widget_destroy (window); + + return run; +} + +static void +reverse_order_toggled (GtkToggleButton *reverse_order, + GtkButton *layers_as_pages) +{ + gchar *text; + + text = g_strdup_printf (_("Layers as pages (%s)"), + gtk_toggle_button_get_active (reverse_order) ? + _("top layers first") : _("bottom layers first")); + gtk_button_set_label (layers_as_pages, text); + g_free (text); +} + +/* A function that is called when the button for browsing for file + * locations was clicked + */ +static void +choose_file_call (GtkWidget *browse_button, + gpointer file_entry) +{ + GFile *file = g_file_new_for_path (gtk_entry_get_text (GTK_ENTRY (file_entry))); + + gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (file_choose), + g_file_get_uri (file)); + + if (gtk_dialog_run (GTK_DIALOG (file_choose)) == GTK_RESPONSE_OK) + { + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (file_choose)); + gtk_entry_set_text (GTK_ENTRY (file_entry), g_file_get_path (file)); + } + + file_name = g_file_get_path (file); + gtk_widget_hide (file_choose); +} + +/* A function to create the basic GtkTreeModel for the icon view */ +static GtkTreeModel* +create_model (void) +{ + GtkListStore *model; + guint32 i; + + /* validate_image_list was called earlier, so all the images + * up to multi_page.image_count are valid + */ + model = gtk_list_store_new (4, + GDK_TYPE_PIXBUF, /* THUMB */ + G_TYPE_STRING, /* PAGE_NUMBER */ + G_TYPE_STRING, /* IMAGE_NAME */ + G_TYPE_INT); /* IMAGE_ID */ + + for (i = 0 ; i < multi_page.image_count && i < MAX_PAGE_COUNT ; i++) + { + GtkTreeIter iter; + gint32 image = multi_page.images[i]; + GdkPixbuf *pixbuf; + + pixbuf = gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT, + GIMP_PIXBUF_SMALL_CHECKS); + + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, + THUMB, pixbuf, + PAGE_NUMBER, g_strdup_printf (_("Page %d"), i + 1), + IMAGE_NAME, gimp_image_get_name (image), + IMAGE_ID, image, + -1); + + g_object_unref (pixbuf); + } + + return GTK_TREE_MODEL (model); +} + +/* A function that puts the images from the model inside the images + * (pages) array + */ +static gboolean +get_image_list (void) +{ + GtkTreeIter iter; + gboolean valid; + + multi_page.image_count = 0; + + for (valid = gtk_tree_model_get_iter_first (model, &iter); + valid; + valid = gtk_tree_model_iter_next (model, &iter)) + { + gint32 image; + + gtk_tree_model_get (model, &iter, + IMAGE_ID, &image, + -1); + multi_page.images[multi_page.image_count] = image; + multi_page.image_count++; + } + + if (multi_page.image_count == 0) + { + g_message (_("Error! In order to save the file, at least one image " + "should be added!")); + return FALSE; + } + + return TRUE; +} + +/* A function that is called when the button for adding an image was + * clicked + */ +static void +add_image_call (GtkWidget *widget, + gpointer img_combo) +{ + GtkListStore *store; + GtkTreeIter iter; + gint32 image; + GdkPixbuf *pixbuf; + + dnd_remove = FALSE; + + gimp_int_combo_box_get_active (img_combo, &image); + + store = GTK_LIST_STORE (model); + + pixbuf = gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT, + GIMP_PIXBUF_SMALL_CHECKS); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + PAGE_NUMBER, g_strdup_printf (_("Page %d"), + multi_page.image_count+1), + THUMB, pixbuf, + IMAGE_NAME, gimp_image_get_name (image), + IMAGE_ID, image, + -1); + + g_object_unref (pixbuf); + + multi_page.image_count++; + + dnd_remove = TRUE; +} + +/* A function that is called when the button for deleting the selected + * images was clicked + */ +static void +del_image_call (GtkWidget *widget, + gpointer icon_view) +{ + GList *list; + GtkTreeRowReference **items; + GtkTreePath *item_path; + GtkTreeIter item; + gpointer temp; + guint32 len; + + dnd_remove = FALSE; + + list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view)); + + len = g_list_length (list); + if (len > 0) + { + gint i; + + items = g_newa (GtkTreeRowReference*, len); + + for (i = 0; i < len; i++) + { + temp = g_list_nth_data (list, i); + items[i] = gtk_tree_row_reference_new (model, temp); + gtk_tree_path_free (temp); + } + g_list_free (list); + + for (i = 0; i < len; i++) + { + item_path = gtk_tree_row_reference_get_path (items[i]); + + gtk_tree_model_get_iter (model, &item, item_path); + gtk_list_store_remove (GTK_LIST_STORE (model), &item); + + gtk_tree_path_free (item_path); + + gtk_tree_row_reference_free (items[i]); + multi_page.image_count--; + } + } + + dnd_remove = TRUE; + + recount_pages (); +} + +/* A function that is called on rows-deleted signal. It will call the + * function to relabel the pages + */ +static void +remove_call (GtkTreeModel *tree_model, + GtkTreePath *path, + gpointer user_data) +{ + + if (dnd_remove) + /* The gtk documentation says that we should not free the indices array */ + recount_pages (); +} + +/* A function to relabel the pages in the icon view, when their order + * was changed + */ +static void +recount_pages (void) +{ + GtkListStore *store; + GtkTreeIter iter; + gboolean valid; + gint32 i = 0; + + store = GTK_LIST_STORE (model); + + for (valid = gtk_tree_model_get_iter_first (model, &iter); + valid; + valid = gtk_tree_model_iter_next (model, &iter)) + { + gtk_list_store_set (store, &iter, + PAGE_NUMBER, g_strdup_printf (_("Page %d"), i + 1), + -1); + i++; + } +} + + +/******************************************************/ +/* Beginning of the actual PDF functions */ +/******************************************************/ + +static cairo_surface_t * +get_cairo_surface (gint32 drawable_ID, + gboolean as_mask, + GError **error) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + cairo_surface_t *surface; + cairo_status_t status; + cairo_format_t format; + gint width; + gint height; + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (src_buffer); + height = gegl_buffer_get_height (src_buffer); + + if (as_mask) + format = CAIRO_FORMAT_A8; + else if (gimp_drawable_has_alpha (drawable_ID)) + format = CAIRO_FORMAT_ARGB32; + else + format = CAIRO_FORMAT_RGB24; + + surface = cairo_image_surface_create (format, width, height); + + status = cairo_surface_status (surface); + if (status != CAIRO_STATUS_SUCCESS) + { + switch (status) + { + case CAIRO_STATUS_INVALID_SIZE: + g_set_error_literal (error, + GIMP_PLUGIN_PDF_SAVE_ERROR, + GIMP_PLUGIN_PDF_SAVE_ERROR_FAILED, + _("Cannot handle the size (either width or height) of the image.")); + break; + default: + g_set_error (error, + GIMP_PLUGIN_PDF_SAVE_ERROR, + GIMP_PLUGIN_PDF_SAVE_ERROR_FAILED, + "Cairo error: %s", + cairo_status_to_string (status)); + break; + } + + return NULL; + } + + dest_buffer = gimp_cairo_surface_create_buffer (surface); + if (as_mask) + { + /* src_buffer represents a mask in "Y u8", "Y u16", etc. formats. + * Yet cairo_mask*() functions only care about the alpha channel of + * the surface. Hence I change the format of dest_buffer so that the + * Y channel of src_buffer actually refers to the A channel of + * dest_buffer/surface in Cairo. + */ + gegl_buffer_set_format (dest_buffer, babl_format ("Y u8")); + } + + gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dest_buffer, NULL); + + cairo_surface_mark_dirty (surface); + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + return surface; +} + +/* A function to check if a drawable is single colored This allows to + * convert bitmaps to vector where possible + */ +static GimpRGB +get_layer_color (gint32 layer_ID, + gboolean *single) +{ + GimpRGB col; + gdouble red, green, blue, alpha; + gdouble dev, devSum; + gdouble median, pixels, count, precentile; + + devSum = 0; + red = 0; + green = 0; + blue = 0; + alpha = 0; + dev = 0; + + if (gimp_drawable_is_indexed (layer_ID)) + { + /* FIXME: We can't do a proper histogram on indexed layers! */ + *single = FALSE; + col. r = col.g = col.b = col.a = 0; + return col; + } + + if (gimp_drawable_bpp (layer_ID) >= 3) + { + /* Are we in RGB mode? */ + + gimp_drawable_histogram (layer_ID, GIMP_HISTOGRAM_RED, 0.0, 1.0, + &red, &dev, &median, &pixels, &count, &precentile); + devSum += dev; + gimp_drawable_histogram (layer_ID, GIMP_HISTOGRAM_GREEN, 0.0, 1.0, + &green, &dev, &median, &pixels, &count, &precentile); + devSum += dev; + gimp_drawable_histogram (layer_ID, GIMP_HISTOGRAM_BLUE, 0.0, 1.0, + &blue, &dev, &median, &pixels, &count, &precentile); + devSum += dev; + } + else + { + /* We are in Grayscale mode (or Indexed) */ + + gimp_drawable_histogram (layer_ID, GIMP_HISTOGRAM_VALUE, 0.0, 1.0, + &red, &dev, &median, &pixels, &count, &precentile); + devSum += dev; + green = red; + blue = red; + } + + if (gimp_drawable_has_alpha (layer_ID)) + gimp_drawable_histogram (layer_ID, GIMP_HISTOGRAM_ALPHA, 0.0, 1.0, + &alpha, &dev, &median, &pixels, &count, &precentile); + else + alpha = 255; + + devSum += dev; + *single = devSum == 0; + + col.r = red/255; + col.g = green/255; + col.b = blue/255; + col.a = alpha/255; + + return col; +} + +/* A function that uses Pango to render the text to our cairo surface, + * in the same way it was the user saw it inside gimp. + * Needs some work on choosing the font name better, and on hinting + * (freetype and pango differences) + */ +static void +drawText (gint32 text_id, + gdouble opacity, + cairo_t *cr, + gdouble x_res, + gdouble y_res) +{ + GimpImageType type = gimp_drawable_type (text_id); + gchar *text = gimp_text_layer_get_text (text_id); + gchar *markup = gimp_text_layer_get_markup (text_id); + gchar *font_family; + gchar *language; + cairo_font_options_t *options; + gint x; + gint y; + GimpRGB color; + GimpUnit unit; + gdouble size; + GimpTextHintStyle hinting; + GimpTextJustification j; + gboolean justify; + PangoAlignment align; + GimpTextDirection dir; + PangoLayout *layout; + PangoContext *context; + PangoFontDescription *font_description; + gdouble indent; + gdouble line_spacing; + gdouble letter_spacing; + PangoAttribute *letter_spacing_at; + PangoAttrList *attr_list = pango_attr_list_new (); + PangoFontMap *fontmap; + + cairo_save (cr); + + options = cairo_font_options_create (); + attr_list = pango_attr_list_new (); + cairo_get_font_options (cr, options); + + /* Position */ + gimp_drawable_offsets (text_id, &x, &y); + cairo_translate (cr, x, y); + + /* Color */ + /* When dealing with a gray/indexed image, the viewed color of the text layer + * can be different than the one kept in the memory */ + if (type == GIMP_RGBA_IMAGE) + gimp_text_layer_get_color (text_id, &color); + else + gimp_image_pick_color (gimp_item_get_image (text_id), + text_id, x, y, FALSE, FALSE, 0, &color); + + cairo_set_source_rgba (cr, color.r, color.g, color.b, opacity); + + /* Hinting */ + hinting = gimp_text_layer_get_hint_style (text_id); + switch (hinting) + { + case GIMP_TEXT_HINT_STYLE_NONE: + cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE); + break; + + case GIMP_TEXT_HINT_STYLE_SLIGHT: + cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_SLIGHT); + break; + + case GIMP_TEXT_HINT_STYLE_MEDIUM: + cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_MEDIUM); + break; + + case GIMP_TEXT_HINT_STYLE_FULL: + cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_FULL); + break; + } + + /* Antialiasing */ + if (gimp_text_layer_get_antialias (text_id)) + cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_DEFAULT); + else + cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_NONE); + + /* We are done with cairo's settings. It's time to create the + * context + */ + fontmap = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT); + + pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (fontmap), y_res); + + context = pango_font_map_create_context (fontmap); + g_object_unref (fontmap); + + pango_cairo_context_set_font_options (context, options); + + /* Language */ + language = gimp_text_layer_get_language (text_id); + if (language) + pango_context_set_language (context, + pango_language_from_string(language)); + + /* Text Direction */ + dir = gimp_text_layer_get_base_direction (text_id); + + switch (dir) + { + case GIMP_TEXT_DIRECTION_LTR: + pango_context_set_base_dir (context, PANGO_DIRECTION_LTR); + pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_NATURAL); + pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH); + break; + + case GIMP_TEXT_DIRECTION_RTL: + pango_context_set_base_dir (context, PANGO_DIRECTION_RTL); + pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_NATURAL); + pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH); + break; + + case GIMP_TEXT_DIRECTION_TTB_RTL: + pango_context_set_base_dir (context, PANGO_DIRECTION_LTR); + pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_LINE); + pango_context_set_base_gravity (context, PANGO_GRAVITY_EAST); + break; + + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + pango_context_set_base_dir (context, PANGO_DIRECTION_LTR); + pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_STRONG); + pango_context_set_base_gravity (context, PANGO_GRAVITY_EAST); + break; + + case GIMP_TEXT_DIRECTION_TTB_LTR: + pango_context_set_base_dir (context, PANGO_DIRECTION_LTR); + pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_LINE); + pango_context_set_base_gravity (context, PANGO_GRAVITY_WEST); + break; + + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + pango_context_set_base_dir (context, PANGO_DIRECTION_LTR); + pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_STRONG); + pango_context_set_base_gravity (context, PANGO_GRAVITY_WEST); + break; + } + + /* We are done with the context's settings. It's time to create the + * layout + */ + layout = pango_layout_new (context); + pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR); + + /* Font */ + font_family = gimp_text_layer_get_font (text_id); + + /* We need to find a way to convert GIMP's returned font name to a + * normal Pango name... Hopefully GIMP 2.8 with Pango will fix it. + */ + font_description = pango_font_description_from_string (font_family); + + /* Font Size */ + size = gimp_text_layer_get_font_size (text_id, &unit); + size = gimp_units_to_pixels (size, unit, y_res); + pango_font_description_set_absolute_size (font_description, size * PANGO_SCALE); + + pango_layout_set_font_description (layout, font_description); + + /* Width */ + if (! PANGO_GRAVITY_IS_VERTICAL (pango_context_get_base_gravity (context))) + pango_layout_set_width (layout, gimp_drawable_width (text_id) * PANGO_SCALE); + else + pango_layout_set_width (layout, gimp_drawable_height (text_id) * PANGO_SCALE); + + /* Justification, and Alignment */ + justify = FALSE; + j = gimp_text_layer_get_justification (text_id); + align = PANGO_ALIGN_LEFT; + switch (j) + { + case GIMP_TEXT_JUSTIFY_LEFT: + align = PANGO_ALIGN_LEFT; + break; + case GIMP_TEXT_JUSTIFY_RIGHT: + align = PANGO_ALIGN_RIGHT; + break; + case GIMP_TEXT_JUSTIFY_CENTER: + align = PANGO_ALIGN_CENTER; + break; + case GIMP_TEXT_JUSTIFY_FILL: + align = PANGO_ALIGN_LEFT; + justify = TRUE; + break; + } + pango_layout_set_alignment (layout, align); + pango_layout_set_justify (layout, justify); + + /* Indentation */ + indent = gimp_text_layer_get_indent (text_id); + pango_layout_set_indent (layout, (int)(PANGO_SCALE * indent)); + + /* Line Spacing */ + line_spacing = gimp_text_layer_get_line_spacing (text_id); + pango_layout_set_spacing (layout, (int)(PANGO_SCALE * line_spacing)); + + /* Letter Spacing */ + letter_spacing = gimp_text_layer_get_letter_spacing (text_id); + letter_spacing_at = pango_attr_letter_spacing_new ((int)(PANGO_SCALE * letter_spacing)); + pango_attr_list_insert (attr_list, letter_spacing_at); + + + pango_layout_set_attributes (layout, attr_list); + + /* Use the pango markup of the text layer */ + + if (markup != NULL && markup[0] != '\0') + pango_layout_set_markup (layout, markup, -1); + else /* If we can't find a markup, then it has just text */ + pango_layout_set_text (layout, text, -1); + + if (dir == GIMP_TEXT_DIRECTION_TTB_RTL || + dir == GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT) + { + cairo_translate (cr, gimp_drawable_width (text_id), 0); + cairo_rotate (cr, G_PI_2); + } + + if (dir == GIMP_TEXT_DIRECTION_TTB_LTR || + dir == GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT) + { + cairo_translate (cr, 0, gimp_drawable_height (text_id)); + cairo_rotate (cr, -G_PI_2); + } + + pango_cairo_show_layout (cr, layout); + + g_free (text); + g_free (font_family); + g_free (language); + + g_object_unref (layout); + pango_font_description_free (font_description); + g_object_unref (context); + pango_attr_list_unref (attr_list); + + cairo_font_options_destroy (options); + + cairo_restore (cr); +} + +static gboolean +draw_layer (gint32 *layers, + gint n_layers, + gint j, + cairo_t *cr, + gdouble x_res, + gdouble y_res, + const gchar *name, + GError **error) +{ + gint32 layer_ID; + + if (optimize.reverse_order && optimize.layers_as_pages) + layer_ID = layers [j]; + else + layer_ID = layers [n_layers - j - 1]; + + if (gimp_item_is_group (layer_ID)) + { + gint *children; + gint children_num; + gint i; + + children = gimp_item_get_children (layer_ID, &children_num); + for (i = 0; i < children_num; i++) + { + if (! draw_layer (children, children_num, i, + cr, x_res, y_res, name, error)) + { + g_free (children); + return FALSE; + } + } + g_free (children); + } + else + { + cairo_surface_t *mask_image = NULL; + gint32 mask_ID = -1; + gdouble opacity; + + opacity = gimp_layer_get_opacity (layer_ID) / 100.0; + + if ((gimp_item_get_visible (layer_ID) && opacity > 0.0) || + ! optimize.ignore_hidden) + { + gint x, y; + + mask_ID = gimp_layer_get_mask (layer_ID); + if (mask_ID != -1) + { + mask_image = get_cairo_surface (mask_ID, TRUE, error); + + if (*error) + return FALSE; + } + gimp_drawable_offsets (layer_ID, &x, &y); + + if (! gimp_item_is_text_layer (layer_ID)) + { + /* For raster layers */ + + GimpRGB layer_color; + gboolean single_color = FALSE; + + layer_color = get_layer_color (layer_ID, &single_color); + + cairo_rectangle (cr, x, y, + gimp_drawable_width (layer_ID), + gimp_drawable_height (layer_ID)); + + if (optimize.vectorize && single_color) + { + cairo_set_source_rgba (cr, + layer_color.r, + layer_color.g, + layer_color.b, + layer_color.a * opacity); + if (mask_ID != -1) + cairo_mask_surface (cr, mask_image, x, y); + else + cairo_fill (cr); + } + else + { + cairo_surface_t *layer_image; + + layer_image = get_cairo_surface (layer_ID, FALSE, error); + + if (*error) + return FALSE; + + cairo_clip (cr); + + cairo_set_source_surface (cr, layer_image, x, y); + cairo_push_group (cr); + cairo_paint_with_alpha (cr, opacity); + cairo_pop_group_to_source (cr); + + if (mask_ID != -1) + cairo_mask_surface (cr, mask_image, x, y); + else + cairo_paint (cr); + + cairo_reset_clip (cr); + + cairo_surface_destroy (layer_image); + } + } + else + { + /* For text layers */ + drawText (layer_ID, opacity, cr, x_res, y_res); + } + /* draw new page if "layers as pages" option is checked */ + if (optimize.layers_as_pages && + g_strcmp0 (name, SAVE2_PROC) == 0) + cairo_show_page (cr); + } + /* We are done with the layer - time to free some resources */ + if (mask_ID != -1) + cairo_surface_destroy (mask_image); + } + + return TRUE; +} diff --git a/plug-ins/common/file-pix.c b/plug-ins/common/file-pix.c new file mode 100644 index 0000000..751a5a7 --- /dev/null +++ b/plug-ins/common/file-pix.c @@ -0,0 +1,736 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * Alias|Wavefront pix/matte image reading and writing code + * Copyright (C) 1997 Mike Taylor + * (email: mtaylor@aw.sgi.com, WWW: http://reality.sgi.com/mtaylor) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* This plug-in was written using the online documentation from + * Alias|Wavefront Inc's PowerAnimator product. + * + * Bug reports or suggestions should be e-mailed to mtaylor@aw.sgi.com + */ + +/* Event history: + * V 1.0, MT, 02-Jul-97: initial version of plug-in + * V 1.1, MT, 04-Dec-97: added .als file extension + */ + +/* Features + * - loads and exports + * - 24-bit (.pix) + * - 8-bit (.matte, .alpha, or .mask) images + * + * NOTE: pix and matte files do not support alpha channels or indexed + * color, so neither does this plug-in + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-pix-load" +#define SAVE_PROC "file-pix-save" +#define PLUG_IN_BINARY "file-pix" +#define PLUG_IN_ROLE "gimp-file-pix" + + +/* #define PIX_DEBUG */ + +#ifdef PIX_DEBUG +# define PIX_DEBUG_PRINT(a,b) g_printerr (a,b) +#else +# define PIX_DEBUG_PRINT(a,b) +#endif + + +/************** + * Prototypes * + **************/ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (GFile *file, + GError **error); +static gboolean save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + +static gboolean get_short (GInputStream *input, + guint16 *value, + GError **error); +static gboolean put_short (GOutputStream *output, + guint16 value, + GError **error); + + +/****************** + * Implementation * + ******************/ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + /* + * Description: + * Register the services provided by this plug-in + */ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" } + }; + + gimp_install_procedure (LOAD_PROC, + "loads files of the Alias|Wavefront Pix file format", + "loads files of the Alias|Wavefront Pix file format", + "Michael Taylor", + "Michael Taylor", + "1997", + N_("Alias Pix image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_uri (LOAD_PROC); + gimp_register_load_handler (LOAD_PROC, "pix,matte,mask,alpha,als", ""); + + gimp_install_procedure (SAVE_PROC, + "export file in the Alias|Wavefront pix/matte file format", + "export file in the Alias|Wavefront pix/matte file format", + "Michael Taylor", + "Michael Taylor", + "1997", + N_("Alias Pix image"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "pix,matte,mask,alpha,als", ""); +} + +/* + * Description: + * perform registered plug-in function + * + * Arguments: + * name - name of the function to perform + * nparams - number of parameters passed to the function + * param - parameters passed to the function + * nreturn_vals - number of parameters returned by the function + * return_vals - parameters returned by the function + */ + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + /* Perform the image load */ + image_ID = load_image (g_file_new_for_uri (param[1].data.d_string), + &error); + + if (image_ID != -1) + { + /* The image load was successful */ + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + /* The image load failed */ + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "PIX", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (! save_image (g_file_new_for_uri (param[3].data.d_string), + image_ID, drawable_ID, + &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +/* + * Description: + * Reads a 16-bit integer from a file in such a way that the machine's + * byte order should not matter. + */ + +static gboolean +get_short (GInputStream *input, + guint16 *value, + GError **error) +{ + guchar buf[2]; + gsize bytes_read; + + if (! g_input_stream_read_all (input, buf, 2, + &bytes_read, NULL, error) || + bytes_read != 2) + { + return FALSE; + } + + if (value) + *value = (buf[0] << 8) + buf[1]; + + return TRUE; +} + +/* + * Description: + * Writes a 16-bit integer to a file in such a way that the machine's + * byte order should not matter. + */ + +static gboolean +put_short (GOutputStream *output, + guint16 value, + GError **error) +{ + guchar buf[2]; + + buf[0] = (value >> 8) & 0xFF; + buf[1] = value & 0xFF; + + return g_output_stream_write_all (output, buf, 2, NULL, NULL, error); +} + +/* + * Description: + * load the given image into gimp + * + * Arguments: + * filename - name on the file to read + * + * Return Value: + * Image id for the loaded image + * + */ + +static gint32 +load_image (GFile *file, + GError **error) +{ + GInputStream *input; + GeglBuffer *buffer; + GimpImageBaseType imgtype; + GimpImageType gdtype; + guchar *dest; + guchar *dest_base; + gint32 image_ID; + gint32 layer_ID; + gushort width, height, depth; + gint i, j, tile_height, row; + + PIX_DEBUG_PRINT ("Opening file: %s\n", filename); + + gimp_progress_init_printf (_("Opening '%s'"), + g_file_get_parse_name (file)); + + input = G_INPUT_STREAM (g_file_read (file, NULL, error)); + if (! input) + return -1; + + /* Read header information */ + if (! get_short (input, &width, error) || + ! get_short (input, &height, error) || + ! get_short (input, NULL, error) || /* Discard obsolete field */ + ! get_short (input, NULL, error) || /* Discard obsolete field */ + ! get_short (input, &depth, error)) + { + g_object_unref (input); + return -1; + } + + PIX_DEBUG_PRINT ("Width %hu\n", width); + PIX_DEBUG_PRINT ("Height %hu\n", height); + + if (depth == 8) + { + /* Loading a matte file */ + imgtype = GIMP_GRAY; + gdtype = GIMP_GRAY_IMAGE; + } + else if (depth == 24) + { + /* Loading an RGB file */ + imgtype = GIMP_RGB; + gdtype = GIMP_RGB_IMAGE; + } + else + { + /* Header is invalid */ + g_object_unref (input); + return -1; + } + + image_ID = gimp_image_new (width, height, imgtype); + gimp_image_set_filename (image_ID, g_file_get_uri (file)); + + layer_ID = gimp_layer_new (image_ID, _("Background"), + width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_ID); + + tile_height = gimp_tile_height (); + + if (depth == 24) + { + /* Read a 24-bit Pix image */ + + dest_base = dest = g_new (guchar, 3 * width * tile_height); + + for (i = 0; i < height;) + { + for (dest = dest_base, row = 0; + row < tile_height && i < height; + i++, row++) + { + guchar record[4]; + gsize bytes_read; + guchar count; + + /* Read a row of the image */ + j = 0; + while (j < width) + { + if (! g_input_stream_read_all (input, record, 4, + &bytes_read, NULL, error) || + bytes_read != 4) + break; + + for (count = 0; count < record[0]; ++count) + { + dest[0] = record[3]; + dest[1] = record[2]; + dest[2] = record[1]; + dest += 3; + j++; + if (j >= width) + break; + } + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - row, width, row), 0, + NULL, dest_base, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) i / (double) height); + } + + g_free (dest_base); + } + else + { + /* Read an 8-bit Matte image */ + + dest_base = dest = g_new (guchar, width * tile_height); + + for (i = 0; i < height;) + { + for (dest = dest_base, row = 0; + row < tile_height && i < height; + i++, row++) + { + guchar record[2]; + gsize bytes_read; + guchar count; + + /* Read a row of the image */ + j = 0; + while (j < width) + { + if (! g_input_stream_read_all (input, record, 2, + &bytes_read, NULL, error) || + bytes_read != 2) + break; + + for (count = 0; count < record[0]; ++count) + { + dest[j] = record[1]; + j++; + if (j >= width) + break; + } + } + + dest += width; + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - row, width, row), 0, + NULL, dest_base, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) i / (double) height); + } + + g_free (dest_base); + } + + g_object_unref (buffer); + g_object_unref (input); + + gimp_progress_update (1.0); + + return image_ID; +} + +/* + * Description: + * save the given file out as an alias pix or matte file + * + * Arguments: + * filename - name of file to save to + * image_ID - ID of image to save + * drawable_ID - current drawable + */ + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GOutputStream *output; + GeglBuffer *buffer; + const Babl *format; + GCancellable *cancellable; + gint width; + gint height; + gint depth, i, j, row, tile_height, rectHeight; + gboolean savingColor = TRUE; + guchar *src; + guchar *src_base; + + gimp_progress_init_printf (_("Exporting '%s'"), + g_file_get_parse_name (file)); + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (! output) + return FALSE; + + /* Get info about image */ + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + savingColor = ! gimp_drawable_is_gray (drawable_ID); + + if (savingColor) + format = babl_format ("R'G'B' u8"); + else + format = babl_format ("Y' u8"); + + depth = babl_format_get_bytes_per_pixel (format); + + /* Write the image header */ + PIX_DEBUG_PRINT ("Width %hu\n", width); + PIX_DEBUG_PRINT ("Height %hu\n", height); + + if (! put_short (output, width, error) || + ! put_short (output, height, error) || + ! put_short (output, 0, error) || + ! put_short (output, 0, error)) + { + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + + g_object_unref (output); + g_object_unref (buffer); + return FALSE; + } + + tile_height = gimp_tile_height (); + src_base = g_new (guchar, tile_height * width * depth); + + if (savingColor) + { + /* Writing a 24-bit Pix image */ + + if (! put_short (output, 24, error)) + goto fail; + + for (i = 0; i < height;) + { + rectHeight = (tile_height < (height - i)) ? + tile_height : (height - i); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, rectHeight), 1.0, + format, src_base, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (src = src_base, row = 0; + row < tile_height && i < height; + i += 1, row += 1) + { + /* Write a row of the image */ + + guchar record[4]; + + record[0] = 1; + record[3] = src[0]; + record[2] = src[1]; + record[1] = src[2]; + src += depth; + for (j = 1; j < width; ++j) + { + if ((record[3] != src[0]) || + (record[2] != src[1]) || + (record[1] != src[2]) || + (record[0] == 255)) + { + /* Write current RLE record and start a new one */ + + if (! g_output_stream_write_all (output, record, 4, + NULL, NULL, error)) + { + goto fail; + } + + record[0] = 1; + record[3] = src[0]; + record[2] = src[1]; + record[1] = src[2]; + } + else + { + /* increment run length in current record */ + record[0]++; + } + src += depth; + } + + /* Write last record in row */ + + if (! g_output_stream_write_all (output, record, 4, + NULL, NULL, error)) + { + goto fail; + } + } + + gimp_progress_update ((double) i / (double) height); + } + } + else + { + /* Writing a 8-bit Matte (Mask) image */ + + if (! put_short (output, 8, error)) + goto fail; + + for (i = 0; i < height;) + { + rectHeight = (tile_height < (height - i)) ? + tile_height : (height - i); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, rectHeight), 1.0, + format, src_base, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (src = src_base, row = 0; + row < tile_height && i < height; + i += 1, row += 1) + { + /* Write a row of the image */ + + guchar record[2]; + + record[0] = 1; + record[1] = src[0]; + src += depth; + for (j = 1; j < width; ++j) + { + if ((record[1] != src[0]) || (record[0] == 255)) + { + /* Write current RLE record and start a new one */ + + if (! g_output_stream_write_all (output, record, 2, + NULL, NULL, error)) + { + goto fail; + } + + record[0] = 1; + record[1] = src[0]; + } + else + { + /* increment run length in current record */ + record[0] ++; + } + src += depth; + } + + /* Write last record in row */ + + if (! g_output_stream_write_all (output, record, 2, + NULL, NULL, error)) + { + goto fail; + } + } + + gimp_progress_update ((double) i / (double) height); + } + } + + g_free (src_base); + g_object_unref (output); + g_object_unref (buffer); + + gimp_progress_update (1.0); + + return TRUE; + + fail: + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + + g_free (src_base); + g_object_unref (output); + g_object_unref (buffer); + + return FALSE; +} diff --git a/plug-ins/common/file-png.c b/plug-ins/common/file-png.c new file mode 100644 index 0000000..22ec2ff --- /dev/null +++ b/plug-ins/common/file-png.c @@ -0,0 +1,2650 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Portable Network Graphics (PNG) plug-in + * + * Copyright 1997-1998 Michael Sweet (mike@easysw.com) and + * Daniel Skarda (0rfelyus@atrey.karlin.mff.cuni.cz). + * and 1999-2000 Nick Lamb (njl195@zepler.org.uk) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contents: + * + * main() - Main entry - just call gimp_main()... + * query() - Respond to a plug-in query... + * run() - Run the plug-in... + * load_image() - Load a PNG image into a new image window. + * offsets_dialog() - Asks the user about offsets when loading. + * respin_cmap() - Re-order a Gimp colormap for PNG tRNS + * save_image() - Export the specified image to a PNG file. + * save_compression_callback() - Update the image compression level. + * save_interlace_update() - Update the interlacing option. + * save_dialog() - Pop up the export dialog. + * + * Revision History: + * + * see ChangeLog + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include /* PNG library definitions */ + +#include "libgimp/stdplugins-intl.h" + + +/* + * Constants... + */ + +#define LOAD_PROC "file-png-load" +#define SAVE_PROC "file-png-save" +#define SAVE2_PROC "file-png-save2" +#define SAVE_DEFAULTS_PROC "file-png-save-defaults" +#define GET_DEFAULTS_PROC "file-png-get-defaults" +#define SET_DEFAULTS_PROC "file-png-set-defaults" +#define PLUG_IN_BINARY "file-png" +#define PLUG_IN_ROLE "gimp-file-png" + +#define PLUG_IN_VERSION "1.3.4 - 03 September 2002" +#define SCALE_WIDTH 125 + +#define DEFAULT_GAMMA 2.20 + +#define PNG_DEFAULTS_PARASITE "png-save-defaults" + +/* + * Structures... + */ + +typedef enum _PngExportformat { + PNG_FORMAT_AUTO = 0, + PNG_FORMAT_RGB8, + PNG_FORMAT_GRAY8, + PNG_FORMAT_RGBA8, + PNG_FORMAT_GRAYA8, + PNG_FORMAT_RGB16, + PNG_FORMAT_GRAY16, + PNG_FORMAT_RGBA16, + PNG_FORMAT_GRAYA16 +} PngExportFormat; + +typedef struct +{ + gboolean interlaced; + gboolean bkgd; + gboolean gama; + gboolean offs; + gboolean phys; + gboolean time; + gboolean comment; + gboolean save_transp_pixels; + gint compression_level; + gboolean save_exif; + gboolean save_xmp; + gboolean save_iptc; + gboolean save_thumbnail; + gboolean save_profile; + PngExportFormat export_format; +} +PngSaveVals; + +typedef struct +{ + gboolean run; + + GtkWidget *interlaced; + GtkWidget *bkgd; + GtkWidget *gama; + GtkWidget *offs; + GtkWidget *phys; + GtkWidget *time; + GtkWidget *comment; + GtkWidget *pixelformat; + GtkWidget *save_transp_pixels; + GtkAdjustment *compression_level; + GtkWidget *save_exif; + GtkWidget *save_xmp; + GtkWidget *save_iptc; + GtkWidget *save_thumbnail; + GtkWidget *save_profile; +} +PngSaveGui; + +/* These are not saved or restored. */ +typedef struct +{ + gboolean has_trns; + png_bytep trans; + int num_trans; + gboolean has_plte; + png_colorp palette; + int num_palette; +} +PngGlobals; + + +/* + * Local functions... + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + gboolean interactive, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error); +static gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + GError **error); + +static int respin_cmap (png_structp pp, + png_infop info, + guchar *remap, + gint32 image_ID, + gint32 drawable_ID); + +static gboolean save_dialog (gint32 image_ID, + gboolean alpha); + +static void save_dialog_response (GtkWidget *widget, + gint response_id, + gpointer data); + +static gboolean offsets_dialog (gint offset_x, + gint offset_y); + +static gboolean ia_has_transparent_pixels (GeglBuffer *buffer); + +static gint find_unused_ia_color (GeglBuffer *buffer, + gint *colors); + +static void load_parasite (void); +static void save_parasite (void); +static void load_gui_defaults (PngSaveGui *pg); + + +/* + * Globals... + */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run +}; + +static const PngSaveVals defaults = +{ + FALSE, + TRUE, + FALSE, + FALSE, + TRUE, + TRUE, + TRUE, + FALSE, + 9, + FALSE, /* save exif */ + FALSE, /* save xmp */ + FALSE, /* save iptc */ + TRUE, /* save thumbnail */ + PNG_FORMAT_AUTO +}; + +static PngSaveVals pngvals; +static PngGlobals pngg; + + +/* + * 'main()' - Main entry - just call gimp_main()... + */ + +MAIN () + + +/* + * 'query()' - Respond to a plug-in query... + */ + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + +#define COMMON_SAVE_ARGS \ + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, \ + { GIMP_PDB_IMAGE, "image", "Input image" }, \ + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, \ + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in"}, \ + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in"} + +#define OLD_CONFIG_ARGS \ + { GIMP_PDB_INT32, "interlace", "Use Adam7 interlacing?" }, \ + { GIMP_PDB_INT32, "compression", "Deflate Compression factor (0--9)" }, \ + { GIMP_PDB_INT32, "bkgd", "Write bKGD chunk?" }, \ + { GIMP_PDB_INT32, "gama", "Write gAMA chunk?" }, \ + { GIMP_PDB_INT32, "offs", "Write oFFs chunk?" }, \ + { GIMP_PDB_INT32, "phys", "Write pHYs chunk?" }, \ + { GIMP_PDB_INT32, "time", "Write tIME chunk?" } + +#define FULL_CONFIG_ARGS \ + OLD_CONFIG_ARGS, \ + { GIMP_PDB_INT32, "comment", "Write comment?" }, \ + { GIMP_PDB_INT32, "svtrans", "Preserve color of transparent pixels?" } + + static const GimpParamDef save_args[] = + { + COMMON_SAVE_ARGS, + OLD_CONFIG_ARGS + }; + + static const GimpParamDef save_args2[] = + { + COMMON_SAVE_ARGS, + FULL_CONFIG_ARGS + }; + + static const GimpParamDef save_args_defaults[] = + { + COMMON_SAVE_ARGS + }; + + static const GimpParamDef save_get_defaults_return_vals[] = + { + FULL_CONFIG_ARGS + }; + + static const GimpParamDef save_args_set_defaults[] = + { + FULL_CONFIG_ARGS + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in PNG file format", + "This plug-in loads Portable Network Graphics " + "(PNG) files.", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, " + "Nick Lamb ", + PLUG_IN_VERSION, + N_("PNG image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/png"); + gimp_register_magic_load_handler (LOAD_PROC, + "png", "", "0,string,\211PNG\r\n\032\n"); + + gimp_install_procedure (SAVE_PROC, + "Exports files in PNG file format", + "This plug-in exports Portable Network Graphics " + "(PNG) files.", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, " + "Nick Lamb ", + PLUG_IN_VERSION, + N_("PNG image"), + "RGB*,GRAY*,INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (SAVE2_PROC, + "Exports files in PNG file format", + "This plug-in exports Portable Network Graphics " + "(PNG) files. " + "This procedure adds 2 extra parameters to " + "file-png-save that control whether " + "image comments are saved and whether transparent " + "pixels are saved or nullified.", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, " + "Nick Lamb ", + PLUG_IN_VERSION, + N_("PNG image"), + "RGB*,GRAY*,INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args2), 0, + save_args2, NULL); + + gimp_install_procedure (SAVE_DEFAULTS_PROC, + "Exports files in PNG file format", + "This plug-in exports Portable Network Graphics (PNG) " + "files, using the default settings stored as " + "a parasite.", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, " + "Nick Lamb ", + PLUG_IN_VERSION, + N_("PNG image"), + "RGB*,GRAY*,INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args_defaults), 0, + save_args_defaults, NULL); + + gimp_register_file_handler_mime (SAVE_DEFAULTS_PROC, "image/png"); + gimp_register_save_handler (SAVE_DEFAULTS_PROC, "png", ""); + + gimp_install_procedure (GET_DEFAULTS_PROC, + "Get the current set of defaults used by the " + "PNG file export plug-in", + "This procedure returns the current set of " + "defaults stored as a parasite for the PNG " + "export plug-in. " + "These defaults are used to seed the UI, by the " + "file_png_save_defaults procedure, and by " + "gimp_file_save when it detects to use PNG.", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, " + "Nick Lamb ", + PLUG_IN_VERSION, + NULL, + NULL, + GIMP_PLUGIN, + 0, G_N_ELEMENTS (save_get_defaults_return_vals), + NULL, save_get_defaults_return_vals); + + gimp_install_procedure (SET_DEFAULTS_PROC, + "Set the current set of defaults used by the " + "PNG file export plug-in", + "This procedure set the current set of defaults " + "stored as a parasite for the PNG export plug-in. " + "These defaults are used to seed the UI, by the " + "file_png_save_defaults procedure, and by " + "gimp_file_save when it detects to use PNG.", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>", + "Michael Sweet , " + "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, " + "Nick Lamb ", + PLUG_IN_VERSION, + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (save_args_set_defaults), 0, + save_args_set_defaults, NULL); +} + + +/* + * 'run()' - Run the plug-in... + */ + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[10]; + GimpRunMode run_mode = GIMP_RUN_NONINTERACTIVE; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GError *error = NULL; + + if (nparams) + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + gboolean interactive; + gboolean resolution_loaded = FALSE; + gboolean profile_loaded = FALSE; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + interactive = TRUE; + break; + default: + interactive = FALSE; + break; + } + + image_ID = load_image (param[1].data.d_string, + interactive, + &resolution_loaded, + &profile_loaded, + &error); + + if (image_ID != -1) + { + GFile *file = g_file_new_for_path (param[1].data.d_string); + GimpMetadata *metadata; + + metadata = gimp_image_metadata_load_prepare (image_ID, "image/png", + file, NULL); + + if (metadata) + { + GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_ALL; + + if (resolution_loaded) + flags &= ~GIMP_METADATA_LOAD_RESOLUTION; + + if (profile_loaded) + flags &= ~GIMP_METADATA_LOAD_COLORSPACE; + + gimp_image_metadata_load_finish (image_ID, "image/png", + metadata, flags, + interactive); + + g_object_unref (metadata); + } + + g_object_unref (file); + + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0 || + strcmp (name, SAVE2_PROC) == 0 || + strcmp (name, SAVE_DEFAULTS_PROC) == 0) + { + GimpMetadata *metadata; + GimpMetadataSaveFlags metadata_flags; + gint32 orig_image_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + gboolean alpha; + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + orig_image_ID = image_ID; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "PNG", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + *nreturn_vals = 1; + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + /* Initialize with hardcoded defaults */ + pngvals = defaults; + + /* Override the defaults with preferences. */ + metadata = gimp_image_metadata_save_prepare (orig_image_ID, + "image/png", + &metadata_flags); + pngvals.save_exif = (metadata_flags & GIMP_METADATA_SAVE_EXIF) != 0; + pngvals.save_xmp = (metadata_flags & GIMP_METADATA_SAVE_XMP) != 0; + pngvals.save_iptc = (metadata_flags & GIMP_METADATA_SAVE_IPTC) != 0; + pngvals.save_thumbnail = (metadata_flags & GIMP_METADATA_SAVE_THUMBNAIL) != 0; + pngvals.save_profile = (metadata_flags & GIMP_METADATA_SAVE_COLOR_PROFILE) != 0; + + /* Override preferences from PNG export defaults (if saved). */ + load_parasite (); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Finally possibly retrieve data from previous run. */ + gimp_get_data (SAVE_PROC, &pngvals); + + alpha = gimp_drawable_has_alpha (drawable_ID); + + /* If the image has no transparency, then there is usually + * no need to save a bKGD chunk. For more information, see: + * http://bugzilla.gnome.org/show_bug.cgi?id=92395 + */ + if (! alpha) + pngvals.bkgd = FALSE; + + /* Then acquire information with a dialog... + */ + if (! save_dialog (orig_image_ID, alpha)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* + * Make sure all the arguments are there! + */ + if (nparams != 5) + { + if (nparams != 12 && nparams != 14) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + pngvals.interlaced = param[5].data.d_int32; + pngvals.compression_level = param[6].data.d_int32; + pngvals.bkgd = param[7].data.d_int32; + pngvals.gama = param[8].data.d_int32; + pngvals.offs = param[9].data.d_int32; + pngvals.phys = param[10].data.d_int32; + pngvals.time = param[11].data.d_int32; + + if (nparams == 14) + { + pngvals.comment = param[12].data.d_int32; + pngvals.save_transp_pixels = param[13].data.d_int32; + } + else + { + pngvals.comment = TRUE; + pngvals.save_transp_pixels = TRUE; + } + + if (pngvals.compression_level < 0 || + pngvals.compression_level > 9) + { + status = GIMP_PDB_CALLING_ERROR; + } + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* possibly retrieve data */ + gimp_get_data (SAVE_PROC, &pngvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (param[3].data.d_string, + image_ID, drawable_ID, orig_image_ID, &error)) + { + if (metadata) + { + GFile *file; + + gimp_metadata_set_bits_per_sample (metadata, 8); + + if (pngvals.save_exif) + metadata_flags |= GIMP_METADATA_SAVE_EXIF; + else + metadata_flags &= ~GIMP_METADATA_SAVE_EXIF; + + if (pngvals.save_xmp) + metadata_flags |= GIMP_METADATA_SAVE_XMP; + else + metadata_flags &= ~GIMP_METADATA_SAVE_XMP; + + if (pngvals.save_iptc) + metadata_flags |= GIMP_METADATA_SAVE_IPTC; + else + metadata_flags &= ~GIMP_METADATA_SAVE_IPTC; + + if (pngvals.save_thumbnail) + metadata_flags |= GIMP_METADATA_SAVE_THUMBNAIL; + else + metadata_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL; + + if (pngvals.save_profile) + metadata_flags |= GIMP_METADATA_SAVE_COLOR_PROFILE; + else + metadata_flags &= ~GIMP_METADATA_SAVE_COLOR_PROFILE; + + file = g_file_new_for_path (param[3].data.d_string); + gimp_image_metadata_save_finish (orig_image_ID, + "image/png", + metadata, metadata_flags, + file, NULL); + g_object_unref (file); + } + + gimp_set_data (SAVE_PROC, &pngvals, sizeof (pngvals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + if (metadata) + g_object_unref (metadata); + } + else if (strcmp (name, GET_DEFAULTS_PROC) == 0) + { + pngvals = defaults; + load_parasite (); + + *nreturn_vals = 10; + +#define SET_VALUE(index, field) G_STMT_START { \ + values[(index)].type = GIMP_PDB_INT32; \ + values[(index)].data.d_int32 = pngvals.field; \ +} G_STMT_END + + SET_VALUE (1, interlaced); + SET_VALUE (2, compression_level); + SET_VALUE (3, bkgd); + SET_VALUE (4, gama); + SET_VALUE (5, offs); + SET_VALUE (6, phys); + SET_VALUE (7, time); + SET_VALUE (8, comment); + SET_VALUE (9, save_transp_pixels); + +#undef SET_VALUE + } + else if (strcmp (name, SET_DEFAULTS_PROC) == 0) + { + if (nparams == 9) + { + pngvals = defaults; + load_parasite (); + + pngvals.interlaced = param[0].data.d_int32; + pngvals.compression_level = param[1].data.d_int32; + pngvals.bkgd = param[2].data.d_int32; + pngvals.gama = param[3].data.d_int32; + pngvals.offs = param[4].data.d_int32; + pngvals.phys = param[5].data.d_int32; + pngvals.time = param[6].data.d_int32; + pngvals.comment = param[7].data.d_int32; + pngvals.save_transp_pixels = param[8].data.d_int32; + + save_parasite (); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +struct read_error_data +{ + guchar *pixel; /* Pixel data */ + GeglBuffer *buffer; /* GEGL buffer for layer */ + const Babl *file_format; + guint32 width; /* png_infop->width */ + guint32 height; /* png_infop->height */ + gint bpp; /* Bytes per pixel */ + gint tile_height; /* Height of tile in GIMP */ + gint begin; /* Beginning tile row */ + gint end; /* Ending tile row */ + gint num; /* Number of rows to load */ +}; + +static void +on_read_error (png_structp png_ptr, + png_const_charp error_msg) +{ + struct read_error_data *error_data = png_get_error_ptr (png_ptr); + gint begin; + gint end; + gint num; + + g_printerr (_("Error loading PNG file: %s\n"), error_msg); + + /* Flush the current half-read row of tiles */ + + gegl_buffer_set (error_data->buffer, + GEGL_RECTANGLE (0, error_data->begin, + error_data->width, + error_data->num), + 0, + error_data->file_format, + error_data->pixel, + GEGL_AUTO_ROWSTRIDE); + + begin = error_data->begin + error_data->tile_height; + + if (begin < error_data->height) + { + end = MIN (error_data->end + error_data->tile_height, error_data->height); + num = end - begin; + + gegl_buffer_clear (error_data->buffer, + GEGL_RECTANGLE (0, begin, error_data->width, num)); + } + + g_object_unref (error_data->buffer); + longjmp (png_jmpbuf (png_ptr), 1); +} + +static int +get_bit_depth_for_palette (int num_palette) +{ + if (num_palette <= 2) + return 1; + else if (num_palette <= 4) + return 2; + else if (num_palette <= 16) + return 4; + else + return 8; +} + +static GimpColorProfile * +load_color_profile (png_structp pp, + png_infop info, + gchar **profile_name) +{ + GimpColorProfile *profile = NULL; + +#if defined(PNG_iCCP_SUPPORTED) + png_uint_32 proflen; + png_charp profname; + png_bytep prof; + int profcomp; + + if (png_get_iCCP (pp, info, &profname, &profcomp, &prof, &proflen)) + { + profile = gimp_color_profile_new_from_icc_profile ((guint8 *) prof, + proflen, NULL); + if (profile && profname) + { + *profile_name = g_convert (profname, strlen (profname), + "ISO-8859-1", "UTF-8", NULL, NULL, NULL); + } + } +#endif + + return profile; +} + +/* + * 'load_image()' - Load a PNG image into a new image window. + */ +static gint32 +load_image (const gchar *filename, + gboolean interactive, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error) +{ + gint i; /* Looping var */ + gint trns; /* Transparency present */ + gint bpp; /* Bytes per pixel */ + gint width; /* image width */ + gint height; /* image height */ + gint num_passes; /* Number of interlace passes in file */ + gint pass; /* Current pass in file */ + gint tile_height; /* Height of tile in GIMP */ + gint begin; /* Beginning tile row */ + gint end; /* Ending tile row */ + gint num; /* Number of rows to load */ + GimpImageBaseType image_type; /* Type of image */ + GimpPrecision image_precision; /* Precision of image */ + GimpImageType layer_type; /* Type of drawable/layer */ + GimpColorProfile *profile = NULL; /* Color profile */ + gchar *profile_name = NULL; /* Profile's name */ + gboolean linear = FALSE; /* Linear RGB */ + FILE *fp; /* File pointer */ + volatile gint32 image = -1; /* Image -- protected for setjmp() */ + gint32 layer; /* Layer */ + GeglBuffer *buffer; /* GEGL buffer for layer */ + const Babl *file_format; /* BABL format for layer */ + png_structp pp; /* PNG read pointer */ + png_infop info; /* PNG info pointers */ + guchar **pixels; /* Pixel rows */ + guchar *pixel; /* Pixel data */ + guchar alpha[256]; /* Index -> Alpha */ + png_textp text; + gint num_texts; + struct read_error_data error_data; + + pp = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (! pp) + { + /* this could happen if the compile time and run-time libpng + versions do not match. */ + + g_set_error (error, 0, 0, + _("Error creating PNG read struct while loading '%s'."), + gimp_filename_to_utf8 (filename)); + return -1; + } + + info = png_create_info_struct (pp); + if (! info) + { + g_set_error (error, 0, 0, + _("Error while reading '%s'. Could not create PNG header info structure."), + gimp_filename_to_utf8 (filename)); + return -1; + } + + if (setjmp (png_jmpbuf (pp))) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error while reading '%s'. File corrupted?"), + gimp_filename_to_utf8 (filename)); + return image; + } + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED + /* Change some libpng errors to warnings (e.g. bug 721135) */ + png_set_benign_errors (pp, TRUE); + + /* bug 765850 */ + png_set_option (pp, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); +#endif + + /* + * Open the file and initialize the PNG read "engine"... + */ + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + fp = g_fopen (filename, "rb"); + + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + png_init_io (pp, fp); + png_set_compression_buffer_size (pp, 512); + + /* + * Get the image info + */ + + png_read_info (pp, info); + + if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + png_set_swap (pp); + + /* + * Get the iCCP (color profile) chunk, if any, and figure if it's + * a linear RGB profile + */ + profile = load_color_profile (pp, info, &profile_name); + + if (profile) + { + *profile_loaded = TRUE; + + linear = gimp_color_profile_is_linear (profile); + } + + /* + * Get image precision and color model + */ + + if (png_get_bit_depth (pp, info) == 16) + { + if (linear) + image_precision = GIMP_PRECISION_U16_LINEAR; + else + image_precision = GIMP_PRECISION_U16_GAMMA; + } + else + { + if (linear) + image_precision = GIMP_PRECISION_U8_LINEAR; + else + image_precision = GIMP_PRECISION_U8_GAMMA; + } + + if (png_get_bit_depth (pp, info) < 8) + { + if (png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY) + png_set_expand (pp); + + if (png_get_color_type (pp, info) == PNG_COLOR_TYPE_PALETTE) + png_set_packing (pp); + } + + /* + * Expand G+tRNS to GA, RGB+tRNS to RGBA + */ + + if (png_get_color_type (pp, info) != PNG_COLOR_TYPE_PALETTE && + png_get_valid (pp, info, PNG_INFO_tRNS)) + png_set_expand (pp); + + /* + * Turn on interlace handling... libpng returns just 1 (ie single pass) + * if the image is not interlaced + */ + + num_passes = png_set_interlace_handling (pp); + + /* + * Special handling for INDEXED + tRNS (transparency palette) + */ + + if (png_get_valid (pp, info, PNG_INFO_tRNS) && + png_get_color_type (pp, info) == PNG_COLOR_TYPE_PALETTE) + { + guchar *alpha_ptr; + + png_get_tRNS (pp, info, &alpha_ptr, &num, NULL); + + /* Copy the existing alpha values from the tRNS chunk */ + for (i = 0; i < num; ++i) + alpha[i] = alpha_ptr[i]; + + /* And set any others to fully opaque (255) */ + for (i = num; i < 256; ++i) + alpha[i] = 255; + + trns = 1; + } + else + { + trns = 0; + } + + /* + * Update the info structures after the transformations take effect + */ + + png_read_update_info (pp, info); + + switch (png_get_color_type (pp, info)) + { + case PNG_COLOR_TYPE_RGB: + image_type = GIMP_RGB; + layer_type = GIMP_RGB_IMAGE; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + image_type = GIMP_RGB; + layer_type = GIMP_RGBA_IMAGE; + break; + + case PNG_COLOR_TYPE_GRAY: + image_type = GIMP_GRAY; + layer_type = GIMP_GRAY_IMAGE; + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + image_type = GIMP_GRAY; + layer_type = GIMP_GRAYA_IMAGE; + break; + + case PNG_COLOR_TYPE_PALETTE: + image_type = GIMP_INDEXED; + layer_type = GIMP_INDEXED_IMAGE; + break; + + default: + g_set_error (error, 0, 0, + _("Unknown color model in PNG file '%s'."), + gimp_filename_to_utf8 (filename)); + return -1; + } + + width = png_get_image_width (pp, info); + height = png_get_image_height (pp, info); + + image = gimp_image_new_with_precision (width, height, + image_type, image_precision); + if (image == -1) + { + g_set_error (error, 0, 0, + _("Could not create new image for '%s': %s"), + gimp_filename_to_utf8 (filename), gimp_get_pdb_error ()); + return -1; + } + + /* + * Create the "background" layer to hold the image... + */ + + layer = gimp_layer_new (image, _("Background"), width, height, + layer_type, + 100, + gimp_image_get_default_new_layer_mode (image)); + gimp_image_insert_layer (image, layer, -1, 0); + + file_format = gimp_drawable_get_format (layer); + + /* + * Find out everything we can about the image resolution + * This is only practical with the new 1.0 APIs, I'm afraid + * due to a bug in libpng-1.0.6, see png-implement for details + */ + + if (png_get_valid (pp, info, PNG_INFO_gAMA)) + { + GimpParasite *parasite; + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + gdouble gamma; + + png_get_gAMA (pp, info, &gamma); + + g_ascii_dtostr (buf, sizeof (buf), gamma); + + parasite = gimp_parasite_new ("gamma", + GIMP_PARASITE_PERSISTENT, + strlen (buf) + 1, buf); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + } + + if (png_get_valid (pp, info, PNG_INFO_oFFs)) + { + gint offset_x = png_get_x_offset_pixels (pp, info); + gint offset_y = png_get_y_offset_pixels (pp, info); + + if (! interactive) + { + gimp_layer_set_offsets (layer, offset_x, offset_y); + } + else if (offsets_dialog (offset_x, offset_y)) + { + gimp_layer_set_offsets (layer, offset_x, offset_y); + + if (abs (offset_x) > width || + abs (offset_y) > height) + { + g_message (_("The PNG file specifies an offset that caused " + "the layer to be positioned outside the image.")); + } + } + } + + if (png_get_valid (pp, info, PNG_INFO_pHYs)) + { + png_uint_32 xres; + png_uint_32 yres; + gint unit_type; + + if (png_get_pHYs (pp, info, + &xres, &yres, &unit_type) && xres > 0 && yres > 0) + { + switch (unit_type) + { + case PNG_RESOLUTION_UNKNOWN: + { + gdouble image_xres, image_yres; + + gimp_image_get_resolution (image, &image_xres, &image_yres); + + if (xres > yres) + image_xres = image_yres * (gdouble) xres / (gdouble) yres; + else + image_yres = image_xres * (gdouble) yres / (gdouble) xres; + + gimp_image_set_resolution (image, image_xres, image_yres); + + *resolution_loaded = TRUE; + } + break; + + case PNG_RESOLUTION_METER: + gimp_image_set_resolution (image, + (gdouble) xres * 0.0254, + (gdouble) yres * 0.0254); + gimp_image_set_unit (image, GIMP_UNIT_MM); + + *resolution_loaded = TRUE; + break; + + default: + break; + } + } + + } + + gimp_image_set_filename (image, filename); + + /* + * Load the colormap as necessary... + */ + + if (png_get_color_type (pp, info) & PNG_COLOR_MASK_PALETTE) + { + png_colorp palette; + int num_palette; + + png_get_PLTE (pp, info, &palette, &num_palette); + gimp_image_set_colormap (image, (guchar *) palette, + num_palette); + } + + bpp = babl_format_get_bytes_per_pixel (file_format); + + buffer = gimp_drawable_get_buffer (layer); + + /* + * Temporary buffer... + */ + + tile_height = gimp_tile_height (); + pixel = g_new0 (guchar, tile_height * width * bpp); + pixels = g_new (guchar *, tile_height); + + for (i = 0; i < tile_height; i++) + pixels[i] = pixel + width * bpp * i; + + /* Install our own error handler to handle incomplete PNG files better */ + error_data.buffer = buffer; + error_data.pixel = pixel; + error_data.file_format = file_format; + error_data.tile_height = tile_height; + error_data.width = width; + error_data.height = height; + error_data.bpp = bpp; + + png_set_error_fn (pp, &error_data, on_read_error, NULL); + + for (pass = 0; pass < num_passes; pass++) + { + /* + * This works if you are only reading one row at a time... + */ + + for (begin = 0; begin < height; begin += tile_height) + { + end = MIN (begin + tile_height, height); + num = end - begin; + + if (pass != 0) /* to handle interlaced PiNGs */ + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, begin, width, num), + 1.0, + file_format, + pixel, + GEGL_AUTO_ROWSTRIDE, + GEGL_ABYSS_NONE); + + error_data.begin = begin; + error_data.end = end; + error_data.num = num; + + png_read_rows (pp, pixels, NULL, num); + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, begin, width, num), + 0, + file_format, + pixel, + GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update + (((gdouble) pass + + (gdouble) end / (gdouble) height) / + (gdouble) num_passes); + } + } + + png_read_end (pp, info); + + /* Switch back to default error handler */ + png_set_error_fn (pp, NULL, NULL, NULL); + + if (png_get_text (pp, info, &text, &num_texts)) + { + gchar *comment = NULL; + + for (i = 0; i < num_texts && !comment; i++, text++) + { + if (text->key == NULL || strcmp (text->key, "Comment")) + continue; + + if (text->text_length > 0) /* tEXt */ + { + comment = g_convert (text->text, -1, + "UTF-8", "ISO-8859-1", + NULL, NULL, NULL); + } + else if (g_utf8_validate (text->text, -1, NULL)) + { /* iTXt */ + comment = g_strdup (text->text); + } + } + + if (comment && *comment) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (comment) + 1, comment); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + } + + g_free (comment); + } + + /* + * Attach the color profile, if any + */ + + if (profile) + { + gimp_image_set_color_profile (image, profile); + g_object_unref (profile); + + if (profile_name) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new ("icc-profile-name", + GIMP_PARASITE_PERSISTENT | + GIMP_PARASITE_UNDOABLE, + strlen (profile_name), + profile_name); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + + g_free (profile_name); + } + } + + /* + * Done with the file... + */ + + png_destroy_read_struct (&pp, &info, NULL); + + g_free (pixel); + g_free (pixels); + g_object_unref (buffer); + free (pp); + free (info); + + fclose (fp); + + if (trns) + { + GeglBufferIterator *iter; + gint n_components; + + gimp_layer_add_alpha (layer); + buffer = gimp_drawable_get_buffer (layer); + file_format = gegl_buffer_get_format (buffer); + + iter = gegl_buffer_iterator_new (buffer, NULL, 0, file_format, + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); + n_components = babl_format_get_n_components (file_format); + g_warn_if_fail (n_components == 2); + + while (gegl_buffer_iterator_next (iter)) + { + guchar *data = iter->items[0].data; + gint length = iter->length; + + while (length--) + { + data[1] = alpha[data[0]]; + + data += n_components; + } + } + + g_object_unref (buffer); + } + + return image; +} + +/* + * 'offsets_dialog ()' - Asks the user about offsets when loading. + */ +static gboolean +offsets_dialog (gint offset_x, + gint offset_y) +{ + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *image; + GtkWidget *label; + gchar *message; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Apply PNG Offset"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, LOAD_PROC, + + _("Ignore PNG offset"), GTK_RESPONSE_NO, + _("Apply PNG offset to layer"), GTK_RESPONSE_YES, + + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_YES, + GTK_RESPONSE_NO, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + image = gtk_image_new_from_icon_name (GIMP_ICON_DIALOG_QUESTION, + GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + message = g_strdup_printf (_("The PNG image you are importing specifies an " + "offset of %d, %d. Do you want to apply " + "this offset to the layer?"), + offset_x, offset_y); + label = gtk_label_new (message); + gtk_label_set_yalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_YES); + + gtk_widget_destroy (dialog); + + return run; +} + +/* + * 'save_image ()' - Export the specified image to a PNG file. + */ + +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + GError **error) +{ + gint i, k; /* Looping vars */ + gint bpp = 0; /* Bytes per pixel */ + gint type; /* Type of drawable/layer */ + gint num_passes; /* Number of interlace passes in file */ + gint pass; /* Current pass in file */ + gint tile_height; /* Height of tile in GIMP */ + gint width; /* image width */ + gint height; /* image height */ + gint begin; /* Beginning tile row */ + gint end; /* Ending tile row */ + gint num; /* Number of rows to load */ + FILE *fp; /* File pointer */ + GimpColorProfile *profile = NULL; /* Color profile */ + gboolean out_linear; /* Save linear RGB */ + GeglBuffer *buffer; /* GEGL buffer for layer */ + const Babl *file_format; /* BABL format of file */ + png_structp pp; /* PNG read pointer */ + png_infop info; /* PNG info pointer */ + gint offx, offy; /* Drawable offsets from origin */ + guchar **pixels; /* Pixel rows */ + guchar *fixed; /* Fixed-up pixel data */ + guchar *pixel; /* Pixel data */ + gdouble xres, yres; /* GIMP resolution (dpi) */ + png_color_16 background; /* Background color */ + png_time mod_time; /* Modification time (ie NOW) */ + time_t cutime; /* Time since epoch */ + struct tm *gmt; /* GMT broken down */ + gint color_type; /* PNG color type */ + gint bit_depth; /* Default to bit depth 16 */ + + guchar remap[256]; /* Re-mapping for the palette */ + + png_textp text = NULL; + + out_linear = FALSE; +#if defined(PNG_iCCP_SUPPORTED) + /* If no profile is written: export as sRGB. + * If manually assigned profile written: follow its TRC. + * If default profile written: + * - when export as auto or 16-bit: follow the storage TRC. + * - when export from 8-bit storage: follow the storage TRC. + * - when converting high bit depth to 8-bit: export as sRGB. + */ + if (pngvals.save_profile) + { + profile = gimp_image_get_color_profile (orig_image_ID); + + if (profile || + pngvals.export_format == PNG_FORMAT_AUTO || + pngvals.export_format == PNG_FORMAT_RGB16 || + pngvals.export_format == PNG_FORMAT_RGBA16 || + pngvals.export_format == PNG_FORMAT_GRAY16 || + pngvals.export_format == PNG_FORMAT_GRAYA16 || + gimp_image_get_precision (image_ID) == GIMP_PRECISION_U8_LINEAR || + gimp_image_get_precision (image_ID) == GIMP_PRECISION_U8_GAMMA) + { + if (! profile) + profile = gimp_image_get_effective_color_profile (orig_image_ID); + out_linear = (gimp_color_profile_is_linear (profile)); + } + else + { + /* When converting higher bit depth work image into 8-bit, + * with no manually assigned profile, make sure the result is + * sRGB. + */ + profile = gimp_image_get_effective_color_profile (orig_image_ID); + + if (gimp_color_profile_is_linear (profile)) + { + GimpColorProfile *saved_profile; + + saved_profile = gimp_color_profile_new_srgb_trc_from_color_profile (profile); + g_object_unref (profile); + profile = saved_profile; + } + } + } +#endif + + /* We save as 8-bit PNG only if: + * (1) Work image is 8-bit linear with linear profile to be saved. + * (2) Work image is 8-bit non-linear or perceptual with or without + * profile. + */ + bit_depth = 16; + switch (gimp_image_get_precision (image_ID)) + { + case GIMP_PRECISION_U8_LINEAR: + if (out_linear) + bit_depth = 8; + break; + + case GIMP_PRECISION_U8_GAMMA: + if (! out_linear) + bit_depth = 8; + break; + + default: + break; + } + + pp = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!pp) + { + /* this could happen if the compile time and run-time libpng + * versions do not match. + */ + g_set_error (error, 0, 0, + _("Error creating PNG write struct while exporting '%s'."), + gimp_filename_to_utf8 (filename)); + return FALSE; + } + + info = png_create_info_struct (pp); + if (! info) + { + g_set_error (error, 0, 0, + _("Error while exporting '%s'. Could not create PNG header info structure."), + gimp_filename_to_utf8 (filename)); + return FALSE; + } + + if (setjmp (png_jmpbuf (pp))) + { + g_set_error (error, 0, 0, + _("Error while exporting '%s'. Could not export image."), + gimp_filename_to_utf8 (filename)); + return FALSE; + } + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED + /* Change some libpng errors to warnings (e.g. bug 721135) */ + png_set_benign_errors (pp, TRUE); + + /* bug 765850 */ + png_set_option (pp, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); +#endif + + /* + * Open the file and initialize the PNG write "engine"... + */ + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_filename_to_utf8 (filename)); + + fp = g_fopen (filename, "wb"); + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + png_init_io (pp, fp); + + /* + * Get the buffer for the current image... + */ + + buffer = gimp_drawable_get_buffer (drawable_ID); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + type = gimp_drawable_type (drawable_ID); + + /* + * Initialise remap[] + */ + for (i = 0; i < 256; i++) + remap[i] = i; + + if (pngvals.export_format == PNG_FORMAT_AUTO) + { + /* + * Set color type and remember bytes per pixel count + */ + + switch (type) + { + case GIMP_RGB_IMAGE: + color_type = PNG_COLOR_TYPE_RGB; + if (bit_depth == 8) + { + if (out_linear) + file_format = babl_format ("RGB u8"); + else + file_format = babl_format ("R'G'B' u8"); + } + else + { + if (out_linear) + file_format = babl_format ("RGB u16"); + else + file_format = babl_format ("R'G'B' u16"); + } + break; + + case GIMP_RGBA_IMAGE: + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + if (bit_depth == 8) + { + if (out_linear) + file_format = babl_format ("RGBA u8"); + else + file_format = babl_format ("R'G'B'A u8"); + } + else + { + if (out_linear) + file_format = babl_format ("RGBA u16"); + else + file_format = babl_format ("R'G'B'A u16"); + } + break; + + case GIMP_GRAY_IMAGE: + color_type = PNG_COLOR_TYPE_GRAY; + if (bit_depth == 8) + { + if (out_linear) + file_format = babl_format ("Y u8"); + else + file_format = babl_format ("Y' u8"); + } + else + { + if (out_linear) + file_format = babl_format ("Y u16"); + else + file_format = babl_format ("Y' u16"); + } + break; + + case GIMP_GRAYA_IMAGE: + color_type = PNG_COLOR_TYPE_GRAY_ALPHA; + if (bit_depth == 8) + { + if (out_linear) + file_format = babl_format ("YA u8"); + else + file_format = babl_format ("Y'A u8"); + } + else + { + if (out_linear) + file_format = babl_format ("YA u16"); + else + file_format = babl_format ("Y'A u16"); + } + break; + + case GIMP_INDEXED_IMAGE: + color_type = PNG_COLOR_TYPE_PALETTE; + file_format = gimp_drawable_get_format (drawable_ID); + pngg.has_plte = TRUE; + pngg.palette = (png_colorp) gimp_image_get_colormap (image_ID, + &pngg.num_palette); + bit_depth = get_bit_depth_for_palette (pngg.num_palette); + break; + + case GIMP_INDEXEDA_IMAGE: + color_type = PNG_COLOR_TYPE_PALETTE; + file_format = gimp_drawable_get_format (drawable_ID); + /* fix up transparency */ + bit_depth = respin_cmap (pp, info, remap, image_ID, drawable_ID); + break; + + default: + g_set_error (error, 0, 0, "Image type can't be exported as PNG"); + return FALSE; + } + } + else + { + switch (pngvals.export_format) + { + case PNG_FORMAT_RGB8: + color_type = PNG_COLOR_TYPE_RGB; + if (out_linear) + file_format = babl_format ("RGB u8"); + else + file_format = babl_format ("R'G'B' u8"); + bit_depth = 8; + break; + case PNG_FORMAT_GRAY8: + color_type = PNG_COLOR_TYPE_GRAY; + if (out_linear) + file_format = babl_format ("Y u8"); + else + file_format = babl_format ("Y' u8"); + bit_depth = 8; + break; + case PNG_FORMAT_RGBA8: + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + if (out_linear) + file_format = babl_format ("RGBA u8"); + else + file_format = babl_format ("R'G'B'A u8"); + bit_depth = 8; + break; + case PNG_FORMAT_GRAYA8: + color_type = PNG_COLOR_TYPE_GRAY_ALPHA; + if (out_linear) + file_format = babl_format ("YA u8"); + else + file_format = babl_format ("Y'A u8"); + bit_depth = 8; + break; + case PNG_FORMAT_RGB16: + color_type = PNG_COLOR_TYPE_RGB; + if (out_linear) + file_format = babl_format ("RGB u16"); + else + file_format = babl_format ("R'G'B' u16"); + bit_depth = 16; + break; + case PNG_FORMAT_GRAY16: + color_type = PNG_COLOR_TYPE_GRAY; + if (out_linear) + file_format = babl_format ("Y u16"); + else + file_format = babl_format ("Y' u16"); + bit_depth = 16; + break; + case PNG_FORMAT_RGBA16: + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + if (out_linear) + file_format = babl_format ("RGBA u16"); + else + file_format = babl_format ("R'G'B'A u16"); + bit_depth = 16; + break; + case PNG_FORMAT_GRAYA16: + color_type = PNG_COLOR_TYPE_GRAY_ALPHA; + if (out_linear) + file_format = babl_format ("YA u16"); + else + file_format = babl_format ("Y'A u16"); + bit_depth = 16; + break; + case PNG_FORMAT_AUTO: + g_return_val_if_reached (FALSE); + } + } + + bpp = babl_format_get_bytes_per_pixel (file_format); + + /* Note: png_set_IHDR() must be called before any other png_set_*() + functions. */ + png_set_IHDR (pp, info, width, height, bit_depth, color_type, + pngvals.interlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + if (pngg.has_trns) + png_set_tRNS (pp, info, pngg.trans, pngg.num_trans, NULL); + + if (pngg.has_plte) + png_set_PLTE (pp, info, pngg.palette, pngg.num_palette); + + /* Set the compression level */ + + png_set_compression_level (pp, pngvals.compression_level); + + /* All this stuff is optional extras, if the user is aiming for smallest + possible file size she can turn them all off */ + + if (pngvals.bkgd) + { + GimpRGB color; + guchar red, green, blue; + + gimp_context_get_background (&color); + gimp_rgb_get_uchar (&color, &red, &green, &blue); + + background.index = 0; + background.red = red; + background.green = green; + background.blue = blue; + background.gray = gimp_rgb_luminance_uchar (&color); + png_set_bKGD (pp, info, &background); + } + + if (pngvals.gama) + { + GimpParasite *parasite; + gdouble gamma = 1.0 / DEFAULT_GAMMA; + + parasite = gimp_image_get_parasite (orig_image_ID, "gamma"); + if (parasite) + { + gamma = g_ascii_strtod (gimp_parasite_data (parasite), NULL); + gimp_parasite_free (parasite); + } + + png_set_gAMA (pp, info, gamma); + } + + if (pngvals.offs) + { + gimp_drawable_offsets (drawable_ID, &offx, &offy); + if (offx != 0 || offy != 0) + png_set_oFFs (pp, info, offx, offy, PNG_OFFSET_PIXEL); + } + + if (pngvals.phys) + { + gimp_image_get_resolution (orig_image_ID, &xres, &yres); + png_set_pHYs (pp, info, RINT (xres / 0.0254), RINT (yres / 0.0254), + PNG_RESOLUTION_METER); + } + + if (pngvals.time) + { + cutime = time (NULL); /* time right NOW */ + gmt = gmtime (&cutime); + + mod_time.year = gmt->tm_year + 1900; + mod_time.month = gmt->tm_mon + 1; + mod_time.day = gmt->tm_mday; + mod_time.hour = gmt->tm_hour; + mod_time.minute = gmt->tm_min; + mod_time.second = gmt->tm_sec; + png_set_tIME (pp, info, &mod_time); + } + +#if defined(PNG_iCCP_SUPPORTED) + if (pngvals.save_profile) + { + GimpParasite *parasite; + gchar *profile_name = NULL; + const guint8 *icc_data; + gsize icc_length; + + icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length); + + parasite = gimp_image_get_parasite (orig_image_ID, + "icc-profile-name"); + if (parasite) + profile_name = g_convert (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite), + "UTF-8", "ISO-8859-1", NULL, NULL, NULL); + + png_set_iCCP (pp, + info, + profile_name ? profile_name : "ICC profile", + 0, + icc_data, + icc_length); + + g_free (profile_name); + g_object_unref (profile); + } +#endif + +#ifdef PNG_zTXt_SUPPORTED +/* Small texts are not worth compressing and will be even bigger if compressed. + Empirical length limit of a text being worth compressing. */ +#define COMPRESSION_WORTHY_LENGTH 200 +#endif + + if (pngvals.comment) + { + GimpParasite *parasite; + gsize text_length = 0; + + parasite = gimp_image_get_parasite (orig_image_ID, "gimp-comment"); + if (parasite) + { + gchar *comment = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + + gimp_parasite_free (parasite); + + if (comment && strlen (comment) > 0) + { + text = g_new0 (png_text, 1); + + text[0].key = "Comment"; + +#ifdef PNG_iTXt_SUPPORTED + + text[0].text = g_convert (comment, -1, + "ISO-8859-1", + "UTF-8", + NULL, + &text_length, + NULL); + + if (text[0].text == NULL || strlen (text[0].text) == 0) + { + /* We can't convert to ISO-8859-1 without loss. + Save the comment as iTXt (UTF-8). */ + g_free (text[0].text); + + text[0].text = g_strdup (comment); + text[0].itxt_length = strlen (text[0].text); + +#ifdef PNG_zTXt_SUPPORTED + text[0].compression = strlen (text[0].text) > COMPRESSION_WORTHY_LENGTH ? + PNG_ITXT_COMPRESSION_zTXt : PNG_ITXT_COMPRESSION_NONE; +#else + text[0].compression = PNG_ITXT_COMPRESSION_NONE; +#endif /* PNG_zTXt_SUPPORTED */ + } + else + /* The comment is ISO-8859-1 compatible, so we use tEXt + even if there is iTXt support for compatibility to more + png reading programs. */ +#endif /* PNG_iTXt_SUPPORTED */ + { +#ifndef PNG_iTXt_SUPPORTED + /* No iTXt support, so we are forced to use tEXt (ISO-8859-1). + A broken comment is better than no comment at all, so the + conversion does not fail on unknown character. + They are simply ignored. */ + text[0].text = g_convert_with_fallback (comment, -1, + "ISO-8859-1", + "UTF-8", + "", + NULL, + &text_length, + NULL); +#endif + +#ifdef PNG_zTXt_SUPPORTED + text[0].compression = strlen (text[0].text) > COMPRESSION_WORTHY_LENGTH ? + PNG_TEXT_COMPRESSION_zTXt : PNG_TEXT_COMPRESSION_NONE; +#else + text[0].compression = PNG_TEXT_COMPRESSION_NONE; +#endif /* PNG_zTXt_SUPPORTED */ + + text[0].text_length = text_length; + } + + if (! text[0].text || strlen (text[0].text) == 0) + { + g_free (text[0].text); + g_free (text); + text = NULL; + } + + g_free (comment); + } + } + } + +#ifdef PNG_zTXt_SUPPORTED +#undef COMPRESSION_WORTHY_LENGTH +#endif + + if (text) + png_set_text (pp, info, text, 1); + + png_write_info (pp, info); + if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + png_set_swap (pp); + + /* + * Turn on interlace handling... + */ + + if (pngvals.interlaced) + num_passes = png_set_interlace_handling (pp); + else + num_passes = 1; + + /* + * Convert unpacked pixels to packed if necessary + */ + + if (color_type == PNG_COLOR_TYPE_PALETTE && + bit_depth < 8) + png_set_packing (pp); + + /* + * Allocate memory for "tile_height" rows and export the image... + */ + + tile_height = gimp_tile_height (); + pixel = g_new (guchar, tile_height * width * bpp); + pixels = g_new (guchar *, tile_height); + + for (i = 0; i < tile_height; i++) + pixels[i] = pixel + width * bpp * i; + + for (pass = 0; pass < num_passes; pass++) + { + /* This works if you are only writing one row at a time... */ + for (begin = 0, end = tile_height; + begin < height; begin += tile_height, end += tile_height) + { + if (end > height) + end = height; + + num = end - begin; + + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, begin, width, num), + 1.0, + file_format, + pixel, + GEGL_AUTO_ROWSTRIDE, + GEGL_ABYSS_NONE); + + /* If we are with a RGBA image and have to pre-multiply the + alpha channel */ + if (bpp == 4 && ! pngvals.save_transp_pixels) + { + for (i = 0; i < num; ++i) + { + fixed = pixels[i]; + for (k = 0; k < width; ++k) + { + if (!fixed[3]) + fixed[0] = fixed[1] = fixed[2] = 0; + fixed += bpp; + } + } + } + + if (bpp == 8 && ! pngvals.save_transp_pixels) + { + for (i = 0; i < num; ++i) + { + fixed = pixels[i]; + for (k = 0; k < width; ++k) + { + if (!fixed[6] && !fixed[7]) + fixed[0] = fixed[1] = fixed[2] = + fixed[3] = fixed[4] = fixed[5] = 0; + fixed += bpp; + } + } + } + + /* If we're dealing with a paletted image with + * transparency set, write out the remapped palette */ + if (png_get_valid (pp, info, PNG_INFO_tRNS)) + { + guchar inverse_remap[256]; + + for (i = 0; i < 256; i++) + inverse_remap[ remap[i] ] = i; + + for (i = 0; i < num; ++i) + { + fixed = pixels[i]; + for (k = 0; k < width; ++k) + { + fixed[k] = (fixed[k*2+1] > 127) ? + inverse_remap[ fixed[k*2] ] : + 0; + } + } + } + + /* Otherwise if we have a paletted image and transparency + * couldn't be set, we ignore the alpha channel */ + else if (png_get_valid (pp, info, PNG_INFO_PLTE) && + bpp == 2) + { + for (i = 0; i < num; ++i) + { + fixed = pixels[i]; + for (k = 0; k < width; ++k) + { + fixed[k] = fixed[k * 2]; + } + } + } + + png_write_rows (pp, pixels, num); + + gimp_progress_update (((double) pass + (double) end / + (double) height) / + (double) num_passes); + } + } + + gimp_progress_update (1.0); + + png_write_end (pp, info); + png_destroy_write_struct (&pp, &info); + + g_free (pixel); + g_free (pixels); + + /* + * Done with the file... + */ + + if (text) + { + g_free (text[0].text); + g_free (text); + } + + free (pp); + free (info); + + fclose (fp); + + return TRUE; +} + +static gboolean +ia_has_transparent_pixels (GeglBuffer *buffer) +{ + GeglBufferIterator *iter; + const Babl *format; + gint n_components; + + format = gegl_buffer_get_format (buffer); + iter = gegl_buffer_iterator_new (buffer, NULL, 0, format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1); + n_components = babl_format_get_n_components (format); + g_return_val_if_fail (n_components == 2, FALSE); + + while (gegl_buffer_iterator_next (iter)) + { + const guchar *data = iter->items[0].data; + gint length = iter->length; + + while (length--) + { + if (data[1] <= 127) + { + gegl_buffer_iterator_stop (iter); + return TRUE; + } + + data += n_components; + } + } + + return FALSE; +} + +/* Try to find a color in the palette which isn't actually + * used in the image, so that we can use it as the transparency + * index. Taken from gif.c */ +static gint +find_unused_ia_color (GeglBuffer *buffer, + gint *colors) +{ + GeglBufferIterator *iter; + const Babl *format; + gint n_components; + gboolean ix_used[256]; + gboolean trans_used = FALSE; + gint i; + + for (i = 0; i < *colors; i++) + ix_used[i] = FALSE; + + format = gegl_buffer_get_format (buffer); + iter = gegl_buffer_iterator_new (buffer, NULL, 0, format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1); + n_components = babl_format_get_n_components (format); + g_return_val_if_fail (n_components == 2, FALSE); + + while (gegl_buffer_iterator_next (iter)) + { + const guchar *data = iter->items[0].data; + gint length = iter->length; + + while (length--) + { + if (data[1] > 127) + ix_used[data[0]] = TRUE; + else + trans_used = TRUE; + + data += n_components; + } + } + + /* If there is no transparency, ignore alpha. */ + if (trans_used == FALSE) + return -1; + + /* If there is still some room at the end of the palette, increment + * the number of colors in the image and assign a transparent pixel + * there. */ + if ((*colors) < 256) + { + (*colors)++; + + return (*colors) - 1; + } + + for (i = 0; i < *colors; i++) + { + if (ix_used[i] == FALSE) + return i; + } + + return -1; +} + + +static int +respin_cmap (png_structp pp, + png_infop info, + guchar *remap, + gint32 image_ID, + gint32 drawable_ID) +{ + static guchar trans[] = { 0 }; + GeglBuffer *buffer; + + gint colors; + guchar *before; + + before = gimp_image_get_colormap (image_ID, &colors); + buffer = gimp_drawable_get_buffer (drawable_ID); + + /* + * Make sure there is something in the colormap. + */ + if (colors == 0) + { + before = g_newa (guchar, 3); + memset (before, 0, sizeof (guchar) * 3); + + colors = 1; + } + + /* Try to find an entry which isn't actually used in the + image, for a transparency index. */ + + if (ia_has_transparent_pixels (buffer)) + { + gint transparent = find_unused_ia_color (buffer, &colors); + + if (transparent != -1) /* we have a winner for a transparent + * index - do like gif2png and swap + * index 0 and index transparent */ + { + static png_color palette[256]; + gint i; + + /* Set tRNS chunk values for writing later. */ + pngg.has_trns = TRUE; + pngg.trans = trans; + pngg.num_trans = 1; + + /* Transform all pixels with a value = transparent to + * 0 and vice versa to compensate for re-ordering in palette + * due to png_set_tRNS() */ + + remap[0] = transparent; + for (i = 1; i <= transparent; i++) + remap[i] = i - 1; + + /* Copy from index 0 to index transparent - 1 to index 1 to + * transparent of after, then from transparent+1 to colors-1 + * unchanged, and finally from index transparent to index 0. */ + + for (i = 0; i < colors; i++) + { + palette[i].red = before[3 * remap[i]]; + palette[i].green = before[3 * remap[i] + 1]; + palette[i].blue = before[3 * remap[i] + 2]; + } + + /* Set PLTE chunk values for writing later. */ + pngg.has_plte = TRUE; + pngg.palette = palette; + pngg.num_palette = colors; + } + else + { + /* Inform the user that we couldn't losslessly save the + * transparency & just use the full palette */ + g_message (_("Couldn't losslessly save transparency, " + "saving opacity instead.")); + + /* Set PLTE chunk values for writing later. */ + pngg.has_plte = TRUE; + pngg.palette = (png_colorp) before; + pngg.num_palette = colors; + } + } + else + { + /* Set PLTE chunk values for writing later. */ + pngg.has_plte = TRUE; + pngg.palette = (png_colorp) before; + pngg.num_palette = colors; + } + + g_object_unref (buffer); + + return get_bit_depth_for_palette (colors); +} + +static GtkWidget * +toggle_button_init (GtkBuilder *builder, + const gchar *name, + gboolean initial_value, + gboolean *value_pointer) +{ + GtkWidget *toggle = NULL; + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, name)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), initial_value); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + value_pointer); + + return toggle; +} + +static void pixformat_changed (GtkWidget *widget, + void *foo) +{ + PngExportFormat *ep = foo; + *ep = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)); +} + +static gboolean +save_dialog (gint32 image_ID, + gboolean alpha) +{ + PngSaveGui pg; + GtkWidget *dialog; + GtkBuilder *builder; + gchar *ui_file; + GimpParasite *parasite; + GError *error = NULL; + + /* Dialog init */ + dialog = gimp_export_dialog_new (_("PNG"), PLUG_IN_BINARY, SAVE_PROC); + g_signal_connect (dialog, "response", + G_CALLBACK (save_dialog_response), + &pg); + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_main_quit), + NULL); + + /* GtkBuilder init */ + builder = gtk_builder_new (); + ui_file = g_build_filename (gimp_data_directory (), + "ui/plug-ins/plug-in-file-png.ui", + NULL); + if (! gtk_builder_add_from_file (builder, ui_file, &error)) + { + gchar *display_name = g_filename_display_name (ui_file); + + g_printerr (_("Error loading UI file '%s': %s"), + display_name, error ? error->message : _("Unknown error")); + + g_free (display_name); + } + + g_free (ui_file); + + /* Table */ + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + GTK_WIDGET (gtk_builder_get_object (builder, "table")), + FALSE, FALSE, 0); + + /* Toggles */ + pg.interlaced = toggle_button_init (builder, "interlace", + pngvals.interlaced, + &pngvals.interlaced); + pg.bkgd = toggle_button_init (builder, "save-background-color", + pngvals.bkgd, + &pngvals.bkgd); + pg.gama = toggle_button_init (builder, "save-gamma", + pngvals.gama, + &pngvals.gama); + pg.offs = toggle_button_init (builder, "save-layer-offset", + pngvals.offs, + &pngvals.offs); + pg.phys = toggle_button_init (builder, "save-resolution", + pngvals.phys, + &pngvals.phys); + pg.time = toggle_button_init (builder, "save-creation-time", + pngvals.time, + &pngvals.time); + pg.save_exif = toggle_button_init (builder, "save-exif", + pngvals.save_exif, + &pngvals.save_exif); + pg.save_xmp = toggle_button_init (builder, "save-xmp", + pngvals.save_xmp, + &pngvals.save_xmp); + pg.save_iptc = toggle_button_init (builder, "save-iptc", + pngvals.save_iptc, + &pngvals.save_iptc); + pg.save_thumbnail = toggle_button_init (builder, "save-thumbnail", + pngvals.save_thumbnail, + &pngvals.save_thumbnail); + pg.save_profile = toggle_button_init (builder, "save-color-profile", + pngvals.save_profile, + &pngvals.save_profile); + +#if !defined(PNG_iCCP_SUPPORTED) + gtk_widget_hide (pg.save_profile); +#endif + + /* Comment toggle */ + parasite = gimp_image_get_parasite (image_ID, "gimp-comment"); + pg.comment = + toggle_button_init (builder, "save-comment", + pngvals.comment && parasite != NULL, + &pngvals.comment); + gtk_widget_set_sensitive (pg.comment, parasite != NULL); + gimp_parasite_free (parasite); + + /* Transparent pixels toggle */ + pg.save_transp_pixels = + toggle_button_init (builder, + "save-transparent-pixels", + alpha && pngvals.save_transp_pixels, + &pngvals.save_transp_pixels); + gtk_widget_set_sensitive (pg.save_transp_pixels, alpha); + + /* Compression level scale */ + pg.compression_level = + GTK_ADJUSTMENT (gtk_builder_get_object (builder, "compression-level")); + gtk_adjustment_set_value (pg.compression_level, pngvals.compression_level); + g_signal_connect (pg.compression_level, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &pngvals.compression_level); + + /* Compression level scale */ + pg.pixelformat = + GTK_WIDGET (gtk_builder_get_object (builder, "pixelformat-combo")); + gtk_combo_box_set_active (GTK_COMBO_BOX (pg.pixelformat), pngvals.export_format); + g_signal_connect (pg.pixelformat, "changed", + G_CALLBACK (pixformat_changed), + &pngvals.export_format); + +#if 0 + gtk_adjustment_set_value (pg.compression_level, pngvals.compression_level); + g_signal_connect (pg.compression_level, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &pngvals.compression_level); +#endif + + /* Load/save defaults buttons */ + g_signal_connect_swapped (gtk_builder_get_object (builder, "load-defaults"), + "clicked", + G_CALLBACK (load_gui_defaults), + &pg); + + g_signal_connect_swapped (gtk_builder_get_object (builder, "save-defaults"), + "clicked", + G_CALLBACK (save_parasite), + &pg); + + /* Show dialog and run */ + gtk_widget_show (dialog); + + pg.run = FALSE; + + gtk_main (); + + return pg.run; +} + +static void +save_dialog_response (GtkWidget *widget, + gint response_id, + gpointer data) +{ + PngSaveGui *pg = data; + + switch (response_id) + { + case GTK_RESPONSE_OK: + pg->run = TRUE; + + default: + gtk_widget_destroy (widget); + break; + } +} + +static void +load_parasite (void) +{ + GimpParasite *parasite; + + parasite = gimp_get_parasite (PNG_DEFAULTS_PARASITE); + + if (parasite) + { + gchar *def_str; + PngSaveVals tmpvals = defaults; + gint num_fields; + + def_str = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + + gimp_parasite_free (parasite); + + num_fields = sscanf (def_str, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d", + &tmpvals.interlaced, + &tmpvals.bkgd, + &tmpvals.gama, + &tmpvals.offs, + &tmpvals.phys, + &tmpvals.time, + &tmpvals.comment, + &tmpvals.save_transp_pixels, + &tmpvals.compression_level, + &tmpvals.save_exif, + &tmpvals.save_xmp, + &tmpvals.save_iptc, + &tmpvals.save_thumbnail, + &tmpvals.save_profile); + + g_free (def_str); + + if (num_fields == 9 || num_fields == 13 || num_fields == 14) + pngvals = tmpvals; + } +} + +static void +save_parasite (void) +{ + GimpParasite *parasite; + gchar *def_str; + + def_str = g_strdup_printf ("%d %d %d %d %d %d %d %d %d %d %d %d %d %d", + pngvals.interlaced, + pngvals.bkgd, + pngvals.gama, + pngvals.offs, + pngvals.phys, + pngvals.time, + pngvals.comment, + pngvals.save_transp_pixels, + pngvals.compression_level, + pngvals.save_exif, + pngvals.save_xmp, + pngvals.save_iptc, + pngvals.save_thumbnail, + pngvals.save_profile); + + parasite = gimp_parasite_new (PNG_DEFAULTS_PARASITE, + GIMP_PARASITE_PERSISTENT, + strlen (def_str), def_str); + + gimp_attach_parasite (parasite); + + gimp_parasite_free (parasite); + g_free (def_str); +} + +static void +load_gui_defaults (PngSaveGui *pg) +{ + /* initialize with hardcoded defaults */ + pngvals = defaults; + /* Override with parasite. */ + load_parasite (); + +#define SET_ACTIVE(field) \ + if (gtk_widget_is_sensitive (pg->field)) \ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (pg->field), pngvals.field) + + SET_ACTIVE (interlaced); + SET_ACTIVE (bkgd); + SET_ACTIVE (gama); + SET_ACTIVE (offs); + SET_ACTIVE (phys); + SET_ACTIVE (time); + SET_ACTIVE (comment); + SET_ACTIVE (save_transp_pixels); + SET_ACTIVE (save_exif); + SET_ACTIVE (save_xmp); + SET_ACTIVE (save_iptc); + SET_ACTIVE (save_thumbnail); + SET_ACTIVE (save_profile); + +#undef SET_ACTIVE + + gtk_adjustment_set_value (pg->compression_level, + pngvals.compression_level); +} diff --git a/plug-ins/common/file-pnm.c b/plug-ins/common/file-pnm.c new file mode 100644 index 0000000..2132eec --- /dev/null +++ b/plug-ins/common/file-pnm.c @@ -0,0 +1,1820 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * PNM reading and writing code Copyright (C) 1996 Erik Nygren + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * The pnm reading and writing code was written from scratch by Erik Nygren + * (nygren@mit.edu) based on the specifications in the man pages and + * does not contain any code from the netpbm or pbmplus distributions. + * + * 2006: pbm saving written by Martin K Collins (martin@mkcollins.org) + * 2015: pfm reading written by Tobias Ellinghaus (me@houz.org) + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-pnm-load" +#define PNM_SAVE_PROC "file-pnm-save" +#define PBM_SAVE_PROC "file-pbm-save" +#define PGM_SAVE_PROC "file-pgm-save" +#define PPM_SAVE_PROC "file-ppm-save" +#define PFM_SAVE_PROC "file-pfm-save" +#define PLUG_IN_BINARY "file-pnm" +#define PLUG_IN_ROLE "gimp-file-pnm" + + +/* Declare local data types + */ + +typedef struct _PNMScanner PNMScanner; +typedef struct _PNMInfo PNMInfo; +typedef struct _PNMRowInfo PNMRowInfo; + +typedef void (* PNMLoaderFunc) (PNMScanner *scanner, + PNMInfo *info, + GeglBuffer *buffer); +typedef gboolean (* PNMSaverowFunc) (PNMRowInfo *info, + guchar *data, + GError **error); + +struct _PNMScanner +{ + GInputStream *input; /* The input stream of the file being read */ + gchar cur; /* The current character in the input stream */ + gint eof; /* Have we reached end of file? */ + gchar *inbuf; /* Input buffer - initially 0 */ + gint inbufsize; /* Size of input buffer */ + gint inbufvalidsize; /* Size of input buffer with valid data */ + gint inbufpos; /* Position in input buffer */ +}; + +struct _PNMInfo +{ + gint xres, yres; /* The size of the image */ + gboolean float_format; /* Whether it is a floating point format */ + gint maxval; /* For integer format image files, the max value + * which we need to normalize to */ + gfloat scale_factor; /* PFM files have a scale factor */ + gint np; /* Number of image planes (0 for pbm) */ + gboolean asciibody; /* 1 if ascii body, 0 if raw body */ + jmp_buf jmpbuf; /* Where to jump to on an error loading */ + + PNMLoaderFunc loader; /* Routine to use to load the pnm body */ +}; + +/* Contains the information needed to write out PNM rows */ +struct _PNMRowInfo +{ + GOutputStream *output; /* Output stream */ + gchar *rowbuf; /* Buffer for writing out rows */ + gint xres; /* X resolution */ + gint np; /* Number of planes */ + gint bpc; /* Bytes per color */ + guchar *red; /* Colormap red */ + guchar *grn; /* Colormap green */ + guchar *blu; /* Colormap blue */ + gboolean zero_is_black; /* index zero is black (PBM only) */ +}; + +/* Export info */ +typedef struct +{ + gint raw; /* raw or ascii */ +} PNMSaveVals; + +#define BUFLEN 512 /* The input buffer size for data returned + * from the scanner. Note that lines + * aren't allowed to be over 256 characters + * by the spec anyways so this shouldn't + * be an issue. */ + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 load_image (GFile *file, + GError **error); +static gint save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + gboolean pbm, + gboolean float_format, + GError **error); + +static gboolean save_dialog (void); + +static void pnm_load_ascii (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); +static void pnm_load_raw (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); +static void pnm_load_rawpbm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); +static void pnm_load_rawpfm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); + +static gboolean pnmsaverow_ascii (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_raw (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_raw_pbm (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_ascii_pbm (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_ascii_indexed (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_raw_indexed (PNMRowInfo *ri, + guchar *data, + GError **error); + +static void pnmscanner_destroy (PNMScanner *s); +static void pnmscanner_createbuffer (PNMScanner *s, + gint bufsize); +static void pnmscanner_getchar (PNMScanner *s); +static void pnmscanner_eatwhitespace (PNMScanner *s); +static void pnmscanner_gettoken (PNMScanner *s, + gchar *buf, + gint bufsize); +static void pnmscanner_getsmalltoken (PNMScanner *s, + gchar *buf); + +static PNMScanner * pnmscanner_create (GInputStream *input); + + +#define pnmscanner_eof(s) ((s)->eof) +#define pnmscanner_input(s) ((s)->input) + +/* Checks for a fatal error */ +#define CHECK_FOR_ERROR(predicate, jmpbuf, ...) \ + if ((predicate)) \ + { g_message (__VA_ARGS__); longjmp ((jmpbuf), 1); } + +static const struct +{ + gchar name; + gint np; + gint asciibody; + gint maxval; + PNMLoaderFunc loader; +} pnm_types[] = +{ + { '1', 0, 1, 1, pnm_load_ascii }, /* ASCII PBM */ + { '2', 1, 1, 255, pnm_load_ascii }, /* ASCII PGM */ + { '3', 3, 1, 255, pnm_load_ascii }, /* ASCII PPM */ + { '4', 0, 0, 1, pnm_load_rawpbm }, /* RAW PBM */ + { '5', 1, 0, 255, pnm_load_raw }, /* RAW PGM */ + { '6', 3, 0, 255, pnm_load_raw }, /* RAW PPM */ + { 'F', 3, 0, 0, pnm_load_rawpfm }, /* RAW PFM (color) */ + { 'f', 1, 0, 0, pnm_load_rawpfm }, /* RAW PFM (grayscale) */ + { 0 , 0, 0, 0, NULL} +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static PNMSaveVals psvals = +{ + TRUE /* raw? or ascii */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_INT32, "raw", "TRUE for raw output, FALSE for ascii output" } + }; + + static const GimpParamDef pfm_save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in the PNM file format", + "This plug-in loads files in the various Netpbm portable file formats.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PNM Image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-portable-anymap"); + gimp_register_file_handler_uri (LOAD_PROC); + gimp_register_magic_load_handler (LOAD_PROC, + "pnm,ppm,pgm,pbm,pfm", + "", + "0,string,P1,0,string,P2,0,string,P3," + "0,string,P4,0,string,P5,0,string,P6," + "0,string,PF,0,string,Pf"); + + gimp_install_procedure (PNM_SAVE_PROC, + "Exports files in the PNM file format", + "PNM exporting handles all image types without transparency.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PNM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PBM_SAVE_PROC, + "Exports files in the PBM file format", + "PBM exporting produces mono images without transparency.", + "Martin K Collins", + "Erik Nygren", + "2006", + N_("PBM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PGM_SAVE_PROC, + "Exports files in the PGM file format", + "PGM exporting produces grayscale images without transparency.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PGM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PPM_SAVE_PROC, + "Exports files in the PPM file format", + "PPM exporting handles RGB images without transparency.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PPM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PFM_SAVE_PROC, + "Exports files in the PFM file format", + "PFM exporting handles all images without transparency.", + "Mukund Sivaraman", + "Mukund Sivaraman", + "2015", + N_("PFM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (pfm_save_args), 0, + pfm_save_args, NULL); + + gimp_register_file_handler_mime (PNM_SAVE_PROC, "image/x-portable-anymap"); + gimp_register_file_handler_mime (PBM_SAVE_PROC, "image/x-portable-bitmap"); + gimp_register_file_handler_mime (PGM_SAVE_PROC, "image/x-portable-graymap"); + gimp_register_file_handler_mime (PPM_SAVE_PROC, "image/x-portable-pixmap"); + gimp_register_file_handler_mime (PPM_SAVE_PROC, "image/x-portable-floatmap"); + + gimp_register_file_handler_uri (PNM_SAVE_PROC); + gimp_register_file_handler_uri (PBM_SAVE_PROC); + gimp_register_file_handler_uri (PGM_SAVE_PROC); + gimp_register_file_handler_uri (PPM_SAVE_PROC); + gimp_register_file_handler_uri (PFM_SAVE_PROC); + + gimp_register_save_handler (PNM_SAVE_PROC, "pnm", ""); + gimp_register_save_handler (PBM_SAVE_PROC, "pbm", ""); + gimp_register_save_handler (PGM_SAVE_PROC, "pgm", ""); + gimp_register_save_handler (PPM_SAVE_PROC, "ppm", ""); + gimp_register_save_handler (PFM_SAVE_PROC, "pfm", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + gboolean pbm = FALSE; /* flag for PBM output */ + gboolean float_format = FALSE; /* flag for PFM output */ + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (g_file_new_for_uri (param[1].data.d_string), + &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, PNM_SAVE_PROC) == 0 || + strcmp (name, PBM_SAVE_PROC) == 0 || + strcmp (name, PGM_SAVE_PROC) == 0 || + strcmp (name, PPM_SAVE_PROC) == 0 || + strcmp (name, PFM_SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + if (strcmp (name, PNM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PNM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + } + else if (strcmp (name, PBM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PBM", + GIMP_EXPORT_CAN_HANDLE_BITMAP); + pbm = TRUE; /* gimp has no mono image type so hack it */ + } + else if (strcmp (name, PGM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PGM", + GIMP_EXPORT_CAN_HANDLE_GRAY); + } + else if (strcmp (name, PPM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PPM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + } + else + { + export = gimp_export_image (&image_ID, &drawable_ID, "PFM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY); + float_format = TRUE; + } + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + if (strcmp (name, PFM_SAVE_PROC) != 0) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (name, &psvals); + + /* First acquire information with a dialog */ + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + psvals.raw = (param[5].data.d_int32) ? TRUE : FALSE; + pbm = (strcmp (name, PBM_SAVE_PROC) == 0); + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (name, &psvals); + break; + + default: + break; + } + } + else + { + switch (run_mode) + { + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 5) + { + status = GIMP_PDB_CALLING_ERROR; + } + break; + + default: + break; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (g_file_new_for_uri (param[3].data.d_string), + image_ID, drawable_ID, pbm, float_format, + &error)) + { + if (strcmp (name, PFM_SAVE_PROC) != 0) + { + /* Store psvals data */ + gimp_set_data (name, &psvals, sizeof (PNMSaveVals)); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; + + gegl_exit (); +} + +static gint32 +load_image (GFile *file, + GError **error) +{ + GInputStream *input; + GeglBuffer *buffer; + gint32 volatile image_ID = -1; + gint32 layer_ID; + char buf[BUFLEN + 4]; /* buffer for random things like scanning */ + PNMInfo *pnminfo; + PNMScanner *volatile scan; + int ctr; + GimpPrecision precision; + + gimp_progress_init_printf (_("Opening '%s'"), + g_file_get_parse_name (file)); + + input = G_INPUT_STREAM (g_file_read (file, NULL, error)); + if (! input) + return -1; + + /* allocate the necessary structures */ + pnminfo = g_new (PNMInfo, 1); + + scan = NULL; + /* set error handling */ + if (setjmp (pnminfo->jmpbuf)) + { + /* If we get here, we had a problem reading the file */ + if (scan) + pnmscanner_destroy (scan); + + g_object_unref (input); + g_free (pnminfo); + + if (image_ID != -1) + gimp_image_delete (image_ID); + + return -1; + } + + if (! (scan = pnmscanner_create (input))) + longjmp (pnminfo->jmpbuf, 1); + + /* Get magic number */ + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + CHECK_FOR_ERROR ((buf[0] != 'P' || buf[2]), pnminfo->jmpbuf, + _("Invalid file.")); + + /* Look up magic number to see what type of PNM this is */ + for (ctr = 0; pnm_types[ctr].name; ctr++) + if (buf[1] == pnm_types[ctr].name) + { + pnminfo->np = pnm_types[ctr].np; + pnminfo->asciibody = pnm_types[ctr].asciibody; + pnminfo->float_format = g_ascii_tolower (pnm_types[ctr].name) == 'f'; + pnminfo->maxval = pnm_types[ctr].maxval; + pnminfo->loader = pnm_types[ctr].loader; + } + + if (!pnminfo->loader) + { + g_message (_("File not in a supported format.")); + longjmp (pnminfo->jmpbuf, 1); + } + + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + pnminfo->xres = g_ascii_isdigit(*buf) ? atoi (buf) : 0; + CHECK_FOR_ERROR (pnminfo->xres <= 0, pnminfo->jmpbuf, + _("Invalid X resolution.")); + CHECK_FOR_ERROR (pnminfo->xres > GIMP_MAX_IMAGE_SIZE, pnminfo->jmpbuf, + _("Image width is larger than GIMP can handle.")); + + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + pnminfo->yres = g_ascii_isdigit (*buf) ? atoi (buf) : 0; + CHECK_FOR_ERROR (pnminfo->yres <= 0, pnminfo->jmpbuf, + _("Invalid Y resolution.")); + CHECK_FOR_ERROR (pnminfo->yres > GIMP_MAX_IMAGE_SIZE, pnminfo->jmpbuf, + _("Image height is larger than GIMP can handle.")); + + if (pnminfo->float_format) + { + gchar *endptr = NULL; + + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + + pnminfo->scale_factor = g_ascii_strtod (buf, &endptr); + CHECK_FOR_ERROR (endptr == NULL || *endptr != 0 || errno == ERANGE, + pnminfo->jmpbuf, _("Bogus scale factor.")); + CHECK_FOR_ERROR (!isnormal (pnminfo->scale_factor), + pnminfo->jmpbuf, _("Unsupported scale factor.")); + precision = GIMP_PRECISION_FLOAT_LINEAR; + } + else if (pnminfo->np != 0) /* pbm's don't have a maxval field */ + { + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + + pnminfo->maxval = g_ascii_isdigit (*buf) ? atoi (buf) : 0; + CHECK_FOR_ERROR (((pnminfo->maxval<=0) || (pnminfo->maxval>65535)), + pnminfo->jmpbuf, _("Unsupported maximum value.")); + if (pnminfo->maxval < 256) + { + precision = GIMP_PRECISION_U8_GAMMA; + } + else + { + precision = GIMP_PRECISION_U16_GAMMA; + } + } + else + { + precision = GIMP_PRECISION_U8_GAMMA; + } + + /* Create a new image of the proper size and associate the filename + with it. */ + image_ID = gimp_image_new_with_precision + (pnminfo->xres, pnminfo->yres, + (pnminfo->np >= 3) ? GIMP_RGB : GIMP_GRAY, + precision); + + gimp_image_set_filename (image_ID, g_file_get_uri (file)); + + layer_ID = gimp_layer_new (image_ID, _("Background"), + pnminfo->xres, pnminfo->yres, + (pnminfo->np >= 3 ? + GIMP_RGB_IMAGE : GIMP_GRAY_IMAGE), + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_ID); + + pnminfo->loader (scan, pnminfo, buffer); + + /* Destroy the scanner */ + pnmscanner_destroy (scan); + + g_object_unref (buffer); + g_free (pnminfo); + g_object_unref (input); + + return image_ID; +} + +static void +pnm_load_ascii (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + gint bpc; + guchar *data, *d; + gushort *s; + gint x, y, i, b; + gint start, end, scanlines; + gint np; + gchar buf[BUFLEN]; + gboolean aborted = FALSE; + + np = (info->np) ? (info->np) : 1; + + if (info->maxval > 255) + bpc = 2; + else + bpc = 1; + + /* No overflow as long as gimp_tile_height() < 1365 = 2^(31 - 18) / 6 */ + data = g_new (guchar, gimp_tile_height () * info->xres * np * bpc); + + /* Buffer reads to increase performance */ + pnmscanner_createbuffer (scan, 4096); + + for (y = 0; y < info->yres; y += scanlines) + { + start = y; + end = y + gimp_tile_height (); + end = MIN (end, info->yres); + + scanlines = end - start; + + d = data; + s = (gushort *)d; + + for (i = 0; i < scanlines; i++) + for (x = 0; x < info->xres; x++) + { + for (b = 0; b < np; b++) + { + if (aborted) + { + d[b] = 0; + continue; + } + + /* Truncated files will just have all 0's + at the end of the images */ + if (pnmscanner_eof (scan)) + { + g_message (_("Premature end of file.")); + aborted = TRUE; + + d[b] = 0; + continue; + } + + if (info->np) + pnmscanner_gettoken (scan, buf, BUFLEN); + else + pnmscanner_getsmalltoken (scan, buf); + + if (info->maxval == 1) + { + if (info->np) + d[b] = (*buf == '0') ? 0x00 : 0xff; + else + d[b] = (*buf == '0') ? 0xff : 0x00; /* invert for PBM */ + } + else if (bpc > 1) + { + s[b] = (65535.0 * (((gdouble) (g_ascii_isdigit (*buf) ? + atoi (buf) : 0)) + / (gdouble) (info->maxval))); + } + else + { + d[b] = (255.0 * (((gdouble) (g_ascii_isdigit (*buf) ? + atoi (buf) : 0)) + / (gdouble) (info->maxval))); + } + } + + if (bpc > 1) + { + s += np; + } + else + { + d += np; + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y, info->xres, scanlines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) y / (double) info->yres); + } + + g_free (data); + + gimp_progress_update (1.0); +} + +static void +pnm_load_raw (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + GInputStream *input; + gint bpc; + guchar *data, *d; + gushort *s; + gint x, y, i; + gint start, end, scanlines; + + if (info->maxval > 255) + bpc = 2; + else + bpc = 1; + + /* No overflow as long as gimp_tile_height() < 1365 = 2^(31 - 18) / 6 */ + data = g_new (guchar, gimp_tile_height () * info->xres * info->np * bpc); + + input = pnmscanner_input (scan); + + for (y = 0; y < info->yres; y += scanlines) + { + start = y; + end = y + gimp_tile_height (); + end = MIN (end, info->yres); + scanlines = end - start; + d = data; + s = (gushort *)data; + + for (i = 0; i < scanlines; i++) + { + gsize bytes_read; + GError *error = NULL; + + if (g_input_stream_read_all (input, d, info->xres * info->np * bpc, + &bytes_read, NULL, &error)) + { + CHECK_FOR_ERROR (info->xres * info->np * bpc != bytes_read, + info->jmpbuf, + _("Premature end of file.")); + } + else + { + CHECK_FOR_ERROR (FALSE, info->jmpbuf, "%s", error->message); + } + + if (bpc > 1) + { + for (x = 0; x < info->xres * info->np; x++) + { + int v; + + v = *d++ << 8; + v += *d++; + + s[x] = MIN (v, info->maxval); /* guard against overflow */ + s[x] = 65535.0 * (gdouble) v / (gdouble) info->maxval; + } + s += info->xres * info->np; + } + else + { + if (info->maxval != 255) /* Normalize if needed */ + { + for (x = 0; x < info->xres * info->np; x++) + { + d[x] = MIN (d[x], info->maxval); /* guard against overflow */ + d[x] = 255.0 * (gdouble) d[x] / (gdouble) info->maxval; + } + } + + d += info->xres * info->np; + } + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, y, info->xres, scanlines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) y / (double) info->yres); + } + + g_free (data); + + gimp_progress_update (1.0); +} + +static void +pnm_load_rawpbm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + GInputStream *input; + guchar *buf; + guchar curbyte; + guchar *data, *d; + gint x, y, i; + gint start, end, scanlines; + gint rowlen, bufpos; + + input = pnmscanner_input (scan); + + rowlen = (int)ceil ((double)(info->xres)/8.0); + data = g_new (guchar, gimp_tile_height () * info->xres); + buf = g_new (guchar, rowlen); + + for (y = 0; y < info->yres; y += scanlines) + { + start = y; + end = y + gimp_tile_height (); + end = MIN (end, info->yres); + scanlines = end - start; + d = data; + + for (i = 0; i < scanlines; i++) + { + gsize bytes_read; + GError *error = NULL; + + if (g_input_stream_read_all (input, buf, rowlen, + &bytes_read, NULL, &error)) + { + CHECK_FOR_ERROR (rowlen != bytes_read, + info->jmpbuf, + _("Premature end of file.")); + } + else + { + CHECK_FOR_ERROR (FALSE, info->jmpbuf, "%s", error->message); + } + + bufpos = 0; + curbyte = buf[0]; + + for (x = 0; x < info->xres; x++) + { + if ((x % 8) == 0) + curbyte = buf[bufpos++]; + d[x] = (curbyte & 0x80) ? 0x00 : 0xff; + curbyte <<= 1; + } + + d += info->xres; + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y, info->xres, scanlines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) y / (double) info->yres); + } + + g_free (buf); + g_free (data); + + gimp_progress_update (1.0); +} + +static void +pnm_load_rawpfm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + GInputStream *input; + gfloat *data; + gint x, y; + gboolean swap_byte_order; + + swap_byte_order = + (info->scale_factor >= 0.0) ^ (G_BYTE_ORDER == G_BIG_ENDIAN); + + data = g_new (gfloat, info->xres * info->np); + + input = pnmscanner_input (scan); + + for (y = info->yres - 1; y >= 0; y--) + { + gsize bytes_read; + GError *error = NULL; + + if (g_input_stream_read_all (input, data, + info->xres * info->np * sizeof (float), + &bytes_read, NULL, &error)) + { + CHECK_FOR_ERROR + (info->xres * info->np * sizeof (float) != bytes_read, + info->jmpbuf, _("Premature end of file.")); + } + else + { + CHECK_FOR_ERROR (FALSE, info->jmpbuf, "%s", error->message); + } + + for (x = 0; x < info->xres * info->np; x++) + { + if (swap_byte_order) + { + union { gfloat f; guint32 i; } v; + + v.f = data[x]; + v.i = GUINT32_SWAP_LE_BE (v.i); + data[x] = v.f; + } + + /* let's see if this is what people want, the PFM specs are a + * little vague about what the scale factor should be used + * for */ + data[x] *= fabsf (info->scale_factor); + /* Keep values smaller than zero. That is in line with what the + * TIFF loader does. If the user doesn't want the negative numbers + * he has to get rid of them afterwards */ + /* data[x] = fmaxf (0.0f, fminf (FLT_MAX, data[x])); */ + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, y, info->xres, 1), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + if (y % 32 == 0) + gimp_progress_update ((double) (info->yres - y) / (double) info->yres); + } + + g_free (data); + + gimp_progress_update (1.0); +} + +static gboolean +output_write (GOutputStream *output, + gconstpointer buffer, + gsize count, + GError **error) +{ + return g_output_stream_write_all (output, buffer, count, NULL, NULL, error); +} + +/* Writes out mono raw rows */ +static gboolean +pnmsaverow_raw_pbm (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint b, p = 0; + gchar *rbcur = ri->rowbuf; + gint32 len = (gint) ceil ((gdouble) (ri->xres) / 8.0); + + for (b = 0; b < len; b++) /* each output byte */ + { + gint i; + + rbcur[b] = 0; + + for (i = 0; i < 8; i++) /* each bit in this byte */ + { + if (p >= ri->xres) + break; + + if (data[p] != ri->zero_is_black) + rbcur[b] |= (char) (1 << (7 - i)); + + p++; + } + } + + return output_write (ri->output, ri->rowbuf, len, error); +} + +/* Writes out mono ascii rows */ +static gboolean +pnmsaverow_ascii_pbm (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + static gint line_len = 0; /* ascii pbm lines must be <= 70 chars long */ + gint32 len = 0; + gint i; + gchar *rbcur = ri->rowbuf; + + for (i = 0; i < ri->xres; i++) + { + if (line_len > 69) + { + rbcur[i] = '\n'; + line_len = 0; + len++; + rbcur++; + } + + if (data[i] == ri->zero_is_black) + rbcur[i] = '0'; + else + rbcur[i] = '1'; + + line_len++; + len++; + } + + *(rbcur+i) = '\n'; + + return output_write (ri->output, ri->rowbuf, len, error); +} + +/* Writes out RGB and grayscale raw rows */ +static gboolean +pnmsaverow_raw (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + if (ri->bpc == 2) + { + gushort *d = (gushort *)data; + for (i = 0; i < ri->xres * ri->np; i++) + { + *d = g_htons(*d); + d++; + } + } + return output_write (ri->output, data, ri->xres * ri->np * ri->bpc, error); +} + +/* Writes out RGB and grayscale float rows */ +static gboolean +pnmsaverow_float (PNMRowInfo *ri, + const float *data, + GError **error) +{ + return output_write (ri->output, data, + ri->xres * ri->np * sizeof (float), + error); +} + +/* Writes out indexed raw rows */ +static gboolean +pnmsaverow_raw_indexed (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + gchar *rbcur = ri->rowbuf; + + for (i = 0; i < ri->xres; i++) + { + *(rbcur++) = ri->red[*data]; + *(rbcur++) = ri->grn[*data]; + *(rbcur++) = ri->blu[*(data++)]; + } + + return output_write (ri->output, ri->rowbuf, ri->xres * 3, error); +} + +/* Writes out RGB and grayscale ascii rows */ +static gboolean +pnmsaverow_ascii (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + gchar *rbcur = ri->rowbuf; + gushort *sdata = (gushort *)data; + + for (i = 0; i < ri->xres * ri->np; i++) + { + if (ri->bpc == 2) + { + sprintf ((gchar *) rbcur,"%d\n", 0xffff & *(sdata++)); + } + else + { + sprintf ((gchar *) rbcur,"%d\n", 0xff & *(data++)); + } + rbcur += strlen (rbcur); + } + + return output_write (ri->output, ri->rowbuf, rbcur - ri->rowbuf, + error); +} + +/* Writes out RGB and grayscale ascii rows */ +static gboolean +pnmsaverow_ascii_indexed (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + gchar *rbcur = ri->rowbuf; + + for (i = 0; i < ri->xres; i++) + { + sprintf (rbcur, "%d\n", 0xff & ri->red[*(data)]); + rbcur += strlen (rbcur); + sprintf (rbcur, "%d\n", 0xff & ri->grn[*(data)]); + rbcur += strlen (rbcur); + sprintf (rbcur, "%d\n", 0xff & ri->blu[*(data++)]); + rbcur += strlen (rbcur); + } + + return output_write (ri->output, ri->rowbuf, strlen ((char *) ri->rowbuf), + error); +} + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + gboolean pbm, + gboolean float_format, + GError **error) +{ + gboolean status = FALSE; + GOutputStream *output = NULL; + GeglBuffer *buffer = NULL; + const Babl *format; + const gchar *header_string = NULL; + GimpImageType drawable_type; + PNMRowInfo rowinfo; + PNMSaverowFunc saverow = NULL; + guchar red[256]; + guchar grn[256]; + guchar blu[256]; + gchar buf[BUFLEN]; + gint np = 0; + gint xres, yres; + gint ypos, yend; + gint rowbufsize = 0; + gchar *comment = NULL; + + /* Make sure we're not saving an image with an alpha channel */ + if (gimp_drawable_has_alpha (drawable_ID)) + { + g_message (_("Cannot export images with alpha channel.")); + goto out; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + g_file_get_parse_name (file)); + + /* open the file */ + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (! output) + goto out; + + buffer = gimp_drawable_get_buffer (drawable_ID); + + xres = gegl_buffer_get_width (buffer); + yres = gegl_buffer_get_height (buffer); + + drawable_type = gimp_drawable_type (drawable_ID); + + switch (gimp_image_get_precision (image_ID)) + { + case GIMP_PRECISION_U8_LINEAR: + case GIMP_PRECISION_U8_GAMMA: + rowinfo.bpc = 1; + break; + default: + rowinfo.bpc = 2; + break; + } + + /* write out magic number */ + if (!float_format && !psvals.raw) + { + if (pbm) + { + header_string = "P1\n"; + format = gegl_buffer_get_format (buffer); + np = 0; + rowbufsize = xres + (int) (xres / 70) + 1; + saverow = pnmsaverow_ascii_pbm; + } + else + { + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + header_string = "P2\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("Y' u8"); + rowbufsize = xres * 4; + } + else + { + format = babl_format ("Y' u16"); + rowbufsize = xres * 6; + } + np = 1; + saverow = pnmsaverow_ascii; + break; + + case GIMP_RGB_IMAGE: + header_string = "P3\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("R'G'B' u8"); + rowbufsize = xres * 12; + } + else + { + format = babl_format ("R'G'B' u16"); + rowbufsize = xres * 18; + } + np = 3; + saverow = pnmsaverow_ascii; + break; + + case GIMP_INDEXED_IMAGE: + header_string = "P3\n"; + format = gegl_buffer_get_format (buffer); + np = 1; + rowbufsize = xres * 12; + saverow = pnmsaverow_ascii_indexed; + break; + + default: + g_warning ("PNM: Unknown drawable_type\n"); + goto out; + } + } + } + else if (!float_format) + { + if (pbm) + { + header_string = "P4\n"; + format = gegl_buffer_get_format (buffer); + np = 0; + rowbufsize = (gint) ceil ((gdouble) xres / 8.0); + saverow = pnmsaverow_raw_pbm; + } + else + { + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + header_string = "P5\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("Y' u8"); + rowbufsize = xres; + } + else + { + format = babl_format ("Y' u16"); + rowbufsize = xres * 2; + } + np = 1; + saverow = pnmsaverow_raw; + break; + + case GIMP_RGB_IMAGE: + header_string = "P6\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("R'G'B' u8"); + rowbufsize = xres * 3; + } + else + { + format = babl_format ("R'G'B' u16"); + rowbufsize = xres * 6; + } + np = 3; + saverow = pnmsaverow_raw; + break; + + case GIMP_INDEXED_IMAGE: + header_string = "P6\n"; + format = gegl_buffer_get_format (buffer); + np = 1; + rowbufsize = xres * 3; + saverow = pnmsaverow_raw_indexed; + break; + + default: + g_warning ("PNM: Unknown drawable_type\n"); + goto out; + } + } + } + else + { + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + header_string = "Pf\n"; + format = babl_format ("Y float"); + np = 1; + break; + + case GIMP_RGB_IMAGE: + header_string = "PF\n"; + format = babl_format ("RGB float"); + np = 3; + break; + + default: + g_warning ("PFM: Unknown drawable_type\n"); + goto out; + } + } + + if (! output_write (output, header_string, strlen (header_string), error)) + goto out; + + rowinfo.zero_is_black = FALSE; + + if (drawable_type == GIMP_INDEXED_IMAGE) + { + guchar *cmap; + gint num_colors; + + cmap = gimp_image_get_colormap (image_ID, &num_colors); + + if (pbm) + { + /* Test which of the two colors is white and which is black */ + switch (num_colors) + { + case 1: + rowinfo.zero_is_black = (GIMP_RGB_LUMINANCE (cmap[0], + cmap[1], + cmap[2]) < 128); + break; + + case 2: + rowinfo.zero_is_black = (GIMP_RGB_LUMINANCE (cmap[0], + cmap[1], + cmap[2]) < + GIMP_RGB_LUMINANCE (cmap[3], + cmap[4], + cmap[5])); + break; + + default: + g_warning ("Images exported as PBM should be black/white"); + break; + } + } + else + { + const guchar *c = cmap; + gint i; + + for (i = 0; i < num_colors; i++) + { + red[i] = *c++; + grn[i] = *c++; + blu[i] = *c++; + } + + rowinfo.red = red; + rowinfo.grn = grn; + rowinfo.blu = blu; + } + + g_free (cmap); + } + + if (!float_format) + { + /* write out comment string */ + comment = g_strdup_printf("# Created by GIMP version %s PNM plug-in\n", + GIMP_VERSION); + + if (! output_write (output, comment, strlen (comment), error)) + goto out; + } + + /* write out resolution and maxval */ + if (pbm) + g_snprintf (buf, sizeof (buf), "%d %d\n", xres, yres); + else if (!float_format) + g_snprintf (buf, sizeof (buf), "%d %d\n%d\n", xres, yres, + rowinfo.bpc == 1 ? 255 : 65535); + else + g_snprintf (buf, sizeof (buf), "%d %d\n%s\n", xres, yres, + G_BYTE_ORDER == G_BIG_ENDIAN ? "1.0" : "-1.0"); + + if (! output_write (output, buf, strlen (buf), error)) + goto out; + + if (!float_format) + { + guchar *data; + guchar *d; + gchar *rowbuf = NULL; + + /* allocate a buffer for retrieving information from the pixel region */ + data = g_new (guchar, + gimp_tile_height () * xres * + babl_format_get_bytes_per_pixel (format)); + + rowbuf = g_new (gchar, rowbufsize + 1); + + rowinfo.output = output; + rowinfo.rowbuf = rowbuf; + rowinfo.xres = xres; + rowinfo.np = np; + + d = NULL; /* only to please the compiler */ + + /* Write the body out */ + for (ypos = 0; ypos < yres; ypos++) + { + if ((ypos % gimp_tile_height ()) == 0) + { + yend = ypos + gimp_tile_height (); + yend = MIN (yend, yres); + + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, ypos, xres, yend - ypos), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + d = data; + } + + if (! saverow (&rowinfo, d, error)) + { + g_free (rowbuf); + g_free (data); + goto out; + } + + d += xres * (np ? np : 1) * rowinfo.bpc; + + if (ypos % 32 == 0) + gimp_progress_update ((double) ypos / (double) yres); + } + + g_free (rowbuf); + g_free (data); + } + else + { + /* allocate a buffer for retrieving information from the pixel + region */ + gfloat *data = g_new (gfloat, xres * np); + + rowinfo.output = output; + rowinfo.rowbuf = NULL; + rowinfo.xres = xres; + rowinfo.np = np; + + /* Write the body out in reverse row order */ + for (ypos = yres - 1; ypos >= 0; ypos--) + { + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, ypos, xres, 1), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (! pnmsaverow_float (&rowinfo, data, error)) + { + g_free (data); + goto out; + } + + if (ypos % 32 == 0) + gimp_progress_update ((double) (yres - ypos) / (double) yres); + } + + g_free (data); + } + + gimp_progress_update (1.0); + status = TRUE; + + out: + if (! status) + { + GCancellable *cancellable = g_cancellable_new (); + + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + } + + if (comment) + g_free (comment); + if (buffer) + g_object_unref (buffer); + if (output) + g_object_unref (output); + + return status; +} + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *frame; + gboolean run; + + dialog = gimp_export_dialog_new (_("PNM"), PLUG_IN_BINARY, PNM_SAVE_PROC); + + /* file save type */ + frame = gimp_int_radio_group_new (TRUE, _("Data formatting"), + G_CALLBACK (gimp_radio_button_update), + &psvals.raw, psvals.raw, + + _("_Raw"), TRUE, NULL, + _("_ASCII"), FALSE, NULL, + + NULL); + gtk_container_set_border_width (GTK_CONTAINER (frame), 6); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + frame, FALSE, TRUE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + + +/**************** FILE SCANNER UTILITIES **************/ + +/* pnmscanner_create --- + * Creates a new scanner based on a file descriptor. The + * look ahead buffer is one character initially. + */ +static PNMScanner * +pnmscanner_create (GInputStream *input) +{ + PNMScanner *s; + gsize bytes_read; + + s = g_new (PNMScanner, 1); + + s->input = input; + s->inbuf = NULL; + s->eof = FALSE; + + if (! g_input_stream_read_all (input, &s->cur, 1, + &bytes_read, NULL, NULL) || + bytes_read != 1) + { + s->eof = TRUE; + } + + return s; +} + +/* pnmscanner_destroy --- + * Destroys a scanner and its resources. Doesn't close the fd. + */ +static void +pnmscanner_destroy (PNMScanner *s) +{ + if (s->inbuf) + g_free (s->inbuf); + + g_free (s); +} + +/* pnmscanner_createbuffer --- + * Creates a buffer so we can do buffered reads. + */ +static void +pnmscanner_createbuffer (PNMScanner *s, + gint bufsize) +{ + gsize bytes_read; + + s->inbuf = g_new (gchar, bufsize); + s->inbufsize = bufsize; + s->inbufpos = 0; + s->inbufvalidsize = 0; + + g_input_stream_read_all (s->input, s->inbuf, bufsize, + &bytes_read, NULL, NULL); + + s->inbufvalidsize = bytes_read; +} + +/* pnmscanner_gettoken --- + * Gets the next token, eating any leading whitespace. + */ +static void +pnmscanner_gettoken (PNMScanner *s, + gchar *buf, + gint bufsize) +{ + gint ctr = 0; + + pnmscanner_eatwhitespace (s); + + while (! s->eof && + ! g_ascii_isspace (s->cur) && + (s->cur != '#') && + (ctr < bufsize)) + { + buf[ctr++] = s->cur; + pnmscanner_getchar (s); + } + + buf[ctr] = '\0'; +} + +/* pnmscanner_getsmalltoken --- + * Gets the next char, eating any leading whitespace. + */ +static void +pnmscanner_getsmalltoken (PNMScanner *s, + gchar *buf) +{ + pnmscanner_eatwhitespace (s); + + if (! s->eof && ! g_ascii_isspace (s->cur) && (s->cur != '#')) + { + *buf = s->cur; + pnmscanner_getchar (s); + } +} + +/* pnmscanner_getchar --- + * Reads a character from the input stream + */ +static void +pnmscanner_getchar (PNMScanner *s) +{ + if (s->inbuf) + { + s->cur = s->inbuf[s->inbufpos++]; + + if (s->inbufpos >= s->inbufvalidsize) + { + if (s->inbufpos > s->inbufvalidsize) + { + s->eof = 1; + } + else + { + gsize bytes_read; + + g_input_stream_read_all (s->input, s->inbuf, s->inbufsize, + &bytes_read, NULL, NULL); + + s->inbufvalidsize = bytes_read; + } + + s->inbufpos = 0; + } + } + else + { + gsize bytes_read; + + s->eof = FALSE; + + if (! g_input_stream_read_all (s->input, &s->cur, 1, + &bytes_read, NULL, NULL) || + bytes_read != 1) + { + s->eof = TRUE; + } + } +} + +/* pnmscanner_eatwhitespace --- + * Eats up whitespace from the input and returns when done or eof. + * Also deals with comments. + */ +static void +pnmscanner_eatwhitespace (PNMScanner *s) +{ + gint state = 0; + + while (!(s->eof) && (state != -1)) + { + switch (state) + { + case 0: /* in whitespace */ + if (s->cur == '#') + { + state = 1; /* goto comment */ + pnmscanner_getchar (s); + } + else if (! g_ascii_isspace (s->cur)) + { + state = -1; + } + else + { + pnmscanner_getchar (s); + } + break; + + case 1: /* in comment */ + if (s->cur == '\n') + state = 0; /* goto whitespace */ + pnmscanner_getchar (s); + break; + } + } +} diff --git a/plug-ins/common/file-ps.c b/plug-ins/common/file-ps.c new file mode 100644 index 0000000..62d88c4 --- /dev/null +++ b/plug-ins/common/file-ps.c @@ -0,0 +1,3891 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * PostScript file plugin + * PostScript writing and GhostScript interfacing code + * Copyright (C) 1997-98 Peter Kirchgessner + * (email: peter@kirchgessner.net, WWW: http://www.kirchgessner.net) + * + * Added controls for TextAlphaBits and GraphicsAlphaBits + * George White + * + * Added Ascii85 encoding + * Austin Donnelly + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* Event history: + * V 0.90, PK, 28-Mar-97: Creation. + * V 0.91, PK, 03-Apr-97: Clip everything outside BoundingBox. + * 24-Apr-97: Multi page read support. + * V 1.00, PK, 30-Apr-97: PDF support. + * V 1.01, PK, 05-Oct-97: Parse rc-file. + * V 1.02, GW, 09-Oct-97: Antialiasing support. + * PK, 11-Oct-97: No progress bars when running non-interactive. + * New procedure file_ps_load_setargs to set + * load-arguments non-interactively. + * If GS_OPTIONS are not set, use at least "-dSAFER" + * V 1.03, nn, 20-Dec-97: Initialize some variables + * V 1.04, PK, 20-Dec-97: Add Encapsulated PostScript output and preview + * V 1.05, PK, 21-Sep-98: Write b/w-images (indexed) using image-operator + * V 1.06, PK, 22-Dec-98: Fix problem with writing color PS files. + * Ghostview may hang when displaying the files. + * V 1.07, PK, 14-Sep-99: Add resolution to image + * V 1.08, PK, 16-Jan-2000: Add PostScript-Level 2 by Austin Donnelly + * V 1.09, PK, 15-Feb-2000: Force showpage on EPS-files + * Add "RunLength" compression + * Fix problem with "Level 2" toggle + * V 1.10, PK, 15-Mar-2000: For load EPSF, allow negative Bounding Box Values + * Save PS: don't start lines of image data with %% + * to prevent problems with stupid PostScript + * analyzer programs (Stanislav Brabec) + * Add BeginData/EndData comments + * Save PS: Set default rotation to 0 + * V 1.11, PK, 20-Aug-2000: Fix problem with BoundingBox recognition + * for Mac files. + * Fix problem with loop when reading not all + * images of a multi page file. + * PK, 31-Aug-2000: Load PS: Add checks for space in filename. + * V 1.12 PK, 19-Jun-2001: Fix problem with command line switch -- + * (reported by Ferenc Wagner) + * V 1.13 PK, 07-Apr-2002: Fix problem with DOS binary EPS files + * V 1.14 PK, 14-May-2002: Workaround EPS files of Adb. Ill. 8.0 + * V 1.15 PK, 04-Oct-2002: Be more accurate with using BoundingBox + * V 1.16 PK, 22-Jan-2004: Don't use popen(), use g_spawn_async_with_pipes() + * or g_spawn_sync(). + * V 1.17 PK, 19-Sep-2004: Fix problem with interpretation of bounding box + */ + +#include "config.h" + +#include +#include +#include + +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#include +#include +#include + +#define VERSIO 1.17 +static const gchar dversio[] = "v1.17 19-Sep-2004"; + +#define LOAD_PS_PROC "file-ps-load" +#define LOAD_EPS_PROC "file-eps-load" +#define LOAD_PS_SETARGS_PROC "file-ps-load-setargs" +#define LOAD_PS_THUMB_PROC "file-ps-load-thumb" +#define SAVE_PS_PROC "file-ps-save" +#define SAVE_EPS_PROC "file-eps-save" +#define PLUG_IN_BINARY "file-ps" +#define PLUG_IN_ROLE "gimp-file-ps" + + +#define STR_LENGTH 64 +#define MIN_RESOLUTION 5 +#define MAX_RESOLUTION 8192 + +/* Load info */ +typedef struct +{ + guint resolution; /* resolution (dpi) at which to run ghostscript */ + guint width, height; /* desired size (ghostscript may ignore this) */ + gboolean use_bbox; /* 0: use width/height, 1: try to use BoundingBox */ + gchar pages[STR_LENGTH]; /* Pages to load (eg.: 1,3,5-7) */ + gint pnm_type; /* 4: pbm, 5: pgm, 6: ppm, 7: automatic */ + gint textalpha; /* antialiasing: 1,2, or 4 TextAlphaBits */ + gint graphicsalpha; /* antialiasing: 1,2, or 4 GraphicsAlphaBits */ +} PSLoadVals; + +static PSLoadVals plvals = +{ + 100, /* 100 dpi */ + 826, 1170, /* default width/height (A4) */ + TRUE, /* try to use BoundingBox */ + "1", /* pages to load */ + 6, /* use ppm (color) */ + 1, /* don't use text antialiasing */ + 1 /* don't use graphics antialiasing */ +}; + +/* Widgets for width and height of PostScript image to +* be loaded, so that they can be updated when desired resolution is +* changed +*/ +static GtkWidget *ps_width_spinbutton; +static GtkWidget *ps_height_spinbutton; + +/* Save info */ +typedef struct +{ + gdouble width, height; /* Size of image */ + gdouble x_offset, y_offset; /* Offset to image on page */ + gboolean unit_mm; /* Unit of measure (0: inch, 1: mm) */ + gboolean keep_ratio; /* Keep aspect ratio */ + gint rotate; /* Rotation (0, 90, 180, 270) */ + gint level; /* PostScript Level */ + gboolean eps; /* Encapsulated PostScript flag */ + gboolean preview; /* Preview Flag */ + gint preview_size; /* Preview size */ +} PSSaveVals; + +static PSSaveVals psvals = +{ + 287.0, 200.0, /* Image size (A4) */ + 5.0, 5.0, /* Offset */ + TRUE, /* Unit is mm */ + TRUE, /* Keep edge ratio */ + 0, /* Rotate */ + 2, /* PostScript Level */ + FALSE, /* Encapsulated PostScript flag */ + FALSE, /* Preview flag */ + 256 /* Preview size */ +}; + +static const char hex[] = "0123456789abcdef"; + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); +static gboolean save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + +static gboolean save_ps_header (GOutputStream *output, + GFile *file, + GError **error); +static gboolean save_ps_setup (GOutputStream *output, + gint32 drawable_ID, + gint width, + gint height, + gint bpp, + GError **error); +static gboolean save_ps_trailer (GOutputStream *output, + GError **error); + +static gboolean save_ps_preview (GOutputStream *output, + gint32 drawable_ID, + GError **error); + +static gboolean save_gray (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static gboolean save_bw (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static gboolean save_index (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static gboolean save_rgb (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + +static gboolean print (GOutputStream *output, + GError **error, + const gchar *format, + ...) G_GNUC_PRINTF (3, 4); + +static gint32 create_new_image (const gchar *filename, + guint pagenum, + guint width, + guint height, + GimpImageBaseType type, + gint32 *layer_ID); + +static void check_load_vals (void); +static void check_save_vals (void); + +static gint page_in_list (gchar *list, + guint pagenum); + +static gint get_bbox (const gchar *filename, + gint *x0, + gint *y0, + gint *x1, + gint *y1); + +static FILE * ps_open (const gchar *filename, + const PSLoadVals *loadopt, + gint *llx, + gint *lly, + gint *urx, + gint *ury, + gboolean *is_epsf); + +static void ps_close (FILE *ifp); + +static gboolean skip_ps (FILE *ifp); + +static gint32 load_ps (const gchar *filename, + guint pagenum, + FILE *ifp, + gint llx, + gint lly, + gint urx, + gint ury); + +static void dither_grey (const guchar *grey, + guchar *bw, + gint npix, + gint linecount); + + +/* Dialog-handling */ + +static gint32 count_ps_pages (const gchar *filename); +static gboolean load_dialog (const gchar *filename); +static void load_pages_entry_callback (GtkWidget *widget, + gpointer data); + +static gboolean resolution_change_callback (GtkAdjustment *adjustment, + gpointer data); + +typedef struct +{ + GtkAdjustment *adjustment[4]; + gint level; +} SaveDialogVals; + +static gboolean save_dialog (void); +static void save_unit_toggle_update (GtkWidget *widget, + gpointer data); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +/* The run mode */ +static GimpRunMode l_run_mode; + +static void compress_packbits (int nin, + unsigned char *src, + int *nout, + unsigned char *dst); + + +static guint32 ascii85_buf = 0; +static gint ascii85_len = 0; +static gint ascii85_linewidth = 0; + +static GimpPageSelectorTarget ps_pagemode = GIMP_PAGE_SELECTOR_TARGET_LAYERS; + +static void +ascii85_init (void) +{ + ascii85_len = 0; + ascii85_linewidth = 0; +} + +static gboolean +ascii85_flush (GOutputStream *output, + GError **error) +{ + gchar c[5]; + gint i; + gboolean zero_case = (ascii85_buf == 0); + GString *string = g_string_new (NULL); + + static gint max_linewidth = 75; + + for (i = 4; i >= 0; i--) + { + c[i] = (ascii85_buf % 85) + '!'; + ascii85_buf /= 85; + } + + /* check for special case: "!!!!!" becomes "z", but only if not + * at end of data. */ + if (zero_case && (ascii85_len == 4)) + { + if (ascii85_linewidth >= max_linewidth) + { + g_string_append_c (string, '\n'); + + ascii85_linewidth = 0; + } + + g_string_append_c (string, 'z'); + + ascii85_linewidth++; + } + else + { + for (i = 0; i < ascii85_len + 1; i++) + { + if ((ascii85_linewidth >= max_linewidth) && (c[i] != '%')) + { + g_string_append_c (string, '\n'); + + ascii85_linewidth = 0; + } + + g_string_append_c (string, c[i]); + + ascii85_linewidth++; + } + } + + ascii85_len = 0; + ascii85_buf = 0; + + if (string->len > 0 && + ! g_output_stream_write_all (output, + string->str, string->len, NULL, + NULL, error)) + { + g_string_free (string, TRUE); + + return FALSE; + } + + g_string_free (string, TRUE); + + return TRUE; +} + +static inline gboolean +ascii85_out (GOutputStream *output, + guchar byte, + GError **error) +{ + if (ascii85_len == 4) + if (! ascii85_flush (output, error)) + return FALSE; + + ascii85_buf <<= 8; + ascii85_buf |= byte; + ascii85_len++; + + return TRUE; +} + +static gboolean +ascii85_nout (GOutputStream *output, + gint n, + guchar *uptr, + GError **error) +{ + while (n-- > 0) + { + if (! ascii85_out (output, *uptr, error)) + return FALSE; + + uptr++; + } + + return TRUE; +} + +static gboolean +ascii85_done (GOutputStream *output, + GError **error) +{ + if (ascii85_len) + { + /* zero any unfilled buffer portion, then flush */ + ascii85_buf <<= (8 * (4 - ascii85_len)); + + if (! ascii85_flush (output, error)) + return FALSE; + } + + if (! print (output, error, "~>\n")) + return FALSE; + + return TRUE; +} + + +static void +compress_packbits (int nin, + unsigned char *src, + int *nout, + unsigned char *dst) + +{ + unsigned char c; + int nrepeat, nliteral; + unsigned char *run_start; + unsigned char *start_dst = dst; + unsigned char *last_literal = NULL; + + for (;;) + { + if (nin <= 0) break; + + run_start = src; + c = *run_start; + + /* Search repeat bytes */ + if ((nin > 1) && (c == src[1])) + { + nrepeat = 1; + nin -= 2; + src += 2; + while ((nin > 0) && (c == *src)) + { + nrepeat++; + src++; + nin--; + if (nrepeat == 127) break; /* Maximum repeat */ + } + + /* Add two-byte repeat to last literal run ? */ + if ( (nrepeat == 1) + && (last_literal != NULL) && (((*last_literal)+1)+2 <= 128)) + { + *last_literal += 2; + *(dst++) = c; + *(dst++) = c; + continue; + } + + /* Add repeat run */ + *(dst++) = (unsigned char)((-nrepeat) & 0xff); + *(dst++) = c; + last_literal = NULL; + continue; + } + /* Search literal bytes */ + nliteral = 1; + nin--; + src++; + + for (;;) + { + if (nin <= 0) break; + + if ((nin >= 2) && (src[0] == src[1])) /* A two byte repeat ? */ + break; + + nliteral++; + nin--; + src++; + if (nliteral == 128) break; /* Maximum literal run */ + } + + /* Could be added to last literal run ? */ + if ((last_literal != NULL) && (((*last_literal)+1)+nliteral <= 128)) + { + *last_literal += nliteral; + } + else + { + last_literal = dst; + *(dst++) = (unsigned char)(nliteral-1); + } + while (nliteral-- > 0) *(dst++) = *(run_start++); + } + *nout = dst - start_dst; +} + + +typedef struct +{ + goffset eol; + goffset begin_data; +} PS_DATA_POS; + +static PS_DATA_POS ps_data_pos = { 0, 0 }; + +static gboolean +ps_begin_data (GOutputStream *output, + GError **error) +{ + /* %%BeginData: 123456789012 ASCII Bytes */ + if (! print (output, error, "%s", "%%BeginData: ")) + return FALSE; + + ps_data_pos.eol = g_seekable_tell (G_SEEKABLE (output)); + + if (! print (output, error, "\n")) + return FALSE; + + ps_data_pos.begin_data = g_seekable_tell (G_SEEKABLE (output)); + + return TRUE; +} + +static gboolean +ps_end_data (GOutputStream *output, + GError **error) +{ + goffset end_data; + gchar s[64]; + + if ((ps_data_pos.begin_data > 0) && (ps_data_pos.eol > 0)) + { + end_data = g_seekable_tell (G_SEEKABLE (output)); + + if (end_data > 0) + { + g_snprintf (s, sizeof (s), + "%"G_GOFFSET_FORMAT" ASCII Bytes", end_data - ps_data_pos.begin_data); + + if (! g_seekable_seek (G_SEEKABLE (output), + ps_data_pos.eol - strlen (s), G_SEEK_SET, + NULL, error)) + return FALSE; + + if (! print (output, error, "%s", s)) + return FALSE; + + if (! g_seekable_seek (G_SEEKABLE (output), + end_data, G_SEEK_SET, + NULL, error)) + return FALSE; + } + } + + if (! print (output, error, "%s\n", "%%EndData")) + return FALSE; + + return TRUE; +} + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef set_load_args[] = + { + { GIMP_PDB_INT32, "resolution", "Resolution to interpret image (dpi)" }, + { GIMP_PDB_INT32, "width", "Desired width" }, + { GIMP_PDB_INT32, "height", "Desired height" }, + { GIMP_PDB_INT32, "check-bbox", "0: Use width/height, 1: Use BoundingBox" }, + { GIMP_PDB_STRING, "pages", "Pages to load (e.g.: 1,3,5-7)" }, + { GIMP_PDB_INT32, "coloring", "4: b/w, 5: grey, 6: color image, 7: automatic" }, + { GIMP_PDB_INT32, "text-alpha-bits", "1, 2, or 4" }, + { GIMP_PDB_INT32, "graphic-alpha-bits", "1, 2, or 4" } + }; + + static const GimpParamDef thumb_args[] = + { + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_INT32, "thumb-size", "Preferred thumbnail size" } + }; + static const GimpParamDef thumb_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_FLOAT, "width", "Width of the image in PostScript file (0: use input image size)" }, + { GIMP_PDB_FLOAT, "height", "Height of image in PostScript file (0: use input image size)" }, + { GIMP_PDB_FLOAT, "x-offset", "X-offset to image from lower left corner" }, + { GIMP_PDB_FLOAT, "y-offset", "Y-offset to image from lower left corner" }, + { GIMP_PDB_INT32, "unit", "Unit for width/height/offset. 0: inches, 1: millimeters" }, + { GIMP_PDB_INT32, "keep-ratio", "0: use width/height, 1: keep aspect ratio" }, + { GIMP_PDB_INT32, "rotation", "0, 90, 180, 270" }, + { GIMP_PDB_INT32, "eps-flag", "0: PostScript, 1: Encapsulated PostScript" }, + { GIMP_PDB_INT32, "preview", "0: no preview, >0: max. size of preview" }, + { GIMP_PDB_INT32, "level", "1: PostScript Level 1, 2: PostScript Level 2" } + }; + + gimp_install_procedure (LOAD_PS_PROC, + "load PostScript documents", + "load PostScript documents", + "Peter Kirchgessner ", + "Peter Kirchgessner", + dversio, + N_("PostScript document"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PS_PROC, "application/postscript"); + gimp_register_magic_load_handler (LOAD_PS_PROC, + "ps", + "", + "0,string,%!,0,long,0xc5d0d3c6"); + + gimp_install_procedure (LOAD_EPS_PROC, + "load Encapsulated PostScript images", + "load Encapsulated PostScript images", + "Peter Kirchgessner ", + "Peter Kirchgessner", + dversio, + N_("Encapsulated PostScript image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_EPS_PROC, "image/x-eps"); + gimp_register_magic_load_handler (LOAD_EPS_PROC, + "eps", + "", + "0,string,%!,0,long,0xc5d0d3c6"); + + gimp_install_procedure (LOAD_PS_SETARGS_PROC, + "set additional parameters for procedure file-ps-load", + "set additional parameters for procedure file-ps-load", + "Peter Kirchgessner ", + "Peter Kirchgessner", + dversio, + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (set_load_args), 0, + set_load_args, NULL); + + gimp_install_procedure (LOAD_PS_THUMB_PROC, + "Loads a small preview from a PostScript or PDF document", + "", + "Peter Kirchgessner ", + "Peter Kirchgessner", + dversio, + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (thumb_args), + G_N_ELEMENTS (thumb_return_vals), + thumb_args, thumb_return_vals); + + gimp_register_thumbnail_loader (LOAD_PS_PROC, LOAD_PS_THUMB_PROC); + gimp_register_thumbnail_loader (LOAD_EPS_PROC, LOAD_PS_THUMB_PROC); + + gimp_install_procedure (SAVE_PS_PROC, + "export image as PostScript document", + "PostScript exporting handles all image types except " + "those with alpha channels.", + "Peter Kirchgessner ", + "Peter Kirchgessner", + dversio, + N_("PostScript document"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PS_PROC, "application/postscript"); + gimp_register_file_handler_uri (SAVE_PS_PROC); + gimp_register_save_handler (SAVE_PS_PROC, "ps", ""); + + gimp_install_procedure (SAVE_EPS_PROC, + "export image as Encapsulated PostScript image", + "PostScript exporting handles all image types except " + "those with alpha channels.", + "Peter Kirchgessner ", + "Peter Kirchgessner", + dversio, + N_("Encapsulated PostScript image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_EPS_PROC, "application/x-eps"); + gimp_register_file_handler_uri (SAVE_EPS_PROC); + gimp_register_save_handler (SAVE_EPS_PROC, "eps", ""); +} + +static void +ps_set_save_size (PSSaveVals *vals, + gint32 image_ID) +{ + gdouble xres, yres, factor, iw, ih; + guint width, height; + GimpUnit unit; + + gimp_image_get_resolution (image_ID, &xres, &yres); + + if ((xres < 1e-5) || (yres < 1e-5)) + xres = yres = 72.0; + + /* Calculate size of image in inches */ + width = gimp_image_width (image_ID); + height = gimp_image_height (image_ID); + iw = width / xres; + ih = height / yres; + + unit = gimp_image_get_unit (image_ID); + factor = gimp_unit_get_factor (unit); + + if (factor == 0.0254 || + factor == 0.254 || + factor == 2.54 || + factor == 25.4) + { + vals->unit_mm = TRUE; + } + + if (vals->unit_mm) + { + iw *= 25.4; + ih *= 25.4; + } + + vals->width = iw; + vals->height = ih; +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID = -1; + gint32 drawable_ID = -1; + gint32 orig_image_ID = -1; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + l_run_mode = run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PS_PROC) == 0 || + strcmp (name, LOAD_EPS_PROC) == 0) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (LOAD_PS_PROC, &plvals); + + if (! load_dialog (param[1].data.d_string)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 3) + status = GIMP_PDB_CALLING_ERROR; + else /* Get additional interpretation arguments */ + gimp_get_data (LOAD_PS_PROC, &plvals); + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (LOAD_PS_PROC, &plvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + check_load_vals (); + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + /* Store plvals data */ + if (status == GIMP_PDB_SUCCESS) + gimp_set_data (LOAD_PS_PROC, &plvals, sizeof (PSLoadVals)); + } + else if (strcmp (name, LOAD_PS_THUMB_PROC) == 0) + { + if (nparams < 2) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + gint size = param[1].data.d_int32; + + /* We should look for an embedded preview but for now we + * just load the document at a small resolution and the + * first page only. + */ + + plvals.resolution = size / 4; + plvals.width = size; + plvals.height = size; + strncpy (plvals.pages, "1", sizeof (plvals.pages) - 1); + + check_load_vals (); + image_ID = load_image (param[0].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + } + else if (strcmp (name, SAVE_PS_PROC) == 0 || + strcmp (name, SAVE_EPS_PROC) == 0) + { + psvals.eps = strcmp (name, SAVE_PS_PROC); + + image_ID = orig_image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, + psvals.eps ? "EPS" : "PostScript", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (name, &psvals); + + ps_set_save_size (&psvals, orig_image_ID); + + /* First acquire information with a dialog */ + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 15) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + psvals.width = param[5].data.d_float; + psvals.height = param[6].data.d_float; + psvals.x_offset = param[7].data.d_float; + psvals.y_offset = param[8].data.d_float; + psvals.unit_mm = (param[9].data.d_int32 != 0); + psvals.keep_ratio = (param[10].data.d_int32 != 0); + psvals.rotate = param[11].data.d_int32; + psvals.eps = (param[12].data.d_int32 != 0); + psvals.preview = (param[13].data.d_int32 != 0); + psvals.preview_size = param[13].data.d_int32; + psvals.level = param[14].data.d_int32; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (name, &psvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if ((psvals.width == 0.0) || (psvals.height == 0.0)) + ps_set_save_size (&psvals, orig_image_ID); + + check_save_vals (); + + if (save_image (g_file_new_for_uri (param[3].data.d_string), + image_ID, drawable_ID, + &error)) + { + /* Store psvals data */ + gimp_set_data (name, &psvals, sizeof (PSSaveVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else if (strcmp (name, LOAD_PS_SETARGS_PROC) == 0) + { + /* Make sure all the arguments are there! */ + if (nparams != 8) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + plvals.resolution = param[0].data.d_int32; + plvals.width = param[1].data.d_int32; + plvals.height = param[2].data.d_int32; + plvals.use_bbox = param[3].data.d_int32; + if (param[4].data.d_string != NULL) + strncpy (plvals.pages, param[4].data.d_string, + sizeof (plvals.pages)); + else + plvals.pages[0] = '\0'; + plvals.pages[sizeof (plvals.pages) - 1] = '\0'; + plvals.pnm_type = param[5].data.d_int32; + plvals.textalpha = param[6].data.d_int32; + plvals.graphicsalpha = param[7].data.d_int32; + check_load_vals (); + + gimp_set_data (LOAD_PS_PROC, &plvals, sizeof (PSLoadVals)); + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + gint32 image_ID = 0; + gint32 *image_list, *nl; + guint page_count; + FILE *ifp; + gchar *temp; + gint llx, lly, urx, ury; + gint k, n_images, max_images, max_pagenum; + gboolean is_epsf; + +#ifdef PS_DEBUG + g_print ("load_image:\n resolution = %d\n", plvals.resolution); + g_print (" %dx%d pixels\n", plvals.width, plvals.height); + g_print (" BoundingBox: %d\n", plvals.use_bbox); + g_print (" Coloring: %d\n", plvals.pnm_type); + g_print (" TextAlphaBits: %d\n", plvals.textalpha); + g_print (" GraphicsAlphaBits: %d\n", plvals.graphicsalpha); +#endif + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + /* Try to see if PostScript file is available */ + ifp = g_fopen (filename, "r"); + if (ifp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + fclose (ifp); + + ifp = ps_open (filename, &plvals, &llx, &lly, &urx, &ury, &is_epsf); + if (!ifp) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR, + _("Could not interpret PostScript file '%s'"), + gimp_filename_to_utf8 (filename)); + return -1; + } + + image_list = g_new (gint32, 10); + n_images = 0; + max_images = 10; + + max_pagenum = 9999; /* Try to get the maximum pagenumber to read */ + if (is_epsf) + max_pagenum = 1; + + if (!page_in_list (plvals.pages, max_pagenum)) /* Is there a limit in list ? */ + { + max_pagenum = -1; + for (temp = plvals.pages; *temp != '\0'; temp++) + { + if ((*temp < '0') || (*temp > '9')) + continue; /* Search next digit */ + sscanf (temp, "%d", &k); + if (k > max_pagenum) + max_pagenum = k; + while ((*temp >= '0') && (*temp <= '9')) + temp++; + temp--; + } + + if (max_pagenum < 1) + max_pagenum = 9999; + } + + /* Load all images */ + for (page_count = 1; page_count <= max_pagenum; page_count++) + { + if (page_in_list (plvals.pages, page_count)) + { + image_ID = load_ps (filename, page_count, ifp, llx, lly, urx, ury); + if (image_ID == -1) + break; + + gimp_image_set_resolution (image_ID, + (gdouble) plvals.resolution, + (gdouble) plvals.resolution); + + if (n_images == max_images) + { + nl = (gint32 *) g_realloc (image_list, + (max_images+10)*sizeof (gint32)); + if (nl == NULL) break; + image_list = nl; + max_images += 10; + } + image_list[n_images++] = image_ID; + } + else /* Skip an image */ + { + image_ID = -1; + if (! skip_ps (ifp)) + break; + } + } + + ps_close (ifp); + + if (ps_pagemode == GIMP_PAGE_SELECTOR_TARGET_LAYERS) + { + for (k = 0; k < n_images; k++) + { + gchar *name; + + if (k == 0) + { + image_ID = image_list[0]; + + name = g_strdup_printf (_("%s-pages"), filename); + gimp_image_set_filename (image_ID, name); + g_free (name); + } + else + { + gint32 current_layer; + gint32 tmp_ID; + + tmp_ID = gimp_image_get_active_drawable (image_list[k]); + + name = gimp_item_get_name (tmp_ID); + + current_layer = gimp_layer_new_from_drawable (tmp_ID, image_ID); + gimp_item_set_name (current_layer, name); + gimp_image_insert_layer (image_ID, current_layer, -1, -1); + gimp_image_delete (image_list[k]); + + g_free (name); + } + } + + gimp_image_undo_enable (image_ID); + } + else + { + /* Display images in reverse order. + * The last will be displayed by GIMP itself + */ + for (k = n_images - 1; k >= 0; k--) + { + gimp_image_undo_enable (image_list[k]); + gimp_image_clean_all (image_list[k]); + + if (l_run_mode != GIMP_RUN_NONINTERACTIVE && k > 0) + gimp_display_new (image_list[k]); + } + + image_ID = (n_images > 0) ? image_list[0] : -1; + } + + g_free (image_list); + + return image_ID; +} + + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GOutputStream *output; + GCancellable *cancellable; + GimpImageType drawable_type; + + drawable_type = gimp_drawable_type (drawable_ID); + + /* Make sure we're not exporting an image with an alpha channel */ + if (gimp_drawable_has_alpha (drawable_ID)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("PostScript export cannot handle images with alpha channels")); + return FALSE; + } + + switch (drawable_type) + { + case GIMP_INDEXED_IMAGE: + case GIMP_GRAY_IMAGE: + case GIMP_RGB_IMAGE: + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Cannot operate on unknown image types.")); + return FALSE; + break; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_file_get_utf8_name (file)); + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (output) + { + GOutputStream *buffered; + + buffered = g_buffered_output_stream_new (output); + g_object_unref (output); + + output = buffered; + } + else + { + return FALSE; + } + + if (! save_ps_header (output, file, error)) + goto fail; + + switch (drawable_type) + { + case GIMP_INDEXED_IMAGE: + if (! save_index (output, image_ID, drawable_ID, error)) + goto fail; + break; + + case GIMP_GRAY_IMAGE: + if (! save_gray (output, image_ID, drawable_ID, error)) + goto fail; + break; + + case GIMP_RGB_IMAGE: + if (! save_rgb (output, image_ID, drawable_ID, error)) + goto fail; + break; + + default: + g_return_val_if_reached (FALSE); + } + + if (! save_ps_trailer (output, error)) + goto fail; + + if (! g_output_stream_close (output, NULL, error)) + goto fail; + + g_object_unref (output); + + return TRUE; + + fail: + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + + g_object_unref (output); + g_object_unref (cancellable); + + return FALSE; +} + + +/* Check (and correct) the load values plvals */ +static void +check_load_vals (void) +{ + if (plvals.resolution < MIN_RESOLUTION) + plvals.resolution = MIN_RESOLUTION; + else if (plvals.resolution > MAX_RESOLUTION) + plvals.resolution = MAX_RESOLUTION; + + if (plvals.width < 2) + plvals.width = 2; + if (plvals.height < 2) + plvals.height = 2; + plvals.use_bbox = (plvals.use_bbox != 0); + if (plvals.pages[0] == '\0') + strncpy (plvals.pages, "1-99", sizeof (plvals.pages) - 1); + if ((plvals.pnm_type < 4) || (plvals.pnm_type > 7)) + plvals.pnm_type = 6; + if ( (plvals.textalpha != 1) && (plvals.textalpha != 2) + && (plvals.textalpha != 4)) + plvals.textalpha = 1; + if ( (plvals.graphicsalpha != 1) && (plvals.graphicsalpha != 2) + && (plvals.graphicsalpha != 4)) + plvals.graphicsalpha = 1; +} + + +/* Check (and correct) the save values psvals */ +static void +check_save_vals (void) +{ + int i; + + i = psvals.rotate; + if ((i != 0) && (i != 90) && (i != 180) && (i != 270)) + psvals.rotate = 90; + if (psvals.preview_size <= 0) + psvals.preview = FALSE; +} + + +/* Check if a page is in a given list */ +static gint +page_in_list (gchar *list, + guint page_num) +{ + char tmplist[STR_LENGTH], *c0, *c1; + int state, start_num, end_num; +#define READ_STARTNUM 0 +#define READ_ENDNUM 1 +#define CHK_LIST(a,b,c) {int low=(a),high=(b),swp; \ + if ((low>0) && (high>0)) { \ + if (low>high) {swp=low; low=high; high=swp;} \ + if ((low<=(c))&&(high>=(c))) return (1); } } + + if ((list == NULL) || (*list == '\0')) + return 1; + + strncpy (tmplist, list, STR_LENGTH); + tmplist[STR_LENGTH-1] = '\0'; + + c0 = c1 = tmplist; + while (*c1) /* Remove all whitespace and break on unsupported characters */ + { + if ((*c1 >= '0') && (*c1 <= '9')) + { + *(c0++) = *c1; + } + else if ((*c1 == '-') || (*c1 == ',')) + { /* Try to remove double occurrences of these characters */ + if (c0 == tmplist) + { + *(c0++) = *c1; + } + else + { + if (*(c0-1) != *c1) + *(c0++) = *c1; + } + } + else + break; + + c1++; + } + + if (c0 == tmplist) + return 1; + + *c0 = '\0'; + + /* Now we have a comma separated list like 1-4-1,-3,1- */ + + start_num = end_num = -1; + state = READ_STARTNUM; + for (c0 = tmplist; *c0 != '\0'; c0++) + { + switch (state) + { + case READ_STARTNUM: + if (*c0 == ',') + { + if ((start_num > 0) && (start_num == (int)page_num)) + return -1; + start_num = -1; + } + else if (*c0 == '-') + { + if (start_num < 0) start_num = 1; + state = READ_ENDNUM; + } + else /* '0' - '9' */ + { + if (start_num < 0) start_num = 0; + start_num *= 10; + start_num += *c0 - '0'; + } + break; + + case READ_ENDNUM: + if (*c0 == ',') + { + if (end_num < 0) end_num = 9999; + CHK_LIST (start_num, end_num, (int)page_num); + start_num = end_num = -1; + state = READ_STARTNUM; + } + else if (*c0 == '-') + { + CHK_LIST (start_num, end_num, (int)page_num); + start_num = end_num; + end_num = -1; + } + else /* '0' - '9' */ + { + if (end_num < 0) end_num = 0; + end_num *= 10; + end_num += *c0 - '0'; + } + break; + } + } + if (state == READ_STARTNUM) + { + if (start_num > 0) + return (start_num == (int) page_num); + } + else + { + if (end_num < 0) end_num = 9999; + CHK_LIST (start_num, end_num, (int)page_num); + } + + return 0; +#undef CHK_LIST +} + + +/* A function like fgets, but treats single CR-character as line break. */ +/* As a line break the newline-character is returned. */ +static char *psfgets (char *s, int size, FILE *stream) + +{ + int c; + char *sptr = s; + + if (size <= 0) + return NULL; + + if (size == 1) + { + *s = '\0'; + return NULL; + } + + c = getc (stream); + if (c == EOF) + return NULL; + + for (;;) + { + /* At this point we have space in sptr for at least two characters */ + if (c == '\n') /* Got end of line (UNIX line end) ? */ + { + *(sptr++) = '\n'; + break; + } + else if (c == '\r') /* Got a carriage return. Check next character */ + { + c = getc (stream); + if ((c == EOF) || (c == '\n')) /* EOF or DOS line end ? */ + { + *(sptr++) = '\n'; /* Return UNIX line end */ + break; + } + else /* Single carriage return. Return UNIX line end. */ + { + ungetc (c, stream); /* Save the extra character */ + *(sptr++) = '\n'; + break; + } + } + else /* no line end character */ + { + *(sptr++) = (char)c; + size--; + } + if (size == 1) + break; /* Only space for the nul-character ? */ + + c = getc (stream); + if (c == EOF) + break; + } + + *sptr = '\0'; + + return s; +} + + +/* Get the BoundingBox of a PostScript file. On success, 0 is returned. */ +/* On failure, -1 is returned. */ +static gint +get_bbox (const gchar *filename, + gint *x0, + gint *y0, + gint *x1, + gint *y1) +{ + char line[1024], *src; + FILE *ifp; + int retval = -1; + + ifp = g_fopen (filename, "rb"); + if (ifp == NULL) + return -1; + + for (;;) + { + if (psfgets (line, sizeof (line)-1, ifp) == NULL) break; + if ((line[0] != '%') || (line[1] != '%')) continue; + src = &(line[2]); + while ((*src == ' ') || (*src == '\t')) src++; + if (strncmp (src, "BoundingBox", 11) != 0) continue; + src += 11; + while ((*src == ' ') || (*src == '\t') || (*src == ':')) src++; + if (strncmp (src, "(atend)", 7) == 0) continue; + if (sscanf (src, "%d%d%d%d", x0, y0, x1, y1) == 4) + retval = 0; + break; + } + fclose (ifp); + + return retval; +} + +static gchar *pnmfile; + +/* Open the PostScript file. On failure, NULL is returned. */ +/* The filepointer returned will give a PNM-file generated */ +/* by the PostScript-interpreter. */ +static FILE * +ps_open (const gchar *filename, + const PSLoadVals *loadopt, + gint *llx, + gint *lly, + gint *urx, + gint *ury, + gboolean *is_epsf) +{ + const gchar *driver; + GPtrArray *cmdA; + gchar **pcmdA; + FILE *fd_popen = NULL; + FILE *eps_file; + gint width, height; + gint resolution; + gint x0, y0, x1, y1; + gint offx = 0; + gint offy = 0; + gboolean is_pdf; + gboolean maybe_epsf = FALSE; + int code; + void *instance = NULL; + + resolution = loadopt->resolution; + *llx = *lly = 0; + width = loadopt->width; + height = loadopt->height; + *urx = width - 1; + *ury = height - 1; + + /* Check if the file is a PDF. For PDF, we can't set geometry */ + is_pdf = FALSE; + + /* Check if it is a EPS-file */ + *is_epsf = FALSE; + + eps_file = g_fopen (filename, "rb"); + + if (eps_file != NULL) + { + gchar hdr[512]; + + fread (hdr, 1, sizeof(hdr), eps_file); + is_pdf = (strncmp (hdr, "%PDF", 4) == 0); + + if (!is_pdf) /* Check for EPSF */ + { + char *adobe, *epsf; + int ds = 0; + static unsigned char doseps[5] = { 0xc5, 0xd0, 0xd3, 0xc6, 0 }; + + hdr[sizeof(hdr)-1] = '\0'; + adobe = strstr (hdr, "PS-Adobe-"); + epsf = strstr (hdr, "EPSF-"); + + if ((adobe != NULL) && (epsf != NULL)) + ds = epsf - adobe; + + *is_epsf = ((ds >= 11) && (ds <= 15)); + + /* Illustrator uses negative values in BoundingBox without marking */ + /* files as EPSF. Try to handle that. */ + maybe_epsf = + (strstr (hdr, "%%Creator: Adobe Illustrator(R) 8.0") != 0); + + /* Check DOS EPS binary file */ + if ((!*is_epsf) && (strncmp (hdr, (char *)doseps, 4) == 0)) + *is_epsf = 1; + } + + fclose (eps_file); + } + + if ((!is_pdf) && (loadopt->use_bbox)) /* Try the BoundingBox ? */ + { + if (get_bbox (filename, &x0, &y0, &x1, &y1) == 0) + { + if (maybe_epsf && ((x0 < 0) || (y0 < 0))) + *is_epsf = 1; + + if (*is_epsf) /* Handle negative BoundingBox for EPSF */ + { + offx = -x0; x1 += offx; x0 += offx; + offy = -y0; y1 += offy; y0 += offy; + } + if ((x0 >= 0) && (y0 >= 0) && (x1 > x0) && (y1 > y0)) + { + *llx = (int)((x0/72.0) * resolution + 0.0001); + *lly = (int)((y0/72.0) * resolution + 0.0001); + /* Use upper bbox values as image size */ + width = (int)((x1/72.0) * resolution + 0.5); + height = (int)((y1/72.0) * resolution + 0.5); + /* Pixel coordinates must be one less */ + *urx = width - 1; + *ury = height - 1; + if (*urx < *llx) *urx = *llx; + if (*ury < *lly) *ury = *lly; + } + } + } + + switch (loadopt->pnm_type) + { + case 4: + driver = "pbmraw"; + break; + case 5: + driver = "pgmraw"; + break; + case 7: + driver = "pnmraw"; + break; + default: + driver = "ppmraw"; + break; + } + + /* For instance, the Win32 port of ghostscript doesn't work correctly when + * using standard output as output file. + * Thus, use a real output file. + */ + pnmfile = gimp_temp_name ("pnm"); + + /* Build command array */ + cmdA = g_ptr_array_new (); + + g_ptr_array_add (cmdA, g_strdup (g_get_prgname ())); + g_ptr_array_add (cmdA, g_strdup_printf ("-sDEVICE=%s", driver)); + g_ptr_array_add (cmdA, g_strdup_printf ("-r%d", resolution)); + + if (is_pdf) + { + /* Acrobat Reader honors CropBox over MediaBox, so let's match that + * behavior. + */ + g_ptr_array_add (cmdA, g_strdup ("-dUseCropBox")); + } + else + { + /* For PDF, we can't set geometry */ + g_ptr_array_add (cmdA, g_strdup_printf ("-g%dx%d", width, height)); + } + + /* Antialiasing not available for PBM-device */ + if ((loadopt->pnm_type != 4) && (loadopt->textalpha != 1)) + g_ptr_array_add (cmdA, g_strdup_printf ("-dTextAlphaBits=%d", + loadopt->textalpha)); + if ((loadopt->pnm_type != 4) && (loadopt->graphicsalpha != 1)) + g_ptr_array_add (cmdA, g_strdup_printf ("-dGraphicsAlphaBits=%d", + loadopt->graphicsalpha)); + g_ptr_array_add (cmdA, g_strdup ("-q")); + g_ptr_array_add (cmdA, g_strdup ("-dBATCH")); + g_ptr_array_add (cmdA, g_strdup ("-dNOPAUSE")); + + /* If no additional options specified, use at least -dSAFER */ + if (g_getenv ("GS_OPTIONS") == NULL) + g_ptr_array_add (cmdA, g_strdup ("-dSAFER")); + + /* Output file name */ + g_ptr_array_add (cmdA, g_strdup_printf ("-sOutputFile=%s", pnmfile)); + + /* Offset command for gs to get image part with negative x/y-coord. */ + if ((offx != 0) || (offy != 0)) + { + g_ptr_array_add (cmdA, g_strdup ("-c")); + g_ptr_array_add (cmdA, g_strdup_printf ("%d", offx)); + g_ptr_array_add (cmdA, g_strdup_printf ("%d", offy)); + g_ptr_array_add (cmdA, g_strdup ("translate")); + } + + /* input file name */ + g_ptr_array_add (cmdA, g_strdup ("-f")); + g_ptr_array_add (cmdA, g_strdup (filename)); + + if (*is_epsf) + { + g_ptr_array_add (cmdA, g_strdup ("-c")); + g_ptr_array_add (cmdA, g_strdup ("showpage")); + } + + g_ptr_array_add (cmdA, g_strdup ("-c")); + g_ptr_array_add (cmdA, g_strdup ("quit")); + g_ptr_array_add (cmdA, NULL); + + pcmdA = (gchar **) cmdA->pdata; + +#ifdef PS_DEBUG + { + gchar **p = pcmdA; + g_print ("Passing args (argc=%d):\n", cmdA->len - 1); + + while (*p) + { + g_print ("%s\n", *p); + p++; + } + } +#endif + + code = gsapi_new_instance (&instance, NULL); + if (code == 0) { + code = gsapi_init_with_args (instance, cmdA->len - 1, pcmdA); + code = gsapi_exit (instance); + gsapi_delete_instance (instance); + } + + /* Don't care about exit status of ghostscript. */ + /* Just try to read what it wrote. */ + + fd_popen = g_fopen (pnmfile, "rb"); + + g_ptr_array_free (cmdA, FALSE); + g_strfreev (pcmdA); + + return fd_popen; +} + + +/* Close the PNM-File of the PostScript interpreter */ +static void +ps_close (FILE *ifp) +{ + /* If a real outputfile was used, close the file and remove it. */ + fclose (ifp); + g_unlink (pnmfile); +} + + +/* Read the header of a raw PNM-file and return type (4-6) or -1 on failure */ +static gint +read_pnmraw_type (FILE *ifp, + gint *width, + gint *height, + gint *maxval) +{ + int frst, scnd, thrd; + gint pnmtype; + gchar line[1024]; + + /* GhostScript may write some informational messages infront of the header. */ + /* We are just looking at a Px\n in the input stream. */ + frst = getc (ifp); + scnd = getc (ifp); + thrd = getc (ifp); + for (;;) + { + if (thrd == EOF) return -1; +#ifdef G_OS_WIN32 + if (thrd == '\r') thrd = getc (ifp); +#endif + if ((thrd == '\n') && (frst == 'P') && (scnd >= '1') && (scnd <= '6')) + break; + frst = scnd; + scnd = thrd; + thrd = getc (ifp); + } + pnmtype = scnd - '0'; + /* We don't use the ASCII-versions */ + if ((pnmtype >= 1) && (pnmtype <= 3)) + return -1; + + /* Read width/height */ + for (;;) + { + if (fgets (line, sizeof (line)-1, ifp) == NULL) + return -1; + if (line[0] != '#') + break; + } + if (sscanf (line, "%d%d", width, height) != 2) + return -1; + + *maxval = 255; + + if (pnmtype != 4) /* Read maxval */ + { + for (;;) + { + if (fgets (line, sizeof (line)-1, ifp) == NULL) + return -1; + if (line[0] != '#') + break; + } + if (sscanf (line, "%d", maxval) != 1) + return -1; + } + + return pnmtype; +} + + +/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */ +static gint32 +create_new_image (const gchar *filename, + guint pagenum, + guint width, + guint height, + GimpImageBaseType type, + gint32 *layer_ID) +{ + gint32 image_ID; + GimpImageType gdtype; + gchar *tmp; + + switch (type) + { + case GIMP_GRAY: + gdtype = GIMP_GRAY_IMAGE; + break; + case GIMP_INDEXED: + gdtype = GIMP_INDEXED_IMAGE; + break; + case GIMP_RGB: + default: + gdtype = GIMP_RGB_IMAGE; + } + + image_ID = gimp_image_new_with_precision (width, height, type, + GIMP_PRECISION_U8_GAMMA); + gimp_image_undo_disable (image_ID); + + tmp = g_strdup_printf ("%s-%d", filename, pagenum); + gimp_image_set_filename (image_ID, tmp); + g_free (tmp); + + tmp = g_strdup_printf (_("Page %d"), pagenum); + *layer_ID = gimp_layer_new (image_ID, tmp, width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + g_free (tmp); + + gimp_image_insert_layer (image_ID, *layer_ID, -1, 0); + + return image_ID; +} + + +/* Skip PNM image generated from PostScript file. */ +/* Return TRUE on success, FALSE otherwise. */ +static gboolean +skip_ps (FILE *ifp) +{ + guchar buf[8192]; + gsize len; + gint pnmtype, width, height, maxval, bpl; + + pnmtype = read_pnmraw_type (ifp, &width, &height, &maxval); + + if (pnmtype == 4) /* Portable bitmap */ + bpl = (width + 7) / 8; + else if (pnmtype == 5) + bpl = width; + else if (pnmtype == 6) + bpl = width * 3; + else + return FALSE; + + len = bpl * height; + while (len) + { + gsize bytes = fread (buf, 1, MIN (len, sizeof (buf)), ifp); + + if (bytes < MIN (len, sizeof (buf))) + return FALSE; + + len -= bytes; + } + + return TRUE; +} + + +/* Load PNM image generated from PostScript file */ +static gint32 +load_ps (const gchar *filename, + guint pagenum, + FILE *ifp, + gint llx, + gint lly, + gint urx, + gint ury) +{ + guchar *dest; + guchar *data, *bitline = NULL, *byteline = NULL, *byteptr, *temp; + guchar bit2byte[256*8]; + int width, height, tile_height, scan_lines, total_scan_lines; + int image_width, image_height; + int skip_left, skip_bottom; + int i, j, pnmtype, maxval, bpp, nread; + GimpImageBaseType imagetype; + gint32 layer_ID, image_ID; + GeglBuffer *buffer = NULL; + int err = 0, e; + + pnmtype = read_pnmraw_type (ifp, &width, &height, &maxval); + + if ((width == urx+1) && (height == ury+1)) /* gs respected BoundingBox ? */ + { + skip_left = llx; skip_bottom = lly; + image_width = width - skip_left; + image_height = height - skip_bottom; + } + else + { + skip_left = skip_bottom = 0; + image_width = width; + image_height = height; + } + if (pnmtype == 4) /* Portable Bitmap */ + { + imagetype = GIMP_INDEXED; + nread = (width+7)/8; + bpp = 1; + bitline = g_new (guchar, nread); + byteline = g_new (guchar, nread * 8); + + /* Get an array for mapping 8 bits in a byte to 8 bytes */ + temp = bit2byte; + for (j = 0; j < 256; j++) + for (i = 7; i >= 0; i--) + *(temp++) = ((j & (1 << i)) != 0); + } + else if (pnmtype == 5) /* Portable Greymap */ + { + imagetype = GIMP_GRAY; + nread = width; + bpp = 1; + byteline = g_new (guchar, nread); + } + else if (pnmtype == 6) /* Portable Pixmap */ + { + imagetype = GIMP_RGB; + nread = width * 3; + bpp = 3; + byteline = g_new (guchar, nread); + } + else + return -1; + + image_ID = create_new_image (filename, pagenum, + image_width, image_height, imagetype, + &layer_ID); + buffer = gimp_drawable_get_buffer (layer_ID); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * image_width * bpp); + + dest = data; + total_scan_lines = scan_lines = 0; + + if (pnmtype == 4) /* Read bitimage ? Must be mapped to indexed */ + { + const guchar BWColorMap[2*3] = { 255, 255, 255, 0, 0, 0 }; + + gimp_image_set_colormap (image_ID, BWColorMap, 2); + + for (i = 0; i < height; i++) + { + e = (fread (bitline, 1, nread, ifp) != nread); + if (total_scan_lines >= image_height) + continue; + err |= e; + if (err) + break; + + j = width; /* Map 1 byte of bitimage to 8 bytes of indexed image */ + temp = bitline; + byteptr = byteline; + while (j >= 8) + { + memcpy (byteptr, bit2byte + *(temp++)*8, 8); + byteptr += 8; + j -= 8; + } + if (j > 0) + memcpy (byteptr, bit2byte + *temp*8, j); + + memcpy (dest, byteline+skip_left, image_width); + dest += image_width; + scan_lines++; + total_scan_lines++; + + if ((scan_lines == tile_height) || ((i + 1) == image_height)) + { + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, i-scan_lines+1, + image_width, scan_lines), + 0, + NULL, + data, + GEGL_AUTO_ROWSTRIDE); + scan_lines = 0; + dest = data; + } + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) (i + 1) / (gdouble) image_height); + + if (err) + break; + } + } + else /* Read gray/rgb-image */ + { + for (i = 0; i < height; i++) + { + e = (fread (byteline, bpp, width, ifp) != width); + if (total_scan_lines >= image_height) + continue; + err |= e; + if (err) + break; + + memcpy (dest, byteline+skip_left*bpp, image_width*bpp); + dest += image_width*bpp; + scan_lines++; + total_scan_lines++; + + if ((scan_lines == tile_height) || ((i + 1) == image_height)) + { + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, i-scan_lines+1, + image_width, scan_lines), + 0, + NULL, + data, + GEGL_AUTO_ROWSTRIDE); + scan_lines = 0; + dest = data; + } + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) (i + 1) / (gdouble) image_height); + + if (err) + break; + } + } + gimp_progress_update (1.0); + + g_free (data); + g_free (byteline); + g_free (bitline); + + if (err) + g_message ("EOF encountered on reading"); + + g_object_unref (buffer); + + return (err ? -1 : image_ID); +} + + +/* Write out the PostScript file header */ +static gboolean +save_ps_header (GOutputStream *output, + GFile *file, + GError **error) +{ + gchar *basename = g_path_get_basename (gimp_file_get_utf8_name (file)); + time_t cutime = time (NULL); + + if (! print (output, error, + "%%!PS-Adobe-3.0%s\n", psvals.eps ? " EPSF-3.0" : "")) + goto fail; + + if (! print (output, error, + "%%%%Creator: GIMP PostScript file plug-in V %4.2f " + "by Peter Kirchgessner\n", VERSIO)) + goto fail; + + if (! print (output, error, + "%%%%Title: %s\n" + "%%%%CreationDate: %s" + "%%%%DocumentData: Clean7Bit\n", + basename, ctime (&cutime))) + goto fail; + + if (psvals.eps || (psvals.level > 1)) + if (! print (output, error,"%%%%LanguageLevel: 2\n")) + goto fail; + + if (! print (output, error, "%%%%Pages: 1\n")) + goto fail; + + g_free (basename); + + return TRUE; + + fail: + + g_free (basename); + + return FALSE; +} + + +/* Write out transformation for image */ +static gboolean +save_ps_setup (GOutputStream *output, + gint32 drawable_ID, + gint width, + gint height, + gint bpp, + GError **error) +{ + gdouble x_offset, y_offset, x_size, y_size; + gdouble urx, ury; + gdouble width_inch, height_inch; + gdouble f1, f2, dx, dy; + gint xtrans, ytrans; + gint i_urx, i_ury; + gchar tmpbuf1[G_ASCII_DTOSTR_BUF_SIZE]; + gchar tmpbuf2[G_ASCII_DTOSTR_BUF_SIZE]; + + /* initialize */ + + dx = 0.0; + dy = 0.0; + + x_offset = psvals.x_offset; + y_offset = psvals.y_offset; + width_inch = fabs (psvals.width); + height_inch = fabs (psvals.height); + + if (psvals.unit_mm) + { + x_offset /= 25.4; y_offset /= 25.4; + width_inch /= 25.4; height_inch /= 25.4; + } + + if (psvals.keep_ratio) /* Proportions to keep ? */ + { /* Fit the image into the allowed size */ + f1 = width_inch / width; + f2 = height_inch / height; + if (f1 < f2) + height_inch = width_inch * (gdouble) height / (gdouble) width; + else + width_inch = fabs (height_inch) * (gdouble) width / (gdouble) height; + } + + if ((psvals.rotate == 0) || (psvals.rotate == 180)) + { + x_size = width_inch; + y_size = height_inch; + } + else + { + y_size = width_inch; + x_size = height_inch; + } + + /* Round up upper right corner only for non-integer values */ + urx = (x_offset + x_size) * 72.0; + ury = (y_offset + y_size) * 72.0; + i_urx = (gint) urx; + i_ury = (gint) ury; + if (urx != (gdouble) i_urx) i_urx++; /* Check for non-integer value */ + if (ury != (gdouble) i_ury) i_ury++; + + if (! print (output, error, + "%%%%BoundingBox: %d %d %d %d\n" + "%%%%EndComments\n", + (gint) (x_offset * 72.0), (gint) (y_offset * 72.0), i_urx, i_ury)) + return FALSE; + + if (psvals.preview && (psvals.preview_size > 0)) + { + if (! save_ps_preview (output, drawable_ID, error)) + return FALSE; + } + + if (! print (output, error, + "%%%%BeginProlog\n" + "%% Use own dictionary to avoid conflicts\n" + "10 dict begin\n" + "%%%%EndProlog\n" + "%%%%Page: 1 1\n" + "%% Translate for offset\n" + "%s %s translate\n", + g_ascii_dtostr (tmpbuf1, sizeof (tmpbuf1), x_offset * 72.0), + g_ascii_dtostr (tmpbuf2, sizeof (tmpbuf2), y_offset * 72.0))) + return FALSE; + + /* Calculate translation to startpoint of first scanline */ + switch (psvals.rotate) + { + case 0: dx = 0.0; dy = y_size * 72.0; + break; + case 90: dx = dy = 0.0; + break; + case 180: dx = x_size * 72.0; dy = 0.0; + break; + case 270: dx = x_size * 72.0; dy = y_size * 72.0; + break; + } + + if ((dx != 0.0) || (dy != 0.0)) + { + if (! print (output, error, + "%% Translate to begin of first scanline\n" + "%s %s translate\n", + g_ascii_dtostr (tmpbuf1, sizeof (tmpbuf1), dx), + g_ascii_dtostr (tmpbuf2, sizeof (tmpbuf2), dy))) + return FALSE; + } + + if (psvals.rotate) + if (! print (output, error, "%d rotate\n", (gint) psvals.rotate)) + return FALSE; + + if (! print (output, error, + "%s %s scale\n", + g_ascii_dtostr (tmpbuf1, sizeof (tmpbuf1), 72.0 * width_inch), + g_ascii_dtostr (tmpbuf2, sizeof (tmpbuf2), -72.0 * height_inch))) + return FALSE; + + /* Write the PostScript procedures to read the image */ + if (psvals.level <= 1) + { + if (! print (output, error, + "%% Variable to keep one line of raster data\n")) + return FALSE; + + if (bpp == 1) + { + if (! print (output, error, + "/scanline %d string def\n", (width + 7) / 8)) + return FALSE; + } + else + { + if (! print (output, error, + "/scanline %d %d mul string def\n", width, bpp / 8)) + return FALSE; + } + } + + if (! print (output, error, + "%% Image geometry\n%d %d %d\n" + "%% Transformation matrix\n", + width, height, (bpp == 1) ? 1 : 8)) + return FALSE; + + xtrans = ytrans = 0; + if (psvals.width < 0.0) { width = -width; xtrans = -width; } + if (psvals.height < 0.0) { height = -height; ytrans = -height; } + + if (! print (output, error, + "[ %d 0 0 %d %d %d ]\n", width, height, xtrans, ytrans)) + return FALSE; + + return TRUE; +} + +static gboolean +save_ps_trailer (GOutputStream *output, + GError **error) +{ + return print (output, error, + "%%%%Trailer\n" + "end\n%%%%EOF\n"); +} + +/* Do a Floyd-Steinberg dithering on a grayscale scanline. */ +/* linecount must keep the counter for the actual scanline (0, 1, 2, ...). */ +/* If linecount is less than zero, all used memory is freed. */ + +static void +dither_grey (const guchar *grey, + guchar *bw, + gint npix, + gint linecount) +{ + static gboolean do_init_arrays = TRUE; + static gint *fs_error = NULL; + static gint limit[1278]; + static gint east_error[256]; + static gint seast_error[256]; + static gint south_error[256]; + static gint swest_error[256]; + + const guchar *greyptr; + guchar *bwptr, mask; + gint *fse; + gint x, greyval, fse_inline; + + if (linecount <= 0) + { + g_free (fs_error); + + if (linecount < 0) + return; + + fs_error = g_new0 (gint, npix + 2); + + /* Initialize some arrays that speed up dithering */ + if (do_init_arrays) + { + gint i; + + do_init_arrays = FALSE; + + for (i = 0, x = -511; x <= 766; i++, x++) + limit[i] = (x < 0) ? 0 : ((x > 255) ? 255 : x); + + for (greyval = 0; greyval < 256; greyval++) + { + east_error[greyval] = (greyval < 128) ? + ((greyval * 79) >> 8) : (((greyval - 255) * 79) >> 8); + seast_error[greyval] = (greyval < 128) ? + ((greyval * 34) >> 8) : (((greyval - 255) * 34) >> 8); + south_error[greyval] = (greyval < 128) ? + ((greyval * 56) >> 8) : (((greyval - 255) * 56) >> 8); + swest_error[greyval] = (greyval < 128) ? + ((greyval * 12) >> 8) : (((greyval - 255) * 12) >> 8); + } + } + } + + g_return_if_fail (fs_error != NULL); + + memset (bw, 0, (npix + 7) / 8); /* Initialize with white */ + + greyptr = grey; + bwptr = bw; + mask = 0x80; + + fse_inline = fs_error[1]; + + for (x = 0, fse = fs_error + 1; x < npix; x++, fse++) + { + greyval = + limit[*(greyptr++) + fse_inline + 512]; /* 0 <= greyval <= 255 */ + + if (greyval < 128) + *bwptr |= mask; /* Set a black pixel */ + + /* Error distribution */ + fse_inline = east_error[greyval] + fse[1]; + fse[1] = seast_error[greyval]; + fse[0] += south_error[greyval]; + fse[-1] += swest_error[greyval]; + + mask >>= 1; /* Get mask for next b/w-pixel */ + + if (!mask) + { + mask = 0x80; + bwptr++; + } + } +} + +/* Write a device independent screen preview */ +static gboolean +save_ps_preview (GOutputStream *output, + gint32 drawable_ID, + GError **error) +{ + GimpImageType drawable_type; + GeglBuffer *buffer = NULL; + const Babl *format; + gint bpp; + guchar *bwptr, *greyptr; + gint width, height, x, y, nbsl, out_count; + gint nchar_pl = 72, src_y; + gdouble f1, f2; + guchar *grey, *bw, *src_row, *src_ptr; + guchar *cmap; + gint ncols, cind; + + if (psvals.preview_size <= 0) + return TRUE; + + buffer = gimp_drawable_get_buffer (drawable_ID); + cmap = NULL; + + drawable_type = gimp_drawable_type (drawable_ID); + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + format = babl_format ("Y' u8"); + break; + + case GIMP_INDEXED_IMAGE: + cmap = gimp_image_get_colormap (gimp_item_get_image (drawable_ID), + &ncols); + format = gimp_drawable_get_format (drawable_ID); + break; + + case GIMP_RGB_IMAGE: + default: + format = babl_format ("R'G'B' u8"); + break; + } + + bpp = babl_format_get_bytes_per_pixel (format); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + /* Calculate size of preview */ + if ((width > psvals.preview_size) || + (height > psvals.preview_size)) + { + f1 = (gdouble) psvals.preview_size / (gdouble) width; + f2 = (gdouble) psvals.preview_size / (gdouble) height; + + if (f1 < f2) + { + width = psvals.preview_size; + height *= f1; + if (height <= 0) + height = 1; + } + else + { + height = psvals.preview_size; + width *= f1; + if (width <= 0) + width = 1; + } + } + + nbsl = (width + 7) / 8; /* Number of bytes per scanline in bitmap */ + + grey = g_new (guchar, width); + bw = g_new (guchar, nbsl); + src_row = g_new (guchar, gegl_buffer_get_width (buffer) * bpp); + + if (! print (output, error, + "%%%%BeginPreview: %d %d 1 %d\n", + width, height, + ((nbsl * 2 + nchar_pl - 1) / nchar_pl) * height)) + goto fail; + + for (y = 0; y < height; y++) + { + /* Get a scanline from the input image and scale it to the desired + width */ + src_y = (y * gegl_buffer_get_height (buffer)) / height; + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, src_y, + gegl_buffer_get_width (buffer), 1), + 1.0, format, src_row, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + greyptr = grey; + if (bpp == 3) /* RGB-image */ + { + for (x = 0; x < width; x++) + { /* Convert to grey */ + src_ptr = src_row + ((x * gegl_buffer_get_width (buffer)) / width) * 3; + *(greyptr++) = (3*src_ptr[0] + 6*src_ptr[1] + src_ptr[2]) / 10; + } + } + else if (cmap) /* Indexed image */ + { + for (x = 0; x < width; x++) + { + src_ptr = src_row + ((x * gegl_buffer_get_width (buffer)) / width); + cind = *src_ptr; /* Get color index and convert to grey */ + src_ptr = (cind >= ncols) ? cmap : (cmap + 3*cind); + *(greyptr++) = (3*src_ptr[0] + 6*src_ptr[1] + src_ptr[2]) / 10; + } + } + else /* Grey image */ + { + for (x = 0; x < width; x++) + *(greyptr++) = *(src_row + ((x * gegl_buffer_get_width (buffer)) / width)); + } + + /* Now we have a grayscale line for the desired width. */ + /* Dither it to b/w */ + dither_grey (grey, bw, width, y); + + /* Write out the b/w line */ + out_count = 0; + bwptr = bw; + for (x = 0; x < nbsl; x++) + { + if (out_count == 0) + if (! print (output, error, "%% ")) + goto fail; + + if (! print (output, error, "%02x", *(bwptr++))) + goto fail; + + out_count += 2; + if (out_count >= nchar_pl) + { + if (! print (output, error, "\n")) + goto fail; + + out_count = 0; + } + } + + if (! print (output, error, "\n")) + goto fail; + + if ((y % 20) == 0) + gimp_progress_update ((gdouble) y / (gdouble) height); + } + + gimp_progress_update (1.0); + + if (! print (output, error, "%%%%EndPreview\n")) + goto fail; + + dither_grey (grey, bw, width, -1); + g_free (src_row); + g_free (bw); + g_free (grey); + + g_object_unref (buffer); + + return TRUE; + + fail: + + g_free (src_row); + g_free (bw); + g_free (grey); + + g_object_unref (buffer); + + return FALSE; +} + +static gboolean +save_gray (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglBuffer *buffer = NULL; + const Babl *format; + gint bpp; + gint height, width, i, j; + gint tile_height; + guchar *data; + guchar *src; + guchar *packb = NULL; + gboolean level2 = (psvals.level > 1); + + buffer = gimp_drawable_get_buffer (drawable_ID); + format = babl_format ("Y' u8"); + bpp = babl_format_get_bytes_per_pixel (format); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + tile_height = gimp_tile_height (); + + /* allocate a buffer for retrieving information from the pixel region */ + src = data = (guchar *) g_malloc (tile_height * width * bpp); + + /* Set up transformation in PostScript */ + if (! save_ps_setup (output, drawable_ID, width, height, 1 * 8, error)) + goto fail; + + /* Write read image procedure */ + if (! level2) + { + if (! print (output, error, + "{ currentfile scanline readhexstring pop }\n")) + goto fail; + } + else + { + if (! print (output, error, + "currentfile /ASCII85Decode filter /RunLengthDecode filter\n")) + goto fail; + + ascii85_init (); + + /* Allocate buffer for packbits data. Worst case: Less than 1% increase */ + packb = (guchar *) g_malloc ((width * 105) / 100 + 2); + } + + if (! ps_begin_data (output, error)) + goto fail; + + if (! print (output, error, "image\n")) + goto fail; + +#define GET_GRAY_TILE(begin) \ + { gint scan_lines; \ + scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \ + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), \ + 1.0, format, begin, \ + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \ + src = begin; } + + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_GRAY_TILE (data); /* Get more data */ + + if (! level2) + { + for (j = 0; j < width; j++) + { + if (! print (output, error, "%c", hex[(*src) >> 4])) + goto fail; + + if (! print (output, error, "%c", hex[(*(src++)) & 0x0f])) + goto fail; + + if (((j + 1) % 39) == 0) + if (! print (output, error, "\n")) + goto fail; + } + + if (! print (output, error, "\n")) + goto fail; + } + else + { + gint nout; + + compress_packbits (width, src, &nout, packb); + + if (! ascii85_nout (output, nout, packb, error)) + goto fail; + + src += width; + } + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) i / (gdouble) height); + } + + gimp_progress_update (1.0); + + if (level2) + { + /* Write EOD of RunLengthDecode filter */ + if (! ascii85_out (output, 128, error)) + goto fail; + + if (! ascii85_done (output, error)) + goto fail; + } + + if (! ps_end_data (output, error)) + return FALSE; + + if (! print (output, error, "showpage\n")) + goto fail; + + g_free (data); + g_free (packb); + + g_object_unref (buffer); + + return TRUE; + + fail: + + g_free (data); + g_free (packb); + + g_object_unref (buffer); + + return FALSE; + +#undef GET_GRAY_TILE +} + +static gboolean +save_bw (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglBuffer *buffer = NULL; + const Babl *format; + gint bpp; + gint height, width, i, j; + gint ncols, nbsl, nwrite; + gint tile_height; + guchar *cmap, *ct; + guchar *data, *src; + guchar *packb = NULL; + guchar *scanline, *dst, mask; + guchar *hex_scanline; + gboolean level2 = (psvals.level > 1); + + cmap = gimp_image_get_colormap (image_ID, &ncols); + + buffer = gimp_drawable_get_buffer (drawable_ID); + format = gimp_drawable_get_format (drawable_ID); + bpp = babl_format_get_bytes_per_pixel (format); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + tile_height = gimp_tile_height (); + + /* allocate a buffer for retrieving information from the pixel region */ + src = data = g_new (guchar, tile_height * width * bpp); + + nbsl = (width + 7) / 8; + + scanline = g_new (guchar, nbsl + 1); + hex_scanline = g_new (guchar, (nbsl + 1) * 2); + + /* Set up transformation in PostScript */ + if (! save_ps_setup (output, drawable_ID, width, height, 1, error)) + goto fail; + + /* Write read image procedure */ + if (! level2) + { + if (! print (output, error, + "{ currentfile scanline readhexstring pop }\n")) + goto fail; + } + else + { + if (! print (output, error, + "currentfile /ASCII85Decode filter /RunLengthDecode filter\n")) + goto fail; + + ascii85_init (); + + /* Allocate buffer for packbits data. Worst case: Less than 1% increase */ + packb = g_new (guchar, ((nbsl+1) * 105) / 100 + 2); + } + + if (! ps_begin_data (output, error)) + goto fail; + + if (! print (output, error, "image\n")) + goto fail; + +#define GET_BW_TILE(begin) \ + { gint scan_lines; \ + scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \ + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), \ + 1.0, format, begin, \ + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \ + src = begin; } + + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_BW_TILE (data); /* Get more data */ + + dst = scanline; + memset (dst, 0, nbsl); + mask = 0x80; + + /* Build a bitmap for a scanline */ + for (j = 0; j < width; j++) + { + ct = cmap + *(src++)*3; + if (ct[0] || ct[1] || ct[2]) + *dst |= mask; + + if (mask == 0x01) + { + mask = 0x80; dst++; + } + else + { + mask >>= 1; + } + } + + if (! level2) + { + /* Convert to hexstring */ + for (j = 0; j < nbsl; j++) + { + hex_scanline[j * 2] = (guchar) hex[scanline[j] >> 4]; + hex_scanline[j * 2 + 1] = (guchar) hex[scanline[j] & 0x0f]; + } + + /* Write out hexstring */ + j = nbsl * 2; + dst = hex_scanline; + + while (j > 0) + { + nwrite = (j > 78) ? 78 : j; + + if (! g_output_stream_write_all (output, + dst, nwrite, NULL, + NULL, error)) + goto fail; + + if (! print (output, error, "\n")) + goto fail; + + j -= nwrite; + dst += nwrite; + } + } + else + { + gint nout; + + compress_packbits (nbsl, scanline, &nout, packb); + + if (! ascii85_nout (output, nout, packb, error)) + goto fail; + } + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) i / (gdouble) height); + } + + gimp_progress_update (1.0); + + if (level2) + { + /* Write EOD of RunLengthDecode filter */ + if (! ascii85_out (output, 128, error)) + goto fail; + + if (! ascii85_done (output, error)) + goto fail; + } + + if (! ps_end_data (output, error)) + goto fail; + + if (! print (output, error, "showpage\n")) + goto fail; + + g_free (hex_scanline); + g_free (scanline); + g_free (data); + g_free (packb); + + g_object_unref (buffer); + + return TRUE; + + fail: + + g_free (hex_scanline); + g_free (scanline); + g_free (data); + g_free (packb); + + g_object_unref (buffer); + + return FALSE; + +#undef GET_BW_TILE +} + +static gboolean +save_index (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglBuffer *buffer = NULL; + const Babl *format; + gint bpp; + gint height, width, i, j; + gint ncols, bw; + gint tile_height; + guchar *cmap, *cmap_start; + guchar *data, *src; + guchar *packb = NULL; + guchar *plane = NULL; + gchar coltab[256 * 6], *ct; + gboolean level2 = (psvals.level > 1); + + cmap = cmap_start = gimp_image_get_colormap (image_ID, &ncols); + + ct = coltab; + bw = 1; + for (j = 0; j < 256; j++) + { + if (j >= ncols) + { + memset (ct, 0, 6); + ct += 6; + } + else + { + bw &= ((cmap[0] == 0) && (cmap[1] == 0) && (cmap[2] == 0)) || + ((cmap[0] == 255) && (cmap[1] == 255) && (cmap[2] == 255)); + + *(ct++) = (guchar) hex[(*cmap) >> 4]; + *(ct++) = (guchar) hex[(*(cmap++)) & 0x0f]; + *(ct++) = (guchar) hex[(*cmap) >> 4]; + *(ct++) = (guchar) hex[(*(cmap++)) & 0x0f]; + *(ct++) = (guchar) hex[(*cmap) >> 4]; + *(ct++) = (guchar) hex[(*(cmap++)) & 0x0f]; + } + } + + if (bw) + return save_bw (output, image_ID, drawable_ID, error); + + buffer = gimp_drawable_get_buffer (drawable_ID); + format = gimp_drawable_get_format (drawable_ID); + bpp = babl_format_get_bytes_per_pixel (format); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + tile_height = gimp_tile_height (); + + /* allocate a buffer for retrieving information from the pixel region */ + src = data = (guchar *) g_malloc (tile_height * width * bpp); + + /* Set up transformation in PostScript */ + if (! save_ps_setup (output, drawable_ID, width, height, 3 * 8, error)) + goto fail; + + /* Write read image procedure */ + if (! level2) + { + if (! print (output, error, + "{ currentfile scanline readhexstring pop } false 3\n")) + goto fail; + } + else + { + if (! print (output, error, + "%% Strings to hold RGB-samples per scanline\n" + "/rstr %d string def\n" + "/gstr %d string def\n" + "/bstr %d string def\n", + width, width, width)) + goto fail; + + if (! print (output, error, + "{currentfile /ASCII85Decode filter /RunLengthDecode filter\ + rstr readstring pop}\n" + "{currentfile /ASCII85Decode filter /RunLengthDecode filter\ + gstr readstring pop}\n" + "{currentfile /ASCII85Decode filter /RunLengthDecode filter\ + bstr readstring pop}\n" + "true 3\n")) + goto fail; + + /* Allocate buffer for packbits data. Worst case: Less than 1% increase */ + packb = (guchar *) g_malloc ((width * 105) / 100 + 2); + plane = (guchar *) g_malloc (width); + } + + if (! ps_begin_data (output, error)) + goto fail; + + if (! print (output, error, "colorimage\n")) + goto fail; + +#define GET_INDEX_TILE(begin) \ + { gint scan_lines; \ + scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \ + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), \ + 1.0, format, begin, \ + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \ + src = begin; } + + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_INDEX_TILE (data); /* Get more data */ + + if (! level2) + { + for (j = 0; j < width; j++) + { + if (! g_output_stream_write_all (output, + coltab + (*(src++)) * 6, 6, NULL, + NULL, error)) + goto fail; + + if (((j + 1) % 13) == 0) + if (! print (output, error, "\n")) + goto fail; + } + + if (! print (output, error, "\n")) + goto fail; + } + else + { + gint rgb; + + for (rgb = 0; rgb < 3; rgb++) + { + guchar *src_ptr = src; + guchar *plane_ptr = plane; + gint nout; + + for (j = 0; j < width; j++) + *(plane_ptr++) = cmap_start[3 * *(src_ptr++) + rgb]; + + compress_packbits (width, plane, &nout, packb); + + ascii85_init (); + + if (! ascii85_nout (output, nout, packb, error)) + goto fail; + + /* Write EOD of RunLengthDecode filter */ + if (! ascii85_out (output, 128, error)) + goto fail; + + if (! ascii85_done (output, error)) + goto fail; + } + + src += width; + } + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) i / (gdouble) height); + } + + gimp_progress_update (1.0); + + if (! ps_end_data (output, error)) + goto fail; + + if (! print (output, error, "showpage\n")) + goto fail; + + g_free (data); + g_free (packb); + g_free (plane); + + g_object_unref (buffer); + + return TRUE; + + fail: + + g_free (data); + g_free (packb); + g_free (plane); + + g_object_unref (buffer); + + return FALSE; + +#undef GET_INDEX_TILE +} + +static gboolean +save_rgb (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglBuffer *buffer = NULL; + const Babl *format; + gint bpp; + gint height, width, tile_height; + gint i, j; + guchar *data, *src; + guchar *packb = NULL; + guchar *plane = NULL; + gboolean level2 = (psvals.level > 1); + + buffer = gimp_drawable_get_buffer (drawable_ID); + format = babl_format ("R'G'B' u8"); + bpp = babl_format_get_bytes_per_pixel (format); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + tile_height = gimp_tile_height (); + + /* allocate a buffer for retrieving information from the pixel region */ + src = data = g_new (guchar, tile_height * width * bpp); + + /* Set up transformation in PostScript */ + if (! save_ps_setup (output, drawable_ID, width, height, 3 * 8, error)) + goto fail; + + /* Write read image procedure */ + if (! level2) + { + if (! print (output, error, + "{ currentfile scanline readhexstring pop } false 3\n")) + goto fail; + } + else + { + if (! print (output, error, + "%% Strings to hold RGB-samples per scanline\n" + "/rstr %d string def\n" + "/gstr %d string def\n" + "/bstr %d string def\n", + width, width, width)) + goto fail; + + if (! print (output, error, + "{currentfile /ASCII85Decode filter /RunLengthDecode filter\ + rstr readstring pop}\n" + "{currentfile /ASCII85Decode filter /RunLengthDecode filter\ + gstr readstring pop}\n" + "{currentfile /ASCII85Decode filter /RunLengthDecode filter\ + bstr readstring pop}\n" + "true 3\n")) + goto fail; + + /* Allocate buffer for packbits data. Worst case: Less than 1% increase */ + packb = g_new (guchar, (width * 105) / 100 + 2); + plane = g_new (guchar, width); + } + + if (! ps_begin_data (output, error)) + goto fail; + + if (! print (output, error, "colorimage\n")) + goto fail; + +#define GET_RGB_TILE(begin) \ + { gint scan_lines; \ + scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \ + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), \ + 1.0, format, begin, \ + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \ + src = begin; } + + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_RGB_TILE (data); /* Get more data */ + + if (! level2) + { + for (j = 0; j < width; j++) + { + if (! print (output, error, "%c", + hex[(*src) >> 4])) /* Red */ + goto fail; + + if (! print (output, error, "%c", + hex[(*(src++)) & 0x0f])) + goto fail; + + if (! print (output, error, "%c", + hex[(*src) >> 4])) /* Green */ + goto fail; + + if (! print (output, error, "%c", + hex[(*(src++)) & 0x0f])) + goto fail; + + if (! print (output, error, "%c", + hex[(*src) >> 4])) /* Blue */ + goto fail; + + if (! print (output, error, "%c", + hex[(*(src++)) & 0x0f])) + goto fail; + + if (((j+1) % 13) == 0) + if (! print (output, error, "\n")) + goto fail; + } + + if (! print (output, error, "\n")) + goto fail; + } + else + { + gint rgb; + + for (rgb = 0; rgb < 3; rgb++) + { + guchar *src_ptr = src + rgb; + guchar *plane_ptr = plane; + gint nout; + + for (j = 0; j < width; j++) + { + *(plane_ptr++) = *src_ptr; + src_ptr += 3; + } + + compress_packbits (width, plane, &nout, packb); + + ascii85_init (); + + if (! ascii85_nout (output, nout, packb, error)) + goto fail; + + /* Write EOD of RunLengthDecode filter */ + if (! ascii85_out (output, 128, error)) + goto fail; + + if (! ascii85_done (output, error)) + goto fail; + } + + src += 3 * width; + } + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) i / (gdouble) height); + } + + gimp_progress_update (1.0); + + if (! ps_end_data (output, error)) + goto fail; + + if (! print (output, error, "showpage\n")) + goto fail; + + g_free (data); + g_free (packb); + g_free (plane); + + g_object_unref (buffer); + + return TRUE; + + fail: + + g_free (data); + g_free (packb); + g_free (plane); + + g_object_unref (buffer); + + return FALSE; + +#undef GET_RGB_TILE +} + +static gboolean +print (GOutputStream *output, + GError **error, + const gchar *format, + ...) +{ + va_list args; + gboolean success; + + va_start (args, format); + success = g_output_stream_vprintf (output, NULL, NULL, + error, format, args); + va_end (args); + + return success; +} + +/* Load interface functions */ + +static gint32 +count_ps_pages (const gchar *filename) +{ + FILE *psfile; + gchar *extension; + gchar buf[1024]; + gint32 num_pages = 0; + gint32 showpage_count = 0; + + extension = strrchr (filename, '.'); + if (extension) + { + extension = g_ascii_strdown (extension + 1, -1); + + if (strcmp (extension, "eps") == 0) + { + g_free (extension); + return 1; + } + + g_free (extension); + } + + psfile = g_fopen (filename, "r"); + + if (psfile == NULL) + { + g_message (_("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return 0; + } + + while (num_pages == 0 && !feof (psfile)) + { + fgets (buf, sizeof (buf), psfile); + + if (strncmp (buf + 2, "Pages:", 6) == 0) + sscanf (buf + strlen ("%%Pages:"), "%d", &num_pages); + else if (strncmp (buf, "showpage", 8) == 0) + showpage_count++; + } + + if (feof (psfile) && num_pages < 1 && showpage_count > 0) + num_pages = showpage_count; + + fclose (psfile); + + return num_pages; +} + +static gboolean +load_dialog (const gchar *filename) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *spinbutton; + GtkAdjustment *adj; + GtkWidget *entry = NULL; + GtkWidget *target = NULL; + GtkWidget *toggle; + GtkWidget *selector = NULL; + gint32 page_count; + gchar *range = NULL; + gboolean run; + + page_count = count_ps_pages (filename); + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Import from PostScript"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, LOAD_PS_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Import"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + if (page_count > 1) + { + selector = gimp_page_selector_new (); + gtk_box_pack_start (GTK_BOX (main_vbox), selector, TRUE, TRUE, 0); + gimp_page_selector_set_n_pages (GIMP_PAGE_SELECTOR (selector), + page_count); + gimp_page_selector_set_target (GIMP_PAGE_SELECTOR (selector), + ps_pagemode); + + gtk_widget_show (selector); + + g_signal_connect_swapped (selector, "activate", + G_CALLBACK (gtk_window_activate_default), + dialog); + } + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* Rendering */ + frame = gimp_frame_new (_("Rendering")); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, TRUE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + /* Resolution/Width/Height/Pages labels */ + table = gtk_table_new (4, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + adj = (GtkAdjustment *) gtk_adjustment_new (plvals.resolution, + MIN_RESOLUTION, MAX_RESOLUTION, + 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Resolution:"), 0.0, 0.5, + spinbutton, 1, FALSE); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (resolution_change_callback), + &plvals.resolution); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &plvals.resolution); + + adj = (GtkAdjustment *) gtk_adjustment_new (plvals.width, + 1, GIMP_MAX_IMAGE_SIZE, + 1, 10, 0); + ps_width_spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Width:"), 0.0, 0.5, + ps_width_spinbutton, 1, FALSE); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &plvals.width); + + adj = (GtkAdjustment *) gtk_adjustment_new (plvals.height, + 1, GIMP_MAX_IMAGE_SIZE, + 1, 10, 0); + ps_height_spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_Height:"), 0.0, 0.5, + ps_height_spinbutton, 1, FALSE); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &plvals.height); + + if (page_count == 0) + { + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 80, -1); + gtk_entry_set_text (GTK_ENTRY (entry), plvals.pages); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("Pages:"), 0.0, 0.5, + entry, 1, FALSE); + + g_signal_connect (entry, "changed", + G_CALLBACK (load_pages_entry_callback), + NULL); + gimp_help_set_help_data (GTK_WIDGET (entry), + _("Pages to load (e.g.: 1-4 or 1,3,5-7)"), NULL); + + target = gtk_combo_box_text_new (); + gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (target), + GIMP_PAGE_SELECTOR_TARGET_LAYERS, + _("Layers")); + gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (target), + GIMP_PAGE_SELECTOR_TARGET_IMAGES, + _("Images")); + gtk_combo_box_set_active (GTK_COMBO_BOX (target), (int) ps_pagemode); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, + _("Open as"), 0.0, 0.5, + target, 1, FALSE); + } + + toggle = gtk_check_button_new_with_label (_("Try Bounding Box")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), plvals.use_bbox); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &plvals.use_bbox); + + gtk_widget_show (vbox); + gtk_widget_show (frame); + + /* Coloring */ + frame = gimp_int_radio_group_new (TRUE, _("Coloring"), + G_CALLBACK (gimp_radio_button_update), + &plvals.pnm_type, plvals.pnm_type, + + _("B/W"), 4, NULL, + _("Gray"), 5, NULL, + _("Color"), 6, NULL, + _("Automatic"), 7, NULL, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, TRUE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + frame = gimp_int_radio_group_new (TRUE, _("Text antialiasing"), + G_CALLBACK (gimp_radio_button_update), + &plvals.textalpha, plvals.textalpha, + + C_("antialiasing", "None"), 1, NULL, + _("Weak"), 2, NULL, + _("Strong"), 4, NULL, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, TRUE, 0); + gtk_widget_show (frame); + + frame = gimp_int_radio_group_new (TRUE, _("Graphic antialiasing"), + G_CALLBACK (gimp_radio_button_update), + &plvals.graphicsalpha, plvals.graphicsalpha, + + C_("antialiasing", "None"), 1, NULL, + _("Weak"), 2, NULL, + _("Strong"), 4, NULL, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, TRUE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (selector) + { + range = gimp_page_selector_get_selected_range (GIMP_PAGE_SELECTOR (selector)); + + if (strlen (range) < 1) + { + gimp_page_selector_select_all (GIMP_PAGE_SELECTOR (selector)); + range = gimp_page_selector_get_selected_range (GIMP_PAGE_SELECTOR (selector)); + } + + strncpy (plvals.pages, range, sizeof (plvals.pages)); + plvals.pages[strlen (range)] = '\0'; + + ps_pagemode = gimp_page_selector_get_target (GIMP_PAGE_SELECTOR (selector)); + } + else if (page_count == 0) + { + ps_pagemode = gtk_combo_box_get_active (GTK_COMBO_BOX (target)); + } + else + { + strncpy (plvals.pages, "1", 1); + plvals.pages[1] = '\0'; + ps_pagemode = GIMP_PAGE_SELECTOR_TARGET_IMAGES; + } + + gtk_widget_destroy (dialog); + + return run; +} + +static void +load_pages_entry_callback (GtkWidget *widget, + gpointer data) +{ + gsize nelem = sizeof (plvals.pages); + + strncpy (plvals.pages, gtk_entry_get_text (GTK_ENTRY (widget)), nelem); + plvals.pages[nelem-1] = '\0'; +} + + +/* Save interface functions */ + +static gboolean +save_dialog (void) +{ + SaveDialogVals *vals; + GtkWidget *dialog; + GtkWidget *toggle; + GtkWidget *frame, *uframe; + GtkWidget *hbox, *vbox; + GtkWidget *main_vbox[2]; + GtkWidget *table; + GtkWidget *spinbutton; + GtkAdjustment *adj; + gint j; + gboolean run; + + vals = g_new (SaveDialogVals, 1); + vals->level = (psvals.level > 1); + + dialog = gimp_export_dialog_new (_("PostScript"), PLUG_IN_BINARY, SAVE_PS_PROC); + + /* Main hbox */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + hbox, FALSE, FALSE, 0); + main_vbox[0] = main_vbox[1] = NULL; + + for (j = 0; j < G_N_ELEMENTS (main_vbox); j++) + { + main_vbox[j] = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (hbox), main_vbox[j], FALSE, TRUE, 0); + gtk_widget_show (main_vbox[j]); + } + + /* Image Size */ + frame = gimp_frame_new (_("Image Size")); + gtk_box_pack_start (GTK_BOX (main_vbox[0]), frame, FALSE, TRUE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + /* Width/Height/X-/Y-offset labels */ + table = gtk_table_new (4, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + vals->adjustment[0] = (GtkAdjustment *) + gtk_adjustment_new (psvals.width, + 1e-5, GIMP_MAX_IMAGE_SIZE, 1, 10, 0); + spinbutton = gimp_spin_button_new (vals->adjustment[0], 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Width:"), 0.0, 0.5, + spinbutton, 1, FALSE); + g_signal_connect (vals->adjustment[0], "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &psvals.width); + + vals->adjustment[1] = (GtkAdjustment *) + gtk_adjustment_new (psvals.height, + 1e-5, GIMP_MAX_IMAGE_SIZE, 1, 10, 0); + spinbutton = gimp_spin_button_new (vals->adjustment[1], 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Height:"), 0.0, 0.5, + spinbutton, 1, FALSE); + g_signal_connect (vals->adjustment[1], "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &psvals.height); + + vals->adjustment[2] = (GtkAdjustment *) + gtk_adjustment_new (psvals.x_offset, + 0.0, GIMP_MAX_IMAGE_SIZE, 1, 10, 0); + spinbutton = gimp_spin_button_new (vals->adjustment[2], 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_X offset:"), 0.0, 0.5, + spinbutton, 1, FALSE); + g_signal_connect (vals->adjustment[2], "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &psvals.x_offset); + + vals->adjustment[3] = (GtkAdjustment *) + gtk_adjustment_new (psvals.y_offset, + 0.0, GIMP_MAX_IMAGE_SIZE, 1, 10, 0); + spinbutton = gimp_spin_button_new (vals->adjustment[3], 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("_Y offset:"), 0.0, 0.5, + spinbutton, 1, FALSE); + g_signal_connect (vals->adjustment[3], "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &psvals.y_offset); + + toggle = gtk_check_button_new_with_mnemonic (_("_Keep aspect ratio")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), psvals.keep_ratio); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("When toggled, the resulting image will be " + "scaled to fit into the given size without " + "changing the aspect ratio."), + "#keep_aspect_ratio"), + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &psvals.keep_ratio); + + /* Unit */ + uframe = gimp_int_radio_group_new (TRUE, _("Unit"), + G_CALLBACK (save_unit_toggle_update), + vals, psvals.unit_mm, + + _("_Inch"), FALSE, NULL, + _("_Millimeter"), TRUE, NULL, + + NULL); + + gtk_box_pack_start (GTK_BOX (main_vbox[0]), uframe, TRUE, TRUE, 0); + gtk_widget_show (uframe); + + gtk_widget_show (vbox); + gtk_widget_show (frame); + + /* Rotation */ + frame = gimp_int_radio_group_new (TRUE, _("Rotation"), + G_CALLBACK (gimp_radio_button_update), + &psvals.rotate, psvals.rotate, + + "_0", 0, NULL, + "_90", 90, NULL, + "_180", 180, NULL, + "_270", 270, NULL, + + NULL); + + gtk_box_pack_start (GTK_BOX (main_vbox[1]), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + /* Format */ + frame = gimp_frame_new (_("Output")); + gtk_box_pack_start (GTK_BOX (main_vbox[1]), frame, TRUE, TRUE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + toggle = gtk_check_button_new_with_mnemonic (_("_PostScript level 2")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), vals->level); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &vals->level); + + toggle = gtk_check_button_new_with_mnemonic (_("_Encapsulated PostScript")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), psvals.eps); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &psvals.eps); + + toggle = gtk_check_button_new_with_mnemonic (_("P_review")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), psvals.preview); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &psvals.preview); + + /* Preview size label/entry */ + table = gtk_table_new (1, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + g_object_bind_property (toggle, "active", + table, "sensitive", + G_BINDING_SYNC_CREATE); + + adj = (GtkAdjustment *) gtk_adjustment_new (psvals.preview_size, + 0, 1024, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Preview _size:"), 1.0, 0.5, + spinbutton, 1, FALSE); + gtk_widget_show (spinbutton); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &psvals.preview_size); + + gtk_widget_show (vbox); + gtk_widget_show (frame); + + gtk_widget_show (hbox); + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + psvals.level = (vals->level) ? 2 : 1; + + g_free (vals); + + return run; +} + +static void +save_unit_toggle_update (GtkWidget *widget, + gpointer data) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + SaveDialogVals *vals = (SaveDialogVals *) data; + gdouble factor; + gdouble value; + gint unit_mm; + gint i; + + unit_mm = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-item-data")); + + psvals.unit_mm = unit_mm; + + if (unit_mm) + factor = 25.4; + else + factor = 1.0 / 25.4; + + for (i = 0; i < 4; i++) + { + value = gtk_adjustment_get_value (vals->adjustment[i]) * factor; + + gtk_adjustment_set_value (vals->adjustment[i], value); + } + } +} + +static gboolean +resolution_change_callback (GtkAdjustment *adjustment, + gpointer data) +{ + guint *old_resolution = (guint *) data; + gdouble ratio; + + if (*old_resolution) + ratio = (gdouble) gtk_adjustment_get_value (adjustment) / *old_resolution; + else + ratio = 1.0; + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (ps_width_spinbutton), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (ps_width_spinbutton)) * ratio); + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (ps_height_spinbutton), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (ps_height_spinbutton)) * ratio); + + return TRUE; + +} diff --git a/plug-ins/common/file-psp.c b/plug-ins/common/file-psp.c new file mode 100644 index 0000000..50c1974 --- /dev/null +++ b/plug-ins/common/file-psp.c @@ -0,0 +1,2571 @@ +/* GIMP plug-in to load and export Paint Shop Pro files (.PSP and .TUB) + * + * Copyright (C) 1999 Tor Lillqvist + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * + * Work in progress! Doesn't handle exporting yet. + * + * For a copy of the PSP file format documentation, surf to + * http://www.jasc.com. + * + */ + +#define LOAD_PROC "file-psp-load" +#define SAVE_PROC "file-psp-save" +#define PLUG_IN_BINARY "file-psp" +#define PLUG_IN_ROLE "gimp-file-psp" + +/* set to the level of debugging output you want, 0 for none */ +#define PSP_DEBUG 0 + +#define IFDBG(level) if (PSP_DEBUG >= level) + +#include "config.h" + +#include +#include +#include + +#include + +#include +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Note that the upcoming PSP version 6 writes PSP file format version + * 4.0, but the documentation for that apparently isn't publicly + * available (yet). The format is luckily designed to be somwehat + * downward compatible, however. The semantics of many of the + * additional fields and block types can be relatively easily reverse + * engineered. + */ + +/* The following was cut and pasted from the PSP file format + * documentation version 3.0.(Minor stylistic changes done.) + * + * + * To be on the safe side, here is the whole copyright notice from the + * specification: + * + * The Paint Shop Pro File Format Specification (the Specification) is + * copyright 1998 by Jasc Software, Inc. Jasc grants you a + * nonexclusive license to use the Specification for the sole purposes + * of developing software products(s) incorporating the + * Specification. You are also granted the right to identify your + * software product(s) as incorporating the Paint Shop Pro Format + * (PSP) provided that your software in incorporating the + * Specification complies with the terms, definitions, constraints and + * specifications contained in the Specification and subject to the + * following: DISCLAIMER OF WARRANTIES. THE SPECIFICATION IS PROVIDED + * AS IS. JASC DISCLAIMS ALL OTHER WARRANTIES, EXPRESS OR IMPLIED, + * INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. + * + * You are solely responsible for the selection, use, efficiency and + * suitability of the Specification for your software products. OTHER + * WARRANTIES EXCLUDED. JASC SHALL NOT BE LIABLE FOR ANY DIRECT, + * INDIRECT, CONSEQUENTIAL, EXEMPLARY, PUNITIVE OR INCIDENTAL DAMAGES + * ARISING FROM ANY CAUSE EVEN IF JASC HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. CERTAIN JURISDICTIONS DO NOT PERMIT + * THE LIMITATION OR EXCLUSION OF INCIDENTAL DAMAGES, SO THIS + * LIMITATION MAY NOT APPLY TO YOU. IN NO EVENT WILL JASC BE LIABLE + * FOR ANY AMOUNT GREATER THAN WHAT YOU ACTUALLY PAID FOR THE + * SPECIFICATION. Should any warranties be found to exist, such + * warranties shall be limited in duration to ninety (90) days + * following the date you receive the Specification. + * + * Indemnification. By your inclusion of the Paint Shop Pro File + * Format in your software product(s) you agree to indemnify and hold + * Jasc Software, Inc. harmless from any and all claims of any kind or + * nature made by any of your customers with respect to your software + * product(s). + * + * Export Laws. You agree that you and your customers will not export + * your software or Specification except in compliance with the laws + * and regulations of the United States. + * + * US Government Restricted Rights. The Specification and any + * accompanying materials are provided with Restricted Rights. Use, + * duplication or disclosure by the Government is subject to + * restrictions as set forth in subparagraph (c)(1)(ii) of The Rights + * in Technical Data and Computer Software clause at DFARS + * 252.227-7013, or subparagraphs (c)(1) and (2) of the Commercial + * Computer Software - Restricted Rights at 48 CFR 52.227-19, as + * applicable. Contractor/manufacturer is Jasc Software, Inc., PO Box + * 44997, Eden Prairie MN 55344. + * + * Jasc reserves the right to amend, modify, change, revoke or + * withdraw the Specification at any time and from time to time. Jasc + * shall have no obligation to support or maintain the Specification. + */ + +/* Block identifiers. + */ +typedef enum { + PSP_IMAGE_BLOCK = 0, /* General Image Attributes Block (main) */ + PSP_CREATOR_BLOCK, /* Creator Data Block (main) */ + PSP_COLOR_BLOCK, /* Color Palette Block (main and sub) */ + PSP_LAYER_START_BLOCK, /* Layer Bank Block (main) */ + PSP_LAYER_BLOCK, /* Layer Block (sub) */ + PSP_CHANNEL_BLOCK, /* Channel Block (sub) */ + PSP_SELECTION_BLOCK, /* Selection Block (main) */ + PSP_ALPHA_BANK_BLOCK, /* Alpha Bank Block (main) */ + PSP_ALPHA_CHANNEL_BLOCK, /* Alpha Channel Block (sub) */ + PSP_THUMBNAIL_BLOCK, /* Thumbnail Block (main) */ + PSP_EXTENDED_DATA_BLOCK, /* Extended Data Block (main) */ + PSP_TUBE_BLOCK, /* Picture Tube Data Block (main) */ + PSP_ADJUSTMENT_EXTENSION_BLOCK, /* Adjustment Layer Extension Block (sub) (since PSP6)*/ + PSP_VECTOR_EXTENSION_BLOCK, /* Vector Layer Extension Block (sub) (since PSP6) */ + PSP_SHAPE_BLOCK, /* Vector Shape Block (sub) (since PSP6) */ + PSP_PAINTSTYLE_BLOCK, /* Paint Style Block (sub) (since PSP6) */ + PSP_COMPOSITE_IMAGE_BANK_BLOCK, /* Composite Image Bank (main) (since PSP6) */ + PSP_COMPOSITE_ATTRIBUTES_BLOCK, /* Composite Image Attributes (sub) (since PSP6) */ + PSP_JPEG_BLOCK, /* JPEG Image Block (sub) (since PSP6) */ + PSP_LINESTYLE_BLOCK, /* Line Style Block (sub) (since PSP7) */ + PSP_TABLE_BANK_BLOCK, /* Table Bank Block (main) (since PSP7) */ + PSP_TABLE_BLOCK, /* Table Block (sub) (since PSP7) */ + PSP_PAPER_BLOCK, /* Vector Table Paper Block (sub) (since PSP7) */ + PSP_PATTERN_BLOCK, /* Vector Table Pattern Block (sub) (since PSP7) */ + PSP_GRADIENT_BLOCK, /* Vector Table Gradient Block (not used) (since PSP8) */ + PSP_GROUP_EXTENSION_BLOCK, /* Group Layer Block (sub) (since PSP8) */ + PSP_MASK_EXTENSION_BLOCK, /* Mask Layer Block (sub) (since PSP8) */ + PSP_BRUSH_BLOCK, /* Brush Data Block (main) (since PSP8) */ + PSP_ART_MEDIA_BLOCK, /* Art Media Layer Block (main) (since PSP9) */ + PSP_ART_MEDIA_MAP_BLOCK, /* Art Media Layer map data Block (main) (since PSP9) */ + PSP_ART_MEDIA_TILE_BLOCK, /* Art Media Layer map tile Block(main) (since PSP9) */ + PSP_ART_MEDIA_TEXTURE_BLOCK, /* AM Layer map texture Block (main) (since PSP9) */ + PSP_COLORPROFILE_BLOCK, /* ICC Color profile block (since PSP10) */ + PSP_RASTER_EXTENSION_BLOCK, /* Assumed name based on usage, probably since PSP11 */ +} PSPBlockID; + +/* Bitmap type. + */ +typedef enum { + PSP_DIB_IMAGE = 0, /* Layer color bitmap */ + PSP_DIB_TRANS_MASK, /* Layer transparency mask bitmap */ + PSP_DIB_USER_MASK, /* Layer user mask bitmap */ + PSP_DIB_SELECTION, /* Selection mask bitmap */ + PSP_DIB_ALPHA_MASK, /* Alpha channel mask bitmap */ + PSP_DIB_THUMBNAIL, /* Thumbnail bitmap */ + PSP_DIB_THUMBNAIL_TRANS_MASK, /* Thumbnail transparency mask (since PSP6) */ + PSP_DIB_ADJUSTMENT_LAYER, /* Adjustment layer bitmap (since PSP6) */ + PSP_DIB_COMPOSITE, /* Composite image bitmap (since PSP6) */ + PSP_DIB_COMPOSITE_TRANS_MASK, /* Composite image transparency (since PSP6) */ + PSP_DIB_PAPER, /* Paper bitmap (since PSP7) */ + PSP_DIB_PATTERN, /* Pattern bitmap (since PSP7) */ + PSP_DIB_PATTERN_TRANS_MASK, /* Pattern transparency mask (since PSP7) */ +} PSPDIBType; + +/* Type of image in the composite image bank block. (since PSP6) + */ +typedef enum { + PSP_IMAGE_COMPOSITE = 0, /* Composite Image */ + PSP_IMAGE_THUMBNAIL, /* Thumbnail Image */ +} PSPCompositeImageType; + +/* Graphic contents flags. (since PSP6) + */ +typedef enum { + /* Layer types */ + keGCRasterLayers = 0x00000001, /* At least one raster layer */ + keGCVectorLayers = 0x00000002, /* At least one vector layer */ + keGCAdjustmentLayers = 0x00000004, /* At least one adjustment layer */ + keGCGroupLayers = 0x00000008, /* at least one group layer */ + keGCMaskLayers = 0x00000010, /* at least one mask layer */ + keGCArtMediaLayers = 0x00000020, /* at least one art media layer */ + + /* Additional attributes */ + keGCMergedCache = 0x00800000, /* merged cache (composite image) */ + keGCThumbnail = 0x01000000, /* Has a thumbnail */ + keGCThumbnailTransparency = 0x02000000, /* Thumbnail transp. */ + keGCComposite = 0x04000000, /* Has a composite image */ + keGCCompositeTransparency = 0x08000000, /* Composite transp. */ + keGCFlatImage = 0x10000000, /* Just a background */ + keGCSelection = 0x20000000, /* Has a selection */ + keGCFloatingSelectionLayer = 0x40000000, /* Has float. selection */ + keGCAlphaChannels = 0x80000000, /* Has alpha channel(s) */ +} PSPGraphicContents; + +/* Character style flags. (since PSP6) + */ +typedef enum { + keStyleItalic = 0x00000001, /* Italic property bit */ + keStyleStruck = 0x00000002, /* Strike­out property bit */ + keStyleUnderlined = 0x00000004, /* Underlined property bit */ + keStyleWarped = 0x00000008, /* Warped property bit (since PSP8) */ + keStyleAntiAliased = 0x00000010, /* Anti­aliased property bit (since PSP8) */ +} PSPCharacterProperties; + +/* Table type. (since PSP7) + */ +typedef enum { + keTTUndefined = 0, /* Undefined table type */ + keTTGradientTable, /* Gradient table type */ + keTTPaperTable, /* Paper table type */ + keTTPatternTable /* Pattern table type */ +} PSPTableType; + +/* Layer flags. (since PSP6) + */ +typedef enum { + keVisibleFlag = 0x00000001, /* Layer is visible */ + keMaskPresenceFlag = 0x00000002, /* Layer has a mask */ +} PSPLayerProperties; + +/* Shape property flags. (since PSP6) + */ +typedef enum { + keShapeAntiAliased = 0x00000001, /* Shape is anti­aliased */ + keShapeSelected = 0x00000002, /* Shape is selected */ + keShapeVisible = 0x00000004, /* Shape is visible */ +} PSPShapeProperties; + +/* Polyline node type flags. (since PSP7) + */ +typedef enum { + keNodeUnconstrained = 0x0000, /* Default node type */ + keNodeSmooth = 0x0001, /* Node is smooth */ + keNodeSymmetric = 0x0002, /* Node is symmetric */ + keNodeAligned = 0x0004, /* Node is aligned */ + keNodeActive = 0x0008, /* Node is active */ + keNodeLocked = 0x0010, /* Node is locked */ + keNodeSelected = 0x0020, /* Node is selected */ + keNodeVisible = 0x0040, /* Node is visible */ + keNodeClosed = 0x0080, /* Node is closed */ + + /* TODO: This might be a thinko in the spec document only or in the image + * format itself. Need to investigate that later + */ + keNodeLockedPSP6 = 0x0016, /* Node is locked */ + keNodeSelectedPSP6 = 0x0032, /* Node is selected */ + keNodeVisiblePSP6 = 0x0064, /* Node is visible */ + keNodeClosedPSP6 = 0x0128, /* Node is closed */ + +} PSPPolylineNodeTypes; + +/* Blend modes. (since PSP6) + */ +typedef enum { + PSP_BLEND_NORMAL, + PSP_BLEND_DARKEN, + PSP_BLEND_LIGHTEN, + PSP_BLEND_HUE, + PSP_BLEND_SATURATION, + PSP_BLEND_COLOR, + PSP_BLEND_LUMINOSITY, + PSP_BLEND_MULTIPLY, + PSP_BLEND_SCREEN, + PSP_BLEND_DISSOLVE, + PSP_BLEND_OVERLAY, + PSP_BLEND_HARD_LIGHT, + PSP_BLEND_SOFT_LIGHT, + PSP_BLEND_DIFFERENCE, + PSP_BLEND_DODGE, + PSP_BLEND_BURN, + PSP_BLEND_EXCLUSION, + PSP_BLEND_TRUE_HUE, /* since PSP8 */ + PSP_BLEND_TRUE_SATURATION, /* since PSP8 */ + PSP_BLEND_TRUE_COLOR, /* since PSP8 */ + PSP_BLEND_TRUE_LIGHTNESS, /* since PSP8 */ + PSP_BLEND_ADJUST = 255, +} PSPBlendModes; + +/* Adjustment layer types. (since PSP6) + */ +typedef enum { + keAdjNone = 0, /* Undefined adjustment layer type */ + keAdjLevel, /* Level adjustment */ + keAdjCurve, /* Curve adjustment */ + keAdjBrightContrast, /* Brightness­contrast adjustment */ + keAdjColorBal, /* Color balance adjustment */ + keAdjHSL, /* HSL adjustment */ + keAdjChannelMixer, /* Channel mixer adjustment */ + keAdjInvert, /* Invert adjustment */ + keAdjThreshold, /* Threshold adjustment */ + keAdjPoster /* Posterize adjustment */ +} PSPAdjustmentLayerType; + +/* Vector shape types. (since PSP6) + */ +typedef enum { + keVSTUnknown = 0, /* Undefined vector type */ + keVSTText, /* Shape represents lines of text */ + keVSTPolyline, /* Shape represents a multiple segment line */ + keVSTEllipse, /* Shape represents an ellipse (or circle) */ + keVSTPolygon, /* Shape represents a closed polygon */ + keVSTGroup, /* Shape represents a group shape (since PSP7) */ +} PSPVectorShapeType; + +/* Text element types. (since PSP6) + */ +typedef enum { + keTextElemUnknown = 0, /* Undefined text element type */ + keTextElemChar, /* A single character code */ + keTextElemCharStyle, /* A character style change */ + keTextElemLineStyle /* A line style change */ +} PSPTextElementType; + +/* Text alignment types. (since PSP6) + */ +typedef enum { + keTextAlignmentLeft = 0, /* Left text alignment */ + keTextAlignmentCenter, /* Center text alignment */ + keTextAlignmentRight /* Right text alignment */ +} PSPTextAlignment; + +/* Text antialias modes. */ +typedef enum { + keNoAntialias = 0, /* Antialias off */ + keSharpAntialias, /* Sharp */ + keSmoothAntialias /* Smooth */ +} PSPAntialiasMode; + +/* Text flow types */ +typedef enum { + keTFHorizontalDown = 0, /* Horizontal then down */ + keTFVerticalLeft, /* Vertical then left */ + keTFVerticalRight, /* Vertical then right */ + keTFHorizontalUp /* Horizontal then up */ +} PSPTextFlow; + +/* Paint style types. (since PSP6) + */ +typedef enum { + keStyleNone = 0x0000, /* No paint style info applies */ + keStyleColor = 0x0001, /* Color paint style info */ + keStyleGradient = 0x0002, /* Gradient paint style info */ + keStylePattern = 0x0004, /* Pattern paint style info (since PSP7) */ + keStylePaper = 0x0008, /* Paper paint style info (since PSP7) */ + keStylePen = 0x0010, /* Organic pen paint style info (since PSP7) */ +} PSPPaintStyleType; + +/* Gradient type. (since PSP7) + */ +typedef enum { + keSGTLinear = 0, /* Linera gradient type */ + keSGTRadial, /* Radial gradient type */ + keSGTRectangular, /* Rectangulat gradient type */ + keSGTSunburst /* Sunburst gradient type */ +} PSPStyleGradientType; + +/* Paint Style Cap Type (Start & End). (since PSP7) + */ +typedef enum { + keSCTCapFlat = 0, /* Flat cap type (was round in psp6) */ + keSCTCapRound, /* Round cap type (was square in psp6) */ + keSCTCapSquare, /* Square cap type (was flat in psp6) */ + keSCTCapArrow, /* Arrow cap type */ + keSCTCapCadArrow, /* Cad arrow cap type */ + keSCTCapCurvedTipArrow, /* Curved tip arrow cap type */ + keSCTCapRingBaseArrow, /* Ring base arrow cap type */ + keSCTCapFluerDelis, /* Fluer deLis cap type */ + keSCTCapFootball, /* Football cap type */ + keSCTCapXr71Arrow, /* Xr71 arrow cap type */ + keSCTCapLilly, /* Lilly cap type */ + keSCTCapPinapple, /* Pinapple cap type */ + keSCTCapBall, /* Ball cap type */ + keSCTCapTulip /* Tulip cap type */ +} PSPStyleCapType; + +/* Paint Style Join Type. (since PSP7) + */ +typedef enum { + keSJTJoinMiter = 0, + keSJTJoinRound, + keSJTJoinBevel +} PSPStyleJoinType; + +/* Organic pen type. (since PSP7) + */ +typedef enum { + keSPTOrganicPenNone = 0, /* Undefined pen type */ + keSPTOrganicPenMesh, /* Mesh pen type */ + keSPTOrganicPenSand, /* Sand pen type */ + keSPTOrganicPenCurlicues, /* Curlicues pen type */ + keSPTOrganicPenRays, /* Rays pen type */ + keSPTOrganicPenRipple, /* Ripple pen type */ + keSPTOrganicPenWave, /* Wave pen type */ + keSPTOrganicPen /* Generic pen type */ +} PSPStylePenType; + + +/* Channel types. + */ +typedef enum { + PSP_CHANNEL_COMPOSITE = 0, /* Channel of single channel bitmap */ + PSP_CHANNEL_RED, /* Red channel of 24 bit bitmap */ + PSP_CHANNEL_GREEN, /* Green channel of 24 bit bitmap */ + PSP_CHANNEL_BLUE /* Blue channel of 24 bit bitmap */ +} PSPChannelType; + +/* Possible metrics used to measure resolution. + */ +typedef enum { + PSP_METRIC_UNDEFINED = 0, /* Metric unknown */ + PSP_METRIC_INCH, /* Resolution is in inches */ + PSP_METRIC_CM /* Resolution is in centimeters */ +} PSP_METRIC; + +/* Possible types of compression. + */ +typedef enum { + PSP_COMP_NONE = 0, /* No compression */ + PSP_COMP_RLE, /* RLE compression */ + PSP_COMP_LZ77, /* LZ77 compression */ + PSP_COMP_JPEG /* JPEG compression (only used by thumbnail and composite image) (since PSP6) */ +} PSPCompression; + +/* Picture tube placement mode. + */ +typedef enum { + tpmRandom, /* Place tube images in random intervals */ + tpmConstant /* Place tube images in constant intervals */ +} TubePlacementMode; + +/* Picture tube selection mode. + */ +typedef enum { + tsmRandom, /* Randomly select the next image in */ + /* tube to display */ + tsmIncremental, /* Select each tube image in turn */ + tsmAngular, /* Select image based on cursor direction */ + tsmPressure, /* Select image based on pressure */ + /* (from pressure-sensitive pad) */ + tsmVelocity /* Select image based on cursor speed */ +} TubeSelectionMode; + +/* Extended data field types. + */ +typedef enum { + PSP_XDATA_TRNS_INDEX = 0, /* Transparency index field */ + PSP_XDATA_GRID, /* Image grid information (since PSP7) */ + PSP_XDATA_GUIDE, /* Image guide information (since PSP7) */ + PSP_XDATA_EXIF, /* Image Exif information (since PSP8) */ + PSP_XDATA_IPTC, /* Image IPTC information (since PSP10) */ +} PSPExtendedDataID; + +/* Creator field types. + */ +typedef enum { + PSP_CRTR_FLD_TITLE = 0, /* Image document title field */ + PSP_CRTR_FLD_CRT_DATE, /* Creation date field */ + PSP_CRTR_FLD_MOD_DATE, /* Modification date field */ + PSP_CRTR_FLD_ARTIST, /* Artist name field */ + PSP_CRTR_FLD_CPYRGHT, /* Copyright holder name field */ + PSP_CRTR_FLD_DESC, /* Image document description field */ + PSP_CRTR_FLD_APP_ID, /* Creating app id field */ + PSP_CRTR_FLD_APP_VER /* Creating app version field */ +} PSPCreatorFieldID; + +/* Grid units type. (since PSP7) + */ +typedef enum { + keGridUnitsPixels = 0, /* Grid units is pixels */ + keGridUnitsInches, /* Grid units is inches */ + keGridUnitsCentimeters /* Grid units is centimeters */ +} PSPGridUnitsType; + +/* Guide orientation type. (since PSP7) + */ +typedef enum { + keHorizontalGuide = 0, + keVerticalGuide +} PSPGuideOrientationType; + +/* Creator application identifiers. + */ +typedef enum { + PSP_CREATOR_APP_UNKNOWN = 0, /* Creator application unknown */ + PSP_CREATOR_APP_PAINT_SHOP_PRO /* Creator is Paint Shop Pro */ +} PSPCreatorAppID; + +/* Layer types. + */ +typedef enum { + PSP_LAYER_NORMAL = 0, /* Normal layer */ + PSP_LAYER_FLOATING_SELECTION /* Floating selection layer */ +} PSPLayerTypePSP5; + +/* Layer types. (since PSP6) + */ +typedef enum { + keGLTUndefined = 0, /* Undefined layer type */ + keGLTRaster, /* Standard raster layer */ + keGLTFloatingRasterSelection, /* Floating selection (raster layer) */ + keGLTVector, /* Vector layer */ + keGLTAdjustment, /* Adjustment layer */ + keGLTGroup, /* Group layer (since PSP8) */ + keGLTMask, /* Mask layer (since PSP8) */ + keGLTArtMedia /* Art media layer (since PSP9) */ +} PSPLayerTypePSP6; + +/* Art media layer map types (since PSP7) */ +typedef enum { +keArtMediaColorMap = 0, +keArtMediaBumpMap, +keArtMediaShininessMap, +keArtMediaReflectivityMap, +keArtMediaDrynessMap +} PSPArtMediaMapType; + + +/* Truth values. + */ +#if 0 /* FALSE and TRUE taken by GLib */ +typedef enum { + FALSE = 0, + TRUE +} PSP_BOOLEAN; +#else +typedef gboolean PSP_BOOLEAN; +#endif + +/* End of cut&paste from psp spec */ + +/* We store the various PSP data in own structures. + * We cannot use structs intended to be direct copies of the file block + * headers because of struct alignment issues. + */ +typedef struct +{ + guint32 width, height; + gdouble resolution; + guchar metric; + guint16 compression; + guint16 depth; + guchar grayscale; + guint32 active_layer; + guint16 layer_count; + guint16 bytes_per_sample; + GimpImageBaseType base_type; + GimpPrecision precision; +} PSPimage; + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 load_image (const gchar *filename, + GError **error); +static gint save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + +/* Various local variables... + */ +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +/* Save info */ +typedef struct +{ + PSPCompression compression; +} PSPSaveVals; + +static PSPSaveVals psvals = +{ + PSP_COMP_LZ77 +}; + +static guint16 psp_ver_major, psp_ver_minor; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + +#if 0 + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_INT32, "compression", "Specify 0 for no compression, 1 for RLE, and 2 for LZ77" } + }; +#endif + + gimp_install_procedure (LOAD_PROC, + "loads images from the Paint Shop Pro PSP file format", + "This plug-in loads and exports images in " + "Paint Shop Pro's native PSP format. " + "Vector layers aren't handled. Exporting isn't " + "yet implemented.", + "Tor Lillqvist", + "Tor Lillqvist", + "1999", + N_("Paint Shop Pro image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-psp"); + gimp_register_magic_load_handler (LOAD_PROC, + "psp,tub,pspimage", + "", + "0,string,Paint\\040Shop\\040Pro\\040Image\\040File\n\032"); + + /* commented out until exporting is implemented */ +#if 0 + gimp_install_procedure (SAVE_PROC, + "exports images in the Paint Shop Pro PSP file format", + "This plug-in loads and exports images in " + "Paint Shop Pro's native PSP format. " + "Vector layers aren't handled. Exporting isn't " + "yet implemented.", + "Tor Lillqvist", + "Tor Lillqvist", + "1999", + N_("Paint Shop Pro image"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_save_handler (SAVE_PROC, "psp,tub", ""); +#endif +} + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *frame; + gint run; + + dialog = gimp_export_dialog_new (_("PSP"), PLUG_IN_BINARY, SAVE_PROC); + + /* file save type */ + frame = gimp_int_radio_group_new (TRUE, _("Data Compression"), + G_CALLBACK (gimp_radio_button_update), + &psvals.compression, psvals.compression, + + C_("compression", "None"), PSP_COMP_NONE, NULL, + _("RLE"), PSP_COMP_RLE, NULL, + _("LZ77"), PSP_COMP_LZ77, NULL, + + NULL); + + gtk_container_set_border_width (GTK_CONTAINER (frame), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + frame, FALSE, TRUE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +/* This helper method is used to get the name of the block for the known block + * types. The enum PSPBlockID must cover the input values. + */ +static const gchar * +block_name (gint id) +{ + static const gchar *block_names[] = + { + "IMAGE", + "CREATOR", + "COLOR", + "LAYER_START", + "LAYER", + "CHANNEL", + "SELECTION", + "ALPHA_BANK", + "ALPHA_CHANNEL", + "THUMBNAIL", + "EXTENDED_DATA", + "TUBE", + "ADJUSTMENT_EXTENSION", + "VECTOR_EXTENSION_BLOCK", + "SHAPE_BLOCK", + "PAINTSTYLE_BLOCK", + "COMPOSITE_IMAGE_BANK_BLOCK", + "COMPOSITE_ATTRIBUTES_BLOCK", + "JPEG_BLOCK", + "LINESTYLE_BLOCK", + "TABLE_BANK_BLOCK", + "TABLE_BLOCK", + "PAPER_BLOCK", + "PATTERN_BLOCK", + "GRADIENT_BLOCK", + "GROUP_EXTENSION_BLOCK", + "MASK_EXTENSION_BLOCK", + "BRUSH_BLOCK", + "ART_MEDIA_BLOCK", + "ART_MEDIA_MAP_BLOCK", + "ART_MEDIA_TILE_BLOCK", + "ART_MEDIA_TEXTURE_BLOCK", + "COLORPROFILE_BLOCK", + "RASTER_EXTENSION_BLOCK", +}; + static gchar *err_name = NULL; + + if (id >= 0 && id <= PSP_RASTER_EXTENSION_BLOCK) + return block_names[id]; + + g_free (err_name); + err_name = g_strdup_printf ("id=%d", id); + + return err_name; +} + +/* This helper method is used during loading. It verifies the block we are + * reading has a valid header. Fills the variables init_len and total_len + */ +static gint +read_block_header (FILE *f, + guint32 *init_len, + guint32 *total_len, + GError **error) +{ + guchar buf[4]; + guint16 id; + long header_start; + guint32 len; + + IFDBG(3) header_start = ftell (f); + + if (fread (buf, 4, 1, f) < 1 + || fread (&id, 2, 1, f) < 1 + || fread (&len, 4, 1, f) < 1 + || (psp_ver_major < 4 && fread (total_len, 4, 1, f) < 1)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading block header")); + return -1; + } + if (memcmp (buf, "~BK\0", 4) != 0) + { + IFDBG(3) + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid block header at %ld"), header_start); + else + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid block header")); + return -1; + } + + IFDBG(3) g_message ("%s at %ld", block_name (id), header_start); + + if (psp_ver_major < 4) + { + *init_len = GUINT32_FROM_LE (len); + *total_len = GUINT32_FROM_LE (*total_len); + } + else + { + /* Version 4.0 seems to have dropped the initial data chunk length + * field. + */ + *init_len = 0xDEADBEEF; /* Intentionally bogus, should not be used */ + *total_len = GUINT32_FROM_LE (len); + } + + return GUINT16_FROM_LE (id); +} + +static gint +try_fseek (FILE *f, + glong pos, + gint whence, + GError **error) +{ + if (fseek (f, pos, whence) < 0) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Seek error: %s"), g_strerror (errno)); + fclose (f); + return -1; + } + return 0; +} + +/* Read the PSP_IMAGE_BLOCK */ +static gint +read_general_image_attribute_block (FILE *f, + guint init_len, + guint total_len, + PSPimage *ia, + GError **error) +{ + gchar buf[6]; + guint64 res; + gchar graphics_content[4]; + long chunk_start; + + if (init_len < 38 || total_len < 38) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid general image attribute chunk size.")); + return -1; + } + + chunk_start = ftell (f); + if ((psp_ver_major >= 4 + && (fread (&init_len, 4, 1, f) < 1 || (init_len = GUINT32_FROM_LE (init_len) < 42))) + || fread (&ia->width, 4, 1, f) < 1 + || fread (&ia->height, 4, 1, f) < 1 + || fread (&res, 8, 1, f) < 1 + || fread (&ia->metric, 1, 1, f) < 1 + || fread (&ia->compression, 2, 1, f) < 1 + || fread (&ia->depth, 2, 1, f) < 1 + || fread (buf, 2+4, 1, f) < 1 /* Skip plane and color count */ + || fread (&ia->grayscale, 1, 1, f) < 1 + || fread (buf, 4, 1, f) < 1 /* Skip total image size */ + || fread (&ia->active_layer, 4, 1, f) < 1 + || fread (&ia->layer_count, 2, 1, f) < 1 + || (psp_ver_major >= 4 && fread (graphics_content, 4, 1, f) < 1) + || try_fseek (f, chunk_start + init_len, SEEK_SET, error) < 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading general image attribute block.")); + return -1; + } + ia->width = GUINT32_FROM_LE (ia->width); + ia->height = GUINT32_FROM_LE (ia->height); + + res = GUINT64_FROM_LE (res); + memcpy (&ia->resolution, &res, 8); + if (ia->metric == PSP_METRIC_CM) + ia->resolution /= 2.54; + + ia->compression = GUINT16_FROM_LE (ia->compression); + if (ia->compression > PSP_COMP_LZ77) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unknown compression type %d"), ia->compression); + return -1; + } + + ia->depth = GUINT16_FROM_LE (ia->depth); + switch (ia->depth) + { + case 24: + case 48: + ia->base_type = GIMP_RGB; + if (ia->depth == 24) + ia->precision = GIMP_PRECISION_U8_GAMMA; + else + ia->precision = GIMP_PRECISION_U16_GAMMA; + break; + + case 1: + case 4: + case 8: + case 16: + if (ia->grayscale && ia->depth >= 8) + { + ia->base_type = GIMP_GRAY; + if (ia->depth == 8) + ia->precision = GIMP_PRECISION_U8_GAMMA; + else + ia->precision = GIMP_PRECISION_U16_GAMMA; + } + else if (ia->depth <= 8 && ! ia->grayscale) + { + ia->base_type = GIMP_INDEXED; + ia->precision = GIMP_PRECISION_U8_GAMMA; + } + else + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported bit depth %d"), ia->depth); + return -1; + } + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported bit depth %d"), ia->depth); + return -1; + break; + } + + if (ia->precision == GIMP_PRECISION_U16_GAMMA) + ia->bytes_per_sample = 2; + else + ia->bytes_per_sample = 1; + + ia->active_layer = GUINT32_FROM_LE (ia->active_layer); + ia->layer_count = GUINT16_FROM_LE (ia->layer_count); + + return 0; +} + +static gint +read_creator_block (FILE *f, + gint image_ID, + guint total_len, + PSPimage *ia, + GError **error) +{ + long data_start; + guchar buf[4]; + guint16 keyword; + guint32 length; + gchar *string; + gchar *title = NULL, *artist = NULL, *copyright = NULL, *description = NULL; + guint32 dword; + guint32 __attribute__((unused))cdate = 0; + guint32 __attribute__((unused))mdate = 0; + guint32 __attribute__((unused))appid; + guint32 __attribute__((unused))appver; + GString *comment; + GimpParasite *comment_parasite; + + data_start = ftell (f); + comment = g_string_new (NULL); + + while (ftell (f) < data_start + total_len) + { + if (fread (buf, 4, 1, f) < 1 + || fread (&keyword, 2, 1, f) < 1 + || fread (&length, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading creator keyword chunk")); + return -1; + } + if (memcmp (buf, "~FL\0", 4) != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid keyword chunk header")); + return -1; + } + keyword = GUINT16_FROM_LE (keyword); + length = GUINT32_FROM_LE (length); + switch (keyword) + { + case PSP_CRTR_FLD_TITLE: + case PSP_CRTR_FLD_ARTIST: + case PSP_CRTR_FLD_CPYRGHT: + case PSP_CRTR_FLD_DESC: + string = g_malloc (length + 1); + if (fread (string, length, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading creator keyword data")); + g_free (string); + return -1; + } + /* PSP does not zero terminate strings */ + string[length] = '\0'; + switch (keyword) + { + case PSP_CRTR_FLD_TITLE: + g_free (title); title = string; break; + case PSP_CRTR_FLD_ARTIST: + g_free (artist); artist = string; break; + case PSP_CRTR_FLD_CPYRGHT: + g_free (copyright); copyright = string; break; + case PSP_CRTR_FLD_DESC: + g_free (description); description = string; break; + default: + g_free (string); + } + break; + case PSP_CRTR_FLD_CRT_DATE: + case PSP_CRTR_FLD_MOD_DATE: + case PSP_CRTR_FLD_APP_ID: + case PSP_CRTR_FLD_APP_VER: + if (fread (&dword, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading creator keyword data")); + return -1; + } + switch (keyword) + { + case PSP_CRTR_FLD_CRT_DATE: + cdate = dword; break; + case PSP_CRTR_FLD_MOD_DATE: + mdate = dword; break; + case PSP_CRTR_FLD_APP_ID: + appid = dword; break; + case PSP_CRTR_FLD_APP_VER: + appver = dword; break; + } + break; + default: + if (try_fseek (f, length, SEEK_CUR, error) < 0) + { + return -1; + } + break; + } + } + + if (title) + { + g_string_append (comment, title); + g_free (title); + g_string_append (comment, "\n"); + } + if (artist) + { + g_string_append (comment, artist); + g_free (artist); + g_string_append (comment, "\n"); + } + if (copyright) + { + g_string_append (comment, "Copyright "); + g_string_append (comment, copyright); + g_free (copyright); + g_string_append (comment, "\n"); + } + if (description) + { + g_string_append (comment, description); + g_free (description); + g_string_append (comment, "\n"); + } + if (comment->len > 0) + { + comment_parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (comment->str) + 1, + comment->str); + gimp_image_attach_parasite (image_ID, comment_parasite); + gimp_parasite_free (comment_parasite); + } + + g_string_free (comment, FALSE); + + return 0; +} + +static gint +read_color_block (FILE *f, + gint image_ID, + guint total_len, + PSPimage *ia, + GError **error) +{ + long block_start; + guint32 chunk_len, entry_count, pal_size; + guint32 color_palette_entries; + guchar *color_palette; + + block_start = ftell (f); + + if (psp_ver_major >= 4) + { + if (fread (&chunk_len, 4, 1, f) < 1 + || fread (&entry_count, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading color block")); + return -1; + } + + chunk_len = GUINT32_FROM_LE (chunk_len); + + if (try_fseek (f, block_start + chunk_len, SEEK_SET, error) < 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading color block")); + return -1; + } + } + else + { + if (fread (&entry_count, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading color block")); + return -1; + } + } + + color_palette_entries = GUINT32_FROM_LE (entry_count); + /* psp color palette entries are stored as RGBA so 4 bytes per entry + where the fourth bytes is always zero */ + pal_size = color_palette_entries * 4; + color_palette = g_malloc (pal_size); + if (fread (color_palette, pal_size, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading color palette")); + return -1; + } + else + { + guchar *tmpmap; + gint i; + + /* Convert to BGR palette */ + tmpmap = g_malloc (3 * color_palette_entries); + for (i = 0; i < color_palette_entries; ++i) + { + tmpmap[i*3 ] = color_palette[i*4+2]; + tmpmap[i*3+1] = color_palette[i*4+1]; + tmpmap[i*3+2] = color_palette[i*4]; + } + + memcpy (color_palette, tmpmap, color_palette_entries * 3); + g_free (tmpmap); + + gimp_image_set_colormap (image_ID, color_palette, color_palette_entries); + g_free (color_palette); + } + + return 0; +} + +static void inline +swab_rect (guint32 *rect) +{ + rect[0] = GUINT32_FROM_LE (rect[0]); + rect[1] = GUINT32_FROM_LE (rect[1]); + rect[2] = GUINT32_FROM_LE (rect[2]); + rect[3] = GUINT32_FROM_LE (rect[3]); +} + +static GimpLayerMode +gimp_layer_mode_from_psp_blend_mode (PSPBlendModes mode) +{ + switch (mode) + { + case PSP_BLEND_NORMAL: + return GIMP_LAYER_MODE_NORMAL_LEGACY; + + case PSP_BLEND_DARKEN: + return GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY; + + case PSP_BLEND_LIGHTEN: + return GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY; + + case PSP_BLEND_HUE: + return GIMP_LAYER_MODE_HSV_HUE_LEGACY; + + case PSP_BLEND_SATURATION: + return GIMP_LAYER_MODE_HSV_SATURATION_LEGACY; + + case PSP_BLEND_COLOR: + return GIMP_LAYER_MODE_HSL_COLOR_LEGACY; + + case PSP_BLEND_LUMINOSITY: + return GIMP_LAYER_MODE_HSV_VALUE_LEGACY; /* ??? */ + + case PSP_BLEND_MULTIPLY: + return GIMP_LAYER_MODE_MULTIPLY_LEGACY; + + case PSP_BLEND_SCREEN: + return GIMP_LAYER_MODE_SCREEN_LEGACY; + + case PSP_BLEND_DISSOLVE: + return GIMP_LAYER_MODE_DISSOLVE; + + case PSP_BLEND_OVERLAY: + return GIMP_LAYER_MODE_OVERLAY; + + case PSP_BLEND_HARD_LIGHT: + return GIMP_LAYER_MODE_HARDLIGHT_LEGACY; + + case PSP_BLEND_SOFT_LIGHT: + return GIMP_LAYER_MODE_SOFTLIGHT_LEGACY; + + case PSP_BLEND_DIFFERENCE: + return GIMP_LAYER_MODE_DIFFERENCE_LEGACY; + + case PSP_BLEND_DODGE: + return GIMP_LAYER_MODE_DODGE_LEGACY; + + case PSP_BLEND_BURN: + return GIMP_LAYER_MODE_BURN_LEGACY; + + case PSP_BLEND_EXCLUSION: + return GIMP_LAYER_MODE_EXCLUSION; + + case PSP_BLEND_TRUE_HUE: + return GIMP_LAYER_MODE_HSV_HUE; + + case PSP_BLEND_TRUE_SATURATION: + return GIMP_LAYER_MODE_HSV_SATURATION; + + case PSP_BLEND_TRUE_COLOR: + return GIMP_LAYER_MODE_HSL_COLOR; + + case PSP_BLEND_TRUE_LIGHTNESS: + return GIMP_LAYER_MODE_HSV_VALUE; + + case PSP_BLEND_ADJUST: + return -1; /* ??? */ + } + return -1; +} + +static const gchar * +blend_mode_name (PSPBlendModes mode) +{ + static const gchar *blend_mode_names[] = + { + "NORMAL", + "DARKEN", + "LIGHTEN", + "HUE", + "SATURATION", + "COLOR", + "LUMINOSITY", + "MULTIPLY", + "SCREEN", + "DISSOLVE", + "OVERLAY", + "HARD_LIGHT", + "SOFT_LIGHT", + "DIFFERENCE", + "DODGE", + "BURN", + "EXCLUSION", + "TRUE HUE", + "TRUE SATURATION", + "TRUE COLOR", + "TRUE LIGHTNESS", + /* ADJUST should always be the last one. */ + "ADJUST" + }; + static gchar *err_name = NULL; + + if (mode >= 0 && mode <= PSP_BLEND_TRUE_LIGHTNESS) + return blend_mode_names[mode]; + else if (mode == PSP_BLEND_ADJUST) + return blend_mode_names[PSP_BLEND_TRUE_LIGHTNESS+1]; + + g_free (err_name); + err_name = g_strdup_printf ("unknown layer blend mode %d", mode); + + return err_name; +} + +static const gchar * +layer_type_name (PSPLayerTypePSP6 type) +{ + static const gchar *layer_type_names[] = + { + "Undefined", + "Raster", + "Floating Raster Selection", + "Vector", + "Adjustment", + "Group", + "Mask", + "Art Media", + }; + static gchar *err_name = NULL; + + if (type >= 0 && type <= keGLTArtMedia) + return layer_type_names[type]; + + g_free (err_name); + err_name = g_strdup_printf ("unknown layer type %d", type); + + return err_name; +} + +static const gchar * +bitmap_type_name (gint type) +{ + static const gchar *bitmap_type_names[] = + { + "IMAGE", + "TRANS_MASK", + "USER_MASK", + "SELECTION", + "ALPHA_MASK", + "THUMBNAIL" + }; + static gchar *err_name = NULL; + + if (type >= 0 && type <= PSP_DIB_THUMBNAIL) + return bitmap_type_names[type]; + + g_free (err_name); + err_name = g_strdup_printf ("unknown bitmap type %d", type); + + return err_name; +} + +static const gchar * +channel_type_name (gint type) +{ + static const gchar *channel_type_names[] = + { + "COMPOSITE", + "RED", + "GREEN", + "BLUE" + }; + static gchar *err_name = NULL; + + if (type >= 0 && type <= PSP_CHANNEL_BLUE) + return channel_type_names[type]; + + g_free (err_name); + err_name = g_strdup_printf ("unknown channel type %d", type); + + return err_name; +} + +static void * +psp_zalloc (void *opaque, + guint items, + guint size) +{ + return g_malloc (items*size); +} + +static void +psp_zfree (void *opaque, + void *ptr) +{ + g_free (ptr); +} + +static void +upscale_indexed_sub_8 (FILE *f, + gint width, + gint height, + gint bpp, + guchar *buf) +{ + gint x, y, b, line_width; + gint bpp_zero_based = bpp - 1; + gint current_bit = 0; + guchar *tmpbuf, *buf_start, *src; + + /* Scanlines for 1 and 4 bit only end on a 4-byte boundary. */ + line_width = (((width * bpp + 7) / 8) + bpp_zero_based) / 4 * 4; + buf_start = g_malloc0 (width * height); + tmpbuf = buf_start; + + for (y = 0; y < height; tmpbuf += width, ++y) + { + src = buf + y * line_width; + for (x = 0; x < width; ++x) + { + for (b = 0; b < bpp; b++) + { + current_bit = bpp * x + b; + if (src[current_bit / 8] & (128 >> (current_bit % 8))) + tmpbuf[x] += (1 << (bpp_zero_based - b)); + } + } + } + + memcpy (buf, buf_start, width * height); + g_free (buf_start); +} + +static int +read_channel_data (FILE *f, + PSPimage *ia, + guchar **pixels, + guint bytespp, + guint offset, + GeglBuffer *buffer, + guint32 compressed_len, + GError **error) +{ + gint i, y, line_width; + gint width = gegl_buffer_get_width (buffer); + gint height = gegl_buffer_get_height (buffer); + gint npixels = width * height; + guchar *buf; + guchar *buf2 = NULL; /* please the compiler */ + guchar runcount, byte; + z_stream zstream; + + g_assert (ia->bytes_per_sample <= 2); + + if (ia->depth < 8) + { + /* Scanlines for 1 and 4 bit only end on a 4-byte boundary. */ + line_width = (((width * ia->depth + 7) / 8) + ia->depth - 1) / 4 * 4; + } + else + { + line_width = width * ia->bytes_per_sample; + } + + switch (ia->compression) + { + case PSP_COMP_NONE: + if (bytespp == 1) + { + fread (pixels[0], height * line_width, 1, f); + } + else + { + buf = g_malloc (line_width); + if (ia->bytes_per_sample == 1) + { + for (y = 0; y < height; y++) + { + guchar *p, *q; + + fread (buf, width, 1, f); + /* Contrary to what the PSP specification seems to suggest + scanlines are not stored on a 4-byte boundary. */ + p = buf; + q = pixels[y] + offset; + for (i = 0; i < width; i++) + { + *q = *p++; + q += bytespp; + } + } + } + else if (ia->bytes_per_sample == 2) + { + for (y = 0; y < height; y++) + { + guint16 *p, *q; + + fread (buf, width * ia->bytes_per_sample, 1, f); + /* Contrary to what the PSP specification seems to suggest + scanlines are not stored on a 4-byte boundary. */ + p = (guint16 *) buf; + q = (guint16 *) (pixels[y] + offset); + for (i = 0; i < width; i++) + { + *q = GUINT16_FROM_LE (*p++); + q += bytespp / 2; + } + } + } + + g_free (buf); + } + break; + + case PSP_COMP_RLE: + { + guchar *q, *endq; + + q = pixels[0] + offset; + if (ia->depth >= 8) + endq = q + npixels * bytespp; + else + endq = q + line_width * height; + + buf = g_malloc (127); + while (q < endq) + { + fread (&runcount, 1, 1, f); + if (runcount > 128) + { + runcount -= 128; + fread (&byte, 1, 1, f); + memset (buf, byte, runcount); + } + else + fread (buf, runcount, 1, f); + + /* prevent buffer overflow for bogus data */ + if (runcount > (endq - q) / bytespp + ia->bytes_per_sample - 1) + { + g_printerr ("Buffer overflow decompressing RLE data.\n"); + break; + } + + if (bytespp == 1) + { + memmove (q, buf, runcount); + q += runcount; + } + else if (ia->bytes_per_sample == 1) + { + guchar *p = buf; + + for (i = 0; i < runcount; i++) + { + *q = *p++; + q += bytespp; + } + } + else if (ia->bytes_per_sample == 2) + { + guint16 *p = (guint16 *) buf; + guint16 *r = (guint16 *) q; + + for (i = 0; i < runcount / 2; i++) + { + *r = GUINT16_FROM_LE (*p++); + r += bytespp / 2; + } + q = (guchar *) r; + } + } + g_free (buf); + } + break; + + case PSP_COMP_LZ77: + buf = g_malloc (compressed_len); + fread (buf, compressed_len, 1, f); + zstream.next_in = buf; + zstream.avail_in = compressed_len; + zstream.zalloc = psp_zalloc; + zstream.zfree = psp_zfree; + zstream.opaque = f; + if (inflateInit (&zstream) != Z_OK) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("zlib error")); + return -1; + } + if (bytespp == 1) + zstream.next_out = pixels[0]; + else + { + buf2 = g_malloc (npixels * ia->bytes_per_sample); + zstream.next_out = buf2; + } + zstream.avail_out = npixels * ia->bytes_per_sample; + if (inflate (&zstream, Z_FINISH) != Z_STREAM_END) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("zlib error")); + inflateEnd (&zstream); + return -1; + } + inflateEnd (&zstream); + g_free (buf); + + if (bytespp > 1) + { + if (ia->bytes_per_sample == 1) + { + guchar *p, *q; + + p = buf2; + q = pixels[0] + offset; + for (i = 0; i < npixels; i++) + { + *q = *p++; + q += bytespp; + } + g_free (buf2); + } + else if (ia->bytes_per_sample == 2) + { + guint16 *p, *q; + + p = (guint16 *) buf2; + q = (guint16 *) (pixels[0] + offset); + for (i = 0; i < npixels; i++) + { + *q = GUINT16_FROM_LE (*p++); + q += bytespp / 2; + } + g_free (buf2); + } + } + break; + } + + if (ia->base_type == GIMP_INDEXED && ia->depth < 8) + { + /* We need to convert 1 and 4 bit to 8 bit indexed */ + upscale_indexed_sub_8 (f, width, height, ia->depth, pixels[0]); + } + + return 0; +} + +static gboolean +read_raster_layer_info (FILE *f, + long layer_extension_start, + guint16 *bitmap_count, + guint16 *channel_count, + GError **error) +{ + long block_start; + guint32 layer_extension_len, block_len; + gint block_id; + + if (fseek (f, layer_extension_start, SEEK_SET) < 0 + || fread (&layer_extension_len, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading layer extension information")); + return FALSE; + } + + /* Newer versions of PSP have an extra block here for raster layers + with block id = 0x21. Most likely this was to fix an oversight in + the specification since vector and adjustment layers already were + using a similar extension block since file version 4. + The old chunk with bitmap_count and channel_count starts after this block. + We do not know starting from which version this change was implemented + but most likely version 9 (could also be version 10) so we can't test + based on version number only. + Although this is kind of a hack we can safely test for the block starting + code since the layer_extension_len here is always a small number. + */ + if (psp_ver_major > 8 && memcmp (&layer_extension_len, "~BK\0", 4) == 0) + { + if (fread (&block_id, 2, 1, f) < 1 + || fread (&block_len, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading block information")); + return FALSE; + } + block_id = GUINT16_FROM_LE (block_id); + block_len = GUINT32_FROM_LE (block_len); + + block_start = ftell (f); + layer_extension_start = block_start + block_len; + + if (fseek (f, layer_extension_start, SEEK_SET) < 0 + || fread (&layer_extension_len, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading layer extension information")); + return FALSE; + } + } + layer_extension_len = GUINT32_FROM_LE (layer_extension_len); + + if (fread (bitmap_count, 2, 1, f) < 1 + || fread (channel_count, 2, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading layer extension information")); + return FALSE; + } + if (try_fseek (f, layer_extension_start + layer_extension_len, SEEK_SET, error) < 0) + { + return FALSE; + } + + return TRUE; +} + +static gint +read_layer_block (FILE *f, + gint image_ID, + guint total_len, + PSPimage *ia, + GError **error) +{ + gint i; + long block_start, sub_block_start, channel_start; + long layer_extension_start; + gint sub_id; + guint32 sub_init_len, sub_total_len; + guint32 chunk_len; + gchar *name = NULL; + gchar *layer_name = NULL; + guint16 namelen; + guchar type, opacity, blend_mode, visibility, transparency_protected; + guchar link_group_id, mask_linked, mask_disabled; + guint32 image_rect[4], saved_image_rect[4], mask_rect[4], saved_mask_rect[4]; + gboolean null_layer, can_handle_layer; + guint16 bitmap_count, channel_count; + GimpImageType drawable_type; + guint32 layer_ID = 0; + GimpLayerMode layer_mode; + guint32 channel_init_len, channel_total_len; + guint32 compressed_len, uncompressed_len; + guint16 bitmap_type, channel_type; + gint width, height, bytespp, offset; + guchar **pixels, *pixel; + GeglBuffer *buffer; + + block_start = ftell (f); + + while (ftell (f) < block_start + total_len) + { + null_layer = FALSE; + can_handle_layer = FALSE; + + /* Read the layer sub-block header */ + sub_id = read_block_header (f, &sub_init_len, &sub_total_len, error); + if (sub_id == -1) + return -1; + + if (sub_id != PSP_LAYER_BLOCK) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid layer sub-block %s, should be LAYER"), + block_name (sub_id)); + return -1; + } + + sub_block_start = ftell (f); + + /* Read layer information chunk */ + if (psp_ver_major >= 4) + { + if (fread (&chunk_len, 4, 1, f) < 1 + || fread (&namelen, 2, 1, f) < 1 + /* A zero length layer name is apparently valid. To not get a warning for + namelen < 0 always being false we use this more complicated comparison. */ + || ((namelen = GUINT16_FROM_LE (namelen)) && (FALSE || namelen == 0)) + || (name = g_malloc (namelen + 1)) == NULL + || (namelen > 0 && fread (name, namelen, 1, f) < 1) + || fread (&type, 1, 1, f) < 1 + || fread (&image_rect, 16, 1, f) < 1 + || fread (&saved_image_rect, 16, 1, f) < 1 + || fread (&opacity, 1, 1, f) < 1 + || fread (&blend_mode, 1, 1, f) < 1 + || fread (&visibility, 1, 1, f) < 1 + || fread (&transparency_protected, 1, 1, f) < 1 + || fread (&link_group_id, 1, 1, f) < 1 + || fread (&mask_rect, 16, 1, f) < 1 + || fread (&saved_mask_rect, 16, 1, f) < 1 + || fread (&mask_linked, 1, 1, f) < 1 + || fread (&mask_disabled, 1, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading layer information chunk")); + g_free (name); + return -1; + } + + name[namelen] = 0; + layer_name = g_convert (name, -1, "utf-8", "iso8859-1", NULL, NULL, NULL); + g_free (name); + + chunk_len = GUINT32_FROM_LE (chunk_len); + layer_extension_start = sub_block_start + chunk_len; + + switch (type) + { + case keGLTFloatingRasterSelection: + g_message ("Floating selection restored as normal layer (%s)", layer_name); + case keGLTRaster: + if (! read_raster_layer_info (f, layer_extension_start, + &bitmap_count, + &channel_count, + error)) + { + g_free (layer_name); + return -1; + } + can_handle_layer = TRUE; + break; + default: + bitmap_count = 0; + channel_count = 0; + g_message ("Unsupported layer type %s (%s)", layer_type_name(type), layer_name); + break; + } + } + else + { + name = g_malloc (257); + name[256] = 0; + + if (fread (name, 256, 1, f) < 1 + || fread (&type, 1, 1, f) < 1 + || fread (&image_rect, 16, 1, f) < 1 + || fread (&saved_image_rect, 16, 1, f) < 1 + || fread (&opacity, 1, 1, f) < 1 + || fread (&blend_mode, 1, 1, f) < 1 + || fread (&visibility, 1, 1, f) < 1 + || fread (&transparency_protected, 1, 1, f) < 1 + || fread (&link_group_id, 1, 1, f) < 1 + || fread (&mask_rect, 16, 1, f) < 1 + || fread (&saved_mask_rect, 16, 1, f) < 1 + || fread (&mask_linked, 1, 1, f) < 1 + || fread (&mask_disabled, 1, 1, f) < 1 + || fseek (f, 43, SEEK_CUR) < 0 + || fread (&bitmap_count, 2, 1, f) < 1 + || fread (&channel_count, 2, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading layer information chunk")); + g_free (name); + return -1; + } + layer_name = g_convert (name, -1, "utf-8", "iso8859-1", NULL, NULL, NULL); + g_free (name); + if (type == PSP_LAYER_FLOATING_SELECTION) + g_message ("Floating selection restored as normal layer"); + type = keGLTRaster; + can_handle_layer = TRUE; + if (try_fseek (f, sub_block_start + sub_init_len, SEEK_SET, error) < 0) + { + g_free (layer_name); + return -1; + } + } + + swab_rect (image_rect); + swab_rect (saved_image_rect); + swab_rect (mask_rect); + swab_rect (saved_mask_rect); + bitmap_count = GUINT16_FROM_LE (bitmap_count); + channel_count = GUINT16_FROM_LE (channel_count); + + layer_mode = gimp_layer_mode_from_psp_blend_mode (blend_mode); + if ((int) layer_mode == -1) + { + g_message ("Unsupported PSP layer blend mode %s " + "for layer %s, setting layer invisible", + blend_mode_name (blend_mode), layer_name); + layer_mode = GIMP_LAYER_MODE_NORMAL_LEGACY; + visibility = FALSE; + } + + width = saved_image_rect[2] - saved_image_rect[0]; + height = saved_image_rect[3] - saved_image_rect[1]; + + if ((width < 0) || (width > GIMP_MAX_IMAGE_SIZE) /* w <= 2^18 */ + || (height < 0) || (height > GIMP_MAX_IMAGE_SIZE) /* h <= 2^18 */ + || ((width / 256) * (height / 256) >= 8192)) /* w * h < 2^29 */ + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid layer dimensions: %dx%d"), + width, height); + g_free (layer_name); + return -1; + } + + IFDBG(2) g_message + ("layer: %s %dx%d (%dx%d) @%d,%d opacity %d blend_mode %s " + "%d bitmaps %d channels", + layer_name, + image_rect[2] - image_rect[0], image_rect[3] - image_rect[1], + width, height, + image_rect[0]+saved_image_rect[0], image_rect[1]+saved_image_rect[1], + opacity, blend_mode_name (blend_mode), + bitmap_count, channel_count); + + IFDBG(2) g_message + ("mask %dx%d (%dx%d) @%d,%d", + mask_rect[2] - mask_rect[0], + mask_rect[3] - mask_rect[1], + saved_mask_rect[2] - saved_mask_rect[0], + saved_mask_rect[3] - saved_mask_rect[1], + saved_mask_rect[0], saved_mask_rect[1]); + + if (width == 0) + { + width++; + null_layer = TRUE; + } + if (height == 0) + { + height++; + null_layer = TRUE; + } + + if (ia->base_type == GIMP_RGB) + if (bitmap_count == 1) + drawable_type = GIMP_RGB_IMAGE, bytespp = 3; + else + drawable_type = GIMP_RGBA_IMAGE, bytespp = 4; + else if (ia->base_type == GIMP_GRAY) + if (bitmap_count == 1) + drawable_type = GIMP_GRAY_IMAGE, bytespp = 1; + else + drawable_type = GIMP_GRAYA_IMAGE, bytespp = 2; + else + if (bitmap_count == 1) + drawable_type = GIMP_INDEXED_IMAGE, bytespp = 1; + else + drawable_type = GIMP_INDEXEDA_IMAGE, bytespp = 2; + bytespp *= ia->bytes_per_sample; + + layer_ID = gimp_layer_new (image_ID, layer_name, + width, height, + drawable_type, + 100.0 * opacity / 255.0, + layer_mode); + g_free (layer_name); + if (layer_ID == -1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error creating layer")); + return -1; + } + + gimp_image_insert_layer (image_ID, layer_ID, -1, -1); + + if (image_rect[0] != 0 || image_rect[1] != 0 || saved_image_rect[0] != 0 || saved_image_rect[1] != 0) + gimp_layer_set_offsets (layer_ID, + image_rect[0] + saved_image_rect[0], image_rect[1] + saved_image_rect[1]); + + if (!visibility) + gimp_item_set_visible (layer_ID, FALSE); + + gimp_layer_set_lock_alpha (layer_ID, transparency_protected); + + if (can_handle_layer) + { + pixel = g_malloc0 (height * width * bytespp); + if (null_layer) + { + pixels = NULL; + } + else + { + pixels = g_new (guchar *, height); + for (i = 0; i < height; i++) + pixels[i] = pixel + width * bytespp * i; + } + + buffer = gimp_drawable_get_buffer (layer_ID); + + /* Read the layer channel sub-blocks */ + while (ftell (f) < sub_block_start + sub_total_len) + { + sub_id = read_block_header (f, &channel_init_len, + &channel_total_len, error); + if (sub_id == -1) + { + gimp_image_delete (image_ID); + return -1; + } + + if (sub_id != PSP_CHANNEL_BLOCK) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid layer sub-block %s, should be CHANNEL"), + block_name (sub_id)); + return -1; + } + + channel_start = ftell (f); + chunk_len = channel_init_len; /* init chunk_len for psp_ver_major == 3 */ + if ((psp_ver_major >= 4 + && (fread (&chunk_len, 4, 1, f) < 1 + || ((chunk_len = GUINT32_FROM_LE (chunk_len)) < 16))) + || fread (&compressed_len, 4, 1, f) < 1 + || fread (&uncompressed_len, 4, 1, f) < 1 + || fread (&bitmap_type, 2, 1, f) < 1 + || fread (&channel_type, 2, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading channel information chunk")); + return -1; + } + + compressed_len = GUINT32_FROM_LE (compressed_len); + uncompressed_len = GUINT32_FROM_LE (uncompressed_len); + bitmap_type = GUINT16_FROM_LE (bitmap_type); + channel_type = GUINT16_FROM_LE (channel_type); + + if (bitmap_type > PSP_DIB_USER_MASK) + { + g_message ("Conversion of bitmap type %d is not supported.", bitmap_type); + } + else if (bitmap_type == PSP_DIB_USER_MASK) + { + /* FIXME: Add as layer mask */ + g_message ("Conversion of layer mask is not supported"); + } + else + { + if (channel_type > PSP_CHANNEL_BLUE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Invalid channel type %d in channel information chunk"), + channel_type); + return -1; + } + + IFDBG(2) g_message ("channel: %s %s %d (%d) bytes %d bytespp", + bitmap_type_name (bitmap_type), + channel_type_name (channel_type), + uncompressed_len, compressed_len, + bytespp); + + if (bitmap_type == PSP_DIB_TRANS_MASK || channel_type == PSP_CHANNEL_COMPOSITE) + offset = bytespp - ia->bytes_per_sample; + else + offset = (channel_type - PSP_CHANNEL_RED) * ia->bytes_per_sample; + + if (!null_layer) + { + if (try_fseek (f, channel_start + chunk_len, SEEK_SET, error) < 0) + { + return -1; + } + + if (read_channel_data (f, ia, pixels, bytespp, offset, + buffer, compressed_len, error) == -1) + { + return -1; + } + } + } + if (try_fseek (f, channel_start + channel_total_len, SEEK_SET, error) < 0) + { + return -1; + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0, + NULL, pixel, GEGL_AUTO_ROWSTRIDE); + + g_object_unref (buffer); + + g_free (pixels); + g_free (pixel); + if (psp_ver_major >= 4) + { + if (try_fseek (f, sub_block_start + sub_total_len, SEEK_SET, error) < 0) + { + return -1; + } + } + } + else + { + /* Can't handle this type of layer, skip the data so we can read the next layer. */ + if (psp_ver_major >= 4) + { + if (try_fseek (f, sub_block_start + sub_total_len, SEEK_SET, error) < 0) + { + return -1; + } + } + } + } + + if (try_fseek (f, block_start + total_len, SEEK_SET, error) < 0) + { + return -1; + } + + return layer_ID; +} + +static gint +read_tube_block (FILE *f, + gint image_ID, + guint total_len, + PSPimage *ia, + GError **error) +{ + guint16 version; + guchar name[514]; + guint32 step_size, column_count, row_count, cell_count; + guint32 placement_mode, selection_mode; + guint32 chunk_len; + gint i; + GimpPixPipeParams params; + GimpParasite *pipe_parasite; + gchar *parasite_text; + + gimp_pixpipe_params_init (¶ms); + + if (psp_ver_major >= 4) + { + name[0] = 0; + if (fread (&chunk_len, 4, 1, f) < 1 + || fread (&version, 2, 1, f) < 1 + || fread (&step_size, 4, 1, f) < 1 + || fread (&column_count, 4, 1, f) < 1 + || fread (&row_count, 4, 1, f) < 1 + || fread (&cell_count, 4, 1, f) < 1 + || fread (&placement_mode, 4, 1, f) < 1 + || fread (&selection_mode, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading tube data chunk")); + return -1; + } + } + else + { + chunk_len = 0; + if (fread (&version, 2, 1, f) < 1 + || fread (name, 513, 1, f) < 1 + || fread (&step_size, 4, 1, f) < 1 + || fread (&column_count, 4, 1, f) < 1 + || fread (&row_count, 4, 1, f) < 1 + || fread (&cell_count, 4, 1, f) < 1 + || fread (&placement_mode, 4, 1, f) < 1 + || fread (&selection_mode, 4, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading tube data chunk")); + return -1; + } + name[513] = 0; + } + + version = GUINT16_FROM_LE (version); + params.step = GUINT32_FROM_LE (step_size); + params.cols = GUINT32_FROM_LE (column_count); + params.rows = GUINT32_FROM_LE (row_count); + params.ncells = GUINT32_FROM_LE (cell_count); + placement_mode = GUINT32_FROM_LE (placement_mode); + selection_mode = GUINT32_FROM_LE (selection_mode); + + for (i = 1; i < params.cols; i++) + gimp_image_add_vguide (image_ID, (ia->width * i)/params.cols); + for (i = 1; i < params.rows; i++) + gimp_image_add_hguide (image_ID, (ia->height * i)/params.rows); + + /* We use a parasite to pass in the tube (pipe) parameters in + * case we will have any use of those, for instance in the gpb + * plug-in that exports a GIMP image pipe. + */ + params.dim = 1; + params.cellwidth = ia->width / params.cols; + params.cellheight = ia->height / params.rows; + params.placement = (placement_mode == tpmRandom ? "random" : + (placement_mode == tpmConstant ? "constant" : + "default")); + params.rank[0] = params.ncells; + params.selection[0] = (selection_mode == tsmRandom ? "random" : + (selection_mode == tsmIncremental ? "incremental" : + (selection_mode == tsmAngular ? "angular" : + (selection_mode == tsmPressure ? "pressure" : + (selection_mode == tsmVelocity ? "velocity" : + "default"))))); + parasite_text = gimp_pixpipe_params_build (¶ms); + + IFDBG(2) g_message ("parasite: %s", parasite_text); + + pipe_parasite = gimp_parasite_new ("gimp-brush-pipe-parameters", + GIMP_PARASITE_PERSISTENT, + strlen (parasite_text) + 1, parasite_text); + gimp_image_attach_parasite (image_ID, pipe_parasite); + gimp_parasite_free (pipe_parasite); + g_free (parasite_text); + + return 0; +} + +static const gchar * +compression_name (gint compression) +{ + switch (compression) + { + case PSP_COMP_NONE: + return "no compression"; + case PSP_COMP_RLE: + return "RLE"; + case PSP_COMP_LZ77: + return "LZ77"; + } + g_assert_not_reached (); + + return NULL; +} + +/* The main function for loading PSP-images + */ +static gint32 +load_image (const gchar *filename, + GError **error) +{ + FILE *f; + GStatBuf st; + char buf[32]; + PSPimage ia; + guint32 block_init_len, block_total_len; + long block_start; + PSPBlockID id = -1; + gint block_number; + gint32 image_ID = -1; + + if (g_stat (filename, &st) == -1) + return -1; + + f = g_fopen (filename, "rb"); + if (f == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + /* Read the PSP File Header and determine file version */ + if (fread (buf, 32, 1, f) < 1 + || fread (&psp_ver_major, 2, 1, f) < 1 + || fread (&psp_ver_minor, 2, 1, f) < 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading file header.")); + goto error; + } + + if (memcmp (buf, "Paint Shop Pro Image File\n\032\0\0\0\0\0", 32) != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Incorrect file signature.")); + goto error; + } + + psp_ver_major = GUINT16_FROM_LE (psp_ver_major); + psp_ver_minor = GUINT16_FROM_LE (psp_ver_minor); + + /* We don't have the documentation for file format versions before 3.0, + * but newer versions should be mostly backwards compatible so that + * we can still read the image and skip unknown parts safely. + */ + if (psp_ver_major < 3) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported PSP file format version %d.%d."), + psp_ver_major, psp_ver_minor); + goto error; + } + + /* Read all the blocks */ + block_number = 0; + + IFDBG(3) g_message ("size = %d", (int)st.st_size); + while (ftell (f) != st.st_size + && (id = read_block_header (f, &block_init_len, + &block_total_len, error)) != -1) + { + block_start = ftell (f); + + if (block_start + block_total_len > st.st_size) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), + _("invalid block size")); + goto error; + } + + if (id == PSP_IMAGE_BLOCK) + { + if (block_number != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Duplicate General Image Attributes block.")); + goto error; + } + if (read_general_image_attribute_block (f, block_init_len, + block_total_len, &ia, error) == -1) + { + goto error; + } + + IFDBG(2) g_message ("%d dpi %dx%d %s", + (int) ia.resolution, + ia.width, ia.height, + compression_name (ia.compression)); + + image_ID = gimp_image_new_with_precision (ia.width, ia.height, + ia.base_type, ia.precision); + if (image_ID == -1) + { + goto error; + } + + gimp_image_set_filename (image_ID, filename); + + gimp_image_set_resolution (image_ID, ia.resolution, ia.resolution); + } + else + { + if (block_number == 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Missing General Image Attributes block.")); + goto error; + } + + switch (id) + { + case PSP_CREATOR_BLOCK: + if (read_creator_block (f, image_ID, block_total_len, &ia, error) == -1) + goto error; + break; + + case PSP_COLOR_BLOCK: + if (read_color_block (f, image_ID, block_total_len, &ia, error) == -1) + goto error; + break; + + case PSP_LAYER_START_BLOCK: + if (read_layer_block (f, image_ID, block_total_len, &ia, error) == -1) + goto error; + break; + + case PSP_SELECTION_BLOCK: + break; /* Not yet implemented */ + + case PSP_ALPHA_BANK_BLOCK: + break; /* Not yet implemented */ + + case PSP_THUMBNAIL_BLOCK: + break; /* No use for it */ + + case PSP_EXTENDED_DATA_BLOCK: + break; /* Not yet implemented */ + + case PSP_TUBE_BLOCK: + if (read_tube_block (f, image_ID, block_total_len, &ia, error) == -1) + goto error; + break; + + case PSP_COMPOSITE_IMAGE_BANK_BLOCK: + break; /* Not yet implemented */ + + case PSP_TABLE_BANK_BLOCK: + break; /* Not yet implemented */ + + case PSP_BRUSH_BLOCK: + break; /* Not yet implemented */ + + case PSP_ART_MEDIA_BLOCK: + case PSP_ART_MEDIA_MAP_BLOCK: + case PSP_ART_MEDIA_TILE_BLOCK: + case PSP_ART_MEDIA_TEXTURE_BLOCK: + break; /* Not yet implemented */ + + case PSP_COLORPROFILE_BLOCK: + break; /* Not yet implemented */ + + case PSP_LAYER_BLOCK: + case PSP_CHANNEL_BLOCK: + case PSP_ALPHA_CHANNEL_BLOCK: + case PSP_ADJUSTMENT_EXTENSION_BLOCK: + case PSP_VECTOR_EXTENSION_BLOCK: + case PSP_SHAPE_BLOCK: + case PSP_PAINTSTYLE_BLOCK: + case PSP_COMPOSITE_ATTRIBUTES_BLOCK: + case PSP_JPEG_BLOCK: + case PSP_LINESTYLE_BLOCK: + case PSP_TABLE_BLOCK: + case PSP_PAPER_BLOCK: + case PSP_PATTERN_BLOCK: + case PSP_GRADIENT_BLOCK: + case PSP_GROUP_EXTENSION_BLOCK: + case PSP_MASK_EXTENSION_BLOCK: + case PSP_RASTER_EXTENSION_BLOCK: + g_message ("Sub-block %s should not occur " + "at main level of file", + block_name (id)); + break; + + default: + g_message ("Unrecognized block id %d", id); + break; + } + } + + if (block_start + block_total_len >= st.st_size) + break; + + if (try_fseek (f, block_start + block_total_len, SEEK_SET, error) < 0) + goto error; + + block_number++; + } + + if (id == -1) + { + error: + fclose (f); + if (image_ID != -1) + gimp_image_delete (image_ID); + return -1; + } + + fclose (f); + + return image_ID; +} + +static gint +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Exporting not implemented yet.")); + + return FALSE; +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "PSP", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA | + GIMP_EXPORT_CAN_HANDLE_LAYERS); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &psvals); + + /* First acquire information with a dialog */ + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + psvals.compression = (param[5].data.d_int32) ? TRUE : FALSE; + + if (param[5].data.d_int32 < 0 || + param[5].data.d_int32 > PSP_COMP_LZ77) + status = GIMP_PDB_CALLING_ERROR; + } + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (SAVE_PROC, &psvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (param[3].data.d_string, image_ID, drawable_ID, + &error)) + { + gimp_set_data (SAVE_PROC, &psvals, sizeof (PSPSaveVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} diff --git a/plug-ins/common/file-raw-data.c b/plug-ins/common/file-raw-data.c new file mode 100644 index 0000000..3d2daad --- /dev/null +++ b/plug-ins/common/file-raw-data.c @@ -0,0 +1,2280 @@ +/* Raw data image loader (and saver) plugin 3.4 + * + * by tim copperfield [timecop@japan.co.jp] + * http://www.ne.jp/asahi/linux/timecop + * + * Updated for Gimp 2.1 by pg@futureware.at and mitch@gimp.org + * + * This plugin is not based on any other plugin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#ifdef G_OS_WIN32 +#include +#endif + +#include "libgimp/gimp.h" +#include "libgimp/gimpui.h" + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-raw-load" +#define LOAD_HGT_PROC "file-hgt-load" +#define SAVE_PROC "file-raw-save" +#define SAVE_PROC2 "file-raw-save2" +#define GET_DEFAULTS_PROC "file-raw-get-defaults" +#define SET_DEFAULTS_PROC "file-raw-set-defaults" +#define PLUG_IN_BINARY "file-raw-data" +#define PLUG_IN_ROLE "gimp-file-raw-data" +#define PREVIEW_SIZE 350 + +#define RAW_DEFAULTS_PARASITE "raw-save-defaults" + +#define GIMP_PLUGIN_HGT_LOAD_ERROR gimp_plugin_hgt_load_error_quark () + +typedef enum +{ + GIMP_PLUGIN_HGT_LOAD_ARGUMENT_ERROR +} GimpPluginHGTError; + +static GQuark +gimp_plugin_hgt_load_error_quark (void) +{ + return g_quark_from_static_string ("gimp-plugin-hgt-load-error-quark"); +} + +typedef enum +{ + RAW_RGB, /* RGB Image */ + RAW_RGBA, /* RGB Image with an Alpha channel */ + RAW_RGB565_BE, /* RGB Image 16bit, 5,6,5 bits per channel, big-endian */ + RAW_RGB565_LE, /* RGB Image 16bit, 5,6,5 bits per channel, little-endian */ + RAW_BGR565_BE, /* RGB Image 16bit, 5,6,5 bits per channel, big-endian, red and blue swapped */ + RAW_BGR565_LE, /* RGB Image 16bit, 5,6,5 bits per channel, little-endian, red and blue swapped */ + RAW_PLANAR, /* Planar RGB */ + RAW_GRAY_1BPP, + RAW_GRAY_2BPP, + RAW_GRAY_4BPP, + RAW_GRAY_8BPP, + RAW_INDEXED, /* Indexed image */ + RAW_INDEXEDA, /* Indexed image with an Alpha channel */ + RAW_GRAY_16BPP_BE, + RAW_GRAY_16BPP_LE, + RAW_GRAY_16BPP_SBE, + RAW_GRAY_16BPP_SLE, +} RawType; + +typedef enum +{ + RAW_PALETTE_RGB, /* standard RGB */ + RAW_PALETTE_BGR /* Windows BGRX */ +} RawPaletteType; + +typedef struct +{ + RawType image_type; /* type of image (RGB, PLANAR) */ + RawPaletteType palette_type; /* type of palette (RGB/BGR) */ +} RawSaveVals; + +typedef struct +{ + gboolean run; + + GtkWidget *image_type_standard; + GtkWidget *image_type_planar; + GtkWidget *palette_type_normal; + GtkWidget *palette_type_bmp; +} RawSaveGui; + +typedef struct +{ + gint32 file_offset; /* offset to beginning of image in raw data */ + gint32 image_width; /* width of the raw image */ + gint32 image_height; /* height of the raw image */ + RawType image_type; /* type of image (RGB, INDEXED, etc) */ + gint32 palette_offset; /* offset inside the palette file, if any */ + RawPaletteType palette_type; /* type of palette (RGB/BGR) */ +} RawConfig; + +typedef struct +{ + FILE *fp; /* pointer to the already open file */ + GeglBuffer *buffer; /* gimp drawable buffer */ + gint32 image_id; /* gimp image id */ + guchar cmap[768]; /* color map for indexed images */ +} RawGimpData; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +/* prototypes for the new load functions */ +static gboolean raw_load_standard (RawGimpData *data, + gint bpp); +static gboolean raw_load_gray (RawGimpData *data, + gint bpp, + gint bitspp); +static gboolean raw_load_rgb565 (RawGimpData *data, + RawType type); +static gboolean raw_load_planar (RawGimpData *data); +static gboolean raw_load_palette (RawGimpData *data, + const gchar *palette_filename); + +/* support functions */ +static goffset get_file_info (const gchar *filename); +static void raw_read_row (FILE *fp, + guchar *buf, + gint32 offset, + gint32 size); +static int mmap_read (gint fd, + gpointer buf, + gint32 len, + gint32 pos, + gint rowstride); +static void rgb_565_to_888 (guint16 *in, + guchar *out, + gint32 num_pixels, + RawType type); + +static gint32 load_image (const gchar *filename, + GError **error); +static gboolean save_image (const gchar *filename, + gint32 image_id, + gint32 drawable_id, + GError **error); + +/* gui functions */ +static void preview_update_size (GimpPreviewArea *preview); +static void preview_update (GimpPreviewArea *preview); +static void palette_update (GimpPreviewArea *preview); +static gboolean load_dialog (const gchar *filename, + gboolean is_hgt); +static gboolean save_dialog (gint32 image_id); +static void save_dialog_response (GtkWidget *widget, + gint response_id, + gpointer data); +static void palette_callback (GtkFileChooser *button, + GimpPreviewArea *preview); + +static void load_defaults (void); +static void save_defaults (void); +static void load_gui_defaults (RawSaveGui *rg); + +static RawConfig *runtime = NULL; +static gchar *palfile = NULL; +static gint preview_fd = -1; +static guchar preview_cmap[1024]; +static gboolean preview_cmap_update = TRUE; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static const RawSaveVals defaults = +{ + RAW_RGB, + RAW_PALETTE_RGB +}; + +static RawSaveVals rawvals; + +MAIN() + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load_hgt_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + { GIMP_PDB_INT32, "samplespacing", "The sample spacing of the data. " + "Only supported values are 0, 1 and 3 " + "(respectively auto-detect, SRTM-1 " + "and SRTM-3 data)" }, + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + +#define COMMON_SAVE_ARGS \ + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, \ + { GIMP_PDB_IMAGE, "image", "Input image" }, \ + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, \ + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, \ + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + +#define CONFIG_ARGS \ + { GIMP_PDB_INT32, "image-type", "The image type { RAW_RGB (0), RAW_PLANAR (3) }" }, \ + { GIMP_PDB_INT32, "palette-type", "The palette type { RAW_PALETTE_RGB (0), RAW_PALETTE_BGR (1) }" } + + static const GimpParamDef save_args[] = + { + COMMON_SAVE_ARGS + }; + + static const GimpParamDef save_args2[] = + { + COMMON_SAVE_ARGS, + CONFIG_ARGS + }; + + static const GimpParamDef save_get_defaults_return_vals[] = + { + CONFIG_ARGS + }; + + static const GimpParamDef save_args_set_defaults[] = + { + CONFIG_ARGS + }; + + gimp_install_procedure (LOAD_PROC, + "Load raw images, specifying image information", + "Load raw images, specifying image information", + "timecop, pg@futureware.at", + "timecop, pg@futureware.at", + "Aug 2004", + N_("Raw image data"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + gimp_register_load_handler (LOAD_PROC, "data", ""); + + gimp_install_procedure (LOAD_HGT_PROC, + "Load HGT data as images", + "Load Digital Elevation Model data in HGT format " + "from the Shuttle Radar Topography Mission as " + "images. Though the output image will be RGB, all " + "colors are grayscale by default and the contrast " + "will be quite low on most earth relief. Therefore " + "You will likely want to remap elevation to colors " + "as a second step, for instance with the \"Gradient " + "Map\" plug-in.", + "", + "", + "2017-12-09", + N_("Digital Elevation Model data"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_hgt_args), + G_N_ELEMENTS (load_return_vals), + load_hgt_args, load_return_vals); + gimp_register_load_handler (LOAD_HGT_PROC, "hgt", ""); + + gimp_install_procedure (SAVE_PROC, + "Dump images to disk in raw format", + "This plug-in dumps images to disk in raw format, " + "using the default settings stored as a parasite.", + "timecop, pg@futureware.at", + "timecop, pg@futureware.at", + "Aug 2004", + N_("Raw image data"), + "INDEXED, GRAY, RGB, RGBA", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (SAVE_PROC2, + "Dump images to disk in raw format", + "Dump images to disk in raw format", + "Björn Kautler, Bjoern@Kautler.net", + "Björn Kautler, Bjoern@Kautler.net", + "April 2014", + N_("Raw image data"), + "INDEXED, GRAY, RGB, RGBA", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args2), 0, + save_args2, NULL); + + gimp_register_save_handler (SAVE_PROC2, "data,raw", ""); + + gimp_install_procedure (GET_DEFAULTS_PROC, + "Get the current set of defaults used by the " + "raw image data dump plug-in", + "This procedure returns the current set of " + "defaults stored as a parasite for the raw " + "image data dump plug-in. " + "These defaults are used to seed the UI, by the " + "file_raw_save_defaults procedure, and by " + "gimp_file_save when it detects to use RAW.", + "Björn Kautler, Bjoern@Kautler.net", + "Björn Kautler, Bjoern@Kautler.net", + "April 2014", + NULL, + NULL, + GIMP_PLUGIN, + 0, G_N_ELEMENTS (save_get_defaults_return_vals), + NULL, save_get_defaults_return_vals); + + gimp_install_procedure (SET_DEFAULTS_PROC, + "Set the current set of defaults used by the " + "raw image dump plug-in", + "This procedure sets the current set of defaults " + "stored as a parasite for the raw image data dump plug-in. " + "These defaults are used to seed the UI, by the " + "file_raw_save_defaults procedure, and by " + "gimp_file_save when it detects to use RAW.", + "Björn Kautler, Bjoern@Kautler.net", + "Björn Kautler, Bjoern@Kautler.net", + "April 2014", + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (save_args_set_defaults), 0, + save_args_set_defaults, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[3]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + gint32 image_id; + gint32 drawable_id; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0 || + strcmp (name, LOAD_HGT_PROC) == 0) + { + gboolean is_hgt = (strcmp (name, LOAD_HGT_PROC) == 0); + + run_mode = param[0].data.d_int32; + + /* allocate config structure and fill with defaults */ + runtime = g_new0 (RawConfig, 1); + + runtime->file_offset = 0; + runtime->palette_offset = 0; + runtime->palette_type = RAW_PALETTE_RGB; + if (is_hgt) + { + FILE *fp; + glong pos; + gint hgt_size; + + runtime->image_type = RAW_GRAY_16BPP_SBE; + + fp = g_fopen (param[1].data.d_string, "rb"); + if (! fp) + { + g_set_error (&error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for size verification: %s"), + gimp_filename_to_utf8 (param[1].data.d_string), + g_strerror (errno)); + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + fseek (fp, 0, SEEK_END); + pos = ftell (fp); + + /* HGT files have always the same size, either 1201*1201 + * or 3601*3601 of 16-bit values. + */ + if (pos == 1201*1201*2) + { + hgt_size = 1201; + } + else if (pos == 3601*3601*2) + { + hgt_size = 3601; + } + else + { + /* As a special exception, if the file looks like an HGT + * format from extension, yet it doesn't have the right + * size, we will degrade a bit the experience by + * adding sample spacing choice. + */ + hgt_size = 0; + } + runtime->image_width = hgt_size; + runtime->image_height = hgt_size; + + fclose (fp); + } + } + else + { + runtime->image_width = PREVIEW_SIZE; + runtime->image_height = PREVIEW_SIZE; + runtime->image_type = RAW_RGB; + } + + if (run_mode == GIMP_RUN_INTERACTIVE) + { + if (! is_hgt) + gimp_get_data (LOAD_PROC, runtime); + + preview_fd = g_open (param[1].data.d_string, O_RDONLY, 0); + if (preview_fd < 0) + { + g_set_error (&error, + G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (param[1].data.d_string), + g_strerror (errno)); + + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + if (! load_dialog (param[1].data.d_string, is_hgt)) + status = GIMP_PDB_CANCEL; + + close (preview_fd); + } + } + else if (is_hgt) /* HGT file in non-interactive mode. */ + { + gint32 sample_spacing = param[3].data.d_int32; + + if (sample_spacing != 0 && + sample_spacing != 1 && + sample_spacing != 3) + { + status = GIMP_PDB_CALLING_ERROR; + g_set_error (&error, + GIMP_PLUGIN_HGT_LOAD_ERROR, GIMP_PLUGIN_HGT_LOAD_ARGUMENT_ERROR, + _("%d is not a valid sample spacing. " + "Valid values are: 0 (auto-detect), 1 and 3."), + sample_spacing); + } + else + { + switch (sample_spacing) + { + case 0: + /* Auto-detection already occurred. Let's just check if + *it was successful. + */ + if (runtime->image_width != 1201 && + runtime->image_width != 3601) + { + status = GIMP_PDB_CALLING_ERROR; + g_set_error (&error, + G_FILE_ERROR, G_FILE_ERROR_INVAL, + _("Auto-detection of sample spacing failed. " + "\"%s\" does not appear to be a valid HGT file " + "or its variant is not supported yet. " + "Supported HGT files are: SRTM-1 and SRTM-3. " + "If you know the variant, run with argument 1 or 3."), + gimp_filename_to_utf8 (param[1].data.d_string)); + } + break; + case 1: + runtime->image_width = 3601; + runtime->image_height = 3601; + break; + default: /* 3 */ + runtime->image_width = 1201; + runtime->image_height = 1201; + break; + } + status = GIMP_PDB_SUCCESS; + } + } + else + { + /* we only run interactively due to the nature of this plugin. + * things like generate preview etc like to call us non- + * interactively. here we stop that. + */ + status = GIMP_PDB_CALLING_ERROR; + } + + /* we are okay, and the user clicked OK in the load dialog */ + if (status == GIMP_PDB_SUCCESS) + { + image_id = load_image (param[1].data.d_string, &error); + + if (image_id != -1) + { + if (! is_hgt) + gimp_set_data (LOAD_PROC, runtime, sizeof (RawConfig)); + + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_id; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + if (status != GIMP_PDB_SUCCESS && error) + { + g_warning ("Loading \"%s\" failed with error: %s", + param[1].data.d_string, + error->message); + } + + g_free (runtime); + } + else if (strcmp (name, SAVE_PROC) == 0 || + strcmp (name, SAVE_PROC2) == 0) + { + run_mode = param[0].data.d_int32; + image_id = param[1].data.d_int32; + drawable_id = param[2].data.d_int32; + + load_defaults (); + + /* export the image */ + export = gimp_export_image (&image_id, &drawable_id, "RAW", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + *nreturn_vals = 1; + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* + * Possibly retrieve data... + */ + gimp_get_data (SAVE_PROC, &rawvals); + + /* + * Then acquire information with a dialog... + */ + if (! save_dialog (image_id)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* + * Make sure all the arguments are there! + */ + if (nparams != 5) + { + if (nparams != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + rawvals.image_type = param[5].data.d_int32; + rawvals.palette_type = param[6].data.d_int32; + + if (((rawvals.image_type != RAW_RGB) && (rawvals.image_type != RAW_PLANAR)) || + ((rawvals.palette_type != RAW_PALETTE_RGB) && (rawvals.palette_type != RAW_PALETTE_BGR))) + { + status = GIMP_PDB_CALLING_ERROR; + } + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* + * Possibly retrieve data... + */ + gimp_get_data (SAVE_PROC, &rawvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (param[3].data.d_string, + image_id, drawable_id, &error)) + { + gimp_set_data (SAVE_PROC, &rawvals, sizeof (rawvals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_id); + } + else if (strcmp (name, GET_DEFAULTS_PROC) == 0) + { + load_defaults (); + + *nreturn_vals = 3; + +#define SET_VALUE(index, field) G_STMT_START { \ + values[(index)].type = GIMP_PDB_INT32; \ + values[(index)].data.d_int32 = rawvals.field; \ +} G_STMT_END + + SET_VALUE (1, image_type); + SET_VALUE (2, palette_type); + +#undef SET_VALUE + } + else if (strcmp (name, SET_DEFAULTS_PROC) == 0) + { + if (nparams == 2) + { + load_defaults (); + + rawvals.image_type = param[0].data.d_int32; + rawvals.palette_type = param[1].data.d_int32; + + save_defaults (); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +/* get file size from a filename */ +static goffset +get_file_info (const gchar *filename) +{ + GFile *file = g_file_new_for_path (filename); + GFileInfo *info; + goffset size = 0; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (info) + { + size = g_file_info_get_size (info); + + g_object_unref (info); + } + + g_object_unref (file); + + return size; +} + +/* new image handle functions */ +static void +raw_read_row (FILE *fp, + guchar *buf, + gint32 offset, + gint32 size) +{ + fseek (fp, offset, SEEK_SET); + + if (! fread (buf, size, 1, fp)) + { + g_printerr ("fread failed\n"); + memset (buf, 0xFF, size); + } +} + +/* similar to the above function but memset is done differently. has nothing + * to do with mmap, by the way + */ +static gint +mmap_read (gint fd, + void *buf, + gint32 len, + gint32 pos, + gint rowstride) +{ + lseek (fd, pos, SEEK_SET); + if (! read (fd, buf, len)) + memset (buf, 0xFF, rowstride); + return 0; +} + +/* this handles 1, 2, 3, 4 bpp "standard" images */ +static gboolean +raw_load_standard (RawGimpData *data, + gint bpp) +{ + guchar *row = NULL; + + row = g_try_malloc (runtime->image_width * runtime->image_height * bpp); + if (! row) + return FALSE; + + raw_read_row (data->fp, row, runtime->file_offset, + runtime->image_width * runtime->image_height * bpp); + + gegl_buffer_set (data->buffer, GEGL_RECTANGLE (0, 0, + runtime->image_width, + runtime->image_height), 0, + NULL, row, GEGL_AUTO_ROWSTRIDE); + + g_free (row); + + return TRUE; +} + +static gboolean +raw_load_gray16 (RawGimpData *data, + RawType type) +{ + guint16 *in_raw = NULL; + guint16 *out_raw = NULL; + gsize in_size; + gsize out_size; + gsize i; + + in_size = runtime->image_width * runtime->image_height; + out_size = runtime->image_width * runtime->image_height * 3 * sizeof (*out_raw); + + in_raw = g_try_malloc (in_size * sizeof (*in_raw)); + if (! in_raw) + return FALSE; + + out_raw = g_try_malloc0 (out_size); + if (! out_raw) + { + g_free (in_raw); + return FALSE; + } + + raw_read_row (data->fp, (guchar*) in_raw, runtime->file_offset, + in_size * sizeof (*in_raw)); + + for (i = 0; i < in_size; i++) + { + gint pixel_val; + + if (type == RAW_GRAY_16BPP_BE) + pixel_val = GUINT16_FROM_BE (in_raw[i]); + else if (type == RAW_GRAY_16BPP_LE) + pixel_val = GUINT16_FROM_LE (in_raw[i]); + else if (type == RAW_GRAY_16BPP_SBE) + pixel_val = GINT16_FROM_BE (in_raw[i]) - G_MININT16; + else /* if (type == RAW_GRAY_16BPP_SLE)*/ + pixel_val = GINT16_FROM_LE (in_raw[i]) - G_MININT16; + + out_raw[3 * i + 0] = pixel_val; + out_raw[3 * i + 1] = pixel_val; + out_raw[3 * i + 2] = pixel_val; + } + + gegl_buffer_set (data->buffer, GEGL_RECTANGLE (0, 0, + runtime->image_width, + runtime->image_height), 0, + NULL, out_raw, GEGL_AUTO_ROWSTRIDE); + + g_free (in_raw); + g_free (out_raw); + + return TRUE; +} + +/* this handles black and white, gray with 1, 2, 4, and 8 _bits_ per + * pixel images - hopefully lots of binaries too + */ +static gboolean +raw_load_gray (RawGimpData *data, + gint bpp, + gint bitspp) +{ + guchar *in_raw = NULL; + guchar *out_raw = NULL; + gint in_size; + gint out_size; + guchar pixel_mask_hi; + guchar pixel_mask_lo; + guint x; + gint i; + + in_size = runtime->image_width * runtime->image_height / (8 / bitspp); + out_size = runtime->image_width * runtime->image_height * 3; + + in_raw = g_try_malloc (in_size); + if (! in_raw) + return FALSE; + + out_raw = g_try_malloc (out_size); + if (! out_raw) + return FALSE; + memset (out_raw, 0, out_size); + + /* calculate a pixel_mask_hi + 0x80 for 1 bitspp + 0xc0 for 2 bitspp + 0xf0 for 4 bitspp + 0xff for 8 bitspp + and a pixel_mask_lo + 0x01 for 1 bitspp + 0x03 for 2 bitspp + 0x0f for 4 bitspp + 0xff for 8 bitspp + */ + pixel_mask_hi = 0x80; + pixel_mask_lo = 0x01; + for (i = 1; i < bitspp; i++) + { + pixel_mask_hi |= pixel_mask_hi >> 1; + pixel_mask_lo |= pixel_mask_lo << 1; + } + + raw_read_row (data->fp, in_raw, runtime->file_offset, + in_size); + + x = 0; /* walks though all output pixels */ + for (i = 0; i < in_size; i++) + { + guchar bit; + + for (bit = 0; bit < 8 / bitspp; bit++) + { + guchar pixel_val; + + pixel_val = in_raw[i] & (pixel_mask_hi >> (bit * bitspp)); + pixel_val >>= 8 - bitspp - bit * bitspp; + pixel_val *= 0xff / pixel_mask_lo; + + out_raw[3 * x + 0] = pixel_val; + out_raw[3 * x + 1] = pixel_val; + out_raw[3 * x + 2] = pixel_val; + + x++; + } + } + + gegl_buffer_set (data->buffer, GEGL_RECTANGLE (0, 0, + runtime->image_width, + runtime->image_height), 0, + NULL, out_raw, GEGL_AUTO_ROWSTRIDE); + + g_free (in_raw); + g_free (out_raw); + + return TRUE; +} + +/* this handles RGB565 images */ +static gboolean +raw_load_rgb565 (RawGimpData *data, + RawType type) +{ + gint32 num_pixels = runtime->image_width * runtime->image_height; + guint16 *in = g_malloc (num_pixels * 2); + guchar *row = g_malloc (num_pixels * 3); + + raw_read_row (data->fp, (guchar *)in, runtime->file_offset, num_pixels * 2); + rgb_565_to_888 (in, row, num_pixels, type); + + gegl_buffer_set (data->buffer, GEGL_RECTANGLE (0, 0, + runtime->image_width, + runtime->image_height), 0, + NULL, row, GEGL_AUTO_ROWSTRIDE); + + g_free (in); + g_free (row); + + return TRUE; +} + +/* this converts a 2bpp buffer to a 3bpp buffer in is a buffer of + * 16bit pixels, out is a buffer of 24bit pixels + */ +static void +rgb_565_to_888 (guint16 *in, + guchar *out, + gint32 num_pixels, + RawType type) +{ + guint32 i, j; + guint16 input; + gboolean swap_endian; + + if (G_BYTE_ORDER == G_LITTLE_ENDIAN || G_BYTE_ORDER == G_PDP_ENDIAN) + { + swap_endian = (type == RAW_RGB565_BE || type == RAW_BGR565_BE); + } + else if (G_BYTE_ORDER == G_BIG_ENDIAN) + { + swap_endian = (type == RAW_RGB565_LE || type == RAW_BGR565_LE); + } + + switch (type) + { + case RAW_RGB565_LE: + case RAW_RGB565_BE: + for (i = 0, j = 0; i < num_pixels; i++) + { + input = in[i]; + + if (swap_endian) + input = GUINT16_SWAP_LE_BE (input); + + out[j++] = ((((input >> 11) & 0x1f) * 0x21) >> 2); + out[j++] = ((((input >> 5) & 0x3f) * 0x41) >> 4); + out[j++] = ((((input >> 0) & 0x1f) * 0x21) >> 2); + } + break; + + case RAW_BGR565_BE: + case RAW_BGR565_LE: + for (i = 0, j = 0; i < num_pixels; i++) + { + input = in[i]; + + if (swap_endian) + input = GUINT16_SWAP_LE_BE (input); + + out[j++] = ((((input >> 0) & 0x1f) * 0x21) >> 2); + out[j++] = ((((input >> 5) & 0x3f) * 0x41) >> 4); + out[j++] = ((((input >> 11) & 0x1f) * 0x21) >> 2); + } + break; + + default: + /*This conversion function does not handle the passed in image-type*/ + g_assert_not_reached (); + } +} + +/* this handles 3 bpp "planar" images */ +static gboolean +raw_load_planar (RawGimpData *data) +{ + gint32 r_offset, g_offset, b_offset, i, j, k; + guchar *r_row, *b_row, *g_row, *row; + gint bpp = 3; /* adding support for alpha channel should be easy */ + + /* red, green, blue rows temporary data */ + r_row = g_malloc (runtime->image_width); + g_row = g_malloc (runtime->image_width); + b_row = g_malloc (runtime->image_width); + + /* row for the pixel region, after combining RGB together */ + row = g_malloc (runtime->image_width * bpp); + + r_offset = runtime->file_offset; + g_offset = r_offset + runtime->image_width * runtime->image_height; + b_offset = g_offset + runtime->image_width * runtime->image_height; + + for (i = 0; i < runtime->image_height; i++) + { + /* Read R, G, B rows */ + raw_read_row (data->fp, r_row, r_offset + (runtime->image_width * i), + runtime->image_width); + raw_read_row (data->fp, g_row, g_offset + (runtime->image_width * i), + runtime->image_width); + raw_read_row (data->fp, b_row, b_offset + (runtime->image_width * i), + runtime->image_width); + + /* Combine separate R, G and B rows into RGB triples */ + for (j = 0, k = 0; j < runtime->image_width; j++) + { + row[k++] = r_row[j]; + row[k++] = g_row[j]; + row[k++] = b_row[j]; + } + + gegl_buffer_set (data->buffer, + GEGL_RECTANGLE (0, i, runtime->image_width, 1), 0, + NULL, row, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((gfloat) i / (gfloat) runtime->image_height); + } + + gimp_progress_update (1.0); + + g_free (row); + g_free (r_row); + g_free (g_row); + g_free (b_row); + + return TRUE; +} + +static gboolean +raw_load_palette (RawGimpData *data, + const gchar *palette_file) +{ + guchar temp[1024]; + gint fd, i, j; + + if (palette_file) + { + fd = g_open (palette_file, O_RDONLY, 0); + + if (! fd) + return FALSE; + + lseek (fd, runtime->palette_offset, SEEK_SET); + + switch (runtime->palette_type) + { + case RAW_PALETTE_RGB: + read (fd, data->cmap, 768); + break; + + case RAW_PALETTE_BGR: + read (fd, temp, 1024); + for (i = 0, j = 0; i < 256; i++) + { + data->cmap[j++] = temp[i * 4 + 2]; + data->cmap[j++] = temp[i * 4 + 1]; + data->cmap[j++] = temp[i * 4 + 0]; + } + break; + } + + close (fd); + } + else + { + /* make a fake grayscale color map */ + for (i = 0, j = 0; i < 256; i++) + { + data->cmap[j++] = i; + data->cmap[j++] = i; + data->cmap[j++] = i; + } + } + + gimp_image_set_colormap (data->image_id, data->cmap, 256); + + return TRUE; +} + +/* end new image handle functions */ + +static gboolean +save_image (const gchar *filename, + gint32 image_id, + gint32 drawable_id, + GError **error) +{ + GeglBuffer *buffer; + const Babl *format = NULL; + guchar *cmap = NULL; /* colormap for indexed images */ + guchar *buf; + guchar *components[4] = { 0, }; + gint n_components; + gint32 width, height, bpp; + FILE *fp; + gint i, j, c; + gint palsize = 0; + gboolean ret = FALSE; + + /* get info about the current image */ + buffer = gimp_drawable_get_buffer (drawable_id); + + switch (gimp_drawable_type (drawable_id)) + { + case GIMP_RGB_IMAGE: + format = babl_format ("R'G'B' u8"); + break; + + case GIMP_RGBA_IMAGE: + format = babl_format ("R'G'B'A u8"); + break; + + case GIMP_GRAY_IMAGE: + format = babl_format ("Y' u8"); + break; + + case GIMP_GRAYA_IMAGE: + format = babl_format ("Y'A u8"); + break; + + case GIMP_INDEXED_IMAGE: + case GIMP_INDEXEDA_IMAGE: + format = gimp_drawable_get_format (drawable_id); + break; + } + + n_components = babl_format_get_n_components (format); + bpp = babl_format_get_bytes_per_pixel (format); + + if (gimp_drawable_is_indexed (drawable_id)) + cmap = gimp_image_get_colormap (image_id, &palsize); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + buf = g_new (guchar, width * height * bpp); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + g_object_unref (buffer); + + fp = g_fopen (filename, "wb"); + + if (! fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + ret = TRUE; + + switch (rawvals.image_type) + { + case RAW_RGB: + if (! fwrite (buf, width * height * bpp, 1, fp)) + { + fclose (fp); + return FALSE; + } + + fclose (fp); + + if (cmap) + { + /* we have colormap, too.write it into filename+pal */ + gchar *newfile = g_strconcat (filename, ".pal", NULL); + gchar *temp; + + fp = g_fopen (newfile, "wb"); + + if (! fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (newfile), g_strerror (errno)); + return FALSE; + } + + switch (rawvals.palette_type) + { + case RAW_PALETTE_RGB: + if (!fwrite (cmap, palsize * 3, 1, fp)) + ret = FALSE; + fclose (fp); + break; + + case RAW_PALETTE_BGR: + temp = g_malloc0 (palsize * 4); + for (i = 0, j = 0; i < palsize * 3; i += 3) + { + temp[j++] = cmap[i + 2]; + temp[j++] = cmap[i + 1]; + temp[j++] = cmap[i + 0]; + temp[j++] = 0; + } + if (!fwrite (temp, palsize * 4, 1, fp)) + ret = FALSE; + fclose (fp); + g_free (temp); + break; + } + } + break; + + case RAW_PLANAR: + for (c = 0; c < n_components; c++) + components[c] = g_new (guchar, width * height); + + for (i = 0, j = 0; i < width * height * bpp; i += bpp, j++) + { + for (c = 0; c < n_components; c++) + components[c][j] = buf[i + c]; + } + + ret = TRUE; + for (c = 0; c < n_components; c++) + { + if (! fwrite (components[c], width * height, 1, fp)) + ret = FALSE; + + g_free (components[c]); + } + + fclose (fp); + break; + + default: + fclose (fp); + break; + } + + return ret; +} + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + RawGimpData *data; + gint32 layer_id = -1; + GimpImageType ltype = GIMP_RGB_IMAGE; + GimpImageBaseType itype = GIMP_RGB; + goffset size; + gint bpp = 0; + gint bitspp = 8; + + data = g_new0 (RawGimpData, 1); + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + data->fp = g_fopen (filename, "rb"); + if (! data->fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + size = get_file_info (filename); + + switch (runtime->image_type) + { + case RAW_RGB: /* standard RGB */ + case RAW_PLANAR: /* planar RGB */ + bpp = 3; + ltype = GIMP_RGB_IMAGE; + itype = GIMP_RGB; + break; + + case RAW_RGB565_BE: /* RGB565 big endian */ + case RAW_RGB565_LE: /* RGB565 little endian */ + case RAW_BGR565_BE: /* RGB565 big endian */ + case RAW_BGR565_LE: /* RGB565 little endian */ + bpp = 2; + ltype = GIMP_RGB_IMAGE; + itype = GIMP_RGB; + break; + + case RAW_RGBA: /* RGB + alpha */ + bpp = 4; + ltype = GIMP_RGBA_IMAGE; + itype = GIMP_RGB; + break; + + case RAW_GRAY_1BPP: + bpp = 1; + bitspp = 1; + ltype = GIMP_RGB_IMAGE; + itype = GIMP_RGB; + break; + case RAW_GRAY_2BPP: + bpp = 1; + bitspp = 2; + ltype = GIMP_RGB_IMAGE; + itype = GIMP_RGB; + break; + case RAW_GRAY_4BPP: + bpp = 1; + bitspp = 4; + ltype = GIMP_RGB_IMAGE; + itype = GIMP_RGB; + break; + case RAW_GRAY_8BPP: + bpp = 1; + ltype = GIMP_RGB_IMAGE; + itype = GIMP_RGB; + break; + + case RAW_INDEXED: /* Indexed */ + bpp = 1; + ltype = GIMP_INDEXED_IMAGE; + itype = GIMP_INDEXED; + break; + + case RAW_INDEXEDA: /* Indexed + alpha */ + bpp = 2; + ltype = GIMP_INDEXEDA_IMAGE; + itype = GIMP_INDEXED; + break; + + case RAW_GRAY_16BPP_BE: + case RAW_GRAY_16BPP_LE: + case RAW_GRAY_16BPP_SBE: + case RAW_GRAY_16BPP_SLE: + bpp = 2; + ltype = GIMP_RGB_IMAGE; + itype = GIMP_RGB; + break; + } + + /* make sure we don't load image bigger than file size */ + if (runtime->image_height > (size / runtime->image_width / bpp * 8 / bitspp)) + runtime->image_height = size / runtime->image_width / bpp * 8 / bitspp; + + if (runtime->image_type >= RAW_GRAY_16BPP_BE) + data->image_id = gimp_image_new_with_precision (runtime->image_width, + runtime->image_height, + itype, + GIMP_PRECISION_U16_GAMMA); + else + data->image_id = gimp_image_new (runtime->image_width, + runtime->image_height, + itype); + gimp_image_set_filename (data->image_id, filename); + layer_id = gimp_layer_new (data->image_id, _("Background"), + runtime->image_width, runtime->image_height, + ltype, + 100, + gimp_image_get_default_new_layer_mode (data->image_id)); + gimp_image_insert_layer (data->image_id, layer_id, -1, 0); + + data->buffer = gimp_drawable_get_buffer (layer_id); + + switch (runtime->image_type) + { + case RAW_RGB: + case RAW_RGBA: + raw_load_standard (data, bpp); + break; + + case RAW_RGB565_BE: + case RAW_RGB565_LE: + case RAW_BGR565_BE: + case RAW_BGR565_LE: + raw_load_rgb565 (data, runtime->image_type); + break; + + case RAW_PLANAR: + raw_load_planar (data); + break; + + case RAW_GRAY_1BPP: + raw_load_gray (data, bpp, bitspp); + break; + case RAW_GRAY_2BPP: + raw_load_gray (data, bpp, bitspp); + break; + case RAW_GRAY_4BPP: + raw_load_gray (data, bpp, bitspp); + break; + case RAW_GRAY_8BPP: + raw_load_gray (data, bpp, bitspp); + break; + + case RAW_INDEXED: + case RAW_INDEXEDA: + raw_load_palette (data, palfile); + raw_load_standard (data, bpp); + break; + + case RAW_GRAY_16BPP_BE: + case RAW_GRAY_16BPP_LE: + case RAW_GRAY_16BPP_SBE: + case RAW_GRAY_16BPP_SLE: + raw_load_gray16 (data, runtime->image_type); + break; + } + + fclose (data->fp); + + g_object_unref (data->buffer); + + return data->image_id; +} + + +/* misc GUI stuff */ + +static void +preview_update_size (GimpPreviewArea *preview) +{ + gtk_widget_set_size_request (GTK_WIDGET (preview), + runtime->image_width, runtime->image_height); +} + +static void +preview_update (GimpPreviewArea *preview) +{ + gint width; + gint height; + gint32 pos; + gint x, y; + gint bitspp = 0; + + width = MIN (runtime->image_width, preview->width); + height = MIN (runtime->image_height, preview->height); + + gimp_preview_area_fill (preview, + 0, 0, preview->width, preview->height, + 255, 255, 255); + + switch (runtime->image_type) + { + case RAW_RGB: + /* standard RGB image */ + { + guchar *row = g_malloc0 (width * 3); + + for (y = 0; y < height; y++) + { + pos = runtime->file_offset + runtime->image_width * y * 3; + mmap_read (preview_fd, row, width * 3, pos, width * 3); + + gimp_preview_area_draw (preview, 0, y, width, 1, + GIMP_RGB_IMAGE, row, width * 3); + } + + g_free (row); + } + break; + + case RAW_RGBA: + /* RGB + alpha image */ + { + guchar *row = g_malloc0 (width * 4); + + for (y = 0; y < height; y++) + { + pos = runtime->file_offset + runtime->image_width * y * 4; + mmap_read (preview_fd, row, width * 4, pos, width * 4); + + gimp_preview_area_draw (preview, 0, y, width, 1, + GIMP_RGBA_IMAGE, row, width * 4); + } + + g_free (row); + } + break; + + case RAW_RGB565_BE: + case RAW_RGB565_LE: + case RAW_BGR565_BE: + case RAW_BGR565_LE: + /* RGB565 image, big/little endian */ + { + guint16 *in = g_malloc0 (width * 2); + guchar *row = g_malloc0 (width * 3); + + for (y = 0; y < height; y++) + { + pos = runtime->file_offset + runtime->image_width * y * 2; + mmap_read (preview_fd, in, width * 2, pos, width * 2); + rgb_565_to_888 (in, row, width, runtime->image_type); + + gimp_preview_area_draw (preview, 0, y, width, 1, + GIMP_RGB_IMAGE, row, width * 3); + } + + g_free (row); + g_free (in); + } + break; + + case RAW_PLANAR: + { + guchar *r_row = g_malloc0 (width); + guchar *g_row = g_malloc0 (width); + guchar *b_row = g_malloc0 (width); + guchar *row = g_malloc0 (width * 3); + + for (y = 0; y < height; y++) + { + gint j, k; + + pos = (runtime->file_offset + + (y * runtime->image_width)); + mmap_read (preview_fd, r_row, width, pos, width); + + pos = (runtime->file_offset + + (runtime->image_width * (runtime->image_height + y))); + mmap_read (preview_fd, g_row, width, pos, width); + + pos = (runtime->file_offset + + (runtime->image_width * (runtime->image_height * 2 + y))); + mmap_read (preview_fd, b_row, width, pos, width); + + for (j = 0, k = 0; j < width; j++) + { + row[k++] = r_row[j]; + row[k++] = g_row[j]; + row[k++] = b_row[j]; + } + + gimp_preview_area_draw (preview, 0, y, width, 1, + GIMP_RGB_IMAGE, row, width * 3); + } + + g_free (b_row); + g_free (g_row); + g_free (r_row); + g_free (row); + } + break; + + case RAW_GRAY_1BPP: + if (! bitspp) bitspp = 1; + case RAW_GRAY_2BPP: + if (! bitspp) bitspp = 2; + case RAW_GRAY_4BPP: + if (! bitspp) bitspp = 4; + case RAW_GRAY_8BPP: + if (! bitspp) bitspp = 8; + + { + guint in_size = height * width / (8 / bitspp); + guint out_size = height * width * 3; + guchar *in_raw = g_malloc0 (in_size); + guchar *out_raw = g_malloc0 (out_size); + guchar pixel_mask_hi; + guchar pixel_mask_lo; + gint i; + + /* calculate a pixel_mask_hi + 0x80 for 1 bitspp + 0xc0 for 2 bitspp + 0xf0 for 4 bitspp + 0xff for 8 bitspp + and a pixel_mask_lo + 0x01 for 1 bitspp + 0x03 for 2 bitspp + 0x0f for 4 bitspp + 0xff for 8 bitspp + */ + pixel_mask_hi = 0x80; + pixel_mask_lo = 0x01; + + for (i = 1; i < bitspp; i++) + { + pixel_mask_hi |= pixel_mask_hi >> 1; + pixel_mask_lo |= pixel_mask_lo << 1; + } + + mmap_read (preview_fd, in_raw, in_size, + runtime->file_offset, + in_size); + + x = 0; /* walks though all output pixels */ + for (i = 0; i < in_size; i++) + { + guchar bit; + + for (bit = 0; bit < 8 / bitspp; bit++) + { + guchar pixel_val; + + pixel_val = in_raw[i] & (pixel_mask_hi >> (bit * bitspp)); + pixel_val >>= 8 - bitspp - bit * bitspp; + pixel_val *= 0xff / pixel_mask_lo; + + out_raw[3 * x + 0] = pixel_val; + out_raw[3 * x + 1] = pixel_val; + out_raw[3 * x + 2] = pixel_val; + + x++; + } + } + + gimp_preview_area_draw (preview, 0, 0, width, height, + GIMP_RGB_IMAGE, out_raw, width * 3); + g_free (in_raw); + g_free (out_raw); + } + break; + + case RAW_INDEXED: + case RAW_INDEXEDA: + /* indexed image */ + { + gboolean alpha = (runtime->image_type == RAW_INDEXEDA); + guchar *index = g_malloc0 (width * (alpha ? 2 : 1)); + guchar *row = g_malloc0 (width * (alpha ? 4 : 3)); + + if (preview_cmap_update) + { + if (palfile) + { + gint fd; + + fd = g_open (palfile, O_RDONLY, 0); + lseek (fd, runtime->palette_offset, SEEK_SET); + read (fd, preview_cmap, + (runtime->palette_type == RAW_PALETTE_RGB) ? 768 : 1024); + close (fd); + } + else + { + /* make fake palette, maybe overwrite it later */ + for (y = 0, x = 0; y < 256; y++) + { + preview_cmap[x++] = y; + preview_cmap[x++] = y; + + if (runtime->palette_type == RAW_PALETTE_RGB) + { + preview_cmap[x++] = y; + } + else + { + preview_cmap[x++] = y; + preview_cmap[x++] = 0; + } + } + } + + preview_cmap_update = FALSE; + } + + for (y = 0; y < height; y++) + { + guchar *p = row; + + if (alpha) + { + pos = runtime->file_offset + runtime->image_width * 2 * y; + mmap_read (preview_fd, index, width * 2, pos, width); + + for (x = 0; x < width; x++) + { + switch (runtime->palette_type) + { + case RAW_PALETTE_RGB: + *p++ = preview_cmap[index[2 * x] * 3 + 0]; + *p++ = preview_cmap[index[2 * x] * 3 + 1]; + *p++ = preview_cmap[index[2 * x] * 3 + 2]; + *p++ = index[2 * x + 1]; + break; + case RAW_PALETTE_BGR: + *p++ = preview_cmap[index[2 * x] * 4 + 2]; + *p++ = preview_cmap[index[2 * x] * 4 + 1]; + *p++ = preview_cmap[index[2 * x] * 4 + 0]; + *p++ = index[2 * x + 1]; + break; + } + } + + gimp_preview_area_draw (preview, 0, y, width, 1, + GIMP_RGBA_IMAGE, row, width * 4); + } + else + { + pos = runtime->file_offset + runtime->image_width * y; + mmap_read (preview_fd, index, width, pos, width); + + for (x = 0; x < width; x++) + { + switch (runtime->palette_type) + { + case RAW_PALETTE_RGB: + *p++ = preview_cmap[index[x] * 3 + 0]; + *p++ = preview_cmap[index[x] * 3 + 1]; + *p++ = preview_cmap[index[x] * 3 + 2]; + break; + case RAW_PALETTE_BGR: + *p++ = preview_cmap[index[x] * 4 + 2]; + *p++ = preview_cmap[index[x] * 4 + 1]; + *p++ = preview_cmap[index[x] * 4 + 0]; + break; + } + } + + gimp_preview_area_draw (preview, 0, y, width, 1, + GIMP_RGB_IMAGE, row, width * 3); + } + } + + g_free (row); + g_free (index); + } + break; + + case RAW_GRAY_16BPP_BE: + case RAW_GRAY_16BPP_LE: + case RAW_GRAY_16BPP_SBE: + case RAW_GRAY_16BPP_SLE: + { + guint16 *r_row = g_new (guint16, width); + guchar *row = g_malloc (width); + + for (y = 0; y < height; y++) + { + gint j; + + pos = (runtime->file_offset + (y * runtime->image_width * 2)); + mmap_read (preview_fd, (guchar*) r_row, 2 * width, pos, width); + + for (j = 0; j < width; j++) + { + gint pixel_val; + + if (runtime->image_type == RAW_GRAY_16BPP_BE) + pixel_val = GUINT16_FROM_BE (r_row[j]); + else if (runtime->image_type == RAW_GRAY_16BPP_LE) + pixel_val = GUINT16_FROM_LE (r_row[j]); + else if (runtime->image_type == RAW_GRAY_16BPP_SBE) + pixel_val = GINT16_FROM_BE (r_row[j]) - G_MININT16; + else /* if (runtime->image_type == RAW_GRAY_16BPP_SLE)*/ + pixel_val = GINT16_FROM_LE (r_row[j]) - G_MININT16; + + row[j] = pixel_val / 257; + } + + gimp_preview_area_draw (preview, 0, y, width, 1, + GIMP_GRAY_IMAGE, row, width * 3); + } + + g_free (r_row); + g_free (row); + } + break; + } +} + +static void +palette_update (GimpPreviewArea *preview) +{ + preview_cmap_update = TRUE; + + preview_update (preview); +} + +static gboolean +load_dialog (const gchar *filename, + gboolean is_hgt) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *sw; + GtkWidget *viewport; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *combo; + GtkWidget *button; + GtkObject *adj; + goffset file_size; + gboolean run; + + file_size = get_file_info (filename); + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Load Image from Raw Data"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, + is_hgt ? LOAD_HGT_PROC : LOAD_PROC, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (main_vbox), sw, TRUE, TRUE, 0); + gtk_widget_set_size_request (sw, PREVIEW_SIZE, PREVIEW_SIZE); + gtk_widget_show (sw); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (sw), viewport); + gtk_widget_show (viewport); + + preview = gimp_preview_area_new (); + gtk_widget_set_size_request (preview, + runtime->image_width, runtime->image_height); + gtk_container_add (GTK_CONTAINER (viewport), preview); + gtk_widget_show (preview); + + g_signal_connect_after (preview, "size-allocate", + G_CALLBACK (preview_update), + NULL); + + if (is_hgt) + { + if (runtime->image_width == 1201) + /* Translators: Digital Elevation Model (DEM) is a technical term + * used for 3D surface modeling or relief maps; so it must be + * translated by the proper technical term in your language. + */ + frame = gimp_frame_new (_("Digital Elevation Model data (1 arc-second)")); + else if (runtime->image_width == 3601) + frame = gimp_frame_new (_("Digital Elevation Model data (3 arc-seconds)")); + else + frame = gimp_frame_new (_("Digital Elevation Model data")); + } + else + { + frame = gimp_frame_new (_("Image")); + } + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (4, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + combo = NULL; + if (is_hgt && + runtime->image_width != 1201 && + runtime->image_width != 3601) + { + /* When auto-detection of the HGT variant failed, let's just + * default to SRTM-3 and show a dropdown list. + */ + runtime->image_width = 1201; + runtime->image_height = 1201; + + /* 2 types of HGT files are possible: SRTM-1 and SRTM-3. + * From the documentation: https://dds.cr.usgs.gov/srtm/version1/Documentation/SRTM_Topo.txt + * "SRTM-1 data are sampled at one arc-second of latitude and longitude and + * each file contains 3601 lines and 3601 samples. + * [...] + * SRTM-3 data are sampled at three arc-seconds and contain 1201 lines and + * 1201 samples with similar overlapping rows and columns." + */ + combo = gimp_int_combo_box_new (_("SRTM-1 (1 arc-second)"), 3601, + _("SRTM-3 (3 arc-seconds)"), 1201, + NULL); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Sample Spacing:"), 0.0, 0.5, + combo, 2, FALSE); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &runtime->image_width); + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &runtime->image_height); + /* By default, SRTM-3 is active. */ + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), 1201); + } + else if (! is_hgt) + { + /* Generic case for any data. Let's leave choice to select the + * right type of raw data. + */ + combo = gimp_int_combo_box_new (_("RGB"), RAW_RGB, + _("RGB Alpha"), RAW_RGBA, + _("RGB565 Big Endian"), RAW_RGB565_BE, + _("RGB565 Little Endian"), RAW_RGB565_LE, + _("BGR565 Big Endian"), RAW_BGR565_BE, + _("BGR565 Little Endian"), RAW_BGR565_LE, + _("Planar RGB"), RAW_PLANAR, + _("B&W 1 bit"), RAW_GRAY_1BPP, + _("Gray 2 bit"), RAW_GRAY_2BPP, + _("Gray 4 bit"), RAW_GRAY_4BPP, + _("Gray 8 bit"), RAW_GRAY_8BPP, + _("Indexed"), RAW_INDEXED, + _("Indexed Alpha"), RAW_INDEXEDA, + _("Gray unsigned 16 bit Big Endian"), RAW_GRAY_16BPP_BE, + _("Gray unsigned 16 bit Little Endian"), RAW_GRAY_16BPP_LE, + _("Gray 16 bit Big Endian"), RAW_GRAY_16BPP_SBE, + _("Gray 16 bit Little Endian"), RAW_GRAY_16BPP_SLE, + NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + runtime->image_type); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Image _Type:"), 0.0, 0.5, + combo, 2, FALSE); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &runtime->image_type); + } + if (combo) + g_signal_connect_swapped (combo, "changed", + G_CALLBACK (preview_update), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("O_ffset:"), -1, 9, + runtime->file_offset, 0, file_size, 1, 1000, 0, + TRUE, 0.0, 0.0, + NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &runtime->file_offset); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (preview_update), + preview); + + if (! is_hgt) + { + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("_Width:"), -1, 9, + runtime->image_width, 1, file_size, 1, 10, 0, + TRUE, 0.0, 0.0, + NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &runtime->image_width); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (preview_update_size), + preview); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (preview_update), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 3, + _("_Height:"), -1, 9, + runtime->image_height, 1, file_size, 1, 10, 0, + TRUE, 0.0, 0.0, + NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &runtime->image_height); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (preview_update_size), + preview); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (preview_update), + preview); + } + + + frame = gimp_frame_new (_("Palette")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + combo = gimp_int_combo_box_new (_("R, G, B (normal)"), RAW_PALETTE_RGB, + _("B, G, R, X (BMP style)"), RAW_PALETTE_BGR, + NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + runtime->palette_type); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Palette Type:"), 0.0, 0.5, + combo, 2, FALSE); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &runtime->palette_type); + g_signal_connect_swapped (combo, "changed", + G_CALLBACK (palette_update), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("Off_set:"), -1, 0, + runtime->palette_offset, 0, 1 << 24, 1, 768, 0, + TRUE, 0.0, 0.0, + NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &runtime->palette_offset); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (palette_update), + preview); + + button = gtk_file_chooser_button_new (_("Select Palette File"), + GTK_FILE_CHOOSER_ACTION_OPEN); + if (palfile) + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (button), palfile); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("Pal_ette File:"), 0.0, 0.5, + button, 2, FALSE); + + g_signal_connect (button, "selection-changed", + G_CALLBACK (palette_callback), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static GtkWidget * +radio_button_init (GtkBuilder *builder, + const gchar *name, + gint item_data, + gint initial_value, + gpointer value_pointer) +{ + GtkWidget *radio = NULL; + + radio = GTK_WIDGET (gtk_builder_get_object (builder, name)); + if (item_data) + g_object_set_data (G_OBJECT (radio), "gimp-item-data", GINT_TO_POINTER (item_data)); + if (initial_value == item_data) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); + g_signal_connect (radio, "toggled", + G_CALLBACK (gimp_radio_button_update), + value_pointer); + + return radio; +} + +static gboolean +save_dialog (gint32 image_id) +{ + RawSaveGui rg; + GtkWidget *dialog; + GtkBuilder *builder; + gchar *ui_file; + GError *error = NULL; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + /* Dialog init */ + dialog = gimp_export_dialog_new (_("Raw Image"), PLUG_IN_BINARY, SAVE_PROC); + g_signal_connect (dialog, "response", + G_CALLBACK (save_dialog_response), + &rg); + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_main_quit), + NULL); + + /* GtkBuilder init */ + builder = gtk_builder_new (); + ui_file = g_build_filename (gimp_data_directory (), + "ui/plug-ins/plug-in-file-raw.ui", + NULL); + if (! gtk_builder_add_from_file (builder, ui_file, &error)) + { + gchar *display_name = g_filename_display_name (ui_file); + g_printerr (_("Error loading UI file '%s': %s"), + display_name, error ? error->message : _("Unknown error")); + g_free (display_name); + } + + g_free (ui_file); + + /* VBox */ + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + GTK_WIDGET (gtk_builder_get_object (builder, "vbox")), + FALSE, FALSE, 0); + + /* Radios */ + rg.image_type_standard = radio_button_init (builder, "image-type-standard", + RAW_RGB, + rawvals.image_type, + &rawvals.image_type); + rg.image_type_planar = radio_button_init (builder, "image-type-planar", + RAW_PLANAR, + rawvals.image_type, + &rawvals.image_type); + rg.palette_type_normal = radio_button_init (builder, "palette-type-normal", + RAW_PALETTE_RGB, + rawvals.palette_type, + &rawvals.palette_type); + rg.palette_type_bmp = radio_button_init (builder, "palette-type-bmp", + RAW_PALETTE_BGR, + rawvals.palette_type, + &rawvals.palette_type); + + /* Load/save defaults buttons */ + g_signal_connect_swapped (gtk_builder_get_object (builder, "load-defaults"), + "clicked", + G_CALLBACK (load_gui_defaults), + &rg); + + g_signal_connect_swapped (gtk_builder_get_object (builder, "save-defaults"), + "clicked", + G_CALLBACK (save_defaults), + &rg); + + /* Show dialog and run */ + gtk_widget_show (dialog); + + rg.run = FALSE; + + gtk_main (); + + return rg.run; +} + +static void +save_dialog_response (GtkWidget *widget, + gint response_id, + gpointer data) +{ + RawSaveGui *rg = data; + + switch (response_id) + { + case GTK_RESPONSE_OK: + rg->run = TRUE; + + default: + gtk_widget_destroy (widget); + break; + } +} + + +static void +palette_callback (GtkFileChooser *button, + GimpPreviewArea *preview) +{ + if (palfile) + g_free (palfile); + + palfile = gtk_file_chooser_get_filename (button); + + palette_update (preview); +} + +static void +load_defaults (void) +{ + GimpParasite *parasite; + + /* initialize with hardcoded defaults */ + rawvals = defaults; + + parasite = gimp_get_parasite (RAW_DEFAULTS_PARASITE); + + if (parasite) + { + gchar *def_str; + RawSaveVals tmpvals = defaults; + gint num_fields; + + def_str = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + + gimp_parasite_free (parasite); + + num_fields = sscanf (def_str, "%d %d", + (int *) &tmpvals.image_type, + (int *) &tmpvals.palette_type); + + g_free (def_str); + + if (num_fields == 2) + rawvals = tmpvals; + } +} + +static void +save_defaults (void) +{ + GimpParasite *parasite; + gchar *def_str; + + def_str = g_strdup_printf ("%d %d", + rawvals.image_type, + rawvals.palette_type); + + parasite = gimp_parasite_new (RAW_DEFAULTS_PARASITE, + GIMP_PARASITE_PERSISTENT, + strlen (def_str), def_str); + + gimp_attach_parasite (parasite); + + gimp_parasite_free (parasite); + g_free (def_str); +} + +static void +load_gui_defaults (RawSaveGui *rg) +{ + load_defaults (); + +#define SET_ACTIVE(field, datafield) \ + if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (rg->field), "gimp-item-data")) == rawvals.datafield) \ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rg->field), TRUE) + + SET_ACTIVE (image_type_standard, image_type); + SET_ACTIVE (image_type_planar, image_type); + SET_ACTIVE (palette_type_normal, palette_type); + SET_ACTIVE (palette_type_bmp, palette_type); + +#undef SET_ACTIVE +} diff --git a/plug-ins/common/file-sunras.c b/plug-ins/common/file-sunras.c new file mode 100644 index 0000000..127d67d --- /dev/null +++ b/plug-ins/common/file-sunras.c @@ -0,0 +1,1784 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * SUN raster reading and writing code Copyright (C) 1996 Peter Kirchgessner + * (email: peter@kirchgessner.net, WWW: http://www.kirchgessner.net) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* This program was written using pages 625-629 of the book + * "Encyclopedia of Graphics File Formats", Murray/van Ryper, + * O'Reilly & Associates Inc. + * Bug reports or suggestions should be e-mailed to peter@kirchgessner.net + */ + +/* Event history: + * V 1.00, PK, 25-Jul-96: First try + * V 1.90, PK, 15-Mar-97: Upgrade to work with GIMP V0.99 + * V 1.91, PK, 05-Apr-97: Return all arguments, even in case of an error + * V 1.92, PK, 18-May-97: Ignore EOF-error on reading image data + * V 1.93, PK, 05-Oct-97: Parse rc file + * V 1.94, PK, 12-Oct-97: No progress bars for non-interactive mode + * V 1.95, nn, 20-Dec-97: Initialize some variable + * V 1.96, PK, 21-Nov-99: Internationalization + * V 1.97, PK, 20-Dec-00: Recognize extensions .rs and .ras too + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-sunras-load" +#define SAVE_PROC "file-sunras-save" +#define PLUG_IN_BINARY "file-sunras" +#define PLUG_IN_ROLE "gimp-file-sunras" + + +typedef int WRITE_FUN(void*,size_t,size_t,FILE*); + +typedef gulong L_CARD32; +typedef gushort L_CARD16; +typedef guchar L_CARD8; + +/* Fileheader of SunRaster files */ +typedef struct +{ + L_CARD32 l_ras_magic; /* Magic Number */ + L_CARD32 l_ras_width; /* Width */ + L_CARD32 l_ras_height; /* Height */ + L_CARD32 l_ras_depth; /* Number of bits per pixel (1,8,24,32) */ + L_CARD32 l_ras_length; /* Length of image data (but may also be 0) */ + L_CARD32 l_ras_type; /* Encoding */ + L_CARD32 l_ras_maptype; /* Type of colormap */ + L_CARD32 l_ras_maplength;/* Number of bytes for colormap */ +} L_SUNFILEHEADER; + +/* Sun-raster magic */ +#define RAS_MAGIC 0x59a66a95 + +#define RAS_TYPE_STD 1 /* Standard uncompressed format */ +#define RAS_TYPE_RLE 2 /* Runlength compression format */ + +typedef struct +{ + gint val; /* The value that is to be repeated */ + gint n; /* How many times it is repeated */ +} RLEBUF; + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); +static gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + +static void set_color_table (gint32 image_ID, + L_SUNFILEHEADER *sunhdr, + const guchar *suncolmap); +static gint32 create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageBaseType type, + gint32 *layer_ID, + GeglBuffer **buffer); + +static gint32 load_sun_d1 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap); +static gint32 load_sun_d8 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap); +static gint32 load_sun_d24 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap); +static gint32 load_sun_d32 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap); + +static L_CARD32 read_card32 (FILE *ifp, + int *err); + +static void write_card32 (FILE *ofp, + L_CARD32 c); + +static void byte2bit (guchar *byteline, + int width, + guchar *bitline, + gboolean invert); + +static void rle_startread (FILE *ifp); +static int rle_fread (char *ptr, + int sz, + int nelem, + FILE *ifp); +static int rle_fgetc (FILE *ifp); +#define rle_getc(fp) ((rlebuf.n > 0) ? (rlebuf.n)--,rlebuf.val : rle_fgetc (fp)) + +static void rle_startwrite (FILE *ofp); +static int rle_fwrite (char *ptr, + int sz, + int nelem, + FILE *ofp); +static int rle_fputc (int val, + FILE *ofp); +static int rle_putrun (int n, + int val, + FILE *ofp); +static void rle_endwrite (FILE *ofp); +#define rle_putc rle_fputc + +static void read_sun_header (FILE *ifp, + L_SUNFILEHEADER *sunhdr); +static void write_sun_header (FILE *ofp, + L_SUNFILEHEADER *sunhdr); +static void read_sun_cols (FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *colormap); +static void write_sun_cols (FILE *ofp, + L_SUNFILEHEADER *sunhdr, + guchar *colormap); + +static gint save_index (FILE *ofp, + gint32 image_ID, + gint32 drawable_ID, + gboolean grey, + gboolean rle); +static gint save_rgb (FILE *ofp, + gint32 image_ID, + gint32 drawable_ID, + gboolean rle); + +static gboolean save_dialog (void); + +/* Portability kludge */ +static int my_fwrite (void *ptr, + int size, + int nmemb, + FILE *stream); + + +static int read_msb_first = 1; +static RLEBUF rlebuf; + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +/* Export info */ +typedef struct +{ + gboolean rle; /* rle or standard */ +} SUNRASSaveVals; + + +static SUNRASSaveVals psvals = +{ + TRUE /* rle */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_INT32, "rle", "Specify non-zero for rle output, zero for standard output" } + }; + + gimp_install_procedure (LOAD_PROC, + "load file of the SunRaster file format", + "load file of the SunRaster file format", + "Peter Kirchgessner", + "Peter Kirchgessner", + "1996", + N_("SUN Rasterfile image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-sun-raster"); + gimp_register_magic_load_handler (LOAD_PROC, + "im1,im8,im24,im32,rs,ras", + "", + "0,long,0x59a66a95"); + + gimp_install_procedure (SAVE_PROC, + "export file in the SunRaster file format", + "SUNRAS exporting handles all image types except " + "those with alpha channels.", + "Peter Kirchgessner", + "Peter Kirchgessner", + "1996", + N_("SUN Rasterfile image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-sun-raster"); + gimp_register_save_handler (SAVE_PROC, + "im1,im8,im24,im32,rs,ras", ""); +} + + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "SUNRAS", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &psvals); + + /* First acquire information with a dialog */ + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + psvals.rle = (param[5].data.d_int32) ? TRUE : FALSE; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &psvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (param[3].data.d_string, image_ID, drawable_ID, + &error)) + { + /* Store psvals data */ + gimp_set_data (SAVE_PROC, &psvals, sizeof (SUNRASSaveVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + gint32 image_ID; + FILE *ifp; + L_SUNFILEHEADER sunhdr; + guchar *suncolmap = NULL; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + ifp = g_fopen (filename, "rb"); + if (!ifp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + read_msb_first = 1; /* SUN raster is always most significant byte first */ + + read_sun_header (ifp, &sunhdr); + if (sunhdr.l_ras_magic != RAS_MAGIC) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not open '%s' as SUN-raster-file"), + gimp_filename_to_utf8 (filename)); + fclose (ifp); + return -1; + } + + if (sunhdr.l_ras_type > 5) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", + _("The type of this SUN-rasterfile is not supported")); + fclose (ifp); + return -1; + } + + if (sunhdr.l_ras_maplength > (256 * 3)) + { + g_message ("Map lengths greater than 256 entries are unsupported by GIMP."); + gimp_quit (); + } + + /* Is there a RGB colormap ? */ + if ((sunhdr.l_ras_maptype == 1) && (sunhdr.l_ras_maplength > 0)) + { + suncolmap = g_new (guchar, sunhdr.l_ras_maplength); + + read_sun_cols (ifp, &sunhdr, suncolmap); +#ifdef DEBUG + { + int j, ncols; + printf ("File %s\n",filename); + ncols = sunhdr.l_ras_maplength/3; + for (j=0; j < ncols; j++) + printf ("Entry 0x%08x: 0x%04x, 0x%04x, 0x%04x\n", + j,suncolmap[j],suncolmap[j+ncols],suncolmap[j+2*ncols]); + } +#endif + if (sunhdr.l_ras_magic != RAS_MAGIC) + { + g_message (_("Could not read color entries from '%s'"), + gimp_filename_to_utf8 (filename)); + fclose (ifp); + g_free (suncolmap); + return -1; + } + } + else if (sunhdr.l_ras_maplength > 0) + { + g_message (_("Type of colormap not supported")); + fseek (ifp, (sizeof (L_SUNFILEHEADER)/sizeof (L_CARD32)) + *4 + sunhdr.l_ras_maplength, SEEK_SET); + } + + if (sunhdr.l_ras_width <= 0) + { + g_message (_("'%s':\nNo image width specified"), + gimp_filename_to_utf8 (filename)); + fclose (ifp); + return -1; + } + + if (sunhdr.l_ras_width > GIMP_MAX_IMAGE_SIZE) + { + g_message (_("'%s':\nImage width is larger than GIMP can handle"), + gimp_filename_to_utf8 (filename)); + fclose (ifp); + return -1; + } + + if (sunhdr.l_ras_height <= 0) + { + g_message (_("'%s':\nNo image height specified"), + gimp_filename_to_utf8 (filename)); + fclose (ifp); + return -1; + } + + if (sunhdr.l_ras_height > GIMP_MAX_IMAGE_SIZE) + { + g_message (_("'%s':\nImage height is larger than GIMP can handle"), + gimp_filename_to_utf8 (filename)); + fclose (ifp); + return -1; + } + + switch (sunhdr.l_ras_depth) + { + case 1: /* bitmap */ + image_ID = load_sun_d1 (filename, ifp, &sunhdr, suncolmap); + break; + + case 8: /* 256 colors */ + image_ID = load_sun_d8 (filename, ifp, &sunhdr, suncolmap); + break; + + case 24: /* True color */ + image_ID = load_sun_d24 (filename, ifp, &sunhdr, suncolmap); + break; + + case 32: /* True color with extra byte */ + image_ID = load_sun_d32 (filename, ifp, &sunhdr, suncolmap); + break; + + default: + image_ID = -1; + break; + } + gimp_progress_update (1.0); + + fclose (ifp); + + g_free (suncolmap); + + if (image_ID == -1) + { + g_message (_("This image depth is not supported")); + return -1; + } + + return image_ID; +} + + +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + FILE *ofp; + GimpImageType drawable_type; + gboolean retval; + + drawable_type = gimp_drawable_type (drawable_ID); + + /* Make sure we're not exporting an image with an alpha channel */ + if (gimp_drawable_has_alpha (drawable_ID)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("SUNRAS export cannot handle images with alpha channels")); + return FALSE; + } + + switch (drawable_type) + { + case GIMP_INDEXED_IMAGE: + case GIMP_GRAY_IMAGE: + case GIMP_RGB_IMAGE: + break; + default: + g_message (_("Can't operate on unknown image types")); + return FALSE; + break; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_filename_to_utf8 (filename)); + + /* Open the output file. */ + ofp = g_fopen (filename, "wb"); + if (! ofp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + if (drawable_type == GIMP_INDEXED_IMAGE) + retval = save_index (ofp,image_ID, drawable_ID, FALSE, psvals.rle); + else if (drawable_type == GIMP_GRAY_IMAGE) + retval = save_index (ofp,image_ID, drawable_ID, TRUE, psvals.rle); + else if (drawable_type == GIMP_RGB_IMAGE) + retval = save_rgb (ofp,image_ID, drawable_ID, psvals.rle); + else + retval = FALSE; + + fclose (ofp); + + return retval; +} + + +static L_CARD32 +read_card32 (FILE *ifp, + gint *err) +{ + L_CARD32 c; + + if (read_msb_first) + { + c = (((L_CARD32)(getc (ifp))) << 24); + c |= (((L_CARD32)(getc (ifp))) << 16); + c |= (((L_CARD32)(getc (ifp))) << 8); + c |= ((L_CARD32)(*err = getc (ifp))); + } + else + { + c = ((L_CARD32)(getc (ifp))); + c |= (((L_CARD32)(getc (ifp))) << 8); + c |= (((L_CARD32)(getc (ifp))) << 16); + c |= (((L_CARD32)(*err = getc (ifp))) << 24); + } + + *err = (*err < 0); + + return c; +} + + +static void +write_card32 (FILE *ofp, + L_CARD32 c) +{ + putc ((int)((c >> 24) & 0xff), ofp); + putc ((int)((c >> 16) & 0xff), ofp); + putc ((int)((c >> 8) & 0xff), ofp); + putc ((int)((c) & 0xff), ofp); +} + + +/* Convert n bytes of 0/1 to a line of bits */ +static void +byte2bit (guchar *byteline, + gint width, + guchar *bitline, + gboolean invert) +{ + guchar bitval; + guchar rest[8]; + + while (width >= 8) + { + bitval = 0; + if (*(byteline++)) bitval |= 0x80; + if (*(byteline++)) bitval |= 0x40; + if (*(byteline++)) bitval |= 0x20; + if (*(byteline++)) bitval |= 0x10; + if (*(byteline++)) bitval |= 0x08; + if (*(byteline++)) bitval |= 0x04; + if (*(byteline++)) bitval |= 0x02; + if (*(byteline++)) bitval |= 0x01; + *(bitline++) = invert ? ~bitval : bitval; + width -= 8; + } + if (width > 0) + { + memset (rest, 0, 8); + memcpy (rest, byteline, width); + bitval = 0; + byteline = rest; + if (*(byteline++)) bitval |= 0x80; + if (*(byteline++)) bitval |= 0x40; + if (*(byteline++)) bitval |= 0x20; + if (*(byteline++)) bitval |= 0x10; + if (*(byteline++)) bitval |= 0x08; + if (*(byteline++)) bitval |= 0x04; + if (*(byteline++)) bitval |= 0x02; + *bitline = invert ? ~bitval : bitval; + } +} + + +/* Start reading Runlength Encoded Data */ +static void +rle_startread (FILE *ifp) +{ + /* Clear RLE-buffer */ + rlebuf.val = rlebuf.n = 0; +} + + +/* Read uncompressed elements from RLE-stream */ +static gint +rle_fread (gchar *ptr, + gint sz, + gint nelem, + FILE *ifp) +{ + int elem_read, cnt, val, err = 0; + + for (elem_read = 0; elem_read < nelem; elem_read++) + { + for (cnt = 0; cnt < sz; cnt++) + { + val = rle_getc (ifp); + + if (val < 0) + { + err = 1; + break; + } + + *(ptr++) = (char)val; + } + + if (err) + break; + } + + return elem_read; +} + + +/* Get one byte of uncompressed data from RLE-stream */ +static gint +rle_fgetc (FILE *ifp) +{ + int flag, runcnt, runval; + + if (rlebuf.n > 0) /* Something in the buffer ? */ + { + (rlebuf.n)--; + return rlebuf.val; + } + + /* Nothing in the buffer. We have to read something */ + if ((flag = getc (ifp)) < 0) return -1; + if (flag != 0x0080) return flag; /* Single byte run ? */ + + if ((runcnt = getc (ifp)) < 0) return -1; + if (runcnt == 0) return 0x0080; /* Single 0x80 ? */ + + /* The run */ + if ((runval = getc (ifp)) < 0) return -1; + rlebuf.n = runcnt; + rlebuf.val = runval; + + return runval; +} + + +/* Start writing Runlength Encoded Data */ +static void +rle_startwrite (FILE *ofp) +{ + /* Clear RLE-buffer */ + rlebuf.val = rlebuf.n = 0; +} + + +/* Write uncompressed elements to RLE-stream */ +static gint +rle_fwrite (gchar *ptr, + gint sz, + gint nelem, + FILE *ofp) +{ + int elem_write, cnt, val, err = 0; + guchar *pixels = (unsigned char *)ptr; + + for (elem_write = 0; elem_write < nelem; elem_write++) + { + for (cnt = 0; cnt < sz; cnt++) + { + val = rle_fputc (*(pixels++), ofp); + if (val < 0) + { + err = 1; + break; + } + } + + if (err) + break; + } + + return elem_write; +} + + +/* Write uncompressed character to RLE-stream */ +static gint +rle_fputc (gint val, + FILE *ofp) +{ + int retval; + + if (rlebuf.n == 0) /* Nothing in the buffer ? Save the value */ + { + rlebuf.n = 1; + rlebuf.val = val; + + return val; + } + + /* Something in the buffer */ + + if (rlebuf.val == val) /* Same value in the buffer ? */ + { + (rlebuf.n)++; + if (rlebuf.n == 257) /* Can not be encoded in a single run ? */ + { + retval = rle_putrun (256, rlebuf.val, ofp); + if (retval < 0) + return retval; + + rlebuf.n -= 256; + } + + return val; + } + + /* Something different in the buffer ? Write out the run */ + + retval = rle_putrun (rlebuf.n, rlebuf.val, ofp); + if (retval < 0) + return retval; + + /* Save the new value */ + rlebuf.n = 1; + rlebuf.val = val; + + return val; +} + + +/* Write out a run with 0 < n < 257 */ +static gint +rle_putrun (gint n, + gint val, + FILE *ofp) +{ + int retval, flag = 0x80; + + /* Useful to write a 3 byte run ? */ + if ((n > 2) || ((n == 2) && (val == flag))) + { + putc (flag, ofp); + putc (n-1, ofp); + retval = putc (val, ofp); + } + else if (n == 2) /* Write two single runs (could not be value 0x80) */ + { + putc (val, ofp); + retval = putc (val, ofp); + } + else /* Write a single run */ + { + if (val == flag) + retval = putc (flag, ofp), putc (0x00, ofp); + else + retval = putc (val, ofp); + } + + return (retval < 0) ? retval : val; +} + + +/* End writing Runlength Encoded Data */ +static void +rle_endwrite (FILE *ofp) +{ + if (rlebuf.n > 0) + { + rle_putrun (rlebuf.n, rlebuf.val, ofp); + rlebuf.val = rlebuf.n = 0; /* Clear RLE-buffer */ + } +} + + +static void +read_sun_header (FILE *ifp, + L_SUNFILEHEADER *sunhdr) +{ + int j, err; + L_CARD32 *cp; + + cp = (L_CARD32 *)sunhdr; + + /* Read in all 32-bit values of the header and check for byte order */ + for (j = 0; j < sizeof (L_SUNFILEHEADER) / sizeof(sunhdr->l_ras_magic); j++) + { + *(cp++) = read_card32 (ifp, &err); + if (err) + break; + } + + if (err) + sunhdr->l_ras_magic = 0; /* Not a valid SUN-raster file */ +} + + +/* Write out a SUN-fileheader */ + +static void +write_sun_header (FILE *ofp, + L_SUNFILEHEADER *sunhdr) +{ + int j, hdr_entries; + L_CARD32 *cp; + + hdr_entries = sizeof (L_SUNFILEHEADER) / sizeof(sunhdr->l_ras_magic); + + cp = (L_CARD32 *)sunhdr; + + /* Write out all 32-bit values of the header and check for byte order */ + for (j = 0; j < hdr_entries; j++) + { + write_card32 (ofp, *(cp++)); + } +} + + +/* Read the sun colormap */ + +static void +read_sun_cols (FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *colormap) +{ + int ncols, err = 0; + + /* Read in SUN-raster Colormap */ + ncols = sunhdr->l_ras_maplength / 3; + if (ncols <= 0) + err = 1; + else + err = (fread (colormap, 3, ncols, ifp) != ncols); + + if (err) + sunhdr->l_ras_magic = 0; /* Not a valid SUN-raster file */ +} + + +/* Write a sun colormap */ + +static void +write_sun_cols (FILE *ofp, + L_SUNFILEHEADER *sunhdr, + guchar *colormap) +{ + int ncols; + + ncols = sunhdr->l_ras_maplength / 3; + fwrite (colormap, 3, ncols, ofp); +} + + +/* Set a GIMP colortable using the sun colormap */ + +static void +set_color_table (gint32 image_ID, + L_SUNFILEHEADER *sunhdr, + const guchar *suncolmap) +{ + guchar ColorMap[256 * 3]; + gint ncols, j; + + ncols = sunhdr->l_ras_maplength / 3; + if (ncols <= 0) + return; + + for (j = 0; j < MIN (ncols, 256); j++) + { + ColorMap[j * 3 + 0] = suncolmap[j]; + ColorMap[j * 3 + 1] = suncolmap[j + ncols]; + ColorMap[j * 3 + 2] = suncolmap[j + 2 * ncols]; + } + +#ifdef DEBUG + printf ("Set GIMP colortable:\n"); + for (j = 0; j < ncols; j++) + printf ("%3d: 0x%02x 0x%02x 0x%02x\n", j, + ColorMap[j*3], ColorMap[j*3+1], ColorMap[j*3+2]); +#endif + + gimp_image_set_colormap (image_ID, ColorMap, ncols); +} + + +/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */ +static gint32 +create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageBaseType type, + gint32 *layer_ID, + GeglBuffer **buffer) +{ + gint32 image_ID; + GimpImageType gdtype; + + switch (type) + { + case GIMP_RGB: + gdtype = GIMP_RGB_IMAGE; + break; + case GIMP_GRAY: + gdtype = GIMP_GRAY_IMAGE; + break; + case GIMP_INDEXED: + gdtype = GIMP_INDEXED_IMAGE; + break; + default: + g_warning ("Unsupported image type"); + return -1; + } + + image_ID = gimp_image_new (width, height, type); + gimp_image_set_filename (image_ID, filename); + + *layer_ID = gimp_layer_new (image_ID, _("Background"), width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, *layer_ID, -1, 0); + + *buffer = gimp_drawable_get_buffer (*layer_ID); + + return image_ID; +} + + +/* Load SUN-raster-file with depth 1 */ +static gint32 +load_sun_d1 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap) +{ + int pix8; + int width, height, linepad, scan_lines, tile_height; + int i, j; + guchar *dest, *data; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + guchar bit2byte[256 * 8]; + L_SUNFILEHEADER sun_bwhdr; + guchar sun_bwcolmap[6] = { 255,0,255,0,255,0 }; + int err = 0; + gboolean rle = (sunhdr->l_ras_type == RAS_TYPE_RLE); + + width = sunhdr->l_ras_width; + height = sunhdr->l_ras_height; + + image_ID = create_new_image (filename, width, height, GIMP_INDEXED, + &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width); + + if (suncolmap != NULL) /* Set up the specified color map */ + { + set_color_table (image_ID, sunhdr, suncolmap); + } + else /* No colormap available. Set up a dummy b/w-colormap */ + { /* Copy the original header and simulate b/w-colormap */ + memcpy ((char *)&sun_bwhdr,(char *)sunhdr,sizeof (L_SUNFILEHEADER)); + sun_bwhdr.l_ras_maptype = 2; + sun_bwhdr.l_ras_maplength = 6; + set_color_table (image_ID, &sun_bwhdr, sun_bwcolmap); + } + + /* Get an array for mapping 8 bits in a byte to 8 bytes */ + dest = bit2byte; + for (j = 0; j < 256; j++) + for (i = 7; i >= 0; i--) + *(dest++) = ((j & (1 << i)) != 0); + + linepad = (((sunhdr->l_ras_width+7)/8) % 2); /* Check for 16bit align */ + + if (rle) + rle_startread (ifp); + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + j = width; + while (j >= 8) + { + pix8 = rle ? rle_getc (ifp) : getc (ifp); + if (pix8 < 0) { err = 1; pix8 = 0; } + + memcpy (dest, bit2byte + pix8*8, 8); + dest += 8; + j -= 8; + } + + if (j > 0) + { + pix8 = rle ? rle_getc (ifp) : getc (ifp); + if (pix8 < 0) { err = 1; pix8 = 0; } + + memcpy (dest, bit2byte + pix8*8, j); + dest += j; + } + + if (linepad) + err |= ((rle ? rle_getc (ifp) : getc (ifp)) < 0); + + scan_lines++; + + if ((i % 20) == 0) + gimp_progress_update ((double)(i+1) / (double)height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + scan_lines = 0; + dest = data; + } + } + + g_free (data); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return image_ID; +} + + +/* Load SUN-raster-file with depth 8 */ + +static gint32 +load_sun_d8 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap) +{ + int width, height, linepad, i, j; + gboolean grayscale; + gint ncols; + int scan_lines, tile_height; + guchar *dest, *data; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + int err = 0; + gboolean rle = (sunhdr->l_ras_type == RAS_TYPE_RLE); + + width = sunhdr->l_ras_width; + height = sunhdr->l_ras_height; + + /* This could also be a grayscale image. Check it */ + ncols = sunhdr->l_ras_maplength / 3; + + grayscale = TRUE; /* Also grayscale if no colormap present */ + + if ((ncols > 0) && (suncolmap != NULL)) + { + for (j = 0; j < ncols; j++) + { + if ((suncolmap[j] != j) || + (suncolmap[j + ncols] != j) || + (suncolmap[j + 2 * ncols] != j)) + { + grayscale = FALSE; + break; + } + } + } + + image_ID = create_new_image (filename, width, height, + grayscale ? GIMP_GRAY : GIMP_INDEXED, + &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width); + + if (!grayscale) + set_color_table (image_ID, sunhdr, suncolmap); + + linepad = (sunhdr->l_ras_width % 2); + + if (rle) + rle_startread (ifp); /* Initialize RLE-buffer */ + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + memset ((char *)dest, 0, width); + err |= ((rle ? rle_fread ((char *)dest, 1, width, ifp) + : fread ((char *)dest, 1, width, ifp)) != width); + + if (linepad) + err |= ((rle ? rle_getc (ifp) : getc (ifp)) < 0); + + dest += width; + scan_lines++; + + if ((i % 20) == 0) + gimp_progress_update ((double)(i+1) / (double)height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + scan_lines = 0; + dest = data; + } + } + + g_free (data); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return image_ID; +} + + +/* Load SUN-raster-file with depth 24 */ +static gint32 +load_sun_d24 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap) +{ + guchar *dest, blue; + guchar *data; + int width, height, linepad, tile_height, scan_lines; + int i, j; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + int err = 0; + gboolean rle = (sunhdr->l_ras_type == RAS_TYPE_RLE); + + width = sunhdr->l_ras_width; + height = sunhdr->l_ras_height; + + image_ID = create_new_image (filename, width, height, GIMP_RGB, + &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width * 3); + + linepad = ((sunhdr->l_ras_width*3) % 2); + + if (rle) + rle_startread (ifp); /* Initialize RLE-buffer */ + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + memset ((char *)dest, 0, 3*width); + err |= ((rle ? rle_fread ((char *)dest, 3, width, ifp) + : fread ((char *)dest, 3, width, ifp)) != width); + + if (linepad) + err |= ((rle ? rle_getc (ifp) : getc (ifp)) < 0); + + if (sunhdr->l_ras_type == 3) /* RGB-format ? That is what GIMP wants */ + { + dest += width * 3; + } + else /* We have BGR format. Correct it */ + { + for (j = 0; j < width; j++) + { + blue = *dest; + *dest = *(dest+2); + *(dest+2) = blue; + dest += 3; + } + } + + scan_lines++; + + if ((i % 20) == 0) + gimp_progress_update ((double)(i + 1) / (double)height); + + if ((scan_lines == tile_height) || ((i + 1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + scan_lines = 0; + dest = data; + } + } + + g_free (data); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return image_ID; +} + + +/* Load SUN-raster-file with depth 32 */ + +static gint32 +load_sun_d32 (const gchar *filename, + FILE *ifp, + L_SUNFILEHEADER *sunhdr, + guchar *suncolmap) +{ + guchar *dest, blue; + guchar *data; + int width, height, tile_height, scan_lines; + int i, j; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + int err = 0; + int cerr; + gboolean rle = (sunhdr->l_ras_type == RAS_TYPE_RLE); + + width = sunhdr->l_ras_width; + height = sunhdr->l_ras_height; + + /* initialize */ + + cerr = 0; + + image_ID = create_new_image (filename, width, height, GIMP_RGB, + &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width * 3); + + if (rle) + rle_startread (ifp); /* Initialize RLE-buffer */ + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + if (rle) + { + for (j = 0; j < width; j++) + { + rle_getc (ifp); /* Skip unused byte */ + *(dest++) = rle_getc (ifp); + *(dest++) = rle_getc (ifp); + *(dest++) = (cerr = (rle_getc (ifp))); + } + } + else + { + for (j = 0; j < width; j++) + { + getc (ifp); /* Skip unused byte */ + *(dest++) = getc (ifp); + *(dest++) = getc (ifp); + *(dest++) = (cerr = (getc (ifp))); + } + } + err |= (cerr < 0); + + if (sunhdr->l_ras_type != 3) /* BGR format ? Correct it */ + { + for (j = 0; j < width; j++) + { + dest -= 3; + blue = *dest; + *dest = *(dest+2); + *(dest+2) = blue; + } + dest += width*3; + } + + scan_lines++; + + if ((i % 20) == 0) + gimp_progress_update ((double)(i + 1) / (double)height); + + if ((scan_lines == tile_height) || ((i + 1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + scan_lines = 0; + dest = data; + } + } + + g_free (data); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return image_ID; +} + + +static gint +save_index (FILE *ofp, + gint32 image_ID, + gint32 drawable_ID, + gboolean grey, + gboolean rle) +{ + int height, width, linepad, i, j; + int ncols, bw, is_bw, is_wb, bpl; + int tile_height; + long tmp = 0; + guchar *cmap, *bwline = NULL; + guchar *data, *src; + L_SUNFILEHEADER sunhdr; + guchar sun_colormap[256*3]; + static guchar sun_bwmap[6] = { 0, 255, 0, 255, 0, 255 }; + static guchar sun_wbmap[6] = { 255, 0, 255, 0, 255, 0 }; + unsigned char *suncolmap = sun_colormap; + GeglBuffer *buffer; + const Babl *format; + WRITE_FUN *write_fun; + + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + tile_height = gimp_tile_height (); + + if (grey) + format = babl_format ("Y' u8"); + else + format = gegl_buffer_get_format (buffer); + + /* allocate a buffer for retrieving information from the buffer */ + src = data = g_malloc (tile_height * width * + babl_format_get_bytes_per_pixel (format)); + + /* Fill SUN-color map */ + if (grey) + { + ncols = 256; + + for (j = 0; j < ncols; j++) + { + suncolmap[j] = j; + suncolmap[j + ncols] = j; + suncolmap[j + ncols * 2] = j; + } + } + else + { + cmap = gimp_image_get_colormap (image_ID, &ncols); + + for (j = 0; j < ncols; j++) + { + suncolmap[j] = *(cmap++); + suncolmap[j + ncols] = *(cmap++); + suncolmap[j + ncols * 2] = *(cmap++); + } + } + + bw = (ncols == 2); /* Maybe this is a two-color image */ + if (bw) + { + bwline = g_malloc ((width + 7) / 8); + if (bwline == NULL) + bw = 0; + } + + is_bw = is_wb = 0; + if (bw) /* The Sun-OS imagetool generates index 0 for white and */ + { /* index 1 for black. Do the same without colortable. */ + is_bw = (memcmp (suncolmap, sun_bwmap, 6) == 0); + is_wb = (memcmp (suncolmap, sun_wbmap, 6) == 0); + } + + /* Number of data bytes per line */ + bpl = bw ? (width+7)/8 : width; + linepad = bpl % 2; + + /* Fill in the SUN header */ + sunhdr.l_ras_magic = RAS_MAGIC; + sunhdr.l_ras_width = width; + sunhdr.l_ras_height = height; + sunhdr.l_ras_depth = bw ? 1 : 8; + sunhdr.l_ras_length = (bpl + linepad) * height; + sunhdr.l_ras_type = rle ? RAS_TYPE_RLE : RAS_TYPE_STD; + + if (is_bw || is_wb) /* No colortable for real b/w images */ + { + sunhdr.l_ras_maptype = 0; /* No colormap */ + sunhdr.l_ras_maplength = 0; /* Length of colormap */ + } + else + { + sunhdr.l_ras_maptype = 1; /* RGB colormap */ + sunhdr.l_ras_maplength = ncols*3; /* Length of colormap */ + } + + write_sun_header (ofp, &sunhdr); + + if (sunhdr.l_ras_maplength > 0) + write_sun_cols (ofp, &sunhdr, suncolmap); + +#define GET_INDEX_TILE(begin) \ + {int scan_lines; \ + scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \ + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), 1.0, \ + format, begin, \ + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \ + src = begin; } + + if (rle) + { + write_fun = (WRITE_FUN *) &rle_fwrite; + rle_startwrite (ofp); + } + else + { + write_fun = (WRITE_FUN *) &my_fwrite; + } + + if (bw) /* Two color image */ + { + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_INDEX_TILE (data); /* Get more data */ + + byte2bit (src, width, bwline, is_bw); + (*write_fun) (bwline, bpl, 1, ofp); + if (linepad) + (*write_fun) ((char *)&tmp, linepad, 1, ofp); + src += width; + + if ((i % 20) == 0) + gimp_progress_update ((double) i / (double) height); + } + } + else /* Color or grey-image */ + { + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_INDEX_TILE (data); /* Get more data */ + + (*write_fun) ((char *)src, width, 1, ofp); + if (linepad) + (*write_fun) ((char *)&tmp, linepad, 1, ofp); + src += width; + + if ((i % 20) == 0) + gimp_progress_update ((double) i / (double) height); + } + } + +#undef GET_INDEX_TILE + + if (rle) + rle_endwrite (ofp); + + g_free (data); + + if (bwline) + g_free (bwline); + + g_object_unref (buffer); + + if (ferror (ofp)) + { + g_message (_("Write error occurred")); + return FALSE; + } + + return TRUE; +} + + +static gint +save_rgb (FILE *ofp, + gint32 image_ID, + gint32 drawable_ID, + gboolean rle) +{ + int height, width, tile_height, linepad; + int i, j, bpp; + guchar *data, *src; + L_SUNFILEHEADER sunhdr; + GeglBuffer *buffer; + const Babl *format; + + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + tile_height = gimp_tile_height (); + + format = babl_format ("R'G'B' u8"); + + /* allocate a buffer for retrieving information from the pixel region */ + src = data = g_malloc (tile_height * width * + babl_format_get_bytes_per_pixel (format)); + +/* #define SUNRAS_32 */ +#ifdef SUNRAS_32 + bpp = 4; +#else + bpp = 3; +#endif + linepad = (width * bpp) % 2; + + /* Fill in the SUN header */ + sunhdr.l_ras_magic = RAS_MAGIC; + sunhdr.l_ras_width = width; + sunhdr.l_ras_height = height; + sunhdr.l_ras_depth = 8 * bpp; + sunhdr.l_ras_length = (width * bpp + linepad) * height; + sunhdr.l_ras_type = rle ? RAS_TYPE_RLE : RAS_TYPE_STD; + sunhdr.l_ras_maptype = 0; /* No colormap */ + sunhdr.l_ras_maplength = 0; /* Length of colormap */ + + write_sun_header (ofp, &sunhdr); + +#define GET_RGB_TILE(begin) \ + {int scan_lines; \ + scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \ + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), 1.0, \ + format, begin, \ + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \ + src = begin; } + + if (! rle) + { + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_RGB_TILE (data); /* Get more data */ + + for (j = 0; j < width; j++) + { + if (bpp == 4) putc (0, ofp); /* Dummy */ + putc (*(src + 2), ofp); /* Blue */ + putc (*(src + 1), ofp); /* Green */ + putc (*src, ofp); /* Red */ + src += 3; + } + + for (j = 0; j < linepad; j++) + putc (0, ofp); + + if ((i % 20) == 0) + gimp_progress_update ((double) i / (double) height); + } + } + else /* Write runlength encoded */ + { + rle_startwrite (ofp); + + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) + GET_RGB_TILE (data); /* Get more data */ + + for (j = 0; j < width; j++) + { + if (bpp == 4) rle_putc (0, ofp); /* Dummy */ + rle_putc (*(src + 2), ofp); /* Blue */ + rle_putc (*(src + 1), ofp); /* Green */ + rle_putc (*src, ofp); /* Red */ + src += 3; + } + + for (j = 0; j < linepad; j++) + rle_putc (0, ofp); + + if ((i % 20) == 0) + gimp_progress_update ((double) i / (double) height); + } + + rle_endwrite (ofp); + } + +#undef GET_RGB_TILE + + g_free (data); + + g_object_unref (buffer); + + if (ferror (ofp)) + { + g_message (_("Write error occurred")); + return FALSE; + } + + return TRUE; +} + + +/* Save interface functions */ + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *frame; + gboolean run; + + dialog = gimp_export_dialog_new (_("SUNRAS"), PLUG_IN_BINARY, SAVE_PROC); + + /* file save type */ + frame = gimp_int_radio_group_new (TRUE, _("Data Formatting"), + G_CALLBACK (gimp_radio_button_update), + &psvals.rle, psvals.rle, + + _("_RunLength Encoded"), TRUE, NULL, + _("_Standard"), FALSE, NULL, + + NULL); + + gtk_container_set_border_width (GTK_CONTAINER (frame), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static int +my_fwrite (void *ptr, + int size, + int nmemb, + FILE *stream) +{ + return fwrite (ptr, size, nmemb, stream); +} diff --git a/plug-ins/common/file-svg.c b/plug-ins/common/file-svg.c new file mode 100644 index 0000000..6786bee --- /dev/null +++ b/plug-ins/common/file-svg.c @@ -0,0 +1,914 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* SVG loader plug-in + * (C) Copyright 2003 Dom Lachowicz + * + * Largely rewritten in September 2003 by Sven Neumann + */ + +#include "config.h" + +#include +#include + +#include + +#include "libgimp/gimp.h" +#include "libgimp/gimpui.h" + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-svg-load" +#define LOAD_THUMB_PROC "file-svg-load-thumb" +#define PLUG_IN_BINARY "file-svg" +#define PLUG_IN_ROLE "gimp-file-svg" +#define SVG_VERSION "2.5.0" +#define SVG_DEFAULT_RESOLUTION 90.0 +#define SVG_DEFAULT_SIZE 500 +#define SVG_PREVIEW_SIZE 128 + + +typedef struct +{ + gdouble resolution; + gint width; + gint height; + gboolean import; + gboolean merge; +} SvgLoadVals; + +static SvgLoadVals load_vals = +{ + SVG_DEFAULT_RESOLUTION, + 0, + 0, + FALSE, + FALSE +}; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); +static GdkPixbuf * load_rsvg_pixbuf (const gchar *filename, + SvgLoadVals *vals, + GError **error); +static gboolean load_rsvg_size (const gchar *filename, + SvgLoadVals *vals, + GError **error); +static GimpPDBStatusType load_dialog (const gchar *filename, + GError **error); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run, +}; + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" }, + { GIMP_PDB_FLOAT, "resolution", + "Resolution to use for rendering the SVG (defaults to 90 dpi)" }, + { GIMP_PDB_INT32, "width", + "Width (in pixels) to load the SVG in. " + "(0 for original width, a negative width to specify a maximum width)" }, + { GIMP_PDB_INT32, "height", + "Height (in pixels) to load the SVG in. " + "(0 for original height, a negative width to specify a maximum height)"}, + { GIMP_PDB_INT32, "paths", + "Whether to not import paths (0), import paths individually (1) " + "or merge all imported paths (2)" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef thumb_args[] = + { + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_INT32, "thumb-size", "Preferred thumbnail size" } + }; + static const GimpParamDef thumb_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Thumbnail image" }, + { GIMP_PDB_INT32, "image-width", "Width of full-sized image" }, + { GIMP_PDB_INT32, "image-height", "Height of full-sized image" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in the SVG file format", + "Renders SVG files to raster graphics using librsvg.", + "Dom Lachowicz, Sven Neumann", + "Dom Lachowicz ", + SVG_VERSION, + N_("SVG image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/svg+xml"); + gimp_register_magic_load_handler (LOAD_PROC, + "svg", "", + "0,string,", + SVG_VERSION, + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (thumb_args), + G_N_ELEMENTS (thumb_return_vals), + thumb_args, thumb_return_vals); + + gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[4]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + + INIT_I18N (); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + gimp_get_data (LOAD_PROC, &load_vals); + + switch (run_mode) + { + case GIMP_RUN_NONINTERACTIVE: + if (nparams > 3) load_vals.resolution = param[3].data.d_float; + if (nparams > 4) load_vals.width = param[4].data.d_int32; + if (nparams > 5) load_vals.height = param[5].data.d_int32; + if (nparams > 6) + { + load_vals.import = param[6].data.d_int32 != FALSE; + load_vals.merge = param[6].data.d_int32 > TRUE; + } + break; + + case GIMP_RUN_INTERACTIVE: + status = load_dialog (param[1].data.d_string, &error); + break; + + case GIMP_RUN_WITH_LAST_VALS: + break; + } + + /* Don't clamp this; insane values are probably not meant to be + * used as resolution anyway. + */ + if (load_vals.resolution < GIMP_MIN_RESOLUTION || + load_vals.resolution > GIMP_MAX_RESOLUTION) + { + load_vals.resolution = SVG_DEFAULT_RESOLUTION; + } + + if (status == GIMP_PDB_SUCCESS) + { + const gchar *filename = param[1].data.d_string; + gint32 image_ID = load_image (filename, &error); + + if (image_ID != -1) + { + if (load_vals.import) + { + gint32 *vectors; + gint num_vectors; + + gimp_vectors_import_from_file (image_ID, filename, + load_vals.merge, TRUE, + &num_vectors, &vectors); + if (num_vectors > 0) + g_free (vectors); + } + + *nreturn_vals = 2; + + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + gimp_set_data (LOAD_PROC, &load_vals, sizeof (load_vals)); + } + } + else if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + if (nparams < 2) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + const gchar *filename = param[0].data.d_string; + gint width = 0; + gint height = 0; + gint32 image_ID; + + if (load_rsvg_size (filename, &load_vals, NULL)) + { + width = load_vals.width; + height = load_vals.height; + } + + load_vals.resolution = SVG_DEFAULT_RESOLUTION; + load_vals.width = - param[1].data.d_int32; + load_vals.height = - param[1].data.d_int32; + + image_ID = load_image (filename, &error); + + if (image_ID != -1) + { + *nreturn_vals = 4; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + values[2].type = GIMP_PDB_INT32; + values[2].data.d_int32 = width; + values[3].type = GIMP_PDB_INT32; + values[3].data.d_int32 = height; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gint32 +load_image (const gchar *filename, + GError **load_error) +{ + gint32 image; + gint32 layer; + GdkPixbuf *pixbuf; + gint width; + gint height; + GError *error = NULL; + + pixbuf = load_rsvg_pixbuf (filename, &load_vals, &error); + + if (! pixbuf) + { + /* Do not rely on librsvg setting GError on failure! */ + g_set_error (load_error, + error ? error->domain : 0, error ? error->code : 0, + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), + error ? error->message : _("Unknown reason")); + g_clear_error (&error); + + return -1; + } + + gimp_progress_init (_("Rendering SVG")); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + image = gimp_image_new (width, height, GIMP_RGB); + gimp_image_undo_disable (image); + + gimp_image_set_filename (image, filename); + gimp_image_set_resolution (image, + load_vals.resolution, load_vals.resolution); + + layer = gimp_layer_new_from_pixbuf (image, _("Rendered SVG"), pixbuf, + 100, + gimp_image_get_default_new_layer_mode (image), + 0.0, 1.0); + gimp_image_insert_layer (image, layer, -1, 0); + + gimp_image_undo_enable (image); + + return image; +} + +/* This is the callback used from load_rsvg_pixbuf(). */ +static void +load_set_size_callback (gint *width, + gint *height, + gpointer data) +{ + SvgLoadVals *vals = data; + + if (*width < 1 || *height < 1) + { + *width = SVG_DEFAULT_SIZE; + *height = SVG_DEFAULT_SIZE; + } + + if (!vals->width || !vals->height) + return; + + /* either both arguments negative or none */ + if ((vals->width * vals->height) < 0) + return; + + if (vals->width > 0) + { + *width = vals->width; + *height = vals->height; + } + else + { + gdouble w = *width; + gdouble h = *height; + gdouble aspect = (gdouble) vals->width / (gdouble) vals->height; + + if (aspect > (w / h)) + { + *height = abs (vals->height); + *width = (gdouble) abs (vals->width) * (w / h) + 0.5; + } + else + { + *width = abs (vals->width); + *height = (gdouble) abs (vals->height) / (w / h) + 0.5; + } + + vals->width = *width; + vals->height = *height; + } +} + +/* This function renders a pixbuf from an SVG file according to vals. */ +static GdkPixbuf * +load_rsvg_pixbuf (const gchar *filename, + SvgLoadVals *vals, + GError **error) +{ + GdkPixbuf *pixbuf = NULL; + RsvgHandle *handle; + GFile *file; + + file = g_file_new_for_path (filename); + + handle = rsvg_handle_new_from_gfile_sync (file, RSVG_HANDLE_FLAGS_NONE, NULL, error); + + g_object_unref (file); + + if (!handle) + { + return NULL; + } + + rsvg_handle_set_dpi (handle, vals->resolution); + rsvg_handle_set_size_callback (handle, load_set_size_callback, vals, NULL); + + pixbuf = rsvg_handle_get_pixbuf (handle); + + g_object_unref (handle); + + return pixbuf; +} + +static GtkWidget *size_label = NULL; + +/* This function retrieves the pixel size from an SVG file. */ +static gboolean +load_rsvg_size (const gchar *filename, + SvgLoadVals *vals, + GError **error) +{ + RsvgHandle *handle; + GFile *file; + RsvgDimensionData dim; + gboolean has_size; + + file = g_file_new_for_path (filename); + + handle = rsvg_handle_new_from_gfile_sync (file, RSVG_HANDLE_FLAGS_NONE, NULL, error); + + g_object_unref (file); + + if (!handle) + { + return FALSE; + } + + rsvg_handle_set_dpi (handle, vals->resolution); + + rsvg_handle_get_dimensions (handle, &dim); + + if (dim.width > 0 && dim.height > 0) + { + vals->width = dim.width; + vals->height = dim.height; + has_size = TRUE; + } + else + { + vals->width = SVG_DEFAULT_SIZE; + vals->height = SVG_DEFAULT_SIZE; + has_size = FALSE; + } + + if (size_label) + { + if (has_size) + { + gchar *text = g_strdup_printf (_("%d × %d"), + vals->width, vals->height); + gtk_label_set_text (GTK_LABEL (size_label), text); + g_free (text); + } + else + { + gtk_label_set_text (GTK_LABEL (size_label), + _("SVG file does not\nspecify a size!")); + } + } + + g_object_unref (handle); + + if (vals->width < 1) vals->width = 1; + if (vals->height < 1) vals->height = 1; + + return TRUE; +} + + +/* User interface */ + +static GimpSizeEntry *size = NULL; +static GtkAdjustment *xadj = NULL; +static GtkAdjustment *yadj = NULL; +static GtkWidget *constrain = NULL; +static gdouble ratio_x = 1.0; +static gdouble ratio_y = 1.0; +static gint svg_width = 0; +static gint svg_height = 0; + +static void load_dialog_set_ratio (gdouble x, + gdouble y); + + +static void +load_dialog_size_callback (GtkWidget *widget, + gpointer data) +{ + if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (constrain))) + { + gdouble x = gimp_size_entry_get_refval (size, 0) / (gdouble) svg_width; + gdouble y = gimp_size_entry_get_refval (size, 1) / (gdouble) svg_height; + + if (x != ratio_x) + { + load_dialog_set_ratio (x, x); + } + else if (y != ratio_y) + { + load_dialog_set_ratio (y, y); + } + } +} + +static void +load_dialog_ratio_callback (GtkAdjustment *adj, + gpointer data) +{ + gdouble x = gtk_adjustment_get_value (xadj); + gdouble y = gtk_adjustment_get_value (yadj); + + if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (constrain))) + { + if (x != ratio_x) + y = x; + else + x = y; + } + + load_dialog_set_ratio (x, y); +} + +static void +load_dialog_resolution_callback (GimpSizeEntry *res, + const gchar *filename) +{ + SvgLoadVals vals = { 0.0, 0, 0 }; + + load_vals.resolution = vals.resolution = gimp_size_entry_get_refval (res, 0); + + if (!load_rsvg_size (filename, &vals, NULL)) + return; + + g_signal_handlers_block_by_func (size, load_dialog_size_callback, NULL); + + gimp_size_entry_set_resolution (size, 0, load_vals.resolution, FALSE); + gimp_size_entry_set_resolution (size, 1, load_vals.resolution, FALSE); + + g_signal_handlers_unblock_by_func (size, load_dialog_size_callback, NULL); + + if (gimp_size_entry_get_unit (size) != GIMP_UNIT_PIXEL) + { + ratio_x = gimp_size_entry_get_refval (size, 0) / vals.width; + ratio_y = gimp_size_entry_get_refval (size, 1) / vals.height; + } + + svg_width = vals.width; + svg_height = vals.height; + + load_dialog_set_ratio (ratio_x, ratio_y); +} + +static void +load_dialog_set_ratio (gdouble x, + gdouble y) +{ + ratio_x = x; + ratio_y = y; + + g_signal_handlers_block_by_func (size, load_dialog_size_callback, NULL); + + gimp_size_entry_set_refval (size, 0, svg_width * x); + gimp_size_entry_set_refval (size, 1, svg_height * y); + + g_signal_handlers_unblock_by_func (size, load_dialog_size_callback, NULL); + + g_signal_handlers_block_by_func (xadj, load_dialog_ratio_callback, NULL); + g_signal_handlers_block_by_func (yadj, load_dialog_ratio_callback, NULL); + + gtk_adjustment_set_value (xadj, x); + gtk_adjustment_set_value (yadj, y); + + g_signal_handlers_unblock_by_func (xadj, load_dialog_ratio_callback, NULL); + g_signal_handlers_unblock_by_func (yadj, load_dialog_ratio_callback, NULL); +} + +static GimpPDBStatusType +load_dialog (const gchar *filename, + GError **load_error) +{ + GtkWidget *dialog; + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *image; + GdkPixbuf *preview; + GtkWidget *table; + GtkWidget *table2; + GtkWidget *abox; + GtkWidget *res; + GtkWidget *label; + GtkWidget *spinbutton; + GtkWidget *toggle; + GtkWidget *toggle2; + GtkAdjustment *adj; + gboolean run; + GError *error = NULL; + SvgLoadVals vals = + { + SVG_DEFAULT_RESOLUTION, + - SVG_PREVIEW_SIZE, + - SVG_PREVIEW_SIZE + }; + + preview = load_rsvg_pixbuf (filename, &vals, &error); + + if (! preview) + { + /* Do not rely on librsvg setting GError on failure! */ + g_set_error (load_error, + error ? error->domain : 0, error ? error->code : 0, + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), + error ? error->message : _("Unknown reason")); + g_clear_error (&error); + + return GIMP_PDB_EXECUTION_ERROR; + } + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + /* Scalable Vector Graphics is SVG, should perhaps not be translated */ + dialog = gimp_dialog_new (_("Render Scalable Vector Graphics"), + PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, LOAD_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + /* The SVG preview */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (vbox), abox, FALSE, FALSE, 0); + gtk_widget_show (abox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (abox), frame); + gtk_widget_show (frame); + + image = gtk_image_new_from_pixbuf (preview); + gtk_container_add (GTK_CONTAINER (frame), image); + gtk_widget_show (image); + + size_label = gtk_label_new (NULL); + gtk_label_set_justify (GTK_LABEL (size_label), GTK_JUSTIFY_CENTER); + gtk_box_pack_start (GTK_BOX (vbox), size_label, TRUE, TRUE, 4); + gtk_widget_show (size_label); + + /* query the initial size after the size label is created */ + vals.resolution = load_vals.resolution; + + load_rsvg_size (filename, &vals, NULL); + + svg_width = vals.width; + svg_height = vals.height; + + table = gtk_table_new (7, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2); + gtk_table_set_row_spacing (GTK_TABLE (table), 2, 2); + gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0); + gtk_widget_show (table); + + /* Width and Height */ + label = gtk_label_new (_("Width:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + label = gtk_label_new (_("Height:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + adj = (GtkAdjustment *) gtk_adjustment_new (1, 1, 1, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + size = GIMP_SIZE_ENTRY (gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a", + TRUE, FALSE, FALSE, 10, + GIMP_SIZE_ENTRY_UPDATE_SIZE)); + gtk_table_set_col_spacing (GTK_TABLE (size), 1, 6); + + gimp_size_entry_add_field (size, GTK_SPIN_BUTTON (spinbutton), NULL); + + gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (size), FALSE, FALSE, 0); + gtk_widget_show (GTK_WIDGET (size)); + + gimp_size_entry_set_refval_boundaries (size, 0, + GIMP_MIN_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE); + gimp_size_entry_set_refval_boundaries (size, 1, + GIMP_MIN_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE); + + gimp_size_entry_set_refval (size, 0, svg_width); + gimp_size_entry_set_refval (size, 1, svg_height); + + gimp_size_entry_set_resolution (size, 0, load_vals.resolution, FALSE); + gimp_size_entry_set_resolution (size, 1, load_vals.resolution, FALSE); + + g_signal_connect (size, "value-changed", + G_CALLBACK (load_dialog_size_callback), + NULL); + + /* Scale ratio */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 2, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + table2 = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacing (GTK_TABLE (table2), 0, 2); + gtk_table_set_row_spacing (GTK_TABLE (table2), 0, 4); + gtk_box_pack_start (GTK_BOX (hbox), table2, FALSE, FALSE, 0); + + xadj = (GtkAdjustment *) + gtk_adjustment_new (ratio_x, + (gdouble) GIMP_MIN_IMAGE_SIZE / (gdouble) svg_width, + (gdouble) GIMP_MAX_IMAGE_SIZE / (gdouble) svg_width, + 0.01, 0.1, 0); + spinbutton = gimp_spin_button_new (xadj, 0.01, 4); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); + gtk_table_attach_defaults (GTK_TABLE (table2), spinbutton, 0, 1, 0, 1); + gtk_widget_show (spinbutton); + + g_signal_connect (xadj, "value-changed", + G_CALLBACK (load_dialog_ratio_callback), + NULL); + + label = gtk_label_new_with_mnemonic (_("_X ratio:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + yadj = (GtkAdjustment *) + gtk_adjustment_new (ratio_y, + (gdouble) GIMP_MIN_IMAGE_SIZE / (gdouble) svg_height, + (gdouble) GIMP_MAX_IMAGE_SIZE / (gdouble) svg_height, + 0.01, 0.1, 0); + spinbutton = gimp_spin_button_new (yadj, 0.01, 4); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); + gtk_table_attach_defaults (GTK_TABLE (table2), spinbutton, 0, 1, 1, 2); + gtk_widget_show (spinbutton); + + g_signal_connect (yadj, "value-changed", + G_CALLBACK (load_dialog_ratio_callback), + NULL); + + label = gtk_label_new_with_mnemonic (_("_Y ratio:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + /* the constrain ratio chainbutton */ + constrain = gimp_chain_button_new (GIMP_CHAIN_RIGHT); + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (constrain), TRUE); + gtk_table_attach_defaults (GTK_TABLE (table2), constrain, 1, 2, 0, 2); + gtk_widget_show (constrain); + + gimp_help_set_help_data (GIMP_CHAIN_BUTTON (constrain)->button, + _("Constrain aspect ratio"), NULL); + + gtk_widget_show (table2); + + /* Resolution */ + label = gtk_label_new (_("Resolution:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 4, 5, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + res = gimp_size_entry_new (1, GIMP_UNIT_INCH, _("pixels/%a"), + FALSE, FALSE, FALSE, 10, + GIMP_SIZE_ENTRY_UPDATE_RESOLUTION); + gtk_table_set_col_spacing (GTK_TABLE (res), 1, 6); + + gtk_table_attach (GTK_TABLE (table), res, 1, 2, 4, 5, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (res); + + /* don't let the resolution become too small, librsvg tends to + crash with very small resolutions */ + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (res), 0, + 5.0, GIMP_MAX_RESOLUTION); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (res), 0, load_vals.resolution); + + g_signal_connect (res, "value-changed", + G_CALLBACK (load_dialog_resolution_callback), + (gpointer) filename); + + /* Path Import */ + toggle = gtk_check_button_new_with_mnemonic (_("Import _paths")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), load_vals.import); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 2, 5, 6, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("Import path elements of the SVG so they " + "can be used with the GIMP path tool"), + NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &load_vals.import); + + toggle2 = gtk_check_button_new_with_mnemonic (_("Merge imported paths")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle2), load_vals.merge); + gtk_table_attach (GTK_TABLE (table), toggle2, 0, 2, 6, 7, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (toggle2); + + g_signal_connect (toggle2, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &load_vals.merge); + + g_object_bind_property (toggle, "active", + toggle2, "sensitive", + G_BINDING_SYNC_CREATE); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + load_vals.width = ROUND (gimp_size_entry_get_refval (size, 0)); + load_vals.height = ROUND (gimp_size_entry_get_refval (size, 1)); + } + + gtk_widget_destroy (dialog); + + return run ? GIMP_PDB_SUCCESS : GIMP_PDB_CANCEL; +} diff --git a/plug-ins/common/file-tga.c b/plug-ins/common/file-tga.c new file mode 100644 index 0000000..6e20b01 --- /dev/null +++ b/plug-ins/common/file-tga.c @@ -0,0 +1,1475 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * TrueVision Targa loading and exporting file filter for GIMP. + * Targa code Copyright (C) 1997 Raphael FRANCOIS and Gordon Matzigkeit + * + * The Targa reading and writing code was written from scratch by + * Raphael FRANCOIS and Gordon Matzigkeit + * based on the TrueVision TGA File Format + * Specification, Version 2.0: + * + * + * + * It does not contain any code written for other TGA file loaders. + * Not even the RLE handling. ;) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Modified 2007-07-20, Raphaël Quinet : + * - Workaround for loading indexed images with full alpha channel. + * - Bug fix: save_image() was saving uninitialized tile data for + * indexed images with alpha. + * + * Modified August-November 2000, Nick Lamb + * - Clean-up more code, avoid structure implementation dependency, + * - Load more types of images reliably, reject others firmly + * - This is not perfect, but I think it's much better. Test please! + * + * Release 1.2, 1997-09-24, Gordon Matzigkeit : + * - Bug fixes and source cleanups. + * + * Release 1.1, 1997-09-19, Gordon Matzigkeit : + * - Preserve alpha channels. For indexed images, this can only be + * done if there is at least one free colormap entry. + * + * Release 1.0, 1997-09-06, Gordon Matzigkeit : + * - Handle loading all image types from the 2.0 specification. + * - Fix many alignment and endianness problems. + * - Use tiles for lower memory consumption and better speed. + * - Rewrite RLE code for clarity and speed. + * - Handle saving with RLE. + * + * Release 0.9, 1997-06-18, Raphael FRANCOIS : + * - Can load 24 and 32-bit Truecolor images, with and without RLE. + * - Saving currently only works without RLE. + * + * + * TODO: + * - Handle TGA images with version 2 extensions (image comment, + * resolution, date, ...). + * - GIMP stores the indexed alpha channel as a separate byte, + * one for each pixel. The TGA file format spec requires that the + * alpha channel be stored as part of the colormap, not with each + * individual pixel. This means that we have no good way of + * saving and loading INDEXEDA images that use alpha channel values + * other than 0 and 255. Workaround implemented for loading by + * promoting the image to RGBA, but saving indexed TGA images with + * full alpha information in the coloramp is not supported yet (only + * one fully transparent color is allowed in INDEXEDA mode). + */ + +/* Set these for debugging. */ +/* #define PROFILE 1 */ + +#include "config.h" + +#ifdef PROFILE +# include +#endif + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-tga-load" +#define SAVE_PROC "file-tga-save" +#define PLUG_IN_BINARY "file-tga" +#define PLUG_IN_ROLE "gimp-file-tga" + +typedef enum +{ + ORIGIN_TOP_LEFT = 0, + ORIGIN_BOTTOM_LEFT = 1 +} TgaOrigin; + +typedef struct _TgaSaveVals +{ + gboolean rle; + TgaOrigin origin; +} TgaSaveVals; + +static TgaSaveVals tsvals = +{ + TRUE, /* rle */ + ORIGIN_BOTTOM_LEFT /* origin */ +}; + + + /* TRUEVISION-XFILE magic signature string */ +static guchar magic[18] = +{ + 0x54, 0x52, 0x55, 0x45, 0x56, 0x49, 0x53, 0x49, 0x4f, + 0x4e, 0x2d, 0x58, 0x46, 0x49, 0x4c, 0x45, 0x2e, 0x0 +}; + +typedef struct tga_info_struct +{ + guint8 idLength; + guint8 colorMapType; + + guint8 imageType; + /* Known image types. */ +#define TGA_TYPE_MAPPED 1 +#define TGA_TYPE_COLOR 2 +#define TGA_TYPE_GRAY 3 + + guint8 imageCompression; + /* Only known compression is RLE */ +#define TGA_COMP_NONE 0 +#define TGA_COMP_RLE 1 + + /* Color Map Specification. */ + /* We need to separately specify high and low bytes to avoid endianness + and alignment problems. */ + + guint16 colorMapIndex; + guint16 colorMapLength; + guint8 colorMapSize; + + /* Image Specification. */ + guint16 xOrigin; + guint16 yOrigin; + + guint16 width; + guint16 height; + + guint8 bpp; + guint8 bytes; + + guint8 alphaBits; + guint8 flipHoriz; + guint8 flipVert; + + /* Extensions (version 2) */ + +/* Not all the structures described in the standard are transcribed here + only those which seem applicable to Gimp */ + + gchar authorName[41]; + gchar comment[324]; + guint month, day, year, hour, minute, second; + gchar jobName[41]; + gchar softwareID[41]; + guint pixelWidth, pixelHeight; /* write dpi? */ + gdouble gamma; +} tga_info; + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); +static gint save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + +static gboolean save_dialog (void); + +static gint32 ReadImage (FILE *fp, + tga_info *info, + const gchar *filename); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_INT32, "rle", "Use RLE compression" }, + { GIMP_PDB_INT32, "origin", "Image origin (0 = top-left, 1 = bottom-left)"} + } ; + + gimp_install_procedure (LOAD_PROC, + "Loads files of Targa file format", + "FIXME: write help for tga_load", + "Raphael FRANCOIS, Gordon Matzigkeit", + "Raphael FRANCOIS, Gordon Matzigkeit", + "1997,2000,2007", + N_("TarGA image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-tga"); + gimp_register_magic_load_handler (LOAD_PROC, + "tga,vda,icb,vst", + "", + "-18&,string,TRUEVISION-XFILE.,-1,byte,0"); + + gimp_install_procedure (SAVE_PROC, + "exports files in the Targa file format", + "FIXME: write help for tga_save", + "Raphael FRANCOIS, Gordon Matzigkeit", + "Raphael FRANCOIS, Gordon Matzigkeit", + "1997,2000", + N_("TarGA image"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-tga"); + gimp_register_save_handler (SAVE_PROC, "tga", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + +#ifdef PROFILE + struct tms tbuf1, tbuf2; +#endif + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { +#ifdef PROFILE + times (&tbuf1); +#endif + + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + export = gimp_export_image (&image_ID, &drawable_ID, "TGA", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &tsvals); + + /* First acquire information with a dialog */ + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + tsvals.rle = (param[5].data.d_int32) ? TRUE : FALSE; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &tsvals); + break; + + default: + break; + } + +#ifdef PROFILE + times (&tbuf1); +#endif + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (param[3].data.d_string, image_ID, drawable_ID, + &error)) + { + /* Store psvals data */ + gimp_set_data (SAVE_PROC, &tsvals, sizeof (tsvals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; + +#ifdef PROFILE + times (&tbuf2); + printf ("TGA: %s profile: %ld user %ld system\n", name, + (long) tbuf2.tms_utime - tbuf1.tms_utime, + (long) tbuf2.tms_stime - tbuf2.tms_stime); +#endif +} + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + FILE *fp; + tga_info info; + guchar header[18]; + guchar footer[26]; + guchar extension[495]; + long offset; + gint32 image_ID = -1; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + fp = g_fopen (filename, "rb"); + + if (! fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + /* Is file big enough for a footer? */ + if (!fseek (fp, -26L, SEEK_END)) + { + if (fread (footer, sizeof (footer), 1, fp) != 1) + { + g_message (_("Cannot read footer from '%s'"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + else if (memcmp (footer + 8, magic, sizeof (magic)) == 0) + { + /* Check the signature. */ + + offset = (footer[0] + + footer[1] * 256L + + footer[2] * 65536L + + footer[3] * 16777216L); + + if (offset != 0) + { + if (fseek (fp, offset, SEEK_SET) || + fread (extension, sizeof (extension), 1, fp) != 1) + { + g_message (_("Cannot read extension from '%s'"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + /* Eventually actually handle version 2 TGA here */ + } + } + } + + if (fseek (fp, 0, SEEK_SET) || + fread (header, sizeof (header), 1, fp) != 1) + { + g_message (_("Cannot read header from '%s'"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + switch (header[2]) + { + case 1: + info.imageType = TGA_TYPE_MAPPED; + info.imageCompression = TGA_COMP_NONE; + break; + case 2: + info.imageType = TGA_TYPE_COLOR; + info.imageCompression = TGA_COMP_NONE; + break; + case 3: + info.imageType = TGA_TYPE_GRAY; + info.imageCompression = TGA_COMP_NONE; + break; + + case 9: + info.imageType = TGA_TYPE_MAPPED; + info.imageCompression = TGA_COMP_RLE; + break; + case 10: + info.imageType = TGA_TYPE_COLOR; + info.imageCompression = TGA_COMP_RLE; + break; + case 11: + info.imageType = TGA_TYPE_GRAY; + info.imageCompression = TGA_COMP_RLE; + break; + + default: + info.imageType = 0; + } + + info.idLength = header[0]; + info.colorMapType = header[1]; + + info.colorMapIndex = header[3] + header[4] * 256; + info.colorMapLength = header[5] + header[6] * 256; + info.colorMapSize = header[7]; + + info.xOrigin = header[8] + header[9] * 256; + info.yOrigin = header[10] + header[11] * 256; + info.width = header[12] + header[13] * 256; + info.height = header[14] + header[15] * 256; + + info.bpp = header[16]; + info.bytes = (info.bpp + 7) / 8; + info.alphaBits = header[17] & 0x0f; /* Just the low 4 bits */ + info.flipHoriz = (header[17] & 0x10) ? 1 : 0; + info.flipVert = (header[17] & 0x20) ? 0 : 1; + + /* hack to handle some existing files with incorrect headers, see bug #306675 */ + if (info.alphaBits == info.bpp) + info.alphaBits = 0; + + /* hack to handle yet another flavor of incorrect headers, see bug #540969 */ + if (info.alphaBits == 0) + { + if (info.imageType == TGA_TYPE_COLOR && info.bpp == 32) + info.alphaBits = 8; + + if (info.imageType == TGA_TYPE_GRAY && info.bpp == 16) + info.alphaBits = 8; + } + + switch (info.imageType) + { + case TGA_TYPE_MAPPED: + if (info.bpp != 8) + { + g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)", + gimp_filename_to_utf8 (filename), + info.imageType, info.bpp); + fclose (fp); + return -1; + } + break; + case TGA_TYPE_COLOR: + if ((info.bpp != 15 && info.bpp != 16 && + info.bpp != 24 && info.bpp != 32) || + ((info.bpp == 15 || info.bpp == 24) && + info.alphaBits != 0) || + (info.bpp == 16 && info.alphaBits != 1 && + info.alphaBits != 0) || + (info.bpp == 32 && info.alphaBits != 8)) + { + g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u, alpha = %u)", + gimp_filename_to_utf8 (filename), + info.imageType, info.bpp, info.alphaBits); + fclose (fp); + return -1; + } + break; + case TGA_TYPE_GRAY: + if (info.bpp != 8 && + (info.alphaBits != 8 || (info.bpp != 16 && info.bpp != 15))) + { + g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)", + gimp_filename_to_utf8 (filename), + info.imageType, info.bpp); + fclose (fp); + return -1; + } + break; + + default: + g_message ("Unknown image type %u for '%s'", + info.imageType, gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + /* Plausible but unhandled formats */ + if (info.bytes * 8 != info.bpp && info.bpp != 15) + { + g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)", + gimp_filename_to_utf8 (filename), + info.imageType, info.bpp); + fclose (fp); + return -1; + } + + /* Check that we have a color map only when we need it. */ + if (info.imageType == TGA_TYPE_MAPPED && info.colorMapType != 1) + { + g_message ("Indexed image has invalid color map type %u", + info.colorMapType); + fclose (fp); + return -1; + } + else if (info.imageType != TGA_TYPE_MAPPED && info.colorMapType != 0) + { + g_message ("Non-indexed image has invalid color map type %u", + info.colorMapType); + fclose (fp); + return -1; + } + + /* Skip the image ID field. */ + if (info.idLength && fseek (fp, info.idLength, SEEK_CUR)) + { + g_message ("File '%s' is truncated or corrupted", + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + image_ID = ReadImage (fp, &info, filename); + + fclose (fp); + + return image_ID; +} + +static void +rle_write (FILE *fp, + guchar *buf, + guint width, + guint bytes) +{ + gint repeat = 0; + gint direct = 0; + guchar *from = buf; + guint x; + + for (x = 1; x < width; ++x) + { + if (memcmp (buf, buf + bytes, bytes)) + { + /* next pixel is different */ + if (repeat) + { + putc (128 + repeat, fp); + fwrite (from, bytes, 1, fp); + from = buf + bytes; /* point to first different pixel */ + repeat = 0; + direct = 0; + } + else + { + direct += 1; + } + } + else + { + /* next pixel is the same */ + if (direct) + { + putc (direct - 1, fp); + fwrite (from, bytes, direct, fp); + from = buf; /* point to first identical pixel */ + direct = 0; + repeat = 1; + } + else + { + repeat += 1; + } + } + + if (repeat == 128) + { + putc (255, fp); + fwrite (from, bytes, 1, fp); + from = buf + bytes; + direct = 0; + repeat = 0; + } + else if (direct == 128) + { + putc (127, fp); + fwrite (from, bytes, direct, fp); + from = buf+ bytes; + direct = 0; + repeat = 0; + } + + buf += bytes; + } + + if (repeat > 0) + { + putc (128 + repeat, fp); + fwrite (from, bytes, 1, fp); + } + else + { + putc (direct, fp); + fwrite (from, bytes, direct + 1, fp); + } +} + +static gint +rle_read (FILE *fp, + guchar *buf, + tga_info *info) +{ + static gint repeat = 0; + static gint direct = 0; + static guchar sample[4]; + gint head; + gint x, k; + + for (x = 0; x < info->width; x++) + { + if (repeat == 0 && direct == 0) + { + head = getc (fp); + + if (head == EOF) + { + return EOF; + } + else if (head >= 128) + { + repeat = head - 127; + + if (fread (sample, info->bytes, 1, fp) < 1) + return EOF; + } + else + { + direct = head + 1; + } + } + + if (repeat > 0) + { + for (k = 0; k < info->bytes; ++k) + { + buf[k] = sample[k]; + } + + repeat--; + } + else /* direct > 0 */ + { + if (fread (buf, info->bytes, 1, fp) < 1) + return EOF; + + direct--; + } + + buf += info->bytes; + } + + return 0; +} + +static void +flip_line (guchar *buf, + tga_info *info) +{ + guchar temp; + guchar *alt; + gint x, s; + + alt = buf + (info->bytes * (info->width - 1)); + + for (x = 0; x * 2 < info->width; x++) + { + for (s = 0; s < info->bytes; ++s) + { + temp = buf[s]; + buf[s] = alt[s]; + alt[s] = temp; + } + + buf += info->bytes; + alt -= info->bytes; + } +} + +/* Some people write 16-bit RGB TGA files. The spec would probably + allow 27-bit RGB too, for what it's worth, but I won't fix that + unless someone actually provides an existence proof */ + +static void +upsample (guchar *dest, + const guchar *src, + guint width, + guint bytes, + guint alpha) +{ + guint x; + + for (x = 0; x < width; x++) + { + dest[0] = ((src[1] << 1) & 0xf8); + dest[0] += (dest[0] >> 5); + + dest[1] = ((src[0] & 0xe0) >> 2) + ((src[1] & 0x03) << 6); + dest[1] += (dest[1] >> 5); + + dest[2] = ((src[0] << 3) & 0xf8); + dest[2] += (dest[2] >> 5); + + if (alpha) + { + dest[3] = (src[1] & 0x80) ? 255 : 0; + dest += 4; + } + else + { + dest += 3; + } + + src += bytes; + } +} + +static void +bgr2rgb (guchar *dest, + const guchar *src, + guint width, + guint bytes, + guint alpha) +{ + guint x; + + if (alpha) + { + for (x = 0; x < width; x++) + { + *(dest++) = src[2]; + *(dest++) = src[1]; + *(dest++) = src[0]; + *(dest++) = src[3]; + + src += bytes; + } + } + else + { + for (x = 0; x < width; x++) + { + *(dest++) = src[2]; + *(dest++) = src[1]; + *(dest++) = src[0]; + + src += bytes; + } + } +} + +static void +apply_colormap (guchar *dest, + const guchar *src, + guint width, + const guchar *cmap, + gboolean alpha, + guint16 index) +{ + guint x; + + if (alpha) + { + for (x = 0; x < width; x++) + { + *(dest++) = cmap[(*src - index) * 4]; + *(dest++) = cmap[(*src - index) * 4 + 1]; + *(dest++) = cmap[(*src - index) * 4 + 2]; + *(dest++) = cmap[(*src - index) * 4 + 3]; + + src++; + } + } + else + { + for (x = 0; x < width; x++) + { + *(dest++) = cmap[(*src - index) * 3]; + *(dest++) = cmap[(*src - index) * 3 + 1]; + *(dest++) = cmap[(*src - index) * 3 + 2]; + + src++; + } + } +} + +static void +apply_index (guchar *dest, + const guchar *src, + guint width, + guint16 index) +{ + guint x; + + for (x = 0; x < width; x++) + { + *(dest++) = *(src++) - index; + } +} + +static void +read_line (FILE *fp, + guchar *row, + guchar *buf, + tga_info *info, + gint bpp, + const guchar *convert_cmap) +{ + if (info->imageCompression == TGA_COMP_RLE) + { + rle_read (fp, buf, info); + } + else + { + fread (buf, info->bytes, info->width, fp); + } + + if (info->flipHoriz) + { + flip_line (buf, info); + } + + if (info->imageType == TGA_TYPE_COLOR) + { + if (info->bpp == 16 || info->bpp == 15) + { + upsample (row, buf, info->width, info->bytes, info->alphaBits); + } + else + { + bgr2rgb (row, buf, info->width, info->bytes, info->alphaBits); + } + } + else if (convert_cmap) + { + gboolean has_alpha = (info->alphaBits > 0); + + apply_colormap (row, buf, info->width, convert_cmap, has_alpha, + info->colorMapIndex); + } + else if (info->imageType == TGA_TYPE_MAPPED) + { + g_assert (bpp == 1); + + apply_index (row, buf, info->width, info->colorMapIndex); + } + else + { + memcpy (row, buf, info->width * bpp); + } +} + +static gint32 +ReadImage (FILE *fp, + tga_info *info, + const gchar *filename) +{ + static gint32 image_ID; + gint32 layer_ID; + GeglBuffer *buffer; + guchar *data, *buf, *row; + GimpImageType dtype = 0; + GimpImageBaseType itype = 0; + gint bpp; + gint i, y; + gint max_tileheight, tileheight; + guint cmap_bytes = 0; + guchar *tga_cmap = NULL; + guchar *gimp_cmap = NULL; + guchar *convert_cmap = NULL; + + switch (info->imageType) + { + case TGA_TYPE_MAPPED: + cmap_bytes = (info->colorMapSize + 7 ) / 8; + tga_cmap = g_new (guchar, info->colorMapLength * cmap_bytes); + + if (info->colorMapSize > 24) + { + /* indexed + full alpha => promoted to RGBA */ + itype = GIMP_RGB; + dtype = GIMP_RGBA_IMAGE; + convert_cmap = g_new (guchar, info->colorMapLength * 4); + } + else if (info->colorMapIndex + info->colorMapLength > 256) + { + /* more than 256 colormap entries => promoted to RGB */ + itype = GIMP_RGB; + dtype = GIMP_RGB_IMAGE; + convert_cmap = g_new (guchar, info->colorMapLength * 3); + } + else if (info->alphaBits > 0) + { + /* if alpha exists here, promote to RGB */ + itype = GIMP_RGB; + dtype = GIMP_RGBA_IMAGE; + convert_cmap = g_new (guchar, info->colorMapLength * 4); + } + else + { + itype = GIMP_INDEXED; + dtype = GIMP_INDEXED_IMAGE; + gimp_cmap = g_new (guchar, info->colorMapLength * 3); + } + break; + + case TGA_TYPE_GRAY: + itype = GIMP_GRAY; + + if (info->alphaBits) + dtype = GIMP_GRAYA_IMAGE; + else + dtype = GIMP_GRAY_IMAGE; + break; + + case TGA_TYPE_COLOR: + itype = GIMP_RGB; + + if (info->alphaBits) + dtype = GIMP_RGBA_IMAGE; + else + dtype = GIMP_RGB_IMAGE; + break; + } + + /* Handle colormap */ + + if (info->imageType == TGA_TYPE_MAPPED) + { + if (cmap_bytes <= 4 && + fread (tga_cmap, info->colorMapLength * cmap_bytes, 1, fp) == 1) + { + if (convert_cmap) + { + if (info->colorMapSize == 32) + bgr2rgb (convert_cmap, tga_cmap, + info->colorMapLength, cmap_bytes, 1); + else if (info->colorMapSize == 24) + bgr2rgb (convert_cmap, tga_cmap, + info->colorMapLength, cmap_bytes, 0); + else if (info->colorMapSize == 16 || info->colorMapSize == 15) + upsample (convert_cmap, tga_cmap, + info->colorMapLength, cmap_bytes, info->alphaBits); + else + { + g_message ("Unsupported colormap depth: %u", + info->colorMapSize); + return -1; + } + } + else + { + if (info->colorMapSize == 24) + bgr2rgb (gimp_cmap, tga_cmap, + info->colorMapLength, cmap_bytes, 0); + else if (info->colorMapSize == 16 || info->colorMapSize == 15) + upsample (gimp_cmap, tga_cmap, + info->colorMapLength, cmap_bytes, info->alphaBits); + else + { + g_message ("Unsupported colormap depth: %u", + info->colorMapSize); + return -1; + } + } + } + else + { + g_message ("File '%s' is truncated or corrupted", + gimp_filename_to_utf8 (filename)); + return -1; + } + } + + image_ID = gimp_image_new (info->width, info->height, itype); + gimp_image_set_filename (image_ID, filename); + + if (gimp_cmap) + gimp_image_set_colormap (image_ID, gimp_cmap, info->colorMapLength); + + layer_ID = gimp_layer_new (image_ID, + _("Background"), + info->width, info->height, + dtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_ID); + + bpp = gimp_drawable_bpp (layer_ID); + + /* Allocate the data. */ + max_tileheight = gimp_tile_height (); + data = g_new (guchar, info->width * max_tileheight * bpp); + buf = g_new (guchar, info->width * info->bytes); + + if (info->flipVert) + { + for (i = 0; i < info->height; i += tileheight) + { + tileheight = i ? max_tileheight : (info->height % max_tileheight); + if (tileheight == 0) + tileheight = max_tileheight; + + for (y = 1; y <= tileheight; ++y) + { + row = data + (info->width * bpp * (tileheight - y)); + read_line (fp, row, buf, info, bpp, convert_cmap); + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, info->height - i - tileheight, + info->width, tileheight), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((gdouble) (i + tileheight) / + (gdouble) info->height); + } + } + else + { + for (i = 0; i < info->height; i += max_tileheight) + { + tileheight = MIN (max_tileheight, info->height - i); + + for (y = 0; y < tileheight; ++y) + { + row= data + (info->width * bpp * y); + read_line (fp, row, buf, info, bpp, convert_cmap); + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, i, info->width, tileheight), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((gdouble) (i + tileheight) / + (gdouble) info->height); + } + } + + g_free (data); + g_free (buf); + + g_free (convert_cmap); + g_free (gimp_cmap); + g_free (tga_cmap); + + g_object_unref (buffer); + + gimp_progress_update (1.0); + + return image_ID; +} + + +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglBuffer *buffer; + const Babl *format = NULL; + GimpImageType dtype; + gint width; + gint height; + FILE *fp; + gint out_bpp = 0; + gboolean status = TRUE; + gint i, row; + guchar header[18]; + guchar footer[26]; + guchar *pixels; + guchar *data; + gint num_colors; + guchar *gimp_cmap = NULL; + + buffer = gimp_drawable_get_buffer (drawable_ID); + + dtype = gimp_drawable_type (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_filename_to_utf8 (filename)); + + if ((fp = g_fopen (filename, "wb")) == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + header[0] = 0; /* No image identifier / description */ + + if (dtype == GIMP_INDEXED_IMAGE) + { + gimp_cmap = gimp_image_get_colormap (image_ID, &num_colors); + + header[1] = 1; /* cmap type */ + header[2] = (tsvals.rle) ? 9 : 1; + header[3] = header[4] = 0; /* no offset */ + header[5] = num_colors % 256; + header[6] = num_colors / 256; + header[7] = 24; /* cmap size / bits */ + } + else if (dtype == GIMP_INDEXEDA_IMAGE) + { + gimp_cmap = gimp_image_get_colormap (image_ID, &num_colors); + + header[1] = 1; /* cmap type */ + header[2] = (tsvals.rle) ? 9 : 1; + header[3] = header[4] = 0; /* no offset */ + header[5] = (num_colors + 1) % 256; + header[6] = (num_colors + 1) / 256; + header[7] = 32; /* cmap size / bits */ + } + else + { + header[1]= 0; + + if (dtype == GIMP_RGB_IMAGE || dtype == GIMP_RGBA_IMAGE) + { + header[2]= (tsvals.rle) ? 10 : 2; + } + else + { + header[2]= (tsvals.rle) ? 11 : 3; + } + + header[3] = header[4] = header[5] = header[6] = header[7] = 0; + } + + header[8] = header[9] = 0; /* xorigin */ + header[10] = tsvals.origin ? 0 : (height % 256); /* yorigin */ + header[11] = tsvals.origin ? 0 : (height / 256); /* yorigin */ + + + header[12] = width % 256; + header[13] = width / 256; + + header[14] = height % 256; + header[15] = height / 256; + + switch (dtype) + { + case GIMP_INDEXED_IMAGE: + case GIMP_INDEXEDA_IMAGE: + format = NULL; + out_bpp = 1; + header[16] = 8; /* bpp */ + header[17] = tsvals.origin ? 0 : 0x20; /* alpha + orientation */ + break; + + case GIMP_GRAY_IMAGE: + format = babl_format ("Y' u8"); + out_bpp = 1; + header[16] = 8; /* bpp */ + header[17] = tsvals.origin ? 0 : 0x20; /* alpha + orientation */ + break; + + case GIMP_GRAYA_IMAGE: + format = babl_format ("Y'A u8"); + out_bpp = 2; + header[16] = 16; /* bpp */ + header[17] = tsvals.origin ? 8 : 0x28; /* alpha + orientation */ + break; + + case GIMP_RGB_IMAGE: + format = babl_format ("R'G'B' u8"); + out_bpp = 3; + header[16] = 24; /* bpp */ + header[17] = tsvals.origin ? 0 : 0x20; /* alpha + orientation */ + break; + + case GIMP_RGBA_IMAGE: + format = babl_format ("R'G'B'A u8"); + out_bpp = 4; + header[16] = 32; /* bpp */ + header[17] = tsvals.origin ? 8 : 0x28; /* alpha + orientation */ + break; + } + + /* write header to front of file */ + fwrite (header, sizeof (header), 1, fp); + + if (dtype == GIMP_INDEXED_IMAGE) + { + /* write out palette */ + for (i = 0; i < num_colors; ++i) + { + fputc (gimp_cmap[(i * 3) + 2], fp); + fputc (gimp_cmap[(i * 3) + 1], fp); + fputc (gimp_cmap[(i * 3) + 0], fp); + } + } + else if (dtype == GIMP_INDEXEDA_IMAGE) + { + /* write out palette */ + for (i = 0; i < num_colors; ++i) + { + fputc (gimp_cmap[(i * 3) + 2], fp); + fputc (gimp_cmap[(i * 3) + 1], fp); + fputc (gimp_cmap[(i * 3) + 0], fp); + fputc (255, fp); + } + + fputc (0, fp); + fputc (0, fp); + fputc (0, fp); + fputc (0, fp); + } + + pixels = g_new (guchar, width * out_bpp); + data = g_new (guchar, width * out_bpp); + + for (row = 0; row < height; ++row) + { + if (tsvals.origin) + { + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, height - (row + 1), width, 1), 1.0, + format, pixels, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + else + { + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, row, width, 1), 1.0, + format, pixels, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + if (dtype == GIMP_RGB_IMAGE) + { + bgr2rgb (data, pixels, width, out_bpp, 0); + } + else if (dtype == GIMP_RGBA_IMAGE) + { + bgr2rgb (data, pixels, width, out_bpp, 1); + } + else if (dtype == GIMP_INDEXEDA_IMAGE) + { + for (i = 0; i < width; ++i) + { + if (pixels[i * 2 + 1] > 127) + data[i] = pixels[i * 2]; + else + data[i] = num_colors; + } + } + else + { + memcpy (data, pixels, width * out_bpp); + } + + if (tsvals.rle) + { + rle_write (fp, data, width, out_bpp); + } + else + { + fwrite (data, width * out_bpp, 1, fp); + } + + if (row % 16 == 0) + gimp_progress_update ((gdouble) row / (gdouble) height); + } + + g_object_unref (buffer); + + g_free (data); + g_free (pixels); + + /* footer must be the last thing written to file */ + memset (footer, 0, 8); /* No extensions, no developer directory */ + memcpy (footer + 8, magic, sizeof (magic)); /* magic signature */ + fwrite (footer, sizeof (footer), 1, fp); + + fclose (fp); + + gimp_progress_update (1.0); + + return status; +} + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *toggle; + GtkWidget *combo; + GtkWidget *vbox; + GtkWidget *hbox; + gboolean run; + + dialog = gimp_export_dialog_new (_("TGA"), PLUG_IN_BINARY, SAVE_PROC); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + /* rle */ + toggle = gtk_check_button_new_with_mnemonic (_("_RLE compression")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), tsvals.rle); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals.rle); + + /* origin */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("Or_igin:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + combo = gimp_int_combo_box_new (_("Bottom left"), ORIGIN_BOTTOM_LEFT, + _("Top left"), ORIGIN_TOP_LEFT, + NULL); + gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + tsvals.origin, + G_CALLBACK (gimp_int_combo_box_get_active), + &tsvals.origin); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/file-wmf.c b/plug-ins/common/file-wmf.c new file mode 100644 index 0000000..8c86510 --- /dev/null +++ b/plug-ins/common/file-wmf.c @@ -0,0 +1,1049 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* WMF loading file filter for GIMP + * -Dom Lachowicz 2003 + * -Francis James Franklin + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-wmf-load" +#define LOAD_THUMB_PROC "file-wmf-load-thumb" +#define PLUG_IN_BINARY "file-wmf" +#define PLUG_IN_ROLE "gimp-file-wmf" + +#define WMF_DEFAULT_RESOLUTION 90.0 +#define WMF_DEFAULT_SIZE 500 +#define WMF_PREVIEW_SIZE 128 + +typedef struct +{ + gdouble resolution; + gint width; + gint height; +} WmfLoadVals; + +static WmfLoadVals load_vals = +{ + WMF_DEFAULT_RESOLUTION, + 0, + 0, +}; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gint32 load_image (const gchar *filename, + GError **error); +static gboolean load_wmf_size (const gchar *filename, + WmfLoadVals *vals); +static gboolean load_dialog (const gchar *filename); +static guchar *wmf_get_pixbuf (const gchar *filename, + gint *width, + gint *height); +static guchar *wmf_load_file (const gchar *filename, + guint *width, + guint *height, + GError **error); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +MAIN () + + +/* + * 'query()' - Respond to a plug-in query... + */ +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" }, + { GIMP_PDB_FLOAT, "resolution", "Resolution to use for rendering the WMF (defaults to 72 dpi" }, + { GIMP_PDB_INT32, "width", "Width (in pixels) to load the WMF in, 0 for original width" }, + { GIMP_PDB_INT32, "height", "Height (in pixels) to load the WMF in, 0 for original height" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef thumb_args[] = + { + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_INT32, "thumb-size", "Preferred thumbnail size" } + }; + static const GimpParamDef thumb_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Thumbnail image" }, + { GIMP_PDB_INT32, "image-width", "Width of full-sized image" }, + { GIMP_PDB_INT32, "image-height", "Height of full-sized image" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in the WMF file format", + "Loads files in the WMF file format", + "Dom Lachowicz ", + "Dom Lachowicz ", + "(c) 2003 - Version 0.3.0", + N_("Microsoft WMF file"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-wmf"); + gimp_register_magic_load_handler (LOAD_PROC, + "wmf,apm", "", + "0,string,\\327\\315\\306\\232,0,string,\\1\\0\\11\\0"); + + gimp_install_procedure (LOAD_THUMB_PROC, + "Loads a small preview from a WMF image", + "", + "Dom Lachowicz ", + "Dom Lachowicz ", + "(c) 2003 - Version 0.3.0", + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (thumb_args), + G_N_ELEMENTS (thumb_return_vals), + thumb_args, thumb_return_vals); + + gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC); +} + +/* + * 'run()' - Run the plug-in... + */ +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[4]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + const gchar *filename = NULL; + GError *error = NULL; + gint32 image_ID = -1; + gint width = 0; + gint height = 0; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + filename = param[1].data.d_string; + + gimp_get_data (LOAD_PROC, &load_vals); + + switch (run_mode) + { + case GIMP_RUN_NONINTERACTIVE: + if (nparams > 3) load_vals.resolution = param[3].data.d_float; + if (nparams > 4) load_vals.width = param[4].data.d_int32; + if (nparams > 5) load_vals.height = param[5].data.d_int32; + break; + + case GIMP_RUN_INTERACTIVE: + if (!load_dialog (param[1].data.d_string)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_WITH_LAST_VALS: + break; + } + } + else if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + gint size = param[1].data.d_int32; + + filename = param[0].data.d_string; + + if (size > 0 && + load_wmf_size (filename, &load_vals) && + load_vals.width > 0 && + load_vals.height > 0) + { + width = load_vals.width; + height = load_vals.height; + + if ((gdouble) load_vals.width > (gdouble) load_vals.height) + { + load_vals.width = size; + load_vals.height *= size / (gdouble) load_vals.width; + } + else + { + load_vals.width *= size / (gdouble) load_vals.height; + load_vals.height = size; + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (load_vals.resolution < GIMP_MIN_RESOLUTION || + load_vals.resolution > GIMP_MAX_RESOLUTION) + { + load_vals.resolution = WMF_DEFAULT_RESOLUTION; + } + + image_ID = load_image (filename, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + *nreturn_vals = 4; + values[2].type = GIMP_PDB_INT32; + values[2].data.d_int32 = width; + values[3].type = GIMP_PDB_INT32; + values[3].data.d_int32 = height; + } + else + { + gimp_set_data (LOAD_PROC, &load_vals, sizeof (load_vals)); + } + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +static GtkWidget *size_label = NULL; + + +/* This function retrieves the pixel size from a WMF file. */ +static gboolean +load_wmf_size (const gchar *filename, + WmfLoadVals *vals) +{ + GMappedFile *file; + /* the bits we need to decode the WMF via libwmf2's GD layer */ + wmf_error_t err; + gulong flags; + wmf_gd_t *ddata = NULL; + wmfAPI *API = NULL; + wmfAPI_Options api_options; + wmfD_Rect bbox; + guint width = -1; + guint height = -1; + gboolean success = TRUE; + char* wmffontdirs[2] = { NULL, NULL }; + + file = g_mapped_file_new (filename, FALSE, NULL); + if (! file) + return FALSE; + + flags = WMF_OPT_IGNORE_NONFATAL | WMF_OPT_FUNCTION; + api_options.function = wmf_gd_function; + +#ifdef ENABLE_RELOCATABLE_RESOURCES + wmffontdirs[0] = g_build_filename (gimp_installation_directory (), + "share/libwmf/fonts", NULL); + flags |= WMF_OPT_FONTDIRS; + api_options.fontdirs = wmffontdirs; +#endif + + err = wmf_api_create (&API, flags, &api_options); + if (wmffontdirs[0]) + g_free (wmffontdirs[0]); + if (err != wmf_E_None) + success = FALSE; + + ddata = WMF_GD_GetData (API); + ddata->type = wmf_gd_image; + + err = wmf_mem_open (API, + (guchar *) g_mapped_file_get_contents (file), + g_mapped_file_get_length (file)); + if (err != wmf_E_None) + success = FALSE; + + err = wmf_scan (API, 0, &bbox); + if (err != wmf_E_None) + success = FALSE; + + err = wmf_display_size (API, &width, &height, + vals->resolution, vals->resolution); + if (err != wmf_E_None || width <= 0 || height <= 0) + success = FALSE; + + wmf_mem_close (API); + g_mapped_file_unref (file); + + if (width < 1 || height < 1) + { + width = WMF_DEFAULT_SIZE; + height = WMF_DEFAULT_SIZE; + + if (size_label) + gtk_label_set_text (GTK_LABEL (size_label), + _("WMF file does not\nspecify a size!")); + } + else + { + if (size_label) + { + gchar *text = g_strdup_printf (_("%d × %d"), width, height); + + gtk_label_set_text (GTK_LABEL (size_label), text); + g_free (text); + } + } + + vals->width = width; + vals->height = height; + + return success; +} + + +/* User interface */ + +static GimpSizeEntry *size = NULL; +static GtkAdjustment *xadj = NULL; +static GtkAdjustment *yadj = NULL; +static GtkWidget *constrain = NULL; +static gdouble ratio_x = 1.0; +static gdouble ratio_y = 1.0; +static gint wmf_width = 0; +static gint wmf_height = 0; + +static void load_dialog_set_ratio (gdouble x, + gdouble y); + + +static void +load_dialog_size_callback (GtkWidget *widget, + gpointer data) +{ + if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (constrain))) + { + gdouble x = gimp_size_entry_get_refval (size, 0) / (gdouble) wmf_width; + gdouble y = gimp_size_entry_get_refval (size, 1) / (gdouble) wmf_height; + + if (x != ratio_x) + { + load_dialog_set_ratio (x, x); + } + else if (y != ratio_y) + { + load_dialog_set_ratio (y, y); + } + } +} + +static void +load_dialog_ratio_callback (GtkAdjustment *adj, + gpointer data) +{ + gdouble x = gtk_adjustment_get_value (xadj); + gdouble y = gtk_adjustment_get_value (yadj); + + if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (constrain))) + { + if (x != ratio_x) + y = x; + else + x = y; + } + + load_dialog_set_ratio (x, y); +} + +static void +load_dialog_resolution_callback (GimpSizeEntry *res, + const gchar *filename) +{ + WmfLoadVals vals = { 0.0, 0, 0 }; + + load_vals.resolution = vals.resolution = gimp_size_entry_get_refval (res, 0); + + if (!load_wmf_size (filename, &vals)) + return; + + wmf_width = vals.width; + wmf_height = vals.height; + + load_dialog_set_ratio (ratio_x, ratio_y); +} + +static void +wmf_preview_callback (GtkWidget *widget, + GtkAllocation *allocation, + guchar *pixels) +{ + gimp_preview_area_draw (GIMP_PREVIEW_AREA (widget), + 0, 0, allocation->width, allocation->height, + GIMP_RGBA_IMAGE, + pixels, allocation->width * 4); +} + +static void +load_dialog_set_ratio (gdouble x, + gdouble y) +{ + ratio_x = x; + ratio_y = y; + + g_signal_handlers_block_by_func (size, load_dialog_size_callback, NULL); + + gimp_size_entry_set_refval (size, 0, wmf_width * x); + gimp_size_entry_set_refval (size, 1, wmf_height * y); + + g_signal_handlers_unblock_by_func (size, load_dialog_size_callback, NULL); + + g_signal_handlers_block_by_func (xadj, load_dialog_ratio_callback, NULL); + g_signal_handlers_block_by_func (yadj, load_dialog_ratio_callback, NULL); + + gtk_adjustment_set_value (xadj, x); + gtk_adjustment_set_value (yadj, y); + + g_signal_handlers_unblock_by_func (xadj, load_dialog_ratio_callback, NULL); + g_signal_handlers_unblock_by_func (yadj, load_dialog_ratio_callback, NULL); +} + +static gboolean +load_dialog (const gchar *filename) +{ + GtkWidget *dialog; + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *image; + GtkWidget *table; + GtkWidget *table2; + GtkWidget *abox; + GtkWidget *res; + GtkWidget *label; + GtkWidget *spinbutton; + GtkAdjustment *adj; + guchar *pixels; + gboolean run = FALSE; + + WmfLoadVals vals = { WMF_DEFAULT_RESOLUTION, + - WMF_PREVIEW_SIZE, - WMF_PREVIEW_SIZE }; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Render Windows Metafile"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, LOAD_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + /* The WMF preview */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (vbox), abox, FALSE, FALSE, 0); + gtk_widget_show (abox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (abox), frame); + gtk_widget_show (frame); + + pixels = wmf_get_pixbuf (filename, &vals.width, &vals.height); + image = gimp_preview_area_new (); + gtk_widget_set_size_request (image, vals.width, vals.height); + gtk_container_add (GTK_CONTAINER (frame), image); + gtk_widget_show (image); + + g_signal_connect (image, "size-allocate", + G_CALLBACK (wmf_preview_callback), + pixels); + + size_label = gtk_label_new (NULL); + gtk_label_set_justify (GTK_LABEL (size_label), GTK_JUSTIFY_CENTER); + gtk_box_pack_start (GTK_BOX (vbox), size_label, TRUE, TRUE, 4); + gtk_widget_show (size_label); + + /* query the initial size after the size label is created */ + vals.resolution = load_vals.resolution; + + load_wmf_size (filename, &vals); + + wmf_width = vals.width; + wmf_height = vals.height; + + table = gtk_table_new (7, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2); + gtk_table_set_row_spacing (GTK_TABLE (table), 2, 2); + gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0); + gtk_widget_show (table); + + /* Width and Height */ + label = gtk_label_new (_("Width:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + label = gtk_label_new (_("Height:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + adj = (GtkAdjustment *) gtk_adjustment_new (1, 1, 1, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + size = GIMP_SIZE_ENTRY (gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a", + TRUE, FALSE, FALSE, 10, + GIMP_SIZE_ENTRY_UPDATE_SIZE)); + gtk_table_set_col_spacing (GTK_TABLE (size), 1, 6); + + gimp_size_entry_add_field (size, GTK_SPIN_BUTTON (spinbutton), NULL); + + gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (size), FALSE, FALSE, 0); + gtk_widget_show (GTK_WIDGET (size)); + + gimp_size_entry_set_refval_boundaries (size, 0, + GIMP_MIN_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE); + gimp_size_entry_set_refval_boundaries (size, 1, + GIMP_MIN_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE); + + gimp_size_entry_set_refval (size, 0, wmf_width); + gimp_size_entry_set_refval (size, 1, wmf_height); + + gimp_size_entry_set_resolution (size, 0, load_vals.resolution, FALSE); + gimp_size_entry_set_resolution (size, 1, load_vals.resolution, FALSE); + + g_signal_connect (size, "value-changed", + G_CALLBACK (load_dialog_size_callback), + NULL); + + /* Scale ratio */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 2, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + table2 = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacing (GTK_TABLE (table2), 0, 2); + gtk_table_set_row_spacing (GTK_TABLE (table2), 0, 4); + gtk_box_pack_start (GTK_BOX (hbox), table2, FALSE, FALSE, 0); + + xadj = (GtkAdjustment *) + gtk_adjustment_new (ratio_x, + (gdouble) GIMP_MIN_IMAGE_SIZE / (gdouble) wmf_width, + (gdouble) GIMP_MAX_IMAGE_SIZE / (gdouble) wmf_width, + 0.01, 0.1, 0); + spinbutton = gimp_spin_button_new (xadj, 0.01, 4); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); + gtk_table_attach_defaults (GTK_TABLE (table2), spinbutton, 0, 1, 0, 1); + gtk_widget_show (spinbutton); + + g_signal_connect (xadj, "value-changed", + G_CALLBACK (load_dialog_ratio_callback), + NULL); + + label = gtk_label_new_with_mnemonic (_("_X ratio:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + yadj = (GtkAdjustment *) + gtk_adjustment_new (ratio_y, + (gdouble) GIMP_MIN_IMAGE_SIZE / (gdouble) wmf_height, + (gdouble) GIMP_MAX_IMAGE_SIZE / (gdouble) wmf_height, + 0.01, 0.1, 0); + spinbutton = gimp_spin_button_new (yadj, 0.01, 4); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); + gtk_table_attach_defaults (GTK_TABLE (table2), spinbutton, 0, 1, 1, 2); + gtk_widget_show (spinbutton); + + g_signal_connect (yadj, "value-changed", + G_CALLBACK (load_dialog_ratio_callback), + NULL); + + label = gtk_label_new_with_mnemonic (_("_Y ratio:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + /* the constrain ratio chainbutton */ + constrain = gimp_chain_button_new (GIMP_CHAIN_RIGHT); + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (constrain), TRUE); + gtk_table_attach_defaults (GTK_TABLE (table2), constrain, 1, 2, 0, 2); + gtk_widget_show (constrain); + + gimp_help_set_help_data (GIMP_CHAIN_BUTTON (constrain)->button, + _("Constrain aspect ratio"), NULL); + + gtk_widget_show (table2); + + /* Resolution */ + label = gtk_label_new (_("Resolution:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 4, 5, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + res = gimp_size_entry_new (1, GIMP_UNIT_INCH, _("pixels/%a"), + FALSE, FALSE, FALSE, 10, + GIMP_SIZE_ENTRY_UPDATE_RESOLUTION); + gtk_table_set_col_spacing (GTK_TABLE (res), 1, 6); + + gtk_table_attach (GTK_TABLE (table), res, 1, 2, 4, 5, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (res); + + /* don't let the resolution become too small ? does libwmf tend to + crash with very small resolutions */ + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (res), 0, + 5.0, GIMP_MAX_RESOLUTION); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (res), 0, load_vals.resolution); + + g_signal_connect (res, "value-changed", + G_CALLBACK (load_dialog_resolution_callback), + (gpointer) filename); + + gtk_widget_show (dialog); + + if (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK) + { + load_vals.width = ROUND (gimp_size_entry_get_refval (size, 0)); + load_vals.height = ROUND (gimp_size_entry_get_refval (size, 1)); + run = TRUE; + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + return run; +} + +static guchar * +pixbuf_gd_convert (const gint *gd_pixels, + gint width, + gint height) +{ + const gint *gd_ptr = gd_pixels; + guchar *pixels; + guchar *px_ptr; + gint i, j; + + pixels = (guchar *) g_try_malloc (width * height * sizeof (guchar) * 4); + if (! pixels) + return NULL; + + px_ptr = pixels; + + for (i = 0; i < height; i++) + for (j = 0; j < width; j++) + { + guchar r, g, b, a; + guint pixel = (guint) (*gd_ptr++); + + b = (guchar) (pixel & 0xff); + pixel >>= 8; + g = (guchar) (pixel & 0xff); + pixel >>= 8; + r = (guchar) (pixel & 0xff); + pixel >>= 7; + a = (guchar) (pixel & 0xfe); + a ^= 0xff; + + *px_ptr++ = r; + *px_ptr++ = g; + *px_ptr++ = b; + *px_ptr++ = a; + } + + return pixels; +} + +static guchar * +wmf_get_pixbuf (const gchar *filename, + gint *width, + gint *height) +{ + GMappedFile *file; + guchar *pixels = NULL; + + /* the bits we need to decode the WMF via libwmf2's GD layer */ + wmf_error_t err; + gulong flags; + wmf_gd_t *ddata = NULL; + wmfAPI *API = NULL; + wmfAPI_Options api_options; + guint file_width; + guint file_height; + wmfD_Rect bbox; + gint *gd_pixels = NULL; + char* wmffontdirs[2] = { NULL, NULL }; + + file = g_mapped_file_new (filename, FALSE, NULL); + if (! file) + return NULL; + + flags = WMF_OPT_IGNORE_NONFATAL | WMF_OPT_FUNCTION; + api_options.function = wmf_gd_function; + +#ifdef ENABLE_RELOCATABLE_RESOURCES + wmffontdirs[0] = g_build_filename (gimp_installation_directory (), + "share/libwmf/fonts", NULL); + flags |= WMF_OPT_FONTDIRS; + api_options.fontdirs = wmffontdirs; +#endif + + err = wmf_api_create (&API, flags, &api_options); + if (wmffontdirs[0]) + g_free (wmffontdirs[0]); + if (err != wmf_E_None) + goto _wmf_error; + + ddata = WMF_GD_GetData (API); + ddata->type = wmf_gd_image; + + err = wmf_mem_open (API, + (guchar *) g_mapped_file_get_contents (file), + g_mapped_file_get_length (file)); + if (err != wmf_E_None) + goto _wmf_error; + + err = wmf_scan (API, 0, &bbox); + if (err != wmf_E_None) + goto _wmf_error; + + err = wmf_display_size (API, &file_width, &file_height, + WMF_DEFAULT_RESOLUTION, WMF_DEFAULT_RESOLUTION); + if (err != wmf_E_None || file_width <= 0 || file_height <= 0) + goto _wmf_error; + + if (!*width || !*height) + goto _wmf_error; + + /* either both arguments negative or none */ + if ((*width * *height) < 0) + goto _wmf_error; + + ddata->bbox = bbox; + + if (*width > 0) + { + ddata->width = *width; + ddata->height = *height; + } + else + { + gdouble w = file_width; + gdouble h = file_height; + gdouble aspect = ((gdouble) *width) / (gdouble) *height; + + if (aspect > (w / h)) + { + ddata->height = abs (*height); + ddata->width = (gdouble) abs (*width) * (w / h) + 0.5; + } + else + { + ddata->width = abs (*width); + ddata->height = (gdouble) abs (*height) / (w / h) + 0.5; + } + } + + err = wmf_play (API, 0, &bbox); + if (err != wmf_E_None) + goto _wmf_error; + + if (ddata->gd_image != NULL) + gd_pixels = wmf_gd_image_pixels (ddata->gd_image); + if (gd_pixels == NULL) + goto _wmf_error; + + pixels = pixbuf_gd_convert (gd_pixels, ddata->width, ddata->height); + if (pixels == NULL) + goto _wmf_error; + + *width = ddata->width; + *height = ddata->height; + + _wmf_error: + if (API) + { + wmf_mem_close (API); + wmf_api_destroy (API); + } + + g_mapped_file_unref (file); + + return pixels; +} + +static guchar * +wmf_load_file (const gchar *filename, + guint *width, + guint *height, + GError **error) +{ + GMappedFile *file; + guchar *pixels = NULL; + + /* the bits we need to decode the WMF via libwmf2's GD layer */ + wmf_error_t err; + gulong flags; + wmf_gd_t *ddata = NULL; + wmfAPI *API = NULL; + wmfAPI_Options api_options; + wmfD_Rect bbox; + gint *gd_pixels = NULL; + char* wmffontdirs[2] = { NULL, NULL }; + + *width = *height = -1; + + file = g_mapped_file_new (filename, FALSE, error); + if (! file) + return NULL; + + flags = WMF_OPT_IGNORE_NONFATAL | WMF_OPT_FUNCTION; + api_options.function = wmf_gd_function; + +#ifdef ENABLE_RELOCATABLE_RESOURCES + wmffontdirs[0] = g_build_filename (gimp_installation_directory (), + "share/libwmf/fonts/", NULL); + flags |= WMF_OPT_FONTDIRS; + api_options.fontdirs = wmffontdirs; +#endif + + err = wmf_api_create (&API, flags, &api_options); + if (wmffontdirs[0]) + g_free (wmffontdirs[0]); + if (err != wmf_E_None) + goto _wmf_error; + + ddata = WMF_GD_GetData (API); + ddata->type = wmf_gd_image; + + err = wmf_mem_open (API, + (guchar *) g_mapped_file_get_contents (file), + g_mapped_file_get_length (file)); + if (err != wmf_E_None) + goto _wmf_error; + + err = wmf_scan (API, 0, &bbox); + if (err != wmf_E_None) + goto _wmf_error; + + err = wmf_display_size (API, + width, height, + load_vals.resolution, load_vals.resolution); + if (err != wmf_E_None || *width <= 0 || *height <= 0) + goto _wmf_error; + + if (load_vals.width > 0 && load_vals.height > 0) + { + *width = load_vals.width; + *height = load_vals.height; + } + + ddata->bbox = bbox; + ddata->width = *width; + ddata->height = *height; + + err = wmf_play (API, 0, &bbox); + if (err != wmf_E_None) + goto _wmf_error; + + if (ddata->gd_image != NULL) + gd_pixels = wmf_gd_image_pixels (ddata->gd_image); + if (gd_pixels == NULL) + goto _wmf_error; + + pixels = pixbuf_gd_convert (gd_pixels, *width, *height); + if (pixels == NULL) + goto _wmf_error; + + _wmf_error: + if (API) + { + wmf_mem_close (API); + wmf_api_destroy (API); + } + + g_mapped_file_unref (file); + + /* FIXME: improve error message */ + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not open '%s' for reading"), + gimp_filename_to_utf8 (filename)); + + return pixels; +} + +/* + * 'load_image()' - Load a WMF image into a new image window. + */ +static gint32 +load_image (const gchar *filename, + GError **error) +{ + gint32 image; + gint32 layer; + GeglBuffer *buffer; + guchar *pixels; + guint width, height; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + pixels = wmf_load_file (filename, &width, &height, error); + + if (! pixels) + return -1; + + image = gimp_image_new (width, height, GIMP_RGB); + gimp_image_set_filename (image, filename); + gimp_image_set_resolution (image, + load_vals.resolution, load_vals.resolution); + + layer = gimp_layer_new (image, + _("Rendered WMF"), + width, height, + GIMP_RGBA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image)); + + buffer = gimp_drawable_get_buffer (layer); + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0, + babl_format ("R'G'B'A u8"), + pixels, GEGL_AUTO_ROWSTRIDE); + + g_object_unref (buffer); + + g_free (pixels); + + gimp_image_insert_layer (image, layer, -1, 0); + + gimp_progress_update (1.0); + + return image; +} diff --git a/plug-ins/common/file-xbm.c b/plug-ins/common/file-xbm.c new file mode 100644 index 0000000..5ab3b00 --- /dev/null +++ b/plug-ins/common/file-xbm.c @@ -0,0 +1,1445 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * X10 and X11 bitmap (XBM) loading and exporting file filter for GIMP. + * XBM code Copyright (C) 1998 Gordon Matzigkeit + * + * The XBM reading and writing code was written from scratch by Gordon + * Matzigkeit based on the XReadBitmapFile(3X11) manual + * page distributed with X11R6 and by staring at valid XBM files. It + * does not contain any code written for other XBM file loaders. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Release 1.0, 1998-02-04, Gordon Matzigkeit : + * - Load and save X10 and X11 bitmaps. + * - Allow the user to specify the C identifier prefix. + * + * TODO: + * - Parsing is very tolerant, and the algorithms are quite hairy, so + * load_image should be carefully tested to make sure there are no XBM's + * that fail. + */ + +/* Set this for debugging. */ +/* #define VERBOSE 2 */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-xbm-load" +#define SAVE_PROC "file-xbm-save" +#define PLUG_IN_BINARY "file-xbm" +#define PLUG_IN_ROLE "gimp-file-xbm" + + +/* Wear your GIMP with pride! */ +#define DEFAULT_USE_COMMENT TRUE +#define MAX_COMMENT 72 +#define MAX_MASK_EXT 32 + +/* C identifier prefix. */ +#define DEFAULT_PREFIX "bitmap" +#define MAX_PREFIX 64 + +/* Whether or not to export as X10 bitmap. */ +#define DEFAULT_X10_FORMAT FALSE + +typedef struct _XBMSaveVals +{ + gchar comment[MAX_COMMENT + 1]; + gint x10_format; + gint use_hot; + gint x_hot; + gint y_hot; + gchar prefix[MAX_PREFIX + 1]; + gboolean write_mask; + gchar mask_ext[MAX_MASK_EXT + 1]; +} XBMSaveVals; + +static XBMSaveVals xsvals = +{ + "###", /* comment */ + DEFAULT_X10_FORMAT, /* x10_format */ + FALSE, + 0, /* x_hot */ + 0, /* y_hot */ + DEFAULT_PREFIX, /* prefix */ + FALSE, /* write_mask */ + "-mask" +}; + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); +static gint save_image (GFile *file, + const gchar *prefix, + const gchar *comment, + gboolean save_mask, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static gboolean save_dialog (gint32 drawable_ID); + +static gboolean print (GOutputStream *output, + GError **error, + const gchar *format, + ...) G_GNUC_PRINTF (3, 4); + +#if 0 +/* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */ +static void comment_entry_callback (GtkWidget *widget, + gpointer data); +#endif +static void prefix_entry_callback (GtkWidget *widget, + gpointer data); +static void mask_ext_entry_callback (GtkWidget *widget, + gpointer data); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +#ifdef VERBOSE +static int verbose = VERBOSE; +#endif + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + { GIMP_PDB_STRING, "comment", "Image description (maximum 72 bytes)" }, + { GIMP_PDB_INT32, "x10", "Export in X10 format" }, + { GIMP_PDB_INT32, "x-hot", "X coordinate of hotspot" }, + { GIMP_PDB_INT32, "y-hot", "Y coordinate of hotspot" }, + { GIMP_PDB_STRING, "prefix", "Identifier prefix [determined from filename]"}, + { GIMP_PDB_INT32, "write-mask", "(0 = ignore, 1 = save as extra file)" }, + { GIMP_PDB_STRING, "mask-extension", "Extension of the mask file" } + } ; + + gimp_install_procedure (LOAD_PROC, + "Load a file in X10 or X11 bitmap (XBM) file format", + "Load a file in X10 or X11 bitmap (XBM) file format. XBM is a lossless format for flat black-and-white (two color indexed) images.", + "Gordon Matzigkeit", + "Gordon Matzigkeit", + "1998", + N_("X BitMap image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-xbitmap"); + gimp_register_load_handler (LOAD_PROC, + "xbm,icon,bitmap", + ""); + + gimp_install_procedure (SAVE_PROC, + "Export a file in X10 or X11 bitmap (XBM) file format", + "Export a file in X10 or X11 bitmap (XBM) file format. XBM is a lossless format for flat black-and-white (two color indexed) images.", + "Gordon Matzigkeit", + "Gordon Matzigkeit", + "1998", + N_("X BitMap image"), + "INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-xbitmap"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "xbm,icon,bitmap", ""); +} + +static gchar * +init_prefix (const gchar *filename) +{ + gchar *p, *prefix; + gint len; + + prefix = g_path_get_basename (filename); + + memset (xsvals.prefix, 0, sizeof (xsvals.prefix)); + + if (prefix) + { + /* Strip any extension. */ + p = strrchr (prefix, '.'); + if (p && p != prefix) + len = MIN (MAX_PREFIX, p - prefix); + else + len = MAX_PREFIX; + + strncpy (xsvals.prefix, prefix, len); + g_free (prefix); + } + + return xsvals.prefix; +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpParasite *parasite = NULL; + gchar *mask_basename = NULL; + GError *error = NULL; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + strncpy (xsvals.comment, "Created with GIMP", MAX_COMMENT); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + +#ifdef VERBOSE + if (verbose) + printf ("XBM: RUN %s\n", name); +#endif + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "XBM", + GIMP_EXPORT_CAN_HANDLE_BITMAP | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (SAVE_PROC, &xsvals); + + /* Always override the prefix with the filename. */ + mask_basename = g_strdup (init_prefix (param[3].data.d_string)); + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the required arguments are there! */ + if (nparams < 5) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + gint i = 5; + + if (nparams > i) + { + memset (xsvals.comment, 0, sizeof (xsvals.comment)); + strncpy (xsvals.comment, param[i].data.d_string, + MAX_COMMENT); + } + + i ++; + if (nparams > i) + xsvals.x10_format = (param[i].data.d_int32) ? TRUE : FALSE; + + i += 2; + if (nparams > i) + { + /* They've asked for a hotspot. */ + xsvals.use_hot = TRUE; + xsvals.x_hot = param[i - 1].data.d_int32; + xsvals.y_hot = param[i].data.d_int32; + } + + mask_basename = g_strdup (init_prefix (param[3].data.d_string)); + + i ++; + if (nparams > i) + { + memset (xsvals.prefix, 0, sizeof (xsvals.prefix)); + strncpy (xsvals.prefix, param[i].data.d_string, + MAX_PREFIX); + } + + i += 2; + if (nparams > i) + { + xsvals.write_mask = param[i - 1].data.d_int32; + memset (xsvals.mask_ext, 0, sizeof (xsvals.mask_ext)); + strncpy (xsvals.mask_ext, param[i].data.d_string, + MAX_MASK_EXT); + } + + i ++; + /* Too many arguments. */ + if (nparams > i) + status = GIMP_PDB_CALLING_ERROR; + } + break; + + default: + break; + } + + if (run_mode == GIMP_RUN_INTERACTIVE) + { + /* Get the parasites */ + parasite = gimp_image_get_parasite (image_ID, "gimp-comment"); + + if (parasite) + { + gint size = gimp_parasite_data_size (parasite); + + strncpy (xsvals.comment, + gimp_parasite_data (parasite), MIN (size, MAX_COMMENT)); + xsvals.comment[MIN (size, MAX_COMMENT) + 1] = 0; + + gimp_parasite_free (parasite); + } + + parasite = gimp_image_get_parasite (image_ID, "hot-spot"); + + if (parasite) + { + gint x, y; + + if (sscanf (gimp_parasite_data (parasite), "%i %i", &x, &y) == 2) + { + xsvals.use_hot = TRUE; + xsvals.x_hot = x; + xsvals.y_hot = y; + } + gimp_parasite_free (parasite); + } + + /* Acquire information with a dialog */ + if (! save_dialog (drawable_ID)) + status = GIMP_PDB_CANCEL; + } + + if (status == GIMP_PDB_SUCCESS) + { + GFile *file = g_file_new_for_uri (param[3].data.d_string); + GFile *mask_file; + GFile *dir; + gchar *mask_prefix; + gchar *temp; + + dir = g_file_get_parent (file); + temp = g_strdup_printf ("%s%s.xbm", mask_basename, xsvals.mask_ext); + + mask_file = g_file_get_child (dir, temp); + + g_free (temp); + g_object_unref (dir); + + /* Change any non-alphanumeric prefix characters to underscores. */ + for (temp = xsvals.prefix; *temp; temp++) + if (! g_ascii_isalnum (*temp)) + *temp = '_'; + + mask_prefix = g_strdup_printf ("%s%s", + xsvals.prefix, xsvals.mask_ext); + + for (temp = mask_prefix; *temp; temp++) + if (! g_ascii_isalnum (*temp)) + *temp = '_'; + + if (save_image (file, + xsvals.prefix, + xsvals.comment, + FALSE, + image_ID, drawable_ID, + &error) + + && (! xsvals.write_mask || + save_image (mask_file, + mask_prefix, + xsvals.comment, + TRUE, + image_ID, drawable_ID, + &error))) + { + /* Store xsvals data */ + gimp_set_data (SAVE_PROC, &xsvals, sizeof (xsvals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + g_free (mask_prefix); + g_free (mask_basename); + + g_object_unref (file); + g_object_unref (mask_file); + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +/* Return the value of a digit. */ +static gint +getval (gint c, + gint base) +{ + const gchar *digits = "0123456789abcdefABCDEF"; + gint val; + + /* Include uppercase hex digits. */ + if (base == 16) + base = 22; + + /* Find a match. */ + for (val = 0; val < base; val ++) + if (c == digits[val]) + return (val < 16) ? val : (val - 6); + return -1; +} + + +/* Get a comment */ +static gchar * +fgetcomment (FILE *fp) +{ + GString *str = NULL; + gint comment, c; + + comment = 0; + do + { + c = fgetc (fp); + if (comment) + { + if (c == '*') + { + /* In a comment, with potential to leave. */ + comment = 1; + } + else if (comment == 1 && c == '/') + { + gchar *retval; + + /* Leaving a comment. */ + comment = 0; + + retval = g_strstrip (g_strdup (str->str)); + g_string_free (str, TRUE); + return retval; + } + else + { + /* In a comment, with no potential to leave. */ + comment = 2; + g_string_append_c (str, c); + } + } + else + { + /* Not in a comment. */ + if (c == '/') + { + /* Potential to enter a comment. */ + c = fgetc (fp); + if (c == '*') + { + /* Entered a comment, with no potential to leave. */ + comment = 2; + str = g_string_new (NULL); + } + else + { + /* put everything back and return */ + ungetc (c, fp); + c = '/'; + ungetc (c, fp); + return NULL; + } + } + else if (c != EOF && g_ascii_isspace (c)) + { + /* Skip leading whitespace */ + continue; + } + } + } + while (comment && c != EOF); + + if (str) + g_string_free (str, TRUE); + + return NULL; +} + + +/* Same as fgetc, but skip C-style comments and insert whitespace. */ +static gint +cpp_fgetc (FILE *fp) +{ + gint comment, c; + + /* FIXME: insert whitespace as advertised. */ + comment = 0; + do + { + c = fgetc (fp); + if (comment) + { + if (c == '*') + /* In a comment, with potential to leave. */ + comment = 1; + else if (comment == 1 && c == '/') + /* Leaving a comment. */ + comment = 0; + else + /* In a comment, with no potential to leave. */ + comment = 2; + } + else + { + /* Not in a comment. */ + if (c == '/') + { + /* Potential to enter a comment. */ + c = fgetc (fp); + if (c == '*') + /* Entered a comment, with no potential to leave. */ + comment = 2; + else + { + /* Just a slash in the open. */ + ungetc (c, fp); + c = '/'; + } + } + } + } + while (comment && c != EOF); + return c; +} + + +/* Match a string with a file. */ +static gint +match (FILE *fp, + const gchar *s) +{ + gint c; + + do + { + c = fgetc (fp); + if (c == *s) + s ++; + else + break; + } + while (c != EOF && *s); + + if (!*s) + return TRUE; + + if (c != EOF) + ungetc (c, fp); + return FALSE; +} + + +/* Read the next integer from the file, skipping all non-integers. */ +static gint +get_int (FILE *fp) +{ + int digval, base, val, c; + + do + c = cpp_fgetc (fp); + while (c != EOF && ! g_ascii_isdigit (c)); + + if (c == EOF) + return 0; + + /* Check for the base. */ + if (c == '0') + { + c = fgetc (fp); + if (c == 'x' || c == 'X') + { + c = fgetc (fp); + base = 16; + } + else if (g_ascii_isdigit (c)) + base = 8; + else + { + ungetc (c, fp); + return 0; + } + } + else + base = 10; + + val = 0; + for (;;) + { + digval = getval (c, base); + if (digval == -1) + { + ungetc (c, fp); + break; + } + val *= base; + val += digval; + c = fgetc (fp); + } + + return val; +} + + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + FILE *fp; + GeglBuffer *buffer; + gint32 image_ID; + gint32 layer_ID; + guchar *data; + gint intbits; + gint width = 0; + gint height = 0; + gint x_hot = 0; + gint y_hot = 0; + gint c, i, j, k; + gint tileheight, rowoffset; + gchar *comment; + + const guchar cmap[] = + { + 0x00, 0x00, 0x00, /* black */ + 0xff, 0xff, 0xff /* white */ + }; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + fp = g_fopen (filename, "rb"); + if (! fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + comment = fgetcomment (fp); + + /* Loosely parse the header */ + intbits = height = width = 0; + c = ' '; + do + { + if (g_ascii_isspace (c)) + { + if (match (fp, "char")) + { + c = fgetc (fp); + if (g_ascii_isspace (c)) + { + intbits = 8; + continue; + } + } + else if (match (fp, "short")) + { + c = fgetc (fp); + if (g_ascii_isspace (c)) + { + intbits = 16; + continue; + } + } + } + + if (c == '_') + { + if (match (fp, "width")) + { + c = fgetc (fp); + if (g_ascii_isspace (c)) + { + width = get_int (fp); + continue; + } + } + else if (match (fp, "height")) + { + c = fgetc (fp); + if (g_ascii_isspace (c)) + { + height = get_int (fp); + continue; + } + } + else if (match (fp, "x_hot")) + { + c = fgetc (fp); + if (g_ascii_isspace (c)) + { + x_hot = get_int (fp); + continue; + } + } + else if (match (fp, "y_hot")) + { + c = fgetc (fp); + if (g_ascii_isspace (c)) + { + y_hot = get_int (fp); + continue; + } + } + } + + c = cpp_fgetc (fp); + } + while (c != '{' && c != EOF); + + if (c == EOF) + { + g_message (_("'%s':\nCould not read header (ftell == %ld)"), + gimp_filename_to_utf8 (filename), ftell (fp)); + fclose (fp); + return -1; + } + + if (width <= 0) + { + g_message (_("'%s':\nNo image width specified"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + if (width > GIMP_MAX_IMAGE_SIZE) + { + g_message (_("'%s':\nImage width is larger than GIMP can handle"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + if (height <= 0) + { + g_message (_("'%s':\nNo image height specified"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + if (height > GIMP_MAX_IMAGE_SIZE) + { + g_message (_("'%s':\nImage height is larger than GIMP can handle"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + if (intbits == 0) + { + g_message (_("'%s':\nNo image data type specified"), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + image_ID = gimp_image_new (width, height, GIMP_INDEXED); + gimp_image_set_filename (image_ID, filename); + + if (comment) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (comment) + 1, (gpointer) comment); + gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + + g_free (comment); + } + + x_hot = CLAMP (x_hot, 0, width); + y_hot = CLAMP (y_hot, 0, height); + + if (x_hot > 0 || y_hot > 0) + { + GimpParasite *parasite; + gchar *str; + + str = g_strdup_printf ("%d %d", x_hot, y_hot); + parasite = gimp_parasite_new ("hot-spot", + GIMP_PARASITE_PERSISTENT, + strlen (str) + 1, (gpointer) str); + g_free (str); + gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + } + + /* Set a black-and-white colormap. */ + gimp_image_set_colormap (image_ID, cmap, 2); + + layer_ID = gimp_layer_new (image_ID, + _("Background"), + width, height, + GIMP_INDEXED_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_ID); + + /* Allocate the data. */ + tileheight = gimp_tile_height (); + data = (guchar *) g_malloc (width * tileheight); + + for (i = 0; i < height; i += tileheight) + { + tileheight = MIN (tileheight, height - i); + +#ifdef VERBOSE + if (verbose > 1) + printf ("XBM: reading %dx(%d+%d) pixel region\n", width, i, + tileheight); +#endif + + /* Parse the data from the file */ + for (j = 0; j < tileheight; j ++) + { + /* Read each row. */ + rowoffset = j * width; + for (k = 0; k < width; k ++) + { + /* Expand each integer into INTBITS pixels. */ + if (k % intbits == 0) + { + c = get_int (fp); + + /* Flip all the bits so that 1's become black and + 0's become white. */ + c ^= 0xffff; + } + + data[rowoffset + k] = c & 1; + c >>= 1; + } + } + + /* Put the data into the image. */ + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) (i + tileheight) / (double) height); + } + + g_free (data); + g_object_unref (buffer); + fclose (fp); + + gimp_progress_update (1.0); + + return image_ID; +} + +static gboolean +save_image (GFile *file, + const gchar *prefix, + const gchar *comment, + gboolean save_mask, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GOutputStream *output; + GeglBuffer *buffer; + GCancellable *cancellable; + gint width, height, colors, dark; + gint intbits, lineints, need_comma, nints, rowoffset, tileheight; + gint c, i, j, k, thisbit; + gboolean has_alpha; + gint bpp; + guchar *data = NULL; + guchar *cmap; + const gchar *intfmt; + +#if 0 + if (save_mask) + g_printerr ("%s: save_mask '%s'\n", G_STRFUNC, prefix); + else + g_printerr ("%s: save_image '%s'\n", G_STRFUNC, prefix); +#endif + + cmap = gimp_image_get_colormap (image_ID, &colors); + + if (! gimp_drawable_is_indexed (drawable_ID) || colors > 2) + { + /* The image is not black-and-white. */ + g_message (_("The image which you are trying to export as " + "an XBM contains more than two colors.\n\n" + "Please convert it to a black and white " + "(1-bit) indexed image and try again.")); + g_free (cmap); + return FALSE; + } + + has_alpha = gimp_drawable_has_alpha (drawable_ID); + + if (! has_alpha && save_mask) + { + g_message (_("You cannot save a cursor mask for an image\n" + "which has no alpha channel.")); + return FALSE; + } + + buffer = gimp_drawable_get_buffer (drawable_ID); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + bpp = gimp_drawable_bpp (drawable_ID); + + /* Figure out which color is black, and which is white. */ + dark = 0; + if (colors > 1) + { + gint first, second; + + /* Maybe the second color is darker than the first. */ + first = (cmap[0] * cmap[0]) + (cmap[1] * cmap[1]) + (cmap[2] * cmap[2]); + second = (cmap[3] * cmap[3]) + (cmap[4] * cmap[4]) + (cmap[5] * cmap[5]); + + if (second < first) + dark = 1; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_file_get_utf8_name (file)); + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (output) + { + GOutputStream *buffered; + + buffered = g_buffered_output_stream_new (output); + g_object_unref (output); + + output = buffered; + } + else + { + return FALSE; + } + + /* Maybe write the image comment. */ +#if 0 + /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */ + /* a future version should write the comment at the end of the file */ + if (*comment) + { + if (! print (output, error, "/* %s */\n", comment)) + goto fail; + } +#endif + + /* Write out the image height and width. */ + if (! print (output, error, "#define %s_width %d\n", prefix, width) || + ! print (output, error, "#define %s_height %d\n", prefix, height)) + goto fail; + + /* Write out the hotspot, if any. */ + if (xsvals.use_hot) + { + if (! print (output, error, + "#define %s_x_hot %d\n", prefix, xsvals.x_hot) || + ! print (output, error, + "#define %s_y_hot %d\n", prefix, xsvals.y_hot)) + goto fail; + } + + /* Now write the actual data. */ + if (xsvals.x10_format) + { + /* We can fit 9 hex shorts on a single line. */ + lineints = 9; + intbits = 16; + intfmt = " 0x%04x"; + } + else + { + /* We can fit 12 hex chars on a single line. */ + lineints = 12; + intbits = 8; + intfmt = " 0x%02x"; + } + + if (! print (output, error, + "static %s %s_bits[] = {\n ", + xsvals.x10_format ? "unsigned short" : "unsigned char", prefix)) + goto fail; + + /* Allocate a new set of pixels. */ + tileheight = gimp_tile_height (); + data = (guchar *) g_malloc (width * tileheight * bpp); + + /* Write out the integers. */ + need_comma = 0; + nints = 0; + for (i = 0; i < height; i += tileheight) + { + /* Get a horizontal slice of the image. */ + tileheight = MIN (tileheight, height - i); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 1.0, + NULL, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + +#ifdef VERBOSE + if (verbose > 1) + printf ("XBM: writing %dx(%d+%d) pixel region\n", + width, i, tileheight); +#endif + + for (j = 0; j < tileheight; j ++) + { + /* Write out a row at a time. */ + rowoffset = j * width * bpp; + c = 0; + thisbit = 0; + + for (k = 0; k < width * bpp; k += bpp) + { + if (k != 0 && thisbit == intbits) + { + /* Output a completed integer. */ + if (need_comma) + { + if (! print (output, error, ",")) + goto fail; + } + + need_comma = 1; + + /* Maybe start a new line. */ + if (nints ++ >= lineints) + { + nints = 1; + + if (! print (output, error, "\n ")) + goto fail; + } + + if (! print (output, error, intfmt, c)) + goto fail; + + /* Start a new integer. */ + c = 0; + thisbit = 0; + } + + /* Pack INTBITS pixels into an integer. */ + if (save_mask) + { + c |= ((data[rowoffset + k + 1] < 128) ? 0 : 1) << (thisbit ++); + } + else + { + if (has_alpha && (data[rowoffset + k + 1] < 128)) + c |= 0 << (thisbit ++); + else + c |= ((data[rowoffset + k] == dark) ? 1 : 0) << (thisbit ++); + } + } + + if (thisbit != 0) + { + /* Write out the last oddball int. */ + if (need_comma) + { + if (! print (output, error, ",")) + goto fail; + } + + need_comma = 1; + + /* Maybe start a new line. */ + if (nints ++ == lineints) + { + nints = 1; + + if (! print (output, error, "\n ")) + goto fail; + } + + if (! print (output, error, intfmt, c)) + goto fail; + } + } + + gimp_progress_update ((double) (i + tileheight) / (double) height); + } + + /* Write the trailer. */ + if (! print (output, error, " };\n")) + goto fail; + + if (! g_output_stream_close (output, NULL, error)) + goto fail; + + g_free (data); + g_object_unref (buffer); + g_object_unref (output); + + gimp_progress_update (1.0); + + return TRUE; + + fail: + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + + g_free (data); + g_object_unref (buffer); + g_object_unref (output); + + return FALSE; +} + +static gboolean +save_dialog (gint32 drawable_ID) +{ + GtkWidget *dialog; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *toggle; + GtkWidget *table; + GtkWidget *entry; + GtkWidget *spinbutton; + GtkAdjustment *adj; + gboolean run; + + dialog = gimp_export_dialog_new (_("XBM"), PLUG_IN_BINARY, SAVE_PROC); + + /* parameter settings */ + frame = gimp_frame_new (_("XBM Options")); + gtk_container_set_border_width (GTK_CONTAINER (frame), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + /* X10 format */ + toggle = gtk_check_button_new_with_mnemonic (_("_X10 format bitmap")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.x10_format); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &xsvals.x10_format); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* prefix */ + entry = gtk_entry_new (); + gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_PREFIX); + gtk_entry_set_text (GTK_ENTRY (entry), xsvals.prefix); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Identifier prefix:"), 0.0, 0.5, + entry, 1, TRUE); + g_signal_connect (entry, "changed", + G_CALLBACK (prefix_entry_callback), + NULL); + + /* comment string. */ +#if 0 + /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */ + entry = gtk_entry_new (); + gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_COMMENT); + gtk_widget_set_size_request (entry, 240, -1); + gtk_entry_set_text (GTK_ENTRY (entry), xsvals.comment); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Comment:"), 0.0, 0.5, + entry, 1, TRUE); + g_signal_connect (entry, "changed", + G_CALLBACK (comment_entry_callback), + NULL); +#endif + + /* hotspot toggle */ + toggle = gtk_check_button_new_with_mnemonic (_("_Write hot spot values")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.use_hot); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &xsvals.use_hot); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + g_object_bind_property (toggle, "active", + table, "sensitive", + G_BINDING_SYNC_CREATE); + + adj = (GtkAdjustment *) + gtk_adjustment_new (xsvals.x_hot, 0, + gimp_drawable_width (drawable_ID) - 1, + 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Hot spot _X:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &xsvals.x_hot); + + adj = (GtkAdjustment *) + gtk_adjustment_new (xsvals.y_hot, 0, + gimp_drawable_height (drawable_ID) - 1, + 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Hot spot _Y:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &xsvals.y_hot); + + /* mask file */ + frame = gimp_frame_new (_("Mask File")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + toggle = gtk_check_button_new_with_mnemonic (_("W_rite extra mask file")); + gtk_table_attach_defaults (GTK_TABLE (table), toggle, 0, 2, 0, 1); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.write_mask); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &xsvals.write_mask); + + entry = gtk_entry_new (); + gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_MASK_EXT); + gtk_entry_set_text (GTK_ENTRY (entry), xsvals.mask_ext); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Mask file extension:"), 0.0, 0.5, + entry, 1, TRUE); + g_signal_connect (entry, "changed", + G_CALLBACK (mask_ext_entry_callback), + NULL); + + g_object_bind_property (toggle, "active", + entry, "sensitive", + G_BINDING_SYNC_CREATE); + + gtk_widget_set_sensitive (frame, gimp_drawable_has_alpha (drawable_ID)); + + /* Done. */ + gtk_widget_show (vbox); + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static gboolean +print (GOutputStream *output, + GError **error, + const gchar *format, + ...) +{ + va_list args; + gboolean success; + + va_start (args, format); + success = g_output_stream_vprintf (output, NULL, NULL, + error, format, args); + va_end (args); + + return success; +} + +/* Update the comment string. */ +#if 0 +/* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */ +static void +comment_entry_callback (GtkWidget *widget, + gpointer data) +{ + memset (xsvals.comment, 0, sizeof (xsvals.comment)); + strncpy (xsvals.comment, + gtk_entry_get_text (GTK_ENTRY (widget)), MAX_COMMENT); +} +#endif + +static void +prefix_entry_callback (GtkWidget *widget, + gpointer data) +{ + memset (xsvals.prefix, 0, sizeof (xsvals.prefix)); + strncpy (xsvals.prefix, + gtk_entry_get_text (GTK_ENTRY (widget)), MAX_PREFIX); +} + +static void +mask_ext_entry_callback (GtkWidget *widget, + gpointer data) +{ + memset (xsvals.mask_ext, 0, sizeof (xsvals.mask_ext)); + strncpy (xsvals.mask_ext, + gtk_entry_get_text (GTK_ENTRY (widget)), MAX_MASK_EXT); +} diff --git a/plug-ins/common/file-xmc.c b/plug-ins/common/file-xmc.c new file mode 100644 index 0000000..a5c41f7 --- /dev/null +++ b/plug-ins/common/file-xmc.c @@ -0,0 +1,2492 @@ +/* + * X11 Mouse Cursor (XMC) plug-in for GIMP + * + * Copyright 2008-2009 Takeshi Matsuyama + * + * Special thanks: Alexia Death, Sven Neumann, Martin Nordholts + * and all community members. + */ + +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Todo: if drawable->bpp != 4 in save_image for GIMP-2.8? + * Todo: support for "gimp-metadata" parasite. + * "xmc-copyright" and "xmc-license" may be deprecated in future? + */ + +/* + * This plug-in use these four parasites. + * "hot-spot" common with file-xbm plug-in + * "xmc-copyright" original, store contents of type1 comment chunk of Xcursor + * "xmc-license" original, store contents of type2 comment chunk of Xcursor + * "gimp-comment" common, store contents of type3 comment chunk of Xcursor + */ + +/* *** Caution: Size vs Dimension *** + * + * In this file, "size" and "dimension" are used in definitely + * different contexts. "Size" means nominal size of Xcursor which is + * used to determine which frame depends on which animation sequence + * and which sequence is really used. (for more detail, please read + * Xcursor(3).) On the other hand, "Dimension" simply means width + * and/or height. + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +/* For debug */ +/* #define XMC_DEBUG */ +#ifdef XMC_DEBUG +# define DM_XMC(...) g_fprintf(stderr, __VA_ARGS__) +#else +# define DM_XMC(...) +#endif + +/* + * Constants... + */ + +#define LOAD_PROC "file-xmc-load" +#define LOAD_THUMB_PROC "file-xmc-load-thumb" +#define SAVE_PROC "file-xmc-save" + +#define PLUG_IN_BINARY "file-xmc" +#define PLUG_IN_ROLE "gimp-file-xmc" + +/* We use "xmc" as the file extension of X cursor for convenience */ +#define XCURSOR_EXTENSION "xmc" +#define XCURSOR_MIME_TYPE "image/x-xcursor" + +/* The maximum dimension of Xcursor which is fully supported in any + * environments. This is defined on line 59 of xcursorint.h in + * libXcursor source code. Make sure this is about dimensions (width + * and height) not about nominal size despite of it's name. + * + * As of 2018, this macro still exists in libXCursor codebase, but I am + * unsure how far this restriction is enforced since this is a very low + * max dimension for today's displays. Therefore our code will not + * enforce this value anymore, but only warn about possible + * incompatibilities when using higher values. + */ +#define MAX_BITMAP_CURSOR_SIZE 64 + +/* The maximum dimension of each frame of X cursor we want to save + * should be MAX_BITMAP_CURSOR_SIZE but about loading, xhot(& yhot) of + * each frame varies from 0 to MAX_BITMAP_CURSOR_SIZE-1, so we need to + * set the maximum dimension of image no less than + * MAX_BITMAP_CURSOR_SIZE * 2( -1 to be precise) to remain hotspots on + * the same coordinates. + * + * We use four times value (256 for saving, 512 for loading) as a + * limitation because some cursors generated by CursorXP/FX to X11 + * Mouse Theme Converter is very large. + * + * The biggest cursor I found is "watch" of OuterLimits which size is + * 213x208. If you found bigger one, please tell me ;-) + */ +#define MAX_LOAD_DIMENSION 512 +#define MAX_SAVE_DIMENSION 256 + +/* The maximum number of different nominal sizes in one cursor this + * plug-in can treat. This is based on the number of cursor size which + * gnome-appearance-properties supports.(12,16,24,32,36,40,48,64) + * ref. capplets/common/gnome-theme-info.c in source of + * gnome-control-center + */ +#define MAX_SIZE_NUM 8 + +/* cursor delay is guint32 defined in Xcursor.h */ +#define CURSOR_MAX_DELAY 100000000 +#define CURSOR_DEFAULT_DELAY 50 +#define CURSOR_MINIMUM_DELAY 5 + +#define div_255(x) (((x) + 0x80 + (((x) + 0x80) >> 8)) >> 8) +#define READ32(f, e) read32 ((f), (e)); if (*(e)) return -1; +#define DISPLAY_DIGIT(x) ((x) > 100) ? 3 : ((x) > 10) ? 2 : 1 + +/* + * Structures... + */ + +typedef struct +{ + gboolean crop; + gint size; + gboolean size_replace; + gint32 delay; + gboolean delay_replace; +} XmcSaveVals; + +/* + * Local functions... + */ + +static void query (void); + +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); + +static gint32 load_thumbnail (const gchar *filename, + gint32 thumb_size, + gint32 *width, + gint32 *height, + gint32 *num_layers, + GError **error); + +static guint32 read32 (FILE *f, + GError **error); + +static gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + GError **error); + +static gboolean save_dialog (const gint32 image_ID, + GeglRectangle *hotspotRange); + +static void comment_entry_callback (GtkWidget *widget, + gchar **commentp); + +static void text_view_callback (GtkTextBuffer *buffer, + gchar **commentp); + +static gboolean load_default_hotspot (const gint32 image_ID, + GeglRectangle *hotspotRange); + +static inline guint32 separate_alpha (guint32 pixel); + +static inline guint32 premultiply_alpha (guint32 pixel); + +static XcursorComments *set_cursor_comments (void); + +static void load_comments (const gint32 image_ID); + +static gboolean set_comment_to_pname (const gint32 image_ID, + const gchar *content, + const gchar *pname); + +static gchar *get_comment_from_pname (const gint32 image_ID, + const gchar *pname); + +static gboolean set_hotspot_to_parasite (gint32 image_ID); + +static gboolean get_hotspot_from_parasite (gint32 image_ID); + +static void set_size_and_delay (const gchar *framename, + guint32 *sizep, + guint32 *delayp, + GRegex *re, + gboolean *size_warnp); + +static gchar *make_framename (guint32 size, + guint32 delay, + guint indent, + GError **errorp); + +static void get_cropped_region (GeglRectangle *retrun_rgn, + GeglBuffer *buffer); + +static inline gboolean pix_is_opaque (guint32 pix); + +static GeglRectangle * get_intersection_of_frames (gint32 image_ID); + +static gboolean pix_in_region (gint32 x, + gint32 y, + GeglRectangle *xmcrp); + +static void find_hotspots_and_dimensions (XcursorImages *xcIs, + gint32 *xhot, + gint32 *yhot, + gint32 *width, + gint32 *height); + +/* + * Globals... + */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run +}; + +static XmcSaveVals xmcvals = +{ + /* saved in pdb after this plug-in's process has gone. */ + FALSE, /* crop */ + 32, /* size */ + FALSE, /* size_replace */ + CURSOR_DEFAULT_DELAY, /* delay */ + FALSE /* delay_replace */ +}; + +static struct +{ + /* saved as parasites of original image after this plug-in's process has gone.*/ + gint32 x; /* hotspot x */ + gint32 y; /* hotspot y */ + gchar *comments[3]; /* copyright, license, other */ +} xmcparas = {0,}; + +/* parasites correspond to XcursorComment type */ +static const gchar *parasiteName[3] = { "xmc-copyright", + "xmc-license", + "gimp-comment" }; + +/* + * 'main()' - Main entry - just call gimp_main()... + */ + +MAIN () + + +/* + * 'query()' - Respond to a plug-in query... + */ + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef thumb_args[] = + { + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_INT32, "thumb-size", "Preferred thumbnail size" } + }; + static const GimpParamDef thumb_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Thumbnail image" }, + { GIMP_PDB_INT32, "image-width", "The width of image" }, + { GIMP_PDB_INT32, "image-height", "The height of image" }, + { GIMP_PDB_INT32, "image-type", "The color type of image"}, + { GIMP_PDB_INT32, "image-num-layers", "The number of layeres" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + /* following elements are XMC specific options */ + { GIMP_PDB_INT32, "x_hot", "X-coordinate of hot spot" }, + { GIMP_PDB_INT32, "y_hot", "Y-coordinate of hot spot\n" + "Use (-1, -1) to keep original hot spot."}, + { GIMP_PDB_INT32, "crop", "Auto-crop or not" }, + { GIMP_PDB_INT32, "size", "Default nominal size" }, + { GIMP_PDB_INT32, "size_replace", "Replace existent size or not." }, + { GIMP_PDB_INT32, "delay", "Default delay" }, + { GIMP_PDB_INT32, "delay_replace","Replace existent delay or not."}, + { GIMP_PDB_STRING, "copyright", "Copyright information." }, + { GIMP_PDB_STRING, "license", "License information." }, + { GIMP_PDB_STRING, "other", "Other comment.(taken from " + "\"gimp-comment\" parasite)" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files of X11 Mouse Cursor file format", + "This plug-in loads X11 Mouse Cursor (XMC) files.", + "Takeshi Matsuyama ", + "Takeshi Matsuyama", + "26 May 2009", + N_("X11 Mouse Cursor"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, + load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, XCURSOR_MIME_TYPE); + gimp_register_magic_load_handler (LOAD_PROC, + XCURSOR_EXTENSION, + "", + "0,string,Xcur"); + + gimp_install_procedure (LOAD_THUMB_PROC, + "Loads only first frame of X11 Mouse Cursor's " + "animation sequence which nominal size is the closest " + "of thumb-size to be used as a thumbnail", + "", + "Takeshi Matsuyama ", + "Takeshi Matsuyama", + "26 May 2009", + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (thumb_args), + G_N_ELEMENTS (thumb_return_vals), + thumb_args, + thumb_return_vals); + + gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC); + + gimp_install_procedure (SAVE_PROC, + "Exports files of X11 cursor file", + "This plug-in exports X11 Mouse Cursor (XMC) files", + "Takeshi Matsuyama ", + "Takeshi Matsuyama", + "26 May 2009", + N_("X11 Mouse Cursor"), + "RGBA", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, XCURSOR_MIME_TYPE); + gimp_register_save_handler (SAVE_PROC, XCURSOR_EXTENSION, ""); +} + +/* + * 'run()' - Run the plug-in... + */ + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[6]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + gint32 orig_image_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GeglRectangle *hotspotRange = NULL; + gint32 width, height; + gint32 num_layers; + GError *error = NULL; + gint i; + + INIT_I18N (); + gegl_init (NULL, NULL); + + DM_XMC ("run: start.\n"); + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + DM_XMC ("Starting to load file.\tparam.data=%s\n", + param[1].data.d_string); + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + DM_XMC ("LOAD_PROC successfully load image. image_ID=%i\n", image_ID); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + DM_XMC ("Starting to load thumbnail.\tfilename=%s\tthumb-size=%d\n", + param[0].data.d_string, param[1].data.d_int32); + image_ID = load_thumbnail (param[0].data.d_string, + param[1].data.d_int32, + &width, + &height, + &num_layers, + &error); + + if (image_ID != -1) + { + *nreturn_vals = 6; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + values[2].type = GIMP_PDB_INT32; + values[2].data.d_int32 = width; /* width */ + values[3].type = GIMP_PDB_INT32; + values[3].data.d_int32 = height; /* height */ + /* This will not work on GIMP 2.6, but not harmful. */ + values[4].type = GIMP_PDB_INT32; + values[4].data.d_int32 = GIMP_RGBA_IMAGE; /* type */ + values[5].type = GIMP_PDB_INT32; + values[5].data.d_int32 = num_layers; /* num_layers */ + + DM_XMC ("LOAD_THUMB_PROC successfully load image. image_ID=%i\n", image_ID); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + DM_XMC ("run: export %s\n", name); + run_mode = param[0].data.d_int32; + image_ID = orig_image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + hotspotRange = get_intersection_of_frames (image_ID); + + if (! hotspotRange) + { + g_set_error (&error, 0, 0, + _("Cannot set the hot spot!\n" + "You must arrange layers so that all of them have an intersection.")); + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + return; + } + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "XMC", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_ALPHA | + GIMP_EXPORT_CAN_HANDLE_LAYERS | + GIMP_EXPORT_NEEDS_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + *nreturn_vals = 1; + values[0].data.d_status = GIMP_PDB_CANCEL; + + return; + } + break; + + default: + break; + } + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* + * Possibly retrieve data... + */ + gimp_get_data (SAVE_PROC, &xmcvals); + load_comments (image_ID); + + load_default_hotspot (image_ID, hotspotRange); + + if (! save_dialog (image_ID, hotspotRange)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* + * Make sure all the arguments are there! + */ + if (nparams != 15) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + if (pix_in_region (param[5].data.d_int32, param[6].data.d_int32, + hotspotRange)) + { /* if passed hotspot is acceptable, use that ones. */ + xmcparas.x = param[5].data.d_int32; + xmcparas.y = param[6].data.d_int32; + } + else + { + load_default_hotspot (image_ID, hotspotRange); + /* you can purposely choose non acceptable values for hotspot + to use cursor's original values. */ + } + xmcvals.crop = param[7].data.d_int32; + xmcvals.size = param[8].data.d_int32; + xmcvals.size_replace = param[9].data.d_int32; + /* load delay */ + if (param[10].data.d_int32 < CURSOR_MINIMUM_DELAY) + { + xmcvals.delay = CURSOR_DEFAULT_DELAY; + } + else + { + xmcvals.delay = param[10].data.d_int32; + } + xmcvals.delay_replace = param[11].data.d_int32; + load_comments (image_ID); + for (i = 0; i < 3; ++i) + { + if (param[i + 12].data.d_string && + g_utf8_validate (param[i + 12].data.d_string, -1, NULL)) + { + xmcparas.comments[i] = g_strdup (param[i + 12].data.d_string); + } + } + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data... */ + gimp_get_data (SAVE_PROC, &xmcvals); + load_comments (image_ID); + load_default_hotspot (image_ID, hotspotRange); + break; + + default: + break; + } + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (param[3].data.d_string, image_ID, + drawable_ID, orig_image_ID, &error)) + { + gimp_set_data (SAVE_PROC, &xmcvals, sizeof (XmcSaveVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + g_free (hotspotRange); + + for (i = 0; i < 3 ; ++i) + { + g_free (xmcparas.comments[i]); + xmcparas.comments[i] = NULL; + } + } + else + { + DM_XMC ("name=%s\n", name); + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; + DM_XMC ("run: finish\n"); +} + + +/* + * 'load_image()' - Load a X cursor image into a new image window. + */ + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + FILE *fp; + gint32 image_ID; + gint32 layer_ID; + GeglBuffer *buffer; + XcursorComments *commentsp; /* pointer to comments */ + XcursorImages *imagesp; /* pointer to images*/ + guint32 delay; /* use guint32 instead CARD32(in X11/Xmd.h) */ + gchar *framename; /* name of layer */ + guint32 *tmppixel; /* pixel data (guchar * bpp = guint32) */ + gint img_width; + gint img_height; + gint i, j; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + /* Open the file and check it is a valid X cursor */ + + fp = g_fopen (filename, "rb"); + + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + if (! XcursorFileLoad (fp, &commentsp, &imagesp)) + { + g_set_error (error, 0, 0, _("'%s' is not a valid X cursor."), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + /* check dimension is valid. */ + + for (i = 0; i < imagesp->nimage; i++) + { + if (imagesp->images[i]->width > MAX_LOAD_DIMENSION) + { + g_set_error (error, 0, 0, + _("Frame %d of '%s' is too wide for an X cursor."), + i + 1, gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + if (imagesp->images[i]->height > MAX_LOAD_DIMENSION) + { + g_set_error (error, 0, 0, + _("Frame %d of '%s' is too high for an X cursor."), + i + 1, gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + } + + find_hotspots_and_dimensions (imagesp, + &xmcparas.x, &xmcparas.y, + &img_width, &img_height); + + DM_XMC ("xhot=%i,\tyhot=%i,\timg_width=%i,\timg_height=%i\n", + xmcparas.x, xmcparas.y, img_width, img_height); + + image_ID = gimp_image_new (img_width, img_height, GIMP_RGB); + + gimp_image_set_filename (image_ID, filename); + + if (! set_hotspot_to_parasite (image_ID)) + { + fclose (fp); + return -1; + } + + /* Temporary buffer */ + tmppixel = g_new (guint32, img_width * img_height); + + /* load each frame to each layer one by one */ + for (i = 0; i < imagesp->nimage; i++) + { + gint width = imagesp->images[i]->width; + gint height = imagesp->images[i]->height; + + delay = imagesp->images[i]->delay; + + if (delay < CURSOR_MINIMUM_DELAY) + { + delay = CURSOR_DEFAULT_DELAY; + } + + DM_XMC ("images[%i]->delay=%i\twidth=%d\theight=%d\n", + i ,delay, imagesp->images[i]->width, imagesp->images[i]->height); + + framename = make_framename (imagesp->images[i]->size, delay, + DISPLAY_DIGIT (imagesp->nimage), error); + if (! framename) + { + fclose (fp); + return -1; + } + + layer_ID = gimp_layer_new (image_ID, framename, width, height, + GIMP_RGBA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + /* Adjust layer position to let hotspot sit on the same point. */ + gimp_item_transform_translate (layer_ID, + xmcparas.x - imagesp->images[i]->xhot, + xmcparas.y - imagesp->images[i]->yhot); + + g_free (framename); + + /* Get the buffer for our load... */ + + buffer = gimp_drawable_get_buffer (layer_ID); + + /* set color to each pixel */ + for (j = 0; j < width * height; j++) + { + tmppixel[j] = separate_alpha (imagesp->images[i]->pixels[j]); + } + + /* set pixel */ + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0, + NULL, tmppixel, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((i + 1) / imagesp->nimage); + + g_object_unref (buffer); + } + + g_free (tmppixel); + + gimp_progress_update (1.0); + + /* Comment parsing */ + + if (commentsp) + { + for (i = 0; i < commentsp->ncomment; ++i) + { + DM_XMC ("comment type=%d\tcomment=%s\n", + commentsp->comments[i]->comment_type, + commentsp->comments[i]->comment); + if (! set_comment_to_pname (image_ID, + commentsp->comments[i]->comment, + parasiteName[commentsp->comments[i]->comment_type -1])) + { + DM_XMC ("Failed to write %ith comment.\n", i); + fclose (fp); + return -1; + } + } + } + + DM_XMC ("Comment parsing done.\n"); + XcursorImagesDestroy (imagesp); + XcursorCommentsDestroy (commentsp); + fclose (fp); + + gimp_progress_end (); + + return image_ID; +} + +/* + * load_thumbnail + */ + +static gint32 +load_thumbnail (const gchar *filename, + gint32 thumb_size, + gint32 *thumb_width, + gint32 *thumb_height, + gint32 *thumb_num_layers, + GError **error) +{ + /* Return only one frame for thumbnail. + * We select first frame of an animation sequence which nominal size is the + * closest of thumb_size. + */ + + XcursorImages *xcIs = NULL; /* use to find the dimensions of thumbnail */ + XcursorImage *xcI; /* temporary pointer to XcursorImage */ + guint32 *positions; /* array of the offsets of image chunks */ + guint32 size; /* nominal size */ + guint32 diff; /* difference between thumb_size and current size */ + guint32 min_diff = XCURSOR_IMAGE_MAX_SIZE; /* minimum value of diff */ + guint32 type; /* chunk type */ + FILE *fp = NULL; + gint32 image_ID = -1; + gint32 layer_ID; + GeglBuffer *buffer; + guint32 *tmppixel; /* pixel data (guchar * bpp = guint32) */ + guint32 ntoc = 0; /* the number of table of contents */ + gint sel_num = -1; /* the index of selected image chunk */ + gint width; + gint height; + gint i; + + g_return_val_if_fail (thumb_width, -1); + g_return_val_if_fail (thumb_height, -1); + g_return_val_if_fail (thumb_num_layers, -1); + + *thumb_width = 0; + *thumb_height = 0; + *thumb_num_layers = 0; + + fp = g_fopen (filename, "rb"); + + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + /* From this line, we make a XcursorImages struct so that we can find out the + * width and height of entire image. + * We can use XcursorFileLoadImages (fp, thumb_size) from libXcursor instead + * of this ugly code but XcursorFileLoadImages loads all pixel data of the + * image chunks on memory thus we should not use it. + */ + + /* find which image chunk is preferred to load. */ + + /* skip magic, headersize, version */ + fseek (fp, 12, SEEK_SET); + /* read the number of chunks */ + ntoc = READ32 (fp, error) + if (ntoc > (G_MAXUINT32 / sizeof (guint32))) + { + g_set_error (error, 0, 0, + "'%s' seems to have an incorrect toc size.", + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + positions = g_malloc (ntoc * sizeof (guint32)); + + /* enter list of toc(table of contents) */ + for (; ntoc > 0; --ntoc) + { + /* read entry type */ + type = READ32 (fp, error) + if (type != XCURSOR_IMAGE_TYPE) + { + /* not a image */ + + /* skip rest of this content */ + fseek (fp, 8, SEEK_CUR); + } + else + { + /* this content is image */ + + size = READ32 (fp, error) + positions[*thumb_num_layers] = READ32 (fp, error) + /* is this image is more preferred than selected before? */ + diff = MAX (thumb_size, size) - MIN (thumb_size, size); + if (diff < min_diff) + {/* the image size is closer than current selected image */ + min_diff = diff; + sel_num = *thumb_num_layers; + } + ++*thumb_num_layers; + } + } + + if (sel_num < 0) + { + g_set_error (error, 0, 0, + _("there is no image chunk in \"%s\"."), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + /* get width and height of entire image */ + + /* Let's make XcursorImages */ + xcIs = XcursorImagesCreate (*thumb_num_layers); + xcIs->nimage = *thumb_num_layers; + for (i = 0; i < xcIs->nimage; ++i) + { + /* make XcursorImage with no pixel buffer */ + xcI = XcursorImageCreate (0, 0); + /* go to the image chunk header */ + fseek (fp, positions[i], SEEK_SET); + /* skip chunk header */ + fseek (fp, 16, SEEK_CUR); + /* read properties of this image to determine entire image dimensions */ + xcI->width = READ32 (fp, error) + xcI->height = READ32 (fp, error) + xcI->xhot = READ32 (fp, error) + xcI->yhot = READ32 (fp, error) + + xcIs->images[i] = xcI; + } + + DM_XMC ("selected size is %i or %i\n", + thumb_size - min_diff, thumb_size + min_diff); + + /* get entire image dimensions */ + find_hotspots_and_dimensions (xcIs, NULL, NULL, thumb_width, thumb_height); + + DM_XMC ("width=%i\theight=%i\tnum-layers=%i\n", + *thumb_width, *thumb_height, xcIs->nimage); + + /* dimension check */ + if (*thumb_width > MAX_LOAD_DIMENSION) + { + g_set_error (error, 0, 0, + _("'%s' is too wide for an X cursor."), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + if (*thumb_height > MAX_LOAD_DIMENSION) + { + g_set_error (error, 0, 0, + _("'%s' is too high for an X cursor."), + gimp_filename_to_utf8 (filename)); + fclose (fp); + return -1; + } + + /* create new image! */ + + width = xcIs->images[sel_num]->width; + height = xcIs->images[sel_num]->height; + + image_ID = gimp_image_new (width, height, GIMP_RGB); + + layer_ID = gimp_layer_new (image_ID, NULL, width, height, + GIMP_RGBA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + /* + * Get the drawable and set the pixel region for our load... + */ + + buffer = gimp_drawable_get_buffer (layer_ID); + + /* Temporary buffer */ + tmppixel = g_new (guint32, width * height); + + /* copy the chunk data to tmppixel */ + fseek (fp, positions[sel_num], SEEK_SET); + fseek (fp, 36, SEEK_CUR); /* skip chunk header(16bytes), xhot, yhot, width, height, delay */ + + for (i = 0; i < width * height; i++) + { + tmppixel[i] = READ32 (fp, error) + /* get back separate alpha */ + tmppixel[i] = separate_alpha (tmppixel[i]); + } + + /* set pixel */ + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0, + NULL, tmppixel, GEGL_AUTO_ROWSTRIDE); + + /* free tmppixel */ + g_free(tmppixel); + g_free (positions); + fclose (fp); + + g_object_unref (buffer); + + return image_ID; +} + +/* read guint32 value from f despite of host's byte order. */ +static guint32 +read32 (FILE *f, + GError **error) +{ + guchar p[4]; + guint32 ret; + + if (fread (p, 1, 4, f) != 4) + { + g_set_error (error, 0, 0, _("A read error occurred.")); + return 0; + } + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + ret = p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24); +#elif G_BYTE_ORDER == G_BIG_ENDIAN + ret = p[3] + (p[2]<<8) + (p[1]<<16) + (p[0]<<24); +#elif G_BYTE_ORDER == G_PDP_ENDIAN + ret = p[2] + (p[3]<<8) + (p[0]<<16) + (p[1]<<24); +#else + g_return_val_if_rearched (); +#endif + + return ret; +} + +/* 'save_dialog ()' + */ +static gboolean +save_dialog (const gint32 image_ID, + GeglRectangle *hotspotRange) +{ + GtkWidget *dialog; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *box; + GtkAdjustment *adjustment; + GtkWidget *alignment; + GtkWidget *tmpwidget; + GtkWidget *label; + GtkTextBuffer *textbuffer; + GValue val = G_VALUE_INIT; + gint x1, x2, y1, y2; + gboolean run; + + g_value_init (&val, G_TYPE_DOUBLE); + dialog = gimp_export_dialog_new (_("X11 Mouse Cursor"), + PLUG_IN_BINARY, SAVE_PROC); + + /* + * parameter settings + */ + frame = gimp_frame_new (_("XMC Options")); + gtk_container_set_border_width (GTK_CONTAINER (frame), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (9, 3, FALSE); + gtk_widget_show (table); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_container_add (GTK_CONTAINER (frame), table); + + /* + * Hotspot + */ + /* label "Hot spot _X:" + spinbox */ + x1 = hotspotRange->x; + x2 = hotspotRange->width + hotspotRange->x - 1; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (xmcparas.x, x1, x2, 1, 5, 0); + tmpwidget = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (tmpwidget), TRUE); + g_value_set_double (&val, 1.0); + g_object_set_property (G_OBJECT (tmpwidget), "xalign", &val);/* align right*/ + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Hot spot _X:"), 0, 0.5, tmpwidget, 1, TRUE); + gtk_widget_show (tmpwidget); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &xmcparas.x); + + gimp_help_set_help_data (tmpwidget, + _("Enter the X coordinate of the hot spot. " + "The origin is top left corner."), + NULL); + + /* label "Y:" + spinbox */ + y1 = hotspotRange->y; + y2 = hotspotRange->height + hotspotRange->y - 1; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (xmcparas.y, y1, y2, 1, 5, 0); + tmpwidget = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (tmpwidget), TRUE); + g_value_set_double (&val, 1.0); + g_object_set_property (G_OBJECT (tmpwidget), "xalign", &val);/* align right*/ + gimp_table_attach_aligned (GTK_TABLE (table), 1, 0, + "_Y:", 1.0, 0.5, tmpwidget, 1, TRUE); + gtk_widget_show (tmpwidget); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &xmcparas.y); + + gimp_help_set_help_data (tmpwidget, + _("Enter the Y coordinate of the hot spot. " + "The origin is top left corner."), + NULL); + + /* + * Auto-crop + */ + /* check button */ + tmpwidget = + gtk_check_button_new_with_mnemonic (_("_Auto-Crop all frames.")); + gtk_table_attach (GTK_TABLE (table), + tmpwidget, 0, 3, 1, 2, GTK_FILL, 0, 0, 10); + gtk_widget_show (tmpwidget); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tmpwidget), + xmcvals.crop); + gtk_widget_show (tmpwidget); + g_signal_connect (tmpwidget, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &xmcvals.crop); + /* tooltip */ + gimp_help_set_help_data (tmpwidget, + _("Remove the empty borders of all frames.\n" + "This reduces the file size and may fix " + "the problem that some large cursors disorder " + "the screen.\n" + "Uncheck if you plan to edit the exported " + "cursor using other programs."), + NULL); + + /* + * size + */ + tmpwidget = + gimp_int_combo_box_new ("12px", 12, "16px", 16, + "24px", 24, "32px", 32, + "36px", 36, "40px", 40, + "48px", 48, "64px", 64, NULL); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (tmpwidget), + 32, + G_CALLBACK (gimp_int_combo_box_get_active), + &xmcvals.size); + gtk_widget_show (tmpwidget); + /* tooltip */ + gimp_help_set_help_data (tmpwidget, + _("Choose the nominal size of frames.\n" + "If you don't have plans to make multi-sized " + "cursor, or you have no idea, leave it \"32px\".\n" + "Nominal size has no relation with the actual " + "size (width or height).\n" + "It is only used to determine which frame depends " + "on which animation sequence, and which sequence " + "is used based on the value of " + "\"gtk-cursor-theme-size\"."), + NULL); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_Size:"), 0, 0.5, tmpwidget, 3, TRUE); + /* Replace size ? */ + tmpwidget = + gimp_int_radio_group_new (FALSE, NULL, G_CALLBACK (gimp_radio_button_update), + &xmcvals.size_replace, xmcvals.size_replace, + _("_Use this value only for a frame which size " + "is not specified."), + FALSE, NULL, + _("_Replace the size of all frames even if it " + "is specified."), + TRUE, NULL, + NULL); + alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_widget_show (alignment); + gtk_table_attach (GTK_TABLE (table), alignment, 0, 3, 3, 4, 0, 0, 0, 0); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 6, 20, 0); /*padding left*/ + gtk_container_add (GTK_CONTAINER (alignment), tmpwidget); + gtk_widget_show (tmpwidget); + + /* + * delay + */ + /* spin button */ + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, _("_Delay:"), + 0, 0.5, box, 3, TRUE); + gtk_widget_show (box); + + gimp_help_set_help_data (box, + _("Enter time span in milliseconds in which " + "each frame is rendered."), + NULL); + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (xmcvals.delay, CURSOR_MINIMUM_DELAY, + CURSOR_MAX_DELAY, 1, 5, 0); + tmpwidget = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (tmpwidget), TRUE); + g_value_set_double (&val, 1.0); + g_object_set_property (G_OBJECT (tmpwidget), "xalign", &val);/* align right*/ + gtk_box_pack_start (GTK_BOX (box), tmpwidget, TRUE, TRUE, 0); + gtk_widget_show (tmpwidget); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &xmcvals.delay); + + /* appended "ms" */ + tmpwidget = gtk_label_new ("ms"); + gtk_label_set_xalign (GTK_LABEL (tmpwidget), 0.0); /*align left*/ + gtk_box_pack_start (GTK_BOX (box), tmpwidget, TRUE, TRUE, 0); + gtk_widget_show (tmpwidget); + + /* Replace delay? */ + tmpwidget = + gimp_int_radio_group_new (FALSE, NULL, G_CALLBACK (gimp_radio_button_update), + &xmcvals.delay_replace, xmcvals.delay_replace, + _("_Use this value only for a frame which delay " + "is not specified."), + FALSE, NULL, + _("_Replace the delay of all frames even if it " + "is specified."), + TRUE, NULL, + NULL); + alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_widget_show (alignment); + gtk_table_attach (GTK_TABLE (table), alignment, 0, 3, 5, 6, 0, 0, 0, 0); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 6, 20, 0); /*padding left*/ + gtk_container_add (GTK_CONTAINER (alignment), tmpwidget); + gtk_widget_show (tmpwidget); + + /* + * Copyright + */ + tmpwidget = gtk_entry_new (); + /* Maximum length will be clamped to 65536 */ + gtk_entry_set_max_length (GTK_ENTRY (tmpwidget), XCURSOR_COMMENT_MAX_LEN); + + if (xmcparas.comments[0]) + { + gtk_entry_set_text (GTK_ENTRY (tmpwidget), + gimp_any_to_utf8 (xmcparas.comments[0], - 1, NULL)); + /* show warning if comment is over 65535 characters + * because gtk_entry can hold only that. */ + if (strlen (gtk_entry_get_text (GTK_ENTRY (tmpwidget))) >= 65535) + g_message (_("The part of copyright information " + "that exceeded 65535 characters was removed.")); + } + + g_signal_connect (tmpwidget, "changed", + G_CALLBACK (comment_entry_callback), + xmcparas.comments); + gtk_widget_show (tmpwidget); + /* tooltip */ + gimp_help_set_help_data (tmpwidget, + _("Enter copyright information."), + NULL); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 6, _("_Copyright:"), + 0, 0.5, tmpwidget, 3, FALSE); + /* + * License + */ + tmpwidget = gtk_entry_new (); + /* Maximum length will be clamped to 65536 */ + gtk_entry_set_max_length (GTK_ENTRY (tmpwidget), XCURSOR_COMMENT_MAX_LEN); + + if (xmcparas.comments[1]) + { + gtk_entry_set_text (GTK_ENTRY (tmpwidget), + gimp_any_to_utf8 (xmcparas.comments[1], - 1, NULL)); + /* show warning if comment is over 65535 characters + * because gtk_entry can hold only that. */ + if (strlen (gtk_entry_get_text (GTK_ENTRY (tmpwidget))) >= 65535) + g_message (_("The part of license information " + "that exceeded 65535 characters was removed.")); + } + + g_signal_connect (tmpwidget, "changed", + G_CALLBACK (comment_entry_callback), + xmcparas.comments + 1); + gtk_widget_show (tmpwidget); + /* tooltip */ + gimp_help_set_help_data (tmpwidget, + _("Enter license information."), + NULL); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 7, _("_License:"), + 0, 0.5, tmpwidget, 3, FALSE); + /* + * Other + */ + /* We use gtk_text_view for "Other" while "Copyright" & "License" is entered + * in gtk_entry because We want allow '\n' for "Other". */ + label = gtk_label_new_with_mnemonic (_("_Other:")); + gtk_widget_show (label); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); /*align top-left*/ + gtk_label_set_yalign (GTK_LABEL (label), 0.0); /*align top-left*/ + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 8, 9, GTK_FILL, 0, 0, 0); + /* content of Other */ + /* scrolled window */ + box = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_table_attach (GTK_TABLE (table), box, 1, 3, 8, 9, GTK_FILL, 0, 0, 0); + gtk_widget_show (box); + /* textbuffer */ + textbuffer = gtk_text_buffer_new (NULL); + if (xmcparas.comments[2]) + gtk_text_buffer_set_text (textbuffer, + gimp_any_to_utf8 (xmcparas.comments[2], -1, NULL), + -1); + g_signal_connect (textbuffer, "changed", + G_CALLBACK (text_view_callback), + xmcparas.comments + 2); + /* textview */ + tmpwidget = + gtk_text_view_new_with_buffer (GTK_TEXT_BUFFER (textbuffer)); + gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (tmpwidget), FALSE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tmpwidget), GTK_WRAP_WORD); + g_object_unref (textbuffer); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tmpwidget), GTK_WRAP_WORD); + gtk_container_add (GTK_CONTAINER (box), tmpwidget); + gtk_widget_show (tmpwidget); + /* tooltip */ + gimp_help_set_help_data (tmpwidget, + _("Enter other comment if you want."), + NULL); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), tmpwidget); + + /* + * all widget is prepared. Let's show dialog. + */ + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +/* + * callback function of gtk_entry for "copyright" and "license". + * "other" is processed by text_view_callback + */ +static void +comment_entry_callback (GtkWidget *widget, + gchar **commentp) +{ + const gchar *text; + + g_return_if_fail (commentp); + + text = gtk_entry_get_text (GTK_ENTRY (widget)); + + /* This will not happen because sizeof(gtk_entry) < XCURSOR_COMMENT_MAX_LEN */ + g_return_if_fail (strlen (text) <= XCURSOR_COMMENT_MAX_LEN); + + g_free (*commentp); + *commentp = g_strdup (text); +} + +static void +text_view_callback (GtkTextBuffer *buffer, + gchar **commentp) +{ + GtkTextIter start_iter; + GtkTextIter end_iter; + gchar *text; + + g_return_if_fail (commentp != NULL); + + gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter); + text = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE); + + if (strlen (text) > XCURSOR_COMMENT_MAX_LEN) + { + g_message (_("Comment is limited to %d characters."), + XCURSOR_COMMENT_MAX_LEN); + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, + XCURSOR_COMMENT_MAX_LEN - 1); + gtk_text_buffer_get_end_iter (buffer, &end_iter); + + gtk_text_buffer_delete (buffer, &start_iter, &end_iter); + } + else + { + g_free (*commentp); + *commentp = g_strdup (text); + } +} + +/** + * Set default hotspot based on hotspotRange. +**/ +static gboolean +load_default_hotspot (const gint32 image_ID, + GeglRectangle *hotspotRange) +{ + + g_return_val_if_fail (hotspotRange, FALSE); + + if ( /* if we cannot load hotspot correctly */ + ! get_hotspot_from_parasite (image_ID) || + /* ,or hostspot is out of range */ + ! pix_in_region (xmcparas.x, xmcparas.y, hotspotRange)) + { + /* then use top left point of hotspotRange as fallback. */ + xmcparas.x = hotspotRange->x; + xmcparas.y = hotspotRange->y; + } + + return TRUE; +} + +/* + * 'save_image ()' - Save the specified image to X cursor file. + */ + +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + GError **error) +{ + FILE *fp; /* File pointer */ + gboolean dimension_warn = FALSE; /* become TRUE if even one + * of the dimensions of the + * frames of the cursor is + * over + * MAX_BITMAP_CURSOR_SIZE */ + gboolean size_warn = FALSE; /* become TRUE if even one + * of the nominal size of + * the frames is not + * supported by + * gnome-appearance-properties */ + GRegex *re; /* used to get size and delay from + * framename */ + XcursorComments *commentsp; /* pointer to comments */ + XcursorImages *imagesp; /* pointer to images */ + gint32 *layers; /* Array of layer */ + gint32 *orig_layers; /* Array of layer of orig_image */ + gint nlayers; /* Number of layers */ + gchar *framename; /* framename of a layer */ + GeglRectangle save_rgn; /* region to save */ + gint layer_xoffset, layer_yoffset; + /* temporary buffer which store pixel data (guchar * bpp = guint32) */ + guint32 pixelbuf[SQR (MAX_SAVE_DIMENSION)]; + gint i, j; /* Looping vars */ + + /* This will be used in set_size_and_delay function later. To + * define this in that function is easy to read but place here to + * reduce overheads. + */ + re = g_regex_new ("[(][ 0]*(\\d+)[ ]*(px|ms)[ ]*[)]", + G_REGEX_CASELESS | G_REGEX_OPTIMIZE, + 0, + NULL); + + gimp_progress_init_printf (_("Saving '%s'"), + gimp_filename_to_utf8 (filename)); + + /* + * Open the file pointer. + */ + DM_XMC ("Open the file pointer.\n"); + fp = g_fopen (filename, "wb"); + if (fp == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return FALSE; + } + + /* get layers */ + orig_layers = gimp_image_get_layers (orig_image_ID, &nlayers); + layers = gimp_image_get_layers (image_ID, &nlayers); + + /* create new XcursorImages. */ + imagesp = XcursorImagesCreate (nlayers); + if (!imagesp) + { + DM_XMC ("Failed to XcursorImagesCreate!\n"); + fclose (fp); + return FALSE; + } + imagesp->nimage = nlayers; + + /* XcursorImages also have `name' member but it is not used as long as I know. + We leave it NULL here. */ + + /* + * Now we start to convert each layer to a XcurosrImage one by one. + */ + for (i = 0; i < nlayers; i++) + { + GeglBuffer *buffer; + const Babl *format; + gint width; + gint height; + + buffer = gimp_drawable_get_buffer (layers[nlayers - 1 - i]); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + format = babl_format ("R'G'B'A u8"); + + /* get framename of this layer */ + framename = gimp_item_get_name (layers[nlayers - 1 - i]); + /* get offset of this layer. */ + gimp_drawable_offsets (layers[nlayers - 1 - i], &layer_xoffset, &layer_yoffset); + + /* + * layer dimension check. + */ + DM_XMC ("layer size check.\n"); + /* We allow to save a cursor which dimensions are no more than + * MAX_SAVE_DIMENSION but after auto-cropping, we warn (only + * warn, don't stop) if dimension is over + * MAX_BITMAP_CURSOR_SIZE. + */ + if (width > MAX_SAVE_DIMENSION) + { + g_set_error (error, 0, 0, + _("Frame '%s' is too wide. Please reduce to no more than %dpx."), + gimp_any_to_utf8 (framename, -1, NULL), + MAX_SAVE_DIMENSION); + fclose (fp); + return FALSE; + } + + if (height > MAX_SAVE_DIMENSION) + { + g_set_error (error, 0, 0, + _("Frame '%s' is too high. Please reduce to no more than %dpx."), + gimp_any_to_utf8 (framename, -1, NULL), + MAX_SAVE_DIMENSION); + fclose (fp); + return FALSE; + } + + if (height == 0 || width == 0) + { + g_set_error (error, 0, 0, + _("Width and/or height of frame '%s' is zero!"), + gimp_any_to_utf8 (framename, -1, NULL)); + fclose (fp); + return FALSE; + } + + if (xmcvals.crop) /* with auto-cropping */ + { + /* get the region of auto-cropped area. */ + DM_XMC ("get_cropped_region\n"); + get_cropped_region (&save_rgn, buffer); + + /* don't forget save_rgn's origin is not a entire image + * but a layer which we are doing on. + */ + + if (save_rgn.width == 0 || save_rgn.height == 0) + {/* perfectly transparent frames become 1x1px transparent pixel. */ + DM_XMC ("get_cropped_region return 0.\n"); + imagesp->images[i] = XcursorImageCreate (1, 1); + if (!imagesp->images[i]) + { + DM_XMC ("Failed to XcursorImageCreate.\n"); + fclose (fp); + return FALSE; + } + imagesp->images[i]->pixels[0] = 0x0; + imagesp->images[i]->xhot = 0; + imagesp->images[i]->yhot = 0; + set_size_and_delay (framename, &(imagesp->images[i]->size), + &(imagesp->images[i]->delay), re, + &size_warn); + continue; + } + /* OK save_rgn is not 0x0 */ + /* is hotspot in save_rgn ? */ + if (! pix_in_region (xmcparas.x - layer_xoffset, + xmcparas.y - layer_yoffset, + &save_rgn)) + { /* if hotspot is not on save_rgn */ + g_set_error (error, 0, 0, + _("Cannot export the cursor because the hot spot " + "is not on frame '%s'.\n" + "Try to change the hot spot position, " + "layer geometry or export without auto-crop."), + gimp_any_to_utf8 (framename, -1, NULL)); + fclose (fp); + return FALSE; + } + } + else /* if without auto-cropping... */ + { + /* set save_rgn for the case not to auto-crop */ + save_rgn.width = width; + save_rgn.height = height; + save_rgn.x = 0; + save_rgn.y = 0; + } + + /* We warn if the dimension of the layer is over MAX_BITMAP_CURSOR_SIZE. */ + if (! dimension_warn) + { + if (save_rgn.width > MAX_BITMAP_CURSOR_SIZE || + save_rgn.height > MAX_BITMAP_CURSOR_SIZE) + { + dimension_warn = TRUE; + /* actual warning is done after the cursor is successfully saved.*/ + } + } + /* + * Create new XcursorImage. + */ + DM_XMC ("create new xcursorimage.\twidth=%i\theight=%i\n", + save_rgn.width, save_rgn.height); + imagesp->images[i] = XcursorImageCreate (save_rgn.width, save_rgn.height); + /* Cursor width & height is automatically set by function */ + /* XcursorImageCreate, so no need to set manually. */ + if (!imagesp->images[i]) + { + DM_XMC ("Failed to XcursorImageCreate.\n"); + fclose (fp); + return FALSE; + } + /* + ** set images[i]'s xhot & yhot. + */ + /* [Cropped layer's hotspot] = + [image's hotspot] - [layer's offset] - [save_rgn's offset]. */ + DM_XMC ("xhot=%i\tsave_rgn->xoffset=%i\tlayer_xoffset=%i\n", + xmcparas.x, layer_xoffset, save_rgn.x); + DM_XMC ("yhot=%i\tsave_rgn->yoffset=%i\tlayer_yoffset=%i\n", + xmcparas.y, layer_yoffset, save_rgn.y); + imagesp->images[i]->xhot = xmcparas.x - layer_xoffset - save_rgn.x; + imagesp->images[i]->yhot = xmcparas.y - layer_yoffset - save_rgn.y; + DM_XMC ("images[%i]->xhot=%i\tyhot=%i\n", i, + imagesp->images[i]->xhot, imagesp->images[i]->yhot); + + /* + * set images[i]->pixels + */ + /* get image data to pixelbuf. */ + gegl_buffer_get (buffer, + GEGL_RECTANGLE (save_rgn.x, save_rgn.y, + save_rgn.width, save_rgn.height), 1.0, + format, pixelbuf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + /*convert pixel date to XcursorPixel. */ + g_assert (save_rgn.width * save_rgn.height < SQR (MAX_SAVE_DIMENSION)); + for (j = 0; j < save_rgn.width * save_rgn.height; j++) + { + imagesp->images[i]->pixels[j] = premultiply_alpha (pixelbuf[j]); + } + + /* + * get back size & delay from framename. + */ + set_size_and_delay (framename, &(imagesp->images[i]->size), + &(imagesp->images[i]->delay), re, &size_warn); + + /* + * All property of this XcursorImage is loaded. + */ + + /* set the layer name of original image with the saved value */ + g_free (framename); + framename = make_framename (imagesp->images[i]->size, + imagesp->images[i]->delay, + DISPLAY_DIGIT (imagesp->nimage), + error); + if (! framename) + { + fclose (fp); + return FALSE; + } + + gimp_item_set_name (orig_layers[nlayers - 1 - i], framename); + g_free (framename); + + g_object_unref (buffer); + + gimp_progress_update ((i + 1) / imagesp->nimage); + } + + gimp_progress_update (1.0); + + /* + * comment parsing + */ + commentsp = set_cursor_comments (); + +#ifdef XMC_DEBUG + DM_XMC ("imagesp->nimage=%i\tname=%s\n", imagesp->nimage, imagesp->name); + for (i = 0; i < imagesp->nimage; ++i) + { + DM_XMC ("\timages[%i]->size=%i\n\ + \twidth=%i\n\ + \theight=%i\n\ + \txhot=%i\n\ + \tyhot=%i\n\ + \tdelay=%i\n\ + \t*pixels=%p\n", + i, + imagesp->images[i]->size, + imagesp->images[i]->width, + imagesp->images[i]->height, + imagesp->images[i]->xhot, + imagesp->images[i]->yhot, + imagesp->images[i]->delay, + imagesp->images[i]->pixels); + } + + if (commentsp) + { + for (i = 0; i < commentsp->ncomment; ++i) + { + DM_XMC ("comment type=%d\tcomment=%s\n", + commentsp->comments[i]->comment_type, + commentsp->comments[i]->comment); + } + } +#endif + + /* + * save cursor to file *fp. + */ + + if (commentsp) + { + if (! XcursorFileSave (fp, commentsp, imagesp)) + { + DM_XMC ("Failed to XcursorFileSave.\t%p\t%p\t%p\n", + fp, commentsp, imagesp); + fclose (fp); + return FALSE; + } + + } + else /* if no comments exist */ + { + if (! XcursorFileSaveImages (fp, imagesp)) + { + DM_XMC ("Failed to XcursorFileSaveImages.\t%p\t%p\n", fp, imagesp); + fclose (fp); + return FALSE; + } + } + + /* actual warning about dimensions */ + if (dimension_warn) + { + g_message (_("Your cursor was successfully exported but it contains one or " + "more frames whose width or height is more than %ipx, " + "a historical max dimension value for X bitmap cursors.\n" + "It might be unsupported by some environments."), + MAX_BITMAP_CURSOR_SIZE); + } + if (size_warn) + { + g_message (_("Your cursor was successfully exported but it contains one " + "or more frames whose nominal size is not supported by " + "GNOME settings.\n" + "You can satisfy it by checking \"Replace the size of all " + "frames...\" in the export dialog, or your cursor may not " + "appear in GNOME settings.")); + } + /* + * Done with the file... + */ + g_regex_unref (re); + DM_XMC ("fp=%p\n", fp); + fclose (fp); + DM_XMC ("%i frames written.\n", imagesp->nimage); + XcursorImagesDestroy (imagesp); + DM_XMC ("Xcursor destroyed.\n"); + XcursorCommentsDestroy (commentsp); /* this is safe even if commentsp is NULL. */ + gimp_progress_end (); + + /* Save the comment back to the original image */ + for (i = 0; i < 3; i++) + { + gimp_image_detach_parasite (orig_image_ID, parasiteName[i]); + + if (xmcparas.comments[i]) + { + if (! set_comment_to_pname (orig_image_ID, + xmcparas.comments[i], parasiteName[i])) + { + DM_XMC ("Failed to write back %ith comment to orig_image.\n", i); + } + } + } + /* Save hotspot back to the original image */ + set_hotspot_to_parasite (orig_image_ID); + + return TRUE; +} + +static inline guint32 +separate_alpha (guint32 pixel) +{ + guint alpha, red, green, blue; + guint32 retval; + +#if G_BYTE_ORDER != G_LITTLE_ENDIAN + pixel = GUINT32_TO_LE (pixel); +#endif + + blue = pixel & 0xff; + green = (pixel>>8) & 0xff; + red = (pixel>>16) & 0xff; + alpha = (pixel>>24) & 0xff; + + if (alpha == 0) + return 0; + + /* resume separate alpha data. */ + red = MIN (red * 255 / alpha, 255); + blue = MIN (blue * 255 / alpha, 255); + green = MIN (green * 255 / alpha, 255); + + retval = red + (green<<8) + (blue<<16) + (alpha<<24); + +#if G_BYTE_ORDER != G_LITTLE_ENDIAN + pixel = GUINT32_FROM_LE (pixel); +#endif + + return retval; +} + +static inline guint32 +premultiply_alpha (guint32 pixel) +{ + guint alpha, red, green, blue; + guint32 retval; + +#if G_BYTE_ORDER != G_LITTLE_ENDIAN + pixel = GUINT32_TO_LE (pixel); +#endif + + red = pixel & 0xff; + green = (pixel >> 8) & 0xff; + blue = (pixel >> 16) & 0xff; + alpha = (pixel >> 24) & 0xff; + + /* premultiply alpha + (see "premultiply_data" function at line 154 of xcursorgen.c) */ + red = div_255 (red * alpha); + green = div_255 (green * alpha); + blue = div_255 (blue * alpha); + + retval = blue + (green << 8) + (red << 16) + (alpha << 24); + +#if G_BYTE_ORDER != G_LITTLE_ENDIAN + pixel = GUINT32_FROM_LE (pixel); +#endif + + return retval; +} + +/* set comments to cursor from xmcparas.comments. + * don't forget to XcursorCommentsDestroy returned pointer later. + */ +static XcursorComments * +set_cursor_comments (void) +{ + gint i; + guint gcomlen, arraylen; + GArray *xcCommentsArray; + XcursorComment *(xcCommentp[3]) = {NULL,}; + XcursorComments *xcCommentsp; + + xcCommentsArray = g_array_new (FALSE, FALSE, sizeof (XcursorComment *)); + + for (i = 0; i < 3; ++i) + { + if (xmcparas.comments[i]) + { + gcomlen = strlen (xmcparas.comments[i]); + if (gcomlen > 0) + { + xcCommentp[i] = XcursorCommentCreate (i + 1, gcomlen); + /* first argument of XcursorCommentCreate is comment_type + defined in Xcursor.h as enumerator. + i + 1 is appropriate when we dispose parasiteName before MAIN(). */ + if (!xcCommentp[i]) + { + g_warning ("Cannot create xcCommentp[%i]\n", i); + return NULL; + } + else + { + g_stpcpy (xcCommentp[i]->comment, xmcparas.comments[i]); + g_array_append_val (xcCommentsArray, xcCommentp[i]); + } + } + } + } + + arraylen = xcCommentsArray->len; + + if (arraylen == 0) + return NULL; + + xcCommentsp = XcursorCommentsCreate (arraylen); + xcCommentsp->ncomment = arraylen; + + for (i = 0; i < arraylen; ++i) + { + xcCommentsp->comments[i] = + g_array_index (xcCommentsArray, XcursorComment* ,i); + } + + return xcCommentsp; +} + +/* Load xmcparas.comments from three parasites named as "xmc-copyright", + * "xmc-license","gimp-comment". + * This alignment sequence is depends on the definition of comment_type + * in Xcursor.h . + * Don't forget to g_free each element of xmcparas.comments later. + */ +static void +load_comments (const gint32 image_ID) +{ + gint i; + + g_return_if_fail (image_ID != -1); + + for (i = 0; i < 3; ++i) + xmcparas.comments[i] = get_comment_from_pname (image_ID, parasiteName[i]); +} + +/* Set content to a parasite named as pname. if parasite already + * exists, append the new one to the old one with "\n" + */ +static gboolean +set_comment_to_pname (const gint32 image_ID, + const gchar *content, + const gchar *pname) +{ + gboolean ret = FALSE; + gchar *tmpstring, *joind; + GimpParasite *parasite; + + g_return_val_if_fail (image_ID != -1, FALSE); + g_return_val_if_fail (content, FALSE); + + parasite = gimp_image_get_parasite (image_ID, pname); + if (! parasite) + { + parasite = gimp_parasite_new (pname, GIMP_PARASITE_PERSISTENT, + strlen (content) + 1, content); + } + else + { + tmpstring = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + gimp_parasite_free (parasite); + joind = g_strjoin ("\n", tmpstring, content, NULL); + g_free (tmpstring); + parasite = gimp_parasite_new (pname, GIMP_PARASITE_PERSISTENT, + strlen (joind) + 1, joind); + g_free (joind); + } + + if (parasite) + { + ret = gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + } + + return ret; +} + +/* get back comment from parasite name, don't forget to call + * g_free(returned pointer) later + */ +static gchar * +get_comment_from_pname (const gint32 image_ID, + const gchar *pname) +{ + gchar *string = NULL; + GimpParasite *parasite; + glong length; + + g_return_val_if_fail (image_ID != -1, NULL); + + parasite = gimp_image_get_parasite (image_ID, pname); + length = gimp_parasite_data_size (parasite); + + if (parasite) + { + if (length > XCURSOR_COMMENT_MAX_LEN) + { + length = XCURSOR_COMMENT_MAX_LEN; + g_message (_("The parasite \"%s\" is too long for an X cursor " + "comment. It was cut off to fit."), + gimp_any_to_utf8 (pname, -1,NULL)); + } + + string = g_strndup (gimp_parasite_data (parasite), length); + gimp_parasite_free (parasite); + } + + return string; +} + +/* Set hotspot to "hot-spot" parasite which format is common with that + * of file-xbm. + */ +static gboolean +set_hotspot_to_parasite (gint32 image_ID) +{ + gboolean ret = FALSE; + gchar *tmpstr; + GimpParasite *parasite; + + g_return_val_if_fail (image_ID != -1, FALSE); + + tmpstr = g_strdup_printf ("%d %d", xmcparas.x, xmcparas.y); + parasite = gimp_parasite_new ("hot-spot", + GIMP_PARASITE_PERSISTENT, + strlen (tmpstr) + 1, + tmpstr); + g_free (tmpstr); + + if (parasite) + { + ret = gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + } + + return ret; +} + +/* Get back xhot & yhot from "hot-spot" parasite. + * If succeed, hotspot coordinate is set to xmcparas.x, xmcparas.y and + * return TRUE. + * If "hot-spot" is not found or broken, return FALSE. + */ +static gboolean +get_hotspot_from_parasite (gint32 image_ID) +{ + GimpParasite *parasite; + + g_return_val_if_fail (image_ID != -1, FALSE); + + DM_XMC ("function: getHotsopt\n"); + + parasite = gimp_image_get_parasite (image_ID, "hot-spot"); + if (!parasite) /* cannot find a parasite named "hot-spot". */ + { + return FALSE; + } + + if (sscanf (gimp_parasite_data (parasite), + "%i %i", &xmcparas.x, &xmcparas.y) < 2) + { /*cannot load hotspot.(parasite is broken?) */ + return FALSE; + } + + /*OK, hotspot is set to *xhotp & *yhotp. */ + return TRUE; +} + +/* Set size to sizep, delay to delayp from drawable's framename. + */ +static void +set_size_and_delay (const gchar *framename, + guint32 *sizep, + guint32 *delayp, + GRegex *re, + gboolean *size_warnp) +{ + guint32 size = 0; + guint32 delay = 0; + gchar *digits = NULL; + gchar *suffix = NULL; + GMatchInfo *info = NULL; + + g_return_if_fail (framename); + g_return_if_fail (sizep); + g_return_if_fail (delayp); + g_return_if_fail (re); + + DM_XMC ("function: set_size_and_delay\tframename=%s\n", framename); + + /* re is defined at the start of save_image() as + [(] : open parenthesis + [ ]* : ignore zero or more spaces + (\\d+) : the number we want to get out + [ ]* : ignore zero or more spaces + (px|ms) : whether "px"(size) or "ms"(delay) + [ ]* : ignore zero or more spaces + [)] : close parenthesis + This is intended to match for the animation-play plug-in. */ + + g_regex_match (re, framename, 0, &info); + + while (g_match_info_matches (info)) + { + digits = g_match_info_fetch (info, 1); + suffix = g_match_info_fetch (info, 2); + + if (g_ascii_strcasecmp (suffix, "px") == 0) + { + if (!size) /* substitute it only for the first time */ + { + if (strlen (digits) > 8) /* too large number should be clamped */ + { + g_message (_("Your cursor was successfully exported but it contains one or " + "more frames whose size is over 8 digits.\n" + "We clamped it to %dpx. You should check the exported cursor."), + MAX_BITMAP_CURSOR_SIZE); + size = MAX_BITMAP_CURSOR_SIZE; + } + else + { + size = atoi (digits); + } + } + } + else /* suffix is "ms" */ + { + if (!delay) /* substitute it only for the first time */ + { + if (strlen (digits) > 8) /* too large number should be clamped */ + delay = CURSOR_MAX_DELAY; + else + delay = MIN (CURSOR_MAX_DELAY, atoi (digits)); + } + } + + g_free (digits); + g_free (suffix); + + g_match_info_next (info, NULL); + } + + g_match_info_free (info); + + /* if size is not set, or size_replace is TRUE, set default size + * (which was chosen in save dialog) */ + if (size == 0 || xmcvals.size_replace == TRUE) + { + size = xmcvals.size; + } + else if (! *size_warnp && + size != 12 && size != 16 && size != 24 && size != 32 && + size != 36 && size != 40 && size != 48 && size != 64 && + size != 96) + { /* if the size is different from these values, we warn about it after + successfully saving because gnome-appearance-properties only support + them. */ + *size_warnp = TRUE; + } + + *sizep = size; + + /* if delay is not set, or delay_replace is TRUE, set default delay + * (which was chosen in save dialog) */ + if (delay == 0 || xmcvals.delay_replace == TRUE) + { + delay = xmcvals.delay; + } + + *delayp = delay; + + DM_XMC ("set_size_and_delay return\tsize=%i\tdelay=%i\n", size, delay); +} + +/* Return framename as format: "([x]px)_[i] ([t]ms) (replace)" + * where [x] is nominal size, [t] is delay passed as argument respectively, + * and [i] is an index separately counted by [x]. + * This format is compatible with "animation-play" plug-in. + * Don't forget to g_free returned framename later. + */ +static gchar * +make_framename (guint32 size, + guint32 delay, + guint indent, + GError **errorp) +{ + static struct + { + guint32 size; + guint count; + } Counter[MAX_SIZE_NUM + 1] = {{0,}}; + + int i; /* loop index */ + + /* don't pass 0 for size. */ + g_return_val_if_fail (size > 0, NULL); + + /* "count" member of Counter's element means how many time corresponding + "size" is passed to this function. The size member of the last element + of Counter must be 0, so Counter can have MAX_SIZE_NUM elements at most. + This is not a smart way but rather simple than using dynamic method. */ + + for (i = 0; Counter[i].size != size; ++i) + { + if (Counter[i].size == 0) /* the end of Counter elements */ + { + if (i > MAX_SIZE_NUM) + { + g_set_error (errorp, 0, 0, + /* translators: the %i is *always* 8 here */ + _("Sorry, this plug-in cannot handle a cursor " + "which contains over %i different nominal sizes."), + MAX_SIZE_NUM); + return NULL; + } + else /* append new element which "size" is given value. */ + { + Counter[i].size = size; + break; + } + } + } + + Counter[i].count += 1; + + return g_strdup_printf ("(%dpx)_%0*d (%dms) (replace)", size, indent, + Counter[i].count, delay); +} + +/* Get the region which is maintained when auto-crop. + */ +static void +get_cropped_region (GeglRectangle *return_rgn, + GeglBuffer *buffer) +{ + gint width = gegl_buffer_get_width (buffer); + gint height = gegl_buffer_get_height (buffer); + guint32 *buf = g_malloc (MAX (width, height) * sizeof (guint32)); + const Babl *format = babl_format ("R'G'B'A u8"); + guint i, j; + + g_return_if_fail (GEGL_IS_BUFFER (buffer)); + + DM_XMC ("function:get_cropped_region\n"); + + DM_XMC ("getTrim:\tMAX=%i\tpr->w=%i\tpr->h=%i\n", sizeof (buf)/4, pr->w, pr->h); + + /* find left border. */ + for (i = 0; i < width; ++i) + { + DM_XMC ("i=%i width=%i\n", i, width); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (i, 0, 1, height), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (j = 0; j < height; ++j) + { + if (pix_is_opaque (buf[j])) /* if a opaque pixel exist. */ + { + return_rgn->x = i; + goto find_right; + } + } + } + + /* pr has no opaque pixel. */ + return_rgn->width = 0; + return; + + /* find right border. */ + find_right: + for (i = 0; i < width ; ++i) + { + DM_XMC ("width-1-i=%i height=%i\n", width - 1 - i, height); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (width - 1 - i, 0, 1, height), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (j = 0; j < height; ++j) + { + if (pix_is_opaque (buf[j])) /* if a opaque pixel exist. */ + { + return_rgn->width = width - i - return_rgn->x; + goto find_top; + } + } + } + g_return_if_reached (); + + /* find top border. */ + find_top: + for (j = 0; j < height; ++j) + { + DM_XMC ("j=%i width=%i\n", j, width); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, j, width, 1), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (i = 0; i < width; ++i) + { + if (pix_is_opaque (buf[i])) /* if a opaque pixel exist. */ + { + return_rgn->y = j; + goto find_bottom; + } + } + } + + g_return_if_reached (); + + /* find bottom border. */ + find_bottom: + for (j = 0; j < height; ++j) + { + DM_XMC ("height-1-j=%i width=%i\n", height - 1 - j, width); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, height - 1 - j, width, 1), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (i = 0; i < width; ++i) + { + if (pix_is_opaque (buf[i])) /* if a opaque pixel exist. */ + { + return_rgn->height = height - j - return_rgn->y; + goto end_trim; + } + } + } + + g_return_if_reached (); + + end_trim: + DM_XMC ("width=%i\theight=%i\txoffset=%i\tyoffset=%i\n", + return_rgn->width, return_rgn->height, + return_rgn->x, return_rgn->y); + + g_free (buf); +} + +/* Return true if alpha of pix is not 0. + */ +static inline gboolean +pix_is_opaque (guint32 pix) +{ +#if G_BYTE_ORDER != G_LITTLE_ENDIAN + pix = GUINT32_TO_LE (pix); +#endif + + return ((pix >> 24) != 0); +} + +/* Get the intersection of the all layers of the image specified by image_ID. + * if the intersection is empty return NULL. + * don't forget to g_free returned pointer later. + */ +static GeglRectangle * +get_intersection_of_frames (gint32 image_ID) +{ + GeglRectangle *iregion; + gint i; + gint32 x1 = G_MININT32, x2 = G_MAXINT32; + gint32 y1 = G_MININT32, y2 = G_MAXINT32; + gint32 x_off, y_off; + gint nlayers; + gint *layers; + + g_return_val_if_fail (image_ID != -1, FALSE); + + layers = gimp_image_get_layers (image_ID, &nlayers); + + for (i = 0; i < nlayers; ++i) + { + if (! gimp_drawable_offsets (layers[i], &x_off, &y_off)) + return NULL; + + x1 = MAX (x1, x_off); + y1 = MAX (y1, y_off); + x2 = MIN (x2, x_off + gimp_drawable_width (layers[i]) - 1); + y2 = MIN (y2, y_off + gimp_drawable_height (layers[i]) - 1); + } + + if (x1 > x2 || y1 > y2) + return NULL; + + /* OK intersection exists. */ + iregion = g_new (GeglRectangle, 1); + iregion->x = x1; + iregion->y = y1; + iregion->width = x2 - x1 + 1; + iregion->height = y2 - y1 + 1; + + return iregion; +} + +/* If (x,y) is in xmcrp, return TRUE. + */ +static gboolean +pix_in_region (gint32 x, + gint32 y, + GeglRectangle *xmcrp) +{ + g_return_val_if_fail (xmcrp, FALSE); + + if (x < xmcrp->x || y < xmcrp->y || + x >= xmcrp->x + xmcrp->width || y >= xmcrp->y + xmcrp->height) + return FALSE; + else + return TRUE; +} + +/** + * Find out xhot, yhot, width and height of the Xcursor specified by xcIs. + * Use NULL for the value you don't want to return. +**/ +static void +find_hotspots_and_dimensions (XcursorImages *xcIs, + gint32 *xhotp, + gint32 *yhotp, + gint32 *widthp, + gint32 *heightp) +{ + gint32 dw, dh; /* the distance between hotspot and right(bottom) border */ + gint32 max_xhot; + gint32 max_yhot; /* the maximum value of xhot(yhot) */ + gint i; + + g_return_if_fail (xcIs); + + max_xhot = max_yhot = dw = dh = 0; + + for (i = 0; i < xcIs->nimage; ++i) + { + /* xhot of entire image is the maximum value of xhot of all frames */ + max_xhot = MAX (xcIs->images[i]->xhot, max_xhot); + /* same for yhot */ + max_yhot = MAX (xcIs->images[i]->yhot, max_yhot); + /* the maximum distance between right border and xhot */ + dw = MAX (dw, xcIs->images[i]->width - xcIs->images[i]->xhot); + /* the maximum distance between bottom border and yhot */ + dh = MAX (dh, xcIs->images[i]->height - xcIs->images[i]->yhot); + } + + if (xhotp) + *xhotp = max_xhot; + if (yhotp) + *yhotp = max_yhot; + if (widthp) + *widthp = dw + max_xhot; + if (heightp) + *heightp = dh + max_yhot; +} diff --git a/plug-ins/common/file-xpm.c b/plug-ins/common/file-xpm.c new file mode 100644 index 0000000..34c946e --- /dev/null +++ b/plug-ins/common/file-xpm.c @@ -0,0 +1,881 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* XPM plugin version 1.2.7 */ + +/* +1.2.7 fixes saving unused transparency (bug #4560) + +1.2.6 fixes crash when saving indexed images (bug #109567) + +1.2.5 only creates a "None" color entry if the image has alpha (bug #108034) + +1.2.4 displays an error message if saving fails (bug #87588) + +1.2.3 fixes bug when running in noninteractive mode +changes alpha_threshold range from [0, 1] to [0,255] for consistency with +the threshold_alpha plugin + +1.2.2 fixes bug that generated bad digits on images with more than 20000 +colors. (thanks, yanele) +parses gtkrc (thanks, yosh) +doesn't load parameter screen on images that don't have alpha + +1.2.1 fixes some minor bugs -- spaces in #XXXXXX strings, small typos in code. + +1.2 compute color indexes so that we don't have to use XpmSaveXImage* + +Previous...Inherited code from Ray Lehtiniemi, who inherited it from S & P. +*/ + +#include "config.h" + +#include + +#include + +#include /* For GDK_WINDOWING_WIN32 */ + +#ifndef GDK_WINDOWING_X11 +#ifndef XPM_NO_X +#define XPM_NO_X +#endif +#else +#include +#endif + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +static const gchar linenoise [] = +" .+@#$%&*=-;>,')!~{]^/(_:<[}|1234567890abcdefghijklmnopqrstuvwxyz\ +ABCDEFGHIJKLMNOPQRSTUVWXYZ`"; + +#define LOAD_PROC "file-xpm-load" +#define SAVE_PROC "file-xpm-save" +#define PLUG_IN_BINARY "file-xpm" +#define PLUG_IN_ROLE "gimp-file-xpm" +#define SCALE_WIDTH 125 + +/* Structs for the save dialog */ +typedef struct +{ + gint threshold; +} XpmSaveVals; + +typedef struct +{ + guchar r; + guchar g; + guchar b; +} rgbkey; + +/* whether the image is color or not. global so I only have to pass + * one user value to the GHFunc + */ +static gboolean color; + +/* bytes per pixel. global so I only have to pass one user value + * to the GHFunc + */ +static gint cpp; + +/* Declare local functions */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); +static guchar * parse_colors (XpmImage *xpm_image); +static void parse_image (gint32 image_ID, + XpmImage *xpm_image, + guchar *cmap); +static gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static gboolean save_dialog (void); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static XpmSaveVals xpmvals = +{ + 127 /* alpha threshold */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_INT32, "threshold", "Alpha threshold (0-255)" } + }; + + gimp_install_procedure (LOAD_PROC, + "Load files in XPM (X11 Pixmap) format.", + "Load files in XPM (X11 Pixmap) format. " + "XPM is a portable image format designed to be " + "included in C source code. XLib provides utility " + "functions to read this format. Newer code should " + "however be using gdk-pixbuf-csource instead. " + "XPM supports colored images, unlike the XBM " + "format which XPM was designed to replace.", + "Spencer Kimball & Peter Mattis & Ray Lehtiniemi", + "Spencer Kimball & Peter Mattis", + "1997", + N_("X PixMap image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-xpixmap"); + gimp_register_magic_load_handler (LOAD_PROC, + "xpm", + "", + "0, string,/*\\040XPM\\040*/"); + + gimp_install_procedure (SAVE_PROC, + "Export files in XPM (X11 Pixmap) format.", + "Export files in XPM (X11 Pixmap) format. " + "XPM is a portable image format designed to be " + "included in C source code. XLib provides utility " + "functions to read this format. Newer code should " + "however be using gdk-pixbuf-csource instead. " + "XPM supports colored images, unlike the XBM " + "format which XPM was designed to replace.", + "Spencer Kimball & Peter Mattis & Ray Lehtiniemi & Nathan Summers", + "Spencer Kimball & Peter Mattis", + "1997", + N_("X PixMap image"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-xpixmap"); + gimp_register_save_handler (SAVE_PROC, "xpm", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + export = gimp_export_image (&image_ID, &drawable_ID, "XPM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data ("file_xpm_save", &xpmvals); + + /* First acquire information with a dialog */ + if (gimp_drawable_has_alpha (drawable_ID)) + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + xpmvals.threshold = param[5].data.d_int32; + + if (xpmvals.threshold < 0 || + xpmvals.threshold > 255) + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data ("file_xpm_save", &xpmvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (param[3].data.d_string, + image_ID, drawable_ID, &error)) + { + gimp_set_data ("file_xpm_save", &xpmvals, sizeof (XpmSaveVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + XpmImage xpm_image; + guchar *cmap; + gint32 image_ID; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + /* read the raw file */ + switch (XpmReadFileToXpmImage ((char *) filename, &xpm_image, NULL)) + { + case XpmSuccess: + break; + + case XpmOpenFailed: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error opening file '%s'"), + gimp_filename_to_utf8 (filename)); + return -1; + + case XpmFileInvalid: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", _("XPM file invalid")); + return -1; + + default: + return -1; + } + + /* parse out the colors into a cmap */ + cmap = parse_colors (&xpm_image); + + /* create the new image */ + image_ID = gimp_image_new (xpm_image.width, + xpm_image.height, + GIMP_RGB); + + /* name it */ + gimp_image_set_filename (image_ID, filename); + + /* fill it */ + parse_image (image_ID, &xpm_image, cmap); + + /* clean up and exit */ + g_free (cmap); + + return image_ID; +} + +static guchar * +parse_colors (XpmImage *xpm_image) +{ +#ifndef XPM_NO_X + Display *display; + Colormap colormap; +#endif + gint i, j; + guchar *cmap; + +#ifndef XPM_NO_X + /* open the display and get the default color map */ + display = XOpenDisplay (NULL); + if (display == NULL) + g_printerr ("Could not open display\n"); + + colormap = DefaultColormap (display, DefaultScreen (display)); +#endif + + /* alloc a buffer to hold the parsed colors */ + cmap = g_new0 (guchar, 4 * xpm_image->ncolors); + + /* parse each color in the file */ + for (i = 0, j = 0; i < xpm_image->ncolors; i++) + { + gchar *colorspec = "None"; + XpmColor *xpm_color; +#ifndef XPM_NO_X + XColor xcolor; +#else + GdkColor xcolor; +#endif + + xpm_color = &(xpm_image->colorTable[i]); + + /* pick the best spec available */ + if (xpm_color->c_color) + colorspec = xpm_color->c_color; + else if (xpm_color->g_color) + colorspec = xpm_color->g_color; + else if (xpm_color->g4_color) + colorspec = xpm_color->g4_color; + else if (xpm_color->m_color) + colorspec = xpm_color->m_color; + + /* parse if it's not transparent */ + if (strcmp (colorspec, "None") != 0) + { +#ifndef XPM_NO_X + XParseColor (display, colormap, colorspec, &xcolor); +#else + gdk_color_parse (colorspec, &xcolor); +#endif + cmap[j++] = xcolor.red >> 8; + cmap[j++] = xcolor.green >> 8; + cmap[j++] = xcolor.blue >> 8; + cmap[j++] = ~0; + } + else + { + j += 4; + } + } +#ifndef XPM_NO_X + XCloseDisplay (display); +#endif + return cmap; +} + +static void +parse_image (gint32 image_ID, + XpmImage *xpm_image, + guchar *cmap) +{ + GeglBuffer *buffer; + gint tile_height; + gint scanlines; + gint val; + guchar *buf; + guchar *dest; + guint *src; + gint32 layer_ID; + gint i; + + layer_ID = gimp_layer_new (image_ID, + _("Color"), + xpm_image->width, + xpm_image->height, + GIMP_RGBA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_ID); + + tile_height = gimp_tile_height (); + + buf = g_new (guchar, tile_height * xpm_image->width * 4); + + src = xpm_image->data; + for (i = 0; i < xpm_image->height; i += tile_height) + { + gint j; + + dest = buf; + scanlines = MIN (tile_height, xpm_image->height - i); + j = scanlines * xpm_image->width; + while (j--) + { + { + val = *(src++) * 4; + *(dest) = cmap[val]; + *(dest+1) = cmap[val+1]; + *(dest+2) = cmap[val+2]; + *(dest+3) = cmap[val+3]; + dest += 4; + } + + if ((j % 100) == 0) + gimp_progress_update ((double) i / (double) xpm_image->height); + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, i, xpm_image->width, scanlines), 0, + NULL, buf, GEGL_AUTO_ROWSTRIDE); + } + + g_free (buf); + g_object_unref (buffer); + + gimp_progress_update (1.0); +} + +static guint +rgbhash (rgbkey *c) +{ + return ((guint)c->r) ^ ((guint)c->g) ^ ((guint)c->b); +} + +static guint +compare (rgbkey *c1, + rgbkey *c2) +{ + return (c1->r == c2->r) && (c1->g == c2->g) && (c1->b == c2->b); +} + +static void +set_XpmImage (XpmColor *array, + guint index, + gchar *colorstring) +{ + gchar *p; + gint i, charnum, indtemp; + + indtemp=index; + array[index].string = p = g_new (gchar, cpp+1); + + /*convert the index number to base sizeof(linenoise)-1 */ + for (i=0; ir, (int)key->g, (int)key->b); + set_XpmImage (user_data, *((int *) value), string); +} + +static void +decrement_hash_values (gpointer gkey, + gpointer value, + gpointer user_data) +{ + --(*((guint*) value)); +} + +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GeglBuffer *buffer; + const Babl *format; + gint width; + gint height; + gint ncolors = 1; + gint *indexno; + gboolean indexed; + gboolean alpha; + gboolean alpha_used = FALSE; + XpmColor *colormap; + XpmImage *image; + guint *ibuff = NULL; + guchar *buf; + guchar *data; + GHashTable *hash = NULL; + gint i, j, k; + gint threshold = xpmvals.threshold; + gboolean success = FALSE; + + buffer = gimp_drawable_get_buffer (drawable_ID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + alpha = gimp_drawable_has_alpha (drawable_ID); + color = !gimp_drawable_is_gray (drawable_ID); + indexed = gimp_drawable_is_indexed (drawable_ID); + + switch (gimp_drawable_type (drawable_ID)) + { + case GIMP_RGB_IMAGE: + format = babl_format ("R'G'B' u8"); + break; + + case GIMP_RGBA_IMAGE: + format = babl_format ("R'G'B'A u8"); + break; + + case GIMP_GRAY_IMAGE: + format = babl_format ("Y' u8"); + break; + + case GIMP_GRAYA_IMAGE: + format = babl_format ("Y'A u8"); + break; + + case GIMP_INDEXED_IMAGE: + case GIMP_INDEXEDA_IMAGE: + format = gegl_buffer_get_format (buffer); + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported drawable type")); + g_object_unref (buffer); + return FALSE; + } + + /* allocate buffer making the assumption that ibuff is 32 bit aligned... */ + ibuff = g_new (guint, width * height); + + hash = g_hash_table_new ((GHashFunc) rgbhash, (GCompareFunc) compare); + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_filename_to_utf8 (filename)); + + ncolors = alpha ? 1 : 0; + + /* allocate a pixel region to work with */ + buf = g_new (guchar, + gimp_tile_height () * width * + babl_format_get_bytes_per_pixel (format)); + + /* process each row of tiles */ + for (i = 0; i < height; i += gimp_tile_height ()) + { + gint scanlines; + + /* read the next row of tiles */ + scanlines = MIN (gimp_tile_height(), height - i); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scanlines), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + data = buf; + + /* process each pixel row */ + for (j = 0; j < scanlines; j++) + { + /* go to the start of this row in each image */ + guint *idata = ibuff + (i+j) * width; + + /* do each pixel in the row */ + for (k = 0; k < width; k++) + { + rgbkey *key = g_new (rgbkey, 1); + guchar a; + + /* get pixel data */ + key->r = *(data++); + key->g = color && !indexed ? *(data++) : key->r; + key->b = color && !indexed ? *(data++) : key->r; + a = alpha ? *(data++) : 255; + + if (a < threshold) + { + *(idata++) = 0; + alpha_used = TRUE; + } + else + { + if (indexed) + { + *(idata++) = (key->r) + (alpha ? 1 : 0); + } + else + { + indexno = g_hash_table_lookup (hash, key); + if (!indexno) + { + indexno = g_new (gint, 1); + *indexno = ncolors++; + g_hash_table_insert (hash, key, indexno); + key = g_new (rgbkey, 1); + } + *(idata++) = *indexno; + } + } + } + + /* kick the progress bar */ + gimp_progress_update ((gdouble) (i+j) / (gdouble) height); + } + } + + g_free (buf); + + /* remove alpha if not actually used */ + if (alpha && !alpha_used) + { + gint i; + --ncolors; + for (i = 0; i < width * height; ++i) + --ibuff[i]; + + g_hash_table_foreach (hash, decrement_hash_values, NULL); + } + + if (indexed) + { + guchar *cmap = gimp_image_get_colormap (image_ID, &ncolors); + guchar *c; + + c = cmap; + + if (alpha_used) + ncolors++; + + colormap = g_new (XpmColor, ncolors); + cpp = + 1 + (gdouble) log (ncolors) / (gdouble) log (sizeof (linenoise) - 1.0); + + if (alpha_used) + set_XpmImage (colormap, 0, "None"); + + for (i = alpha_used ? 1 : 0; i < ncolors; i++) + { + gchar *string; + guchar r, g, b; + + r = *c++; + g = *c++; + b = *c++; + + string = g_new (gchar, 8); + sprintf (string, "#%02X%02X%02X", (int)r, (int)g, (int)b); + set_XpmImage (colormap, i, string); + } + + g_free (cmap); + } + else + { + colormap = g_new (XpmColor, ncolors); + cpp = + 1 + (gdouble) log (ncolors) / (gdouble) log (sizeof (linenoise) - 1.0); + + if (alpha_used) + set_XpmImage (colormap, 0, "None"); + + g_hash_table_foreach (hash, create_colormap_from_hash, colormap); + } + + image = g_new (XpmImage, 1); + + image->width = width; + image->height = height; + image->ncolors = ncolors; + image->cpp = cpp; + image->colorTable = colormap; + image->data = ibuff; + + /* do the save */ + switch (XpmWriteFileFromXpmImage ((char *) filename, image, NULL)) + { + case XpmSuccess: + success = TRUE; + break; + + case XpmOpenFailed: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error opening file '%s'"), + gimp_filename_to_utf8 (filename)); + break; + + case XpmFileInvalid: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", _("XPM file invalid")); + break; + + default: + break; + } + + g_object_unref (buffer); + g_free (ibuff); + + if (hash) + g_hash_table_destroy (hash); + + gimp_progress_update (1.0); + + return success; +} + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *table; + GtkObject *scale_data; + gboolean run; + + dialog = gimp_export_dialog_new (_("XPM"), PLUG_IN_BINARY, SAVE_PROC); + + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + table, TRUE, TRUE, 0); + gtk_widget_show (table); + + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Alpha threshold:"), SCALE_WIDTH, 0, + xpmvals.threshold, 0, 255, 1, 8, 0, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &xpmvals.threshold); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/file-xwd.c b/plug-ins/common/file-xwd.c new file mode 100644 index 0000000..20943c9 --- /dev/null +++ b/plug-ins/common/file-xwd.c @@ -0,0 +1,2587 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * XWD reading and writing code Copyright (C) 1996 Peter Kirchgessner + * (email: peter@kirchgessner.net, WWW: http://www.kirchgessner.net) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* + * XWD-input/output was written by Peter Kirchgessner (peter@kirchgessner.net) + * Examples from mainly used UNIX-systems have been used for testing. + * If a file does not work, please return a small (!!) compressed example. + * Currently the following formats are supported: + * pixmap_format | pixmap_depth | bits_per_pixel + * --------------------------------------------- + * 0 | 1 | 1 + * 1 | 1,...,24 | 1 + * 2 | 1 | 1 + * 2 | 1,...,8 | 8 + * 2 | 1,...,16 | 16 + * 2 | 1,...,24 | 24 + * 2 | 1,...,32 | 32 + */ +/* Event history: + * PK = Peter Kirchgessner, ME = Mattias EngdegÃ¥rd + * V 1.00, PK, xx-Aug-96: First try + * V 1.01, PK, 03-Sep-96: Check for bitmap_bit_order + * V 1.90, PK, 17-Mar-97: Upgrade to work with GIMP V0.99 + * Use visual class 3 to write indexed image + * Set gimp b/w-colormap if no xwdcolormap present + * V 1.91, PK, 05-Apr-97: Return all arguments, even in case of an error + * V 1.92, PK, 12-Oct-97: No progress bars for non-interactive mode + * V 1.93, PK, 11-Apr-98: Fix problem with overwriting memory + * V 1.94, ME, 27-Feb-00: Remove superfluous little-endian support (format is + specified as big-endian). Trim magic header + * V 1.95, PK, 02-Jul-01: Fix problem with 8 bit image + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-xwd-load" +#define SAVE_PROC "file-xwd-save" +#define PLUG_IN_BINARY "file-xwd" +#define PLUG_IN_ROLE "gimp-file-xwd" + + +typedef gulong L_CARD32; +typedef gushort L_CARD16; +typedef guchar L_CARD8; + +typedef struct +{ + L_CARD32 l_header_size; /* Header size */ + + L_CARD32 l_file_version; /* File version (7) */ + L_CARD32 l_pixmap_format; /* Image type */ + L_CARD32 l_pixmap_depth; /* Number of planes */ + L_CARD32 l_pixmap_width; /* Image width */ + L_CARD32 l_pixmap_height; /* Image height */ + L_CARD32 l_xoffset; /* x-offset (0 ?) */ + L_CARD32 l_byte_order; /* Byte ordering */ + + L_CARD32 l_bitmap_unit; + L_CARD32 l_bitmap_bit_order; /* Bit order */ + L_CARD32 l_bitmap_pad; + L_CARD32 l_bits_per_pixel; /* Number of bits per pixel */ + + L_CARD32 l_bytes_per_line; /* Number of bytes per scanline */ + L_CARD32 l_visual_class; /* Visual class */ + L_CARD32 l_red_mask; /* Red mask */ + L_CARD32 l_green_mask; /* Green mask */ + L_CARD32 l_blue_mask; /* Blue mask */ + L_CARD32 l_bits_per_rgb; /* Number of bits per RGB-part */ + L_CARD32 l_colormap_entries; /* Number of colors in color table (?) */ + L_CARD32 l_ncolors; /* Number of xwdcolor structures */ + L_CARD32 l_window_width; /* Window width */ + L_CARD32 l_window_height; /* Window height */ + L_CARD32 l_window_x; /* Window position x */ + L_CARD32 l_window_y; /* Window position y */ + L_CARD32 l_window_bdrwidth;/* Window border width */ +} L_XWDFILEHEADER; + +typedef struct +{ + L_CARD32 l_pixel; /* Color index */ + L_CARD16 l_red, l_green, l_blue; /* RGB-values */ + L_CARD8 l_flags, l_pad; +} L_XWDCOLOR; + + +/* Some structures for mapping up to 32bit-pixel */ +/* values which are kept in the XWD-Colormap */ + +#define MAPPERBITS 12 +#define MAPPERMASK ((1 << MAPPERBITS)-1) + +typedef struct +{ + L_CARD32 pixel_val; + guchar red; + guchar green; + guchar blue; +} PMAP; + +typedef struct +{ + gint npixel; /* Number of pixel values in map */ + guchar pixel_in_map[1 << MAPPERBITS]; + PMAP pmap[256]; +} PIXEL_MAP; + +#define XWDHDR_PAD 0 /* Total number of padding bytes for XWD header */ +#define XWDCOL_PAD 0 /* Total number of padding bytes for each XWD color */ + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + GError **error); +static gboolean save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error); +static gint32 create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageBaseType type, + GimpImageType gdtype, + gint32 *layer_ID, + GeglBuffer **buffer); + +static int set_pixelmap (gint ncols, + L_XWDCOLOR *xwdcol, + PIXEL_MAP *pixelmap); +static gboolean get_pixelmap (L_CARD32 pixelval, + PIXEL_MAP *pixelmap, + guchar *red, + guchar *green, + guchar *glue); + +static void set_bw_color_table (gint32 image_ID); +static void set_color_table (gint32 image_ID, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap); + +static gint32 load_xwd_f2_d1_b1 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap); +static gint32 load_xwd_f2_d8_b8 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap); +static gint32 load_xwd_f2_d16_b16 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap); +static gint32 load_xwd_f2_d24_b32 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap, + GError **error); +static gint32 load_xwd_f2_d32_b32 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap); +static gint32 load_xwd_f1_d24_b1 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap, + GError **error); + +static L_CARD32 read_card32 (FILE *ifp, + gint *err); +static L_CARD16 read_card16 (FILE *ifp, + gint *err); +static L_CARD8 read_card8 (FILE *ifp, + gint *err); + +static gboolean write_card32 (GOutputStream *output, + L_CARD32 c, + GError **error); +static gboolean write_card16 (GOutputStream *output, + L_CARD32 c, + GError **error); +static gboolean write_card8 (GOutputStream *output, + L_CARD32 c, + GError **error); + +static void read_xwd_header (FILE *ifp, + L_XWDFILEHEADER *xwdhdr); + +static gboolean write_xwd_header (GOutputStream *output, + L_XWDFILEHEADER *xwdhdr, + GError **error); + +static void read_xwd_cols (FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap); + +static gboolean write_xwd_cols (GOutputStream *output, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *colormap, + GError **error); + +static gint save_index (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + gboolean gray, + GError **error); +static gint save_rgb (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in the XWD (X Window Dump) format", + "Loads files in the XWD (X Window Dump) format. " + "XWD image files are produced by the program xwd. " + "Xwd is an X Window System window dumping utility.", + "Peter Kirchgessner", + "Peter Kirchgessner", + "1996", + N_("X window dump"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-xwindowdump"); + gimp_register_magic_load_handler (LOAD_PROC, + "xwd", + "", + "4,long,0x00000007"); + + gimp_install_procedure (SAVE_PROC, + "Exports files in the XWD (X Window Dump) format", + "XWD exporting handles all image types except " + "those with alpha channels.", + "Peter Kirchgessner", + "Peter Kirchgessner", + "1996", + N_("X window dump"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-xwindowdump"); + gimp_register_file_handler_uri (SAVE_PROC); + gimp_register_save_handler (SAVE_PROC, "xwd", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (param[1].data.d_string, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_ID, "XWD", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + /* No additional data to retrieve */ + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + GFile *file = g_file_new_for_uri (param[3].data.d_string); + + if (! save_image (file, image_ID, drawable_ID, &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + g_object_unref (file); + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + } + else + { + status = GIMP_PDB_CANCEL; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + + +static gint32 +load_image (const gchar *filename, + GError **error) +{ + FILE *ifp = NULL; + gint depth, bpp; + gint32 image_ID = -1; + L_XWDFILEHEADER xwdhdr; + L_XWDCOLOR *xwdcolmap = NULL; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + ifp = g_fopen (filename, "rb"); + if (!ifp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + goto out; + } + + read_xwd_header (ifp, &xwdhdr); + if (xwdhdr.l_file_version != 7) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Could not read XWD header from '%s'"), + gimp_filename_to_utf8 (filename)); + goto out; + } + +#ifdef XWD_COL_WAIT_DEBUG + { + int k = 1; + + while (k) + k = k; + } +#endif + + /* Position to start of XWDColor structures */ + fseek (ifp, (long)xwdhdr.l_header_size, SEEK_SET); + + /* Guard against insanely huge color maps -- gimp_image_set_colormap() only + * accepts colormaps with 0..256 colors anyway. */ + if (xwdhdr.l_colormap_entries > 256) + { + g_message (_("'%s':\nIllegal number of colormap entries: %ld"), + gimp_filename_to_utf8 (filename), + (long)xwdhdr.l_colormap_entries); + goto out; + } + + if (xwdhdr.l_colormap_entries > 0) + { + if (xwdhdr.l_colormap_entries < xwdhdr.l_ncolors) + { + g_message (_("'%s':\nNumber of colormap entries < number of colors"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + xwdcolmap = g_new (L_XWDCOLOR, xwdhdr.l_colormap_entries); + + read_xwd_cols (ifp, &xwdhdr, xwdcolmap); + +#ifdef XWD_COL_DEBUG + { + int j; + g_printf ("File %s\n",filename); + for (j = 0; j < xwdhdr.l_colormap_entries; j++) + g_printf ("Entry 0x%08lx: 0x%04lx, 0x%04lx, 0x%04lx, %d\n", + (long)xwdcolmap[j].l_pixel,(long)xwdcolmap[j].l_red, + (long)xwdcolmap[j].l_green,(long)xwdcolmap[j].l_blue, + (int)xwdcolmap[j].l_flags); + } +#endif + + if (xwdhdr.l_file_version != 7) + { + g_message (_("Can't read color entries")); + goto out; + } + } + + if (xwdhdr.l_pixmap_width <= 0) + { + g_message (_("'%s':\nNo image width specified"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + if (xwdhdr.l_pixmap_width > GIMP_MAX_IMAGE_SIZE + || xwdhdr.l_bytes_per_line > GIMP_MAX_IMAGE_SIZE * 3) + { + g_message (_("'%s':\nImage width is larger than GIMP can handle"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + if (xwdhdr.l_pixmap_height <= 0) + { + g_message (_("'%s':\nNo image height specified"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + if (xwdhdr.l_pixmap_height > GIMP_MAX_IMAGE_SIZE) + { + g_message (_("'%s':\nImage height is larger than GIMP can handle"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + depth = xwdhdr.l_pixmap_depth; + bpp = xwdhdr.l_bits_per_pixel; + + image_ID = -1; + switch (xwdhdr.l_pixmap_format) + { + case 0: /* Single plane bitmap */ + if ((depth == 1) && (bpp == 1)) + { /* Can be performed by format 2 loader */ + image_ID = load_xwd_f2_d1_b1 (filename, ifp, &xwdhdr, xwdcolmap); + } + break; + + case 1: /* Single plane pixmap */ + if ((depth <= 24) && (bpp == 1)) + { + image_ID = load_xwd_f1_d24_b1 (filename, ifp, &xwdhdr, xwdcolmap, + error); + } + break; + + case 2: /* Multiplane pixmaps */ + if ((depth == 1) && (bpp == 1)) + { + image_ID = load_xwd_f2_d1_b1 (filename, ifp, &xwdhdr, xwdcolmap); + } + else if ((depth <= 8) && (bpp == 8)) + { + image_ID = load_xwd_f2_d8_b8 (filename, ifp, &xwdhdr, xwdcolmap); + } + else if ((depth <= 16) && (bpp == 16)) + { + image_ID = load_xwd_f2_d16_b16 (filename, ifp, &xwdhdr, xwdcolmap); + } + else if ((depth <= 24) && ((bpp == 24) || (bpp == 32))) + { + image_ID = load_xwd_f2_d24_b32 (filename, ifp, &xwdhdr, xwdcolmap, + error); + } + else if ((depth <= 32) && (bpp == 32)) + { + image_ID = load_xwd_f2_d32_b32 (filename, ifp, &xwdhdr, xwdcolmap); + } + break; + } + gimp_progress_update (1.0); + + if (image_ID == -1 && ! (error && *error)) + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("XWD-file %s has format %d, depth %d and bits per pixel %d. " + "Currently this is not supported."), + gimp_filename_to_utf8 (filename), + (gint) xwdhdr.l_pixmap_format, depth, bpp); + +out: + if (ifp) + { + fclose (ifp); + } + + if (xwdcolmap) + { + g_free (xwdcolmap); + } + + return image_ID; +} + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + GOutputStream *output; + GimpImageType drawable_type; + gboolean success; + + drawable_type = gimp_drawable_type (drawable_ID); + + /* Make sure we're not exporting an image with an alpha channel */ + if (gimp_drawable_has_alpha (drawable_ID)) + { + g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Cannot export images with alpha channels.")); + return FALSE; + } + + switch (drawable_type) + { + case GIMP_INDEXED_IMAGE: + case GIMP_GRAY_IMAGE: + case GIMP_RGB_IMAGE: + break; + default: + g_message (_("Cannot operate on unknown image types.")); + return FALSE; + break; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_file_get_utf8_name (file)); + + output = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, 0, NULL, error)); + if (! output) + { + g_prefix_error (error, + _("Could not open '%s' for writing: "), + gimp_file_get_utf8_name (file)); + return FALSE; + } + + switch (drawable_type) + { + case GIMP_INDEXED_IMAGE: + success = save_index (output, image_ID, drawable_ID, FALSE, error); + break; + + case GIMP_GRAY_IMAGE: + success = save_index (output, image_ID, drawable_ID, TRUE, error); + break; + + case GIMP_RGB_IMAGE: + success = save_rgb (output, image_ID, drawable_ID, error); + break; + + default: + success = FALSE; + break; + } + + if (success && ! g_output_stream_close (output, NULL, error)) + { + g_prefix_error (error, + _("Error exporting '%s': "), + gimp_file_get_utf8_name (file)); + success = FALSE; + } + else if (! success) + { + GCancellable *cancellable; + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + } + + g_object_unref (output); + + gimp_progress_update (1.0); + + return success; +} + + +static L_CARD32 +read_card32 (FILE *ifp, + int *err) + +{ + L_CARD32 c; + + c = (((L_CARD32) (getc (ifp))) << 24); + c |= (((L_CARD32) (getc (ifp))) << 16); + c |= (((L_CARD32) (getc (ifp))) << 8); + c |= ((L_CARD32) (*err = getc (ifp))); + + *err = (*err < 0); + + return c; +} + +static L_CARD16 +read_card16 (FILE *ifp, + int *err) + +{ + L_CARD16 c; + + c = (((L_CARD16) (getc (ifp))) << 8); + c |= ((L_CARD16) (*err = getc (ifp))); + + *err = (*err < 0); + + return c; +} + +static L_CARD8 +read_card8 (FILE *ifp, + int *err) +{ + L_CARD8 c; + + c = ((L_CARD8) (*err = getc (ifp))); + + *err = (*err < 0); + + return c; +} + + +static gboolean +write_card32 (GOutputStream *output, + L_CARD32 c, + GError **error) +{ + guchar buffer[4]; + + buffer[0] = (c >> 24) & 0xff; + buffer[1] = (c >> 16) & 0xff; + buffer[2] = (c >> 8) & 0xff; + buffer[3] = (c) & 0xff; + + return g_output_stream_write_all (output, buffer, 4, + NULL, NULL, error); +} + +static gboolean +write_card16 (GOutputStream *output, + L_CARD32 c, + GError **error) +{ + guchar buffer[2]; + + buffer[0] = (c >> 8) & 0xff; + buffer[1] = (c) & 0xff; + + return g_output_stream_write_all (output, buffer, 2, + NULL, NULL, error); +} + +static gboolean +write_card8 (GOutputStream *output, + L_CARD32 c, + GError **error) +{ + guchar buffer[1]; + + buffer[0] = c & 0xff; + + return g_output_stream_write_all (output, buffer, 1, + NULL, NULL, error); +} + + +static void +read_xwd_header (FILE *ifp, + L_XWDFILEHEADER *xwdhdr) +{ + gint j, err; + L_CARD32 *cp; + + cp = (L_CARD32 *) xwdhdr; + + /* Read in all 32-bit values of the header and check for byte order */ + for (j = 0; j < sizeof (L_XWDFILEHEADER) / sizeof(xwdhdr->l_file_version); j++) + { + *(cp++) = read_card32 (ifp, &err); + + if (err) + break; + } + + if (err) + xwdhdr->l_file_version = 0; /* Not a valid XWD-file */ +} + + +/* Write out an XWD-fileheader. The header size is calculated here */ +static gboolean +write_xwd_header (GOutputStream *output, + L_XWDFILEHEADER *xwdhdr, + GError **error) + +{ + gint j, hdrpad, hdr_entries; + L_CARD32 *cp; + + hdrpad = XWDHDR_PAD; + hdr_entries = sizeof (L_XWDFILEHEADER) / sizeof(xwdhdr->l_file_version); + xwdhdr->l_header_size = hdr_entries * 4 + hdrpad; + + cp = (L_CARD32 *) xwdhdr; + + /* Write out all 32-bit values of the header and check for byte order */ + for (j = 0; j < sizeof (L_XWDFILEHEADER) / sizeof(xwdhdr->l_file_version); j++) + { + if (! write_card32 (output, *(cp++), error)) + return FALSE; + } + + /* Add padding bytes after XWD header */ + for (j = 0; j < hdrpad; j++) + if (! write_card8 (output, (L_CARD32) 0, error)) + return FALSE; + + return TRUE; +} + + +static void +read_xwd_cols (FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *colormap) +{ + gint j, err = 0; + gint flag_is_bad, index_is_bad; + gint indexed = (xwdhdr->l_pixmap_depth <= 8); + glong colmappos = ftell (ifp); + + /* Read in XWD-Color structures (the usual case) */ + flag_is_bad = index_is_bad = 0; + for (j = 0; j < xwdhdr->l_ncolors; j++) + { + colormap[j].l_pixel = read_card32 (ifp, &err); + + colormap[j].l_red = read_card16 (ifp, &err); + colormap[j].l_green = read_card16 (ifp, &err); + colormap[j].l_blue = read_card16 (ifp, &err); + + colormap[j].l_flags = read_card8 (ifp, &err); + colormap[j].l_pad = read_card8 (ifp, &err); + + if (indexed && (colormap[j].l_pixel > 255)) + index_is_bad++; + + if (err) + break; + } + + if (err) /* Not a valid XWD-file ? */ + xwdhdr->l_file_version = 0; + if (err || ((flag_is_bad == 0) && (index_is_bad == 0))) + return; + + /* Read in XWD-Color structures (with 4 bytes inserted infront of RGB) */ + fseek (ifp, colmappos, SEEK_SET); + flag_is_bad = index_is_bad = 0; + for (j = 0; j < xwdhdr->l_ncolors; j++) + { + colormap[j].l_pixel = read_card32 (ifp, &err); + + read_card32 (ifp, &err); /* Empty bytes on Alpha OSF */ + + colormap[j].l_red = read_card16 (ifp, &err); + colormap[j].l_green = read_card16 (ifp, &err); + colormap[j].l_blue = read_card16 (ifp, &err); + + colormap[j].l_flags = read_card8 (ifp, &err); + colormap[j].l_pad = read_card8 (ifp, &err); + + if (indexed && (colormap[j].l_pixel > 255)) + index_is_bad++; + + if (err) + break; + } + + if (err) /* Not a valid XWD-file ? */ + xwdhdr->l_file_version = 0; + if (err || ((flag_is_bad == 0) && (index_is_bad == 0))) + return; + + /* Read in XWD-Color structures (with 2 bytes inserted infront of RGB) */ + fseek (ifp, colmappos, SEEK_SET); + flag_is_bad = index_is_bad = 0; + for (j = 0; j < xwdhdr->l_ncolors; j++) + { + colormap[j].l_pixel = read_card32 (ifp, &err); + + read_card16 (ifp, &err); /* Empty bytes (from where ?) */ + + colormap[j].l_red = read_card16 (ifp, &err); + colormap[j].l_green = read_card16 (ifp, &err); + colormap[j].l_blue = read_card16 (ifp, &err); + + colormap[j].l_flags = read_card8 (ifp, &err); + colormap[j].l_pad = read_card8 (ifp, &err); + + /* if ((colormap[j].l_flags == 0) || (colormap[j].l_flags > 7)) + flag_is_bad++; */ + + if (indexed && (colormap[j].l_pixel > 255)) + index_is_bad++; + + if (err) + break; + } + + if (err) /* Not a valid XWD-file ? */ + xwdhdr->l_file_version = 0; + if (err || ((flag_is_bad == 0) && (index_is_bad == 0))) + return; + + /* Read in XWD-Color structures (every value is 8 bytes from a CRAY) */ + fseek (ifp, colmappos, SEEK_SET); + flag_is_bad = index_is_bad = 0; + for (j = 0; j < xwdhdr->l_ncolors; j++) + { + read_card32 (ifp, &err); + colormap[j].l_pixel = read_card32 (ifp, &err); + + read_card32 (ifp, &err); + colormap[j].l_red = read_card32 (ifp, &err); + read_card32 (ifp, &err); + colormap[j].l_green = read_card32 (ifp, &err); + read_card32 (ifp, &err); + colormap[j].l_blue = read_card32 (ifp, &err); + + /* The flag byte is kept in the first byte */ + colormap[j].l_flags = read_card8 (ifp, &err); + colormap[j].l_pad = read_card8 (ifp, &err); + read_card16 (ifp, &err); + read_card32 (ifp, &err); + + if (indexed && (colormap[j].l_pixel > 255)) + index_is_bad++; + + if (err) + break; + } + + if (flag_is_bad || index_is_bad) + { + g_printf ("xwd: Warning. Error in XWD-color-structure ("); + + if (flag_is_bad) g_printf ("flag"); + if (index_is_bad) g_printf ("index"); + + g_printf (")\n"); + } + + if (err) + xwdhdr->l_file_version = 0; /* Not a valid XWD-file */ +} + +static gboolean +write_xwd_cols (GOutputStream *output, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *colormap, + GError **error) +{ + gint j; + + for (j = 0; j < xwdhdr->l_colormap_entries; j++) + { +#ifdef CRAY + if (! (write_card32 (output, (L_CARD32)0, error) && + write_card32 (output, colormap[j].l_pixel, error) && + write_card32 (output, (L_CARD32)0, error) && + write_card32 (output, (L_CARD32)colormap[j].l_red, error) && + write_card32 (output, (L_CARD32)0, error) && + write_card32 (output, (L_CARD32)colormap[j].l_green, error) && + write_card32 (output, (L_CARD32)0, error) && + write_card32 (output, (L_CARD32)colormap[j].l_blue, error) && + write_card8 (output, (L_CARD32)colormap[j].l_flags, error) && + write_card8 (output, (L_CARD32)colormap[j].l_pad, error) && + write_card16 (output, (L_CARD32)0, error) && + write_card32 (output, (L_CARD32)0, error))) + { + return FALSE; + } +#else +#ifdef __alpha + if (! (write_card32 (output, colormap[j].l_pixel, error) && + write_card32 (output, (L_CARD32)0, error) && + write_card16 (output, (L_CARD32)colormap[j].l_red, error) && + write_card16 (output, (L_CARD32)colormap[j].l_green, error) && + write_card16 (output, (L_CARD32)colormap[j].l_blue, error) && + write_card8 (output, (L_CARD32)colormap[j].l_flags, error) && + write_card8 (output, (L_CARD32)colormap[j].l_pad, error))) + { + return FALSE; + } +#else + if (! (write_card32 (output, colormap[j].l_pixel, error) && + write_card16 (output, (L_CARD32)colormap[j].l_red, error) && + write_card16 (output, (L_CARD32)colormap[j].l_green, error) && + write_card16 (output, (L_CARD32)colormap[j].l_blue, error) && + write_card8 (output, (L_CARD32)colormap[j].l_flags, error) && + write_card8 (output, (L_CARD32)colormap[j].l_pad, error))) + { + return FALSE; + } +#endif +#endif + } + + return TRUE; +} + + +/* Create a map for mapping up to 32 bit pixelvalues to RGB. + * Returns number of colors kept in the map (up to 256) + */ +static gint +set_pixelmap (int ncols, + L_XWDCOLOR *xwdcol, + PIXEL_MAP *pixelmap) + +{ + gint i, j, k, maxcols; + L_CARD32 pixel_val; + + memset ((gchar *) pixelmap, 0, sizeof (PIXEL_MAP)); + + maxcols = 0; + + for (j = 0; j < ncols; j++) /* For each entry of the XWD colormap */ + { + pixel_val = xwdcol[j].l_pixel; + for (k = 0; k < maxcols; k++) /* Where to insert in list ? */ + { + if (pixel_val <= pixelmap->pmap[k].pixel_val) + break; + } + if ((k < maxcols) && (pixel_val == pixelmap->pmap[k].pixel_val)) + break; /* It was already in list */ + + if (k >= 256) + break; + + if (k < maxcols) /* Must move entries to the back ? */ + { + for (i = maxcols-1; i >= k; i--) + memcpy ((char *)&(pixelmap->pmap[i+1]),(char *)&(pixelmap->pmap[i]), + sizeof (PMAP)); + } + pixelmap->pmap[k].pixel_val = pixel_val; + pixelmap->pmap[k].red = xwdcol[j].l_red >> 8; + pixelmap->pmap[k].green = xwdcol[j].l_green >> 8; + pixelmap->pmap[k].blue = xwdcol[j].l_blue >> 8; + pixelmap->pixel_in_map[pixel_val & MAPPERMASK] = 1; + maxcols++; + } + pixelmap->npixel = maxcols; +#ifdef XWD_COL_DEBUG + g_printf ("Colors in pixelmap: %d\n",pixelmap->npixel); + for (j=0; jnpixel; j++) + g_printf ("Pixelvalue 0x%08lx, 0x%02x 0x%02x 0x%02x\n", + pixelmap->pmap[j].pixel_val, + pixelmap->pmap[j].red,pixelmap->pmap[j].green, + pixelmap->pmap[j].blue); + for (j=0; j<=MAPPERMASK; j++) + g_printf ("0x%08lx: %d\n",(long)j,pixelmap->pixel_in_map[j]); +#endif + + return pixelmap->npixel; +} + + +/* Search a pixel value in the pixel map. Returns FALSE if the + * pixelval was not found in map. Returns TRUE if found. + */ +static gboolean +get_pixelmap (L_CARD32 pixelval, + PIXEL_MAP *pixelmap, + guchar *red, + guchar *green, + guchar *blue) + +{ + register PMAP *low, *high, *middle; + + if (pixelmap->npixel == 0) + return FALSE; + + if (!(pixelmap->pixel_in_map[pixelval & MAPPERMASK])) + return FALSE; + + low = &(pixelmap->pmap[0]); + high = &(pixelmap->pmap[pixelmap->npixel-1]); + + /* Do a binary search on the array */ + while (low < high) + { + middle = low + ((high - low)/2); + if (pixelval <= middle->pixel_val) + high = middle; + else + low = middle+1; + } + + if (pixelval == low->pixel_val) + { + *red = low->red; *green = low->green; *blue = low->blue; + return TRUE; + } + + return FALSE; +} + + +static void +set_bw_color_table (gint32 image_ID) +{ + static guchar BWColorMap[2*3] = { 255, 255, 255, 0, 0, 0 }; + +#ifdef XWD_COL_DEBUG + g_printf ("Set GIMP b/w-colortable:\n"); +#endif + + gimp_image_set_colormap (image_ID, BWColorMap, 2); +} + + +/* Initialize an 8-bit colortable from the mask-values of the XWD-header */ +static void +init_color_table256 (L_XWDFILEHEADER *xwdhdr, + guchar *ColorMap) + +{ + gint i, j, k, cuind; + gint redshift, greenshift, blueshift; + gint maxred, maxgreen, maxblue; + + /* Assume: the bit masks for red/green/blue are grouped together + * Example: redmask = 0xe0, greenmask = 0x1c, bluemask = 0x03 + * We need to know where to place the RGB-values (shifting) + * and the maximum value for each component. + */ + redshift = greenshift = blueshift = 0; + if ((maxred = xwdhdr->l_red_mask) == 0) + return; + + /* Shift the redmask to the rightmost bit position to get + * maximum value for red. + */ + while ((maxred & 1) == 0) + { + redshift++; + maxred >>= 1; + } + + if ((maxgreen = xwdhdr->l_green_mask) == 0) + return; + + while ((maxgreen & 1) == 0) + { + greenshift++; + maxgreen >>= 1; + } + + if ((maxblue = xwdhdr->l_blue_mask) == 0) + return; + + while ((maxblue & 1) == 0) + { + blueshift++; + maxblue >>= 1; + } + + memset ((gchar *) ColorMap, 0, 256 * 3); + + for (i = 0; i <= maxred; i++) + for (j = 0; j <= maxgreen; j++) + for (k = 0; k <= maxblue; k++) + { + cuind = (i << redshift) | (j << greenshift) | (k << blueshift); + + if (cuind < 256) + { + ColorMap[cuind*3] = (i * 255)/maxred; + ColorMap[cuind*3+1] = (j * 255)/maxgreen; + ColorMap[cuind*3+2] = (k * 255)/maxblue; + } + } +} + + +static void +set_color_table (gint32 image_ID, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap) + +{ + gint ncols, i, j; + guchar ColorMap[256 * 3]; + + ncols = xwdhdr->l_colormap_entries; + if (xwdhdr->l_ncolors < ncols) + ncols = xwdhdr->l_ncolors; + if (ncols <= 0) + return; + if (ncols > 256) + ncols = 256; + + /* Initialize color table for all 256 entries from mask-values */ + init_color_table256 (xwdhdr, ColorMap); + + for (j = 0; j < ncols; j++) + { + i = xwdcolmap[j].l_pixel; + if ((i >= 0) && (i < 256)) + { + ColorMap[i*3] = (xwdcolmap[j].l_red) >> 8; + ColorMap[i*3+1] = (xwdcolmap[j].l_green) >> 8; + ColorMap[i*3+2] = (xwdcolmap[j].l_blue) >> 8; + } + } + +#ifdef XWD_COL_DEBUG + g_printf ("Set GIMP colortable:\n"); + for (j = 0; j < 256; j++) + g_printf ("%3d: 0x%02x 0x%02x 0x%02x\n", j, + ColorMap[j*3], ColorMap[j*3+1], ColorMap[j*3+2]); +#endif + + gimp_image_set_colormap (image_ID, ColorMap, 256); +} + + + + +/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */ +static gint32 +create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageBaseType type, + GimpImageType gdtype, + gint32 *layer_ID, + GeglBuffer **buffer) +{ + gint32 image_ID; + + image_ID = gimp_image_new (width, height, type); + gimp_image_set_filename (image_ID, filename); + + *layer_ID = gimp_layer_new (image_ID, "Background", width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, *layer_ID, -1, 0); + + *buffer = gimp_drawable_get_buffer (*layer_ID); + + return image_ID; +} + + +/* Load XWD with pixmap_format 2, pixmap_depth 1, bits_per_pixel 1 */ + +static gint32 +load_xwd_f2_d1_b1 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap) +{ + register int pix8; + register guchar *dest, *src; + guchar c1, c2, c3, c4; + gint width, height, scan_lines, tile_height; + gint i, j, ncols; + gchar *temp; + guchar bit2byte[256 * 8]; + guchar *data, *scanline; + gint err = 0; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + +#ifdef XWD_DEBUG + g_printf ("load_xwd_f2_d1_b1 (%s)\n", filename); +#endif + + width = xwdhdr->l_pixmap_width; + height = xwdhdr->l_pixmap_height; + + image_ID = create_new_image (filename, width, height, GIMP_INDEXED, + GIMP_INDEXED_IMAGE, &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width); + + scanline = g_new (guchar, xwdhdr->l_bytes_per_line + 8); + + ncols = xwdhdr->l_colormap_entries; + if (xwdhdr->l_ncolors < ncols) + ncols = xwdhdr->l_ncolors; + + if (ncols < 2) + set_bw_color_table (image_ID); + else + set_color_table (image_ID, xwdhdr, xwdcolmap); + + temp = (gchar *) bit2byte; + + /* Get an array for mapping 8 bits in a byte to 8 bytes */ + if (!xwdhdr->l_bitmap_bit_order) + { + for (j = 0; j < 256; j++) + for (i = 0; i < 8; i++) + *(temp++) = ((j & (1 << i)) != 0); + } + else + { + for (j = 0; j < 256; j++) + for (i = 7; i >= 0; i--) + *(temp++) = ((j & (1 << i)) != 0); + } + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + if (fread (scanline, xwdhdr->l_bytes_per_line, 1, ifp) != 1) + { + err = 1; + break; + } + + /* Need to check byte order ? */ + if (xwdhdr->l_bitmap_bit_order != xwdhdr->l_byte_order) + { + src = scanline; + switch (xwdhdr->l_bitmap_unit) + { + case 16: + j = xwdhdr->l_bytes_per_line; + while (j > 0) + { + c1 = src[0]; c2 = src[1]; + *(src++) = c2; *(src++) = c1; + j -= 2; + } + break; + + case 32: + j = xwdhdr->l_bytes_per_line; + while (j > 0) + { + c1 = src[0]; c2 = src[1]; c3 = src[2]; c4 = src[3]; + *(src++) = c4; *(src++) = c3; *(src++) = c2; *(src++) = c1; + j -= 4; + } + break; + } + } + src = scanline; + j = width; + while (j >= 8) + { + pix8 = *(src++); + memcpy (dest, bit2byte + pix8*8, 8); + dest += 8; + j -= 8; + } + if (j > 0) + { + pix8 = *(src++); + memcpy (dest, bit2byte + pix8*8, j); + dest += j; + } + + scan_lines++; + + if ((i % 20) == 0) + gimp_progress_update ((double)(i+1) / (double)height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + scan_lines = 0; + dest = data; + } + if (err) break; + } + + g_free (data); + g_free (scanline); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return err ? -1 : image_ID; +} + + +/* Load XWD with pixmap_format 2, pixmap_depth 8, bits_per_pixel 8 */ + +static gint32 +load_xwd_f2_d8_b8 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap) +{ + gint width, height, linepad, tile_height, scan_lines; + gint i, j, ncols; + gint grayscale; + guchar *dest, *data; + gint err = 0; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + +#ifdef XWD_DEBUG + g_printf ("load_xwd_f2_d8_b8 (%s)\n", filename); +#endif + + width = xwdhdr->l_pixmap_width; + height = xwdhdr->l_pixmap_height; + + /* This could also be a grayscale image. Check it */ + grayscale = 0; + if ((xwdhdr->l_ncolors == 256) && (xwdhdr->l_colormap_entries == 256)) + { + for (j = 0; j < 256; j++) + { + if ((xwdcolmap[j].l_pixel != j) + || ((xwdcolmap[j].l_red >> 8) != j) + || ((xwdcolmap[j].l_green >> 8) != j) + || ((xwdcolmap[j].l_blue >> 8) != j)) + break; + } + + grayscale = (j == 256); + } + + image_ID = create_new_image (filename, width, height, + grayscale ? GIMP_GRAY : GIMP_INDEXED, + grayscale ? GIMP_GRAY_IMAGE : GIMP_INDEXED_IMAGE, + &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width); + + if (!grayscale) + { + ncols = xwdhdr->l_colormap_entries; + if (xwdhdr->l_ncolors < ncols) ncols = xwdhdr->l_ncolors; + if (ncols < 2) + set_bw_color_table (image_ID); + else + set_color_table (image_ID, xwdhdr, xwdcolmap); + } + + linepad = xwdhdr->l_bytes_per_line - xwdhdr->l_pixmap_width; + if (linepad < 0) + linepad = 0; + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + if (fread (dest, 1, width, ifp) != width) + { + err = 1; + break; + } + dest += width; + + for (j = 0; j < linepad; j++) + getc (ifp); + + scan_lines++; + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) (i + 1) / (gdouble) height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + scan_lines = 0; + dest = data; + } + } + + g_free (data); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return err ? -1 : image_ID; +} + + +/* Load XWD with pixmap_format 2, pixmap_depth up to 16, bits_per_pixel 16 */ + +static gint32 +load_xwd_f2_d16_b16 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap) +{ + register guchar *dest, lsbyte_first; + gint width, height, linepad, i, j, c0, c1, ncols; + gint red, green, blue, redval, greenval, blueval; + gint maxred, maxgreen, maxblue; + gint tile_height, scan_lines; + gulong redmask, greenmask, bluemask; + guint redshift, greenshift, blueshift; + gulong maxval; + guchar *ColorMap, *cm, *data; + gint err = 0; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + +#ifdef XWD_DEBUG + g_printf ("load_xwd_f2_d16_b16 (%s)\n", filename); +#endif + + width = xwdhdr->l_pixmap_width; + height = xwdhdr->l_pixmap_height; + + image_ID = create_new_image (filename, width, height, GIMP_RGB, + GIMP_RGB_IMAGE, &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width * 3); + + /* Get memory for mapping 16 bit XWD-pixel to GIMP-RGB */ + maxval = 0x10000 * 3; + ColorMap = g_new0 (guchar, maxval); + + redmask = xwdhdr->l_red_mask; + greenmask = xwdhdr->l_green_mask; + bluemask = xwdhdr->l_blue_mask; + + /* How to shift RGB to be right aligned ? */ + /* (We rely on the the mask bits are grouped and not mixed) */ + redshift = greenshift = blueshift = 0; + + while (((1 << redshift) & redmask) == 0) redshift++; + while (((1 << greenshift) & greenmask) == 0) greenshift++; + while (((1 << blueshift) & bluemask) == 0) blueshift++; + + /* The bits_per_rgb may not be correct. Use redmask instead */ + maxred = 0; while (redmask >> (redshift + maxred)) maxred++; + maxred = (1 << maxred) - 1; + + maxgreen = 0; while (greenmask >> (greenshift + maxgreen)) maxgreen++; + maxgreen = (1 << maxgreen) - 1; + + maxblue = 0; while (bluemask >> (blueshift + maxblue)) maxblue++; + maxblue = (1 << maxblue) - 1; + + /* Built up the array to map XWD-pixel value to GIMP-RGB */ + for (red = 0; red <= maxred; red++) + { + redval = (red * 255) / maxred; + for (green = 0; green <= maxgreen; green++) + { + greenval = (green * 255) / maxgreen; + for (blue = 0; blue <= maxblue; blue++) + { + blueval = (blue * 255) / maxblue; + cm = ColorMap + ((red << redshift) + (green << greenshift) + + (blue << blueshift)) * 3; + *(cm++) = redval; + *(cm++) = greenval; + *cm = blueval; + } + } + } + + /* Now look what was written to the XWD-Colormap */ + + ncols = xwdhdr->l_colormap_entries; + if (xwdhdr->l_ncolors < ncols) + ncols = xwdhdr->l_ncolors; + + for (j = 0; j < ncols; j++) + { + cm = ColorMap + xwdcolmap[j].l_pixel * 3; + *(cm++) = (xwdcolmap[j].l_red >> 8); + *(cm++) = (xwdcolmap[j].l_green >> 8); + *cm = (xwdcolmap[j].l_blue >> 8); + } + + /* What do we have to consume after a line has finished ? */ + linepad = xwdhdr->l_bytes_per_line + - (xwdhdr->l_pixmap_width*xwdhdr->l_bits_per_pixel)/8; + if (linepad < 0) linepad = 0; + + lsbyte_first = (xwdhdr->l_byte_order == 0); + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + { + c0 = getc (ifp); + c1 = getc (ifp); + if (c1 < 0) + { + err = 1; + break; + } + + if (lsbyte_first) + c0 = c0 | (c1 << 8); + else + c0 = (c0 << 8) | c1; + + cm = ColorMap + c0 * 3; + *(dest++) = *(cm++); + *(dest++) = *(cm++); + *(dest++) = *cm; + } + + if (err) + break; + + for (j = 0; j < linepad; j++) + getc (ifp); + + scan_lines++; + + if ((i % 20) == 0) + gimp_progress_update ((double)(i+1) / (double)height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + scan_lines = 0; + dest = data; + } + } + g_free (data); + g_free (ColorMap); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return err ? -1 : image_ID; +} + + +/* Load XWD with pixmap_format 2, pixmap_depth up to 24, bits_per_pixel 24/32 */ + +static gint32 +load_xwd_f2_d24_b32 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap, + GError **error) +{ + register guchar *dest, lsbyte_first; + gint width, height, linepad, i, j, c0, c1, c2, c3; + gint tile_height, scan_lines; + L_CARD32 pixelval; + gint red, green, blue, ncols; + gint maxred, maxgreen, maxblue; + gulong redmask, greenmask, bluemask; + guint redshift, greenshift, blueshift; + guchar redmap[256], greenmap[256], bluemap[256]; + guchar *data; + PIXEL_MAP pixel_map; + gint err = 0; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + +#ifdef XWD_DEBUG + g_printf ("load_xwd_f2_d24_b32 (%s)\n", filename); +#endif + + width = xwdhdr->l_pixmap_width; + height = xwdhdr->l_pixmap_height; + + redmask = xwdhdr->l_red_mask; + greenmask = xwdhdr->l_green_mask; + bluemask = xwdhdr->l_blue_mask; + + if (redmask == 0) redmask = 0xff0000; + if (greenmask == 0) greenmask = 0x00ff00; + if (bluemask == 0) bluemask = 0x0000ff; + + /* How to shift RGB to be right aligned ? */ + /* (We rely on the the mask bits are grouped and not mixed) */ + redshift = greenshift = blueshift = 0; + + while (((1 << redshift) & redmask) == 0) redshift++; + while (((1 << greenshift) & greenmask) == 0) greenshift++; + while (((1 << blueshift) & bluemask) == 0) blueshift++; + + /* The bits_per_rgb may not be correct. Use redmask instead */ + + maxred = 0; while (redmask >> (redshift + maxred)) maxred++; + maxred = (1 << maxred) - 1; + + maxgreen = 0; while (greenmask >> (greenshift + maxgreen)) maxgreen++; + maxgreen = (1 << maxgreen) - 1; + + maxblue = 0; while (bluemask >> (blueshift + maxblue)) maxblue++; + maxblue = (1 << maxblue) - 1; + + if (maxred > sizeof (redmap) || + maxgreen > sizeof (greenmap) || + maxblue > sizeof (bluemap)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("XWD-file %s is corrupt."), + gimp_filename_to_utf8 (filename)); + return -1; + } + + image_ID = create_new_image (filename, width, height, GIMP_RGB, + GIMP_RGB_IMAGE, &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width * 3); + + /* Set map-arrays for red, green, blue */ + for (red = 0; red <= maxred; red++) + redmap[red] = (red * 255) / maxred; + for (green = 0; green <= maxgreen; green++) + greenmap[green] = (green * 255) / maxgreen; + for (blue = 0; blue <= maxblue; blue++) + bluemap[blue] = (blue * 255) / maxblue; + + ncols = xwdhdr->l_colormap_entries; + if (xwdhdr->l_ncolors < ncols) + ncols = xwdhdr->l_ncolors; + + set_pixelmap (ncols, xwdcolmap, &pixel_map); + + /* What do we have to consume after a line has finished ? */ + linepad = xwdhdr->l_bytes_per_line + - (xwdhdr->l_pixmap_width*xwdhdr->l_bits_per_pixel)/8; + if (linepad < 0) linepad = 0; + + lsbyte_first = (xwdhdr->l_byte_order == 0); + + dest = data; + scan_lines = 0; + + if (xwdhdr->l_bits_per_pixel == 32) + { + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + { + c0 = getc (ifp); + c1 = getc (ifp); + c2 = getc (ifp); + c3 = getc (ifp); + if (c3 < 0) + { + err = 1; + break; + } + if (lsbyte_first) + pixelval = c0 | (c1 << 8) | (c2 << 16) | (c3 << 24); + else + pixelval = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; + + if (get_pixelmap (pixelval, &pixel_map, dest, dest+1, dest+2)) + { + dest += 3; + } + else + { + *(dest++) = redmap[(pixelval & redmask) >> redshift]; + *(dest++) = greenmap[(pixelval & greenmask) >> greenshift]; + *(dest++) = bluemap[(pixelval & bluemask) >> blueshift]; + } + } + scan_lines++; + + if (err) + break; + + for (j = 0; j < linepad; j++) + getc (ifp); + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) (i + 1) / (gdouble) height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + scan_lines = 0; + dest = data; + } + } + } + else /* 24 bits per pixel */ + { + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + { + c0 = getc (ifp); + c1 = getc (ifp); + c2 = getc (ifp); + if (c2 < 0) + { + err = 1; + break; + } + if (lsbyte_first) + pixelval = c0 | (c1 << 8) | (c2 << 16); + else + pixelval = (c0 << 16) | (c1 << 8) | c2; + + if (get_pixelmap (pixelval, &pixel_map, dest, dest+1, dest+2)) + { + dest += 3; + } + else + { + *(dest++) = redmap[(pixelval & redmask) >> redshift]; + *(dest++) = greenmap[(pixelval & greenmask) >> greenshift]; + *(dest++) = bluemap[(pixelval & bluemask) >> blueshift]; + } + } + scan_lines++; + + if (err) + break; + + for (j = 0; j < linepad; j++) + getc (ifp); + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) (i + 1) / (gdouble) height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + scan_lines = 0; + dest = data; + } + } + } + + g_free (data); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return err ? -1 : image_ID; +} + +/* Load XWD with pixmap_format 2, pixmap_depth up to 32, bits_per_pixel 32 */ + +static gint32 +load_xwd_f2_d32_b32 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap) +{ + register guchar *dest, lsbyte_first; + gint width, height, linepad, i, j, c0, c1, c2, c3; + gint tile_height, scan_lines; + L_CARD32 pixelval; + gint red, green, blue, alpha, ncols; + gint maxred, maxgreen, maxblue, maxalpha; + gulong redmask, greenmask, bluemask, alphamask; + guint redshift, greenshift, blueshift, alphashift; + guchar redmap[256], greenmap[256], bluemap[256], alphamap[256]; + guchar *data; + PIXEL_MAP pixel_map; + gint err = 0; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + +#ifdef XWD_DEBUG + g_printf ("load_xwd_f2_d32_b32 (%s)\n", filename); +#endif + + width = xwdhdr->l_pixmap_width; + height = xwdhdr->l_pixmap_height; + + image_ID = create_new_image (filename, width, height, GIMP_RGB, + GIMP_RGBA_IMAGE, &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width * 4); + + redmask = xwdhdr->l_red_mask; + greenmask = xwdhdr->l_green_mask; + bluemask = xwdhdr->l_blue_mask; + + if (redmask == 0) redmask = 0xff0000; + if (greenmask == 0) greenmask = 0x00ff00; + if (bluemask == 0) bluemask = 0x0000ff; + alphamask = 0xffffffff & ~(redmask | greenmask | bluemask); + + /* How to shift RGB to be right aligned ? */ + /* (We rely on the the mask bits are grouped and not mixed) */ + redshift = greenshift = blueshift = alphashift = 0; + + while (((1 << redshift) & redmask) == 0) redshift++; + while (((1 << greenshift) & greenmask) == 0) greenshift++; + while (((1 << blueshift) & bluemask) == 0) blueshift++; + while (((1 << alphashift) & alphamask) == 0) alphashift++; + + /* The bits_per_rgb may not be correct. Use redmask instead */ + + maxred = 0; while (redmask >> (redshift + maxred)) maxred++; + maxred = (1 << maxred) - 1; + + maxgreen = 0; while (greenmask >> (greenshift + maxgreen)) maxgreen++; + maxgreen = (1 << maxgreen) - 1; + + maxblue = 0; while (bluemask >> (blueshift + maxblue)) maxblue++; + maxblue = (1 << maxblue) - 1; + + maxalpha = 0; while (alphamask >> (alphashift + maxalpha)) maxalpha++; + maxalpha = (1 << maxalpha) - 1; + + /* Set map-arrays for red, green, blue */ + for (red = 0; red <= maxred; red++) + redmap[red] = (red * 255) / maxred; + for (green = 0; green <= maxgreen; green++) + greenmap[green] = (green * 255) / maxgreen; + for (blue = 0; blue <= maxblue; blue++) + bluemap[blue] = (blue * 255) / maxblue; + for (alpha = 0; alpha <= maxalpha; alpha++) + alphamap[alpha] = (alpha * 255) / maxalpha; + + ncols = xwdhdr->l_colormap_entries; + if (xwdhdr->l_ncolors < ncols) + ncols = xwdhdr->l_ncolors; + + set_pixelmap (ncols, xwdcolmap, &pixel_map); + + /* What do we have to consume after a line has finished ? */ + linepad = xwdhdr->l_bytes_per_line + - (xwdhdr->l_pixmap_width*xwdhdr->l_bits_per_pixel)/8; + if (linepad < 0) linepad = 0; + + lsbyte_first = (xwdhdr->l_byte_order == 0); + + dest = data; + scan_lines = 0; + + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + { + c0 = getc (ifp); + c1 = getc (ifp); + c2 = getc (ifp); + c3 = getc (ifp); + if (c3 < 0) + { + err = 1; + break; + } + if (lsbyte_first) + pixelval = c0 | (c1 << 8) | (c2 << 16) | (c3 << 24); + else + pixelval = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; + + if (get_pixelmap (pixelval, &pixel_map, dest, dest+1, dest+2)) + { + /* FIXME: is it always transparent or encoded in an unknown way? */ + *(dest+3) = 0x00; + dest += 4; + } + else + { + *(dest++) = redmap[(pixelval & redmask) >> redshift]; + *(dest++) = greenmap[(pixelval & greenmask) >> greenshift]; + *(dest++) = bluemap[(pixelval & bluemask) >> blueshift]; + *(dest++) = alphamap[(pixelval & alphamask) >> alphashift]; + } + } + scan_lines++; + + if (err) + break; + + for (j = 0; j < linepad; j++) + getc (ifp); + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) (i + 1) / (gdouble) height); + + if ((scan_lines == tile_height) || ((i+1) == height)) + { + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1, + width, scan_lines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + scan_lines = 0; + dest = data; + } + } + + g_free (data); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return err ? -1 : image_ID; +} + +/* Load XWD with pixmap_format 1, pixmap_depth up to 24, bits_per_pixel 1 */ + +static gint32 +load_xwd_f1_d24_b1 (const gchar *filename, + FILE *ifp, + L_XWDFILEHEADER *xwdhdr, + L_XWDCOLOR *xwdcolmap, + GError **error) +{ + register guchar *dest, outmask, inmask, do_reverse; + gint width, height, i, j, plane, fromright; + gint tile_height, tile_start, tile_end; + gint indexed, bytes_per_pixel; + gint maxred, maxgreen, maxblue; + gint red, green, blue, ncols, standard_rgb; + glong data_offset, plane_offset, tile_offset; + gulong redmask, greenmask, bluemask; + guint redshift, greenshift, blueshift; + gulong g; + guchar redmap[256], greenmap[256], bluemap[256]; + guchar bit_reverse[256]; + guchar *xwddata, *xwdin, *data; + L_CARD32 pixelval; + PIXEL_MAP pixel_map; + gint err = 0; + gint32 layer_ID, image_ID; + GeglBuffer *buffer; + +#ifdef XWD_DEBUG + g_printf ("load_xwd_f1_d24_b1 (%s)\n", filename); +#endif + + xwddata = g_malloc (xwdhdr->l_bytes_per_line); + if (xwddata == NULL) + return -1; + + width = xwdhdr->l_pixmap_width; + height = xwdhdr->l_pixmap_height; + indexed = (xwdhdr->l_pixmap_depth <= 8); + bytes_per_pixel = (indexed ? 1 : 3); + + for (j = 0; j < 256; j++) /* Create an array for reversing bits */ + { + inmask = 0; + for (i = 0; i < 8; i++) + { + inmask <<= 1; + if (j & (1 << i)) inmask |= 1; + } + bit_reverse[j] = inmask; + } + + redmask = xwdhdr->l_red_mask; + greenmask = xwdhdr->l_green_mask; + bluemask = xwdhdr->l_blue_mask; + + if (redmask == 0) redmask = 0xff0000; + if (greenmask == 0) greenmask = 0x00ff00; + if (bluemask == 0) bluemask = 0x0000ff; + + standard_rgb = (redmask == 0xff0000) && (greenmask == 0x00ff00) + && (bluemask == 0x0000ff); + redshift = greenshift = blueshift = 0; + + if (!standard_rgb) /* Do we need to re-map the pixel-values ? */ + { + /* How to shift RGB to be right aligned ? */ + /* (We rely on the the mask bits are grouped and not mixed) */ + + while (((1 << redshift) & redmask) == 0) redshift++; + while (((1 << greenshift) & greenmask) == 0) greenshift++; + while (((1 << blueshift) & bluemask) == 0) blueshift++; + + /* The bits_per_rgb may not be correct. Use redmask instead */ + + maxred = 0; while (redmask >> (redshift + maxred)) maxred++; + maxred = (1 << maxred) - 1; + + maxgreen = 0; while (greenmask >> (greenshift + maxgreen)) maxgreen++; + maxgreen = (1 << maxgreen) - 1; + + maxblue = 0; while (bluemask >> (blueshift + maxblue)) maxblue++; + maxblue = (1 << maxblue) - 1; + + if (maxred > sizeof (redmap) || + maxgreen > sizeof (greenmap) || + maxblue > sizeof (bluemap)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("XWD-file %s is corrupt."), + gimp_filename_to_utf8 (filename)); + return -1; + } + + /* Set map-arrays for red, green, blue */ + for (red = 0; red <= maxred; red++) + redmap[red] = (red * 255) / maxred; + for (green = 0; green <= maxgreen; green++) + greenmap[green] = (green * 255) / maxgreen; + for (blue = 0; blue <= maxblue; blue++) + bluemap[blue] = (blue * 255) / maxblue; + } + + image_ID = create_new_image (filename, width, height, + indexed ? GIMP_INDEXED : GIMP_RGB, + indexed ? GIMP_INDEXED_IMAGE : GIMP_RGB_IMAGE, + &layer_ID, &buffer); + + tile_height = gimp_tile_height (); + data = g_malloc (tile_height * width * bytes_per_pixel); + + ncols = xwdhdr->l_colormap_entries; + if (xwdhdr->l_ncolors < ncols) + ncols = xwdhdr->l_ncolors; + + if (indexed) + { + if (ncols < 2) + set_bw_color_table (image_ID); + else + set_color_table (image_ID, xwdhdr, xwdcolmap); + } + else + { + set_pixelmap (ncols, xwdcolmap, &pixel_map); + } + + do_reverse = !xwdhdr->l_bitmap_bit_order; + + /* This is where the image data starts within the file */ + data_offset = ftell (ifp); + + for (tile_start = 0; tile_start < height; tile_start += tile_height) + { + memset (data, 0, width*tile_height*bytes_per_pixel); + + tile_end = tile_start + tile_height - 1; + if (tile_end >= height) + tile_end = height - 1; + + for (plane = 0; plane < xwdhdr->l_pixmap_depth; plane++) + { + dest = data; /* Position to start of tile within the plane */ + plane_offset = data_offset + plane*height*xwdhdr->l_bytes_per_line; + tile_offset = plane_offset + tile_start*xwdhdr->l_bytes_per_line; + fseek (ifp, tile_offset, SEEK_SET); + + /* Place the last plane at the least significant bit */ + + if (indexed) /* Only 1 byte per pixel */ + { + fromright = xwdhdr->l_pixmap_depth-1-plane; + outmask = (1 << fromright); + } + else /* 3 bytes per pixel */ + { + fromright = xwdhdr->l_pixmap_depth-1-plane; + dest += 2 - fromright/8; + outmask = (1 << (fromright % 8)); + } + + for (i = tile_start; i <= tile_end; i++) + { + if (fread (xwddata,xwdhdr->l_bytes_per_line,1,ifp) != 1) + { + err = 1; + break; + } + xwdin = xwddata; + + /* Handle bitmap unit */ + if (xwdhdr->l_bitmap_unit == 16) + { + if (xwdhdr->l_bitmap_bit_order != xwdhdr->l_byte_order) + { + j = xwdhdr->l_bytes_per_line/2; + while (j--) + { + inmask = xwdin[0]; xwdin[0] = xwdin[1]; xwdin[1] = inmask; + xwdin += 2; + } + xwdin = xwddata; + } + } + else if (xwdhdr->l_bitmap_unit == 32) + { + if (xwdhdr->l_bitmap_bit_order != xwdhdr->l_byte_order) + { + j = xwdhdr->l_bytes_per_line/4; + while (j--) + { + inmask = xwdin[0]; xwdin[0] = xwdin[3]; xwdin[3] = inmask; + inmask = xwdin[1]; xwdin[1] = xwdin[2]; xwdin[2] = inmask; + xwdin += 4; + } + xwdin = xwddata; + } + } + + g = inmask = 0; + for (j = 0; j < width; j++) + { + if (!inmask) + { + g = *(xwdin++); + if (do_reverse) + g = bit_reverse[g]; + inmask = 0x80; + } + + if (g & inmask) + *dest |= outmask; + dest += bytes_per_pixel; + + inmask >>= 1; + } + } + } + + /* For indexed images, the mapping to colors is done by the color table. */ + /* Otherwise we must do the mapping by ourself. */ + if (!indexed) + { + dest = data; + for (i = tile_start; i <= tile_end; i++) + { + for (j = 0; j < width; j++) + { + pixelval = (*dest << 16) | (*(dest+1) << 8) | *(dest+2); + + if (get_pixelmap (pixelval, &pixel_map, dest, dest+1, dest+2) + || standard_rgb) + { + dest += 3; + } + else /* We have to map RGB to 0,...,255 */ + { + *(dest++) = redmap[(pixelval & redmask) >> redshift]; + *(dest++) = greenmap[(pixelval & greenmask) >> greenshift]; + *(dest++) = bluemap[(pixelval & bluemask) >> blueshift]; + } + } + } + } + + gimp_progress_update ((gdouble) tile_end / (gdouble) height); + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, tile_start, + width, tile_end-tile_start+1), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + } + + g_free (data); + g_free (xwddata); + + if (err) + g_message (_("EOF encountered on reading")); + + g_object_unref (buffer); + + return err ? -1 : image_ID; +} + + +static gboolean +save_index (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + gboolean gray, + GError **error) +{ + gint height, width; + gint linepad; + gint tile_height; + gint i, j; + gint ncolors, vclass; + glong tmp = 0; + guchar *data, *src, *cmap; + L_XWDFILEHEADER xwdhdr; + L_XWDCOLOR xwdcolmap[256]; + const Babl *format; + GeglBuffer *buffer; + gboolean success = TRUE; + +#ifdef XWD_DEBUG + g_printf ("save_index ()\n"); +#endif + + buffer = gimp_drawable_get_buffer (drawable_ID); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + tile_height = gimp_tile_height (); + + if (gray) + format = babl_format ("Y' u8"); + else + format = gegl_buffer_get_format (buffer); + + /* allocate a buffer for retrieving information from the pixel region */ + src = data = g_new (guchar, + tile_height * width * + babl_format_get_bytes_per_pixel (format)); + + linepad = width % 4; + if (linepad) + linepad = 4 - linepad; + + /* Fill XWD-color map */ + if (gray) + { + vclass = 0; + ncolors = 256; + + for (j = 0; j < ncolors; j++) + { + xwdcolmap[j].l_pixel = j; + xwdcolmap[j].l_red = (j << 8) | j; + xwdcolmap[j].l_green = (j << 8) | j; + xwdcolmap[j].l_blue = (j << 8) | j; + xwdcolmap[j].l_flags = 7; + xwdcolmap[j].l_pad = 0; + } + } + else + { + vclass = 3; + cmap = gimp_image_get_colormap (image_ID, &ncolors); + + for (j = 0; j < ncolors; j++) + { + xwdcolmap[j].l_pixel = j; + xwdcolmap[j].l_red = ((*cmap) << 8) | *cmap; cmap++; + xwdcolmap[j].l_green = ((*cmap) << 8) | *cmap; cmap++; + xwdcolmap[j].l_blue = ((*cmap) << 8) | *cmap; cmap++; + xwdcolmap[j].l_flags = 7; + xwdcolmap[j].l_pad = 0; + } + } + + /* Fill in the XWD header (header_size is evaluated by write_xwd_hdr ()) */ + xwdhdr.l_header_size = 0; + xwdhdr.l_file_version = 7; + xwdhdr.l_pixmap_format = 2; + xwdhdr.l_pixmap_depth = 8; + xwdhdr.l_pixmap_width = width; + xwdhdr.l_pixmap_height = height; + xwdhdr.l_xoffset = 0; + xwdhdr.l_byte_order = 1; + xwdhdr.l_bitmap_unit = 32; + xwdhdr.l_bitmap_bit_order = 1; + xwdhdr.l_bitmap_pad = 32; + xwdhdr.l_bits_per_pixel = 8; + xwdhdr.l_bytes_per_line = width + linepad; + xwdhdr.l_visual_class = vclass; + xwdhdr.l_red_mask = 0x000000; + xwdhdr.l_green_mask = 0x000000; + xwdhdr.l_blue_mask = 0x000000; + xwdhdr.l_bits_per_rgb = 8; + xwdhdr.l_colormap_entries = ncolors; + xwdhdr.l_ncolors = ncolors; + xwdhdr.l_window_width = width; + xwdhdr.l_window_height = height; + xwdhdr.l_window_x = 64; + xwdhdr.l_window_y = 64; + xwdhdr.l_window_bdrwidth = 0; + + success = (write_xwd_header (output, &xwdhdr, error) && + write_xwd_cols (output, &xwdhdr, xwdcolmap, error)); + if (! success) + goto out; + + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) /* Get more data */ + { + gint scan_lines = (i + tile_height - 1 < height) ? tile_height : (height - i); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + src = data; + } + + success = g_output_stream_write_all (output, src, width, + NULL, NULL, error); + if (! success) + goto out; + + if (linepad) + { + success = g_output_stream_write_all (output, &tmp, linepad, + NULL, NULL, error); + if (! success) + goto out; + } + + src += width; + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) i / (gdouble) height); + } + + out: + g_free (data); + g_object_unref (buffer); + + return success; +} + +static gboolean +save_rgb (GOutputStream *output, + gint32 image_ID, + gint32 drawable_ID, + GError **error) +{ + gint height, width; + gint linepad; + gint tile_height; + gint i; + glong tmp = 0; + guchar *data, *src; + L_XWDFILEHEADER xwdhdr; + const Babl *format; + GeglBuffer *buffer; + gboolean success = TRUE; + +#ifdef XWD_DEBUG + g_printf ("save_rgb ()\n"); +#endif + + buffer = gimp_drawable_get_buffer (drawable_ID); + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + tile_height = gimp_tile_height (); + format = babl_format ("R'G'B' u8"); + + /* allocate a buffer for retrieving information from the pixel region */ + src = data = g_new (guchar, + tile_height * width * + babl_format_get_bytes_per_pixel (format)); + + linepad = (width * 3) % 4; + if (linepad) + linepad = 4 - linepad; + + /* Fill in the XWD header (header_size is evaluated by write_xwd_hdr ()) */ + xwdhdr.l_header_size = 0; + xwdhdr.l_file_version = 7; + xwdhdr.l_pixmap_format = 2; + xwdhdr.l_pixmap_depth = 24; + xwdhdr.l_pixmap_width = width; + xwdhdr.l_pixmap_height = height; + xwdhdr.l_xoffset = 0; + xwdhdr.l_byte_order = 1; + + xwdhdr.l_bitmap_unit = 32; + xwdhdr.l_bitmap_bit_order = 1; + xwdhdr.l_bitmap_pad = 32; + xwdhdr.l_bits_per_pixel = 24; + + xwdhdr.l_bytes_per_line = width * 3 + linepad; + xwdhdr.l_visual_class = 5; + xwdhdr.l_red_mask = 0xff0000; + xwdhdr.l_green_mask = 0x00ff00; + xwdhdr.l_blue_mask = 0x0000ff; + xwdhdr.l_bits_per_rgb = 8; + xwdhdr.l_colormap_entries = 0; + xwdhdr.l_ncolors = 0; + xwdhdr.l_window_width = width; + xwdhdr.l_window_height = height; + xwdhdr.l_window_x = 64; + xwdhdr.l_window_y = 64; + xwdhdr.l_window_bdrwidth = 0; + + success = write_xwd_header (output, &xwdhdr, error); + if (! success) + goto out; + + for (i = 0; i < height; i++) + { + if ((i % tile_height) == 0) /* Get more data */ + { + gint scan_lines = (i + tile_height - 1 < height) ? tile_height : (height - i); + + gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + src = data; + } + + success = g_output_stream_write_all (output, src, width * 3, + NULL, NULL, error); + if (! success) + goto out; + + if (linepad) + { + success = g_output_stream_write_all (output, &tmp, linepad, + NULL, NULL, error); + if (! success) + goto out; + } + + src += width * 3; + + if ((i % 20) == 0) + gimp_progress_update ((gdouble) i / (gdouble) height); + } + + out: + g_free (data); + g_object_unref (buffer); + + return success; +} diff --git a/plug-ins/common/film.c b/plug-ins/common/film.c new file mode 100644 index 0000000..59548b9 --- /dev/null +++ b/plug-ins/common/film.c @@ -0,0 +1,1287 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * Film plug-in (C) 1997 Peter Kirchgessner + * e-mail: pkirchg@aol.com, WWW: http://members.aol.com/pkirchg + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This plug-in generates a film roll with several images + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-film" +#define PLUG_IN_BINARY "film" +#define PLUG_IN_ROLE "gimp-film" + +/* Maximum number of pictures per film */ +#define MAX_FILM_PICTURES 64 +#define COLOR_BUTTON_WIDTH 50 +#define COLOR_BUTTON_HEIGHT 20 + +#define FONT_LEN 256 + +/* Define how the plug-in works. Values marked (r) are with regard */ +/* to film_height (i.e. it should be a value from 0.0 to 1.0) */ +typedef struct +{ + gint film_height; /* height of the film */ + GimpRGB film_color; /* color of film */ + gdouble picture_height; /* height of picture (r) */ + gdouble picture_space; /* space between pictures (r) */ + gdouble hole_offset; /* distance from hole to edge of film (r) */ + gdouble hole_width; /* width of hole (r) */ + gdouble hole_height; /* height of holes (r) */ + gdouble hole_space; /* distance of holes (r) */ + gdouble number_height; /* height of picture numbering (r) */ + gint number_start; /* number for first picture */ + GimpRGB number_color; /* color of number */ + gchar number_font[FONT_LEN]; /* font family to use for numbering */ + gint number_pos[2]; /* flags where to draw numbers (top/bottom) */ + gint keep_height; /* flag if to keep max. image height */ + gint num_images; /* number of images */ + gint32 image[MAX_FILM_PICTURES]; /* list of image IDs */ +} FilmVals; + +/* Data to use for the dialog */ +typedef struct +{ + GtkObject *advanced_adj[7]; + GtkTreeModel *image_list_all; + GtkTreeModel *image_list_film; +} FilmInterface; + + +/* Declare local functions + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + + +static gint32 create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageType gdtype, + gint32 *layer_ID); + +static gchar * compose_image_name (gint32 image_ID); + +static gint32 film (void); + +static gboolean check_filmvals (void); + +static void set_pixels (gint numpix, + guchar *dst, + GimpRGB *color); + +static guchar * create_hole_rgb (gint width, + gint height); + +static void draw_number (gint32 layer_ID, + gint num, + gint x, + gint y, + gint height); + + +static void add_list_item_callback (GtkWidget *widget, + GtkTreeSelection *sel); +static void del_list_item_callback (GtkWidget *widget, + GtkTreeSelection *sel); + +static GtkTreeModel * add_image_list (gboolean add_box_flag, + gint n, + gint32 *image_id, + GtkWidget *hbox); + +static gboolean film_dialog (gint32 image_ID); +static void film_reset_callback (GtkWidget *widget, + gpointer data); +static void film_font_select_callback (GimpFontSelectButton *button, + const gchar *name, + gboolean closing, + gpointer data); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static gdouble advanced_defaults[] = +{ + 0.695, /* Picture height */ + 0.040, /* Picture spacing */ + 0.058, /* Hole offset to edge of film */ + 0.052, /* Hole width */ + 0.081, /* Hole height */ + 0.081, /* Hole distance */ + 0.052 /* Image number height */ +}; + +static FilmVals filmvals = +{ + 256, /* Height of film */ + { 0.0, 0.0, 0.0, 1.0 }, /* Color of film */ + 0.695, /* Picture height */ + 0.040, /* Picture spacing */ + 0.058, /* Hole offset to edge of film */ + 0.052, /* Hole width */ + 0.081, /* Hole height */ + 0.081, /* Hole distance */ + 0.052, /* Image number height */ + 1, /* Start index of numbering */ + { 0.93, 0.61, 0.0, 1.0 }, /* Color of number */ + "Monospace", /* Font family for numbering */ + { TRUE, TRUE }, /* Numbering on top and bottom */ + 0, /* Don't keep max. image height */ + 0, /* Number of images */ + { 0 } /* Input image list */ +}; + + +static FilmInterface filmint = +{ + { NULL }, /* advanced adjustments */ + NULL, NULL /* image list widgets */ +}; + + +static GimpRunMode run_mode; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (only used as default image in interactive mode)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (not used)" }, + { GIMP_PDB_INT32, "film-height", "Height of film (0: fit to images)" }, + { GIMP_PDB_COLOR, "film-color", "Color of the film" }, + { GIMP_PDB_INT32, "number-start", "Start index for numbering" }, + { GIMP_PDB_STRING, "number-font", "Font for drawing numbers" }, + { GIMP_PDB_COLOR, "number-color", "Color for numbers" }, + { GIMP_PDB_INT32, "at-top", "Flag for drawing numbers at top of film" }, + { GIMP_PDB_INT32, "at-bottom", "Flag for drawing numbers at bottom of film" }, + { GIMP_PDB_INT32, "num-images", "Number of images to be used for film" }, + { GIMP_PDB_INT32ARRAY, "image-ids", "num-images image IDs to be used for film" } + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "new-image", "Output image" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Combine several images on a film strip"), + "Compose several images to a roll film", + "Peter Kirchgessner", + "Peter Kirchgessner (peter@kirchgessner.net)", + "1997", + N_("_Filmstrip..."), + "INDEXED*, GRAY*, RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Combine"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint k; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 2; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_int32 = -1; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &filmvals); + + /* First acquire information with a dialog */ + if (! film_dialog (param[1].data.d_int32)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + /* Also we want to have some images to compose */ + if ((nparams != 12) || (param[10].data.d_int32 < 1)) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + filmvals.keep_height = (param[3].data.d_int32 <= 0); + filmvals.film_height = (filmvals.keep_height ? + 128 : param[3].data.d_int32); + filmvals.film_color = param[4].data.d_color; + filmvals.number_start = param[5].data.d_int32; + g_strlcpy (filmvals.number_font, param[6].data.d_string, FONT_LEN); + filmvals.number_color = param[7].data.d_color; + filmvals.number_pos[0] = param[8].data.d_int32; + filmvals.number_pos[1] = param[9].data.d_int32; + filmvals.num_images = param[10].data.d_int32; + if (filmvals.num_images > MAX_FILM_PICTURES) + filmvals.num_images = MAX_FILM_PICTURES; + for (k = 0; k < filmvals.num_images; k++) + filmvals.image[k] = param[11].data.d_int32array[k]; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &filmvals); + break; + + default: + break; + } + + if (! check_filmvals ()) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + gimp_progress_init (_("Composing images")); + + image_ID = film (); + + if (image_ID < 0) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + else + { + values[1].data.d_int32 = image_ID; + gimp_image_undo_enable (image_ID); + gimp_image_clean_all (image_ID); + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_display_new (image_ID); + } + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &filmvals, sizeof (FilmVals)); + } + + values[0].data.d_status = status; +} + +/* Compose a roll film image from several images */ +static gint32 +film (void) +{ + gint width, height; + guchar *hole; + gint film_height, film_width; + gint picture_width, picture_height; + gint picture_space, picture_x0, picture_y0; + gint hole_offset, hole_width, hole_height, hole_space, hole_x; + gint number_height, num_images, num_pictures; + gint j, k, picture_count; + gdouble f; + gint num_layers; + gint32 *image_ID_src, image_ID_dst, layer_ID_src, layer_ID_dst; + gint image_ID_tmp; + gint32 *layers; + gint new_layer; + gint floating_sel; + + /* initialize */ + + layers = NULL; + + num_images = filmvals.num_images; + image_ID_src = filmvals.image; + + if (num_images <= 0) + return (-1); + + gimp_context_push (); + gimp_context_set_foreground (&filmvals.number_color); + gimp_context_set_background (&filmvals.film_color); + + if (filmvals.keep_height) /* Search maximum picture height */ + { + picture_height = 0; + for (j = 0; j < num_images; j++) + { + height = gimp_image_height (image_ID_src[j]); + if (height > picture_height) picture_height = height; + } + film_height = (int)(picture_height / filmvals.picture_height + 0.5); + filmvals.film_height = film_height; + } + else + { + film_height = filmvals.film_height; + picture_height = (int)(film_height * filmvals.picture_height + 0.5); + } + + picture_space = (int)(film_height * filmvals.picture_space + 0.5); + picture_y0 = (film_height - picture_height)/2; + + number_height = film_height * filmvals.number_height; + + /* Calculate total film width */ + film_width = 0; + num_pictures = 0; + for (j = 0; j < num_images; j++) + { + layers = gimp_image_get_layers (image_ID_src[j], &num_layers); + /* Get scaled image size */ + width = gimp_image_width (image_ID_src[j]); + height = gimp_image_height (image_ID_src[j]); + f = ((double)picture_height) / (double)height; + picture_width = width * f; + + for (k = 0; k < num_layers; k++) + { + if (gimp_layer_is_floating_sel (layers[k])) + continue; + + film_width += (picture_space/2); /* Leading space */ + film_width += picture_width; /* Scaled image width */ + film_width += (picture_space/2); /* Trailing space */ + num_pictures++; + } + + g_free (layers); + } + +#ifdef FILM_DEBUG + g_printerr ("film_height = %d, film_width = %d\n", film_height, film_width); + g_printerr ("picture_height = %d, picture_space = %d, picture_y0 = %d\n", + picture_height, picture_space, picture_y0); + g_printerr ("Number of pictures = %d\n", num_pictures); +#endif + + image_ID_dst = create_new_image (_("Untitled"), + (guint) film_width, (guint) film_height, + GIMP_RGB_IMAGE, &layer_ID_dst); + + /* Fill film background */ + gimp_drawable_fill (layer_ID_dst, GIMP_FILL_BACKGROUND); + + /* Draw all the holes */ + hole_offset = film_height * filmvals.hole_offset; + hole_width = film_height * filmvals.hole_width; + hole_height = film_height * filmvals.hole_height; + hole_space = film_height * filmvals.hole_space; + hole_x = hole_space / 2; + +#ifdef FILM_DEBUG + g_printerr ("hole_x %d hole_offset %d hole_width %d hole_height %d hole_space %d\n", + hole_x, hole_offset, hole_width, hole_height, hole_space ); +#endif + + hole = create_hole_rgb (hole_width, hole_height); + if (hole) + { + GeglBuffer *buffer = gimp_drawable_get_buffer (layer_ID_dst); + + while (hole_x < film_width) + { + gegl_buffer_set (buffer, + GEGL_RECTANGLE (hole_x, + hole_offset, + hole_width, + hole_height), 0, + babl_format ("R'G'B' u8"), hole, + GEGL_AUTO_ROWSTRIDE); + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (hole_x, + film_height - hole_offset - hole_height, + hole_width, + hole_height), 0, + babl_format ("R'G'B' u8"), hole, + GEGL_AUTO_ROWSTRIDE); + + hole_x += hole_width + hole_space; + } + + g_object_unref (buffer); + g_free (hole); + } + + /* Compose all images and layers */ + picture_x0 = 0; + picture_count = 0; + for (j = 0; j < num_images; j++) + { + image_ID_tmp = gimp_image_duplicate (image_ID_src[j]); + width = gimp_image_width (image_ID_tmp); + height = gimp_image_height (image_ID_tmp); + f = ((gdouble) picture_height) / (gdouble) height; + picture_width = width * f; + if (gimp_image_base_type (image_ID_tmp) != GIMP_RGB) + gimp_image_convert_rgb (image_ID_tmp); + gimp_image_scale (image_ID_tmp, picture_width, picture_height); + + layers = gimp_image_get_layers (image_ID_tmp, &num_layers); + for (k = 0; k < num_layers; k++) + { + if (gimp_layer_is_floating_sel (layers[k])) + continue; + + picture_x0 += picture_space / 2; + + layer_ID_src = layers[k]; + gimp_layer_resize_to_image_size (layer_ID_src); + new_layer = gimp_layer_new_from_drawable (layer_ID_src, + image_ID_dst); + gimp_image_insert_layer (image_ID_dst, new_layer, -1, -1); + gimp_layer_set_offsets (new_layer, picture_x0, picture_y0); + + /* Draw picture numbers */ + if ((number_height > 0) && + (filmvals.number_pos[0] || filmvals.number_pos[1])) + { + if (filmvals.number_pos[0]) + draw_number (layer_ID_dst, + filmvals.number_start + picture_count, + picture_x0 + picture_width/2, + (hole_offset-number_height)/2, number_height); + if (filmvals.number_pos[1]) + draw_number (layer_ID_dst, + filmvals.number_start + picture_count, + picture_x0 + picture_width/2, + film_height - (hole_offset + number_height)/2, + number_height); + } + + picture_x0 += picture_width + (picture_space/2); + + gimp_progress_update (((gdouble) (picture_count + 1)) / + (gdouble) num_pictures); + + picture_count++; + } + + g_free (layers); + gimp_image_delete (image_ID_tmp); + } + gimp_progress_update (1.0); + + gimp_image_flatten (image_ID_dst); + + /* Drawing text/numbers leaves us with a floating selection. Stop it */ + floating_sel = gimp_image_get_floating_sel (image_ID_dst); + if (floating_sel != -1) + gimp_floating_sel_anchor (floating_sel); + + gimp_context_pop (); + + return image_ID_dst; +} + +/* Check filmvals. Unreasonable values are reset to a default. */ +/* If this is not possible, FALSE is returned. Otherwise TRUE is returned. */ +static gboolean +check_filmvals (void) +{ + if (filmvals.film_height < 10) + filmvals.film_height = 10; + + if (filmvals.number_start < 0) + filmvals.number_start = 0; + + if (filmvals.number_font[0] == '\0') + strcpy (filmvals.number_font, "Monospace"); + + if (filmvals.num_images < 1) + return FALSE; + + return TRUE; +} + +/* Assigns numpix pixels starting at dst with color r,g,b */ +static void +set_pixels (gint numpix, + guchar *dst, + GimpRGB *color) +{ + register gint k; + register guchar ur, ug, ub, *udest; + + ur = color->r * 255.999; + ug = color->g * 255.999; + ub = color->b * 255.999; + k = numpix; + udest = dst; + + while (k-- > 0) + { + *(udest++) = ur; + *(udest++) = ug; + *(udest++) = ub; + } +} + +/* Create the RGB-pixels that make up the hole */ +static guchar * +create_hole_rgb (gint width, + gint height) +{ + guchar *hole, *top, *bottom; + gint radius, length, k; + + hole = g_new (guchar, width * height * 3); + + /* Fill a rectangle with white */ + memset (hole, 255, width * height * 3); + radius = height / 4; + if (radius > width / 2) + radius = width / 2; + top = hole; + bottom = hole + (height-1)*width*3; + for (k = radius-1; k > 0; k--) /* Rounding corners */ + { + length = (int)(radius - sqrt ((gdouble) (radius * radius - k * k)) + - 0.5); + if (length > 0) + { + set_pixels (length, top, &filmvals.film_color); + set_pixels (length, top + (width-length)*3, &filmvals.film_color); + set_pixels (length, bottom, &filmvals.film_color); + set_pixels (length, bottom + (width-length)*3, &filmvals.film_color); + } + top += width*3; + bottom -= width*3; + } + + return hole; +} + +/* Draw the number of the picture onto the film */ +static void +draw_number (gint32 layer_ID, + gint num, + gint x, + gint y, + gint height) +{ + gchar buf[32]; + gint k, delta, max_delta; + gint32 image_ID; + gint32 text_layer_ID; + gint text_width, text_height, text_ascent, descent; + gchar *fontname = filmvals.number_font; + + g_snprintf (buf, sizeof (buf), "%d", num); + + image_ID = gimp_item_get_image (layer_ID); + + max_delta = height / 10; + if (max_delta < 1) + max_delta = 1; + + /* Numbers don't need the descent. Inquire it and move the text down */ + for (k = 0; k < max_delta * 2 + 1; k++) + { + /* Try different font sizes if inquire of extent failed */ + gboolean success; + + delta = (k+1) / 2; + + if ((k & 1) == 0) + delta = -delta; + + success = gimp_text_get_extents_fontname (buf, + height + delta, GIMP_PIXELS, + fontname, + &text_width, &text_height, + &text_ascent, &descent); + + if (success) + { + height += delta; + break; + } + } + + text_layer_ID = gimp_text_fontname (image_ID, layer_ID, + x, y + descent / 2, + buf, 1, FALSE, + height, GIMP_PIXELS, + fontname); + + if (text_layer_ID == -1) + g_message ("draw_number: Error in drawing text\n"); +} + +/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */ +static gint32 +create_new_image (const gchar *filename, + guint width, + guint height, + GimpImageType gdtype, + gint32 *layer_ID) +{ + gint32 image_ID; + GimpImageBaseType gitype; + + if ((gdtype == GIMP_GRAY_IMAGE) || (gdtype == GIMP_GRAYA_IMAGE)) + gitype = GIMP_GRAY; + else if ((gdtype == GIMP_INDEXED_IMAGE) || (gdtype == GIMP_INDEXEDA_IMAGE)) + gitype = GIMP_INDEXED; + else + gitype = GIMP_RGB; + + image_ID = gimp_image_new (width, height, gitype); + gimp_image_set_filename (image_ID, filename); + + gimp_image_undo_disable (image_ID); + *layer_ID = gimp_layer_new (image_ID, _("Background"), width, height, + gdtype, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + gimp_image_insert_layer (image_ID, *layer_ID, -1, 0); + + return image_ID; +} + +static gchar * +compose_image_name (gint32 image_ID) +{ + gchar *image_name; + gchar *name; + + /* Compose a name of the basename and the image-ID */ + + name = gimp_image_get_name (image_ID); + + image_name = g_strdup_printf ("%s-%d", name, image_ID); + + g_free (name); + + return image_name; +} + +static void +add_list_item_callback (GtkWidget *widget, + GtkTreeSelection *sel) +{ + GtkTreeModel *model; + GList *paths; + GList *list; + + paths = gtk_tree_selection_get_selected_rows (sel, &model); + + for (list = paths; list; list = g_list_next (list)) + { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (model, &iter, list->data)) + { + gint32 image_ID; + gchar *name; + + gtk_tree_model_get (model, &iter, + 0, &image_ID, + 1, &name, + -1); + + gtk_list_store_append (GTK_LIST_STORE (filmint.image_list_film), + &iter); + + gtk_list_store_set (GTK_LIST_STORE (filmint.image_list_film), + &iter, + 0, image_ID, + 1, name, + -1); + + g_free (name); + } + + gtk_tree_path_free (list->data); + } + + g_list_free (paths); +} + +static void +del_list_item_callback (GtkWidget *widget, + GtkTreeSelection *sel) +{ + GtkTreeModel *model; + GList *paths; + GList *references = NULL; + GList *list; + + paths = gtk_tree_selection_get_selected_rows (sel, &model); + + for (list = paths; list; list = g_list_next (list)) + { + GtkTreeRowReference *ref; + + ref = gtk_tree_row_reference_new (model, list->data); + references = g_list_prepend (references, ref); + gtk_tree_path_free (list->data); + } + + g_list_free (paths); + + for (list = references; list; list = g_list_next (list)) + { + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_row_reference_get_path (list->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + gtk_tree_path_free (path); + gtk_tree_row_reference_free (list->data); + } + + g_list_free (references); +} + +static GtkTreeModel * +add_image_list (gboolean add_box_flag, + gint n, + gint32 *image_id, + GtkWidget *hbox) +{ + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *scrolled_win; + GtkWidget *tv; + GtkWidget *button; + GtkListStore *store; + GtkTreeSelection *sel; + gint i; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + label = gtk_label_new (add_box_flag ? + _("Available images:") : + _("On film:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0); + gtk_widget_show (scrolled_win); + + store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING); + tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); + g_object_unref (store); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tv), FALSE); + + if (! add_box_flag) + gtk_tree_view_set_reorderable (GTK_TREE_VIEW (tv), TRUE); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tv), 0, NULL, + gtk_cell_renderer_text_new (), + "text", 1, + NULL); + + gtk_container_add (GTK_CONTAINER (scrolled_win), tv); + gtk_widget_show (tv); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE); + + for (i = 0; i < n; i++) + { + GtkTreeIter iter; + gchar *name; + + gtk_list_store_append (store, &iter); + + name = compose_image_name (image_id[i]); + + gtk_list_store_set (store, &iter, + 0, image_id[i], + 1, name, + -1); + + g_free (name); + } + + button = gtk_button_new_with_mnemonic (add_box_flag ? + _("_Add") : _("_Remove")); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + add_box_flag ? + G_CALLBACK (add_list_item_callback) : + G_CALLBACK (del_list_item_callback), + sel); + + return GTK_TREE_MODEL (store); +} + +static void +create_selection_tab (GtkWidget *notebook, + gint32 image_ID) +{ + GimpColorConfig *config; + GtkSizeGroup *group; + GtkWidget *vbox; + GtkWidget *vbox2; + GtkWidget *hbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *frame; + GtkWidget *toggle; + GtkWidget *spinbutton; + GtkAdjustment *adj; + GtkWidget *button; + GtkWidget *font_button; + gint32 *image_id_list; + gint nimages, j; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), hbox, + gtk_label_new_with_mnemonic (_("Selection"))); + gtk_widget_show (hbox); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0); + gtk_widget_show (vbox2); + + group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + /* Film height/color */ + frame = gimp_frame_new (_("Filmstrip")); + gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + /* Keep maximum image height */ + toggle = gtk_check_button_new_with_mnemonic (_("_Fit height to images")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &filmvals.keep_height); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* Film height */ + adj = GTK_ADJUSTMENT (gtk_adjustment_new (filmvals.film_height, 10, + GIMP_MAX_IMAGE_SIZE, 1, 10, 0)); + spinbutton = gimp_spin_button_new (adj, 1, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Height:"), 0.0, 0.5, + spinbutton, 1, TRUE); + gtk_size_group_add_widget (group, label); + g_object_unref (group); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &filmvals.film_height); + + g_object_bind_property (toggle, "active", + spinbutton, "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + g_object_bind_property (toggle, "active", + /* FIXME: eeeeeek */ + g_list_nth_data (gtk_container_get_children (GTK_CONTAINER (table)), 1), "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + filmvals.keep_height); + + /* Film color */ + button = gimp_color_button_new (_("Select Film Color"), + COLOR_BUTTON_WIDTH, COLOR_BUTTON_HEIGHT, + &filmvals.film_color, + GIMP_COLOR_AREA_FLAT); + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Co_lor:"), 0.0, 0.5, + button, 1, FALSE); + gtk_size_group_add_widget (group, label); + + g_signal_connect (button, "color-changed", + G_CALLBACK (gimp_color_button_get_color), + &filmvals.film_color); + + config = gimp_get_color_configuration (); + gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (button), config); + + /* Film numbering: Startindex/Font/color */ + frame = gimp_frame_new (_("Numbering")); + gtk_box_pack_start (GTK_BOX (vbox2), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + table = gtk_table_new (3, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* Startindex */ + adj = GTK_ADJUSTMENT (gtk_adjustment_new (filmvals.number_start, 0, + GIMP_MAX_IMAGE_SIZE, 1, 10, 0)); + spinbutton = gimp_spin_button_new (adj, 1, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Start _index:"), 0.0, 0.5, + spinbutton, 1, TRUE); + gtk_size_group_add_widget (group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &filmvals.number_start); + + /* Fontfamily for numbering */ + font_button = gimp_font_select_button_new (NULL, filmvals.number_font); + g_signal_connect (font_button, "font-set", + G_CALLBACK (film_font_select_callback), &filmvals); + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Font:"), 0.0, 0.5, + font_button, 1, FALSE); + gtk_size_group_add_widget (group, label); + + /* Numbering color */ + button = gimp_color_button_new (_("Select Number Color"), + COLOR_BUTTON_WIDTH, COLOR_BUTTON_HEIGHT, + &filmvals.number_color, + GIMP_COLOR_AREA_FLAT); + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("Co_lor:"), 0.0, 0.5, + button, 1, FALSE); + gtk_size_group_add_widget (group, label); + + g_signal_connect (button, "color-changed", + G_CALLBACK (gimp_color_button_get_color), + &filmvals.number_color); + + gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (button), config); + g_object_unref (config); + + for (j = 0; j < 2; j++) + { + toggle = gtk_check_button_new_with_mnemonic (j ? _("At _bottom") + : _("At _top")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + filmvals.number_pos[j]); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &filmvals.number_pos[j]); + } + + + /*** The right frame keeps the image selection ***/ + frame = gimp_frame_new (_("Image Selection")); + gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + /* Get a list of all image names */ + image_id_list = gimp_image_list (&nimages); + filmint.image_list_all = add_image_list (TRUE, nimages, image_id_list, hbox); + + /* Get a list of the images used for the film */ + filmint.image_list_film = add_image_list (FALSE, 1, &image_ID, hbox); + + gtk_widget_show (hbox); +} + +static void +create_advanced_tab (GtkWidget *notebook) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *table; + GtkWidget *frame; + GtkObject *adj; + GtkWidget *button; + gint row; + + frame = gimp_frame_new (_("All Values are Fractions of the Strip Height")); + gtk_container_set_border_width (GTK_CONTAINER (frame), 12); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, + gtk_label_new_with_mnemonic (_("Ad_vanced"))); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + table = gtk_table_new (7, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 1, 12); + gtk_table_set_row_spacing (GTK_TABLE (table), 5, 12); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + row = 0; + + filmint.advanced_adj[0] = adj = + gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("Image _height:"), 0, 0, + filmvals.picture_height, + 0.0, 1.0, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &filmvals.picture_height); + + filmint.advanced_adj[1] = adj = + gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("Image spac_ing:"), 0, 0, + filmvals.picture_space, + 0.0, 1.0, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &filmvals.picture_space); + + filmint.advanced_adj[2] = adj = + gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("_Hole offset:"), 0, 0, + filmvals.hole_offset, + 0.0, 1.0, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &filmvals.hole_offset); + + filmint.advanced_adj[3] = adj = + gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("Ho_le width:"), 0, 0, + filmvals.hole_width, + 0.0, 1.0, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &filmvals.hole_width); + + filmint.advanced_adj[4] = adj = + gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("Hol_e height:"), 0, 0, + filmvals.hole_height, + 0.0, 1.0, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &filmvals.hole_height); + + filmint.advanced_adj[5] = adj = + gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("Hole sp_acing:"), 0, 0, + filmvals.hole_space, + 0.0, 1.0, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &filmvals.hole_space); + + filmint.advanced_adj[6] = adj = + gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("_Number height:"), 0, 0, + filmvals.number_height, + 0.0, 1.0, 0.001, 0.01, 3, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &filmvals.number_height); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + button = gtk_button_new_with_mnemonic (_("Re_set")); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (film_reset_callback), + NULL); +} + +static gboolean +film_dialog (gint32 image_ID) +{ + GtkWidget *dlg; + GtkWidget *main_vbox; + GtkWidget *notebook; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dlg = gimp_dialog_new (_("Filmstrip"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (main_vbox), notebook, TRUE, TRUE, 0); + + create_selection_tab (notebook, image_ID); + create_advanced_tab (notebook); + + gtk_widget_show (notebook); + + gtk_widget_show (dlg); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + if (run) + { + gint num_images = 0; + gboolean iter_valid; + GtkTreeIter iter; + + for (iter_valid = gtk_tree_model_get_iter_first (filmint.image_list_film, + &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (filmint.image_list_film, + &iter)) + { + gint image_ID; + + gtk_tree_model_get (filmint.image_list_film, &iter, + 0, &image_ID, + -1); + + if ((image_ID >= 0) && (num_images < MAX_FILM_PICTURES)) + filmvals.image[num_images++] = image_ID; + } + + filmvals.num_images = num_images; + } + + gtk_widget_destroy (dlg); + + return run; +} + +static void +film_reset_callback (GtkWidget *widget, + gpointer data) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (advanced_defaults) ; i++) + gtk_adjustment_set_value (GTK_ADJUSTMENT (filmint.advanced_adj[i]), + advanced_defaults[i]); +} + +static void +film_font_select_callback (GimpFontSelectButton *button, + const gchar *name, + gboolean closing, + gpointer data) +{ + FilmVals *vals = (FilmVals *) data; + + g_strlcpy (vals->number_font, name, FONT_LEN); +} diff --git a/plug-ins/common/filter-pack.c b/plug-ins/common/filter-pack.c new file mode 100644 index 0000000..9664596 --- /dev/null +++ b/plug-ins/common/filter-pack.c @@ -0,0 +1,2178 @@ +/* + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This is a plug-in for GIMP. + * + * Copyright (C) Pavel Grinfeld (pavel@ml.com) + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-filter-pack" +#define PLUG_IN_BINARY "filter-pack" +#define PLUG_IN_ROLE "gimp-filter-pack" + +#define MAX_PREVIEW_SIZE 125 +#define MAX_ROUGHNESS 128 +#define RANGE_HEIGHT 15 +#define PR_BX_BRDR 4 +#define MARGIN 4 + +#define RANGE_ADJUST_MASK (GDK_EXPOSURE_MASK | \ + GDK_ENTER_NOTIFY_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_RELEASE_MASK | \ + GDK_BUTTON1_MOTION_MASK | \ + GDK_POINTER_MOTION_HINT_MASK) + + +typedef struct +{ + gboolean run; +} fpInterface; + +typedef struct +{ + gint width; + gint height; + guchar *rgb; + gdouble *hsv; + guchar *mask; +} ReducedImage; + +typedef enum +{ + SHADOWS, + MIDTONES, + HIGHLIGHTS, + INTENSITIES +} FPIntensity; + +enum +{ + NONEATALL = 0, + CURRENT = 1, + HUE = 2, + SATURATION = 4, + VALUE = 8 +}; + +enum +{ + BY_HUE, + BY_SAT, + BY_VAL, + JUDGE_BY +}; + +enum +{ + RED, + GREEN, + BLUE, + CYAN, + YELLOW, + MAGENTA, + ALL_PRIMARY +}; + +enum +{ + DOWN = -1, + UP = 1 +}; + +typedef struct +{ + GtkWidget *window; + GtkWidget *range_preview; + GtkWidget *aliasing_preview; + GtkWidget *aliasing_graph; +} AdvancedWindow; + + +typedef struct +{ + gdouble roughness; + gdouble aliasing; + gdouble preview_size; + FPIntensity intensity_range; + gint value_by; + gint selection_only; + guchar offset; + guchar visible_frames; + guchar cutoff[INTENSITIES]; + gboolean touched[JUDGE_BY]; + gint red_adjust[JUDGE_BY][256]; + gint blue_adjust[JUDGE_BY][256]; + gint green_adjust[JUDGE_BY][256]; + gint sat_adjust[JUDGE_BY][256]; +} FPValues; + +typedef struct +{ + GtkWidget *roughness_scale; + GtkWidget *aliasing_scale; + GtkWidget *preview_size_scale; + GtkWidget *range_label[12]; +} FPWidgets; + +static void fp_show_hide_frame (GtkWidget *button, + GtkWidget *frame); + +static ReducedImage * fp_reduce_image (GimpDrawable *drawable, + GimpDrawable *mask, + gint longer_size, + gint selection); + +static void fp_render_preview (GtkWidget *preview, + gint change_what, + gint change_which); + +static void update_current_fp (gint change_what, + gint change_which); + +static void fp_create_nudge (gint *adj_array); + +static gboolean fp_dialog (void); +static void fp_advanced_dialog (GtkWidget *parent); + +static void fp_selection_made (GtkWidget *widget, + gpointer data); +static void fp_scale_update (GtkAdjustment *adjustment, + gdouble *scale_val); +static void fp_reset_filter_packs (void); + +static void fp_create_smoothness_graph (GtkWidget *preview); + +static void fp_range_preview_spill (GtkWidget *preview, + gint type); +static void fp_adjust_preview_sizes (gint width, + gint height); +static void fp_redraw_all_windows (void); +static void fp_refresh_previews (gint which); +static void fp_init_filter_packs (void); + +static void fp_preview_scale_update (GtkAdjustment *adjustment, + gdouble *scale_val); + +static void fp (GimpDrawable *drawable); +static GtkWidget * fp_create_bna (void); +static GtkWidget * fp_create_rough (void); +static GtkWidget * fp_create_range (void); +static GtkWidget * fp_create_circle_palette (GtkWidget *parent); +static GtkWidget * fp_create_lnd (GtkWidget *parent); +static GtkWidget * fp_create_show (void); +static GtkWidget * fp_create_msnls (GtkWidget *parent); +static GtkWidget * fp_create_pixels_select_by (void); +static void update_range_labels (void); +static gboolean fp_range_change_events (GtkWidget *widget, + GdkEvent *event, + FPValues *current); + +static void fp_create_preview (GtkWidget **preview, + GtkWidget **frame, + gint preview_width, + gint preview_height); + +static void fp_create_table_entry (GtkWidget **box, + GtkWidget *smaller_frame, + const gchar *description); + +static void fp_frames_checkbutton_in_box (GtkWidget *vbox, + const gchar *label, + GCallback func, + GtkWidget *frame, + gboolean clicked); + +static void fp_preview_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +#define RESPONSE_RESET 1 + +/* These values are translated for the GUI but also used internally + to figure out which button the user pushed, etc. + Not my design, please don't blame me -- njl */ + +static const gchar *hue_red = N_("Red:"); +static const gchar *hue_green = N_("Green:"); +static const gchar *hue_blue = N_("Blue:"); +static const gchar *hue_cyan = N_("Cyan:"); +static const gchar *hue_yellow = N_("Yellow:"); +static const gchar *hue_magenta = N_("Magenta:"); + +static const gchar *val_darker = N_("Darker:"); +static const gchar *val_lighter = N_("Lighter:"); + +static const gchar *sat_more = N_("More Sat:"); +static const gchar *sat_less = N_("Less Sat:"); + +static const gchar *current_val = N_("Current:"); + +static const gint colorSign[3][ALL_PRIMARY]= +{{1,-1,-1,-1,1,1},{-1,1,-1,1,1,-1},{-1,-1,1,1,-1,1}}; + +static AdvancedWindow AW = { NULL, NULL, NULL, NULL }; + +static FPWidgets fp_widgets = { NULL, NULL, NULL }; + +static gint nudgeArray[256]; + +static GtkWidget *origPreview, *curPreview; +static GtkWidget *rPreview, *gPreview, *bPreview; +static GtkWidget *cPreview, *yPreview, *mPreview; +static GtkWidget *centerPreview; +static GtkWidget *darkerPreview, *lighterPreview, *middlePreview; +static GtkWidget *dlg; +static GtkWidget *plusSatPreview, *SatPreview, *minusSatPreview; + +static struct +{ + GtkWidget *bna; + GtkWidget *palette; + GtkWidget *rough; + GtkWidget *range; + GtkWidget *show; + GtkWidget *lnd; + GtkWidget *pixelsBy; + GtkWidget *frameSelect; + GtkWidget *satur; +} fp_frames; + +static fpInterface FPint = +{ + FALSE /* run */ +}; + +static ReducedImage *reduced; + +static FPValues fpvals = +{ + .25, /* Initial Roughness */ + .6, /* Initial Degree of Aliasing */ + 80, /* Initial preview size */ + MIDTONES, /* Initial Range */ + BY_VAL, /* Initial God knows what */ + TRUE, /* Selection Only */ + 0, /* Offset */ + 0, /* Visible frames */ + { 32, 224, 255 }, /* cutoffs */ + { FALSE, FALSE, FALSE } /* touched */ +}; + +static GimpDrawable *drawable = NULL; +static GimpDrawable *mask = NULL; + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN() + +static void +query (void) +{ + GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (used for indexed images)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Interactively modify the image colors"), + "Interactively modify the image colors.", + "Pavel Grinfeld (pavel@ml.com)", + "Pavel Grinfeld (pavel@ml.com)", + "27th March 1997", + N_("_Filter Pack..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +/********************************STANDARD RUN*************************/ + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + + *nreturn_vals = 1; + *return_vals = values; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + fp_init_filter_packs(); + + drawable = gimp_drawable_get (param[2].data.d_drawable); + + if (gimp_selection_is_empty (param[1].data.d_image)) + mask = NULL; + else + mask = gimp_drawable_get (gimp_image_get_selection (param[1].data.d_image)); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &fpvals); + + if (gimp_drawable_is_indexed (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id) ) + { + gimp_message (_("FP can only be used on RGB images.")); + status = GIMP_PDB_EXECUTION_ERROR; + } + else if (! fp_dialog()) + { + status = GIMP_PDB_CANCEL; + } + break; + + case GIMP_RUN_NONINTERACTIVE: + gimp_message (_("FP can only be run interactively.")); + status = GIMP_PDB_CALLING_ERROR; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &fpvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_is_rgb (drawable->drawable_id)) + { + gimp_progress_init (_("Applying filter pack")); + gimp_tile_cache_ntiles (2 * (drawable->width / + gimp_tile_width () + 1)); + fp (drawable); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &fpvals, sizeof (FPValues)); + + gimp_displays_flush (); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + gimp_drawable_detach (drawable); + if (mask) + gimp_drawable_detach (mask); + + values[0].data.d_status = status; +} + +static void +fp_func (const guchar *src, + guchar *dest, + gint bpp, + gpointer data) +{ + gint bytenum, k; + gint JudgeBy, Intensity = 0, P[3]; + GimpRGB rgb; + GimpHSV hsv; + gint M, m, middle; + + P[0] = src[0]; + P[1] = src[1]; + P[2] = src[2]; + + gimp_rgb_set_uchar (&rgb, (guchar) P[0], (guchar) P[1], (guchar) P[2]); + gimp_rgb_to_hsv (&rgb, &hsv); + + for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++) + { + if (!fpvals.touched[JudgeBy]) + continue; + + switch (JudgeBy) + { + case BY_HUE: + Intensity = 255 * hsv.h; + break; + + case BY_SAT: + Intensity = 255 * hsv.s; + break; + + case BY_VAL: + Intensity = 255 * hsv.v; + break; + } + + + /* It's important to take care of Saturation first!!! */ + + m = MIN (MIN (P[0], P[1]), P[2]); + M = MAX (MAX (P[0], P[1]), P[2]); + middle = (M + m) / 2; + + for (k = 0; k < 3; k++) + if (P[k] != m && P[k] != M) + middle = P[k]; + + for (k = 0; k < 3; k++) + if (M != m) + { + if (P[k] == M) + P[k] = MAX (P[k] + fpvals.sat_adjust[JudgeBy][Intensity], middle); + else if (P[k] == m) + P[k] = MIN (P[k] - fpvals.sat_adjust[JudgeBy][Intensity], middle); + } + + P[0] += fpvals.red_adjust[JudgeBy][Intensity]; + P[1] += fpvals.green_adjust[JudgeBy][Intensity]; + P[2] += fpvals.blue_adjust[JudgeBy][Intensity]; + + P[0] = CLAMP0255(P[0]); + P[1] = CLAMP0255(P[1]); + P[2] = CLAMP0255(P[2]); + } + + dest[0] = P[0]; + dest[1] = P[1]; + dest[2] = P[2]; + + for (bytenum = 3; bytenum < bpp; bytenum++) + dest[bytenum] = src[bytenum]; +} + +static void +fp (GimpDrawable *drawable) +{ + GimpPixelRgn srcPR, destPR; + gint x1, y1, x2, y2; + gpointer pr; + gint total_area; + gint area_so_far; + gint count; + + g_return_if_fail (drawable != NULL); + + gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); + + total_area = (x2 - x1) * (y2 - y1); + area_so_far = 0; + + if (total_area <= 0) + return; + + /* Initialize the pixel regions. */ + gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, (x2 - x1), (y2 - y1), + FALSE, FALSE); + gimp_pixel_rgn_init (&destPR, drawable, x1, y1, (x2 - x1), (y2 - y1), + TRUE, TRUE); + + for (pr = gimp_pixel_rgns_register (2, &srcPR, &destPR), count = 0; + pr != NULL; + pr = gimp_pixel_rgns_process (pr), count++) + { + const guchar *src = srcPR.data; + guchar *dest = destPR.data; + gint row; + + for (row = 0; row < srcPR.h; row++) + { + const guchar *s = src; + guchar *d = dest; + gint pixels = srcPR.w; + + while (pixels--) + { + fp_func (s, d, srcPR.bpp, NULL); + + s += srcPR.bpp; + d += destPR.bpp; + } + + src += srcPR.rowstride; + dest += destPR.rowstride; + } + + area_so_far += srcPR.w * srcPR.h; + + if ((count % 16) == 0) + gimp_progress_update ((gdouble) area_so_far / (gdouble) total_area); + } + + /* update the processed region */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1), (y2 - y1)); +} + +/***********************************************************/ +/************ Main Dialog Window ******************/ +/***********************************************************/ + +static GtkWidget * +fp_create_bna (void) +{ + GtkWidget *table; + GtkWidget *label; + GtkWidget *bframe, *aframe; + + fp_create_preview (&origPreview, &bframe, reduced->width, reduced->height); + fp_create_preview (&curPreview, &aframe, reduced->width, reduced->height); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + + label = gtk_label_new (_("Original:")); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_SHRINK, GTK_SHRINK, 0, 0); + gtk_widget_show (label); + + gtk_table_attach (GTK_TABLE (table), bframe, 0, 1, 1, 2, + GTK_EXPAND, 0, 0, 0); + + label = gtk_label_new (_("Current:")); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 1, + GTK_SHRINK, GTK_SHRINK, 0, 0); + gtk_widget_show (label); + + gtk_table_attach (GTK_TABLE (table), aframe, 1, 2, 1, 2, + GTK_EXPAND, 0, 0, 0); + + gtk_widget_show (table); + + return table; +} + +/* close a sub dialog (from window manager) by simulating toggle click */ +static gboolean +sub_dialog_destroy (GtkWidget *dialog, + GdkEvent *ev, + gpointer dummy) +{ + GtkWidget *button = + GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "ctrlButton")); + + gtk_button_clicked (GTK_BUTTON (button)); + + return TRUE; +} + +static GtkWidget * +fp_create_circle_palette (GtkWidget *parent) +{ + GtkWidget *table; + GtkWidget *rVbox, *rFrame; + GtkWidget *gVbox, *gFrame; + GtkWidget *bVbox, *bFrame; + GtkWidget *cVbox, *cFrame; + GtkWidget *yVbox, *yFrame; + GtkWidget *mVbox, *mFrame; + GtkWidget *centerVbox, *centerFrame; + GtkWidget *win; + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gimp_help_connect (win, gimp_standard_help_func, PLUG_IN_PROC, NULL); + + gtk_window_set_title (GTK_WINDOW (win), _("Hue Variations")); + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent)); + + g_signal_connect (win, "delete-event", + G_CALLBACK (sub_dialog_destroy), + NULL); + + table = gtk_table_new (11, 11, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_container_add (GTK_CONTAINER (win), table); + gtk_widget_show (table); + + fp_create_preview (&rPreview, &rFrame, reduced->width, reduced->height); + fp_create_preview (&gPreview, &gFrame, reduced->width, reduced->height); + fp_create_preview (&bPreview, &bFrame, reduced->width, reduced->height); + fp_create_preview (&cPreview, &cFrame, reduced->width, reduced->height); + fp_create_preview (&yPreview, &yFrame, reduced->width, reduced->height); + fp_create_preview (&mPreview, &mFrame, reduced->width, reduced->height); + fp_create_preview (¢erPreview, ¢erFrame, + reduced->width, reduced->height); + + fp_create_table_entry (&rVbox, rFrame, hue_red); + fp_create_table_entry (&gVbox, gFrame, hue_green); + fp_create_table_entry (&bVbox, bFrame, hue_blue); + fp_create_table_entry (&cVbox, cFrame, hue_cyan); + fp_create_table_entry (&yVbox, yFrame, hue_yellow); + fp_create_table_entry (&mVbox, mFrame, hue_magenta); + fp_create_table_entry (¢erVbox, centerFrame, current_val); + + gtk_table_attach (GTK_TABLE (table), rVbox, 8, 11 ,4 , 7, + GTK_EXPAND , GTK_EXPAND, 0 ,0); + gtk_table_attach (GTK_TABLE (table), gVbox, 2, 5, 0, 3, + GTK_EXPAND, GTK_EXPAND, 0, 0); + gtk_table_attach (GTK_TABLE (table), bVbox, 2, 5, 8, 11, + GTK_EXPAND, GTK_EXPAND,0,0); + gtk_table_attach (GTK_TABLE (table), cVbox, 0, 3, 4, 7, + GTK_EXPAND, GTK_EXPAND, 0 ,0); + gtk_table_attach (GTK_TABLE (table), yVbox, 6, 9, 0, 3, + GTK_EXPAND, GTK_EXPAND, 0 ,0); + gtk_table_attach (GTK_TABLE (table), mVbox, 6, 9, 8, 11, + GTK_EXPAND, GTK_EXPAND, 0 ,0); + gtk_table_attach (GTK_TABLE (table), centerVbox, 4, 7, 4, 7, + GTK_EXPAND, GTK_EXPAND, 0 ,0); + + return win; +} + +static GtkWidget * +fp_create_rough (void) +{ + GtkWidget *frame, *vbox, *scale; + GtkAdjustment *data; + + frame = gimp_frame_new (_("Roughness")); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + data = (GtkAdjustment *) + gtk_adjustment_new (fpvals.roughness, 0, 1.0, 0.05, 0.01, 0.0); + fp_widgets.roughness_scale = scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, + data); + + gtk_widget_set_size_request (scale, 60, -1); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); + gtk_scale_set_digits (GTK_SCALE (scale), 2); + gtk_widget_show (scale); + + g_signal_connect (data, "value-changed", + G_CALLBACK (fp_scale_update), + &fpvals.roughness); + + gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0); + + return frame; +} + +static void +fp_change_current_range (GtkWidget *widget, + gpointer data) +{ + gimp_radio_button_update (widget, data); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + fp_refresh_previews (fpvals.visible_frames); + if (AW.window && gtk_widget_get_visible (AW.window)) + fp_create_smoothness_graph (AW.aliasing_preview); + } +} + +static GtkWidget * +fp_create_range (void) +{ + GtkWidget *frame; + + frame = gimp_int_radio_group_new (TRUE, _("Affected Range"), + G_CALLBACK (fp_change_current_range), + &fpvals.intensity_range, fpvals.intensity_range, + + _("Sha_dows"), SHADOWS, NULL, + _("_Midtones"), MIDTONES, NULL, + _("H_ighlights"), HIGHLIGHTS, NULL, + + NULL); + + gtk_widget_show (frame); + + return frame; +} + +static GtkWidget * +fp_create_control (void) +{ + GtkWidget *frame, *box; + + frame = gimp_frame_new (_("Windows")); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (frame), box); + gtk_widget_show (box); + + fp_frames_checkbutton_in_box (box, _("_Hue"), + G_CALLBACK (fp_show_hide_frame), + fp_frames.palette, + fpvals.visible_frames & HUE); + fp_frames_checkbutton_in_box (box, _("_Saturation"), + G_CALLBACK (fp_show_hide_frame), + fp_frames.satur, + fpvals.visible_frames & SATURATION); + fp_frames_checkbutton_in_box (box, _("_Value"), + G_CALLBACK (fp_show_hide_frame), + fp_frames.lnd, + fpvals.visible_frames & VALUE); + fp_frames_checkbutton_in_box (box, _("A_dvanced"), + G_CALLBACK (fp_show_hide_frame), + AW.window, + FALSE); + gtk_widget_show (frame); + + return frame; +} + +static GtkWidget * +fp_create_lnd (GtkWidget *parent) +{ + GtkWidget *table, *lighterFrame, *middleFrame, *darkerFrame; + GtkWidget *lighterVbox, *middleVbox, *darkerVbox; + GtkWidget *win; + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gimp_help_connect (win, gimp_standard_help_func, PLUG_IN_PROC, NULL); + + gtk_window_set_title (GTK_WINDOW (win), _("Value Variations")); + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent)); + + g_signal_connect (win, "delete-event", + G_CALLBACK (sub_dialog_destroy), + NULL); + + fp_create_preview (&lighterPreview, &lighterFrame, + reduced->width, reduced->height); + fp_create_preview (&middlePreview, &middleFrame, + reduced->width, reduced->height); + fp_create_preview (&darkerPreview, &darkerFrame, + reduced->width, reduced->height); + + fp_create_table_entry (&lighterVbox, lighterFrame, val_lighter); + fp_create_table_entry (&middleVbox, middleFrame, current_val); + fp_create_table_entry (&darkerVbox, darkerFrame, val_darker); + + table = gtk_table_new (1, 11, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_container_add (GTK_CONTAINER (win), table); + gtk_widget_show (table); + + gtk_table_attach (GTK_TABLE (table), lighterVbox, 0, 3, 0, 1, + GTK_EXPAND , GTK_EXPAND, 0, 0); + gtk_table_attach (GTK_TABLE (table), middleVbox, 4, 7, 0, 1, + GTK_EXPAND, GTK_EXPAND, 0, 0); + gtk_table_attach (GTK_TABLE (table), darkerVbox, 8, 11, 0, 1, + GTK_EXPAND, GTK_EXPAND, 0, 0); + + return win; +} + +static GtkWidget * +fp_create_msnls (GtkWidget *parent) +{ + GtkWidget *table, *lessFrame, *middleFrame, *moreFrame; + GtkWidget *lessVbox, *middleVbox, *moreVbox; + GtkWidget *win; + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gimp_help_connect (win, gimp_standard_help_func, PLUG_IN_PROC, NULL); + + gtk_window_set_title (GTK_WINDOW (win), _("Saturation Variations")); + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent)); + + g_signal_connect (win, "delete-event", + G_CALLBACK (sub_dialog_destroy), + NULL); + + fp_create_preview (&minusSatPreview, &lessFrame, + reduced->width, reduced->height); + fp_create_preview (&SatPreview, &middleFrame, + reduced->width, reduced->height); + fp_create_preview (&plusSatPreview, &moreFrame, + reduced->width, reduced->height); + + fp_create_table_entry (&moreVbox, moreFrame, sat_more); + fp_create_table_entry (&middleVbox, middleFrame, current_val); + fp_create_table_entry (&lessVbox, lessFrame, sat_less); + + table = gtk_table_new (1, 11, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_container_add (GTK_CONTAINER (win), table); + gtk_widget_show (table); + + gtk_table_attach (GTK_TABLE (table), moreVbox, 0, 3, 0, 1, + GTK_EXPAND, GTK_EXPAND, 0, 0); + gtk_table_attach (GTK_TABLE (table), middleVbox, 4, 7, 0, 1, + GTK_EXPAND, GTK_EXPAND, 0, 0); + gtk_table_attach (GTK_TABLE (table), lessVbox, 8, 11, 0, 1, + GTK_EXPAND, GTK_EXPAND, 0, 0); + + return win; +} + +static void +fp_change_current_pixels_by (GtkWidget *widget, + gpointer data) +{ + gimp_radio_button_update (widget, data); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + fp_refresh_previews (fpvals.visible_frames); + if (AW.window && gtk_widget_get_visible (AW.window) && AW.range_preview) + fp_range_preview_spill (AW.range_preview,fpvals.value_by); + } +} + +static GtkWidget * +fp_create_pixels_select_by (void) +{ + GtkWidget *frame; + + frame = gimp_int_radio_group_new (TRUE, _("Select Pixels By"), + G_CALLBACK (fp_change_current_pixels_by), + &fpvals.value_by, + fpvals.value_by, + + _("H_ue"), 0, NULL, + _("Satu_ration"), 1, NULL, + _("V_alue"), 2, NULL, + + NULL); + + gtk_widget_show (frame); + + return frame; +} + +static void +fp_change_selection (GtkWidget *widget, + gpointer data) +{ + gimp_radio_button_update (widget, data); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + fp_redraw_all_windows (); + } +} + +static GtkWidget * +fp_create_show (void) +{ + GtkWidget *frame; + + frame = gimp_int_radio_group_new (TRUE, _("Show"), + G_CALLBACK (fp_change_selection), + &fpvals.selection_only, + fpvals.selection_only, + + _("_Entire image"), 0, NULL, + _("Se_lection only"), 1, NULL, + _("Selec_tion in context"), 2, NULL, + + NULL); + + gtk_widget_show (frame); + + return frame; +} + +static void +fp_create_preview (GtkWidget **preview, + GtkWidget **frame, + gint preview_width, + gint preview_height) +{ + *frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (*frame), GTK_SHADOW_IN); + gtk_widget_show (*frame); + + *preview = gimp_preview_area_new (); + gtk_widget_set_size_request (*preview, preview_width, preview_height); + g_signal_connect (*preview, "size-allocate", + G_CALLBACK (fp_preview_size_allocate), NULL); + gtk_widget_show (*preview); + gtk_container_add (GTK_CONTAINER (*frame), *preview); +} + +static void +fp_frames_checkbutton_in_box (GtkWidget *vbox, + const gchar *label, + GCallback function, + GtkWidget *frame, + gboolean clicked) +{ + GtkWidget *button; + + button = gtk_check_button_new_with_mnemonic (label); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (frame), "ctrlButton", (gpointer) button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + function, + frame); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), clicked); +} + +static void +fp_create_table_entry (GtkWidget **box, + GtkWidget *smaller_frame, + const gchar *description) +{ + GtkWidget *label; + GtkWidget *button; + GtkWidget *table; + + *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1); + gtk_container_set_border_width (GTK_CONTAINER (*box), PR_BX_BRDR); + gtk_widget_show (*box); + + /* Delayed translation applied here */ + label = gtk_label_new (gettext (description)); + + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_widget_show (label); + + table = gtk_table_new (2, 1, FALSE); + gtk_widget_show (table); + + gtk_box_pack_start (GTK_BOX (*box), table, TRUE, TRUE, 0); + + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + 0, 0, 0, 0); + + if (description != current_val) + { + button = gtk_button_new (); + gtk_table_attach (GTK_TABLE (table), button, 0, 1, 1, 2, + 0, 0, 0, 4); + gtk_widget_show (button); + + gtk_container_add (GTK_CONTAINER (button), smaller_frame); + + g_signal_connect (button, "clicked", + G_CALLBACK (fp_selection_made), + (gchar *) description); + } + else + { + gtk_table_attach (GTK_TABLE (table), smaller_frame, 0, 1, 1, 2, + 0, 0, 0, 4); + } +} + +static void +fp_redraw_all_windows (void) +{ + if (reduced) + { + g_free (reduced->rgb); + g_free (reduced->hsv); + g_free (reduced->mask); + + g_free (reduced); + } + + reduced = fp_reduce_image (drawable, mask, + fpvals.preview_size, + fpvals.selection_only); + + fp_adjust_preview_sizes (reduced->width, reduced->height); + + gtk_widget_queue_draw (fp_frames.palette); + gtk_widget_queue_draw (fp_frames.satur); + gtk_widget_queue_draw (fp_frames.lnd); + gtk_widget_queue_draw (dlg); + + fp_refresh_previews (fpvals.visible_frames); +} + +static void +fp_show_hide_frame (GtkWidget *button, + GtkWidget *frame) +{ + gint prev = fpvals.visible_frames; + + if (frame == NULL) + return; + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) + { + if (! gtk_widget_get_visible (frame)) + { + gtk_widget_show (frame); + + if (frame==fp_frames.palette) + fpvals.visible_frames |= HUE; + else if (frame==fp_frames.satur) + fpvals.visible_frames |= SATURATION; + else if (frame==fp_frames.lnd) + fpvals.visible_frames |= VALUE; + + fp_refresh_previews (fpvals.visible_frames & ~prev); + fp_create_smoothness_graph (AW.aliasing_preview); + fp_range_preview_spill (AW.range_preview,fpvals.value_by); + } + } + else + { + if (gtk_widget_get_visible (frame)) + { + gtk_widget_hide (frame); + + if (frame==fp_frames.palette) + fpvals.visible_frames &= ~HUE; + else if (frame==fp_frames.satur) + fpvals.visible_frames &= ~SATURATION; + else if (frame==fp_frames.lnd) + fpvals.visible_frames &= ~VALUE; + } + } +} + +static void +fp_adjust_preview_sizes (gint width, + gint height) +{ + gtk_widget_set_size_request (origPreview, width, height); + gtk_widget_set_size_request (curPreview, width, height); + gtk_widget_set_size_request (rPreview, width, height); + gtk_widget_set_size_request (gPreview, width, height); + gtk_widget_set_size_request (bPreview, width, height); + gtk_widget_set_size_request (cPreview, width, height); + gtk_widget_set_size_request (yPreview, width, height); + gtk_widget_set_size_request (mPreview, width, height); + gtk_widget_set_size_request (centerPreview, width, height); + gtk_widget_set_size_request (lighterPreview, width, height); + gtk_widget_set_size_request (darkerPreview, width, height); + gtk_widget_set_size_request (middlePreview, width, height); + gtk_widget_set_size_request (minusSatPreview, width, height); + gtk_widget_set_size_request (SatPreview, width, height); + gtk_widget_set_size_request (plusSatPreview, width, height); + +} + +static void +fp_selection_made (GtkWidget *widget, + gpointer data) +{ + fpvals.touched[fpvals.value_by] = TRUE; + + if (data == (gpointer) hue_red) + { + update_current_fp (HUE, RED); + } + else if (data == (gpointer) hue_green) + { + update_current_fp (HUE, GREEN); + } + else if (data == (gpointer) hue_blue) + { + update_current_fp (HUE, BLUE); + } + else if (data == (gpointer) hue_cyan) + { + update_current_fp (HUE, CYAN); + } + else if (data == (gpointer) hue_yellow) + { + update_current_fp (HUE, YELLOW); + } + else if (data == (gpointer) hue_magenta) + { + update_current_fp (HUE, MAGENTA); + } + else if (data == (gpointer) val_darker) + { + update_current_fp (VALUE, DOWN); + } + else if (data == (gpointer) val_lighter) + { + update_current_fp (VALUE, UP); + } + else if (data == (gpointer) sat_more) + { + update_current_fp (SATURATION, UP); + } + else if (data == (gpointer) sat_less) + { + update_current_fp (SATURATION, DOWN); + } + + fp_refresh_previews (fpvals.visible_frames); +} + +static void +fp_refresh_previews (gint which) +{ + fp_create_nudge (nudgeArray); + fp_render_preview (origPreview, NONEATALL, 0); + fp_render_preview (curPreview, CURRENT, 0); + + if (which & HUE) + { + fp_render_preview (rPreview, HUE, RED); + fp_render_preview (gPreview, HUE, GREEN); + fp_render_preview (bPreview, HUE, BLUE); + fp_render_preview (cPreview, HUE, CYAN); + fp_render_preview (yPreview, HUE, YELLOW); + fp_render_preview (mPreview, HUE, MAGENTA); + fp_render_preview (centerPreview, CURRENT, 0); + } + + if (which & VALUE) + { + fp_render_preview (lighterPreview, VALUE, UP); + fp_render_preview (middlePreview, CURRENT, 0); + fp_render_preview (darkerPreview, VALUE, DOWN); + } + + if (which & SATURATION) + { + fp_render_preview (plusSatPreview, SATURATION, UP); + fp_render_preview (SatPreview, CURRENT, 0); + fp_render_preview (minusSatPreview, SATURATION, DOWN); + } +} + +static void +fp_response (GtkWidget *widget, + gint response_id, + gpointer data) +{ + switch (response_id) + { + case RESPONSE_RESET: + fp_reset_filter_packs (); + break; + + case GTK_RESPONSE_OK: + FPint.run = TRUE; + gtk_widget_destroy (widget); + break; + + default: + gtk_widget_destroy (widget); + break; + } +} + +static void +fp_scale_update (GtkAdjustment *adjustment, + gdouble *scale_val) +{ + static gdouble prevValue = 0.25; + + *scale_val = gtk_adjustment_get_value (adjustment); + + if (prevValue != gtk_adjustment_get_value (adjustment)) + { + fp_create_nudge (nudgeArray); + fp_refresh_previews (fpvals.visible_frames); + + if (AW.window != NULL && gtk_widget_get_visible (AW.window)) + fp_create_smoothness_graph (AW.aliasing_preview); + + prevValue = gtk_adjustment_get_value (adjustment); + } +} + +static gboolean +fp_dialog (void) +{ + GtkWidget *bna; + GtkWidget *palette; + GtkWidget *lnd; + GtkWidget *show; + GtkWidget *rough; + GtkWidget *range; + GtkWidget *pixelsBy; + GtkWidget *satur; + GtkWidget *control; + GtkWidget *table; + + reduced = fp_reduce_image (drawable, mask, + fpvals.preview_size, + fpvals.selection_only); + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dlg = gimp_dialog_new (_("Filter Pack Simulation"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Reset"), RESPONSE_RESET, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + RESPONSE_RESET, + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + g_signal_connect (dlg, "response", + G_CALLBACK (fp_response), + dlg); + + g_signal_connect (dlg, "destroy", + G_CALLBACK (gtk_main_quit), + NULL); + + fp_advanced_dialog (dlg); + + fp_frames.bna = bna = fp_create_bna (); + fp_frames.rough = rough = fp_create_rough (); + fp_frames.range = range = fp_create_range (); + fp_frames.palette = palette = fp_create_circle_palette (dlg); + fp_frames.lnd = lnd = fp_create_lnd (dlg); + fp_frames.show = show = fp_create_show (); + fp_frames.satur = satur = fp_create_msnls (dlg); + fp_frames.pixelsBy = pixelsBy = fp_create_pixels_select_by (); + control = fp_create_control (); + /********************************************************************/ + /******************** PUT EVERYTHING TOGETHER ******************/ + + table = gtk_table_new (4, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + table, TRUE, TRUE, 0); + gtk_widget_show (table); + + gtk_table_attach (GTK_TABLE (table), bna, 0, 2, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + + gtk_table_attach (GTK_TABLE (table), control, 1, 2, 1, 3, + GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 0, 0); + + gtk_table_attach (GTK_TABLE (table), rough, 1, 2, 3, 4, + GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 0, 0); + + gtk_table_attach (GTK_TABLE (table), show, 0, 1, 1, 2, + GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 0, 0); + + gtk_table_attach (GTK_TABLE (table), range, 0, 1, 2, 3, + GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 0, 0); + + gtk_table_attach (GTK_TABLE (table), pixelsBy, 0, 1, 3, 4, + GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 0, 0); + + gtk_widget_show (dlg); + + fp_refresh_previews (fpvals.visible_frames); + + gtk_main (); + + return FPint.run; +} + +/***********************************************************/ +/************ Advanced Options Window ******************/ +/***********************************************************/ + +static void +fp_preview_scale_update (GtkAdjustment *adjustment, + gdouble *scale_val) +{ + fpvals.preview_size = gtk_adjustment_get_value (adjustment); + fp_redraw_all_windows(); +} + +static void +fp_advanced_dialog (GtkWidget *parent) +{ + const gchar *rangeNames[] = { N_("Shadows:"), + N_("Midtones:"), + N_("Highlights:") }; + GtkWidget *frame, *hbox; + GtkAdjustment *smoothnessData; + GtkWidget *graphFrame, *scale; + GtkWidget *vbox, *label, *labelTable, *alignment; + GtkWidget *inner_vbox, *innermost_vbox; + gint i; + + AW.window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gimp_help_connect (AW.window, gimp_standard_help_func, PLUG_IN_PROC, NULL); + + gtk_window_set_title (GTK_WINDOW (AW.window), + _("Advanced Filter Pack Options")); + gtk_window_set_transient_for (GTK_WINDOW (AW.window), GTK_WINDOW (parent)); + + g_signal_connect (AW.window, "delete-event", + G_CALLBACK (sub_dialog_destroy), + NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_container_add (GTK_CONTAINER (AW.window), hbox); + gtk_widget_show (hbox); + + frame = gimp_frame_new (_("Affected Range")); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + graphFrame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1, TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (graphFrame), GTK_SHADOW_IN); + gtk_container_set_border_width (GTK_CONTAINER (graphFrame), 0); + gtk_box_pack_start (GTK_BOX (vbox), graphFrame, FALSE, FALSE, 0); + gtk_widget_show (graphFrame); + + inner_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (graphFrame), inner_vbox); + gtk_widget_show (inner_vbox); + + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (inner_vbox), alignment, TRUE, TRUE, 0); + gtk_widget_show (alignment); + + innermost_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (alignment), innermost_vbox); + gtk_widget_show (innermost_vbox); + + AW.aliasing_preview = gimp_preview_area_new (); + gtk_widget_set_size_request (AW.aliasing_preview, 256, MAX_ROUGHNESS); + gtk_box_pack_start (GTK_BOX (innermost_vbox), + AW.aliasing_preview, TRUE, TRUE, 0); + gtk_widget_show (AW.aliasing_preview); + + fp_create_smoothness_graph (AW.aliasing_preview); + + AW.range_preview = gimp_preview_area_new (); + gtk_widget_set_size_request (AW.range_preview, 256, RANGE_HEIGHT); + gtk_box_pack_start(GTK_BOX (innermost_vbox), + AW.range_preview, TRUE, TRUE, 0); + gtk_widget_show (AW.range_preview); + + fp_range_preview_spill (AW.range_preview, fpvals.value_by); + + labelTable = gtk_table_new (3, 4, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (labelTable), 6); + gtk_table_set_row_spacings (GTK_TABLE (labelTable), 6); + gtk_box_pack_start (GTK_BOX (vbox), labelTable, FALSE, FALSE, 0); + gtk_widget_show (labelTable); + + /************************************************************/ + + AW.aliasing_graph = gtk_drawing_area_new (); + gtk_widget_set_size_request (AW.aliasing_graph, + 2 * MARGIN + 256, + RANGE_HEIGHT); + gtk_box_pack_start (GTK_BOX (inner_vbox), AW.aliasing_graph, TRUE, TRUE, 0); + gtk_widget_show (AW.aliasing_graph); + gtk_widget_set_events (AW.aliasing_graph, RANGE_ADJUST_MASK); + + g_signal_connect (AW.aliasing_graph, "event", + G_CALLBACK (fp_range_change_events), + &fpvals); + + /************************************************************/ + + for (i = 0; i < 12; i++) + { + label = fp_widgets.range_label[i] = gtk_label_new ("-"); + + if (!(i % 4)) + { + gtk_label_set_text (GTK_LABEL(label), gettext (rangeNames[i/4])); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_label_set_yalign (GTK_LABEL (label), 1.0); + } + + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (labelTable), label, i%4, i%4+1, i/4, i/4+1, + GTK_EXPAND | GTK_FILL, 0, 0, 0); + } + + smoothnessData = (GtkAdjustment *) + gtk_adjustment_new (fpvals.aliasing, + 0, 1.0, 0.05, 0.01, 0.0); + + fp_widgets.aliasing_scale = scale = + gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, smoothnessData); + gtk_widget_set_size_request (scale, 200, -1); + gtk_scale_set_digits (GTK_SCALE (scale), 2); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); + gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + g_signal_connect (smoothnessData, "value-changed", + G_CALLBACK (fp_scale_update), + &fpvals.aliasing); + + /******************* MISC OPTIONS ***************************/ + + frame = gimp_frame_new (_("Preview Size")); + gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + smoothnessData = (GtkAdjustment *) + gtk_adjustment_new (fpvals.preview_size, + 50, MAX_PREVIEW_SIZE, + 5, 5, 0.0); + + fp_widgets.preview_size_scale = scale = + gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, smoothnessData); + gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0); + gtk_widget_set_size_request (scale, 100, -1); + gtk_scale_set_digits (GTK_SCALE (scale), 0); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); + gtk_widget_show (scale); + + g_signal_connect (smoothnessData, "value-changed", + G_CALLBACK (fp_preview_scale_update), + &fpvals.preview_size); + + update_range_labels (); +} + +static void +slider_erase (GdkWindow *window, + int xpos) +{ + gdk_window_clear_area (window, MARGIN + xpos - (RANGE_HEIGHT - 1) / 2, 0, + RANGE_HEIGHT, RANGE_HEIGHT); +} + +static void +draw_slider (cairo_t *cr, + GdkColor *border_color, + GdkColor *fill_color, + gint xpos) +{ + cairo_move_to (cr, MARGIN + xpos, 0); + cairo_line_to (cr, MARGIN + xpos - (RANGE_HEIGHT - 1) / 2, RANGE_HEIGHT - 1); + cairo_line_to (cr, MARGIN + xpos + (RANGE_HEIGHT - 1) / 2, RANGE_HEIGHT - 1); + cairo_line_to (cr, MARGIN + xpos, 0); + + gdk_cairo_set_source_color (cr, fill_color); + cairo_fill_preserve (cr); + + gdk_cairo_set_source_color (cr, border_color); + cairo_stroke (cr); +} + +static void +draw_it (GtkWidget *widget) +{ + GtkStyle *style = gtk_widget_get_style (AW.aliasing_graph); + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (AW.aliasing_graph)); + + cairo_translate (cr, 0.5, 0.5); + cairo_set_line_width (cr, 1.0); + + draw_slider (cr, + &style->black, + &style->dark[GTK_STATE_NORMAL], + fpvals.cutoff[SHADOWS]); + + draw_slider (cr, + &style->black, + &style->dark[GTK_STATE_NORMAL], + fpvals.cutoff[MIDTONES]); + + draw_slider (cr, + &style->black, + &style->dark[GTK_STATE_SELECTED], + fpvals.offset); + + cairo_destroy (cr); +} + +static gboolean +fp_range_change_events (GtkWidget *widget, + GdkEvent *event, + FPValues *current) +{ + GdkEventButton *bevent; + GdkEventMotion *mevent; + gint shad, mid, offset, min; + static guchar *new; + gint x; + + switch (event->type) + { + case GDK_EXPOSE: + draw_it (NULL); + break; + + case GDK_BUTTON_PRESS: + bevent= (GdkEventButton *) event; + + shad = abs (bevent->x - fpvals.cutoff[SHADOWS]); + mid = abs (bevent->x - fpvals.cutoff[MIDTONES]); + offset = abs (bevent->x - fpvals.offset); + + min = MIN (MIN (shad, mid), offset); + + if (bevent->x >0 && bevent->x<256) + { + if (min == shad) + new = &fpvals.cutoff[SHADOWS]; + else if (min == mid) + new = &fpvals.cutoff[MIDTONES]; + else + new = &fpvals.offset; + + slider_erase (gtk_widget_get_window (AW.aliasing_graph), *new); + *new = bevent->x; + } + + draw_it (NULL); + + fp_range_preview_spill (AW.range_preview, fpvals.value_by); + update_range_labels (); + fp_create_smoothness_graph (AW.aliasing_preview); + break; + + case GDK_BUTTON_RELEASE: + fp_refresh_previews (fpvals.visible_frames); + break; + + case GDK_MOTION_NOTIFY: + mevent = (GdkEventMotion *) event; + x = mevent->x; + + if (x >= 0 && x < 256) + { + slider_erase (gtk_widget_get_window (AW.aliasing_graph), *new); + *new = x; + draw_it (NULL); + fp_range_preview_spill (AW.range_preview, fpvals.value_by); + update_range_labels (); + fp_create_smoothness_graph (AW.aliasing_preview); + } + + gdk_event_request_motions (mevent); + break; + + default: + break; + } + + return FALSE; +} + +static void +update_range_labels (void) +{ + gchar buffer[4]; + + gtk_label_set_text (GTK_LABEL(fp_widgets.range_label[1]), "0"); + + g_snprintf (buffer, sizeof (buffer), "%d", fpvals.cutoff[SHADOWS]); + gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[3]), buffer); + gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[5]), buffer); + + g_snprintf (buffer, sizeof (buffer), "%d", fpvals.cutoff[MIDTONES]); + gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[7]), buffer); + gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[9]), buffer); + + gtk_label_set_text (GTK_LABEL(fp_widgets.range_label[11]), "255"); +} + +static void +fp_init_filter_packs (void) +{ + gint i, j; + + for (i = 0; i < 256; i++) + for (j = BY_HUE; j < JUDGE_BY; j++) + { + fpvals.red_adjust [j][i] = 0; + fpvals.green_adjust [j][i] = 0; + fpvals.blue_adjust [j][i] = 0; + fpvals.sat_adjust [j][i] = 0; + } +} + +static void +fp_reset_filter_packs (void) +{ + fp_init_filter_packs (); + fp_refresh_previews (fpvals.visible_frames); +} + +static ReducedImage * +fp_reduce_image (GimpDrawable *drawable, + GimpDrawable *mask, + gint longer_size, + gint selection) +{ + gint RH, RW, bytes = drawable->bpp; + gint x, y, width, height; + ReducedImage *temp = g_new0 (ReducedImage, 1); + guchar *tempRGB, *src_row, *tempmask, *src_mask_row, R, G, B; + gint i, j, whichcol, whichrow; + GimpPixelRgn srcPR, srcMask; + gdouble *tempHSV; + GimpRGB rgb; + GimpHSV hsv; + + switch (selection) + { + case 0: + x = 0; + width = drawable->width; + y = 0; + height = drawable->height; + break; + + case 1: + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x, &y, &width, &height)) + return temp; + break; + + case 2: + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x, &y, &width, &height) || + ! gimp_rectangle_intersect (x - width / 2, y - height / 2, + 2 * width, 2 * height, + 0, 0, drawable->width, drawable->height, + &x, &y, &width, &height)) + return temp; + break; + + default: + return temp; + } + + if (width > height) + { + RW = longer_size; + RH = (gdouble) height * (gdouble) longer_size / (gdouble) width; + } + else + { + RH = longer_size; + RW = (gdouble) width * (gdouble) longer_size / (gdouble) height; + } + + tempRGB = g_new (guchar, RW * RH * bytes); + tempHSV = g_new (gdouble, RW * RH * bytes); + tempmask = g_new (guchar, RW * RH); + + src_row = g_new (guchar, width * bytes); + src_mask_row = g_new (guchar, width); + + gimp_pixel_rgn_init (&srcPR, drawable, x, y, width, height, FALSE, FALSE); + + if (mask) + { + gimp_pixel_rgn_init (&srcMask, mask, x, y, width, height, FALSE, FALSE); + } + else + { + memset (src_mask_row, 255, width); + } + + for (i = 0; i < RH; i++) + { + whichrow = (gdouble) i * (gdouble) height / (gdouble) RH; + + gimp_pixel_rgn_get_row (&srcPR, src_row, x, y + whichrow, width); + + if (mask) + gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x, y + whichrow, width); + + for (j = 0; j < RW; j++) + { + whichcol = (gdouble) j * (gdouble) width / (gdouble) RW; + + tempmask[i * RW + j] = src_mask_row[whichcol]; + + R = src_row[whichcol * bytes + 0]; + G = src_row[whichcol * bytes + 1]; + B = src_row[whichcol * bytes + 2]; + + gimp_rgb_set_uchar (&rgb, R, G, B); + gimp_rgb_to_hsv (&rgb, &hsv); + + tempRGB[i * RW * bytes + j * bytes + 0] = R; + tempRGB[i * RW * bytes + j * bytes + 1] = G; + tempRGB[i * RW * bytes + j * bytes + 2] = B; + + tempHSV[i * RW * bytes + j * bytes + 0] = hsv.h; + tempHSV[i * RW * bytes + j * bytes + 1] = hsv.s; + tempHSV[i * RW * bytes + j * bytes + 2] = hsv.v; + + if (bytes == 4) + { + tempRGB[i * RW * bytes + j * bytes + 3] = + src_row[whichcol * bytes + 3]; + } + } + } + + g_free (src_row); + g_free (src_mask_row); + + temp->width = RW; + temp->height = RH; + temp->rgb = tempRGB; + temp->hsv = tempHSV; + temp->mask = tempmask; + + return temp; +} + +static void +fp_render_preview (GtkWidget *preview, + gint change_what, + gint change_which) +{ + guchar *a; + gint Inten; + gint bytes = drawable->bpp; + gint i, j, k, nudge, M, m, middle, JudgeBy; + gdouble partial; + gint RW = reduced->width; + gint RH = reduced->height; + gint backupP[3]; + gint P[3]; + gint tempSat[JUDGE_BY][256]; + + a = g_new (guchar, 4 * RW * RH); + + if (change_what == SATURATION) + for (k = 0; k < 256; k++) + { + for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++) + tempSat[JudgeBy][k] = 0; + + tempSat[fpvals.value_by][k] += + change_which * nudgeArray[(k + fpvals.offset) % 256]; + } + + for (i = 0; i < RH; i++) + { + for (j = 0; j < RW; j++) + { + backupP[0] = P[0] = reduced->rgb[i * RW * bytes + j * bytes + 0]; + backupP[1] = P[1] = reduced->rgb[i * RW * bytes + j * bytes + 1]; + backupP[2] = P[2] = reduced->rgb[i * RW * bytes + j * bytes + 2]; + + m = MIN (MIN (P[0], P[1]), P[2]); + M = MAX (MAX (P[0], P[1]), P[2]); + + middle = (M + m) / 2; + + for (k = 0; k < 3; k++) + if (P[k] != m && P[k] != M) middle = P[k]; + + partial = reduced->mask[i * RW + j] / 255.0; + + for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++) + { + if (!fpvals.touched[JudgeBy]) + continue; + + Inten = + reduced->hsv[i * RW * bytes + j * bytes + JudgeBy] * 255.0; + + /*DO SATURATION FIRST*/ + if (change_what != NONEATALL) + { + gint adjust = partial * fpvals.sat_adjust[JudgeBy][Inten]; + + if (M != m) + { + for (k = 0; k < 3; k++) + if (backupP[k] == M) + { + P[k] = MAX (P[k] + adjust, middle); + } + else if (backupP[k] == m) + { + P[k] = MIN (P[k] - adjust, middle); + } + } + + P[0] += partial * fpvals.red_adjust[JudgeBy][Inten]; + P[1] += partial * fpvals.green_adjust[JudgeBy][Inten]; + P[2] += partial * fpvals.blue_adjust[JudgeBy][Inten]; + } + } + + Inten = + reduced->hsv[i * RW * bytes + j * bytes + fpvals.value_by] * 255.0; + nudge = partial * nudgeArray[(Inten + fpvals.offset) % 256]; + + switch (change_what) + { + case HUE: + P[0] += colorSign[RED][change_which] * nudge; + P[1] += colorSign[GREEN][change_which] * nudge; + P[2] += colorSign[BLUE][change_which] * nudge; + break; + + case SATURATION: + for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++) + { + gint adjust = partial * tempSat[JudgeBy][Inten]; + + for (k = 0; k < 3; k++) + if (M != m) + { + if (backupP[k] == M) + { + P[k] = MAX (P[k] + adjust, middle); + } + else if (backupP[k] == m) + { + P[k] = MIN (P[k] - adjust, middle); + } + } + } + break; + + case VALUE: + P[0] += change_which * nudge; + P[1] += change_which * nudge; + P[2] += change_which * nudge; + break; + + default: + break; + } + + a[(i * RW + j) * 4 + 0] = CLAMP0255 (P[0]); + a[(i * RW + j) * 4 + 1] = CLAMP0255 (P[1]); + a[(i * RW + j) * 4 + 2] = CLAMP0255 (P[2]); + + if (bytes == 4) + a[(i * RW + j) * 4 + 3] = reduced->rgb[i * RW * bytes + j * bytes + 3]; + else + a[(i * RW + j) * 4 + 3] = 255; + } + } + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), + 0, 0, RW, RH, + GIMP_RGBA_IMAGE, + a, + RW * 4); + g_free (a); +} + +static void +update_current_fp (gint change_what, + gint change_which) +{ + gint i; + + for (i = 0; i < 256; i++) + { + gint nudge; + + fp_create_nudge (nudgeArray); + nudge = nudgeArray[(i + fpvals.offset) % 256]; + + switch (change_what) { + case HUE: + fpvals.red_adjust[fpvals.value_by][i] += + colorSign[RED][change_which] * nudge; + + fpvals.green_adjust[fpvals.value_by][i] += + colorSign[GREEN][change_which] * nudge; + + fpvals.blue_adjust[fpvals.value_by][i] += + colorSign[BLUE][change_which] * nudge; + break; + + case SATURATION: + fpvals.sat_adjust[fpvals.value_by][i] += change_which * nudge; + break; + + case VALUE: + fpvals.red_adjust[fpvals.value_by][i] += change_which * nudge; + fpvals.green_adjust[fpvals.value_by][i] += change_which * nudge; + fpvals.blue_adjust[fpvals.value_by][i] += change_which * nudge; + break; + + default: + break; + } + } +} + +static void +fp_create_smoothness_graph (GtkWidget *preview) +{ + guchar data[256 * MAX_ROUGHNESS * 3]; + gint nArray[256]; + gint i, j; + gboolean toBeBlack; + + fp_create_nudge(nArray); + + for (i = 0; i < MAX_ROUGHNESS; i++) + { + gint coor = MAX_ROUGHNESS - i; + + for (j = 0; j < 256; j++) + { + data[3 * (i * 256 + j) + 0] = 255; + data[3 * (i * 256 + j) + 1] = 255; + data[3 * (i * 256 + j) + 2] = 255; + + if (!(i % (MAX_ROUGHNESS / 4))) + { + data[3 * (i * 256 + j) + 0] = 255; + data[3 * (i * 256 + j) + 1] = 128; + data[3 * (i * 256 + j) + 2] = 128; + } + + if (!((j + 1) % 32)) + { + data[3 * (i * 256 + j) + 0] = 255; + data[3 * (i * 256 + j) + 1] = 128; + data[3 * (i * 256 + j) + 2] = 128; + } + + toBeBlack = FALSE; + + if (nArray[j] == coor) + toBeBlack = TRUE; + + if (j < 255) + { + gint jump = abs (nArray[j] - nArray[j+1]); + + if (abs (coor - nArray[j]) < jump && + abs (coor - nArray[j + 1]) < jump) + toBeBlack = TRUE; + } + + if (toBeBlack) + { + data[3 * (i * 256 + j) + 0] = 0; + data[3 * (i * 256 + j) + 1] = 0; + data[3 * (i * 256 + j) + 2] = 0; + } + } + } + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), + 0, 0, 256, MAX_ROUGHNESS, + GIMP_RGB_IMAGE, + data, + 256 * 3); +} + +static void +fp_range_preview_spill (GtkWidget *preview, + gint type) +{ + gint i, j; + guchar data[256 * RANGE_HEIGHT * 3]; + + for (i = 0; i < RANGE_HEIGHT; i++) + { + for (j = 0; j < 256; j++) + { + GimpRGB rgb; + GimpHSV hsv; + + if (! ((j + 1) % 32)) + { + data[3 * (i * 256 + j) + 0] = 255; + data[3 * (i * 256 + j) + 1] = 128; + data[3 * (i * 256 + j) + 2] = 128; + } + else + { + switch (type) + { + case BY_VAL: + data[3 * (i * 256 + j) + 0] = j - fpvals.offset; + data[3 * (i * 256 + j) + 1] = j - fpvals.offset; + data[3 * (i * 256 + j) + 2] = j - fpvals.offset; + break; + + case BY_HUE: + gimp_hsv_set (&hsv, + ((j - fpvals.offset + 256) % 256) / 255.0, + 1.0, + 0.5); + gimp_hsv_to_rgb (&hsv, &rgb); + gimp_rgb_get_uchar (&rgb, + &data[3 * (i * 256 + j) + 0], + &data[3 * (i * 256 + j) + 1], + &data[3 * (i * 256 + j) + 2]); + break; + + case BY_SAT: + gimp_hsv_set (&hsv, + 0.5, + ((j - (gint) fpvals.offset + 256) % 256) / 255.0, + 0.5); + gimp_hsv_to_rgb (&hsv, &rgb); + gimp_rgb_get_uchar (&rgb, + &data[3 * (i * 256 + j) + 0], + &data[3 * (i * 256 + j) + 1], + &data[3 * (i * 256 + j) + 2]); + break; + } + } + } + } + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), + 0, 0, 256, RANGE_HEIGHT, + GIMP_RGB_IMAGE, + data, + 256 * 3); +} + +static void +fp_create_nudge (gint *adj_array) +{ + gint left, right, middle,i; + /* The following function was determined by trial and error */ + gdouble Steepness = pow (1 - fpvals.aliasing, 4) * .8; + + left = (fpvals.intensity_range == SHADOWS) ? 0 : fpvals.cutoff[fpvals.intensity_range - 1]; + right = fpvals.cutoff[fpvals.intensity_range]; + middle = (left + right)/2; + + if (fpvals.aliasing) + for (i = 0; i < 256; i++) + if (i <= middle) + adj_array[i] = MAX_ROUGHNESS * + fpvals.roughness * (1 + tanh (Steepness * (i - left))) / 2; + else + adj_array[i] = MAX_ROUGHNESS * + fpvals.roughness * (1 + tanh (Steepness * (right - i))) / 2; + else + for (i = 0; i < 256; i++) + adj_array[i] = (left <= i && i <= right) + ? MAX_ROUGHNESS * fpvals.roughness : 0; +} + +static void +fp_preview_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + gint which = fpvals.visible_frames; + + if (widget == origPreview) + fp_render_preview (origPreview, NONEATALL, 0); + else if (widget == curPreview) + fp_render_preview (curPreview, CURRENT, 0); + + if (which & HUE) + { + if (widget == rPreview) + fp_render_preview (rPreview, HUE, RED); + else if (widget == gPreview) + fp_render_preview (gPreview, HUE, GREEN); + else if (widget == bPreview) + fp_render_preview (bPreview, HUE, BLUE); + else if (widget == cPreview) + fp_render_preview (cPreview, HUE, CYAN); + else if (widget == yPreview) + fp_render_preview (yPreview, HUE, YELLOW); + else if (widget == mPreview) + fp_render_preview (mPreview, HUE, MAGENTA); + else if (widget == centerPreview) + fp_render_preview (centerPreview, CURRENT, 0); + } + + if (which & VALUE) + { + if (widget == lighterPreview) + fp_render_preview (lighterPreview, VALUE, UP); + else if (widget == middlePreview) + fp_render_preview (middlePreview, CURRENT, 0); + else if (widget == darkerPreview) + fp_render_preview (darkerPreview, VALUE, DOWN); + } + + if (which & SATURATION) + { + if (widget == plusSatPreview) + fp_render_preview (plusSatPreview, SATURATION, UP); + else if (widget == SatPreview) + fp_render_preview (SatPreview, CURRENT, 0); + else if (widget == minusSatPreview) + fp_render_preview (minusSatPreview, SATURATION, DOWN); + } +} diff --git a/plug-ins/common/fractal-trace.c b/plug-ins/common/fractal-trace.c new file mode 100644 index 0000000..9ac702e --- /dev/null +++ b/plug-ins/common/fractal-trace.c @@ -0,0 +1,829 @@ +/****************************************************************************** + + fractaltrace.c -- This is a plug-in for GIMP 1.0 + + Copyright (C) 1997 Hirotsuna Mizuno + s1041150@u-aizu.ac.jp + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + +******************************************************************************/ + +#define PLUG_IN_PROC "plug-in-fractal-trace" +#define PLUG_IN_BINARY "fractal-trace" +#define PLUG_IN_ROLE "gimp-fractal-trace" +#define PLUG_IN_VERSION "v0.4 test version (Dec. 25 1997)" + +/*****************************************************************************/ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +/******************************************************************************/ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void filter (GimpDrawable *drawable); + +static void pixels_init (GimpDrawable *drawable); +static void pixels_free (void); + +static int dialog_show (void); +static void dialog_preview_draw (void); + +/******************************************************************************/ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +MAIN () + +/******************************************************************************/ + +enum +{ + OUTSIDE_TYPE_WRAP, + OUTSIDE_TYPE_TRANSPARENT, + OUTSIDE_TYPE_BLACK, + OUTSIDE_TYPE_WHITE +}; + +typedef struct +{ + gdouble x1; + gdouble x2; + gdouble y1; + gdouble y2; + gint32 depth; + gint32 outside_type; +} parameter_t; + +static parameter_t parameters = +{ + -1.0, + +0.5, + -1.0, + +1.0, + 3, + OUTSIDE_TYPE_WRAP +}; + +/******************************************************************************/ + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_FLOAT, "xmin", "xmin fractal image delimiter" }, + { GIMP_PDB_FLOAT, "xmax", "xmax fractal image delimiter" }, + { GIMP_PDB_FLOAT, "ymin", "ymin fractal image delimiter" }, + { GIMP_PDB_FLOAT, "ymax", "ymax fractal image delimiter" }, + { GIMP_PDB_INT32, "depth", "Trace depth" }, + { GIMP_PDB_INT32, "outside-type", "Outside type " + "{ WRAP (0), TRANS (1), BLACK (2), WHITE (3) }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Transform image with the Mandelbrot Fractal"), + "transform image with the Mandelbrot Fractal", + "Hirotsuna Mizuno ", + "Copyright (C) 1997 Hirotsuna Mizuno", + PLUG_IN_VERSION, + N_("_Fractal Trace (legacy)..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Map"); +} + +/******************************************************************************/ + +typedef struct +{ + gint x1; + gint x2; + gint y1; + gint y2; + gint width; + gint height; + gdouble center_x; + gdouble center_y; +} selection_t; + +typedef struct +{ + gint width; + gint height; + gint bpp; + gint alpha; +} image_t; + +static selection_t selection; +static image_t image; + +/******************************************************************************/ + +static void +run (const gchar *name, + gint argc, + const GimpParam *args, + gint *retc, + GimpParam **rets) +{ + GimpDrawable *drawable; + GimpRunMode run_mode; + GimpPDBStatusType status; + static GimpParam returns[1]; + + run_mode = args[0].data.d_int32; + status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + + drawable = gimp_drawable_get (args[2].data.d_drawable); + image.width = gimp_drawable_width( drawable->drawable_id); + image.height = gimp_drawable_height (drawable->drawable_id); + image.bpp = gimp_drawable_bpp (drawable->drawable_id); + image.alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &selection.x1, &selection.y1, + &selection.width, &selection.height)) + { + returns[0].type = GIMP_PDB_STATUS; + returns[0].data.d_status = status; + *retc = 1; + *rets = returns; + + return; + } + + selection.x2 = selection.x1 + selection.width; + selection.y2 = selection.y1 + selection.height; + selection.center_x = selection.x1 + (gdouble) selection.width / 2.0; + selection.center_y = selection.y1 + (gdouble) selection.height / 2.0; + + pixels_init (drawable); + + if (!gimp_drawable_is_rgb(drawable->drawable_id) && + !gimp_drawable_is_gray(drawable->drawable_id)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + switch (run_mode) + { + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, ¶meters); + break; + + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, ¶meters); + if (!dialog_show ()) + { + status = GIMP_PDB_EXECUTION_ERROR; + break; + } + gimp_set_data (PLUG_IN_PROC, ¶meters, sizeof (parameter_t)); + break; + + case GIMP_RUN_NONINTERACTIVE: + if (argc != 9) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + parameters.x1 = args[3].data.d_float; + parameters.x2 = args[4].data.d_float; + parameters.y1 = args[5].data.d_float; + parameters.y2 = args[6].data.d_float; + parameters.depth = args[7].data.d_int32; + parameters.outside_type = args[8].data.d_int32; + } + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_tile_cache_ntiles(2 * (drawable->width / gimp_tile_width() + 1)); + filter (drawable); + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush(); + } + + gimp_drawable_detach (drawable); + + pixels_free (); + + returns[0].type = GIMP_PDB_STATUS; + returns[0].data.d_status = status; + *retc = 1; + *rets = returns; +} + +/******************************************************************************/ + +static guchar **spixels; +static guchar **dpixels; +static GimpPixelRgn sPR; +static GimpPixelRgn dPR; + +typedef struct +{ + guchar r; + guchar g; + guchar b; + guchar a; +} pixel_t; + +static void +pixels_init (GimpDrawable *drawable) +{ + gint y; + + gimp_pixel_rgn_init (&sPR, drawable, + 0, 0, image.width, image.height, FALSE, FALSE); + gimp_pixel_rgn_init (&dPR, drawable, + 0, 0, image.width, image.height, TRUE, TRUE); + + spixels = g_new (guchar *, image.height); + dpixels = g_new (guchar *, image.height); + + for (y = 0; y < image.height; y++) + { + spixels[y] = g_new (guchar, image.width * image.bpp); + dpixels[y] = g_new (guchar, image.width * image.bpp); + gimp_pixel_rgn_get_row (&sPR, spixels[y], 0, y, image.width); + } +} + +static void +pixels_free (void) +{ + gint y; + + for (y = 0; y < image.height; y++) + { + g_free (spixels[y]); + g_free (dpixels[y]); + } + g_free (spixels); + g_free (dpixels); +} + +static void +pixels_get (gint x, + gint y, + pixel_t *pixel) +{ + if(x < 0) x = 0; else if (image.width <= x) x = image.width - 1; + if(y < 0) y = 0; else if (image.height <= y) y = image.height - 1; + + switch (image.bpp) + { + case 1: /* GRAY */ + pixel->r = spixels[y][x*image.bpp]; + pixel->g = spixels[y][x*image.bpp]; + pixel->b = spixels[y][x*image.bpp]; + pixel->a = 255; + break; + case 2: /* GRAY+A */ + pixel->r = spixels[y][x*image.bpp]; + pixel->g = spixels[y][x*image.bpp]; + pixel->b = spixels[y][x*image.bpp]; + pixel->a = spixels[y][x*image.bpp+1]; + break; + case 3: /* RGB */ + pixel->r = spixels[y][x*image.bpp]; + pixel->g = spixels[y][x*image.bpp+1]; + pixel->b = spixels[y][x*image.bpp+2]; + pixel->a = 255; + break; + case 4: /* RGB+A */ + pixel->r = spixels[y][x*image.bpp]; + pixel->g = spixels[y][x*image.bpp+1]; + pixel->b = spixels[y][x*image.bpp+2]; + pixel->a = spixels[y][x*image.bpp+3]; + break; + } +} + +static void +pixels_get_biliner (gdouble x, + gdouble y, + pixel_t *pixel) +{ + pixel_t A, B, C, D; + gdouble a, b, c, d; + gint x1, y1, x2, y2; + gdouble dx, dy; + gdouble alpha; + + x1 = (gint) floor (x); + x2 = x1 + 1; + y1 = (gint) floor (y); + y2 = y1 + 1; + + dx = x - (gdouble) x1; + dy = y - (gdouble) y1; + a = (1.0 - dx) * (1.0 - dy); + b = dx * (1.0 - dy); + c = (1.0 - dx) * dy; + d = dx * dy; + + pixels_get (x1, y1, &A); + pixels_get (x2, y1, &B); + pixels_get (x1, y2, &C); + pixels_get (x2, y2, &D); + + alpha = 1.0001*(a * (gdouble) A.a + b * (gdouble) B.a + + c * (gdouble) C.a + d * (gdouble) D.a); + pixel->a = (guchar) alpha; + + if (pixel->a) + { + pixel->r = (guchar) ((a * (gdouble) A.r * A.a + + b * (gdouble) B.r * B.a + + c * (gdouble) C.r * C.a + + d * (gdouble) D.r * D.a) / alpha); + pixel->g = (guchar) ((a * (gdouble) A.g * A.a + + b * (gdouble) B.g * B.a + + c * (gdouble) C.g * C.a + + d * (gdouble) D.g * D.a) / alpha); + pixel->b = (guchar) ((a * (gdouble) A.b * A.a + + b * (gdouble) B.b * B.a + + c * (gdouble) C.b * C.a + + d * (gdouble) D.b * D.a) / alpha); + } +} + +static void +pixels_set (gint x, + gint y, + pixel_t *pixel) +{ + switch (image.bpp) + { + case 1: /* GRAY */ + dpixels[y][x*image.bpp] = pixel->r; + break; + case 2: /* GRAY+A */ + dpixels[y][x*image.bpp] = pixel->r; + dpixels[y][x*image.bpp+1] = pixel->a; + break; + case 3: /* RGB */ + dpixels[y][x*image.bpp] = pixel->r; + dpixels[y][x*image.bpp+1] = pixel->g; + dpixels[y][x*image.bpp+2] = pixel->b; + break; + case 4: /* RGB+A */ + dpixels[y][x*image.bpp] = pixel->r; + dpixels[y][x*image.bpp+1] = pixel->g; + dpixels[y][x*image.bpp+2] = pixel->b; + dpixels[y][x*image.bpp+3] = pixel->a; + break; + } +} + +static void +pixels_store (void) +{ + gint y; + + for (y = selection.y1; y < selection.y2; y++) + { + gimp_pixel_rgn_set_row (&dPR, dpixels[y], 0, y, image.width); + } +} + +/******************************************************************************/ + +static void +mandelbrot (gdouble x, + gdouble y, + gdouble *u, + gdouble *v) +{ + gint iter = 0; + gdouble xx = x; + gdouble yy = y; + gdouble x2 = xx * xx; + gdouble y2 = yy * yy; + gdouble tmp; + + while (iter < parameters.depth) + { + tmp = x2 - y2 + x; + yy = 2 * xx * yy + y; + xx = tmp; + x2 = xx * xx; + y2 = yy * yy; + iter++; + } + *u = xx; + *v = yy; +} + +/******************************************************************************/ + +static void +filter (GimpDrawable *drawable) +{ + gint x, y; + pixel_t pixel; + gdouble scale_x, scale_y; + gdouble cx, cy; + gdouble px, py; + gint h_percent; + + gimp_progress_init (_("Fractal Trace")); + + if (selection.width == 0 || selection.height == 0) + return; + + h_percent = selection.height / 100; + + scale_x = (parameters.x2 - parameters.x1) / selection.width; + scale_y = (parameters.y2 - parameters.y1) / selection.height; + + for (y = selection.y1; y < selection.y2; y++) + { + cy = parameters.y1 + (y - selection.y1) * scale_y; + for (x = selection.x1; x < selection.x2; x++) + { + cx = parameters.x1 + (x - selection.x1) * scale_x; + mandelbrot (cx, cy, &px, &py); + px = (px - parameters.x1) / scale_x + selection.x1; + py = (py - parameters.y1) / scale_y + selection.y1; + if (0 <= px && px < image.width && 0 <= py && py < image.height) + { + pixels_get_biliner (px, py, &pixel); + } + else + { + switch (parameters.outside_type) + { + case OUTSIDE_TYPE_WRAP: + px = fmod (px, image.width); + py = fmod (py, image.height); + if( px < 0.0) px += image.width; + if (py < 0.0) py += image.height; + pixels_get_biliner (px, py, &pixel); + break; + case OUTSIDE_TYPE_TRANSPARENT: + pixel.r = pixel.g = pixel.b = 0; + pixel.a = 0; + break; + case OUTSIDE_TYPE_BLACK: + pixel.r = pixel.g = pixel.b = 0; + pixel.a = 255; + break; + case OUTSIDE_TYPE_WHITE: + pixel.r = pixel.g = pixel.b = 255; + pixel.a = 255; + break; + } + } + pixels_set (x, y, &pixel); + } + + if (h_percent == 0 || ((y - selection.y1) % h_percent) == 0) + gimp_progress_update ((gdouble) (y-selection.y1) / selection.height); + } + + gimp_progress_update (1.0); + + pixels_store (); + + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, + selection.x1, selection.y1, + selection.width, selection.height); +} + +/******************************************************************************/ + +#define PREVIEW_SIZE 200 + +typedef struct +{ + GtkWidget *preview; + guchar *pixels; + gdouble scale; + gint width; + gint height; + gint bpp; +} preview_t; + +static preview_t preview; + +static void +dialog_preview_setpixel (gint x, + gint y, + pixel_t *pixel) +{ + preview.pixels[(y * preview.width + x) * 4 + 0] = pixel->r; + preview.pixels[(y * preview.width + x) * 4 + 1] = pixel->g; + preview.pixels[(y * preview.width + x) * 4 + 2] = pixel->b; + preview.pixels[(y * preview.width + x) * 4 + 3] = pixel->a; +} + +static void +dialog_preview_init (void) +{ + pixel_t pixel; + gint x, y; + gdouble cx, cy; + + if (image.width < image.height) + preview.scale = (gdouble)selection.height / (gdouble)PREVIEW_SIZE; + else + preview.scale = (gdouble)selection.width / (gdouble)PREVIEW_SIZE; + preview.width = (gdouble)selection.width / preview.scale; + preview.height = (gdouble)selection.height / preview.scale; + + preview.preview = gimp_preview_area_new (); + gtk_widget_set_size_request (preview.preview, + preview.width, preview.height); + + preview.pixels = g_new (guchar, preview.height * preview.width * 4); + + for (y = 0; y < preview.height; y++) + { + cy = selection.y1 + (gdouble)y * preview.scale; + for (x = 0; x < preview.width; x++) + { + cx = selection.x1 + (gdouble)x * preview.scale; + pixels_get_biliner (cx, cy, &pixel); + dialog_preview_setpixel (x, y, &pixel); + } + } + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview.preview), + 0, 0, preview.width, preview.height, + GIMP_RGBA_IMAGE, + preview.pixels, + preview.width *4); +} + +static void +dialog_preview_draw (void) +{ + gint x, y; + pixel_t pixel; + gdouble scale_x, scale_y; + gdouble cx, cy; + gdouble px, py; + + scale_x = (parameters.x2 - parameters.x1) / preview.width; + scale_y = (parameters.y2 - parameters.y1) / preview.height; + + for (y = 0; y < preview.height; y++) + { + cy = parameters.y1 + y * scale_y; + for (x = 0; x < preview.width; x++) + { + cx = parameters.x1 + x * scale_x; + mandelbrot(cx, cy, &px, &py); + px = (px - parameters.x1) / scale_x * preview.scale + selection.x1; + py = (py - parameters.y1) / scale_y * preview.scale + selection.y1; + if (0 <= px && px < image.width && 0 <= py && py < image.height) + { + pixels_get_biliner (px, py, &pixel); + } + else + { + switch (parameters.outside_type) + { + case OUTSIDE_TYPE_WRAP: + px = fmod (px, image.width); + py = fmod (py, image.height); + if (px < 0.0) px += image.width; + if (py < 0.0) py += image.height; + pixels_get_biliner (px, py, &pixel); + break; + case OUTSIDE_TYPE_TRANSPARENT: + pixel.r = pixel.g = pixel.b = 0; + pixel.a = 0; + break; + case OUTSIDE_TYPE_BLACK: + pixel.r = pixel.g = pixel.b = 0; + pixel.a = 255; + break; + case OUTSIDE_TYPE_WHITE: + pixel.r = pixel.g = pixel.b = 255; + pixel.a = 255; + break; + } + } + dialog_preview_setpixel (x, y, &pixel); + } + } + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview.preview), + 0, 0, preview.width, preview.height, + GIMP_RGBA_IMAGE, + preview.pixels, + preview.width *4); +} + +/******************************************************************************/ + +static void +dialog_int_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gimp_int_adjustment_update (adjustment, data); + + dialog_preview_draw (); +} + +static void +dialog_double_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gimp_double_adjustment_update (adjustment, data); + + dialog_preview_draw (); +} + +static void +dialog_outside_type_callback (GtkWidget *widget, + gpointer *data) +{ + gimp_radio_button_update (widget, data); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + dialog_preview_draw (); +} + +/******************************************************************************/ + +static gboolean +dialog_show (void) +{ + GtkWidget *dialog; + GtkWidget *mainbox; + GtkWidget *hbox; + GtkWidget *table; + GtkWidget *frame; + GtkWidget *abox; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Fractal Trace"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + mainbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (mainbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + mainbox, TRUE, TRUE, 0); + gtk_widget_show (mainbox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (mainbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* Preview */ + abox = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), abox, FALSE, FALSE, 0); + gtk_widget_show (abox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (abox), frame); + gtk_widget_show (frame); + + dialog_preview_init (); + gtk_container_add (GTK_CONTAINER (frame), preview.preview); + gtk_widget_show (preview.preview); + + /* Settings */ + frame = gimp_int_radio_group_new (TRUE, _("Outside Type"), + G_CALLBACK (dialog_outside_type_callback), + ¶meters.outside_type, + parameters.outside_type, + + _("_Wrap"), + OUTSIDE_TYPE_WRAP, NULL, + _("_Transparent"), + OUTSIDE_TYPE_TRANSPARENT, NULL, + _("_Black"), + OUTSIDE_TYPE_BLACK, NULL, + _("_White"), + OUTSIDE_TYPE_WHITE, NULL, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + frame = gimp_frame_new (_("Mandelbrot Parameters")); + gtk_box_pack_start (GTK_BOX (mainbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (5, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("X_1:"), 0, 6, + parameters.x1, -50, 50, 0.1, 0.5, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialog_double_adjustment_update), + ¶meters.x1); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("X_2:"), 0, 6, + parameters.x2, -50, 50, 0.1, 0.5, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialog_double_adjustment_update), + ¶meters.x2); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("Y_1:"), 0, 6, + parameters.y1, -50, 50, 0.1, 0.5, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialog_double_adjustment_update), + ¶meters.y1); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 3, + _("Y_2:"), 0, 6, + parameters.y2, -50, 50, 0.1, 0.5, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialog_double_adjustment_update), + ¶meters.y2); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 4, + _("_Depth:"), 0, 6, + parameters.depth, 1, 50, 1, 5, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (dialog_int_adjustment_update), + ¶meters.depth); + + gtk_widget_show (dialog); + dialog_preview_draw (); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/gimprc.common b/plug-ins/common/gimprc.common new file mode 100644 index 0000000..96d04b6 --- /dev/null +++ b/plug-ins/common/gimprc.common @@ -0,0 +1,89 @@ +align_layers_RC = align-layers.rc.o +animation_optimize_RC = animation-optimize.rc.o +animation_play_RC = animation-play.rc.o +blinds_RC = blinds.rc.o +blur_RC = blur.rc.o +border_average_RC = border-average.rc.o +busy_dialog_RC = busy-dialog.rc.o +cartoon_RC = cartoon.rc.o +checkerboard_RC = checkerboard.rc.o +cml_explorer_RC = cml-explorer.rc.o +color_cube_analyze_RC = color-cube-analyze.rc.o +color_enhance_RC = color-enhance.rc.o +colorify_RC = colorify.rc.o +colormap_remap_RC = colormap-remap.rc.o +compose_RC = compose.rc.o +contrast_retinex_RC = contrast-retinex.rc.o +crop_zealous_RC = crop-zealous.rc.o +curve_bend_RC = curve-bend.rc.o +decompose_RC = decompose.rc.o +depth_merge_RC = depth-merge.rc.o +despeckle_RC = despeckle.rc.o +destripe_RC = destripe.rc.o +edge_dog_RC = edge-dog.rc.o +emboss_RC = emboss.rc.o +file_aa_RC = file-aa.rc.o +file_cel_RC = file-cel.rc.o +file_compressor_RC = file-compressor.rc.o +file_csource_RC = file-csource.rc.o +file_desktop_link_RC = file-desktop-link.rc.o +file_dicom_RC = file-dicom.rc.o +file_gbr_RC = file-gbr.rc.o +file_gegl_RC = file-gegl.rc.o +file_gif_load_RC = file-gif-load.rc.o +file_gif_save_RC = file-gif-save.rc.o +file_gih_RC = file-gih.rc.o +file_glob_RC = file-glob.rc.o +file_header_RC = file-header.rc.o +file_heif_RC = file-heif.rc.o +file_html_table_RC = file-html-table.rc.o +file_jp2_load_RC = file-jp2-load.rc.o +file_mng_RC = file-mng.rc.o +file_pat_RC = file-pat.rc.o +file_pcx_RC = file-pcx.rc.o +file_pdf_load_RC = file-pdf-load.rc.o +file_pdf_save_RC = file-pdf-save.rc.o +file_pix_RC = file-pix.rc.o +file_png_RC = file-png.rc.o +file_pnm_RC = file-pnm.rc.o +file_ps_RC = file-ps.rc.o +file_psp_RC = file-psp.rc.o +file_raw_data_RC = file-raw-data.rc.o +file_sunras_RC = file-sunras.rc.o +file_svg_RC = file-svg.rc.o +file_tga_RC = file-tga.rc.o +file_wmf_RC = file-wmf.rc.o +file_xbm_RC = file-xbm.rc.o +file_xmc_RC = file-xmc.rc.o +file_xpm_RC = file-xpm.rc.o +file_xwd_RC = file-xwd.rc.o +film_RC = film.rc.o +filter_pack_RC = filter-pack.rc.o +fractal_trace_RC = fractal-trace.rc.o +goat_exercise_RC = goat-exercise.rc.o +gradient_map_RC = gradient-map.rc.o +grid_RC = grid.rc.o +guillotine_RC = guillotine.rc.o +hot_RC = hot.rc.o +jigsaw_RC = jigsaw.rc.o +mail_RC = mail.rc.o +max_rgb_RC = max-rgb.rc.o +nl_filter_RC = nl-filter.rc.o +photocopy_RC = photocopy.rc.o +plugin_browser_RC = plugin-browser.rc.o +procedure_browser_RC = procedure-browser.rc.o +qbist_RC = qbist.rc.o +sample_colorize_RC = sample-colorize.rc.o +sharpen_RC = sharpen.rc.o +smooth_palette_RC = smooth-palette.rc.o +softglow_RC = softglow.rc.o +sparkle_RC = sparkle.rc.o +sphere_designer_RC = sphere-designer.rc.o +tile_RC = tile.rc.o +tile_small_RC = tile-small.rc.o +unit_editor_RC = unit-editor.rc.o +van_gogh_lic_RC = van-gogh-lic.rc.o +warp_RC = warp.rc.o +wavelet_decompose_RC = wavelet-decompose.rc.o +web_browser_RC = web-browser.rc.o +web_page_RC = web-page.rc.o diff --git a/plug-ins/common/goat-exercise.c b/plug-ins/common/goat-exercise.c new file mode 100644 index 0000000..b01d938 --- /dev/null +++ b/plug-ins/common/goat-exercise.c @@ -0,0 +1,119 @@ +/* + * Goat exercise plug-in by Øyvind KolÃ¥s, pippin@gimp.org + */ + +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-goat-exercise" + + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Exercise a goat"), + "takes a goat for a walk", + "Øyvind KolÃ¥s ", + "Øyvind KolÃ¥s ", + "21march 2012", + N_("Goat-e_xercise"), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 drawable_id; + gint x, y, width, height; + + INIT_I18N(); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + drawable_id = param[2].data.d_drawable; + + if (gimp_drawable_mask_intersect (drawable_id, &x, &y, &width, &height)) + { + GeglBuffer *buffer; + GeglBuffer *shadow_buffer; + + buffer = gimp_drawable_get_buffer (drawable_id); + shadow_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + gegl_render_op (buffer, shadow_buffer, "gegl:invert", NULL); + + g_object_unref (shadow_buffer); /* flushes the shadow tiles */ + g_object_unref (buffer); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, x, y, width, height); + gimp_displays_flush (); + } + + values[0].data.d_status = status; + gegl_exit (); +} diff --git a/plug-ins/common/gradient-map.c b/plug-ins/common/gradient-map.c new file mode 100644 index 0000000..94c3f00 --- /dev/null +++ b/plug-ins/common/gradient-map.c @@ -0,0 +1,412 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Gradient Map plug-in + * Copyright (C) 1997 Eiichi Takamori + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include + +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Some useful macros */ +#define GRADMAP_PROC "plug-in-gradmap" +#define PALETTEMAP_PROC "plug-in-palettemap" +#define PLUG_IN_BINARY "gradient-map" +#define PLUG_IN_ROLE "gimp-gradient-map" +#define NSAMPLES 2048 + +typedef enum + { + GRADIENT_MODE = 1, + PALETTE_MODE + } MapMode; + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static void map (GeglBuffer *buffer, + GeglBuffer *shadow_buffer, + gint32 drawable_id, + MapMode mode); +static gdouble * get_samples_gradient (gint32 drawable_id); +static gdouble * get_samples_palette (gint32 drawable_id); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[]= + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (GRADMAP_PROC, + N_("Recolor the image using colors from the active gradient"), + "This plug-in maps the contents of the specified " + "drawable with active gradient. It calculates " + "luminosity of each pixel and replaces the pixel " + "by the sample of active gradient at the position " + "proportional to that luminosity. Complete black " + "pixel becomes the leftmost color of the gradient, " + "and complete white becomes the rightmost. Works on " + "both Grayscale and RGB image with/without alpha " + "channel.", + "Eiichi Takamori", + "Eiichi Takamori", + "1997", + N_("_Gradient Map"), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (GRADMAP_PROC, "/Colors/Map"); + + gimp_install_procedure (PALETTEMAP_PROC, + N_("Recolor the image using colors from the active palette"), + "This plug-in maps the contents of the specified " + "drawable with the active palette. It calculates " + "luminosity of each pixel and replaces the pixel " + "by the palette sample at the corresponding " + "index. A complete black " + "pixel becomes the lowest palette entry, " + "and complete white becomes the highest. Works on " + "both Grayscale and RGB image with/without alpha " + "channel.", + "Bill Skaggs", + "Bill Skaggs", + "2004", + N_("_Palette Map"), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PALETTEMAP_PROC, "/Colors/Map"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpRunMode run_mode; + gint32 drawable_id; + GeglBuffer *shadow_buffer; + GeglBuffer *buffer; + + run_mode = param[0].data.d_int32; + drawable_id = param[2].data.d_drawable; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + /* Get the specified drawable */ + shadow_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + buffer = gimp_drawable_get_buffer (drawable_id); + + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_is_rgb (drawable_id) || + gimp_drawable_is_gray (drawable_id)) + { + MapMode mode = 0; + + if ( !strcmp (name, GRADMAP_PROC)) + { + mode = GRADIENT_MODE; + gimp_progress_init (_("Gradient Map")); + } + else if ( !strcmp (name, PALETTEMAP_PROC)) + { + mode = PALETTE_MODE; + gimp_progress_init (_("Palette Map")); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (mode) + map (buffer, shadow_buffer, drawable_id, mode); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + g_object_unref (buffer); + g_object_unref (shadow_buffer); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + + gimp_drawable_update (drawable_id, 0, 0, + gimp_drawable_width (drawable_id), + gimp_drawable_height (drawable_id)); + + values[0].data.d_status = status; + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); +} + +static void +map (GeglBuffer *buffer, + GeglBuffer *shadow_buffer, + gint32 drawable_id, + MapMode mode) +{ + GeglBufferIterator *gi; + gint nb_color_chan; + gint nb_chan; + gint nb_chan2; + gint nb_chan_samp; + gint index_iter; + gboolean interpolate; + gdouble *samples; + gboolean is_rgb; + gboolean has_alpha; + const Babl *format_shadow; + const Babl *format_buffer; + + is_rgb = gimp_drawable_is_rgb (drawable_id); + has_alpha = gimp_drawable_has_alpha (drawable_id); + + switch (mode) + { + case GRADIENT_MODE: + samples = get_samples_gradient (drawable_id); + interpolate = TRUE; + break; + case PALETTE_MODE: + samples = get_samples_palette (drawable_id); + interpolate = FALSE; + break; + default: + g_error ("plug_in_gradmap: invalid mode"); + } + + if (is_rgb) + { + nb_color_chan = 3; + nb_chan_samp = 4; + if (has_alpha) + format_shadow = babl_format ("R'G'B'A float"); + else + format_shadow = babl_format ("R'G'B' float"); + } + else + { + nb_color_chan = 1; + nb_chan_samp = 2; + if (has_alpha) + format_shadow = babl_format ("Y'A float"); + else + format_shadow = babl_format ("Y' float"); + } + + + if (has_alpha) + { + nb_chan = nb_color_chan + 1; + nb_chan2 = 2; + format_buffer = babl_format ("Y'A float"); + } + else + { + nb_chan = nb_color_chan; + nb_chan2 = 1; + format_buffer = babl_format ("Y' float"); + } + + gi = gegl_buffer_iterator_new (shadow_buffer, NULL, 0, format_shadow, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 2); + + index_iter = gegl_buffer_iterator_add (gi, buffer, NULL, + 0, format_buffer, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE); + + while (gegl_buffer_iterator_next (gi)) + { + guint k; + gfloat *data; + gfloat *data2; + + data = (gfloat*) gi->items[0].data; + data2 = (gfloat*) gi->items[index_iter].data; + + if (interpolate) + { + for (k = 0; k < gi->length; k++) + { + gint b, ind1, ind2; + gdouble *samp1, *samp2; + gfloat c1, c2, val; + + val = data2[0] * (NSAMPLES-1); + + ind1 = CLAMP (floor (val), 0, NSAMPLES-1); + ind2 = CLAMP (ceil (val), 0, NSAMPLES-1); + + c1 = 1.0 - (val - ind1); + c2 = 1.0 - c1; + + samp1 = &(samples[ind1 * nb_chan_samp]); + samp2 = &(samples[ind2 * nb_chan_samp]); + + for (b = 0; b < nb_color_chan; b++) + data[b] = (samp1[b] * c1 + samp2[b] * c2); + + if (has_alpha) + { + float alpha = (samp1[b] * c1 + samp2[b] * c2); + data[b] = alpha * data2[1]; + } + + data += nb_chan; + data2 += nb_chan2; + } + } + else + { + for (k = 0; k < gi->length; k++) + { + gint b, ind; + gdouble *samp; + ind = CLAMP (data2[0] * (NSAMPLES-1), 0, NSAMPLES-1); + + samp = &(samples[ind * nb_chan_samp]); + + for (b = 0; b < nb_color_chan; b++) + data[b] = samp[b]; + + if (has_alpha) + { + data[b] = samp[b] * data2[1]; + } + + data += nb_chan; + data2 += nb_chan2; + } + } + } + + g_free (samples); +} + +/* + Returns 2048 samples of the gradient. + Each sample is (R'G'B'A float) or (Y'A float), depending on the drawable + */ +static gdouble * +get_samples_gradient (gint32 drawable_id) +{ + gchar *gradient_name; + gint n_d_samples; + gdouble *d_samples = NULL; + + gradient_name = gimp_context_get_gradient (); + + /* FIXME: "reverse" hardcoded to FALSE. */ + gimp_gradient_get_uniform_samples (gradient_name, NSAMPLES, FALSE, + &n_d_samples, &d_samples); + g_free (gradient_name); + + if (!gimp_drawable_is_rgb (drawable_id)) + { + const Babl *format_src = babl_format ("R'G'B'A double"); + const Babl *format_dst = babl_format ("Y'A double"); + const Babl *fish = babl_fish (format_src, format_dst); + babl_process (fish, d_samples, d_samples, NSAMPLES); + } + + return d_samples; +} + +/* + Returns 2048 samples of the palette. + Each sample is (R'G'B'A float) or (Y'A float), depending on the drawable + */ +static gdouble * +get_samples_palette (gint32 drawable_id) +{ + gchar *palette_name; + GimpRGB color_sample; + gdouble *d_samples, *d_samp; + gboolean is_rgb; + gdouble factor; + gint pal_entry, num_colors; + gint nb_color_chan, nb_chan, i; + const Babl *format; + + palette_name = gimp_context_get_palette (); + gimp_palette_get_info (palette_name, &num_colors); + + is_rgb = gimp_drawable_is_rgb (drawable_id); + + factor = ((double) num_colors) / NSAMPLES; + format = is_rgb ? babl_format ("R'G'B'A double") : babl_format ("Y'A double"); + nb_color_chan = is_rgb ? 3 : 1; + nb_chan = nb_color_chan + 1; + + d_samples = g_new (gdouble, NSAMPLES * nb_chan); + + for (i = 0; i < NSAMPLES; i++) + { + d_samp = &d_samples[i * nb_chan]; + pal_entry = CLAMP ((int)(i * factor), 0, num_colors - 1); + + gimp_palette_entry_get_color (palette_name, pal_entry, &color_sample); + gimp_rgb_get_pixel (&color_sample, + format, + d_samp); + } + + g_free (palette_name); + return d_samples; +} diff --git a/plug-ins/common/grid.c b/plug-ins/common/grid.c new file mode 100644 index 0000000..7abdf2f --- /dev/null +++ b/plug-ins/common/grid.c @@ -0,0 +1,1011 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Original plug-in coded by Tim Newsome. + * + * Changed to make use of real-life units by Sven Neumann . + * + * The interface code is heavily commented in the hope that it will + * help other plug-in developers to adapt their plug-ins to make use + * of the gimp_size_entry functionality. + * + * Note: There is a convenience constructor called gimp_coordinetes_new () + * which simplifies the task of setting up a standard X,Y sizeentry. + * + * For more info and bugs see libgimp/gimpsizeentry.h and libgimp/gimpwidgets.h + * + * May 2000 tim copperfield [timecop@japan.co.jp] + * http://www.ne.jp/asahi/linux/timecop + * Added dynamic preview. Due to weird implementation of signals from all + * controls, preview will not auto-update. But this plugin isn't really + * crying for real-time updating either. + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-grid" +#define PLUG_IN_BINARY "grid" +#define PLUG_IN_ROLE "gimp-grid" +#define SPIN_BUTTON_WIDTH 8 +#define COLOR_BUTTON_WIDTH 55 + + +/* Declare local functions. */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static guchar best_cmap_match (const guchar *cmap, + gint ncolors, + const GimpRGB *color); +static void grid (gint32 image_ID, + gint32 drawable_ID, + GimpPreview *preview); +static gint dialog (gint32 image_ID, + gint32 drawable_ID); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static gint sx1, sy1, sx2, sy2; + +static GtkWidget *main_dialog = NULL; +static GtkWidget *hcolor_button = NULL; +static GtkWidget *vcolor_button = NULL; + +typedef struct +{ + gint hwidth; + gint hspace; + gint hoffset; + GimpRGB hcolor; + gint vwidth; + gint vspace; + gint voffset; + GimpRGB vcolor; + gint iwidth; + gint ispace; + gint ioffset; + GimpRGB icolor; +} Config; + +static Config grid_cfg = +{ + 1, 16, 8, { 0.0, 0.0, 0.0, 1.0 }, /* horizontal */ + 1, 16, 8, { 0.0, 0.0, 0.0, 1.0 }, /* vertical */ + 0, 2, 6, { 0.0, 0.0, 0.0, 1.0 }, /* intersection */ +}; + + +MAIN () + +static +void query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + + { GIMP_PDB_INT32, "hwidth", "Horizontal Width (>= 0)" }, + { GIMP_PDB_INT32, "hspace", "Horizontal Spacing (>= 1)" }, + { GIMP_PDB_INT32, "hoffset", "Horizontal Offset (>= 0)" }, + { GIMP_PDB_COLOR, "hcolor", "Horizontal Colour" }, + { GIMP_PDB_INT8, "hopacity", "Horizontal Opacity (0...255)" }, + + { GIMP_PDB_INT32, "vwidth", "Vertical Width (>= 0)" }, + { GIMP_PDB_INT32, "vspace", "Vertical Spacing (>= 1)" }, + { GIMP_PDB_INT32, "voffset", "Vertical Offset (>= 0)" }, + { GIMP_PDB_COLOR, "vcolor", "Vertical Colour" }, + { GIMP_PDB_INT8, "vopacity", "Vertical Opacity (0...255)" }, + + { GIMP_PDB_INT32, "iwidth", "Intersection Width (>= 0)" }, + { GIMP_PDB_INT32, "ispace", "Intersection Spacing (>= 0)" }, + { GIMP_PDB_INT32, "ioffset", "Intersection Offset (>= 0)" }, + { GIMP_PDB_COLOR, "icolor", "Intersection Colour" }, + { GIMP_PDB_INT8, "iopacity", "Intersection Opacity (0...255)" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Draw a grid on the image"), + "Draws a grid using the specified colors. " + "The grid origin is the upper left corner.", + "Tim Newsome", + "Tim Newsome, Sven Neumann, Tom Rathborne, TC", + "1997 - 2000", + N_("_Grid (legacy)..."), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Render/Pattern"); +} + +static void +run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + gint32 image_ID; + gint32 drawable_ID; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + run_mode = param[0].data.d_int32; + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + if (run_mode == GIMP_RUN_NONINTERACTIVE) + { + if (n_params != 18) + status = GIMP_PDB_CALLING_ERROR; + + if (status == GIMP_PDB_SUCCESS) + { + grid_cfg.hwidth = MAX (0, param[3].data.d_int32); + grid_cfg.hspace = MAX (1, param[4].data.d_int32); + grid_cfg.hoffset = MAX (0, param[5].data.d_int32); + grid_cfg.hcolor = param[6].data.d_color; + + gimp_rgb_set_alpha (&(grid_cfg.hcolor), + ((double) param[7].data.d_int8) / 255.0); + + + grid_cfg.vwidth = MAX (0, param[8].data.d_int32); + grid_cfg.vspace = MAX (1, param[9].data.d_int32); + grid_cfg.voffset = MAX (0, param[10].data.d_int32); + grid_cfg.vcolor = param[11].data.d_color; + + gimp_rgb_set_alpha (&(grid_cfg.vcolor), + ((double) param[12].data.d_int8) / 255.0); + + + + grid_cfg.iwidth = MAX (0, param[13].data.d_int32); + grid_cfg.ispace = MAX (0, param[14].data.d_int32); + grid_cfg.ioffset = MAX (0, param[15].data.d_int32); + grid_cfg.icolor = param[16].data.d_color; + + gimp_rgb_set_alpha (&(grid_cfg.icolor), + ((double) (guint) param[17].data.d_int8) / 255.0); + + + } + } + else + { + gimp_context_get_foreground (&grid_cfg.hcolor); + grid_cfg.vcolor = grid_cfg.icolor = grid_cfg.hcolor; + + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &grid_cfg); + } + + if (run_mode == GIMP_RUN_INTERACTIVE) + { + if (! dialog (image_ID, drawable_ID)) + { + /* The dialog was closed, or something similarly evil happened. */ + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (grid_cfg.hspace <= 0 || grid_cfg.vspace <= 0) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + if (status == GIMP_PDB_SUCCESS) + { + gimp_progress_init (_("Drawing grid")); + + grid (image_ID, drawable_ID, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &grid_cfg, sizeof (grid_cfg)); + } + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; +} + + +#define MAXDIFF 195076 + +static guchar +best_cmap_match (const guchar *cmap, + gint ncolors, + const GimpRGB *color) +{ + guchar cmap_index = 0; + gint max = MAXDIFF; + gint i, diff, sum; + guchar r, g, b; + + gimp_rgb_get_uchar (color, &r, &g, &b); + + for (i = 0; i < ncolors; i++) + { + diff = r - *cmap++; + sum = SQR (diff); + diff = g - *cmap++; + sum += SQR (diff); + diff = b - *cmap++; + sum += SQR (diff); + + if (sum < max) + { + cmap_index = i; + max = sum; + } + } + + return cmap_index; +} + +static inline void +pix_composite (guchar *p1, + guchar p2[4], + gint bytes, + gboolean blend, + gboolean alpha) +{ + gint b; + + if (blend) + { + if (alpha) + bytes--; + + for (b = 0; b < bytes; b++) + { + *p1 = *p1 * (1.0 - p2[3]/255.0) + p2[b] * p2[3]/255.0; + p1++; + } + } + else + { + /* blend should only be TRUE for indexed (bytes == 1) */ + *p1++ = *p2; + } + + if (alpha && *p1 < 255) + { + b = *p1 + 255.0 * ((gdouble) p2[3] / (255.0 - *p1)); + + *p1 = b > 255 ? 255 : b; + } +} + +static void +grid (gint32 image_ID, + gint32 drawable_ID, + GimpPreview *preview) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + gint bytes; + gint x_offset; + gint y_offset; + guchar *dest; + guchar *buffer = NULL; + gint x, y; + gboolean alpha; + gboolean blend; + guchar hcolor[4]; + guchar vcolor[4]; + guchar icolor[4]; + guchar *cmap; + gint ncolors; + + gimp_rgba_get_uchar (&grid_cfg.hcolor, + hcolor, hcolor + 1, hcolor + 2, hcolor + 3); + gimp_rgba_get_uchar (&grid_cfg.vcolor, + vcolor, vcolor + 1, vcolor + 2, vcolor + 3); + gimp_rgba_get_uchar (&grid_cfg.icolor, + icolor, icolor + 1, icolor + 2, icolor + 3); + + alpha = gimp_drawable_has_alpha (drawable_ID); + + switch (gimp_image_base_type (image_ID)) + { + case GIMP_RGB: + blend = TRUE; + + if (alpha) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + break; + + case GIMP_GRAY: + hcolor[0] = gimp_rgb_luminance_uchar (&grid_cfg.hcolor); + vcolor[0] = gimp_rgb_luminance_uchar (&grid_cfg.vcolor); + icolor[0] = gimp_rgb_luminance_uchar (&grid_cfg.icolor); + blend = TRUE; + + if (alpha) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + break; + + case GIMP_INDEXED: + cmap = gimp_image_get_colormap (image_ID, &ncolors); + + hcolor[0] = best_cmap_match (cmap, ncolors, &grid_cfg.hcolor); + vcolor[0] = best_cmap_match (cmap, ncolors, &grid_cfg.vcolor); + icolor[0] = best_cmap_match (cmap, ncolors, &grid_cfg.icolor); + + g_free (cmap); + blend = FALSE; + + format = gimp_drawable_get_format (drawable_ID); + break; + + default: + g_assert_not_reached (); + blend = FALSE; + } + + bytes = babl_format_get_bytes_per_pixel (format); + + if (preview) + { + gimp_preview_get_position (preview, &sx1, &sy1); + gimp_preview_get_size (preview, &sx2, &sy2); + + buffer = g_new (guchar, bytes * sx2 * sy2); + + sx2 += sx1; + sy2 += sy1; + } + else + { + gint w, h; + + if (! gimp_drawable_mask_intersect (drawable_ID, + &sx1, &sy1, &w, &h)) + return; + + sx2 = sx1 + w; + sy2 = sy1 + h; + + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + } + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + + dest = g_new (guchar, (sx2 - sx1) * bytes); + + for (y = sy1; y < sy2; y++) + { + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (sx1, y, sx2 - sx1, 1), 1.0, + format, dest, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + y_offset = y - grid_cfg.hoffset; + while (y_offset < 0) + y_offset += grid_cfg.hspace; + + if ((y_offset + + (grid_cfg.hwidth / 2)) % grid_cfg.hspace < grid_cfg.hwidth) + { + for (x = sx1; x < sx2; x++) + { + pix_composite (&dest[(x-sx1) * bytes], + hcolor, bytes, blend, alpha); + } + } + + for (x = sx1; x < sx2; x++) + { + x_offset = grid_cfg.vspace + x - grid_cfg.voffset; + while (x_offset < 0) + x_offset += grid_cfg.vspace; + + if ((x_offset + + (grid_cfg.vwidth / 2)) % grid_cfg.vspace < grid_cfg.vwidth) + { + pix_composite (&dest[(x-sx1) * bytes], + vcolor, bytes, blend, alpha); + } + + if ((x_offset + + (grid_cfg.iwidth / 2)) % grid_cfg.vspace < grid_cfg.iwidth + && + ((y_offset % grid_cfg.hspace >= grid_cfg.ispace + && + y_offset % grid_cfg.hspace < grid_cfg.ioffset) + || + (grid_cfg.hspace - + (y_offset % grid_cfg.hspace) >= grid_cfg.ispace + && + grid_cfg.hspace - + (y_offset % grid_cfg.hspace) < grid_cfg.ioffset))) + { + pix_composite (&dest[(x-sx1) * bytes], + icolor, bytes, blend, alpha); + } + } + + if ((y_offset + + (grid_cfg.iwidth / 2)) % grid_cfg.hspace < grid_cfg.iwidth) + { + for (x = sx1; x < sx2; x++) + { + x_offset = grid_cfg.vspace + x - grid_cfg.voffset; + while (x_offset < 0) + x_offset += grid_cfg.vspace; + + if ((x_offset % grid_cfg.vspace >= grid_cfg.ispace + && + x_offset % grid_cfg.vspace < grid_cfg.ioffset) + || + (grid_cfg.vspace - + (x_offset % grid_cfg.vspace) >= grid_cfg.ispace + && + grid_cfg.vspace - + (x_offset % grid_cfg.vspace) < grid_cfg.ioffset)) + { + pix_composite (&dest[(x-sx1) * bytes], + icolor, bytes, blend, alpha); + } + } + } + + if (preview) + { + memcpy (buffer + (y - sy1) * (sx2 - sx1) * bytes, + dest, + (sx2 - sx1) * bytes); + } + else + { + gegl_buffer_set (dest_buffer, + GEGL_RECTANGLE (sx1, y, sx2 - sx1, 1), 0, + format, dest, + GEGL_AUTO_ROWSTRIDE); + + if (y % 16 == 0) + gimp_progress_update ((gdouble) y / (gdouble) (sy2 - sy1)); + } + } + + g_free (dest); + + g_object_unref (src_buffer); + + if (preview) + { + gimp_preview_draw_buffer (preview, buffer, bytes * (sx2 - sx1)); + g_free (buffer); + } + else + { + gimp_progress_update (1.0); + + g_object_unref (dest_buffer); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, + sx1, sy1, sx2 - sx1, sy2 - sy1); + } +} + + +/*************************************************** + * GUI stuff + */ + + +static void +update_values (void) +{ + GtkWidget *entry; + + entry = g_object_get_data (G_OBJECT (main_dialog), "width"); + + grid_cfg.hwidth = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 0)); + grid_cfg.vwidth = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 1)); + grid_cfg.iwidth = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 2)); + + entry = g_object_get_data (G_OBJECT (main_dialog), "space"); + + grid_cfg.hspace = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 0)); + grid_cfg.vspace = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 1)); + grid_cfg.ispace = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 2)); + + entry = g_object_get_data (G_OBJECT (main_dialog), "offset"); + + grid_cfg.hoffset = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 0)); + grid_cfg.voffset = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 1)); + grid_cfg.ioffset = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 2)); +} + +static void +update_preview (GimpPreview *preview, + gpointer drawable_ID) +{ + update_values (); + + grid (gimp_item_get_image (GPOINTER_TO_INT (drawable_ID)), + GPOINTER_TO_INT (drawable_ID), + preview); +} + +static void +entry_callback (GtkWidget *widget, + gpointer data) +{ + static gdouble x = -1.0; + static gdouble y = -1.0; + gdouble new_x; + gdouble new_y; + + new_x = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0); + new_y = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1); + + if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (data))) + { + if (new_x != x) + { + y = new_y = x = new_x; + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 1, y); + } + if (new_y != y) + { + x = new_x = y = new_y; + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 0, x); + } + } + else + { + x = new_x; + y = new_y; + } +} + +static void +color_callback (GtkWidget *widget, + gpointer data) +{ + if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (data))) + { + GimpRGB color; + + gimp_color_button_get_color (GIMP_COLOR_BUTTON (widget), &color); + + if (widget == vcolor_button) + gimp_color_button_set_color (GIMP_COLOR_BUTTON (hcolor_button), &color); + else if (widget == hcolor_button) + gimp_color_button_set_color (GIMP_COLOR_BUTTON (vcolor_button), &color); + } +} + + +static gint +dialog (gint32 image_ID, + gint32 drawable_ID) +{ + GimpColorConfig *config; + GtkWidget *dlg; + GtkWidget *main_vbox; + GtkWidget *vbox; + GtkSizeGroup *group; + GtkWidget *label; + GtkWidget *preview; + GtkWidget *button; + GtkWidget *width; + GtkWidget *space; + GtkWidget *offset; + GtkWidget *chain_button; + GtkWidget *table; + GimpUnit unit; + gint d_width; + gint d_height; + gdouble xres; + gdouble yres; + gboolean run; + + g_return_val_if_fail (main_dialog == NULL, FALSE); + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + d_width = gimp_drawable_width (drawable_ID); + d_height = gimp_drawable_height (drawable_ID); + + main_dialog = dlg = gimp_dialog_new (_("Grid"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + /* Get the image resolution and unit */ + gimp_image_get_resolution (image_ID, &xres, &yres); + unit = gimp_image_get_unit (image_ID); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable_ID); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect (preview, "invalidated", + G_CALLBACK (update_preview), + GINT_TO_POINTER (drawable_ID)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (main_vbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + /* The width entries */ + width = gimp_size_entry_new (3, /* number_of_fields */ + unit, /* unit */ + "%a", /* unit_format */ + TRUE, /* menu_show_pixels */ + TRUE, /* menu_show_percent */ + FALSE, /* show_refval */ + SPIN_BUTTON_WIDTH, /* spinbutton_usize */ + GIMP_SIZE_ENTRY_UPDATE_SIZE); /* update_policy */ + + + gtk_box_pack_start (GTK_BOX (vbox), width, FALSE, FALSE, 0); + gtk_widget_show (width); + + /* set the unit back to pixels, since most times we will want pixels */ + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (width), GIMP_UNIT_PIXEL); + + /* set the resolution to the image resolution */ + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (width), 0, xres, TRUE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (width), 1, yres, TRUE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (width), 2, xres, TRUE); + + /* set the size (in pixels) that will be treated as 0% and 100% */ + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (width), 0, 0.0, d_height); + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (width), 1, 0.0, d_width); + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (width), 2, 0.0, d_width); + + /* set upper and lower limits (in pixels) */ + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (width), 0, 0.0, + d_height); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (width), 1, 0.0, + d_width); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (width), 2, 0.0, + MAX (d_width, d_height)); + gtk_table_set_row_spacing (GTK_TABLE (width), 0, 6); + gtk_table_set_col_spacings (GTK_TABLE (width), 6); + gtk_table_set_col_spacing (GTK_TABLE (width), 2, 12); + + /* initialize the values */ + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (width), 0, grid_cfg.hwidth); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (width), 1, grid_cfg.vwidth); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (width), 2, grid_cfg.iwidth); + + /* attach labels */ + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Horizontal\nLines"), + 0, 1, 0.0); + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Vertical\nLines"), + 0, 2, 0.0); + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Intersection"), + 0, 3, 0.0); + + label = gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Width:"), + 1, 0, 0.0); + + group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (group, label); + g_object_unref (group); + + /* put a chain_button under the size_entries */ + chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM); + if (grid_cfg.hwidth == grid_cfg.vwidth) + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE); + gtk_table_attach_defaults (GTK_TABLE (width), chain_button, 1, 3, 2, 3); + gtk_widget_show (chain_button); + + /* connect to the 'value-changed' signal because we have to take care + * of keeping the entries in sync when the chainbutton is active + */ + g_signal_connect (width, "value-changed", + G_CALLBACK (entry_callback), + chain_button); + g_signal_connect_swapped (width, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* The spacing entries */ + space = gimp_size_entry_new (3, /* number_of_fields */ + unit, /* unit */ + "%a", /* unit_format */ + TRUE, /* menu_show_pixels */ + TRUE, /* menu_show_percent */ + FALSE, /* show_refval */ + SPIN_BUTTON_WIDTH, /* spinbutton_usize */ + GIMP_SIZE_ENTRY_UPDATE_SIZE); /* update_policy */ + + gtk_box_pack_start (GTK_BOX (vbox), space, FALSE, FALSE, 0); + gtk_widget_show (space); + + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (space), GIMP_UNIT_PIXEL); + + /* set the resolution to the image resolution */ + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (space), 0, xres, TRUE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (space), 1, yres, TRUE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (space), 2, xres, TRUE); + + /* set the size (in pixels) that will be treated as 0% and 100% */ + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (space), 0, 0.0, d_height); + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (space), 1, 0.0, d_width); + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (space), 2, 0.0, d_width); + + /* set upper and lower limits (in pixels) */ + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (space), 0, 1.0, + d_height); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (space), 1, 1.0, + d_width); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (space), 2, 0.0, + MAX (d_width, d_height)); + gtk_table_set_col_spacings (GTK_TABLE (space), 6); + gtk_table_set_col_spacing (GTK_TABLE (space), 2, 12); + + /* initialize the values */ + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (space), 0, grid_cfg.hspace); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (space), 1, grid_cfg.vspace); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (space), 2, grid_cfg.ispace); + + /* attach labels */ + label = gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (space), _("Spacing:"), + 1, 0, 0.0); + gtk_size_group_add_widget (group, label); + + /* put a chain_button under the spacing_entries */ + chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM); + if (grid_cfg.hspace == grid_cfg.vspace) + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE); + gtk_table_attach_defaults (GTK_TABLE (space), chain_button, 1, 3, 2, 3); + gtk_widget_show (chain_button); + + /* connect to the 'value-changed' and "unit-changed" signals because + * we have to take care of keeping the entries in sync when the + * chainbutton is active + */ + g_signal_connect (space, "value-changed", + G_CALLBACK (entry_callback), + chain_button); + g_signal_connect (space, "unit-changed", + G_CALLBACK (entry_callback), + chain_button); + g_signal_connect_swapped (space, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* The offset entries */ + offset = gimp_size_entry_new (3, /* number_of_fields */ + unit, /* unit */ + "%a", /* unit_format */ + TRUE, /* menu_show_pixels */ + TRUE, /* menu_show_percent */ + FALSE, /* show_refval */ + SPIN_BUTTON_WIDTH, /* spinbutton_usize */ + GIMP_SIZE_ENTRY_UPDATE_SIZE); /* update_policy */ + + gtk_box_pack_start (GTK_BOX (vbox), offset, FALSE, FALSE, 0); + gtk_widget_show (offset); + + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (offset), GIMP_UNIT_PIXEL); + + /* set the resolution to the image resolution */ + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (offset), 0, xres, TRUE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (offset), 1, yres, TRUE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (offset), 2, xres, TRUE); + + /* set the size (in pixels) that will be treated as 0% and 100% */ + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (offset), 0, 0.0, d_height); + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (offset), 1, 0.0, d_width); + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (offset), 2, 0.0, d_width); + + /* set upper and lower limits (in pixels) */ + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (offset), 0, 0.0, + d_height); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (offset), 1, 0.0, + d_width); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (offset), 2, 0.0, + MAX (d_width, d_height)); + gtk_table_set_col_spacings (GTK_TABLE (offset), 6); + gtk_table_set_col_spacing (GTK_TABLE (offset), 2, 12); + + /* initialize the values */ + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset), 0, grid_cfg.hoffset); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset), 1, grid_cfg.voffset); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset), 2, grid_cfg.ioffset); + + /* attach labels */ + label = gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (offset), _("Offset:"), + 1, 0, 0.0); + gtk_size_group_add_widget (group, label); + + /* this is a weird hack: we put a table into the offset table */ + table = gtk_table_new (3, 3, FALSE); + gtk_table_attach_defaults (GTK_TABLE (offset), table, 1, 4, 2, 3); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 10); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12); + + /* put a chain_button under the offset_entries */ + chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM); + if (grid_cfg.hoffset == grid_cfg.voffset) + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE); + gtk_table_attach_defaults (GTK_TABLE (table), chain_button, 0, 2, 0, 1); + gtk_widget_show (chain_button); + + /* connect to the 'value-changed' and "unit-changed" signals because + * we have to take care of keeping the entries in sync when the + * chainbutton is active + */ + g_signal_connect (offset, "value-changed", + G_CALLBACK (entry_callback), + chain_button); + g_signal_connect (offset, "unit-changed", + G_CALLBACK (entry_callback), + chain_button); + g_signal_connect_swapped (offset, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* put a chain_button under the color_buttons */ + chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM); + if (gimp_rgba_distance (&grid_cfg.hcolor, &grid_cfg.vcolor) < 0.0001) + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE); + gtk_table_attach_defaults (GTK_TABLE (table), chain_button, 0, 2, 2, 3); + gtk_widget_show (chain_button); + + /* attach color selectors */ + hcolor_button = gimp_color_button_new (_("Horizontal Color"), + COLOR_BUTTON_WIDTH, 16, + &grid_cfg.hcolor, + GIMP_COLOR_AREA_SMALL_CHECKS); + gimp_color_button_set_update (GIMP_COLOR_BUTTON (hcolor_button), TRUE); + gtk_table_attach_defaults (GTK_TABLE (table), hcolor_button, 0, 1, 1, 2); + gtk_widget_show (hcolor_button); + + config = gimp_get_color_configuration (); + gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (hcolor_button), + config); + + g_signal_connect (hcolor_button, "color-changed", + G_CALLBACK (gimp_color_button_get_color), + &grid_cfg.hcolor); + g_signal_connect (hcolor_button, "color-changed", + G_CALLBACK (color_callback), + chain_button); + g_signal_connect_swapped (hcolor_button, "color-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + vcolor_button = gimp_color_button_new (_("Vertical Color"), + COLOR_BUTTON_WIDTH, 16, + &grid_cfg.vcolor, + GIMP_COLOR_AREA_SMALL_CHECKS); + gimp_color_button_set_update (GIMP_COLOR_BUTTON (vcolor_button), TRUE); + gtk_table_attach_defaults (GTK_TABLE (table), vcolor_button, 1, 2, 1, 2); + gtk_widget_show (vcolor_button); + + gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (vcolor_button), + config); + + g_signal_connect (vcolor_button, "color-changed", + G_CALLBACK (gimp_color_button_get_color), + &grid_cfg.vcolor); + g_signal_connect (vcolor_button, "color-changed", + G_CALLBACK (color_callback), + chain_button); + g_signal_connect_swapped (vcolor_button, "color-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + button = gimp_color_button_new (_("Intersection Color"), + COLOR_BUTTON_WIDTH, 16, + &grid_cfg.icolor, + GIMP_COLOR_AREA_SMALL_CHECKS); + gimp_color_button_set_update (GIMP_COLOR_BUTTON (button), TRUE); + gtk_table_attach_defaults (GTK_TABLE (table), button, 2, 3, 1, 2); + gtk_widget_show (button); + + gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (button), + config); + g_object_unref (config); + + g_signal_connect (button, "color-changed", + G_CALLBACK (gimp_color_button_get_color), + &grid_cfg.icolor); + g_signal_connect_swapped (button, "color-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (table); + + gtk_widget_show (dlg); + + g_object_set_data (G_OBJECT (dlg), "width", width); + g_object_set_data (G_OBJECT (dlg), "space", space); + g_object_set_data (G_OBJECT (dlg), "offset", offset); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + if (run) + update_values (); + + gtk_widget_destroy (dlg); + + return run; +} + diff --git a/plug-ins/common/guillotine.c b/plug-ins/common/guillotine.c new file mode 100644 index 0000000..fd12599 --- /dev/null +++ b/plug-ins/common/guillotine.c @@ -0,0 +1,298 @@ +/* + * Guillotine plug-in v0.9 by Adam D. Moss, adam@foxbox.org. 1998/09/01 + */ + +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-guillotine" + + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static GList * guillotine (gint32 image_ID, + gboolean interactive); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" } + }; + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_INT32, "image-count", "Number of images created" }, + { GIMP_PDB_INT32ARRAY, "image-ids", "Output images" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Slice the image into subimages using guides"), + "This function takes an image and slices it along " + "its guides, creating new images. The original " + "image is not modified.", + "Adam D. Moss (adam@foxbox.org)", + "Adam D. Moss (adam@foxbox.org)", + "1998", + N_("Slice Using G_uides"), + "RGB*, INDEXED*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Image/Crop"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[3]; + GimpRunMode run_mode = param[0].data.d_int32; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + *nreturn_vals = 3; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + values[1].type = GIMP_PDB_INT32; + values[1].data.d_int32 = 0; + values[2].type = GIMP_PDB_INT32ARRAY; + values[2].data.d_int32array = NULL; + + INIT_I18N(); + + if (status == GIMP_PDB_SUCCESS) + { + GList *images; + GList *list; + gint i; + + gimp_progress_init (_("Guillotine")); + + images = guillotine (param[1].data.d_image, + run_mode == GIMP_RUN_INTERACTIVE); + + values[1].data.d_int32 = g_list_length (images); + values[2].data.d_int32array = g_new (gint32, values[1].data.d_int32); + + for (list = images, i = 0; list; list = g_list_next (list), i++) + { + values[2].data.d_int32array[i] = GPOINTER_TO_INT (list->data); + } + + g_list_free (images); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_displays_flush (); + } + + values[0].data.d_status = status; +} + + +static gint +guide_sort_func (gconstpointer a, + gconstpointer b) +{ + return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b); +} + +static GList * +guillotine (gint32 image_ID, + gboolean interactive) +{ + GList *images = NULL; + gint guide; + gint image_width; + gint image_height; + gboolean guides_found = FALSE; + GList *hguides, *hg; + GList *vguides, *vg; + + image_width = gimp_image_width (image_ID); + image_height = gimp_image_height (image_ID); + + hguides = g_list_append (NULL, GINT_TO_POINTER (0)); + hguides = g_list_append (hguides, GINT_TO_POINTER (image_height)); + + vguides = g_list_append (NULL, GINT_TO_POINTER (0)); + vguides = g_list_append (vguides, GINT_TO_POINTER (image_width)); + + for (guide = gimp_image_find_next_guide (image_ID, 0); + guide > 0; + guide = gimp_image_find_next_guide (image_ID, guide)) + { + gint position = gimp_image_get_guide_position (image_ID, guide); + + switch (gimp_image_get_guide_orientation (image_ID, guide)) + { + case GIMP_ORIENTATION_HORIZONTAL: + if (! g_list_find (hguides, GINT_TO_POINTER (position))) + { + hguides = g_list_insert_sorted (hguides, + GINT_TO_POINTER (position), + guide_sort_func); + guides_found = TRUE; + } + break; + + case GIMP_ORIENTATION_VERTICAL: + if (! g_list_find (vguides, GINT_TO_POINTER (position))) + { + vguides = g_list_insert_sorted (vguides, + GINT_TO_POINTER (position), + guide_sort_func); + guides_found = TRUE; + } + break; + + case GIMP_ORIENTATION_UNKNOWN: + g_assert_not_reached (); + break; + } + } + + if (guides_found) + { + gchar *filename; + gint h, v, hpad, vpad; + gint x, y; + gchar *hformat; + gchar *format; + + filename = gimp_image_get_filename (image_ID); + + if (! filename) + filename = g_strdup (_("Untitled")); + + /* get the number horizontal and vertical slices */ + h = g_list_length (hguides); + v = g_list_length (vguides); + + /* need the number of digits of h and v for the padding */ + hpad = log10(h) + 1; + vpad = log10(v) + 1; + + /* format for the x-y coordinates in the filename */ + hformat = g_strdup_printf ("%%0%i", MAX (hpad, vpad)); + format = g_strdup_printf ("-%si-%si", hformat, hformat); + + /* Do the actual dup'ing and cropping... this isn't a too naive a + * way to do this since we got copy-on-write tiles, either. + */ + for (y = 0, hg = hguides; hg && hg->next; y++, hg = hg->next) + { + for (x = 0, vg = vguides; vg && vg->next; x++, vg = vg->next) + { + gint32 new_image = gimp_image_duplicate (image_ID); + GString *new_filename; + gchar *fileextension; + gchar *fileindex; + gint pos; + + if (new_image == -1) + { + g_warning ("Couldn't create new image."); + g_free (hformat); + g_free (format); + return images; + } + + gimp_image_undo_disable (new_image); + + gimp_image_crop (new_image, + GPOINTER_TO_INT (vg->next->data) - + GPOINTER_TO_INT (vg->data), + GPOINTER_TO_INT (hg->next->data) - + GPOINTER_TO_INT (hg->data), + GPOINTER_TO_INT (vg->data), + GPOINTER_TO_INT (hg->data)); + + + new_filename = g_string_new (filename); + + /* show the rough coordinates of the image in the title */ + fileindex = g_strdup_printf (format, x, y); + + /* get the position of the file extension - last . in filename */ + fileextension = strrchr (new_filename->str, '.'); + pos = fileextension - new_filename->str; + + /* insert the coordinates before the extension */ + g_string_insert (new_filename, pos, fileindex); + g_free (fileindex); + + gimp_image_set_filename (new_image, new_filename->str); + g_string_free (new_filename, TRUE); + + while ((guide = gimp_image_find_next_guide (new_image, 0))) + gimp_image_delete_guide (new_image, guide); + + gimp_image_undo_enable (new_image); + + if (interactive) + gimp_display_new (new_image); + + images = g_list_prepend (images, GINT_TO_POINTER (new_image)); + } + } + + g_free (filename); + g_free (hformat); + g_free (format); + } + + g_list_free (hguides); + g_list_free (vguides); + + return g_list_reverse (images); +} diff --git a/plug-ins/common/hot.c b/plug-ins/common/hot.c new file mode 100644 index 0000000..8ed215e --- /dev/null +++ b/plug-ins/common/hot.c @@ -0,0 +1,782 @@ +/* + * GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * hot.c - Scan an image for pixels with RGB values that will give + * "unsafe" values of chrominance signal or composite signal + * amplitude when encoded into an NTSC or PAL color signal. + * (This happens for certain high-intensity high-saturation colors + * that are rare in real scenes, but can easily be present + * in synthetic images.) + * + * Such pixels can be flagged so the user may then choose other + * colors. Or, the offending pixels can be made "safe" + * in a manner that preserves hue. + * + * There are two reasonable ways to make a pixel "safe": + * We can reduce its intensity (luminance) while leaving + * hue and saturation the same. Or, we can reduce saturation + * while leaving hue and luminance the same. A #define selects + * which strategy to use. + * + * Note to the user: You must add your own read_pixel() and write_pixel() + * routines. You may have to modify pix_decode() and pix_encode(). + * MAXPIX, WID, and HGT are likely to need modification. + */ + +/* + * Originally written as "ikNTSC.c" by Alan Wm Paeth, + * University of Waterloo, August, 1985 + * Updated by Dave Martindale, Imax Systems Corp., December 1990 + */ + +/* + * Compile time options: + * + * + * CHROMA_LIM is the limit (in IRE units) of the overall + * chrominance amplitude; it should be 50 or perhaps + * very slightly higher. + * + * COMPOS_LIM is the maximum amplitude (in IRE units) allowed for + * the composite signal. A value of 100 is the maximum + * monochrome white, and is always safe. 120 is the absolute + * limit for NTSC broadcasting, since the transmitter's carrier + * goes to zero with 120 IRE input signal. Generally, 110 + * is a good compromise - it allows somewhat brighter colors + * than 100, while staying safely away from the hard limit. + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-hot" +#define PLUG_IN_BINARY "hot" +#define PLUG_IN_ROLE "gimp-hot" + + +typedef struct +{ + gint32 image; + gint32 drawable; + gint32 mode; + gint32 action; + gint32 new_layerp; +} piArgs; + +typedef enum +{ + ACT_LREDUX, + ACT_SREDUX, + ACT_FLAG +} hotAction; + +typedef enum +{ + MODE_NTSC, + MODE_PAL +} hotModes; + +#define CHROMA_LIM 50.0 /* chroma amplitude limit */ +#define COMPOS_LIM 110.0 /* max IRE amplitude */ + +/* + * RGB to YIQ encoding matrix. + */ + +struct +{ + gdouble pedestal; + gdouble gamma; + gdouble code[3][3]; +} static mode[2] = { + { + 7.5, + 2.2, + { + { 0.2989, 0.5866, 0.1144 }, + { 0.5959, -0.2741, -0.3218 }, + { 0.2113, -0.5227, 0.3113 } + } + }, + { + 0.0, + 2.8, + { + { 0.2989, 0.5866, 0.1144 }, + { -0.1473, -0.2891, 0.4364 }, + { 0.6149, -0.5145, -0.1004 } + } + } +}; + + +#define SCALE 8192 /* scale factor: do floats with int math */ +#define MAXPIX 255 /* white value */ + +static gint tab[3][3][MAXPIX+1]; /* multiply lookup table */ +static gdouble chroma_lim; /* chroma limit */ +static gdouble compos_lim; /* composite amplitude limit */ +static glong ichroma_lim2; /* chroma limit squared (scaled integer) */ +static gint icompos_lim; /* composite amplitude limit (scaled integer) */ + +static void query (void); +static void run (const gchar *name, + gint nparam, + const GimpParam *param, + gint *nretvals, + GimpParam **retvals); + +static gboolean pluginCore (piArgs *argp); +static gboolean plugin_dialog (piArgs *argp); +static gboolean hotp (guint8 r, + guint8 g, + guint8 b); +static void build_tab (gint m); + +/* + * gc: apply the gamma correction specified for this video standard. + * inv_gc: inverse function of gc. + * + * These are generally just a call to pow(), but be careful! + * Future standards may use more complex functions. + * (e.g. SMPTE 240M's "electro-optic transfer characteristic"). + */ +#define gc(x,m) pow(x, 1.0 / mode[m].gamma) +#define inv_gc(x,m) pow(x, mode[m].gamma) + +/* + * pix_decode: decode an integer pixel value into a floating-point + * intensity in the range [0, 1]. + * + * pix_encode: encode a floating-point intensity into an integer + * pixel value. + * + * The code given here assumes simple linear encoding; you must change + * these routines if you use a different pixel encoding technique. + */ +#define pix_decode(v) ((double)v / (double)MAXPIX) +#define pix_encode(v) ((int)(v * (double)MAXPIX + 0.5)) + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "The Image" }, + { GIMP_PDB_DRAWABLE, "drawable", "The Drawable" }, + { GIMP_PDB_INT32, "mode", "Mode { NTSC (0), PAL (1) }" }, + { GIMP_PDB_INT32, "action", "The action to perform" }, + { GIMP_PDB_INT32, "new-layer", "Create a new layer { TRUE, FALSE }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Find and fix pixels that may be unsafely bright"), + "hot scans an image for pixels that will give unsave " + "values of chrominance or composite signale " + "amplitude when encoded into an NTSC or PAL signal. " + "Three actions can be performed on these ``hot'' " + "pixels. (0) reduce luminance, (1) reduce " + "saturation, or (2) Blacken.", + "Eric L. Hernes, Alan Wm Paeth", + "Eric L. Hernes", + "1997", + N_("_Hot..."), + "RGB", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Colors/Modify"); +} + +static void +run (const gchar *name, + gint nparam, + const GimpParam *param, + gint *nretvals, + GimpParam **retvals) +{ + static GimpParam rvals[1]; + piArgs args; + + *nretvals = 1; + *retvals = rvals; + + INIT_I18N (); + gegl_init (NULL, NULL); + + memset (&args, 0, sizeof (args)); + args.mode = -1; + + gimp_get_data (PLUG_IN_PROC, &args); + + args.image = param[1].data.d_image; + args.drawable = param[2].data.d_drawable; + + rvals[0].type = GIMP_PDB_STATUS; + rvals[0].data.d_status = GIMP_PDB_SUCCESS; + + switch (param[0].data.d_int32) + { + case GIMP_RUN_INTERACTIVE: + /* XXX: add code here for interactive running */ + if (args.mode == -1) + { + args.mode = MODE_NTSC; + args.action = ACT_LREDUX; + args.new_layerp = 1; + } + + if (plugin_dialog (&args)) + { + if (! pluginCore (&args)) + { + rvals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + } + } + else + { + rvals[0].data.d_status = GIMP_PDB_CANCEL; + } + + gimp_set_data (PLUG_IN_PROC, &args, sizeof (args)); + break; + + case GIMP_RUN_NONINTERACTIVE: + /* XXX: add code here for non-interactive running */ + if (nparam != 6) + { + rvals[0].data.d_status = GIMP_PDB_CALLING_ERROR; + break; + } + args.mode = param[3].data.d_int32; + args.action = param[4].data.d_int32; + args.new_layerp = param[5].data.d_int32; + + if (! pluginCore (&args)) + { + rvals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + break; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* XXX: add code here for last-values running */ + if (! pluginCore (&args)) + { + rvals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + } + break; + } +} + +static gboolean +pluginCore (piArgs *argp) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *src_format; + const Babl *dest_format; + gint src_bpp; + gint dest_bpp; + gboolean success = TRUE; + gint nl = 0; + gint y, i; + gint Y, I, Q; + gint width, height; + gint sel_x1, sel_x2, sel_y1, sel_y2; + gint prog_interval; + guchar *src, *s, *dst, *d; + guchar r, prev_r=0, new_r=0; + guchar g, prev_g=0, new_g=0; + guchar b, prev_b=0, new_b=0; + gdouble fy, fc, t, scale; + gdouble pr, pg, pb; + gdouble py; + + width = gimp_drawable_width (argp->drawable); + height = gimp_drawable_height (argp->drawable); + + if (gimp_drawable_has_alpha (argp->drawable)) + src_format = babl_format ("R'G'B'A u8"); + else + src_format = babl_format ("R'G'B' u8"); + + dest_format = src_format; + + if (argp->new_layerp) + { + gchar name[40]; + const gchar *mode_names[] = + { + "ntsc", + "pal", + }; + const gchar *action_names[] = + { + "lum redux", + "sat redux", + "flag", + }; + + g_snprintf (name, sizeof (name), "hot mask (%s, %s)", + mode_names[argp->mode], + action_names[argp->action]); + + nl = gimp_layer_new (argp->image, name, width, height, + GIMP_RGBA_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (argp->image)); + + gimp_drawable_fill (nl, GIMP_FILL_TRANSPARENT); + gimp_image_insert_layer (argp->image, nl, -1, 0); + + dest_format = babl_format ("R'G'B'A u8"); + } + + if (! gimp_drawable_mask_intersect (argp->drawable, + &sel_x1, &sel_y1, &width, &height)) + return success; + + src_bpp = babl_format_get_bytes_per_pixel (src_format); + dest_bpp = babl_format_get_bytes_per_pixel (dest_format); + + sel_x2 = sel_x1 + width; + sel_y2 = sel_y1 + height; + + src = g_new (guchar, width * height * src_bpp); + dst = g_new (guchar, width * height * dest_bpp); + + src_buffer = gimp_drawable_get_buffer (argp->drawable); + + if (argp->new_layerp) + { + dest_buffer = gimp_drawable_get_buffer (nl); + } + else + { + dest_buffer = gimp_drawable_get_shadow_buffer (argp->drawable); + } + + gegl_buffer_get (src_buffer, + GEGL_RECTANGLE (sel_x1, sel_y1, width, height), 1.0, + src_format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + s = src; + d = dst; + + build_tab (argp->mode); + + gimp_progress_init (_("Hot")); + prog_interval = height / 10; + + for (y = sel_y1; y < sel_y2; y++) + { + gint x; + + if (y % prog_interval == 0) + gimp_progress_update ((double) y / (double) (sel_y2 - sel_y1)); + + for (x = sel_x1; x < sel_x2; x++) + { + if (hotp (r = *(s + 0), g = *(s + 1), b = *(s + 2))) + { + if (argp->action == ACT_FLAG) + { + for (i = 0; i < 3; i++) + *d++ = 0; + s += 3; + if (src_bpp == 4) + *d++ = *s++; + else if (argp->new_layerp) + *d++ = 255; + } + else + { + /* + * Optimization: cache the last-computed hot pixel. + */ + if (r == prev_r && g == prev_g && b == prev_b) + { + *d++ = new_r; + *d++ = new_g; + *d++ = new_b; + s += 3; + if (src_bpp == 4) + *d++ = *s++; + else if (argp->new_layerp) + *d++ = 255; + } + else + { + Y = tab[0][0][r] + tab[0][1][g] + tab[0][2][b]; + I = tab[1][0][r] + tab[1][1][g] + tab[1][2][b]; + Q = tab[2][0][r] + tab[2][1][g] + tab[2][2][b]; + + prev_r = r; + prev_g = g; + prev_b = b; + /* + * Get Y and chroma amplitudes in floating point. + * + * If your C library doesn't have hypot(), just use + * hypot(a,b) = sqrt(a*a, b*b); + * + * Then extract linear (un-gamma-corrected) + * floating-point pixel RGB values. + */ + fy = (double)Y / (double)SCALE; + fc = hypot ((double) I / (double) SCALE, + (double) Q / (double) SCALE); + + pr = (double) pix_decode (r); + pg = (double) pix_decode (g); + pb = (double) pix_decode (b); + + /* + * Reducing overall pixel intensity by scaling R, + * G, and B reduces Y, I, and Q by the same factor. + * This changes luminance but not saturation, since + * saturation is determined by the chroma/luminance + * ratio. + * + * On the other hand, by linearly interpolating + * between the original pixel value and a grey + * pixel with the same luminance (R=G=B=Y), we + * change saturation without affecting luminance. + */ + if (argp->action == ACT_LREDUX) + { + /* + * Calculate a scale factor that will bring the pixel + * within both chroma and composite limits, if we scale + * luminance and chroma simultaneously. + * + * The calculated chrominance reduction applies + * to the gamma-corrected RGB values that are + * the input to the RGB-to-YIQ operation. + * Multiplying the original un-gamma-corrected + * pixel values by the scaling factor raised to + * the "gamma" power is equivalent, and avoids + * calling gc() and inv_gc() three times each. */ + scale = chroma_lim / fc; + t = compos_lim / (fy + fc); + if (t < scale) + scale = t; + scale = pow (scale, mode[argp->mode].gamma); + + r = (guint8) pix_encode (scale * pr); + g = (guint8) pix_encode (scale * pg); + b = (guint8) pix_encode (scale * pb); + } + else + { /* ACT_SREDUX hopefully */ + /* + * Calculate a scale factor that will bring the + * pixel within both chroma and composite + * limits, if we scale chroma while leaving + * luminance unchanged. + * + * We have to interpolate gamma-corrected RGB + * values, so we must convert from linear to + * gamma-corrected before interpolation and then + * back to linear afterwards. + */ + scale = chroma_lim / fc; + t = (compos_lim - fy) / fc; + if (t < scale) + scale = t; + + pr = gc (pr, argp->mode); + pg = gc (pg, argp->mode); + pb = gc (pb, argp->mode); + + py = pr * mode[argp->mode].code[0][0] + + pg * mode[argp->mode].code[0][1] + + pb * mode[argp->mode].code[0][2]; + + r = pix_encode (inv_gc (py + scale * (pr - py), + argp->mode)); + g = pix_encode (inv_gc (py + scale * (pg - py), + argp->mode)); + b = pix_encode (inv_gc (py + scale * (pb - py), + argp->mode)); + } + + *d++ = new_r = r; + *d++ = new_g = g; + *d++ = new_b = b; + + s += 3; + + if (src_bpp == 4) + *d++ = *s++; + else if (argp->new_layerp) + *d++ = 255; + } + } + } + else + { + if (! argp->new_layerp) + { + for (i = 0; i < src_bpp; i++) + *d++ = *s++; + } + else + { + s += src_bpp; + d += dest_bpp; + } + } + } + } + + gegl_buffer_set (dest_buffer, + GEGL_RECTANGLE (sel_x1, sel_y1, width, height), 0, + dest_format, dst, + GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update (1.0); + + g_free (src); + g_free (dst); + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + if (argp->new_layerp) + { + gimp_drawable_update (nl, sel_x1, sel_y1, width, height); + } + else + { + gimp_drawable_merge_shadow (argp->drawable, TRUE); + gimp_drawable_update (argp->drawable, sel_x1, sel_y1, width, height); + } + + gimp_displays_flush (); + + return success; +} + +static gboolean +plugin_dialog (piArgs *argp) +{ + GtkWidget *dlg; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *toggle; + GtkWidget *frame; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dlg = gimp_dialog_new (_("Hot"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + frame = gimp_int_radio_group_new (TRUE, _("Mode"), + G_CALLBACK (gimp_radio_button_update), + &argp->mode, argp->mode, + + "N_TSC", MODE_NTSC, NULL, + "_PAL", MODE_PAL, NULL, + + NULL); + + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + toggle = gtk_check_button_new_with_mnemonic (_("Create _new layer")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), argp->new_layerp); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &argp->new_layerp); + + frame = gimp_int_radio_group_new (TRUE, _("Action"), + G_CALLBACK (gimp_radio_button_update), + &argp->action, argp->action, + + _("Reduce _Luminance"), ACT_LREDUX, NULL, + _("Reduce _Saturation"), ACT_SREDUX, NULL, + _("_Blacken"), ACT_FLAG, NULL, + + NULL); + + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dlg); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dlg); + + return run; +} + +/* + * build_tab: Build multiply lookup table. + * + * For each possible pixel value, decode value into floating-point + * intensity. Then do gamma correction required by the video + * standard. Scale the result by our fixed-point scale factor. + * Then calculate 9 lookup table entries for this pixel value. + * + * We also calculate floating-point and scaled integer versions + * of our limits here. This prevents evaluating expressions every pixel + * when the compiler is too stupid to evaluate constant-valued + * floating-point expressions at compile time. + * + * For convenience, the limits are #defined using IRE units. + * We must convert them here into the units in which YIQ + * are measured. The conversion from IRE to internal units + * depends on the pedestal level in use, since as Y goes from + * 0 to 1, the signal goes from the pedestal level to 100 IRE. + * Chroma is always scaled to remain consistent with Y. + */ +static void +build_tab (int m) +{ + double f; + int pv; + + for (pv = 0; pv <= MAXPIX; pv++) + { + f = (double)SCALE * (double)gc((double)pix_decode(pv),m); + tab[0][0][pv] = (int)(f * mode[m].code[0][0] + 0.5); + tab[0][1][pv] = (int)(f * mode[m].code[0][1] + 0.5); + tab[0][2][pv] = (int)(f * mode[m].code[0][2] + 0.5); + tab[1][0][pv] = (int)(f * mode[m].code[1][0] + 0.5); + tab[1][1][pv] = (int)(f * mode[m].code[1][1] + 0.5); + tab[1][2][pv] = (int)(f * mode[m].code[1][2] + 0.5); + tab[2][0][pv] = (int)(f * mode[m].code[2][0] + 0.5); + tab[2][1][pv] = (int)(f * mode[m].code[2][1] + 0.5); + tab[2][2][pv] = (int)(f * mode[m].code[2][2] + 0.5); + } + + chroma_lim = (double)CHROMA_LIM / (100.0 - mode[m].pedestal); + compos_lim = ((double)COMPOS_LIM - mode[m].pedestal) / + (100.0 - mode[m].pedestal); + + ichroma_lim2 = (int)(chroma_lim * SCALE + 0.5); + ichroma_lim2 *= ichroma_lim2; + icompos_lim = (int)(compos_lim * SCALE + 0.5); +} + +static gboolean +hotp (guint8 r, + guint8 g, + guint8 b) +{ + int y, i, q; + long y2, c2; + + /* + * Pixel decoding, gamma correction, and matrix multiplication + * all done by lookup table. + * + * "i" and "q" are the two chrominance components; + * they are I and Q for NTSC. + * For PAL, "i" is U (scaled B-Y) and "q" is V (scaled R-Y). + * Since we only care about the length of the chroma vector, + * not its angle, we don't care which is which. + */ + y = tab[0][0][r] + tab[0][1][g] + tab[0][2][b]; + i = tab[1][0][r] + tab[1][1][g] + tab[1][2][b]; + q = tab[2][0][r] + tab[2][1][g] + tab[2][2][b]; + + /* + * Check to see if the chrominance vector is too long or the + * composite waveform amplitude is too large. + * + * Chrominance is too large if + * + * sqrt(i^2, q^2) > chroma_lim. + * + * The composite signal amplitude is too large if + * + * y + sqrt(i^2, q^2) > compos_lim. + * + * We avoid doing the sqrt by checking + * + * i^2 + q^2 > chroma_lim^2 + * and + * y + sqrt(i^2 + q^2) > compos_lim + * sqrt(i^2 + q^2) > compos_lim - y + * i^2 + q^2 > (compos_lim - y)^2 + * + */ + + c2 = (long)i * i + (long)q * q; + y2 = (long)icompos_lim - y; + y2 *= y2; + + if (c2 <= ichroma_lim2 && c2 <= y2) + { /* no problems */ + return FALSE; + } + + return TRUE; +} diff --git a/plug-ins/common/jigsaw.c b/plug-ins/common/jigsaw.c new file mode 100644 index 0000000..eade3d3 --- /dev/null +++ b/plug-ins/common/jigsaw.c @@ -0,0 +1,2563 @@ +/* + * jigsaw - a plug-in for GIMP + * + * Copyright (C) Nigel Wetten + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contact info: nigel@cs.nwu.edu + * + * Version: 1.0.0 + * + * Version: 1.0.1 + * + * tim coppefield [timecop@japan.co.jp] + * + * Added dynamic preview mode. + * + * Damn, this plugin is the tightest piece of code I ever seen. + * I wish all filters in the plugins operated on guchar *buffer + * of the entire image :) sweet stuff. + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-jigsaw" +#define PLUG_IN_BINARY "jigsaw" +#define PLUG_IN_ROLE "gimp-jigsaw" + + +typedef enum +{ + BEZIER_1, + BEZIER_2 +} style_t; + +typedef enum +{ + LEFT, + RIGHT, + UP, + DOWN +} bump_t; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void jigsaw (guint32 drawable_id, + GimpPreview *preview); +static void jigsaw_preview (gpointer drawable_id, + GimpPreview *preview); + +static gboolean jigsaw_dialog (guint32 drawable_id); + +static void draw_jigsaw (guchar *buffer, + gint bufsize, + gint width, + gint height, + gint bytes, + gboolean preview_mode); + +static void draw_vertical_border (guchar *buffer, gint bufsize, + gint width, gint height, + gint bytes, gint x_offset, gint ytiles, + gint blend_lines, gdouble blend_amount); +static void draw_horizontal_border (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint y_offset, gint xtiles, + gint blend_lines, gdouble blend_amount); +static void draw_vertical_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint px[2], gint py[2]); +static void draw_horizontal_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint px[2], gint py[2]); +static void darken_vertical_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void lighten_vertical_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void darken_horizontal_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void lighten_horizontal_line(guchar *buffer, gint bufsize, + gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void draw_right_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps); +static void draw_left_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps); +static void draw_up_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps); +static void draw_down_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps); +static void darken_right_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_right_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_left_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_left_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_up_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint y_offset, gint curve_start_offest, + gint steps, gdouble delta, gint counter); +static void lighten_up_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_down_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_down_bump (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void generate_grid (gint width, gint height, gint xtiles, gint ytiles, + gint *x, gint *y); +static void generate_bezier (gint px[4], gint py[4], gint steps, + gint *cachex, gint *cachey); +static void malloc_cache (void); +static void free_cache (void); +static void init_right_bump (gint width, gint height); +static void init_left_bump (gint width, gint height); +static void init_up_bump (gint width, gint height); +static void init_down_bump (gint width, gint height); +static void draw_bezier_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint steps, gint *cx, gint *cy); +static void darken_bezier_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta); +static void lighten_bezier_line (guchar *buffer, gint bufsize, + gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta); +static void draw_bezier_vertical_border (guchar *buffer, gint bufsize, + gint width, gint height, + gint bytes, + gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps); +static void draw_bezier_horizontal_border (guchar *buffer, gint bufsize, + gint width, gint height, + gint bytes, + gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps); +static void check_config (gint width, gint height); + + + +#define XFACTOR2 0.0833 +#define XFACTOR3 0.2083 +#define XFACTOR4 0.2500 + +#define XFACTOR5 0.2500 +#define XFACTOR6 0.2083 +#define XFACTOR7 0.0833 + +#define YFACTOR2 0.1000 +#define YFACTOR3 0.2200 +#define YFACTOR4 0.1000 + +#define YFACTOR5 0.1000 +#define YFACTOR6 0.4666 +#define YFACTOR7 0.1000 +#define YFACTOR8 0.2000 + +#define MAX_VALUE 255 +#define MIN_VALUE 0 +#define DELTA 0.15 + +#define BLACK_R 30 +#define BLACK_G 30 +#define BLACK_B 30 + +#define WALL_XFACTOR2 0.05 +#define WALL_XFACTOR3 0.05 +#define WALL_YFACTOR2 0.05 +#define WALL_YFACTOR3 0.05 + +#define WALL_XCONS2 0.2 +#define WALL_XCONS3 0.3 +#define WALL_YCONS2 0.2 +#define WALL_YCONS3 0.3 + +#define FUDGE 1.2 + +#define MIN_XTILES 1 +#define MAX_XTILES 20 +#define MIN_YTILES 1 +#define MAX_YTILES 20 +#define MIN_BLEND_LINES 0 +#define MAX_BLEND_LINES 10 +#define MIN_BLEND_AMOUNT 0 +#define MAX_BLEND_AMOUNT 1.0 + +#define SCALE_WIDTH 200 + +#define DRAW_POINT(buffer, bufsize, index) \ + do \ + { \ + if ((index) >= 0 && (index) + 2 < (bufsize)) \ + { \ + buffer[(index) + 0] = BLACK_R; \ + buffer[(index) + 1] = BLACK_G; \ + buffer[(index) + 2] = BLACK_B; \ + } \ + } \ + while (0) + +#define DARKEN_POINT(buffer, bufsize, index, delta, temp) \ + do \ + { \ + if ((index) >= 0 && (index) + 2 < (bufsize)) \ + { \ + temp = MAX (buffer[(index) + 0] * (1.0 - (delta)), MIN_VALUE); \ + buffer[(index) + 0] = temp; \ + temp = MAX (buffer[(index) + 1] * (1.0 - (delta)), MIN_VALUE); \ + buffer[(index) + 1] = temp; \ + temp = MAX (buffer[(index) + 2] * (1.0 - (delta)), MIN_VALUE); \ + buffer[(index) + 2] = temp; \ + } \ + } \ + while (0) + +#define LIGHTEN_POINT(buffer, bufsize, index, delta, temp) \ + do \ + { \ + if ((index) >= 0 && (index) + 2 < (bufsize)) \ + { \ + temp = MIN (buffer[(index) + 0] * (1.0 + (delta)), MAX_VALUE); \ + buffer[(index) + 0] = temp; \ + temp = MIN (buffer[(index) + 1] * (1.0 + (delta)), MAX_VALUE); \ + buffer[(index) + 1] = temp; \ + temp = MIN (buffer[(index) + 2] * (1.0 + (delta)), MAX_VALUE); \ + buffer[(index) + 2] = temp; \ + } \ + } \ + while (0) + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run +}; + +struct config_tag +{ + gint x; + gint y; + style_t style; + gint blend_lines; + gdouble blend_amount; +}; + + +typedef struct config_tag config_t; + +static config_t config = +{ + 5, + 5, + BEZIER_1, + 3, + 0.5 +}; + +struct globals_tag +{ + gint *cachex1[4]; + gint *cachex2[4]; + gint *cachey1[4]; + gint *cachey2[4]; + gint steps[4]; + gint *gridx; + gint *gridy; + gint **blend_outer_cachex1[4]; + gint **blend_outer_cachex2[4]; + gint **blend_outer_cachey1[4]; + gint **blend_outer_cachey2[4]; + gint **blend_inner_cachex1[4]; + gint **blend_inner_cachex2[4]; + gint **blend_inner_cachey1[4]; + gint **blend_inner_cachey2[4]; +}; + +typedef struct globals_tag globals_t; + +static globals_t globals; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "x", "Number of tiles across > 0" }, + { GIMP_PDB_INT32, "y", "Number of tiles down > 0" }, + { GIMP_PDB_INT32, "style", "The style/shape of the jigsaw puzzle { 0, 1 }" }, + { GIMP_PDB_INT32, "blend-lines", "Number of lines for shading bevels >= 0" }, + { GIMP_PDB_FLOAT, "blend-amount", "The power of the light highlights 0 =< 5" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Add a jigsaw-puzzle pattern to the image"), + "Jigsaw puzzle look", + "Nigel Wetten", + "Nigel Wetten", + "May 2000", + N_("_Jigsaw..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Render/Pattern"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + guint32 drawable_id; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + drawable_id = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_NONINTERACTIVE: + if (nparams == 8) + { + config.x = param[3].data.d_int32; + config.y = param[4].data.d_int32; + config.style = param[5].data.d_int32; + config.blend_lines = param[6].data.d_int32; + config.blend_amount = param[7].data.d_float; + + jigsaw (drawable_id, NULL); + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &config); + if (! jigsaw_dialog (drawable_id)) + { + status = GIMP_PDB_CANCEL; + break; + } + gimp_progress_init (_("Assembling jigsaw")); + + jigsaw (drawable_id, NULL); + gimp_set_data (PLUG_IN_PROC, &config, sizeof(config_t)); + gimp_displays_flush (); + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &config); + jigsaw (drawable_id, NULL); + gimp_displays_flush (); + } /* switch */ + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; +} + +static void +jigsaw (guint32 drawable_id, + GimpPreview *preview) +{ + GeglBuffer *gegl_buffer = NULL; + const Babl *format = NULL; + guchar *buffer; + gint width; + gint height; + gint bytes; + gint buffer_size; + + if (preview) + { + gimp_preview_get_size (preview, &width, &height); + buffer = gimp_drawable_get_thumbnail_data (drawable_id, + &width, &height, &bytes); + buffer_size = bytes * width * height; + } + else + { + gegl_buffer = gimp_drawable_get_buffer (drawable_id); + + width = gimp_drawable_width (drawable_id); + height = gimp_drawable_height (drawable_id); + + if (gimp_drawable_has_alpha (drawable_id)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + bytes = babl_format_get_bytes_per_pixel (format); + + /* setup image buffer */ + buffer_size = bytes * width * height; + buffer = g_new (guchar, buffer_size); + + gegl_buffer_get (gegl_buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0, + format, buffer, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + g_object_unref (gegl_buffer); + } + + check_config (width, height); + globals.steps[LEFT] = globals.steps[RIGHT] = globals.steps[UP] + = globals.steps[DOWN] = (config.x < config.y) ? + (width / config.x * 2) : (height / config.y * 2); + + malloc_cache (); + draw_jigsaw (buffer, buffer_size, width, height, bytes, preview != NULL); + free_cache (); + + /* cleanup */ + if (preview) + { + gimp_preview_draw_buffer (preview, buffer, width * bytes); + } + else + { + gegl_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + gegl_buffer_set (gegl_buffer, GEGL_RECTANGLE (0, 0, width, height), 0, + format, buffer, + GEGL_AUTO_ROWSTRIDE); + g_object_unref (gegl_buffer); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, 0, 0, width, height); + } + + g_free (buffer); +} + +static void +jigsaw_preview (gpointer drawable_id, + GimpPreview *preview) +{ + jigsaw (GPOINTER_TO_INT (drawable_id), preview); +} + +static void +generate_bezier (gint px[4], + gint py[4], + gint steps, + gint *cachex, + gint *cachey) +{ + gdouble t = 0.0; + gdouble sigma = 1.0 / steps; + gint i; + + for (i = 0; i < steps; i++) + { + gdouble t2, t3, x, y, t_1; + + t += sigma; + t2 = t * t; + t3 = t2 * t; + t_1 = 1 - t; + x = t_1 * t_1 * t_1 * px[0] + + 3 * t * t_1 * t_1 * px[1] + + 3 * t2 * t_1 * px[2] + + t3 * px[3]; + y = t_1 * t_1 * t_1 * py[0] + + 3 * t * t_1 * t_1 * py[1] + + 3 * t2 * t_1 * py[2] + + t3 * py[3]; + cachex[i] = (gint) (x + 0.2); + cachey[i] = (gint) (y + 0.2); + } /* for */ +} + +static void +draw_jigsaw (guchar *buffer, + gint bufsize, + gint width, + gint height, + gint bytes, + gboolean preview_mode) +{ + gint i; + gint *x, *y; + gint xtiles = config.x; + gint ytiles = config.y; + gint xlines = xtiles - 1; + gint ylines = ytiles - 1; + gint blend_lines = config.blend_lines; + gdouble blend_amount = config.blend_amount; + gint steps = globals.steps[RIGHT]; + style_t style = config.style; + gint progress_total = xlines + ylines - 1; + + g_return_if_fail (buffer != NULL); + + globals.gridx = g_new (gint, xtiles); + globals.gridy = g_new (gint, ytiles); + x = globals.gridx; + y = globals.gridy; + + generate_grid (width, height, xtiles, ytiles, globals.gridx, globals.gridy); + + init_right_bump (width, height); + init_left_bump (width, height); + init_up_bump (width, height); + init_down_bump (width, height); + + if (style == BEZIER_1) + { + for (i = 0; i < xlines; i++) + { + draw_vertical_border (buffer, bufsize, width, height, bytes, + x[i], ytiles, + blend_lines, blend_amount); + if (!preview_mode) + gimp_progress_update ((gdouble) i / (gdouble) progress_total); + } + for (i = 0; i < ylines; i++) + { + draw_horizontal_border (buffer, bufsize, width, bytes, y[i], xtiles, + blend_lines, blend_amount); + if (!preview_mode) + gimp_progress_update ((gdouble) (i + xlines) / (gdouble) progress_total); + } + } + else if (style == BEZIER_2) + { + for (i = 0; i < xlines; i++) + { + draw_bezier_vertical_border (buffer, bufsize, width, height, bytes, + x[i], xtiles, ytiles, blend_lines, + blend_amount, steps); + if (!preview_mode) + gimp_progress_update ((gdouble) i / (gdouble) progress_total); + } + for (i = 0; i < ylines; i++) + { + draw_bezier_horizontal_border (buffer, bufsize, width, height, bytes, + y[i], xtiles, ytiles, blend_lines, + blend_amount, steps); + if (!preview_mode) + gimp_progress_update ((gdouble) (i + xlines) / (gdouble) progress_total); + } + } + else + { + g_printerr ("draw_jigsaw: bad style\n"); + gimp_quit (); + } + gimp_progress_update (1.0); + + g_free (globals.gridx); + g_free (globals.gridy); +} + +static void +draw_vertical_border (guchar *buffer, + gint bufsize, + gint width, + gint height, + gint bytes, + gint x_offset, + gint ytiles, + gint blend_lines, + gdouble blend_amount) +{ + gint i, j; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 3 * tile_height_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint px[2], py[2]; + gint ly[2], dy[2]; + gint y_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint right; + + for (i = 0; i < ytiles; i++) + { + right = g_random_int_range (0, 2); + + /* first straight line from top downwards */ + px[0] = px[1] = x_offset; + py[0] = y_offset; py[1] = y_offset + curve_start_offset - 1; + draw_vertical_line (buffer, bufsize, width, bytes, px, py); + delta = blend_amount; + dy[0] = ly[0] = py[0]; dy[1] = ly[1] = py[1]; + if (!right) + { + ly[1] += blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dy[0]++; dy[1]--; ly[0]++; ly[1]--; + px[0] = x_offset - j - 1; + darken_vertical_line (buffer, bufsize, width, bytes, px, dy, delta); + px[0] = x_offset + j + 1; + lighten_vertical_line (buffer, bufsize, width, bytes, px, ly, delta); + delta -= sigma; + } + + /* top half of curve */ + if (right) + { + draw_right_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_right_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + /* use to be +j + 1 */ + lighten_right_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + delta -= sigma; + } + } + else + { + draw_left_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_left_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + /* use to be -j - 1 */ + lighten_left_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + delta -= sigma; + } + } + /* bottom straight line of a tile wall */ + px[0] = px[1] = x_offset; + py[0] = y_offset + curve_end_offset; + py[1] = globals.gridy[i]; + draw_vertical_line (buffer, bufsize, width, bytes, px, py); + delta = blend_amount; + dy[0] = ly[0] = py[0]; dy[1] = ly[1] = py[1]; + if (right) + { + dy[0] -= blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dy[0]++; dy[1]--; ly[0]++; ly[1]--; + px[0] = x_offset - j - 1; + darken_vertical_line (buffer, bufsize, width, bytes, px, dy, delta); + px[0] = x_offset + j + 1; + lighten_vertical_line (buffer, bufsize, width, bytes, px, ly, delta); + delta -= sigma; + } + + y_offset = globals.gridy[i]; + } /* for */ +} + +/* assumes RGB* */ +static void +draw_horizontal_border (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint y_offset, + gint xtiles, + gint blend_lines, + gdouble blend_amount) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 3 * tile_width_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint px[2], py[2]; + gint dx[2], lx[2]; + gint x_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint up; + + for (i = 0; i < xtiles; i++) + { + up = g_random_int_range (0, 2); + + /* first horizontal line across */ + px[0] = x_offset; px[1] = x_offset + curve_start_offset - 1; + py[0] = py[1] = y_offset; + draw_horizontal_line (buffer, bufsize, width, bytes, px, py); + delta = blend_amount; + dx[0] = lx[0] = px[0]; dx[1] = lx[1] = px[1]; + if (up) + { + lx[1] += blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dx[0]++; dx[1]--; lx[0]++; lx[1]--; + py[0] = y_offset - j - 1; + darken_horizontal_line (buffer, bufsize, width, bytes, dx, py, + delta); + py[0] = y_offset + j + 1; + lighten_horizontal_line (buffer, bufsize, width, bytes, lx, py, + delta); + delta -= sigma; + } + + if (up) + { + draw_up_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_up_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + /* use to be +j + 1 */ + lighten_up_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + delta -= sigma; + } + } + else + { + draw_down_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be +j + 1 */ + darken_down_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + /* use to be -j -1 */ + lighten_down_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + delta -= sigma; + } + } + /* right horizontal line of tile */ + px[0] = x_offset + curve_end_offset; + px[1] = globals.gridx[i]; + py[0] = py[1] = y_offset; + draw_horizontal_line (buffer, bufsize, width, bytes, px, py); + delta = blend_amount; + dx[0] = lx[0] = px[0]; dx[1] = lx[1] = px[1]; + if (!up) + { + dx[0] -= blend_lines + 2; + } + for (j = 0;j < blend_lines; j++) + { + dx[0]++; dx[1]--; lx[0]++; lx[1]--; + py[0] = y_offset - j - 1; + darken_horizontal_line (buffer, bufsize, width, bytes, dx, py, + delta); + py[0] = y_offset + j + 1; + lighten_horizontal_line (buffer, bufsize, width, bytes, lx, py, + delta); + delta -= sigma; + } + x_offset = globals.gridx[i]; + } /* for */ +} + +/* assumes going top to bottom */ +static void +draw_vertical_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint px[2], + gint py[2]) +{ + gint i; + gint rowstride; + gint index; + gint length; + + rowstride = bytes * width; + index = px[0] * bytes + rowstride * py[0]; + length = py[1] - py[0] + 1; + + for (i = 0; i < length; i++) + { + DRAW_POINT (buffer, bufsize, index); + index += rowstride; + } +} + +/* assumes going left to right */ +static void +draw_horizontal_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint px[2], + gint py[2]) +{ + gint i; + gint rowstride; + gint index; + gint length; + + rowstride = bytes * width; + index = px[0] * bytes + rowstride * py[0]; + length = px[1] - px[0] + 1; + + for (i = 0; i < length; i++) + { + DRAW_POINT (buffer, bufsize, index); + index += bytes; + } +} + +static void +draw_right_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint curve_start_offset, + gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = globals.cachex1[RIGHT][i] + x_offset; + y = globals.cachey1[RIGHT][i] + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + + x = globals.cachex2[RIGHT][i] + x_offset; + y = globals.cachey2[RIGHT][i] + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + } +} + +static void +draw_left_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint curve_start_offset, + gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = globals.cachex1[LEFT][i] + x_offset; + y = globals.cachey1[LEFT][i] + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + + x = globals.cachex2[LEFT][i] + x_offset; + y = globals.cachey2[LEFT][i] + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + } +} + +static void +draw_up_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint y_offset, + gint curve_start_offset, + gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = globals.cachex1[UP][i] + curve_start_offset; + y = globals.cachey1[UP][i] + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + + x = globals.cachex2[UP][i] + curve_start_offset; + y = globals.cachey2[UP][i] + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + } +} + +static void +draw_down_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint y_offset, + gint curve_start_offset, + gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = globals.cachex1[DOWN][i] + curve_start_offset; + y = globals.cachey1[DOWN][i] + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + + x = globals.cachex2[DOWN][i] + curve_start_offset; + y = globals.cachey2[DOWN][i] + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + } +} + +static void +malloc_cache (void) +{ + gint i, j; + gint blend_lines = config.blend_lines; + + for (i = 0; i < 4; i++) + { + gint steps = globals.steps[i]; + + globals.cachex1[i] = g_new (gint, steps); + globals.cachex2[i] = g_new (gint, steps); + globals.cachey1[i] = g_new (gint, steps); + globals.cachey2[i] = g_new (gint, steps); + globals.blend_outer_cachex1[i] = g_new (gint *, blend_lines); + globals.blend_outer_cachex2[i] = g_new (gint *, blend_lines); + globals.blend_outer_cachey1[i] = g_new (gint *, blend_lines); + globals.blend_outer_cachey2[i] = g_new (gint *, blend_lines); + globals.blend_inner_cachex1[i] = g_new (gint *, blend_lines); + globals.blend_inner_cachex2[i] = g_new (gint *, blend_lines); + globals.blend_inner_cachey1[i] = g_new (gint *, blend_lines); + globals.blend_inner_cachey2[i] = g_new (gint *, blend_lines); + for (j = 0; j < blend_lines; j++) + { + globals.blend_outer_cachex1[i][j] = g_new (gint, steps); + globals.blend_outer_cachex2[i][j] = g_new (gint, steps); + globals.blend_outer_cachey1[i][j] = g_new (gint, steps); + globals.blend_outer_cachey2[i][j] = g_new (gint, steps); + globals.blend_inner_cachex1[i][j] = g_new (gint, steps); + globals.blend_inner_cachex2[i][j] = g_new (gint, steps); + globals.blend_inner_cachey1[i][j] = g_new (gint, steps); + globals.blend_inner_cachey2[i][j] = g_new (gint, steps); + } + } +} + +static void +free_cache (void) +{ + gint i, j; + gint blend_lines = config.blend_lines; + + for (i = 0; i < 4; i ++) + { + g_free (globals.cachex1[i]); + g_free (globals.cachex2[i]); + g_free (globals.cachey1[i]); + g_free (globals.cachey2[i]); + for (j = 0; j < blend_lines; j++) + { + g_free (globals.blend_outer_cachex1[i][j]); + g_free (globals.blend_outer_cachex2[i][j]); + g_free (globals.blend_outer_cachey1[i][j]); + g_free (globals.blend_outer_cachey2[i][j]); + g_free (globals.blend_inner_cachex1[i][j]); + g_free (globals.blend_inner_cachex2[i][j]); + g_free (globals.blend_inner_cachey1[i][j]); + g_free (globals.blend_inner_cachey2[i][j]); + } + g_free (globals.blend_outer_cachex1[i]); + g_free (globals.blend_outer_cachex2[i]); + g_free (globals.blend_outer_cachey1[i]); + g_free (globals.blend_outer_cachey2[i]); + g_free (globals.blend_inner_cachex1[i]); + g_free (globals.blend_inner_cachex2[i]); + g_free (globals.blend_inner_cachey1[i]); + g_free (globals.blend_inner_cachey2[i]); + } +} + +static void +init_right_bump (gint width, + gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[RIGHT]; + gint px[4], py[4]; + gint x_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint blend_lines = config.blend_lines; + + px[0] = x_offset; + px[1] = x_offset + XFACTOR2 * tile_width; + px[2] = x_offset + XFACTOR3 * tile_width; + px[3] = x_offset + XFACTOR4 * tile_width; + py[0] = curve_start_offset; + py[1] = curve_start_offset + YFACTOR2 * tile_height; + py[2] = curve_start_offset - YFACTOR3 * tile_height; + py[3] = curve_start_offset + YFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[RIGHT], + globals.cachey1[RIGHT]); + /* outside right bump */ + for (i = 0; i < blend_lines; i++) + { + py[0]--; py[1]--; py[2]--; px[3]++; + generate_bezier(px, py, steps, + globals.blend_outer_cachex1[RIGHT][i], + globals.blend_outer_cachey1[RIGHT][i]); + } + /* inside right bump */ + py[0] += blend_lines; py[1] += blend_lines; py[2] += blend_lines; + px[3] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[0]++; py[1]++; py[2]++; px[3]--; + generate_bezier(px, py, steps, + globals.blend_inner_cachex1[RIGHT][i], + globals.blend_inner_cachey1[RIGHT][i]); + } + + /* bottom half of bump */ + px[0] = x_offset + XFACTOR5 * tile_width; + px[1] = x_offset + XFACTOR6 * tile_width; + px[2] = x_offset + XFACTOR7 * tile_width; + px[3] = x_offset; + py[0] = curve_start_offset + YFACTOR5 * tile_height; + py[1] = curve_start_offset + YFACTOR6 * tile_height; + py[2] = curve_start_offset + YFACTOR7 * tile_height; + py[3] = curve_end_offset; + generate_bezier(px, py, steps, globals.cachex2[RIGHT], + globals.cachey2[RIGHT]); + /* outer right bump */ + for (i = 0; i < blend_lines; i++) + { + py[1]++; py[2]++; py[3]++; px[0]++; + generate_bezier(px, py, steps, + globals.blend_outer_cachex2[RIGHT][i], + globals.blend_outer_cachey2[RIGHT][i]); + } + /* inner right bump */ + py[1] -= blend_lines; py[2] -= blend_lines; py[3] -= blend_lines; + px[0] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[1]--; py[2]--; py[3]--; px[0]--; + generate_bezier(px, py, steps, + globals.blend_inner_cachex2[RIGHT][i], + globals.blend_inner_cachey2[RIGHT][i]); + } +} + +static void +init_left_bump (gint width, + gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[LEFT]; + gint px[4], py[4]; + gint x_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint blend_lines = config.blend_lines; + + px[0] = x_offset; + px[1] = x_offset - XFACTOR2 * tile_width; + px[2] = x_offset - XFACTOR3 * tile_width; + px[3] = x_offset - XFACTOR4 * tile_width; + py[0] = curve_start_offset; + py[1] = curve_start_offset + YFACTOR2 * tile_height; + py[2] = curve_start_offset - YFACTOR3 * tile_height; + py[3] = curve_start_offset + YFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[LEFT], + globals.cachey1[LEFT]); + /* outer left bump */ + for (i = 0; i < blend_lines; i++) + { + py[0]--; py[1]--; py[2]--; px[3]--; + generate_bezier(px, py, steps, + globals.blend_outer_cachex1[LEFT][i], + globals.blend_outer_cachey1[LEFT][i]); + } + /* inner left bump */ + py[0] += blend_lines; py[1] += blend_lines; py[2] += blend_lines; + px[3] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[0]++; py[1]++; py[2]++; px[3]++; + generate_bezier(px, py, steps, + globals.blend_inner_cachex1[LEFT][i], + globals.blend_inner_cachey1[LEFT][i]); + } + + /* bottom half of bump */ + px[0] = x_offset - XFACTOR5 * tile_width; + px[1] = x_offset - XFACTOR6 * tile_width; + px[2] = x_offset - XFACTOR7 * tile_width; + px[3] = x_offset; + py[0] = curve_start_offset + YFACTOR5 * tile_height; + py[1] = curve_start_offset + YFACTOR6 * tile_height; + py[2] = curve_start_offset + YFACTOR7 * tile_height; + py[3] = curve_end_offset; + generate_bezier(px, py, steps, globals.cachex2[LEFT], + globals.cachey2[LEFT]); + /* outer left bump */ + for (i = 0; i < blend_lines; i++) + { + py[1]++; py[2]++; py[3]++; px[0]--; + generate_bezier(px, py, steps, + globals.blend_outer_cachex2[LEFT][i], + globals.blend_outer_cachey2[LEFT][i]); + } + /* inner left bump */ + py[1] -= blend_lines; py[2] -= blend_lines; py[3] -= blend_lines; + px[0] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[1]--; py[2]--; py[3]--; px[0]++; + generate_bezier(px, py, steps, + globals.blend_inner_cachex2[LEFT][i], + globals.blend_inner_cachey2[LEFT][i]); + } +} + +static void +init_up_bump (gint width, + gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[UP]; + gint px[4], py[4]; + gint y_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint blend_lines = config.blend_lines; + + px[0] = curve_start_offset; + px[1] = curve_start_offset + YFACTOR2 * tile_width; + px[2] = curve_start_offset - YFACTOR3 * tile_width; + px[3] = curve_start_offset + YFACTOR4 * tile_width; + py[0] = y_offset; + py[1] = y_offset - XFACTOR2 * tile_height; + py[2] = y_offset - XFACTOR3 * tile_height; + py[3] = y_offset - XFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[UP], + globals.cachey1[UP]); + /* outer up bump */ + for (i = 0; i < blend_lines; i++) + { + px[0]--; px[1]--; px[2]--; py[3]--; + generate_bezier(px, py, steps, + globals.blend_outer_cachex1[UP][i], + globals.blend_outer_cachey1[UP][i]); + } + /* inner up bump */ + px[0] += blend_lines; px[1] += blend_lines; px[2] += blend_lines; + py[3] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[0]++; px[1]++; px[2]++; py[3]++; + generate_bezier(px, py, steps, + globals.blend_inner_cachex1[UP][i], + globals.blend_inner_cachey1[UP][i]); + } + + /* bottom half of bump */ + px[0] = curve_start_offset + YFACTOR5 * tile_width; + px[1] = curve_start_offset + YFACTOR6 * tile_width; + px[2] = curve_start_offset + YFACTOR7 * tile_width; + px[3] = curve_end_offset; + py[0] = y_offset - XFACTOR5 * tile_height; + py[1] = y_offset - XFACTOR6 * tile_height; + py[2] = y_offset - XFACTOR7 * tile_height; + py[3] = y_offset; + generate_bezier(px, py, steps, globals.cachex2[UP], + globals.cachey2[UP]); + /* outer up bump */ + for (i = 0; i < blend_lines; i++) + { + px[1]++; px[2]++; px[3]++; py[0]--; + generate_bezier(px, py, steps, + globals.blend_outer_cachex2[UP][i], + globals.blend_outer_cachey2[UP][i]); + } + /* inner up bump */ + px[1] -= blend_lines; px[2] -= blend_lines; px[3] -= blend_lines; + py[0] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[1]--; px[2]--; px[3]--; py[0]++; + generate_bezier(px, py, steps, + globals.blend_inner_cachex2[UP][i], + globals.blend_inner_cachey2[UP][i]); + } +} + +static void +init_down_bump (gint width, + gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[DOWN]; + gint px[4], py[4]; + gint y_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint blend_lines = config.blend_lines; + + px[0] = curve_start_offset; + px[1] = curve_start_offset + YFACTOR2 * tile_width; + px[2] = curve_start_offset - YFACTOR3 * tile_width; + px[3] = curve_start_offset + YFACTOR4 * tile_width; + py[0] = y_offset; + py[1] = y_offset + XFACTOR2 * tile_height; + py[2] = y_offset + XFACTOR3 * tile_height; + py[3] = y_offset + XFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[DOWN], + globals.cachey1[DOWN]); + /* outer down bump */ + for (i = 0; i < blend_lines; i++) + { + px[0]--; px[1]--; px[2]--; py[3]++; + generate_bezier(px, py, steps, + globals.blend_outer_cachex1[DOWN][i], + globals.blend_outer_cachey1[DOWN][i]); + } + /* inner down bump */ + px[0] += blend_lines; px[1] += blend_lines; px[2] += blend_lines; + py[3] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[0]++; px[1]++; px[2]++; py[3]--; + generate_bezier(px, py, steps, + globals.blend_inner_cachex1[DOWN][i], + globals.blend_inner_cachey1[DOWN][i]); + } + + /* bottom half of bump */ + px[0] = curve_start_offset + YFACTOR5 * tile_width; + px[1] = curve_start_offset + YFACTOR6 * tile_width; + px[2] = curve_start_offset + YFACTOR7 * tile_width; + px[3] = curve_end_offset; + py[0] = y_offset + XFACTOR5 * tile_height; + py[1] = y_offset + XFACTOR6 * tile_height; + py[2] = y_offset + XFACTOR7 * tile_height; + py[3] = y_offset; + generate_bezier(px, py, steps, globals.cachex2[DOWN], + globals.cachey2[DOWN]); + /* outer down bump */ + for (i = 0; i < blend_lines; i++) + { + px[1]++; px[2]++; px[3]++; py[0]++; + generate_bezier(px, py, steps, + globals.blend_outer_cachex2[DOWN][i], + globals.blend_outer_cachey2[DOWN][i]); + } + /* inner down bump */ + px[1] -= blend_lines; px[2] -= blend_lines; px[3] -= blend_lines; + py[0] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[1]--; px[2]--; px[3]--; py[0]--; + generate_bezier(px, py, steps, + globals.blend_inner_cachex2[DOWN][i], + globals.blend_inner_cachey2[DOWN][i]); + } +} + +static void +generate_grid (gint width, + gint height, + gint xtiles, + gint ytiles, + gint *x, + gint *y) +{ + gint i; + gint xlines = xtiles - 1; + gint ylines = ytiles - 1; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_width_leftover = width % xtiles; + gint tile_height_leftover = height % ytiles; + gint x_offset = tile_width; + gint y_offset = tile_height; + gint carry; + + for (i = 0; i < xlines; i++) + { + x[i] = x_offset; + x_offset += tile_width; + } + carry = 0; + while (tile_width_leftover) + { + for (i = carry; i < xlines; i++) + { + x[i] += 1; + } + tile_width_leftover--; + carry++; + } + x[xlines] = width - 1; /* padding for draw_horizontal_border */ + + for (i = 0; i < ytiles; i++) + { + y[i] = y_offset; + y_offset += tile_height; + } + carry = 0; + while (tile_height_leftover) + { + for (i = carry; i < ylines; i++) + { + y[i] += 1; + } + tile_height_leftover--; + carry++; + } + y[ylines] = height - 1; /* padding for draw_vertical_border */ +} + +/* assumes RGB* */ +/* assumes py[1] > py[0] and px[0] = px[1] */ +static void +darken_vertical_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint px[2], + gint py[2], + gdouble delta) +{ + gint i; + gint rowstride; + gint index; + gint length; + gint temp; + + rowstride = bytes * width; + index = px[0] * bytes + py[0] * rowstride; + length = py[1] - py[0] + 1; + + for (i = 0; i < length; i++) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + index += rowstride; + } +} + +/* assumes RGB* */ +/* assumes py[1] > py[0] and px[0] = px[1] */ +static void +lighten_vertical_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint px[2], + gint py[2], + gdouble delta) +{ + gint i; + gint rowstride; + gint index; + gint length; + gint temp; + + rowstride = bytes * width; + index = px[0] * bytes + py[0] * rowstride; + length = py[1] - py[0] + 1; + + for (i = 0; i < length; i++) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + index += rowstride; + } +} + +/* assumes RGB* */ +/* assumes px[1] > px[0] and py[0] = py[1] */ +static void +darken_horizontal_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint px[2], + gint py[2], + gdouble delta) +{ + gint i; + gint rowstride; + gint index; + gint length; + gint temp; + + rowstride = bytes * width; + index = px[0] * bytes + py[0] * rowstride; + length = px[1] - px[0] + 1; + + for (i = 0; i < length; i++) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + index += bytes; + } +} + +/* assumes RGB* */ +/* assumes px[1] > px[0] and py[0] = py[1] */ +static void +lighten_horizontal_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint px[2], + gint py[2], + gdouble delta) +{ + gint i; + gint rowstride; + gint index; + gint length; + gint temp; + + rowstride = bytes * width; + index = px[0] * bytes + py[0] * rowstride; + length = px[1] - px[0] + 1; + + for (i = 0; i < length; i++) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + index += bytes; + } +} + +static void +darken_right_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = x_offset + + globals.blend_inner_cachex1[RIGHT][j][i]; + y = curve_start_offset + + globals.blend_inner_cachey1[RIGHT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.3) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index1 = index; + } + + x = x_offset + + globals.blend_inner_cachex2[RIGHT][j][i]; + y = curve_start_offset + + globals.blend_inner_cachey2[RIGHT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + last_index2 = index; + } + } +} + +static void +lighten_right_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = x_offset + + globals.blend_outer_cachex1[RIGHT][j][i]; + y = curve_start_offset + + globals.blend_outer_cachey1[RIGHT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.3) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index1 = index; + } + + x = x_offset + + globals.blend_outer_cachex2[RIGHT][j][i]; + y = curve_start_offset + + globals.blend_outer_cachey2[RIGHT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + last_index2 = index; + } + } +} + +static void +darken_left_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = x_offset + + globals.blend_outer_cachex1[LEFT][j][i]; + y = curve_start_offset + + globals.blend_outer_cachey1[LEFT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + last_index1 = index; + } + + x = x_offset + + globals.blend_outer_cachex2[LEFT][j][i]; + y = curve_start_offset + + globals.blend_outer_cachey2[LEFT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index2 = index; + } + } +} + +static void +lighten_left_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = x_offset + + globals.blend_inner_cachex1[LEFT][j][i]; + y = curve_start_offset + + globals.blend_inner_cachey1[LEFT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + last_index1 = index; + } + + x = x_offset + + globals.blend_inner_cachex2[LEFT][j][i]; + y = curve_start_offset + + globals.blend_inner_cachey2[LEFT][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index2 = index; + } + } +} + +static void +darken_up_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint y_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + globals.blend_outer_cachex1[UP][j][i]; + y = y_offset + + globals.blend_outer_cachey1[UP][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + last_index1 = index; + } + + x = curve_start_offset + + globals.blend_outer_cachex2[UP][j][i]; + y = y_offset + + globals.blend_outer_cachey2[UP][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index2 = index; + } + } +} + +static void +lighten_up_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint y_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + globals.blend_inner_cachex1[UP][j][i]; + y = y_offset + + globals.blend_inner_cachey1[UP][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + last_index1 = index; + } + + x = curve_start_offset + + globals.blend_inner_cachex2[UP][j][i]; + y = y_offset + + globals.blend_inner_cachey2[UP][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index2 = index; + } + } +} + +static void +darken_down_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint y_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + globals.blend_inner_cachex1[DOWN][j][i]; + y = y_offset + + globals.blend_inner_cachey1[DOWN][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.2) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index1 = index; + } + + x = curve_start_offset + + globals.blend_inner_cachex2[DOWN][j][i]; + y = y_offset + + globals.blend_inner_cachey2[DOWN][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + last_index2 = index; + } + } +} + +static void +lighten_down_bump (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint y_offset, + gint curve_start_offset, + gint steps, + gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride; + gint temp; + gint j = counter; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + globals.blend_outer_cachex1[DOWN][j][i]; + y = y_offset + + globals.blend_outer_cachey1[DOWN][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.2) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + } + else + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + } + last_index1 = index; + } + + x = curve_start_offset + + globals.blend_outer_cachex2[DOWN][j][i]; + y = y_offset + + globals.blend_outer_cachey2[DOWN][j][i]; + index = y * rowstride + x * bytes; + if (index != last_index2) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + last_index2 = index; + } + } +} + +static void +draw_bezier_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint steps, + gint *cx, + gint *cy) +{ + gint i; + gint x, y; + gint index; + gint rowstride; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = cx[i]; + y = cy[i]; + index = y * rowstride + x * bytes; + DRAW_POINT (buffer, bufsize, index); + } +} + +static void +darken_bezier_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint y_offset, + gint steps, + gint *cx, + gint *cy, + gdouble delta) +{ + gint i; + gint x, y; + gint index; + gint last_index = -1; + gint rowstride; + gint temp; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = cx[i] + x_offset; + y = cy[i] + y_offset; + index = y * rowstride + x * bytes; + if (index != last_index) + { + DARKEN_POINT (buffer, bufsize, index, delta, temp); + last_index = index; + } + } +} + +static void +lighten_bezier_line (guchar *buffer, + gint bufsize, + gint width, + gint bytes, + gint x_offset, + gint y_offset, + gint steps, + gint *cx, + gint *cy, + gdouble delta) +{ + gint i; + gint x, y; + gint index; + gint last_index = -1; + gint rowstride; + gint temp; + + rowstride = bytes * width; + + for (i = 0; i < steps; i++) + { + x = cx[i] + x_offset; + y = cy[i] + y_offset; + index = y * rowstride + x * bytes; + if (index != last_index) + { + LIGHTEN_POINT (buffer, bufsize, index, delta, temp); + last_index = index; + } + } +} + +static void +draw_bezier_vertical_border (guchar *buffer, + gint bufsize, + gint width, + gint height, + gint bytes, + gint x_offset, + gint xtiles, + gint ytiles, + gint blend_lines, + gdouble blend_amount, + gint steps) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 3 * tile_height_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint px[4], py[4]; + gint y_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint right; + gint *cachex, *cachey; + + cachex = g_new (gint, steps); + cachey = g_new (gint, steps); + + for (i = 0; i < ytiles; i++) + { + right = g_random_int_range (0, 2); + + px[0] = px[3] = x_offset; + px[1] = x_offset + WALL_XFACTOR2 * tile_width * FUDGE; + px[2] = x_offset + WALL_XFACTOR3 * tile_width * FUDGE; + py[0] = y_offset; + py[1] = y_offset + WALL_YCONS2 * tile_height; + py[2] = y_offset + WALL_YCONS3 * tile_height; + py[3] = y_offset + curve_start_offset; + + if (right) + { + px[1] = x_offset - WALL_XFACTOR2 * tile_width; + px[2] = x_offset - WALL_XFACTOR3 * tile_width; + } + generate_bezier (px, py, steps, cachex, cachey); + draw_bezier_line (buffer, bufsize, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + px[0] = -j - 1; + darken_bezier_line (buffer, bufsize, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + px[0] = j + 1; + lighten_bezier_line (buffer, bufsize, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + delta -= sigma; + } + if (right) + { + draw_right_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_right_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + /* use to be +j + 1 */ + lighten_right_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + delta -= sigma; + } + } + else + { + draw_left_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_left_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + /* use to be -j - 1 */ + lighten_left_bump (buffer, bufsize, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + delta -= sigma; + } + } + px[0] = px[3] = x_offset; + px[1] = x_offset + WALL_XFACTOR2 * tile_width * FUDGE; + px[2] = x_offset + WALL_XFACTOR3 * tile_width * FUDGE; + py[0] = y_offset + curve_end_offset; + py[1] = y_offset + curve_end_offset + WALL_YCONS2 * tile_height; + py[2] = y_offset + curve_end_offset + WALL_YCONS3 * tile_height; + py[3] = globals.gridy[i]; + if (right) + { + px[1] = x_offset - WALL_XFACTOR2 * tile_width; + px[2] = x_offset - WALL_XFACTOR3 * tile_width; + } + generate_bezier (px, py, steps, cachex, cachey); + draw_bezier_line (buffer, bufsize, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + px[0] = -j - 1; + darken_bezier_line (buffer, bufsize, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + px[0] = j + 1; + lighten_bezier_line (buffer, bufsize, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + delta -= sigma; + } + y_offset = globals.gridy[i]; + } /* for */ + g_free(cachex); + g_free(cachey); +} + +static void +draw_bezier_horizontal_border (guchar *buffer, + gint bufsize, + gint width, + gint height, + gint bytes, + gint y_offset, + gint xtiles, + gint ytiles, + gint blend_lines, + gdouble blend_amount, + gint steps) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 3 * tile_width_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint px[4], py[4]; + gint x_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint up; + gint *cachex, *cachey; + + cachex = g_new (gint, steps); + cachey = g_new (gint, steps); + + for (i = 0; i < xtiles; i++) + { + up = g_random_int_range (0, 2); + + px[0] = x_offset; + px[1] = x_offset + WALL_XCONS2 * tile_width; + px[2] = x_offset + WALL_XCONS3 * tile_width; + px[3] = x_offset + curve_start_offset; + py[0] = py[3] = y_offset; + py[1] = y_offset + WALL_YFACTOR2 * tile_height * FUDGE; + py[2] = y_offset + WALL_YFACTOR3 * tile_height * FUDGE; + if (!up) + { + py[1] = y_offset - WALL_YFACTOR2 * tile_height; + py[2] = y_offset - WALL_YFACTOR3 * tile_height; + } + generate_bezier (px, py, steps, cachex, cachey); + draw_bezier_line (buffer, bufsize, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + py[0] = -j - 1; + darken_bezier_line (buffer, bufsize, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + py[0] = j + 1; + lighten_bezier_line (buffer, bufsize, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + delta -= sigma; + } + /* bumps */ + if (up) + { + draw_up_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_up_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + /* use to be +j + 1 */ + lighten_up_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + delta -= sigma; + } + } + else + { + draw_down_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be +j + 1 */ + darken_down_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + /* use to be -j -1 */ + lighten_down_bump (buffer, bufsize, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + delta -= sigma; + } + } + /* ending side wall line */ + px[0] = x_offset + curve_end_offset; + px[1] = x_offset + curve_end_offset + WALL_XCONS2 * tile_width; + px[2] = x_offset + curve_end_offset + WALL_XCONS3 * tile_width; + px[3] = globals.gridx[i]; + py[0] = py[3] = y_offset; + py[1] = y_offset + WALL_YFACTOR2 * tile_height * FUDGE; + py[2] = y_offset + WALL_YFACTOR3 * tile_height * FUDGE; + if (!up) + { + py[1] = y_offset - WALL_YFACTOR2 * tile_height; + py[2] = y_offset - WALL_YFACTOR3 * tile_height; + } + generate_bezier (px, py, steps, cachex, cachey); + draw_bezier_line (buffer, bufsize, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + py[0] = -j - 1; + darken_bezier_line (buffer, bufsize, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + py[0] = j + 1; + lighten_bezier_line (buffer, bufsize, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + delta -= sigma; + } + x_offset = globals.gridx[i]; + } /* for */ + g_free(cachex); + g_free(cachey); +} + +static void +check_config (gint width, + gint height) +{ + gint tile_width, tile_height; + gint tile_width_limit, tile_height_limit; + + if (config.x < 1) + { + config.x = 1; + } + if (config.y < 1) + { + config.y = 1; + } + if (config.blend_amount < 0) + { + config.blend_amount = 0; + } + if (config.blend_amount > 5) + { + config.blend_amount = 5; + } + tile_width = width / config.x; + tile_height = height / config.y; + tile_width_limit = 0.4 * tile_width; + tile_height_limit = 0.4 * tile_height; + if ((config.blend_lines > tile_width_limit) + || (config.blend_lines > tile_height_limit)) + { + config.blend_lines = MIN(tile_width_limit, tile_height_limit); + } +} + +/******************************************************** + GUI +********************************************************/ + +static gboolean +jigsaw_dialog (guint32 drawable_id) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkSizeGroup *group; + GtkWidget *frame; + GtkWidget *rbutton1; + GtkWidget *rbutton2; + GtkWidget *table; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Jigsaw"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_aspect_preview_new_from_drawable_id (drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (jigsaw_preview), + GINT_TO_POINTER (drawable_id)); + + frame = gimp_frame_new (_("Number of Tiles")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + + group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + /* xtiles */ + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Horizontal:"), SCALE_WIDTH, 0, + config.x, MIN_XTILES, MAX_XTILES, 1.0, 4.0, 0, + TRUE, 0, 0, + _("Number of pieces going across"), NULL); + + gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (adj)); + g_object_unref (group); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &config.x); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* ytiles */ + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Vertical:"), SCALE_WIDTH, 0, + config.y, MIN_YTILES, MAX_YTILES, 1.0, 4.0, 0, + TRUE, 0, 0, + _("Number of pieces going down"), NULL); + + gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (adj)); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &config.y); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (table); + gtk_widget_show (frame); + + frame = gimp_frame_new (_("Bevel Edges")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + + /* number of blending lines */ + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Bevel width:"), SCALE_WIDTH, 4, + config.blend_lines, + MIN_BLEND_LINES, MAX_BLEND_LINES, 1.0, 2.0, 0, + TRUE, 0, 0, + _("Degree of slope of each piece's edge"), NULL); + + gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (adj)); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &config.blend_lines); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* blending amount */ + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("H_ighlight:"), SCALE_WIDTH, 4, + config.blend_amount, + MIN_BLEND_AMOUNT, MAX_BLEND_AMOUNT, 0.05, 0.1, 2, + TRUE, 0, 0, + _("The amount of highlighting on the edges " + "of each piece"), NULL); + + gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (adj)); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &config.blend_amount); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (table); + gtk_widget_show (frame); + + /* frame for primitive radio buttons */ + + frame = gimp_int_radio_group_new (TRUE, _("Jigsaw Style"), + G_CALLBACK (gimp_radio_button_update), + &config.style, config.style, + + _("_Square"), BEZIER_1, &rbutton1, + _("C_urved"), BEZIER_2, &rbutton2, + + NULL); + + gimp_help_set_help_data (rbutton1, _("Each piece has straight sides"), NULL); + gimp_help_set_help_data (rbutton2, _("Each piece has curved sides"), NULL); + g_signal_connect_swapped (rbutton1, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (rbutton2, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/mail.c b/plug-ins/common/mail.c new file mode 100644 index 0000000..668c932 --- /dev/null +++ b/plug-ins/common/mail.c @@ -0,0 +1,830 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * Copyright (C) 1997 Daniel Risacher + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * GUMP - Gimp Useless Mail Plugin + * (or Gump Useless Mail Plugin if you prefer) + * + * by Adrian Likins + * MIME encapsulation by Reagan Blundell + * + * As always: The utility of this plugin is left as an exercise for + * the reader + * + */ + +#include "config.h" + +#include + +#ifdef SENDMAIL +#include +#include +#endif + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#define BUFFER_SIZE 256 + +#define PLUG_IN_PROC "plug-in-mail-image" +#define PLUG_IN_BINARY "mail" +#define PLUG_IN_ROLE "gimp-mail" + +typedef struct +{ + gchar filename[BUFFER_SIZE]; + gchar receipt[BUFFER_SIZE]; + gchar from[BUFFER_SIZE]; + gchar subject[BUFFER_SIZE]; + gchar comment[BUFFER_SIZE]; +} m_info; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static GimpPDBStatusType send_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 run_mode); + +static gboolean send_dialog (void); +static void mail_entry_callback (GtkWidget *widget, + gchar *data); +static gboolean valid_file (const gchar *filename); +static gchar * find_extension (const gchar *filename); + +#ifdef SENDMAIL +static void mesg_body_callback (GtkTextBuffer *buffer, + gpointer data); + +static gchar * sendmail_content_type (const gchar *filename); +static void sendmail_create_headers (FILE *mailpipe); +static gboolean sendmail_to64 (const gchar *filename, + FILE *outfile, + GError **error); +static FILE * sendmail_pipe (gchar **cmd, + GPid *pid); +#endif + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static m_info mail_info = +{ + "", "", "", "", "" +}; + +static gchar *mesg_body = NULL; + + +MAIN () + +static void +query (void) +{ + gchar *email_bin; + + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "to-address", "The email address to send to" }, + { GIMP_PDB_STRING, "from-address", "The email address for the From: field" }, + { GIMP_PDB_STRING, "subject", "The subject" }, + { GIMP_PDB_STRING, "comment", "The Comment" }, + { GIMP_PDB_INT32, "encapsulation", "ignored" } + }; + + /* Check if xdg-email or sendmail is installed. + * TODO: allow setting the location of the executable in preferences. + */ +#ifdef SENDMAIL + if (strlen (SENDMAIL) == 0) + { + email_bin = g_find_program_in_path ("sendmail"); + } + else + { + /* If a directory has been set at build time, we assume that sendmail + * can only be in this directory. */ + email_bin = g_build_filename (SENDMAIL, "sendmail", NULL); + if (! g_file_test (email_bin, G_FILE_TEST_IS_EXECUTABLE)) + { + g_free (email_bin); + email_bin = NULL; + } + } +#else + email_bin = g_find_program_in_path ("xdg-email"); +#endif + + if (email_bin == NULL) + return; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Send the image by email"), +#ifdef SENDMAIL + "Sendmail is used to send emails and must be properly configured.", +#else /* xdg-email */ + "The preferred email composer is used to send emails and must be properly configured.", +#endif + "Adrian Likins, Reagan Blundell", + "Adrian Likins, Reagan Blundell, Daniel Risacher, " + "Spencer Kimball and Peter Mattis", + "1995-1997", + N_("Send by E_mail..."), + "*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/File/Send"); + gimp_plugin_icon_register (PLUG_IN_PROC, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) GIMP_ICON_EDIT); + + g_free (email_bin); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + gint32 drawable_ID; + + INIT_I18N (); + + run_mode = param[0].data.d_int32; + image_ID = param[1].data.d_image; + drawable_ID = param[2].data.d_drawable; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, PLUG_IN_PROC) == 0) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &mail_info); + { + gchar *filename = gimp_image_get_filename (image_ID); + + if (filename) + { + gchar *basename = g_filename_display_basename (filename); + + g_strlcpy (mail_info.filename, basename, BUFFER_SIZE); + g_free (basename); + g_free (filename); + } + } + + if (! send_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams < 8) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + g_strlcpy (mail_info.filename, + param[3].data.d_string, BUFFER_SIZE); + g_strlcpy (mail_info.receipt, + param[4].data.d_string, BUFFER_SIZE); + g_strlcpy (mail_info.from, + param[5].data.d_string, BUFFER_SIZE); + g_strlcpy (mail_info.subject, + param[6].data.d_string, BUFFER_SIZE); + g_strlcpy (mail_info.comment, + param[7].data.d_string, BUFFER_SIZE); + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &mail_info); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + status = send_image (mail_info.filename, + image_ID, + drawable_ID, + run_mode); + + if (status == GIMP_PDB_SUCCESS) + { + if (mesg_body) + g_strlcpy (mail_info.comment, mesg_body, BUFFER_SIZE); + + gimp_set_data (PLUG_IN_PROC, &mail_info, sizeof (m_info)); + } + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].data.d_status = status; +} + +static GimpPDBStatusType +send_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 run_mode) +{ + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gchar *ext; + gchar *tmpname; +#ifndef SENDMAIL /* xdg-email */ + gchar *mailcmd[9]; + gchar *filepath = NULL; + GFile *tmp_dir = NULL; + GFileEnumerator *enumerator; + gint i; +#else /* SENDMAIL */ + gchar *mailcmd[3]; + GPid mailpid; + FILE *mailpipe = NULL; +#endif + GError *error = NULL; + + ext = find_extension (filename); + + if (ext == NULL) + return GIMP_PDB_CALLING_ERROR; + + /* get a temp name with the right extension and save into it. */ + tmpname = gimp_temp_name (ext + 1); + + if (! (gimp_file_save (run_mode, + image_ID, + drawable_ID, + tmpname, + tmpname) && valid_file (tmpname))) + { + goto error; + } + +#ifndef SENDMAIL /* xdg-email */ + /* From xdg-email doc: + * "Some e-mail applications require the file to remain present + * after xdg-email returns." + * As a consequence, the file cannot be removed at the end of the + * function. We actually have no way to ever know *when* the file can + * be removed since the caller could leave the email window opened for + * hours. Yet we still want to clean sometimes and not have temporary + * images piling up. + * So I use a known directory that we control under $GIMP_DIRECTORY/tmp/, + * and clean it out each time the plugin runs. This means that *if* you + * are in the above case (your email client requires the file to stay + * alive), * you cannot run twice the plugin at the same time. + */ + tmp_dir = gimp_directory_file ("tmp", PLUG_IN_PROC, NULL); + + if (g_mkdir_with_parents (gimp_file_get_utf8_name (tmp_dir), + S_IRUSR | S_IWUSR | S_IXUSR) == -1) + { + g_message ("Temporary directory %s could not be created.", + gimp_file_get_utf8_name (tmp_dir)); + g_error_free (error); + goto error; + } + + enumerator = g_file_enumerate_children (tmp_dir, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, NULL); + if (enumerator) + { + GFileInfo *info; + + while ((info = g_file_enumerator_next_file (enumerator, + NULL, NULL))) + { + if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR) + { + GFile *file = g_file_enumerator_get_child (enumerator, info); + g_file_delete (file, NULL, NULL); + g_object_unref (file); + } + + g_object_unref (info); + } + + g_object_unref (enumerator); + } + + filepath = g_build_filename (gimp_file_get_utf8_name (tmp_dir), + mail_info.filename, NULL); + g_rename (tmpname, filepath); + + mailcmd[0] = g_strdup ("xdg-email"); + mailcmd[1] = "--attach"; + mailcmd[2] = filepath; + i = 3; + if (strlen (mail_info.subject) > 0) + { + mailcmd[i++] = "--subject"; + mailcmd[i++] = mail_info.subject; + } + if (strlen (mail_info.comment) > 0) + { + mailcmd[i++] = "--body"; + mailcmd[i++] = mail_info.comment; + } + if (strlen (mail_info.receipt) > 0) + { + mailcmd[i++] = mail_info.receipt; + } + mailcmd[i] = NULL; + + if (! g_spawn_async (NULL, mailcmd, NULL, + G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, &error)) + { + g_message ("%s", error->message); + g_error_free (error); + goto error; + } + +#else /* SENDMAIL */ + /* construct the "sendmail user@location" line */ + if (strlen (SENDMAIL) == 0) + mailcmd[0] = g_strdup ("sendmail"); + else + mailcmd[0] = g_build_filename (SENDMAIL, "sendmail", NULL); + + mailcmd[1] = mail_info.receipt; + mailcmd[2] = NULL; + + /* create a pipe to sendmail */ + mailpipe = sendmail_pipe (mailcmd, &mailpid); + + if (mailpipe == NULL) + return GIMP_PDB_EXECUTION_ERROR; + + sendmail_create_headers (mailpipe); + + fflush (mailpipe); + + if (! sendmail_to64 (tmpname, mailpipe, &error)) + { + g_message ("%s", error->message); + g_error_free (error); + goto error; + } + + fprintf (mailpipe, "\n--GUMP-MIME-boundary--\n"); +#endif + + goto cleanup; + +error: + /* stop sendmail from doing anything */ +#ifdef SENDMAIL + kill (mailpid, SIGINT); +#endif + status = GIMP_PDB_EXECUTION_ERROR; + +cleanup: + /* close out the sendmail process */ +#ifdef SENDMAIL + if (mailpipe) + { + fclose (mailpipe); + waitpid (mailpid, NULL, 0); + g_spawn_close_pid (mailpid); + } + + /* delete the tmpfile that was generated */ + g_unlink (tmpname); +#else + if (tmp_dir) + g_object_unref (tmp_dir); + if (filepath) + g_free (filepath); +#endif + + g_free (mailcmd[0]); + g_free (tmpname); + + return status; +} + + +static gboolean +send_dialog (void) +{ + GtkWidget *dlg; + GtkWidget *main_vbox; + GtkWidget *entry; + GtkWidget *table; +#ifdef SENDMAIL + GtkWidget *scrolled_window; + GtkWidget *text_view; + GtkTextBuffer *text_buffer; +#endif + gchar *gump_from; + gint row = 0; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + /* check gimprc for a preferred "From:" address */ + gump_from = gimp_gimprc_query ("gump-from"); + + if (gump_from) + { + g_strlcpy (mail_info.from, gump_from, BUFFER_SIZE); + g_free (gump_from); + } + + dlg = gimp_dialog_new (_("Send by Email"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Send"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + /* table */ + table = gtk_table_new (5, 2, FALSE); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 12); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + + /* Filename entry */ + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 200, -1); + gtk_entry_set_max_length (GTK_ENTRY (entry), BUFFER_SIZE - 1); + gtk_entry_set_text (GTK_ENTRY (entry), mail_info.filename); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("_Filename:"), 0.0, 0.5, + entry, 1, FALSE); + g_signal_connect (entry, "changed", + G_CALLBACK (mail_entry_callback), + mail_info.filename); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + +#ifdef SENDMAIL + /* To entry */ + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 200, -1); + gtk_entry_set_max_length (GTK_ENTRY (entry), BUFFER_SIZE - 1); + gtk_entry_set_text (GTK_ENTRY (entry), mail_info.receipt); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + C_("email-address", "_To:"), 0.0, 0.5, + entry, 1, FALSE); + g_signal_connect (entry, "changed", + G_CALLBACK (mail_entry_callback), + mail_info.receipt); + + gtk_widget_grab_focus (entry); + + /* From entry */ + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 200, -1); + gtk_entry_set_max_length (GTK_ENTRY (entry), BUFFER_SIZE - 1); + gtk_entry_set_text (GTK_ENTRY (entry), mail_info.from); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + C_("email-address", "_From:"), 0.0, 0.5, + entry, 1, FALSE); + g_signal_connect (entry, "changed", + G_CALLBACK (mail_entry_callback), + mail_info.from); + + /* Subject entry */ + entry = gtk_entry_new (); + gtk_widget_set_size_request (entry, 200, -1); + gtk_entry_set_max_length (GTK_ENTRY (entry), BUFFER_SIZE - 1); + gtk_entry_set_text (GTK_ENTRY (entry), mail_info.subject); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("S_ubject:"), 0.0, 0.5, + entry, 1, FALSE); + g_signal_connect (entry, "changed", + G_CALLBACK (mail_entry_callback), + mail_info.subject); + + /* Body */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (main_vbox), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + text_buffer = gtk_text_buffer_new (NULL); + + g_signal_connect (text_buffer, "changed", + G_CALLBACK (mesg_body_callback), + NULL); + + gtk_text_buffer_set_text (text_buffer, mail_info.comment, -1); + + text_view = gtk_text_view_new_with_buffer (text_buffer); + g_object_unref (text_buffer); + + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view), GTK_WRAP_WORD); + gtk_container_add (GTK_CONTAINER (scrolled_window), text_view); + gtk_widget_show (text_view); +#endif + + gtk_widget_show (dlg); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dlg); + + return run; +} + +static gboolean +valid_file (const gchar *filename) +{ + GStatBuf buf; + + return g_stat (filename, &buf) == 0 && buf.st_size > 0; +} + +static gchar * +find_extension (const gchar *filename) +{ + gchar *filename_copy; + gchar *ext; + + /* we never free this copy - aren't we evil! */ + filename_copy = g_strdup (filename); + + /* find the extension, boy! */ + ext = strrchr (filename_copy, '.'); + + while (TRUE) + { + if (!ext || ext[1] == '\0' || strchr (ext, G_DIR_SEPARATOR)) + { + g_message (_("some sort of error with the file extension " + "or lack thereof")); + + return NULL; + } + + if (0 != g_ascii_strcasecmp (ext, ".gz") && + 0 != g_ascii_strcasecmp (ext, ".bz2")) + { + return ext; + } + else + { + /* we found something, loop back, and look again */ + *ext = 0; + ext = strrchr (filename_copy, '.'); + } + } + + g_free (filename_copy); + + return ext; +} + +static void +mail_entry_callback (GtkWidget *widget, + gchar *data) +{ + g_strlcpy (data, gtk_entry_get_text (GTK_ENTRY (widget)), BUFFER_SIZE); +} + +#ifdef SENDMAIL +static void +mesg_body_callback (GtkTextBuffer *buffer, + gpointer data) +{ + GtkTextIter start_iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter); + + g_free (mesg_body); + mesg_body = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE); +} + +static gchar * +sendmail_content_type (const gchar *filename) +{ + /* This function returns a MIME Content-type: value based on the + filename it is given. */ + const gchar *type_mappings[20] = + { + "gif" , "image/gif", + "jpg" , "image/jpeg", + "jpeg", "image/jpeg", + "tif" , "image/tiff", + "tiff", "image/tiff", + "png" , "image/png", + "g3" , "image/g3fax", + "ps" , "application/postscript", + "eps" , "application/postscript", + NULL, NULL + }; + + gchar *ext; + gint i; + + ext = find_extension (filename); + + if (!ext) + { + return g_strdup ("application/octet-stream"); + } + + i = 0; + ext += 1; + + while (type_mappings[i]) + { + if (g_ascii_strcasecmp (ext, type_mappings[i]) == 0) + { + return g_strdup (type_mappings[i + 1]); + } + + i += 2; + } + + return g_strdup_printf ("image/x-%s", ext); +} + +static void +sendmail_create_headers (FILE *mailpipe) +{ + /* create all the mail header stuff. Feel free to add your own */ + /* It is advisable to leave the X-Mailer header though, as */ + /* there is a possibility of a Gimp mail scanner/reader in the */ + /* future. It will probabaly need that header. */ + + fprintf (mailpipe, "To: %s \n", mail_info.receipt); + fprintf (mailpipe, "Subject: %s \n", mail_info.subject); + if (strlen (mail_info.from) > 0) + fprintf (mailpipe, "From: %s \n", mail_info.from); + + fprintf (mailpipe, "X-Mailer: GIMP Useless Mail plug-in %s\n", GIMP_VERSION); + + fprintf (mailpipe, "MIME-Version: 1.0\n"); + fprintf (mailpipe, "Content-type: multipart/mixed; " + "boundary=GUMP-MIME-boundary\n"); + + fprintf (mailpipe, "\n\n"); + + fprintf (mailpipe, "--GUMP-MIME-boundary\n"); + fprintf (mailpipe, "Content-type: text/plain; charset=UTF-8\n\n"); + + if (mesg_body) + fprintf (mailpipe, "%s", mesg_body); + + fprintf (mailpipe, "\n\n"); + + { + gchar *content = sendmail_content_type (mail_info.filename); + + fprintf (mailpipe, "--GUMP-MIME-boundary\n"); + fprintf (mailpipe, "Content-type: %s\n", content); + fprintf (mailpipe, "Content-transfer-encoding: base64\n"); + fprintf (mailpipe, "Content-disposition: attachment; filename=\"%s\"\n", + mail_info.filename); + fprintf (mailpipe, "Content-description: %s\n\n", mail_info.filename); + + g_free (content); + } +} + +static gboolean +sendmail_to64 (const gchar *filename, + FILE *outfile, + GError **error) +{ + GMappedFile *infile; + const guchar *in; + gchar out[2048]; + gint state = 0; + gint save = 0; + gsize len; + gsize bytes; + gsize c; + + infile = g_mapped_file_new (filename, FALSE, error); + if (! infile) + return FALSE; + + in = (const guchar *) g_mapped_file_get_contents (infile); + len = g_mapped_file_get_length (infile); + + for (c = 0; c < len;) + { + gsize step = MIN (1024, len - c); + + bytes = g_base64_encode_step (in + c, step, TRUE, out, &state, &save); + fwrite (out, 1, bytes, outfile); + + c += step; + } + + bytes = g_base64_encode_close (TRUE, out, &state, &save); + fwrite (out, 1, bytes, outfile); + + g_mapped_file_unref (infile); + + return TRUE; +} + +static FILE * +sendmail_pipe (gchar **cmd, + GPid *pid) +{ + gint fd; + GError *err = NULL; + + if (! g_spawn_async_with_pipes (NULL, cmd, NULL, + G_SPAWN_DO_NOT_REAP_CHILD | + G_SPAWN_SEARCH_PATH, + NULL, NULL, pid, &fd, NULL, NULL, &err)) + { + g_message (_("Could not start sendmail (%s)"), err->message); + g_error_free (err); + + *pid = -1; + return NULL; + } + + return fdopen (fd, "wb"); +} +#endif diff --git a/plug-ins/common/max-rgb.c b/plug-ins/common/max-rgb.c new file mode 100644 index 0000000..1261548 --- /dev/null +++ b/plug-ins/common/max-rgb.c @@ -0,0 +1,373 @@ +/* max_rgb.c -- This is a plug-in for GIMP + * Author: Shuji Narazaki + * Time-stamp: <2000-02-08 16:26:24 yasuhiro> + * Version: 0.35 + * + * Copyright (C) 1997 Shuji Narazaki + * + * May 2000 - tim copperfield [timecop@japan.co.jp] + * + * Added a preview mode. After adding preview mode realised just exactly + * how useless this plugin is :) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-max-rgb" +#define PLUG_IN_BINARY "max-rgb" +#define PLUG_IN_ROLE "gimp-max-rgb" + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static GimpPDBStatusType main_function (GimpDrawable *drawable, + GimpPreview *preview); + +static gint max_rgb_dialog (GimpDrawable *drawable); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +enum +{ + MIN_CHANNELS = 0, + MAX_CHANNELS = 1 +}; + +typedef struct +{ + gint max_p; +} ValueType; + +static ValueType pvals = +{ + MAX_CHANNELS +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args [] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (not used)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "max-p", "{ MINIMIZE (0), MAXIMIZE (1) }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Reduce image to pure red, green, and blue"), + "There's no help yet.", + "Shuji Narazaki (narazaki@InetQ.or.jp)", + "Shuji Narazaki", + "May 2000", + N_("Maxim_um RGB..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + GimpDrawable *drawable; + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_EXECUTION_ERROR; + GimpRunMode run_mode; + + run_mode = param[0].data.d_int32; + drawable = gimp_drawable_get (param[2].data.d_drawable); + + INIT_I18N (); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &pvals); + /* Since a channel might be selected, we must check whether RGB or not. */ + if (!gimp_drawable_is_rgb (drawable->drawable_id)) + { + g_message (_("Can only operate on RGB drawables.")); + return; + } + if (! max_rgb_dialog (drawable)) + return; + break; + case GIMP_RUN_NONINTERACTIVE: + /* You must copy the values of parameters to pvals or dialog variables. */ + pvals.max_p = param[3].data.d_int32; + break; + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &pvals); + break; + } + + status = main_function (drawable, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + if (run_mode == GIMP_RUN_INTERACTIVE && status == GIMP_PDB_SUCCESS) + gimp_set_data (PLUG_IN_PROC, &pvals, sizeof (ValueType)); + + values[0].data.d_status = status; +} + +typedef struct +{ + gint init_value; + gint flag; + gboolean has_alpha; +} MaxRgbParam_t; + +static void +max_rgb_func (const guchar *src, + guchar *dest, + gint bpp, + gpointer data) +{ + MaxRgbParam_t *param = (MaxRgbParam_t*) data; + gint ch, max_ch = 0; + guchar max, tmp_value; + + max = param->init_value; + for (ch = 0; ch < 3; ch++) + if (param->flag * max <= param->flag * (tmp_value = (*src++))) + { + if (max == tmp_value) + { + max_ch += 1 << ch; + } + else + { + max_ch = 1 << ch; /* clear memories of old channels */ + max = tmp_value; + } + } + + dest[0] = (max_ch & (1 << 0)) ? max : 0; + dest[1] = (max_ch & (1 << 1)) ? max : 0; + dest[2] = (max_ch & (1 << 2)) ? max : 0; + if (param->has_alpha) + dest[3] = *src; +} + +static GimpPDBStatusType +main_function (GimpDrawable *drawable, + GimpPreview *preview) +{ + MaxRgbParam_t param; + + param.init_value = (pvals.max_p > 0) ? 0 : 255; + param.flag = (0 < pvals.max_p) ? 1 : -1; + param.has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + if (preview) + { + gint i; + guchar *buffer; + guchar *src; + gint width, height, bpp; + + src = gimp_zoom_preview_get_source (GIMP_ZOOM_PREVIEW (preview), + &width, &height, &bpp); + + buffer = g_new (guchar, width * height * bpp); + + for (i = 0; i < width * height; i++) + { + max_rgb_func (src + i * bpp, + buffer + i * bpp, + bpp, + ¶m); + } + + gimp_preview_draw_buffer (preview, buffer, width * bpp); + g_free (buffer); + g_free (src); + } + else + { + GimpPixelRgn srcPR, destPR; + gint x1, y1, x2, y2; + gpointer pr; + gint total_area; + gint area_so_far; + gint count; + + gimp_progress_init (_("Max RGB")); + + gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); + + total_area = (x2 - x1) * (y2 - y1); + area_so_far = 0; + + if (total_area <= 0) + goto out; + + /* Initialize the pixel regions. */ + gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, (x2 - x1), (y2 - y1), + FALSE, FALSE); + gimp_pixel_rgn_init (&destPR, drawable, x1, y1, (x2 - x1), (y2 - y1), + TRUE, TRUE); + + for (pr = gimp_pixel_rgns_register (2, &srcPR, &destPR), count = 0; + pr != NULL; + pr = gimp_pixel_rgns_process (pr), count++) + { + const guchar *src = srcPR.data; + guchar *dest = destPR.data; + gint row; + + for (row = 0; row < srcPR.h; row++) + { + const guchar *s = src; + guchar *d = dest; + gint pixels = srcPR.w; + + while (pixels--) + { + max_rgb_func (s, d, srcPR.bpp, ¶m); + + s += srcPR.bpp; + d += destPR.bpp; + } + + src += srcPR.rowstride; + dest += destPR.rowstride; + } + + area_so_far += srcPR.w * srcPR.h; + + if ((count % 16) == 0) + gimp_progress_update ((gdouble) area_so_far / (gdouble) total_area); + } + + /* update the processed region */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1), (y2 - y1)); + + out: + gimp_drawable_detach (drawable); + } + + return GIMP_PDB_SUCCESS; +} + + +/* dialog stuff */ +static gint +max_rgb_dialog (GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *frame; + GtkWidget *max; + GtkWidget *min; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Maximum RGB Value"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_zoom_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (main_function), + drawable); + + frame = gimp_int_radio_group_new (FALSE, NULL, + G_CALLBACK (gimp_radio_button_update), + &pvals.max_p, pvals.max_p, + + _("_Hold the maximal channels"), + MAX_CHANNELS, &max, + + _("Ho_ld the minimal channels"), + MIN_CHANNELS, &min, + + NULL); + + g_signal_connect_swapped (max, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (min, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/mkgen.pl b/plug-ins/common/mkgen.pl new file mode 100755 index 0000000..1e95284 --- /dev/null +++ b/plug-ins/common/mkgen.pl @@ -0,0 +1,234 @@ +#!/usr/bin/perl -w + +use lib '../../pdb'; + +require 'util.pl'; + +*write_file = \&Gimp::CodeGen::util::write_file; +*FILE_EXT = \$Gimp::CodeGen::util::FILE_EXT; + +$destdir = "."; +$builddir = "."; + +$ignorefile = ".gitignore"; +$rcfile = "gimprc.common"; + +$outmk = "$builddir/Makefile.am$FILE_EXT"; +$outignore = "$builddir/$ignorefile$FILE_EXT"; +$outrc = "$builddir/$rcfile$FILE_EXT"; + +open MK, "> $outmk"; +open IGNORE, "> $outignore"; +open RC, "> $outrc"; + +require './plugin-defs.pl'; + +$bins = ""; $opts = ""; $dirs = ""; + +foreach (sort keys %plugins) { + my $makename = $_; + $makename =~ s/-/_/g; + + if (exists $plugins{$_}->{optional}) { + $bins .= "${makename}_libexec_PROGRAMS = \$(\U$makename\E)\n"; + $opts .= "\t$_ \\\n"; + } + else { + $bins .= "${makename}_libexec_PROGRAMS = $_\n"; + } + + $dirs .= "${makename}_libexecdir = \$(gimpplugindir)/plug-ins/$_\n"; +} + +$extra = ""; +foreach (@extra) { $extra .= "\t$_\t\\\n" } +if ($extra) { + $extra =~ s/\t\\\n$//s; + $extra = "\t\\\n$extra"; +} + +foreach ($bins, $opts) { s/ \\\n$//s } + +print MK <{ui}) { + $libgimp .= "\$(libgimpui)"; + $libgimp .= "\t\t\\\n\t\$(libgimpwidgets)"; + $libgimp .= "\t\\\n\t\$(libgimpmodule)"; + $libgimp .= "\t\\\n\t"; + } + + $libgimp .= "\$(libgimp)"; + $libgimp .= "\t\t\\\n\t\$(libgimpmath)"; + $libgimp .= "\t\t\\\n\t\$(libgimpconfig)"; + $libgimp .= "\t\\\n\t\$(libgimpcolor)"; + $libgimp .= "\t\t\\\n\t\$(libgimpbase)"; + + my $glib; + if (exists $plugins{$_}->{ui}) { + $glib = "\$(GTK_LIBS)\t\t\\"; + } else { + $glib = "\$(CAIRO_LIBS)\t\t\\\n\t\$(GDK_PIXBUF_LIBS)\t\\"; + + if (exists $plugins{$_}->{gio} && + ! exists $plugins{$_}->{gegl}) { + $glib .= "\n\t\$(GIO_LIBS)\t\t\\"; + } + } + + if (exists $plugins{$_}->{gegl}) { + $glib .= "\n\t\$(GEGL_LIBS)\t\t\\"; + } + + my $optlib = ""; + + if (exists $plugins{$_}->{libs}) { + $optlib = "\n\t\$(" . $plugins{$_}->{libs} . ")\t\t\\"; + } + + if (exists $plugins{$_}->{ldflags}) { + my $ldflags = $plugins{$_}->{ldflags}; + + print MK <{cflags}) { + my $cflags = $plugins{$_}->{cflags}; + my $cflagsvalue = $cflags =~ /FLAGS/ ? "\$($cflags)" : $cflags; + + print MK <{cppflags}) { + my $cppflags = $plugins{$_}->{cppflags}; + + print MK <{libdep}) { + my @lib = split(/:/, $plugins{$_}->{libdep}); + foreach $lib (@lib) { + $deplib = "\$(\U$lib\E_LIBS)\t\t\\\n\t$deplib"; + } + } + + my $rclib = "\$(${makename}_RC)"; + + print MK < + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-nlfilt" +#define PLUG_IN_BINARY "nl-filter" +#define PLUG_IN_ROLE "gimp-nl-filter" + + +typedef struct +{ + gdouble alpha; + gdouble radius; + gint filter; +} NLFilterValues; + +typedef enum +{ + filter_alpha_trim, + filter_opt_est, + filter_edge_enhance +} FilterType; + +static NLFilterValues nlfvals = +{ + 0.3, + 0.3, + 0 +}; + +/* function protos */ + +static void query (void); +static void run (const gchar *name, + gint nparam, + const GimpParam *param, + gint *nretvals, + GimpParam **retvals); + +static void nlfilter (gint32 drawable_id, + GimpPreview *preview); +static void nlfilter_preview (gpointer drawable_id, + GimpPreview *preview); + +static gboolean nlfilter_dialog (gint32 drawable_id); + +static gint nlfiltInit (gdouble alpha, + gdouble radius, + FilterType filter); + +static void nlfiltRow (guchar *srclast, + guchar *srcthis, + guchar *srcnext, + guchar *dst, + gint width, + gint bpp, + gint filtno); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "img", "The Image to Filter" }, + { GIMP_PDB_DRAWABLE, "drw", "The Drawable" }, + { GIMP_PDB_FLOAT, "alpha", "The amount of the filter to apply" }, + { GIMP_PDB_FLOAT, "radius", "The filter radius" }, + { GIMP_PDB_INT32, "filter", "The Filter to Run, " + "0 - alpha trimmed mean; " + "1 - optimal estimation (alpha controls noise variance); " + "2 - edge enhancement" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Nonlinear swiss army knife filter"), + "This is the pnmnlfilt, in gimp's clothing. " + "See the pnmnlfilt manpage for details.", + "Graeme W. Gill, gimp 0.99 plug-in by Eric L. Hernes", + "Graeme W. Gill, Eric L. Hernes", + "1997", + N_("_NL Filter..."), + "RGB,GRAY", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Enhance"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + gint32 drawable_id; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + drawable_id = param[2].data.d_drawable; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &nlfvals); + + if (! nlfilter_dialog (drawable_id)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + nlfvals.alpha = param[3].data.d_float; + nlfvals.radius = param[4].data.d_float; + nlfvals.filter = param[5].data.d_int32; + } + + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &nlfvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + nlfilter (drawable_id, NULL); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &nlfvals, sizeof (NLFilterValues)); + } + + values[0].data.d_status = status; +} + +/* pnmnlfilt.c - 4 in 1 (2 non-linear) filter +** - smooth an anyimage +** - do alpha trimmed mean filtering on an anyimage +** - do optimal estimation smoothing on an anyimage +** - do edge enhancement on an anyimage +** +** Version 1.0 +** +** The implementation of an alpha-trimmed mean filter +** is based on the description in IEEE CG&A May 1990 +** Page 23 by Mark E. Lee and Richard A. Redner. +** +** The paper recommends using a hexagon sampling region around each +** pixel being processed, allowing an effective sub pixel radius to be +** specified. The hexagon values are sythesised by area sampling the +** rectangular pixels with a hexagon grid. The seven hexagon values +** obtained from the 3x3 pixel grid are used to compute the alpha +** trimmed mean. Note that an alpha value of 0.0 gives a conventional +** mean filter (where the radius controls the contribution of +** surrounding pixels), while a value of 0.5 gives a median filter. +** Although there are only seven values to trim from before finding +** the mean, the algorithm has been extended from that described in +** CG&A by using interpolation, to allow a continuous selection of +** alpha value between and including 0.0 to 0.5 The useful values +** for radius are between 0.3333333 (where the filter will have no +** effect because only one pixel is sampled), to 1.0, where all +** pixels in the 3x3 grid are sampled. +** +** The optimal estimation filter is taken from an article "Converting Dithered +** Images Back to Gray Scale" by Allen Stenger, Dr Dobb's Journal, November +** 1992, and this article references "Digital Image Enhancement andNoise Filtering by +** Use of Local Statistics", Jong-Sen Lee, IEEE Transactions on Pattern Analysis and +** Machine Intelligence, March 1980. +** +** Also borrow the technique used in pgmenhance(1) to allow edge +** enhancement if the alpha value is negative. +** +** Author: +** Graeme W. Gill, 30th Jan 1993 +** graeme@labtam.oz.au +** +** Permission is hereby granted, to use, copy, modify, distribute, +** and sell this software and its associated documentation files +** (the "Software") for any purpose without fee, provided +** that: +** +** 1) The above copyright notices and this permission notice +** accompany all source code copies of the Software and +** related documentation. +** and +** +** 2) If executable code based on the Software only is distributed, +** then the accompanying documentation must acknowledge that +** "this software is based in part on the work of Graeme W. Gill". +** and +** +** 3) It is accepted that Graeme W. Gill (the "Author") accepts +** NO LIABILITY for damages of any kind. The Software is +** provided without fee by the Author "AS-IS" and without +** warranty of any kind, express, implied or otherwise, +** including without limitation, any warranty of merchantability +** or fitness for a particular purpose. +** and +** +** 4) These conditions apply to any software derived from or based +** on the Software, not just to the unmodified library. +** +*/ + +/* ************************************************** */ +/* Hexagon intersecting square area functions */ +/* Compute the area of the intersection of a triangle */ +/* and a rectangle */ + +static gdouble triang_area(gdouble, gdouble, gdouble, gdouble, gdouble, + gdouble, gdouble, gdouble, gint); +static gdouble rectang_area(gdouble, gdouble, gdouble, gdouble, + gdouble, gdouble, gdouble, gdouble); +static gdouble hex_area(gdouble, gdouble, gdouble, gdouble, gdouble); + +static gint atfilt0 (gint *p); +static gint atfilt1 (gint *p); +static gint atfilt2 (gint *p); +static gint atfilt3 (gint *p); +static gint atfilt4 (gint *p); +static gint atfilt5 (gint *p); + +gint (*atfuncs[6])(gint *) = +{ + atfilt0, + atfilt1, + atfilt2, + atfilt3, + atfilt4, + atfilt5 +}; + +static gint noisevariance; + +#define MXIVAL 255 /* maximum input value */ +#define NOIVAL (MXIVAL + 1) /* number of possible input values */ + +#define SCALEB 8 /* scale bits */ +#define SCALE (1 << SCALEB) /* scale factor */ + +#define CSCALEB 2 /* coarse scale bits */ +#define CSCALE (1 << CSCALEB) /* coarse scale factor */ +#define MXCSVAL (MXIVAL * CSCALE) /* maximum coarse scaled values */ +#define NOCSVAL (MXCSVAL + 1) /* number of coarse scaled values */ +#define SCTOCSC(x) ((x) >> (SCALEB - CSCALEB)) /* convert from scaled to coarse scaled */ +#define CSCTOSC(x) ((x) << (SCALEB - CSCALEB)) /* convert from course scaled to scaled */ + +/* round and scale floating point to scaled integer */ +#define SROUND(x) ((gint)(((x) * (gdouble)SCALE) + 0.5)) +/* round and un-scale scaled integer value */ +#define RUNSCALE(x) (((x) + (1 << (SCALEB-1))) >> SCALEB) /* rounded un-scale */ +#define UNSCALE(x) ((x) >> SCALEB) + +/* Note: modified by David Hodson, nlfiltRow now accesses + * srclast, srcthis, and srcnext from [-bpp] to [width*bpp-1]. + * Beware if you use this code anywhere else! + */ +static void +nlfiltRow (guchar *srclast, guchar *srcthis, guchar *srcnext, guchar *dst, + gint width, gint bpp, gint filtno) +{ + gint pf[9]; + guchar *ip0, *ip1, *ip2, *or, *orend; + + orend = dst + width * bpp; + ip0 = srclast; + ip1 = srcthis; + ip2 = srcnext; + + for (or = dst; or < orend; ip0++, ip1++, ip2++, or++) + { + pf[0] = *ip1; + pf[1] = *(ip1 - bpp); + pf[2] = *(ip2 - bpp); + pf[3] = *(ip2); + pf[4] = *(ip2 + bpp); + pf[5] = *(ip1 + bpp); + pf[6] = *(ip0 + bpp); + pf[7] = *(ip0); + pf[8] = *(ip0 - bpp); + *or=(atfuncs[filtno])(pf); + } +} + +/* We restrict radius to the values: 0.333333 <= radius <= 1.0 */ +/* so that no fewer and no more than a 3x3 grid of pixels around */ +/* the pixel in question needs to be read. Given this, we only */ +/* need 3 or 4 weightings per hexagon, as follows: */ +/* _ _ */ +/* Virtical hex: |_|_| 1 2 */ +/* |X|_| 0 3 */ +/* _ */ +/* _ _|_| 1 */ +/* Middle hex: |_| 1 Horizontal hex: |X|_| 0 2 */ +/* |X| 0 |_| 3 */ +/* |_| 2 */ + +/* all filters */ +gint V0[NOIVAL],V1[NOIVAL],V2[NOIVAL],V3[NOIVAL]; /* vertical hex */ +gint M0[NOIVAL],M1[NOIVAL],M2[NOIVAL]; /* middle hex */ +gint H0[NOIVAL],H1[NOIVAL],H2[NOIVAL],H3[NOIVAL]; /* horizontal hex */ + +/* alpha trimmed and edge enhancement only */ +gint ALFRAC[NOIVAL * 8]; /* fractional alpha divider table */ + +/* optimal estimation only */ +gint AVEDIV[7 * NOCSVAL]; /* divide by 7 to give average value */ +gint SQUARE[2 * NOCSVAL]; /* scaled square lookup table */ + +/* Table initialisation function - return alpha range */ +static gint +nlfiltInit (gdouble alpha, gdouble radius, FilterType filter) +{ + gint alpharange; /* alpha range value 0 - 3 */ + gdouble meanscale; /* scale for finding mean */ + gdouble mmeanscale; /* scale for finding mean - midle hex */ + gdouble alphafraction; /* fraction of next largest/smallest + * to subtract from sum + */ + switch (filter) + { + case filter_alpha_trim: + { + gdouble noinmean; + /* alpha only makes sense in range 0.0 - 0.5 */ + alpha /= 2.0; + /* number of elements (out of a possible 7) used in the mean */ + noinmean = ((0.5 - alpha) * 12.0) + 1.0; + mmeanscale = meanscale = 1.0/noinmean; + if (alpha == 0.0) { /* mean filter */ + alpharange = 0; + alphafraction = 0.0; /* not used */ + } else if (alpha < (1.0/6.0)) { /* mean of 5 to 7 middle values */ + alpharange = 1; + alphafraction = (7.0 - noinmean)/2.0; + } else if (alpha < (1.0/3.0)) { /* mean of 3 to 5 middle values */ + alpharange = 2; + alphafraction = (5.0 - noinmean)/2.0; + } else { /* mean of 1 to 3 middle values */ + /* alpha==0.5 => median filter */ + alpharange = 3; + alphafraction = (3.0 - noinmean)/2.0; + } + } + break; + case filter_opt_est: { + gint i; + gdouble noinmean = 7.0; + + /* edge enhancement function */ + alpharange = 5; + + /* compute scaled hex values */ + mmeanscale=meanscale=1.0; + + /* Set up 1:1 division lookup - not used */ + alphafraction=1.0/noinmean; + + /* estimate of noise variance */ + noisevariance = alpha * (gdouble)255; + noisevariance = noisevariance * noisevariance / 8.0; + + /* set yp optimal estimation specific stuff */ + + for (i=0;i<(7*NOCSVAL);i++) { /* divide scaled value by 7 lookup */ + AVEDIV[i] = CSCTOSC(i)/7; /* scaled divide by 7 */ + } + /* compute square and rescale by + * (val >> (2 * SCALEB + 2)) table + */ + for (i=0;i<(2*NOCSVAL);i++) { + gint val; + /* NOCSVAL offset to cope with -ve input values */ + val = CSCTOSC(i - NOCSVAL); + SQUARE[i] = (val * val) >> (2 * SCALEB + 2); + } + } + break; + case filter_edge_enhance: { + if (alpha == 1.0) alpha = 0.99; + alpharange = 4; + /* mean of 7 and scaled by -alpha/(1-alpha) */ + meanscale = 1.0 * (-alpha/((1.0 - alpha) * 7.0)); + + /* middle pixel has 1/(1-alpha) as well */ + mmeanscale = 1.0 * (1.0/(1.0 - alpha) + meanscale); + alphafraction = 0.0; /* not used */ + } + break; + default: + g_printerr ("unknown filter %d\n", filter); + return -1; + } + /* + * Setup pixel weighting tables - + * note we pre-compute mean division here too. + */ + { + gint i; + gdouble hexhoff,hexvoff; + gdouble tabscale,mtabscale; + gdouble v0,v1,v2,v3,m0,m1,m2,h0,h1,h2,h3; + + /* horizontal offset of virtical hex centers */ + hexhoff = radius/2; + + /* vertical offset of virtical hex centers */ + hexvoff = 3.0 * radius/sqrt(12.0); + + /* + * scale tables to normalise by hexagon + * area, and number of hexes used in mean + */ + tabscale = meanscale / (radius * hexvoff); + mtabscale = mmeanscale / (radius * hexvoff); + v0 = hex_area(0.0,0.0,hexhoff,hexvoff,radius) * tabscale; + v1 = hex_area(0.0,1.0,hexhoff,hexvoff,radius) * tabscale; + v2 = hex_area(1.0,1.0,hexhoff,hexvoff,radius) * tabscale; + v3 = hex_area(1.0,0.0,hexhoff,hexvoff,radius) * tabscale; + m0 = hex_area(0.0,0.0,0.0,0.0,radius) * mtabscale; + m1 = hex_area(0.0,1.0,0.0,0.0,radius) * mtabscale; + m2 = hex_area(0.0,-1.0,0.0,0.0,radius) * mtabscale; + h0 = hex_area(0.0,0.0,radius,0.0,radius) * tabscale; + h1 = hex_area(1.0,1.0,radius,0.0,radius) * tabscale; + h2 = hex_area(1.0,0.0,radius,0.0,radius) * tabscale; + h3 = hex_area(1.0,-1.0,radius,0.0,radius) * tabscale; + + for (i=0; i <= MXIVAL; i++) { + gdouble fi; + fi = (gdouble)i; + V0[i] = SROUND(fi * v0); + V1[i] = SROUND(fi * v1); + V2[i] = SROUND(fi * v2); + V3[i] = SROUND(fi * v3); + M0[i] = SROUND(fi * m0); + M1[i] = SROUND(fi * m1); + M2[i] = SROUND(fi * m2); + H0[i] = SROUND(fi * h0); + H1[i] = SROUND(fi * h1); + H2[i] = SROUND(fi * h2); + H3[i] = SROUND(fi * h3); + } + /* set up alpha fraction lookup table used on big/small */ + for (i=0; i < (NOIVAL * 8); i++) { + ALFRAC[i] = SROUND((gdouble)i * alphafraction); + } + } + return alpharange; +} + +/* Core pixel processing function - hand it 3x3 pixels and return result. */ +/* Mean filter */ +static gint +atfilt0(gint32 *p) +{ + gint retv; + /* map to scaled hexagon values */ + retv = M0[p[0]] + M1[p[3]] + M2[p[7]]; + retv += H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]]; + retv += V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]]; + retv += V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]]; + retv += H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]]; + retv += V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]]; + retv += V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]]; + return UNSCALE(retv); +} + +/* Mean of 5 - 7 middle values */ +static gint +atfilt1 (gint32 *p) +{ + gint h0,h1,h2,h3,h4,h5,h6; /* hexagon values 2 3 */ + /* 1 0 4 */ + /* 6 5 */ + gint big,small; + /* map to scaled hexagon values */ + h0 = M0[p[0]] + M1[p[3]] + M2[p[7]]; + h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]]; + h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]]; + h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]]; + h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]]; + h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]]; + h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]]; + /* sum values and also discover the largest and smallest */ + big = small = h0; +#define CHECK(xx) \ + h0 += xx; \ + if (xx > big) \ + big = xx; \ + else if (xx < small) \ + small = xx; + CHECK(h1) + CHECK(h2) + CHECK(h3) + CHECK(h4) + CHECK(h5) + CHECK(h6) +#undef CHECK + /* Compute mean of middle 5-7 values */ + return UNSCALE(h0 -ALFRAC[(big + small)>>SCALEB]); +} + +/* Mean of 3 - 5 middle values */ +static gint +atfilt2 (gint32 *p) +{ + gint h0,h1,h2,h3,h4,h5,h6; /* hexagon values 2 3 */ + /* 1 0 4 */ + /* 6 5 */ + gint big0,big1,small0,small1; + /* map to scaled hexagon values */ + h0 = M0[p[0]] + M1[p[3]] + M2[p[7]]; + h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]]; + h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]]; + h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]]; + h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]]; + h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]]; + h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]]; + /* sum values and also discover the 2 largest and 2 smallest */ + big0 = small0 = h0; + small1 = G_MAXINT; + big1 = 0; +#define CHECK(xx) \ + h0 += xx; \ + if (xx > big1) \ + { \ + if (xx > big0) \ + { \ + big1 = big0; \ + big0 = xx; \ + } \ + else \ + big1 = xx; \ + } \ + if (xx < small1) \ + { \ + if (xx < small0) \ + { \ + small1 = small0; \ + small0 = xx; \ + } \ + else \ + small1 = xx; \ + } + CHECK(h1) + CHECK(h2) + CHECK(h3) + CHECK(h4) + CHECK(h5) + CHECK(h6) +#undef CHECK + /* Compute mean of middle 3-5 values */ + return UNSCALE(h0 -big0 -small0 -ALFRAC[(big1 + small1)>>SCALEB]); +} + +/* + * Mean of 1 - 3 middle values. + * If only 1 value, then this is a median filter. + */ +static gint32 +atfilt3(gint32 *p) +{ + gint h0,h1,h2,h3,h4,h5,h6; /* hexagon values 2 3 */ + /* 1 0 4 */ + /* 6 5 */ + gint big0,big1,big2,small0,small1,small2; + /* map to scaled hexagon values */ + h0 = M0[p[0]] + M1[p[3]] + M2[p[7]]; + h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]]; + h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]]; + h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]]; + h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]]; + h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]]; + h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]]; + /* sum values and also discover the 3 largest and 3 smallest */ + big0 = small0 = h0; + small1 = small2 = G_MAXINT; + big1 = big2 = 0; +#define CHECK(xx) \ + h0 += xx; \ + if (xx > big2) \ + { \ + if (xx > big1) \ + { \ + if (xx > big0) \ + { \ + big2 = big1; \ + big1 = big0; \ + big0 = xx; \ + } \ + else \ + { \ + big2 = big1; \ + big1 = xx; \ + } \ + } \ + else \ + big2 = xx; \ + } \ + if (xx < small2) \ + { \ + if (xx < small1) \ + { \ + if (xx < small0) \ + { \ + small2 = small1; \ + small1 = small0; \ + small0 = xx; \ + } \ + else \ + { \ + small2 = small1; \ + small1 = xx; \ + } \ + } \ + else \ + small2 = xx; \ + } + CHECK(h1) + CHECK(h2) + CHECK(h3) + CHECK(h4) + CHECK(h5) + CHECK(h6) +#undef CHECK + /* Compute mean of middle 1-3 values */ + return UNSCALE(h0-big0-big1-small0-small1-ALFRAC[(big2+small2)>>SCALEB]); +} + +/* Edge enhancement */ +static gint +atfilt4 (gint *p) +{ + gint hav; + /* map to scaled hexagon values and compute enhance value */ + hav = M0[p[0]] + M1[p[3]] + M2[p[7]]; + hav += H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]]; + hav += V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]]; + hav += V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]]; + hav += H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]]; + hav += V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]]; + hav += V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]]; + if (hav < 0) + hav = 0; + hav = UNSCALE(hav); + if (hav > (gdouble)255) + hav = (gdouble)255; + return hav; +} + +/* Optimal estimation - do smoothing in inverse proportion */ +/* to the local variance. */ +/* notice we use the globals noisevariance */ +gint +atfilt5(gint *p) { + gint mean,variance,temp; + gint h0,h1,h2,h3,h4,h5,h6; /* hexagon values 2 3 */ + /* 1 0 4 */ + /* 6 5 */ + /* map to scaled hexagon values */ + h0 = M0[p[0]] + M1[p[3]] + M2[p[7]]; + h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]]; + h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]]; + h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]]; + h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]]; + h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]]; + h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]]; + mean = h0 + h1 + h2 + h3 + h4 + h5 + h6; + /* compute scaled mean by dividing by 7 */ + mean = AVEDIV[SCTOCSC(mean)]; + + /* compute scaled variance */ + temp = (h1 - mean); variance = SQUARE[NOCSVAL + SCTOCSC(temp)]; + + /* and rescale to keep */ + temp = (h2 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; + + /* within 32 bit limits */ + temp = (h3 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; + temp = (h4 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; + temp = (h5 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; + temp = (h6 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; + /* (temp = h0 - mean) */ + temp = (h0 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; + if (variance != 0) /* avoid possible divide by 0 */ + /* optimal estimate */ + temp = mean + (variance * temp) / (variance + noisevariance); + else temp = h0; + if (temp < 0) + temp = 0; + temp = RUNSCALE(temp); + if (temp > (gdouble)255) temp = (gdouble)255; + return temp; +} + + +/* Triangle orientation is per geometric axes (not graphical axies) */ + +#define NW 0 /* North west triangle /| */ +#define NE 1 /* North east triangle |\ */ +#define SW 2 /* South west triangle \| */ +#define SE 3 /* South east triangle |/ */ +#define STH 2 +#define EST 1 + +#define SWAPI(a,b) (t = a, a = -b, b = -t) + +/* compute the area of overlap of a hexagon diameter d, */ +/* centered at hx,hy, with a unit square of center sx,sy. */ +static gdouble +hex_area (gdouble sx, gdouble sy, gdouble hx, gdouble hy, gdouble d) +{ + gdouble hx0,hx1,hx2,hy0,hy1,hy2,hy3; + gdouble sx0,sx1,sy0,sy1; + + /* compute square co-ordinates */ + sx0 = sx - 0.5; + sy0 = sy - 0.5; + sx1 = sx + 0.5; + sy1 = sy + 0.5; + /* compute hexagon co-ordinates */ + hx0 = hx - d/2.0; + hx1 = hx; + hx2 = hx + d/2.0; + hy0 = hy - 0.5773502692 * d; /* d / sqrt(3) */ + hy1 = hy - 0.2886751346 * d; /* d / sqrt(12) */ + hy2 = hy + 0.2886751346 * d; /* d / sqrt(12) */ + hy3 = hy + 0.5773502692 * d; /* d / sqrt(3) */ + + return + triang_area(sx0,sy0,sx1,sy1,hx0,hy2,hx1,hy3,NW) + + triang_area(sx0,sy0,sx1,sy1,hx1,hy2,hx2,hy3,NE) + + rectang_area(sx0,sy0,sx1,sy1,hx0,hy1,hx2,hy2) + + triang_area(sx0,sy0,sx1,sy1,hx0,hy0,hx1,hy1,SW) + + triang_area(sx0,sy0,sx1,sy1,hx1,hy0,hx2,hy1,SE); +} + +static gdouble +triang_area (gdouble rx0, gdouble ry0, gdouble rx1, gdouble ry1, gdouble tx0, + gdouble ty0, gdouble tx1, gdouble ty1, gint tt) +{ + gdouble a,b,c,d; + gdouble lx0,ly0,lx1,ly1; + /* Convert everything to a NW triangle */ + if (tt & STH) { + gdouble t; + SWAPI(ry0,ry1); + SWAPI(ty0,ty1); + } if (tt & EST) { + gdouble t; + SWAPI(rx0,rx1); + SWAPI(tx0,tx1); + } + /* Compute overlapping box */ + if (tx0 > rx0) + rx0 = tx0; + if (ty0 > ry0) + ry0 = ty0; + if (tx1 < rx1) + rx1 = tx1; + if (ty1 < ry1) + ry1 = ty1; + if (rx1 <= rx0 || ry1 <= ry0) + return 0.0; + /* Need to compute diagonal line intersection with the box */ + /* First compute co-efficients to formulas x = a + by and y = c + dx */ + b = (tx1 - tx0)/(ty1 - ty0); + a = tx0 - b * ty0; + d = (ty1 - ty0)/(tx1 - tx0); + c = ty0 - d * tx0; + + /* compute top or right intersection */ + tt = 0; + ly1 = ry1; + lx1 = a + b * ly1; + if (lx1 <= rx0) + return (rx1 - rx0) * (ry1 - ry0); + else if (lx1 > rx1) { /* could be right hand side */ + lx1 = rx1; + ly1 = c + d * lx1; + if (ly1 <= ry0) + return (rx1 - rx0) * (ry1 - ry0); + tt = 1; /* right hand side intersection */ + } + /* compute left or bottom intersection */ + lx0 = rx0; + ly0 = c + d * lx0; + if (ly0 >= ry1) + return (rx1 - rx0) * (ry1 - ry0); + else if (ly0 < ry0) { /* could be right hand side */ + ly0 = ry0; + lx0 = a + b * ly0; + if (lx0 >= rx1) + return (rx1 - rx0) * (ry1 - ry0); + tt |= 2; /* bottom intersection */ + } + + if (tt == 0) { /* top and left intersection */ + /* rectangle minus triangle */ + return ((rx1 - rx0) * (ry1 - ry0)) + - (0.5 * (lx1 - rx0) * (ry1 - ly0)); + } + else if (tt == 1) { /* right and left intersection */ + return ((rx1 - rx0) * (ly0 - ry0)) + + (0.5 * (rx1 - rx0) * (ly1 - ly0)); + } else if (tt == 2) { /* top and bottom intersection */ + return ((rx1 - lx1) * (ry1 - ry0)) + + (0.5 * (lx1 - lx0) * (ry1 - ry0)); + } else { /* tt == 3 */ /* right and bottom intersection */ + /* triangle */ + return (0.5 * (rx1 - lx0) * (ly1 - ry0)); + } +} + +/* Compute rectangle area */ +static gdouble +rectang_area (gdouble rx0, gdouble ry0, gdouble rx1, gdouble ry1, gdouble tx0, + gdouble ty0, gdouble tx1, gdouble ty1) +{ + /* Compute overlapping box */ + if (tx0 > rx0) + rx0 = tx0; + if (ty0 > ry0) + ry0 = ty0; + if (tx1 < rx1) + rx1 = tx1; + if (ty1 < ry1) + ry1 = ty1; + if (rx1 <= rx0 || ry1 <= ry0) + return 0.0; + return (rx1 - rx0) * (ry1 - ry0); +} + +static void +nlfilter (gint32 drawable_id, + GimpPreview *preview) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + guchar *srcbuf, *dstbuf; + guchar *lastrow, *thisrow, *nextrow, *temprow; + gint x1, y1, y2; + gint width, height, bpp; + gint filtno, y, rowsize, exrowsize, p_update; + + if (preview) + { + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &width, &height); + y2 = y1 + height; + } + else + { + if (! gimp_drawable_mask_intersect (drawable_id, + &x1, &y1, &width, &height)) + return; + + y2 = y1 + height; + } + + if (gimp_drawable_has_alpha (drawable_id)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + src_buffer = gimp_drawable_get_buffer (drawable_id); + + if (preview) + dest_buffer = gegl_buffer_new (gegl_buffer_get_extent (src_buffer), format); + else + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + bpp = babl_format_get_bytes_per_pixel (format); + + rowsize = width * bpp; + exrowsize = (width + 2) * bpp; + p_update = width / 20 + 1; + + /* source buffer gives one pixel margin all around destination buffer */ + srcbuf = g_new0 (guchar, exrowsize * 3); + dstbuf = g_new0 (guchar, rowsize); + + /* pointers to second pixel in each source row */ + lastrow = srcbuf + bpp; + thisrow = lastrow + exrowsize; + nextrow = thisrow + exrowsize; + + filtno = nlfiltInit (nlfvals.alpha, nlfvals.radius, nlfvals.filter); + + if (!preview) + gimp_progress_init (_("NL Filter")); + + /* first row */ + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y1, width, 1), 1.0, + format, thisrow, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + /* copy thisrow[0] to thisrow[-1], thisrow[width-1] to thisrow[width] */ + memcpy (thisrow - bpp, thisrow, bpp); + memcpy (thisrow + rowsize, thisrow + rowsize - bpp, bpp); + /* copy whole thisrow to lastrow */ + memcpy (lastrow - bpp, thisrow - bpp, exrowsize); + + for (y = y1; y < y2 - 1; y++) + { + if (((y % p_update) == 0) && !preview) + gimp_progress_update ((gdouble) y / (gdouble) height); + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y + 1, width, 1), 1.0, + format, nextrow, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + memcpy (nextrow - bpp, nextrow, bpp); + memcpy (nextrow + rowsize, nextrow + rowsize - bpp, bpp); + nlfiltRow (lastrow, thisrow, nextrow, dstbuf, width, bpp, filtno); + + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x1, y, width, 1), 0, + format, dstbuf, + GEGL_AUTO_ROWSTRIDE); + + /* rotate row buffers */ + temprow = lastrow; lastrow = thisrow; + thisrow = nextrow; nextrow = temprow; + } + + /* last row */ + memcpy (nextrow - bpp, thisrow - bpp, exrowsize); + nlfiltRow (lastrow, thisrow, nextrow, dstbuf, width, bpp, filtno); + + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x1, y2 - 1, width, 1), 0, + format, dstbuf, + GEGL_AUTO_ROWSTRIDE); + + g_free (srcbuf); + g_free (dstbuf); + + if (preview) + { + guchar *buf = g_new (guchar, width * height * bpp); + + gegl_buffer_get (dest_buffer, GEGL_RECTANGLE (x1, y1, width, height), 1.0, + format, buf, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + gimp_preview_draw_buffer (GIMP_PREVIEW (preview), buf, width * bpp); + + g_free (buf); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + if (! preview) + { + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, x1, y1, width, height); + gimp_displays_flush (); + } +} + +static void +nlfilter_preview (gpointer drawable_id, + GimpPreview *preview) +{ + nlfilter (GPOINTER_TO_INT (drawable_id), preview); +} + +static gboolean +nlfilter_dialog (gint32 drawable_id) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *frame; + GtkWidget *alpha_trim; + GtkWidget *opt_est; + GtkWidget *edge_enhance; + GtkWidget *table; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("NL Filter"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (nlfilter_preview), + GINT_TO_POINTER (drawable_id)); + + frame = gimp_int_radio_group_new (TRUE, _("Filter"), + G_CALLBACK (gimp_radio_button_update), + &nlfvals.filter, nlfvals.filter, + + _("_Alpha trimmed mean"), + filter_alpha_trim, &alpha_trim, + _("Op_timal estimation"), + filter_opt_est, &opt_est, + _("_Edge enhancement"), + filter_edge_enhance, &edge_enhance, + + NULL); + + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + g_signal_connect_swapped (alpha_trim, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (opt_est, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (edge_enhance, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("A_lpha:"), 0, 0, + nlfvals.alpha, 0.0, 1.0, 0.05, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &nlfvals.alpha); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Radius:"), 0, 0, + nlfvals.radius, 1.0 / 3.0, 1.0, 0.05, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &nlfvals.radius); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/photocopy.c b/plug-ins/common/photocopy.c new file mode 100644 index 0000000..5a2c7f2 --- /dev/null +++ b/plug-ins/common/photocopy.c @@ -0,0 +1,935 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Photocopy filter for GIMP for BIPS + * -Spencer Kimball + * + * This filter propagates dark values in an image based on + * each pixel's relative darkness to a neighboring average + * and sets the remaining pixels to white. + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Some useful macros */ + +#define PLUG_IN_PROC "plug-in-photocopy" +#define PLUG_IN_BINARY "photocopy" +#define PLUG_IN_ROLE "gimp-photocopy" +#define TILE_CACHE_SIZE 48 +#define GAMMA 1.0 +#define EPSILON 2 + + +typedef struct +{ + gdouble mask_radius; + gdouble sharpness; + gdouble threshold; + gdouble pct_black; + gdouble pct_white; +} PhotocopyVals; + + +/* + * Function prototypes. + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void photocopy (GimpDrawable *drawable, + GimpPreview *preview); +static gboolean photocopy_dialog (GimpDrawable *drawable); + +static gdouble compute_ramp (guchar *dest1, + guchar *dest2, + gint length, + gdouble pct_black, + gint under_threshold); + +/* + * Gaussian blur helper functions + */ +static void find_constants (gdouble n_p[], + gdouble n_m[], + gdouble d_p[], + gdouble d_m[], + gdouble bd_p[], + gdouble bd_m[], + gdouble std_dev); +static void transfer_pixels (gdouble *src1, + gdouble *src2, + guchar *dest, + gint jump, + gint width); + +/***** Local vars *****/ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init */ + NULL, /* quit */ + query, /* query */ + run, /* run */ +}; + +static PhotocopyVals pvals = +{ + 8.0, /* mask_radius */ + 0.8, /* sharpness */ + 0.75, /* threshold */ + 0.2, /* pct_black */ + 0.2 /* pct_white */ +}; + + +/***** Functions *****/ + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_FLOAT, "mask-radius", "Photocopy mask radius (radius of pixel neighborhood)" }, + { GIMP_PDB_FLOAT, "sharpness", "Sharpness (detail level) (0.0 - 1.0)" }, + { GIMP_PDB_FLOAT, "pct-black", "Percentage of darkened pixels to set to black (0.0 - 1.0)" }, + { GIMP_PDB_FLOAT, "pct-white", "Percentage of non-darkened pixels left white (0.0 - 1.0)" } + }; + + gchar *help_string = + "Propagates dark values in an image based on " + "each pixel's relative darkness to a neighboring average. The idea behind " + "this filter is to give the look of a photocopied version of the image, " + "with toner transferred based on the relative darkness of a particular " + "region. This is achieved by darkening areas of the image which are " + "measured to be darker than a neighborhood average and setting other " + "pixels to white. In this way, sufficiently large shifts in intensity " + "are darkened to black. The rate at which they are darkened to black is " + "determined by the second pct_black parameter. The mask_radius parameter " + "controls the size of the pixel neighborhood over which the average " + "intensity is computed and then compared to each pixel in the neighborhood " + "to decide whether or not to darken it to black. Large values for " + "mask_radius result in very thick black areas bordering the regions " + "of white and much less detail for black areas everywhere including " + "inside regions of color. Small values result in less toner overall " + "and more detail everywhere. Small values for the pct_black make the " + "blend from the white regions to the black border lines smoother and " + "the toner regions themselves thinner and less noticeable; larger values " + "achieve the opposite effect."; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Simulate color distortion produced by a copy machine"), + help_string, + "Spencer Kimball", + "Bit Specialists, Inc.", + "2001", + N_("_Photocopy (legacy)..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Artistic"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpDrawable *drawable; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + run_mode = param[0].data.d_int32; + + /* Get the specified drawable */ + drawable = gimp_drawable_get (param[2].data.d_drawable); + + /* set the tile cache size */ + gimp_tile_cache_ntiles (TILE_CACHE_SIZE); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + INIT_I18N(); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &pvals); + + /* First acquire information with a dialog */ + if (! photocopy_dialog (drawable)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + pvals.mask_radius = param[3].data.d_float; + pvals.sharpness = param[4].data.d_float; + pvals.pct_black = param[5].data.d_float; + pvals.pct_white = param[6].data.d_float; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &pvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Make sure that the drawable is RGB or GRAY color */ + if (gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id)) + { + gimp_progress_init ("Photocopy"); + + photocopy (drawable, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &pvals, sizeof (PhotocopyVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = _("Cannot operate on indexed color images."); + } + } + + values[0].data.d_status = status; + + gimp_drawable_detach (drawable); +} + +/* + * Photocopy algorithm + * ----------------- + * Mask radius = radius of pixel neighborhood for intensity comparison + * Threshold = relative intensity difference which will result in darkening + * Ramp = amount of relative intensity difference before total black + * Blur radius = mask radius / 3.0 + * + * Algorithm: + * For each pixel, calculate pixel intensity value to be: avg (blur radius) + * relative diff = pixel intensity / avg (mask radius) + * If relative diff < Threshold + * intensity mult = (Ramp - MIN (Ramp, (Threshold - relative diff))) / Ramp + * pixel intensity *= intensity mult + * Else + * pixel intensity = white + */ +static void +photocopy (GimpDrawable *drawable, + GimpPreview *preview) +{ + GimpPixelRgn src_rgn, dest_rgn; + GimpPixelRgn *pr; + gint x, y, width, height; + gint bytes; + gboolean has_alpha; + guchar *dest1; + guchar *dest2; + guchar *src1, *sp_p1, *sp_m1; + guchar *src2, *sp_p2, *sp_m2; + gdouble n_p1[5], n_m1[5]; + gdouble n_p2[5], n_m2[5]; + gdouble d_p1[5], d_m1[5]; + gdouble d_p2[5], d_m2[5]; + gdouble bd_p1[5], bd_m1[5]; + gdouble bd_p2[5], bd_m2[5]; + gdouble *val_p1, *val_m1, *vp1, *vm1; + gdouble *val_p2, *val_m2, *vp2, *vm2; + gint i, j; + gint row, col; + gint terms; + gint progress, max_progress; + gint initial_p1[4]; + gint initial_p2[4]; + gint initial_m1[4]; + gint initial_m2[4]; + gdouble radius; + gdouble val; + gdouble std_dev1; + gdouble std_dev2; + gdouble ramp_down; + gdouble ramp_up; + + if (preview) + { + gimp_preview_get_position (preview, &x, &y); + gimp_preview_get_size (preview, &width, &height); + } + else + { + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x, &y, &width, &height)) + return; + } + + bytes = drawable->bpp; + has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + val_p1 = g_new (gdouble, MAX (width, height)); + val_p2 = g_new (gdouble, MAX (width, height)); + val_m1 = g_new (gdouble, MAX (width, height)); + val_m2 = g_new (gdouble, MAX (width, height)); + + dest1 = g_new0 (guchar, width * height); + dest2 = g_new0 (guchar, width * height); + + progress = 0; + max_progress = width * height * 3; + + gimp_pixel_rgn_init (&src_rgn, drawable, + x, y, width, height, FALSE, FALSE); + + for (pr = gimp_pixel_rgns_register (1, &src_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) + { + guchar *src_ptr = src_rgn.data; + guchar *dest_ptr = dest1 + (src_rgn.y - y) * width + (src_rgn.x - x); + + for (row = 0; row < src_rgn.h; row++) + { + for (col = 0; col < src_rgn.w; col++) + { + /* desaturate */ + if (bytes > 2) + dest_ptr[col] = (guchar) gimp_rgb_to_l_int (src_ptr[col * bytes + 0], + src_ptr[col * bytes + 1], + src_ptr[col * bytes + 2]); + else + dest_ptr[col] = (guchar) src_ptr[col * bytes]; + + /* compute transfer */ + val = pow (dest_ptr[col], (1.0 / GAMMA)); + dest_ptr[col] = (guchar) CLAMP (val, 0, 255); + } + + src_ptr += src_rgn.rowstride; + dest_ptr += width; + } + + if (!preview) + { + progress += src_rgn.w * src_rgn.h; + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + /* Calculate the standard deviations */ + radius = MAX (1.0, 10 * (1.0 - pvals.sharpness)); + radius = fabs (radius) + 1.0; + std_dev1 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0))); + + radius = fabs (pvals.mask_radius) + 1.0; + std_dev2 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0))); + + /* derive the constants for calculating the gaussian from the std dev */ + find_constants (n_p1, n_m1, d_p1, d_m1, bd_p1, bd_m1, std_dev1); + find_constants (n_p2, n_m2, d_p2, d_m2, bd_p2, bd_m2, std_dev2); + + /* First the vertical pass */ + for (col = 0; col < width; col++) + { + memset (val_p1, 0, height * sizeof (gdouble)); + memset (val_p2, 0, height * sizeof (gdouble)); + memset (val_m1, 0, height * sizeof (gdouble)); + memset (val_m2, 0, height * sizeof (gdouble)); + + src1 = dest1 + col; + sp_p1 = src1; + sp_m1 = src1 + (height - 1) * width; + vp1 = val_p1; + vp2 = val_p2; + vm1 = val_m1 + (height - 1); + vm2 = val_m2 + (height - 1); + + /* Set up the first vals */ + initial_p1[0] = sp_p1[0]; + initial_m1[0] = sp_m1[0]; + + for (row = 0; row < height; row++) + { + gdouble *vpptr1, *vmptr1; + gdouble *vpptr2, *vmptr2; + + terms = (row < 4) ? row : 4; + + vpptr1 = vp1; vmptr1 = vm1; + vpptr2 = vp2; vmptr2 = vm2; + + for (i = 0; i <= terms; i++) + { + *vpptr1 += n_p1[i] * sp_p1[-i * width] - d_p1[i] * vp1[-i]; + *vmptr1 += n_m1[i] * sp_m1[i * width] - d_m1[i] * vm1[i]; + + *vpptr2 += n_p2[i] * sp_p1[-i * width] - d_p2[i] * vp2[-i]; + *vmptr2 += n_m2[i] * sp_m1[i * width] - d_m2[i] * vm2[i]; + } + + for (j = i; j <= 4; j++) + { + *vpptr1 += (n_p1[j] - bd_p1[j]) * initial_p1[0]; + *vmptr1 += (n_m1[j] - bd_m1[j]) * initial_m1[0]; + + *vpptr2 += (n_p2[j] - bd_p2[j]) * initial_p1[0]; + *vmptr2 += (n_m2[j] - bd_m2[j]) * initial_m1[0]; + } + + sp_p1 += width; + sp_m1 -= width; + vp1 += 1; + vp2 += 1; + vm1 -= 1; + vm2 -= 1; + } + + transfer_pixels (val_p1, val_m1, dest1 + col, width, height); + transfer_pixels (val_p2, val_m2, dest2 + col, width, height); + + if (!preview) + { + progress += height; + if ((col % 5) == 0) + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + for (row = 0; row < height; row++) + { + memset (val_p1, 0, width * sizeof (gdouble)); + memset (val_p2, 0, width * sizeof (gdouble)); + memset (val_m1, 0, width * sizeof (gdouble)); + memset (val_m2, 0, width * sizeof (gdouble)); + + src1 = dest1 + row * width; + src2 = dest2 + row * width; + + sp_p1 = src1; + sp_p2 = src2; + sp_m1 = src1 + width - 1; + sp_m2 = src2 + width - 1; + vp1 = val_p1; + vp2 = val_p2; + vm1 = val_m1 + width - 1; + vm2 = val_m2 + width - 1; + + /* Set up the first vals */ + initial_p1[0] = sp_p1[0]; + initial_p2[0] = sp_p2[0]; + initial_m1[0] = sp_m1[0]; + initial_m2[0] = sp_m2[0]; + + for (col = 0; col < width; col++) + { + gdouble *vpptr1, *vmptr1; + gdouble *vpptr2, *vmptr2; + + terms = (col < 4) ? col : 4; + + vpptr1 = vp1; vmptr1 = vm1; + vpptr2 = vp2; vmptr2 = vm2; + + for (i = 0; i <= terms; i++) + { + *vpptr1 += n_p1[i] * sp_p1[-i] - d_p1[i] * vp1[-i]; + *vmptr1 += n_m1[i] * sp_m1[i] - d_m1[i] * vm1[i]; + + *vpptr2 += n_p2[i] * sp_p2[-i] - d_p2[i] * vp2[-i]; + *vmptr2 += n_m2[i] * sp_m2[i] - d_m2[i] * vm2[i]; + } + + for (j = i; j <= 4; j++) + { + *vpptr1 += (n_p1[j] - bd_p1[j]) * initial_p1[0]; + *vmptr1 += (n_m1[j] - bd_m1[j]) * initial_m1[0]; + + *vpptr2 += (n_p2[j] - bd_p2[j]) * initial_p2[0]; + *vmptr2 += (n_m2[j] - bd_m2[j]) * initial_m2[0]; + } + + sp_p1 ++; + sp_p2 ++; + sp_m1 --; + sp_m2 --; + vp1 ++; + vp2 ++; + vm1 --; + vm2 --; + } + + transfer_pixels (val_p1, val_m1, dest1 + row * width, 1, width); + transfer_pixels (val_p2, val_m2, dest2 + row * width, 1, width); + + if (!preview) + { + progress += width; + if ((row % 5) == 0) + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + /* Compute the ramp value which sets 'pct_black' % of the darkened pixels black */ + ramp_down = compute_ramp (dest1, dest2, width * height, pvals.pct_black, 1); + ramp_up = compute_ramp (dest1, dest2, width * height, 1.0 - pvals.pct_white, 0); + + /* Initialize the pixel regions. */ + gimp_pixel_rgn_init (&src_rgn, drawable, x, y, width, height, FALSE, FALSE); + gimp_pixel_rgn_init (&dest_rgn, drawable, x, y, width, height, + (preview == NULL), TRUE); + + pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn); + + while (pr) + { + guchar *src_ptr = src_rgn.data; + guchar *dest_ptr = dest_rgn.data; + guchar *blur_ptr = dest1 + (src_rgn.y - y) * width + (src_rgn.x - x); + guchar *avg_ptr = dest2 + (src_rgn.y - y) * width + (src_rgn.x - x); + gdouble diff, mult; + gdouble lightness = 0.0; + + for (row = 0; row < src_rgn.h; row++) + { + for (col = 0; col < src_rgn.w; col++) + { + if (avg_ptr[col] > EPSILON) + { + diff = (gdouble) blur_ptr[col] / (gdouble) avg_ptr[col]; + + if (diff < pvals.threshold) + { + if (ramp_down == 0.0) + mult = 0.0; + else + mult = (ramp_down - MIN (ramp_down, + (pvals.threshold - diff))) / ramp_down; + lightness = CLAMP (blur_ptr[col] * mult, 0, 255); + } + else + { + if (ramp_up == 0.0) + mult = 1.0; + else + mult = MIN (ramp_up, + (diff - pvals.threshold)) / ramp_up; + + lightness = 255 - (1.0 - mult) * (255 - blur_ptr[col]); + lightness = CLAMP (lightness, 0, 255); + } + } + else + { + lightness = 0; + } + + if (bytes < 3) + { + dest_ptr[col * bytes] = (guchar) lightness; + if (has_alpha) + dest_ptr[col * bytes + 1] = src_ptr[col * src_rgn.bpp + 1]; + } + else + { + dest_ptr[col * bytes + 0] = lightness; + dest_ptr[col * bytes + 1] = lightness; + dest_ptr[col * bytes + 2] = lightness; + + if (has_alpha) + dest_ptr[col * bytes + 3] = src_ptr[col * src_rgn.bpp + 3]; + } + } + + src_ptr += src_rgn.rowstride; + dest_ptr += dest_rgn.rowstride; + blur_ptr += width; + avg_ptr += width; + } + + if (preview) + { + gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview), + &dest_rgn); + } + else + { + progress += src_rgn.w * src_rgn.h; + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + + pr = gimp_pixel_rgns_process (pr); + } + + if (! preview) + { + gimp_progress_update (1.0); + /* merge the shadow, update the drawable */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, x, y, width, height); + } + + /* free up buffers */ + g_free (val_p1); + g_free (val_p2); + g_free (val_m1); + g_free (val_m2); + g_free (dest1); + g_free (dest2); +} + +static gdouble +compute_ramp (guchar *dest1, + guchar *dest2, + gint length, + gdouble pct, + gint under_threshold) +{ + gint hist[2000]; + gdouble diff; + gint count; + gint i; + gint sum; + + memset (hist, 0, sizeof (int) * 2000); + count = 0; + + for (i = 0; i < length; i++) + { + if (*dest2 != 0) + { + diff = (gdouble) *dest1 / (gdouble) *dest2; + + if (under_threshold) + { + if (diff < pvals.threshold) + { + hist[(int) (diff * 1000)] += 1; + count += 1; + } + } + else + { + if (diff >= pvals.threshold && diff < 2.0) + { + hist[(int) (diff * 1000)] += 1; + count += 1; + } + } + } + + dest1++; + dest2++; + } + + if (pct == 0.0 || count == 0) + return (under_threshold ? 1.0 : 0.0); + + sum = 0; + for (i = 0; i < 2000; i++) + { + sum += hist[i]; + if (((gdouble) sum / (gdouble) count) > pct) + { + if (under_threshold) + return (pvals.threshold - (gdouble) i / 1000.0); + else + return ((gdouble) i / 1000.0 - pvals.threshold); + } + } + + return (under_threshold ? 0.0 : 1.0); +} + + +/* + * Gaussian blur helper functions + */ + +static void +transfer_pixels (gdouble *src1, + gdouble *src2, + guchar *dest, + gint jump, + gint width) +{ + gint i; + gdouble sum; + + for(i = 0; i < width; i++) + { + sum = src1[i] + src2[i]; + if (sum > 255) sum = 255; + else if(sum < 0) sum = 0; + + *dest = (guchar) sum; + + dest += jump; + } +} + +static void +find_constants (gdouble n_p[], + gdouble n_m[], + gdouble d_p[], + gdouble d_m[], + gdouble bd_p[], + gdouble bd_m[], + gdouble std_dev) +{ + gint i; + gdouble constants [8]; + gdouble div; + + /* The constants used in the implementation of a casual sequence + * using a 4th order approximation of the gaussian operator + */ + + div = sqrt (2 * G_PI) * std_dev; + constants [0] = -1.783 / std_dev; + constants [1] = -1.723 / std_dev; + constants [2] = 0.6318 / std_dev; + constants [3] = 1.997 / std_dev; + constants [4] = 1.6803 / div; + constants [5] = 3.735 / div; + constants [6] = -0.6803 / div; + constants [7] = -0.2598 / div; + + n_p [0] = constants[4] + constants[6]; + n_p [1] = exp (constants[1]) * + (constants[7] * sin (constants[3]) - + (constants[6] + 2 * constants[4]) * cos (constants[3])) + + exp (constants[0]) * + (constants[5] * sin (constants[2]) - + (2 * constants[6] + constants[4]) * cos (constants[2])); + n_p [2] = 2 * exp (constants[0] + constants[1]) * + ((constants[4] + constants[6]) * cos (constants[3]) * cos (constants[2]) - + constants[5] * cos (constants[3]) * sin (constants[2]) - + constants[7] * cos (constants[2]) * sin (constants[3])) + + constants[6] * exp (2 * constants[0]) + + constants[4] * exp (2 * constants[1]); + n_p [3] = exp (constants[1] + 2 * constants[0]) * + (constants[7] * sin (constants[3]) - constants[6] * cos (constants[3])) + + exp (constants[0] + 2 * constants[1]) * + (constants[5] * sin (constants[2]) - constants[4] * cos (constants[2])); + n_p [4] = 0.0; + + d_p [0] = 0.0; + d_p [1] = -2 * exp (constants[1]) * cos (constants[3]) - + 2 * exp (constants[0]) * cos (constants[2]); + d_p [2] = 4 * cos (constants[3]) * cos (constants[2]) * exp (constants[0] + constants[1]) + + exp (2 * constants[1]) + exp (2 * constants[0]); + d_p [3] = -2 * cos (constants[2]) * exp (constants[0] + 2 * constants[1]) - + 2 * cos (constants[3]) * exp (constants[1] + 2 * constants[0]); + d_p [4] = exp (2 * constants[0] + 2 * constants[1]); + +#ifndef ORIGINAL_READABLE_CODE + memcpy(d_m, d_p, 5 * sizeof(gdouble)); +#else + for (i = 0; i <= 4; i++) + d_m [i] = d_p [i]; +#endif + + n_m[0] = 0.0; + for (i = 1; i <= 4; i++) + n_m [i] = n_p[i] - d_p[i] * n_p[0]; + + { + gdouble sum_n_p, sum_n_m, sum_d; + gdouble a, b; + + sum_n_p = 0.0; + sum_n_m = 0.0; + sum_d = 0.0; + + for (i = 0; i <= 4; i++) + { + sum_n_p += n_p[i]; + sum_n_m += n_m[i]; + sum_d += d_p[i]; + } + +#ifndef ORIGINAL_READABLE_CODE + sum_d++; + a = sum_n_p / sum_d; + b = sum_n_m / sum_d; +#else + a = sum_n_p / (1 + sum_d); + b = sum_n_m / (1 + sum_d); +#endif + + for (i = 0; i <= 4; i++) + { + bd_p[i] = d_p[i] * a; + bd_m[i] = d_m[i] * b; + } + } +} + +/*******************************************************/ +/* Dialog */ +/*******************************************************/ + +static gboolean +photocopy_dialog (GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *table; + GtkObject *scale_data; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Photocopy"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (photocopy), + drawable); + + table = gtk_table_new (4, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* Label, scale, entry for pvals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Mask radius:"), 100, 5, + pvals.mask_radius, 3.0, 50.0, 1, 5.0, 2, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &pvals.mask_radius); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* Label, scale, entry for pvals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Sharpness:"), 50, 5, + pvals.sharpness, 0.0, 1.0, 0.01, 0.1, 3, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &pvals.sharpness); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* Label, scale, entry for pvals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("Percent _black:"), 50, 5, + pvals.pct_black, 0.0, 1.0, 0.01, 0.1, 3, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &pvals.pct_black); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* Label, scale, entry for pvals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 3, + _("Percent _white:"), 50, 5, + pvals.pct_white, 0.0, 1.0, 0.01, 0.1, 3, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &pvals.pct_white); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/plugin-browser.c b/plug-ins/common/plugin-browser.c new file mode 100644 index 0000000..202cc97 --- /dev/null +++ b/plug-ins/common/plugin-browser.c @@ -0,0 +1,918 @@ +/* + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This is a plug-in for GIMP. + * + * Copyright (C) 1999 Andy Thomas alt@picnic.demon.co.uk + * + * Note some portions of the UI comes from the dbbrowser plugin. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-plug-in-details" +#define PLUG_IN_BINARY "plugin-browser" +#define PLUG_IN_ROLE "gimp-plugin-browser" +#define DBL_LIST_WIDTH 250 +#define DBL_WIDTH (DBL_LIST_WIDTH + 400) +#define DBL_HEIGHT 250 + + +enum +{ + LIST_COLUMN_NAME, + LIST_COLUMN_DATE, + LIST_COLUMN_DATE_STRING, + LIST_COLUMN_PATH, + LIST_COLUMN_IMAGE_TYPES, + LIST_COLUMN_PINFO, + N_LIST_COLUMNS +}; + +enum +{ + TREE_COLUMN_PATH_NAME, + TREE_COLUMN_DATE, + TREE_COLUMN_DATE_STRING, + TREE_COLUMN_IMAGE_TYPES, + TREE_COLUMN_MPATH, + TREE_COLUMN_PINFO, + N_TREE_OLUMNS +}; + +typedef struct +{ + GtkWidget *dialog; + + GtkWidget *browser; + + GtkTreeView *list_view; + GtkTreeView *tree_view; +} PluginBrowser; + +typedef struct +{ + gchar *menu; + gchar *accel; + gchar *prog; + gchar *types; + gchar *realname; + gint instime; +} PInfo; + + +/* Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + + +static GtkWidget * browser_dialog_new (void); +static void browser_dialog_response (GtkWidget *widget, + gint response_id, + PluginBrowser *browser); +static void browser_list_selection_changed (GtkTreeSelection *selection, + PluginBrowser *browser); +static void browser_tree_selection_changed (GtkTreeSelection *selection, + PluginBrowser *browser); +static void browser_show_plugin (PluginBrowser *browser, + PInfo *pinfo); + +static gboolean find_existing_mpath (GtkTreeModel *model, + const gchar *mpath, + GtkTreeIter *return_iter); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Display information about plug-ins"), + "Allows one to browse the plug-in menus system. You " + "can search for plug-in names, sort by name or menu " + "location and you can view a tree representation " + "of the plug-in menus. Can also be of help to find " + "where new plug-ins have installed themselves in " + "the menus.", + "Andy Thomas", + "Andy Thomas", + "1999", + N_("_Plug-in Browser"), + "", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Help/Programming"); + gimp_plugin_icon_register (PLUG_IN_PROC, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) GIMP_ICON_PLUGIN); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + + INIT_I18N (); + + if (strcmp (name, PLUG_IN_PROC) == 0) + { + *nreturn_vals = 1; + + values[0].data.d_status = GIMP_PDB_SUCCESS; + + browser_dialog_new (); + gtk_main (); + } +} + +static gboolean +find_existing_mpath_helper (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreePath *path, + const gchar *mpath, + GtkTreeIter *return_iter) +{ + do + { + GtkTreeIter child; + gchar *picked_mpath; + + gtk_tree_model_get (model, iter, + TREE_COLUMN_MPATH, &picked_mpath, + -1); + + if (! strcmp (mpath, picked_mpath)) + { + *return_iter = *iter; + g_free (picked_mpath); + return TRUE; + } + + if (gtk_tree_model_iter_children (model, &child, iter)) + { + gtk_tree_path_down (path); + + if (find_existing_mpath_helper (model, &child, path, + mpath, return_iter)) + { + g_free (picked_mpath); + return TRUE; + } + + gtk_tree_path_up (path); + } + + gtk_tree_path_next (path); + g_free (picked_mpath); + } + while (gtk_tree_model_iter_next (model, iter)); + + return FALSE; +} + +static gboolean +find_existing_mpath (GtkTreeModel *model, + const gchar *mpath, + GtkTreeIter *return_iter) +{ + GtkTreePath *path = gtk_tree_path_new_first (); + GtkTreeIter parent; + gboolean found; + + if (! gtk_tree_model_get_iter (model, &parent, path)) + { + gtk_tree_path_free (path); + return FALSE; + } + + found = find_existing_mpath_helper (model, &parent, path, mpath, return_iter); + + gtk_tree_path_free (path); + + return found; +} + +static void +get_parent (PluginBrowser *browser, + const gchar *mpath, + GtkTreeIter *parent) +{ + GtkTreeIter last_parent; + gchar *tmp_ptr; + gchar *str_ptr; + GtkTreeStore *tree_store; + + if (! mpath) + return; + + tree_store = GTK_TREE_STORE (gtk_tree_view_get_model (browser->tree_view)); + + /* Lookup for existing mpath */ + if (find_existing_mpath (GTK_TREE_MODEL (tree_store), mpath, parent)) + return; + + tmp_ptr = g_strdup (mpath); + + /* Strip off trailing ellipsis */ + str_ptr = strstr (mpath, "..."); + if (str_ptr && str_ptr == (mpath + strlen (mpath) - 3)) + *str_ptr = '\0'; + + str_ptr = strrchr (tmp_ptr, '/'); + + if (str_ptr == NULL) + { + gtk_tree_store_append (tree_store, parent, NULL); + gtk_tree_store_set (tree_store, parent, + TREE_COLUMN_MPATH, mpath, + TREE_COLUMN_PATH_NAME, mpath, + -1); + } + else + { + gchar *leaf_ptr; + + leaf_ptr = g_strdup (str_ptr + 1); + *str_ptr = '\0'; + + get_parent (browser, tmp_ptr, &last_parent); + gtk_tree_store_append (tree_store, parent, &last_parent); + gtk_tree_store_set (tree_store, parent, + TREE_COLUMN_MPATH, mpath, + TREE_COLUMN_PATH_NAME, leaf_ptr, + -1); + + g_free (leaf_ptr); + } +} + +static void +insert_into_tree_view (PluginBrowser *browser, + const gchar *name, + gint64 xtime, + const gchar *xtimestr, + const gchar *menu_str, + const gchar *types_str, + PInfo *pinfo) +{ + gchar *str_ptr; + gchar *tmp_ptr; + GtkTreeIter parent, iter; + GtkTreeStore *tree_store; + + /* Find all nodes */ + /* Last one is the leaf part */ + + tmp_ptr = g_strdup (menu_str); + + str_ptr = strrchr (tmp_ptr, '/'); + + if (str_ptr == NULL) + { + g_free (tmp_ptr); + return; /* No node */ + } + + *str_ptr = '\0'; + + /* printf("inserting %s...\n",menu_str); */ + + get_parent (browser, tmp_ptr, &parent); + + tree_store = GTK_TREE_STORE (gtk_tree_view_get_model (browser->tree_view)); + gtk_tree_store_append (tree_store, &iter, &parent); + gtk_tree_store_set (tree_store, &iter, + TREE_COLUMN_MPATH, menu_str, + TREE_COLUMN_PATH_NAME, name, + TREE_COLUMN_IMAGE_TYPES, types_str, + TREE_COLUMN_DATE, xtime, + TREE_COLUMN_DATE_STRING, xtimestr, + TREE_COLUMN_PINFO, pinfo, + -1); +} + +static void +browser_search (GimpBrowser *gimp_browser, + const gchar *search_text, + gint search_type, + PluginBrowser *browser) +{ + GimpParam *return_vals; + gint nreturn_vals; + gint num_plugins; + gchar *str; + GtkListStore *list_store; + GtkTreeStore *tree_store; + + gimp_browser_show_message (GIMP_BROWSER (browser->browser), + _("Searching by name")); + + return_vals = gimp_run_procedure ("gimp-plugins-query", + &nreturn_vals, + GIMP_PDB_STRING, search_text, + GIMP_PDB_END); + + if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS) + num_plugins = return_vals[1].data.d_int32; + else + num_plugins = 0; + + if (! search_text || strlen (search_text) == 0) + { + str = g_strdup_printf (ngettext ("%d plug-in", "%d plug-ins", + num_plugins), + num_plugins); + } + else + { + switch (num_plugins) + { + case 0: + str = g_strdup (_("No matches for your query")); + break; + default: + str = g_strdup_printf (ngettext ("%d plug-in matches your query", + "%d plug-ins match your query", + num_plugins), num_plugins); + break; + } + } + + gtk_label_set_text (GTK_LABEL (gimp_browser->count_label), str); + g_free (str); + + list_store = GTK_LIST_STORE (gtk_tree_view_get_model (browser->list_view)); + gtk_list_store_clear (list_store); + + tree_store = GTK_TREE_STORE (gtk_tree_view_get_model (browser->tree_view)); + gtk_tree_store_clear (tree_store); + + if (num_plugins > 0) + { + GtkTreeSelection *sel; + GtkTreeIter iter; + gchar **menu_strs; + gchar **accel_strs; + gchar **prog_strs; + gchar **types_strs; + gchar **realname_strs; + gint *time_ints; + gint i; + + menu_strs = return_vals[2].data.d_stringarray; + accel_strs = return_vals[4].data.d_stringarray; + prog_strs = return_vals[6].data.d_stringarray; + types_strs = return_vals[8].data.d_stringarray; + time_ints = return_vals[10].data.d_int32array; + realname_strs = return_vals[12].data.d_stringarray; + + for (i = 0; i < num_plugins; i++) + { + PInfo *pinfo; + gchar *name; + gchar xtimestr[50]; + struct tm *x; + time_t tx; + gint ret; + + /* Strip off trailing ellipsis */ + name = strstr (menu_strs[i], "..."); + if (name && name == (menu_strs[i] + strlen (menu_strs[i]) - 3)) + *name = '\0'; + + name = strrchr (menu_strs[i], '/'); + + if (name) + { + *name = '\0'; + name = name + 1; + } + else + { + name = menu_strs[i]; + } + + tx = time_ints[i]; + if (tx) + { + const gchar *format = "%c"; /* gcc workaround to avoid warning */ + gchar *utf8; + + x = localtime (&tx); + ret = strftime (xtimestr, sizeof (xtimestr), format, x); + xtimestr[ret] = 0; + + if ((utf8 = g_locale_to_utf8 (xtimestr, -1, NULL, NULL, NULL))) + { + strncpy (xtimestr, utf8, sizeof (xtimestr)); + xtimestr[sizeof (xtimestr) - 1] = 0; + g_free (utf8); + } + } + else + { + strcpy (xtimestr, ""); + } + + pinfo = g_new0 (PInfo, 1); + + pinfo->menu = g_strdup (menu_strs[i]); + pinfo->accel = g_strdup (accel_strs[i]); + pinfo->prog = g_strdup (prog_strs[i]); + pinfo->types = g_strdup (types_strs[i]); + pinfo->instime = time_ints[i]; + pinfo->realname = g_strdup (realname_strs[i]); + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + LIST_COLUMN_NAME, name, + LIST_COLUMN_DATE, (gint64) tx, + LIST_COLUMN_DATE_STRING, xtimestr, + LIST_COLUMN_PATH, menu_strs[i], + LIST_COLUMN_IMAGE_TYPES, types_strs[i], + LIST_COLUMN_PINFO, pinfo, + -1); + + /* Now do the tree view.... */ + insert_into_tree_view (browser, + name, + (gint64) tx, + xtimestr, + menu_strs[i], + types_strs[i], + pinfo); + } + + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (browser->list_view)); + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (browser->tree_view)); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store), + LIST_COLUMN_NAME, + GTK_SORT_ASCENDING); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (tree_store), + TREE_COLUMN_PATH_NAME, + GTK_SORT_ASCENDING); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (browser->list_view)); + + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), + &iter); + gtk_tree_selection_select_iter (sel, &iter); + } + else + { + gimp_browser_show_message (GIMP_BROWSER (browser->browser), + _("No matches")); + } + + gimp_destroy_params (return_vals, nreturn_vals); +} + +static GtkWidget * +browser_dialog_new (void) +{ + PluginBrowser *browser; + GtkWidget *label, *notebook; + GtkWidget *scrolled_window; + GtkListStore *list_store; + GtkTreeStore *tree_store; + GtkWidget *list_view; + GtkWidget *tree_view; + GtkWidget *parent; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkTreeSelection *selection; + GtkTreeIter iter; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + browser = g_new0 (PluginBrowser, 1); + + browser->dialog = gimp_dialog_new (_("Plug-in Browser"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + g_signal_connect (browser->dialog, "response", + G_CALLBACK (browser_dialog_response), + browser); + + browser->browser = gimp_browser_new (); + gtk_container_set_border_width (GTK_CONTAINER (browser->browser), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (browser->dialog))), + browser->browser, TRUE, TRUE, 0); + gtk_widget_show (browser->browser); + + g_signal_connect (browser->browser, "search", + G_CALLBACK (browser_search), + browser); + + /* left = notebook */ + + notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (GIMP_BROWSER (browser->browser)->left_vbox), + notebook, TRUE, TRUE, 0); + + /* list : list in a scrolled_win */ + list_store = gtk_list_store_new (N_LIST_COLUMNS, + G_TYPE_STRING, + G_TYPE_INT64, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_POINTER); + + list_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list_store)); + g_object_unref (list_store); + + browser->list_view = GTK_TREE_VIEW (list_view); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Name"), + renderer, + "text", LIST_COLUMN_NAME, + NULL); + gtk_tree_view_column_set_sort_column_id (column, LIST_COLUMN_NAME); + gtk_tree_view_append_column (GTK_TREE_VIEW (list_view), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Menu Path"), + renderer, + "text", LIST_COLUMN_PATH, + NULL); + gtk_tree_view_column_set_sort_column_id (column, LIST_COLUMN_PATH); + gtk_tree_view_append_column (GTK_TREE_VIEW (list_view), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Image Types"), + renderer, + "text", + LIST_COLUMN_IMAGE_TYPES, + NULL); + gtk_tree_view_column_set_sort_column_id (column, LIST_COLUMN_IMAGE_TYPES); + gtk_tree_view_append_column (GTK_TREE_VIEW (list_view), column); + + renderer = gtk_cell_renderer_text_new (); + + column = gtk_tree_view_column_new_with_attributes (_("Installation Date"), + renderer, + "text", + LIST_COLUMN_DATE_STRING, + NULL); + gtk_tree_view_column_set_sort_column_id (column, LIST_COLUMN_DATE); + gtk_tree_view_append_column (GTK_TREE_VIEW (list_view), column); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 2); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + gtk_widget_set_size_request (list_view, DBL_LIST_WIDTH, DBL_HEIGHT); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list_view)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + + g_signal_connect (selection, "changed", + G_CALLBACK (browser_list_selection_changed), + browser); + + label = gtk_label_new (_("List View")); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled_window, label); + gtk_container_add (GTK_CONTAINER (scrolled_window), list_view); + gtk_widget_show (list_view); + gtk_widget_show (scrolled_window); + + /* notebook->ctree */ + tree_store = gtk_tree_store_new (N_LIST_COLUMNS, + G_TYPE_STRING, + G_TYPE_INT64, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_POINTER); + + tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (tree_store)); + g_object_unref (tree_store); + + browser->tree_view = GTK_TREE_VIEW (tree_view); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Menu Path"), + renderer, + "text", + TREE_COLUMN_PATH_NAME, + NULL); + gtk_tree_view_column_set_sort_column_id (column, TREE_COLUMN_PATH_NAME); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Image Types"), + renderer, + "text", + TREE_COLUMN_IMAGE_TYPES, + NULL); + gtk_tree_view_column_set_sort_column_id (column, TREE_COLUMN_IMAGE_TYPES); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Installation Date"), + renderer, + "text", + TREE_COLUMN_DATE_STRING, + NULL); + gtk_tree_view_column_set_sort_column_id (column, TREE_COLUMN_DATE); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 2); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_set_size_request (tree_view, DBL_LIST_WIDTH, DBL_HEIGHT); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + + g_signal_connect (selection, "changed", + G_CALLBACK (browser_tree_selection_changed), + browser); + + label = gtk_label_new (_("Tree View")); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled_window, label); + gtk_container_add (GTK_CONTAINER (scrolled_window), tree_view); + + gtk_widget_show (tree_view); + gtk_widget_show (scrolled_window); + gtk_widget_show (notebook); + + parent = gtk_widget_get_parent (GIMP_BROWSER (browser->browser)->right_vbox); + parent = gtk_widget_get_parent (parent); + + gtk_widget_set_size_request (parent, DBL_WIDTH - DBL_LIST_WIDTH, -1); + + /* now build the list */ + browser_search (GIMP_BROWSER (browser->browser), "", 0, browser); + + gtk_widget_show (browser->dialog); + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter)) + gtk_tree_selection_select_iter (gtk_tree_view_get_selection (GTK_TREE_VIEW (list_view)), + &iter); + + return browser->dialog; +} + +static void +browser_dialog_response (GtkWidget *widget, + gint response_id, + PluginBrowser *browser) +{ + gtk_widget_destroy (browser->dialog); + gtk_main_quit (); +} + +static void +browser_list_selection_changed (GtkTreeSelection *selection, + PluginBrowser *browser) +{ + PInfo *pinfo = NULL; + GtkTreeIter iter; + GtkTreeModel *model; + gchar *mpath = NULL; + + g_return_if_fail (browser != NULL); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, + LIST_COLUMN_PINFO, &pinfo, + LIST_COLUMN_PATH, &mpath, + -1); + } + + if (!pinfo || !mpath) + return; + + model = gtk_tree_view_get_model (browser->tree_view); + + if (find_existing_mpath (model, mpath, &iter)) + { + GtkTreeSelection *tree_selection; + GtkTreePath *tree_path; + + tree_path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_expand_to_path (browser->tree_view, tree_path); + tree_selection = gtk_tree_view_get_selection (browser->tree_view); + + g_signal_handlers_block_by_func (tree_selection, + browser_tree_selection_changed, + browser); + gtk_tree_selection_select_iter (tree_selection, &iter); + g_signal_handlers_unblock_by_func (tree_selection, + browser_tree_selection_changed, + browser); + + gtk_tree_view_scroll_to_cell (browser->tree_view, + tree_path, NULL, + TRUE, 0.5, 0.0); + } + else + { + g_warning ("Failed to find node in tree"); + } + + g_free (mpath); + + browser_show_plugin (browser, pinfo); +} + +static void +browser_tree_selection_changed (GtkTreeSelection *selection, + PluginBrowser *browser) +{ + PInfo *pinfo = NULL; + GtkTreeIter iter; + GtkTreeModel *model; + gchar *mpath = NULL; + gboolean valid, found; + + g_return_if_fail (browser != NULL); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, + TREE_COLUMN_PINFO, &pinfo, + TREE_COLUMN_MPATH, &mpath, + -1); + } + + if (!pinfo || !mpath) + return; + + /* Get the first iter in the list */ + model = gtk_tree_view_get_model (browser->list_view); + valid = gtk_tree_model_get_iter_first (model, &iter); + found = FALSE; + + while (valid) + { + /* Walk through the list, reading each row */ + gchar *picked_mpath; + + gtk_tree_model_get (model, &iter, + LIST_COLUMN_PATH, &picked_mpath, + -1); + if (picked_mpath && !strcmp (mpath, picked_mpath)) + { + found = TRUE; + break; + } + + g_free (picked_mpath); + valid = gtk_tree_model_iter_next (model, &iter); + } + + g_free (mpath); + + if (found) + { + GtkTreeSelection *list_selection; + GtkTreePath *tree_path; + + tree_path = gtk_tree_model_get_path (model, &iter); + list_selection = gtk_tree_view_get_selection (browser->list_view); + + g_signal_handlers_block_by_func (list_selection, + browser_list_selection_changed, + browser); + gtk_tree_selection_select_iter (list_selection, &iter); + g_signal_handlers_unblock_by_func (list_selection, + browser_list_selection_changed, + browser); + + gtk_tree_view_scroll_to_cell (browser->list_view, + tree_path, NULL, + TRUE, 0.5, 0.0); + } + else + { + g_warning ("Failed to find node in list"); + } + + browser_show_plugin (browser, pinfo); +} + +static void +browser_show_plugin (PluginBrowser *browser, + PInfo *pinfo) +{ + gchar *blurb = NULL; + gchar *help = NULL; + gchar *author = NULL; + gchar *copyright = NULL; + gchar *date = NULL; + GimpPDBProcType type = 0; + gint n_params = 0; + gint n_return_vals = 0; + GimpParamDef *params = NULL; + GimpParamDef *return_vals = NULL; + + g_return_if_fail (browser != NULL); + g_return_if_fail (pinfo != NULL); + + gimp_procedural_db_proc_info (pinfo->realname, + &blurb, + &help, + &author, + ©right, + &date, + &type, + &n_params, + &n_return_vals, + ¶ms, + &return_vals); + + gimp_browser_set_widget (GIMP_BROWSER (browser->browser), + gimp_proc_view_new (pinfo->realname, + pinfo->menu, + blurb, + help, + author, + copyright, + date, + type, + n_params, + n_return_vals, + params, + return_vals)); + + g_free (blurb); + g_free (help); + g_free (author); + g_free (copyright); + g_free (date); + + gimp_destroy_paramdefs (params, n_params); + gimp_destroy_paramdefs (return_vals, n_return_vals); +} diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl new file mode 100644 index 0000000..488970c --- /dev/null +++ b/plug-ins/common/plugin-defs.pl @@ -0,0 +1,91 @@ +%plugins = ( + 'align-layers' => { ui => 1 }, + 'animation-optimize' => { gegl => 1}, + 'animation-play' => { ui => 1, gegl => 1 }, + 'blinds' => { ui => 1, gegl => 1 }, + 'blur' => {}, + 'border-average' => { ui => 1, gegl => 1 }, + 'busy-dialog' => { ui => 1, gegl => 1 }, + 'cartoon' => { ui => 1 }, + 'checkerboard' => { ui => 1, gegl => 1 }, + 'cml-explorer' => { ui => 1, gegl => 1 }, + 'color-cube-analyze' => { ui => 1 }, + 'color-enhance' => { ui => 1 }, + 'colorify' => { ui => 1 }, + 'colormap-remap' => { ui => 1, gegl => 1 }, + 'compose' => { ui => 1, gegl => 1 }, + 'contrast-retinex' => { ui => 1, gegl => 1 }, + 'crop-zealous' => { gegl => 1 }, + 'curve-bend' => { ui => 1, gegl => 1 }, + 'decompose' => { ui => 1, gegl => 1 }, + 'depth-merge' => { ui => 1, gegl => 1 }, + 'despeckle' => { ui => 1, gegl => 1 }, + 'destripe' => { ui => 1, gegl => 1 }, + 'edge-dog' => { ui => 1 }, + 'emboss' => { ui => 1 }, + 'file-aa' => { ui => 1, gegl => 1, optional => 1, libs => 'AA_LIBS' }, + 'file-cel' => { ui => 1, gegl => 1 }, + 'file-csource' => { ui => 1, gegl => 1 }, + 'file-compressor' => { gio => 1, libdep => 'Z:BZIP2:LZMA', cflags => 'LZMA_CFLAGS' }, + 'file-desktop-link' => {}, + 'file-dicom' => { ui => 1, gegl => 1, cflags => '-fno-strict-aliasing' }, + 'file-gbr' => { ui => 1, gegl => 1 }, + 'file-gegl' => { ui => 1, gegl => 1 }, + 'file-gif-load' => { gegl => 1 }, + 'file-gif-save' => { ui => 1, gegl => 1 }, + 'file-gih' => { ui => 1, gegl => 1 }, + 'file-glob' => {}, + 'file-header' => { ui => 1, gegl => 1 }, + 'file-heif' => { ui => 1, optional => 1, gegl => 1, libs => 'LIBHEIF_LIBS', cflags => 'LIBHEIF_CFLAGS' }, + 'file-html-table' => { ui => 1, gegl => 1 }, + 'file-jp2-load' => { ui => 1, optional => 1, gegl => 1, libs => 'OPENJPEG_LIBS', cflags => 'OPENJPEG_CFLAGS' }, + 'file-mng' => { ui => 1, gegl => 1, optional => 1, libs => 'MNG_LIBS', cflags => 'MNG_CFLAGS' }, + 'file-pat' => { ui => 1, gegl => 1 }, + 'file-pcx' => { ui => 1, gegl => 1 }, + 'file-pix' => { ui => 1, gegl => 1 }, + 'file-png' => { ui => 1, gegl => 1, libs => 'PNG_LIBS', cflags => 'PNG_CFLAGS' }, + 'file-pnm' => { ui => 1, gegl => 1 }, + 'file-pdf-load' => { ui => 1, gegl => 1, libs => 'POPPLER_LIBS', cflags => 'POPPLER_CFLAGS' }, + 'file-pdf-save' => { ui => 1, gegl => 1, optional => 1, libs => 'CAIRO_PDF_LIBS', cflags => 'CAIRO_PDF_CFLAGS' }, + 'file-ps' => { ui => 1, gegl => 1, optional => 1, libs => 'GS_LIBS' }, + 'file-psp' => { ui => 1, gegl => 1, libs => 'Z_LIBS' }, + 'file-raw-data' => { ui => 1, gegl => 1 }, + 'file-sunras' => { ui => 1, gegl => 1 }, + 'file-svg' => { ui => 1, libs => 'SVG_LIBS', cflags => 'SVG_CFLAGS' }, + 'file-tga' => { ui => 1, gegl => 1 }, + 'file-wmf' => { ui => 1, gegl => 1, optional => 1, libs => 'WMF_LIBS', cflags => 'WMF_CFLAGS' }, + 'file-xbm' => { ui => 1, gegl => 1 }, + 'file-xmc' => { ui => 1, gegl => 1, optional => 1, libs => 'XMC_LIBS' }, + 'file-xpm' => { ui => 1, gegl => 1, optional => 1, libs => 'XPM_LIBS' }, + 'file-xwd' => { ui => 1, gegl => 1 }, + 'film' => { ui => 1, gegl => 1 }, + 'filter-pack' => { ui => 1 }, + 'fractal-trace' => { ui => 1 }, + 'goat-exercise' => { gegl => 1 }, + 'gradient-map' => { gegl => 1 }, + 'grid' => { ui => 1, gegl => 1 }, + 'guillotine' => {}, + 'hot' => { ui => 1, gegl => 1 }, + 'jigsaw' => { ui => 1, gegl => 1 }, + 'mail' => { ui => 1, optional => 1 }, + 'max-rgb' => { ui => 1 }, + 'nl-filter' => { ui => 1, gegl => 1 }, + 'photocopy' => { ui => 1 }, + 'plugin-browser' => { ui => 1 }, + 'procedure-browser' => { ui => 1 }, + 'qbist' => { ui => 1, gegl => 1 }, + 'sample-colorize' => { ui => 1, gegl => 1 }, + 'sharpen' => { ui => 1 }, + 'smooth-palette' => { ui => 1, gegl => 1 }, + 'softglow' => { ui => 1 }, + 'sparkle' => { ui => 1, gegl => 1 }, + 'sphere-designer' => { ui => 1, gegl => 1 }, + 'tile' => { ui => 1, gegl => 1 }, + 'tile-small' => { ui => 1, gegl => 1 }, + 'unit-editor' => { ui => 1 }, + 'van-gogh-lic' => { ui => 1, gegl => 1 }, + 'warp' => { ui => 1, gegl => 1 }, + 'wavelet-decompose' => { ui => 1, gegl => 1 }, + 'web-browser' => { ui => 1, ldflags => '$(framework_cocoa)', cppflags => '$(AM_CPPFLAGS) $(xobjective_c)' }, + 'web-page' => { ui => 1, optional => 1, libs => 'WEBKIT_LIBS', cflags => 'WEBKIT_CFLAGS' } +); diff --git a/plug-ins/common/procedure-browser.c b/plug-ins/common/procedure-browser.c new file mode 100644 index 0000000..1651aa4 --- /dev/null +++ b/plug-ins/common/procedure-browser.c @@ -0,0 +1,147 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * dbbrowser + * 0.08 26th sept 97 by Thomas NOEL + */ + +/* + * This plugin gives you the list of available procedure, with the + * name, description and parameters for each procedure. + * You can do regexp search (by name and by description) + * Useful for scripts development. + * + * NOTE : + * this is only a exercice for me (my first "plug-in" (extension)) + * so it's very (very) dirty. + * Btw, hope it gives you some ideas about gimp possibilities. + * + * The core of the plugin is not here. See dbbrowser_utils (shared + * with script-fu-console). + * + * TODO + * - bug fixes... (my method : rewrite from scratch :) + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-dbbrowser" +#define PLUG_IN_BINARY "procedure-browser" +#define PLUG_IN_ROLE "gimp-procedure-browser" + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("List available procedures in the PDB"), + "", + "Thomas Noel", + "Thomas Noel", + "23th june 1997", + N_("Procedure _Browser"), + "", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Help/Programming"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + { + GtkWidget *dialog; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = + gimp_proc_browser_dialog_new (_("Procedure Browser"), PLUG_IN_BINARY, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + case GIMP_RUN_NONINTERACTIVE: + g_warning (PLUG_IN_PROC " allows only interactive invocation"); + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + break; + + default: + break; + } +} diff --git a/plug-ins/common/qbist.c b/plug-ins/common/qbist.c new file mode 100644 index 0000000..f8f94d4 --- /dev/null +++ b/plug-ins/common/qbist.c @@ -0,0 +1,912 @@ +/* + * Written 1997 Jens Ch. Restemeier + * This program is based on an algorithm / article by + * Jörn Loviscach. + * + * It appeared in c't 10/95, page 326 and is called + * "Ausgewürfelt - Moderne Kunst algorithmisch erzeugen" + * (~modern art created with algorithms). + * + * It generates one main formula (the middle button) and 8 variations of it. + * If you select a variation it becomes the new main formula. If you + * press "OK" the main formula will be applied to the image. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/** qbist renderer ***********************************************************/ + +#define MAX_TRANSFORMS 36 +#define NUM_REGISTERS 6 +#define PREVIEW_SIZE 64 + +#define PLUG_IN_PROC "plug-in-qbist" +#define PLUG_IN_BINARY "qbist" +#define PLUG_IN_ROLE "gimp-qbist" +#define PLUG_IN_VERSION "January 2001, 1.12" + +/** types *******************************************************************/ + +/* experiment with this */ +typedef gfloat vreg[3]; + +typedef enum +{ + PROJECTION, + SHIFT, + SHIFTBACK, + ROTATE, + ROTATE2, + MULTIPLY, + SINE, + CONDITIONAL, + COMPLEMENT +} TransformType; + +#define NUM_TRANSFORMS (COMPLEMENT + 1) + + +typedef struct +{ + TransformType transformSequence[MAX_TRANSFORMS]; + gint source[MAX_TRANSFORMS]; + gint control[MAX_TRANSFORMS]; + gint dest[MAX_TRANSFORMS]; +} ExpInfo; + +typedef struct +{ + ExpInfo info; + gint oversampling; + gchar path[PATH_MAX]; +} QbistInfo; + + +/** prototypes **************************************************************/ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean dialog_run (void); +static void dialog_new_variations (GtkWidget *widget, + gpointer data); +static void dialog_update_previews (GtkWidget *widget, + gpointer data); +static void dialog_select_preview (GtkWidget *widget, + ExpInfo *n_info); + +static QbistInfo qbist_info; +static GRand *gr = NULL; + + +/** qbist functions *********************************************************/ + +static void +create_info (ExpInfo *info) +{ + gint k; + + for (k = 0; k < MAX_TRANSFORMS; k++) + { + info->transformSequence[k] = g_rand_int_range (gr, 0, NUM_TRANSFORMS); + info->source[k] = g_rand_int_range (gr, 0, NUM_REGISTERS); + info->control[k] = g_rand_int_range (gr, 0, NUM_REGISTERS); + info->dest[k] = g_rand_int_range (gr, 0, NUM_REGISTERS); + } +} + +static void +modify_info (ExpInfo *o_info, + ExpInfo *n_info) +{ + gint k, n; + + *n_info = *o_info; + n = g_rand_int_range (gr, 0, MAX_TRANSFORMS); + for (k = 0; k < n; k++) + { + switch (g_rand_int_range (gr, 0, 4)) + { + case 0: + n_info->transformSequence[g_rand_int_range (gr, 0, MAX_TRANSFORMS)] = + g_rand_int_range (gr, 0, NUM_TRANSFORMS); + break; + + case 1: + n_info->source[g_rand_int_range (gr, 0, MAX_TRANSFORMS)] = + g_rand_int_range (gr, 0, NUM_REGISTERS); + break; + + case 2: + n_info->control[g_rand_int_range (gr, 0, MAX_TRANSFORMS)] = + g_rand_int_range (gr, 0, NUM_REGISTERS); + break; + + case 3: + n_info->dest[g_rand_int_range (gr, 0, MAX_TRANSFORMS)] = + g_rand_int_range (gr, 0, NUM_REGISTERS); + break; + } + } +} + +/* + * Optimizer + */ +static gint used_trans_flag[MAX_TRANSFORMS]; +static gint used_reg_flag[NUM_REGISTERS]; + +static void +check_last_modified (ExpInfo *info, + gint p, + gint n) +{ + p--; + while ((p >= 0) && (info->dest[p] != n)) + p--; + if (p < 0) + used_reg_flag[n] = 1; + else + { + used_trans_flag[p] = 1; + check_last_modified (info, p, info->source[p]); + check_last_modified (info, p, info->control[p]); + } +} + +static void +optimize (ExpInfo *info) +{ + gint i; + + /* double-arg fix: */ + for (i = 0; i < MAX_TRANSFORMS; i++) + { + used_trans_flag[i] = 0; + if (i < NUM_REGISTERS) + used_reg_flag[i] = 0; + /* double-arg fix: */ + switch (info->transformSequence[i]) + { + case ROTATE: + case ROTATE2: + case COMPLEMENT: + info->control[i] = info->dest[i]; + break; + + default: + break; + } + } + /* check for last modified item */ + check_last_modified (info, MAX_TRANSFORMS, 0); +} + +static void +qbist (ExpInfo *info, + gfloat *buffer, + gint xp, + gint yp, + gint num, + gint width, + gint height, + gint oversampling) +{ + gint gx; + + vreg reg[NUM_REGISTERS]; + + for (gx = 0; gx < num; gx++) + { + gfloat accum[3]; + gint yy, i; + + for (i = 0; i < 3; i++) + { + accum[i] = 0.0; + } + + for (yy = 0; yy < oversampling; yy++) + { + gint xx; + + for (xx = 0; xx < oversampling; xx++) + { + for (i = 0; i < NUM_REGISTERS; i++) + { + if (used_reg_flag[i]) + { + reg[i][0] = ((gfloat) ((gx + xp) * oversampling + xx)) / ((gfloat) (width * oversampling)); + reg[i][1] = ((gfloat) (yp * oversampling + yy)) / ((gfloat) (height * oversampling)); + reg[i][2] = ((gfloat) i) / ((gfloat) NUM_REGISTERS); + } + } + + for (i = 0; i < MAX_TRANSFORMS; i++) + { + gushort sr, cr, dr; + + sr = info->source[i]; + cr = info->control[i]; + dr = info->dest[i]; + + if (used_trans_flag[i]) + switch (info->transformSequence[i]) + { + case PROJECTION: + { + gfloat scalarProd; + + scalarProd = (reg[sr][0] * reg[cr][0]) + + (reg[sr][1] * reg[cr][1]) + (reg[sr][2] * reg[cr][2]); + + reg[dr][0] = scalarProd * reg[sr][0]; + reg[dr][1] = scalarProd * reg[sr][1]; + reg[dr][2] = scalarProd * reg[sr][2]; + break; + } + + case SHIFT: + reg[dr][0] = reg[sr][0] + reg[cr][0]; + if (reg[dr][0] >= 1.0) + reg[dr][0] -= 1.0; + reg[dr][1] = reg[sr][1] + reg[cr][1]; + if (reg[dr][1] >= 1.0) + reg[dr][1] -= 1.0; + reg[dr][2] = reg[sr][2] + reg[cr][2]; + if (reg[dr][2] >= 1.0) + reg[dr][2] -= 1.0; + break; + + case SHIFTBACK: + reg[dr][0] = reg[sr][0] - reg[cr][0]; + if (reg[dr][0] <= 0.0) + reg[dr][0] += 1.0; + reg[dr][1] = reg[sr][1] - reg[cr][1]; + if (reg[dr][1] <= 0.0) + reg[dr][1] += 1.0; + reg[dr][2] = reg[sr][2] - reg[cr][2]; + if (reg[dr][2] <= 0.0) + reg[dr][2] += 1.0; + break; + + case ROTATE: + reg[dr][0] = reg[sr][1]; + reg[dr][1] = reg[sr][2]; + reg[dr][2] = reg[sr][0]; + break; + + case ROTATE2: + reg[dr][0] = reg[sr][2]; + reg[dr][1] = reg[sr][0]; + reg[dr][2] = reg[sr][1]; + break; + + case MULTIPLY: + reg[dr][0] = reg[sr][0] * reg[cr][0]; + reg[dr][1] = reg[sr][1] * reg[cr][1]; + reg[dr][2] = reg[sr][2] * reg[cr][2]; + break; + + case SINE: + reg[dr][0] = 0.5 + (0.5 * sin (20.0 * reg[sr][0] * reg[cr][0])); + reg[dr][1] = 0.5 + (0.5 * sin (20.0 * reg[sr][1] * reg[cr][1])); + reg[dr][2] = 0.5 + (0.5 * sin (20.0 * reg[sr][2] * reg[cr][2])); + break; + + case CONDITIONAL: + if ((reg[cr][0] + reg[cr][1] + reg[cr][2]) > 0.5) + { + reg[dr][0] = reg[sr][0]; + reg[dr][1] = reg[sr][1]; + reg[dr][2] = reg[sr][2]; + } + else + { + reg[dr][0] = reg[cr][0]; + reg[dr][1] = reg[cr][1]; + reg[dr][2] = reg[cr][2]; + } + break; + + case COMPLEMENT: + reg[dr][0] = 1.0 - reg[sr][0]; + reg[dr][1] = 1.0 - reg[sr][1]; + reg[dr][2] = 1.0 - reg[sr][2]; + break; + } + } + + accum[0] += reg[0][0]; + accum[1] += reg[0][1]; + accum[2] += reg[0][2]; + } + } + + buffer[0] = accum[0] / (gfloat) (oversampling * oversampling); + buffer[1] = accum[1] / (gfloat) (oversampling * oversampling); + buffer[2] = accum[2] / (gfloat) (oversampling * oversampling); + buffer[3] = 1.0; + + buffer += 4; + } +} + +/** Plugin interface *********************************************************/ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Generate a huge variety of abstract patterns"), + "This Plug-in is based on an article by " + "Jörn Loviscach (appeared in c't 10/95, page 326). " + "It generates modern art pictures from a random " + "genetic formula.", + "Jörn Loviscach, Jens Ch. Restemeier", + "Jörn Loviscach, Jens Ch. Restemeier", + PLUG_IN_VERSION, + N_("_Qbist..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Render/Pattern"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + gint sel_x1, sel_y1, sel_width, sel_height; + gint img_height, img_width; + GimpRunMode run_mode; + gint32 drawable_id; + GimpPDBStatusType status; + + *nreturn_vals = 1; + *return_vals = values; + + status = GIMP_PDB_SUCCESS; + + if (param[0].type != GIMP_PDB_INT32) + status = GIMP_PDB_CALLING_ERROR; + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + if (param[2].type != GIMP_PDB_DRAWABLE) + status = GIMP_PDB_CALLING_ERROR; + + drawable_id = param[2].data.d_drawable; + + img_width = gimp_drawable_width (drawable_id); + img_height = gimp_drawable_height (drawable_id); + + if (! gimp_drawable_is_rgb (drawable_id)) + status = GIMP_PDB_CALLING_ERROR; + + if (! gimp_drawable_mask_intersect (drawable_id, + &sel_x1, &sel_y1, + &sel_width, &sel_height)) + { + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + return; + } + + if (status == GIMP_PDB_SUCCESS) + { + gr = g_rand_new (); + + memset (&qbist_info, 0, sizeof (qbist_info)); + create_info (&qbist_info.info); + qbist_info.oversampling = 4; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &qbist_info); + + /* Get information from the dialog */ + if (dialog_run ()) + { + status = GIMP_PDB_SUCCESS; + gimp_set_data (PLUG_IN_PROC, &qbist_info, sizeof (QbistInfo)); + } + else + status = GIMP_PDB_EXECUTION_ERROR; + break; + + case GIMP_RUN_NONINTERACTIVE: + status = GIMP_PDB_CALLING_ERROR; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &qbist_info); + status = GIMP_PDB_SUCCESS; + break; + + default: + status = GIMP_PDB_CALLING_ERROR; + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + GeglBuffer *buffer; + GeglBufferIterator *iter; + gint total_pixels = img_width * img_height; + gint done_pixels = 0; + + buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + iter = gegl_buffer_iterator_new (buffer, + GEGL_RECTANGLE (0, 0, + img_width, img_height), + 0, babl_format ("R'G'B'A float"), + GEGL_ACCESS_READWRITE, + GEGL_ABYSS_NONE, 1); + + optimize (&qbist_info.info); + + gimp_progress_init (_("Qbist")); + + while (gegl_buffer_iterator_next (iter)) + { + gfloat *data = iter->items[0].data; + GeglRectangle roi = iter->items[0].roi; + gint row; + + for (row = 0; row < roi.height; row++) + { + qbist (&qbist_info.info, + data + row * roi.width * 4, + roi.x, + roi.y + row, + roi.width, + sel_width, + sel_height, + qbist_info.oversampling); + } + + done_pixels += roi.width * roi.height; + + gimp_progress_update ((gdouble) done_pixels / + (gdouble) total_pixels); + } + + g_object_unref (buffer); + + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, sel_x1, sel_y1, + sel_width, sel_height); + + gimp_displays_flush (); + } + + g_rand_free (gr); + } + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; +} + +/** User interface ***********************************************************/ + +static GtkWidget *preview[9]; +static ExpInfo info[9]; +static ExpInfo last_info[9]; + +static void +dialog_new_variations (GtkWidget *widget, + gpointer data) +{ + gint i; + + for (i = 1; i < 9; i++) + modify_info (&(info[0]), &(info[i])); +} + +static void +dialog_update_previews (GtkWidget *widget, + gpointer data) +{ + static const Babl *fish = NULL; + + gfloat buf[PREVIEW_SIZE * PREVIEW_SIZE * 4]; + guchar u8_buf[PREVIEW_SIZE * PREVIEW_SIZE * 4]; + gint i, j; + + if (! fish) + fish = babl_fish (babl_format ("R'G'B'A float"), + babl_format ("R'G'B'A u8")); + + for (j = 0; j < 9; j++) + { + optimize (&info[(j + 5) % 9]); + + for (i = 0; i < PREVIEW_SIZE; i++) + { + qbist (&info[(j + 5) % 9], buf + i * PREVIEW_SIZE * 4, + 0, i, PREVIEW_SIZE, PREVIEW_SIZE, PREVIEW_SIZE, 1); + } + + babl_process (fish, buf, u8_buf, PREVIEW_SIZE * PREVIEW_SIZE); + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview[j]), + 0, 0, PREVIEW_SIZE, PREVIEW_SIZE, + GIMP_RGBA_IMAGE, + u8_buf, + PREVIEW_SIZE * 4); + } +} + +static void +dialog_select_preview (GtkWidget *widget, + ExpInfo *n_info) +{ + memcpy (last_info, info, sizeof (info)); + info[0] = *n_info; + dialog_new_variations (widget, NULL); + dialog_update_previews (widget, NULL); +} + +/* File I/O stuff */ + +static guint16 +get_be16 (guint8 *buf) +{ + return (guint16) buf[0] << 8 | buf[1]; +} + +static void +set_be16 (guint8 *buf, + guint16 val) +{ + buf[0] = val >> 8; + buf[1] = val & 0xFF; +} + +static gboolean +load_data (gchar *name) +{ + gint i; + FILE *f; + guint8 buf[288]; + + f = g_fopen (name, "rb"); + if (f == NULL) + { + return FALSE; + } + if (fread (buf, 1, sizeof (buf), f) != sizeof (buf)) + { + fclose (f); + return FALSE; + } + fclose (f); + + for (i = 0; i < MAX_TRANSFORMS; i++) + info[0].transformSequence[i] = + get_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 0); + + for (i = 0; i < MAX_TRANSFORMS; i++) + info[0].source[i] = get_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 1); + + for (i = 0; i < MAX_TRANSFORMS; i++) + info[0].control[i] = get_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 2); + + for (i = 0; i < MAX_TRANSFORMS; i++) + info[0].dest[i] = get_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 3); + + return TRUE; +} + +static gboolean +save_data (gchar *name) +{ + gint i = 0; + FILE *f; + guint8 buf[288]; + + f = g_fopen (name, "wb"); + if (f == NULL) + { + return FALSE; + } + + for (i = 0; i < MAX_TRANSFORMS; i++) + set_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 0, + info[0].transformSequence[i]); + + for (i = 0; i < MAX_TRANSFORMS; i++) + set_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 1, info[0].source[i]); + + for (i = 0; i < MAX_TRANSFORMS; i++) + set_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 2, info[0].control[i]); + + for (i = 0; i < MAX_TRANSFORMS; i++) + set_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 3, info[0].dest[i]); + + fwrite (buf, 1, sizeof (buf), f); + fclose (f); + + return TRUE; +} + +static void +dialog_undo (GtkWidget *widget, + gpointer data) +{ + ExpInfo temp_info[9]; + + memcpy (temp_info, info, sizeof (info)); + memcpy (info, last_info, sizeof (info)); + dialog_update_previews (NULL, NULL); + memcpy (last_info, temp_info, sizeof (info)); +} + +static void +dialog_load (GtkWidget *widget, + gpointer data) +{ + GtkWidget *parent; + GtkWidget *dialog; + + parent = gtk_widget_get_toplevel (widget); + + dialog = gtk_file_chooser_dialog_new (_("Load QBE File"), + GTK_WINDOW (parent), + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), qbist_info.path); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) + { + gchar *name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + strncpy (qbist_info.path, name, PATH_MAX - 1); + load_data (qbist_info.path); + + g_free (name); + + dialog_new_variations (NULL, NULL); + dialog_update_previews (NULL, NULL); + } + + gtk_widget_destroy (dialog); +} + +static void +dialog_save (GtkWidget *widget, + gpointer data) +{ + GtkWidget *parent; + GtkWidget *dialog; + + parent = gtk_widget_get_toplevel (widget); + + dialog = gtk_file_chooser_dialog_new (_("Save as QBE File"), + GTK_WINDOW (parent), + GTK_FILE_CHOOSER_ACTION_SAVE, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), + TRUE); + + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), qbist_info.path); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) + { + gchar *name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + strncpy (qbist_info.path, name, PATH_MAX - 1); + save_data (qbist_info.path); + + g_free (name); + } + + gtk_widget_destroy (dialog); +} + +static void +dialog_toggle_antialaising (GtkWidget *widget, + gpointer data) +{ + qbist_info.oversampling = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? 4 : 1; +} + +static gboolean +dialog_run (void) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *bbox; + GtkWidget *button; + GtkWidget *table; + gint i; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("G-Qbist"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + info[0] = qbist_info.info; + dialog_new_variations (NULL, NULL); + memcpy (last_info, info, sizeof (info)); + + for (i = 0; i < 9; i++) + { + button = gtk_button_new (); + gtk_table_attach (GTK_TABLE (table), + button, i % 3, (i % 3) + 1, i / 3, (i / 3) + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (dialog_select_preview), + (gpointer) & (info[(i + 5) % 9])); + + preview[i] = gimp_preview_area_new (); + gtk_widget_set_size_request (preview[i], PREVIEW_SIZE, PREVIEW_SIZE); + gtk_container_add (GTK_CONTAINER (button), preview[i]); + gtk_widget_show (preview[i]); + } + + button = gtk_check_button_new_with_mnemonic (_("_Antialiasing")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + qbist_info.oversampling > 1); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "toggled", + G_CALLBACK (dialog_toggle_antialaising), + NULL); + + bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_START); + gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); + gtk_widget_show (bbox); + + button = gtk_button_new_with_mnemonic (_("_Undo")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (dialog_undo), + NULL); + + button = gtk_button_new_with_mnemonic (_("_Open")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (dialog_load), + NULL); + + button = gtk_button_new_with_mnemonic (_("_Save")); + gtk_container_add (GTK_CONTAINER (bbox), button); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (dialog_save), + NULL); + + gtk_widget_show (dialog); + dialog_update_previews (NULL, NULL); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + qbist_info.info = info[0]; + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/sample-colorize.c b/plug-ins/common/sample-colorize.c new file mode 100644 index 0000000..bae918b --- /dev/null +++ b/plug-ins/common/sample-colorize.c @@ -0,0 +1,3113 @@ +/* sample_colorize.c + * A GIMP Plug-In by Wolfgang Hofer + */ + +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Some useful macros */ +#define RESPONSE_RESET 1 +#define RESPONSE_GET_COLORS 2 + +#define PLUG_IN_PROC "plug-in-sample-colorize" +#define PLUG_IN_BINARY "sample-colorize" +#define PLUG_IN_ROLE "gimp-sample-colorize" +#define NUMBER_IN_ARGS 13 + +#define LUMINOSITY_0(X) ((X[0] * 30 + X[1] * 59 + X[2] * 11)) +#define LUMINOSITY_1(X) ((X[0] * 30 + X[1] * 59 + X[2] * 11) / 100) +#define MIX_CHANNEL(a, b, m) (((a * m) + (b * (255 - m))) / 255) + +#define SMP_GRADIENT -444 +#define SMP_INV_GRADIENT -445 + + +#define PREVIEW_BPP 3 +#define PREVIEW_SIZE_X 256 +#define PREVIEW_SIZE_Y 256 +#define DA_WIDTH 256 +#define DA_HEIGHT 25 +#define GRADIENT_HEIGHT 15 +#define CONTROL_HEIGHT DA_HEIGHT - GRADIENT_HEIGHT +#define LEVELS_DA_MASK (GDK_EXPOSURE_MASK | \ + GDK_ENTER_NOTIFY_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_RELEASE_MASK | \ + GDK_BUTTON1_MOTION_MASK | \ + GDK_POINTER_MOTION_HINT_MASK) + +#define LOW_INPUT 0x1 +#define GAMMA 0x2 +#define HIGH_INPUT 0x4 +#define LOW_OUTPUT 0x8 +#define HIGH_OUTPUT 0x10 +#define INPUT_LEVELS 0x20 +#define OUTPUT_LEVELS 0x40 +#define INPUT_SLIDERS 0x80 +#define OUTPUT_SLIDERS 0x100 +#define DRAW 0x200 +#define REFRESH_DST 0x400 +#define ALL 0xFFF + +#define MC_GET_SAMPLE_COLORS 1 +#define MC_DST_REMAP 2 +#define MC_ALL (MC_GET_SAMPLE_COLORS | MC_DST_REMAP) + +typedef struct +{ + gint32 dst_id; + gint32 sample_id; + + gint32 hold_inten; /* TRUE or FALSE */ + gint32 orig_inten; /* TRUE or FALSE */ + gint32 rnd_subcolors; /* TRUE or FALSE */ + gint32 guess_missing; /* TRUE or FALSE */ + gint32 lvl_in_min; /* 0 up to 254 */ + gint32 lvl_in_max; /* 1 up to 255 */ + float lvl_in_gamma; /* 0.1 up to 10.0 (1.0 == linear) */ + gint32 lvl_out_min; /* 0 up to 254 */ + gint32 lvl_out_max; /* 1 up to 255 */ + + float tol_col_err; /* 0.0% up to 100.0% + * this is used to find out colors of the same + * colortone, while analyzing sample colors, + * It does not make much sense for the user to adjust this + * value. (I used a param file to find out a suitable value) + */ +} t_values; + +typedef struct +{ + GtkWidget *dialog; + GtkWidget *sample_preview; + GtkWidget *dst_preview; + GtkWidget *sample_colortab_preview; + GtkWidget *sample_drawarea; + GtkWidget *in_lvl_gray_preview; + GtkWidget *in_lvl_drawarea; + GtkAdjustment *adj_lvl_in_min; + GtkAdjustment *adj_lvl_in_max; + GtkAdjustment *adj_lvl_in_gamma; + GtkAdjustment *adj_lvl_out_min; + GtkAdjustment *adj_lvl_out_max; + GtkWidget *orig_inten_button; + gint active_slider; + gint slider_pos[5]; /* positions for the five sliders */ + + gboolean enable_preview_update; + gboolean sample_show_selection; + gboolean dst_show_selection; + gboolean sample_show_color; + gboolean dst_show_color; +} t_samp_interface; + + + +typedef struct +{ + guchar color[4]; /* R,G,B,A */ + gint32 sum_color; /* nr. of sourcepixels with (nearly the same) color */ + void *next; +} t_samp_color_elem; + + + +typedef struct +{ + gint32 all_samples; /* number of all source pixels with this luminosity */ + gint from_sample; /* TRUE: color found in sample, FALSE: interpolated color added */ + t_samp_color_elem *col_ptr; /* List of sample colors at same luminosity */ +} t_samp_table_elem; + + +typedef struct +{ + gint32 drawable_id; + GeglBuffer *buffer; + const Babl *format; + gint width; + gint height; + void *sel_gdrw; + gint x1; + gint y1; + gint x2; + gint y2; + gint index_alpha; /* 0 == no alpha, 1 == GREYA, 3 == RGBA */ + gint bpp; + gint tile_width; + gint tile_height; + gint shadow; + gint32 seldeltax; + gint32 seldeltay; +} t_GDRW; + +/* + * Some globals + */ + +static t_samp_interface g_di; /* global dialog interface variables */ +static t_values g_values = { -1, -1, 1, 1, 0, 1, 0, 255, 1.0, 0, 255, 5.5 }; +static t_samp_table_elem g_lum_tab[256]; +static guchar g_lvl_trans_tab[256]; +static guchar g_out_trans_tab[256]; +static guchar g_sample_color_tab[256 * 3]; +static guchar g_dst_preview_buffer[PREVIEW_SIZE_X * PREVIEW_SIZE_Y * 4 ]; /* color copy with mask of dst in previewsize */ + +static gint32 g_tol_col_err; +static gint32 g_max_col_err; +static gint g_Sdebug = FALSE; +static gint g_show_progress = FALSE; + +/* Declare a local function. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint main_colorize (gint mc_flags); +static void get_filevalues (void); +static void smp_dialog (void); +static void refresh_dst_preview (GtkWidget *preview, + guchar *src_buffer); +static void update_preview (gint32 *id_ptr); +static void clear_tables (void); +static void free_colors (void); +static void levels_update (gint update); +static gint level_in_events (GtkWidget *widget, + GdkEvent *event); +static gint level_out_events (GtkWidget *widget, + GdkEvent *event); +static void calculate_level_transfers (void); +static void get_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *pixel); +static void init_gdrw (t_GDRW *gdrw, + gint32 drawable_id, + gboolean shadow); +static void end_gdrw (t_GDRW *gdrw); +static gint32 is_layer_alive (gint32 drawable_id); +static void remap_pixel (guchar *pixel, + const guchar *original, + gint bpp2); +static void guess_missing_colors (void); +static void fill_missing_colors (void); +static void smp_get_colors (GtkWidget *dialog); +static void get_gradient (gint mode); +static void clear_preview (GtkWidget *preview); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[]= + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "dst-drawable", "The drawable to be colorized (Type GRAY* or RGB*)" }, + { GIMP_PDB_DRAWABLE, "sample-drawable", "Sample drawable (should be of Type RGB or RGBA)" }, + { GIMP_PDB_INT32, "hold-inten", "hold brightness intensity levels (TRUE, FALSE)" }, + { GIMP_PDB_INT32, "orig-inten", "TRUE: hold brightness of original intensity levels. FALSE: Hold Intensity of input levels" }, + { GIMP_PDB_INT32, "rnd-subcolors", "TRUE: Use all subcolors of same intensity, FALSE: use only one color per intensity" }, + { GIMP_PDB_INT32, "guess-missing", "TRUE: guess samplecolors for the missing intensity values FALSE: use only colors found in the sample" }, + { GIMP_PDB_INT32, "in-low", "intensity of lowest input (0 <= in_low <= 254)" }, + { GIMP_PDB_INT32, "in-high", "intensity of highest input (1 <= in_high <= 255)" }, + { GIMP_PDB_FLOAT, "gamma", "gamma adjustment factor (0.1 <= gamma <= 10) where 1.0 is linear" }, + { GIMP_PDB_INT32, "out-low", "lowest sample color intensity (0 <= out_low <= 254)" }, + { GIMP_PDB_INT32, "out-high", "highest sample color intensity (1 <= out_high <= 255)" } + }; + + static gchar *help_string = + "This plug-in colorizes the contents of the specified (gray) layer" + " with the help of a sample (color) layer." + " It analyzes all colors in the sample layer." + " The sample colors are sorted by brightness (== intentisty) and amount" + " and stored in a sample colortable (where brightness is the index)" + " The pixels of the destination layer are remapped with the help of the" + " sample colortable. If use_subcolors is TRUE, the remapping process uses" + " all sample colors of the corresponding brightness-intensity and" + " distributes the subcolors according to their amount in the sample" + " (If the sample has 5 green, 3 yellow, and 1 red pixel of the " + " intensity value 105, the destination pixels at intensity value 105" + " are randomly painted in green, yellow and red in a relation of 5:3:1" + " If use_subcolors is FALSE only one sample color per intensity is used." + " (green will be used in this example)" + " The brightness intensity value is transformed at the remapping process" + " according to the levels: out_lo, out_hi, in_lo, in_high and gamma" + " The in_low / in_high levels specify an initial mapping of the intensity." + " The gamma value determines how intensities are interpolated between" + " the in_lo and in_high levels. A gamma value of 1.0 results in linear" + " interpolation. Higher gamma values results in more high-level intensities" + " Lower gamma values results in more low-level intensities" + " The out_low/out_high levels constrain the resulting intensity index" + " The intensity index is used to pick the corresponding color" + " in the sample colortable. If hold_inten is FALSE the picked color" + " is used 1:1 as resulting remap_color." + " If hold_inten is TRUE The brightness of the picked color is adjusted" + " back to the origial intensity value (only hue and saturation are" + " taken from the picked sample color)" + " (or to the input level, if orig_inten is set FALSE)" + " Works on both Grayscale and RGB image with/without alpha channel." + " (the image with the dst_drawable is converted to RGB if necessary)" + " The sample_drawable should be of type RGB or RGBA"; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Colorize image using a sample image as a guide"), + help_string, + "Wolfgang Hofer", + "hof@hotbot.com", + "02/2000", + N_("_Sample Colorize..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Colors/Map"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + const gchar *env; + GimpRunMode run_mode; + gint32 drawable_id; + + INIT_I18N (); + gegl_init (NULL, NULL); + + env = g_getenv ("SAMPLE_COLORIZE_DEBUG"); + if (env != NULL && (*env != 'n') && (*env != 'N')) + g_Sdebug = TRUE; + + if (g_Sdebug) + g_printf ("sample colorize run\n"); + g_show_progress = FALSE; + + run_mode = param[0].data.d_int32; + drawable_id = param[2].data.d_drawable; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + g_values.lvl_out_min = 0; + g_values.lvl_out_max = 255; + g_values.lvl_in_min = 0; + g_values.lvl_in_max = 255; + g_values.lvl_in_gamma = 1.0; + + /* Possibly retrieve data from a previous run */ + gimp_get_data (PLUG_IN_PROC, &g_values); + if (g_values.sample_id == SMP_GRADIENT || + g_values.sample_id == SMP_INV_GRADIENT) + g_values.sample_id = -1; + + /* fix value */ + g_values.tol_col_err = 5.5; + + /* Get the specified dst_drawable */ + g_values.dst_id = drawable_id; + + clear_tables (); + + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_is_rgb (drawable_id) || + gimp_drawable_is_gray (drawable_id)) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + smp_dialog (); + free_colors (); + gimp_set_data (PLUG_IN_PROC, &g_values, sizeof (t_values)); + gimp_displays_flush (); + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams == NUMBER_IN_ARGS) + { + g_values.sample_id = param[3].data.d_drawable; + g_values.hold_inten = param[4].data.d_int32; + g_values.orig_inten = param[5].data.d_int32; + g_values.rnd_subcolors = param[6].data.d_int32; + g_values.guess_missing = param[7].data.d_int32; + g_values.lvl_in_min = param[8].data.d_int32; + g_values.lvl_in_max = param[9].data.d_int32; + g_values.lvl_in_gamma = param[10].data.d_float; + g_values.lvl_out_min = param[11].data.d_int32; + g_values.lvl_out_max = param[12].data.d_int32; + if (main_colorize (MC_GET_SAMPLE_COLORS) >= 0) + { + main_colorize (MC_DST_REMAP); + status = GIMP_PDB_SUCCESS; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + break; + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + values[0].data.d_status = status; +} + +/* ============================================================================ + * callback and constraint procedures for the dialog + * ============================================================================ + */ + +static void +smp_response_callback (GtkWidget *widget, + gint response_id) +{ + switch (response_id) + { + case RESPONSE_RESET: + g_values.lvl_in_min = 0; + g_values.lvl_in_max = 255; + g_values.lvl_in_gamma = 1.0; + g_values.lvl_out_min = 0; + g_values.lvl_out_max = 255; + + levels_update (ALL); + break; + + case RESPONSE_GET_COLORS: + smp_get_colors (widget); + break; + + case GTK_RESPONSE_APPLY: + g_show_progress = TRUE; + if (main_colorize (MC_DST_REMAP) == 0) + { + gimp_displays_flush (); + g_show_progress = FALSE; + return; + } + gtk_dialog_set_response_sensitive (GTK_DIALOG (widget), + GTK_RESPONSE_APPLY, FALSE); + break; + + default: + gtk_widget_destroy (widget); + gtk_main_quit (); + break; + } +} + +static void +smp_toggle_callback (GtkWidget *widget, + gpointer data) +{ + gboolean *toggle_val = (gboolean *)data; + + *toggle_val = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if ((data == &g_di.sample_show_selection) || + (data == &g_di.sample_show_color)) + { + update_preview (&g_values.sample_id); + return; + } + + if ((data == &g_di.dst_show_selection) || + (data == &g_di.dst_show_color)) + { + update_preview (&g_values.dst_id); + return; + } + + if ((data == &g_values.hold_inten) || + (data == &g_values.orig_inten) || + (data == &g_values.rnd_subcolors)) + { + if (g_di.orig_inten_button) + gtk_widget_set_sensitive (g_di.orig_inten_button,g_values.hold_inten); + refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); + } + + if (data == &g_values.guess_missing) + { + if (g_values.guess_missing) + guess_missing_colors (); + else + fill_missing_colors (); + smp_get_colors (NULL); + } +} + +static void +smp_sample_combo_callback (GtkWidget *widget) +{ + gint value; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value); + + g_values.sample_id = value; + + if (value == SMP_GRADIENT || value == SMP_INV_GRADIENT) + { + get_gradient (value); + smp_get_colors (NULL); + + if (g_di.sample_preview) + clear_preview (g_di.sample_preview); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog), + GTK_RESPONSE_APPLY, TRUE); + gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog), + RESPONSE_GET_COLORS, FALSE); + } + else + { + update_preview (&g_values.sample_id); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog), + RESPONSE_GET_COLORS, TRUE); + } +} + +static void +smp_dest_combo_callback (GtkWidget *widget) +{ + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), + &g_values.dst_id); + + update_preview (&g_values.dst_id); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog), + RESPONSE_GET_COLORS, TRUE); +} + +static gint +smp_constrain (gint32 image_id, + gint32 drawable_id, + gpointer data) +{ + if (image_id < 0) + return FALSE; + + /* don't accept layers from indexed images */ + if (gimp_drawable_is_indexed (drawable_id)) + return FALSE; + + return TRUE; +} + + +static void +smp_adj_lvl_in_max_upd_callback (GtkAdjustment *adjustment) +{ + gint32 value; + gint upd_flags; + + value = CLAMP ((gtk_adjustment_get_value (adjustment)), 1, 255); + + if (value != g_values.lvl_in_max) + { + g_values.lvl_in_max = value; + upd_flags = INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST; + if (g_values.lvl_in_max < g_values.lvl_in_min) + { + g_values.lvl_in_min = g_values.lvl_in_max; + upd_flags |= LOW_INPUT; + } + levels_update (upd_flags); + } +} + +static void +smp_adj_lvl_in_min_upd_callback (GtkAdjustment *adjustment) +{ + double value; + gint upd_flags; + + value = CLAMP ((gtk_adjustment_get_value (adjustment)), 0, 254); + + if (value != g_values.lvl_in_min) + { + g_values.lvl_in_min = value; + upd_flags = INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST; + if (g_values.lvl_in_min > g_values.lvl_in_max) + { + g_values.lvl_in_max = g_values.lvl_in_min; + upd_flags |= HIGH_INPUT; + } + levels_update (upd_flags); + } +} + +static void +smp_text_gamma_upd_callback (GtkAdjustment *adjustment) +{ + double value; + + value = CLAMP ((gtk_adjustment_get_value (adjustment)), 0.1, 10.0); + + if (value != g_values.lvl_in_gamma) + { + g_values.lvl_in_gamma = value; + levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST); + } +} + +static void +smp_adj_lvl_out_max_upd_callback (GtkAdjustment *adjustment) +{ + gint32 value; + gint upd_flags; + + value = CLAMP ((gtk_adjustment_get_value (adjustment)), 1, 255); + + if (value != g_values.lvl_out_max) + { + g_values.lvl_out_max = value; + upd_flags = OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW | REFRESH_DST; + if (g_values.lvl_out_max < g_values.lvl_out_min) + { + g_values.lvl_out_min = g_values.lvl_out_max; + upd_flags |= LOW_OUTPUT; + } + levels_update (upd_flags); + } +} + +static void +smp_adj_lvl_out_min_upd_callback (GtkAdjustment *adjustment) +{ + double value; + gint upd_flags; + + value = CLAMP ((gtk_adjustment_get_value (adjustment)), 0, 254); + + if (value != g_values.lvl_out_min) + { + g_values.lvl_out_min = value; + upd_flags = OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW | REFRESH_DST; + if (g_values.lvl_out_min > g_values.lvl_out_max) + { + g_values.lvl_out_max = g_values.lvl_out_min; + upd_flags |= HIGH_OUTPUT; + } + levels_update (upd_flags); + } +} + +/* ============================================================================ + * DIALOG helper procedures + * (workers for the updates on the preview widgets) + * ============================================================================ + */ + +static void +refresh_dst_preview (GtkWidget *preview, + guchar *src_buffer) +{ + guchar allrowsbuf[3 * PREVIEW_SIZE_X * PREVIEW_SIZE_Y]; + guchar *ptr; + guchar *src_ptr; + guchar lum; + guchar maskbyte; + gint x, y; + gint preview_bpp; + gint src_bpp; + + preview_bpp = PREVIEW_BPP; + src_bpp = PREVIEW_BPP +1; /* 3 colors + 1 maskbyte */ + src_ptr = src_buffer; + + ptr = allrowsbuf; + for (y = 0; y < PREVIEW_SIZE_Y; y++) + { + for (x = 0; x < PREVIEW_SIZE_X; x++) + { + if ((maskbyte = src_ptr[3]) == 0) + { + ptr[0] = src_ptr[0]; + ptr[1] = src_ptr[1]; + ptr[2] = src_ptr[2]; + } + else + { + if (g_di.dst_show_color) + { + remap_pixel (ptr, src_ptr, 3); + } + else + { + /* lum = g_out_trans_tab[g_lvl_trans_tab[LUMINOSITY_1(src_ptr)]]; */ + /* get brightness from (uncolorized) original */ + + lum = g_lvl_trans_tab[LUMINOSITY_1 (src_ptr)]; + /* get brightness from (uncolorized) original */ + + ptr[0] = lum; + ptr[1] = lum; + ptr[2] = lum; + } + + if (maskbyte < 255) + { + ptr[0] = MIX_CHANNEL (ptr[0], src_ptr[0], maskbyte); + ptr[1] = MIX_CHANNEL (ptr[1], src_ptr[1], maskbyte); + ptr[2] = MIX_CHANNEL (ptr[2], src_ptr[2], maskbyte); + } + } + ptr += preview_bpp; + src_ptr += src_bpp; + } + } + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), + 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, + GIMP_RGB_IMAGE, + allrowsbuf, + PREVIEW_SIZE_X * 3); +} + +static void +clear_preview (GtkWidget *preview) +{ + gimp_preview_area_fill (GIMP_PREVIEW_AREA (preview), + 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, + 170, 170, 170); +} + +static void +update_pv (GtkWidget *preview, + gboolean show_selection, + t_GDRW *gdrw, + guchar *dst_buffer, + gboolean is_color) +{ + guchar allrowsbuf[4 * PREVIEW_SIZE_X * PREVIEW_SIZE_Y]; + guchar pixel[4]; + guchar *ptr; + gint x, y; + gint x2, y2; + gint ofx, ofy; + gint sel_width, sel_height; + double scale_x, scale_y; + guchar *buf_ptr; + guchar dummy[4]; + guchar maskbytes[4]; + gint dstep; + guchar alpha; + + + if (!preview) + return; + + /* init gray pixel (if we are called without a sourceimage (gdwr == NULL) */ + pixel[0] = pixel[1] =pixel[2] = pixel[3] = 127; + + /* calculate scale factors and offsets */ + if (show_selection) + { + sel_width = gdrw->x2 - gdrw->x1; + sel_height = gdrw->y2 - gdrw->y1; + + if (sel_height > sel_width) + { + scale_y = (gfloat) sel_height / PREVIEW_SIZE_Y; + scale_x = scale_y; + ofx = (gdrw->x1 + + ((sel_width - (PREVIEW_SIZE_X * scale_x)) / 2)); + ofy = gdrw->y1; + } + else + { + scale_x = (gfloat) sel_width / PREVIEW_SIZE_X; + scale_y = scale_x; + ofx = gdrw->x1; + ofy = (gdrw->y1 + + ((sel_height - (PREVIEW_SIZE_Y * scale_y)) / 2)); + } + } + else + { + if (gdrw->height > gdrw->width) + { + scale_y = (gfloat) gdrw->height / PREVIEW_SIZE_Y; + scale_x = scale_y; + ofx = (gdrw->width - (PREVIEW_SIZE_X * scale_x)) / 2; + ofy = 0; + } + else + { + scale_x = (gfloat) gdrw->width / PREVIEW_SIZE_X; + scale_y = scale_x; + ofx = 0; + ofy = (gdrw->height - (PREVIEW_SIZE_Y * scale_y)) / 2; + } + } + + /* check if output goes to previw widget or to dst_buffer */ + if (dst_buffer) + { + buf_ptr = dst_buffer; + dstep = PREVIEW_BPP +1; + } + else + { + buf_ptr = &dummy[0]; + dstep = 0; + } + + + /* render preview */ + ptr = allrowsbuf; + for (y = 0; y < PREVIEW_SIZE_Y; y++) + { + for (x = 0; x < PREVIEW_SIZE_X; x++) + { + if (gdrw->drawable_id > 0) + { + x2 = ofx + (x * scale_x); + y2 = ofy + (y * scale_y); + get_pixel (gdrw, x2, y2, &pixel[0]); + if (gdrw->sel_gdrw) + { + get_pixel (gdrw->sel_gdrw, + x2 + gdrw->seldeltax, + y2 + gdrw->seldeltay, + &maskbytes[0]); + } + else + { + maskbytes[0] = 255; + } + } + + alpha = pixel[gdrw->index_alpha]; + if (is_color && (gdrw->bpp > 2)) + { + buf_ptr[0] = ptr[0] = pixel[0]; + buf_ptr[1] = ptr[1] = pixel[1]; + buf_ptr[2] = ptr[2] = pixel[2]; + } + else + { + if (gdrw->bpp > 2) + *ptr = LUMINOSITY_1 (pixel); + else + *ptr = pixel[0]; + + *buf_ptr = *ptr; + buf_ptr[1] = ptr[1] = *ptr; + buf_ptr[2] = ptr[2] = *ptr; + } + if (gdrw->index_alpha == 0) /* has no alpha channel */ + buf_ptr[3] = ptr[3] = 255; + else + buf_ptr[3] = ptr[3] = MIN (maskbytes[0], alpha); + buf_ptr += dstep; /* advance (or stay at dummy byte) */ + ptr += 4; + } + + } + + if (dst_buffer == NULL) + { + gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview), + 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, + GIMP_RGBA_IMAGE, + allrowsbuf, + PREVIEW_SIZE_X * 4); + gtk_widget_queue_draw (preview); + } +} + +static void +update_preview (gint32 *id_ptr) +{ + t_GDRW gdrw; + gboolean drawable = FALSE; + + if (g_Sdebug) + g_printf ("UPD PREVIEWS ID:%d ENABLE_UPD:%d\n", + id_ptr ? (int) *id_ptr : -1, (int)g_di.enable_preview_update); + + if (id_ptr == NULL || !g_di.enable_preview_update) + return; + if (is_layer_alive (*id_ptr) < 0) + { + /* clear preview on invalid drawable id + * (SMP_GRADIENT and SMP_INV_GRADIENT) + */ + if (id_ptr == &g_values.sample_id) + clear_preview (g_di.sample_preview); + if (id_ptr == &g_values.dst_id) + clear_preview (g_di.dst_preview); + return; + } + + if (id_ptr == &g_values.sample_id) + { + drawable = TRUE; + + init_gdrw (&gdrw, *id_ptr, FALSE); + update_pv (g_di.sample_preview, g_di.sample_show_selection, &gdrw, + NULL, g_di.sample_show_color); + } + else if (id_ptr == &g_values.dst_id) + { + drawable = TRUE; + + init_gdrw (&gdrw, *id_ptr, FALSE); + update_pv (g_di.dst_preview, g_di.dst_show_selection, &gdrw, + &g_dst_preview_buffer[0], g_di.dst_show_color); + refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); + } + + if (drawable) + end_gdrw (&gdrw); +} + +static void +levels_draw_slider (cairo_t *cr, + GdkColor *border_color, + GdkColor *fill_color, + gint xpos) +{ + cairo_move_to (cr, xpos, 0); + cairo_line_to (cr, xpos - (CONTROL_HEIGHT - 1) / 2, CONTROL_HEIGHT - 1); + cairo_line_to (cr, xpos + (CONTROL_HEIGHT - 1) / 2, CONTROL_HEIGHT - 1); + cairo_line_to (cr, xpos, 0); + + gdk_cairo_set_source_color (cr, fill_color); + cairo_fill_preserve (cr); + + gdk_cairo_set_source_color (cr, border_color); + cairo_stroke (cr); +} + +static void +smp_get_colors (GtkWidget *dialog) +{ + gint i; + guchar buffer[3 * DA_WIDTH * GRADIENT_HEIGHT]; + + update_preview (&g_values.sample_id); + + if (dialog && main_colorize (MC_GET_SAMPLE_COLORS) >= 0) /* do not colorize, just analyze sample colors */ + gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog), + GTK_RESPONSE_APPLY, TRUE); + for (i = 0; i < GRADIENT_HEIGHT; i++) + memcpy (buffer + i * 3 * DA_WIDTH, g_sample_color_tab, 3 * DA_WIDTH); + + update_preview (&g_values.dst_id); + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (g_di.sample_colortab_preview), + 0, 0, DA_WIDTH, GRADIENT_HEIGHT, + GIMP_RGB_IMAGE, + buffer, + DA_WIDTH * 3); +} + + +static void +levels_update (gint update) +{ + gint i; + + if (g_Sdebug) + g_printf ("levels_update: update reques %x\n", update); + + /* Recalculate the transfer array */ + calculate_level_transfers (); + if (update & REFRESH_DST) + refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); + + /* update the spinbutton entry widgets */ + if (update & LOW_INPUT) + gtk_adjustment_set_value (g_di.adj_lvl_in_min, + g_values.lvl_in_min); + if (update & GAMMA) + gtk_adjustment_set_value (g_di.adj_lvl_in_gamma, + g_values.lvl_in_gamma); + if (update & HIGH_INPUT) + gtk_adjustment_set_value (g_di.adj_lvl_in_max, + g_values.lvl_in_max); + if (update & LOW_OUTPUT) + gtk_adjustment_set_value (g_di.adj_lvl_out_min, + g_values.lvl_out_min); + if (update & HIGH_OUTPUT) + gtk_adjustment_set_value (g_di.adj_lvl_out_max, + g_values.lvl_out_max); + if (update & INPUT_LEVELS) + { + guchar buffer[DA_WIDTH * GRADIENT_HEIGHT]; + for (i = 0; i < GRADIENT_HEIGHT; i++) + memcpy (buffer + DA_WIDTH * i, g_lvl_trans_tab, DA_WIDTH); + gimp_preview_area_draw (GIMP_PREVIEW_AREA (g_di.in_lvl_gray_preview), + 0, 0, DA_WIDTH, GRADIENT_HEIGHT, + GIMP_GRAY_IMAGE, + buffer, + DA_WIDTH); + } + + if (update & INPUT_SLIDERS) + { + GtkStyle *style = gtk_widget_get_style (g_di.in_lvl_drawarea); + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (g_di.in_lvl_drawarea)); + gdouble width, mid, tmp; + + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]); + cairo_paint (cr); + + cairo_translate (cr, 0.5, 0.5); + cairo_set_line_width (cr, 1.0); + + g_di.slider_pos[0] = DA_WIDTH * ((double) g_values.lvl_in_min / 255.0); + g_di.slider_pos[2] = DA_WIDTH * ((double) g_values.lvl_in_max / 255.0); + + width = (double) (g_di.slider_pos[2] - g_di.slider_pos[0]) / 2.0; + mid = g_di.slider_pos[0] + width; + tmp = log10 (1.0 / g_values.lvl_in_gamma); + g_di.slider_pos[1] = (int) (mid + width * tmp + 0.5); + + levels_draw_slider (cr, + &style->black, + &style->dark[GTK_STATE_NORMAL], + g_di.slider_pos[1]); + levels_draw_slider (cr, + &style->black, + &style->black, + g_di.slider_pos[0]); + levels_draw_slider (cr, + &style->black, + &style->white, + g_di.slider_pos[2]); + + cairo_destroy (cr); + } + + if (update & OUTPUT_SLIDERS) + { + GtkStyle *style = gtk_widget_get_style (g_di.sample_drawarea); + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (g_di.sample_drawarea)); + + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]); + cairo_paint (cr); + + cairo_translate (cr, 0.5, 0.5); + cairo_set_line_width (cr, 1.0); + + g_di.slider_pos[3] = DA_WIDTH * ((double) g_values.lvl_out_min / 255.0); + g_di.slider_pos[4] = DA_WIDTH * ((double) g_values.lvl_out_max / 255.0); + + levels_draw_slider (cr, + &style->black, + &style->black, + g_di.slider_pos[3]); + levels_draw_slider (cr, + &style->black, + &style->black, + g_di.slider_pos[4]); + + cairo_destroy (cr); + } +} + +static gboolean +level_in_events (GtkWidget *widget, + GdkEvent *event) +{ + GdkEventButton *bevent; + GdkEventMotion *mevent; + gdouble width, mid, tmp; + gint x, distance; + gint i; + gint update = FALSE; + + switch (event->type) + { + case GDK_EXPOSE: + if (g_Sdebug) + g_printf ("EVENT: GDK_EXPOSE\n"); + if (widget == g_di.in_lvl_drawarea) + levels_update (INPUT_SLIDERS); + break; + + case GDK_BUTTON_PRESS: + if (g_Sdebug) + g_printf ("EVENT: GDK_BUTTON_PRESS\n"); + gtk_grab_add (widget); + bevent = (GdkEventButton *) event; + + distance = G_MAXINT; + for (i = 0; i < 3; i++) + { + if (fabs (bevent->x - g_di.slider_pos[i]) < distance) + { + g_di.active_slider = i; + distance = fabs (bevent->x - g_di.slider_pos[i]); + } + } + x = bevent->x; + update = TRUE; + break; + + case GDK_BUTTON_RELEASE: + if (g_Sdebug) + g_printf ("EVENT: GDK_BUTTON_RELEASE\n"); + gtk_grab_remove (widget); + switch (g_di.active_slider) + { + case 0: /* low input */ + levels_update (LOW_INPUT | GAMMA | DRAW); + break; + + case 1: /* gamma */ + levels_update (GAMMA); + break; + + case 2: /* high input */ + levels_update (HIGH_INPUT | GAMMA | DRAW); + break; + } + + refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); + break; + + case GDK_MOTION_NOTIFY: + if (g_Sdebug) + g_printf ("EVENT: GDK_MOTION_NOTIFY\n"); + mevent = (GdkEventMotion *) event; + x = mevent->x; + gdk_event_request_motions (mevent); + update = TRUE; + break; + + default: + if (g_Sdebug) + g_printf ("EVENT: default\n"); + break; + } + + if (update) + { + if (g_Sdebug) + g_printf ("EVENT: ** update **\n"); + switch (g_di.active_slider) + { + case 0: /* low input */ + g_values.lvl_in_min = ((double) x / (double) DA_WIDTH) * 255.0; + g_values.lvl_in_min = CLAMP (g_values.lvl_in_min, + 0, g_values.lvl_in_max); + break; + + case 1: /* gamma */ + width = (double) (g_di.slider_pos[2] - g_di.slider_pos[0]) / 2.0; + mid = g_di.slider_pos[0] + width; + + x = CLAMP (x, g_di.slider_pos[0], g_di.slider_pos[2]); + tmp = (double) (x - mid) / width; + g_values.lvl_in_gamma = 1.0 / pow (10, tmp); + + /* round the gamma value to the nearest 1/100th */ + g_values.lvl_in_gamma = + floor (g_values.lvl_in_gamma * 100 + 0.5) / 100.0; + break; + + case 2: /* high input */ + g_values.lvl_in_max = ((double) x / (double) DA_WIDTH) * 255.0; + g_values.lvl_in_max = CLAMP (g_values.lvl_in_max, + g_values.lvl_in_min, 255); + break; + } + + levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW); + } + + return FALSE; +} + +static gboolean +level_out_events (GtkWidget *widget, + GdkEvent *event) +{ + GdkEventButton *bevent; + GdkEventMotion *mevent; + gint x, distance; + gint i; + gint update = FALSE; + + switch (event->type) + { + case GDK_EXPOSE: + if (g_Sdebug) + g_printf ("OUT_EVENT: GDK_EXPOSE\n"); + if (widget == g_di.sample_drawarea) + levels_update (OUTPUT_SLIDERS); + break; + + case GDK_BUTTON_PRESS: + if (g_Sdebug) + g_printf ("OUT_EVENT: GDK_BUTTON_PRESS\n"); + bevent = (GdkEventButton *) event; + + distance = G_MAXINT; + for (i = 3; i < 5; i++) + { + if (fabs (bevent->x - g_di.slider_pos[i]) < distance) + { + g_di.active_slider = i; + distance = fabs (bevent->x - g_di.slider_pos[i]); + } + } + + x = bevent->x; + update = TRUE; + break; + + case GDK_BUTTON_RELEASE: + if (g_Sdebug) + g_printf ("OUT_EVENT: GDK_BUTTON_RELEASE\n"); + switch (g_di.active_slider) + { + case 3: /* low output */ + levels_update (LOW_OUTPUT | DRAW); + break; + + case 4: /* high output */ + levels_update (HIGH_OUTPUT | DRAW); + break; + } + + refresh_dst_preview (g_di.dst_preview, &g_dst_preview_buffer[0]); + break; + + case GDK_MOTION_NOTIFY: + if (g_Sdebug) + g_printf ("OUT_EVENT: GDK_MOTION_NOTIFY\n"); + mevent = (GdkEventMotion *) event; + x = mevent->x; + gdk_event_request_motions (mevent); + update = TRUE; + break; + + default: + if (g_Sdebug) + g_printf ("OUT_EVENT: default\n"); + break; + } + + if (update) + { + if (g_Sdebug) + g_printf ("OUT_EVENT: ** update **\n"); + switch (g_di.active_slider) + { + case 3: /* low output */ + g_values.lvl_out_min = ((double) x / (double) DA_WIDTH) * 255.0; + g_values.lvl_out_min = CLAMP (g_values.lvl_out_min, + 0, g_values.lvl_out_max); + break; + + case 4: /* high output */ + g_values.lvl_out_max = ((double) x / (double) DA_WIDTH) * 255.0; + g_values.lvl_out_max = CLAMP (g_values.lvl_out_max, + g_values.lvl_out_min, 255); + break; + } + + levels_update (OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW); + } + + return FALSE; +} + + +/* ============================================================================ + * smp_dialog + * The Interactive Dialog + * ============================================================================ + */ +static void +smp_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *vbox2; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *check_button; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *spinbutton; + GtkObject *data; + gint ty; + + /* set flags for check buttons from mode value bits */ + if (g_Sdebug) + g_print ("smp_dialog START\n"); + + /* init some dialog variables */ + g_di.enable_preview_update = FALSE; + g_di.sample_show_selection = TRUE; + g_di.dst_show_selection = TRUE; + g_di.dst_show_color = TRUE; + g_di.sample_show_color = TRUE; + g_di.orig_inten_button = NULL; + + /* Init GTK */ + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + /* Main Dialog */ + g_di.dialog = dialog = + gimp_dialog_new (_("Sample Colorize"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Reset"), RESPONSE_RESET, + _("Get _Sample Colors"), RESPONSE_GET_COLORS, + _("_Close"), GTK_RESPONSE_CLOSE, + _("_Apply"), GTK_RESPONSE_APPLY, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + RESPONSE_GET_COLORS, + RESPONSE_RESET, + GTK_RESPONSE_APPLY, + GTK_RESPONSE_CLOSE, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + g_signal_connect (dialog, "response", + G_CALLBACK (smp_response_callback), + NULL); + + /* table for values */ + table = gtk_table_new (7, 5, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 12); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + table, TRUE, TRUE, 0); + + ty = 0; + /* layer combo_box (Dst) */ + label = gtk_label_new (_("Destination:")); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, ty, ty + 1, + GTK_FILL, GTK_FILL, 4, 0); + gtk_widget_show (label); + + combo = gimp_layer_combo_box_new (smp_constrain, NULL); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), g_values.dst_id, + G_CALLBACK (smp_dest_combo_callback), + NULL); + + gtk_table_attach (GTK_TABLE (table), combo, 1, 2, ty, ty + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + /* layer combo_box (Sample) */ + label = gtk_label_new (_("Sample:")); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_table_attach (GTK_TABLE (table), label, 3, 4, ty, ty + 1, + GTK_FILL, GTK_FILL, 4, 0); + gtk_widget_show (label); + + combo = gimp_layer_combo_box_new (smp_constrain, NULL); + + gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo), + GIMP_INT_STORE_VALUE, SMP_INV_GRADIENT, + GIMP_INT_STORE_LABEL, _("From reverse gradient"), + GIMP_INT_STORE_ICON_NAME, GIMP_ICON_GRADIENT, + -1); + gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo), + GIMP_INT_STORE_VALUE, SMP_GRADIENT, + GIMP_INT_STORE_LABEL, _("From gradient"), + GIMP_INT_STORE_ICON_NAME, GIMP_ICON_GRADIENT, + -1); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), g_values.sample_id, + G_CALLBACK (smp_sample_combo_callback), + NULL); + + gtk_table_attach (GTK_TABLE (table), combo, 4, 5, ty, ty + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + ty++; + + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_table_attach (GTK_TABLE (table), hbox, 0, 2, ty, ty + 1, + GTK_FILL, 0, 0, 0); + gtk_widget_show (hbox); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("Sho_w selection")); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_di.dst_show_selection); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_di.dst_show_selection); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("Show co_lor")); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_di.dst_show_color); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_di.dst_show_color); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_table_attach (GTK_TABLE (table), hbox, 3, 5, ty, ty + 1, + GTK_FILL, 0, 0, 0); + gtk_widget_show (hbox); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("Show selec_tion")); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_di.sample_show_selection); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_di.sample_show_selection); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("Show c_olor")); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_di.sample_show_color); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_di.sample_show_color); + + ty++; + + /* Preview (Dst) */ + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_table_attach (GTK_TABLE (table), + frame, 0, 2, ty, ty + 1, 0, 0, 0, 0); + gtk_widget_show (frame); + + g_di.dst_preview = gimp_preview_area_new (); + gtk_widget_set_size_request (g_di.dst_preview, + PREVIEW_SIZE_X, PREVIEW_SIZE_Y); + gtk_container_add (GTK_CONTAINER (frame), g_di.dst_preview); + gtk_widget_show (g_di.dst_preview); + + /* Preview (Sample)*/ + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_table_attach (GTK_TABLE (table), + frame, 3, 5, ty, ty + 1, 0, 0, 0, 0); + gtk_widget_show (frame); + + g_di.sample_preview = gimp_preview_area_new (); + gtk_widget_set_size_request (g_di.sample_preview, + PREVIEW_SIZE_X, PREVIEW_SIZE_Y); + gtk_container_add (GTK_CONTAINER (frame), g_di.sample_preview); + gtk_widget_show (g_di.sample_preview); + + ty++; + + /* The levels graylevel prevev */ + frame = gtk_frame_new (NULL); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_container_add (GTK_CONTAINER (frame), vbox2); + gtk_table_attach (GTK_TABLE (table), + frame, 0, 2, ty, ty + 1, 0, 0, 0, 0); + + g_di.in_lvl_gray_preview = gimp_preview_area_new (); + gtk_widget_set_size_request (g_di.in_lvl_gray_preview, + DA_WIDTH, GRADIENT_HEIGHT); + gtk_widget_set_events (g_di.in_lvl_gray_preview, LEVELS_DA_MASK); + gtk_box_pack_start (GTK_BOX (vbox2), g_di.in_lvl_gray_preview, FALSE, TRUE, 0); + gtk_widget_show (g_di.in_lvl_gray_preview); + + g_signal_connect (g_di.in_lvl_gray_preview, "event", + G_CALLBACK (level_in_events), + NULL); + + /* The levels drawing area */ + g_di.in_lvl_drawarea = gtk_drawing_area_new (); + gtk_widget_set_size_request (g_di.in_lvl_drawarea, + DA_WIDTH, CONTROL_HEIGHT); + gtk_widget_set_events (g_di.in_lvl_drawarea, LEVELS_DA_MASK); + gtk_box_pack_start (GTK_BOX (vbox2), g_di.in_lvl_drawarea, FALSE, TRUE, 0); + gtk_widget_show (g_di.in_lvl_drawarea); + + g_signal_connect (g_di.in_lvl_drawarea, "event", + G_CALLBACK (level_in_events), + NULL); + + gtk_widget_show (vbox2); + gtk_widget_show (frame); + + /* The sample_colortable prevev */ + frame = gtk_frame_new (NULL); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_container_add (GTK_CONTAINER (frame), vbox2); + gtk_table_attach (GTK_TABLE (table), + frame, 3, 5, ty, ty + 1, 0, 0, 0, 0); + + g_di.sample_colortab_preview = gimp_preview_area_new (); + gtk_widget_set_size_request (g_di.sample_colortab_preview, + DA_WIDTH, GRADIENT_HEIGHT); + gtk_box_pack_start (GTK_BOX (vbox2), g_di.sample_colortab_preview, FALSE, TRUE, 0); + gtk_widget_show (g_di.sample_colortab_preview); + + /* The levels drawing area */ + g_di.sample_drawarea = gtk_drawing_area_new (); + gtk_widget_set_size_request (g_di.sample_drawarea, + DA_WIDTH, CONTROL_HEIGHT); + gtk_widget_set_events (g_di.sample_drawarea, LEVELS_DA_MASK); + gtk_box_pack_start (GTK_BOX (vbox2), g_di.sample_drawarea, FALSE, TRUE, 0); + gtk_widget_show (g_di.sample_drawarea); + + g_signal_connect (g_di.sample_drawarea, "event", + G_CALLBACK (level_out_events), + NULL); + + gtk_widget_show (vbox2); + gtk_widget_show (frame); + + + ty++; + + /* Horizontal box for INPUT levels text widget */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_table_attach (GTK_TABLE (table), hbox, 0, 2, ty, ty+1, + GTK_FILL, 0, 0, 0); + + label = gtk_label_new (_("Input levels:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* min input spinbutton */ + data = gtk_adjustment_new ((gfloat)g_values.lvl_in_min, 0.0, 254.0, 1, 10, 0); + g_di.adj_lvl_in_min = GTK_ADJUSTMENT (data); + + spinbutton = gimp_spin_button_new (g_di.adj_lvl_in_min, 0.5, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (g_di.adj_lvl_in_min, "value-changed", + G_CALLBACK (smp_adj_lvl_in_min_upd_callback), + &g_di); + + /* input gamma spinbutton */ + data = gtk_adjustment_new ((gfloat)g_values.lvl_in_gamma, 0.1, 10.0, 0.02, 0.2, 0); + g_di.adj_lvl_in_gamma = GTK_ADJUSTMENT (data); + + spinbutton = gimp_spin_button_new (g_di.adj_lvl_in_gamma, 0.5, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (g_di.adj_lvl_in_gamma, "value-changed", + G_CALLBACK (smp_text_gamma_upd_callback), + &g_di); + + /* high input spinbutton */ + data = gtk_adjustment_new ((gfloat)g_values.lvl_in_max, 1.0, 255.0, 1, 10, 0); + g_di.adj_lvl_in_max = GTK_ADJUSTMENT (data); + + spinbutton = gimp_spin_button_new (g_di.adj_lvl_in_max, 0.5, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (g_di.adj_lvl_in_max, "value-changed", + G_CALLBACK (smp_adj_lvl_in_max_upd_callback), + &g_di); + + gtk_widget_show (hbox); + + /* Horizontal box for OUTPUT levels text widget */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_table_attach (GTK_TABLE (table), hbox, 3, 5, ty, ty+1, + GTK_FILL, 0, 0, 0); + + label = gtk_label_new (_("Output levels:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* min output spinbutton */ + data = gtk_adjustment_new ((gfloat)g_values.lvl_out_min, 0.0, 254.0, 1, 10, 0); + g_di.adj_lvl_out_min = GTK_ADJUSTMENT (data); + + spinbutton = gimp_spin_button_new (g_di.adj_lvl_out_min, 0.5, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (g_di.adj_lvl_out_min, "value-changed", + G_CALLBACK (smp_adj_lvl_out_min_upd_callback), + &g_di); + + /* high output spinbutton */ + data = gtk_adjustment_new ((gfloat)g_values.lvl_out_max, 0.0, 255.0, 1, 10, 0); + g_di.adj_lvl_out_max = GTK_ADJUSTMENT (data); + + spinbutton = gimp_spin_button_new (g_di.adj_lvl_out_max, 0.5, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (g_di.adj_lvl_out_max, "value-changed", + G_CALLBACK (smp_adj_lvl_out_max_upd_callback), + &g_di); + + gtk_widget_show (hbox); + + ty++; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_table_attach (GTK_TABLE (table), hbox, 0, 2, ty, ty+1, + GTK_FILL, 0, 0, 0); + gtk_widget_show (hbox); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("Hold _intensity")); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_values.hold_inten); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_values.hold_inten); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("Original i_ntensity")); + g_di.orig_inten_button = check_button; + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_set_sensitive (g_di.orig_inten_button, g_values.hold_inten); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_values.orig_inten); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_values.orig_inten); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_table_attach (GTK_TABLE (table), hbox, 3, 5, ty, ty+1, + GTK_FILL, 0, 0, 0); + gtk_widget_show (hbox); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("Us_e subcolors")); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_values.rnd_subcolors); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_values.rnd_subcolors); + + /* check button */ + check_button = gtk_check_button_new_with_mnemonic (_("S_mooth samples")); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0); + gtk_widget_show (check_button); + + g_signal_connect (check_button, "toggled", + G_CALLBACK (smp_toggle_callback), + &g_values.guess_missing); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + g_values.guess_missing); + + ty++; + + gtk_widget_show (table); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + /* set old_id's different (to force updates of the previews) */ + g_di.enable_preview_update = TRUE; + smp_get_colors (NULL); + update_preview (&g_values.dst_id); + levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW); + + gtk_main (); +} + +/* ----------------------------- + * DEBUG print procedures START + * ----------------------------- + */ + +static void +print_ppm (const gchar *ppm_name) +{ + FILE *fp; + gint idx; + gint cnt; + gint r; + gint g; + gint b; + t_samp_color_elem *col_ptr; + + if (ppm_name == NULL) + return; + + fp = g_fopen (ppm_name, "w"); + if (fp) + { + fprintf (fp, "P3\n# CREATOR: Gimp sample coloros\n256 256\n255\n"); + for (idx = 0; idx < 256; idx++) + { + col_ptr = g_lum_tab[idx].col_ptr; + + for (cnt = 0; cnt < 256; cnt++) + { + r = g = b = 0; + if (col_ptr) + { + if ((col_ptr->sum_color > 0) && (cnt != 20)) + { + r = (gint) col_ptr->color[0]; + g = (gint) col_ptr->color[1]; + b = (gint) col_ptr->color[2]; + } + + if (cnt > 20) + col_ptr = col_ptr->next; + } + fprintf (fp, "%d %d %d\n", r, g, b); + } + } + fclose (fp); + } +} + +static void +print_color_list (FILE *fp, + t_samp_color_elem *col_ptr) +{ + if (fp == NULL) + return; + + while (col_ptr) + { + fprintf (fp, " RGBA: %03d %03d %03d %03d sum: [%d]\n", + (gint)col_ptr->color[0], + (gint)col_ptr->color[1], + (gint)col_ptr->color[2], + (gint)col_ptr->color[3], + (gint)col_ptr->sum_color); + + col_ptr = col_ptr->next; + } +} + +static void +print_table (FILE *fp) +{ + gint idx; + + if (fp == NULL) + return; + + fprintf (fp, "---------------------------\n"); + fprintf (fp, "print_table\n"); + fprintf (fp, "---------------------------\n"); + + for (idx = 0; idx < 256; idx++) + { + fprintf (fp, "LUM [%03d] pixcount:%d\n", + idx, (int)g_lum_tab[idx].all_samples); + print_color_list (fp, g_lum_tab[idx].col_ptr); + } +} + +static void +print_transtable (FILE *fp) +{ + gint idx; + + if (fp == NULL) + return; + + fprintf (fp, "---------------------------\n"); + fprintf (fp, "print_transtable\n"); + fprintf (fp, "---------------------------\n"); + + for (idx = 0; idx < 256; idx++) + { + fprintf (fp, "LVL_TRANS [%03d] in_lvl: %3d out_lvl: %3d\n", + idx, (int)g_lvl_trans_tab[idx], (int)g_out_trans_tab[idx]); + } +} + +static void +print_values (FILE *fp) +{ + if (fp == NULL) + return; + + fprintf (fp, "sample_colorize: params\n"); + fprintf (fp, "g_values.hold_inten :%d\n", (int)g_values.hold_inten); + fprintf (fp, "g_values.orig_inten :%d\n", (int)g_values.orig_inten); + fprintf (fp, "g_values.rnd_subcolors :%d\n", (int)g_values.rnd_subcolors); + fprintf (fp, "g_values.guess_missing :%d\n", (int)g_values.guess_missing); + fprintf (fp, "g_values.lvl_in_min :%d\n", (int)g_values.lvl_in_min); + fprintf (fp, "g_values.lvl_in_max :%d\n", (int)g_values.lvl_in_max); + fprintf (fp, "g_values.lvl_in_gamma :%f\n", g_values.lvl_in_gamma); + fprintf (fp, "g_values.lvl_out_min :%d\n", (int)g_values.lvl_out_min); + fprintf (fp, "g_values.lvl_out_max :%d\n", (int)g_values.lvl_out_max); + + fprintf (fp, "g_values.tol_col_err :%f\n", g_values.tol_col_err); +} + +/* ----------------------------- + * DEBUG print procedures END + * ----------------------------- + */ + +/* DEBUG: read values from file */ +static void +get_filevalues (void) +{ + FILE *fp; + gchar buf[1000]; + +/* + g_values.lvl_out_min = 0; + g_values.lvl_out_max = 255; + g_values.lvl_in_min = 0; + g_values.lvl_in_max = 255; + g_values.lvl_in_gamma = 1.0; +*/ + g_values.tol_col_err = 5.5; + + fp = g_fopen ("sample_colorize.values", "r"); + if (fp != NULL) + { + fgets (buf, 999, fp); + sscanf (buf, "%f", &g_values.tol_col_err); + fclose (fp); + } + + g_printf ("g_values.tol_col_err :%f\n", g_values.tol_col_err); +} + +static gint32 +color_error (guchar ref_red, guchar ref_green, guchar ref_blue, + guchar cmp_red, guchar cmp_green, guchar cmp_blue) +{ + glong ff; + glong fs; + glong cmp_h, ref_h; + + /* 1. Brightness differences */ + cmp_h = (3 * cmp_red + 6 * cmp_green + cmp_blue) / 10; + ref_h = (3 * ref_red + 6 * ref_green + ref_blue) / 10; + + fs = labs (ref_h - cmp_h); + ff = fs * fs; + + /* 2. add Red Color differences */ + fs = abs (ref_red - cmp_red); + ff += (fs * fs); + + /* 3. add Green Color differences */ + fs = abs (ref_green - cmp_green); + ff += (fs * fs); + + /* 4. add Blue Color differences */ + fs = abs (ref_blue - cmp_blue); + ff += (fs * fs); + + return ((gint32)(ff)); +} + +/* get pixel value + * return light gray transparent pixel if out of bounds + * (should occur in the previews only) + */ +static void +get_pixel (t_GDRW *gdrw, + gint32 x, + gint32 y, + guchar *pixel) +{ + gegl_buffer_sample (gdrw->buffer, x, y, NULL, pixel, gdrw->format, + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); +} + +/* clear table */ +static void +clear_tables (void) +{ + guint i; + + for (i = 0; i < 256; i++) + { + g_lum_tab[i].col_ptr = NULL; + g_lum_tab[i].all_samples = 0; + g_lvl_trans_tab[i] = i; + g_out_trans_tab[i] = i; + g_sample_color_tab[3 * i + 0] = i; + g_sample_color_tab[3 * i + 1] = i; + g_sample_color_tab[3 * i + 2] = i; + } +} + +/* free all allocated sample colors in table g_lum_tab */ +static void +free_colors (void) +{ + gint lum; + t_samp_color_elem *col_ptr; + t_samp_color_elem *next_ptr; + + for (lum = 0; lum < 256; lum++) + { + for (col_ptr = g_lum_tab[lum].col_ptr; + col_ptr != NULL; + col_ptr = next_ptr) + { + next_ptr = (t_samp_color_elem *)col_ptr->next; + g_free (col_ptr); + } + + g_lum_tab[lum].col_ptr = NULL; + g_lum_tab[lum].all_samples = 0; + } +} + +/* setup lum transformer table according to input_levels, gamma and output levels + * (uses sam algorithm as GIMP Level Tool) + */ +static void +calculate_level_transfers (void) +{ + double inten; + gint i; + gint in_min, in_max; + gint out_min, out_max; + + if (g_values.lvl_in_max >= g_values.lvl_in_min) + { + in_max = g_values.lvl_in_max; + in_min = g_values.lvl_in_min; + } + else + { + in_max = g_values.lvl_in_min; + in_min = g_values.lvl_in_max; + } + if (g_values.lvl_out_max >= g_values.lvl_out_min) + { + out_max = g_values.lvl_out_max; + out_min = g_values.lvl_out_min; + } + else + { + out_max = g_values.lvl_out_min; + out_min = g_values.lvl_out_max; + } + + /* Recalculate the levels arrays */ + for (i = 0; i < 256; i++) + { + /* determine input intensity */ + inten = (double) i / 255.0; + if (g_values.lvl_in_gamma != 0.0) + { + inten = pow (inten, (1.0 / g_values.lvl_in_gamma)); + } + inten = (double) (inten * (in_max - in_min) + in_min); + inten = CLAMP (inten, 0.0, 255.0); + g_lvl_trans_tab[i] = (guchar) (inten + 0.5); + + /* determine the output intensity */ + inten = (double) i / 255.0; + inten = (double) (inten * (out_max - out_min) + out_min); + inten = CLAMP (inten, 0.0, 255.0); + g_out_trans_tab[i] = (guchar) (inten + 0.5); + } +} + + + +/* alloc and init new col Element */ +static t_samp_color_elem * +new_samp_color (const guchar *color) +{ + t_samp_color_elem *col_ptr; + + col_ptr = g_new0 (t_samp_color_elem, 1); + + memcpy (&col_ptr->color[0], color, 4); + + col_ptr->sum_color = 1; + col_ptr->next = NULL; + + return col_ptr; +} + + +/* store color in g_lum_tab */ +static void +add_color (const guchar *color) +{ + gint32 lum; + t_samp_color_elem *col_ptr; + + lum = LUMINOSITY_1(color); + + g_lum_tab[lum].all_samples++; + g_lum_tab[lum].from_sample = TRUE; + + /* check if exactly the same color is already in the list */ + for (col_ptr = g_lum_tab[lum].col_ptr; + col_ptr != NULL; + col_ptr = (t_samp_color_elem *)col_ptr->next) + { + if ((color[0] == col_ptr->color[0]) && + (color[1] == col_ptr->color[1]) && + (color[2] == col_ptr->color[2])) + { + col_ptr->sum_color++; + return; + } + } + + /* alloc and init element for the new color */ + col_ptr = new_samp_color (color); + + if (col_ptr != NULL) + { + /* add new color element as 1.st of the list */ + col_ptr->next = g_lum_tab[lum].col_ptr; + g_lum_tab[lum].col_ptr = col_ptr; + } +} + +/* sort Sublists (color) by descending sum_color in g_lum_tab + */ +static void +sort_color (gint32 lum) +{ + t_samp_color_elem *col_ptr; + t_samp_color_elem *next_ptr; + t_samp_color_elem *prev_ptr; + t_samp_color_elem *sorted_col_ptr; + gint32 min; + gint32 min_next; + + sorted_col_ptr = NULL; + min_next = 0; + + while (g_lum_tab[lum].col_ptr != NULL) + { + min = min_next; + next_ptr = NULL; + prev_ptr = NULL; + + for (col_ptr = g_lum_tab[lum].col_ptr; + col_ptr != NULL; + col_ptr = next_ptr) + { + next_ptr = col_ptr->next; + if (col_ptr->sum_color > min) + { + /* check min value for next loop */ + if ((col_ptr->sum_color < min_next) || (min == min_next)) + { + min_next = col_ptr->sum_color; + } + prev_ptr = col_ptr; + } + else + { + /* add element at head of sorted list */ + col_ptr->next = sorted_col_ptr; + sorted_col_ptr = col_ptr; + + /* remove element from list */ + if (prev_ptr == NULL) + { + g_lum_tab[lum].col_ptr = next_ptr; /* remove 1.st element */ + } + else + { + prev_ptr->next = next_ptr; + } + } + } + } + + g_lum_tab[lum].col_ptr = sorted_col_ptr; +} + +static void +cnt_same_sample_colortones (t_samp_color_elem *ref_ptr, + guchar *prev_color, + guchar *color_tone, + gint *csum) +{ + gint32 col_error, ref_error; + t_samp_color_elem *col_ptr; + + ref_error = 0; + if (prev_color != NULL) + { + ref_error = color_error (ref_ptr->color[0], ref_ptr->color[1], ref_ptr->color[2], + prev_color[0], prev_color[1], prev_color[2]); + } + + /* collect colors that are (nearly) the same */ + for (col_ptr = ref_ptr->next; + col_ptr != NULL; + col_ptr = (t_samp_color_elem *)col_ptr->next) + { + if (col_ptr->sum_color < 1) + continue; + col_error = color_error (ref_ptr->color[0], ref_ptr->color[1], ref_ptr->color[2], + col_ptr->color[0], col_ptr->color[1], col_ptr->color[2]); + + if (col_error <= g_tol_col_err) + { + /* cout color of the same colortone */ + *csum += col_ptr->sum_color; + /* mark the already checked color with negative sum_color value */ + col_ptr->sum_color = 0 - col_ptr->sum_color; + + if (prev_color != NULL) + { + col_error = color_error (col_ptr->color[0], col_ptr->color[1], col_ptr->color[2], + prev_color[0], prev_color[1], prev_color[2]); + if (col_error < ref_error) + { + /* use the color that is closest to prev_color */ + memcpy (color_tone, &col_ptr->color[0], 3); + ref_error = col_error; + } + } + } + } +} + +/* find the dominant colortones (out of all sample colors) + * for each available brightness intensity value. + * and store them in g_sample_color_tab + */ +static void +ideal_samples (void) +{ + gint32 lum; + t_samp_color_elem *col_ptr; + guchar *color; + + guchar color_tone[4]; + guchar color_ideal[4]; + gint csum, maxsum; + + color = NULL; + for (lum = 0; lum < 256; lum++) + { + if (g_lum_tab[lum].col_ptr == NULL) + continue; + + sort_color (lum); + col_ptr = g_lum_tab[lum].col_ptr; + memcpy (&color_ideal[0], &col_ptr->color[0], 3); + + maxsum = 0; + + /* collect colors that are (nearly) the same */ + for (; + col_ptr != NULL; + col_ptr = (t_samp_color_elem *)col_ptr->next) + { + csum = 0; + if (col_ptr->sum_color > 0) + { + memcpy (&color_tone[0], &col_ptr->color[0], 3); + cnt_same_sample_colortones (col_ptr, color, + &color_tone[0], &csum); + if (csum > maxsum) + { + maxsum = csum; + memcpy (&color_ideal[0], &color_tone[0], 3); + } + } + else + col_ptr->sum_color = abs (col_ptr->sum_color); + } + + /* store ideal color and keep track of the color */ + color = &g_sample_color_tab[3 * lum]; + memcpy (color, &color_ideal[0], 3); + } +} + +static void +guess_missing_colors (void) +{ + gint32 lum; + gint32 idx; + gfloat div; + + guchar lo_color[4]; + guchar hi_color[4]; + guchar new_color[4]; + + lo_color[0] = 0; + lo_color[1] = 0; + lo_color[2] = 0; + lo_color[3] = 255; + + hi_color[0] = 255; + hi_color[1] = 255; + hi_color[2] = 255; + hi_color[3] = 255; + + new_color[0] = 0; + new_color[1] = 0; + new_color[2] = 0; + new_color[3] = 255; + + for (lum = 0; lum < 256; lum++) + { + if ((g_lum_tab[lum].col_ptr == NULL) || + (g_lum_tab[lum].from_sample == FALSE)) + { + if (lum > 0) + { + for (idx = lum; idx < 256; idx++) + { + if ((g_lum_tab[idx].col_ptr != NULL) && + g_lum_tab[idx].from_sample) + { + memcpy (&hi_color[0], + &g_sample_color_tab[3 * idx], 3); + break; + } + if (idx == 255) + { + hi_color[0] = 255; + hi_color[1] = 255; + hi_color[2] = 255; + break; + } + } + + div = idx - (lum -1); + new_color[0] = lo_color[0] + ((float)(hi_color[0] - lo_color[0]) / div); + new_color[1] = lo_color[1] + ((float)(hi_color[1] - lo_color[1]) / div); + new_color[2] = lo_color[2] + ((float)(hi_color[2] - lo_color[2]) / div); + +/* + * printf ("LO: %03d %03d %03d HI: %03d %03d %03d NEW: %03d %03d %03d\n", + * (int)lo_color[0], (int)lo_color[1], (int)lo_color[2], + * (int)hi_color[0], (int)hi_color[1], (int)hi_color[2], + * (int)new_color[0], (int)new_color[1], (int)new_color[2]); + */ + + } + g_lum_tab[lum].col_ptr = new_samp_color (&new_color[0]); + g_lum_tab[lum].from_sample = FALSE; + memcpy (&g_sample_color_tab [3 * lum], &new_color[0], 3); + } + memcpy (&lo_color[0], &g_sample_color_tab [3 * lum], 3); + } +} + +static void +fill_missing_colors (void) +{ + gint32 lum; + gint32 idx; + gint32 lo_idx; + + guchar lo_color[4]; + guchar hi_color[4]; + guchar new_color[4]; + + lo_color[0] = 0; + lo_color[1] = 0; + lo_color[2] = 0; + lo_color[3] = 255; + + hi_color[0] = 255; + hi_color[1] = 255; + hi_color[2] = 255; + hi_color[3] = 255; + + new_color[0] = 0; + new_color[1] = 0; + new_color[2] = 0; + new_color[3] = 255; + + lo_idx = 0; + for (lum = 0; lum < 256; lum++) + { + if ((g_lum_tab[lum].col_ptr == NULL) || + (g_lum_tab[lum].from_sample == FALSE)) + { + if (lum > 0) + { + for (idx = lum; idx < 256; idx++) + { + if ((g_lum_tab[idx].col_ptr != NULL) && + (g_lum_tab[idx].from_sample)) + { + memcpy (&hi_color[0], + &g_sample_color_tab[3 * idx], 3); + break; + } + + if (idx == 255) + { +/* + * hi_color[0] = 255; + * hi_color[1] = 255; + * hi_color[2] = 255; + */ + memcpy (&hi_color[0], &lo_color[0], 3); + break; + } + } + + if ((lum > (lo_idx + ((idx - lo_idx ) / 2))) || + (lo_idx == 0)) + { + new_color[0] = hi_color[0]; + new_color[1] = hi_color[1]; + new_color[2] = hi_color[2]; + } + else + { + new_color[0] = lo_color[0]; + new_color[1] = lo_color[1]; + new_color[2] = lo_color[2]; + } + } + + g_lum_tab[lum].col_ptr = new_samp_color (&new_color[0]); + g_lum_tab[lum].from_sample = FALSE; + memcpy (&g_sample_color_tab[3 * lum], &new_color[0], 3); + } + else + { + lo_idx = lum; + memcpy (&lo_color[0], &g_sample_color_tab[3 * lum], 3); + } + } +} + +/* get 256 samples of active gradient (optional in inverse order) */ +static void +get_gradient (gint mode) +{ + gchar *name; + gint n_f_samples; + gdouble *f_samples; + gdouble *f_samp; /* float samples */ + gint lum; + + free_colors (); + + name = gimp_context_get_gradient (); + + gimp_gradient_get_uniform_samples (name, 256 /* n_samples */, + mode == SMP_INV_GRADIENT, + &n_f_samples, &f_samples); + + g_free (name); + + for (lum = 0; lum < 256; lum++) + { + f_samp = &f_samples[lum * 4]; + + g_sample_color_tab[3 * lum + 0] = f_samp[0] * 255; + g_sample_color_tab[3 * lum + 1] = f_samp[1] * 255; + g_sample_color_tab[3 * lum + 2] = f_samp[2] * 255; + + g_lum_tab[lum].col_ptr = + new_samp_color (&g_sample_color_tab[3 * lum]); + g_lum_tab[lum].from_sample = TRUE; + g_lum_tab[lum].all_samples = 1; + } + + g_free (f_samples); +} + +static gint32 +is_layer_alive (gint32 drawable_id) +{ + /* return -1 if layer has become invalid */ + if (drawable_id < 0) + return -1; + + if (gimp_item_get_image (drawable_id) < 0) + { + g_printf ("sample colorize: unknown layer_id %d (Image closed?)\n", + (int)drawable_id); + return -1; + } + + return drawable_id; +} + +static void +end_gdrw (t_GDRW *gdrw) +{ + t_GDRW *sel_gdrw = (t_GDRW *) gdrw->sel_gdrw; + + if (sel_gdrw && sel_gdrw->buffer) + { + g_object_unref (sel_gdrw->buffer); + sel_gdrw->buffer = NULL; + } + + g_object_unref (gdrw->buffer); + gdrw->buffer = NULL; +} + +static void +init_gdrw (t_GDRW *gdrw, + gint32 drawable_id, + gboolean shadow) +{ + gint32 image_id; + gint32 sel_channel_id; + gint32 x1, x2, y1, y2; + gint offsetx, offsety; + gint w, h; + gint sel_offsetx, sel_offsety; + t_GDRW *sel_gdrw; + gint32 non_empty; + + if (g_Sdebug) + g_printf ("\np_init_gdrw: drawable_ID: %d\n", drawable_id); + + gdrw->drawable_id = drawable_id; + + if (shadow) + gdrw->buffer = gimp_drawable_get_shadow_buffer (drawable_id); + else + gdrw->buffer = gimp_drawable_get_buffer (drawable_id); + + gdrw->width = gimp_drawable_width (drawable_id); + gdrw->height = gimp_drawable_height (drawable_id); + gdrw->tile_width = gimp_tile_width (); + gdrw->tile_height = gimp_tile_height (); + gdrw->shadow = shadow; + gdrw->seldeltax = 0; + gdrw->seldeltay = 0; + /* get offsets within the image */ + gimp_drawable_offsets (gdrw->drawable_id, &offsetx, &offsety); + + if (! gimp_drawable_mask_intersect (gdrw->drawable_id, + &gdrw->x1, &gdrw->y1, &w, &h)) + return; + + gdrw->x2 = gdrw->x1 + w; + gdrw->y2 = gdrw->y1 + h; + + if (gimp_drawable_has_alpha (drawable_id)) + gdrw->format = babl_format ("R'G'B'A u8"); + else + gdrw->format = babl_format ("R'G'B' u8"); + + gdrw->bpp = babl_format_get_bytes_per_pixel (gdrw->format); + + if (gimp_drawable_has_alpha (drawable_id)) + { + /* index of the alpha channelbyte {1|3} */ + gdrw->index_alpha = gdrw->bpp -1; + } + else + { + gdrw->index_alpha = 0; /* there is no alpha channel */ + } + + image_id = gimp_item_get_image (gdrw->drawable_id); + + /* check and see if we have a selection mask */ + sel_channel_id = gimp_image_get_selection (image_id); + + if (g_Sdebug) + { + g_printf ("init_gdrw: image_id %d sel_channel_id: %d\n", + (int)image_id, (int)sel_channel_id); + g_printf ("init_gdrw: BOUNDS x1: %d y1: %d x2:%d y2: %d\n", + (int)gdrw->x1, (int)gdrw->y1, (int)gdrw->x2,(int)gdrw->y2); + g_printf ("init_gdrw: OFFS x: %d y: %d\n", + (int)offsetx, (int)offsety ); + } + + gimp_selection_bounds (image_id, &non_empty, &x1, &y1, &x2, &y2); + + if (non_empty && (sel_channel_id >= 0)) + { + /* selection is TRUE */ + sel_gdrw = g_new0 (t_GDRW, 1); + sel_gdrw->drawable_id = sel_channel_id; + + sel_gdrw->buffer = gimp_drawable_get_buffer (sel_channel_id); + sel_gdrw->format = babl_format ("Y u8"); + + sel_gdrw->width = gimp_drawable_width (sel_channel_id); + sel_gdrw->height = gimp_drawable_height (sel_channel_id); + + sel_gdrw->tile_width = gimp_tile_width (); + sel_gdrw->tile_height = gimp_tile_height (); + sel_gdrw->shadow = shadow; + sel_gdrw->x1 = x1; + sel_gdrw->y1 = y1; + sel_gdrw->x2 = x2; + sel_gdrw->y2 = y2; + sel_gdrw->seldeltax = 0; + sel_gdrw->seldeltay = 0; + sel_gdrw->bpp = babl_format_get_bytes_per_pixel (sel_gdrw->format); + sel_gdrw->index_alpha = 0; /* there is no alpha channel */ + sel_gdrw->sel_gdrw = NULL; + + /* offset delta between drawable and selection + * (selection always has image size and should always have offsets of 0 ) + */ + gimp_drawable_offsets (sel_channel_id, &sel_offsetx, &sel_offsety); + gdrw->seldeltax = offsetx - sel_offsetx; + gdrw->seldeltay = offsety - sel_offsety; + + gdrw->sel_gdrw = (t_GDRW *) sel_gdrw; + + if (g_Sdebug) + { + g_printf ("init_gdrw: SEL_BOUNDS x1: %d y1: %d x2:%d y2: %d\n", + (int)sel_gdrw->x1, (int)sel_gdrw->y1, + (int)sel_gdrw->x2, (int)sel_gdrw->y2); + g_printf ("init_gdrw: SEL_OFFS x: %d y: %d\n", + (int)sel_offsetx, (int)sel_offsety ); + g_printf ("init_gdrw: SEL_DELTA x: %d y: %d\n", + (int)gdrw->seldeltax, (int)gdrw->seldeltay ); + } + } + else + gdrw->sel_gdrw = NULL; /* selection is FALSE */ +} + +/* analyze the colors in the sample_drawable */ +static int +sample_analyze (t_GDRW *sample_gdrw) +{ + gint32 sample_pixels; + gint32 row, col; + gint32 first_row, first_col, last_row, last_col; + gint32 x, y; + gint32 x2, y2; + float progress_step; + float progress_max; + float progress; + guchar color[4]; + FILE *prot_fp; + + sample_pixels = 0; + + /* init progress */ + progress_max = (sample_gdrw->x2 - sample_gdrw->x1); + progress_step = 1.0 / progress_max; + progress = 0.0; + if (g_show_progress) + gimp_progress_init (_("Sample analyze")); + + prot_fp = NULL; + if (g_Sdebug) + prot_fp = g_fopen ("sample_colors.dump", "w"); + print_values (prot_fp); + + /* ------------------------------------------------ + * foreach pixel in the SAMPLE_drawable: + * calculate brightness intensity LUM + * ------------------------------------------------ + * the inner loops (x/y) are designed to process + * all pixels of one tile in the sample drawable, the outer loops (row/col) do step + * to the next tiles. (this was done to reduce tile swapping) + */ + + first_row = sample_gdrw->y1 / sample_gdrw->tile_height; + last_row = (sample_gdrw->y2 / sample_gdrw->tile_height); + first_col = sample_gdrw->x1 / sample_gdrw->tile_width; + last_col = (sample_gdrw->x2 / sample_gdrw->tile_width); + + for (row = first_row; row <= last_row; row++) + { + for (col = first_col; col <= last_col; col++) + { + if (col == first_col) + x = sample_gdrw->x1; + else + x = col * sample_gdrw->tile_width; + if (col == last_col) + x2 = sample_gdrw->x2; + else + x2 = (col +1) * sample_gdrw->tile_width; + + for ( ; x < x2; x++) + { + if (row == first_row) + y = sample_gdrw->y1; + else + y = row * sample_gdrw->tile_height; + if (row == last_row) + y2 = sample_gdrw->y2; + else + y2 = (row +1) * sample_gdrw->tile_height ; + + /* printf ("X: %4d Y:%4d Y2:%4d\n", (int)x, (int)y, (int)y2); */ + + for ( ; y < y2; y++) + { + /* check if the pixel is in the selection */ + if (sample_gdrw->sel_gdrw) + { + get_pixel (sample_gdrw->sel_gdrw, + (x + sample_gdrw->seldeltax), + (y + sample_gdrw->seldeltay), + &color[0]); + + if (color[0] == 0) + continue; + } + get_pixel (sample_gdrw, x, y, &color[0]); + + /* if this is a visible (non-transparent) pixel */ + if ((sample_gdrw->index_alpha < 1) || + (color[sample_gdrw->index_alpha] != 0)) + { + /* store color in the sublists of g_lum_tab */ + add_color (&color[0]); + sample_pixels++; + } + } + + if (g_show_progress) + gimp_progress_update (progress += progress_step); + } + } + } + + if (g_show_progress) + gimp_progress_update (1.0); + + if (g_Sdebug) + g_printf ("ROWS: %d - %d COLS: %d - %d\n", + (int)first_row, (int)last_row, + (int)first_col, (int)last_col); + + print_table (prot_fp); + + if (g_Sdebug) + print_ppm ("sample_color_all.ppm"); + + /* find out ideal sample colors for each brightness intensity (lum) + * and set g_sample_color_tab to the ideal colors. + */ + ideal_samples (); + calculate_level_transfers (); + + if (g_values.guess_missing) + guess_missing_colors (); + else + fill_missing_colors (); + + print_table (prot_fp); + if (g_Sdebug) + print_ppm ("sample_color_2.ppm"); + print_transtable (prot_fp); + if (prot_fp) + fclose (prot_fp); + + /* check if there was at least one visible pixel */ + if (sample_pixels == 0) + { + g_printf ("Error: Source sample has no visible Pixel\n"); + return -1; + } + return 0; +} + +static void +rnd_remap (gint32 lum, + guchar *mapped_color) +{ + t_samp_color_elem *col_ptr; + gint rnd; + gint ct; + gint idx; + + if (g_lum_tab[lum].all_samples > 1) + { + rnd = g_random_int_range (0, g_lum_tab[lum].all_samples); + ct = 0; + idx = 0; + + for (col_ptr = g_lum_tab[lum].col_ptr; + col_ptr != NULL; + col_ptr = (t_samp_color_elem *)col_ptr->next) + { + ct += col_ptr->sum_color; + + if (rnd < ct) + { + /* printf ("RND_remap: rnd: %d all:%d ct:%d idx:%d\n", + * rnd, (int)g_lum_tab[lum].all_samples, ct, idx); + */ + memcpy (mapped_color, &col_ptr->color[0], 3); + return; + } + idx++; + } + } + + memcpy (mapped_color, &g_sample_color_tab[lum + lum + lum], 3); +} + +static void +remap_pixel (guchar *pixel, + const guchar *original, + gint bpp2) +{ + guchar mapped_color[4]; + gint lum; + double orig_lum, mapped_lum; + double grn, red, blu; + double mg, mr, mb; + double dg, dr, db; + double dlum; + + /* get brightness from (uncolorized) original */ + lum = g_out_trans_tab[g_lvl_trans_tab[LUMINOSITY_1 (original)]]; + if (g_values.rnd_subcolors) + rnd_remap (lum, mapped_color); + else + memcpy (mapped_color, &g_sample_color_tab[3 * lum], 3); + + if (g_values.hold_inten) + { + if (g_values.orig_inten) + orig_lum = LUMINOSITY_0(original); + else + orig_lum = 100.0 * g_lvl_trans_tab[LUMINOSITY_1 (original)]; + + mapped_lum = LUMINOSITY_0 (mapped_color); + + if (mapped_lum == 0) + { + /* convert black to greylevel with desired brightness value */ + mapped_color[0] = orig_lum / 100.0; + mapped_color[1] = mapped_color[0]; + mapped_color[2] = mapped_color[0]; + } + else + { + /* Calculate theoretical RGB to reach given intensity LUM + * value (orig_lum) + */ + mr = mapped_color[0]; + mg = mapped_color[1]; + mb = mapped_color[2]; + + if (mr > 0.0) + { + red = + orig_lum / (30 + (59 * mg / mr) + (11 * mb / mr)); + grn = mg * red / mr; + blu = mb * red / mr; + } + else if (mg > 0.0) + { + grn = + orig_lum / ((30 * mr / mg) + 59 + (11 * mb / mg)); + red = mr * grn / mg; + blu = mb * grn / mg; + } + else + { + blu = + orig_lum / ((30 * mr / mb) + (59 * mg / mb) + 11); + grn = mg * blu / mb; + red = mr * blu / mb; + } + + /* on overflow: Calculate real RGB values + * (this may change the hue and saturation, + * more and more into white) + */ + + if (red > 255) + { + if ((blu < 255) && (grn < 255)) + { + /* overflow in the red channel (compensate with green and blue) */ + dlum = (red - 255.0) * 30.0; + if (mg > 0) + { + dg = dlum / (59.0 + (11.0 * mb / mg)); + db = dg * mb / mg; + } + else if (mb > 0) + { + db = dlum / (11.0 + (59.0 * mg / mb)); + dg = db * mg / mb; + } + else + { + db = dlum / (11.0 + 59.0); + dg = dlum / (59.0 + 11.0); + } + + grn += dg; + blu += db; + } + + red = 255.0; + + if (grn > 255) + { + grn = 255.0; + blu = (orig_lum - 22695) / 11; /* 22695 = (255 * 30) + (255 * 59) */ + } + if (blu > 255) + { + blu = 255.0; + grn = (orig_lum - 10455) / 59; /* 10455 = (255 * 30) + (255 * 11) */ + } + } + else if (grn > 255) + { + if ((blu < 255) && (red < 255)) + { + /* overflow in the green channel (compensate with red and blue) */ + dlum = (grn - 255.0) * 59.0; + + if (mr > 0) + { + dr = dlum / (30.0 + (11.0 * mb / mr)); + db = dr * mb / mr; + } + else if (mb > 0) + { + db = dlum / (11.0 + (30.0 * mr / mb)); + dr = db * mr / mb; + } + else + { + db = dlum / (11.0 + 30.0); + dr = dlum / (30.0 + 11.0); + } + + red += dr; + blu += db; + } + + grn = 255.0; + + if (red > 255) + { + red = 255.0; + blu = (orig_lum - 22695) / 11; /* 22695 = (255*59) + (255*30) */ + } + if (blu > 255) + { + blu = 255.0; + red = (orig_lum - 17850) / 30; /* 17850 = (255*59) + (255*11) */ + } + } + else if (blu > 255) + { + if ((red < 255) && (grn < 255)) + { + /* overflow in the blue channel (compensate with green and red) */ + dlum = (blu - 255.0) * 11.0; + + if (mg > 0) + { + dg = dlum / (59.0 + (30.0 * mr / mg)); + dr = dg * mr / mg; + } + else if (mr > 0) + { + dr = dlum / (30.0 + (59.0 * mg / mr)); + dg = dr * mg / mr; + } + else + { + dr = dlum / (30.0 + 59.0); + dg = dlum / (59.0 + 30.0); + } + + grn += dg; + red += dr; + } + + blu = 255.0; + + if (grn > 255) + { + grn = 255.0; + red = (orig_lum - 17850) / 30; /* 17850 = (255*11) + (255*59) */ + } + if (red > 255) + { + red = 255.0; + grn = (orig_lum - 10455) / 59; /* 10455 = (255*11) + (255*30) */ + } + } + + mapped_color[0] = CLAMP0255 (red + 0.5); + mapped_color[1] = CLAMP0255 (grn + 0.5); + mapped_color[2] = CLAMP0255 (blu + 0.5); + } + } + + /* set colorized pixel in shadow pr */ + memcpy (pixel, &mapped_color[0], bpp2); +} + +static void +colorize_func (const guchar *src, + guchar *dest, + gint bpp, + gboolean has_alpha) +{ + if (has_alpha) + { + bpp--; + dest[bpp] = src[bpp]; + } + + remap_pixel (dest, src, bpp); +} + +static void +colorize_drawable (gint32 drawable_id) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + GeglBufferIterator *iter; + const Babl *format; + gboolean has_alpha; + gint bpp; + gint x, y, w, h; + gint total_area; + gint area_so_far; + + if (! gimp_drawable_mask_intersect (drawable_id, &x, &y, &w, &h)) + return; + + src_buffer = gimp_drawable_get_buffer (drawable_id); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + has_alpha = gimp_drawable_has_alpha (drawable_id); + + if (has_alpha) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + bpp = babl_format_get_bytes_per_pixel (format); + + if (g_show_progress) + gimp_progress_init (_("Remap colorized")); + + + total_area = w * h; + area_so_far = 0; + + if (total_area <= 0) + goto out; + + iter = gegl_buffer_iterator_new (src_buffer, + GEGL_RECTANGLE (x, y, w, h), 0, format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2); + + gegl_buffer_iterator_add (iter, dest_buffer, + GEGL_RECTANGLE (x, y, w, h), 0 , format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE); + + while (gegl_buffer_iterator_next (iter)) + { + const guchar *src = iter->items[0].data; + guchar *dest = iter->items[1].data; + gint row; + + for (row = 0; row < iter->items[0].roi.height; row++) + { + const guchar *s = src; + guchar *d = dest; + gint pixels = iter->items[0].roi.width; + + while (pixels--) + { + colorize_func (s, d, bpp, has_alpha); + + s += bpp; + d += bpp; + } + + src += iter->items[0].roi.width * bpp; + dest += iter->items[1].roi.width * bpp; + } + + area_so_far += iter->items[0].roi.width * iter->items[0].roi.height; + + gimp_progress_update ((gdouble) area_so_far / (gdouble) total_area); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, x, y, w, h); + + out: + if (g_show_progress) + gimp_progress_update (0.0); +} + +/* colorize dst_drawable like sample_drawable */ +static int +main_colorize (gint mc_flags) +{ + t_GDRW sample_gdrw; + gboolean sample_drawable = FALSE; + gint32 max; + gint32 id; + gint rc; + + if (g_Sdebug) + get_filevalues (); /* for debugging: read values from file */ + + /* calculate value of tolerable color error */ + max = color_error (0,0, 0, 255, 255, 255); /* 260100 */ + g_tol_col_err = (((float)max * (g_values.tol_col_err * g_values.tol_col_err)) / (100.0 *100.0)); + g_max_col_err = max; + + rc = 0; + + if (mc_flags & MC_GET_SAMPLE_COLORS) + { + id = g_values.sample_id; + if ((id == SMP_GRADIENT) || (id == SMP_INV_GRADIENT)) + { + get_gradient (id); + } + else + { + if (is_layer_alive (id) < 0) + return -1; + + sample_drawable = TRUE; + + init_gdrw (&sample_gdrw, id, FALSE); + free_colors (); + rc = sample_analyze (&sample_gdrw); + } + } + + if ((mc_flags & MC_DST_REMAP) && (rc == 0)) + { + if (is_layer_alive (g_values.dst_id) < 0) + return -1; + + if (gimp_drawable_is_gray (g_values.dst_id) && + (mc_flags & MC_DST_REMAP)) + { + gimp_image_convert_rgb (gimp_item_get_image (g_values.dst_id)); + } + + colorize_drawable (g_values.dst_id); + } + + if (sample_drawable) + end_gdrw (&sample_gdrw); + + return rc; +} diff --git a/plug-ins/common/sharpen.c b/plug-ins/common/sharpen.c new file mode 100644 index 0000000..eeb0f64 --- /dev/null +++ b/plug-ins/common/sharpen.c @@ -0,0 +1,800 @@ +/* + * Sharpen filters for GIMP - The GNU Image Manipulation Program + * + * Copyright 1997-1998 Michael Sweet (mike@easysw.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +/* + * Constants... + */ + +#define PLUG_IN_PROC "plug-in-sharpen" +#define PLUG_IN_BINARY "sharpen" +#define PLUG_IN_ROLE "gimp-sharpen" +#define PLUG_IN_VERSION "1.4.2 - 3 June 1998" +#define SCALE_WIDTH 100 + +/* + * Local functions... + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **returm_vals); + +static void compute_luts (void); +static void sharpen (GimpDrawable *drawable); + +static gboolean sharpen_dialog (GimpDrawable *drawable); + +static void preview_update (GimpPreview *preview, + GimpDrawable *drawable); + +typedef gint32 intneg; +typedef gint32 intpos; + +static void gray_filter (int width, guchar *src, guchar *dst, intneg *neg0, + intneg *neg1, intneg *neg2); +static void graya_filter (int width, guchar *src, guchar *dst, intneg *neg0, + intneg *neg1, intneg *neg2); +static void rgb_filter (int width, guchar *src, guchar *dst, intneg *neg0, + intneg *neg1, intneg *neg2); +static void rgba_filter (int width, guchar *src, guchar *dst, intneg *neg0, + intneg *neg1, intneg *neg2); + + +/* + * Globals... + */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + +typedef struct +{ + gint sharpen_percent; +} SharpenParams; + +static SharpenParams sharpen_params = +{ + 10 +}; + +static intneg neg_lut[256]; /* Negative coefficient LUT */ +static intpos pos_lut[256]; /* Positive coefficient LUT */ + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "percent", "Percent sharpening (default = 10)" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Make image sharper " + "(less powerful than Unsharp Mask)"), + "This plug-in selectively performs a convolution " + "filter on an image.", + "Michael Sweet ", + "Copyright 1997-1998 by Michael Sweet", + PLUG_IN_VERSION, + N_("_Sharpen..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; /* Return values */ + GimpRunMode run_mode; /* Current run mode */ + GimpPDBStatusType status; /* Return status */ + GimpDrawable *drawable; /* Current image */ + + /* + * Initialize parameter data... + */ + + status = GIMP_PDB_SUCCESS; + run_mode = param[0].data.d_int32; + + INIT_I18N (); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + /* + * Get drawable information... + */ + + drawable = gimp_drawable_get (param[2].data.d_drawable); + gimp_tile_cache_ntiles (2 * drawable->ntile_cols); + + + /* + * See how we will run + */ + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* + * Possibly retrieve data... + */ + gimp_get_data (PLUG_IN_PROC, &sharpen_params); + + /* + * Get information from the dialog... + */ + if (!sharpen_dialog (drawable)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* + * Make sure all the arguments are present... + */ + if (nparams != 4) + status = GIMP_PDB_CALLING_ERROR; + else + sharpen_params.sharpen_percent = param[3].data.d_int32; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* + * Possibly retrieve data... + */ + gimp_get_data (PLUG_IN_PROC, &sharpen_params); + break; + + default: + status = GIMP_PDB_CALLING_ERROR; + break; + } + + /* + * Sharpen the image... + */ + + if (status == GIMP_PDB_SUCCESS) + { + if ((gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id))) + { + /* + * Run! + */ + sharpen (drawable); + + /* + * If run mode is interactive, flush displays... + */ + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + /* + * Store data... + */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, + &sharpen_params, sizeof (SharpenParams)); + } + else + status = GIMP_PDB_EXECUTION_ERROR; + } + + /* + * Reset the current run status... + */ + values[0].data.d_status = status; + + /* + * Detach from the drawable... + */ + gimp_drawable_detach (drawable); +} + + +static void +compute_luts (void) +{ + gint i; /* Looping var */ + gint fact; /* 1 - sharpness */ + + fact = 100 - sharpen_params.sharpen_percent; + if (fact < 1) + fact = 1; + + for (i = 0; i < 256; i ++) + { + pos_lut[i] = 800 * i / fact; + neg_lut[i] = (4 + pos_lut[i] - (i << 3)) >> 3; + } +} + +/* + * 'sharpen()' - Sharpen an image using a convolution filter. + */ + +static void +sharpen (GimpDrawable *drawable) +{ + GimpPixelRgn src_rgn; /* Source image region */ + GimpPixelRgn dst_rgn; /* Destination image region */ + guchar *src_rows[4]; /* Source pixel rows */ + guchar *src_ptr; /* Current source pixel */ + guchar *dst_row; /* Destination pixel row */ + intneg *neg_rows[4]; /* Negative coefficient rows */ + intneg *neg_ptr; /* Current negative coefficient */ + gint i; /* Looping vars */ + gint y; /* Current location in image */ + gint row; /* Current row in src_rows */ + gint count; /* Current number of filled src_rows */ + gint width; /* Byte width of the image */ + gint x1; /* Selection bounds */ + gint y1; + gint y2; + gint sel_width; /* Selection width */ + gint sel_height; /* Selection height */ + gint img_bpp; /* Bytes-per-pixel in image */ + void (*filter)(int, guchar *, guchar *, intneg *, intneg *, intneg *); + + filter = NULL; + + if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x1, &y1, &sel_width, &sel_height)) + return; + + y2 = y1 + sel_height; + + img_bpp = gimp_drawable_bpp (drawable->drawable_id); + + /* + * Let the user know what we're doing... + */ + gimp_progress_init (_("Sharpening")); + + /* + * Setup for filter... + */ + + gimp_pixel_rgn_init (&src_rgn, drawable, + x1, y1, sel_width, sel_height, FALSE, FALSE); + gimp_pixel_rgn_init (&dst_rgn, drawable, + x1, y1, sel_width, sel_height, TRUE, TRUE); + + compute_luts (); + + width = sel_width * img_bpp; + + for (row = 0; row < 4; row ++) + { + src_rows[row] = g_new (guchar, width); + neg_rows[row] = g_new (intneg, width); + } + + dst_row = g_new (guchar, width); + + /* + * Pre-load the first row for the filter... + */ + + gimp_pixel_rgn_get_row (&src_rgn, src_rows[0], x1, y1, sel_width); + + for (i = width, src_ptr = src_rows[0], neg_ptr = neg_rows[0]; + i > 0; + i --, src_ptr ++, neg_ptr ++) + *neg_ptr = neg_lut[*src_ptr]; + + row = 1; + count = 1; + + /* + * Select the filter... + */ + + switch (img_bpp) + { + case 1 : + filter = gray_filter; + break; + case 2 : + filter = graya_filter; + break; + case 3 : + filter = rgb_filter; + break; + case 4 : + filter = rgba_filter; + break; + }; + + /* + * Sharpen... + */ + + for (y = y1; y < y2; y ++) + { + /* + * Load the next pixel row... + */ + + if ((y + 1) < y2) + { + /* + * Check to see if our src_rows[] array is overflowing yet... + */ + + if (count >= 3) + count --; + + /* + * Grab the next row... + */ + + gimp_pixel_rgn_get_row (&src_rgn, src_rows[row], + x1, y + 1, sel_width); + for (i = width, src_ptr = src_rows[row], neg_ptr = neg_rows[row]; + i > 0; + i --, src_ptr ++, neg_ptr ++) + *neg_ptr = neg_lut[*src_ptr]; + + count ++; + row = (row + 1) & 3; + } + else + { + /* + * No more pixels at the bottom... Drop the oldest samples... + */ + + count --; + } + + /* + * Now sharpen pixels and save the results... + */ + + if (count == 3) + { + (* filter) (sel_width, src_rows[(row + 2) & 3], dst_row, + neg_rows[(row + 1) & 3] + img_bpp, + neg_rows[(row + 2) & 3] + img_bpp, + neg_rows[(row + 3) & 3] + img_bpp); + + /* + * Set the row... + */ + + gimp_pixel_rgn_set_row (&dst_rgn, dst_row, x1, y, sel_width); + } + else if (count == 2) + { + if (y == y1) /* first row */ + gimp_pixel_rgn_set_row (&dst_rgn, src_rows[0], + x1, y, sel_width); + else /* last row */ + gimp_pixel_rgn_set_row (&dst_rgn, src_rows[(sel_height - 1) & 3], + x1, y, sel_width); + } + + if ((y & 15) == 0) + gimp_progress_update ((gdouble) (y - y1) / (gdouble) sel_height); + } + + /* + * OK, we're done. Free all memory used... + */ + + for (row = 0; row < 4; row ++) + { + g_free (src_rows[row]); + g_free (neg_rows[row]); + } + + g_free (dst_row); + + /* + * Update the screen... + */ + + gimp_progress_update (1.0); + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, + x1, y1, sel_width, sel_height); +} + + +/* + * 'sharpen_dialog()' - Popup a dialog window for the filter box size... + */ + +static gboolean +sharpen_dialog (GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *table; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Sharpen"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect (preview, "invalidated", + G_CALLBACK (preview_update), + drawable); + + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Sharpness:"), SCALE_WIDTH, 0, + sharpen_params.sharpen_percent, + 1, 99, 1, 10, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &sharpen_params.sharpen_percent); + g_signal_connect_swapped (adj, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +preview_update (GimpPreview *preview, + GimpDrawable *drawable) +{ + GimpPixelRgn src_rgn; /* Source image region */ + guchar *src_ptr; /* Current source pixel */ + guchar *dst_ptr; /* Current destination pixel */ + intneg *neg_ptr; /* Current negative pixel */ + gint i; /* Looping var */ + gint y; /* Current location in image */ + gint width; /* Byte width of the image */ + gint x1, y1; + gint preview_width, preview_height; + guchar *preview_src, *preview_dst; + intneg *preview_neg; + gint img_bpp; /* Bytes-per-pixel in image */ + + void (*filter)(int, guchar *, guchar *, intneg *, intneg *, intneg *); + + filter = NULL; + + compute_luts(); + + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &preview_width, &preview_height); + + img_bpp = gimp_drawable_bpp (drawable->drawable_id); + + + preview_src = g_new (guchar, preview_width * preview_height * img_bpp); + preview_neg = g_new (intneg, preview_width * preview_height * img_bpp); + preview_dst = g_new (guchar, preview_width * preview_height * img_bpp); + + gimp_pixel_rgn_init (&src_rgn, drawable, + x1, y1, preview_width, preview_height, + FALSE, FALSE); + + width = preview_width * img_bpp; + + /* + * Load the preview area... + */ + + gimp_pixel_rgn_get_rect (&src_rgn, preview_src, x1, y1, + preview_width, preview_height); + + for (i = width * preview_height, src_ptr = preview_src, neg_ptr = preview_neg; + i > 0; + i --) + *neg_ptr++ = neg_lut[*src_ptr++]; + + /* + * Select the filter... + */ + + switch (img_bpp) + { + case 1: + filter = gray_filter; + break; + case 2: + filter = graya_filter; + break; + case 3: + filter = rgb_filter; + break; + case 4: + filter = rgba_filter; + break; + default: + g_error ("Programmer stupidity error: img_bpp is %d\n", + img_bpp); + } + + /* + * Sharpen... + */ + + memcpy (preview_dst, preview_src, width); + memcpy (preview_dst + width * (preview_height - 1), + preview_src + width * (preview_height - 1), + width); + + for (y = preview_height - 2, src_ptr = preview_src + width, + neg_ptr = preview_neg + width + img_bpp, + dst_ptr = preview_dst + width; + y > 0; + y --, src_ptr += width, neg_ptr += width, dst_ptr += width) + (*filter)(preview_width, src_ptr, dst_ptr, neg_ptr - width, + neg_ptr, neg_ptr + width); + + gimp_preview_draw_buffer (preview, preview_dst, preview_width * img_bpp); + + g_free (preview_src); + g_free (preview_neg); + g_free (preview_dst); +} + +/* + * 'gray_filter()' - Sharpen grayscale pixels. + */ + +static void +gray_filter (gint width, /* I - Width of line in pixels */ + guchar *src, /* I - Source line */ + guchar *dst, /* O - Destination line */ + intneg *neg0, /* I - Top negative coefficient line */ + intneg *neg1, /* I - Middle negative coefficient line */ + intneg *neg2) /* I - Bottom negative coefficient line */ +{ + intpos pixel; /* New pixel value */ + + *dst++ = *src++; + width -= 2; + + while (width > 0) + { + pixel = (pos_lut[*src++] - neg0[-1] - neg0[0] - neg0[1] - + neg1[-1] - neg1[1] - + neg2[-1] - neg2[0] - neg2[1]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + neg0 ++; + neg1 ++; + neg2 ++; + width --; + } + + *dst++ = *src++; +} + +/* + * 'graya_filter()' - Sharpen grayscale+alpha pixels. + */ + +static void +graya_filter (gint width, /* I - Width of line in pixels */ + guchar *src, /* I - Source line */ + guchar *dst, /* O - Destination line */ + intneg *neg0, /* I - Top negative coefficient line */ + intneg *neg1, /* I - Middle negative coefficient line */ + intneg *neg2) /* I - Bottom negative coefficient line */ +{ + intpos pixel; /* New pixel value */ + + *dst++ = *src++; + *dst++ = *src++; + width -= 2; + + while (width > 0) + { + pixel = (pos_lut[*src++] - neg0[-2] - neg0[0] - neg0[2] - + neg1[-2] - neg1[2] - + neg2[-2] - neg2[0] - neg2[2]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + *dst++ = *src++; + neg0 += 2; + neg1 += 2; + neg2 += 2; + width --; + } + + *dst++ = *src++; + *dst++ = *src++; +} + +/* + * 'rgb_filter()' - Sharpen RGB pixels. + */ + +static void +rgb_filter (gint width, /* I - Width of line in pixels */ + guchar *src, /* I - Source line */ + guchar *dst, /* O - Destination line */ + intneg *neg0, /* I - Top negative coefficient line */ + intneg *neg1, /* I - Middle negative coefficient line */ + intneg *neg2) /* I - Bottom negative coefficient line */ +{ + intpos pixel; /* New pixel value */ + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + width -= 2; + + while (width > 0) + { + pixel = (pos_lut[*src++] - neg0[-3] - neg0[0] - neg0[3] - + neg1[-3] - neg1[3] - + neg2[-3] - neg2[0] - neg2[3]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + pixel = (pos_lut[*src++] - neg0[-2] - neg0[1] - neg0[4] - + neg1[-2] - neg1[4] - + neg2[-2] - neg2[1] - neg2[4]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + pixel = (pos_lut[*src++] - neg0[-1] - neg0[2] - neg0[5] - + neg1[-1] - neg1[5] - + neg2[-1] - neg2[2] - neg2[5]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + neg0 += 3; + neg1 += 3; + neg2 += 3; + width --; + } + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; +} + +/* + * 'rgba_filter()' - Sharpen RGBA pixels. + */ + +static void +rgba_filter (gint width, /* I - Width of line in pixels */ + guchar *src, /* I - Source line */ + guchar *dst, /* O - Destination line */ + intneg *neg0, /* I - Top negative coefficient line */ + intneg *neg1, /* I - Middle negative coefficient line */ + intneg *neg2) /* I - Bottom negative coefficient line */ +{ + intpos pixel; /* New pixel value */ + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + width -= 2; + + while (width > 0) + { + pixel = (pos_lut[*src++] - neg0[-4] - neg0[0] - neg0[4] - + neg1[-4] - neg1[4] - + neg2[-4] - neg2[0] - neg2[4]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + pixel = (pos_lut[*src++] - neg0[-3] - neg0[1] - neg0[5] - + neg1[-3] - neg1[5] - + neg2[-3] - neg2[1] - neg2[5]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + pixel = (pos_lut[*src++] - neg0[-2] - neg0[2] - neg0[6] - + neg1[-2] - neg1[6] - + neg2[-2] - neg2[2] - neg2[6]); + pixel = (pixel + 4) >> 3; + *dst++ = CLAMP0255 (pixel); + + *dst++ = *src++; + + neg0 += 4; + neg1 += 4; + neg2 += 4; + width --; + } + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; +} diff --git a/plug-ins/common/smooth-palette.c b/plug-ins/common/smooth-palette.c new file mode 100644 index 0000000..e6916b5 --- /dev/null +++ b/plug-ins/common/smooth-palette.c @@ -0,0 +1,499 @@ +/* + * smooth palette - derive smooth palette from image + * Copyright (C) 1997 Scott Draves + * + * GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-smooth-palette" +#define PLUG_IN_BINARY "smooth-palette" +#define PLUG_IN_ROLE "gimp-smooth-palette" + + +/* Declare local functions. */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean dialog (gint32 drawable_id); + +static gint32 smooth_palette (gint32 drawable_id, + gint32 *layer_id); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "width", "Width" }, + { GIMP_PDB_INT32, "height", "Height" }, + { GIMP_PDB_INT32, "ntries", "Search Depth" }, + { GIMP_PDB_INT32, "show-image", "Show Image?" } + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "new-image", "Output image" }, + { GIMP_PDB_LAYER, "new-layer", "Output layer" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Derive a smooth color palette from the image"), + "help!", + "Scott Draves", + "Scott Draves", + "1997", + N_("Smoo_th Palette..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Colors/Info"); +} + +static struct +{ + gint width; + gint height; + gint ntries; + gint try_size; + gint show_image; +} config = +{ + 256, + 64, + 50, + 10000, + 1 +}; + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[3]; + GimpRunMode run_mode; + gint32 drawable_id; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + + *nreturn_vals = 3; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + values[1].type = GIMP_PDB_IMAGE; + values[2].type = GIMP_PDB_LAYER; + + drawable_id = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &config); + if (! dialog (drawable_id)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + config.width = param[3].data.d_int32; + config.height = param[4].data.d_int32; + config.ntries = param[5].data.d_int32; + config.show_image = param[6].data.d_int32 ? TRUE : FALSE; + } + + if (status == GIMP_PDB_SUCCESS && + ((config.width <= 0) || (config.height <= 0) || config.ntries <= 0)) + status = GIMP_PDB_CALLING_ERROR; + + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &config); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + if (gimp_drawable_is_rgb (drawable_id)) + { + gimp_progress_init (_("Deriving smooth palette")); + + gegl_init (NULL, NULL); + values[1].data.d_image = smooth_palette (drawable_id, + &values[2].data.d_layer); + gegl_exit (); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &config, sizeof (config)); + + if (config.show_image) + gimp_display_new (values[1].data.d_image); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + values[0].data.d_status = status; +} + +static gfloat +pix_diff (gfloat *pal, + guint bpp, + gint i, + gint j) +{ + gfloat r = 0.f; + guint k; + + for (k = 0; k < bpp; k++) + { + gfloat p1 = pal[j * bpp + k]; + gfloat p2 = pal[i * bpp + k]; + r += (p1 - p2) * (p1 - p2); + } + + return r; +} + +static void +pix_swap (gfloat *pal, + guint bpp, + gint i, + gint j) +{ + guint k; + + for (k = 0; k < bpp; k++) + { + gfloat t = pal[j * bpp + k]; + pal[j * bpp + k] = pal[i * bpp + k]; + pal[i * bpp + k] = t; + } +} + +static gint32 +smooth_palette (gint32 drawable_id, + gint32 *layer_id) +{ + gint32 new_image_id; + gint psize, i, j; + guint bpp; + gint sel_x1, sel_y1; + gint width, height; + GeglBuffer *buffer; + GeglSampler *sampler; + gfloat *pal; + GRand *gr; + + const Babl *format = babl_format ("RGB float"); + + new_image_id = gimp_image_new_with_precision (config.width, + config.height, + GIMP_RGB, + GIMP_PRECISION_FLOAT_LINEAR); + + gimp_image_undo_disable (new_image_id); + + *layer_id = gimp_layer_new (new_image_id, _("Background"), + config.width, config.height, + gimp_drawable_type (drawable_id), + 100, + gimp_image_get_default_new_layer_mode (new_image_id)); + + gimp_image_insert_layer (new_image_id, *layer_id, -1, 0); + + if (! gimp_drawable_mask_intersect (drawable_id, + &sel_x1, &sel_y1, &width, &height)) + return new_image_id; + + gr = g_rand_new (); + + psize = config.width; + + buffer = gimp_drawable_get_buffer (drawable_id); + + sampler = gegl_buffer_sampler_new (buffer, format, GEGL_SAMPLER_NEAREST); + + bpp = babl_format_get_n_components (gegl_buffer_get_format (buffer)); + + pal = g_new (gfloat, psize * bpp); + + /* get initial palette */ + + for (i = 0; i < psize; i++) + { + gint x = sel_x1 + g_rand_int_range (gr, 0, width); + gint y = sel_y1 + g_rand_int_range (gr, 0, height); + + gegl_sampler_get (sampler, + (gdouble) x, (gdouble) y, NULL, pal + i * bpp, + GEGL_ABYSS_NONE); + } + + g_object_unref (sampler); + g_object_unref (buffer); + + /* reorder */ + if (1) + { + gfloat *pal_best; + gfloat *original; + gdouble len_best = 0; + gint try; + + pal_best = g_memdup (pal, bpp * psize); + original = g_memdup (pal, bpp * psize); + + for (try = 0; try < config.ntries; try++) + { + gdouble len; + + if (!(try%5)) + gimp_progress_update (try / (double) config.ntries); + memcpy (pal, original, bpp * psize); + + /* scramble */ + for (i = 1; i < psize; i++) + pix_swap (pal, bpp, i, g_rand_int_range (gr, 0, psize)); + + /* measure */ + len = 0.0; + for (i = 1; i < psize; i++) + len += pix_diff (pal, bpp, i, i-1); + + /* improve */ + for (i = 0; i < config.try_size; i++) + { + gint i0 = 1 + g_rand_int_range (gr, 0, psize-2); + gint i1 = 1 + g_rand_int_range (gr, 0, psize-2); + gfloat as_is, swapd; + + if (1 == (i0 - i1)) + { + as_is = (pix_diff (pal, bpp, i1 - 1, i1) + + pix_diff (pal, bpp, i0, i0 + 1)); + swapd = (pix_diff (pal, bpp, i1 - 1, i0) + + pix_diff (pal, bpp, i1, i0 + 1)); + } + else if (1 == (i1 - i0)) + { + as_is = (pix_diff (pal, bpp, i0 - 1, i0) + + pix_diff (pal, bpp, i1, i1 + 1)); + swapd = (pix_diff (pal, bpp, i0 - 1, i1) + + pix_diff (pal, bpp, i0, i1 + 1)); + } + else + { + as_is = (pix_diff (pal, bpp, i0, i0 + 1) + + pix_diff (pal, bpp, i0, i0 - 1) + + pix_diff (pal, bpp, i1, i1 + 1) + + pix_diff (pal, bpp, i1, i1 - 1)); + swapd = (pix_diff (pal, bpp, i1, i0 + 1) + + pix_diff (pal, bpp, i1, i0 - 1) + + pix_diff (pal, bpp, i0, i1 + 1) + + pix_diff (pal, bpp, i0, i1 - 1)); + } + if (swapd < as_is) + { + pix_swap (pal, bpp, i0, i1); + len += swapd - as_is; + } + } + /* best? */ + if (0 == try || len < len_best) + { + memcpy (pal_best, pal, bpp * psize); + len_best = len; + } + } + + gimp_progress_update (1.0); + memcpy (pal, pal_best, bpp * psize); + g_free (pal_best); + g_free (original); + + /* clean */ + for (i = 1; i < 4 * psize; i++) + { + gfloat as_is, swapd; + gint i0 = 1 + g_rand_int_range (gr, 0, psize - 2); + gint i1 = i0 + 1; + + as_is = (pix_diff (pal, bpp, i0 - 1, i0) + + pix_diff (pal, bpp, i1, i1 + 1)); + swapd = (pix_diff (pal, bpp, i0 - 1, i1) + + pix_diff (pal, bpp, i0, i1 + 1)); + + if (swapd < as_is) + { + pix_swap (pal, bpp, i0, i1); + len_best += swapd - as_is; + } + } + } + + /* store smooth palette */ + + buffer = gimp_drawable_get_buffer (*layer_id); + + for (j = 0; j < config.height; j++) + { + GeglRectangle row = {0, j, config.width, 1}; + gegl_buffer_set (buffer, &row, 0, format, pal, GEGL_AUTO_ROWSTRIDE); + } + + gegl_buffer_flush (buffer); + + gimp_drawable_update (*layer_id, 0, 0, + config.width, config.height); + gimp_image_undo_enable (new_image_id); + + g_object_unref (buffer); + g_free (pal); + g_rand_free (gr); + + return new_image_id; +} + +static gboolean +dialog (gint32 drawable_id) +{ + GtkWidget *dlg; + GtkWidget *spinbutton; + GtkAdjustment *adj; + GtkWidget *sizeentry; + guint32 image_id; + GimpUnit unit; + gdouble xres, yres; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dlg = gimp_dialog_new (_("Smooth Palette"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + image_id = gimp_item_get_image (drawable_id); + unit = gimp_image_get_unit (image_id); + gimp_image_get_resolution (image_id, &xres, &yres); + + sizeentry = gimp_coordinates_new (unit, "%a", TRUE, FALSE, 6, + GIMP_SIZE_ENTRY_UPDATE_SIZE, + FALSE, FALSE, + + _("_Width:"), + config.width, xres, + 2, GIMP_MAX_IMAGE_SIZE, + 2, GIMP_MAX_IMAGE_SIZE, + + _("_Height:"), + config.height, yres, + 1, GIMP_MAX_IMAGE_SIZE, + 1, GIMP_MAX_IMAGE_SIZE); + gtk_container_set_border_width (GTK_CONTAINER (sizeentry), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + sizeentry, FALSE, FALSE, 0); + gtk_widget_show (sizeentry); + + adj = GTK_ADJUSTMENT (gtk_adjustment_new (config.ntries, + 1, 1024, 1, 10, 0)); + spinbutton = gimp_spin_button_new (adj, 1, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + gimp_table_attach_aligned (GTK_TABLE (sizeentry), 0, 2, + _("_Search depth:"), 0.0, 0.5, + spinbutton, 1, FALSE); + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &config.ntries); + + gtk_widget_show (dlg); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + if (run) + { + config.width = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry), + 0); + config.height = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry), + 1); + } + + gtk_widget_destroy (dlg); + + return run; +} diff --git a/plug-ins/common/softglow.c b/plug-ins/common/softglow.c new file mode 100644 index 0000000..9a718a0 --- /dev/null +++ b/plug-ins/common/softglow.c @@ -0,0 +1,713 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Softglow filter for GIMP for BIPS + * -Spencer Kimball + * + * This filter screens a desaturated, sigmoidally transferred + * and gaussian blurred version of the drawable over itself + * to create a "softglow" photographic effect. + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Some useful macros */ + +#define PLUG_IN_PROC "plug-in-softglow" +#define PLUG_IN_BINARY "softglow" +#define PLUG_IN_ROLE "gimp-softglow" +#define TILE_CACHE_SIZE 48 +#define SIGMOIDAL_BASE 2 +#define SIGMOIDAL_RANGE 20 + +#define INT_MULT(a,b,t) ((t) = (a) * (b) + 0x80, ((((t) >> 8) + (t)) >> 8)) + +typedef struct +{ + gdouble glow_radius; + gdouble brightness; + gdouble sharpness; +} SoftglowVals; + + +/* + * Function prototypes. + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void softglow (GimpDrawable *drawable, + GimpPreview *preview); +static gboolean softglow_dialog (GimpDrawable *drawable); + +/* + * Gaussian blur helper functions + */ +static void find_constants (gdouble n_p[], + gdouble n_m[], + gdouble d_p[], + gdouble d_m[], + gdouble bd_p[], + gdouble bd_m[], + gdouble std_dev); +static void transfer_pixels (gdouble *src1, + gdouble *src2, + guchar *dest, + gint jump, + gint width); + +/***** Local vars *****/ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init */ + NULL, /* quit */ + query, /* query */ + run, /* run */ +}; + +static SoftglowVals svals = +{ + 10.0, /* glow_radius */ + 0.75, /* brightness */ + 0.85, /* sharpness */ +}; + + +/***** Functions *****/ + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_FLOAT, "glow-radius", "Glow radius (radius in pixels)" }, + { GIMP_PDB_FLOAT, "brightness", "Glow brightness (0.0 - 1.0)" }, + { GIMP_PDB_FLOAT, "sharpness", "Glow sharpness (0.0 - 1.0)" } + }; + + gchar *help_string = + "Gives an image a softglow effect by intensifying the highlights in the " + "image. This is done by screening a modified version of the drawable " + "with itself. The modified version is desaturated and then a sigmoidal " + "transfer function is applied to force the distribution of intensities " + "into very small and very large only. This desaturated version is then " + "blurred to give it a fuzzy 'vaseline-on-the-lens' effect. The glow " + "radius parameter controls the sharpness of the glow effect. The " + "brightness parameter controls the degree of intensification applied " + "to image highlights. The sharpness parameter controls how defined or " + "alternatively, diffuse, the glow effect should be."; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Simulate glow by making highlights intense and fuzzy"), + help_string, + "Spencer Kimball", + "Bit Specialists, Inc.", + "2001", + N_("_Softglow (legacy)..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Artistic"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpDrawable *drawable; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + run_mode = param[0].data.d_int32; + + /* Get the specified drawable */ + drawable = gimp_drawable_get (param[2].data.d_drawable); + + /* set the tile cache size */ + gimp_tile_cache_ntiles (TILE_CACHE_SIZE); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + INIT_I18N(); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &svals); + + /* First acquire information with a dialog */ + if (! softglow_dialog (drawable)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + svals.glow_radius = param[3].data.d_float; + svals.brightness = param[4].data.d_float; + svals.sharpness = param[5].data.d_float; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &svals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* Make sure that the drawable is RGB or GRAY color */ + if (gimp_drawable_is_rgb (drawable->drawable_id) || + gimp_drawable_is_gray (drawable->drawable_id)) + { + gimp_progress_init ("Softglow"); + + softglow (drawable, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &svals, sizeof (SoftglowVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = _("Cannot operate on indexed color images."); + } + } + + values[0].data.d_status = status; + + gimp_drawable_detach (drawable); +} + +static void +softglow (GimpDrawable *drawable, + GimpPreview *preview) +{ + GimpPixelRgn src_rgn, dest_rgn; + GimpPixelRgn *pr; + gint width, height; + gint bytes; + gboolean has_alpha; + guchar *dest; + guchar *src, *sp_p, *sp_m; + gdouble n_p[5], n_m[5]; + gdouble d_p[5], d_m[5]; + gdouble bd_p[5], bd_m[5]; + gdouble *val_p, *val_m, *vp, *vm; + gint x1, y1; + gint i, j; + gint row, col, b; + gint terms; + gint progress, max_progress; + gint initial_p[4]; + gint initial_m[4]; + gint tmp; + gdouble radius; + gdouble std_dev; + gdouble val; + + if (preview) + { + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &width, &height); + } + else if (! gimp_drawable_mask_intersect (drawable->drawable_id, + &x1, &y1, &width, &height)) + return; + + bytes = drawable->bpp; + has_alpha = gimp_drawable_has_alpha (drawable->drawable_id); + + val_p = g_new (gdouble, MAX (width, height)); + val_m = g_new (gdouble, MAX (width, height)); + + dest = g_new0 (guchar, width * height); + + progress = 0; + max_progress = width * height * 3; + + /* Initialize the pixel regions. */ + gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, width, height, FALSE, FALSE); + + for (pr = gimp_pixel_rgns_register (1, &src_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) + { + guchar *src_ptr = src_rgn.data; + guchar *dest_ptr = dest + (src_rgn.y - y1) * width + (src_rgn.x - x1); + + for (row = 0; row < src_rgn.h; row++) + { + for (col = 0; col < src_rgn.w; col++) + { + /* desaturate */ + if (bytes > 2) + dest_ptr[col] = (guchar) gimp_rgb_to_l_int (src_ptr[col * bytes + 0], + src_ptr[col * bytes + 1], + src_ptr[col * bytes + 2]); + else + dest_ptr[col] = (guchar) src_ptr[col * bytes]; + + /* compute sigmoidal transfer */ + val = dest_ptr[col] / 255.0; + val = 255.0 / (1 + exp (-(SIGMOIDAL_BASE + (svals.sharpness * SIGMOIDAL_RANGE)) * (val - 0.5))); + val = val * svals.brightness; + dest_ptr[col] = (guchar) CLAMP (val, 0, 255); + } + + src_ptr += src_rgn.rowstride; + dest_ptr += width; + } + + if (!preview) + { + progress += src_rgn.w * src_rgn.h; + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + /* Calculate the standard deviations */ + radius = fabs (svals.glow_radius) + 1.0; + std_dev = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0))); + + /* derive the constants for calculating the gaussian from the std dev */ + find_constants (n_p, n_m, d_p, d_m, bd_p, bd_m, std_dev); + + /* First the vertical pass */ + for (col = 0; col < width; col++) + { + memset (val_p, 0, height * sizeof (gdouble)); + memset (val_m, 0, height * sizeof (gdouble)); + + src = dest + col; + sp_p = src; + sp_m = src + width * (height - 1); + vp = val_p; + vm = val_m + (height - 1); + + /* Set up the first vals */ + initial_p[0] = sp_p[0]; + initial_m[0] = sp_m[0]; + + for (row = 0; row < height; row++) + { + gdouble *vpptr, *vmptr; + + terms = (row < 4) ? row : 4; + + vpptr = vp; vmptr = vm; + for (i = 0; i <= terms; i++) + { + *vpptr += n_p[i] * sp_p[-i * width] - d_p[i] * vp[-i]; + *vmptr += n_m[i] * sp_m[i * width] - d_m[i] * vm[i]; + } + for (j = i; j <= 4; j++) + { + *vpptr += (n_p[j] - bd_p[j]) * initial_p[0]; + *vmptr += (n_m[j] - bd_m[j]) * initial_m[0]; + } + + sp_p += width; + sp_m -= width; + vp ++; + vm --; + } + + transfer_pixels (val_p, val_m, dest + col, width, height); + + if (!preview) + { + progress += height; + if ((col % 5) == 0) + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + for (row = 0; row < height; row++) + { + memset (val_p, 0, width * sizeof (gdouble)); + memset (val_m, 0, width * sizeof (gdouble)); + + src = dest + row * width; + + sp_p = src; + sp_m = src + width - 1; + vp = val_p; + vm = val_m + width - 1; + + /* Set up the first vals */ + initial_p[0] = sp_p[0]; + initial_m[0] = sp_m[0]; + + for (col = 0; col < width; col++) + { + gdouble *vpptr, *vmptr; + + terms = (col < 4) ? col : 4; + + vpptr = vp; vmptr = vm; + + for (i = 0; i <= terms; i++) + { + *vpptr += n_p[i] * sp_p[-i] - d_p[i] * vp[-i]; + *vmptr += n_m[i] * sp_m[i] - d_m[i] * vm[i]; + } + + for (j = i; j <= 4; j++) + { + *vpptr += (n_p[j] - bd_p[j]) * initial_p[0]; + *vmptr += (n_m[j] - bd_m[j]) * initial_m[0]; + } + + sp_p ++; + sp_m --; + vp ++; + vm --; + } + + transfer_pixels (val_p, val_m, dest + row * width, 1, width); + + if (!preview) + { + progress += width; + if ((row % 5) == 0) + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + /* Initialize the pixel regions. */ + gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, width, height, FALSE, FALSE); + gimp_pixel_rgn_init (&dest_rgn, drawable, + x1, y1, width, height, (preview == NULL), TRUE); + + for (pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) + { + guchar *src_ptr = src_rgn.data; + guchar *dest_ptr = dest_rgn.data; + guchar *blur_ptr = dest + (src_rgn.y - y1) * width + (src_rgn.x - x1); + + for (row = 0; row < src_rgn.h; row++) + { + for (col = 0; col < src_rgn.w; col++) + { + /* screen op */ + for (b = 0; b < (has_alpha ? (bytes - 1) : bytes); b++) + dest_ptr[col * bytes + b] = + 255 - INT_MULT((255 - src_ptr[col * bytes + b]), + (255 - blur_ptr[col]), tmp); + if (has_alpha) + dest_ptr[col * bytes + b] = src_ptr[col * bytes + b]; + } + + src_ptr += src_rgn.rowstride; + dest_ptr += dest_rgn.rowstride; + blur_ptr += width; + } + + if (preview) + { + gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview), + &dest_rgn); + } + else + { + progress += src_rgn.w * src_rgn.h; + gimp_progress_update ((gdouble) progress / (gdouble) max_progress); + } + } + + if (! preview) + { + gimp_progress_update (1.0); + /* merge the shadow, update the drawable */ + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, + x1, y1, width, height); + } + + /* free up buffers */ + g_free (val_p); + g_free (val_m); + g_free (dest); +} + +/* + * Gaussian blur helper functions + */ + +static void +transfer_pixels (gdouble *src1, + gdouble *src2, + guchar *dest, + gint jump, + gint width) +{ + gint i; + gdouble sum; + + for (i = 0; i < width; i++) + { + sum = src1[i] + src2[i]; + + sum = CLAMP0255 (sum); + + *dest = (guchar) sum; + dest += jump; + } +} + +static void +find_constants (gdouble n_p[], + gdouble n_m[], + gdouble d_p[], + gdouble d_m[], + gdouble bd_p[], + gdouble bd_m[], + gdouble std_dev) +{ + gint i; + gdouble constants [8]; + gdouble div; + + /* The constants used in the implementation of a casual sequence + * using a 4th order approximation of the gaussian operator + */ + + div = sqrt(2 * G_PI) * std_dev; + + constants [0] = -1.783 / std_dev; + constants [1] = -1.723 / std_dev; + constants [2] = 0.6318 / std_dev; + constants [3] = 1.997 / std_dev; + constants [4] = 1.6803 / div; + constants [5] = 3.735 / div; + constants [6] = -0.6803 / div; + constants [7] = -0.2598 / div; + + n_p [0] = constants[4] + constants[6]; + n_p [1] = exp (constants[1]) * + (constants[7] * sin (constants[3]) - + (constants[6] + 2 * constants[4]) * cos (constants[3])) + + exp (constants[0]) * + (constants[5] * sin (constants[2]) - + (2 * constants[6] + constants[4]) * cos (constants[2])); + n_p [2] = 2 * exp (constants[0] + constants[1]) * + ((constants[4] + constants[6]) * cos (constants[3]) * cos (constants[2]) - + constants[5] * cos (constants[3]) * sin (constants[2]) - + constants[7] * cos (constants[2]) * sin (constants[3])) + + constants[6] * exp (2 * constants[0]) + + constants[4] * exp (2 * constants[1]); + n_p [3] = exp (constants[1] + 2 * constants[0]) * + (constants[7] * sin (constants[3]) - constants[6] * cos (constants[3])) + + exp (constants[0] + 2 * constants[1]) * + (constants[5] * sin (constants[2]) - constants[4] * cos (constants[2])); + n_p [4] = 0.0; + + d_p [0] = 0.0; + d_p [1] = -2 * exp (constants[1]) * cos (constants[3]) - + 2 * exp (constants[0]) * cos (constants[2]); + d_p [2] = 4 * cos (constants[3]) * cos (constants[2]) * exp (constants[0] + constants[1]) + + exp (2 * constants[1]) + exp (2 * constants[0]); + d_p [3] = -2 * cos (constants[2]) * exp (constants[0] + 2 * constants[1]) - + 2 * cos (constants[3]) * exp (constants[1] + 2 * constants[0]); + d_p [4] = exp (2 * constants[0] + 2 * constants[1]); + +#ifndef ORIGINAL_READABLE_CODE + memcpy(d_m, d_p, 5 * sizeof(gdouble)); +#else + for (i = 0; i <= 4; i++) + d_m [i] = d_p [i]; +#endif + + n_m[0] = 0.0; + for (i = 1; i <= 4; i++) + n_m [i] = n_p[i] - d_p[i] * n_p[0]; + + { + gdouble sum_n_p, sum_n_m, sum_d; + gdouble a, b; + + sum_n_p = 0.0; + sum_n_m = 0.0; + sum_d = 0.0; + + for (i = 0; i <= 4; i++) + { + sum_n_p += n_p[i]; + sum_n_m += n_m[i]; + sum_d += d_p[i]; + } + +#ifndef ORIGINAL_READABLE_CODE + sum_d++; + a = sum_n_p / sum_d; + b = sum_n_m / sum_d; +#else + a = sum_n_p / (1 + sum_d); + b = sum_n_m / (1 + sum_d); +#endif + + for (i = 0; i <= 4; i++) + { + bd_p[i] = d_p[i] * a; + bd_m[i] = d_m[i] * b; + } + } +} + +/*******************************************************/ +/* Dialog */ +/*******************************************************/ + +static gboolean +softglow_dialog (GimpDrawable *drawable) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *table; + GtkObject *scale_data; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Softglow"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (softglow), + drawable); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* Label, scale, entry for svals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Glow radius:"), 100, 5, + svals.glow_radius, 1.0, 50.0, 1, 5.0, 2, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.glow_radius); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* Label, scale, entry for svals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("_Brightness:"), 100, 5, + svals.brightness, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.brightness); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* Label, scale, entry for svals.amount */ + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("_Sharpness:"), 100, 5, + svals.sharpness, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.sharpness); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/sparkle.c b/plug-ins/common/sparkle.c new file mode 100644 index 0000000..6af5044 --- /dev/null +++ b/plug-ins/common/sparkle.c @@ -0,0 +1,1189 @@ +/* Sparkle --- image filter plug-in for GIMP + * Copyright (C) 1996 by John Beale; ported to Gimp by Michael J. Hammel; + * + * It has been optimized a little, bugfixed and modified by Martin Weber + * for additional functionality. Also bugfixed by Seth Burgess (9/17/03) + * to take rowstrides into account when selections are present (bug #50911). + * Attempted reformatting. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * You can contact Michael at mjhammel@csn.net + * You can contact Martin at martweb@gmx.net + * You can contact Seth at sjburges@gimp.org + */ + +/* + * Sparkle 1.27 - simulate pixel bloom and diffraction effects + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-sparkle" +#define PLUG_IN_BINARY "sparkle" +#define PLUG_IN_ROLE "gimp-sparkle" + +#define SCALE_WIDTH 175 +#define ENTRY_WIDTH 7 +#define MAX_CHANNELS 4 +#define PSV 2 /* point spread value */ + +#define NATURAL 0 +#define FOREGROUND 1 +#define BACKGROUND 2 + + +typedef struct +{ + gdouble lum_threshold; + gdouble flare_inten; + gdouble spike_len; + gdouble spike_pts; + gdouble spike_angle; + gdouble density; + gdouble transparency; + gdouble random_hue; + gdouble random_saturation; + gboolean preserve_luminosity; + gboolean inverse; + gboolean border; + gint colortype; +} SparkleVals; + + +/* Declare local functions. + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean sparkle_dialog (gint32 drawable_ID); + +static gint compute_luminosity (const guchar *pixel, + gboolean gray, + gboolean has_alpha); +static gint compute_lum_threshold (gint32 drawable_ID, + gdouble percentile); +static void sparkle (gint32 drawable_ID, + GimpPreview *preview); +static void sparkle_preview (gpointer drawable_ID, + GimpPreview *preview); +static void fspike (GeglBuffer *src_buffer, + GeglBuffer *dest_buffer, + const Babl *format, + gint bytes, + gint x1, + gint y1, + gint x2, + gint y2, + gint xr, + gint yr, + gdouble inten, + gdouble length, + gdouble angle, + GRand *gr, + guchar *dest_buf); +static void rpnt (GeglBuffer *dest_buffer, + const Babl *format, + gint x1, + gint y1, + gint x2, + gint y2, + gdouble xr, + gdouble yr, + gint bytes, + gdouble inten, + guchar color[MAX_CHANNELS], + guchar *dest_buf); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static SparkleVals svals = +{ + 0.001, /* luminosity threshold */ + 0.5, /* flare intensity */ + 20.0, /* spike length */ + 4.0, /* spike points */ + 15.0, /* spike angle */ + 1.0, /* spike density */ + 0.0, /* transparency */ + 0.0, /* random hue */ + 0.0, /* random saturation */ + FALSE, /* preserve_luminosity */ + FALSE, /* inverse */ + FALSE, /* border */ + NATURAL /* colortype */ +}; + +static gint num_sparkles; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_FLOAT, "lum-threshold", "Luminosity threshold (0.0 - 1.0)" }, + { GIMP_PDB_FLOAT, "flare-inten", "Flare intensity (0.0 - 1.0)" }, + { GIMP_PDB_INT32, "spike-len", "Spike length (in pixels)" }, + { GIMP_PDB_INT32, "spike-pts", "# of spike points" }, + { GIMP_PDB_INT32, "spike-angle", "Spike angle (0-360 degrees, -1: random)" }, + { GIMP_PDB_FLOAT, "density", "Spike density (0.0 - 1.0)" }, + { GIMP_PDB_FLOAT, "transparency", "Transparency (0.0 - 1.0)" }, + { GIMP_PDB_FLOAT, "random-hue", "Random hue (0.0 - 1.0)" }, + { GIMP_PDB_FLOAT, "random-saturation", "Random saturation (0.0 - 1.0)" }, + { GIMP_PDB_INT32, "preserve-luminosity", "Preserve luminosity (TRUE/FALSE)" }, + { GIMP_PDB_INT32, "inverse", "Inverse (TRUE/FALSE)" }, + { GIMP_PDB_INT32, "border", "Add border (TRUE/FALSE)" }, + { GIMP_PDB_INT32, "color-type", "Color of sparkles: { NATURAL (0), FOREGROUND (1), BACKGROUND (2) }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Turn bright spots into starry sparkles"), + "Uses a percentage based luminoisty threhsold to find " + "candidate pixels for adding some sparkles (spikes). ", + "John Beale, & (ported to GIMP v0.54) Michael " + "J. Hammel & ted to GIMP v1.0) & Seth Burgess & " + "Spencer Kimball", + "John Beale", + "Version 1.27, September 2003", + N_("_Sparkle..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, + "/Filters/Light and Shadow/Light"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + gint32 drawable_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint x, y, w, h; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + run_mode = param[0].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + if (! gimp_drawable_mask_intersect (drawable_ID, &x, &y, &w, &h)) + { + g_message (_("Region selected for filter is empty")); + return; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &svals); + + /* First acquire information with a dialog */ + if (! sparkle_dialog (drawable_ID)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 16) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + svals.lum_threshold = param[3].data.d_float; + svals.flare_inten = param[4].data.d_float; + svals.spike_len = param[5].data.d_int32; + svals.spike_pts = param[6].data.d_int32; + svals.spike_angle = param[7].data.d_int32; + svals.density = param[8].data.d_float; + svals.transparency = param[9].data.d_float; + svals.random_hue = param[10].data.d_float; + svals.random_saturation = param[11].data.d_float; + svals.preserve_luminosity = (param[12].data.d_int32) ? TRUE : FALSE; + svals.inverse = (param[13].data.d_int32) ? TRUE : FALSE; + svals.border = (param[14].data.d_int32) ? TRUE : FALSE; + svals.colortype = param[15].data.d_int32; + + if (svals.lum_threshold < 0.0 || svals.lum_threshold > 1.0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.flare_inten < 0.0 || svals.flare_inten > 1.0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.spike_len < 0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.spike_pts < 0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.spike_angle < -1 || svals.spike_angle > 360) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.density < 0.0 || svals.density > 1.0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.transparency < 0.0 || svals.transparency > 1.0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.random_hue < 0.0 || svals.random_hue > 1.0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.random_saturation < 0.0 || + svals.random_saturation > 1.0) + status = GIMP_PDB_CALLING_ERROR; + else if (svals.colortype < NATURAL || svals.colortype > BACKGROUND) + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &svals); + break; + + default: + break; + } + + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_is_rgb (drawable_ID) || + gimp_drawable_is_gray (drawable_ID)) + { + gimp_progress_init (_("Sparkling")); + + sparkle (drawable_ID, NULL); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + /* Store mvals data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &svals, sizeof (SparkleVals)); + } + else + { + /* gimp_message ("sparkle: cannot operate on indexed color images"); */ + status = GIMP_PDB_EXECUTION_ERROR; + } + + values[0].data.d_status = status; +} + +static gboolean +sparkle_dialog (gint32 drawable_ID) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *preview; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *table; + GtkWidget *toggle; + GtkWidget *r1, *r2, *r3; + GtkObject *scale_data; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Sparkle"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + preview = gimp_drawable_preview_new_from_drawable_id (drawable_ID); + gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0); + gtk_widget_show (preview); + g_signal_connect_swapped (preview, "invalidated", + G_CALLBACK (sparkle_preview), + GINT_TO_POINTER (drawable_ID)); + + table = gtk_table_new (9, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("Luminosity _threshold:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.lum_threshold, 0.0, 0.1, 0.001, 0.01, 3, + TRUE, 0, 0, + _("Adjust the luminosity threshold"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.lum_threshold); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 1, + _("F_lare intensity:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.flare_inten, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + _("Adjust the flare intensity"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.flare_inten); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 2, + _("_Spike length:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.spike_len, 1, 100, 1, 10, 0, + TRUE, 0, 0, + _("Adjust the spike length"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.spike_len); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 3, + _("Sp_ike points:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.spike_pts, 0, 16, 1, 4, 0, + TRUE, 0, 0, + _("Adjust the number of spikes"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.spike_pts); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 4, + _("Spi_ke angle (-1: random):"), SCALE_WIDTH, ENTRY_WIDTH, + svals.spike_angle, -1, 360, 1, 15, 0, + TRUE, 0, 0, + _("Adjust the spike angle " + "(-1 causes a random angle to be chosen)"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.spike_angle); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 5, + _("Spik_e density:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.density, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + _("Adjust the spike density"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.density); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 6, + _("Tr_ansparency:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.transparency, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + _("Adjust the opacity of the spikes"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.transparency); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 7, + _("_Random hue:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.random_hue, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + _("Adjust how much the hue should be changed randomly"), NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.random_hue); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + scale_data = + gimp_scale_entry_new (GTK_TABLE (table), 0, 8, + _("Rando_m saturation:"), SCALE_WIDTH, ENTRY_WIDTH, + svals.random_saturation, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0, 0, + _("Adjust how much the saturation should be changed randomly"), + NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &svals.random_saturation); + g_signal_connect_swapped (scale_data, "value-changed", + G_CALLBACK (gimp_preview_invalidate), + preview); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + toggle = gtk_check_button_new_with_mnemonic (_("_Preserve luminosity")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + svals.preserve_luminosity); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("Should the luminosity be preserved?"), NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &svals.preserve_luminosity); + g_signal_connect_swapped (toggle, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + toggle = gtk_check_button_new_with_mnemonic (_("In_verse")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), svals.inverse); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("Should the effect be inversed?"), NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &svals.inverse); + g_signal_connect_swapped (toggle, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + toggle = gtk_check_button_new_with_mnemonic (_("A_dd border")); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), svals.border); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("Draw a border of spikes around the image"), NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &svals.border); + g_signal_connect_swapped (toggle, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + /* colortype */ + vbox = gimp_int_radio_group_new (FALSE, NULL, + G_CALLBACK (gimp_radio_button_update), + &svals.colortype, svals.colortype, + + _("_Natural color"), NATURAL, &r1, + _("_Foreground color"), FOREGROUND, &r2, + _("_Background color"), BACKGROUND, &r3, + + NULL); + + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + gimp_help_set_help_data (r1, _("Use the color of the image"), NULL); + gimp_help_set_help_data (r2, _("Use the foreground color"), NULL); + gimp_help_set_help_data (r3, _("Use the background color"), NULL); + g_signal_connect_swapped (r1, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (r2, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + g_signal_connect_swapped (r3, "toggled", + G_CALLBACK (gimp_preview_invalidate), + preview); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static gint +compute_luminosity (const guchar *pixel, + gboolean gray, + gboolean has_alpha) +{ + gint pixel0, pixel1, pixel2; + + if (svals.inverse) + { + pixel0 = 255 - pixel[0]; + pixel1 = 255 - pixel[1]; + pixel2 = 255 - pixel[2]; + } + else + { + pixel0 = pixel[0]; + pixel1 = pixel[1]; + pixel2 = pixel[2]; + } + + if (gray) + { + if (has_alpha) + return (pixel0 * pixel1) / 255; + else + return (pixel0); + } + else + { + gint min, max; + + min = MIN (pixel0, pixel1); + min = MIN (min, pixel2); + max = MAX (pixel0, pixel1); + max = MAX (max, pixel2); + + if (has_alpha) + return ((min + max) * pixel[3]) / 510; + else + return (min + max) / 2; + } +} + +static gint +compute_lum_threshold (gint32 drawable_ID, + gdouble percentile) +{ + GeglBuffer *src_buffer; + GeglBufferIterator *iter; + const Babl *format; + gint bpp; + gint values[256]; + gint total, sum; + gboolean gray; + gboolean has_alpha; + gint i; + gint x1, y1; + gint width, height; + + /* zero out the luminosity values array */ + memset (values, 0, sizeof (gint) * 256); + + if (! gimp_drawable_mask_intersect (drawable_ID, + &x1, &y1, &width, &height)) + return 0; + + gray = gimp_drawable_is_gray (drawable_ID); + has_alpha = gimp_drawable_has_alpha (drawable_ID); + + if (gray) + { + if (has_alpha) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + } + else + { + if (has_alpha) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + } + + bpp = babl_format_get_bytes_per_pixel (format); + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + + iter = gegl_buffer_iterator_new (src_buffer, + GEGL_RECTANGLE (x1, y1, width, height), 0, + format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (iter)) + { + const guchar *src = iter->items[0].data; + gint length = iter->length; + + while (length--) + { + values [compute_luminosity (src, gray, has_alpha)]++; + src += bpp; + } + } + + g_object_unref (src_buffer); + + total = width * height; + sum = 0; + + for (i = 255; i >= 0; i--) + { + sum += values[i]; + if ((gdouble) sum > percentile * (gdouble) total) + { + num_sparkles = sum; + return i; + } + } + + return 0; +} + +static void +sparkle (gint32 drawable_ID, + GimpPreview *preview) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + GeglBufferIterator *iter; + const Babl *format; + gint d_width, d_height; + gdouble nfrac, length, inten, spike_angle; + gint cur_progress, max_progress; + gint x1, y1, x2, y2; + gint width, height; + gint threshold; + gint lum, x, y, b; + gboolean gray, has_alpha; + gint alpha; + gint bytes; + GRand *gr; + guchar *dest_buf = NULL; + + gray = gimp_drawable_is_gray (drawable_ID); + has_alpha = gimp_drawable_has_alpha (drawable_ID); + + if (gray) + { + if (has_alpha) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + } + else + { + if (has_alpha) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + } + + bytes = babl_format_get_bytes_per_pixel (format); + + alpha = (has_alpha) ? bytes - 1 : bytes; + + if (preview) + { + gimp_preview_get_position (preview, &x1, &y1); + gimp_preview_get_size (preview, &width, &height); + + x2 = x1 + width; + y2 = y1 + height; + dest_buf = g_new0 (guchar, width * height * bytes); + } + else + { + if (! gimp_drawable_mask_intersect (drawable_ID, + &x1, &y1, &width, &height)) + return; + + x2 = x1 + width; + y2 = y1 + height; + } + + if (width < 1 || height < 1) + return; + + d_width = gimp_drawable_width (drawable_ID); + d_height = gimp_drawable_height (drawable_ID); + + gr = g_rand_new (); + + if (svals.border) + { + num_sparkles = 2 * (width + height); + threshold = 255; + } + else + { + /* compute the luminosity which exceeds the luminosity threshold */ + threshold = compute_lum_threshold (drawable_ID, svals.lum_threshold); + } + + /* initialize the progress dialog */ + cur_progress = 0; + max_progress = num_sparkles; + + /* copy what is already there */ + src_buffer = gimp_drawable_get_buffer (drawable_ID); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + iter = gegl_buffer_iterator_new (src_buffer, + GEGL_RECTANGLE (x1, y1, width, height), 0, + format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2); + + gegl_buffer_iterator_add (iter, dest_buffer, + GEGL_RECTANGLE (x1, y1, width, height), 0, + format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE); + + while (gegl_buffer_iterator_next (iter)) + { + GeglRectangle roi = iter->items[0].roi; + const guchar *src, *s; + guchar *dest, *d; + + src = iter->items[0].data; + if (preview) + dest = dest_buf + (((roi.y - y1) * width) + (roi.x - x1)) * bytes; + else + dest = iter->items[1].data; + + for (y = 0; y < roi.height; y++) + { + s = src; + d = dest; + + for (x = 0; x < roi.width; x++) + { + if (has_alpha && s[alpha] == 0) + { + memset (d, 0, alpha); + } + else + { + for (b = 0; b < alpha; b++) + d[b] = s[b]; + } + + if (has_alpha) + d[alpha] = s[alpha]; + + s += bytes; + d += bytes; + } + + src += roi.width * bytes; + if (preview) + dest += width * bytes; + else + dest += roi.width * bytes; + } + } + + /* add effects to new image based on intensity of old pixels */ + + iter = gegl_buffer_iterator_new (src_buffer, + GEGL_RECTANGLE (x1, y1, width, height), 0, + format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2); + + gegl_buffer_iterator_add (iter, dest_buffer, + GEGL_RECTANGLE (x1, y1, width, height), 0, + format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE); + + while (gegl_buffer_iterator_next (iter)) + { + GeglRectangle roi = iter->items[0].roi; + const guchar *src, *s; + + src = iter->items[0].data; + + for (y = 0; y < roi.height; y++) + { + s = src; + + for (x = 0; x < roi.width; x++) + { + if (svals.border) + { + if (x + roi.x == 0 || + y + roi.y == 0 || + x + roi.x == d_width - 1 || + y + roi.y == d_height - 1) + { + lum = 255; + } + else + { + lum = 0; + } + } + else + { + lum = compute_luminosity (s, gray, has_alpha); + } + + if (lum >= threshold) + { + nfrac = fabs ((gdouble) (lum + 1 - threshold) / + (gdouble) (256 - threshold)); + length = ((gdouble) svals.spike_len * + (gdouble) pow (nfrac, 0.8)); + inten = svals.flare_inten * nfrac; + + /* fspike im x,y intens rlength angle */ + if (svals.spike_pts > 0) + { + /* major spikes */ + if (svals.spike_angle == -1) + spike_angle = g_rand_double_range (gr, 0, 360.0); + else + spike_angle = svals.spike_angle; + + if (g_rand_double (gr) <= svals.density) + { + fspike (src_buffer, dest_buffer, format, bytes, + x1, y1, x2, y2, + x + roi.x, y + roi.y, + inten, length, spike_angle, gr, dest_buf); + + /* minor spikes */ + fspike (src_buffer, dest_buffer, format, bytes, + x1, y1, x2, y2, + x + roi.x, y + roi.y, + inten * 0.7, length * 0.7, + ((gdouble)spike_angle+180.0/svals.spike_pts), + gr, dest_buf); + } + } + if (!preview) + { + cur_progress ++; + + if ((cur_progress % 5) == 0) + gimp_progress_update ((double) cur_progress / + (double) max_progress); + } + } + + s += bytes; + } + + src += roi.width * bytes; + } + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + if (preview) + { + gimp_preview_draw_buffer (preview, dest_buf, width * bytes); + g_free (dest_buf); + } + else + { + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, x1, y1, width, height); + } + + g_rand_free (gr); +} + +static void +sparkle_preview (gpointer drawable_ID, + GimpPreview *preview) +{ + sparkle (GPOINTER_TO_INT (drawable_ID), preview); +} + +static inline void +rpnt (GeglBuffer *dest_buffer, + const Babl *format, + gint x1, + gint y1, + gint x2, + gint y2, + gdouble xr, + gdouble yr, + gint bytes, + gdouble inten, + guchar color[MAX_CHANNELS], + guchar *dest_buf) +{ + gint x, y, b; + gdouble dx, dy, rs, val; + guchar *pixel; + guchar pixel_buf[4]; + gdouble new; + + x = (int) (xr); /* integer coord. to upper left of real point */ + y = (int) (yr); + + if (x >= x1 && y >= y1 && x < x2 && y < y2) + { + if (dest_buf) + { + pixel = dest_buf + ((y - y1) * (x2 - x1) + (x - x1)) * bytes; + } + else + { + gegl_buffer_sample (dest_buffer, x, y, NULL, + pixel_buf, format, + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); + + pixel = pixel_buf; + } + + dx = xr - x; dy = yr - y; + rs = dx * dx + dy * dy; + val = inten * exp (-rs / PSV); + + for (b = 0; b < bytes; b++) + { + if (svals.inverse) + new = 255 - pixel[b]; + else + new = pixel[b]; + + if (svals.preserve_luminosity) + { + if (new < color[b]) + { + new *= (1.0 - val * (1.0 - svals.transparency)); + } + else + { + new -= val * color[b] * (1.0 - svals.transparency); + if (new < 0.0) + new = 0.0; + } + } + + new *= 1.0 - val * svals.transparency; + new += val * color[b]; + + if (new > 255) + new = 255; + + if (svals.inverse) + pixel[b] = 255 - new; + else + pixel[b] = new; + } + + if (! dest_buf) + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x, y, 1, 1), 0, + format, pixel_buf, + GEGL_AUTO_ROWSTRIDE); + } +} + +static void +fspike (GeglBuffer *src_buffer, + GeglBuffer *dest_buffer, + const Babl *format, + gint bytes, + gint x1, + gint y1, + gint x2, + gint y2, + gint xr, + gint yr, + gdouble inten, + gdouble length, + gdouble angle, + GRand *gr, + guchar *dest_buf) +{ + const gdouble efac = 2.0; + gdouble xrt, yrt, dx, dy; + gdouble rpos; + gdouble in; + gdouble theta; + gdouble sfac; + gint r, g, b; + gint i; + gboolean ok; + GimpRGB gimp_color; + guchar pixel[MAX_CHANNELS]; + guchar chosen_color[MAX_CHANNELS]; + guchar color[MAX_CHANNELS]; + + theta = angle; + + switch (svals.colortype) + { + case NATURAL: + break; + + case FOREGROUND: + gimp_context_get_foreground (&gimp_color); + gimp_rgb_get_uchar (&gimp_color, &chosen_color[0], &chosen_color[1], + &chosen_color[2]); + break; + + case BACKGROUND: + gimp_context_get_background (&gimp_color); + gimp_rgb_get_uchar (&gimp_color, &chosen_color[0], &chosen_color[1], + &chosen_color[2]); + break; + } + + /* draw the major spikes */ + for (i = 0; i < svals.spike_pts; i++) + { + gegl_buffer_sample (dest_buffer, xr, yr, NULL, pixel, format, + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); + + if (svals.colortype == NATURAL) + { + color[0] = pixel[0]; + color[1] = pixel[1]; + color[2] = pixel[2]; + } + else + { + color[0] = chosen_color[0]; + color[1] = chosen_color[1]; + color[2] = chosen_color[2]; + } + + color[3] = pixel[3]; + + if (svals.inverse) + { + color[0] = 255 - color[0]; + color[1] = 255 - color[1]; + color[2] = 255 - color[2]; + } + + if (svals.random_hue > 0.0 || svals.random_saturation > 0.0) + { + r = 255 - color[0]; + g = 255 - color[1]; + b = 255 - color[2]; + + gimp_rgb_to_hsv_int (&r, &g, &b); + + r += svals.random_hue * g_rand_double_range (gr, -0.5, 0.5) * 255; + + if (r >= 255) + r -= 255; + else if (r < 0) + r += 255; + + b += (svals.random_saturation * + g_rand_double_range (gr, -1.0, 1.0)) * 255; + + if (b > 255) + b = 255; + + gimp_hsv_to_rgb_int (&r, &g, &b); + + color[0] = 255 - r; + color[1] = 255 - g; + color[2] = 255 - b; + } + + dx = 0.2 * cos (theta * G_PI / 180.0); + dy = 0.2 * sin (theta * G_PI / 180.0); + xrt = (gdouble) xr; /* (gdouble) is needed because some */ + yrt = (gdouble) yr; /* compilers optimize too much otherwise */ + rpos = 0.2; + + do + { + sfac = inten * exp (-pow (rpos / length, efac)); + ok = FALSE; + + in = 0.2 * sfac; + if (in > 0.01) + ok = TRUE; + + rpnt (dest_buffer, format, x1, y1, x2, y2, + xrt, yrt, + bytes, in, color, dest_buf); + + rpnt (dest_buffer, format, x1, y1, x2, y2, + xrt + 1.0, yrt, + bytes, in, color, dest_buf); + + rpnt (dest_buffer, format, x1, y1, x2, y2, + xrt + 1.0, yrt + 1.0, + bytes, in, color, dest_buf); + + rpnt (dest_buffer, format, x1, y1, x2, y2, + xrt, yrt + 1.0, + bytes, in, color, dest_buf); + + xrt += dx; + yrt += dy; + rpos += 0.2; + + } while (ok); + + theta += 360.0 / svals.spike_pts; + } +} diff --git a/plug-ins/common/sphere-designer.c b/plug-ins/common/sphere-designer.c new file mode 100644 index 0000000..624e6ff --- /dev/null +++ b/plug-ins/common/sphere-designer.c @@ -0,0 +1,3166 @@ +/* + * GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * SphereDesigner v0.4 - creates textured spheres + * by Vidar Madsen + * + * Status: Last updated 1999-09-11 + * + * Known issues: + * - Might crash if you click OK or Cancel before first preview is rendered + * - Phong might look weird with transparent textures + * + * Todo: + * - Saving / Loading of presets needs an overhaul + * - Antialiasing + * - Global controls: Gamma, ++ + * - Beautification of GUI + * - Clean up messy source (lots of Glade remnants) + * - (Probably more. ;-) + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-spheredesigner" +#define PLUG_IN_BINARY "sphere-designer" +#define PLUG_IN_ROLE "gimp-sphere-designer" + +#define RESPONSE_RESET 1 + +#define PREVIEWSIZE 150 + +/* These must be adjusted as more functionality is added */ +#define MAXOBJECT 5 +#define MAXLIGHT 5 +#define MAXTEXTURE 20 +#define MAXTEXTUREPEROBJ 20 +#define MAXNORMAL 20 +#define MAXNORMALPEROBJ 20 +#define MAXATMOS 1 +#define MAXCOLPERGRADIENT 5 + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +enum +{ + TRIANGLE, + DISC, + PLANE, + SPHERE, + CYLINDER, + LIGHT +}; + +enum +{ + SOLID, + CHECKER, + MARBLE, + LIZARD, + IMAGE, + PHONG, + REFLECTION, + REFRACTION, + PERLIN, + WOOD, + TRANSPARENT, + SPIRAL, + SPOTS, + SMOKE +}; + +enum +{ + PERSPECTIVE, + ORTHOGONAL, + FISHEYE +}; + +enum +{ + FOG +}; + +enum +{ + TYPE, + TEXTURE, + NUM_COLUMNS +}; + + +/* World-flags */ +#define SMARTAMBIENT 0x00000001 + +/* Object-flags */ +#define NOSHADOW 0x00000001 + +/* Texture-flags */ +#define GRADIENT 0x00000001 + +typedef struct +{ + gshort xsize, ysize; + guchar *rgb; +} image; + +typedef struct +{ + gshort numcol; + gdouble pos[MAXCOLPERGRADIENT]; + GimpVector4 color[MAXCOLPERGRADIENT]; +} gradient; + +typedef struct +{ + gint majtype; + gint type; + gulong flags; + GimpVector4 color1, color2; + gradient gradient; + GimpVector4 ambient, diffuse; + gdouble oscale; + GimpVector4 scale, translate, rotate; + image image; + GimpVector4 reflection; + GimpVector4 refraction; + GimpVector4 transparent; + gdouble ior; + GimpVector4 phongcolor; + gdouble phongsize; + gdouble amount; + gdouble exp; + GimpVector4 turbulence; +} texture; + +typedef struct +{ + gshort type; + gdouble density; + GimpVector4 color; + gdouble turbulence; +} atmos; + +typedef struct +{ + gshort type; + gulong flags; + gshort numtexture; + texture texture[MAXTEXTUREPEROBJ]; + gshort numnormal; + texture normal[MAXNORMALPEROBJ]; +} common; + +typedef struct +{ + common com; + GimpVector4 a, b, c; +} triangle; + +typedef struct +{ + common com; + GimpVector4 a; + gdouble b, r; +} disc; + +typedef struct +{ + common com; + GimpVector4 a; + gdouble r; +} sphere; + +typedef struct +{ + common com; + GimpVector4 a, b, c; +} cylinder; + +typedef struct +{ + common com; + GimpVector4 a; + gdouble b; +} plane; + +typedef struct +{ + common com; + GimpVector4 color; + GimpVector4 a; +} light; + +typedef struct +{ + GimpVector4 v1, v2; + gshort inside; + gdouble ior; +} ray; + +typedef union +{ + common com; + triangle tri; + disc disc; + plane plane; + sphere sphere; + cylinder cylinder; +} object; + + +struct world_t +{ + gint numobj; + object obj[MAXOBJECT]; + gint numlight; + light light[MAXLIGHT]; + gint numtexture; + texture texture[MAXTEXTURE]; + gulong flags; + gshort quality; + gdouble smartambient; + gshort numatmos; + atmos atmos[MAXATMOS]; +}; + +struct camera_t +{ + GimpVector4 location, lookat, up, right; + short type; + double fov, tilt; +}; + +static GtkWidget *drawarea = NULL; + +static guchar *img; +static gint img_stride; +static cairo_surface_t *buffer; + +static guint idle_id = 0; + +static sphere s; + +struct textures_t +{ + gint index; + gchar *s; + glong n; +}; + +static struct textures_t textures[] = +{ + { 0, N_("Solid"), SOLID }, + { 1, N_("Checker"), CHECKER }, + { 2, N_("Marble"), MARBLE }, + { 3, N_("Lizard"), LIZARD }, + { 4, N_("Phong"), PHONG }, + { 5, N_("Noise"), PERLIN }, + { 6, N_("Wood"), WOOD }, + { 7, N_("Spiral"), SPIRAL }, + { 8, N_("Spots"), SPOTS }, + { 0, NULL, 0 } +}; + +static inline void vset (GimpVector4 *v, + gdouble a, + gdouble b, + gdouble c); +static void restartrender (void); +static void drawcolor1 (GtkWidget *widget); +static void drawcolor2 (GtkWidget *widget); +static gboolean render (void); +static void realrender (gint32 drawable_ID); +static void fileselect (GtkFileChooserAction action, + GtkWidget *parent); +static gint traceray (ray *r, + GimpVector4 *col, + gint level, + gdouble imp); +static gdouble turbulence (gdouble *point, + gdouble lofreq, + gdouble hifreq); + + +#define COLORBUTTONWIDTH 30 +#define COLORBUTTONHEIGHT 20 + +static GtkTreeView *texturelist = NULL; + +static GtkObject *scalexscale, *scaleyscale, *scalezscale; +static GtkObject *rotxscale, *rotyscale, *rotzscale; +static GtkObject *posxscale, *posyscale, *poszscale; +static GtkObject *scalescale; +static GtkObject *turbulencescale; +static GtkObject *amountscale; +static GtkObject *expscale; +static GtkWidget *typemenu; +static GtkWidget *texturemenu; + +#define DOT(a,b) (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]) + +#define B 256 + +static gint p[B + B + 2]; +static gdouble g[B + B + 2][3]; +static gboolean start = TRUE; +static GRand *gr; + + +static void +init (void) +{ + gint i, j, k; + gdouble v[3], s; + + /* Create an array of random gradient vectors uniformly on the unit sphere */ + + gr = g_rand_new (); + g_rand_set_seed (gr, 1); /* Use static seed, to get reproducible results */ + + for (i = 0; i < B; i++) + { + do + { /* Choose uniformly in a cube */ + for (j = 0; j < 3; j++) + v[j] = g_rand_double_range (gr, -1, 1); + s = DOT (v, v); + } + while (s > 1.0); /* If not in sphere try again */ + s = sqrt (s); + for (j = 0; j < 3; j++) /* Else normalize */ + g[i][j] = v[j] / s; + } + +/* Create a pseudorandom permutation of [1..B] */ + + for (i = 0; i < B; i++) + p[i] = i; + for (i = B; i > 0; i -= 2) + { + k = p[i]; + p[i] = p[j = g_rand_int_range (gr, 0, B)]; + p[j] = k; + } + + /* Extend g and p arrays to allow for faster indexing */ + + for (i = 0; i < B + 2; i++) + { + p[B + i] = p[i]; + for (j = 0; j < 3; j++) + g[B + i][j] = g[i][j]; + } + g_rand_free (gr); +} + +#define setup(i,b0,b1,r0,r1) \ + t = vec[i] + 10000.; \ + b0 = ((int)t) & (B-1); \ + b1 = (b0+1) & (B-1); \ + r0 = t - (int)t; \ + r1 = r0 - 1.; + + +static gdouble +noise3 (gdouble * vec) +{ + gint bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11; + gdouble rx0, rx1, ry0, ry1, rz0, rz1, *q, sx, sy, sz, a, b, c, d, t, u, v; + gint i, j; + + if (start) + { + start = FALSE; + init (); + } + + setup (0, bx0, bx1, rx0, rx1); + setup (1, by0, by1, ry0, ry1); + setup (2, bz0, bz1, rz0, rz1); + + i = p[bx0]; + j = p[bx1]; + + b00 = p[i + by0]; + b10 = p[j + by0]; + b01 = p[i + by1]; + b11 = p[j + by1]; + +#define at(rx,ry,rz) ( rx * q[0] + ry * q[1] + rz * q[2] ) + +#define surve(t) ( t * t * (3. - 2. * t) ) + +#define lerp(t, a, b) ( a + t * (b - a) ) + + sx = surve (rx0); + sy = surve (ry0); + sz = surve (rz0); + + + q = g[b00 + bz0]; + u = at (rx0, ry0, rz0); + q = g[b10 + bz0]; + v = at (rx1, ry0, rz0); + a = lerp (sx, u, v); + + q = g[b01 + bz0]; + u = at (rx0, ry1, rz0); + q = g[b11 + bz0]; + v = at (rx1, ry1, rz0); + b = lerp (sx, u, v); + + c = lerp (sy, a, b); /* interpolate in y at lo x */ + + q = g[b00 + bz1]; + u = at (rx0, ry0, rz1); + q = g[b10 + bz1]; + v = at (rx1, ry0, rz1); + a = lerp (sx, u, v); + + q = g[b01 + bz1]; + u = at (rx0, ry1, rz1); + q = g[b11 + bz1]; + v = at (rx1, ry1, rz1); + b = lerp (sx, u, v); + + d = lerp (sy, a, b); /* interpolate in y at hi x */ + + return 1.5 * lerp (sz, c, d); /* interpolate in z */ +} + +static double +turbulence (gdouble * point, gdouble lofreq, gdouble hifreq) +{ + gdouble freq, t, p[3]; + + p[0] = point[0] + 123.456; + p[1] = point[1] + 234.567; + p[2] = point[2] + 345.678; + + t = 0; + for (freq = lofreq; freq < hifreq; freq *= 2.) + { + t += noise3 (p) / freq; + p[0] *= 2.; + p[1] *= 2.; + p[2] *= 2.; + } + return t - 0.3; /* readjust to make mean value = 0.0 */ +} + +static struct world_t world; + +static inline void +vcopy (GimpVector4 *a, GimpVector4 *b) +{ + *a = *b; +} + +static inline void +vcross (GimpVector4 *r, GimpVector4 *a, GimpVector4 *b) +{ + r->x = a->y * b->z - a->z * b->y; + r->y = -(a->x * b->z - a->z * b->x); + r->z = a->x * b->y - a->y * b->x; +} + +static inline gdouble +vdot (GimpVector4 *a, GimpVector4 *b) +{ + return a->x * b->x + a->y * b->y + a->z * b->z; +} + +static inline gdouble +vdist (GimpVector4 *a, GimpVector4 *b) +{ + gdouble x, y, z; + + x = a->x - b->x; + y = a->y - b->y; + z = a->z - b->z; + + return sqrt (x * x + y * y + z * z); +} + +static inline gdouble +vdist2 (GimpVector4 *a, GimpVector4 *b) +{ + gdouble x, y, z; + + x = a->x - b->x; + y = a->y - b->y; + z = a->z - b->z; + + return x * x + y * y + z * z; +} + +static inline gdouble +vlen (GimpVector4 *a) +{ + return sqrt (a->x * a->x + a->y * a->y + a->z * a->z); +} + +static inline void +vnorm (GimpVector4 *a, gdouble v) +{ + gdouble d; + + d = vlen (a); + a->x *= v / d; + a->y *= v / d; + a->z *= v / d; +} + +static inline void +vrotate (GimpVector4 *axis, gdouble ang, GimpVector4 *vector) +{ + gdouble rad = ang / 180.0 * G_PI; + gdouble ax = vector->x; + gdouble ay = vector->y; + gdouble az = vector->z; + gdouble x = axis->x; + gdouble y = axis->y; + gdouble z = axis->z; + gdouble c = cos (rad); + gdouble s = sin (rad); + gdouble c1 = 1.0 - c; + gdouble xx = c1 * x * x; + gdouble yy = c1 * y * y; + gdouble zz = c1 * z * z; + gdouble xy = c1 * x * y; + gdouble xz = c1 * x * z; + gdouble yz = c1 * y * z; + gdouble sx = s * x; + gdouble sy = s * y; + gdouble sz = s * z; + + vector->x = (xx + c) * ax + (xy + sz) * ay + (xz - sy) * az; + vector->y = (xy - sz) * ax + (yy + c) * ay + (yz + sx) * az; + vector->z = (xz + sy) * ax + (yz - sx) * ay + (zz + c) * az; +} + +static inline void +vset (GimpVector4 *v, gdouble a, gdouble b, gdouble c) +{ + v->x = a; + v->y = b; + v->z = c; + v->w = 1.0; +} + +static inline void +vcset (GimpVector4 *v, gdouble a, gdouble b, gdouble c, gdouble d) +{ + v->x = a; + v->y = b; + v->z = c; + v->w = d; +} + +static inline void +vvrotate (GimpVector4 *p, GimpVector4 *rot) +{ + GimpVector4 axis; + + if (rot->x != 0.0) + { + vset (&axis, 1, 0, 0); + vrotate (&axis, rot->x, p); + } + if (rot->y != 0.0) + { + vset (&axis, 0, 1, 0); + vrotate (&axis, rot->y, p); + } + if (rot->z != 0.0) + { + vset (&axis, 0, 0, 1); + vrotate (&axis, rot->z, p); + } +} + +static inline void +vsub (GimpVector4 *a, GimpVector4 *b) +{ + a->x -= b->x; + a->y -= b->y; + a->z -= b->z; + a->w -= b->w; +} + +static inline void +vadd (GimpVector4 *a, GimpVector4 *b) +{ + a->x += b->x; + a->y += b->y; + a->z += b->z; + a->w += b->w; +} + +static inline void +vneg (GimpVector4 *a) +{ + a->x = -a->x; + a->y = -a->y; + a->z = -a->z; + a->w = -a->w; +} + +static inline void +vmul (GimpVector4 *v, gdouble a) +{ + v->x *= a; + v->y *= a; + v->z *= a; + v->w *= a; +} + +static inline void +vvmul (GimpVector4 *a, GimpVector4 *b) +{ + a->x *= b->x; + a->y *= b->y; + a->z *= b->z; + a->w *= b->w; +} + +static inline void +vvdiv (GimpVector4 *a, GimpVector4 *b) +{ + a->x /= b->x; + a->y /= b->y; + a->z /= b->z; +} + +static void +vmix (GimpVector4 *r, GimpVector4 *a, GimpVector4 *b, gdouble v) +{ + gdouble i = 1.0 - v; + + r->x = a->x * v + b->x * i; + r->y = a->y * v + b->y * i; + r->z = a->z * v + b->z * i; + r->w = a->w * v + b->w * i; +} + +static double +vmax (GimpVector4 *a) +{ + gdouble max = fabs (a->x); + + if (fabs (a->y) > max) + max = fabs (a->y); + if (fabs (a->z) > max) + max = fabs (a->z); + if (fabs (a->w) > max) + max = fabs (a->w); + + return max; +} + +#if 0 +static void +vavg (GimpVector4 * a) +{ + gdouble s; + + s = (a->x + a->y + a->z) / 3.0; + a->x = a->y = a->z = s; +} +#endif + +static void +trianglenormal (GimpVector4 * n, gdouble *t, triangle * tri) +{ + triangle tmp; + vcopy (&tmp.b, &tri->b); + vcopy (&tmp.c, &tri->c); + vsub (&tmp.b, &tri->a); + vsub (&tmp.c, &tri->a); + vset (&tmp.a, 0, 0, 0); + vcross (n, &tmp.b, &tmp.c); + if (t) + *t = vdot (&tmp.b, &tmp.c); +} + +static gdouble +checkdisc (ray * r, disc * disc) +{ + GimpVector4 p, *v = &disc->a; + gdouble t, d2; + gdouble i, j, k; + + i = r->v2.x - r->v1.x; + j = r->v2.y - r->v1.y; + k = r->v2.z - r->v1.z; + + t = -(v->x * r->v1.x + v->y * r->v1.y + v->z * r->v1.z - disc->b) / + (v->x * i + v->y * j + v->z * k); + + p.x = r->v1.x + i * t; + p.y = r->v1.y + j * t; + p.z = r->v1.z + k * t; + + d2 = vdist2 (&p, v); + + if (d2 > disc->r * disc->r) + t = 0.0; + + return t; +} + +static gdouble +checksphere (ray * r, sphere * sphere) +{ + GimpVector4 cendir, rdir; + gdouble dirproj, cdlensq; + gdouble linear, constant, rsq, quadratic, discriminant; + gdouble smallzero, solmin, solmax, tolerance = 0.001; + + vcopy (&rdir, &r->v2); + vsub (&rdir, &r->v1); + + rsq = sphere->r * sphere->r; + + vcopy (&cendir, &r->v1); + vsub (&cendir, &sphere->a); + dirproj = vdot (&rdir, &cendir); + cdlensq = vdot (&cendir, &cendir); + + if ((cdlensq >= rsq) && (dirproj > 0.0)) + return 0.0; + + linear = 2.0 * dirproj; + constant = cdlensq - rsq; + quadratic = vdot (&rdir, &rdir); + + smallzero = (constant / linear); + if ((smallzero < tolerance) && (smallzero > -tolerance)) + { + solmin = -linear / quadratic; + + if (solmin > tolerance) + { + return solmin; + /* + *hits = solmin; + return 1; + */ + } + else + return 0.0; + } + discriminant = linear * linear - 4.0 * quadratic * constant; + if (discriminant < 0.0) + return 0.0; + quadratic *= 2.0; + discriminant = sqrt (discriminant); + solmax = (-linear + discriminant) / (quadratic); + solmin = (-linear - discriminant) / (quadratic); + + if (solmax < tolerance) + return 0.0; + + if (solmin < tolerance) + { + return solmax; + /* + * hits = solmax; + * return 1; + */ + } + else + { + return solmin; + /* + * hits++ = solmin; + * hits = solmax; + * return 2; + */ + } +} + +static gdouble +checkcylinder (ray * r, cylinder * cylinder) +{ + /* FIXME */ + return 0.0; +} + + +static gdouble +checkplane (ray * r, plane * plane) +{ + GimpVector4 *v = &plane->a; + gdouble t; + gdouble i, j, k; + + i = r->v2.x - r->v1.x; + j = r->v2.y - r->v1.y; + k = r->v2.z - r->v1.z; + + t = -(v->x * r->v1.x + v->y * r->v1.y + v->z * r->v1.z - plane->b) / + (v->x * i + v->y * j + v->z * k); + + return t; +} + +static gdouble +checktri (ray * r, triangle * tri) +{ + GimpVector4 ed1, ed2; + GimpVector4 tvec, pvec, qvec; + gdouble det, idet, t, u, v; + GimpVector4 *orig, dir; + + orig = &r->v1; + dir = r->v2; + vsub (&dir, orig); + + ed1.x = tri->c.x - tri->a.x; + ed1.y = tri->c.y - tri->a.y; + ed1.z = tri->c.z - tri->a.z; + ed2.x = tri->b.x - tri->a.x; + ed2.y = tri->b.y - tri->a.y; + ed2.z = tri->b.z - tri->a.z; + vcross (&pvec, &dir, &ed2); + det = vdot (&ed1, &pvec); + + idet = 1.0 / det; + + tvec.x = orig->x; + tvec.y = orig->y; + tvec.z = orig->z; + vsub (&tvec, &tri->a); + u = vdot (&tvec, &pvec) * idet; + + if (u < 0.0) + return 0; + if (u > 1.0) + return 0; + + vcross (&qvec, &tvec, &ed1); + v = vdot (&dir, &qvec) * idet; + + if ((v < 0.0) || (u + v > 1.0)) + return 0; + + t = vdot (&ed2, &qvec) * idet; + + return t; +} + +static void +transformpoint (GimpVector4 * p, texture * t) +{ + gdouble point[3], f; + + if ((t->rotate.x != 0.0) || (t->rotate.y != 0.0) || (t->rotate.z != 0.0)) + vvrotate (p, &t->rotate); + vvdiv (p, &t->scale); + + vsub (p, &t->translate); + + if ((t->turbulence.x != 0.0) || (t->turbulence.y != 0.0) || + (t->turbulence.z != 0.0)) + { + point[0] = p->x; + point[1] = p->y; + point[2] = p->z; + f = turbulence (point, 1, 256); + p->x += t->turbulence.x * f; + p->y += t->turbulence.y * f; + p->z += t->turbulence.z * f; + } +} + +static void +checker (GimpVector4 *q, GimpVector4 *col, texture *t) +{ + gint c = 0; + GimpVector4 p; + + p = *q; + transformpoint (&p, t); + + vmul (&p, 0.25); + + p.x += 0.00001; + p.y += 0.00001; + p.z += 0.00001; + + if (p.x < 0.0) + p.x = 0.5 - p.x; + if (p.y < 0.0) + p.y = 0.5 - p.y; + if (p.z < 0.0) + p.z = 0.5 - p.z; + + if ((p.x - (gint) p.x) < 0.5) + c ^= 1; + if ((p.y - (gint) p.y) < 0.5) + c ^= 1; + if ((p.z - (gint) p.z) < 0.5) + c ^= 1; + + *col = (c) ? t->color1 : t->color2; +} + +static void +gradcolor (GimpVector4 *col, gradient *t, gdouble val) +{ + gint i; + gdouble d; + GimpVector4 tmpcol; + + val = CLAMP (val, 0.0, 1.0); + + for (i = 0; i < t->numcol; i++) + { + if (t->pos[i] == val) + { + *col = t->color[i]; + return; + } + if (t->pos[i] > val) + { + d = (val - t->pos[i - 1]) / (t->pos[i] - t->pos[i - 1]); + vcopy (&tmpcol, &t->color[i]); + vmul (&tmpcol, d); + vcopy (col, &tmpcol); + vcopy (&tmpcol, &t->color[i - 1]); + vmul (&tmpcol, 1.0 - d); + vadd (col, &tmpcol); + return; + } + } + g_printerr ("Error in gradient!\n"); + vset (col, 0, 1, 0); +} + +static void +marble (GimpVector4 *q, GimpVector4 *col, texture *t) +{ + gdouble f; + GimpVector4 p; + + p = *q; + transformpoint (&p, t); + + f = sin (p.x * 4) / 2 + 0.5; + f = pow (f, t->exp); + + if (t->flags & GRADIENT) + gradcolor (col, &t->gradient, f); + else + vmix (col, &t->color1, &t->color2, f); +} + +static void +lizard (GimpVector4 *q, GimpVector4 *col, texture *t) +{ + gdouble f; + GimpVector4 p; + + p = *q; + transformpoint (&p, t); + + f = fabs (sin (p.x * 4)); + f += fabs (sin (p.y * 4)); + f += fabs (sin (p.z * 4)); + f /= 3.0; + f = pow (f, t->exp); + + if (t->flags & GRADIENT) + gradcolor (col, &t->gradient, f); + else + vmix (col, &t->color1, &t->color2, f); +} + +static void +wood (GimpVector4 *q, GimpVector4 *col, texture *t) +{ + gdouble f; + GimpVector4 p; + + p = *q; + transformpoint (&p, t); + + f = fabs (p.x); + f = f - (int) f; + + f = pow (f, t->exp); + + if (t->flags & GRADIENT) + gradcolor (col, &t->gradient, f); + else + vmix (col, &t->color1, &t->color2, f); +} + +static void +spiral (GimpVector4 *q, GimpVector4 *col, texture *t) +{ + gdouble f; + GimpVector4 p; + + p = *q; + transformpoint (&p, t); + + f = fabs (atan2 (p.x, p.z) / G_PI / 2 + p.y + 99999); + f = f - (int) f; + + f = pow (f, t->exp); + + if (t->flags & GRADIENT) + gradcolor (col, &t->gradient, f); + else + vmix (col, &t->color1, &t->color2, f); +} + +static void +spots (GimpVector4 *q, GimpVector4 *col, texture *t) +{ + gdouble f; + GimpVector4 p, r; + + p = *q; + transformpoint (&p, t); + + p.x += 10000.0; + p.y += 10000.0; + p.z += 10000.0; + + vset (&r, (gint) (p.x + 0.5), (gint) (p.y + 0.5), (gint) (p.z + 0.5)); + f = vdist (&p, &r); + f = cos (f * G_PI); + f = CLAMP (f, 0.0, 1.0); + f = pow (f, t->exp); + + if (t->flags & GRADIENT) + gradcolor (col, &t->gradient, f); + else + vmix (col, &t->color1, &t->color2, f); +} + +static void +perlin (GimpVector4 * q, GimpVector4 * col, texture * t) +{ + gdouble f, point[3]; + GimpVector4 p; + + p = *q; + transformpoint (&p, t); + + point[0] = p.x; + point[1] = p.y; + point[2] = p.z; + + f = turbulence (point, 1, 256) * 0.3 + 0.5; + + f = pow (f, t->exp); + + if (t->flags & GRADIENT) + gradcolor (col, &t->gradient, f); + else + vmix (col, &t->color1, &t->color2, f); +} + +static void +imagepixel (GimpVector4 * q, GimpVector4 * col, texture * t) +{ + GimpVector4 p; + gint x, y; + guchar *rgb; + + p = *q; + transformpoint (&p, t); + + x = (p.x * t->image.xsize); + y = (p.y * t->image.ysize); + + x = (x % t->image.xsize + t->image.xsize) % t->image.xsize; + y = (y % t->image.ysize + t->image.ysize) % t->image.ysize; + + rgb = &t->image.rgb[x * 3 + (t->image.ysize - 1 - y) * t->image.xsize * 3]; + vset (col, rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0); +} + +static void +objcolor (GimpVector4 *col, GimpVector4 *p, common *obj) +{ + gint i; + texture *t; + GimpVector4 tmpcol; + + vcset (col, 0, 0, 0, 0); + + for (i = 0; i < obj->numtexture; i++) + { + t = &obj->texture[i]; + + if (world.quality < 1) + { + vadd (col, &t->color1); + continue; + } + + vset (&tmpcol, 0, 0, 0); + switch (t->type) + { + case SOLID: + vcopy (&tmpcol, &t->color1); + break; + case CHECKER: + checker (p, &tmpcol, t); + break; + case MARBLE: + marble (p, &tmpcol, t); + break; + case LIZARD: + lizard (p, &tmpcol, t); + break; + case PERLIN: + perlin (p, &tmpcol, t); + break; + case WOOD: + wood (p, &tmpcol, t); + break; + case SPIRAL: + spiral (p, &tmpcol, t); + break; + case SPOTS: + spots (p, &tmpcol, t); + break; + case IMAGE: + imagepixel (p, &tmpcol, t); + break; + case PHONG: + case REFRACTION: + case REFLECTION: + case TRANSPARENT: + case SMOKE: + /* Silently ignore non-color textures */ + continue; + break; + default: + g_printerr ("Warning: unknown texture %d\n", t->type); + break; + } + vmul (&tmpcol, t->amount); + vadd (col, &tmpcol); + } + if (!i) + { + g_printerr ("Warning: object %p has no textures\n", obj); + } +} + +static void +objnormal (GimpVector4 *res, common *obj, GimpVector4 *p) +{ + gint i; + + switch (obj->type) + { + case TRIANGLE: + trianglenormal (res, NULL, (triangle *) obj); + break; + case DISC: + vcopy (res, &((disc *) obj)->a); + break; + case PLANE: + vcopy (res, &((plane *) obj)->a); + break; + case SPHERE: + vcopy (res, &((sphere *) obj)->a); + vsub (res, p); + break; + case CYLINDER: + vset (res, 1, 1, 1); /* fixme */ + break; + default: + g_error ("objnormal(): Unsupported object type!?\n"); + } + vnorm (res, 1.0); + + for (i = 0; i < obj->numnormal; i++) + { + gint k; + GimpVector4 tmpcol[6]; + GimpVector4 q[6], nres; + texture *t = &obj->normal[i]; + gdouble nstep = 0.1; + + vset (&nres, 0, 0, 0); + for (k = 0; k < 6; k++) + { + vcopy (&q[k], p); + } + q[0].x += nstep; + q[1].x -= nstep; + q[2].y += nstep; + q[3].y -= nstep; + q[4].z += nstep; + q[5].z -= nstep; + + switch (t->type) + { + case MARBLE: + for (k = 0; k < 6; k++) + marble (&q[k], &tmpcol[k], t); + break; + case LIZARD: + for (k = 0; k < 6; k++) + lizard (&q[k], &tmpcol[k], t); + break; + case PERLIN: + for (k = 0; k < 6; k++) + perlin (&q[k], &tmpcol[k], t); + break; + case WOOD: + for (k = 0; k < 6; k++) + wood (&q[k], &tmpcol[k], t); + break; + case SPIRAL: + for (k = 0; k < 6; k++) + spiral (&q[k], &tmpcol[k], t); + break; + case SPOTS: + for (k = 0; k < 6; k++) + spots (&q[k], &tmpcol[k], t); + break; + case IMAGE: + for (k = 0; k < 6; k++) + imagepixel (&q[k], &tmpcol[k], t); + break; + case CHECKER: + case SOLID: + case PHONG: + case REFRACTION: + case REFLECTION: + case TRANSPARENT: + case SMOKE: + continue; + break; + default: + g_printerr ("Warning: unknown texture %d\n", t->type); + break; + } + + nres.x = tmpcol[0].x - tmpcol[1].x; + nres.y = tmpcol[2].x - tmpcol[3].x; + nres.z = tmpcol[4].x - tmpcol[5].x; + vadd (&nres, res); + vnorm (&nres, 1.0); + vmul (&nres, t->amount); + vadd (res, &nres); + vnorm (res, 1.0); + } +} + +/* + Quality: + 0 = Color only + 1 = Textures + 2 = Light + Normals + 3 = Shadows + 4 = Phong + 5 = Reflection + Refraction + */ + +static void +calclight (GimpVector4 * col, GimpVector4 * point, common * obj) +{ + gint i, j; + ray r; + gdouble b, a; + GimpVector4 lcol; + GimpVector4 norm; + GimpVector4 pcol; + + vcset (col, 0, 0, 0, 0); + + objcolor (&pcol, point, obj); + a = pcol.w; + + if (world.quality < 2) + { + vcopy (col, &pcol); + return; + } + + for (i = 0; i < obj->numtexture; i++) + { + if (obj->texture[i].type == PHONG) + continue; + if (obj->texture[i].type == REFLECTION) + continue; + if (obj->texture[i].type == REFRACTION) + continue; + if (obj->texture[i].type == TRANSPARENT) + continue; + if (obj->texture[i].type == SMOKE) + continue; + vcopy (&lcol, &pcol); + vvmul (&lcol, &obj->texture[i].ambient); + vadd (col, &lcol); + } + + objnormal (&norm, obj, point); + vnorm (&norm, 1.0); + + r.inside = -1; + r.ior = 1.0; + + for (i = 0; i < world.numlight; i++) + { + vcopy (&r.v1, point); + vcopy (&r.v2, &world.light[i].a); + vmix (&r.v1, &r.v1, &r.v2, 0.9999); + + vsub (&r.v1, &r.v2); + vnorm (&r.v1, 1.0); + b = vdot (&r.v1, &norm); + + if (b < 0.0) + continue; + + for (j = 0; j < obj->numtexture; j++) + { + if (obj->texture[j].type == PHONG) + continue; + if (obj->texture[j].type == REFLECTION) + continue; + if (obj->texture[j].type == REFRACTION) + continue; + if (obj->texture[j].type == TRANSPARENT) + continue; + if (obj->texture[j].type == SMOKE) + continue; + vcopy (&lcol, &pcol); + vvmul (&lcol, &world.light[i].color); + vvmul (&lcol, &obj->texture[j].diffuse); + vmul (&lcol, b); + vadd (col, &lcol); + } + } + col->w = a; +} + +static void +calcphong (common * obj, ray * r2, GimpVector4 * col) +{ + gint i, j; + ray r; + gdouble b; + GimpVector4 lcol; + GimpVector4 norm; + GimpVector4 pcol; + gdouble ps; + + vcopy (&pcol, col); + + vcopy (&norm, &r2->v2); + vsub (&norm, &r2->v1); + vnorm (&norm, 1.0); + + r.inside = -1; + r.ior = 1.0; + + for (i = 0; i < world.numlight; i++) + { + vcopy (&r.v1, &r2->v1); + vcopy (&r.v2, &world.light[i].a); + vmix (&r.v1, &r.v1, &r.v2, 0.9999); + + if (traceray (&r, NULL, -1, 1.0)) + continue; + + /* OK, light is visible */ + + vsub (&r.v1, &r.v2); + vnorm (&r.v1, 1.0); + b = -vdot (&r.v1, &norm); + + for (j = 0; j < obj->numtexture; j++) + { + if (obj->texture[j].type != PHONG) + continue; + + ps = obj->texture[j].phongsize; + + if (b < (1 - ps)) + continue; + ps = (b - (1 - ps)) / ps; + + vcopy (&lcol, &obj->texture[j].phongcolor); + vvmul (&lcol, &world.light[i].color); + vmul (&lcol, ps); + vadd (col, &lcol); + } + } +} + +static int +traceray (ray * r, GimpVector4 * col, gint level, gdouble imp) +{ + gint i, b = -1; + gdouble t = -1.0, min = 0.0; + common *obj, *bobj = NULL; + gint hits = 0; + GimpVector4 p; + + if ((level == 0) || (imp < 0.005)) + { + vset (col, 0, 1, 0); + return 0; + } + + for (i = 0; i < world.numobj; i++) + { + obj = (common *) & world.obj[i]; + switch (obj->type) + { + case TRIANGLE: + t = checktri (r, (triangle *) & world.obj[i]); + break; + case DISC: + t = checkdisc (r, (disc *) & world.obj[i]); + break; + case PLANE: + t = checkplane (r, (plane *) & world.obj[i]); + break; + case SPHERE: + t = checksphere (r, (sphere *) & world.obj[i]); + break; + case CYLINDER: + t = checkcylinder (r, (cylinder *) & world.obj[i]); + break; + default: + g_error ("Illegal object!!\n"); + } + if (t <= 0.0) + continue; + + if (!(obj->flags & NOSHADOW) && (level == -1)) + { + return i + 1; + } + + hits++; + if ((!bobj) || (t < min)) + { + + min = t; + b = i; + bobj = obj; + } + } + if (level == -1) + return 0; + + if (bobj) + { + p.x = r->v1.x + (r->v2.x - r->v1.x) * min; + p.y = r->v1.y + (r->v2.y - r->v1.y) * min; + p.z = r->v1.z + (r->v2.z - r->v1.z) * min; + + calclight (col, &p, bobj); + + if (world.flags & SMARTAMBIENT) + { + gdouble ambient = 0.3 * exp (-min / world.smartambient); + GimpVector4 lcol; + objcolor (&lcol, &p, bobj); + vmul (&lcol, ambient); + vadd (col, &lcol); + } + + for (i = 0; i < bobj->numtexture; i++) + { + + if ((world.quality >= 4) + && ((bobj->texture[i].type == REFLECTION) + || (bobj->texture[i].type == PHONG))) + { + + GimpVector4 refcol, norm, ocol; + ray ref; + + objcolor (&ocol, &p, bobj); + + vcopy (&ref.v1, &p); + vcopy (&ref.v2, &r->v1); + ref.inside = r->inside; + ref.ior = r->ior; + + vmix (&ref.v1, &ref.v1, &ref.v2, 0.9999); /* push it a tad */ + + vsub (&ref.v2, &p); + objnormal (&norm, bobj, &p); + vnorm (&norm, 1.0); + vrotate (&norm, 180.0, &ref.v2); + + vmul (&norm, -0.0001); /* push it a tad */ + vadd (&ref.v1, &norm); + + vnorm (&ref.v2, 1.0); + vadd (&ref.v2, &p); + + if ((world.quality >= 5) + && (bobj->texture[i].type == REFLECTION)) + { + traceray (&ref, &refcol, level - 1, + imp * vmax (&bobj->texture[i].reflection)); + vvmul (&refcol, &bobj->texture[i].reflection); + refcol.w = ocol.w; + vadd (col, &refcol); + } + if (bobj->texture[i].type == PHONG) + { + vcset (&refcol, 0, 0, 0, 0); + calcphong (bobj, &ref, &refcol); + refcol.w = ocol.w; + vadd (col, &refcol); + } + + } + + if ((world.quality >= 5) && (col->w < 1.0)) + { + GimpVector4 refcol; + ray ref; + + vcopy (&ref.v1, &p); + vcopy (&ref.v2, &p); + vsub (&ref.v2, &r->v1); + vnorm (&ref.v2, 1.0); + vadd (&ref.v2, &p); + + vmix (&ref.v1, &ref.v1, &ref.v2, 0.999); /* push it a tad */ + traceray (&ref, &refcol, level - 1, imp * (1.0 - col->w)); + vmul (&refcol, (1.0 - col->w)); + vadd (col, &refcol); + } + + if ((world.quality >= 5) && (bobj->texture[i].type == TRANSPARENT)) + { + GimpVector4 refcol; + ray ref; + + vcopy (&ref.v1, &p); + vcopy (&ref.v2, &p); + vsub (&ref.v2, &r->v1); + vnorm (&ref.v2, 1.0); + vadd (&ref.v2, &p); + + vmix (&ref.v1, &ref.v1, &ref.v2, 0.999); /* push it a tad */ + + traceray (&ref, &refcol, level - 1, + imp * vmax (&bobj->texture[i].transparent)); + vvmul (&refcol, &bobj->texture[i].transparent); + + vadd (col, &refcol); + } + + if ((world.quality >= 5) && (bobj->texture[i].type == SMOKE)) + { + GimpVector4 smcol, raydir, norm; + double tran; + ray ref; + + vcopy (&ref.v1, &p); + vcopy (&ref.v2, &p); + vsub (&ref.v2, &r->v1); + vnorm (&ref.v2, 1.0); + vadd (&ref.v2, &p); + + objnormal (&norm, bobj, &p); + vcopy (&raydir, &r->v2); + vsub (&raydir, &r->v1); + vnorm (&raydir, 1.0); + tran = vdot (&norm, &raydir); + if (tran < 0.0) + continue; + tran *= tran; + vcopy (&smcol, &bobj->texture[i].color1); + vmul (&smcol, tran); + vadd (col, &smcol); + } + + if ((world.quality >= 5) && (bobj->texture[i].type == REFRACTION)) + { + GimpVector4 refcol, norm, tmpv; + ray ref; + double c1, c2, n1, n2, n; + + vcopy (&ref.v1, &p); + vcopy (&ref.v2, &p); + vsub (&ref.v2, &r->v1); + vadd (&ref.v2, &r->v2); + + vmix (&ref.v1, &ref.v1, &ref.v2, 0.999); /* push it a tad */ + + vsub (&ref.v2, &p); + objnormal (&norm, bobj, &p); + + if (r->inside == b) + { + ref.inside = -1; + ref.ior = 1.0; + } + else + { + ref.inside = b; + ref.ior = bobj->texture[i].ior; + } + + c1 = vdot (&norm, &ref.v2); + + if (ref.inside < 0) + c1 = -c1; + + n1 = r->ior; /* IOR of current media */ + n2 = ref.ior; /* IOR of new media */ + n = n1 / n2; + c2 = 1.0 - n * n * (1.0 - c1 * c1); + + if (c2 < 0.0) + { + /* FIXME: Internal reflection should occur */ + c2 = sqrt (-c2); + + } + else + { + c2 = sqrt (c2); + } + + vmul (&ref.v2, n); + vcopy (&tmpv, &norm); + vmul (&tmpv, n * c1 - c2); + vadd (&ref.v2, &tmpv); + + vnorm (&ref.v2, 1.0); + vadd (&ref.v2, &p); + + traceray (&ref, &refcol, level - 1, + imp * vmax (&bobj->texture[i].refraction)); + + vvmul (&refcol, &bobj->texture[i].refraction); + vadd (col, &refcol); + } + } + } + else + { + vcset (col, 0, 0, 0, 0); + min = 10000.0; + vcset (&p, 0, 0, 0, 0); + } + + for (i = 0; i < world.numatmos; i++) + { + GimpVector4 tmpcol; + if (world.atmos[i].type == FOG) + { + gdouble v, pt[3]; + pt[0] = p.x; + pt[1] = p.y; + pt[2] = p.z; + if ((v = world.atmos[i].turbulence) > 0.0) + v = turbulence (pt, 1, 256) * world.atmos[i].turbulence; + v = exp (-(min + v) / world.atmos[i].density); + vmul (col, v); + vcopy (&tmpcol, &world.atmos[i].color); + vmul (&tmpcol, 1.0 - v); + vadd (col, &tmpcol); + } + } + + return hits; +} + +static void +setdefaults (texture * t) +{ + memset (t, 0, sizeof (texture)); + t->type = SOLID; + vcset (&t->color1, 1, 1, 1, 1); + vcset (&t->color2, 0, 0, 0, 1); + vcset (&t->diffuse, 1, 1, 1, 1); + vcset (&t->ambient, 0, 0, 0, 1); + vset (&t->scale, 1, 1, 1); + vset (&t->rotate, 0, 0, 0); + vset (&t->translate, 0, 0, 0); + t->oscale = 1.0; + t->amount = 1.0; + t->exp = 1.0; +} + +static gchar * +mklabel (texture * t) +{ + struct textures_t *l; + static gchar tmps[100]; + + if (t->majtype == 0) + strcpy (tmps, _("Texture")); + else if (t->majtype == 1) + strcpy (tmps, _("Bumpmap")); + else if (t->majtype == 2) + strcpy (tmps, _("Light")); + else + strcpy (tmps, ""); + if ((t->majtype == 0) || (t->majtype == 1)) + { + strcat (tmps, " / "); + l = textures; + while (l->s) + { + if (t->type == l->n) + { + strcat (tmps, gettext (l->s)); + break; + } + l++; + } + } + return tmps; +} + +static texture * +currenttexture (void) +{ + GtkTreeSelection *sel; + GtkTreeIter iter; + texture *t = NULL; + + sel = gtk_tree_view_get_selection (texturelist); + + if (gtk_tree_selection_get_selected (sel, NULL, &iter)) + { + gtk_tree_model_get (gtk_tree_view_get_model (texturelist), &iter, + TEXTURE, &t, + -1); + } + + return t; +} + +static void +relabel (void) +{ + GtkTreeModel *model; + GtkTreeSelection *sel; + GtkTreeIter iter; + texture *t = NULL; + + sel = gtk_tree_view_get_selection (texturelist); + + if (gtk_tree_selection_get_selected (sel, NULL, &iter)) + { + model = gtk_tree_view_get_model (texturelist); + + gtk_tree_model_get (model, &iter, + TEXTURE, &t, + -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + TYPE, mklabel (t), + -1); + } +} + +static gboolean noupdate = FALSE; + +static void +setvals (texture *t) +{ + struct textures_t *l; + + if (!t) + return; + + noupdate = TRUE; + gtk_adjustment_set_value (GTK_ADJUSTMENT (amountscale), t->amount); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (scalescale), t->oscale); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (scalexscale), t->scale.x); + gtk_adjustment_set_value (GTK_ADJUSTMENT (scaleyscale), t->scale.y); + gtk_adjustment_set_value (GTK_ADJUSTMENT (scalezscale), t->scale.z); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (rotxscale), t->rotate.x); + gtk_adjustment_set_value (GTK_ADJUSTMENT (rotyscale), t->rotate.y); + gtk_adjustment_set_value (GTK_ADJUSTMENT (rotzscale), t->rotate.z); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (posxscale), t->translate.x); + gtk_adjustment_set_value (GTK_ADJUSTMENT (posyscale), t->translate.y); + gtk_adjustment_set_value (GTK_ADJUSTMENT (poszscale), t->translate.z); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (turbulencescale), + t->turbulence.x); + gtk_adjustment_set_value (GTK_ADJUSTMENT (expscale), t->exp); + + drawcolor1 (NULL); + drawcolor2 (NULL); + + l = textures; + while (l->s) + { + if (l->n == t->type) + { + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (texturemenu), + l->index); + break; + } + l++; + } + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (typemenu), t->majtype); + + noupdate = FALSE; +} + +static void +selectitem (GtkTreeSelection *treeselection, + gpointer data) +{ + setvals (currenttexture ()); +} + +static void +addtexture (void) +{ + GtkListStore *list_store; + GtkTreeIter iter; + gint n = s.com.numtexture; + + if (n == MAXTEXTUREPEROBJ - 1) + return; + + setdefaults (&s.com.texture[n]); + + list_store = GTK_LIST_STORE (gtk_tree_view_get_model (texturelist)); + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + TYPE, mklabel (&s.com.texture[n]), + TEXTURE, &s.com.texture[n], + -1); + gtk_tree_selection_select_iter (gtk_tree_view_get_selection (texturelist), + &iter); + + s.com.numtexture++; + + restartrender (); +} + +static void +duptexture (void) +{ + GtkListStore *list_store; + GtkTreeIter iter; + texture *t = currenttexture (); + gint n = s.com.numtexture; + + if (n == MAXTEXTUREPEROBJ - 1) + return; + if (!t) + return; + + s.com.texture[n] = *t; + + list_store = GTK_LIST_STORE (gtk_tree_view_get_model (texturelist)); + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + TYPE, mklabel (&s.com.texture[n]), + TEXTURE, &s.com.texture[n], + -1); + gtk_tree_selection_select_iter (gtk_tree_view_get_selection (texturelist), + &iter); + + s.com.numtexture++; + + restartrender (); +} + +static void +rebuildlist (void) +{ + GtkListStore *list_store; + GtkTreeSelection *sel; + GtkTreeIter iter; + gint n; + + sel = gtk_tree_view_get_selection (texturelist); + + for (n = 0; n < s.com.numtexture; n++) + { + if (s.com.numtexture && (s.com.texture[n].majtype < 0)) + { + gint i; + + for (i = n; i < s.com.numtexture - 1; i++) + s.com.texture[i] = s.com.texture[i + 1]; + + s.com.numtexture--; + n--; + } + } + + list_store = GTK_LIST_STORE (gtk_tree_view_get_model (texturelist)); + + for (n = 0; n < s.com.numtexture; n++) + { + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + TYPE, mklabel (&s.com.texture[n]), + TEXTURE, &s.com.texture[n], + -1); + } + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter)) + gtk_tree_selection_select_iter (sel, &iter); + + restartrender (); +} + +static void +deltexture (void) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + texture *t = NULL; + + sel = gtk_tree_view_get_selection (texturelist); + + if (gtk_tree_selection_get_selected (sel, NULL, &iter)) + { + model = gtk_tree_view_get_model (texturelist); + + gtk_tree_model_get (model, &iter, + TEXTURE, &t, + -1); + t->majtype = -1; + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + } + + restartrender (); +} + +static void +loadit (const gchar * fn) +{ + FILE *f; + gchar endbuf[21 * (G_ASCII_DTOSTR_BUF_SIZE + 1)]; + gchar *end = endbuf; + gchar line[1024]; + gchar fmt_str[16]; + gint i; + texture *t; + gint majtype, type; + + f = g_fopen (fn, "rt"); + if (! f) + { + g_message (_("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (fn), g_strerror (errno)); + return; + } + + if (2 != fscanf (f, "%d %d", &majtype, &type) || majtype < 0 || majtype > 2) + { + g_message (_("File '%s' is not a valid save file."), + gimp_filename_to_utf8 (fn)); + fclose (f); + return; + } + + rewind (f); + + s.com.numtexture = 0; + + snprintf (fmt_str, sizeof (fmt_str), "%%d %%d %%%" G_GSIZE_FORMAT "s", sizeof (endbuf) - 1); + + while (!feof (f)) + { + + if (!fgets (line, 1023, f)) + break; + + i = s.com.numtexture; + t = &s.com.texture[i]; + setdefaults (t); + + if (sscanf (line, fmt_str, &t->majtype, &t->type, end) != 3) + t->color1.x = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->color1.y = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->color1.z = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->color1.w = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->color2.x = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->color2.y = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->color2.z = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->color2.w = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->oscale = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->turbulence.x = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->amount = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->exp = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->scale.x = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->scale.y = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->scale.z = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->rotate.x = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->rotate.y = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->rotate.z = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->translate.x = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->translate.y = g_ascii_strtod (end, &end); + if (end && errno != ERANGE) + t->translate.z = g_ascii_strtod (end, &end); + + s.com.numtexture++; + } + + fclose (f); +} + +static void +loadpreset_response (GtkWidget *dialog, + gint response_id, + gpointer data) +{ + if (response_id == GTK_RESPONSE_OK) + { + GtkTreeModel *model = gtk_tree_view_get_model (texturelist); + gchar *name; + + name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + gtk_list_store_clear (GTK_LIST_STORE (model)); + + loadit (name); + g_free (name); + + rebuildlist (); + } + + gtk_widget_hide (dialog); +} + +static void +saveit (const gchar *fn) +{ + gint i; + FILE *f; + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + + f = g_fopen (fn, "wt"); + if (!f) + { + g_message (_("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (fn), g_strerror (errno)); + return; + } + + for (i = 0; i < s.com.numtexture; i++) + { + texture *t = &s.com.texture[i]; + + if (t->majtype < 0) + continue; + + fprintf (f, "%d %d", t->majtype, t->type); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.x)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.y)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.z)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.w)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.x)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.y)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.z)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.w)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->oscale)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->turbulence.x)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->amount)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->exp)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->scale.x)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->scale.y)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->scale.z)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->rotate.x)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->rotate.y)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->rotate.z)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->translate.x)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->translate.y)); + fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->translate.z)); + fprintf (f, "\n"); + } + + fclose (f); +} + +static void +savepreset_response (GtkWidget *dialog, + gint response_id, + gpointer data) +{ + if (response_id == GTK_RESPONSE_OK) + { + gchar *name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + saveit (name); + g_free (name); + } + + gtk_widget_hide (dialog); +} + +static void +loadpreset (GtkWidget *widget, + GtkWidget *parent) +{ + fileselect (GTK_FILE_CHOOSER_ACTION_OPEN, parent); +} + +static void +savepreset (GtkWidget *widget, + GtkWidget *parent) +{ + fileselect (GTK_FILE_CHOOSER_ACTION_SAVE, parent); +} + +static void +fileselect (GtkFileChooserAction action, + GtkWidget *parent) +{ + static GtkWidget *windows[2] = { NULL, NULL }; + + gchar *titles[] = { N_("Open File"), N_("Save File") }; + void *handlers[] = { loadpreset_response, savepreset_response }; + + if (! windows[action]) + { + GtkWidget *dialog = windows[action] = + gtk_file_chooser_dialog_new (gettext (titles[action]), + GTK_WINDOW (parent), + action, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + + action == GTK_FILE_CHOOSER_ACTION_OPEN ? + _("_Open") : _("_Save"), + GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + if (action == GTK_FILE_CHOOSER_ACTION_SAVE) + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), + TRUE); + + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &windows[action]); + g_signal_connect (dialog, "response", + G_CALLBACK (handlers[action]), + NULL); + } + + gtk_window_present (GTK_WINDOW (windows[action])); +} + +static void +initworld (void) +{ + gint i; + + memset (&world, 0, sizeof (world)); + + s.com.type = SPHERE; + s.a.x = s.a.y = s.a.z = 0.0; + s.r = 4.0; + + /* not: world.obj[0] = s; + * s is a sphere so error C2115: '=' : incompatible types + */ + memcpy (&world.obj[0], &s, sizeof (s)); + world.numobj = 1; + + world.obj[0].com.numtexture = 0; + world.obj[0].com.numnormal = 0; + + for (i = 0; i < s.com.numtexture; i++) + { + common *c = &s.com; + common *d = &world.obj[0].com; + texture *t = &c->texture[i]; + if ((t->amount <= 0.0) || (t->majtype < 0)) + continue; + if (t->majtype == 0) + { /* Normal texture */ + if (t->type == PHONG) + { + t->phongcolor = t->color1; + t->phongsize = t->oscale / 25.0; + } + d->texture[d->numtexture] = *t; + vmul (&d->texture[d->numtexture].scale, + d->texture[d->numtexture].oscale); + d->numtexture++; + } + else if (t->majtype == 1) + { /* Bumpmap */ + d->normal[d->numnormal] = *t; + vmul (&d->normal[d->numnormal].scale, + d->texture[d->numnormal].oscale); + d->numnormal++; + } + else if (t->majtype == 2) + { /* Lightsource */ + light l; + vcopy (&l.a, &t->translate); + vcopy (&l.color, &t->color1); + vmul (&l.color, t->amount); + world.light[world.numlight] = l; + world.numlight++; + } + } + + world.quality = 5; + + world.flags |= SMARTAMBIENT; + world.smartambient = 40.0; +} + +static gboolean +expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + cairo_t *cr; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_set_source_surface (cr, buffer, 0.0, 0.0); + + cairo_paint (cr); + + cairo_destroy (cr); + + return TRUE; +} + +static void +restartrender (void) +{ + if (idle_id) + g_source_remove (idle_id); + + idle_id = g_idle_add ((GSourceFunc) render, NULL); +} + +static void +selecttexture (GtkWidget *widget, + gpointer data) +{ + texture *t; + + if (noupdate) + return; + + t = currenttexture (); + if (!t) + return; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &t->type); + + relabel (); + restartrender (); +} + +static void +selecttype (GtkWidget *widget, + gpointer data) +{ + texture *t; + + if (noupdate) + return; + + t = currenttexture (); + if (!t) + return; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &t->majtype); + + relabel (); + restartrender (); +} + +static void +getscales (GtkWidget *widget, + gpointer data) +{ + gdouble f; + texture *t; + + if (noupdate) + return; + + t = currenttexture (); + if (!t) + return; + + t->amount = gtk_adjustment_get_value (GTK_ADJUSTMENT (amountscale)); + t->exp = gtk_adjustment_get_value (GTK_ADJUSTMENT (expscale)); + + f = gtk_adjustment_get_value (GTK_ADJUSTMENT (turbulencescale)); + vset (&t->turbulence, f, f, f); + + t->oscale = gtk_adjustment_get_value (GTK_ADJUSTMENT (scalescale)); + + t->scale.x = gtk_adjustment_get_value (GTK_ADJUSTMENT (scalexscale)); + t->scale.y = gtk_adjustment_get_value (GTK_ADJUSTMENT (scaleyscale)); + t->scale.z = gtk_adjustment_get_value (GTK_ADJUSTMENT (scalezscale)); + + t->rotate.x = gtk_adjustment_get_value (GTK_ADJUSTMENT (rotxscale)); + t->rotate.y = gtk_adjustment_get_value (GTK_ADJUSTMENT (rotyscale)); + t->rotate.z = gtk_adjustment_get_value (GTK_ADJUSTMENT (rotzscale)); + + t->translate.x = gtk_adjustment_get_value (GTK_ADJUSTMENT (posxscale)); + t->translate.y = gtk_adjustment_get_value (GTK_ADJUSTMENT (posyscale)); + t->translate.z = gtk_adjustment_get_value (GTK_ADJUSTMENT (poszscale)); + + restartrender (); +} + + +static void +color1_changed (GimpColorButton *button) +{ + texture *t = currenttexture (); + + if (t) + { + GimpRGB color; + + gimp_color_button_get_color (button, &color); + + t->color1.x = color.r; + t->color1.y = color.g; + t->color1.z = color.b; + t->color1.w = color.a; + + restartrender (); + } +} + +static void +color2_changed (GimpColorButton *button) +{ + texture *t = currenttexture (); + + if (t) + { + GimpRGB color; + + gimp_color_button_get_color (button, &color); + + t->color2.x = color.r; + t->color2.y = color.g; + t->color2.z = color.b; + t->color2.w = color.a; + + restartrender (); + } +} + +static void +drawcolor1 (GtkWidget *w) +{ + static GtkWidget *lastw = NULL; + + GimpRGB color; + texture *t = currenttexture (); + + if (w) + lastw = w; + else + w = lastw; + + if (!w) + return; + if (!t) + return; + + gimp_rgba_set (&color, + t->color1.x, t->color1.y, t->color1.z, t->color1.w); + + gimp_color_button_set_color (GIMP_COLOR_BUTTON (w), &color); +} + +static void +drawcolor2 (GtkWidget *w) +{ + static GtkWidget *lastw = NULL; + + GimpRGB color; + texture *t = currenttexture (); + + if (w) + lastw = w; + else + w = lastw; + + if (!w) + return; + if (!t) + return; + + gimp_rgba_set (&color, + t->color2.x, t->color2.y, t->color2.z, t->color2.w); + + gimp_color_button_set_color (GIMP_COLOR_BUTTON (w), &color); +} + +static gboolean do_run = FALSE; + +static void +sphere_response (GtkWidget *widget, + gint response_id, + gpointer data) +{ + switch (response_id) + { + case RESPONSE_RESET: + s.com.numtexture = 3; + + setdefaults (&s.com.texture[0]); + setdefaults (&s.com.texture[1]); + setdefaults (&s.com.texture[2]); + + s.com.texture[1].majtype = 2; + vset (&s.com.texture[1].color1, 1, 1, 1); + vset (&s.com.texture[1].translate, -15, -15, -15); + + s.com.texture[2].majtype = 2; + vset (&s.com.texture[2].color1, 0, 0.4, 0.4); + vset (&s.com.texture[2].translate, 15, 15, -15); + + gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (texturelist))); + + rebuildlist (); + break; + + case GTK_RESPONSE_OK: + if (idle_id) + { + g_source_remove (idle_id); + idle_id = 0; + } + + do_run = TRUE; + + default: + gtk_widget_hide (widget); + gtk_main_quit (); + break; + } +} + +static GtkWidget * +makewindow (void) +{ + GtkListStore *store; + GtkTreeViewColumn *col; + GtkTreeSelection *selection; + GtkWidget *window; + GtkWidget *main_hbox; + GtkWidget *main_vbox; + GtkWidget *table; + GtkWidget *frame; + GtkWidget *scrolled; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *button; + GtkWidget *list; + GimpRGB rgb = { 0, 0, 0, 0 }; + + window = gimp_dialog_new (_("Sphere Designer"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Reset"), RESPONSE_RESET, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (window), + RESPONSE_RESET, + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (window)); + + g_signal_connect (window, "response", + G_CALLBACK (sphere_response), + NULL); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0); + gtk_widget_show (main_hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (main_hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + drawarea = gtk_drawing_area_new (); + gtk_container_add (GTK_CONTAINER (frame), drawarea); + gtk_widget_set_size_request (drawarea, PREVIEWSIZE, PREVIEWSIZE); + gtk_widget_show (drawarea); + + g_signal_connect (drawarea, "expose-event", + G_CALLBACK (expose_event), NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + button = gtk_button_new_with_mnemonic (_("_Open")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (loadpreset), + window); + + button = gtk_button_new_with_mnemonic (_("_Save")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (savepreset), + window); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_end (GTK_BOX (main_hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0); + gtk_widget_show (scrolled); + + store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER); + list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); + g_object_unref (store); + + texturelist = GTK_TREE_VIEW (list); + + selection = gtk_tree_view_get_selection (texturelist); + + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + + g_signal_connect (selection, "changed", + G_CALLBACK (selectitem), + NULL); + + gtk_widget_set_size_request (list, -1, 150); + gtk_container_add (GTK_CONTAINER (scrolled), list); + gtk_widget_show (list); + + col = gtk_tree_view_column_new_with_attributes (_("Layers"), + gtk_cell_renderer_text_new (), + "text", TYPE, + NULL); + gtk_tree_view_append_column (texturelist, col); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + button = gtk_button_new_with_mnemonic (_("_New")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (addtexture), NULL); + gtk_widget_show (button); + + button = gtk_button_new_with_mnemonic (_("D_uplicate")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (duptexture), NULL); + gtk_widget_show (button); + + button = gtk_button_new_with_mnemonic (_("_Delete")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (deltexture), NULL); + gtk_widget_show (button); + + main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, FALSE, FALSE, 0); + gtk_widget_show (main_hbox); + + frame = gimp_frame_new (_("Properties")); + gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + table = gtk_table_new (7, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 2); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 2, 12); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + typemenu = gimp_int_combo_box_new (_("Texture"), 0, + _("Bump"), 1, + _("Light"), 2, + NULL); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (typemenu), 0, + G_CALLBACK (selecttype), + NULL); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Type:"), 0.0, 0.5, + typemenu, 2, FALSE); + + texturemenu = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + { + struct textures_t *t; + + for (t = textures; t->s; t++) + gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (texturemenu), + GIMP_INT_STORE_VALUE, t->n, + GIMP_INT_STORE_LABEL, gettext (t->s), + -1); + } + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (texturemenu), 0, + G_CALLBACK (selecttexture), + NULL); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Texture:"), 0.0, 0.5, + texturemenu, 2, FALSE); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("Colors:"), 0.0, 0.5, + hbox, 2, FALSE); + + button = gimp_color_button_new (_("Color Selection Dialog"), + COLORBUTTONWIDTH, COLORBUTTONHEIGHT, &rgb, + GIMP_COLOR_AREA_FLAT); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + drawcolor1 (button); + + g_signal_connect (button, "color-changed", + G_CALLBACK (color1_changed), + NULL); + + button = gimp_color_button_new (_("Color Selection Dialog"), + COLORBUTTONWIDTH, COLORBUTTONHEIGHT, &rgb, + GIMP_COLOR_AREA_FLAT); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + drawcolor2 (button); + + g_signal_connect (button, "color-changed", + G_CALLBACK (color2_changed), + NULL); + + scalescale = gimp_scale_entry_new (GTK_TABLE (table), 0, 3, _("Scale:"), + 100, -1, 1.0, 0.0, 10.0, 0.1, 1.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (scalescale, "value-changed", + G_CALLBACK (getscales), + NULL); + + turbulencescale = gimp_scale_entry_new (GTK_TABLE (table), 0, 4, + _("Turbulence:"), + 100, -1, 1.0, 0.0, 10.0, 0.1, 1.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (turbulencescale, "value-changed", + G_CALLBACK (getscales), + NULL); + + amountscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 5, _("Amount:"), + 100, -1, 1.0, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (amountscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + expscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 6, _("Exp.:"), + 100, -1, 1.0, 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (expscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + frame = gimp_frame_new (_("Transformations")); + gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + table = gtk_table_new (9, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 2); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 2, 12); + gtk_table_set_row_spacing (GTK_TABLE (table), 5, 12); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + scalexscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("Scale X:"), + 100, -1, 1.0, 0.0, 10.0, 0.1, 1.0, 2, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (scalexscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + scaleyscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, _("Scale Y:"), + 100, -1, 1.0, 0.0, 10.0, 0.1, 1.0, 2, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (scaleyscale, "value-changed", + G_CALLBACK (getscales), + NULL); + scalezscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, _("Scale Z:"), + 100, -1, 1.0, 0.0, 10.0, 0.1, 1.0, 2, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (scalezscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + rotxscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 3, _("Rotate X:"), + 100, -1, 0.0, 0.0, 360.0, 1.0, 10.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (rotxscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + rotyscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 4, _("Rotate Y:"), + 100, -1, 0.0, 0.0, 360.0, 1.0, 10.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (rotyscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + rotzscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 5, _("Rotate Z:"), + 100, -1, 0.0, 0.0, 360.0, 1.0, 10.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (rotzscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + posxscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 6, _("Position X:"), + 100, -1, 0.0, -20.0, 20.0, 0.1, 1.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (posxscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + posyscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 7, _("Position Y:"), + 100, -1, 0.0, -20.0, 20.0, 0.1, 1.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (posyscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + poszscale = gimp_scale_entry_new (GTK_TABLE (table), 0, 8, _("Position Z:"), + 100, -1, 0.0, -20.0, 20.0, 0.1, 1.0, 1, + TRUE, 0.0, 0.0, NULL, NULL); + g_signal_connect (poszscale, "value-changed", + G_CALLBACK (getscales), + NULL); + + gtk_widget_show (window); + + return window; +} + +static guchar +pixelval (gdouble v) +{ + v += 0.5; + if (v < 0.0) + return 0; + if (v > 255.0) + return 255; + return v; +} + +static gboolean +render (void) +{ + GimpVector4 col; + guchar *dest_row; + ray r; + gint x, y, p; + gint tx = PREVIEWSIZE; + gint ty = PREVIEWSIZE; + gint bpp = 4; + + idle_id = 0; + + initworld (); + + r.v1.z = -10.0; + r.v2.z = 0.0; + + if (world.obj[0].com.numtexture > 0) + { + cairo_surface_flush (buffer); + + for (y = 0; y < ty; y++) + { + dest_row = img + y * img_stride; + + for (x = 0; x < tx; x++) + { + gint g, gridsize = 16; + + g = ((x / gridsize + y / gridsize) % 2) * 60 + 100; + + r.v1.x = r.v2.x = 8.5 * (x / (float) (tx - 1) - 0.5); + r.v1.y = r.v2.y = 8.5 * (y / (float) (ty - 1) - 0.5); + + p = x * bpp; + + traceray (&r, &col, 10, 1.0); + + if (col.w < 0.0) + col.w = 0.0; + else if (col.w > 1.0) + col.w = 1.0; + + GIMP_CAIRO_RGB24_SET_PIXEL ((dest_row + p), + pixelval (255 * col.x) * col.w + g * (1.0 - col.w), + pixelval (255 * col.y) * col.w + g * (1.0 - col.w), + pixelval (255 * col.z) * col.w + g * (1.0 - col.w)); + } + } + + cairo_surface_mark_dirty (buffer); + } + + gtk_widget_queue_draw (drawarea); + + return FALSE; +} + +static void +realrender (gint32 drawable_ID) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + gint x, y; + ray r; + GimpVector4 rcol; + gint width, height; + gint x1, y1; + guchar *dest; + gint bpp; + guchar *buffer, *ibuffer; + + initworld (); + + r.v1.z = -10.0; + r.v2.z = 0.0; + + if (! gimp_drawable_mask_intersect (drawable_ID, + &x1, &y1, &width, &height)) + return; + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + if (gimp_drawable_is_rgb (drawable_ID)) + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + } + else + { + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("Y'A u8"); + else + format = babl_format ("Y' u8"); + } + + bpp = babl_format_get_bytes_per_pixel (format); + + buffer = g_malloc (width * 4); + ibuffer = g_malloc (width * 4); + + gimp_progress_init (_("Rendering sphere")); + + for (y = 0; y < height; y++) + { + dest = buffer; + for (x = 0; x < width; x++) + { + r.v1.x = r.v2.x = 8.1 * (x / (float) (width - 1) - 0.5); + r.v1.y = r.v2.y = 8.1 * (y / (float) (height - 1) - 0.5); + + traceray (&r, &rcol, 10, 1.0); + dest[0] = pixelval (255 * rcol.x); + dest[1] = pixelval (255 * rcol.y); + dest[2] = pixelval (255 * rcol.z); + dest[3] = pixelval (255 * rcol.w); + dest += 4; + } + + gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y1 + y, width, 1), 1.0, + format, ibuffer, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + for (x = 0; x < width; x++) + { + gint k, dx = x * 4, sx = x * bpp; + gfloat a = buffer[dx + 3] / 255.0; + + for (k = 0; k < bpp; k++) + { + ibuffer[sx + k] = + buffer[dx + k] * a + ibuffer[sx + k] * (1.0 - a); + } + } + + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x1, y1 + y, width, 1), 0, + format, ibuffer, + GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((gdouble) y / (gdouble) height); + } + + gimp_progress_update (1.0); + g_free (buffer); + g_free (ibuffer); + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, x1, y1, width, height); +} + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Create an image of a textured sphere"), + "This plug-in can be used to create textured and/or " + "bumpmapped spheres, and uses a small lightweight " + "raytracer to perform the task with good quality", + "Vidar Madsen", + "Vidar Madsen", + "1999", + N_("Sphere _Designer..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Render"); +} + +static gboolean +sphere_main (gint32 drawable_ID) +{ + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + img_stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, PREVIEWSIZE); + img = g_malloc0 (img_stride * PREVIEWSIZE); + + buffer = cairo_image_surface_create_for_data (img, CAIRO_FORMAT_RGB24, + PREVIEWSIZE, + PREVIEWSIZE, + img_stride); + + makewindow (); + + if (s.com.numtexture == 0) + { + /* Setup and use default list */ + sphere_response (NULL, RESPONSE_RESET, NULL); + } + else + { + /* Reuse the list from a previous invocation */ + rebuildlist (); + } + + gtk_main (); + + cairo_surface_destroy (buffer); + g_free (img); + + return do_run; +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + gint32 drawable_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint x, y, w, h; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + run_mode = param[0].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + if (! gimp_drawable_mask_intersect (drawable_ID, &x, &y, &w, &h)) + { + g_message (_("Region selected for plug-in is empty")); + return; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + s.com.numtexture = 0; + gimp_get_data (PLUG_IN_PROC, &s); + if (! sphere_main (drawable_ID)) + return; + break; + + case GIMP_RUN_WITH_LAST_VALS: + s.com.numtexture = 0; + gimp_get_data (PLUG_IN_PROC, &s); + if (s.com.numtexture == 0) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + default: + /* Not implemented yet... */ + return; + } + + gimp_set_data (PLUG_IN_PROC, &s, sizeof (s)); + + realrender (drawable_ID); + gimp_displays_flush (); + + values[0].data.d_status = status; +} + +MAIN () diff --git a/plug-ins/common/tile-small.c b/plug-ins/common/tile-small.c new file mode 100644 index 0000000..534faab --- /dev/null +++ b/plug-ins/common/tile-small.c @@ -0,0 +1,1130 @@ +/* + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This is a plug-in for GIMP. + * + * Tileit - This plugin take an image and makes repeated copies of it. + * + * Copyright (C) 1997 Andy Thomas alt@picnic.demon.co.uk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * A fair proprotion of this code was taken from the Whirl plug-in + * which was copyrighted by Federico Mena Quintero (as below). + * + * Whirl plug-in --- distort an image into a whirlpool + * Copyright (C) 1997 Federico Mena Quintero + * + */ + +/* Change log:- + * 0.2 Added new functions to allow "editing" of the tile patten. + * + * 0.1 First version released. + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#define PLUG_IN_PROC "plug-in-small-tiles" +#define PLUG_IN_BINARY "tile-small" +#define PLUG_IN_ROLE "gimp-tile-small" + +/***** Magic numbers *****/ + +#define PREVIEW_SIZE 128 +#define SCALE_WIDTH 80 + +#define MAX_SEGS 6 + +#define PREVIEW_MASK GDK_EXPOSURE_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_MOTION_MASK + +/* Variables set in dialog box */ +typedef struct data +{ + gint numtiles; +} TileItVals; + +typedef struct +{ + GtkWidget *preview; + guchar preview_row[PREVIEW_SIZE * 4]; + gint img_bpp; + guchar *pv_cache; +} TileItInterface; + +static TileItInterface tint = +{ + NULL, /* Preview */ + { + '4', + 'u' + }, /* Preview_row */ + 4, /* bpp of drawable */ + NULL +}; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean tileit_dialog (gint32 drawable_ID); + +static void tileit_scale_update (GtkAdjustment *adjustment, + gpointer data); + +static void tileit_exp_update (GtkWidget *widget, + gpointer value); +static void tileit_exp_update_f (GtkWidget *widget, + gpointer value); + +static void tileit_reset (GtkWidget *widget, + gpointer value); +static void tileit_radio_update (GtkWidget *widget, + gpointer data); +static void tileit_hvtoggle_update (GtkWidget *widget, + gpointer data); + +static void do_tiles (gint32 drawable_ID); +static gint tiles_xy (gint width, + gint height, + gint x, + gint y, + gint *nx, + gint *ny); +static void all_update (void); +static void alt_update (void); +static void explicit_update (gboolean); + +static void dialog_update_preview (void); +static void cache_preview (gint32 drawable_ID); +static gboolean tileit_preview_expose (GtkWidget *widget, + GdkEvent *event); +static gboolean tileit_preview_events (GtkWidget *widget, + GdkEvent *event); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +/* Values when first invoked */ +static TileItVals itvals = +{ + 2 +}; + +/* Structures for call backs... */ +/* The "explicit tile" & family */ +typedef enum +{ + ALL, + ALT, + EXPLICIT +} AppliedTo; + +typedef struct +{ + AppliedTo type; + + gint x; /* X - pos of tile */ + gint y; /* Y - pos of tile */ + GtkObject *r_adj; /* row adjustment */ + GtkObject *c_adj; /* column adjustment */ + GtkWidget *applybut; /* The apply button */ +} Exp_Call; + +static Exp_Call exp_call = +{ + ALL, + -1, + -1, + NULL, + NULL, + NULL, +}; + +/* The reset button needs to know some toggle widgets.. */ + +typedef struct +{ + GtkWidget *htoggle; + GtkWidget *vtoggle; +} Reset_Call; + +static Reset_Call res_call = +{ + NULL, + NULL, +}; + +/* 2D - Array that holds the actions for each tile */ +/* Action type on cell */ +#define HORIZONTAL 0x1 +#define VERTICAL 0x2 + +static gint tileactions[MAX_SEGS][MAX_SEGS]; + +/* What actions buttons toggled */ +static gint do_horz = FALSE; +static gint do_vert = FALSE; +static gint opacity = 100; + +/* Stuff for the preview bit */ +static gint sel_x1, sel_y1, sel_x2, sel_y2; +static gint sel_width, sel_height; +static gint preview_width, preview_height; +static gboolean has_alpha; + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "num-tiles", "Number of tiles to make" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Tile image into smaller versions of the original"), + "More here later", + "Andy Thomas", + "Andy Thomas", + "1997", + N_("_Small Tiles..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + gint32 drawable_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint pwidth; + gint pheight; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + run_mode = param[0].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + has_alpha = gimp_drawable_has_alpha (drawable_ID); + + if (! gimp_drawable_mask_intersect (drawable_ID, + &sel_x1, &sel_y1, + &sel_width, &sel_height)) + { + g_message (_("Region selected for filter is empty.")); + return; + } + + sel_x2 = sel_x1 + sel_width; + sel_y2 = sel_y1 + sel_height; + + /* Calculate preview size */ + + if (sel_width > sel_height) + { + pwidth = MIN (sel_width, PREVIEW_SIZE); + pheight = sel_height * pwidth / sel_width; + } + else + { + pheight = MIN (sel_height, PREVIEW_SIZE); + pwidth = sel_width * pheight / sel_height; + } + + preview_width = MAX (pwidth, 2); /* Min size is 2 */ + preview_height = MAX (pheight, 2); + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &itvals); + if (! tileit_dialog (drawable_ID)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 4) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + itvals.numtiles = param[3].data.d_int32; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &itvals); + break; + + default: + break; + } + + if (gimp_drawable_is_rgb (drawable_ID) || + gimp_drawable_is_gray (drawable_ID)) + { + /* Set the tile cache size */ + + gimp_progress_init (_("Tiling")); + + do_tiles (drawable_ID); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &itvals, sizeof (TileItVals)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + + values[0].data.d_status = status; +} + +static gboolean +tileit_dialog (gint drawable_ID) +{ + GtkWidget *dlg; + GtkWidget *main_vbox; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *table2; + GtkWidget *button; + GtkWidget *label; + GtkWidget *spinbutton; + GtkObject *adj; + GtkObject *scale; + GtkWidget *toggle; + GSList *orientation_group = NULL; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + cache_preview (drawable_ID); /* Get the preview image */ + + dlg = gimp_dialog_new (_("Small Tiles"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + tint.preview = gimp_preview_area_new (); + gtk_widget_set_size_request (tint.preview, preview_width, preview_height); + gtk_widget_set_events (GTK_WIDGET (tint.preview), PREVIEW_MASK); + gtk_container_add (GTK_CONTAINER (frame), tint.preview); + gtk_widget_show (tint.preview); + + g_signal_connect_after (tint.preview, "expose-event", + G_CALLBACK (tileit_preview_expose), + NULL); + g_signal_connect (tint.preview, "event", + G_CALLBACK (tileit_preview_events), + NULL); + + /* Area for buttons etc */ + + frame = gimp_frame_new (_("Flip")); + gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + toggle = gtk_check_button_new_with_mnemonic (_("_Horizontal")); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (tileit_hvtoggle_update), + &do_horz); + + res_call.htoggle = toggle; + + toggle = gtk_check_button_new_with_mnemonic (_("_Vertical")); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (tileit_hvtoggle_update), + &do_vert); + + res_call.vtoggle = toggle; + + button = gtk_button_new_with_mnemonic (_("_Reset")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (tileit_reset), + &res_call); + + /* Table for the inner widgets..*/ + table = gtk_table_new (4, 4, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + toggle = gtk_radio_button_new_with_mnemonic (orientation_group, + _("A_ll tiles")); + orientation_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 4, 0, 1, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (toggle); + + g_object_set_data (G_OBJECT (toggle), "gimp-item-data", + GINT_TO_POINTER (ALL)); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (tileit_radio_update), + &exp_call.type); + + toggle = gtk_radio_button_new_with_mnemonic (orientation_group, + _("Al_ternate tiles")); + orientation_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 4, 1, 2, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (toggle); + + g_object_set_data (G_OBJECT (toggle), "gimp-item-data", + GINT_TO_POINTER (ALT)); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (tileit_radio_update), + &exp_call.type); + + toggle = gtk_radio_button_new_with_mnemonic (orientation_group, + _("_Explicit tile")); + orientation_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 2, 4, + GTK_FILL | GTK_SHRINK, GTK_FILL, 0, 0); + gtk_widget_show (toggle); + + label = gtk_label_new_with_mnemonic (_("Ro_w:")); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 2, 3, + GTK_FILL | GTK_SHRINK , GTK_FILL, 0, 0); + gtk_widget_show (label); + + g_object_bind_property (toggle, "active", + label, "sensitive", + G_BINDING_SYNC_CREATE); + + spinbutton = gimp_spin_button_new (&adj, 2, 1, 6, 1, 1, 0, 1, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + gtk_table_attach (GTK_TABLE (table), spinbutton, 2, 3, 2, 3, + GTK_FILL | GTK_SHRINK, GTK_FILL, 0, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (tileit_exp_update_f), + &exp_call); + + exp_call.r_adj = adj; + + g_object_bind_property (toggle, "active", + spinbutton, "sensitive", + G_BINDING_SYNC_CREATE); + + label = gtk_label_new_with_mnemonic (_("Col_umn:")); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 3, 4, + GTK_FILL , GTK_FILL, 0, 0); + + g_object_bind_property (toggle, "active", + label, "sensitive", + G_BINDING_SYNC_CREATE); + + spinbutton = gimp_spin_button_new (&adj, 2, 1, 6, 1, 1, 0, 1, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + gtk_table_attach (GTK_TABLE (table), spinbutton, 2, 3, 3, 4, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (tileit_exp_update_f), + &exp_call); + + exp_call.c_adj = adj; + + g_object_bind_property (toggle, "active", + spinbutton, "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_set_data (G_OBJECT (toggle), "gimp-item-data", + GINT_TO_POINTER (EXPLICIT)); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (tileit_radio_update), + &exp_call.type); + + button = gtk_button_new_with_mnemonic (_("_Apply")); + gtk_table_attach (GTK_TABLE (table), button, 3, 4, 2, 4, 0, 0, 0, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (tileit_exp_update), + &exp_call); + + exp_call.applybut = button; + + g_object_bind_property (toggle, "active", + spinbutton, "sensitive", + G_BINDING_SYNC_CREATE); + + /* Widget for selecting the Opacity */ + + table2 = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table2), 6); + gtk_box_pack_start (GTK_BOX (vbox), table2, FALSE, FALSE, 0); + gtk_widget_show (table2); + + scale = gimp_scale_entry_new (GTK_TABLE (table2), 0, 0, + _("O_pacity:"), SCALE_WIDTH, -1, + opacity, 0, 100, 1, 10, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (scale, "value-changed", + G_CALLBACK (tileit_scale_update), + &opacity); + + /* Lower frame saying howmany segments */ + frame = gimp_frame_new (_("Number of Segments")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + gtk_widget_set_sensitive (table2, gimp_drawable_has_alpha (drawable_ID)); + + scale = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + "_n²", SCALE_WIDTH, -1, + itvals.numtiles, 2, MAX_SEGS, 1, 1, 0, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (scale, "value-changed", + G_CALLBACK (tileit_scale_update), + &itvals.numtiles); + + gtk_widget_show (dlg); + dialog_update_preview (); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dlg); + + return run; +} + +static void +tileit_hvtoggle_update (GtkWidget *widget, + gpointer data) +{ + gimp_toggle_button_update (widget, data); + + switch (exp_call.type) + { + case ALL: + /* Clear current settings */ + memset (tileactions, 0, sizeof (tileactions)); + all_update (); + break; + + case ALT: + /* Clear current settings */ + memset (tileactions, 0, sizeof (tileactions)); + alt_update (); + break; + + case EXPLICIT: + break; + } + + dialog_update_preview (); +} + +static gboolean +tileit_preview_expose (GtkWidget *widget, + GdkEvent *event) +{ + if (exp_call.type == EXPLICIT) + { + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (tint.preview)); + gdouble width = (gdouble) preview_width / (gdouble) itvals.numtiles; + gdouble height = (gdouble) preview_height / (gdouble) itvals.numtiles; + gdouble x , y; + + x = width * (exp_call.x - 1); + y = height * (exp_call.y - 1); + + cairo_rectangle (cr, x + 1.5, y + 1.5, width - 2, height - 2); + + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); + cairo_stroke_preserve (cr); + + cairo_destroy (cr); + } + + return FALSE; +} + +static void +exp_need_update (gint nx, + gint ny) +{ + if (nx <= 0 || nx > itvals.numtiles || ny <= 0 || ny > itvals.numtiles) + return; + + if (nx != exp_call.x || ny != exp_call.y) + { + exp_call.x = nx; + exp_call.y = ny; + gtk_widget_queue_draw (tint.preview); + + g_signal_handlers_block_by_func (exp_call.c_adj, + tileit_exp_update_f, + &exp_call); + g_signal_handlers_block_by_func (exp_call.r_adj, + tileit_exp_update_f, + &exp_call); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (exp_call.c_adj), nx); + gtk_adjustment_set_value (GTK_ADJUSTMENT (exp_call.r_adj), ny); + + g_signal_handlers_unblock_by_func (exp_call.c_adj, + tileit_exp_update_f, + &exp_call); + g_signal_handlers_unblock_by_func (exp_call.r_adj, + tileit_exp_update_f, + &exp_call); + } +} + +static gboolean +tileit_preview_events (GtkWidget *widget, + GdkEvent *event) +{ + GdkEventButton *bevent; + GdkEventMotion *mevent; + gint nx, ny; + gint twidth = preview_width / itvals.numtiles; + gint theight = preview_height / itvals.numtiles; + + switch (event->type) + { + case GDK_EXPOSE: + break; + + case GDK_BUTTON_PRESS: + bevent = (GdkEventButton *) event; + nx = bevent->x/twidth + 1; + ny = bevent->y/theight + 1; + exp_need_update (nx, ny); + break; + + case GDK_MOTION_NOTIFY: + mevent = (GdkEventMotion *) event; + if ( !mevent->state ) + break; + if (mevent->x < 0 || mevent->y < 0) + break; + nx = mevent->x/twidth + 1; + ny = mevent->y/theight + 1; + exp_need_update (nx, ny); + break; + + default: + break; + } + + return FALSE; +} + +static void +explicit_update (gboolean settile) +{ + gint x,y; + + /* Make sure bounds are OK */ + y = ROUND (gtk_adjustment_get_value (GTK_ADJUSTMENT (exp_call.r_adj))); + if (y > itvals.numtiles || y <= 0) + { + y = itvals.numtiles; + } + x = ROUND (gtk_adjustment_get_value (GTK_ADJUSTMENT (exp_call.c_adj))); + if (x > itvals.numtiles || x <= 0) + { + x = itvals.numtiles; + } + + /* Set it */ + if (settile) + tileactions[x-1][y-1] = (((do_horz) ? HORIZONTAL : 0) | + ((do_vert) ? VERTICAL : 0)); + + exp_call.x = x; + exp_call.y = y; +} + +static void +all_update (void) +{ + gint x,y; + + for (x = 0 ; x < MAX_SEGS; x++) + for (y = 0 ; y < MAX_SEGS; y++) + tileactions[x][y] |= (((do_horz) ? HORIZONTAL : 0) | + ((do_vert) ? VERTICAL : 0)); +} + +static void +alt_update (void) +{ + gint x,y; + + for (x = 0 ; x < MAX_SEGS; x++) + for (y = 0 ; y < MAX_SEGS; y++) + if (!((x + y) % 2)) + tileactions[x][y] |= (((do_horz) ? HORIZONTAL : 0) | + ((do_vert) ? VERTICAL : 0)); +} + +static void +tileit_radio_update (GtkWidget *widget, + gpointer data) +{ + gimp_radio_button_update (widget, data); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + switch (exp_call.type) + { + case ALL: + /* Clear current settings */ + memset (tileactions, 0, sizeof (tileactions)); + all_update (); + break; + + case ALT: + /* Clear current settings */ + memset (tileactions, 0, sizeof (tileactions)); + alt_update (); + break; + + case EXPLICIT: + explicit_update (FALSE); + break; + } + + dialog_update_preview (); + } +} + + +static void +tileit_scale_update (GtkAdjustment *adjustment, + gpointer data) +{ + gimp_int_adjustment_update (adjustment, data); + + dialog_update_preview (); +} + +static void +tileit_reset (GtkWidget *widget, + gpointer data) +{ + Reset_Call *r = (Reset_Call *) data; + + memset (tileactions, 0, sizeof (tileactions)); + + g_signal_handlers_block_by_func (r->htoggle, + tileit_hvtoggle_update, + &do_horz); + g_signal_handlers_block_by_func (r->vtoggle, + tileit_hvtoggle_update, + &do_vert); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (r->htoggle), FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (r->vtoggle), FALSE); + + g_signal_handlers_unblock_by_func (r->htoggle, + tileit_hvtoggle_update, + &do_horz); + g_signal_handlers_unblock_by_func (r->vtoggle, + tileit_hvtoggle_update, + &do_vert); + + do_horz = do_vert = FALSE; + + dialog_update_preview (); +} + + +/* Could avoid almost dup. functions by using a field in the data + * passed. Must still pass the data since used in sig blocking func. + */ + +static void +tileit_exp_update (GtkWidget *widget, + gpointer applied) +{ + explicit_update (TRUE); + dialog_update_preview (); +} + +static void +tileit_exp_update_f (GtkWidget *widget, + gpointer applied) +{ + explicit_update (FALSE); + dialog_update_preview (); +} + +/* Cache the preview image - updates are a lot faster. */ +/* The preview_cache will contain the small image */ + +static void +cache_preview (gint32 drawable_ID) +{ + GeglBuffer *buffer = gimp_drawable_get_buffer (drawable_ID); + const Babl *format; + gdouble scale; + + if (gimp_drawable_has_alpha (drawable_ID)) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + tint.img_bpp = babl_format_get_bytes_per_pixel (format); + + tint.pv_cache = g_new (guchar, preview_width * preview_height * 4); + + scale = (gdouble) preview_width / (gdouble) sel_width; + + gegl_buffer_get (buffer, GEGL_RECTANGLE (scale * sel_x1, scale * sel_y1, + preview_width, preview_height), + scale, + format, tint.pv_cache, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + g_object_unref (buffer); +} + +static void +do_tiles (gint32 drawable_ID) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + GeglBufferIterator *iter; + const Babl *format; + gboolean has_alpha; + gint progress, max_progress; + gint bpp; + guchar pixel[4]; + gint nc, nr; + gint i; + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + has_alpha = gimp_drawable_has_alpha (drawable_ID); + + if (has_alpha) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + bpp = babl_format_get_bytes_per_pixel (format); + + progress = 0; + max_progress = sel_width * sel_height; + + iter = gegl_buffer_iterator_new (dest_buffer, + GEGL_RECTANGLE (sel_x1, sel_y1, + sel_width, sel_height), 0, + format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1); + + + while (gegl_buffer_iterator_next (iter)) + { + GeglRectangle dest_roi = iter->items[0].roi; + guchar *dest_row = iter->items[0].data; + gint row; + + for (row = dest_roi.y; row < (dest_roi.y + dest_roi.height); row++) + { + guchar *dest = dest_row; + gint col; + + for (col = dest_roi.x; col < (dest_roi.x + dest_roi.width); col++) + { + tiles_xy (sel_width, + sel_height, + col - sel_x1, + row - sel_y1, + &nc, &nr); + + gegl_buffer_sample (src_buffer, nc + sel_x1, nr + sel_y1, NULL, + pixel, format, + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); + + for (i = 0; i < bpp; i++) + dest[i] = pixel[i]; + + if (has_alpha) + dest[bpp - 1] = (pixel[bpp - 1] * opacity) / 100; + + dest += bpp; + } + + dest_row += dest_roi.width * bpp; + } + + progress += dest_roi.width * dest_roi.height; + gimp_progress_update ((double) progress / max_progress); + } + + gimp_progress_update (1.0); + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, + sel_x1, sel_y1, sel_width, sel_height); +} + + +/* Get the xy pos and any action */ +static gint +tiles_xy (gint width, + gint height, + gint x, + gint y, + gint *nx, + gint *ny) +{ + gint px,py; + gint rnum,cnum; + gint actiontype; + gdouble rnd = 1 - (1.0 / (gdouble) itvals.numtiles) + 0.01; + + rnum = y * itvals.numtiles / height; + + py = (y * itvals.numtiles) % height; + px = (x * itvals.numtiles) % width; + cnum = x * itvals.numtiles / width; + + if ((actiontype = tileactions[cnum][rnum])) + { + if (actiontype & VERTICAL) + { + gdouble pyr; + + pyr = height - y - 1 + rnd; + py = ((gint) (pyr * (gdouble) itvals.numtiles)) % height; + } + + if (actiontype & HORIZONTAL) + { + gdouble pxr; + + pxr = width - x - 1 + rnd; + px = ((gint) (pxr * (gdouble) itvals.numtiles)) % width; + } + } + + *nx = px; + *ny = py; + + return(actiontype); +} + + +/* Given a row then shrink it down a bit */ +static void +do_tiles_preview (guchar *dest_row, + guchar *src_rows, + gint width, + gint dh, + gint height, + gint bpp) +{ + gint x; + gint i; + gint px, py; + gint rnum,cnum; + gint actiontype; + gdouble rnd = 1 - (1.0 / (gdouble) itvals.numtiles) + 0.01; + + rnum = dh * itvals.numtiles / height; + + for (x = 0; x < width; x ++) + { + + py = (dh*itvals.numtiles)%height; + + px = (x*itvals.numtiles)%width; + cnum = x*itvals.numtiles/width; + + if ((actiontype = tileactions[cnum][rnum])) + { + if (actiontype & VERTICAL) + { + gdouble pyr; + pyr = height - dh - 1 + rnd; + py = ((int)(pyr*(gdouble)itvals.numtiles))%height; + } + + if (actiontype & HORIZONTAL) + { + gdouble pxr; + pxr = width - x - 1 + rnd; + px = ((int)(pxr*(gdouble)itvals.numtiles))%width; + } + } + + for (i = 0 ; i < bpp; i++ ) + dest_row[x*tint.img_bpp+i] = + src_rows[(px + (py*width))*bpp+i]; + + if (has_alpha) + dest_row[x*tint.img_bpp + (bpp - 1)] = + (dest_row[x*tint.img_bpp + (bpp - 1)]*opacity)/100; + + } +} + +static void +dialog_update_preview (void) +{ + gint y; + guchar *buffer; + + buffer = g_new (guchar, preview_width * preview_height * tint.img_bpp); + + for (y = 0; y < preview_height; y++) + { + do_tiles_preview (tint.preview_row, + tint.pv_cache, + preview_width, + y, + preview_height, + tint.img_bpp); + + memcpy (buffer + y* (preview_width * tint.img_bpp), + tint.preview_row, + preview_width * tint.img_bpp); + } + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (tint.preview), + 0, 0, preview_width, preview_height, + (tint.img_bpp>3)?GIMP_RGBA_IMAGE:GIMP_RGB_IMAGE, + buffer, + preview_width * tint.img_bpp); + + g_free (buffer); + + gtk_widget_queue_draw (tint.preview); +} diff --git a/plug-ins/common/tile.c b/plug-ins/common/tile.c new file mode 100644 index 0000000..a74b7fe --- /dev/null +++ b/plug-ins/common/tile.c @@ -0,0 +1,505 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This filter tiles an image to arbitrary width and height + */ +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-tile" +#define PLUG_IN_BINARY "tile" +#define PLUG_IN_ROLE "gimp-tile" + + +typedef struct +{ + gint new_width; + gint new_height; + gint constrain; + gint new_image; +} TileVals; + + +/* Declare local functions. + */ +static void query (void); + +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void tile (gint32 image_id, + gint32 drawable_id, + gint32 *new_image_id, + gint32 *new_layer_id); + +static void tile_gegl (GeglBuffer *src, + gint src_width, + gint src_height, + GeglBuffer *dst, + gint dst_width, + gint dst_height); + +static gboolean tile_dialog (gint32 image_ID, + gint32 drawable_ID); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static TileVals tvals = +{ + 1, /* new_width */ + 1, /* new_height */ + TRUE, /* constrain */ + TRUE /* new_image */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "new-width", "New (tiled) image width" }, + { GIMP_PDB_INT32, "new-height", "New (tiled) image height" }, + { GIMP_PDB_INT32, "new-image", "Create a new image?" } + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "new-image", "Output image (-1 if new-image == FALSE)" }, + { GIMP_PDB_LAYER, "new-layer", "Output layer (-1 if new-image == FALSE)" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Create an array of copies of the image"), + "This function creates a new image with a single " + "layer sized to the specified 'new_width' and " + "'new_height' parameters. The specified drawable " + "is tiled into this layer. The new layer will have " + "the same type as the specified drawable and the " + "new image will have a corresponding base type.", + "Spencer Kimball & Peter Mattis", + "Spencer Kimball & Peter Mattis", + "1996-1997", + N_("_Tile..."), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Map"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[3]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 3; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + values[1].type = GIMP_PDB_IMAGE; + values[2].type = GIMP_PDB_LAYER; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &tvals); + + /* First acquire information with a dialog */ + if (! tile_dialog (param[1].data.d_image, + param[2].data.d_drawable)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + tvals.new_width = param[3].data.d_int32; + tvals.new_height = param[4].data.d_int32; + tvals.new_image = param[5].data.d_int32 ? TRUE : FALSE; + + if (tvals.new_width < 1 || tvals.new_height < 1) + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &tvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + gint32 new_layer_id; + gint32 new_image_id; + + gimp_progress_init (_("Tiling")); + + tile (param[1].data.d_image, + param[2].data.d_drawable, + &new_image_id, + &new_layer_id); + + values[1].data.d_image = new_image_id; + values[2].data.d_layer = new_layer_id; + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &tvals, sizeof (TileVals)); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + { + if (tvals.new_image) + gimp_display_new (values[1].data.d_image); + else + gimp_displays_flush (); + } + } + + values[0].data.d_status = status; + + gegl_exit (); +} + +static void +tile_gegl (GeglBuffer *src, + gint src_width, + gint src_height, + GeglBuffer *dst, + gint dst_width, + gint dst_height) +{ + GeglNode *node; + GeglNode *buffer_src_node; + GeglNode *tile_node; + GeglNode *crop_src_node; + GeglNode *crop_dst_node; + GeglNode *buffer_dst_node; + + GeglProcessor *processor; + gdouble progress; + + node = gegl_node_new (); + + buffer_src_node = gegl_node_new_child (node, + "operation", "gegl:buffer-source", + "buffer", src, + NULL); + + crop_src_node = gegl_node_new_child (node, + "operation", "gegl:crop", + "width", (gdouble) src_width, + "height", (gdouble) src_height, + NULL); + + tile_node = gegl_node_new_child (node, + "operation", "gegl:tile", + NULL); + + crop_dst_node = gegl_node_new_child (node, + "operation", "gegl:crop", + "width", (gdouble) dst_width, + "height", (gdouble) dst_height, + NULL); + + buffer_dst_node = gegl_node_new_child (node, + "operation", "gegl:write-buffer", + "buffer", dst, + NULL); + + gegl_node_link_many (buffer_src_node, + crop_src_node, + tile_node, + crop_dst_node, + buffer_dst_node, + NULL); + + processor = gegl_node_new_processor (buffer_dst_node, NULL); + + while (gegl_processor_work (processor, &progress)) + if (!((gint) (progress * 100.0) % 10)) + gimp_progress_update (progress); + + gimp_progress_update (1.0); + + g_object_unref (processor); + g_object_unref (node); +} + +static void +tile (gint32 image_id, + gint32 drawable_id, + gint32 *new_image_id, + gint32 *new_layer_id) +{ + gint32 dst_drawable_id; + GeglBuffer *dst_buffer; + GeglBuffer *src_buffer; + gint dst_width = tvals.new_width; + gint dst_height = tvals.new_height; + gint src_width = gimp_drawable_width (drawable_id); + gint src_height = gimp_drawable_height (drawable_id); + + GimpImageBaseType image_type = GIMP_RGB; + + /* sanity check parameters */ + if (dst_width < 1 || dst_height < 1) + { + *new_image_id = -1; + *new_layer_id = -1; + return; + } + + if (tvals.new_image) + { + /* create a new image */ + gint32 precision = gimp_image_get_precision (image_id); + + switch (gimp_drawable_type (drawable_id)) + { + case GIMP_RGB_IMAGE: + case GIMP_RGBA_IMAGE: + image_type = GIMP_RGB; + break; + + case GIMP_GRAY_IMAGE: + case GIMP_GRAYA_IMAGE: + image_type = GIMP_GRAY; + break; + + case GIMP_INDEXED_IMAGE: + case GIMP_INDEXEDA_IMAGE: + image_type = GIMP_INDEXED; + break; + } + + *new_image_id = gimp_image_new_with_precision (dst_width, + dst_height, + image_type, + precision); + gimp_image_undo_disable (*new_image_id); + + /* copy the colormap, if necessary */ + if (image_type == GIMP_INDEXED) + { + guchar *cmap; + gint ncols; + + cmap = gimp_image_get_colormap (image_id, &ncols); + gimp_image_set_colormap (*new_image_id, cmap, ncols); + g_free (cmap); + } + + *new_layer_id = gimp_layer_new (*new_image_id, _("Background"), + dst_width, dst_height, + gimp_drawable_type (drawable_id), + 100, + gimp_image_get_default_new_layer_mode (*new_image_id)); + + if (*new_layer_id == -1) + return; + + gimp_image_insert_layer (*new_image_id, *new_layer_id, -1, 0); + dst_drawable_id = *new_layer_id; + } + else + { + *new_image_id = -1; + *new_layer_id = -1; + + gimp_image_undo_group_start (image_id); + gimp_image_resize (image_id, dst_width, dst_height, 0, 0); + + if (gimp_item_is_layer (drawable_id)) + gimp_layer_resize (drawable_id, dst_width, dst_height, 0, 0); + else if (gimp_item_is_layer_mask (drawable_id)) + { + gint32 layer_id = gimp_layer_from_mask (drawable_id); + gimp_layer_resize (layer_id, dst_width, dst_height, 0, 0); + } + + dst_drawable_id = drawable_id; + } + + src_buffer = gimp_drawable_get_buffer (drawable_id); + dst_buffer = gimp_drawable_get_buffer (dst_drawable_id); + + tile_gegl (src_buffer, src_width, src_height, + dst_buffer, dst_width, dst_height); + + gegl_buffer_flush (dst_buffer); + gimp_drawable_update (dst_drawable_id, 0, 0, dst_width, dst_height); + + if (tvals.new_image) + { + gimp_image_undo_enable (*new_image_id); + } + else + { + gimp_image_undo_group_end (image_id); + } + + g_object_unref (src_buffer); + g_object_unref (dst_buffer); +} + +static gboolean +tile_dialog (gint32 image_ID, + gint32 drawable_ID) +{ + GtkWidget *dlg; + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *sizeentry; + GtkWidget *chainbutton; + GtkWidget *toggle; + gint width; + gint height; + gdouble xres; + gdouble yres; + GimpUnit unit; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + width = gimp_drawable_width (drawable_ID); + height = gimp_drawable_height (drawable_ID); + unit = gimp_image_get_unit (image_ID); + gimp_image_get_resolution (image_ID, &xres, &yres); + + tvals.new_width = width; + tvals.new_height = height; + + dlg = gimp_dialog_new (_("Tile"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + frame = gimp_frame_new (_("Tile to New Size")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + sizeentry = gimp_coordinates_new (unit, "%a", TRUE, TRUE, 8, + GIMP_SIZE_ENTRY_UPDATE_SIZE, + + tvals.constrain, TRUE, + + _("_Width:"), width, xres, + 1, GIMP_MAX_IMAGE_SIZE, + 0, width, + + _("_Height:"), height, yres, + 1, GIMP_MAX_IMAGE_SIZE, + 0, height); + gtk_container_add (GTK_CONTAINER (frame), sizeentry); + gtk_table_set_row_spacing (GTK_TABLE (sizeentry), 1, 6); + gtk_widget_show (sizeentry); + + chainbutton = GTK_WIDGET (GIMP_COORDINATES_CHAINBUTTON (sizeentry)); + + toggle = gtk_check_button_new_with_mnemonic (_("C_reate new image")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), tvals.new_image); + gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tvals.new_image); + + gtk_widget_show (dlg); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + if (run) + { + tvals.new_width = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry), 0)); + tvals.new_height = + RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry), 1)); + + tvals.constrain = + gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (chainbutton)); + } + + gtk_widget_destroy (dlg); + + return run; +} diff --git a/plug-ins/common/unit-editor.c b/plug-ins/common/unit-editor.c new file mode 100644 index 0000000..4156fe6 --- /dev/null +++ b/plug-ins/common/unit-editor.c @@ -0,0 +1,700 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This is a plug-in for GIMP. + * + * Copyright (C) 2000 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-unit-editor" +#define PLUG_IN_BINARY "unit-editor" +#define PLUG_IN_ROLE "gimp-unit-editor" +#define RESPONSE_REFRESH 1 + +enum +{ + SAVE, + IDENTIFIER, + FACTOR, + DIGITS, + SYMBOL, + ABBREVIATION, + SINGULAR, + PLURAL, + UNIT, + USER_UNIT, + BG_COLOR, + NUM_COLUMNS +}; + +typedef struct +{ + const gchar *title; + const gchar *help; + +} UnitColumn; + + +static void query (void); +static void run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *n_return_vals, + GimpParam **return_vals); + +static GimpUnit new_unit_dialog (GtkWidget *main_dialog, + GimpUnit template); +static void unit_editor_dialog (void); +static void unit_editor_response (GtkWidget *widget, + gint response_id, + gpointer data); +static void new_callback (GtkAction *action, + GtkTreeView *tv); +static void duplicate_callback (GtkAction *action, + GtkTreeView *tv); +static void saved_toggled_callback (GtkCellRendererToggle *celltoggle, + gchar *path_string, + GtkListStore *list_store); +static void unit_list_init (GtkTreeView *tv); + + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static const UnitColumn columns[] = +{ + { N_("Saved"), N_("A unit definition will only be saved before " + "GIMP exits if this column is checked.") }, + { N_("ID"), N_("This string will be used to identify a " + "unit in GIMP's configuration files.") }, + { N_("Factor"), N_("How many units make up an inch.") }, + { N_("Digits"), N_("This field is a hint for numerical input " + "fields. It specifies how many decimal digits " + "the input field should provide to get " + "approximately the same accuracy as an " + "\"inch\" input field with two decimal digits.") }, + { N_("Symbol"), N_("The unit's symbol if it has one (e.g. \" " + "for inches). The unit's abbreviation is used " + "if doesn't have a symbol.") }, + { N_("Abbreviation"), N_("The unit's abbreviation (e.g. \"cm\" for " + "centimeters).") }, + { N_("Singular"), N_("The unit's singular form.") }, + { N_("Plural"), N_("The unit's plural form.") } +}; + +static GtkActionEntry actions[] = +{ + { "unit-editor-toolbar", NULL, + "Unit Editor Toolbar", NULL, NULL, NULL + }, + + { "unit-editor-new", GIMP_ICON_DOCUMENT_NEW, + NULL, "N", + N_("Create a new unit from scratch"), + G_CALLBACK (new_callback) + }, + + { "unit-editor-duplicate", GIMP_ICON_OBJECT_DUPLICATE, + NULL, "D", + N_("Create a new unit using the currently selected unit as template"), + G_CALLBACK (duplicate_callback) + } +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Create or alter units used in GIMP"), + "The GIMP unit editor", + "Michael Natterer ", + "Michael Natterer ", + "2000", + N_("U_nits"), + "", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Edit/Preferences"); + gimp_plugin_icon_register (PLUG_IN_PROC, GIMP_ICON_TYPE_ICON_NAME, + (const guint8 *) GIMP_ICON_TOOL_MEASURE); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + + INIT_I18N (); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_CALLING_ERROR; + + if (strcmp (name, PLUG_IN_PROC) == 0) + { + values[0].data.d_status = GIMP_PDB_SUCCESS; + + unit_editor_dialog (); + } +} + +static GimpUnit +new_unit_dialog (GtkWidget *main_dialog, + GimpUnit template) +{ + GtkWidget *dialog; + GtkWidget *table; + GtkWidget *entry; + GtkWidget *spinbutton; + + GtkWidget *identifier_entry; + GtkAdjustment *factor_adj; + GtkAdjustment *digits_adj; + GtkWidget *symbol_entry; + GtkWidget *abbreviation_entry; + GtkWidget *singular_entry; + GtkWidget *plural_entry; + + GimpUnit unit = GIMP_UNIT_PIXEL; + + dialog = gimp_dialog_new (_("Add a New Unit"), PLUG_IN_ROLE, + main_dialog, GTK_DIALOG_MODAL, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Add"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + table = gtk_table_new (7, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + table, FALSE, FALSE, 0); + gtk_widget_show (table); + + entry = identifier_entry = gtk_entry_new (); + if (template != GIMP_UNIT_PIXEL) + { + gtk_entry_set_text (GTK_ENTRY (entry), + gimp_unit_get_identifier (template)); + } + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_ID:"), 0.0, 0.5, + entry, 1, FALSE); + + gimp_help_set_help_data (entry, gettext (columns[IDENTIFIER].help), NULL); + + factor_adj = (GtkAdjustment *) + gtk_adjustment_new ((template != GIMP_UNIT_PIXEL) ? + gimp_unit_get_factor (template) : 1.0, + GIMP_MIN_RESOLUTION, GIMP_MAX_RESOLUTION, + 0.01, 0.1, 0.0); + spinbutton = gimp_spin_button_new (factor_adj, 0.01, 5); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Factor:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + gimp_help_set_help_data (spinbutton, gettext (columns[FACTOR].help), NULL); + + digits_adj = (GtkAdjustment *) + gtk_adjustment_new ((template != GIMP_UNIT_PIXEL) ? + gimp_unit_get_digits (template) : 2.0, + 0, 5, 1, 1, 0); + spinbutton = gimp_spin_button_new (digits_adj, 0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_Digits:"), 0.0, 0.5, + spinbutton, 1, TRUE); + + gimp_help_set_help_data (spinbutton, gettext (columns[DIGITS].help), NULL); + + entry = symbol_entry = gtk_entry_new (); + if (template != GIMP_UNIT_PIXEL) + { + gtk_entry_set_text (GTK_ENTRY (entry), + gimp_unit_get_symbol (template)); + } + gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, + _("_Symbol:"), 0.0, 0.5, + entry, 1, FALSE); + + gimp_help_set_help_data (entry, gettext (columns[SYMBOL].help), NULL); + + entry = abbreviation_entry = gtk_entry_new (); + if (template != GIMP_UNIT_PIXEL) + { + gtk_entry_set_text (GTK_ENTRY (entry), + gimp_unit_get_abbreviation (template)); + } + gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, + _("_Abbreviation:"), 0.0, 0.5, + entry, 1, FALSE); + + gimp_help_set_help_data (entry, gettext (columns[ABBREVIATION].help), NULL); + + entry = singular_entry = gtk_entry_new (); + if (template != GIMP_UNIT_PIXEL) + { + gtk_entry_set_text (GTK_ENTRY (entry), + gimp_unit_get_singular (template)); + } + gimp_table_attach_aligned (GTK_TABLE (table), 0, 5, + _("Si_ngular:"), 0.0, 0.5, + entry, 1, FALSE); + + gimp_help_set_help_data (entry, gettext (columns[SINGULAR].help), NULL); + + entry = plural_entry = gtk_entry_new (); + if (template != GIMP_UNIT_PIXEL) + { + gtk_entry_set_text (GTK_ENTRY (entry), + gimp_unit_get_plural (template)); + } + gimp_table_attach_aligned (GTK_TABLE (table), 0, 6, + _("_Plural:"), 0.0, 0.5, + entry, 1, FALSE); + + gimp_help_set_help_data (entry, gettext (columns[PLURAL].help), NULL); + + gtk_widget_show (dialog); + + while (TRUE) + { + gchar *identifier; + gdouble factor; + gint digits; + gchar *symbol; + gchar *abbreviation; + gchar *singular; + gchar *plural; + + if (gimp_dialog_run (GIMP_DIALOG (dialog)) != GTK_RESPONSE_OK) + break; + + identifier = g_strdup (gtk_entry_get_text (GTK_ENTRY (identifier_entry))); + factor = gtk_adjustment_get_value (GTK_ADJUSTMENT (factor_adj)); + digits = gtk_adjustment_get_value (GTK_ADJUSTMENT (digits_adj)); + symbol = g_strdup (gtk_entry_get_text (GTK_ENTRY (symbol_entry))); + abbreviation = g_strdup (gtk_entry_get_text (GTK_ENTRY (abbreviation_entry))); + singular = g_strdup (gtk_entry_get_text (GTK_ENTRY (singular_entry))); + plural = g_strdup (gtk_entry_get_text (GTK_ENTRY (plural_entry))); + + identifier = g_strstrip (identifier); + symbol = g_strstrip (symbol); + abbreviation = g_strstrip (abbreviation); + singular = g_strstrip (singular); + plural = g_strstrip (plural); + + if (! strlen (identifier) || + ! strlen (symbol) || + ! strlen (abbreviation) || + ! strlen (singular) || + ! strlen (plural)) + { + GtkWidget *msg = gtk_message_dialog_new (GTK_WINDOW (dialog), 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Incomplete input")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (msg), + _("Please fill in all text fields.")); + gtk_dialog_run (GTK_DIALOG (msg)); + gtk_widget_destroy (msg); + + continue; + } + + unit = gimp_unit_new (identifier, + factor, digits, + symbol, abbreviation, singular, plural); + + g_free (identifier); + g_free (symbol); + g_free (abbreviation); + g_free (singular); + g_free (plural); + + break; + } + + gtk_widget_destroy (dialog); + + return unit; +} + +static void +unit_editor_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *scrolled_win; + GtkUIManager *ui_manager; + GtkActionGroup *group; + GtkWidget *toolbar; + GtkListStore *list_store; + GtkWidget *tv; + GtkTreeViewColumn *col; + GtkWidget *col_widget; + GtkWidget *button; + GtkCellRenderer *rend; + gint i; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + list_store = gtk_list_store_new (NUM_COLUMNS, + G_TYPE_BOOLEAN, /* SAVE */ + G_TYPE_STRING, /* IDENTIFIER */ + G_TYPE_DOUBLE, /* FACTOR */ + G_TYPE_INT, /* DIGITS */ + G_TYPE_STRING, /* SYMBOL */ + G_TYPE_STRING, /* ABBREVIATION */ + G_TYPE_STRING, /* SINGULAR */ + G_TYPE_STRING, /* PLURAL */ + GIMP_TYPE_UNIT, /* UNIT */ + G_TYPE_BOOLEAN, /* USER_UNIT */ + GDK_TYPE_COLOR); /* BG_COLOR */ + + tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list_store)); + g_object_unref (list_store); + + dialog = gimp_dialog_new (_("Unit Editor"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Refresh"), RESPONSE_REFRESH, + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE); + + g_signal_connect (dialog, "response", + G_CALLBACK (unit_editor_response), + tv); + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_main_quit), + NULL); + + /* the toolbar */ + ui_manager = gtk_ui_manager_new (); + + group = gtk_action_group_new ("unit-editor"); + + gtk_action_group_set_translation_domain (group, NULL); + gtk_action_group_add_actions (group, actions, G_N_ELEMENTS (actions), tv); + + gtk_window_add_accel_group (GTK_WINDOW (dialog), + gtk_ui_manager_get_accel_group (ui_manager)); + gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager)); + + gtk_ui_manager_insert_action_group (ui_manager, group, -1); + g_object_unref (group); + + gtk_ui_manager_add_ui_from_string + (ui_manager, + "\n" + " \n" + " \n" + " \n" + " \n" + "\n", + -1, NULL); + + toolbar = gtk_ui_manager_get_widget (ui_manager, "/unit-editor-toolbar"); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + toolbar, FALSE, FALSE, 0); + gtk_widget_show (toolbar); + + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_NEVER, + GTK_POLICY_ALWAYS); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + scrolled_win, TRUE, TRUE, 0); + gtk_widget_show (scrolled_win); + + gtk_widget_set_size_request (tv, -1, 220); + gtk_container_add (GTK_CONTAINER (scrolled_win), tv); + gtk_widget_show (tv); + + rend = gtk_cell_renderer_toggle_new (); + col = + gtk_tree_view_column_new_with_attributes (gettext (columns[SAVE].title), + rend, + "active", SAVE, + "activatable", USER_UNIT, + "cell-background-gdk", BG_COLOR, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), col); + + col_widget = gtk_tree_view_column_get_widget (col); + if (col_widget) + { + button = gtk_widget_get_ancestor (col_widget, GTK_TYPE_BUTTON); + + if (button) + gimp_help_set_help_data (button, + gettext (columns[SAVE].help), NULL); + } + + g_signal_connect (rend, "toggled", + G_CALLBACK (saved_toggled_callback), + list_store); + + for (i = 0; i < G_N_ELEMENTS (columns); i++) + { + if (i == SAVE) + continue; + + col = + gtk_tree_view_column_new_with_attributes (gettext (columns[i].title), + gtk_cell_renderer_text_new (), + "text", i, + "cell-background-gdk", BG_COLOR, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), col); + + col_widget = gtk_tree_view_column_get_widget (col); + if (col_widget) + { + button = gtk_widget_get_ancestor (col_widget, GTK_TYPE_BUTTON); + + if (button) + gimp_help_set_help_data (button, gettext (columns[i].help), NULL); + } + } + + unit_list_init (GTK_TREE_VIEW (tv)); + + gtk_widget_show (dialog); + + gtk_main (); +} + +static void +unit_editor_response (GtkWidget *widget, + gint response_id, + gpointer data) +{ + switch (response_id) + { + case RESPONSE_REFRESH: + unit_list_init (GTK_TREE_VIEW (data)); + break; + + default: + gtk_widget_destroy (widget); + break; + } +} + +static void +new_callback (GtkAction *action, + GtkTreeView *tv) +{ + GimpUnit unit; + + unit = new_unit_dialog (gtk_widget_get_toplevel (GTK_WIDGET (tv)), + GIMP_UNIT_PIXEL); + + if (unit != GIMP_UNIT_PIXEL) + { + GtkTreeModel *model; + GtkTreeIter iter; + + unit_list_init (tv); + + model = gtk_tree_view_get_model (tv); + + if (gtk_tree_model_get_iter_first (model, &iter) && + gtk_tree_model_iter_nth_child (model, &iter, + NULL, unit - GIMP_UNIT_INCH)) + { + GtkAdjustment *adj; + + gtk_tree_selection_select_iter (gtk_tree_view_get_selection (tv), + &iter); + + adj = gtk_tree_view_get_vadjustment (tv); + gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj)); + } + } +} + +static void +duplicate_callback (GtkAction *action, + GtkTreeView *tv) +{ + GtkTreeModel *model; + GtkTreeSelection *sel; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (tv); + sel = gtk_tree_view_get_selection (tv); + + if (gtk_tree_selection_get_selected (sel, NULL, &iter)) + { + GimpUnit unit; + + gtk_tree_model_get (model, &iter, + UNIT, &unit, + -1); + + unit = new_unit_dialog (gtk_widget_get_toplevel (GTK_WIDGET (tv)), + unit); + + if (unit != GIMP_UNIT_PIXEL) + { + GtkTreeIter iter; + + unit_list_init (tv); + + if (gtk_tree_model_get_iter_first (model, &iter) && + gtk_tree_model_iter_nth_child (model, &iter, + NULL, unit - GIMP_UNIT_INCH)) + { + GtkAdjustment *adj; + + gtk_tree_selection_select_iter (sel, &iter); + + adj = gtk_tree_view_get_vadjustment (tv); + gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj)); + } + } + } +} + +static void +saved_toggled_callback (GtkCellRendererToggle *celltoggle, + gchar *path_string, + GtkListStore *list_store) +{ + GtkTreePath *path; + GtkTreeIter iter; + gboolean saved; + GimpUnit unit; + + path = gtk_tree_path_new_from_string (path_string); + + if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (list_store), &iter, path)) + { + g_warning ("%s: bad tree path?", G_STRLOC); + return; + } + gtk_tree_path_free (path); + + gtk_tree_model_get (GTK_TREE_MODEL (list_store), &iter, + SAVE, &saved, + UNIT, &unit, + -1); + + if (unit >= gimp_unit_get_number_of_built_in_units ()) + { + gimp_unit_set_deletion_flag (unit, saved); + gtk_list_store_set (GTK_LIST_STORE (list_store), &iter, + SAVE, ! saved, + -1); + } +} + +static void +unit_list_init (GtkTreeView *tv) +{ + GtkListStore *list_store; + GtkTreeIter iter; + gint num_units; + GimpUnit unit; + GdkColor color; + + list_store = GTK_LIST_STORE (gtk_tree_view_get_model (tv)); + + gtk_list_store_clear (list_store); + + num_units = gimp_unit_get_number_of_units (); + + color.red = 0xdddd; + color.green = 0xdddd; + color.blue = 0xffff; + + for (unit = GIMP_UNIT_INCH; unit < num_units; unit++) + { + gboolean user_unit = (unit >= gimp_unit_get_number_of_built_in_units ()); + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + SAVE, ! gimp_unit_get_deletion_flag (unit), + IDENTIFIER, gimp_unit_get_identifier (unit), + FACTOR, gimp_unit_get_factor (unit), + DIGITS, gimp_unit_get_digits (unit), + SYMBOL, gimp_unit_get_symbol (unit), + ABBREVIATION, gimp_unit_get_abbreviation (unit), + SINGULAR, gimp_unit_get_singular (unit), + PLURAL, gimp_unit_get_plural (unit), + UNIT, unit, + USER_UNIT, user_unit, + + user_unit ? -1 : BG_COLOR, &color, + + -1); + } + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter)) + gtk_tree_selection_select_iter (gtk_tree_view_get_selection (tv), &iter); +} diff --git a/plug-ins/common/van-gogh-lic.c b/plug-ins/common/van-gogh-lic.c new file mode 100644 index 0000000..9bb5dc8 --- /dev/null +++ b/plug-ins/common/van-gogh-lic.c @@ -0,0 +1,904 @@ +/* LIC 0.14 -- image filter plug-in for GIMP + * Copyright (C) 1996 Tom Bech + * + * E-mail: tomb@gimp.org + * You can contact the original GIMP authors at gimp@xcf.berkeley.edu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * In other words, you can't sue me for whatever happens while using this ;) + * + * Changes (post 0.10): + * -> 0.11: Fixed a bug in the convolution kernels (Tom). + * -> 0.12: Added Quartic's bilinear interpolation stuff (Tom). + * -> 0.13 Changed some UI stuff causing trouble with the 0.60 release, added + * the (GIMP) tags and changed random() calls to rand() (Tom) + * -> 0.14 Ported to 0.99.11 (Tom) + * + * This plug-in implements the Line Integral Convolution (LIC) as described in + * Cabral et al. "Imaging vector fields using line integral convolution" in the + * Proceedings of ACM SIGGRAPH 93. Publ. by ACM, New York, NY, USA. p. 263-270. + * (See http://www8.cs.umu.se/kurser/TDBD13/VT00/extra/p263-cabral.pdf) + * + * Some of the code is based on code by Steinar Haugen (thanks!), the Perlin + * noise function is practically ripped as is :) + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/************/ +/* Typedefs */ +/************/ + +#define numx 40 /* Pseudo-random vector grid size */ +#define numy 40 + +#define PLUG_IN_PROC "plug-in-lic" +#define PLUG_IN_BINARY "van-gogh-lic" +#define PLUG_IN_ROLE "gimp-van-gogh-lic" + +typedef enum +{ + LIC_HUE, + LIC_SATURATION, + LIC_BRIGHTNESS +} LICEffectChannel; + + +/*****************************/ +/* Global variables and such */ +/*****************************/ + +static gdouble G[numx][numy][2]; + +typedef struct +{ + gdouble filtlen; + gdouble noisemag; + gdouble intsteps; + gdouble minv; + gdouble maxv; + gint effect_channel; + gint effect_operator; + gint effect_convolve; + gint32 effect_image_id; +} LicValues; + +static LicValues licvals; + +static gdouble l = 10.0; +static gdouble dx = 2.0; +static gdouble dy = 2.0; +static gdouble minv = -2.5; +static gdouble maxv = 2.5; +static gdouble isteps = 20.0; + +static gboolean source_drw_has_alpha = FALSE; + +static gint effect_width, effect_height; +static gint border_x, border_y, border_w, border_h; + +static GtkWidget *dialog; + +/************************/ +/* Convenience routines */ +/************************/ + +static void +peek (GeglBuffer *buffer, + gint x, + gint y, + GimpRGB *color) +{ + gegl_buffer_sample (buffer, x, y, NULL, + color, babl_format ("R'G'B'A double"), + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); +} + +static void +poke (GeglBuffer *buffer, + gint x, + gint y, + GimpRGB *color) +{ + gegl_buffer_set (buffer, GEGL_RECTANGLE (x, y, 1, 1), 0, + babl_format ("R'G'B'A double"), color, + GEGL_AUTO_ROWSTRIDE); +} + +static gint +peekmap (const guchar *image, + gint x, + gint y) +{ + while (x < 0) + x += effect_width; + x %= effect_width; + + while (y < 0) + y += effect_height; + y %= effect_height; + + return (gint) image[x + effect_width * y]; +} + +/*************/ +/* Main part */ +/*************/ + +/***************************************************/ +/* Compute the derivative in the x and y direction */ +/* We use these convolution kernels: */ +/* |1 0 -1| | 1 2 1| */ +/* DX: |2 0 -2| DY: | 0 0 0| */ +/* |1 0 -1| | -1 -2 -1| */ +/* (It's a variation of the Sobel kernels, really) */ +/***************************************************/ + +static gint +gradx (const guchar *image, + gint x, + gint y) +{ + gint val = 0; + + val = val + peekmap (image, x-1, y-1); + val = val - peekmap (image, x+1, y-1); + + val = val + 2 * peekmap (image, x-1, y); + val = val - 2 * peekmap (image, x+1, y); + + val = val + peekmap (image, x-1, y+1); + val = val - peekmap (image, x+1, y+1); + + return val; +} + +static gint +grady (const guchar *image, + gint x, + gint y) +{ + gint val = 0; + + val = val + peekmap (image, x-1, y-1); + val = val + 2 * peekmap (image, x, y-1); + val = val + peekmap (image, x+1, y-1); + + val = val - peekmap (image, x-1, y+1); + val = val - 2 * peekmap (image, x, y+1); + val = val - peekmap (image, x+1, y+1); + + return val; +} + +/************************************/ +/* A nice 2nd order cubic spline :) */ +/************************************/ + +static gdouble +cubic (gdouble t) +{ + gdouble at = fabs (t); + + return (at < 1.0) ? at * at * (2.0 * at - 3.0) + 1.0 : 0.0; +} + +static gdouble +omega (gdouble u, + gdouble v, + gint i, + gint j) +{ + while (i < 0) + i += numx; + + while (j < 0) + j += numy; + + i %= numx; + j %= numy; + + return cubic (u) * cubic (v) * (G[i][j][0]*u + G[i][j][1]*v); +} + +/*************************************************************/ +/* The noise function (2D variant of Perlins noise function) */ +/*************************************************************/ + +static gdouble +noise (gdouble x, + gdouble y) +{ + gint i, sti = (gint) floor (x / dx); + gint j, stj = (gint) floor (y / dy); + + gdouble sum = 0.0; + + /* Calculate the gdouble sum */ + /* ======================== */ + + for (i = sti; i <= sti + 1; i++) + for (j = stj; j <= stj + 1; j++) + sum += omega ((x - (gdouble) i * dx) / dx, + (y - (gdouble) j * dy) / dy, + i, j); + + return sum; +} + +/*************************************************/ +/* Generates pseudo-random vectors with length 1 */ +/*************************************************/ + +static void +generatevectors (void) +{ + gdouble alpha; + gint i, j; + GRand *gr; + + gr = g_rand_new(); + + for (i = 0; i < numx; i++) + { + for (j = 0; j < numy; j++) + { + alpha = g_rand_double_range (gr, 0, 2) * G_PI; + G[i][j][0] = cos (alpha); + G[i][j][1] = sin (alpha); + } + } + + g_rand_free (gr); +} + +/* A simple triangle filter */ +/* ======================== */ + +static gdouble +filter (gdouble u) +{ + gdouble f = 1.0 - fabs (u) / l; + + return (f < 0.0) ? 0.0 : f; +} + +/******************************************************/ +/* Compute the Line Integral Convolution (LIC) at x,y */ +/******************************************************/ + +static gdouble +lic_noise (gint x, + gint y, + gdouble vx, + gdouble vy) +{ + gdouble i = 0.0; + gdouble f1 = 0.0, f2 = 0.0; + gdouble u, step = 2.0 * l / isteps; + gdouble xx = (gdouble) x, yy = (gdouble) y; + gdouble c, s; + + /* Get vector at x,y */ + /* ================= */ + + c = vx; + s = vy; + + /* Calculate integral numerically */ + /* ============================== */ + + f1 = filter (-l) * noise (xx + l * c , yy + l * s); + + for (u = -l + step; u <= l; u += step) + { + f2 = filter (u) * noise ( xx - u * c , yy - u * s); + i += (f1 + f2) * 0.5 * step; + f1 = f2; + } + + i = (i - minv) / (maxv - minv); + + i = CLAMP (i, 0.0, 1.0); + + i = (i / 2.0) + 0.5; + + return i; +} + +static void +getpixel (GeglBuffer *buffer, + GimpRGB *p, + gdouble u, + gdouble v) +{ + register gint x1, y1, x2, y2; + gint width, height; + static GimpRGB pp[4]; + + width = border_w; + height = border_h; + + x1 = (gint)u; + y1 = (gint)v; + + if (x1 < 0) + x1 = width - (-x1 % width); + else + x1 = x1 % width; + + if (y1 < 0) + y1 = height - (-y1 % height); + else + y1 = y1 % height; + + x2 = (x1 + 1) % width; + y2 = (y1 + 1) % height; + + peek (buffer, x1, y1, &pp[0]); + peek (buffer, x2, y1, &pp[1]); + peek (buffer, x1, y2, &pp[2]); + peek (buffer, x2, y2, &pp[3]); + + if (source_drw_has_alpha) + *p = gimp_bilinear_rgba (u, v, pp); + else + *p = gimp_bilinear_rgb (u, v, pp); +} + +static void +lic_image (GeglBuffer *buffer, + gint x, + gint y, + gdouble vx, + gdouble vy, + GimpRGB *color) +{ + gdouble u, step = 2.0 * l / isteps; + gdouble xx = (gdouble) x, yy = (gdouble) y; + gdouble c, s; + GimpRGB col = { 0, 0, 0, 0 }; + GimpRGB col1, col2, col3; + + /* Get vector at x,y */ + /* ================= */ + + c = vx; + s = vy; + + /* Calculate integral numerically */ + /* ============================== */ + + getpixel (buffer, &col1, xx + l * c, yy + l * s); + + if (source_drw_has_alpha) + gimp_rgba_multiply (&col1, filter (-l)); + else + gimp_rgb_multiply (&col1, filter (-l)); + + for (u = -l + step; u <= l; u += step) + { + getpixel (buffer, &col2, xx - u * c, yy - u * s); + + if (source_drw_has_alpha) + { + gimp_rgba_multiply (&col2, filter (u)); + + col3 = col1; + gimp_rgba_add (&col3, &col2); + gimp_rgba_multiply (&col3, 0.5 * step); + gimp_rgba_add (&col, &col3); + } + else + { + gimp_rgb_multiply (&col2, filter (u)); + + col3 = col1; + gimp_rgb_add (&col3, &col2); + gimp_rgb_multiply (&col3, 0.5 * step); + gimp_rgb_add (&col, &col3); + } + col1 = col2; + } + if (source_drw_has_alpha) + gimp_rgba_multiply (&col, 1.0 / l); + else + gimp_rgb_multiply (&col, 1.0 / l); + gimp_rgb_clamp (&col); + + *color = col; +} + +static guchar * +rgb_to_hsl (gint32 drawable_ID, + LICEffectChannel effect_channel) +{ + GeglBuffer *buffer; + guchar *themap, data[4]; + gint x, y; + GimpRGB color; + GimpHSL color_hsl; + gdouble val = 0.0; + glong maxc, index = 0; + GRand *gr; + + gr = g_rand_new (); + + maxc = gimp_drawable_width (drawable_ID) * gimp_drawable_height (drawable_ID); + + buffer = gimp_drawable_get_buffer (drawable_ID); + + themap = g_new (guchar, maxc); + + for (y = 0; y < border_h; y++) + { + for (x = 0; x < border_w; x++) + { + data[3] = 255; + + gegl_buffer_sample (buffer, x, y, NULL, + data, babl_format ("R'G'B'A u8"), + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); + + gimp_rgba_set_uchar (&color, data[0], data[1], data[2], data[3]); + gimp_rgb_to_hsl (&color, &color_hsl); + + switch (effect_channel) + { + case LIC_HUE: + val = color_hsl.h * 255; + break; + case LIC_SATURATION: + val = color_hsl.s * 255; + break; + case LIC_BRIGHTNESS: + val = color_hsl.l * 255; + break; + } + + /* add some random to avoid unstructured areas. */ + val += g_rand_double_range (gr, -1.0, 1.0); + + themap[index++] = (guchar) CLAMP0255 (RINT (val)); + } + } + + g_object_unref (buffer); + + g_rand_free (gr); + + return themap; +} + + +static void +compute_lic (gint32 drawable_ID, + const guchar *scalarfield, + gboolean rotate) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + gint xcount, ycount; + GimpRGB color; + gdouble vx, vy, tmp; + + src_buffer = gimp_drawable_get_buffer (drawable_ID); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID); + + for (ycount = 0; ycount < border_h; ycount++) + { + for (xcount = 0; xcount < border_w; xcount++) + { + /* Get derivative at (x,y) and normalize it */ + /* ============================================================== */ + + vx = gradx (scalarfield, xcount, ycount); + vy = grady (scalarfield, xcount, ycount); + + /* Rotate if needed */ + if (rotate) + { + tmp = vy; + vy = -vx; + vx = tmp; + } + + tmp = sqrt (vx * vx + vy * vy); + if (tmp >= 0.000001) + { + tmp = 1.0 / tmp; + vx *= tmp; + vy *= tmp; + } + + /* Convolve with the LIC at (x,y) */ + /* ============================== */ + + if (licvals.effect_convolve == 0) + { + peek (src_buffer, xcount, ycount, &color); + + tmp = lic_noise (xcount, ycount, vx, vy); + + if (source_drw_has_alpha) + gimp_rgba_multiply (&color, tmp); + else + gimp_rgb_multiply (&color, tmp); + } + else + { + lic_image (src_buffer, xcount, ycount, vx, vy, &color); + } + + poke (dest_buffer, xcount, ycount, &color); + } + + gimp_progress_update ((gfloat) ycount / (gfloat) border_h); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_progress_update (1.0); +} + +static void +compute_image (gint32 drawable_ID) +{ + guchar *scalarfield = NULL; + + /* Get some useful info on the input drawable */ + /* ========================================== */ + if (! gimp_drawable_mask_intersect (drawable_ID, + &border_x, &border_y, + &border_w, &border_h)) + return; + + gimp_progress_init (_("Van Gogh (LIC)")); + + if (licvals.effect_convolve == 0) + generatevectors (); + + if (licvals.filtlen < 0.1) + licvals.filtlen = 0.1; + + l = licvals.filtlen; + dx = dy = licvals.noisemag; + minv = licvals.minv / 10.0; + maxv = licvals.maxv / 10.0; + isteps = licvals.intsteps; + + source_drw_has_alpha = gimp_drawable_has_alpha (drawable_ID); + + effect_width = gimp_drawable_width (licvals.effect_image_id); + effect_height = gimp_drawable_height (licvals.effect_image_id); + + switch (licvals.effect_channel) + { + case 0: + scalarfield = rgb_to_hsl (licvals.effect_image_id, LIC_HUE); + break; + case 1: + scalarfield = rgb_to_hsl (licvals.effect_image_id, LIC_SATURATION); + break; + case 2: + scalarfield = rgb_to_hsl (licvals.effect_image_id, LIC_BRIGHTNESS); + break; + } + + compute_lic (drawable_ID, scalarfield, licvals.effect_operator); + + g_free (scalarfield); + + /* Update image */ + /* ============ */ + + gimp_drawable_merge_shadow (drawable_ID, TRUE); + gimp_drawable_update (drawable_ID, border_x, border_y, border_w, border_h); + + gimp_displays_flush (); +} + +/**************************/ +/* Below is only UI stuff */ +/**************************/ + +static gboolean +effect_image_constrain (gint32 image_id, + gint32 drawable_id, + gpointer data) +{ + return gimp_drawable_is_rgb (drawable_id); +} + +static gboolean +create_main_dialog (void) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *combo; + GtkObject *scale_data; + gint row; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Van Gogh (LIC)"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + frame = gimp_int_radio_group_new (TRUE, _("Effect Channel"), + G_CALLBACK (gimp_radio_button_update), + &licvals.effect_channel, + licvals.effect_channel, + + _("_Hue"), 0, NULL, + _("_Saturation"), 1, NULL, + _("_Brightness"), 2, NULL, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + frame = gimp_int_radio_group_new (TRUE, _("Effect Operator"), + G_CALLBACK (gimp_radio_button_update), + &licvals.effect_operator, + licvals.effect_operator, + + _("_Derivative"), 0, NULL, + _("_Gradient"), 1, NULL, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + frame = gimp_int_radio_group_new (TRUE, _("Convolve"), + G_CALLBACK (gimp_radio_button_update), + &licvals.effect_convolve, + licvals.effect_convolve, + + _("_With white noise"), 0, NULL, + _("W_ith source image"), 1, NULL, + + NULL); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + /* Effect image menu */ + table = gtk_table_new (1, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + combo = gimp_drawable_combo_box_new (effect_image_constrain, NULL); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + licvals.effect_image_id, + G_CALLBACK (gimp_int_combo_box_get_active), + &licvals.effect_image_id); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Effect image:"), 0.0, 0.5, combo, 2, TRUE); + + table = gtk_table_new (5, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + row = 0; + + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("_Filter length:"), 0, 6, + licvals.filtlen, 0.1, 64, 1.0, 8.0, 1, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &licvals.filtlen); + + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("_Noise magnitude:"), 0, 6, + licvals.noisemag, 1, 5, 0.1, 1.0, 1, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &licvals.noisemag); + + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("In_tegration steps:"), 0, 6, + licvals.intsteps, 1, 40, 1.0, 5.0, 1, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &licvals.intsteps); + + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("_Minimum value:"), 0, 6, + licvals.minv, -100, 0, 1, 10, 1, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &licvals.minv); + + scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, row++, + _("M_aximum value:"), 0, 6, + licvals.maxv, 0, 100, 1, 10, 1, + TRUE, 0, 0, + NULL, NULL); + g_signal_connect (scale_data, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &licvals.maxv); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +/*************************************/ +/* Set parameters to standard values */ +/*************************************/ + +static void +set_default_settings (void) +{ + licvals.filtlen = 5; + licvals.noisemag = 2; + licvals.intsteps = 25; + licvals.minv = -25; + licvals.maxv = 25; + licvals.effect_channel = 2; + licvals.effect_operator = 1; + licvals.effect_convolve = 1; + licvals.effect_image_id = 0; +} + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Special effects that nobody understands"), + "No help yet", + "Tom Bech & Federico Mena Quintero", + "Tom Bech & Federico Mena Quintero", + "Version 0.14, September 24 1997", + N_("_Van Gogh (LIC)..."), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Artistic"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + gint32 drawable_ID; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + /* Set default values */ + /* ================== */ + + set_default_settings (); + + /* Possibly retrieve data */ + /* ====================== */ + + gimp_get_data (PLUG_IN_PROC, &licvals); + + run_mode = param[0].data.d_int32; + drawable_ID = param[2].data.d_drawable; + + if (status == GIMP_PDB_SUCCESS) + { + /* Make sure that the drawable is RGBA or RGB color */ + /* ================================================ */ + + if (gimp_drawable_is_rgb (drawable_ID)) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + if (create_main_dialog ()) + compute_image (drawable_ID); + + gimp_set_data (PLUG_IN_PROC, &licvals, sizeof (LicValues)); + break; + + case GIMP_RUN_WITH_LAST_VALS: + compute_image (drawable_ID); + break; + + default: + break; + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + values[0].data.d_status = status; +} + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +MAIN () diff --git a/plug-ins/common/warp.c b/plug-ins/common/warp.c new file mode 100644 index 0000000..1d66ac6 --- /dev/null +++ b/plug-ins/common/warp.c @@ -0,0 +1,1793 @@ +/* Warp --- image filter plug-in for GIMP + * Copyright (C) 1997 John P. Beale + * Much of the 'warp' is from the Displace plug-in: 1996 Stephen Robert Norris + * Much of the 'displace' code taken in turn from the pinch plug-in + * which is by 1996 Federico Mena Quintero + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * You can contact me (the warp author) at beale@best.com + * Please send me any patches or enhancements to this code. + * You can contact the original GIMP authors at gimp@xcf.berkeley.edu + * + * -------------------------------------------------------------------- + * Warp Program structure: after running the user interface and setting the + * parameters, warp generates a brand-new image (later to be deleted + * before the user ever sees it) which contains two grayscale layers, + * representing the X and Y gradients of the "control" image. For this + * purpose, all channels of the control image are summed for a scalar + * value at each pixel coordinate for the gradient operation. + * + * The X,Y components of the calculated gradient are then used to + * displace pixels from the source image into the destination + * image. The displacement vector is rotated a user-specified amount + * first. This displacement operation happens iteratively, generating + * a new displaced image from each prior image. + * ------------------------------------------------------------------- + * + * Revision History: + * Version 0.37 12/19/98 Fixed Tooltips and freeing memory + * Version 0.36 11/9/97 Changed XY vector layers back to own image + * fixed 'undo' problem (hopefully) + * + * Version 0.35 11/3/97 Added vector-map, mag-map, grad-map to + * diff vector instead of separate operation + * further futzing with drawable updates + * starting adding tooltips + * + * Version 0.34 10/30/97 'Fixed' drawable update problem + * Added 16-bit resolution to differential map + * Added substep increments for finer control + * + * Version 0.33 10/26/97 Added 'angle increment' to user interface + * + * Version 0.32 10/25/97 Added magnitude control map (secondary control) + * Changed undo behavior to be one undo-step per warp call. + * + * Version 0.31 10/25/97 Fixed src/dest pixregions so program works + * with multiple-layer images. Still don't know + * exactly what I did to fix it :-/ Also, added 'color' option + * for border pixels to use the current selected foreground color. + * + * Version 0.3 10/20/97 Initial release for Gimp 0.99.xx + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +/* Some useful macros */ + +#define PLUG_IN_PROC "plug-in-warp" +#define PLUG_IN_BINARY "warp" +#define PLUG_IN_ROLE "gimp-warp" +#define ENTRY_WIDTH 75 +#define MIN_ARGS 6 /* minimum number of arguments required */ + +enum +{ + WRAP, + SMEAR, + BLACK, + COLOR +}; + +typedef struct +{ + gdouble amount; + gint warp_map; + gint iter; + gdouble dither; + gdouble angle; + gint wrap_type; + gint mag_map; + gint mag_use; + gint substeps; + gint grad_map; + gdouble grad_scale; + gint vector_map; + gdouble vector_scale; + gdouble vector_angle; +} WarpVals; + + +/* + * Function prototypes. + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void blur16 (gint32 drawable_id); + +static void diff (gint32 drawable_id, + gint32 *xl_id, + gint32 *yl_id); + +static void diff_prepare_row (GeglBuffer *buffer, + const Babl *format, + guchar *data, + gint x, + gint y, + gint w); + +static void warp_one (gint32 draw_id, + gint32 new_id, + gint32 map_x_id, + gint32 map_y_id, + gint32 mag_draw_id, + gboolean first_time, + gint step); + +static void warp (gint32 drawable_id); + +static gboolean warp_dialog (gint32 drawable_id); +static void warp_pixel (GeglBuffer *buffer, + const Babl *format, + gint width, + gint height, + gint x1, + gint y1, + gint x2, + gint y2, + gint x, + gint y, + guchar *pixel); + +static gboolean warp_map_constrain (gint32 image_id, + gint32 drawable_id, + gpointer data); +static gdouble warp_map_mag_give_value (guchar *pt, + gint alpha, + gint bytes); + +/* -------------------------------------------------------------------------- */ +/* Variables global over entire plug-in scope */ +/* -------------------------------------------------------------------------- */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static WarpVals dvals = +{ + 10.0, /* amount */ + -1, /* warp_map */ + 5, /* iterations */ + 0.0, /* dither */ + 90.0, /* angle */ + WRAP, /* wrap_type */ + -1, /* mag_map */ + FALSE, /* mag_use */ + 1, /* substeps */ + -1, /* grad_map */ + 0.0, /* grad_scale */ + -1, /* vector_map */ + 0.0, /* vector_scale */ + 0.0 /* vector_angle */ +}; + +/* -------------------------------------------------------------------------- */ + +static gint progress = 0; /* progress indicator bar */ +static GimpRunMode run_mode; /* interactive, non-, etc. */ +static guchar color_pixel[4] = {0, 0, 0, 255}; /* current fg color */ + +/* -------------------------------------------------------------------------- */ + +/***** Functions *****/ + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_FLOAT, "amount", "Pixel displacement multiplier" }, + { GIMP_PDB_DRAWABLE, "warp-map", "Displacement control map" }, + { GIMP_PDB_INT32, "iter", "Iteration count (last required argument)" }, + { GIMP_PDB_FLOAT, "dither", "Random dither amount (first optional argument)" }, + { GIMP_PDB_FLOAT, "angle", "Angle of gradient vector rotation" }, + { GIMP_PDB_INT32, "wrap-type", "Edge behavior: { WRAP (0), SMEAR (1), BLACK (2), COLOR (3) }" }, + { GIMP_PDB_DRAWABLE, "mag-map", "Magnitude control map" }, + { GIMP_PDB_INT32, "mag-use", "Use magnitude map: { FALSE (0), TRUE (1) }" }, + { GIMP_PDB_INT32, "substeps", "Substeps between image updates" }, + { GIMP_PDB_INT32, "grad-map", "Gradient control map" }, + { GIMP_PDB_FLOAT, "grad-scale", "Scaling factor for gradient map (0=don't use)" }, + { GIMP_PDB_INT32, "vector-map", "Fixed vector control map" }, + { GIMP_PDB_FLOAT, "vector-scale", "Scaling factor for fixed vector map (0=don't use)" }, + { GIMP_PDB_FLOAT, "vector-angle", "Angle for fixed vector map" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Twist or smear image in many different ways"), + "Smears an image along vector paths calculated as " + "the gradient of a separate control matrix. The " + "effect can look like brushstrokes of acrylic or " + "watercolor paint, in some cases.", + "John P. Beale", + "John P. Beale", + "1997", + N_("_Warp..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Map"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 drawable_id; + GimpRGB color; + + INIT_I18N (); + gegl_init (NULL, NULL); + + /* get currently selected foreground pixel color */ + gimp_context_get_foreground (&color); + gimp_rgb_get_uchar (&color, + &color_pixel[0], + &color_pixel[1], + &color_pixel[2]); + + run_mode = param[0].data.d_int32; + drawable_id = param[2].data.d_drawable; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &dvals); + + /* First acquire information with a dialog */ + if (! warp_dialog (drawable_id)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure minimum args + * (mode, image, draw, amount, warp_map, iter) are there + */ + if (nparams < MIN_ARGS) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + gint pcnt = MIN_ARGS; + + dvals.amount = param[3].data.d_float; + dvals.warp_map = param[4].data.d_int32; + dvals.iter = param[5].data.d_int32; + + if (nparams > pcnt++) dvals.dither = param[6].data.d_float; + if (nparams > pcnt++) dvals.angle = param[7].data.d_float; + if (nparams > pcnt++) dvals.wrap_type = param[8].data.d_int32; + if (nparams > pcnt++) dvals.mag_map = param[9].data.d_int32; + if (nparams > pcnt++) dvals.mag_use = param[10].data.d_int32; + if (nparams > pcnt++) dvals.substeps = param[11].data.d_int32; + if (nparams > pcnt++) dvals.grad_map = param[12].data.d_int32; + if (nparams > pcnt++) dvals.grad_scale = param[13].data.d_float; + if (nparams > pcnt++) dvals.vector_map = param[14].data.d_int32; + if (nparams > pcnt++) dvals.vector_scale = param[15].data.d_float; + if (nparams > pcnt++) dvals.vector_angle = param[16].data.d_float; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (PLUG_IN_PROC, &dvals); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + /* run the warp effect */ + warp (drawable_id); + + /* Store data */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, &dvals, sizeof (WarpVals)); + } + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + values[0].data.d_status = status; +} + +static gboolean +warp_dialog (gint32 drawable_id) +{ + GtkWidget *dlg; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *toggle; + GtkWidget *toggle_hbox; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *spinbutton; + GtkObject *adj; + GtkWidget *combo; + GtkSizeGroup *label_group; + GtkSizeGroup *spin_group; + GSList *group = NULL; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dlg = gimp_dialog_new (_("Warp"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dlg)); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + frame = gimp_frame_new (_("Basic Options")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + spin_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + label_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + /* amount, iter */ + spinbutton = gimp_spin_button_new (&adj, dvals.amount, + -1000, 1000, /* ??? */ + 1, 10, 0, 1, 2); + gtk_size_group_add_widget (spin_group, spinbutton); + g_object_unref (spin_group); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Step size:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + g_object_unref (label_group); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &dvals.amount); + + spinbutton = gimp_spin_button_new (&adj, dvals.iter, + 1, 100, 1, 5, 0, 1, 0); + gtk_size_group_add_widget (spin_group, spinbutton); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Iterations:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &dvals.iter); + + /* Displacement map menu */ + label = gtk_label_new (_("Displacement map:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_yalign (GTK_LABEL (label), 1.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + combo = gimp_drawable_combo_box_new (warp_map_constrain, + GINT_TO_POINTER (drawable_id)); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dvals.warp_map, + G_CALLBACK (gimp_int_combo_box_get_active), + &dvals.warp_map); + + gtk_table_attach (GTK_TABLE (table), combo, 2, 3, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + /* ======================================================================= */ + + /* Displacement Type */ + label = gtk_label_new (_("On edges:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + toggle_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_table_attach (GTK_TABLE (table), toggle_hbox, 1, 3, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (toggle_hbox); + + toggle = gtk_radio_button_new_with_label (group, _("Wrap")); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (toggle_hbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_object_set_data (G_OBJECT (toggle), "gimp-item-data", + GINT_TO_POINTER (WRAP)); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_radio_button_update), + &dvals.wrap_type); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + dvals.wrap_type == WRAP); + + toggle = gtk_radio_button_new_with_label (group, _("Smear")); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (toggle_hbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_object_set_data (G_OBJECT (toggle), "gimp-item-data", + GINT_TO_POINTER (SMEAR)); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_radio_button_update), + &dvals.wrap_type); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + dvals.wrap_type == SMEAR); + + toggle = gtk_radio_button_new_with_label (group, _("Black")); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (toggle_hbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_object_set_data (G_OBJECT (toggle), "gimp-item-data", + GINT_TO_POINTER (BLACK)); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_radio_button_update), + &dvals.wrap_type); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + dvals.wrap_type == BLACK); + + toggle = gtk_radio_button_new_with_label (group, _("Foreground color")); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (toggle_hbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_object_set_data (G_OBJECT (toggle), "gimp-item-data", + GINT_TO_POINTER (COLOR)); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_radio_button_update), + &dvals.wrap_type); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + dvals.wrap_type == COLOR); + + + + /* -------------------------------------------------------------------- */ + /* --------- The secondary table -------------------------- */ + + frame = gimp_frame_new (_("Advanced Options")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + spinbutton = gimp_spin_button_new (&adj, dvals.dither, + 0, 100, 1, 10, 0, 1, 2); + gtk_size_group_add_widget (spin_group, spinbutton); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Dither size:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &dvals.dither); + + spinbutton = gimp_spin_button_new (&adj, dvals.angle, + 0, 360, 1, 15, 0, 1, 1); + gtk_size_group_add_widget (spin_group, spinbutton); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Rotation angle:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &dvals.angle); + + spinbutton = gimp_spin_button_new (&adj, dvals.substeps, + 1, 100, 1, 5, 0, 1, 0); + gtk_size_group_add_widget (spin_group, spinbutton); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("Substeps:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &dvals.substeps); + + /* Magnitude map menu */ + label = gtk_label_new (_("Magnitude map:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_yalign (GTK_LABEL (label), 1.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + combo = gimp_drawable_combo_box_new (warp_map_constrain, + GINT_TO_POINTER (drawable_id)); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dvals.mag_map, + G_CALLBACK (gimp_int_combo_box_get_active), + &dvals.mag_map); + + gtk_table_attach (GTK_TABLE (table), combo, 2, 3, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + /* Magnitude Usage */ + toggle_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_container_set_border_width (GTK_CONTAINER (toggle_hbox), 1); + gtk_table_attach (GTK_TABLE (table), toggle_hbox, 2, 3, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (toggle_hbox); + + toggle = gtk_check_button_new_with_label (_("Use magnitude map")); + gtk_box_pack_start (GTK_BOX (toggle_hbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), dvals.mag_use); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &dvals.mag_use); + + + /* -------------------------------------------------------------------- */ + /* --------- The "other" table -------------------------- */ + + frame = gimp_frame_new (_("More Advanced Options")); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + spinbutton = gimp_spin_button_new (&adj, dvals.grad_scale, + -1000, 1000, /* ??? */ + 0.01, 0.1, 0, 1, 3); + gtk_size_group_add_widget (spin_group, spinbutton); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Gradient scale:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &dvals.grad_scale); + + /* --------- Gradient map menu ---------------- */ + + combo = gimp_drawable_combo_box_new (warp_map_constrain, + GINT_TO_POINTER (drawable_id)); + gtk_table_attach (GTK_TABLE (table), combo, 2, 3, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dvals.grad_map, + G_CALLBACK (gimp_int_combo_box_get_active), + &dvals.grad_map); + + gimp_help_set_help_data (combo, _("Gradient map selection menu"), NULL); + + /* ---------------------------------------------- */ + + spinbutton = gimp_spin_button_new (&adj, dvals.vector_scale, + -1000, 1000, /* ??? */ + 0.01, 0.1, 0, 1, 3); + gtk_size_group_add_widget (spin_group, spinbutton); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Vector mag:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &dvals.vector_scale); + + /* -------------------------------------------------------- */ + + spinbutton = gimp_spin_button_new (&adj, dvals.vector_angle, + 0, 360, 1, 15, 0, 1, 1); + gtk_size_group_add_widget (spin_group, spinbutton); + + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("Angle:"), 0.0, 0.5, + spinbutton, 1, FALSE); + gtk_size_group_add_widget (label_group, label); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &dvals.vector_angle); + + /* --------- Vector map menu ---------------- */ + combo = gimp_drawable_combo_box_new (warp_map_constrain, + GINT_TO_POINTER (drawable_id)); + gtk_table_attach (GTK_TABLE (table), combo, 2, 3, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (combo); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), dvals.vector_map, + G_CALLBACK (gimp_int_combo_box_get_active), + &dvals.vector_map); + + gimp_help_set_help_data (combo, + _("Fixed-direction-vector map selection menu"), + NULL); + + gtk_widget_show (dlg); + + run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dlg); + + return run; +} + +/* ---------------------------------------------------------------------- */ + +static const Babl * +get_u8_format (gint32 drawable_id) +{ + if (gimp_drawable_is_rgb (drawable_id)) + { + if (gimp_drawable_has_alpha (drawable_id)) + return babl_format ("R'G'B'A u8"); + else + return babl_format ("R'G'B' u8"); + } + else + { + if (gimp_drawable_has_alpha (drawable_id)) + return babl_format ("Y'A u8"); + else + return babl_format ("Y' u8"); + } +} + +static void +blur16 (gint32 drawable_id) +{ + /* blur a 2-or-more byte-per-pixel drawable, + * 1st 2 bytes interpreted as a 16-bit height field. + */ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *format; + gint width, height; + gint src_bytes; + gint dest_bytes; + gint dest_bytes_inc; + gint offb, off1; + + guchar *dest, *d; /* pointers to rows of X and Y diff. data */ + guchar *prev_row, *pr; + guchar *cur_row, *cr; + guchar *next_row, *nr; + guchar *tmp; + gint row, col; /* relating to indexing into pixel row arrays */ + gint x1, y1, x2, y2; + gdouble pval; /* average pixel value of pixel & neighbors */ + + /* --------------------------------------- */ + + if (! gimp_drawable_mask_intersect (drawable_id, + &x1, &y1, &width, &height)) + return; + + x2 = x1 + width; + y2 = y1 + height; + + width = gimp_drawable_width (drawable_id); /* size of input drawable*/ + height = gimp_drawable_height (drawable_id); + + format = get_u8_format (drawable_id); + + /* bytes per pixel in SOURCE drawable, must be 2 or more */ + src_bytes = babl_format_get_bytes_per_pixel (format); + + dest_bytes = src_bytes; /* bytes per pixel in SOURCE drawable, >= 2 */ + dest_bytes_inc = dest_bytes - 2; /* this is most likely zero, but I guess it's more conservative... */ + + /* allocate row buffers for source & dest. data */ + + prev_row = g_new (guchar, (x2 - x1 + 2) * src_bytes); + cur_row = g_new (guchar, (x2 - x1 + 2) * src_bytes); + next_row = g_new (guchar, (x2 - x1 + 2) * src_bytes); + dest = g_new (guchar, (x2 - x1) * src_bytes); + + /* initialize the pixel regions (read from source, write into dest) */ + src_buffer = gimp_drawable_get_buffer (drawable_id); + dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id); + + pr = prev_row + src_bytes; /* row arrays are prepared for indexing to -1 (!) */ + cr = cur_row + src_bytes; + nr = next_row + src_bytes; + + diff_prepare_row (src_buffer, format, pr, x1, y1, (x2 - x1)); + diff_prepare_row (src_buffer, format, cr, x1, y1+1, (x2 - x1)); + + /* loop through the rows, applying the smoothing function */ + for (row = y1; row < y2; row++) + { + /* prepare the next row */ + diff_prepare_row (src_buffer, format, nr, x1, row + 1, (x2 - x1)); + + d = dest; + for (col = 0; col < (x2 - x1); col++) /* over columns of pixels */ + { + offb = col*src_bytes; /* base of byte pointer offset */ + off1 = offb+1; /* offset into row arrays */ + + pval = (256.0 * pr[offb - src_bytes] + pr[off1 - src_bytes] + + 256.0 * pr[offb] + pr[off1] + + 256.0 * pr[offb + src_bytes] + pr[off1 + src_bytes] + + 256.0 * cr[offb - src_bytes] + cr[off1 - src_bytes] + + 256.0 * cr[offb] + cr[off1] + + 256.0 * cr[offb + src_bytes] + cr[off1 + src_bytes] + + 256.0 * nr[offb - src_bytes] + nr[off1 - src_bytes] + + 256.0 * nr[offb] + nr[off1] + + 256.0 * nr[offb + src_bytes]) + nr[off1 + src_bytes]; + + pval /= 9.0; /* take the average */ + *d++ = (guchar) (((gint) pval) >> 8); /* high-order byte */ + *d++ = (guchar) (((gint) pval) % 256); /* low-order byte */ + d += dest_bytes_inc; /* move data pointer on to next destination pixel */ + } + + /* store the dest */ + gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x1, row, (x2 - x1), 1), 0, + format, dest, + GEGL_AUTO_ROWSTRIDE); + + /* shuffle the row pointers */ + tmp = pr; + pr = cr; + cr = nr; + nr = tmp; + + if ((row % 8) == 0) + gimp_progress_update ((double) row / (double) (y2 - y1)); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, x1, y1, (x2 - x1), (y2 - y1)); + + g_free (prev_row); /* row buffers allocated at top of fn. */ + g_free (cur_row); + g_free (next_row); + g_free (dest); + +} + + +/* ====================================================================== */ +/* Get one row of pixels from the PixelRegion and put them in 'data' */ + +static void +diff_prepare_row (GeglBuffer *buffer, + const Babl *format, + guchar *data, + gint x, + gint y, + gint w) +{ + gint bpp = babl_format_get_bytes_per_pixel (format); + gint b; + + /* y = CLAMP (y, 0, pixel_rgn->h - 1); FIXME? */ + + gegl_buffer_get (buffer, GEGL_RECTANGLE (x, y, w, 1), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + /* Fill in edge pixels */ + for (b = 0; b < bpp; b++) + { + data[b - (gint) bpp] = data[b]; + data[w * bpp + b] = data[(w - 1) * bpp + b]; + } +} + +/* -------------------------------------------------------------------------- */ +/* 'diff' combines the input drawables to prepare the two */ +/* 16-bit (X,Y) vector displacement maps */ +/* -------------------------------------------------------------------------- */ + +static void +diff (gint32 drawable_id, + gint32 *xl_id, + gint32 *yl_id) +{ + gint32 draw_xd_id; + gint32 draw_yd_id; /* vector disp. drawables */ + gint32 mdraw_id; + gint32 vdraw_id; + gint32 gdraw_id; + gint32 image_id; /* image holding X and Y diff. arrays */ + gint32 new_image_id; /* image holding X and Y diff. layers */ + gint32 layer_active; /* currently active layer */ + gint32 xlayer_id, ylayer_id; /* individual X and Y layer ID numbers */ + GeglBuffer *src_buffer; + GeglBuffer *destx_buffer; + const Babl *destx_format; + GeglBuffer *desty_buffer; + const Babl *desty_format; + GeglBuffer *vec_buffer; + GeglBuffer *mag_buffer = NULL; + GeglBuffer *grad_buffer; + gint width, height; + const Babl *src_format; + gint src_bytes; + const Babl *mformat = NULL; + gint mbytes = 0; + const Babl *vformat = NULL; + gint vbytes = 0; + const Babl *gformat = NULL; + gint gbytes = 0; /* bytes-per-pixel of various source drawables */ + const Babl *dest_format; + gint dest_bytes; + gint dest_bytes_inc; + gint do_gradmap = FALSE; /* whether to add in gradient of gradmap to final diff. map */ + gint do_vecmap = FALSE; /* whether to add in a fixed vector scaled by the vector map */ + gint do_magmap = FALSE; /* whether to multiply result by the magnitude map */ + + guchar *destx, *dx, *desty, *dy; /* pointers to rows of X and Y diff. data */ + guchar *tmp; + guchar *prev_row, *pr; + guchar *cur_row, *cr; + guchar *next_row, *nr; + guchar *prev_row_g, *prg = NULL; /* pointers to gradient map data */ + guchar *cur_row_g, *crg = NULL; + guchar *next_row_g, *nrg = NULL; + guchar *cur_row_v, *crv = NULL; /* pointers to vector map data */ + guchar *cur_row_m, *crm = NULL; /* pointers to magnitude map data */ + gint row, col, offb, off, bytes; /* relating to indexing into pixel row arrays */ + gint x1, y1, x2, y2; + gint dvalx, dvaly; /* differential value at particular pixel */ + gdouble tx, ty; /* temporary x,y differential value increments from gradmap, etc. */ + gdouble rdx, rdy; /* x,y differential values: real #s */ + gdouble rscalefac; /* scaling factor for x,y differential of 'curl' map */ + gdouble gscalefac; /* scaling factor for x,y differential of 'gradient' map */ + gdouble r, theta, dtheta; /* rectangular<-> spherical coordinate transform for vector rotation */ + gdouble scale_vec_x, scale_vec_y; /* fixed vector X,Y component scaling factors */ + + /* ----------------------------------------------------------------------- */ + + if (dvals.grad_scale != 0.0) + do_gradmap = TRUE; /* add in gradient of gradmap if scale != 0.000 */ + + if (dvals.vector_scale != 0.0) /* add in gradient of vectormap if scale != 0.000 */ + do_vecmap = TRUE; + + do_magmap = (dvals.mag_use == TRUE); /* multiply by magnitude map if so requested */ + + /* Get the input area. This is the bounding box of the selection in + * the image (or the entire image if there is no selection). Only + * operating on the input area is simply an optimization. It doesn't + * need to be done for correct operation. (It simply makes it go + * faster, since fewer pixels need to be operated on). + */ + if (! gimp_drawable_mask_intersect (drawable_id, + &x1, &y1, &width, &height)) + return; + + x2 = x1 + width; + y2 = y1 + height; + + /* Get the size of the input image. (This will/must be the same + * as the size of the output image. + */ + width = gimp_drawable_width (drawable_id); + height = gimp_drawable_height (drawable_id); + + src_format = get_u8_format (drawable_id); + src_bytes = babl_format_get_bytes_per_pixel (src_format); + + /* -- Add two layers: X and Y Displacement vectors -- */ + /* -- I'm using a RGB drawable and using the first two bytes for a + 16-bit pixel value. This is either clever, or a kluge, + depending on your point of view. */ + + image_id = gimp_item_get_image (drawable_id); + layer_active = gimp_image_get_active_layer (image_id); + + /* create new image for X,Y diff */ + new_image_id = gimp_image_new (width, height, GIMP_RGB); + + xlayer_id = gimp_layer_new (new_image_id, "Warp_X_Vectors", + width, height, + GIMP_RGB_IMAGE, + 100.0, + gimp_image_get_default_new_layer_mode (new_image_id)); + + ylayer_id = gimp_layer_new (new_image_id, "Warp_Y_Vectors", + width, height, + GIMP_RGB_IMAGE, + 100.0, + gimp_image_get_default_new_layer_mode (new_image_id)); + + draw_yd_id = ylayer_id; + draw_xd_id = xlayer_id; + + gimp_image_insert_layer (new_image_id, xlayer_id, -1, 1); + gimp_image_insert_layer (new_image_id, ylayer_id, -1, 1); + gimp_drawable_fill (xlayer_id, GIMP_FILL_BACKGROUND); + gimp_drawable_fill (ylayer_id, GIMP_FILL_BACKGROUND); + gimp_image_set_active_layer (image_id, layer_active); + + dest_format = get_u8_format (draw_xd_id); + dest_bytes = babl_format_get_bytes_per_pixel (dest_format); + /* for a GRAYA drawable, I would expect this to be two bytes; any more would be excess */ + dest_bytes_inc = dest_bytes - 2; + + /* allocate row buffers for source & dest. data */ + + prev_row = g_new (guchar, (x2 - x1 + 2) * src_bytes); + cur_row = g_new (guchar, (x2 - x1 + 2) * src_bytes); + next_row = g_new (guchar, (x2 - x1 + 2) * src_bytes); + + prev_row_g = g_new (guchar, (x2 - x1 + 2) * src_bytes); + cur_row_g = g_new (guchar, (x2 - x1 + 2) * src_bytes); + next_row_g = g_new (guchar, (x2 - x1 + 2) * src_bytes); + + cur_row_v = g_new (guchar, (x2 - x1 + 2) * src_bytes); /* vector map */ + cur_row_m = g_new (guchar, (x2 - x1 + 2) * src_bytes); /* magnitude map */ + + destx = g_new (guchar, (x2 - x1) * dest_bytes); + desty = g_new (guchar, (x2 - x1) * dest_bytes); + + /* initialize the source and destination pixel regions */ + + /* 'curl' vector-rotation input */ + src_buffer = gimp_drawable_get_buffer (drawable_id); + + /* destination: X diff output */ + destx_buffer = gimp_drawable_get_buffer (draw_xd_id); + destx_format = get_u8_format (draw_xd_id); + + /* Y diff output */ + desty_buffer = gimp_drawable_get_buffer (draw_yd_id); + desty_format = get_u8_format (draw_yd_id); + + pr = prev_row + src_bytes; + cr = cur_row + src_bytes; + nr = next_row + src_bytes; + + diff_prepare_row (src_buffer, src_format, pr, x1, y1, (x2 - x1)); + diff_prepare_row (src_buffer, src_format, cr, x1, y1+1, (x2 - x1)); + + /* fixed-vector (x,y) component scale factors */ + scale_vec_x = (dvals.vector_scale * + cos ((90 - dvals.vector_angle) * G_PI / 180.0) * 256.0 / 10); + scale_vec_y = (dvals.vector_scale * + sin ((90 - dvals.vector_angle) * G_PI / 180.0) * 256.0 / 10); + + if (do_vecmap) + { + vdraw_id = dvals.vector_map; + + /* bytes per pixel in SOURCE drawable */ + vformat = get_u8_format (vdraw_id); + vbytes = babl_format_get_bytes_per_pixel (vformat); + + /* fixed-vector scale-map */ + vec_buffer = gimp_drawable_get_buffer (vdraw_id); + + crv = cur_row_v + vbytes; + diff_prepare_row (vec_buffer, vformat, crv, x1, y1, (x2 - x1)); + } + + if (do_gradmap) + { + gdraw_id = dvals.grad_map; + + gformat = get_u8_format (gdraw_id); + gbytes = babl_format_get_bytes_per_pixel (gformat); + + /* fixed-vector scale-map */ + grad_buffer = gimp_drawable_get_buffer (gdraw_id); + + prg = prev_row_g + gbytes; + crg = cur_row_g + gbytes; + nrg = next_row_g + gbytes; + diff_prepare_row (grad_buffer, gformat, prg, x1, y1 - 1, (x2 - x1)); + diff_prepare_row (grad_buffer, gformat, crg, x1, y1, (x2 - x1)); + } + + if (do_magmap) + { + mdraw_id = dvals.mag_map; + + mformat = get_u8_format (mdraw_id); + mbytes = babl_format_get_bytes_per_pixel (mformat); + + /* fixed-vector scale-map */ + mag_buffer = gimp_drawable_get_buffer (mdraw_id); + + crm = cur_row_m + mbytes; + diff_prepare_row (mag_buffer, mformat, crm, x1, y1, (x2 - x1)); + } + + dtheta = dvals.angle * G_PI / 180.0; + /* note that '3' is rather arbitrary here. */ + rscalefac = 256.0 / (3 * src_bytes); + /* scale factor for gradient map components */ + gscalefac = dvals.grad_scale * 256.0 / (3 * gbytes); + + /* loop through the rows, applying the differential convolution */ + for (row = y1; row < y2; row++) + { + /* prepare the next row */ + diff_prepare_row (src_buffer, src_format, nr, x1, row + 1, (x2 - x1)); + + if (do_magmap) + diff_prepare_row (mag_buffer, mformat, crm, x1, row + 1, (x2 - x1)); + if (do_vecmap) + diff_prepare_row (vec_buffer, vformat, crv, x1, row + 1, (x2 - x1)); + if (do_gradmap) + diff_prepare_row (grad_buffer, gformat, crg, x1, row + 1, (x2 - x1)); + + dx = destx; + dy = desty; + + for (col = 0; col < (x2 - x1); col++) /* over columns of pixels */ + { + rdx = 0.0; + rdy = 0.0; + ty = 0.0; + tx = 0.0; + + offb = col * src_bytes; /* base of byte pointer offset */ + for (bytes=0; bytes < src_bytes; bytes++) /* add all channels together */ + { + off = offb+bytes; /* offset into row arrays */ + rdx += ((gint) -pr[off - src_bytes] + (gint) pr[off + src_bytes] + + (gint) -2*cr[off - src_bytes] + (gint) 2*cr[off + src_bytes] + + (gint) -nr[off - src_bytes] + (gint) nr[off + src_bytes]); + + rdy += ((gint) -pr[off - src_bytes] - (gint)2*pr[off] - (gint) pr[off + src_bytes] + + (gint) nr[off - src_bytes] + (gint)2*nr[off] + (gint) nr[off + src_bytes]); + } + + rdx *= rscalefac; /* take average, then reduce. Assume max. rdx now 65535 */ + rdy *= rscalefac; /* take average, then reduce */ + + theta = atan2(rdy,rdx); /* convert to polar, then back to rectang. coords */ + r = sqrt(rdy*rdy + rdx*rdx); + theta += dtheta; /* rotate gradient vector by this angle (radians) */ + rdx = r * cos(theta); + rdy = r * sin(theta); + + if (do_gradmap) + { + offb = col*gbytes; /* base of byte pointer offset into pixel values (R,G,B,Alpha, etc.) */ + for (bytes=0; bytes < src_bytes; bytes++) /* add all channels together */ + { + off = offb+bytes; /* offset into row arrays */ + tx += ((gint) -prg[off - gbytes] + (gint) prg[off + gbytes] + + (gint) -2*crg[off - gbytes] + (gint) 2*crg[off + gbytes] + + (gint) -nrg[off - gbytes] + (gint) nrg[off + gbytes]); + + ty += ((gint) -prg[off - gbytes] - (gint)2*prg[off] - (gint) prg[off + gbytes] + + (gint) nrg[off - gbytes] + (gint)2*nrg[off] + (gint) nrg[off + gbytes]); + } + tx *= gscalefac; + ty *= gscalefac; + + rdx += tx; /* add gradient component in to the other one */ + rdy += ty; + + } /* if (do_gradmap) */ + + if (do_vecmap) + { /* add in fixed vector scaled by vec. map data */ + tx = (gdouble) crv[col*vbytes]; /* use first byte only */ + rdx += scale_vec_x * tx; + rdy += scale_vec_y * tx; + } /* if (do_vecmap) */ + + if (do_magmap) + { /* multiply result by mag. map data */ + tx = (gdouble) crm[col*mbytes]; + rdx = (rdx * tx)/(255.0); + rdy = (rdy * tx)/(255.0); + } /* if do_magmap */ + + dvalx = rdx + (2<<14); /* take zero point to be 2^15, since this is two bytes */ + dvaly = rdy + (2<<14); + + if (dvalx < 0) + dvalx = 0; + + if (dvalx > 65535) + dvalx = 65535; + + *dx++ = (guchar) (dvalx >> 8); /* store high order byte in value channel */ + *dx++ = (guchar) (dvalx % 256); /* store low order byte in alpha channel */ + dx += dest_bytes_inc; /* move data pointer on to next destination pixel */ + + if (dvaly < 0) + dvaly = 0; + + if (dvaly > 65535) + dvaly = 65535; + + *dy++ = (guchar) (dvaly >> 8); + *dy++ = (guchar) (dvaly % 256); + dy += dest_bytes_inc; + + } /* ------------------------------- for (col...) ---------------- */ + + /* store the dest */ + gegl_buffer_set (destx_buffer, + GEGL_RECTANGLE (x1, row, (x2 - x1), 1), 0, + destx_format, destx, + GEGL_AUTO_ROWSTRIDE); + + gegl_buffer_set (desty_buffer, + GEGL_RECTANGLE (x1, row, (x2 - x1), 1), 0, + desty_format, desty, + GEGL_AUTO_ROWSTRIDE); + + /* swap around the pointers to row buffers */ + tmp = pr; + pr = cr; + cr = nr; + nr = tmp; + + if (do_gradmap) + { + tmp = prg; + prg = crg; + crg = nrg; + nrg = tmp; + } + + if ((row % 8) == 0) + gimp_progress_update ((gdouble) row / (gdouble) (y2 - y1)); + + } /* for (row..) */ + + gimp_progress_update (1.0); + + g_object_unref (src_buffer); + g_object_unref (destx_buffer); + g_object_unref (desty_buffer); + + gimp_drawable_update (draw_xd_id, x1, y1, (x2 - x1), (y2 - y1)); + gimp_drawable_update (draw_yd_id, x1, y1, (x2 - x1), (y2 - y1)); + + gimp_displays_flush (); /* make sure layer is visible */ + + gimp_progress_init (_("Smoothing X gradient")); + blur16 (draw_xd_id); + + gimp_progress_init (_("Smoothing Y gradient")); + blur16 (draw_yd_id); + + g_free (prev_row); /* row buffers allocated at top of fn. */ + g_free (cur_row); + g_free (next_row); + g_free (prev_row_g); /* row buffers allocated at top of fn. */ + g_free (cur_row_g); + g_free (next_row_g); + g_free (cur_row_v); + g_free (cur_row_m); + + g_free (destx); + g_free (desty); + + *xl_id = xlayer_id; /* pass back the X and Y layer ID numbers */ + *yl_id = ylayer_id; +} + +/* -------------------------------------------------------------------------- */ +/* The Warp displacement is done here. */ +/* -------------------------------------------------------------------------- */ + +static void +warp (gint32 orig_draw_id) +{ + gint32 disp_map_id; /* Displacement map, ie, control array */ + gint32 mag_draw_id; /* Magnitude multiplier factor map */ + gint32 map_x_id = -1; + gint32 map_y_id = -1; + gboolean first_time = TRUE; + gint width; + gint height; + gint x1, y1, x2, y2; + gint32 image_ID; + + /* index var. over all "warp" Displacement iterations */ + gint warp_iter; + + disp_map_id = dvals.warp_map; + mag_draw_id = dvals.mag_map; + + /* calculate new X,Y Displacement image maps */ + + gimp_progress_init (_("Finding XY gradient")); + + /* Get selection area */ + if (! gimp_drawable_mask_intersect (orig_draw_id, + &x1, &y1, &width, &height)) + return; + + x2 = x1 + width; + y2 = y1 + height; + + width = gimp_drawable_width (orig_draw_id); + height = gimp_drawable_height (orig_draw_id); + + /* generate x,y differential images (arrays) */ + diff (disp_map_id, &map_x_id, &map_y_id); + + for (warp_iter = 0; warp_iter < dvals.iter; warp_iter++) + { + gimp_progress_init_printf (_("Flow step %d"), warp_iter+1); + progress = 0; + + warp_one (orig_draw_id, orig_draw_id, + map_x_id, map_y_id, mag_draw_id, + first_time, warp_iter); + + gimp_drawable_update (orig_draw_id, + x1, y1, (x2 - x1), (y2 - y1)); + + if (run_mode != GIMP_RUN_NONINTERACTIVE) + gimp_displays_flush (); + + first_time = FALSE; + } + + image_ID = gimp_item_get_image (map_x_id); + + gimp_image_delete (image_ID); +} + +/* -------------------------------------------------------------------------- */ + +static void +warp_one (gint32 draw_id, + gint32 new_id, + gint32 map_x_id, + gint32 map_y_id, + gint32 mag_draw_id, + gboolean first_time, + gint step) +{ + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + GeglBuffer *map_x_buffer; + GeglBuffer *map_y_buffer; + GeglBuffer *mag_buffer = NULL; + + GeglBufferIterator *iter; + + gint width; + gint height; + + const Babl *src_format; + gint src_bytes; + const Babl *dest_format; + gint dest_bytes; + + guchar pixel[4][4]; + gint x1, y1, x2, y2; + gint x, y; + gint max_progress; + + gdouble needx, needy; + gdouble xval=0; /* initialize to quiet compiler grumbles */ + gdouble yval=0; /* interpolated vector displacement */ + gdouble scalefac; /* multiplier for vector displacement scaling */ + gdouble dscalefac; /* multiplier for incremental displacement vectors */ + gint xi, yi; + gint substep; /* loop variable counting displacement vector substeps */ + + guchar values[4]; + guint32 ivalues[4]; + guchar val; + + gint k; + + gdouble dx, dy; /* X and Y Displacement, integer from GRAY map */ + + const Babl *map_x_format; + gint map_x_bytes; + const Babl *map_y_format; + gint map_y_bytes; + const Babl *mag_format; + gint mag_bytes = 1; + gboolean mag_alpha = FALSE; + + GRand *gr; + + gr = g_rand_new (); /* Seed Pseudo Random Number Generator */ + + /* ================ Outer Loop calculation ================================ */ + + /* Get selection area */ + + if (! gimp_drawable_mask_intersect (draw_id, + &x1, &y1, &width, &height)) + return; + + x2 = x1 + width; + y2 = y1 + height; + + width = gimp_drawable_width (draw_id); + height = gimp_drawable_height (draw_id); + + + max_progress = (x2 - x1) * (y2 - y1); + + + /* --------- Register the (many) pixel regions ---------- */ + + src_buffer = gimp_drawable_get_buffer (draw_id); + + src_format = get_u8_format (draw_id); + src_bytes = babl_format_get_bytes_per_pixel (src_format); + + iter = gegl_buffer_iterator_new (src_buffer, + GEGL_RECTANGLE (x1, y1, (x2 - x1), (y2 - y1)), + 0, src_format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 5); + + + dest_buffer = gimp_drawable_get_shadow_buffer (new_id); + + dest_format = get_u8_format (new_id); + dest_bytes = babl_format_get_bytes_per_pixel (dest_format); + + gegl_buffer_iterator_add (iter, dest_buffer, + GEGL_RECTANGLE (x1, y1, (x2 - x1), (y2 - y1)), + 0, dest_format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE); + + + map_x_buffer = gimp_drawable_get_buffer (map_x_id); + + map_x_format = get_u8_format (map_x_id); + map_x_bytes = babl_format_get_bytes_per_pixel (map_x_format); + + gegl_buffer_iterator_add (iter, map_x_buffer, + GEGL_RECTANGLE (x1, y1, (x2 - x1), (y2 - y1)), + 0, map_x_format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE); + + + map_y_buffer = gimp_drawable_get_buffer (map_y_id); + + map_y_format = get_u8_format (map_y_id); + map_y_bytes = babl_format_get_bytes_per_pixel (map_y_format); + + gegl_buffer_iterator_add (iter, map_y_buffer, + GEGL_RECTANGLE (x1, y1, (x2 - x1), (y2 - y1)), + 0, map_y_format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE); + + + if (dvals.mag_use) + { + mag_buffer = gimp_drawable_get_buffer (mag_draw_id); + + mag_format = get_u8_format (mag_draw_id); + mag_bytes = babl_format_get_bytes_per_pixel (mag_format); + + mag_alpha = gimp_drawable_has_alpha (mag_draw_id); + + gegl_buffer_iterator_add (iter, mag_buffer, + GEGL_RECTANGLE (x1, y1, (x2 - x1), (y2 - y1)), + 0, mag_format, + GEGL_ACCESS_READ, GEGL_ABYSS_NONE); + } + + /* substep displacement vector scale factor */ + dscalefac = dvals.amount / (256 * 127.5 * dvals.substeps); + + while (gegl_buffer_iterator_next (iter)) + { + GeglRectangle roi = iter->items[1].roi; + guchar *srcrow = iter->items[0].data; + guchar *destrow = iter->items[1].data; + guchar *mxrow = iter->items[2].data; + guchar *myrow = iter->items[3].data; + guchar *mmagrow = NULL; + + if (dvals.mag_use) + mmagrow = iter->items[4].data; + + /* loop over destination pixels */ + for (y = roi.y; y < (roi.y + roi.height); y++) + { + guchar *dest = destrow; + guchar *mx = mxrow; + guchar *my = myrow; + guchar *mmag = NULL; + + if (dvals.mag_use == TRUE) + mmag = mmagrow; + + for (x = roi.x; x < (roi.x + roi.width); x++) + { + /* ----- Find displacement vector (amnt_x, amnt_y) ------------ */ + + dx = dscalefac * ((256.0 * mx[0]) + mx[1] -32768); /* 16-bit values */ + dy = dscalefac * ((256.0 * my[0]) + my[1] -32768); + + if (dvals.mag_use) + { + scalefac = warp_map_mag_give_value (mmag, + mag_alpha, + mag_bytes) / 255.0; + dx *= scalefac; + dy *= scalefac; + } + + if (dvals.dither != 0.0) + { /* random dither is +/- dvals.dither pixels */ + dx += g_rand_double_range (gr, -dvals.dither, dvals.dither); + dy += g_rand_double_range (gr, -dvals.dither, dvals.dither); + } + + if (dvals.substeps != 1) + { /* trace (substeps) iterations of displacement vector */ + for (substep = 1; substep < dvals.substeps; substep++) + { + /* In this (substep) loop, (x,y) remain fixed. (dx,dy) vary each step. */ + needx = x + dx; + needy = y + dy; + + if (needx >= 0.0) + xi = (gint) needx; + else + xi = -((gint) -needx + 1); + + if (needy >= 0.0) + yi = (gint) needy; + else + yi = -((gint) -needy + 1); + + /* get 4 neighboring DX values from DiffX drawable for linear interpolation */ + warp_pixel (map_x_buffer, map_x_format, + width, height, + x1, y1, x2, y2, + xi, yi, + pixel[0]); + warp_pixel (map_x_buffer, map_x_format, + width, height, + x1, y1, x2, y2, + xi + 1, yi, + pixel[1]); + warp_pixel (map_x_buffer, map_x_format, + width, height, + x1, y1, x2, y2, + xi, yi + 1, + pixel[2]); + warp_pixel (map_x_buffer, map_x_format, + width, height, + x1, y1, x2, y2, + xi + 1, yi + 1, + pixel[3]); + + ivalues[0] = 256 * pixel[0][0] + pixel[0][1]; + ivalues[1] = 256 * pixel[1][0] + pixel[1][1]; + ivalues[2] = 256 * pixel[2][0] + pixel[2][1]; + ivalues[3] = 256 * pixel[3][0] + pixel[3][1]; + + xval = gimp_bilinear_32 (needx, needy, ivalues); + + /* get 4 neighboring DY values from DiffY drawable for linear interpolation */ + warp_pixel (map_y_buffer, map_y_format, + width, height, + x1, y1, x2, y2, + xi, yi, + pixel[0]); + warp_pixel (map_y_buffer, map_y_format, + width, height, + x1, y1, x2, y2, + xi + 1, yi, + pixel[1]); + warp_pixel (map_y_buffer, map_y_format, + width, height, + x1, y1, x2, y2, + xi, yi + 1, + pixel[2]); + warp_pixel (map_y_buffer, map_y_format, + width, height, + x1, y1, x2, y2, + xi + 1, yi + 1, + pixel[3]); + + ivalues[0] = 256 * pixel[0][0] + pixel[0][1]; + ivalues[1] = 256 * pixel[1][0] + pixel[1][1]; + ivalues[2] = 256 * pixel[2][0] + pixel[2][1]; + ivalues[3] = 256 * pixel[3][0] + pixel[3][1]; + + yval = gimp_bilinear_32 (needx, needy, ivalues); + + /* move displacement vector to this new value */ + dx += dscalefac * (xval - 32768); + dy += dscalefac * (yval - 32768); + + } /* for (substep) */ + } /* if (substeps != 0) */ + + /* --------------------------------------------------------- */ + + needx = x + dx; + needy = y + dy; + + mx += map_x_bytes; /* pointers into x,y displacement maps */ + my += map_y_bytes; + + if (dvals.mag_use == TRUE) + mmag += mag_bytes; + + /* Calculations complete; now copy the proper pixel */ + + if (needx >= 0.0) + xi = (gint) needx; + else + xi = -((gint) -needx + 1); + + if (needy >= 0.0) + yi = (gint) needy; + else + yi = -((gint) -needy + 1); + + /* get 4 neighboring pixel values from source drawable + * for linear interpolation + */ + warp_pixel (src_buffer, src_format, + width, height, + x1, y1, x2, y2, + xi, yi, + pixel[0]); + warp_pixel (src_buffer, src_format, + width, height, + x1, y1, x2, y2, + xi + 1, yi, + pixel[1]); + warp_pixel (src_buffer, src_format, + width, height, + x1, y1, x2, y2, + xi, yi + 1, + pixel[2]); + warp_pixel (src_buffer, src_format, + width, height, + x1, y1, x2, y2, + xi + 1, yi + 1, + pixel[3]); + + for (k = 0; k < dest_bytes; k++) + { + values[0] = pixel[0][k]; + values[1] = pixel[1][k]; + values[2] = pixel[2][k]; + values[3] = pixel[3][k]; + + val = gimp_bilinear_8 (needx, needy, values); + + *dest++ = val; + } + } + + /* srcrow += src_rgn.rowstride; */ + srcrow += src_bytes * roi.width; + destrow += dest_bytes * roi.width; + mxrow += map_x_bytes * roi.width; + myrow += map_y_bytes * roi.width; + + if (dvals.mag_use == TRUE) + mmagrow += mag_bytes * roi.width; + } + + progress += (roi.width * roi.height); + gimp_progress_update ((double) progress / (double) max_progress); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + g_object_unref (map_x_buffer); + g_object_unref (map_y_buffer); + + if (dvals.mag_use == TRUE) + g_object_unref (mag_buffer); + + gimp_progress_update (1.0); + + gimp_drawable_merge_shadow (draw_id, first_time); + + g_rand_free (gr); +} + +/* ------------------------------------------------------------------------- */ + +static gdouble +warp_map_mag_give_value (guchar *pt, + gint alpha, + gint bytes) +{ + gdouble ret, val_alpha; + + if (bytes >= 3) + ret = (pt[0] + pt[1] + pt[2])/3.0; + else + ret = (gdouble) *pt; + + if (alpha) + { + val_alpha = pt[bytes - 1]; + ret = (ret * val_alpha / 255.0); + }; + + return (ret); +} + + +static void +warp_pixel (GeglBuffer *buffer, + const Babl *format, + gint width, + gint height, + gint x1, + gint y1, + gint x2, + gint y2, + gint x, + gint y, + guchar *pixel) +{ + static guchar empty_pixel[4] = { 0, 0, 0, 0 }; + guchar *data; + + /* Tile the image. */ + if (dvals.wrap_type == WRAP) + { + if (x < 0) + x = width - (-x % width); + else + x %= width; + + if (y < 0) + y = height - (-y % height); + else + y %= height; + } + /* Smear out the edges of the image by repeating pixels. */ + else if (dvals.wrap_type == SMEAR) + { + if (x < 0) + x = 0; + else if (x > width - 1) + x = width - 1; + + if (y < 0) + y = 0; + else if (y > height - 1) + y = height - 1; + } + + if (x >= x1 && y >= y1 && x < x2 && y < y2) + { + gegl_buffer_sample (buffer, x, y, NULL, pixel, format, + GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); + } + else + { + gint bpp = babl_format_get_bytes_per_pixel (format); + gint b; + + if (dvals.wrap_type == BLACK) + data = empty_pixel; + else + data = color_pixel; /* must have selected COLOR type */ + + for (b = 0; b < bpp; b++) + pixel[b] = data[b]; + } +} + +/* Warp interface functions */ + +static gboolean +warp_map_constrain (gint32 image_id, + gint32 drawable_id, + gpointer data) +{ + gint32 d_id = GPOINTER_TO_INT (data); + + return (gimp_drawable_width (drawable_id) == gimp_drawable_width (d_id) && + gimp_drawable_height (drawable_id) == gimp_drawable_height (d_id)); +} diff --git a/plug-ins/common/wavelet-decompose.c b/plug-ins/common/wavelet-decompose.c new file mode 100644 index 0000000..4b79c35 --- /dev/null +++ b/plug-ins/common/wavelet-decompose.c @@ -0,0 +1,422 @@ +/* + * Wavelet decompose plug-in by Miroslav Talasek, miroslav.talasek@seznam.cz + */ + +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-wavelet-decompose" +#define PLUG_IN_ROLE "gimp-wavelet-decompose" +#define PLUG_IN_BINARY "wavelet-decompose" + +#define SCALE_WIDTH 120 +#define ENTRY_WIDTH 5 + + +typedef struct +{ + gint scales; + gint create_group; + gint create_masks; +} WaveletDecomposeParams; + + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static void wavelet_blur (gint32 drawable_id, + gint radius); + + +static gboolean wavelet_decompose_dialog (void); + + +/* create a few globals, set default values */ +static WaveletDecomposeParams wavelet_params = +{ + 5, /* default scales */ + 1, /* create group */ + 0 /* do not add mask by default */ +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), " + "RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + { GIMP_PDB_INT32, "scales", "Number of scales (1-7)" }, + { GIMP_PDB_INT32, "create-group", "Create a layer group to store the " + "decomposition" }, + { GIMP_PDB_INT32, "create-masks", "Add a layer mask to each scales " + "layers" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Wavelet decompose"), + "Compute and render wavelet scales", + "Miroslav Talasek ", + "Miroslav Talasek ", + "19january 2017", + N_("_Wavelet-decompose..."), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/Filters/Enhance"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_id; + gint32 drawable_id; + GimpRunMode run_mode; + + run_mode = param[0].data.d_int32; + + INIT_I18N(); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + image_id = param[1].data.d_image; + drawable_id = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (PLUG_IN_PROC, &wavelet_params); + + if (! wavelet_decompose_dialog ()) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + wavelet_params.scales = param[3].data.d_int32; + wavelet_params.create_group = param[4].data.d_int32; + wavelet_params.create_masks = param[5].data.d_int32; + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (PLUG_IN_PROC, &wavelet_params); + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + gint32 *scale_ids; + gint32 new_scale_id; + gint32 parent_id; + GimpLayerMode grain_extract_mode = GIMP_LAYER_MODE_GRAIN_EXTRACT; + GimpLayerMode grain_merge_mode = GIMP_LAYER_MODE_GRAIN_MERGE; + gint id; + + gimp_progress_init (_("Wavelet-Decompose")); + + gimp_image_undo_group_start (image_id); + + gimp_image_freeze_layers (image_id); + + if (wavelet_params.create_group) + { + gint32 group_id = gimp_layer_group_new (image_id); + gimp_item_set_name (group_id, _("Decomposition")); + gimp_item_set_visible (group_id, FALSE); + gimp_image_insert_layer (image_id, group_id, + gimp_item_get_parent (drawable_id), + gimp_image_get_item_position (image_id, + drawable_id)); + parent_id = group_id; + } + else + parent_id = -1; + + scale_ids = g_new (gint32, wavelet_params.scales); + new_scale_id = gimp_layer_copy (drawable_id); + gimp_image_insert_layer (image_id, new_scale_id, parent_id, + gimp_image_get_item_position (image_id, + drawable_id)); + + /* the exact result of the grain-extract and grain-merge modes depends on + * the choice of (gamma-corrected) midpoint intensity value. for the + * non-legacy modes, the midpoint value is 0.5, which isn't representable + * exactly using integer precision. for the legacy modes, the midpoint + * value is 128/255 (i.e., 0x80), which is representable exactly using + * (gamma-corrected) integer precision. we therefore use the legacy + * modes when the input image precision is integer, and only use the + * (preferable) non-legacy modes when the input image precision is + * floating point. + * + * this avoids imperfect reconstruction of the image when using integer + * precision. see bug #786844. + */ + switch (gimp_image_get_precision (image_id)) + { + case GIMP_PRECISION_U8_LINEAR: + case GIMP_PRECISION_U8_GAMMA: + case GIMP_PRECISION_U16_LINEAR: + case GIMP_PRECISION_U16_GAMMA: + case GIMP_PRECISION_U32_LINEAR: + case GIMP_PRECISION_U32_GAMMA: + grain_extract_mode = GIMP_LAYER_MODE_GRAIN_EXTRACT_LEGACY; + grain_merge_mode = GIMP_LAYER_MODE_GRAIN_MERGE_LEGACY; + break; + + case GIMP_PRECISION_HALF_LINEAR: + case GIMP_PRECISION_HALF_GAMMA: + case GIMP_PRECISION_FLOAT_LINEAR: + case GIMP_PRECISION_FLOAT_GAMMA: + case GIMP_PRECISION_DOUBLE_LINEAR: + case GIMP_PRECISION_DOUBLE_GAMMA: + grain_extract_mode = GIMP_LAYER_MODE_GRAIN_EXTRACT; + grain_merge_mode = GIMP_LAYER_MODE_GRAIN_MERGE; + break; + } + + for (id = 0 ; id < wavelet_params.scales; ++id) + { + gint32 blur_id, tmp_id; + gchar scale_name[20]; + + gimp_progress_update ((gdouble) id / (gdouble) wavelet_params.scales); + + scale_ids[id] = new_scale_id; + + g_snprintf (scale_name, sizeof (scale_name), _("Scale %d"), id + 1); + gimp_item_set_name (new_scale_id, scale_name); + + tmp_id = gimp_layer_copy (new_scale_id); + gimp_image_insert_layer (image_id, tmp_id, parent_id, + gimp_image_get_item_position (image_id, + new_scale_id)); + wavelet_blur (tmp_id, pow(2.0, id)); + + blur_id = gimp_layer_copy (tmp_id); + gimp_image_insert_layer (image_id, blur_id, parent_id, + gimp_image_get_item_position (image_id, + tmp_id)); + + gimp_layer_set_mode (tmp_id, grain_extract_mode); + new_scale_id = gimp_image_merge_down (image_id, tmp_id, + GIMP_EXPAND_AS_NECESSARY); + scale_ids[id] = new_scale_id; + + gimp_item_set_visible (new_scale_id, FALSE); + + new_scale_id = blur_id; + } + + gimp_item_set_name (new_scale_id, _("Residual")); + + for (id = 0; id < wavelet_params.scales; id++) + { + gimp_image_reorder_item (image_id, scale_ids[id], parent_id, + gimp_image_get_item_position (image_id, + new_scale_id)); + gimp_layer_set_mode (scale_ids[id], grain_merge_mode); + + if (wavelet_params.create_masks) + { + gint32 mask_id = gimp_layer_create_mask (scale_ids[id], + GIMP_ADD_MASK_WHITE); + gimp_layer_add_mask (scale_ids[id], mask_id); + } + + gimp_item_set_visible (scale_ids[id], TRUE); + } + + if (wavelet_params.create_group) + gimp_item_set_visible (parent_id, TRUE); + + g_free (scale_ids); + + gimp_image_thaw_layers (image_id); + + gimp_image_undo_group_end (image_id); + + gimp_progress_update (1.0); + + values[0].data.d_status = status; + gimp_displays_flush (); + + /* set data for next use of filter */ + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_set_data (PLUG_IN_PROC, + &wavelet_params, sizeof (WaveletDecomposeParams)); + } + + gegl_exit (); +} + +static void +wavelet_blur (gint32 drawable_id, + gint radius) +{ + gint x, y, width, height; + + if (gimp_drawable_mask_intersect (drawable_id, &x, &y, &width, &height)) + { + GeglBuffer *buffer = gimp_drawable_get_buffer (drawable_id); + GeglBuffer *shadow = gimp_drawable_get_shadow_buffer (drawable_id); + + gegl_render_op (buffer, shadow, + "gegl:wavelet-blur", + "radius", (gdouble) radius, + NULL); + + gegl_buffer_flush (shadow); + gimp_drawable_merge_shadow (drawable_id, FALSE); + gimp_drawable_update (drawable_id, x, y, width, height); + g_object_unref (buffer); + g_object_unref (shadow); + } +} + +static gboolean +wavelet_decompose_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *table; + GtkWidget *button; + GtkObject *adj; + gboolean run; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("Wavelet decompose"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + table = gtk_table_new (3, 1, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* scales */ + + adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("Scales:"), SCALE_WIDTH, ENTRY_WIDTH, + wavelet_params.scales, + 1.0, 7.0, 1.0, 1.0, 0, + TRUE, 0, 0, + NULL, NULL); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_int_adjustment_update), + &wavelet_params.scales); + + /* create group layer */ + + button = gtk_check_button_new_with_mnemonic (_("Create a layer group to store the decomposition")); + gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + wavelet_params.create_group); + gtk_widget_show (button); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &wavelet_params.create_group); + + /* create layer masks */ + + button = gtk_check_button_new_with_mnemonic (_("Add a layer mask to each scales layers")); + gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + wavelet_params.create_masks); + gtk_widget_show (button); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &wavelet_params.create_masks); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/web-browser.c b/plug-ins/common/web-browser.c new file mode 100644 index 0000000..c751493 --- /dev/null +++ b/plug-ins/common/web-browser.c @@ -0,0 +1,214 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Web Browser Plug-in + * Copyright (C) 2003 Henrik Brix Andersen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include /* strlen, strstr */ + +#include + +#ifdef PLATFORM_OSX +#import +#endif + +#ifdef G_OS_WIN32 +#include +#endif + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-web-browser" +#define PLUG_IN_BINARY "web-browser" +#define PLUG_IN_ROLE "gimp-web-browser" + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gboolean browser_open_url (const gchar *url, + GError **error); + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_STRING, "url", "URL to open" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + "Open an URL in the user specified web browser", + "Opens the given URL in the user specified web browser.", + "Henrik Brix Andersen ", + "2003", + "2003/09/16", + NULL, NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (args), 0, + args, NULL); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpPDBStatusType status; + GError *error = NULL; + + *nreturn_vals = 1; + *return_vals = values; + + status = GIMP_PDB_SUCCESS; + + INIT_I18N (); + + if (nparams == 1 && + param[0].data.d_string != NULL && + strlen (param[0].data.d_string)) + { + if (! browser_open_url (param[0].data.d_string, &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; +} + +static gboolean +browser_open_url (const gchar *url, + GError **error) +{ +#ifdef G_OS_WIN32 + + HINSTANCE hinst = ShellExecute (GetDesktopWindow(), + "open", url, NULL, NULL, SW_SHOW); + + if ((gint) hinst <= 32) + { + const gchar *err; + + switch ((gint) hinst) + { + case 0 : + err = _("The operating system is out of memory or resources."); + break; + case ERROR_FILE_NOT_FOUND : + err = _("The specified file was not found."); + break; + case ERROR_PATH_NOT_FOUND : + err = _("The specified path was not found."); + break; + case ERROR_BAD_FORMAT : + err = _("The .exe file is invalid (non-Microsoft Win32 .exe or error in .exe image)."); + break; + case SE_ERR_ACCESSDENIED : + err = _("The operating system denied access to the specified file."); + break; + case SE_ERR_ASSOCINCOMPLETE : + err = _("The file name association is incomplete or invalid."); + break; + case SE_ERR_DDEBUSY : + err = _("DDE transaction busy"); + break; + case SE_ERR_DDEFAIL : + err = _("The DDE transaction failed."); + break; + case SE_ERR_DDETIMEOUT : + err = _("The DDE transaction timed out."); + break; + case SE_ERR_DLLNOTFOUND : + err = _("The specified DLL was not found."); + break; + case SE_ERR_NOASSOC : + err = _("There is no application associated with the given file name extension."); + break; + case SE_ERR_OOM : + err = _("There was not enough memory to complete the operation."); + break; + case SE_ERR_SHARE: + err = _("A sharing violation occurred."); + break; + default : + err = _("Unknown Microsoft Windows error."); + } + + g_set_error (error, 0, 0, _("Failed to open '%s': %s"), url, err); + + return FALSE; + } + + return TRUE; + +#elif defined(PLATFORM_OSX) + + NSURL *ns_url; + gboolean retval; + + NSAutoreleasePool *arp = [NSAutoreleasePool new]; + { + ns_url = [NSURL URLWithString: [NSString stringWithUTF8String: url]]; + retval = [[NSWorkspace sharedWorkspace] openURL: ns_url]; + } + [arp release]; + + return retval; + +#else + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + return gtk_show_uri (gdk_screen_get_default (), + url, + gtk_get_current_event_time(), + error); + +#endif +} diff --git a/plug-ins/common/web-page.c b/plug-ins/common/web-page.c new file mode 100644 index 0000000..3ceef8a --- /dev/null +++ b/plug-ins/common/web-page.c @@ -0,0 +1,551 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Webpage plug-in. + * Copyright (C) 2011 Mukund Sivaraman . + * Portions are copyright of the author of the + * file-open-location-dialog.c code. + */ + +#include "config.h" + +#include +#include + +#include + +#include "libgimp/stdplugins-intl.h" + +/* Defines */ +#define PLUG_IN_PROC "plug-in-web-page" +#define PLUG_IN_BINARY "web-page" +#define PLUG_IN_ROLE "gimp-web-page" +#define MAX_URL_LEN 2048 + +typedef struct +{ + char *url; + gint32 width; + gint font_size; + GdkPixbuf *pixbuf; + GError *error; +} WebpageVals; + +static WebpageVals webpagevals; + +typedef struct +{ + char url[MAX_URL_LEN]; + gint32 width; + gint font_size; +} WebpageSaveVals; + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); +static gboolean webpage_dialog (void); +static gint32 webpage_capture (void); + + +/* Global Variables */ +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + + +/* Functions */ + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "url", "URL of the webpage to screenshot" }, + { GIMP_PDB_INT32, "width", "The width of the screenshot (in pixels)" }, + { GIMP_PDB_INT32, "font-size", "The font size to use in the page (in pt)" } + }; + + static const GimpParamDef return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Create an image of a webpage"), + "The plug-in allows you to take a screenshot " + "of a webpage.", + "Mukund Sivaraman ", + "2011", + "2011", + N_("From _Webpage..."), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (args), + G_N_ELEMENTS (return_vals), + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/File/Create/Acquire"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + GimpRunMode run_mode = param[0].data.d_int32; + GimpPDBStatusType status = GIMP_PDB_EXECUTION_ERROR; + gint32 image_id = -1; + static GimpParam values[2]; + WebpageSaveVals save = {"https://www.gimp.org/", 1024, 12}; + + INIT_I18N (); + + /* initialize the return of the status */ + *nreturn_vals = 1; + *return_vals = values; + values[0].type = GIMP_PDB_STATUS; + + gimp_get_data (PLUG_IN_PROC, &save); + + webpagevals.url = g_strdup (save.url); + webpagevals.width = save.width; + webpagevals.font_size = save.font_size; + + /* how are we running today? */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + if (webpage_dialog ()) + status = GIMP_PDB_SUCCESS; + else + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* This is currently not supported. */ + break; + + case GIMP_RUN_NONINTERACTIVE: + webpagevals.url = param[1].data.d_string; + webpagevals.width = param[2].data.d_int32; + webpagevals.font_size = param[3].data.d_int32; + status = GIMP_PDB_SUCCESS; + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + image_id = webpage_capture (); + + if (image_id == -1) + { + status = GIMP_PDB_EXECUTION_ERROR; + + if (webpagevals.error) + { + *nreturn_vals = 2; + + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = webpagevals.error->message; + } + } + else + { + save.width = webpagevals.width; + save.font_size = webpagevals.font_size; + + if (strlen (webpagevals.url) < MAX_URL_LEN) + { + strncpy (save.url, webpagevals.url, MAX_URL_LEN); + save.url[MAX_URL_LEN - 1] = 0; + } + else + { + memset (save.url, 0, MAX_URL_LEN); + } + + gimp_set_data (PLUG_IN_PROC, &save, sizeof save); + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_display_new (image_id); + + *nreturn_vals = 2; + + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_id; + } + } + + values[0].data.d_status = status; +} + +static gboolean +webpage_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *image; + GtkWidget *label; + GtkWidget *entry; + GtkSizeGroup *sizegroup; + GtkAdjustment *adjustment; + GtkWidget *spinbutton; + GtkWidget *combo; + gint active; + gint status; + gboolean ret = FALSE; + + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + dialog = gimp_dialog_new (_("Create from webpage"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("Cre_ate"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + gimp_window_set_transient (GTK_WINDOW (dialog)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + image = gtk_image_new_from_icon_name (GIMP_ICON_WEB, + GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start (GTK_BOX (vbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + label = gtk_label_new (_("Enter location (URI):")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + entry = gtk_entry_new (); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_widget_set_size_request (entry, 400, -1); + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0); + + if (webpagevals.url) + gtk_entry_set_text (GTK_ENTRY (entry), + webpagevals.url); + gtk_widget_show (entry); + + sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + /* Width */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (_("Width (pixels):")); + gtk_size_group_add_widget (sizegroup, label); + + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (webpagevals.width, + 1, 8192, 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + /* Font size */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (_("Font size:")); + gtk_size_group_add_widget (sizegroup, label); + + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + combo = gimp_int_combo_box_new (_("Huge"), 16, + _("Large"), 14, + C_("web-page", "Default"), 12, + _("Small"), 10, + _("Tiny"), 8, + NULL); + + switch (webpagevals.font_size) + { + case 16: + case 14: + case 12: + case 10: + case 8: + active = webpagevals.font_size; + break; + default: + active = 12; + } + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), active); + + gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0); + gtk_widget_show (combo); + + g_object_unref (sizegroup); + + status = gimp_dialog_run (GIMP_DIALOG (dialog)); + if (status == GTK_RESPONSE_OK) + { + g_free (webpagevals.url); + webpagevals.url = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + + webpagevals.width = (gint) gtk_adjustment_get_value + (GTK_ADJUSTMENT (adjustment)); + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), + &webpagevals.font_size); + + ret = TRUE; + } + + gtk_widget_destroy (dialog); + + return ret; +} + +static void +notify_progress_cb (WebKitWebView *view, + GParamSpec *pspec, + gpointer user_data) +{ + static gdouble old_progress = 0.0; + gdouble progress; + + g_object_get (view, + "progress", &progress, + NULL); + + if ((progress - old_progress) > 0.01) + { + gimp_progress_update (progress); + old_progress = progress; + } +} + +static gboolean +load_error_cb (WebKitWebView *view, + WebKitWebFrame *web_frame, + gchar *uri, + gpointer web_error, + gpointer user_data) +{ + webpagevals.error = g_error_copy ((GError *) web_error); + + gtk_main_quit (); + + return TRUE; +} + +static void +notify_load_status_cb (WebKitWebView *view, + GParamSpec *pspec, + gpointer user_data) +{ + WebKitLoadStatus status; + + g_object_get (view, + "load-status", &status, + NULL); + + if (status == WEBKIT_LOAD_FINISHED) + { + if (!webpagevals.error) + { + webpagevals.pixbuf = gtk_offscreen_window_get_pixbuf + (GTK_OFFSCREEN_WINDOW (user_data)); + } + + gtk_main_quit (); + } +} + +static gint32 +webpage_capture (void) +{ + gint32 image = -1; + gchar *scheme; + GtkWidget *window; + GtkWidget *view; + WebKitWebSettings *settings; + char *ua_old; + char *ua; + + if (webpagevals.pixbuf) + { + g_object_unref (webpagevals.pixbuf); + webpagevals.pixbuf = NULL; + } + if (webpagevals.error) + { + g_error_free (webpagevals.error); + webpagevals.error = NULL; + } + + if ((!webpagevals.url) || + (strlen (webpagevals.url) == 0)) + { + g_set_error (&webpagevals.error, 0, 0, _("No URL was specified")); + return -1; + } + + scheme = g_uri_parse_scheme (webpagevals.url); + if (!scheme) + { + char *url; + + /* If we were not given a well-formed URL, make one. */ + + url = g_strconcat ("http://", webpagevals.url, NULL); + g_free (webpagevals.url); + webpagevals.url = url; + + g_free (scheme); + } + + if (webpagevals.width < 32) + { + g_warning ("Width '%d' is too small. Clamped to 32.", + webpagevals.width); + webpagevals.width = 32; + } + else if (webpagevals.width > 8192) + { + g_warning ("Width '%d' is too large. Clamped to 8192.", + webpagevals.width); + webpagevals.width = 8192; + } + + window = gtk_offscreen_window_new (); + gtk_widget_show (window); + + view = webkit_web_view_new (); + gtk_widget_show (view); + + gtk_widget_set_size_request (view, webpagevals.width, -1); + gtk_container_add (GTK_CONTAINER (window), view); + + /* Append "GIMP/" to the user agent string */ + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view)); + g_object_get (settings, + "user-agent", &ua_old, + NULL); + ua = g_strdup_printf ("%s GIMP/%s", ua_old, GIMP_VERSION); + g_object_set (settings, + "user-agent", ua, + NULL); + g_free (ua_old); + g_free (ua); + + /* Set font size */ + g_object_set (settings, + "default-font-size", webpagevals.font_size, + NULL); + + g_signal_connect (view, "notify::progress", + G_CALLBACK (notify_progress_cb), + window); + g_signal_connect (view, "load-error", + G_CALLBACK (load_error_cb), + window); + g_signal_connect (view, "notify::load-status", + G_CALLBACK (notify_load_status_cb), + window); + + gimp_progress_init_printf (_("Downloading webpage '%s'"), webpagevals.url); + + webkit_web_view_open (WEBKIT_WEB_VIEW (view), + webpagevals.url); + + gtk_main (); + + gtk_widget_destroy (window); + + gimp_progress_update (1.0); + + if (webpagevals.pixbuf) + { + gint width; + gint height; + gint32 layer; + + gimp_progress_init_printf (_("Transferring webpage image for '%s'"), + webpagevals.url); + + width = gdk_pixbuf_get_width (webpagevals.pixbuf); + height = gdk_pixbuf_get_height (webpagevals.pixbuf); + + image = gimp_image_new (width, height, GIMP_RGB); + + gimp_image_undo_disable (image); + layer = gimp_layer_new_from_pixbuf (image, _("Webpage"), + webpagevals.pixbuf, + 100, + gimp_image_get_default_new_layer_mode (image), + 0.0, 1.0); + gimp_image_insert_layer (image, layer, -1, 0); + gimp_image_undo_enable (image); + + g_object_unref (webpagevals.pixbuf); + webpagevals.pixbuf = NULL; + + gimp_progress_update (1.0); + } + + return image; +} -- cgit v1.2.3