summaryrefslogtreecommitdiffstats
path: root/plug-ins/common
diff options
context:
space:
mode:
Diffstat (limited to 'plug-ins/common')
-rw-r--r--plug-ins/common/Makefile.am1883
-rw-r--r--plug-ins/common/Makefile.in9492
-rw-r--r--plug-ins/common/align-layers.c748
-rw-r--r--plug-ins/common/animation-optimize.c1340
-rw-r--r--plug-ins/common/animation-play.c1763
-rw-r--r--plug-ins/common/blinds.c730
-rw-r--r--plug-ins/common/blur.c375
-rw-r--r--plug-ins/common/border-average.c468
-rw-r--r--plug-ins/common/busy-dialog.c309
-rw-r--r--plug-ins/common/cartoon.c880
-rw-r--r--plug-ins/common/checkerboard.c525
-rw-r--r--plug-ins/common/cml-explorer.c2569
-rw-r--r--plug-ins/common/color-cube-analyze.c502
-rw-r--r--plug-ins/common/color-enhance.c289
-rw-r--r--plug-ins/common/colorify.c398
-rw-r--r--plug-ins/common/colormap-remap.c752
-rw-r--r--plug-ins/common/compose.c1402
-rw-r--r--plug-ins/common/contrast-retinex.c840
-rw-r--r--plug-ins/common/crop-zealous.c326
-rw-r--r--plug-ins/common/curve-bend.c3402
-rw-r--r--plug-ins/common/decompose.c973
-rw-r--r--plug-ins/common/depth-merge.c1050
-rw-r--r--plug-ins/common/despeckle.c901
-rw-r--r--plug-ins/common/destripe.c530
-rw-r--r--plug-ins/common/edge-dog.c1029
-rw-r--r--plug-ins/common/emboss.c550
-rw-r--r--plug-ins/common/file-aa.c412
-rw-r--r--plug-ins/common/file-cel.c975
-rw-r--r--plug-ins/common/file-compressor.c1017
-rw-r--r--plug-ins/common/file-csource.c1045
-rw-r--r--plug-ins/common/file-desktop-link.c185
-rw-r--r--plug-ins/common/file-dicom.c1796
-rw-r--r--plug-ins/common/file-gbr.c367
-rw-r--r--plug-ins/common/file-gegl.c492
-rw-r--r--plug-ins/common/file-gif-load.c1252
-rw-r--r--plug-ins/common/file-gif-save.c2574
-rw-r--r--plug-ins/common/file-gih.c819
-rw-r--r--plug-ins/common/file-glob.c450
-rw-r--r--plug-ins/common/file-header.c439
-rw-r--r--plug-ins/common/file-heif.c2173
-rw-r--r--plug-ins/common/file-html-table.c750
-rw-r--r--plug-ins/common/file-jp2-load.c1344
-rw-r--r--plug-ins/common/file-jpegxl.c1174
-rw-r--r--plug-ins/common/file-mng.c1791
-rw-r--r--plug-ins/common/file-pat.c320
-rw-r--r--plug-ins/common/file-pcx.c1070
-rw-r--r--plug-ins/common/file-pdf-load.c2058
-rw-r--r--plug-ins/common/file-pdf-save.c1882
-rw-r--r--plug-ins/common/file-pix.c736
-rw-r--r--plug-ins/common/file-png.c2654
-rw-r--r--plug-ins/common/file-pnm.c1820
-rw-r--r--plug-ins/common/file-ps.c3940
-rw-r--r--plug-ins/common/file-psp.c2571
-rw-r--r--plug-ins/common/file-raw-data.c2261
-rw-r--r--plug-ins/common/file-sunras.c1784
-rw-r--r--plug-ins/common/file-svg.c914
-rw-r--r--plug-ins/common/file-tga.c1486
-rw-r--r--plug-ins/common/file-wmf.c1049
-rw-r--r--plug-ins/common/file-xbm.c1445
-rw-r--r--plug-ins/common/file-xmc.c2492
-rw-r--r--plug-ins/common/file-xpm.c881
-rw-r--r--plug-ins/common/file-xwd.c2587
-rw-r--r--plug-ins/common/film.c1287
-rw-r--r--plug-ins/common/filter-pack.c2178
-rw-r--r--plug-ins/common/fractal-trace.c829
-rw-r--r--plug-ins/common/gimprc.common90
-rw-r--r--plug-ins/common/goat-exercise.c119
-rw-r--r--plug-ins/common/gradient-map.c412
-rw-r--r--plug-ins/common/grid.c1011
-rw-r--r--plug-ins/common/guillotine.c305
-rw-r--r--plug-ins/common/hot.c782
-rw-r--r--plug-ins/common/jigsaw.c2563
-rw-r--r--plug-ins/common/mail.c830
-rw-r--r--plug-ins/common/max-rgb.c373
-rwxr-xr-xplug-ins/common/mkgen.pl234
-rw-r--r--plug-ins/common/nl-filter.c1149
-rw-r--r--plug-ins/common/photocopy.c935
-rw-r--r--plug-ins/common/plugin-browser.c918
-rw-r--r--plug-ins/common/plugin-defs.pl92
-rw-r--r--plug-ins/common/procedure-browser.c147
-rw-r--r--plug-ins/common/qbist.c912
-rw-r--r--plug-ins/common/sample-colorize.c3113
-rw-r--r--plug-ins/common/sharpen.c800
-rw-r--r--plug-ins/common/smooth-palette.c499
-rw-r--r--plug-ins/common/softglow.c713
-rw-r--r--plug-ins/common/sparkle.c1189
-rw-r--r--plug-ins/common/sphere-designer.c3166
-rw-r--r--plug-ins/common/tile-small.c1130
-rw-r--r--plug-ins/common/tile.c505
-rw-r--r--plug-ins/common/unit-editor.c700
-rw-r--r--plug-ins/common/van-gogh-lic.c904
-rw-r--r--plug-ins/common/warp.c1793
-rw-r--r--plug-ins/common/wavelet-decompose.c422
-rw-r--r--plug-ins/common/web-browser.c214
-rw-r--r--plug-ins/common/web-page.c551
95 files changed, 115896 insertions, 0 deletions
diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am
new file mode 100644
index 0000000..75fbd61
--- /dev/null
+++ b/plug-ins/common/Makefile.am
@@ -0,0 +1,1883 @@
+
+
+## ---------------------------------------------------------
+## 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_jpegxl_libexecdir = $(gimpplugindir)/plug-ins/file-jpegxl
+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_jpegxl_libexec_PROGRAMS = $(FILE_JPEGXL)
+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-jpegxl \
+ 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) \
+ $(GEGL_LIBS) \
+ $(LIBHEIF_LIBS) \
+ $(LCMS_LIBS) \
+ $(GEXIV2_LIBS) \
+ $(RT_LIBS) \
+ $(INTLLIBS) \
+ $(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_jpegxl_CFLAGS = $(JXL_CFLAGS)
+
+file_jpegxl_SOURCES = \
+ file-jpegxl.c
+
+file_jpegxl_LDADD = \
+ $(libgimpui) \
+ $(libgimpwidgets) \
+ $(libgimpmodule) \
+ $(libgimp) \
+ $(libgimpmath) \
+ $(libgimpconfig) \
+ $(libgimpcolor) \
+ $(libgimpbase) \
+ $(GTK_LIBS) \
+ $(GEGL_LIBS) \
+ $(JXL_THREADS_LIBS) \
+ $(JXL_LIBS) \
+ $(GEXIV2_LIBS) \
+ $(RT_LIBS) \
+ $(INTLLIBS) \
+ $(file_jpegxl_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..11f7613
--- /dev/null
+++ b/plug-ins/common/Makefile.in
@@ -0,0 +1,9492 @@
+# Makefile.in generated by automake 1.16.3 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-jpegxl$(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)/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_jpegxl_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_jpegxl_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_jpegxl_OBJECTS = file_jpegxl-file-jpegxl.$(OBJEXT)
+file_jpegxl_OBJECTS = $(am_file_jpegxl_OBJECTS)
+file_jpegxl_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_jpegxl_RC)
+file_jpegxl_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(file_jpegxl_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_jpegxl-file-jpegxl.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_jpegxl_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_jpegxl_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_JPEGXL = @FILE_JPEGXL@
+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_RELEASE = @GIMP_RELEASE@
+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@
+JXL_CFLAGS = @JXL_CFLAGS@
+JXL_LIBS = @JXL_LIBS@
+JXL_THREADS_CFLAGS = @JXL_THREADS_CFLAGS@
+JXL_THREADS_LIBS = @JXL_THREADS_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@
+LIBJXL_REQUIRED_VERSION = @LIBJXL_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_jpegxl_RC = file-jpegxl.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_jpegxl_libexecdir = $(gimpplugindir)/plug-ins/file-jpegxl
+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_jpegxl_libexec_PROGRAMS = $(FILE_JPEGXL)
+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) \
+ $(GEGL_LIBS) \
+ $(LIBHEIF_LIBS) \
+ $(LCMS_LIBS) \
+ $(GEXIV2_LIBS) \
+ $(RT_LIBS) \
+ $(INTLLIBS) \
+ $(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_jpegxl_CFLAGS = $(JXL_CFLAGS)
+file_jpegxl_SOURCES = \
+ file-jpegxl.c
+
+file_jpegxl_LDADD = \
+ $(libgimpui) \
+ $(libgimpwidgets) \
+ $(libgimpmodule) \
+ $(libgimp) \
+ $(libgimpmath) \
+ $(libgimpconfig) \
+ $(libgimpcolor) \
+ $(libgimpbase) \
+ $(GTK_LIBS) \
+ $(GEGL_LIBS) \
+ $(JXL_THREADS_LIBS) \
+ $(JXL_LIBS) \
+ $(GEXIV2_LIBS) \
+ $(RT_LIBS) \
+ $(INTLLIBS) \
+ $(file_jpegxl_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_jpegxl_libexecPROGRAMS: $(file_jpegxl_libexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(file_jpegxl_libexec_PROGRAMS)'; test -n "$(file_jpegxl_libexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(file_jpegxl_libexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(file_jpegxl_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_jpegxl_libexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(file_jpegxl_libexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-file_jpegxl_libexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(file_jpegxl_libexec_PROGRAMS)'; test -n "$(file_jpegxl_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_jpegxl_libexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(file_jpegxl_libexecdir)" && rm -f $$files
+
+clean-file_jpegxl_libexecPROGRAMS:
+ @list='$(file_jpegxl_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-jpegxl$(EXEEXT): $(file_jpegxl_OBJECTS) $(file_jpegxl_DEPENDENCIES) $(EXTRA_file_jpegxl_DEPENDENCIES)
+ @rm -f file-jpegxl$(EXEEXT)
+ $(AM_V_CCLD)$(file_jpegxl_LINK) $(file_jpegxl_OBJECTS) $(file_jpegxl_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_jpegxl-file-jpegxl.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_jpegxl-file-jpegxl.o: file-jpegxl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_jpegxl_CFLAGS) $(CFLAGS) -MT file_jpegxl-file-jpegxl.o -MD -MP -MF $(DEPDIR)/file_jpegxl-file-jpegxl.Tpo -c -o file_jpegxl-file-jpegxl.o `test -f 'file-jpegxl.c' || echo '$(srcdir)/'`file-jpegxl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_jpegxl-file-jpegxl.Tpo $(DEPDIR)/file_jpegxl-file-jpegxl.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-jpegxl.c' object='file_jpegxl-file-jpegxl.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_jpegxl_CFLAGS) $(CFLAGS) -c -o file_jpegxl-file-jpegxl.o `test -f 'file-jpegxl.c' || echo '$(srcdir)/'`file-jpegxl.c
+
+file_jpegxl-file-jpegxl.obj: file-jpegxl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(file_jpegxl_CFLAGS) $(CFLAGS) -MT file_jpegxl-file-jpegxl.obj -MD -MP -MF $(DEPDIR)/file_jpegxl-file-jpegxl.Tpo -c -o file_jpegxl-file-jpegxl.obj `if test -f 'file-jpegxl.c'; then $(CYGPATH_W) 'file-jpegxl.c'; else $(CYGPATH_W) '$(srcdir)/file-jpegxl.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/file_jpegxl-file-jpegxl.Tpo $(DEPDIR)/file_jpegxl-file-jpegxl.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file-jpegxl.c' object='file_jpegxl-file-jpegxl.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_jpegxl_CFLAGS) $(CFLAGS) -c -o file_jpegxl-file-jpegxl.obj `if test -f 'file-jpegxl.c'; then $(CYGPATH_W) 'file-jpegxl.c'; else $(CYGPATH_W) '$(srcdir)/file-jpegxl.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_jpegxl_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_jpegxl_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_jpegxl-file-jpegxl.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_jpegxl_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_jpegxl-file-jpegxl.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_jpegxl_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_jpegxl_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_jpegxl_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_jpegxl_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 <narazaki@InetQ.or.jp>
+ * Version: 0.26
+ *
+ * Copyright (C) 1997-1998 Shuji Narazaki <narazaki@InetQ.or.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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <narazaki@InetQ.or.jp>",
+ "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>/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<length; 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);
+ count += align_layers_count_visibles_layers (children,
+ children_num);
+ g_free (children);
+ }
+ else if (gimp_item_is_layer (item))
+ {
+ count += 1;
+ }
+ }
+ }
+
+ return count;
+}
+
+static AlignData
+align_layers_gather_data (gint *layers,
+ gint layer_num,
+ gint background)
+{
+ AlignData data;
+ gint min_x = G_MAXINT;
+ gint min_y = G_MAXINT;
+ gint max_x = G_MININT;
+ gint max_y = G_MININT;
+ gint index;
+ gint orig_x = 0;
+ gint orig_y = 0;
+ gint offset_x = 0;
+ gint offset_y = 0;
+
+ data.step_x = 0;
+ data.step_y = 0;
+ data.base_x = 0;
+ data.base_y = 0;
+
+ /* 0 is the top layer */
+ for (index = 0; index < layer_num; index++)
+ {
+ gimp_drawable_offsets (layers[index], &orig_x, &orig_y);
+
+ align_layers_get_align_offsets (layers[index],
+ &offset_x,
+ &offset_y);
+ orig_x += offset_x;
+ orig_y += offset_y;
+
+ min_x = MIN (min_x, orig_x);
+ max_x = MAX (max_x, orig_x);
+ min_y = MIN (min_y, orig_y);
+ max_y = MAX (max_y, orig_y);
+ }
+
+ if (VALS.base_is_bottom_layer)
+ {
+ gimp_drawable_offsets (background, &orig_x, &orig_y);
+
+ align_layers_get_align_offsets (background,
+ &offset_x,
+ &offset_y);
+ orig_x += offset_x;
+ orig_y += offset_y;
+ data.base_x = min_x = orig_x;
+ data.base_y = min_y = orig_y;
+ }
+
+ if (layer_num > 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+#define EXPERIMENTAL_BACKDROP_CODE
+*/
+
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+
+#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@gimp.org>",
+ "Adam D. Moss <adam@gimp.org>",
+ "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@gimp.org>",
+ "Adam D. Moss <adam@gimp.org>",
+ "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@gimp.org>",
+ "Adam D. Moss <adam@gimp.org>",
+ "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, "<Image>/Filters/Animation");
+ gimp_plugin_menu_register (OPTIMIZE_DIFF_PROC, "<Image>/Filters/Animation");
+ gimp_plugin_menu_register (UNOPTIMIZE_PROC, "<Image>/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@gimp.org>",
+ "Adam D. Moss <adam@gimp.org>",
+ "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@gimp.org>",
+ "Adam D. Moss <adam@gimp.org>",
+ "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, "<Image>/Filters/Animation");
+ gimp_plugin_menu_register (FIND_BACKDROP_PROC, "<Image>/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<rawwidth+rawx; i++)
+ {
+ if (i>=0 && i<dest_width)
+ {
+ if ((!has_alpha) || ((*(srcptr+rawbpp-1))&128))
+ {
+ gint pi;
+
+ for (pi = 0; pi < pixelstep-1; pi++)
+ {
+ dest[i*pixelstep +pi] = *(srcptr + pi);
+ }
+
+ dest[i*pixelstep + pixelstep - 1] = 255;
+ }
+ }
+
+ srcptr += rawbpp;
+ }
+}
+
+
+static gint32
+do_optimizations (GimpRunMode run_mode,
+ gboolean diff_only)
+{
+ static guchar *rawframe = NULL;
+ guchar *srcptr;
+ guchar *destptr;
+ gint row, this_frame_num;
+ guint32 frame_sizebytes;
+ gint32 new_layer_id;
+ DisposeType dispose;
+ guchar *this_frame = NULL;
+ guchar *last_frame = NULL;
+ guchar *opti_frame = NULL;
+ guchar *back_frame = NULL;
+
+ gint this_delay;
+ gint cumulated_delay = 0;
+ gint last_true_frame = -1;
+ gint buflen;
+
+ gchar *oldlayer_name;
+ gchar *newlayer_name;
+
+ gboolean can_combine;
+
+ gint32 bbox_top, bbox_bottom, bbox_left, bbox_right;
+ gint32 rbox_top, rbox_bottom, rbox_left, rbox_right;
+
+ switch (opmode)
+ {
+ case OPUNOPTIMIZE:
+ gimp_progress_init (_("Unoptimizing animation"));
+ break;
+ case OPFOREGROUND:
+ gimp_progress_init (_("Removing animation background"));
+ break;
+ case OPBACKGROUND:
+ gimp_progress_init (_("Finding animation background"));
+ break;
+ case OPOPTIMIZE:
+ default:
+ gimp_progress_init (_("Optimizing animation"));
+ break;
+ }
+
+ width = gimp_image_width (image_id);
+ height = gimp_image_height (image_id);
+ layers = gimp_image_get_layers (image_id, &total_frames);
+ imagetype = gimp_image_base_type (image_id);
+ pixelstep = (imagetype == GIMP_RGB) ? 4 : 2;
+
+ drawabletype_alpha = (imagetype == GIMP_RGB) ? GIMP_RGBA_IMAGE :
+ ((imagetype == GIMP_INDEXED) ? GIMP_INDEXEDA_IMAGE : GIMP_GRAYA_IMAGE);
+
+ frame_sizebytes = width * height * pixelstep;
+
+ this_frame = g_malloc (frame_sizebytes);
+ last_frame = g_malloc (frame_sizebytes);
+ opti_frame = g_malloc (frame_sizebytes);
+
+ if (opmode == OPBACKGROUND ||
+ opmode == OPFOREGROUND)
+ back_frame = g_malloc (frame_sizebytes);
+
+ total_alpha (this_frame, width*height, pixelstep);
+ total_alpha (last_frame, width*height, pixelstep);
+
+ new_image_id = gimp_image_new(width, height, imagetype);
+ gimp_image_undo_disable (new_image_id);
+
+ if (imagetype == GIMP_INDEXED)
+ {
+ palette = gimp_image_get_colormap (image_id, &ncolors);
+ gimp_image_set_colormap (new_image_id, palette, ncolors);
+ }
+
+#if 1
+ if (opmode == OPBACKGROUND ||
+ opmode == OPFOREGROUND)
+ {
+ /* iterate through all rows of all frames, find statistical
+ mode for each pixel position. */
+ gint i,j;
+ guchar **these_rows;
+ guchar **red;
+ guchar **green;
+ guchar **blue;
+ guint **count;
+ guint *num_colors;
+
+ these_rows = g_new (guchar *, total_frames);
+ red = g_new (guchar *, total_frames);
+ green = g_new (guchar *, total_frames);
+ blue = g_new (guchar *, total_frames);
+ count = g_new (guint *, total_frames);
+
+ num_colors = g_new (guint, width);
+
+ for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
+ {
+ these_rows[this_frame_num] = g_malloc(width * pixelstep);
+
+ red[this_frame_num] = g_new (guchar, width);
+ green[this_frame_num] = g_new (guchar, width);
+ blue[this_frame_num] = g_new (guchar, width);
+
+ count[this_frame_num] = g_new0(guint, width);
+ }
+
+ for (row = 0; row < height; row++)
+ {
+ memset(num_colors, 0, width * sizeof(guint));
+
+ for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
+ {
+ gint32 drawable_ID = layers[total_frames-(this_frame_num+1)];
+
+ dispose = get_frame_disposal (this_frame_num);
+
+ compose_row (this_frame_num,
+ dispose,
+ row,
+ these_rows[this_frame_num],
+ width,
+ drawable_ID,
+ FALSE);
+ }
+
+ for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
+ {
+ for (i=0; i<width; i++)
+ {
+ if (these_rows[this_frame_num][i * pixelstep + pixelstep -1]
+ >= 128)
+ {
+ for (j=0; j<num_colors[i]; j++)
+ {
+
+ switch (pixelstep)
+ {
+ case 4:
+ if (these_rows[this_frame_num][i * 4 +0] ==
+ red[j][i] &&
+ these_rows[this_frame_num][i * 4 +1] ==
+ green[j][i] &&
+ these_rows[this_frame_num][i * 4 +2] ==
+ blue[j][i])
+ {
+ (count[j][i])++;
+ goto same;
+ }
+ break;
+ case 2:
+ if (these_rows[this_frame_num][i * 2 +0] ==
+ red[j][i])
+ {
+ (count[j][i])++;
+ goto same;
+ }
+ break;
+ default:
+ g_error ("Eeep!");
+ break;
+ }
+ }
+
+ count[num_colors[i]][i] = 1;
+ red[num_colors[i]][i] =
+ these_rows[this_frame_num][i * pixelstep];
+ if (pixelstep == 4)
+ {
+ green[num_colors[i]][i] =
+ these_rows[this_frame_num][i * 4 +1];
+ blue[num_colors[i]][i] =
+ these_rows[this_frame_num][i * 4 +2];
+ }
+ num_colors[i]++;
+ }
+ same:
+ /* nop */;
+ }
+ }
+
+ for (i=0; i<width; i++)
+ {
+ guint best_count = 0;
+ guchar best_r = 255, best_g = 0, best_b = 255;
+
+ for (j=0; j<num_colors[i]; j++)
+ {
+ if (count[j][i] > 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_num<total_frames; this_frame_num++)
+ {
+ g_free (these_rows[this_frame_num]);
+ g_free (red[this_frame_num]);
+ g_free (green[this_frame_num]);
+ g_free (blue[this_frame_num]);
+ g_free (count[this_frame_num]);
+ }
+
+ g_free (these_rows);
+ g_free (red);
+ g_free (green);
+ g_free (blue);
+ g_free (count);
+ g_free (num_colors);
+ }
+#endif
+
+ if (opmode == OPBACKGROUND)
+ {
+ GeglBuffer *buffer;
+ const Babl *format;
+
+ new_layer_id = gimp_layer_new (new_image_id,
+ "Backgroundx",
+ width, height,
+ drawabletype_alpha,
+ 100.0,
+ gimp_image_get_default_new_layer_mode (new_image_id));
+
+ gimp_image_insert_layer (new_image_id, new_layer_id, -1, 0);
+
+ buffer = gimp_drawable_get_buffer (new_layer_id);
+
+ format = get_format (new_layer_id);
+
+ gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
+ format, back_frame,
+ GEGL_ABYSS_NONE);
+
+ g_object_unref (buffer);
+ }
+ else
+ {
+ for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
+ {
+ /*
+ * BUILD THIS FRAME into our 'this_frame' buffer.
+ */
+
+ gint32 drawable_ID = layers[total_frames-(this_frame_num+1)];
+
+ /* Image has been closed/etc since we got the layer list? */
+ /* FIXME - How do we tell if a gimp_drawable_get() fails? */
+ if (gimp_drawable_width (drawable_ID) == 0)
+ {
+ gimp_quit ();
+ }
+
+ this_delay = get_frame_duration (this_frame_num);
+ dispose = get_frame_disposal (this_frame_num);
+
+ for (row = 0; row < height; row++)
+ {
+ compose_row (this_frame_num,
+ dispose,
+ row,
+ &this_frame[pixelstep*width * row],
+ width,
+ drawable_ID,
+ FALSE
+ );
+ }
+
+ if (opmode == OPFOREGROUND)
+ {
+ gint xit, yit, byteit;
+
+ for (yit=0; yit<height; yit++)
+ {
+ for (xit=0; xit<width; xit++)
+ {
+ for (byteit=0; byteit<pixelstep-1; byteit++)
+ {
+ if (back_frame[yit*width*pixelstep + xit*pixelstep
+ + byteit]
+ !=
+ this_frame[yit*width*pixelstep + xit*pixelstep
+ + byteit])
+ {
+ goto enough;
+ }
+ }
+ this_frame[yit*width*pixelstep + xit*pixelstep
+ + pixelstep - 1] = 0;
+ enough:
+ /* nop */;
+ }
+ }
+ }
+
+ can_combine = FALSE;
+ bbox_left = 0;
+ bbox_top = 0;
+ bbox_right = width;
+ bbox_bottom = height;
+ rbox_left = 0;
+ rbox_top = 0;
+ rbox_right = width;
+ rbox_bottom = height;
+
+ /* copy 'this' frame into a buffer which we can safely molest */
+ memcpy (opti_frame, this_frame, frame_sizebytes);
+ /*
+ *
+ * OPTIMIZE HERE!
+ *
+ */
+ if (
+ (this_frame_num != 0) /* Can't delta bottom frame! */
+ && (opmode == OPOPTIMIZE)
+ )
+ {
+ gint xit, yit, byteit;
+
+ can_combine = TRUE;
+
+ /*
+ * SEARCH FOR BOUNDING BOX
+ */
+ bbox_left = width;
+ bbox_top = height;
+ bbox_right = 0;
+ bbox_bottom = 0;
+ rbox_left = width;
+ rbox_top = height;
+ rbox_right = 0;
+ rbox_bottom = 0;
+
+ for (yit=0; yit<height; yit++)
+ {
+ for (xit=0; xit<width; xit++)
+ {
+ gboolean keep_pix;
+ gboolean opaq_pix;
+
+ /* Check if 'this' and 'last' are transparent */
+ if (!(this_frame[yit*width*pixelstep + xit*pixelstep
+ + pixelstep-1]&128)
+ &&
+ !(last_frame[yit*width*pixelstep + xit*pixelstep
+ + pixelstep-1]&128))
+ {
+ keep_pix = FALSE;
+ opaq_pix = FALSE;
+ goto decided;
+ }
+ /* Check if just 'this' is transparent */
+ if ((last_frame[yit*width*pixelstep + xit*pixelstep
+ + pixelstep-1]&128)
+ &&
+ !(this_frame[yit*width*pixelstep + xit*pixelstep
+ + pixelstep-1]&128))
+ {
+ keep_pix = TRUE;
+ opaq_pix = FALSE;
+ can_combine = FALSE;
+ goto decided;
+ }
+ /* Check if just 'last' is transparent */
+ if (!(last_frame[yit*width*pixelstep + xit*pixelstep
+ + pixelstep-1]&128)
+ &&
+ (this_frame[yit*width*pixelstep + xit*pixelstep
+ + pixelstep-1]&128))
+ {
+ keep_pix = TRUE;
+ opaq_pix = TRUE;
+ goto decided;
+ }
+ /* If 'last' and 'this' are opaque, we have
+ * to check if they're the same color - we
+ * only have to keep the pixel if 'last' or
+ * 'this' are opaque and different.
+ */
+ keep_pix = FALSE;
+ opaq_pix = TRUE;
+ for (byteit=0; byteit<pixelstep-1; byteit++)
+ {
+ if ((last_frame[yit*width*pixelstep + xit*pixelstep
+ + byteit]
+ !=
+ this_frame[yit*width*pixelstep + xit*pixelstep
+ + byteit])
+ )
+ {
+ keep_pix = TRUE;
+ goto decided;
+ }
+ }
+ decided:
+ if (opaq_pix)
+ {
+ if (xit<rbox_left) rbox_left=xit;
+ if (xit>rbox_right) rbox_right=xit;
+ if (yit<rbox_top) rbox_top=yit;
+ if (yit>rbox_bottom) rbox_bottom=yit;
+ }
+ if (keep_pix)
+ {
+ if (xit<bbox_left) bbox_left=xit;
+ if (xit>bbox_right) bbox_right=xit;
+ if (yit<bbox_top) bbox_top=yit;
+ if (yit>bbox_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<pixelstep-1; byteit++)
+ {
+ if (opti_frame[yit*width*pixelstep
+ + (xit-1)*pixelstep
+ + byteit]
+ !=
+ last_frame[yit*width*pixelstep
+ + xit*pixelstep
+ + byteit])
+ {
+ goto skip_right;
+ }
+ }
+ /* copy the color and alpha */
+ for (byteit=0; byteit<pixelstep; byteit++)
+ {
+ opti_frame[yit*width*pixelstep
+ + xit*pixelstep
+ + byteit]
+ = last_frame[yit*width*pixelstep
+ + xit*pixelstep
+ + byteit];
+ }
+ }
+ skip_right:
+ /* nop */;
+ } /* xit */
+
+ /* Compare with next pixels from right to left */
+ for (xit = bbox_right - 2; xit >= 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<pixelstep-1; byteit++)
+ {
+ if (opti_frame[yit*width*pixelstep
+ + (xit+1)*pixelstep
+ + byteit]
+ !=
+ last_frame[yit*width*pixelstep
+ + xit*pixelstep
+ + byteit])
+ {
+ goto skip_left;
+ }
+ }
+ /* copy the color and alpha */
+ for (byteit=0; byteit<pixelstep; byteit++)
+ {
+ opti_frame[yit*width*pixelstep
+ + xit*pixelstep
+ + byteit]
+ = last_frame[yit*width*pixelstep
+ + xit*pixelstep
+ + byteit];
+ }
+ }
+ skip_left:
+ /* nop */;
+ } /* xit */
+ } /* yit */
+ }
+
+ /*
+ * Collapse opti_frame data down such that the data
+ * which occupies the bounding box sits at the start
+ * of the data (for convenience with ..set_rect()).
+ */
+ destptr = opti_frame;
+ /*
+ * If can_combine, then it's safe to use our optimized
+ * alpha information. Otherwise, an opaque pixel became
+ * transparent this frame, and we'll have to use the
+ * actual true frame's alpha.
+ */
+ if (can_combine)
+ srcptr = opti_frame;
+ else
+ srcptr = this_frame;
+ for (yit=bbox_top; yit<bbox_bottom; yit++)
+ {
+ for (xit=bbox_left; xit<bbox_right; xit++)
+ {
+ for (byteit=0; byteit<pixelstep; byteit++)
+ {
+ *(destptr++) = srcptr[yit*pixelstep*width +
+ pixelstep*xit + byteit];
+ }
+ }
+ }
+ } /* !bot frame? */
+ else
+ {
+ memcpy (opti_frame, this_frame, frame_sizebytes);
+ }
+
+ /*
+ *
+ * REMEMBER THE ANIMATION STATUS TO DELTA AGAINST NEXT TIME
+ *
+ */
+ memcpy (last_frame, this_frame, frame_sizebytes);
+
+
+ /*
+ *
+ * PUT THIS FRAME INTO A NEW LAYER IN THE NEW IMAGE
+ *
+ */
+
+ oldlayer_name =
+ gimp_item_get_name(layers[total_frames-(this_frame_num+1)]);
+
+ buflen = strlen(oldlayer_name) + 40;
+
+ newlayer_name = g_malloc(buflen);
+
+ remove_disposal_tag(newlayer_name, oldlayer_name);
+ g_free(oldlayer_name);
+
+ oldlayer_name = g_malloc(buflen);
+
+ remove_ms_tag(oldlayer_name, newlayer_name);
+
+ g_snprintf(newlayer_name, buflen, "%s(%dms)%s",
+ oldlayer_name, this_delay,
+ (this_frame_num == 0) ? "" :
+ can_combine ? "(combine)" : "(replace)");
+
+ g_free(oldlayer_name);
+
+ /* Empty frame! */
+ if (bbox_right <= bbox_left ||
+ bbox_bottom <= bbox_top)
+ {
+ cumulated_delay += this_delay;
+
+ g_free (newlayer_name);
+
+ oldlayer_name = gimp_item_get_name (last_true_frame);
+
+ buflen = strlen (oldlayer_name) + 40;
+
+ newlayer_name = g_malloc (buflen);
+
+ remove_disposal_tag (newlayer_name, oldlayer_name);
+ g_free (oldlayer_name);
+
+ oldlayer_name = g_malloc (buflen);
+
+ remove_ms_tag (oldlayer_name, newlayer_name);
+
+ g_snprintf (newlayer_name, buflen, "%s(%dms)%s",
+ oldlayer_name, cumulated_delay,
+ (this_frame_num == 0) ? "" :
+ can_combine ? "(combine)" : "(replace)");
+
+ gimp_item_set_name (last_true_frame, newlayer_name);
+
+ g_free (newlayer_name);
+ }
+ else
+ {
+ GeglBuffer *buffer;
+ const Babl *format;
+
+ cumulated_delay = this_delay;
+
+ last_true_frame =
+ new_layer_id = gimp_layer_new (new_image_id,
+ newlayer_name,
+ bbox_right-bbox_left,
+ bbox_bottom-bbox_top,
+ drawabletype_alpha,
+ 100.0,
+ gimp_image_get_default_new_layer_mode (new_image_id));
+ g_free (newlayer_name);
+
+ gimp_image_insert_layer (new_image_id, new_layer_id, -1, 0);
+
+ buffer = gimp_drawable_get_buffer (new_layer_id);
+
+ format = get_format (new_layer_id);
+
+ gegl_buffer_set (buffer,
+ GEGL_RECTANGLE (0, 0,
+ bbox_right-bbox_left,
+ bbox_bottom-bbox_top), 0,
+ format, opti_frame,
+ GEGL_AUTO_ROWSTRIDE);
+
+ g_object_unref (buffer);
+ gimp_item_transform_translate (new_layer_id, bbox_left, bbox_top);
+ }
+
+ gimp_progress_update (((gdouble) this_frame_num + 1.0) /
+ ((gdouble) total_frames));
+ }
+
+ gimp_progress_update (1.0);
+ }
+
+ gimp_image_undo_enable (new_image_id);
+
+ if (run_mode != GIMP_RUN_NONINTERACTIVE)
+ gimp_display_new (new_image_id);
+
+ g_free (rawframe);
+ rawframe = NULL;
+
+ g_free (last_frame);
+ last_frame = NULL;
+
+ g_free (this_frame);
+ this_frame = NULL;
+
+ g_free (opti_frame);
+ opti_frame = NULL;
+
+ g_free (back_frame);
+ back_frame = NULL;
+
+ return new_image_id;
+}
+
+/* Util. */
+
+static DisposeType
+get_frame_disposal (guint whichframe)
+{
+ gchar *layer_name;
+ DisposeType disposal;
+
+ layer_name = gimp_item_get_name(layers[total_frames-(whichframe+1)]);
+ disposal = parse_disposal_tag(layer_name);
+ g_free(layer_name);
+
+ return disposal;
+}
+
+static guint32
+get_frame_duration (guint whichframe)
+{
+ gchar* layer_name;
+ gint duration = 0;
+
+ layer_name = gimp_item_get_name(layers[total_frames-(whichframe+1)]);
+ if (layer_name)
+ {
+ duration = parse_ms_tag(layer_name);
+ g_free(layer_name);
+ }
+
+ if (duration < 0) duration = 100; /* FIXME for default-if-not-said */
+ if (duration == 0) duration = 100; /* FIXME - 0-wait is nasty */
+
+ return (guint32) duration;
+}
+
+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 0;
+
+ do
+ {
+ sum *= 10;
+ sum += str[offset] - '0';
+ offset++;
+ }
+ while ((offset<length) && (g_ascii_isdigit (str[offset])));
+
+ if (length-offset <= 2)
+ return FALSE;
+
+ /* eat any spaces between number and 'ms' */
+ while ((offset<length) && (str[offset] == ' '))
+ offset++;
+
+ if ((length-offset <= 2) ||
+ (g_ascii_toupper (str[offset]) != 'M') ||
+ (g_ascii_toupper (str[offset+1]) != 'S'))
+ return FALSE;
+
+ offset += 2;
+
+ /* eat any spaces between 'ms' and close-parenthesis */
+ while ((offset<length) && (str[offset] == ' '))
+ offset++;
+
+ if ((length-offset < 1) || (str[offset] != ')'))
+ return FALSE;
+
+ offset++;
+
+ *duration = sum;
+ *taglength = offset;
+
+ return TRUE;
+}
+
+static int
+parse_ms_tag (const char *str)
+{
+ gint i;
+ gint rtn;
+ gint dummy;
+ gint length;
+
+ length = strlen (str);
+
+ for (i = 0; i < length; i++)
+ {
+ if (is_ms_tag (&str[i], &rtn, &dummy))
+ return rtn;
+ }
+
+ return -1;
+}
+
+static gboolean
+is_disposal_tag (const gchar *str,
+ DisposeType *disposal,
+ gint *taglength)
+{
+ if (strlen (str) != 9)
+ return FALSE;
+
+ if (strncmp (str, "(combine)", 9) == 0)
+ {
+ *taglength = 9;
+ *disposal = DISPOSE_COMBINE;
+ return TRUE;
+ }
+ else if (strncmp (str, "(replace)", 9) == 0)
+ {
+ *taglength = 9;
+ *disposal = DISPOSE_REPLACE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static DisposeType
+parse_disposal_tag (const gchar *str)
+{
+ DisposeType rtn;
+ gint i, dummy;
+ gint length;
+
+ length = strlen(str);
+
+ for (i=0; i<length; i++)
+ {
+ if (is_disposal_tag (&str[i], &rtn, &dummy))
+ {
+ return rtn;
+ }
+ }
+
+ return DISPOSE_UNDEFINED; /* FIXME */
+}
+
+static void
+remove_disposal_tag (gchar *dest,
+ gchar *src)
+{
+ gint offset = 0;
+ gint destoffset = 0;
+ gint length;
+ int taglength;
+ DisposeType dummy;
+
+ length = strlen(src);
+
+ strcpy(dest, src);
+
+ while (offset<=length)
+ {
+ if (is_disposal_tag(&src[offset], &dummy, &taglength))
+ {
+ offset += taglength;
+ }
+ dest[destoffset] = src[offset];
+ destoffset++;
+ offset++;
+ }
+
+ dest[offset] = '\0';
+}
+
+static void
+remove_ms_tag (gchar *dest,
+ gchar *src)
+{
+ gint offset = 0;
+ gint destoffset = 0;
+ gint length;
+ gint taglength;
+ gint dummy;
+
+ length = strlen(src);
+
+ strcpy(dest, src);
+
+ while (offset<=length)
+ {
+ if (is_ms_tag(&src[offset], &dummy, &taglength))
+ {
+ offset += taglength;
+ }
+ dest[destoffset] = src[offset];
+ destoffset++;
+ offset++;
+ }
+
+ dest[offset] = '\0';
+}
diff --git a/plug-ins/common/animation-play.c b/plug-ins/common/animation-play.c
new file mode 100644
index 0000000..7e0ab26
--- /dev/null
+++ b/plug-ins/common/animation-play.c
@@ -0,0 +1,1763 @@
+/*
+ * Animation Playback plug-in version 0.99.1
+ *
+ * (c) Adam D. Moss : 1997-2000 : adam@gimp.org : adam@foxbox.org
+ * (c) Mircea Purdea : 2009 : someone_else@exhalus.net
+ * (c) Jehan : 2012 : jehan at girinstud.io
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * TODO:
+ * pdb interface - should we bother?
+ *
+ * speedups (caching? most bottlenecks seem to be in pixelrgns)
+ * -> do pixelrgns properly!
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#undef GDK_DISABLE_DEPRECATED
+#include <libgimp/gimpui.h>
+
+#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@gimp.org>",
+ "Adam D. Moss <adam@gimp.org>",
+ "1997, 1998...",
+ N_("_Playback..."),
+ "RGB*, INDEXED*, GRAY*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (args), 0,
+ args, NULL);
+
+ gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/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, "<control>R", N_("Reload the image"),
+ G_CALLBACK (refresh_callback) },
+
+ { "help", "help-browser",
+ NULL, NULL, NULL,
+ G_CALLBACK (help_callback) },
+
+ { "close", "window-close",
+ NULL, "<control>W", NULL,
+ G_CALLBACK (close_callback)
+ },
+ {
+ "quit", "application-quit",
+ NULL, "<control>Q", NULL,
+ G_CALLBACK (close_callback)
+ },
+ {
+ "speed-up", NULL,
+ N_("Faster"), "<control>L", N_("Increase the speed of the animation"),
+ G_CALLBACK (speed_up_callback)
+ },
+ {
+ "speed-down", NULL,
+ N_("Slower"), "<control>J", N_("Decrease the speed of the animation"),
+ G_CALLBACK (speed_down_callback)
+ },
+ {
+ "speed-reset", NULL,
+ N_("Reset speed"), "<control>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,
+ "<ui>"
+ " <toolbar name=\"anim-play-toolbar\">"
+ " <toolitem action=\"play\" />"
+ " <toolitem action=\"step-back\" />"
+ " <toolitem action=\"step\" />"
+ " <toolitem action=\"rewind\" />"
+ " <separator />"
+ " <toolitem action=\"detach\" />"
+ " <toolitem action=\"refresh\" />"
+ " <separator name=\"space\" />"
+ " <toolitem action=\"help\" />"
+ " </toolbar>"
+ " <accelerator action=\"close\" />"
+ " <accelerator action=\"quit\" />"
+ "</ui>",
+ -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,
+ "<ui>"
+ " <popup name=\"anim-play-popup\">"
+ " <menuitem action=\"play\" />"
+ " <menuitem action=\"step-back\" />"
+ " <menuitem action=\"step\" />"
+ " <menuitem action=\"rewind\" />"
+ " <separator />"
+ " <menuitem action=\"speed-down\" />"
+ " <menuitem action=\"speed-up\" />"
+ " <menuitem action=\"speed-reset\" />"
+ " <separator />"
+ " <menuitem action=\"detach\" />"
+ " <menuitem action=\"refresh\" />"
+ " <menuitem action=\"close\" />"
+ " </popup>"
+ "</ui>",
+ -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))
+#ifndef PLATFORM_OSX
+ 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);
+#else
+ gtk_widget_queue_draw (da);
+#endif /* PLATFORM_OSX */
+
+ /* 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<length) && (g_ascii_isdigit (str[offset])));
+
+ if (length - offset <= 2)
+ return FALSE;
+
+ /* eat any spaces between number and 'ms' */
+ while ((offset < length) && (str[offset] == ' '))
+ offset++;
+
+ if (length - offset <= 2 ||
+ g_ascii_toupper (str[offset]) != 'M' ||
+ g_ascii_toupper (str[offset + 1]) != 'S')
+ return FALSE;
+
+ offset += 2;
+
+ /* eat any spaces between 'ms' and close-parenthesis */
+ while ((offset < length) && (str[offset] == ' '))
+ offset++;
+
+ if ((length - offset < 1) || (str[offset] != ')'))
+ return FALSE;
+
+ offset++;
+
+ *duration = sum;
+ *taglength = offset;
+
+ return TRUE;
+}
+
+static gint
+parse_ms_tag (const gchar *str)
+{
+ gint i;
+ gint length = strlen (str);
+
+ for (i = 0; i < length; i++)
+ {
+ gint rtn;
+ gint dummy;
+
+ if (is_ms_tag (&str[i], &rtn, &dummy))
+ return rtn;
+ }
+
+ return -1;
+}
+
+static gboolean
+is_disposal_tag (const gchar *str,
+ DisposeType *disposal,
+ gint *taglength)
+{
+ if (strlen (str) != 9)
+ return FALSE;
+
+ if (strncmp (str, "(combine)", 9) == 0)
+ {
+ *taglength = 9;
+ *disposal = DISPOSE_COMBINE;
+ return TRUE;
+ }
+ else if (strncmp (str, "(replace)", 9) == 0)
+ {
+ *taglength = 9;
+ *disposal = DISPOSE_REPLACE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static DisposeType
+parse_disposal_tag (const gchar *str)
+{
+ gint i;
+ gint length = strlen (str);
+
+ for (i = 0; i < length; i++)
+ {
+ DisposeType rtn;
+ gint dummy;
+
+ if (is_disposal_tag (&str[i], &rtn, &dummy))
+ return rtn;
+ }
+
+ return settings.default_frame_disposal;
+}
+
diff --git a/plug-ins/common/blinds.c b/plug-ins/common/blinds.c
new file mode 100644
index 0000000..a0ceeea
--- /dev/null
+++ b/plug-ins/common/blinds.c
@@ -0,0 +1,730 @@
+/*
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This is a plug-in for GIMP.
+ *
+ * Blinds plug-in. Distort an image as though it was stuck to
+ * window blinds and the blinds where opened/closed.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <meo@rru.com> 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 <https://www.gnu.org/licenses/>.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+
+#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 <meo@rru.com>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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) + (1<<(bucket_expo - 1));
+ g = (j<<bucket_expo) + (1<<(bucket_expo - 1));
+ b = (k<<bucket_expo) + (1<<(bucket_expo - 1));
+ }
+ }
+ }
+ }
+
+ /* return the color */
+ gimp_rgba_set_uchar (result, r, g, b, 255);
+
+ g_free (cube);
+}
+
+static void
+add_new_color (const guchar *buffer,
+ gint *cube,
+ gint bucket_expo)
+{
+ guchar r, g, b;
+ gint bucket_rexpo;
+
+ bucket_rexpo = 8 - bucket_expo;
+ r = buffer[0] >> 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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, &param);
+ }
+
+ 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, &param);
+ }
+
+ 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 <narazaki@InetQ.or.jp>
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <mooney@dogbert.cc.ndsu.NoDak.edu>
+ * Sean P Cier <scier@andrew.cmu.edu>
+ * David Mosberger-Tang <davidm@azstarnet.com>
+ * Michael Sweet <mike@easysw.com>
+ *
+ */
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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),
+ &copy_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),
+ &copy_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),
+ &param->function);
+
+ CML_explorer_menu_entry_init (&widget_pointers[channel_id][index],
+ combo, &param->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),
+ &param->composition);
+
+ CML_explorer_menu_entry_init (&widget_pointers[channel_id][index],
+ combo, &param->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),
+ &param->arrange);
+
+ CML_explorer_menu_entry_init (&widget_pointers[channel_id][index],
+ combo, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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, &param->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 <mooney@dogbert.cc.ndsu.NoDak.edu> */
+ 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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * Analyze colorcube.
+ *
+ * Author: robert@experimental.net
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+
+#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, "<Image>/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, &param);
+ gimp_rgn_iterate2 (drawable, 0 /* unused */, color_enhance_func, &param);
+}
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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Colormap remapping plug-in
+ * Copyright (C) 2006 Mukund Sivaraman <muks@mukund.org>
+ *
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <muks@mukund.org>",
+ "Mukund Sivaraman <muks@mukund.org>",
+ "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, "<Image>/Colors/Map/Colormap");
+ gimp_plugin_menu_register (PLUG_IN_PROC_REMAP, "<Colormap>");
+ 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 <muks@mukund.org>",
+ "Mukund Sivaraman <muks@mukund.org>",
+ "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,
+ "<ui>"
+ " <popup name=\"remap-popup\">"
+ " <menuitem action=\"sort-hue\" />"
+ " <menuitem action=\"sort-sat\" />"
+ " <menuitem action=\"sort-val\" />"
+ " <separator />"
+ " <menuitem action=\"reverse\" />"
+ " <menuitem action=\"reset\" />"
+ " </popup>"
+ "</ui>",
+ -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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#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, "<Image>/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 <adam@foxbox.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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+
+#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>/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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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,
+ &params, &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, "<Image>/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 <mail_dev@martijn.at>
+ * Copyright 2013 Téo Mazars <teo.mazars@ensimag.fr>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* Lab colorspace support originally written by Alexey Dyachenko,
+ * merged into the officical plug-in by Sven Neumann.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <imagename>-<channel>.<extension> */
+gchar *
+generate_filename (guint32 image_ID,
+ guint colorspace,
+ guint channel)
+{
+ /* Build a filename like <imagename>-<channel>.<extension> */
+ 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 <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <mike@easysw.com>",
+ "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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <pcg@goof.com>",
+ "Marc Lehmann <pcg@goof.com>",
+ PLUG_IN_VERSION,
+ N_("Des_tripe..."),
+ "RGB*, GRAY*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (args), 0,
+ args, NULL);
+
+ gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <nuisance@cmu.edu>
+ */
+
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <aalib.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <nuisance@cmu.edu>",
+ "Tim Newsome <nuisance@cmu.edu>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <njl195@zepler.org.uk>",
+ "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 <njl195@zepler.org.uk>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <acahalan at cs.uml.edu> */
+
+/* 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 <string.h>
+#include <errno.h>
+
+#include <sys/types.h>
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib/gstdio.h>
+#ifndef _O_BINARY
+#define _O_BINARY 0
+#endif
+
+#include <libgimp/gimp.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#include <zlib.h>
+#include <bzlib.h>
+#include <lzma.h>
+
+
+/* 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..9bcc623
--- /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 <https://www.gnu.org/licenses/>.
+ *
+ * This plugin is heavily based on the header plugin by Spencer Kimball and
+ * Peter Mattis.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 + 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, "(\""))
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Desktop Entry Specification
+ * http://standards.freedesktop.org/desktop-entry-spec/latest/
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+
+#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..7a3d400
--- /dev/null
+++ b/plug-ins/common/file-dicom.c
@@ -0,0 +1,1796 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * The dicom reading and writing code was written from scratch
+ * by Dov Grobgeld. (dov.grobgeld@gmail.com).
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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;
+ gboolean planar;
+ gboolean bw_inverted;
+} 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 <dov@imagic.weizmann.ac.il>",
+ "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 <dov@imagic.weizmann.ac.il>",
+ "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;
+ gboolean implicit_encoding = FALSE;
+ gboolean big_endian = FALSE;
+
+ 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;
+
+ 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));
+
+ if (group_word != 0x0002 && big_endian)
+ {
+ group_word = GUINT16_SWAP_LE_BE (group_word);
+ element_word = 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 */
+ if (big_endian && group_word != 0x0002)
+ element_length =
+ g_ntohl (*((gint *) element_length_chars));
+ else
+ 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);
+ if (big_endian && group_word != 0x0002)
+ element_length = g_ntohl (element_length);
+ else
+ element_length = g_ntohl (GUINT32_SWAP_LE_BE (element_length));
+ }
+ /* Short length */
+ else
+ {
+ guint16 el16;
+
+ fread (&el16, 1, 2, DICOM);
+ if (big_endian && group_word != 0x0002)
+ element_length = g_ntohs (el16);
+ else
+ 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;
+ if (big_endian && group_word != 0x0002)
+ ctx_us = GUINT16_SWAP_LE_BE (ctx_us);
+
+ g_debug ("group: %04x, element: %04x, length: %d",
+ group_word, element_word, element_length);
+ g_debug ("Value: %s", (char*)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)
+ {
+ implicit_encoding = TRUE;
+ g_debug ("Transfer syntax: Implicit VR Endian: Default Transfer Syntax for DICOM.");
+ }
+ else if (strcmp("1.2.840.10008.1.2.1", (char*)value) == 0)
+ {
+ g_debug ("Transfer syntax: Explicit VR Little Endian.");
+ }
+ else if (strcmp("1.2.840.10008.1.2.1.99", (char*)value) == 0)
+ {
+ g_debug ("Transfer syntax: Deflated Explicit VR Little Endian.");
+ }
+ else if (strcmp("1.2.840.10008.1.2.2", (char*)value) == 0)
+ {
+ /* This Transfer Syntax was retired in 2006. For the most recent description of it, see PS3.5 2016b */
+ big_endian = TRUE;
+ g_debug ("Transfer syntax: Deprecated Explicit VR Big Endian.");
+ }
+ else
+ {
+ g_debug ("Transfer syntax %s is not supported by GIMP.", (gchar *) value);
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Transfer syntax %s is not supported by GIMP."),
+ (gchar *) value);
+ g_free (dicominfo);
+ fclose (DICOM);
+ return -1;
+ }
+ break;
+ }
+ }
+ else if (group_word == 0x0028)
+ {
+ gboolean supported = TRUE;
+
+ switch (element_word)
+ {
+ case 0x0002: /* samples per pixel */
+ samples_per_pixel = ctx_us;
+ g_debug ("spp: %d", samples_per_pixel);
+ break;
+ case 0x0004: /* photometric interpretation */
+ g_debug ("photometric interpretation: %s", (gchar *) value);
+
+ if (samples_per_pixel == 1)
+ {
+ if (strncmp ((gchar *) value, "MONOCHROME1", 11) == 0)
+ {
+ /* The minimum sample value is intended to be displayed
+ * as white after any VOI gray scale transformations
+ * have been performed. */
+ dicominfo->bw_inverted = TRUE;
+ }
+ else if (strncmp ((gchar *) value, "MONOCHROME2", 11) == 0)
+ {
+ /* The minimum sample value is intended to be displayed
+ * as black after any VOI gray scale transformations
+ * have been performed. */
+ dicominfo->bw_inverted = FALSE;
+ }
+ else
+ supported = FALSE;
+ }
+ else if (samples_per_pixel == 3)
+ {
+ if (strncmp ((gchar *) value, "RGB", 2) != 0)
+ {
+ supported = FALSE;
+ }
+ }
+ else
+ {
+ supported = FALSE;
+ }
+ if (! supported)
+ {
+ g_set_error (error, GIMP_PLUG_IN_ERROR, 0,
+ _("%s is not supported by GIMP in combination "
+ "with samples per pixel: %d"),
+ (gchar *) value, samples_per_pixel);
+ g_free (dicominfo);
+ fclose (DICOM);
+ return -1;
+ }
+
+ break;
+ case 0x0006: /* planar configuration */
+ g_debug ("planar configuration: %u", ctx_us);
+ dicominfo->planar = (ctx_us == 1);
+ break;
+ case 0x0008: /* number of frames */
+ g_debug ("number of frames: %d", ctx_us);
+ break;
+ case 0x0010: /* rows */
+ height = ctx_us;
+ g_debug ("height: %d", height);
+ break;
+ case 0x0011: /* columns */
+ width = ctx_us;
+ g_debug ("width: %d", width);
+ break;
+ case 0x0100: /* bits allocated */
+ bpp = ctx_us;
+ g_debug ("bpp: %d", bpp);
+ break;
+ case 0x0101: /* bits stored */
+ bits_stored = ctx_us;
+ g_debug ("bits stored: %d", bits_stored);
+ break;
+ case 0x0102: /* high bit */
+ high_bit = ctx_us;
+ g_debug ("high bit: %d", high_bit);
+ break;
+ case 0x0103: /* is pixel representation signed? */
+ is_signed = (ctx_us == 0) ? FALSE : TRUE;
+ g_debug ("is signed: %d", ctx_us);
+ 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_ntohs (GUINT16_SWAP_LE_BE (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->bw_inverted)
+ {
+ d[col_idx] = ~d[col_idx];
+ }
+
+ 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)
+ {
+ if (! info->planar)
+ {
+ 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->bw_inverted)
+ {
+ d[col_idx] = ~d[col_idx];
+ }
+
+ 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
+ {
+ /* planar organization of color data */
+ guint8 *row_start;
+ gint col_idx;
+ gint plane_size = width * height;
+
+ row_start = (pix_buffer + (row_idx + i) * width);
+
+ for (col_idx = 0; col_idx < width; col_idx++)
+ {
+ /* Shift it by 0 bits, or more in case bits_stored is
+ * less than bpp.
+ */
+ gint pix_idx;
+ gint src_offset = col_idx;
+
+ for (pix_idx = 0; pix_idx < samples_per_pixel; pix_idx++)
+ {
+ gint dest_idx = col_idx * samples_per_pixel + pix_idx;
+
+ d[dest_idx] = row_start[src_offset] << (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[dest_idx] > 127)
+ d[dest_idx] = 0;
+ else
+ d[dest_idx] <<= 1;
+ }
+ src_offset += plane_size;
+ }
+ }
+ }
+ }
+
+ 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<buf16+length)
+ {
+ if (*(guint8*)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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * gbr plug-in version 1.00
+ * Loads/exports version 2 GIMP .gbr files, by Tim Newsome <drz@frody.bloke.com>
+ * 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 <sjburges@gimp.org>
+ *
+ * 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 <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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..978c7e7
--- /dev/null
+++ b/plug-ins/common/file-gegl.c
@@ -0,0 +1,492 @@
+/* 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 <simon@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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 *load_op;
+
+ const gchar *save_proc;
+ const gchar *save_blurb;
+ const gchar *save_help;
+ const gchar *save_op;
+};
+
+
+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,
+ const gchar *gegl_op,
+ GError **error);
+static gboolean save_image (const gchar *filename,
+ const gchar *gegl_op,
+ 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:rgbe-load",
+ "gegl:rgbe-load",
+
+ "file-save-rgbe",
+ "Saves files in the RGBE file format",
+ "This procedure exports images in the RGBE format, using gegl:rgbe-save",
+ "gegl:rgbe-save",
+ },
+ {
+ N_("OpenEXR image"),
+ "image/x-exr",
+ "exr",
+ "0,lelong,20000630",
+
+ /* no EXR loading (implemented in native GIMP plug-in) */
+ NULL, NULL, NULL, NULL,
+
+ "file-exr-save",
+ "Saves files in the OpenEXR file format",
+ "This procedure saves images in the OpenEXR format, using gegl:exr-save",
+ "gegl:exr-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, format->load_op, &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,
+ format->save_op,
+ 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,
+ const gchar *gegl_op,
+ 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_op,
+ "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,
+ const gchar *gegl_op,
+ 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_op,
+ "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 - <adam@gimp.org> <adam@foxbox.org>
+ */
+
+/* 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 <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+
+#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..ddb555d
--- /dev/null
+++ b/plug-ins/common/file-gif-save.c
@@ -0,0 +1,2574 @@
+/* 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 - <adam@gimp.org> <adam@foxbox.org>
+ */
+/*
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <mgardi@watdscu.waterloo.edu>. A
+** Lempel-Ziv compression based on "compress".
+**
+** Modified by Marcel Wijkstra <wijkstra@fwi.uva.nl>
+**
+**
+** 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;
+
+ /* Layers can be with or without alpha channel. Make sure we set
+ * alpha if there is at least one layer with alpha channel. */
+ if (drawable_type == GIMP_GRAY_IMAGE ||
+ drawable_type == GIMP_INDEXED_IMAGE)
+ {
+ for (i = nlayers - 1; i >= 0; i--)
+ {
+ GimpImageType dr_type = gimp_drawable_type (layers[i]);
+
+ if (dr_type == GIMP_GRAYA_IMAGE ||
+ dr_type == GIMP_INDEXEDA_IMAGE)
+ {
+ drawable_type = dr_type;
+ break;
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ 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]);
+ if (drawable_type == GIMP_GRAYA_IMAGE)
+ {
+ format = babl_format ("Y'A u8");
+ }
+ else if (drawable_type == GIMP_GRAY_IMAGE)
+ {
+ format = babl_format ("Y' u8");
+ }
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+ /* 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 <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+#include <libgimpbase/gimpparasiteio.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/* The idea is taken from a plug-in written by George Hartz; the code isn't.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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..9564355
--- /dev/null
+++ b/plug-ins/common/file-heif.c
@@ -0,0 +1,2173 @@
+/*
+ * GIMP HEIF loader / write plugin.
+ * Copyright (c) 2018 struktur AG, Dirk Farin <farin@struktur.de>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libheif/heif.h>
+#include <lcms2.h>
+#include <gexiv2/gexiv2.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 init (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 =
+{
+ init, /* init_proc */
+ NULL, /* quit_proc */
+ NULL, /* query_proc */
+ run, /* run_proc */
+};
+
+
+MAIN ()
+
+
+static void
+init (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)" }
+ };
+
+ if (heif_have_decoder_for_format (heif_compression_HEVC)
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+ || heif_have_decoder_for_format (heif_compression_AV1)
+#endif
+ )
+ {
+ GString *extensions = g_string_new (NULL);
+ GString *mimetypes = g_string_new (NULL);
+ GString *magic = g_string_new ("4,string,ftypmif1");
+
+ if (heif_have_decoder_for_format (heif_compression_HEVC))
+ {
+ g_string_append (extensions, "heic,heif");
+ g_string_append (mimetypes, "image/heif");
+ g_string_append (magic, ",4,string,ftypheic,4,string,ftypheix,"
+ "4,string,ftyphevc,4,string,ftypheim,"
+ "4,string,ftypheis,4,string,ftyphevm,"
+ "4,string,ftyphevs,4,string,ftypmsf1");
+ }
+
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+ if (heif_have_decoder_for_format (heif_compression_AV1))
+ {
+ g_string_append_printf (extensions, "%savif", extensions->len ? "," : "");
+ g_string_append_printf (mimetypes, "%simage/avif", mimetypes->len ? "," : "");
+ g_string_append (magic, ",4,string,ftypavif");
+ }
+#endif
+
+ 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 <farin@struktur.de>",
+ "Dirk Farin <farin@struktur.de>",
+ "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, extensions->str, "");
+ gimp_register_file_handler_mime (LOAD_PROC, mimetypes->str);
+ gimp_register_file_handler_uri (LOAD_PROC);
+
+ gimp_register_magic_load_handler (LOAD_PROC,
+ extensions->str, "",
+ magic->str);
+
+ g_string_free (magic, TRUE);
+ g_string_free (mimetypes, TRUE);
+ g_string_free (extensions, TRUE);
+ }
+
+ if (heif_have_encoder_for_format (heif_compression_HEVC))
+ {
+ gimp_install_procedure (SAVE_PROC,
+ _("Exports HEIF images"),
+ _("Save image in HEIF format (High Efficiency "
+ "Image File Format)."),
+ "Dirk Farin <farin@struktur.de>",
+ "Dirk Farin <farin@struktur.de>",
+ "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)
+ if (heif_have_encoder_for_format (heif_compression_AV1))
+ {
+ gimp_install_procedure (SAVE_PROC_AV1,
+ _("Exports AVIF images"),
+ _("Save image in AV1 Image File Format (AVIF)"),
+ "Daniel Novomesky <dnovomesky@gmail.com>",
+ "Daniel Novomesky <dnovomesky@gmail.com>",
+ "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, &params);
+
+ if (! save_dialog (&params, image_ID, name))
+ status = GIMP_PDB_CANCEL;
+ break;
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_get_data (SAVE_PROC, &params);
+ 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,
+ &params,
+ &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, &params, sizeof (params));
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ g_object_unref (file);
+ }
+
+ if (export == GIMP_EXPORT_EXPORT)
+ gimp_image_delete (image_ID);
+
+ 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, &params);
+
+ if (! save_dialog (&params, image_ID, name))
+ status = GIMP_PDB_CANCEL;
+ break;
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_get_data (SAVE_PROC_AV1, &params);
+ 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,
+ &params,
+ &error,
+ heif_compression_AV1))
+ {
+ gimp_set_data (SAVE_PROC_AV1, &params, sizeof (params));
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ g_object_unref (file);
+ }
+
+ if (export == GIMP_EXPORT_EXPORT)
+ gimp_image_delete (image_ID);
+
+ 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;
+ char *filename = g_file_get_parse_name (file);
+
+ gimp_progress_init_printf (_("Opening '%s'"),
+ filename);
+ g_free (filename);
+
+ 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[3] == tiffHeaderBE[3])
+ {
+ break;
+ }
+ if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
+ tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3])
+ {
+ 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);
+ }
+ }
+
+ gexiv2_metadata_set_orientation (GEXIV2_METADATA (metadata),
+ GEXIV2_ORIENTATION_NORMAL);
+ gexiv2_metadata_set_metadata_pixel_width (GEXIV2_METADATA (metadata),
+ width);
+ gexiv2_metadata_set_metadata_pixel_height (GEXIV2_METADATA (metadata),
+ height);
+ 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;
+ const struct heif_encoder_descriptor *encoder_descriptor;
+ const char *encoder_name;
+ 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;
+ char *filename;
+
+ if (!context)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ "cannot allocate heif_context");
+ return FALSE;
+ }
+
+ if (compression == heif_compression_HEVC)
+ {
+ if (heif_context_get_encoder_descriptors (context,
+ heif_compression_HEVC,
+ NULL,
+ &encoder_descriptor, 1) == 1)
+ {
+ encoder_name = heif_encoder_descriptor_get_id_name (encoder_descriptor);
+ }
+ else
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ "Unable to find suitable HEIF encoder");
+ heif_context_free (context);
+ return FALSE;
+ }
+ }
+ else /* AV1 compression */
+ {
+ if (heif_context_get_encoder_descriptors (context,
+ compression,
+ "aom", /* we prefer aom rather than rav1e */
+ &encoder_descriptor, 1) == 1)
+ {
+ encoder_name = heif_encoder_descriptor_get_id_name (encoder_descriptor);
+ }
+ else if (heif_context_get_encoder_descriptors (context,
+ compression,
+ NULL,
+ &encoder_descriptor, 1) == 1)
+ {
+ encoder_name = heif_encoder_descriptor_get_id_name (encoder_descriptor);
+ }
+ else
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ "Unable to find suitable AVIF encoder");
+ heif_context_free (context);
+ return FALSE;
+ }
+ }
+
+ filename = g_file_get_parse_name (file);
+ gimp_progress_init_printf (_("Exporting '%s' using %s encoder"),
+ filename, encoder_name);
+ g_free (filename);
+
+ 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 (context,
+ encoder_descriptor,
+ &encoder);
+
+ if (err.code != 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ "Unable to get an encoder instance");
+ heif_image_release (image);
+ heif_context_free (context);
+ return FALSE;
+ }
+
+ /* workaround for a bug in libheif when heif_encoder_set_lossless is not working
+ (known problem with encoding via rav1e) */
+ if (params->lossless)
+ {
+ heif_encoder_set_lossy_quality (encoder, 100);
+ }
+ else
+ {
+ heif_encoder_set_lossy_quality (encoder, params->quality);
+ }
+
+ heif_encoder_set_lossless (encoder, params->lossless);
+ /* heif_encoder_set_logging_level (encoder, logging_level); */
+
+#if LIBHEIF_HAVE_VERSION(1,10,0)
+ if (params->lossless)
+ {
+ err = heif_encoder_set_parameter_string (encoder, "chroma", "444");
+ if (err.code != 0)
+ {
+ g_printerr ("Failed to set chroma=444 for %s encoder: %s", encoder_name, err.message);
+ }
+ }
+#endif
+
+ 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"));
+ 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 (hbox, !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"), 10,
+ _("12 bit/channel"), 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),
+ &params->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), &params->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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 */
+ "&nbsp;", /* 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, &gtmvals);
+
+ 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, &gtmvals, 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,
+ "<HTML>\n<HEAD><TITLE>%s</TITLE></HEAD>\n<BODY>\n",
+ gimp_file_get_utf8_name (file)) ||
+ ! print (output, error, "<H1>%s</H1>\n",
+ gimp_file_get_utf8_name (file)))
+ {
+ goto fail;
+ }
+ }
+
+ if (! print (output, error,
+ "<TABLE BORDER=%d CELLPADDING=%d CELLSPACING=%d>\n",
+ gtmvals.border, gtmvals.cellpadding, gtmvals.cellspacing))
+ goto fail;
+
+ if (gtmvals.caption)
+ {
+ if (! print (output, error, "<CAPTION>%s</CAPTION>\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, " <TR>\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,
+ " <TD%s%sBGCOLOR=#%02x%02x%02x>",
+ width, height, buf[0], buf[1], buf[2]))
+ goto fail;
+ }
+
+ if (palloc[cols * y + x] == 2)
+ {
+ if (! print (output, error,
+ " <TD ROWSPAN=\"%d\" COLSPAN=\"%d\"%s%sBGCOLOR=#%02x%02x%02x>",
+ 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</TD>\n", gtmvals.cellcontent))
+ goto fail;
+ }
+ else
+ {
+ if (! print (output, error,
+ "\n %s\n </TD>\n", gtmvals.cellcontent))
+ goto fail;
+ }
+ }
+ }
+
+ if (! print (output, error, " </TR>\n"))
+ goto fail;
+
+ gimp_progress_update ((double) y / (double) rows);
+ }
+
+ if (gtmvals.fulldoc)
+ {
+ if (! print (output, error, "</TABLE></BODY></HTML>\n"))
+ goto fail;
+ }
+ else
+ {
+ if (! print (output, error, "</TABLE>\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 <HTML>, <BODY>, etc. tags instead of just "
+ "the table html."),
+ NULL);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &gtmvals.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),
+ &gtmvals.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),
+ &gtmvals.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),
+ &gtmvals.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),
+ &gtmvals.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),
+ &gtmvals.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),
+ &gtmvals.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 <aurimas.juska@gmail.com>
+ * Copyright (C) 2004 Florian Traverse <florian.traverse@cpe.fr>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <unistd.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+#include <io.h>
+#endif
+
+#ifndef _O_BINARY
+#define _O_BINARY 0
+#endif
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#include <openjpeg.h>
+
+
+#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 (&parameters);
+ if (opj_setup_decoder (codec, &parameters) != 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-jpegxl.c b/plug-ins/common/file-jpegxl.c
new file mode 100644
index 0000000..e85f647
--- /dev/null
+++ b/plug-ins/common/file-jpegxl.c
@@ -0,0 +1,1174 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * file-jpegxl - JPEG XL file format plug-in for the GIMP
+ * Copyright (C) 2022 Daniel Novomesky
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gexiv2/gexiv2.h>
+#include <glib/gstdio.h>
+
+#include <jxl/decode.h>
+#include <jxl/encode.h>
+#include <jxl/thread_parallel_runner.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#define LOAD_PROC "file-jpegxl-load"
+#define SAVE_PROC "file-jpegxl-save"
+#define PLUG_IN_BINARY "file-jpegxl"
+
+static void query (void);
+static void run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals);
+
+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 the image in" },
+ { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" }
+ };
+
+ gimp_install_procedure (LOAD_PROC,
+ "Loads files in the JPEG XL file format",
+ "Loads files in the JPEG XL file format",
+ "Daniel Novomesky",
+ "(C) 2022 Daniel Novomesky",
+ "2022",
+ N_("JPEG XL 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/jxl");
+ gimp_register_magic_load_handler (LOAD_PROC, "jxl", "", "0,string,\xFF\x0A,0,string,\\000\\000\\000\x0CJXL\\040\\015\\012\x87\\012");
+ gimp_register_file_handler_priority (LOAD_PROC, 100);
+
+ gimp_install_procedure (SAVE_PROC,
+ "Saves files in the JPEG XL file format",
+ "Saves files in the JPEG XL file format",
+ "Daniel Novomesky",
+ "(C) 2022 Daniel Novomesky",
+ "2022",
+ N_("JPEG XL image"),
+ "RGB*, GRAY*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_args), 0,
+ save_args, NULL);
+
+ gimp_register_file_handler_mime (SAVE_PROC, "image/jxl");
+ gimp_register_save_handler (SAVE_PROC, "jxl", "");
+ gimp_register_file_handler_uri (SAVE_PROC);
+ gimp_register_file_handler_priority (SAVE_PROC, 100);
+}
+
+static gint32
+load_image (const gchar *filename,
+ GimpRunMode run_mode,
+ GError **error)
+{
+ FILE *inputFile = g_fopen (filename, "rb");
+
+ gsize inputFileSize;
+ gpointer memory;
+
+ JxlSignature signature;
+ JxlDecoder *decoder;
+ void *runner;
+ JxlBasicInfo basicinfo;
+ JxlDecoderStatus status;
+ JxlPixelFormat pixel_format;
+ JxlColorEncoding color_encoding;
+ size_t icc_size = 0;
+ GimpColorProfile *profile = NULL;
+ gboolean loadlinear = FALSE;
+ size_t channel_depth;
+ size_t result_size;
+ gpointer picture_buffer;
+ gint32 image = -1;
+ gint32 layer;
+ GeglBuffer *buffer;
+ GimpPrecision precision_linear;
+ GimpPrecision precision_non_linear;
+ size_t num_worker_threads = 1;
+
+ if (! inputFile)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "Cannot open file for read: %s\n", filename);
+ return -1;
+ }
+
+ fseek (inputFile, 0, SEEK_END);
+ inputFileSize = ftell (inputFile);
+ fseek (inputFile, 0, SEEK_SET);
+
+ if (inputFileSize < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "File too small: %s\n", filename);
+ fclose (inputFile);
+ return -1;
+ }
+
+ memory = g_malloc (inputFileSize);
+ if (fread (memory, 1, inputFileSize, inputFile) != inputFileSize)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "Failed to read %zu bytes: %s\n",
+ inputFileSize, filename);
+ fclose (inputFile);
+ g_free (memory);
+ return -1;
+ }
+
+ fclose (inputFile);
+
+ signature = JxlSignatureCheck (memory, inputFileSize);
+ if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "File %s is probably not in JXL format!\n", filename);
+ g_free (memory);
+ return -1;
+ }
+
+ decoder = JxlDecoderCreate (NULL);
+ if (! decoder)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JxlDecoderCreate failed");
+ g_free (memory);
+ return -1;
+ }
+
+ num_worker_threads = g_get_num_processors ();
+ if (num_worker_threads > 16)
+ {
+ num_worker_threads = 16;
+ }
+ runner = JxlThreadParallelRunnerCreate (NULL, num_worker_threads);
+ if (JxlDecoderSetParallelRunner (decoder, JxlThreadParallelRunner, runner) != JXL_DEC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JxlDecoderSetParallelRunner failed");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ if (JxlDecoderSetInput (decoder, memory, inputFileSize) != JXL_DEC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JxlDecoderSetInput failed");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ JxlDecoderCloseInput (decoder);
+
+ if (JxlDecoderSubscribeEvents (decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE)
+ != JXL_DEC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JxlDecoderSubscribeEvents failed");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ status = JxlDecoderProcessInput (decoder);
+ if (status == JXL_DEC_ERROR)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JXL decoding failed");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ if (status == JXL_DEC_NEED_MORE_INPUT)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JXL data incomplete");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ status = JxlDecoderGetBasicInfo (decoder, &basicinfo);
+ if (status != JXL_DEC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JXL basic info not available");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ if (basicinfo.xsize == 0 || basicinfo.ysize == 0)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JXL image has zero dimensions");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ status = JxlDecoderProcessInput (decoder);
+ if (status != JXL_DEC_COLOR_ENCODING)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ if (basicinfo.uses_original_profile == JXL_FALSE)
+ {
+ if (basicinfo.num_color_channels == 3)
+ {
+ JxlColorEncodingSetToSRGB (&color_encoding, JXL_FALSE);
+ JxlDecoderSetPreferredColorProfile (decoder, &color_encoding);
+ }
+ else if (basicinfo.num_color_channels == 1)
+ {
+ JxlColorEncodingSetToSRGB (&color_encoding, JXL_TRUE);
+ JxlDecoderSetPreferredColorProfile (decoder, &color_encoding);
+ }
+ }
+
+ pixel_format.endianness = JXL_NATIVE_ENDIAN;
+ pixel_format.align = 0;
+
+ if (basicinfo.uses_original_profile == JXL_FALSE || basicinfo.bits_per_sample > 16)
+ {
+ pixel_format.data_type = JXL_TYPE_FLOAT;
+ channel_depth = 4;
+ precision_linear = GIMP_PRECISION_FLOAT_LINEAR;
+ precision_non_linear = GIMP_PRECISION_FLOAT_GAMMA;
+ }
+ else if (basicinfo.bits_per_sample <= 8)
+ {
+ pixel_format.data_type = JXL_TYPE_UINT8;
+ channel_depth = 1;
+ precision_linear = GIMP_PRECISION_U8_LINEAR;
+ precision_non_linear = GIMP_PRECISION_U8_GAMMA;
+ }
+ else
+ {
+ pixel_format.data_type = JXL_TYPE_UINT16;
+ channel_depth = 2;
+ precision_linear = GIMP_PRECISION_U16_LINEAR;
+ precision_non_linear = GIMP_PRECISION_U16_GAMMA;
+ }
+
+ if (basicinfo.num_color_channels == 1) /* grayscale */
+ {
+ if (basicinfo.alpha_bits > 0)
+ {
+ pixel_format.num_channels = 2;
+ }
+ else
+ {
+ pixel_format.num_channels = 1;
+ }
+ }
+ else /* RGB */
+ {
+
+ if (basicinfo.alpha_bits > 0) /* RGB with alpha */
+ {
+ pixel_format.num_channels = 4;
+ }
+ else /* RGB no alpha */
+ {
+ pixel_format.num_channels = 3;
+ }
+ }
+
+ result_size = channel_depth * pixel_format.num_channels
+ * (size_t) basicinfo.xsize * (size_t) basicinfo.ysize;
+
+ if (JxlDecoderGetColorAsEncodedProfile (decoder, &pixel_format,
+ JXL_COLOR_PROFILE_TARGET_DATA,
+ &color_encoding) == JXL_DEC_SUCCESS)
+ {
+ if (color_encoding.white_point == JXL_WHITE_POINT_D65)
+ {
+ switch (color_encoding.transfer_function)
+ {
+ case JXL_TRANSFER_FUNCTION_LINEAR:
+ loadlinear = TRUE;
+
+ switch (color_encoding.color_space)
+ {
+ case JXL_COLOR_SPACE_RGB:
+ profile = gimp_color_profile_new_rgb_srgb_linear ();
+ break;
+ case JXL_COLOR_SPACE_GRAY:
+ profile = gimp_color_profile_new_d65_gray_linear ();
+ break;
+ default:
+ break;
+ }
+ break;
+ case JXL_TRANSFER_FUNCTION_SRGB:
+ switch (color_encoding.color_space)
+ {
+ case JXL_COLOR_SPACE_RGB:
+ profile = gimp_color_profile_new_rgb_srgb ();
+ break;
+ case JXL_COLOR_SPACE_GRAY:
+ profile = gimp_color_profile_new_d65_gray_srgb_trc ();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (! profile)
+ {
+ if (JxlDecoderGetICCProfileSize (decoder, &pixel_format,
+ JXL_COLOR_PROFILE_TARGET_DATA,
+ &icc_size) == JXL_DEC_SUCCESS)
+ {
+ if (icc_size > 0)
+ {
+ gpointer raw_icc_profile = g_malloc (icc_size);
+
+ if (JxlDecoderGetColorAsICCProfile (decoder, &pixel_format, JXL_COLOR_PROFILE_TARGET_DATA,
+ raw_icc_profile, icc_size)
+ == JXL_DEC_SUCCESS)
+ {
+ profile = gimp_color_profile_new_from_icc_profile (raw_icc_profile,
+ icc_size, error);
+ if (profile)
+ {
+ loadlinear = gimp_color_profile_is_linear (profile);
+ }
+ else
+ {
+ g_printerr ("%s: Failed to read ICC profile: %s\n",
+ G_STRFUNC, (*error)->message);
+ g_clear_error (error);
+ }
+ }
+ else
+ {
+ g_printerr ("Failed to obtain data from JPEG XL decoder");
+ }
+
+ g_free (raw_icc_profile);
+ }
+ else
+ {
+ g_printerr ("Empty ICC data");
+ }
+ }
+ else
+ {
+ g_message ("no ICC, other color profile");
+ }
+ }
+
+ status = JxlDecoderProcessInput (decoder);
+ if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER)
+ {
+ g_set_error (error, G_FILE_ERROR,
+ 0, "Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ picture_buffer = g_try_malloc (result_size);
+ if (! picture_buffer)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "Memory could not be allocated.");
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ if (JxlDecoderSetImageOutBuffer (decoder, &pixel_format, picture_buffer, result_size) != JXL_DEC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0, "ERROR: JxlDecoderSetImageOutBuffer failed");
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ status = JxlDecoderProcessInput (decoder);
+ if (status != JXL_DEC_FULL_IMAGE)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
+ g_free (picture_buffer);
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return -1;
+ }
+
+ if (basicinfo.num_color_channels == 1) /* grayscale */
+ {
+ image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_GRAY,
+ loadlinear ? precision_linear : precision_non_linear);
+
+ if (profile)
+ {
+ if (gimp_color_profile_is_gray (profile))
+ {
+ gimp_image_set_color_profile (image, profile);
+ }
+ }
+
+ layer = gimp_layer_new (image, "Background", basicinfo.xsize, basicinfo.ysize,
+ (basicinfo.alpha_bits > 0) ? GIMP_GRAYA_IMAGE : GIMP_GRAY_IMAGE,
+ 100, gimp_image_get_default_new_layer_mode (image));
+ }
+ else /* RGB */
+ {
+ image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_RGB,
+ loadlinear ? precision_linear : precision_non_linear);
+
+ if (profile)
+ {
+ if (gimp_color_profile_is_rgb (profile))
+ {
+ gimp_image_set_color_profile (image, profile);
+ }
+ }
+
+ layer = gimp_layer_new (image, "Background", basicinfo.xsize, basicinfo.ysize,
+ (basicinfo.alpha_bits > 0) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE,
+ 100, gimp_image_get_default_new_layer_mode (image));
+ }
+
+ gimp_image_insert_layer (image, layer, -1, 0);
+
+ buffer = gimp_drawable_get_buffer (layer);
+
+ gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, basicinfo.xsize, basicinfo.ysize),
+ 0, NULL, picture_buffer, GEGL_AUTO_ROWSTRIDE);
+
+ g_object_unref (buffer);
+
+ g_free (picture_buffer);
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+
+ if (basicinfo.have_container)
+ {
+ JxlDecoderReleaseInput (decoder);
+ JxlDecoderRewind (decoder);
+
+ if (JxlDecoderSetInput (decoder, memory, inputFileSize) != JXL_DEC_SUCCESS)
+ {
+ g_printerr ("%s: JxlDecoderSetInput failed after JxlDecoderRewind\n", G_STRFUNC);
+ }
+ else
+ {
+ JxlDecoderCloseInput (decoder);
+ if (JxlDecoderSubscribeEvents (decoder, JXL_DEC_BOX) != JXL_DEC_SUCCESS)
+ {
+ g_printerr ("%s: JxlDecoderSubscribeEvents for JXL_DEC_BOX failed\n", G_STRFUNC);
+ }
+ else
+ {
+ gboolean search_exif = TRUE;
+ gboolean search_xmp = TRUE;
+ gboolean success_exif = FALSE;
+ gboolean success_xmp = FALSE;
+ JxlBoxType box_type = { 0, 0, 0, 0 };
+ GByteArray *exif_box = NULL;
+ GByteArray *xml_box = NULL;
+ size_t exif_remains = 0;
+ size_t xml_remains = 0;
+
+ while (search_exif || search_xmp)
+ {
+ status = JxlDecoderProcessInput (decoder);
+ switch (status)
+ {
+ case JXL_DEC_SUCCESS:
+ if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif)
+ {
+ exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
+ g_byte_array_set_size (exif_box, exif_box->len - exif_remains);
+ success_exif = TRUE;
+ }
+ else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
+ {
+ xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
+ g_byte_array_set_size (xml_box, xml_box->len - xml_remains);
+ success_xmp = TRUE;
+ }
+
+ search_exif = FALSE;
+ search_xmp = FALSE;
+ break;
+ case JXL_DEC_ERROR:
+ search_exif = FALSE;
+ search_xmp = FALSE;
+ g_printerr ("%s: Metadata decoding error\n", G_STRFUNC);
+ break;
+ case JXL_DEC_NEED_MORE_INPUT:
+ search_exif = FALSE;
+ search_xmp = FALSE;
+ g_printerr ("%s: JXL metadata are probably incomplete\n", G_STRFUNC);
+ break;
+ case JXL_DEC_BOX:
+ JxlDecoderSetDecompressBoxes (decoder, JXL_TRUE);
+
+ if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif && exif_box)
+ {
+ exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
+ g_byte_array_set_size (exif_box, exif_box->len - exif_remains);
+
+ search_exif = FALSE;
+ success_exif = TRUE;
+ }
+ else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp && xml_box)
+ {
+ xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
+ g_byte_array_set_size (xml_box, xml_box->len - xml_remains);
+
+ search_xmp = FALSE;
+ success_xmp = TRUE;
+ }
+
+ if (JxlDecoderGetBoxType (decoder, box_type, JXL_TRUE) == JXL_DEC_SUCCESS)
+ {
+ if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif)
+ {
+ exif_box = g_byte_array_sized_new (4096);
+ g_byte_array_set_size (exif_box, 4096);
+
+ JxlDecoderSetBoxBuffer (decoder, exif_box->data, exif_box->len);
+ }
+ else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
+ {
+ xml_box = g_byte_array_sized_new (4096);
+ g_byte_array_set_size (xml_box, 4096);
+
+ JxlDecoderSetBoxBuffer (decoder, xml_box->data, xml_box->len);
+ }
+ }
+ else
+ {
+ search_exif = FALSE;
+ search_xmp = FALSE;
+ g_printerr ("%s: Error in JxlDecoderGetBoxType\n", G_STRFUNC);
+ }
+ break;
+ case JXL_DEC_BOX_NEED_MORE_OUTPUT:
+ if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif)
+ {
+ exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
+ g_byte_array_set_size (exif_box, exif_box->len + 4096);
+ JxlDecoderSetBoxBuffer (decoder, exif_box->data + exif_box->len - (4096 + exif_remains), 4096 + exif_remains);
+ }
+ else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
+ {
+ xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
+ g_byte_array_set_size (xml_box, xml_box->len + 4096);
+ JxlDecoderSetBoxBuffer (decoder, xml_box->data + xml_box->len - (4096 + xml_remains), 4096 + xml_remains);
+ }
+ else
+ {
+ search_exif = FALSE;
+ search_xmp = FALSE;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (success_exif || success_xmp)
+ {
+ GimpMetadata *metadata = gimp_metadata_new ();
+
+ if (success_exif && exif_box)
+ {
+ const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
+ const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
+ const guint8 *tiffheader = exif_box->data;
+ glong new_exif_size = exif_box->len;
+
+ while (new_exif_size >= 4) /*Searching for TIFF Header*/
+ {
+ if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] &&
+ tiffheader[2] == tiffHeaderBE[2] && tiffheader[3] == tiffHeaderBE[3])
+ {
+ break;
+ }
+ if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
+ tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3])
+ {
+ break;
+ }
+ new_exif_size--;
+ tiffheader++;
+ }
+
+ if (new_exif_size > 4) /* TIFF header + some data found*/
+ {
+ if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (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 (success_xmp && xml_box)
+ {
+ if (! gimp_metadata_set_from_xmp (metadata, xml_box->data, xml_box->len, error))
+ {
+ g_printerr ("%s: Failed to set XMP metadata: %s\n", G_STRFUNC, (*error)->message);
+ g_clear_error (error);
+ }
+ }
+
+ gexiv2_metadata_set_orientation (GEXIV2_METADATA (metadata),
+ GEXIV2_ORIENTATION_NORMAL);
+ gexiv2_metadata_set_metadata_pixel_width (GEXIV2_METADATA (metadata),
+ basicinfo.xsize);
+ gexiv2_metadata_set_metadata_pixel_height (GEXIV2_METADATA (metadata),
+ basicinfo.ysize);
+ gimp_image_metadata_load_finish (image, "image/jxl", metadata,
+ GIMP_METADATA_LOAD_COMMENT | GIMP_METADATA_LOAD_RESOLUTION,
+ (run_mode == GIMP_RUN_INTERACTIVE));
+ }
+
+ if (exif_box)
+ {
+ g_byte_array_free (exif_box, TRUE);
+ }
+
+ if (xml_box)
+ {
+ g_byte_array_free (xml_box, TRUE);
+ }
+ }
+ }
+ }
+
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlDecoderDestroy (decoder);
+ g_free (memory);
+ return image;
+}
+
+static gboolean
+save_image (GFile *file,
+ gint32 image,
+ gint32 drawable,
+ GError **error)
+{
+ JxlEncoder *encoder;
+ void *runner;
+ JxlEncoderFrameSettings *encoder_options;
+ JxlPixelFormat pixel_format;
+ JxlBasicInfo output_info;
+ JxlColorEncoding color_profile;
+ JxlEncoderStatus status;
+ size_t buffer_size;
+
+ GByteArray *compressed;
+
+ FILE *outfile;
+ GeglBuffer *buffer;
+ GimpImageType drawable_type;
+
+ gint drawable_width;
+ gint drawable_height;
+ gpointer picture_buffer;
+
+ GimpColorProfile *profile = NULL;
+ const Babl *file_format = NULL;
+ const Babl *space = NULL;
+ gboolean out_linear = FALSE;
+
+ size_t offset = 0;
+ uint8_t *next_out;
+ size_t avail_out;
+
+ gboolean save_icc;
+ size_t num_worker_threads = 1;
+
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_file_get_utf8_name (file));
+
+ drawable_type = gimp_drawable_type (drawable);
+ buffer = gimp_drawable_get_buffer (drawable);
+ drawable_width = gegl_buffer_get_width (buffer);
+ drawable_height = gegl_buffer_get_height (buffer);
+
+ JxlEncoderInitBasicInfo(&output_info);
+
+ output_info.uses_original_profile = JXL_TRUE;
+
+ profile = gimp_image_get_effective_color_profile (image);
+ out_linear = gimp_color_profile_is_linear (profile);
+
+ space = gimp_color_profile_get_space (profile,
+ GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
+ error);
+
+ if (error && *error)
+ {
+ g_printerr ("%s: error getting the profile space: %s\n",
+ G_STRFUNC, (*error)->message);
+ g_object_unref (buffer);
+ return FALSE;
+ }
+
+ pixel_format.data_type = JXL_TYPE_UINT8;
+ output_info.bits_per_sample = 8;
+
+ pixel_format.endianness = JXL_NATIVE_ENDIAN;
+ pixel_format.align = 0;
+
+ output_info.xsize = drawable_width;
+ output_info.ysize = drawable_height;
+ output_info.exponent_bits_per_sample = 0;
+ output_info.orientation = JXL_ORIENT_IDENTITY;
+ output_info.animation.tps_numerator = 10;
+ output_info.animation.tps_denominator = 1;
+
+ switch (drawable_type)
+ {
+ case GIMP_GRAYA_IMAGE:
+ if (out_linear)
+ {
+ file_format = babl_format ( "YA u8");
+ JxlColorEncodingSetToLinearSRGB (&color_profile, JXL_TRUE);
+ }
+ else
+ {
+ file_format = babl_format ("Y'A u8");
+ JxlColorEncodingSetToSRGB (&color_profile, JXL_TRUE);
+ }
+ pixel_format.num_channels = 2;
+ output_info.num_color_channels = 1;
+ output_info.alpha_bits = 8;
+ output_info.alpha_exponent_bits = 0;
+ output_info.num_extra_channels = 1;
+
+ save_icc = FALSE;
+ break;
+ case GIMP_GRAY_IMAGE:
+ if (out_linear)
+ {
+ file_format = babl_format ("Y u8");
+ JxlColorEncodingSetToLinearSRGB (&color_profile, JXL_TRUE);
+ }
+ else
+ {
+ file_format = babl_format ("Y' u8");
+ JxlColorEncodingSetToSRGB (&color_profile, JXL_TRUE);
+ }
+ pixel_format.num_channels = 1;
+ output_info.num_color_channels = 1;
+ output_info.alpha_bits = 0;
+
+ save_icc = FALSE;
+ break;
+ case GIMP_RGBA_IMAGE:
+ file_format = babl_format_with_space (out_linear ? "RGBA u8" : "R'G'B'A u8", space);
+ output_info.alpha_bits = 8;
+ pixel_format.num_channels = 4;
+ output_info.num_color_channels = 3;
+ output_info.alpha_exponent_bits = 0;
+ output_info.num_extra_channels = 1;
+
+ save_icc = TRUE;
+ break;
+ case GIMP_RGB_IMAGE:
+ file_format = babl_format_with_space (out_linear ? "RGB u8" : "R'G'B' u8", space);
+ pixel_format.num_channels = 3;
+ output_info.num_color_channels = 3;
+ output_info.alpha_bits = 0;
+
+ save_icc = TRUE;
+ break;
+ default:
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ g_object_unref (buffer);
+ return FALSE;
+ break;
+ }
+
+ buffer_size = pixel_format.num_channels * (size_t) output_info.xsize * (size_t) output_info.ysize;
+ picture_buffer = g_malloc (buffer_size);
+
+ gimp_progress_update (0.3);
+
+ gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0,
+ drawable_width, drawable_height), 1.0,
+ file_format, picture_buffer,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ g_object_unref (buffer);
+
+ gimp_progress_update (0.4);
+
+ encoder = JxlEncoderCreate (NULL);
+ if (!encoder)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "Failed to create Jxl encoder");
+ g_free (picture_buffer);
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ return FALSE;
+ }
+
+ num_worker_threads = g_get_num_processors ();
+ if (num_worker_threads > 16)
+ {
+ num_worker_threads = 16;
+ }
+ runner = JxlThreadParallelRunnerCreate (NULL, num_worker_threads);
+ if (JxlEncoderSetParallelRunner (encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "JxlEncoderSetParallelRunner failed");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlEncoderDestroy (encoder);
+ g_free (picture_buffer);
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ return FALSE;
+ }
+
+ status = JxlEncoderSetBasicInfo (encoder, &output_info);
+ if (status != JXL_ENC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "JxlEncoderSetBasicInfo failed!");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlEncoderDestroy (encoder);
+ g_free (picture_buffer);
+ if (profile)
+ {
+ g_object_unref (profile);
+ }
+ return FALSE;
+ }
+
+ if (save_icc)
+ {
+ const uint8_t *icc_data = NULL;
+ size_t icc_length = 0;
+
+ icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
+ status = JxlEncoderSetICCProfile (encoder, icc_data, icc_length);
+ g_object_unref (profile);
+ profile = NULL;
+
+ if (status != JXL_ENC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "JxlEncoderSetICCProfile failed!");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlEncoderDestroy (encoder);
+ g_free (picture_buffer);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (profile)
+ {
+ g_object_unref (profile);
+ profile = NULL;
+ }
+
+ status = JxlEncoderSetColorEncoding (encoder, &color_profile);
+ if (status != JXL_ENC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "JxlEncoderSetColorEncoding failed!");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlEncoderDestroy (encoder);
+ g_free (picture_buffer);
+ return FALSE;
+ }
+ }
+
+ encoder_options = JxlEncoderFrameSettingsCreate (encoder, NULL);
+ JxlEncoderSetFrameDistance (encoder_options, 0);
+ JxlEncoderSetFrameLossless (encoder_options, JXL_TRUE);
+
+ gimp_progress_update (0.5);
+
+ status = JxlEncoderAddImageFrame (encoder_options, &pixel_format, picture_buffer, buffer_size);
+ if (status != JXL_ENC_SUCCESS)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "JxlEncoderAddImageFrame failed!");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlEncoderDestroy (encoder);
+ g_free (picture_buffer);
+ return FALSE;
+ }
+
+ gimp_progress_update (0.65);
+
+ JxlEncoderCloseInput (encoder);
+
+ gimp_progress_update (0.7);
+
+ compressed = g_byte_array_sized_new (4096);
+ g_byte_array_set_size (compressed, 4096);
+ do
+ {
+ next_out = compressed->data + offset;
+ avail_out = compressed->len - offset;
+ status = JxlEncoderProcessOutput (encoder, &next_out, &avail_out);
+
+ if (status == JXL_ENC_NEED_MORE_OUTPUT)
+ {
+ offset = next_out - compressed->data;
+ g_byte_array_set_size (compressed, compressed->len * 2);
+ }
+ else if (status == JXL_ENC_ERROR)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "JxlEncoderProcessOutput failed!");
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlEncoderDestroy (encoder);
+ g_free (picture_buffer);
+ return FALSE;
+ }
+ }
+ while (status != JXL_ENC_SUCCESS);
+
+ JxlThreadParallelRunnerDestroy (runner);
+ JxlEncoderDestroy (encoder);
+
+ g_free (picture_buffer);
+
+ g_byte_array_set_size (compressed, next_out - compressed->data);
+
+ gimp_progress_update (0.8);
+
+ if (compressed->len > 0)
+ {
+ outfile = g_fopen (g_file_peek_path (file), "wb");
+ if (!outfile)
+ {
+ g_set_error (error, G_FILE_ERROR, 0,
+ "Could not open '%s' for writing!\n",
+ g_file_peek_path (file));
+ g_byte_array_free (compressed, TRUE);
+ return FALSE;
+ }
+
+ fwrite (compressed->data, 1, compressed->len, outfile);
+ fclose (outfile);
+
+ gimp_progress_update (1.0);
+
+ g_byte_array_free (compressed, TRUE);
+ return TRUE;
+ }
+
+ g_set_error (error, G_FILE_ERROR, 0,
+ "No data to write");
+ g_byte_array_free (compressed, TRUE);
+ return FALSE;
+}
+
+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;
+ 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)
+ {
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_ui_init (PLUG_IN_BINARY, FALSE);
+ break;
+ default:
+ break;
+ }
+
+ image_ID = load_image (param[1].data.d_string, run_mode, &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)
+ {
+ gint32 image_ID = param[1].data.d_int32;
+ gint32 drawable_ID = param[2].data.d_int32;
+ GimpExportReturn export = GIMP_EXPORT_CANCEL;
+
+ 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, "JPEG XL",
+ GIMP_EXPORT_CAN_HANDLE_RGB |
+ GIMP_EXPORT_CAN_HANDLE_GRAY |
+ GIMP_EXPORT_CAN_HANDLE_ALPHA);
+
+ if (export == GIMP_EXPORT_CANCEL)
+ {
+ values[0].data.d_status = GIMP_PDB_CANCEL;
+ return;
+ }
+ 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_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-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 <muks@mukund.org>
+ * 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 <https://www.gnu.org/licenses/>.
+ * --
+ *
+ * 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 <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib/gstdio.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+
+/* libpng and jpeglib are currently used in this plug-in. */
+
+#include <png.h>
+#include <jpeglib.h>
+
+
+/* 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 <libmng.h>
+
+#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 <muks@mukund.org>",
+ "Mukund Sivaraman <muks@mukund.org>",
+ "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 <drz@frody.bloke.com>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <njl195@zepler.org.uk>",
+ "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 <njl195@zepler.org.uk>",
+ "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..a1ff026
--- /dev/null
+++ b/plug-ins/common/file-pdf-load.c
@@ -0,0 +1,2058 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#undef GTK_DISABLE_SINGLE_INCLUDES
+#include <poppler.h>
+#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;
+ gboolean reverse_order;
+ gchar *PDF_password;
+ gboolean white_background;
+} PdfLoadVals;
+
+static PdfLoadVals loadvals =
+{
+ GIMP_PAGE_SELECTOR_TARGET_LAYERS,
+ 100.00, /* resolution in dpi */
+ TRUE, /* antialias */
+ FALSE, /* reverse_order */
+ NULL, /* pdf_password */
+ TRUE, /* white_background */
+};
+
+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,
+ gdouble resolution,
+ gboolean antialias,
+ gboolean white_background,
+ gboolean reverse_order,
+ 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,
+ gboolean white_background);
+
+static GdkPixbuf * get_thumb_pixbuf (PopplerDocument *doc,
+ gint page,
+ gint preferred_size,
+ gboolean white_background);
+
+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,
+ loadvals.white_background,
+ loadvals.reverse_order,
+ &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, TRUE);
+
+ 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,
+ gboolean white_background)
+{
+ 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);
+
+ if (white_background)
+ {
+ 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,
+ gdouble resolution,
+ gboolean antialias,
+ gboolean white_background,
+ gboolean reverse_order,
+ PdfSelectedPages *pages)
+{
+ gint32 image_ID = 0;
+ gint32 *images = NULL;
+ gint i;
+ gdouble scale;
+ gdouble doc_progress = 0;
+ gint base_index = 0;
+ gint sign = 1;
+
+ if (reverse_order && pages->n_pages > 0)
+ {
+ base_index = pages->n_pages - 1;
+ sign = -1;
+ }
+
+ 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;
+ gint page_index;
+
+ page_index = base_index + sign * i;
+
+ page = poppler_document_get_page (doc, pages->pages[page_index]);
+
+ 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, white_background);
+
+ 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,
+ gboolean white_background)
+{
+ 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, white_background);
+ }
+
+ g_object_unref (page);
+
+ return surface;
+}
+
+static GdkPixbuf *
+get_thumb_pixbuf (PopplerDocument *doc,
+ gint page_num,
+ gint preferred_size,
+ gboolean white_background)
+{
+ cairo_surface_t *surface;
+ GdkPixbuf *pixbuf;
+
+ surface = get_thumb_surface (doc, page_num, preferred_size, white_background);
+ 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;
+ gboolean white_background;
+
+ GMutex mutex;
+ GCond render_thumb;
+} 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;
+ gboolean first_loop = TRUE;
+ gint n_pages;
+ gint i;
+
+ n_pages = poppler_document_get_n_pages (thread_data->document);
+
+ while (TRUE)
+ {
+ gboolean white_background;
+ gboolean stop_thumbnailing;
+
+ g_mutex_lock (&thread_data->mutex);
+ if (! first_loop)
+ g_cond_wait (&thread_data->render_thumb, &thread_data->mutex);
+ white_background = thread_data->white_background;
+ stop_thumbnailing = thread_data->stop_thumbnailing;
+ g_mutex_unlock (&thread_data->mutex);
+
+ if (stop_thumbnailing)
+ break;
+
+ for (i = 0; i < n_pages; i++)
+ {
+ IdleData *idle_data = g_new0 (IdleData, 1);
+ gboolean white_background2;
+
+ 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,
+ white_background);
+
+ g_idle_add (idle_set_thumbnail, idle_data);
+
+ g_mutex_lock (&thread_data->mutex);
+ white_background2 = thread_data->white_background;
+ stop_thumbnailing = thread_data->stop_thumbnailing;
+ g_mutex_unlock (&thread_data->mutex);
+
+ if (stop_thumbnailing || white_background2 != white_background)
+ break;
+ }
+
+ if (stop_thumbnailing)
+ break;
+ }
+
+ return NULL;
+}
+
+static void
+white_background_toggled (GtkToggleButton *widget,
+ ThreadData *thread_data)
+{
+ g_mutex_lock (&thread_data->mutex);
+ thread_data->white_background = gtk_toggle_button_get_active (widget);
+ g_cond_signal (&thread_data->render_thumb);
+ g_mutex_unlock (&thread_data->mutex);
+}
+
+static GimpPDBStatusType
+load_dialog (PopplerDocument *doc,
+ PdfSelectedPages *pages)
+{
+ GtkWidget *dialog;
+ GtkWidget *vbox;
+ GtkWidget *title;
+ GtkWidget *selector;
+ GtkWidget *resolution;
+ GtkWidget *antialias;
+ GtkWidget *white_bg;
+ GtkWidget *hbox;
+ GtkWidget *reverse_order;
+ GtkWidget *separator;
+
+ 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_data.white_background = loadvals.white_background;
+ g_mutex_init (&thread_data.mutex);
+ g_cond_init (&thread_data.render_thumb);
+
+ thread = g_thread_new ("thumbnailer", thumbnail_thread, &thread_data);
+
+ /* "Load in reverse order" toggle button */
+ reverse_order = gtk_check_button_new_with_mnemonic (_("Load in reverse order"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (reverse_order), loadvals.reverse_order);
+ gtk_box_pack_start (GTK_BOX (vbox), reverse_order, FALSE, FALSE, 0);
+
+ g_signal_connect (reverse_order, "toggled",
+ G_CALLBACK (gimp_toggle_button_update), &loadvals.reverse_order);
+ gtk_widget_show (reverse_order);
+
+ /* Separator */
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
+ gtk_widget_show (separator);
+
+ /* 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);
+
+ /* White Background */
+ white_bg = gtk_check_button_new_with_mnemonic (_("_Fill transparent areas with white"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (white_bg), loadvals.white_background);
+ gtk_box_pack_start (GTK_BOX (vbox), white_bg, FALSE, FALSE, 0);
+
+ g_signal_connect (white_bg, "toggled",
+ G_CALLBACK (gimp_toggle_button_update), &loadvals.white_background);
+ g_signal_connect (white_bg, "toggled",
+ G_CALLBACK (white_background_toggled), &thread_data);
+ gtk_widget_show (white_bg);
+
+ /* 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 */
+ g_mutex_lock (&thread_data.mutex);
+ thread_data.stop_thumbnailing = TRUE;
+ g_cond_signal (&thread_data.render_thumb);
+ g_mutex_unlock (&thread_data.mutex);
+ g_thread_join (thread);
+
+ g_mutex_clear (&thread_data.mutex);
+ g_cond_clear (&thread_data.render_thumb);
+
+ 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..ce7fa8d
--- /dev/null
+++ b/plug-ins/common/file-pdf-save.c
@@ -0,0 +1,1882 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * file-pdf-save.c - PDF file exporter, based on the cairo PDF surface
+ *
+ * Copyright (C) 2010 Barak Itkin <lightningismyname@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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <lightningismyname@gmail.com>
+ * First version of the plugin. This is only a proof of concept and not a full
+ * working plugin.
+ *
+ * May 6, 2009 Barak | Itkin <lightningismyname@gmail.com>
+ * 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 <lightningismyname@gmail.com>
+ * 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 "<Image>/File/Create/PDF"
+ *
+ * August 21, 2009 | Barak Itkin <lightningismyname@gmail.com>
+ * 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 <lightningismyname@gmail.com>
+ * 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 <errno.h>
+
+#include <glib/gstdio.h>
+#include <cairo-pdf.h>
+#include <pango/pangocairo.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 SAVE_TRANSPARENT_PROC "file-pdf-save-transparent"
+#define SAVE_MULTI_TRANSPARENT_PROC "file-pdf-save-multi-transparent"
+#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_TRANSPARENT_BACKGROUND,
+ 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_TRANSPARENT_BACKGROUND,
+ SMA_ARG_COUNT
+} SaveMultiArgs;
+
+typedef struct
+{
+ gboolean vectorize;
+ gboolean ignore_hidden;
+ gboolean apply_masks;
+ gboolean layers_as_pages;
+ gboolean reverse_order;
+ gboolean transparent_background;
+} 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 */
+ TRUE /* transparent_background */
+};
+
+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_transparent_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, "fill-background-color", "Fill transparent areas with background color if layer has an alpha channel. 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" }
+ };
+
+ static GimpParamDef save_multi_transparent_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_PDB_INT32, "fill-background-color", "Fill transparent areas with background color if layer has an alpha channel. TRUE or FALSE" }
+ };
+
+ 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_TRANSPARENT_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-save2 to optionally fill transparent "
+ "areas with the background color",
+ "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 (save_transparent_args), 0,
+ save_transparent_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);
+
+ gimp_install_procedure (SAVE_MULTI_TRANSPARENT_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-multi to optionally fill transparent "
+ "areas with the background color",
+ "Barak Itkin",
+ "Copyright Barak Itkin",
+ "August 2009",
+ N_("_Create multipage PDF..."),
+ "RGB*, GRAY*, INDEXED*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_multi_transparent_args), 0,
+ save_multi_transparent_args, NULL);
+
+
+#if 0
+ gimp_plugin_menu_register (SAVE_MULTI_PROC,
+ "<Image>/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 if transparent and
+ * user chose that option.
+ */
+ if (gimp_drawable_has_alpha (layers[n_layers - 1]) &&
+ optimize.transparent_background)
+ {
+ 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 - 3) ||
+ (g_str_equal (name, SAVE2_PROC) && nparams == SA_ARG_COUNT - 1) ||
+ (g_str_equal (name, SAVE_TRANSPARENT_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 - 1)
+ {
+ optimize.layers_as_pages = param[SA_LAYERS_AS_PAGES].data.d_int32;
+ optimize.reverse_order = param[SA_REVERSE_ORDER].data.d_int32;
+ }
+ if (nparams == SA_ARG_COUNT)
+ optimize.transparent_background = param[SA_TRANSPARENT_BACKGROUND].data.d_int32;
+ }
+ else
+ defaults = TRUE;
+ }
+ else if ((g_str_equal (name, SAVE_MULTI_PROC) && nparams == SMA_ARG_COUNT - 1) ||
+ (g_str_equal (name, SAVE_MULTI_TRANSPARENT_PROC) && nparams == SMA_ARG_COUNT))
+ {
+ single = 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;
+
+ if (nparams == SA_ARG_COUNT)
+ optimize.transparent_background = param[SA_TRANSPARENT_BACKGROUND].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 *fill_background_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);
+
+ fill_background_c = gtk_check_button_new_with_mnemonic (_("_Fill transparent areas with background color"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fill_background_c),
+ optimize.transparent_background);
+ gtk_box_pack_end (GTK_BOX (vbox), fill_background_c, TRUE, TRUE, 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, 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.transparent_background =
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fill_background_c));
+ 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 *fill_background_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);
+
+ fill_background_c = gtk_check_button_new_with_mnemonic (_("_Fill transparent areas with background color"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fill_background_c),
+ optimize.transparent_background);
+ gtk_box_pack_end (GTK_BOX (vbox), fill_background_c, TRUE, TRUE, 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.transparent_background =
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fill_background_c));
+ 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;
+ gdouble opacity;
+
+ if (optimize.reverse_order && optimize.layers_as_pages)
+ layer_ID = layers [j];
+ else
+ layer_ID = layers [n_layers - j - 1];
+
+ opacity = gimp_layer_get_opacity (layer_ID) / 100.0;
+ if ((! gimp_item_get_visible (layer_ID) || opacity == 0.0) &&
+ optimize.ignore_hidden)
+ return TRUE;
+
+ 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;
+ 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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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 <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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..063f201
--- /dev/null
+++ b/plug-ins/common/file-png.c
@@ -0,0 +1,2654 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <stdlib.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include <png.h> /* 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 <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
+ "Michael Sweet <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
+ "Nick Lamb <njl195@zepler.org.uk>",
+ 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 <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
+ "Michael Sweet <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
+ "Nick Lamb <njl195@zepler.org.uk>",
+ 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 <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
+ "Michael Sweet <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
+ "Nick Lamb <njl195@zepler.org.uk>",
+ 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 <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
+ "Michael Sweet <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
+ "Nick Lamb <njl195@zepler.org.uk>",
+ 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 <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
+ "Michael Sweet <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
+ "Nick Lamb <njl195@zepler.org.uk>",
+ 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 <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
+ "Michael Sweet <mike@easysw.com>, "
+ "Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
+ "Nick Lamb <njl195@zepler.org.uk>",
+ 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 (offset_x != 0 ||
+ offset_y != 0)
+ {
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <setjmp.h>
+#include <stdlib.h>
+#include <math.h>
+#include <errno.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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..ee432a8
--- /dev/null
+++ b/plug-ins/common/file-ps.c
@@ -0,0 +1,3940 @@
+/* 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 <aa056@chebucto.ns.ca>
+ *
+ * Added Ascii85 encoding
+ * Austin Donnelly <austin@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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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 <errno.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#include <ghostscript/ierrors.h>
+#include <ghostscript/iapi.h>
+#include <ghostscript/gdevdsp.h>
+
+#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,
+ gchar **tmp_filename);
+
+static void ps_close (FILE *ifp,
+ gchar *tmp_filename);
+
+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.net>",
+ "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.net>",
+ "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.net>",
+ "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.net>",
+ "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.net>",
+ "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.net>",
+ "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;
+ GdkPixbuf *pixbuf = NULL;
+ gchar *tmp_filename = NULL;
+
+#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, &tmp_filename);
+ 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;
+ /* Use pixbuf to load transparent EPS as PNGs */
+ pixbuf = gdk_pixbuf_new_from_file (tmp_filename, error);
+ if (! pixbuf)
+ return -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, tmp_filename);
+
+ /* EPS are now imported using pngalpha, so they can be converted
+ * to a layer with gimp_layer_new_from_pixbuf () and exported at
+ * this part of the loading process
+ */
+ if (is_epsf)
+ {
+ gint32 layer;
+
+ image_ID = gimp_image_new (urx, ury, GIMP_RGB);
+
+ gimp_image_undo_disable (image_ID);
+
+ gimp_image_set_filename (image_ID, filename);
+ gimp_image_set_resolution (image_ID,
+ plvals.resolution,
+ plvals.resolution);
+
+ layer = gimp_layer_new_from_pixbuf (image_ID, _("Rendered EPS"), pixbuf,
+ 100,
+ gimp_image_get_default_new_layer_mode (image_ID),
+ 0.0, 1.0);
+ gimp_image_insert_layer (image_ID, layer, -1, 0);
+
+ gimp_image_undo_enable (image_ID);
+
+ g_free (image_list);
+ g_object_unref (pixbuf);
+
+ return image_ID;
+ }
+
+ 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;
+}
+
+/* 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,
+ gchar **tmp_filename)
+{
+ 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.
+ */
+ if (*is_epsf)
+ {
+ driver = "pngalpha";
+ *tmp_filename = gimp_temp_name ("png");
+ }
+ else
+ {
+ *tmp_filename = 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", *tmp_filename));
+
+ /* 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_set_arg_encoding(instance, GS_ARG_ENCODING_UTF8);
+ 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 (*tmp_filename, "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, gchar *tmp_filename)
+{
+ /* If a real outputfile was used, close the file and remove it. */
+ fclose (ifp);
+ g_unlink (tmp_filename);
+}
+
+
+/* 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..c0f3480
--- /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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ *
+ * 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 <errno.h>
+#include <string.h>
+#include <zlib.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+#include <libgimpbase/gimpparasiteio.h>
+
+#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)) < 46)))
+ || 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 (&params);
+
+ 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 (&params);
+
+ 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..484ee4c
--- /dev/null
+++ b/plug-ins/common/file-raw-data.c
@@ -0,0 +1,2261 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+#include <io.h>
+#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)
+{
+ size_t bread;
+
+ fseek (fp, offset, SEEK_SET);
+
+ memset (buf, 0xFF, size);
+ bread = fread (buf, 1, size, fp);
+ if (bread < size)
+ {
+ g_printerr ("fread failed: read %u instead of %u bytes\n", (guint) bread, (guint) 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);
+
+ format = gimp_drawable_get_format (drawable_id);
+
+ 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..62928cf
--- /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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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 <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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,sun",
+ "",
+ "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,sun", "");
+}
+
+
+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 <https://www.gnu.org/licenses/>.
+ */
+
+/* SVG loader plug-in
+ * (C) Copyright 2003 Dom Lachowicz <cinamod@hotmail.com>
+ *
+ * Largely rewritten in September 2003 by Sven Neumann <sven@gimp.org>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <librsvg/rsvg.h>
+
+#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 <cinamod@hotmail.com>",
+ 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,<?xml,0,string,<svg");
+
+ gimp_install_procedure (LOAD_THUMB_PROC,
+ "Generates a thumbnail of an SVG image",
+ "Renders a thumbnail of an SVG file using librsvg.",
+ "Dom Lachowicz, Sven Neumann",
+ "Dom Lachowicz <cinamod@hotmail.com>",
+ 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..e833c60
--- /dev/null
+++ b/plug-ins/common/file-tga.c
@@ -0,0 +1,1486 @@
+/* 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 <fraph@ibm.net> and Gordon Matzigkeit
+ * <gord@gnu.ai.mit.edu> based on the TrueVision TGA File Format
+ * Specification, Version 2.0:
+ *
+ * <URL:ftp://ftp.truevision.com/pub/TGA.File.Format.Spec/>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified 2007-07-20, Raphaël Quinet <raphael@gimp.org>:
+ * - 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 <njl195@zepler.org.uk>
+ * - 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 <gord@gnu.ai.mit.edu>:
+ * - Bug fixes and source cleanups.
+ *
+ * Release 1.1, 1997-09-19, Gordon Matzigkeit <gord@gnu.ai.mit.edu>:
+ * - 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 <gord@gnu.ai.mit.edu>:
+ * - 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 <fraph@ibm.net>:
+ * - 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 <sys/times.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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_MAPPED && info.colorMapSize == 32)
+ info.alphaBits = 8;
+
+ if (info.imageType == TGA_TYPE_COLOR && info.bpp == 32)
+ info.alphaBits = 8;
+
+ if (info.imageType == TGA_TYPE_GRAY && info.bpp == 16)
+ info.alphaBits = 8;
+ }
+ else if (info.alphaBits == 4 && info.imageType == TGA_TYPE_COLOR && info.bpp == 32)
+ {
+ /* Incorrect TGA saved by Krita, see issue #9067*/
+ 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);
+ }
+
+ if (dtype == GIMP_INDEXEDA_IMAGE)
+ pixels = g_new (guchar, width * 2);
+ else
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* WMF loading file filter for GIMP
+ * -Dom Lachowicz <cinamod@hotmail.com> 2003
+ * -Francis James Franklin <fjf@alinameridon.com>
+ */
+
+#include "config.h"
+
+#include <libwmf/api.h>
+#include <libwmf/gd.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+#include <libgimpmath/gimpmath.h>
+
+#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 <cinamod@hotmail.com>",
+ "Dom Lachowicz <cinamod@hotmail.com>",
+ "(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 <cinamod@hotmail.com>",
+ "Dom Lachowicz <cinamod@hotmail.com>",
+ "(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 <gord@gnu.org> 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* Release 1.0, 1998-02-04, Gordon Matzigkeit <gord@gnu.org>:
+ * - 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 <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <tksmashiw@gmail.com>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+#include <glib/gprintf.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xcursor/Xcursor.h>
+
+#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 <tksmashiw@gmail.com>",
+ "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 <tksmashiw@gmail.com>",
+ "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 <tksmashiw@gmail.com>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <string.h>
+
+#include <glib/gstdio.h>
+
+#include <gdk/gdk.h> /* For GDK_WINDOWING_WIN32 */
+
+#ifndef GDK_WINDOWING_X11
+#ifndef XPM_NO_X
+#define XPM_NO_X
+#endif
+#else
+#include <X11/Xlib.h>
+#endif
+
+#include <X11/xpm.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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; i<cpp; ++i)
+ {
+ charnum = indtemp % (sizeof (linenoise) - 1);
+ indtemp = indtemp / (sizeof (linenoise) - 1);
+ *p++ = linenoise[charnum];
+ }
+ /* *p++=linenoise[indtemp]; */
+
+ *p = '\0'; /* C and its stupid null-terminated strings... */
+
+ array[index].symbolic = NULL;
+ array[index].m_color = NULL;
+ array[index].g4_color = NULL;
+
+ if (color)
+ {
+ array[index].g_color = NULL;
+ array[index].c_color = colorstring;
+ }
+ else
+ {
+ array[index].c_color = NULL;
+ array[index].g_color = colorstring;
+ }
+}
+
+static void
+create_colormap_from_hash (gpointer gkey,
+ gpointer value,
+ gpointer user_data)
+{
+ rgbkey *key = gkey;
+ gchar *string = g_new (gchar, 8);
+
+ sprintf (string, "#%02X%02X%02X", (int)key->r, (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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * 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 <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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; j<pixelmap->npixel; 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This plug-in generates a film roll with several images
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 (&centerPreview, &centerFrame,
+ 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 (&centerVbox, 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 <https://www.gnu.org/licenses/>.
+
+******************************************************************************/
+
+#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 <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <s1041150@u-aizu.ac.jp>",
+ "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, "<Image>/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, &parameters);
+ break;
+
+ case GIMP_RUN_INTERACTIVE:
+ gimp_get_data (PLUG_IN_PROC, &parameters);
+ if (!dialog_show ())
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ break;
+ }
+ gimp_set_data (PLUG_IN_PROC, &parameters, 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),
+ &parameters.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),
+ &parameters.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),
+ &parameters.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),
+ &parameters.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),
+ &parameters.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),
+ &parameters.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..e8cbbd1
--- /dev/null
+++ b/plug-ins/common/gimprc.common
@@ -0,0 +1,90 @@
+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_jpegxl_RC = file-jpegxl.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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+
+#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 <pippin@gimp.org>",
+ "Øyvind Kolås <pippin@gimp.org>",
+ "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, "<Image>/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 <taka@ma1.seikyou.ne.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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include <libgimp/gimp.h>
+
+#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, "<Image>/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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+/* Original plug-in coded by Tim Newsome.
+ *
+ * Changed to make use of real-life units by Sven Neumann <sven@gimp.org>.
+ *
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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..ad3ad3f
--- /dev/null
+++ b/plug-ins/common/guillotine.c
@@ -0,0 +1,305 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+
+#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>/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;
+ gchar *fileindex_with_xcf_extension;
+ 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);
+
+ /* preparation to replace original image extension with GIMP default
+ see issue #8581 for details */
+ fileindex_with_xcf_extension = g_strdup_printf ("%s.xcf", fileindex);
+ g_free (fileindex);
+
+ /* 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_truncate (new_filename, pos);
+ g_string_insert (new_filename, pos, fileindex_with_xcf_extension);
+ g_free (fileindex_with_xcf_extension);
+
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * GUMP - Gimp Useless Mail Plugin
+ * (or Gump Useless Mail Plugin if you prefer)
+ *
+ * by Adrian Likins <adrian@gimp.org>
+ * MIME encapsulation by Reagan Blundell <reagan@emails.net>
+ *
+ * As always: The utility of this plugin is left as an exercise for
+ * the reader
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#ifdef SENDMAIL
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <narazaki@InetQ.or.jp>
+ * Time-stamp: <2000-02-08 16:26:24 yasuhiro>
+ * Version: 0.35
+ *
+ * Copyright (C) 1997 Shuji Narazaki <narazaki@InetQ.or.jp>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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,
+ &param);
+ }
+
+ 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, &param);
+
+ 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 <<EOT;
+
+
+## ---------------------------------------------------------
+## 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 $rcfile
+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$extra \\
+ $rcfile
+
+AM_CPPFLAGS = \\
+ -I\$(top_srcdir) \\
+ \$(GTK_CFLAGS) \\
+ \$(GEGL_CFLAGS) \\
+ -I\$(includedir)
+
+$dirs
+
+$bins
+
+EXTRA_PROGRAMS = \\
+$opts
+
+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
+EOT
+
+print IGNORE <<EOT;
+/.deps
+/.libs
+/Makefile
+/Makefile.in
+EOT
+
+foreach (sort keys %plugins) {
+ my $makename = $_;
+ $makename =~ s/-/_/g;
+
+ my $libgimp = "";
+
+ if (exists $plugins{$_}->{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 <<EOT;
+
+${makename}_LDFLAGS = $ldflags
+EOT
+ }
+
+ if (exists $plugins{$_}->{cflags}) {
+ my $cflags = $plugins{$_}->{cflags};
+ my $cflagsvalue = $cflags =~ /FLAGS/ ? "\$($cflags)" : $cflags;
+
+ print MK <<EOT;
+
+${makename}_CFLAGS = $cflagsvalue
+EOT
+ }
+
+ if (exists $plugins{$_}->{cppflags}) {
+ my $cppflags = $plugins{$_}->{cppflags};
+
+ print MK <<EOT;
+
+${makename}_CPPFLAGS = $cppflags
+EOT
+ }
+
+ my $deplib = "\$(RT_LIBS)\t\t\\\n\t\$(INTLLIBS)";
+ if (exists $plugins{$_}->{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 <<EOT;
+
+${makename}_SOURCES = \\
+ $_.c
+
+${makename}_LDADD = \\
+ $libgimp \\
+ $glib$optlib
+ $deplib \\
+ $rclib
+EOT
+
+ print RC <<EOT;
+${makename}_RC = $_.rc.o
+EOT
+
+ print IGNORE "/$_\n";
+ print IGNORE "/$_.exe\n";
+}
+
+close RC;
+close MK;
+close IGNORE;
+
+&write_file($outmk, $destdir);
+&write_file($outignore, $destdir);
+&write_file($outrc, $destdir);
+
diff --git a/plug-ins/common/nl-filter.c b/plug-ins/common/nl-filter.c
new file mode 100644
index 0000000..3f4f302
--- /dev/null
+++ b/plug-ins/common/nl-filter.c
@@ -0,0 +1,1149 @@
+/**************************************************
+ * file: nlfilt/nlfilt.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 without 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.
+ */
+
+/*
+ * Algorithm fixes, V2.0 compatibility by David Hodson hodsond@ozemail.com.au
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <time.h>
+
+#include <gtk/gtk.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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,
+ &copyright,
+ &date,
+ &type,
+ &n_params,
+ &n_return_vals,
+ &params,
+ &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..b4294d7
--- /dev/null
+++ b/plug-ins/common/plugin-defs.pl
@@ -0,0 +1,92 @@
+%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, libdep => 'GEXIV2:LCMS', 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-jpegxl' => { ui => 1, optional => 1, gegl => 1, libdep => 'GEXIV2:JXL:JXL_THREADS', cflags => 'JXL_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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * dbbrowser
+ * 0.08 26th sept 97 by Thomas NOEL <thomas@minet.net>
+ */
+
+/*
+ * 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 <gtk/gtk.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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..5b4a6ed
--- /dev/null
+++ b/plug-ins/common/qbist.c
@@ -0,0 +1,912 @@
+/*
+ * Written 1997 Jens Ch. Restemeier <jrestemeier@currantbun.com>
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <limits.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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) % NUM_TRANSFORMS;
+
+ for (i = 0; i < MAX_TRANSFORMS; i++)
+ info[0].source[i] = get_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 1) % NUM_REGISTERS;
+
+ for (i = 0; i < MAX_TRANSFORMS; i++)
+ info[0].control[i] = get_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 2) % NUM_REGISTERS;
+
+ for (i = 0; i < MAX_TRANSFORMS; i++)
+ info[0].dest[i] = get_be16 (buf + i * 2 + MAX_TRANSFORMS * 2 * 3) % NUM_REGISTERS;
+
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <mike@easysw.com>",
+ "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 <spot@cs.cmu.edu>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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,
+ "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * SphereDesigner v0.4 - creates textured spheres
+ * by Vidar Madsen <vidar@prosalg.no>
+ *
+ * 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 <string.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<unknown>");
+ 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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This filter tiles an image to arbitrary width and height
+ */
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <mitch@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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<control>N",
+ N_("Create a new unit from scratch"),
+ G_CALLBACK (new_callback)
+ },
+
+ { "unit-editor-duplicate", GIMP_ICON_OBJECT_DUPLICATE,
+ NULL, "<control>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 <mitch@gimp.org>",
+ "Michael Natterer <mitch@gimp.org>",
+ "2000",
+ N_("U_nits"),
+ "",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (args), 0,
+ args, NULL);
+
+ gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/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,
+ "<ui>\n"
+ " <toolbar action=\"unit-editor-toolbar\">\n"
+ " <toolitem action=\"unit-editor-new\" />\n"
+ " <toolitem action=\"unit-editor-duplicate\" />\n"
+ " </toolbar>\n"
+ "</ui>\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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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, "<Image>/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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@seznam.cz>",
+ "Miroslav Talasek <miroslav.talasek@seznam.cz>",
+ "19january 2017",
+ N_("_Wavelet-decompose..."),
+ "RGB*, GRAY*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (args), 0,
+ args, NULL);
+
+ gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/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 <brix@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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h> /* strlen, strstr */
+
+#include <gtk/gtk.h>
+
+#ifdef PLATFORM_OSX
+#import <Cocoa/Cocoa.h>
+#endif
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#endif
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#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 <brix@gimp.org>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+/* Webpage plug-in.
+ * Copyright (C) 2011 Mukund Sivaraman <muks@banu.com>.
+ * Portions are copyright of the author of the
+ * file-open-location-dialog.c code.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include <webkit/webkit.h>
+
+#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 <muks@banu.com>",
+ "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, "<Image>/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/<GIMP_VERSION>" 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;
+}