diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
commit | 3c57dd931145d43f2b0aef96c4d178135956bf91 (patch) | |
tree | 3de698981e9f0cc2c4f9569b19a5f3595e741f6b /plug-ins/file-jpeg | |
parent | Initial commit. (diff) | |
download | gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip |
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | plug-ins/file-jpeg/Makefile.am | 77 | ||||
-rw-r--r-- | plug-ins/file-jpeg/Makefile.in | 1069 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-icc.c | 263 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-icc.h | 59 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-load.c | 719 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-load.h | 33 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-quality.c | 148 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-quality.h | 26 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-save.c | 1524 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-save.h | 57 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-settings.c | 397 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg-settings.h | 37 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg.c | 643 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpeg.h | 73 | ||||
-rw-r--r-- | plug-ins/file-jpeg/jpegqual.c | 1082 |
15 files changed, 6207 insertions, 0 deletions
diff --git a/plug-ins/file-jpeg/Makefile.am b/plug-ins/file-jpeg/Makefile.am new file mode 100644 index 0000000..b443475 --- /dev/null +++ b/plug-ins/file-jpeg/Makefile.am @@ -0,0 +1,77 @@ +## Process this file with automake to produce Makefile.in + +libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la + +if OS_WIN32 +mwindows = -mwindows +endif + +if HAVE_WINDRES +include $(top_srcdir)/build/windows/gimprc-plug-ins.rule +file_jpeg_RC = file-jpeg.rc.o +endif + +AM_LDFLAGS = $(mwindows) + +libexecdir = $(gimpplugindir)/plug-ins/file-jpeg + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(EXIF_CFLAGS) \ + $(LCMS_CFLAGS) \ + $(GEGL_CFLAGS) \ + $(GEXIV2_CFLAGS) \ + -I$(includedir) + +libexec_PROGRAMS = file-jpeg + +file_jpeg_SOURCES = \ + jpeg.c \ + jpeg.h \ + jpeg-icc.c \ + jpeg-icc.h \ + jpeg-load.c \ + jpeg-load.h \ + jpeg-save.c \ + jpeg-save.h \ + jpeg-quality.c \ + jpeg-quality.h \ + jpeg-settings.c \ + jpeg-settings.h + +file_jpeg_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpconfig) \ + $(libgimp) \ + $(libgimpcolor) \ + $(libgimpmath) \ + $(libgimpbase) \ + $(JPEG_LIBS) \ + $(LCMS_LIBS) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(GEXIV2_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_jpeg_RC) + +noinst_PROGRAMS = jpegqual + +jpegqual_SOURCES = \ + jpeg-quality.c \ + jpeg-quality.h \ + jpegqual.c + +jpegqual_LDADD = \ + $(JPEG_LIBS) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) diff --git a/plug-ins/file-jpeg/Makefile.in b/plug-ins/file-jpeg/Makefile.in new file mode 100644 index 0000000..ab5f989 --- /dev/null +++ b/plug-ins/file-jpeg/Makefile.in @@ -0,0 +1,1069 @@ +# 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@ +libexec_PROGRAMS = file-jpeg$(EXEEXT) +noinst_PROGRAMS = jpegqual$(EXEEXT) +subdir = plug-ins/file-jpeg +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)$(libexecdir)" +PROGRAMS = $(libexec_PROGRAMS) $(noinst_PROGRAMS) +am_file_jpeg_OBJECTS = jpeg.$(OBJEXT) jpeg-icc.$(OBJEXT) \ + jpeg-load.$(OBJEXT) jpeg-save.$(OBJEXT) jpeg-quality.$(OBJEXT) \ + jpeg-settings.$(OBJEXT) +file_jpeg_OBJECTS = $(am_file_jpeg_OBJECTS) +am__DEPENDENCIES_1 = +file_jpeg_DEPENDENCIES = $(libgimpui) $(libgimpwidgets) \ + $(libgimpconfig) $(libgimp) $(libgimpcolor) $(libgimpmath) \ + $(libgimpbase) $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_jpeg_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_jpegqual_OBJECTS = jpeg-quality.$(OBJEXT) jpegqual.$(OBJEXT) +jpegqual_OBJECTS = $(am_jpegqual_OBJECTS) +jpegqual_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +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)/jpeg-icc.Po ./$(DEPDIR)/jpeg-load.Po \ + ./$(DEPDIR)/jpeg-quality.Po ./$(DEPDIR)/jpeg-save.Po \ + ./$(DEPDIR)/jpeg-settings.Po ./$(DEPDIR)/jpeg.Po \ + ./$(DEPDIR)/jpegqual.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 = $(file_jpeg_SOURCES) $(jpegqual_SOURCES) +DIST_SOURCES = $(file_jpeg_SOURCES) $(jpegqual_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 \ + $(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 = $(gimpplugindir)/plug-ins/file-jpeg +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@ +libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la +@OS_WIN32_TRUE@mwindows = -mwindows +@HAVE_WINDRES_TRUE@GIMPPLUGINRC = $(top_builddir)/build/windows/gimp-plug-ins.rc +@HAVE_WINDRES_TRUE@file_jpeg_RC = file-jpeg.rc.o +AM_LDFLAGS = $(mwindows) +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(EXIF_CFLAGS) \ + $(LCMS_CFLAGS) \ + $(GEGL_CFLAGS) \ + $(GEXIV2_CFLAGS) \ + -I$(includedir) + +file_jpeg_SOURCES = \ + jpeg.c \ + jpeg.h \ + jpeg-icc.c \ + jpeg-icc.h \ + jpeg-load.c \ + jpeg-load.h \ + jpeg-save.c \ + jpeg-save.h \ + jpeg-quality.c \ + jpeg-quality.h \ + jpeg-settings.c \ + jpeg-settings.h + +file_jpeg_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpconfig) \ + $(libgimp) \ + $(libgimpcolor) \ + $(libgimpmath) \ + $(libgimpbase) \ + $(JPEG_LIBS) \ + $(LCMS_LIBS) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(GEXIV2_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_jpeg_RC) + +jpegqual_SOURCES = \ + jpeg-quality.c \ + jpeg-quality.h \ + jpegqual.c + +jpegqual_LDADD = \ + $(JPEG_LIBS) \ + $(GTK_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) + +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 $(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/file-jpeg/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu plug-ins/file-jpeg/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 $(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-libexecPROGRAMS: $(libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(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)$(libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(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)$(libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libexecdir)" && rm -f $$files + +clean-libexecPROGRAMS: + @list='$(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 + +clean-noinstPROGRAMS: + @list='$(noinst_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 + +file-jpeg$(EXEEXT): $(file_jpeg_OBJECTS) $(file_jpeg_DEPENDENCIES) $(EXTRA_file_jpeg_DEPENDENCIES) + @rm -f file-jpeg$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_jpeg_OBJECTS) $(file_jpeg_LDADD) $(LIBS) + +jpegqual$(EXEEXT): $(jpegqual_OBJECTS) $(jpegqual_DEPENDENCIES) $(EXTRA_jpegqual_DEPENDENCIES) + @rm -f jpegqual$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(jpegqual_OBJECTS) $(jpegqual_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpeg-icc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpeg-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpeg-quality.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpeg-save.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpeg-settings.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpeg.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpegqual.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 $@ $< + +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)$(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-generic clean-libexecPROGRAMS clean-libtool \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/jpeg-icc.Po + -rm -f ./$(DEPDIR)/jpeg-load.Po + -rm -f ./$(DEPDIR)/jpeg-quality.Po + -rm -f ./$(DEPDIR)/jpeg-save.Po + -rm -f ./$(DEPDIR)/jpeg-settings.Po + -rm -f ./$(DEPDIR)/jpeg.Po + -rm -f ./$(DEPDIR)/jpegqual.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-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)/jpeg-icc.Po + -rm -f ./$(DEPDIR)/jpeg-load.Po + -rm -f ./$(DEPDIR)/jpeg-quality.Po + -rm -f ./$(DEPDIR)/jpeg-save.Po + -rm -f ./$(DEPDIR)/jpeg-settings.Po + -rm -f ./$(DEPDIR)/jpeg.Po + -rm -f ./$(DEPDIR)/jpegqual.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-libexecPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libexecPROGRAMS clean-libtool \ + clean-noinstPROGRAMS 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-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libexecPROGRAMS install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip 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-am uninstall-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) $@ + +# 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/file-jpeg/jpeg-icc.c b/plug-ins/file-jpeg/jpeg-icc.c new file mode 100644 index 0000000..88f75d4 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-icc.c @@ -0,0 +1,263 @@ +/* jpeg-icc.c + * + * This file provides code to read and write International Color + * Consortium (ICC) device profiles embedded in JFIF JPEG image files. + * The ICC has defined a standard format for including such data in + * JPEG "APP2" markers. The code given here does not know anything + * about the internal structure of the ICC profile data; it just knows + * how to put the profile data into a JPEG file being written, or get + * it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + */ + +/* This code was originally copied from the jpegicc tool as found in + * the lcms source code. This code comes with the following copyright + * notice: + * + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + */ + +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include <stdio.h> + +#include <jpeglib.h> + +#include <glib.h> + +#include "jpeg-icc.h" + + +/* + * Since an ICC profile can be larger than the maximum size of a JPEG + * marker (64K), we need provisions to split it into multiple markers. + * The format defined by the ICC specifies one or more APP2 markers + * containing the following data: + * + * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) + * Number of markers Total number of APP2's used (1 byte) + * Profile data (remainder of APP2 data) + + * Decoders should use the marker sequence numbers to reassemble the + * profile, rather than assuming that the APP2 markers appear in the + * correct sequence. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +void +jpeg_icc_write_profile (j_compress_ptr cinfo, + const guchar *icc_data_ptr, + guint icc_data_len) +{ + unsigned int num_markers; /* total number of markers we'll write */ + int cur_marker = 1; /* per spec, counting starts at 1 */ + unsigned int length; /* number of bytes to write in this marker */ + + /* Calculate the number of markers we'll need, rounding up of course */ + num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER; + if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len) + num_markers++; + + while (icc_data_len > 0) { + /* length of profile to put in this marker */ + length = icc_data_len; + if (length > MAX_DATA_BYTES_IN_MARKER) + length = MAX_DATA_BYTES_IN_MARKER; + icc_data_len -= length; + + /* Write the JPEG marker header (APP2 code and marker length) */ + jpeg_write_m_header(cinfo, ICC_MARKER, + (unsigned int) (length + ICC_OVERHEAD_LEN)); + + /* Write the marker identifying string "ICC_PROFILE" (null-terminated). + * We code it in this less-than-transparent way so that the code works + * even if the local character set is not ASCII. + */ + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x5F); + jpeg_write_m_byte(cinfo, 0x50); + jpeg_write_m_byte(cinfo, 0x52); + jpeg_write_m_byte(cinfo, 0x4F); + jpeg_write_m_byte(cinfo, 0x46); + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x4C); + jpeg_write_m_byte(cinfo, 0x45); + jpeg_write_m_byte(cinfo, 0x0); + + /* Add the sequencing info */ + jpeg_write_m_byte(cinfo, cur_marker); + jpeg_write_m_byte(cinfo, (int) num_markers); + + /* Add the profile data */ + while (length--) { + jpeg_write_m_byte(cinfo, *icc_data_ptr); + icc_data_ptr++; + } + cur_marker++; + } +} + + +/* + * Handy subroutine to test whether a saved marker is an ICC profile marker. + */ + +static boolean +marker_is_icc (jpeg_saved_marker_ptr marker) +{ + return + marker->marker == ICC_MARKER && + marker->data_length >= ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * NOTE: if the file contains invalid ICC APP2 markers, we just silently + * return FALSE. You might want to issue an error message instead. + */ + +gboolean +jpeg_icc_read_profile (j_decompress_ptr cinfo, + guchar **icc_data_ptr, + guint *icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET *icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) + marker_present[seq_no] = 0; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + if (num_markers == 0) + num_markers = GETJOCTET(marker->data[13]); + else if (num_markers != GETJOCTET(marker->data[13])) + return FALSE; /* inconsistent num_markers fields */ + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) + return FALSE; /* bogus sequence number */ + if (marker_present[seq_no]) + return FALSE; /* duplicate sequence numbers */ + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) + return FALSE; + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) + return FALSE; /* missing sequence number */ + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length <= 0) + return FALSE; /* found only empty markers? */ + + /* Allocate space for assembled data */ + icc_data = g_try_malloc (total_length * sizeof(JOCTET)); + if (icc_data == NULL) + return FALSE; /* oops, out of memory */ + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + JOCTET FAR *src_ptr; + JOCTET *dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} diff --git a/plug-ins/file-jpeg/jpeg-icc.h b/plug-ins/file-jpeg/jpeg-icc.h new file mode 100644 index 0000000..b5306a8 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-icc.h @@ -0,0 +1,59 @@ +/* jpeg-icc.h + * + * This code was originally copied from the jpegicc tool as found in + * the lcms source code. This code comes with the following copyright + * notice: + * + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + */ + +#ifndef __JPEG_ICC_H__ +#define __JPEG_ICC_H__ + +void jpeg_icc_write_profile (j_compress_ptr cinfo, + const guchar *icc_data_ptr, + guint icc_data_len); + + +/* + * Reading a JPEG file that may contain an ICC profile requires two steps: + * + * 1. After jpeg_create_decompress() but before jpeg_read_header(), + * ask the IJG library to save in memory any APP2 markers it may find + * in the file. + * + * 2. After jpeg_read_header(), call jpeg_icc_read_profile() to find + * out whether there was a profile and obtain it if so. + * + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + */ + +gboolean jpeg_icc_read_profile (j_decompress_ptr cinfo, + guchar **icc_data_ptr, + guint *icc_data_len); + +#endif /* __JPEG_ICC_H__ */ diff --git a/plug-ins/file-jpeg/jpeg-load.c b/plug-ins/file-jpeg/jpeg-load.c new file mode 100644 index 0000000..df53327 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-load.c @@ -0,0 +1,719 @@ +/* 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 <errno.h> +#include <setjmp.h> + +#include <gio/gio.h> +#include <glib/gstdio.h> +#include <gexiv2/gexiv2.h> + +#include <jpeglib.h> +#include <jerror.h> + +#include <lcms2.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "libgimp/stdplugins-intl.h" + +#include "jpeg.h" +#include "jpeg-icc.h" +#include "jpeg-settings.h" +#include "jpeg-load.h" + +static gboolean jpeg_load_resolution (gint32 image_ID, + struct jpeg_decompress_struct + *cinfo); + +static void jpeg_load_sanitize_comment (gchar *comment); + +static gpointer jpeg_load_cmyk_transform (guint8 *profile_data, + gsize profile_len); +static void jpeg_load_cmyk_to_rgb (guchar *buf, + glong pixels, + gpointer transform); + +gint32 volatile preview_image_ID; +gint32 preview_layer_ID; + +gint32 +load_image (const gchar *filename, + GimpRunMode runmode, + gboolean preview, + gboolean *resolution_loaded, + GError **error) +{ + gint32 volatile image_ID; + gint32 layer_ID; + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + jpeg_saved_marker_ptr marker; + FILE *infile; + guchar *buf; + guchar **rowbuf; + GimpImageBaseType image_type; + GimpImageType layer_type; + GeglBuffer *buffer = NULL; + const Babl *format; + gint tile_height; + gint i; + cmsHTRANSFORM cmyk_transform = NULL; + + /* We set up the normal JPEG error routines. */ + cinfo.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = my_error_exit; + + if (!preview) + { + jerr.pub.output_message = my_output_message; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + } + + if ((infile = g_fopen (filename, "rb")) == 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; + } + + image_ID = -1; + + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp (jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_decompress (&cinfo); + if (infile) + fclose (infile); + + if (image_ID != -1 && !preview) + gimp_image_delete (image_ID); + + if (preview) + destroy_preview (); + + if (buffer) + g_object_unref (buffer); + + return -1; + } + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress (&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src (&cinfo, infile); + + if (! preview) + { + /* - step 2.1: tell the lib to save the comments */ + jpeg_save_markers (&cinfo, JPEG_COM, 0xffff); + + /* - step 2.2: tell the lib to save APP1 data (Exif or XMP) */ + jpeg_save_markers (&cinfo, JPEG_APP0 + 1, 0xffff); + + /* - step 2.3: tell the lib to save APP2 data (ICC profiles) */ + jpeg_save_markers (&cinfo, JPEG_APP0 + 2, 0xffff); + } + + /* Step 3: read file parameters with jpeg_read_header() */ + + jpeg_read_header (&cinfo, TRUE); + + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here, except set the DCT + * method. + */ + + cinfo.dct_method = JDCT_FLOAT; + + /* Step 5: Start decompressor */ + + jpeg_start_decompress (&cinfo); + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + */ + + /* temporary buffer */ + tile_height = gimp_tile_height (); + buf = g_new (guchar, + tile_height * cinfo.output_width * cinfo.output_components); + + rowbuf = g_new (guchar *, tile_height); + + for (i = 0; i < tile_height; i++) + rowbuf[i] = buf + cinfo.output_width * cinfo.output_components * i; + + switch (cinfo.output_components) + { + case 1: + image_type = GIMP_GRAY; + layer_type = GIMP_GRAY_IMAGE; + break; + + case 3: + image_type = GIMP_RGB; + layer_type = GIMP_RGB_IMAGE; + break; + + case 4: + if (cinfo.out_color_space == JCS_CMYK) + { + image_type = GIMP_RGB; + layer_type = GIMP_RGB_IMAGE; + break; + } + /*fallthrough*/ + + default: + g_message ("Don't know how to load JPEG images " + "with %d color channels, using colorspace %d (%d).", + cinfo.output_components, cinfo.out_color_space, + cinfo.jpeg_color_space); + return -1; + break; + } + + if (preview) + { + image_ID = preview_image_ID; + } + else + { + image_ID = gimp_image_new_with_precision (cinfo.output_width, + cinfo.output_height, + image_type, + GIMP_PRECISION_U8_GAMMA); + + gimp_image_undo_disable (image_ID); + gimp_image_set_filename (image_ID, filename); + } + + if (preview) + { + preview_layer_ID = gimp_layer_new (preview_image_ID, _("JPEG preview"), + cinfo.output_width, + cinfo.output_height, + layer_type, + 100, + gimp_image_get_default_new_layer_mode (preview_image_ID)); + layer_ID = preview_layer_ID; + } + else + { + layer_ID = gimp_layer_new (image_ID, _("Background"), + cinfo.output_width, + cinfo.output_height, + layer_type, + 100, + gimp_image_get_default_new_layer_mode (image_ID)); + } + + if (! preview) + { + GString *comment_buffer = NULL; + guint8 *icc_data = NULL; + guint icc_length = 0; + + /* Step 5.0: save the original JPEG settings in a parasite */ + jpeg_detect_original_settings (&cinfo, image_ID); + + /* Step 5.1: check for comments, or Exif metadata in APP1 markers */ + for (marker = cinfo.marker_list; marker; marker = marker->next) + { + const gchar *data = (const gchar *) marker->data; + gsize len = marker->data_length; + + if (marker->marker == JPEG_COM) + { +#ifdef GIMP_UNSTABLE + g_print ("jpeg-load: found image comment (%d bytes)\n", + marker->data_length); +#endif + + if (! comment_buffer) + { + comment_buffer = g_string_new_len (data, len); + } + else + { + /* concatenate multiple comments, separate them with LF */ + g_string_append_c (comment_buffer, '\n'); + g_string_append_len (comment_buffer, data, len); + } + } + else if ((marker->marker == JPEG_APP0 + 1) + && (len > sizeof (JPEG_APP_HEADER_EXIF) + 8) + && ! strcmp (JPEG_APP_HEADER_EXIF, data)) + { +#ifdef GIMP_UNSTABLE + g_print ("jpeg-load: found Exif block (%d bytes)\n", + (gint) (len - sizeof (JPEG_APP_HEADER_EXIF))); +#endif + } + } + + if (jpeg_load_resolution (image_ID, &cinfo)) + { + if (resolution_loaded) + *resolution_loaded = TRUE; + } + + /* if we found any comments, then make a parasite for them */ + if (comment_buffer && comment_buffer->len) + { + GimpParasite *parasite; + + jpeg_load_sanitize_comment (comment_buffer->str); + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (comment_buffer->str) + 1, + comment_buffer->str); + gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + + g_string_free (comment_buffer, TRUE); + } + + /* Step 5.3: check for an embedded ICC profile in APP2 markers */ + jpeg_icc_read_profile (&cinfo, &icc_data, &icc_length); + + if (cinfo.out_color_space == JCS_CMYK) + { + cmyk_transform = jpeg_load_cmyk_transform (icc_data, icc_length); + } + else if (icc_data) /* don't attach the profile if we are transforming */ + { + GimpColorProfile *profile; + + profile = gimp_color_profile_new_from_icc_profile (icc_data, + icc_length, + NULL); + if (profile) + { + gimp_image_set_color_profile (image_ID, profile); + g_object_unref (profile); + } + } + + g_free (icc_data); + + /* Do not attach the "jpeg-save-options" parasite to the image + * because this conflicts with the global defaults (bug #75398). + */ + } + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + + buffer = gimp_drawable_get_buffer (layer_ID); + format = babl_format (image_type == GIMP_RGB ? "R'G'B' u8" : "Y' u8"); + + while (cinfo.output_scanline < cinfo.output_height) + { + gint start, end; + gint scanlines; + gboolean image_truncated = FALSE; + + start = cinfo.output_scanline; + end = cinfo.output_scanline + tile_height; + end = MIN (end, cinfo.output_height); + + scanlines = end - start; + + /* in case of error we now jump here, so pertially loaded imaged + * don't get discarded + */ + if (setjmp (jerr.setjmp_buffer)) + { + image_truncated = TRUE; + + goto set_buffer; + } + + for (i = 0; i < scanlines; i++) + jpeg_read_scanlines (&cinfo, (JSAMPARRAY) &rowbuf[i], 1); + + if (cinfo.out_color_space == JCS_CMYK) + jpeg_load_cmyk_to_rgb (buf, cinfo.output_width * scanlines, + cmyk_transform); + + set_buffer: + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, start, cinfo.output_width, scanlines), + 0, + format, + buf, + GEGL_AUTO_ROWSTRIDE); + + if (image_truncated) + /* jumping to finish skips jpeg_finish_decompress(), its state + * might be broken by whatever caused the loading failure + */ + goto finish; + + if (! preview && (cinfo.output_scanline % 32) == 0) + gimp_progress_update ((gdouble) cinfo.output_scanline / + (gdouble) cinfo.output_height); + } + + /* Step 7: Finish decompression */ + + jpeg_finish_decompress (&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + finish: + + if (cmyk_transform) + cmsDeleteTransform (cmyk_transform); + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress (&cinfo); + + g_object_unref (buffer); + + /* free up the temporary buffers */ + g_free (rowbuf); + g_free (buf); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + fclose (infile); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.num_warnings is nonzero). + */ + + /* Detach from the drawable and add it to the image. + */ + if (! preview) + { + gimp_progress_update (1.0); + } + + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + return image_ID; +} + +static gboolean +jpeg_load_resolution (gint32 image_ID, + struct jpeg_decompress_struct *cinfo) +{ + if (cinfo->saw_JFIF_marker && cinfo->X_density != 0 && cinfo->Y_density != 0) + { + gdouble xresolution = cinfo->X_density; + gdouble yresolution = cinfo->Y_density; + gdouble asymmetry = 1.0; + + switch (cinfo->density_unit) + { + case 0: /* unknown -> set the aspect ratio but use the default + * image resolution + */ + asymmetry = xresolution / yresolution; + + gimp_image_get_resolution (image_ID, &xresolution, &yresolution); + + xresolution *= asymmetry; + break; + + case 1: /* dots per inch */ + break; + + case 2: /* dots per cm */ + xresolution *= 2.54; + yresolution *= 2.54; + gimp_image_set_unit (image_ID, GIMP_UNIT_MM); + break; + + default: + g_message ("Unknown density unit %d, assuming dots per inch.", + cinfo->density_unit); + break; + } + + gimp_image_set_resolution (image_ID, xresolution, yresolution); + + return TRUE; + } + + return FALSE; +} + +/* + * A number of JPEG files have comments written in a local character set + * instead of UTF-8. Some of these files may have been saved by older + * versions of GIMP. It is not possible to reliably detect the character + * set used, but it is better to keep all characters in the ASCII range + * and replace the non-ASCII characters instead of discarding the whole + * comment. This is especially useful if the comment contains only a few + * non-ASCII characters such as a copyright sign, a soft hyphen, etc. + */ +static void +jpeg_load_sanitize_comment (gchar *comment) +{ + const gchar *start_invalid; + + if (! g_utf8_validate (comment, -1, &start_invalid)) + { + guchar *c; + + for (c = (guchar *) start_invalid; *c; c++) + { + if (*c > 126 || (*c < 32 && *c != '\t' && *c != '\n' && *c != '\r')) + *c = '?'; + } + } +} + +gint32 +load_thumbnail_image (GFile *file, + gint *width, + gint *height, + GimpImageType *type, + GError **error) +{ + gint32 volatile image_ID = -1; + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + FILE *infile = NULL; + + gimp_progress_init_printf (_("Opening thumbnail for '%s'"), + g_file_get_parse_name (file)); + + image_ID = gimp_image_metadata_load_thumbnail (file, error); + if (image_ID < 1) + return -1; + + cinfo.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = my_error_exit; + jerr.pub.output_message = my_output_message; + + if ((infile = g_fopen (g_file_get_path (file), "rb")) == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + g_file_get_parse_name (file), g_strerror (errno)); + + if (image_ID != -1) + gimp_image_delete (image_ID); + + return -1; + } + + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp (jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. We + * need to clean up the JPEG object, close the input file, + * and return. + */ + jpeg_destroy_decompress (&cinfo); + + if (image_ID != -1) + gimp_image_delete (image_ID); + + return -1; + } + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress (&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src (&cinfo, infile); + + /* Step 3: read file parameters with jpeg_read_header() */ + + jpeg_read_header (&cinfo, TRUE); + + jpeg_start_decompress (&cinfo); + + *width = cinfo.output_width; + *height = cinfo.output_height; + + switch (cinfo.output_components) + { + case 1: + *type = GIMP_GRAY_IMAGE; + break; + + case 3: + *type = GIMP_RGB_IMAGE; + break; + + case 4: + if (cinfo.out_color_space == JCS_CMYK) + { + *type = GIMP_RGB_IMAGE; + break; + } + /*fallthrough*/ + + default: + g_message ("Don't know how to load JPEG images " + "with %d color channels, using colorspace %d (%d).", + cinfo.output_components, cinfo.out_color_space, + cinfo.jpeg_color_space); + + gimp_image_delete (image_ID); + image_ID = -1; + break; + } + + /* Step 4: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal + * of memory. + */ + jpeg_destroy_decompress (&cinfo); + + fclose (infile); + + return image_ID; +} + +static gpointer +jpeg_load_cmyk_transform (guint8 *profile_data, + gsize profile_len) +{ + GimpColorConfig *config = gimp_get_color_configuration (); + GimpColorProfile *cmyk_profile = NULL; + GimpColorProfile *rgb_profile = NULL; + cmsHPROFILE cmyk_lcms; + cmsHPROFILE rgb_lcms; + cmsUInt32Number flags = 0; + cmsHTRANSFORM transform; + + /* try to load the embedded CMYK profile */ + if (profile_data) + { + cmyk_profile = gimp_color_profile_new_from_icc_profile (profile_data, + profile_len, + NULL); + + if (cmyk_profile && ! gimp_color_profile_is_cmyk (cmyk_profile)) + { + g_object_unref (cmyk_profile); + cmyk_profile = NULL; + } + } + + /* if that fails, try to load the CMYK profile configured in the prefs */ + if (! cmyk_profile) + cmyk_profile = gimp_color_config_get_cmyk_color_profile (config, NULL); + + /* bail out if we can't load any CMYK profile */ + if (! cmyk_profile) + { + g_object_unref (config); + return NULL; + } + + /* always convert to sRGB */ + rgb_profile = gimp_color_profile_new_rgb_srgb (); + + cmyk_lcms = gimp_color_profile_get_lcms_profile (cmyk_profile); + rgb_lcms = gimp_color_profile_get_lcms_profile (rgb_profile); + + if (config->display_intent == + GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC) + { + flags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + } + + transform = cmsCreateTransform (cmyk_lcms, TYPE_CMYK_8_REV, + rgb_lcms, TYPE_RGB_8, + config->display_intent, + flags); + + g_object_unref (cmyk_profile); + g_object_unref (rgb_profile); + + g_object_unref (config); + + return transform; +} + + +static void +jpeg_load_cmyk_to_rgb (guchar *buf, + glong pixels, + gpointer transform) +{ + const guchar *src = buf; + guchar *dest = buf; + + if (transform) + { + cmsDoTransform (transform, buf, buf, pixels); + return; + } + + /* NOTE: The following code assumes inverted CMYK values, even when an + APP14 marker doesn't exist. This is the behavior of recent versions + of PhotoShop as well. */ + + while (pixels--) + { + guint c = src[0]; + guint m = src[1]; + guint y = src[2]; + guint k = src[3]; + + dest[0] = (c * k) / 255; + dest[1] = (m * k) / 255; + dest[2] = (y * k) / 255; + + src += 4; + dest += 3; + } +} diff --git a/plug-ins/file-jpeg/jpeg-load.h b/plug-ins/file-jpeg/jpeg-load.h new file mode 100644 index 0000000..3844097 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-load.h @@ -0,0 +1,33 @@ +/* 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/>. + */ + +#ifndef __JPEG_LOAD_H__ +#define __JPEG_LOAD_H__ + +gint32 load_image (const gchar *filename, + GimpRunMode runmode, + gboolean preview, + gboolean *resolution_loaded, + GError **error); + +gint32 load_thumbnail_image (GFile *file, + gint *width, + gint *height, + GimpImageType *type, + GError **error); + +#endif /* __JPEG_LOAD_H__ */ diff --git a/plug-ins/file-jpeg/jpeg-quality.c b/plug-ins/file-jpeg/jpeg-quality.c new file mode 100644 index 0000000..52bd2c7 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-quality.c @@ -0,0 +1,148 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * jpeg-quality.c + * Copyright (C) 2007 Raphaël Quinet <raphael@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 <glib/gstdio.h> + +#include <jpeglib.h> + +#include "jpeg-quality.h" + +/* + * Note that although 0 is a valid quality for IJG's JPEG library, the + * baseline quantization tables for quality 0 and 1 are identical (all + * divisors are set to the maximum value 255). So 0 can be used here + * to flag unusual settings. + */ + +/* sum of the luminance divisors for quality = 0..100 (IJG standard tables) */ +static gint std_luminance_sum[101] = +{ + G_MAXINT, + 16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859, 12560, 12240, + 11861, 11456, 11081, 10714, 10360, 10027, 9679, 9368, 9056, 8680, 8331, + 7995, 7668, 7376, 7084, 6823, 6562, 6345, 6125, 5939, 5756, 5571, + 5421, 5240, 5086, 4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, + 4092, 3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396, 3323, + 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727, 2657, 2583, 2509, + 2437, 2362, 2290, 2211, 2136, 2068, 1996, 1915, 1858, 1773, 1692, + 1620, 1552, 1477, 1398, 1326, 1251, 1179, 1109, 1031, 961, 884, + 814, 736, 667, 592, 518, 441, 369, 292, 221, 151, 86, + 64 +}; + +/* sum of the chrominance divisors for quality = 0..100 (IJG standard tables) */ +static gint std_chrominance_sum[101] = +{ + G_MAXINT, + 16320, 16320, 16320, 16218, 16010, 15731, 15523, 15369, 15245, 15110, 14985, + 14864, 14754, 14635, 14526, 14429, 14346, 14267, 14204, 13790, 13121, 12511, + 11954, 11453, 11010, 10567, 10175, 9787, 9455, 9122, 8844, 8565, 8288, + 8114, 7841, 7616, 7447, 7227, 7060, 6897, 6672, 6562, 6396, 6226, + 6116, 5948, 5838, 5729, 5614, 5505, 5396, 5281, 5172, 5062, 4947, + 4837, 4726, 4614, 4506, 4395, 4282, 4173, 4061, 3950, 3839, 3727, + 3617, 3505, 3394, 3284, 3169, 3060, 2949, 2836, 2780, 2669, 2556, + 2445, 2336, 2221, 2111, 2000, 1888, 1778, 1666, 1555, 1444, 1332, + 1223, 1110, 999, 891, 779, 668, 558, 443, 333, 224, 115, + 64 +}; + +/** + * jpeg_detect_quality: + * @cinfo: a pointer to a JPEG decompressor info. + * + * Returns the exact or estimated quality value that was used to save + * the JPEG image by analyzing the quantization table divisors. + * + * If an exact match for the IJG quantization tables is found, then a + * quality setting in the range 1..100 is returned. If the quality + * can only be estimated, then a negative number in the range -1..-100 + * is returned; its absolute value represents the maximum IJG quality + * setting to use. If the quality cannot be reliably determined, then + * 0 is returned. + * + * This function must be called after jpeg_read_header() so that + * @cinfo contains the quantization tables read from the DQT markers + * in the file. + * + * Return Value: the JPEG quality setting in the range 1..100, -1..-100 or 0. + */ +gint +jpeg_detect_quality (struct jpeg_decompress_struct *cinfo) +{ + gint t; + gint i; + gint sum[3]; + gint q; + + /* files using CMYK or having 4 quantization tables are unusual */ + if (!cinfo || cinfo->output_components > 3 || cinfo->quant_tbl_ptrs[3]) + return 0; + + /* Most files use table 0 for luminance divisors (Y) and table 1 for + * chrominance divisors (Cb and Cr). Some files use separate tables + * for Cb and Cr, so table 2 may also be used. + */ + for (t = 0; t < 3; t++) + { + sum[t] = 0; + if (cinfo->quant_tbl_ptrs[t]) + for (i = 0; i < DCTSIZE2; i++) + sum[t] += cinfo->quant_tbl_ptrs[t]->quantval[i]; + } + + if (cinfo->output_components > 1) + { + gint sums; + + if (sum[0] < 64 || sum[1] < 64) + return 0; + + /* compare with the chrominance table having the lowest sum */ + if (sum[1] < sum[2] || sum[2] <= 0) + sums = sum[0] + sum[1]; + else + sums = sum[0] + sum[2]; + + q = 100; + while (sums > std_luminance_sum[q] + std_chrominance_sum[q]) + q--; + + if (sum[0] == std_luminance_sum[q] && sum[1] == std_chrominance_sum[q]) + return q; + else + return -q; + } + else + { + if (sum[0] < 64) + return 0; + + q = 100; + while (sum[0] > std_luminance_sum[q]) + q--; + + if (sum[0] == std_luminance_sum[q]) + return q; + else + return -q; + } +} diff --git a/plug-ins/file-jpeg/jpeg-quality.h b/plug-ins/file-jpeg/jpeg-quality.h new file mode 100644 index 0000000..d20600b --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-quality.h @@ -0,0 +1,26 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * jpeg-quality.h + * Copyright (C) 2007 Raphaël Quinet <raphael@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/>. + */ + +#ifndef __JPEG_QUALITY_H__ +#define __JPEG_QUALITY_H__ + +gint jpeg_detect_quality (struct jpeg_decompress_struct *cinfo); + +#endif /* __JPEG_QUALITY_H__ */ diff --git a/plug-ins/file-jpeg/jpeg-save.c b/plug-ins/file-jpeg/jpeg-save.c new file mode 100644 index 0000000..4af4d1d --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-save.c @@ -0,0 +1,1524 @@ +/* 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 <errno.h> +#include <setjmp.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <glib/gstdio.h> + +#include <jpeglib.h> +#include <jerror.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "libgimp/stdplugins-intl.h" + +#include "jpeg.h" +#include "jpeg-icc.h" +#include "jpeg-load.h" +#include "jpeg-save.h" +#include "jpeg-settings.h" + +#ifdef C_ARITH_CODING_SUPPORTED +static gboolean arithc_supported = TRUE; +#else +static gboolean arithc_supported = FALSE; +#endif + +#define SCALE_WIDTH 125 + +/* See bugs #63610 and #61088 for a discussion about the quality settings */ +#define DEFAULT_IJG_QUALITY 90.0 +#define DEFAULT_SMOOTHING 0.0 +#define DEFAULT_OPTIMIZE TRUE +#define DEFAULT_ARITHMETIC_CODING FALSE +#define DEFAULT_PROGRESSIVE TRUE +#define DEFAULT_BASELINE TRUE +#define DEFAULT_SUBSMP JPEG_SUBSAMPLING_1x1_1x1_1x1 +#define DEFAULT_RESTART 0 +#define DEFAULT_RESTART_MCU_ROWS 16 +#define DEFAULT_DCT 0 +#define DEFAULT_PREVIEW FALSE +#define DEFAULT_EXIF FALSE +#define DEFAULT_XMP FALSE +#define DEFAULT_IPTC FALSE +#define DEFAULT_THUMBNAIL FALSE +#define DEFAULT_PROFILE TRUE +#define DEFAULT_USE_ORIG_QUALITY FALSE + +#define JPEG_DEFAULTS_PARASITE "jpeg-save-defaults" + + +typedef struct +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + gint tile_height; + FILE *outfile; + gboolean has_alpha; + gint rowstride; + guchar *data; + guchar *src; + GeglBuffer *buffer; + const Babl *format; + const gchar *file_name; + gboolean abort_me; + guint source_id; +} PreviewPersistent; + +/*le added : struct containing pointers to export dialog*/ +typedef struct +{ + gboolean run; + GtkWidget *use_restart_markers; /*checkbox setting use restart markers*/ + GtkTextBuffer *text_buffer; + GtkAdjustment *scale_data; /*for restart markers*/ + gulong handler_id_restart; + + GtkAdjustment *quality; /*quality slidebar*/ + GtkAdjustment *smoothing; /*smoothing slidebar*/ + GtkWidget *optimize; /*optimize toggle*/ + GtkWidget *arithmetic_coding; /*arithmetic coding toggle*/ + GtkWidget *progressive; /*progressive toggle*/ + GtkWidget *subsmp; /*subsampling side select*/ + GtkWidget *restart; /*spinner for setting frequency restart markers*/ + GtkWidget *dct; /*DCT side select*/ + GtkWidget *preview; /*show preview toggle checkbox*/ + GtkWidget *save_exif; + GtkWidget *save_xmp; + GtkWidget *save_iptc; + GtkWidget *save_thumbnail; + GtkWidget *save_profile; + GtkWidget *use_orig_quality; /*quant tables toggle*/ +} JpegSaveGui; + +static void make_preview (void); + +static void save_restart_update (GtkAdjustment *adjustment, + GtkWidget *toggle); +static void subsampling_changed (GtkWidget *combo, + GtkAdjustment *entry); +static void quality_changed (GtkAdjustment *scale_entry, + GtkWidget *toggle); +static void subsampling_changed2 (GtkWidget *combo, + GtkWidget *toggle); +static void use_orig_qual_changed (GtkWidget *toggle, + GtkAdjustment *scale_entry); +static void use_orig_qual_changed2 (GtkWidget *toggle, + GtkWidget *combo); + + +static GtkWidget *restart_markers_scale = NULL; +static GtkWidget *restart_markers_label = NULL; +static GtkWidget *preview_size = NULL; +static PreviewPersistent *prev_p = NULL; + +static void save_dialog_response (GtkWidget *widget, + gint response_id, + gpointer data); + +static void load_gui_defaults (JpegSaveGui *pg); +static void save_defaults (void); + + +/* + * sg - This is the best I can do, I'm afraid... I think it will fail + * if something bad really happens (but it might not). If you have a + * better solution, send it ;-) + */ +static void +background_error_exit (j_common_ptr cinfo) +{ + if (prev_p) + prev_p->abort_me = TRUE; + (*cinfo->err->output_message) (cinfo); +} + +static gboolean +background_jpeg_save (PreviewPersistent *pp) +{ + gint yend; + + if (pp->abort_me || (pp->cinfo.next_scanline >= pp->cinfo.image_height)) + { + /* clean up... */ + if (pp->abort_me) + { + jpeg_abort_compress (&(pp->cinfo)); + } + else + { + jpeg_finish_compress (&(pp->cinfo)); + } + + fclose (pp->outfile); + jpeg_destroy_compress (&(pp->cinfo)); + + g_free (pp->data); + + if (pp->buffer) + g_object_unref (pp->buffer); + + /* display the preview stuff */ + if (!pp->abort_me) + { + GFile *file = g_file_new_for_path (pp->file_name); + GFileInfo *info; + gchar *text; + GError *error = NULL; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, &error); + + if (info) + { + goffset size = g_file_info_get_size (info); + gchar *size_text; + + size_text = g_format_size (size); + text = g_strdup_printf (_("File size: %s"), size_text); + g_free (size_text); + + g_object_unref (info); + } + else + { + text = g_strdup_printf (_("File size: %s"), error->message); + g_clear_error (&error); + } + + gtk_label_set_text (GTK_LABEL (preview_size), text); + g_free (text); + + g_object_unref (file); + + /* and load the preview */ + load_image (pp->file_name, GIMP_RUN_NONINTERACTIVE, TRUE, NULL, NULL); + } + + /* we cleanup here (load_image doesn't run in the background) */ + g_unlink (pp->file_name); + + g_free (pp); + prev_p = NULL; + + gimp_displays_flush (); + gdk_flush (); + + return FALSE; + } + else + { + if ((pp->cinfo.next_scanline % pp->tile_height) == 0) + { + yend = pp->cinfo.next_scanline + pp->tile_height; + yend = MIN (yend, pp->cinfo.image_height); + gegl_buffer_get (pp->buffer, + GEGL_RECTANGLE (0, pp->cinfo.next_scanline, + pp->cinfo.image_width, + (yend - pp->cinfo.next_scanline)), + 1.0, + pp->format, + pp->data, + GEGL_AUTO_ROWSTRIDE, + GEGL_ABYSS_NONE); + pp->src = pp->data; + } + + jpeg_write_scanlines (&(pp->cinfo), (JSAMPARRAY) &(pp->src), 1); + pp->src += pp->rowstride; + + return TRUE; + } +} + +gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + gboolean preview, + GError **error) +{ + static struct jpeg_compress_struct cinfo; + static struct my_error_mgr jerr; + + GimpImageType drawable_type; + GeglBuffer *buffer; + const Babl *format; + JpegSubsampling subsampling; + FILE * volatile outfile; + guchar *data; + guchar *src; + GimpColorProfile *profile = NULL; + + gboolean has_alpha; + gboolean out_linear = FALSE; + gint rowstride, yend; + + drawable_type = gimp_drawable_type (drawable_ID); + buffer = gimp_drawable_get_buffer (drawable_ID); + + if (! preview) + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_filename_to_utf8 (filename)); + + /* Step 1: allocate and initialize JPEG compression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = my_error_exit; + + outfile = NULL; + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp (jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_compress (&cinfo); + if (outfile) + fclose (outfile); + if (buffer) + g_object_unref (buffer); + + return FALSE; + } + + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress (&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + + /* Here we use the library-supplied code to send compressed data to a + * stdio stream. You can also write your own code to do something else. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to write binary files. + */ + if ((outfile = 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; + } + + /* When we don't save profiles, we convert data to sRGB because + * that's what most/all readers expect on a no-profile JPEG. + * If we save an assigned profile, let's just follow its TRC. + * If we save the default linear profile (i.e. no assigned + * profile), we convert it to sRGB, except when it is 8-bit linear. + */ + if (jsvals.save_profile) + { + profile = gimp_image_get_color_profile (orig_image_ID); + + /* If a profile is explicitly set, follow its TRC, whatever the + * storage format. + */ + if (profile && gimp_color_profile_is_linear (profile)) + out_linear = TRUE; + + if (! profile) + { + /* There is always an effective profile. */ + profile = gimp_image_get_effective_color_profile (orig_image_ID); + + if (gimp_color_profile_is_linear (profile)) + { + if (gimp_image_get_precision (image_ID) != GIMP_PRECISION_U8_LINEAR) + { + GimpColorProfile *saved_profile; + + saved_profile = gimp_color_profile_new_srgb_trc_from_color_profile (profile); + g_object_unref (profile); + profile = saved_profile; + } + else + { + /* Keep linear profile as-is for 8-bit linear image. */ + out_linear = TRUE; + } + } + } + } + + jpeg_stdio_dest (&cinfo, outfile); + + /* Get the input image and a pointer to its data. + */ + switch (drawable_type) + { + case GIMP_RGB_IMAGE: + /* # of color components per pixel */ + cinfo.input_components = 3; + has_alpha = FALSE; + + if (out_linear) + format = babl_format ("RGB u8"); + else + format = babl_format ("R'G'B' u8"); + break; + + case GIMP_GRAY_IMAGE: + /* # of color components per pixel */ + cinfo.input_components = 1; + has_alpha = FALSE; + + if (out_linear) + format = babl_format ("Y u8"); + else + format = babl_format ("Y' u8"); + break; + + case GIMP_RGBA_IMAGE: + /* # of color components per pixel (minus the GIMP alpha channel) */ + cinfo.input_components = 4 - 1; + has_alpha = TRUE; + + if (out_linear) + format = babl_format ("RGB u8"); + else + format = babl_format ("R'G'B' u8"); + break; + + case GIMP_GRAYA_IMAGE: + /* # of color components per pixel (minus the GIMP alpha channel) */ + cinfo.input_components = 2 - 1; + has_alpha = TRUE; + if (out_linear) + format = babl_format ("Y u8"); + else + format = babl_format ("Y' u8"); + break; + + case GIMP_INDEXED_IMAGE: + default: + return FALSE; + } + + /* Step 3: set parameters for compression */ + + /* First we supply a description of the input image. + * Four fields of the cinfo struct must be filled in: + */ + /* image width and height, in pixels */ + cinfo.image_width = gegl_buffer_get_width (buffer); + cinfo.image_height = gegl_buffer_get_height (buffer); + /* colorspace of input image */ + cinfo.in_color_space = (drawable_type == GIMP_RGB_IMAGE || + drawable_type == GIMP_RGBA_IMAGE) + ? JCS_RGB : JCS_GRAYSCALE; + /* Now use the library's routine to set default compression parameters. + * (You must set at least cinfo.in_color_space before calling this, + * since the defaults depend on the source color space.) + */ + jpeg_set_defaults (&cinfo); + + jpeg_set_quality (&cinfo, (gint) (jsvals.quality + 0.5), jsvals.baseline); + + if (jsvals.use_orig_quality && num_quant_tables > 0) + { + guint **quant_tables; + gint t; + + /* override tables generated by jpeg_set_quality() with custom tables */ + quant_tables = jpeg_restore_original_tables (image_ID, num_quant_tables); + if (quant_tables) + { + for (t = 0; t < num_quant_tables; t++) + { + jpeg_add_quant_table (&cinfo, t, quant_tables[t], + 100, jsvals.baseline); + g_free (quant_tables[t]); + } + g_free (quant_tables); + } + } + + if (arithc_supported) + { + cinfo.arith_code = jsvals.arithmetic_coding; + if (!jsvals.arithmetic_coding) + cinfo.optimize_coding = jsvals.optimize; + } + else + cinfo.optimize_coding = jsvals.optimize; + + subsampling = (gimp_drawable_is_rgb (drawable_ID) ? + jsvals.subsmp : JPEG_SUBSAMPLING_1x1_1x1_1x1); + + /* smoothing is not supported with nonstandard sampling ratios */ + if (subsampling != JPEG_SUBSAMPLING_2x1_1x1_1x1 && + subsampling != JPEG_SUBSAMPLING_1x2_1x1_1x1) + { + cinfo.smoothing_factor = (gint) (jsvals.smoothing * 100); + } + + if (jsvals.progressive) + { + jpeg_simple_progression (&cinfo); + } + + switch (subsampling) + { + case JPEG_SUBSAMPLING_2x2_1x1_1x1: + default: + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + + case JPEG_SUBSAMPLING_2x1_1x1_1x1: + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + + case JPEG_SUBSAMPLING_1x1_1x1_1x1: + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + + case JPEG_SUBSAMPLING_1x2_1x1_1x1: + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + } + + cinfo.restart_interval = 0; + cinfo.restart_in_rows = jsvals.restart; + + switch (jsvals.dct) + { + case 0: + default: + cinfo.dct_method = JDCT_ISLOW; + break; + + case 1: + cinfo.dct_method = JDCT_IFAST; + break; + + case 2: + cinfo.dct_method = JDCT_FLOAT; + break; + } + + { + gdouble xresolution; + gdouble yresolution; + + gimp_image_get_resolution (orig_image_ID, &xresolution, &yresolution); + + if (xresolution > 1e-5 && yresolution > 1e-5) + { + gdouble factor; + + factor = gimp_unit_get_factor (gimp_image_get_unit (orig_image_ID)); + + if (factor == 2.54 /* cm */ || + factor == 25.4 /* mm */) + { + cinfo.density_unit = 2; /* dots per cm */ + + xresolution /= 2.54; + yresolution /= 2.54; + } + else + { + cinfo.density_unit = 1; /* dots per inch */ + } + + cinfo.X_density = xresolution; + cinfo.Y_density = yresolution; + } + } + + /* Step 4: Start compressor */ + + /* TRUE ensures that we will write a complete interchange-JPEG file. + * Pass TRUE unless you are very sure of what you're doing. + */ + jpeg_start_compress (&cinfo, TRUE); + + /* Step 4.1: Write the comment out - pw */ + if (image_comment && *image_comment) + { +#ifdef GIMP_UNSTABLE + g_print ("jpeg-save: saving image comment (%d bytes)\n", + (int) strlen (image_comment)); +#endif + jpeg_write_marker (&cinfo, JPEG_COM, + (guchar *) image_comment, strlen (image_comment)); + } + + /* Step 4.2: store the color profile */ + if (jsvals.save_profile) + { + const guint8 *icc_data; + gsize icc_length; + + icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length); + jpeg_icc_write_profile (&cinfo, icc_data, icc_length); + + g_object_unref (profile); + } + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + + /* Here we use the library's state variable cinfo.next_scanline as the + * loop counter, so that we don't have to keep track ourselves. + * To keep things simple, we pass one scanline per call; you can pass + * more if you wish, though. + */ + /* JSAMPLEs per row in image_buffer */ + rowstride = cinfo.input_components * cinfo.image_width; + data = g_new (guchar, rowstride * gimp_tile_height ()); + + /* fault if cinfo.next_scanline isn't initially a multiple of + * gimp_tile_height */ + src = NULL; + + /* + * sg - if we preview, we want this to happen in the background -- do + * not duplicate code in the future; for now, it's OK + */ + + if (preview) + { + PreviewPersistent *pp = g_new (PreviewPersistent, 1); + + /* pass all the information we need */ + pp->cinfo = cinfo; + pp->tile_height = gimp_tile_height(); + pp->data = data; + pp->outfile = outfile; + pp->has_alpha = has_alpha; + pp->rowstride = rowstride; + pp->data = data; + pp->buffer = buffer; + pp->format = format; + pp->src = NULL; + pp->file_name = filename; + pp->abort_me = FALSE; + + g_warn_if_fail (prev_p == NULL); + prev_p = pp; + + pp->cinfo.err = jpeg_std_error(&(pp->jerr)); + pp->jerr.error_exit = background_error_exit; + + gtk_label_set_text (GTK_LABEL (preview_size), + _("Calculating file size...")); + + pp->source_id = g_idle_add ((GSourceFunc) background_jpeg_save, pp); + + /* background_jpeg_save() will cleanup as needed */ + return TRUE; + } + + while (cinfo.next_scanline < cinfo.image_height) + { + if ((cinfo.next_scanline % gimp_tile_height ()) == 0) + { + yend = cinfo.next_scanline + gimp_tile_height (); + yend = MIN (yend, cinfo.image_height); + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, cinfo.next_scanline, + cinfo.image_width, + (yend - cinfo.next_scanline)), + 1.0, + format, + data, + GEGL_AUTO_ROWSTRIDE, + GEGL_ABYSS_NONE); + src = data; + } + + jpeg_write_scanlines (&cinfo, (JSAMPARRAY) &src, 1); + src += rowstride; + + if ((cinfo.next_scanline % 32) == 0) + gimp_progress_update ((gdouble) cinfo.next_scanline / + (gdouble) cinfo.image_height); + } + + /* Step 6: Finish compression */ + jpeg_finish_compress (&cinfo); + /* After finish_compress, we can close the output file. */ + fclose (outfile); + + /* Step 7: release JPEG compression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_compress (&cinfo); + + /* free the temporary buffer */ + g_free (data); + + /* And we're done! */ + gimp_progress_update (1.0); + + g_object_unref (buffer); + + return TRUE; +} + +static void +make_preview (void) +{ + destroy_preview (); + + if (jsvals.preview) + { + gchar *tn = gimp_temp_name ("jpeg"); + + if (! undo_touched) + { + /* we freeze undo saving so that we can avoid sucking up + * tile cache with our unneeded preview steps. */ + gimp_image_undo_freeze (preview_image_ID); + + undo_touched = TRUE; + } + + save_image (tn, + preview_image_ID, + drawable_ID_global, + orig_image_ID_global, + TRUE, NULL); + + if (display_ID == -1) + display_ID = gimp_display_new (preview_image_ID); + } + else + { + gtk_label_set_text (GTK_LABEL (preview_size), _("File size: unknown")); + + gimp_displays_flush (); + } +} + +void +destroy_preview (void) +{ + if (prev_p && !prev_p->abort_me) + { + guint id = prev_p->source_id; + prev_p->abort_me = TRUE; /* signal the background save to stop */ + background_jpeg_save (prev_p); + g_source_remove (id); + } + + if (gimp_image_is_valid (preview_image_ID) && + gimp_item_is_valid (preview_layer_ID)) + { + /* assuming that reference counting is working correctly, + we do not need to delete the layer, removing it from + the image should be sufficient */ + gimp_image_remove_layer (preview_image_ID, preview_layer_ID); + + preview_layer_ID = -1; + } +} + +static void +toggle_arithmetic_coding (GtkToggleButton *togglebutton, + gpointer user_data) +{ + GtkWidget *optimize = GTK_WIDGET (user_data); + + gtk_widget_set_sensitive (optimize, + !gtk_toggle_button_get_active (togglebutton)); +} + +gboolean +save_dialog (void) +{ + JpegSaveGui pg; + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *vbox2; + GtkAdjustment *entry; + GtkWidget *table; + GtkWidget *table2; + GtkWidget *tabledefaults; + GtkWidget *expander; + GtkWidget *frame; + GtkWidget *toggle; + GtkWidget *spinbutton; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *text_view; + GtkTextBuffer *text_buffer; + GtkWidget *scrolled_window; + GtkWidget *button; + gchar *text; + gint row; + + dialog = gimp_export_dialog_new (_("JPEG"), 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); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + 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); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0); + gtk_widget_show (vbox2); + + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + pg.quality = entry = (GtkAdjustment *) + gimp_scale_entry_new (GTK_TABLE (table), 0, 0, + _("_Quality:"), + SCALE_WIDTH, 0, jsvals.quality, + 0.0, 100.0, 1.0, 10.0, 0, + TRUE, 0.0, 0.0, + _("JPEG quality parameter"), + "file-jpeg-save-quality"); + + g_signal_connect (entry, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &jsvals.quality); + g_signal_connect (entry, "value-changed", + G_CALLBACK (make_preview), + NULL); + + /* custom quantization tables - now used also for original quality */ + pg.use_orig_quality = toggle = + gtk_check_button_new_with_mnemonic (_("_Use quality settings from original " + "image")); + gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, + _("If the original image was loaded from a JPEG " + "file using non-standard quality settings " + "(quantization tables), enable this option to " + "get almost the same quality and file size."), + NULL); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.use_orig_quality); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + jsvals.use_orig_quality + && (orig_quality > 0) + && (orig_subsmp == jsvals.subsmp) + ); + gtk_widget_set_sensitive (toggle, (orig_quality > 0)); + + /* changing quality disables custom quantization tables, and vice-versa */ + g_signal_connect (pg.quality, "value-changed", + G_CALLBACK (quality_changed), + pg.use_orig_quality); + g_signal_connect (pg.use_orig_quality, "toggled", + G_CALLBACK (use_orig_qual_changed), + pg.quality); + + /* File size */ + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0); + gtk_widget_show (vbox2); + + preview_size = gtk_label_new (_("File size: unknown")); + gtk_label_set_xalign (GTK_LABEL (preview_size), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (preview_size), PANGO_ELLIPSIZE_END); + gimp_label_set_attributes (GTK_LABEL (preview_size), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (vbox2), preview_size, FALSE, FALSE, 0); + gtk_widget_show (preview_size); + + gimp_help_set_help_data (preview_size, + _("Enable preview to obtain the file size."), NULL); + + pg.preview = toggle = + gtk_check_button_new_with_mnemonic (_("Sho_w preview in image window")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.preview); + gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.preview); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0); + gtk_widget_show (vbox2); + + /* Save EXIF data */ + pg.save_exif = toggle = + gtk_check_button_new_with_mnemonic (_("Save _Exif data")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_exif); + gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.save_exif); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + /* Save XMP metadata */ + pg.save_xmp = toggle = + gtk_check_button_new_with_mnemonic (_("Save _XMP data")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_xmp); + gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.save_xmp); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + /* Save IPTC metadata */ + pg.save_iptc = toggle = + gtk_check_button_new_with_mnemonic (_("Save _IPTC data")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_iptc); + gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.save_iptc); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + /* Save thumbnail */ + pg.save_thumbnail = toggle = + gtk_check_button_new_with_mnemonic (_("Save _thumbnail")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_thumbnail); + gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.save_thumbnail); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + /* Save color profile */ + pg.save_profile = toggle = + gtk_check_button_new_with_mnemonic (_("Save color _profile")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_profile); + gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.save_profile); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + /* Comment */ + frame = gimp_frame_new (_("Comment")); + gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + 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_widget_set_size_request (scrolled_window, 250, 50); + gtk_container_add (GTK_CONTAINER (frame), scrolled_window); + gtk_widget_show (scrolled_window); + + pg.text_buffer = text_buffer = gtk_text_buffer_new (NULL); + if (image_comment) + gtk_text_buffer_set_text (text_buffer, image_comment, -1); + + text_view = gtk_text_view_new_with_buffer (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); + + g_object_unref (text_buffer); + + /* Advanced expander */ + text = g_strdup_printf ("<b>%s</b>", _("_Advanced Options")); + expander = gtk_expander_new_with_mnemonic (text); + gtk_expander_set_use_markup (GTK_EXPANDER (expander), TRUE); + g_free (text); + + gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0); + gtk_widget_show (expander); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add (GTK_CONTAINER (expander), vbox); + gtk_widget_show (vbox); + + frame = gimp_frame_new ("<expander>"); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (4, 8, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_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); + + table2 = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table2), 6); + gtk_table_attach (GTK_TABLE (table), table2, + 2, 6, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (table2); + + pg.smoothing = entry = (GtkAdjustment *) + gimp_scale_entry_new (GTK_TABLE (table2), 0, 0, + _("S_moothing:"), + 100, 0, jsvals.smoothing, + 0.0, 1.0, 0.01, 0.1, 2, + TRUE, 0.0, 0.0, + NULL, + "file-jpeg-save-smoothing"); + g_signal_connect (entry, "value-changed", + G_CALLBACK (gimp_double_adjustment_update), + &jsvals.smoothing); + g_signal_connect (entry, "value-changed", + G_CALLBACK (make_preview), + NULL); + + restart_markers_label = gtk_label_new (_("Interval (MCU rows):")); + gtk_label_set_xalign (GTK_LABEL (restart_markers_label), 1.0); + gtk_table_attach (GTK_TABLE (table), restart_markers_label, 4, 5, 1, 2, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (restart_markers_label); + + pg.scale_data = (GtkAdjustment *) + gtk_adjustment_new (((jsvals.restart == 0) ? + DEFAULT_RESTART_MCU_ROWS : jsvals.restart), + 1.0, 64.0, 1.0, 1.0, 0); + pg.restart = restart_markers_scale = spinbutton = + gimp_spin_button_new (pg.scale_data, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_table_attach (GTK_TABLE (table), spinbutton, 5, 6, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (spinbutton); + + pg.use_restart_markers = toggle = + gtk_check_button_new_with_mnemonic (_("Use _restart markers")); + gtk_table_attach (GTK_TABLE (table), toggle, 2, 4, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show (toggle); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.restart); + + gtk_widget_set_sensitive (restart_markers_label, jsvals.restart); + gtk_widget_set_sensitive (restart_markers_scale, jsvals.restart); + + g_signal_connect (pg.scale_data, "value-changed", + G_CALLBACK (save_restart_update), + toggle); + pg.handler_id_restart = g_signal_connect_swapped (toggle, "toggled", + G_CALLBACK (save_restart_update), + pg.scale_data); + + row = 0; + + /* Optimize */ + pg.optimize = toggle = gtk_check_button_new_with_mnemonic (_("_Optimize")); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, + row, row + 1, GTK_FILL, 0, 0, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.optimize); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.optimize); + + if (arithc_supported) + gtk_widget_set_sensitive (toggle, !jsvals.arithmetic_coding); + + row++; + + if (arithc_supported) + { + /* Arithmetic coding */ + pg.arithmetic_coding = toggle = gtk_check_button_new_with_mnemonic + (_("Use arithmetic _coding")); + gtk_widget_set_tooltip_text + (toggle, _("Older software may have trouble opening " + "arithmetic-coded images")); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, + row, row + 1, GTK_FILL, 0, 0, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.arithmetic_coding); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + g_signal_connect (toggle, "toggled", + G_CALLBACK (toggle_arithmetic_coding), + pg.optimize); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + jsvals.arithmetic_coding); + + row++; + } + + /* Progressive */ + pg.progressive = toggle = + gtk_check_button_new_with_mnemonic (_("_Progressive")); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, + row, row + 1, GTK_FILL, 0, 0, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.progressive); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + jsvals.progressive); + + row++; + + /* Subsampling */ + label = gtk_label_new_with_mnemonic (_("Su_bsampling:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + pg.subsmp = + combo = gimp_int_combo_box_new (_("4:4:4 (best quality)"), + JPEG_SUBSAMPLING_1x1_1x1_1x1, + _("4:2:2 horizontal (chroma halved)"), + JPEG_SUBSAMPLING_2x1_1x1_1x1, + _("4:2:2 vertical (chroma halved)"), + JPEG_SUBSAMPLING_1x2_1x1_1x1, + _("4:2:0 (chroma quartered)"), + JPEG_SUBSAMPLING_2x2_1x1_1x1, + NULL); + gtk_table_attach (GTK_TABLE (table), combo, 3, 6, 2, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + if (gimp_drawable_is_rgb (drawable_ID_global)) + { + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + jsvals.subsmp, + G_CALLBACK (subsampling_changed), + entry); + g_signal_connect (pg.subsmp, "changed", + G_CALLBACK (subsampling_changed2), + pg.use_orig_quality); + g_signal_connect (pg.use_orig_quality, "toggled", + G_CALLBACK (use_orig_qual_changed2), + pg.subsmp); + } + else + { + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + JPEG_SUBSAMPLING_1x1_1x1_1x1); + + gtk_widget_set_sensitive (combo, FALSE); + } + + + /* DCT method */ + label = gtk_label_new_with_mnemonic (_("_DCT method:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + pg.dct = combo = gimp_int_combo_box_new (_("Fast Integer"), 1, + _("Integer"), 0, + _("Floating-Point"), 2, + NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), jsvals.dct); + gtk_table_attach (GTK_TABLE (table), combo, 3, 6, 3, 4, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_int_combo_box_get_active), + &jsvals.dct); + g_signal_connect (combo, "changed", + G_CALLBACK (make_preview), + NULL); + + /* Load/Save defaults */ + tabledefaults = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (tabledefaults), 6); + gtk_container_set_border_width (GTK_CONTAINER (tabledefaults), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + tabledefaults, FALSE, FALSE, 0); + gtk_widget_show (tabledefaults); + + button = gtk_button_new_with_mnemonic (_("_Load Defaults")); + gtk_table_attach (GTK_TABLE (tabledefaults), + button, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show (button); + + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (load_gui_defaults), + &pg); + + button = gtk_button_new_with_mnemonic (_("Sa_ve Defaults")); + gtk_table_attach (GTK_TABLE (tabledefaults), + button, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show (button); + + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (save_defaults), + &pg); + + gtk_widget_show (dialog); + + make_preview (); + + pg.run = FALSE; + + gtk_main (); + + destroy_preview (); + + return pg.run; +} + +static void +save_dialog_response (GtkWidget *widget, + gint response_id, + gpointer data) +{ + JpegSaveGui *pg = data; + GtkTextIter start_iter; + GtkTextIter end_iter; + + switch (response_id) + { + case GTK_RESPONSE_OK: + gtk_text_buffer_get_bounds (pg->text_buffer, &start_iter, &end_iter); + image_comment = gtk_text_buffer_get_text (pg->text_buffer, + &start_iter, &end_iter, FALSE); + pg->run = TRUE; + /* fallthrough */ + + default: + gtk_widget_destroy (widget); + break; + } +} + +void +load_defaults (void) +{ + jsvals.quality = DEFAULT_IJG_QUALITY; + jsvals.smoothing = DEFAULT_SMOOTHING; + jsvals.optimize = DEFAULT_OPTIMIZE; + jsvals.arithmetic_coding= DEFAULT_ARITHMETIC_CODING; + jsvals.progressive = DEFAULT_PROGRESSIVE; + jsvals.baseline = DEFAULT_BASELINE; + jsvals.subsmp = DEFAULT_SUBSMP; + jsvals.restart = DEFAULT_RESTART; + jsvals.dct = DEFAULT_DCT; + jsvals.preview = DEFAULT_PREVIEW; + jsvals.save_exif = DEFAULT_EXIF; + jsvals.save_xmp = DEFAULT_XMP; + jsvals.save_iptc = DEFAULT_IPTC; + jsvals.save_thumbnail = DEFAULT_THUMBNAIL; + jsvals.save_profile = DEFAULT_PROFILE; + jsvals.use_orig_quality = DEFAULT_USE_ORIG_QUALITY; +} + +void +load_parasite (void) +{ + GimpParasite *parasite; + gchar *def_str; + JpegSaveVals tmpvals; + gint num_fields; + gint subsampling; + + parasite = gimp_get_parasite (JPEG_DEFAULTS_PARASITE); + + if (! parasite) + return; + + def_str = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + + gimp_parasite_free (parasite); + + /* Initialize tmpvals in case fewer fields exist in the parasite + (e.g., when importing from a previous version of GIMP). */ + memcpy(&tmpvals, &jsvals, sizeof jsvals); + + num_fields = sscanf (def_str, + "%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %d %d", + &tmpvals.quality, + &tmpvals.smoothing, + &tmpvals.optimize, + &tmpvals.progressive, + &subsampling, + &tmpvals.baseline, + &tmpvals.restart, + &tmpvals.dct, + &tmpvals.preview, + &tmpvals.save_exif, + &tmpvals.save_thumbnail, + &tmpvals.save_xmp, + &tmpvals.use_orig_quality, + &tmpvals.save_iptc, + &tmpvals.arithmetic_coding, + &tmpvals.save_profile); + + tmpvals.subsmp = subsampling; + + if (num_fields == 13 || num_fields == 15 || num_fields == 16) + { + memcpy (&jsvals, &tmpvals, sizeof (tmpvals)); + } + + g_free (def_str); +} + +static void +save_defaults (void) +{ + GimpParasite *parasite; + gchar *def_str; + + def_str = g_strdup_printf ("%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %d %d", + jsvals.quality, + jsvals.smoothing, + jsvals.optimize, + jsvals.progressive, + (gint) jsvals.subsmp, + jsvals.baseline, + jsvals.restart, + jsvals.dct, + jsvals.preview, + jsvals.save_exif, + jsvals.save_thumbnail, + jsvals.save_xmp, + jsvals.use_orig_quality, + jsvals.save_iptc, + jsvals.arithmetic_coding, + jsvals.save_profile); + parasite = gimp_parasite_new (JPEG_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 (JpegSaveGui *pg) +{ + GtkAdjustment *restart_markers; + + load_defaults (); + load_parasite (); + +#define SET_ACTIVE_BTTN(field) \ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (pg->field), jsvals.field) + + SET_ACTIVE_BTTN (optimize); + SET_ACTIVE_BTTN (progressive); + SET_ACTIVE_BTTN (use_orig_quality); + SET_ACTIVE_BTTN (preview); + SET_ACTIVE_BTTN (save_exif); + SET_ACTIVE_BTTN (save_xmp); + SET_ACTIVE_BTTN (save_iptc); + SET_ACTIVE_BTTN (save_thumbnail); + SET_ACTIVE_BTTN (save_profile); + +#undef SET_ACTIVE_BTTN + +/*spin button stuff*/ + g_signal_handler_block (pg->use_restart_markers, pg->handler_id_restart); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (pg->use_restart_markers), + jsvals.restart); + restart_markers = GTK_ADJUSTMENT (pg->scale_data); + gtk_adjustment_set_value (restart_markers, jsvals.restart); + g_signal_handler_unblock (pg->use_restart_markers, pg->handler_id_restart); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (pg->smoothing), + jsvals.smoothing); + + /* Don't override quality and subsampling setting if we already set it from original */ + if (!jsvals.use_orig_quality) + { + gtk_adjustment_set_value (GTK_ADJUSTMENT (pg->quality), + jsvals.quality); + + if (gimp_drawable_is_rgb (drawable_ID_global)) + { + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (pg->subsmp), + jsvals.subsmp); + } + } + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (pg->dct), + jsvals.dct); +} + +static void +save_restart_update (GtkAdjustment *adjustment, + GtkWidget *toggle) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle))) + jsvals.restart = gtk_adjustment_get_value (adjustment); + else + jsvals.restart = 0; + + gtk_widget_set_sensitive (restart_markers_label, jsvals.restart); + gtk_widget_set_sensitive (restart_markers_scale, jsvals.restart); + + make_preview (); +} + +static void +subsampling_changed (GtkWidget *combo, + GtkAdjustment *entry) +{ + gint value; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &value); + + jsvals.subsmp = value; + + /* smoothing is not supported with nonstandard sampling ratios */ + gimp_scale_entry_set_sensitive ((gpointer) entry, + jsvals.subsmp != JPEG_SUBSAMPLING_2x1_1x1_1x1 && + jsvals.subsmp != JPEG_SUBSAMPLING_1x2_1x1_1x1); + + make_preview (); +} + +static void +quality_changed (GtkAdjustment *scale_entry, + GtkWidget *toggle) +{ + if (jsvals.use_orig_quality) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE); + } +} + +static void +subsampling_changed2 (GtkWidget *combo, + GtkWidget *toggle) +{ + if (jsvals.use_orig_quality && orig_subsmp != jsvals.subsmp) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE); + } +} + +static void +use_orig_qual_changed (GtkWidget *toggle, + GtkAdjustment *scale_entry) +{ + if (jsvals.use_orig_quality && orig_quality > 0) + { + g_signal_handlers_block_by_func (scale_entry, quality_changed, toggle); + gtk_adjustment_set_value (scale_entry, orig_quality); + g_signal_handlers_unblock_by_func (scale_entry, quality_changed, toggle); + } +} + +static void +use_orig_qual_changed2 (GtkWidget *toggle, + GtkWidget *combo) +{ + /* the test is (orig_quality > 0), not (orig_subsmp > 0) - this is normal */ + if (jsvals.use_orig_quality && orig_quality > 0) + { + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), orig_subsmp); + } +} diff --git a/plug-ins/file-jpeg/jpeg-save.h b/plug-ins/file-jpeg/jpeg-save.h new file mode 100644 index 0000000..34f0eb5 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-save.h @@ -0,0 +1,57 @@ +/* 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/>. + */ + +#ifndef __JPEG_SAVE_H__ +#define __JPEG_SAVE_H__ + +typedef struct +{ + gdouble quality; + gdouble smoothing; + gboolean optimize; + gboolean arithmetic_coding; + gboolean progressive; + gboolean baseline; + JpegSubsampling subsmp; + gint restart; + gint dct; + gboolean preview; + gboolean save_exif; + gboolean save_xmp; + gboolean save_iptc; + gboolean save_thumbnail; + gboolean save_profile; + gboolean use_orig_quality; +} JpegSaveVals; + +extern JpegSaveVals jsvals; + +extern gint32 orig_image_ID_global; +extern gint32 drawable_ID_global; + + +gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 orig_image_ID, + gboolean preview, + GError **error); +gboolean save_dialog (void); +void load_defaults (void); +void load_parasite (void); + +#endif /* __JPEG_SAVE_H__ */ diff --git a/plug-ins/file-jpeg/jpeg-settings.c b/plug-ins/file-jpeg/jpeg-settings.c new file mode 100644 index 0000000..df6ace2 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-settings.c @@ -0,0 +1,397 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * jpeg-settings.c + * Copyright (C) 2007 Raphaël Quinet <raphael@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/>. + */ + +/* + * Structure of the "jpeg-settings" parasite: + * 1 byte - JPEG color space (JCS_YCbCr, JCS_GRAYSCALE, JCS_CMYK, ...) + * 1 byte - quality (1..100 according to the IJG scale, or 0) + * 1 byte - number of components (0..4) + * 1 byte - number of quantization tables (0..4) + * C * 2 bytes - sampling factors for each component (1..4) + * T * 128 bytes - quantization tables (only if different from IJG tables) + * + * Additional data following the quantization tables is currently + * ignored and can be used for future extensions. + * + * In order to improve the compatibility with future versions of the + * plug-in that may support more subsampling types ("subsmp"), the + * parasite contains the original subsampling for each component + * instead of saving only one byte containing the subsampling type as + * used by the jpeg plug-in. The same applies to the other settings: + * for example, up to 4 quantization tables will be saved in the + * parasite even if the current code cannot restore more than 2 of + * them (4 tables may be needed by unusual JPEG color spaces such as + * JCS_CMYK or JCS_YCCK). + */ + +#include "config.h" + +#include <string.h> +#include <setjmp.h> + +#include <glib/gstdio.h> + +#include <jpeglib.h> + +#include <libgimp/gimp.h> + +#include "libgimp/stdplugins-intl.h" + +#include "jpeg.h" +#include "jpeg-quality.h" +#include "jpeg-settings.h" + +/** + * jpeg_detect_original_settings: + * @cinfo: a pointer to a JPEG decompressor info. + * @image_ID: the image to which the parasite should be attached. + * + * Analyze the image being decompressed (@cinfo) and extract the + * sampling factors, quantization tables and overall image quality. + * Store this information in a parasite and attach it to @image_ID. + * + * This function must be called after jpeg_read_header() so that + * @cinfo contains the quantization tables and the sampling factors + * for each component. + * + * Return Value: TRUE if a parasite has been attached to @image_ID. + */ +gboolean +jpeg_detect_original_settings (struct jpeg_decompress_struct *cinfo, + gint32 image_ID) +{ + guint parasite_size; + guchar *parasite_data; + GimpParasite *parasite; + guchar *dest; + gint quality; + gint num_quant_tables = 0; + gint t; + gint i; + + g_return_val_if_fail (cinfo != NULL, FALSE); + if (cinfo->jpeg_color_space == JCS_UNKNOWN + || cinfo->out_color_space == JCS_UNKNOWN) + return FALSE; + + quality = jpeg_detect_quality (cinfo); + /* no need to attach quantization tables if they are the ones from IJG */ + if (quality <= 0) + { + for (t = 0; t < 4; t++) + if (cinfo->quant_tbl_ptrs[t]) + num_quant_tables++; + } + + parasite_size = 4 + cinfo->num_components * 2 + num_quant_tables * 128; + parasite_data = g_new (guchar, parasite_size); + dest = parasite_data; + + *dest++ = CLAMP0255 (cinfo->jpeg_color_space); + *dest++ = ABS (quality); + *dest++ = CLAMP0255 (cinfo->num_components); + *dest++ = num_quant_tables; + + for (i = 0; i < cinfo->num_components; i++) + { + *dest++ = CLAMP0255 (cinfo->comp_info[i].h_samp_factor); + *dest++ = CLAMP0255 (cinfo->comp_info[i].v_samp_factor); + } + + if (quality <= 0) + { + for (t = 0; t < 4; t++) + if (cinfo->quant_tbl_ptrs[t]) + for (i = 0; i < DCTSIZE2; i++) + { + guint16 c = cinfo->quant_tbl_ptrs[t]->quantval[i]; + *dest++ = c / 256; + *dest++ = c & 255; + } + } + + parasite = gimp_parasite_new ("jpeg-settings", + GIMP_PARASITE_PERSISTENT, + parasite_size, + parasite_data); + g_free (parasite_data); + gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + return TRUE; +} + + +/* + * TODO: compare the JPEG color space found in the parasite with the + * GIMP color space of the drawable to be saved. If one of them is + * grayscale and the other isn't, then the quality setting may be used + * but the subsampling parameters and quantization tables should be + * ignored. The drawable_ID needs to be passed around because the + * color space of the drawable may be different from that of the image + * (e.g., when saving a mask or channel). + */ + +/** + * jpeg_restore_original_settings: + * @image_ID: the image that may contain original jpeg settings in a parasite. + * @quality: where to store the original jpeg quality. + * @subsmp: where to store the original subsampling type. + * @num_quant_tables: where to store the number of quantization tables found. + * + * Retrieve the original JPEG settings (quality, type of subsampling + * and number of quantization tables) from the parasite attached to + * @image_ID. If the number of quantization tables is greater than + * zero, then these tables can be retrieved from the parasite by + * calling jpeg_restore_original_tables(). + * + * Return Value: TRUE if a valid parasite was attached to the image + */ +gboolean +jpeg_restore_original_settings (gint32 image_ID, + gint *quality, + JpegSubsampling *subsmp, + gint *num_quant_tables) +{ + GimpParasite *parasite; + const guchar *src; + glong src_size; + gint color_space; + gint q; + gint num_components; + gint num_tables; + guchar h[3]; + guchar v[3]; + + g_return_val_if_fail (quality != NULL, FALSE); + g_return_val_if_fail (subsmp != NULL, FALSE); + g_return_val_if_fail (num_quant_tables != NULL, FALSE); + + parasite = gimp_image_get_parasite (image_ID, "jpeg-settings"); + if (parasite) + { + src = gimp_parasite_data (parasite); + src_size = gimp_parasite_data_size (parasite); + if (src_size >= 4) + { + color_space = *src++; + q = *src++; + num_components = *src++; + num_tables = *src++; + + if (src_size >= (4 + num_components * 2 + num_tables * 128) + && q <= 100 && num_tables <= 4) + { + *quality = q; + + /* the current plug-in can only create grayscale or YCbCr JPEGs */ + if (color_space == JCS_GRAYSCALE || color_space == JCS_YCbCr) + *num_quant_tables = num_tables; + else + *num_quant_tables = -1; + + /* the current plug-in can only use subsampling for YCbCr (3) */ + *subsmp = -1; + if (num_components == 3) + { + h[0] = *src++; + v[0] = *src++; + h[1] = *src++; + v[1] = *src++; + h[2] = *src++; + v[2] = *src++; + + if (h[1] == 1 && v[1] == 1 && h[2] == 1 && v[2] == 1) + { + if (h[0] == 1 && v[0] == 1) + *subsmp = JPEG_SUBSAMPLING_1x1_1x1_1x1; + else if (h[0] == 2 && v[0] == 1) + *subsmp = JPEG_SUBSAMPLING_2x1_1x1_1x1; + else if (h[0] == 1 && v[0] == 2) + *subsmp = JPEG_SUBSAMPLING_1x2_1x1_1x1; + else if (h[0] == 2 && v[0] == 2) + *subsmp = JPEG_SUBSAMPLING_2x2_1x1_1x1; + } + } + + gimp_parasite_free (parasite); + return TRUE; + } + } + + gimp_parasite_free (parasite); + } + + *quality = -1; + *subsmp = -1; + *num_quant_tables = 0; + + return FALSE; +} + + +/** + * jpeg_restore_original_tables: + * @image_ID: the image that may contain original jpeg settings in a parasite. + * @num_quant_tables: the number of quantization tables to restore. + * + * Retrieve the original quantization tables from the parasite + * attached to @image_ID. Each table is an array of coefficients that + * can be associated with a component of a JPEG image when saving it. + * + * An array of newly allocated tables is returned if @num_quant_tables + * matches the number of tables saved in the parasite. These tables + * are returned as arrays of unsigned integers even if they will never + * use more than 16 bits (8 bits in most cases) because the IJG JPEG + * library expects arrays of unsigned integers. When these tables are + * not needed anymore, the caller should free them using g_free(). If + * no parasite exists or if it cannot be used, this function returns + * NULL. + * + * Return Value: an array of quantization tables, or NULL. + */ +guint ** +jpeg_restore_original_tables (gint32 image_ID, + gint num_quant_tables) +{ + GimpParasite *parasite; + const guchar *src; + glong src_size; + gint num_components; + gint num_tables; + guint **quant_tables; + gint t; + gint i; + + parasite = gimp_image_get_parasite (image_ID, "jpeg-settings"); + if (parasite) + { + src_size = gimp_parasite_data_size (parasite); + if (src_size >= 4) + { + src = gimp_parasite_data (parasite); + num_components = src[2]; + num_tables = src[3]; + + if (src_size >= (4 + num_components * 2 + num_tables * 128) + && num_tables == num_quant_tables) + { + src += 4 + num_components * 2; + quant_tables = g_new (guint *, num_tables); + + for (t = 0; t < num_tables; t++) + { + quant_tables[t] = g_new (guint, 128); + for (i = 0; i < 64; i++) + { + guint c; + + c = *src++ * 256; + c += *src++; + quant_tables[t][i] = c; + } + } + gimp_parasite_free (parasite); + return quant_tables; + } + } + gimp_parasite_free (parasite); + } + return NULL; +} + + +/** + * jpeg_swap_original_settings: + * @image_ID: the image that may contain original jpeg settings in a parasite. + * + * Swap the horizontal and vertical axis for the saved subsampling + * parameters and quantization tables. This should be done if the + * image has been rotated by +90 or -90 degrees or if it has been + * mirrored along its diagonal. + */ +void +jpeg_swap_original_settings (gint32 image_ID) +{ + GimpParasite *parasite; + const guchar *src; + glong src_size; + gint num_components; + gint num_tables; + guchar *new_data; + guchar *dest; + gint t; + gint i; + gint j; + + parasite = gimp_image_get_parasite (image_ID, "jpeg-settings"); + if (parasite) + { + src_size = gimp_parasite_data_size (parasite); + if (src_size >= 4) + { + src = gimp_parasite_data (parasite); + num_components = src[2]; + num_tables = src[3]; + + if (src_size >= (4 + num_components * 2 + num_tables * 128)) + { + new_data = g_new (guchar, src_size); + dest = new_data; + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + for (i = 0; i < num_components; i++) + { + dest[0] = src[1]; + dest[1] = src[0]; + dest += 2; + src += 2; + } + for (t = 0; t < num_tables; t++) + { + for (i = 0; i < 8; i++) + { + for (j = 0; j < 8; j++) + { + dest[i * 16 + j * 2] = src[j * 16 + i * 2]; + dest[i * 16 + j * 2 + 1] = src[j * 16 + i * 2 + 1]; + } + } + dest += 128; + src += 128; + if (src_size > (4 + num_components * 2 + num_tables * 128)) + { + memcpy (dest, src, src_size - (4 + num_components * 2 + + num_tables * 128)); + } + } + gimp_parasite_free (parasite); + parasite = gimp_parasite_new ("jpeg-settings", + GIMP_PARASITE_PERSISTENT, + src_size, + new_data); + g_free (new_data); + gimp_image_attach_parasite (image_ID, parasite); + } + } + gimp_parasite_free (parasite); + } +} diff --git a/plug-ins/file-jpeg/jpeg-settings.h b/plug-ins/file-jpeg/jpeg-settings.h new file mode 100644 index 0000000..0399b84 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg-settings.h @@ -0,0 +1,37 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * jpeg-settings.h + * Copyright (C) 2007 Raphaël Quinet <raphael@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/>. + */ + +#ifndef __JPEG_SETTINGS_H__ +#define __JPEG_SETTINGS_H__ + +gboolean jpeg_detect_original_settings (struct jpeg_decompress_struct *cinfo, + gint32 image_ID); + +gboolean jpeg_restore_original_settings (gint32 image_ID, + gint *quality, + JpegSubsampling *subsmp, + gint *num_quant_tables); + +guint **jpeg_restore_original_tables (gint32 image_ID, + gint num_quant_tables); + +void jpeg_swap_original_settings (gint32 image_ID); + +#endif /* __JPEG_SETTINGS_H__ */ diff --git a/plug-ins/file-jpeg/jpeg.c b/plug-ins/file-jpeg/jpeg.c new file mode 100644 index 0000000..bd2f460 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg.c @@ -0,0 +1,643 @@ +/* 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 <stdio.h> +#include <setjmp.h> +#include <string.h> + +#include <jpeglib.h> +#include <jerror.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "libgimp/stdplugins-intl.h" + +#include "jpeg.h" +#include "jpeg-settings.h" +#include "jpeg-load.h" +#include "jpeg-save.h" + +/* Declare local functions. + */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +gboolean undo_touched; +gboolean load_interactive; +gchar *image_comment; +gint32 display_ID; +JpegSaveVals jsvals; +gint32 orig_image_ID_global; +gint32 drawable_ID_global; +gint orig_quality; +JpegSubsampling orig_subsmp; +gint num_quant_tables; + + +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 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" } + }; + + 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_FLOAT, "quality", "Quality of saved image (0 <= quality <= 1)" }, + { GIMP_PDB_FLOAT, "smoothing", "Smoothing factor for saved image (0 <= smoothing <= 1)" }, + { GIMP_PDB_INT32, "optimize", "Use optimized tables during Huffman coding (0/1)" }, + { GIMP_PDB_INT32, "progressive", "Create progressive JPEG images (0/1)" }, + { GIMP_PDB_STRING, "comment", "Image comment" }, + { GIMP_PDB_INT32, "subsmp", "Sub-sampling type { 0, 1, 2, 3 } 0 == 4:2:0 (chroma quartered), 1 == 4:2:2 Horizontal (chroma halved), 2 == 4:4:4 (best quality), 3 == 4:2:2 Vertical (chroma halved)" }, + { GIMP_PDB_INT32, "baseline", "Force creation of a baseline JPEG (non-baseline JPEGs can't be read by all decoders) (0/1)" }, + { GIMP_PDB_INT32, "restart", "Interval of restart markers (in MCU rows, 0 = no restart markers)" }, + { GIMP_PDB_INT32, "dct", "DCT method to use { INTEGER (0), FIXED (1), FLOAT (2) }" } + }; + + gimp_install_procedure (LOAD_PROC, + "loads files in the JPEG file format", + "loads files in the JPEG file format", + "Spencer Kimball, Peter Mattis & others", + "Spencer Kimball & Peter Mattis", + "1995-2007", + N_("JPEG 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/jpeg"); + gimp_register_magic_load_handler (LOAD_PROC, + "jpg,jpeg,jpe", + "", + "0,string,\xff\xd8\xff"); + + gimp_install_procedure (LOAD_THUMB_PROC, + "Loads a thumbnail from a JPEG image", + "Loads a thumbnail from a JPEG image (only if it exists)", + "Mukund Sivaraman <muks@mukund.org>, Sven Neumann <sven@gimp.org>", + "Mukund Sivaraman <muks@mukund.org>, Sven Neumann <sven@gimp.org>", + "November 15, 2004", + 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, + "saves files in the JPEG file format", + "saves files in the lossy, widely supported JPEG format", + "Spencer Kimball, Peter Mattis & others", + "Spencer Kimball & Peter Mattis", + "1995-2007", + N_("JPEG image"), + "RGB*, GRAY*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/jpeg"); + gimp_register_save_handler (SAVE_PROC, "jpg,jpeg,jpe", ""); +} + +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; + GimpParasite *parasite; + 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; + + preview_image_ID = -1; + preview_layer_ID = -1; + + orig_quality = 0; + orig_subsmp = JPEG_SUBSAMPLING_2x2_1x1_1x1; + num_quant_tables = 0; + + if (strcmp (name, LOAD_PROC) == 0) + { + gboolean resolution_loaded = FALSE; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + load_interactive = TRUE; + break; + default: + load_interactive = FALSE; + break; + } + + image_ID = load_image (param[1].data.d_string, run_mode, FALSE, + &resolution_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/jpeg", + file, NULL); + + if (metadata) + { + GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_ALL; + + if (resolution_loaded) + flags &= ~GIMP_METADATA_LOAD_RESOLUTION; + + gimp_image_metadata_load_finish (image_ID, "image/jpeg", + metadata, flags, + load_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, LOAD_THUMB_PROC) == 0) + { + if (nparams < 2) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + GFile *file = g_file_new_for_path (param[0].data.d_string); + gint width = 0; + gint height = 0; + GimpImageType type = -1; + + image_ID = load_thumbnail_image (file, &width, &height, &type, + &error); + + g_object_unref (file); + + 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; + values[3].type = GIMP_PDB_INT32; + values[3].data.d_int32 = height; + values[4].type = GIMP_PDB_INT32; + values[4].data.d_int32 = type; + values[5].type = GIMP_PDB_INT32; + values[5].data.d_int32 = 1; /* num_layers */ + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + GimpMetadata *metadata; + GimpMetadataSaveFlags metadata_flags; + gint32 orig_image_ID; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + + 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, "JPEG", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY); + + switch (export) + { + case GIMP_EXPORT_EXPORT: + { + gchar *tmp = g_filename_from_utf8 (_("Export Preview"), -1, + NULL, NULL, NULL); + if (tmp) + { + gimp_image_set_filename (image_ID, tmp); + g_free (tmp); + } + + display_ID = -1; + } + break; + + case GIMP_EXPORT_IGNORE: + break; + + case GIMP_EXPORT_CANCEL: + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + break; + } + break; + + default: + break; + } + + /* Initialize with hardcoded defaults */ + load_defaults (); + + /* Override the defaults with preferences. */ + metadata = gimp_image_metadata_save_prepare (orig_image_ID, + "image/jpeg", + &metadata_flags); + jsvals.save_exif = (metadata_flags & GIMP_METADATA_SAVE_EXIF) != 0; + jsvals.save_xmp = (metadata_flags & GIMP_METADATA_SAVE_XMP) != 0; + jsvals.save_iptc = (metadata_flags & GIMP_METADATA_SAVE_IPTC) != 0; + jsvals.save_thumbnail = (metadata_flags & GIMP_METADATA_SAVE_THUMBNAIL) != 0; + jsvals.save_profile = (metadata_flags & GIMP_METADATA_SAVE_COLOR_PROFILE) != 0; + + parasite = gimp_image_get_parasite (orig_image_ID, "gimp-comment"); + if (parasite) + { + image_comment = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + gimp_parasite_free (parasite); + } + + /* Override preferences from JPG export defaults (if saved). */ + load_parasite (); + + switch (run_mode) + { + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + /* pw - added two more progressive and comment */ + /* sg - added subsampling, preview, baseline, restarts and DCT */ + if (nparams != 14) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + /* Once the PDB gets default parameters, remove this hack */ + if (param[5].data.d_float >= 0.01) + { + jsvals.quality = 100.0 * param[5].data.d_float; + jsvals.smoothing = param[6].data.d_float; + jsvals.optimize = param[7].data.d_int32; + jsvals.progressive = param[8].data.d_int32; + jsvals.baseline = param[11].data.d_int32; + jsvals.subsmp = param[10].data.d_int32; + jsvals.restart = param[12].data.d_int32; + jsvals.dct = param[13].data.d_int32; + + /* free up the default -- wasted some effort earlier */ + g_free (image_comment); + image_comment = g_strdup (param[9].data.d_string); + } + + jsvals.preview = FALSE; + + if (jsvals.quality < 0.0 || jsvals.quality > 100.0) + status = GIMP_PDB_CALLING_ERROR; + else if (jsvals.smoothing < 0.0 || jsvals.smoothing > 1.0) + status = GIMP_PDB_CALLING_ERROR; + else if (jsvals.subsmp < 0 || jsvals.subsmp > 3) + status = GIMP_PDB_CALLING_ERROR; + else if (jsvals.dct < 0 || jsvals.dct > 2) + status = GIMP_PDB_CALLING_ERROR; + } + break; + + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + /* restore the values found when loading the file (if available) */ + jpeg_restore_original_settings (orig_image_ID, + &orig_quality, + &orig_subsmp, + &num_quant_tables); + + /* load up the previously used values (if file was saved once) */ + parasite = gimp_image_get_parasite (orig_image_ID, + "jpeg-save-options"); + if (parasite) + { + const JpegSaveVals *save_vals = gimp_parasite_data (parasite); + + jsvals.quality = save_vals->quality; + jsvals.smoothing = save_vals->smoothing; + jsvals.optimize = save_vals->optimize; + jsvals.progressive = save_vals->progressive; + jsvals.baseline = save_vals->baseline; + jsvals.subsmp = save_vals->subsmp; + jsvals.restart = save_vals->restart; + jsvals.dct = save_vals->dct; + jsvals.preview = save_vals->preview; + jsvals.save_exif = save_vals->save_exif; + jsvals.save_thumbnail = save_vals->save_thumbnail; + jsvals.save_xmp = save_vals->save_xmp; + jsvals.save_iptc = save_vals->save_iptc; + jsvals.use_orig_quality = save_vals->use_orig_quality; + + gimp_parasite_free (parasite); + } + else + { + /* We are called with GIMP_RUN_WITH_LAST_VALS but this image + * doesn't have a "jpeg-save-options" parasite. It's better + * to prompt the user with a dialog now so that she has control + * over the JPEG encoding parameters. + */ + run_mode = GIMP_RUN_INTERACTIVE; + + /* If this image was loaded from a JPEG file and has not been + * saved yet, try to use some of the settings from the + * original file if they are better than the default values. + */ + if (orig_quality > jsvals.quality) + { + jsvals.quality = orig_quality; + } + + /* Skip changing subsampling to original if we already have best + * setting or if original have worst setting */ + if (!(jsvals.subsmp == JPEG_SUBSAMPLING_1x1_1x1_1x1 || + orig_subsmp == JPEG_SUBSAMPLING_2x2_1x1_1x1)) + { + jsvals.subsmp = orig_subsmp; + } + + if (orig_quality == jsvals.quality && + orig_subsmp == jsvals.subsmp) + { + jsvals.use_orig_quality = TRUE; + } + } + break; + } + + if (run_mode == GIMP_RUN_INTERACTIVE) + { + if (jsvals.preview) + { + /* we freeze undo saving so that we can avoid sucking up + * tile cache with our unneeded preview steps. */ + gimp_image_undo_freeze (image_ID); + + undo_touched = TRUE; + } + + /* prepare for the preview */ + preview_image_ID = image_ID; + orig_image_ID_global = orig_image_ID; + drawable_ID_global = drawable_ID; + + /* First acquire information with a dialog */ + status = (save_dialog () ? GIMP_PDB_SUCCESS : GIMP_PDB_CANCEL); + + if (undo_touched) + { + /* thaw undo saving and flush the displays to have them + * reflect the current shortcuts */ + gimp_image_undo_thaw (image_ID); + gimp_displays_flush (); + } + } + + if (status == GIMP_PDB_SUCCESS) + { + if (! save_image (param[3].data.d_string, + image_ID, drawable_ID, orig_image_ID, FALSE, + &error)) + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + if (export == GIMP_EXPORT_EXPORT) + { + /* If the image was exported, delete the new display. */ + /* This also deletes the image. + */ + + if (display_ID != -1) + gimp_display_delete (display_ID); + else + gimp_image_delete (image_ID); + } + + if (status == GIMP_PDB_SUCCESS) + { + /* pw - now we need to change the defaults to be whatever + * was used to save this image. Dump the old parasites + * and add new ones. + */ + + gimp_image_detach_parasite (orig_image_ID, "gimp-comment"); + if (image_comment && strlen (image_comment)) + { + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (image_comment) + 1, + image_comment); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + } + + parasite = gimp_parasite_new ("jpeg-save-options", + 0, sizeof (jsvals), &jsvals); + gimp_image_attach_parasite (orig_image_ID, parasite); + gimp_parasite_free (parasite); + + /* write metadata */ + + if (metadata) + { + GFile *file; + + gimp_metadata_set_bits_per_sample (metadata, 8); + + if (jsvals.save_exif) + metadata_flags |= GIMP_METADATA_SAVE_EXIF; + else + metadata_flags &= ~GIMP_METADATA_SAVE_EXIF; + + if (jsvals.save_xmp) + metadata_flags |= GIMP_METADATA_SAVE_XMP; + else + metadata_flags &= ~GIMP_METADATA_SAVE_XMP; + + if (jsvals.save_iptc) + metadata_flags |= GIMP_METADATA_SAVE_IPTC; + else + metadata_flags &= ~GIMP_METADATA_SAVE_IPTC; + + if (jsvals.save_thumbnail) + metadata_flags |= GIMP_METADATA_SAVE_THUMBNAIL; + else + metadata_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL; + + if (jsvals.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); + if (! gimp_image_metadata_save_finish (orig_image_ID, + "image/jpeg", + metadata, metadata_flags, + file, &error)) + { + if (error) + { + /* Even though a failure to write metadata is not enough + reason to say we failed to save the image, we should + still notify the user about the problem. */ + g_message ("%s: saving metadata failed: %s", + G_STRFUNC, error->message); + g_error_free (error); + } + } + g_object_unref (file); + } + } + + if (metadata) + g_object_unref (metadata); + } + 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; +} + +/* + * Here's the routine that will replace the standard error_exit method: + */ + +void +my_error_exit (j_common_ptr cinfo) +{ + /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + + /* Always display the message. */ + /* We could postpone this until after returning, if we chose. */ + (*cinfo->err->output_message) (cinfo); + + /* Return control to the setjmp point */ + longjmp (myerr->setjmp_buffer, 1); +} + + +void +my_output_message (j_common_ptr cinfo) +{ + gchar buffer[JMSG_LENGTH_MAX + 1]; + + (*cinfo->err->format_message)(cinfo, buffer); + + g_message ("%s", buffer); +} diff --git a/plug-ins/file-jpeg/jpeg.h b/plug-ins/file-jpeg/jpeg.h new file mode 100644 index 0000000..0bf9af0 --- /dev/null +++ b/plug-ins/file-jpeg/jpeg.h @@ -0,0 +1,73 @@ +/* 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/>. + */ + +#ifndef __JPEG_H__ +#define __JPEG_H__ + +#define LOAD_PROC "file-jpeg-load" +#define LOAD_THUMB_PROC "file-jpeg-load-thumb" +#define SAVE_PROC "file-jpeg-save" +#define PLUG_IN_BINARY "file-jpeg" +#define PLUG_IN_ROLE "gimp-file-jpeg" + +/* headers used in some APPn markers */ +#define JPEG_APP_HEADER_EXIF "Exif\0\0" +#define JPEG_APP_HEADER_XMP "http://ns.adobe.com/xap/1.0/" + +typedef struct my_error_mgr +{ + struct jpeg_error_mgr pub; /* "public" fields */ + +#ifdef __ia64__ + /* Ugh, the jmp_buf field needs to be 16-byte aligned on ia64 and some + * glibc/icc combinations don't guarantee this. So we pad. See bug #138357 + * for details. + */ + long double dummy; +#endif + + jmp_buf setjmp_buffer; /* for return to caller */ +} *my_error_ptr; + +typedef enum +{ + JPEG_SUBSAMPLING_2x2_1x1_1x1 = 0, /* smallest file */ + JPEG_SUBSAMPLING_2x1_1x1_1x1 = 1, /* 4:2:2 */ + JPEG_SUBSAMPLING_1x1_1x1_1x1 = 2, + JPEG_SUBSAMPLING_1x2_1x1_1x1 = 3 +} JpegSubsampling; + +extern gint32 volatile preview_image_ID; +extern gint32 preview_layer_ID; +extern gboolean undo_touched; +extern gboolean load_interactive; +extern gint32 display_ID; +extern gchar *image_comment; +extern gint orig_quality; +extern JpegSubsampling orig_subsmp; +extern gint num_quant_tables; + + +void destroy_preview (void); + +void my_error_exit (j_common_ptr cinfo); +void my_emit_message (j_common_ptr cinfo, + int msg_level); +void my_output_message (j_common_ptr cinfo); + + +#endif /* __JPEG_H__ */ diff --git a/plug-ins/file-jpeg/jpegqual.c b/plug-ins/file-jpeg/jpegqual.c new file mode 100644 index 0000000..9dd5d9e --- /dev/null +++ b/plug-ins/file-jpeg/jpegqual.c @@ -0,0 +1,1082 @@ +/* jpegqual.c - analyze quality settings of existing JPEG files + * + * Copyright (C) 2007 Raphaël Quinet <raphael@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/>. + */ + +/* + * This program analyzes the quantization tables of the JPEG files + * given on the command line and displays their quality settings. + * + * It is useful for developers or maintainers of the JPEG plug-in + * because it can be used to validate the formula used in + * jpeg_detect_quality(), by comparing the quality reported for + * different JPEG files. + * + * It can also dump quantization tables so that they can be integrated + * into this program and recognized later. This can be used to identify + * which device or which program saved a JPEG file. + */ + +/* + * TODO: + * - rename this program! + * - update quant_info[]. + * - reorganize the options: 2 groups for print options and for selection. + * - re-add the code to compare different formulas for approx. IJG quality. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <setjmp.h> + +#include <glib.h> + +#include <jpeglib.h> +#include <jerror.h> + +#include "jpeg-quality.h" + +/* command-line options */ +static gboolean option_summary = FALSE; +static gboolean option_ctable = FALSE; +static gboolean option_table_2cols = FALSE; +static gboolean option_unknown = FALSE; +static gboolean option_ignore_err = FALSE; +static gchar **filenames = NULL; + +static const GOptionEntry option_entries[] = +{ + { + "ignore-errors", 'i', 0, G_OPTION_ARG_NONE, &option_ignore_err, + "Continue processing other files after a JPEG error", NULL + }, + { + "summary", 's', 0, G_OPTION_ARG_NONE, &option_summary, + "Print summary information and closest IJG quality", NULL + }, + { + "tables", 't', 0, G_OPTION_ARG_NONE, &option_table_2cols, + "Dump quantization tables", NULL + }, + { + "c-tables", 'c', 0, G_OPTION_ARG_NONE, &option_ctable, + "Dump quantization tables as C code", NULL + }, + { + "ctables", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &option_ctable, + NULL, NULL + }, + { + "unknown", 'u', 0, G_OPTION_ARG_NONE, &option_unknown, + "Only print information about files with unknown tables", NULL + }, + { + G_OPTION_REMAINING, 0, 0, + G_OPTION_ARG_FILENAME_ARRAY, &filenames, + NULL, NULL + }, + { NULL } +}; + +/* information about known JPEG quantization tables */ +typedef struct +{ + const guint hash; /* hash of luminance/chrominance tables */ + const gint lum_sum; /* sum of luminance table divisors */ + const gint chrom_sum; /* sum of chrominance table divisors */ + const gint subsmp_h; /* horizontal subsampling (1st component) */ + const gint subsmp_v; /* vertical subsampling (1st component) */ + const gint num_quant_tables; /* number of tables (< 0 if no grayscale) */ + const gchar *source_name; /* name of software of device */ + const gchar *setting_name; /* name of quality setting */ + const gint ijg_qual; /* closest IJG quality setting */ +} QuantInfo; + +static const QuantInfo quant_info[] = +{ + { 0x0a82648b, 64, 64, 0, 0, 2, "IJG JPEG Library", "quality 100", 100 }, + { 0x4d981764, 86, 115, 0, 0, 2, "IJG JPEG Library", "quality 99", 99 }, + { 0x62b71702, 151, 224, 0, 0, 2, "IJG JPEG Library", "quality 98", 98 }, + { 0x29e095c5, 221, 333, 0, 0, 2, "IJG JPEG Library", "quality 97", 97 }, + { 0xb62c754a, 292, 443, 0, 0, 2, "IJG JPEG Library", "quality 96", 96 }, + { 0x8e55c78a, 369, 558, 0, 0, 2, "IJG JPEG Library", "quality 95", 95 }, + { 0x0664d770, 441, 668, 0, 0, 2, "IJG JPEG Library", "quality 94", 94 }, + { 0x59e5c5bc, 518, 779, 0, 0, 2, "IJG JPEG Library", "quality 93", 93 }, + { 0xd6f26606, 592, 891, 0, 0, 2, "IJG JPEG Library", "quality 92", 92 }, + { 0x8aa986ad, 667, 999, 0, 0, 2, "IJG JPEG Library", "quality 91", 91 }, + { 0x17816eb1, 736, 1110, 0, 0, 2, "IJG JPEG Library", "quality 90", 90 }, + { 0x75de9350, 814, 1223, 0, 0, 2, "IJG JPEG Library", "quality 89", 89 }, + { 0x88fdf223, 884, 1332, 0, 0, 2, "IJG JPEG Library", "quality 88", 88 }, + { 0xf40a6a50, 961, 1444, 0, 0, 2, "IJG JPEG Library", "quality 87", 87 }, + { 0xe9f2c235, 1031, 1555, 0, 0, 2, "IJG JPEG Library", "quality 86", 86 }, + { 0x82683892, 1109, 1666, 0, 0, 2, "IJG JPEG Library", "quality 85", 85 }, + { 0xb1aecce8, 1179, 1778, 0, 0, 2, "IJG JPEG Library", "quality 84", 84 }, + { 0x83375efe, 1251, 1888, 0, 0, 2, "IJG JPEG Library", "quality 83", 83 }, + { 0x1e99f479, 1326, 2000, 0, 0, 2, "IJG JPEG Library", "quality 82", 82 }, + { 0x1a02d360, 1398, 2111, 0, 0, 2, "IJG JPEG Library", "quality 81", 81 }, + { 0x96129a0d, 1477, 2221, 0, 0, 2, "IJG JPEG Library", "quality 80", 80 }, + { 0x64d4144b, 1552, 2336, 0, 0, 2, "IJG JPEG Library", "quality 79", 79 }, + { 0x48a344ac, 1620, 2445, 0, 0, 2, "IJG JPEG Library", "quality 78", 78 }, + { 0x16e820e3, 1692, 2556, 0, 0, 2, "IJG JPEG Library", "quality 77", 77 }, + { 0x246b2e95, 1773, 2669, 0, 0, 2, "IJG JPEG Library", "quality 76", 76 }, + { 0x10b035e9, 1858, 2780, 0, 0, 2, "IJG JPEG Library", "quality 75", 75 }, + { 0xd5c653da, 1915, 2836, 0, 0, 2, "IJG JPEG Library", "quality 74", 74 }, + { 0xe349618c, 1996, 2949, 0, 0, 2, "IJG JPEG Library", "quality 73", 73 }, + { 0xb18e3dc3, 2068, 3060, 0, 0, 2, "IJG JPEG Library", "quality 72", 72 }, + { 0x955d6e24, 2136, 3169, 0, 0, 2, "IJG JPEG Library", "quality 71", 71 }, + { 0x641ee862, 2211, 3284, 0, 0, 2, "IJG JPEG Library", "quality 70", 70 }, + { 0xe02eaf0f, 2290, 3394, 0, 0, 2, "IJG JPEG Library", "quality 69", 69 }, + { 0xdb978df6, 2362, 3505, 0, 0, 2, "IJG JPEG Library", "quality 68", 68 }, + { 0x76fa2371, 2437, 3617, 0, 0, 2, "IJG JPEG Library", "quality 67", 67 }, + { 0x4882b587, 2509, 3727, 0, 0, 2, "IJG JPEG Library", "quality 66", 66 }, + { 0x25556ae1, 2583, 3839, 0, 0, 2, "IJG JPEG Library", "quality 65", 65 }, + { 0x103ec03a, 2657, 3950, 0, 0, 2, "IJG JPEG Library", "quality 64", 64 }, + { 0x0627181f, 2727, 4061, 0, 0, 2, "IJG JPEG Library", "quality 63", 63 }, + { 0x7133904c, 2804, 4173, 0, 0, 2, "IJG JPEG Library", "quality 62", 62 }, + { 0x8452ef1f, 2874, 4282, 0, 0, 2, "IJG JPEG Library", "quality 61", 61 }, + { 0xe2b013be, 2952, 4395, 0, 0, 2, "IJG JPEG Library", "quality 60", 60 }, + { 0x6f87fbc2, 3021, 4506, 0, 0, 2, "IJG JPEG Library", "quality 59", 59 }, + { 0x233f1c69, 3096, 4614, 0, 0, 2, "IJG JPEG Library", "quality 58", 58 }, + { 0xa04bbcb3, 3170, 4726, 0, 0, 2, "IJG JPEG Library", "quality 57", 57 }, + { 0xf3ccaaff, 3247, 4837, 0, 0, 2, "IJG JPEG Library", "quality 56", 56 }, + { 0x1967dbe9, 3323, 4947, 0, 0, 2, "IJG JPEG Library", "quality 55", 55 }, + { 0x44050d25, 3396, 5062, 0, 0, 2, "IJG JPEG Library", "quality 54", 54 }, + { 0xd050ecaa, 3467, 5172, 0, 0, 2, "IJG JPEG Library", "quality 53", 53 }, + { 0x9e99f8f1, 3541, 5281, 0, 0, 2, "IJG JPEG Library", "quality 52", 52 }, + { 0xdf2423f4, 3621, 5396, 0, 0, 2, "IJG JPEG Library", "quality 51", 51 }, + { 0xe0f48a64, 3688, 5505, 0, 0, 2, "IJG JPEG Library", "quality 50", 50 }, + { 0xe2c4f0d4, 3755, 5614, 0, 0, 2, "IJG JPEG Library", "quality 49", 49 }, + { 0x234f1bd7, 3835, 5729, 0, 0, 2, "IJG JPEG Library", "quality 48", 48 }, + { 0xf198281e, 3909, 5838, 0, 0, 2, "IJG JPEG Library", "quality 47", 47 }, + { 0x7de407a3, 3980, 5948, 0, 0, 2, "IJG JPEG Library", "quality 46", 46 }, + { 0xb3aa597b, 4092, 6116, 0, 0, 2, "IJG JPEG Library", "quality 45", 45 }, + { 0x32b48093, 4166, 6226, 0, 0, 2, "IJG JPEG Library", "quality 44", 44 }, + { 0x9ea9f85f, 4280, 6396, 0, 0, 2, "IJG JPEG Library", "quality 43", 43 }, + { 0x335d6006, 4393, 6562, 0, 0, 2, "IJG JPEG Library", "quality 42", 42 }, + { 0xa727ea4a, 4463, 6672, 0, 0, 2, "IJG JPEG Library", "quality 41", 41 }, + { 0x1889cfc4, 4616, 6897, 0, 0, 2, "IJG JPEG Library", "quality 40", 40 }, + { 0xb1aa548e, 4719, 7060, 0, 0, 2, "IJG JPEG Library", "quality 39", 39 }, + { 0x99bebdd3, 4829, 7227, 0, 0, 2, "IJG JPEG Library", "quality 38", 38 }, + { 0xf728d062, 4976, 7447, 0, 0, 2, "IJG JPEG Library", "quality 37", 37 }, + { 0xe1ba65b9, 5086, 7616, 0, 0, 2, "IJG JPEG Library", "quality 36", 36 }, + { 0x2c8ba6a4, 5240, 7841, 0, 0, 2, "IJG JPEG Library", "quality 35", 35 }, + { 0x03f7963a, 5421, 8114, 0, 0, 2, "IJG JPEG Library", "quality 34", 34 }, + { 0xa19bed1e, 5571, 8288, 0, 0, 2, "IJG JPEG Library", "quality 33", 33 }, + { 0x7945d01c, 5756, 8565, 0, 0, 2, "IJG JPEG Library", "quality 32", 32 }, + { 0xcc36df1a, 5939, 8844, 0, 0, 2, "IJG JPEG Library", "quality 31", 31 }, + { 0x3eb1b5ca, 6125, 9122, 0, 0, 2, "IJG JPEG Library", "quality 30", 30 }, + { 0xd7f65293, 6345, 9455, 0, 0, 2, "IJG JPEG Library", "quality 29", 29 }, + { 0x4c0a8178, 6562, 9787, 0, 0, 2, "IJG JPEG Library", "quality 28", 28 }, + { 0x8281d1a1, 6823, 10175, 0, 0, 2, "IJG JPEG Library", "quality 27", 27 }, + { 0x0bbc9f7e, 7084, 10567, 0, 0, 2, "IJG JPEG Library", "quality 26", 26 }, + { 0xa8ac1cbd, 7376, 11010, 0, 0, 2, "IJG JPEG Library", "quality 25", 25 }, + { 0x459b99fc, 7668, 11453, 0, 0, 2, "IJG JPEG Library", "quality 24", 24 }, + { 0xda09c178, 7995, 11954, 0, 0, 2, "IJG JPEG Library", "quality 23", 23 }, + { 0x1c651f15, 8331, 12511, 0, 0, 2, "IJG JPEG Library", "quality 22", 22 }, + { 0x59025244, 8680, 13121, 0, 0, 2, "IJG JPEG Library", "quality 21", 21 }, + { 0xa130f919, 9056, 13790, 0, 0, 2, "IJG JPEG Library", "quality 20", 20 }, + { 0x109756cf, 9368, 14204, 0, 0, 2, "IJG JPEG Library", "quality 19", 19 }, + { 0xe929cab5, 9679, 14267, 0, 0, 2, "IJG JPEG Library", "quality 18", 18 }, + { 0xcddca370, 10027, 14346, 0, 0, 2, "IJG JPEG Library", "quality 17", 17 }, + { 0xd5fc76c0, 10360, 14429, 0, 0, 2, "IJG JPEG Library", "quality 16", 16 }, + { 0x533a1a03, 10714, 14526, 0, 0, 2, "IJG JPEG Library", "quality 15", 15 }, + { 0x0d8adaff, 11081, 14635, 0, 0, 2, "IJG JPEG Library", "quality 14", 14 }, + { 0x0d2ee95d, 11456, 14754, 0, 0, 2, "IJG JPEG Library", "quality 13", 13 }, + { 0x3a1d59a0, 11861, 14864, 0, 0, 2, "IJG JPEG Library", "quality 12", 12 }, + { 0x66555d04, 12240, 14985, 0, 0, 2, "IJG JPEG Library", "quality 11", 11 }, + { 0x7fa051b1, 12560, 15110, 0, 0, 2, "IJG JPEG Library", "quality 10", 10 }, + { 0x7b668ca3, 12859, 15245, 0, 0, 2, "IJG JPEG Library", "quality 9", 9 }, + { 0xb44d7082, 13230, 15369, 0, 0, 2, "IJG JPEG Library", "quality 8", 8 }, + { 0xe838d325, 13623, 15523, 0, 0, 2, "IJG JPEG Library", "quality 7", 7 }, + { 0xb6f58977, 14073, 15731, 0, 0, 2, "IJG JPEG Library", "quality 6", 6 }, + { 0xfd3e9fc4, 14655, 16010, 0, 0, 2, "IJG JPEG Library", "quality 5", 5 }, + { 0x7782b922, 15277, 16218, 0, 0, 2, "IJG JPEG Library", "quality 4", 4 }, + { 0x5a03ac45, 15946, 16320, 0, 0, 2, "IJG JPEG Library", "quality 3", 3 }, + { 0xe0afaa36, 16315, 16320, 0, 0, 2, "IJG JPEG Library", "quality 2", 2 }, + { 0x6d640b8b, 16320, 16320, 0, 0, 2, "IJG JPEG Library", "quality 1", 1 }, + { 0x6d640b8b, 16320, 16320, 0, 0, 2, "IJG JPEG Library", "quality 0", 1 }, + { 0x4b1d5895, 8008, 11954, 0, 0, 2, "IJG JPEG Library", "not baseline 23", -22 }, + { 0x36c32c2c, 8370, 12511, 0, 0, 2, "IJG JPEG Library", "not baseline 22", -21 }, + { 0xa971f812, 8774, 13121, 0, 0, 2, "IJG JPEG Library", "not baseline 21", -20 }, + { 0xa01f5a9b, 9234, 13790, 0, 0, 2, "IJG JPEG Library", "not baseline 20", -19 }, + { 0x0e45ab9a, 9700, 14459, 0, 0, 2, "IJG JPEG Library", "not baseline 19", -17 }, + { 0x5e654320, 10209, 15236, 0, 0, 2, "IJG JPEG Library", "not baseline 18", -14 }, + { 0x5fc0115c, 10843, 16182, 0, 0, 2, "IJG JPEG Library", "not baseline 17", -11 }, + { 0x5d8b8e7b, 11505, 17183, 0, 0, 2, "IJG JPEG Library", "not baseline 16", -7 }, + { 0x63f8b8c1, 12279, 18351, 0, 0, 2, "IJG JPEG Library", "not baseline 15", -5 }, + { 0x675ecd7a, 13166, 19633, 0, 0, 2, "IJG JPEG Library", "not baseline 14", 0 }, + { 0x7a65d374, 14160, 21129, 0, 0, 2, "IJG JPEG Library", "not baseline 13", 0 }, + { 0xf5d0af6a, 15344, 22911, 0, 0, 2, "IJG JPEG Library", "not baseline 12", 0 }, + { 0x0227aaf0, 16748, 24969, 0, 0, 2, "IJG JPEG Library", "not baseline 11", 0 }, + { 0xffd2d3c8, 18440, 27525, 0, 0, 2, "IJG JPEG Library", "not baseline 10", 0 }, + { 0x27f48623, 20471, 30529, 0, 0, 2, "IJG JPEG Library", "not baseline 9", 0 }, + { 0xff1fab81, 23056, 34422, 0, 0, 2, "IJG JPEG Library", "not baseline 8", 0 }, + { 0xcfeac62b, 26334, 39314, 0, 0, 2, "IJG JPEG Library", "not baseline 7", 0 }, + { 0x4a8e947e, 30719, 45876, 0, 0, 2, "IJG JPEG Library", "not baseline 6", 0 }, + { 0xe668af85, 36880, 55050, 0, 0, 2, "IJG JPEG Library", "not baseline 5", 0 }, + { 0x6d4b1215, 46114, 68840, 0, 0, 2, "IJG JPEG Library", "not baseline 4", 0 }, + { 0xf2734901, 61445, 91697, 0, 0, 2, "IJG JPEG Library", "not baseline 3", 0 }, + { 0x9a2a42bc, 92200, 137625, 0, 0, 2, "IJG JPEG Library", "not baseline 2", 0 }, + { 0x1b178d6d, 184400, 275250, 0, 0, 2, "IJG JPEG Library", "not baseline 1", 0 }, + { 0x1b178d6d, 184400, 275250, 0, 0, 2, "IJG JPEG Library", "not baseline 0", 0 }, + + /* FIXME: the following entries are incomplete and need to be verified */ + + { 0x31258383, 319, 665, 2, 1, -2, "ACD ?", "?", -94 }, + { 0x91d018a3, 436, 996, 2, 1, -2, "ACD ?", "?", -92 }, + { 0x954ee70e, 664, 1499, 2, 1, -2, "ACD ?", "?", -88 }, + { 0xe351bb55, 1590, 3556, 2, 2, -2, "ACD ?", "?", -71 }, + { 0x5a81e2c0, 95, 166, 1, 1, 2, "Adobe Photoshop CS2", "quality 12", -98 }, + { 0xcd0d41ae, 232, 443, 1, 1, 2, "Adobe Photoshop CS2", "quality 11", -96 }, + { 0x1b141cb3, 406, 722, 1, 1, 2, "Adobe Photoshop CS2", "quality 10", -93 }, + { 0xc84c0187, 539, 801, 1, 1, 2, "Adobe Photoshop CS2", "quality 9", -92 }, + { 0x1e822409, 649, 853, 1, 1, 2, "Adobe Photoshop CS2", "quality 8", -91 }, + { 0x3104202b, 786, 926, 1, 1, 2, "Adobe Photoshop CS2", "quality 7", -90 }, + { 0xcd21f666, 717, 782, 2, 2, 2, "Adobe Photoshop CS2", "quality 6", -91 }, + { 0x1b74e018, 844, 849, 2, 2, 2, "Adobe Photoshop CS2", "quality 5", -90 }, + { 0xde39ed89, 962, 892, 2, 2, 2, "Adobe Photoshop CS2", "quality 4", -89 }, + { 0xbdef8414, 1068, 941, 2, 2, 2, "Adobe Photoshop CS2", "quality 3", -89 }, + { 0xfedf6432, 1281, 998, 2, 2, 2, "Adobe Photoshop CS2", "quality 2", -87 }, + { 0x5d6afd92, 1484, 1083, 2, 2, 2, "Adobe Photoshop CS2", "quality 1", -86 }, + { 0x4c7d2f7d, 1582, 1108, 2, 2, 2, "Adobe Photoshop CS2", "quality 0", -85 }, + { 0x68e798b2, 95, 168, 1, 1, 2, "Adobe Photoshop CS2", "save for web 100", -98 }, + { 0x9f3456f2, 234, 445, 1, 1, 2, "Adobe Photoshop CS2", "save for web 90", -96 }, + { 0xda807dd5, 406, 724, 1, 1, 2, "Adobe Photoshop CS2", "save for web 80", -93 }, + { 0xf70a37ce, 646, 1149, 1, 1, 2, "Adobe Photoshop CS2", "save for web 70", -90 }, + { 0xf36979d2, 974, 1769, 1, 1, 2, "Adobe Photoshop CS2", "save for web 60", -85 }, + { 0x4966f484, 1221, 1348, 2, 2, 2, "Adobe Photoshop CS2", "save for web 50", -86 }, + { 0xaddf6d45, 1821, 1997, 2, 2, 2, "Adobe Photoshop CS2", "save for web 40", -79 }, + { 0xeffa362a, 2223, 2464, 2, 2, 2, "Adobe Photoshop CS2", "save for web 30", -74 }, + { 0x7aa980c1, 2575, 2903, 2, 2, 2, "Adobe Photoshop CS2", "save for web 20", -70 }, + { 0x489e344f, 3514, 3738, 2, 2, 2, "Adobe Photoshop CS2", "save for web 10", -60 }, + { 0x1a2cffe0, 535, 750, 1, 1, 2, "Adobe Photoshop 7.0", "quality 10", -93 }, + { 0x1e96d5d3, 109, 171, 1, 1, 2, "Adobe Photoshop CS", "quality 12", -98 }, + { 0x6771042c, 303, 466, 1, 1, 2, "Adobe Photoshop CS, Camera Raw 3", "quality 11", -95 }, + { 0xd4553f25, 668, 830, 1, 1, 2, "Adobe Photoshop 7.0, CS", "quality 9", -91 }, + { 0xd3b24cb4, 794, 895, 1, 1, 2, "Adobe Photoshop 7.0, CS", "quality 8", -90 }, + { 0x4ad5990c, 971, 950, 1, 1, 2, "Adobe Photoshop CS", "quality 7", -89 }, + { 0x4293dfde, 884, 831, 2, 2, 2, "Adobe Photoshop CS", "quality 6", -90 }, + { 0xba0212ec, 1032, 889, 2, 2, 2, "Adobe Photoshop CS", "quality 5", -89 }, + { 0x4b50947d, 1126, 940, 2, 2, 2, "Adobe Photoshop CS", "quality 4", -88 }, + { 0xad0f8e5c, 1216, 977, 2, 2, 2, "Adobe Photoshop CS", "quality 3", -88 }, + { 0x560b5f0c, 339, 670, 1, 1, 2, "Adobe Photoshop ?", "save for web 85", -94 }, + { 0x9539b14b, 427, 613, 2, 2, 2, "Adobe Photoshop ?", "?", -94 }, + { 0x841f2655, 525, 941, 1, 1, 2, "Adobe Photoshop ?", "save for web 75", -92 }, + { 0xaa2161e2, 803, 1428, 1, 1, 2, "Adobe Photoshop ?", "save for web 65", -87 }, + { 0x743feb84, 1085, 1996, 1, 1, 2, "Adobe Photoshop ?", "save for web 55", -83 }, + { 0xe9f14743, 1156, 2116, 1, 1, 2, "Adobe Photoshop ?", "save for web 52", -82 }, + { 0x1003c8fb, 1175, 2169, 1, 1, 2, "Adobe Photoshop ?", "save for web 51", -81 }, + { 0xd7804c45, 2272, 2522, 2, 2, 2, "Adobe Photoshop ?", "save for web 29", -73 }, + { 0xcb5aa8ad, 2515, 2831, 2, 2, 2, "Adobe ImageReady", "save for web 22", -70 }, + { 0x956d2a00, 3822, 3975, 2, 2, 2, "Adobe ImageReady", "save for web 6", -57 }, + { 0xba53e0c5, 4028, 4174, 2, 2, 2, "Adobe Photoshop ?", "save for web 3", -55 }, + { 0x13c0c8bc, 513, 0, 1, 1, 1, "Adobe Photoshop ?", "?", -93 }, + { 0x3fad5c43, 255, 393, 2, 1, 2, "Apple Quicktime 7.1 or 7.2", "?", -96 }, + { 0x6529bd03, 513, 775, 2, 2, 2, "Apple Quicktime 7.2", "?", -93 }, + { 0x354e610a, 543, 784, 2, 1, -2, "Apple Quicktime 7.1", "?", -92 }, + { 0xd596795e, 361, 506, 2, 1, 2, "Apple ?", "?", -95 }, + { 0x74da8ba7, 1511, 2229, 2, 2, 2, "Apple ?", "?", -79 }, + { 0x6391ca2b, 188, 276, 2, 1, -2, "Canon EOS 300D, 350D or 400D", "Fine", -97 }, + { 0x00474eb0, 708, 1057, 2, 1, -2, "Canon EOS 10D", "Normal", -90 }, + { 0x535174bd, 533, 1325, 2, 1, -2, "Canon Digital Ixus v2", "Fine", -92 }, + { 0x535174bd, 533, 1325, 2, 1, -2, "Canon PowerShot A95, S1, S2, SD400 or SD630", "Fine", -89 }, + { 0xb7be6b97, 192, 556, 2, 1, -2, "Canon PowerShot S5 IS, A300, A430, S200, SD500, SD700, Ixus 700 or 800", "Superfine", -95 }, + { 0xb5b5c61d, 533, 1325, 1, 2, -2, "Canon Digital Ixus 400", "Fine", -89 }, + { 0xa7a2c471, 288, 443, 2, 1, -3, "FujiFilm MX-2700", "?", -96 }, + { 0x8db061f0, 389, 560, 2, 1, -3, "FujiFilm FinePix S700", "Fine", -94 }, + { 0xbb7b97ba, 515, 774, 2, 1, -3, "FujiFilm FinePix 2600 Zoom", "Fine", -93 }, + { 0x71bcdf92, 167, 240, 2, 2, -3, "HP PhotoSmart C850, C935", "?", -97 }, + { 0x9542cc81, 1970, 1970, 2, 2, -3, "HP PhotoSmart C812", "?", -78 }, + { 0xdb7b71d8, 369, 558, 2, 1, -3, "Kodak P880", "?", 95 }, + { 0x82e461f8, 566, 583, 2, 2, -2, "Kodak V610", "Fine", -93 }, + { 0x17816eb1, 736, 1110, 2, 2, -2, "Kodak DC240", "?", 90 }, + { 0x17816eb1, 736, 1110, 1, 1, -2, "Kodak Imaging", "High (high res.)", 90 }, + { 0x17816eb1, 736, 1110, 2, 1, -2, "Kodak Imaging", "High (medium res.)", 90 }, + { 0x17816eb1, 736, 1110, 4, 1, -2, "Kodak Imaging", "High (low res.)", 90 }, + { 0x3841f91b, 736, 0, 1, 1, 1, "Kodak Imaging", "High (grayscale)", 90 }, + { 0xe0f48a64, 3688, 5505, 1, 1, -2, "Kodak Imaging", "Medium (high res.)", 50 }, + { 0xe0f48a64, 3688, 5505, 2, 1, -2, "Kodak Imaging", "Medium (medium res.)", 50 }, + { 0xe0f48a64, 3688, 5505, 4, 1, -2, "Kodak Imaging", "Medium (low res.)", 50 }, + { 0x9ebccf53, 3688, 0, 1, 1, 1, "Kodak Imaging", "Medium (grayscale)", 50 }, + { 0xa130f919, 9056, 13790, 1, 1, -2, "Kodak Imaging", "Low (high res.)", 20 }, + { 0xa130f919, 9056, 13790, 2, 1, -2, "Kodak Imaging", "Low (medium res.)", 20 }, + { 0xa130f919, 9056, 13790, 4, 1, -2, "Kodak Imaging", "Low (low res.)", 20 }, + { 0x34216b8b, 9056, 0, 1, 1, 1, "Kodak Imaging", "Low (grayscale)", 20 }, + { 0x403b528f, 161, 179, 1, 1, -2, "Lead ?", "?", -98 }, + { 0x8550a881, 711, 1055, 1, 1, -2, "Lead ?", "?", -90 }, + { 0x98fb09fc, 1079, 1610, 1, 1, -2, "Lead ?", "?", -85 }, + { 0xfbb88fb8, 2031, 3054, 1, 1, -2, "Lead ?", "?", -72 }, + { 0x5fa57f78, 4835, 7226, 1, 1, -2, "Lead ?", "?", -37 }, + { 0x85b97881, 8199, 12287, 1, 1, -2, "Lead ?", "?", -22 }, + { 0xd3cd4ad0, 96, 117, 2, 1, -2, "Leica Digilux 3", "?", -98 }, + { 0x29e095c5, 221, 333, 2, 1, -2, "Leica M8", "?", 97 }, + { 0xee344795, 582, 836, 2, 1, -2, "Medion ?", "?", -92 }, + { 0x991408d7, 433, 667, 2, 1, -2, "Medion ?", "?", -94 }, + { 0x10b035e9, 1858, 2780, 2, 2, 2, "Microsoft Office", "Default", 75 }, + { 0x20fcfcb8, 116, 169, 2, 1, -2, "Nikon D50, D70, D70s, D80", "Fine", -98 }, + { 0x2530fec2, 218, 333, 2, 1, -2, "Nikon D70 or D70s", "Normal", -97 }, + { 0xe5dbee70, 616, 941, 2, 1, -2, "Nikon D70 or D70s", "Basic", -91 }, + { 0x0e082d61, 671, 999, 2, 1, -2, "Nikon D70 or D70s", "Basic + raw", -90 }, + { 0xcc6c9703, 127, 169, 2, 1, -2, "Nikon D70 v1.0", "Fine", -98 }, + { 0x8cdfa365, 302, 444, 2, 1, -2, "Nikon D70 v1.0", "Fine", -95 }, + { 0x23246639, 315, 499, 2, 1, -2, "Nikon D70 v1.0", "Fine", -95 }, + { 0x978378a8, 329, 500, 2, 1, -2, "Nikon D70 v1.0", "Fine", -95 }, + { 0x748a8379, 346, 500, 2, 1, -2, "Nikon D70 v1.0", "Fine", -95 }, + { 0xa85255cd, 372, 558, 2, 1, -2, "Nikon D70 v1.0", "Fine", -94 }, + { 0x016406e0, 389, 560, 2, 1, -2, "Nikon D70 v1.0", "Fine", -94 }, + { 0xda3a50f1, 419, 611, 2, 1, -2, "Nikon D70 v1.0", "Fine", -94 }, + { 0xd8e45108, 449, 668, 2, 1, -2, "Nikon D70 v1.0", "Fine", -93 }, + { 0x8a62bf3c, 506, 775, 2, 1, -2, "Nikon D70 v1.0", "Fine", -93 }, + { 0xc3108c99, 529, 781, 2, 1, -2, "Nikon D70 v1.0", "Fine", -92 }, + { 0xeabc51a5, 261, 389, 2, 1, -2, "Nikon D50", "Normal", -96 }, + { 0x0cddf617, 345, 499, 2, 1, -2, "Nikon D50", "Normal", -95 }, + { 0x2b3b6401, 855, 1279, 2, 1, -2, "Nikon D40", "?", -88 }, + { 0x5d1ca944, 667, 999, 2, 1, -3, "Nikon E4300", "Normal", 91 }, + { 0xabcbdc47, 736, 1110, 2, 1, -3, "Nikon E4300", "Normal", 90 }, + { 0x10b2ad77, 884, 1332, 2, 1, -3, "Nikon E4300", "Normal", 88 }, + { 0x0a82648b, 64, 64, 1, 1, -2, "Nikon Browser 6", "High quality", 100 }, + { 0xb091eaf2, 779, 1164, 1, 1, -2, "Nikon Browser 6 or PictureProject 1.6", "Standard quality", -89 }, + { 0x1a856066, 1697, 2554, 2, 1, -2, "Nikon Browser 6", "Standard eq", -76 }, + { 0xdf0774bd, 2746, 5112, 2, 2, -2, "Nikon Browser 6", "Standard compression", -57 }, + { 0xe2fd6fb9, 8024, 12006, 2, 2, -2, "Nikon Browser 6", "Maximum compression", -22 }, + { 0x17816eb1, 736, 1110, 2, 2, -2, "Olympus Camedia Master", "High quality?", 90 }, + { 0x96129a0d, 1477, 2221, 2, 2, -2, "Olympus u710,S710", "Super high quality?", 80 }, + { 0x824f84b9, 437, 617, 2, 1, -2, "Olympus u30D,S410D,u410D", "High quality", -94 }, + { 0x1b050d58, 447, 670, 2, 1, -2, "Olympus u30D,S410D,u410D", "High quality", -93 }, + { 0x1b050d58, 447, 670, 2, 1, -2, "Olympus u30D,S410D,u410D", "High quality", -93 }, + { 0x68058c37, 814, 1223, 2, 1, -3, "Olympus C960Z,D460Z", "Standard quality", 89 }, + { 0x10b2ad77, 884, 1332, 2, 1, -3, "Olympus C211Z", "Standard quality", 88 }, + { 0x0f5fa4cb, 1552, 2336, 2, 1, -3, "Olympus C990Z,D490Z", "High quality", 79 }, + { 0xf51554a8, 261, 392, 2, 1, -2, "Panasonic DMC-FZ5", "High", -96 }, + { 0xf01efe6e, 251, 392, 2, 1, -2, "Panasonic DMC-FZ30", "High", -96 }, + { 0x08064360, 280, 445, 2, 1, -2, "Panasonic DMC-FZ30", "High", -96 }, + { 0x05831bbb, 304, 448, 2, 1, -2, "Panasonic DMC-FZ30", "High", -95 }, + { 0xe6c08bea, 316, 499, 2, 1, -2, "Panasonic DMC-FZ30", "High", -95 }, + { 0xcb5f5f7d, 332, 550, 2, 1, -2, "Panasonic DMC-FZ30", "High", -95 }, + { 0xb53cf359, 355, 555, 2, 1, -2, "Panasonic DMC-FZ30", "High", -95 }, + { 0xdbcd2690, 375, 606, 2, 1, -2, "Panasonic DMC-FZ30", "High", -94 }, + { 0x594a3212, 400, 615, 2, 1, -2, "Panasonic DMC-FZ30", "High", -94 }, + { 0xde23f16a, 420, 667, 2, 1, -2, "Panasonic DMC-FZ30", "High", -94 }, + { 0xc0a43b37, 501, 775, 2, 1, -2, "Panasonic DMC-FZ30", "High", -93 }, + { 0xc298e887, 577, 891, 2, 1, -2, "Panasonic DMC-FZ30", "High", -92 }, + { 0x039b6bc2, 324, 499, 2, 1, 2, "Ricoh Caplio R1V", "?", -95 }, + { 0xf60dc348, 274, 443, 2, 1, 2, "Roxio PhotoSuite", "?", -96 }, + { 0xc6f47fa4, 634, 943, 2, 1, -2, "Samsung Digimax V3", "?", -91 }, + { 0xb9284f39, 1313, 1997, 2, 1, -2, "Samsung Digimax V3", "?", -82 }, + { 0x5dedca50, 218, 331, 2, 1, -2, "Samsung Digimax S600", "?", -97 }, + { 0x095451e2, 258, 389, 2, 1, -2, "Sony Cybershot", "?", -96 }, + { 0x4d981764, 86, 115, 2, 1, -2, "Sony DSC-W55", "Fine", 99 }, + { 0x6d2b20ce, 122, 169, 2, 1, -2, "Sony DSC-F828, DSC-F88", "?", -98 }, + { 0x29e095c5, 221, 333, 2, 1, -2, "Sony DSC-W30, DSC-W50, DSC-H2, DSC-H5", "?", 97 }, + { 0x59e5c5bc, 518, 779, 2, 1, -2, "Sony DSC-W70", "?", 93 }, + { 0x96129a0d, 1477, 2221, 2, 2, -2, "Sony DSC-W30, DSC-P43, DSC-S600", "?", 80 }, + { 0xa4d9a6d9, 324, 682, 2, 1, -2, "Sony DSLR-A100", "?", -94 }, + { 0x17816eb1, 736, 1110, 2, 1, -2, "SonyEricsson K750i", "Fine", 90 }, + { 0x10b035e9, 1858, 2780, 2, 1, -2, "SonyEricsson K750i or W200i", "Normal", 75 }, + { 0x1b0ad9d5, 836, 1094, 2, 2, -2, "SonyEricsson K750i", "Panorama fine", -89 }, + { 0x1cd8bb9f, 1672, 2188, 2, 2, -2, "SonyEricsson K750i", "Panorama normal", -79 }, + { 0x81d174af, 361, 555, 2, 1, -2, "SonyEricsson K750i", "?", -95 }, + { 0x991408d7, 433, 667, 2, 1, -2, "SonyEricsson K750i", "?", -94 }, + { 0x00034978, 954, 1443, 2, 1, -2, "SonyEricsson K750i", "?", -87 }, + { 0xd27667ab, 1024, 1504, 2, 1, -2, "SonyEricsson K750i", "?", -86 }, + { 0x94e96153, 1097, 1615, 2, 1, -2, "SonyEricsson K750i", "?", -85 }, + { 0xf524688a, 1168, 1727, 2, 1, -2, "SonyEricsson K750i", "?", -84 }, + { 0x5e5e4237, 1324, 2000, 2, 1, -2, "SonyEricsson K750i", "?", -82 }, + { 0x2e94a836, 1473, 2170, 2, 1, -2, "SonyEricsson K750i", "?", -80 }, + { 0xdd957ed4, 1615, 2394, 2, 1, -2, "SonyEricsson K750i", "?", -78 }, + { 0x4147561e, 1759, 2612, 2, 1, -2, "SonyEricsson K750i", "?", -76 }, + { 0x6f5af2b1, 1491, 1491, 2, 1, -2, "SonyEricsson Z600", "Default", -83 }, + { 0x641ee862, 2211, 3284, 2, 1, -2, "Trust 760 Powerc@m", "?", 70 }, + { 0x0bd95282, 2211, 3284, 1, 2, -2, "Trust 760 Powerc@m", "?", 70 }, + { 0xe9814c86, 1830, 2725, 1, 1, 2, "Xing VT-Compress", "?", -75 }, +}; + +typedef struct +{ + guint32 hashval; + gint subsmp_h; + gint subsmp_v; + gint num_quant_tables; + gint ijg_qual; + GSList *files; + guint16 luminance[DCTSIZE2]; + guint16 chrominance[DCTSIZE2]; +} QuantTableData; + +static GSList *found_tables = NULL; + +#if 0 /* FIXME ---v-v-v---------------------------------------------v-v-v--- */ + +static guint16 **ijg_luminance = NULL; /* luminance, baseline */ +static guint16 **ijg_chrominance = NULL; /* chrominance, baseline */ +static guint16 **ijg_luminance_nb = NULL; /* luminance, not baseline */ +static guint16 **ijg_chrominance_nb = NULL; /* chrominance, not baseline */ + +/* + * Initialize the IJG quantization tables for each quality setting. + */ +static void +init_ijg_tables (void) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + gint q, i; + + ijg_luminance = g_new (guint16 *, 101); + ijg_chrominance = g_new (guint16 *, 101); + for (q = 0; q <= 100; q++) + { + ijg_luminance[q] = g_new (guint16, DCTSIZE2); + ijg_chrominance[q] = g_new (guint16, DCTSIZE2); + } + + cinfo.err = jpeg_std_error (&jerr); + jpeg_create_compress (&cinfo); + + for (q = 0; q <= 100; q++) + { + jpeg_set_quality (&cinfo, q, TRUE); + for (i = 0; i < DCTSIZE2; i++) + ijg_luminance[q][i] = cinfo.quant_tbl_ptrs[0]->quantval[i]; + for (i = 0; i < DCTSIZE2; i++) + ijg_chrominance[q][i] = cinfo.quant_tbl_ptrs[1]->quantval[i]; + } + for (q = 0; q <= 100; q++) + { + jpeg_set_quality (&cinfo, q, FALSE); + for (i = 0; i < DCTSIZE2; i++) + ijg_luminance_nb[q][i] = cinfo.quant_tbl_ptrs[0]->quantval[i]; + for (i = 0; i < DCTSIZE2; i++) + ijg_chrominance_nb[q][i] = cinfo.quant_tbl_ptrs[1]->quantval[i]; + } + jpeg_destroy_compress (&cinfo); +} + +/* + * Check if two quantization tables are identical. + */ +static gboolean +compare_tables (const guint16 *quant_table1, + const guint16 *quant_table2) +{ + gint i; + + g_return_val_if_fail (quant_table1 != NULL, FALSE); + g_return_val_if_fail (quant_table2 != NULL, FALSE); + + for (i = 0; i < DCTSIZE2; i++) + if (quant_table1[i] != quant_table2[i]) + return FALSE; + return TRUE; +} + +#endif /* FIXME ---^-^-^--------------------------------------------^-^-^--- */ + +/* + * Trivial hash function (simple, but good enough for 1 to 4 * 64 short ints). + */ +static guint32 +hash_quant_tables (struct jpeg_decompress_struct *cinfo) +{ + guint32 hashval; + gint t; + gint i; + + hashval = 11; + for (t = 0; t < 4; t++) + if (cinfo->quant_tbl_ptrs[t]) + for (i = 0; i < DCTSIZE2; i++) + hashval = hashval * 4177 + cinfo->quant_tbl_ptrs[t]->quantval[i]; + return hashval; +} + +static guint32 +hash_transposed_quant_tables (struct jpeg_decompress_struct *cinfo) +{ + guint32 hashval; + gint t; + gint i; + gint j; + + hashval = 11; + for (t = 0; t < 4; t++) + if (cinfo->quant_tbl_ptrs[t]) + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + hashval = hashval * 4177 + cinfo->quant_tbl_ptrs[t]->quantval[j * 8 + + i]; + return hashval; +} + +static void +add_unknown_table (struct jpeg_decompress_struct *cinfo, + gchar *filename) +{ + guint32 hashval; + GSList *list; + QuantTableData *table_data; + gint num_quant_tables; + gint t; + gint i; + + hashval = hash_quant_tables (cinfo); + + /* linear search - the number of unknown tables is usually very small */ + for (list = found_tables; list; list = list->next) + { + table_data = list->data; + if (table_data->hashval == hashval + && table_data->subsmp_h == cinfo->comp_info[0].h_samp_factor + && table_data->subsmp_v == cinfo->comp_info[0].v_samp_factor) + { + /* this unknown table has already been seen in previous files */ + table_data->files = g_slist_prepend (table_data->files, filename); + return; + } + } + + /* not found => new table */ + table_data = g_new (QuantTableData, 1); + table_data->hashval = hashval; + table_data->subsmp_h = cinfo->comp_info[0].h_samp_factor; + table_data->subsmp_v = cinfo->comp_info[0].v_samp_factor; + + num_quant_tables = 0; + for (t = 0; t < 4; t++) + if (cinfo->quant_tbl_ptrs[t]) + num_quant_tables++; + + table_data->num_quant_tables = num_quant_tables; + table_data->ijg_qual = jpeg_detect_quality (cinfo); + table_data->files = g_slist_prepend (NULL, filename); + + if (cinfo->quant_tbl_ptrs[0]) + { + for (i = 0; i < DCTSIZE2; i++) + table_data->luminance[i] = cinfo->quant_tbl_ptrs[0]->quantval[i]; + } + else + { + for (i = 0; i < DCTSIZE2; i++) + table_data->luminance[i] = 0; + } + + if (cinfo->quant_tbl_ptrs[1]) + { + for (i = 0; i < DCTSIZE2; i++) + table_data->chrominance[i] = cinfo->quant_tbl_ptrs[1]->quantval[i]; + } + else + { + for (i = 0; i < DCTSIZE2; i++) + table_data->chrominance[i] = 0; + } + + found_tables = g_slist_prepend (found_tables, table_data); +} + +/* + * Analyze the JPEG quantization tables and return a list of devices or + * software that can generate the same tables and subsampling factors. + */ +static GSList * +detect_source (struct jpeg_decompress_struct *cinfo, + gint num_quant_tables) +{ + guint lum_sum; + guint chrom_sum; + gint i; + GSList *source_list; + + /* compute sum of luminance and chrominance quantization tables */ + lum_sum = 0; + chrom_sum = 0; + if (cinfo->quant_tbl_ptrs[0]) + { + for (i = 0; i < DCTSIZE2; i++) + lum_sum += cinfo->quant_tbl_ptrs[0]->quantval[i]; + } + if (cinfo->quant_tbl_ptrs[1]) + { + for (i = 0; i < DCTSIZE2; i++) + chrom_sum += cinfo->quant_tbl_ptrs[1]->quantval[i]; + } + + /* there can be more than one match (if sampling factors are compatible) */ + source_list = NULL; + if (chrom_sum == 0 && num_quant_tables == 1) + { + /* grayscale */ + for (i = 0; i < G_N_ELEMENTS (quant_info); i++) + { + if (quant_info[i].lum_sum == lum_sum + && (quant_info[i].subsmp_h == 0 + || quant_info[i].subsmp_h + == cinfo->comp_info[0].h_samp_factor) + && (quant_info[i].subsmp_v == 0 + || quant_info[i].subsmp_v + == cinfo->comp_info[0].v_samp_factor) + && quant_info[i].num_quant_tables > 0) + { + source_list = g_slist_append (source_list, + (gpointer) (quant_info + i)); + } + } + } + else + { + /* RGB and other color spaces */ + for (i = 0; i < G_N_ELEMENTS (quant_info); i++) + { + if (quant_info[i].lum_sum == lum_sum + && quant_info[i].chrom_sum == chrom_sum + && (quant_info[i].subsmp_h == 0 + || quant_info[i].subsmp_h + == cinfo->comp_info[0].h_samp_factor) + && (quant_info[i].subsmp_v == 0 + || quant_info[i].subsmp_v + == cinfo->comp_info[0].v_samp_factor) + && (quant_info[i].num_quant_tables == num_quant_tables + || quant_info[i].num_quant_tables == -num_quant_tables)) + { + source_list = g_slist_append (source_list, + (gpointer) (quant_info + i)); + } + } + } + + return source_list; +} + +/* + * ... FIXME: docs + */ +static void +print_summary (struct jpeg_decompress_struct *cinfo, + gint num_quant_tables) +{ + gint quality; + gint i; + GSList *source_list; + + /* detect JPEG quality - test the formula used in the jpeg plug-in */ + quality = jpeg_detect_quality (cinfo); + if (quality > 0) + g_print ("\tQuality: %02d (exact)\n", quality); + else if (quality < 0) + g_print ("\tQuality: %02d (approx)\n", -quality); + else + g_print ("\tQuality: unknown\n"); + + /* JPEG sampling factors */ + g_print ("\tSampling: %dx%d", + cinfo->comp_info[0].h_samp_factor, + cinfo->comp_info[0].v_samp_factor); + if ((cinfo->num_components > 1 && cinfo->num_components != 3) + || cinfo->comp_info[1].h_samp_factor != 1 + || cinfo->comp_info[1].v_samp_factor != 1 + || cinfo->comp_info[2].h_samp_factor != 1 + || cinfo->comp_info[2].v_samp_factor != 1) + { + for (i = 1; i < cinfo->num_components; i++) + g_print (",%dx%d", + cinfo->comp_info[i].h_samp_factor, + cinfo->comp_info[i].v_samp_factor); + } + g_print ("\n"); + + /* Number of quantization tables */ + g_print ("\tQ.tables: %d\n", num_quant_tables); + + source_list = detect_source (cinfo, num_quant_tables); + if (source_list) + { + GSList *l; + guint32 hash; + guint32 hash_t; + + hash = hash_quant_tables (cinfo); + hash_t = hash_transposed_quant_tables (cinfo); + + for (l = source_list; l; l = l->next) + { + QuantInfo *source_info = l->data; + const gchar *comment = ""; + + if (source_info->hash == hash) + comment = ""; + else if (source_info->hash == hash_t) + comment = " (rotated)"; + else if (num_quant_tables == 1) + comment = " (grayscale)"; + else + comment = " (FALSE MATCH)"; + + g_print ("\tSource: %s - %s%s\n", + source_info->source_name, + source_info->setting_name, + comment); + } + g_slist_free (source_list); + } + else + g_print ("\tSource: unknown\n"); +} + +/* + * Print a quantization table as a C array. + */ +static void +print_ctable (gint table_id, + const guint16 *quant_table, + gboolean more) +{ + gint i; + + g_return_if_fail (quant_table != NULL); + if (table_id >= 0) + g_print (" { /* table %d */\n ", table_id); + else + g_print (" {\n "); + for (i = 0; i < DCTSIZE2; i++) + { + if (i == DCTSIZE2 - 1) + g_print ("%3d\n", quant_table[i]); + else if ((i + 1) % DCTSIZE == 0) + g_print ("%3d,\n ", quant_table[i]); + else + g_print ("%3d, ", quant_table[i]); + } + if (more) + g_print (" },\n"); + else + g_print (" }\n"); +} + +/* + * Print one or two quantization tables, two columns. + */ +static void +print_table_2cols (gint table1_id, + const guint16 *quant_table1, + gint table2_id, + const guint16 *quant_table2) +{ + gint i; + gint j; + + if (quant_table2) + g_print ("\tQuantization table %d: Quantization table %d:\n\t", + table1_id, table2_id); + else + g_print ("\tQuantization table %d:\n\t", table1_id); + for (i = 0; i < DCTSIZE; i++) + { + if (quant_table1) + { + for (j = 0; j < DCTSIZE; j++) + { + if (j != DCTSIZE - 1) + g_print ("%3d ", quant_table1[i * DCTSIZE + j]); + else + { + if (quant_table2) + g_print ("%3d | ", quant_table1[i * DCTSIZE + j]); + else if (i != DCTSIZE - 1) + g_print ("%3d\n\t", quant_table1[i * DCTSIZE + j]); + else + g_print ("%3d\n", quant_table1[i * DCTSIZE + j]); + } + } + } + else + { + g_print (" | "); + } + if (quant_table2) + { + for (j = 0; j < DCTSIZE; j++) + { + if (j != DCTSIZE - 1) + g_print ("%3d ", quant_table2[i * DCTSIZE + j]); + else if (i != DCTSIZE - 1) + g_print ("%3d\n\t", quant_table2[i * DCTSIZE + j]); + else + g_print ("%3d\n", quant_table2[i * DCTSIZE + j]); + } + } + } +} + +/* + * Error handling as in the IJG libjpeg example. + */ +typedef struct my_error_mgr +{ + struct jpeg_error_mgr pub; /* "public" fields */ +#ifdef __ia64__ + long double dummy; /* bug #138357 */ +#endif + jmp_buf setjmp_buffer; /* for return to caller */ +} *my_error_ptr; + +static void +my_error_exit (j_common_ptr cinfo) +{ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp (myerr->setjmp_buffer, 1); +} + +/* + * Analyze a JPEG file according to the command-line options. + */ +static gboolean +analyze_file (gchar *filename) +{ + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + FILE *f; + gint i; + gint num_quant_tables; + GSList *source_list; + + if ((f = fopen (filename, "rb")) == NULL) + { + g_printerr ("Cannot open '%s'\n", filename); + return FALSE; + } + + if (option_summary) + g_print ("%s:\n", filename); + + cinfo.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = my_error_exit; + if (setjmp (jerr.setjmp_buffer)) + { + /* if we get here, the JPEG code has signaled an error. */ + jpeg_destroy_decompress (&cinfo); + fclose (f); + return FALSE; + } + jpeg_create_decompress (&cinfo); + + jpeg_stdio_src (&cinfo, f); + + jpeg_read_header (&cinfo, TRUE); + + num_quant_tables = 0; + for (i = 0; i < 4; i++) + if (cinfo.quant_tbl_ptrs[i]) + num_quant_tables++; + + source_list = detect_source (&cinfo, num_quant_tables); + if (! source_list) + { + add_unknown_table (&cinfo, filename); + } + + if (! option_unknown) + { + if (option_summary) + print_summary (&cinfo, num_quant_tables); + + if (option_ctable) + { + g_print (" {\n /* %s */\n \"?\", \"?\",\n %d, %d,\n %d,\n", + filename, + cinfo.comp_info[0].h_samp_factor, + cinfo.comp_info[0].v_samp_factor, + num_quant_tables); + for (i = 0; i < 4; i++) + if (cinfo.quant_tbl_ptrs[i]) + print_ctable (i, cinfo.quant_tbl_ptrs[i]->quantval, + (i < 3) && cinfo.quant_tbl_ptrs[i + 1]); + g_print (" },\n"); + } + + if (option_table_2cols) + { + print_table_2cols (0, cinfo.quant_tbl_ptrs[0]->quantval, + 1, cinfo.quant_tbl_ptrs[1]->quantval); + if (cinfo.quant_tbl_ptrs[2] || cinfo.quant_tbl_ptrs[3]) + print_table_2cols (2, cinfo.quant_tbl_ptrs[2]->quantval, + 3, cinfo.quant_tbl_ptrs[3]->quantval); + } + } + + if (source_list) + g_slist_free (source_list); + + jpeg_destroy_decompress (&cinfo); + fclose (f); + + return TRUE; +} + +/* + * ... FIXME: docs + */ +static void +print_unknown_tables (void) +{ + GSList *list; + GSList *flist; + QuantTableData *table_data; + gint num_files; + gint total_files = 0; + + for (list = found_tables; list; list = list->next) + { + table_data = list->data; + + if (option_ctable) + { + g_print (" {\n"); + num_files = 0; + for (flist = table_data->files; flist; flist = flist->next) + { + g_print(" /* %s */\n", (gchar *)(flist->data)); + num_files++; + } + + { /* FIXME */ + guint lum_sum; + guint chrom_sum; + gint i; + + total_files += num_files; + lum_sum = 0; + chrom_sum = 0; + for (i = 0; i < DCTSIZE2; i++) + lum_sum += table_data->luminance[i]; + for (i = 0; i < DCTSIZE2; i++) + chrom_sum += table_data->chrominance[i]; + g_print (" /* hash 0x%x, IJG %d, lum %d, chrom %d, files: %d */\n", + table_data->hashval, + table_data->ijg_qual, + lum_sum, chrom_sum, + num_files); + + if (chrom_sum == 0 && table_data->num_quant_tables == 1) + { + /* grayscale */ + for (i = 0; i < G_N_ELEMENTS (quant_info); i++) + { + if (quant_info[i].lum_sum == lum_sum + && (quant_info[i].subsmp_h == 0 + || quant_info[i].subsmp_h + == table_data->subsmp_h) + && (quant_info[i].subsmp_v == 0 + || quant_info[i].subsmp_v + == table_data->subsmp_v) + && quant_info[i].num_quant_tables > 0) + { + g_print(" XXX \"%s\", \"%s\",\n", + quant_info[i].source_name, + quant_info[i].setting_name); + } + } + } + else + { + /* RGB and other color spaces */ + for (i = 0; i < G_N_ELEMENTS (quant_info); i++) + { + if (quant_info[i].lum_sum == lum_sum + && quant_info[i].chrom_sum == chrom_sum + && (quant_info[i].subsmp_h == 0 + || quant_info[i].subsmp_h + == table_data->subsmp_h) + && (quant_info[i].subsmp_v == 0 + || quant_info[i].subsmp_v + == table_data->subsmp_v) + && (quant_info[i].num_quant_tables == table_data->num_quant_tables + || quant_info[i].num_quant_tables == -table_data->num_quant_tables)) + { + g_print(" XXX \"%s\", \"%s\",\n", + quant_info[i].source_name, + quant_info[i].setting_name); + } + } + } + } /* FIXME */ + + g_print (" \"?\", \"? (hash %x)\",\n" + " %d, %d,\n %d,\n", + table_data->hashval, + table_data->subsmp_h, + table_data->subsmp_v, + -table_data->num_quant_tables); + print_ctable (-1, table_data->luminance, TRUE); + print_ctable (-1, table_data->chrominance, FALSE); + g_print (" },\n"); + } + } + g_print ("/* TOTAL FILES: %d */\n", total_files); +} + +/* + * Some compiler told me that it needed a function called main()... + */ +int +main (int argc, + char *argv[]) +{ + GOptionContext *context; + GError *error = NULL; + gint i; + + g_set_prgname ("jpegqual"); + + context = + g_option_context_new ("FILE [...] - analyzes JPEG quantization tables"); + g_option_context_add_main_entries (context, option_entries, + NULL /* skip i18n? */); + + if (! g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s\n", error->message); + g_error_free (error); + return EXIT_FAILURE; + } + + if (! filenames) + { + g_printerr ("Missing file name. Try the option --help for help\n"); + return EXIT_FAILURE; + } + + if (!option_summary && !option_ctable && !option_table_2cols) + { + g_printerr ("Missing output option. Assuming that you wanted --summary.\n"); + option_summary = TRUE; + } + + for (i = 0; filenames[i]; i++) + { + if (! analyze_file (filenames[i]) && ! option_ignore_err) + return EXIT_FAILURE; + } + + if (option_unknown && found_tables) + { + print_unknown_tables (); + } + + return EXIT_SUCCESS; +} |