From 2714a3b7bbb209b0bee6333b84846ce3b177d5af Mon Sep 17 00:00:00 2001 From: "pavlov@pavlov.net" Date: Mon, 23 Jul 2007 15:02:17 -0700 Subject: [PATCH] adding color management capabilities -- preffed off. bug 16769. patch from tor. r=bsmedberg, sr=me --- allmakefiles.sh | 8 + config/autoconf.mk.in | 11 +- config/static-config.mk | 1 + config/system-headers | 3 + configure.in | 30 +++ gfx/thebes/public/gfxPlatform.h | 25 +++ gfx/thebes/public/gfxPlatformGtk.h | 3 + gfx/thebes/public/gfxPlatformMac.h | 3 + gfx/thebes/public/gfxWindowsPlatform.h | 2 + gfx/thebes/src/Makefile.in | 2 + gfx/thebes/src/gfxContext.cpp | 24 ++- gfx/thebes/src/gfxPlatform.cpp | 110 +++++++++++ gfx/thebes/src/gfxPlatformGtk.cpp | 129 ++++++++++++ gfx/thebes/src/gfxPlatformMac.cpp | 49 +++++ gfx/thebes/src/gfxWindowsPlatform.cpp | 23 +++ modules/libimg/png/mozpngconf.h | 2 - modules/libpr0n/build/Makefile.in | 2 + modules/libpr0n/decoders/gif/Makefile.in | 1 + modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp | 35 +++- modules/libpr0n/decoders/jpeg/Makefile.in | 3 + modules/libpr0n/decoders/jpeg/iccjpeg.c | 248 ++++++++++++++++++++++++ modules/libpr0n/decoders/jpeg/iccjpeg.h | 73 +++++++ modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp | 136 ++++++++++++- modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h | 7 + modules/libpr0n/decoders/png/Makefile.in | 1 + modules/libpr0n/decoders/png/nsPNGDecoder.cpp | 217 +++++++++++++++++++-- modules/libpr0n/decoders/png/nsPNGDecoder.h | 11 +- modules/libpref/src/init/all.js | 3 + toolkit/toolkit-tiers.mk | 4 + 29 files changed, 1139 insertions(+), 27 deletions(-) create mode 100644 modules/libpr0n/decoders/jpeg/iccjpeg.c create mode 100644 modules/libpr0n/decoders/jpeg/iccjpeg.h diff --git a/allmakefiles.sh b/allmakefiles.sh index 03ba4d5714..dedc68d9d3 100755 --- a/allmakefiles.sh +++ b/allmakefiles.sh @@ -176,6 +176,14 @@ gfx/cairo/glitz/src/wgl/Makefile " fi +if [ !"$MOZ_NATIVE_LCMS" ] ; then +MAKEFILES_gfx="$MAKEFILES_gfx +modules/lcms/Makefile +modules/lcms/include/Makefile +modules/lcms/src/Makefile +" +fi + MAKEFILES_htmlparser=" parser/htmlparser/Makefile parser/htmlparser/robot/Makefile diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index a738547148..6b5e91cb0e 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -263,7 +263,7 @@ OS_LDFLAGS = @LDFLAGS@ OS_COMPILE_CFLAGS = $(OS_CPPFLAGS) @COMPILE_CFLAGS@ OS_COMPILE_CXXFLAGS = $(OS_CPPFLAGS) @COMPILE_CXXFLAGS@ -OS_INCLUDES = $(NSPR_CFLAGS) $(JPEG_CFLAGS) $(PNG_CFLAGS) $(ZLIB_CFLAGS) +OS_INCLUDES = $(NSPR_CFLAGS) $(JPEG_CFLAGS) $(PNG_CFLAGS) $(ZLIB_CFLAGS) $(LCMS_CFLAGS) OS_LIBS = @LIBS@ ACDEFINES = @MOZ_DEFINES@ @@ -406,6 +406,15 @@ PNG_LIBS = @MOZ_PNG_LIBS@ PNG_REQUIRES = png endif +MOZ_NATIVE_LCMS = @MOZ_NATIVE_LCMS@ +LCMS_CFLAGS = @LCMS_CFLAGS@ +LCMS_LIBS = @LCMS_LIBS@ +ifdef MOZ_NATIVE_LCMS +LCMS_REQUIRES = +else +LCMS_REQUIRES = lcms +endif + NSPR_CONFIG = @NSPR_CONFIG@ NSPR_CFLAGS = @NSPR_CFLAGS@ NSPR_LIBS = @NSPR_LIBS@ diff --git a/config/static-config.mk b/config/static-config.mk index 8b9c9f8208..d59f235642 100644 --- a/config/static-config.mk +++ b/config/static-config.mk @@ -69,6 +69,7 @@ STATIC_EXTRA_LIBS += \ $(PNG_LIBS) \ $(JPEG_LIBS) \ $(ZLIB_LIBS) \ + $(LCMS_LIBS) \ $(NULL) ifdef MOZ_PSM diff --git a/config/system-headers b/config/system-headers index b9995484c0..fe998dc985 100644 --- a/config/system-headers +++ b/config/system-headers @@ -937,3 +937,6 @@ png.h #if MOZ_NATIVE_ZLIB==1 zlib.h #endif +#if MOZ_TREE_LCMS==1 +lcms.h +#endif diff --git a/configure.in b/configure.in index 697ac6810f..5e1ab51f99 100644 --- a/configure.in +++ b/configure.in @@ -126,6 +126,7 @@ GCONF_VERSION=1.2.1 LIBGNOME_VERSION=2.0 STARTUP_NOTIFICATION_VERSION=0.8 DBUS_VERSION=0.60 +LCMS_VERSION=1.17 MSMANIFEST_TOOL= @@ -7050,6 +7051,35 @@ AC_SUBST(MOZ_CAIRO_CFLAGS) AC_SUBST(MOZ_CAIRO_LIBS) dnl ======================================================== +dnl Check for lcms +dnl ======================================================== + +MOZ_NATIVE_LCMS= +MOZ_ARG_ENABLE_BOOL(system-lcms, +[ --enable-system-lcms Use system lcms (located with pkgconfig)], +MOZ_NATIVE_LCMS=1, +MOZ_NATIVE_LCMS= ) + +if test -z "$MOZ_NATIVE_LCMS" +then + LCMS_CFLAGS= + if test "$OS_ARCH" = "WINNT"; then + if test -z "$BUILD_STATIC_LIBS" -a -z "$MOZ_ENABLE_LIBXUL"; then + LCMS_CFLAGS=-DLCMS_DLL + fi + LCMS_LIBS='$(LIBXUL_DIST)/lib/mozlcms.lib' + else + LCMS_LIBS='-L$(LIBXUL_DIST)/bin -lmozlcms' + fi +else + PKG_CHECK_MODULES(LCMS, lcms >= $LCMS_VERSION) +fi + +AC_SUBST(MOZ_NATIVE_LCMS) +AC_SUBST(LCMS_CFLAGS) +AC_SUBST(LCMS_LIBS) + +dnl ======================================================== dnl disable xul dnl ======================================================== MOZ_ARG_DISABLE_BOOL(xul, diff --git a/gfx/thebes/public/gfxPlatform.h b/gfx/thebes/public/gfxPlatform.h index 00f7bd3573..bc9cf09669 100644 --- a/gfx/thebes/public/gfxPlatform.h +++ b/gfx/thebes/public/gfxPlatform.h @@ -45,6 +45,9 @@ #include "gfxTypes.h" #include "gfxASurface.h" +typedef void* cmsHPROFILE; +typedef void* cmsHTRANSFORM; + class gfxImageSurface; class gfxFontGroup; struct gfxFontStyle; @@ -135,10 +138,32 @@ public: void GetPrefFonts(const char *aLangGroup, nsString& array, PRBool aAppendUnicode = PR_TRUE); + /** + * Are we going to try color management? + */ + static PRBool IsCMSEnabled(); + + /** + * Return the output device ICC profile. + */ + static cmsHPROFILE GetCMSOutputProfile(); + + /** + * Return sRGB -> output device transform. + */ + static cmsHTRANSFORM GetCMSRGBTransform(); + + /** + * Return sRGBA -> output device transform. + */ + static cmsHTRANSFORM GetCMSRGBATransform(); + protected: gfxPlatform() { } virtual ~gfxPlatform(); +private: + virtual cmsHPROFILE GetPlatformCMSOutputProfile(); }; #endif /* GFX_PLATFORM_H */ diff --git a/gfx/thebes/public/gfxPlatformGtk.h b/gfx/thebes/public/gfxPlatformGtk.h index 154931ad74..01773046f8 100644 --- a/gfx/thebes/public/gfxPlatformGtk.h +++ b/gfx/thebes/public/gfxPlatformGtk.h @@ -88,6 +88,9 @@ protected: static PRInt32 sDPI; static gfxFontconfigUtils *sFontconfigUtils; + +private: + virtual cmsHPROFILE GetPlatformCMSOutputProfile(); }; #endif /* GFX_PLATFORM_GTK_H */ diff --git a/gfx/thebes/public/gfxPlatformMac.h b/gfx/thebes/public/gfxPlatformMac.h index 416397240a..ea725c95a0 100644 --- a/gfx/thebes/public/gfxPlatformMac.h +++ b/gfx/thebes/public/gfxPlatformMac.h @@ -63,6 +63,9 @@ public: const nsACString& aGenericFamily, nsStringArray& aListOfFonts); nsresult UpdateFontList(); + +private: + virtual cmsHPROFILE GetPlatformCMSOutputProfile(); }; #endif /* GFX_PLATFORM_MAC_H */ diff --git a/gfx/thebes/public/gfxWindowsPlatform.h b/gfx/thebes/public/gfxWindowsPlatform.h index 57f966fed6..078395478b 100644 --- a/gfx/thebes/public/gfxWindowsPlatform.h +++ b/gfx/thebes/public/gfxWindowsPlatform.h @@ -107,6 +107,8 @@ private: nsRefPtr& aFontEntry, void* userArg); + virtual cmsHPROFILE GetPlatformCMSOutputProfile(); + nsDataHashtable > mFonts; nsDataHashtable > mFontAliases; nsDataHashtable > mFontSubstitutes; diff --git a/gfx/thebes/src/Makefile.in b/gfx/thebes/src/Makefile.in index 10722895ac..ac679d1113 100644 --- a/gfx/thebes/src/Makefile.in +++ b/gfx/thebes/src/Makefile.in @@ -18,6 +18,7 @@ REQUIRES = \ pref \ xpcom \ unicharutil \ + $(LCMS_REQUIRES) \ $(NULL) CPPSRCS = \ @@ -44,6 +45,7 @@ EXTRA_DSO_LDOPTS += \ $(XPCOM_LIBS) \ $(NSPR_LIBS) \ $(ZLIB_LIBS) \ + $(LCMS_LIBS) \ $(NULL) diff --git a/gfx/thebes/src/gfxContext.cpp b/gfx/thebes/src/gfxContext.cpp index 9254c43216..59593926c0 100644 --- a/gfx/thebes/src/gfxContext.cpp +++ b/gfx/thebes/src/gfxContext.cpp @@ -46,6 +46,7 @@ #endif #include "cairo.h" +#include "lcms.h" #include "gfxContext.h" @@ -53,7 +54,7 @@ #include "gfxMatrix.h" #include "gfxASurface.h" #include "gfxPattern.h" - +#include "gfxPlatform.h" gfxContext::gfxContext(gfxASurface *surface) : @@ -606,6 +607,27 @@ gfxContext::GetClipExtents() void gfxContext::SetColor(const gfxRGBA& c) { + if (gfxPlatform::IsCMSEnabled()) { + cmsHTRANSFORM transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { +#ifdef IS_LITTLE_ENDIAN + PRUint32 packed = c.Packed(gfxRGBA::PACKED_ABGR); + cmsDoTransform(transform, + (PRUint8 *)&packed, (PRUint8 *)&packed, + 1); + gfxRGBA cms(packed, gfxRGBA::PACKED_ABGR); +#else + PRUint32 packed = c.Packed(gfxRGBA::PACKED_ARGB); + cmsDoTransform(transform, + (PRUint8 *)&packed + 1, (PRUint8 *)&packed + 1, + 1); + gfxRGBA cms(packed, gfxRGBA::PACKED_ARGB); +#endif + cairo_set_source_rgba(mCairo, cms.r, cms.g, cms.b, cms.a); + return; + } + } + cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a); } diff --git a/gfx/thebes/src/gfxPlatform.cpp b/gfx/thebes/src/gfxPlatform.cpp index ac046b0125..45c6c72fb9 100644 --- a/gfx/thebes/src/gfxPlatform.cpp +++ b/gfx/thebes/src/gfxPlatform.cpp @@ -63,9 +63,16 @@ #endif #include "cairo.h" +#include "lcms.h" + +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" gfxPlatform *gPlatform = nsnull; int gGlitzState = -1; +static cmsHPROFILE gCMSOutputProfile = nsnull; +static cmsHTRANSFORM gCMSRGBTransform = nsnull; +static cmsHTRANSFORM gCMSRGBATransform = nsnull; gfxPlatform* gfxPlatform::GetPlatform() @@ -296,3 +303,106 @@ gfxPlatform::GetPrefFonts(const char *aLangGroup, nsString& aFonts, PRBool aAppe AppendGenericFontFromPref(aFonts, "x-unicode", nsnull); } +PRBool +gfxPlatform::IsCMSEnabled() +{ + static PRBool sEnabled = -1; + if (sEnabled == -1) { + sEnabled = PR_TRUE; + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + PRBool enabled; + nsresult rv = + prefs->GetBoolPref("gfx.color_management.enabled", &enabled); + if (NS_SUCCEEDED(rv)) { + sEnabled = enabled; + } + } + } + return sEnabled; +} + +cmsHPROFILE +gfxPlatform::GetPlatformCMSOutputProfile() +{ + return nsnull; +} + +cmsHPROFILE +gfxPlatform::GetCMSOutputProfile() +{ + if (!gCMSOutputProfile) { + /* Default lcms error action is to abort on error - change */ +#ifdef DEBUG_tor + cmsErrorAction(LCMS_ERROR_SHOW); +#else + cmsErrorAction(LCMS_ERROR_IGNORE); +#endif + + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + nsXPIDLCString fname; + nsresult rv = + prefs->GetCharPref("gfx.color_management.display_profile", + getter_Copies(fname)); + if (NS_SUCCEEDED(rv) && !fname.IsEmpty()) { + gCMSOutputProfile = cmsOpenProfileFromFile(fname, "r"); +#ifdef DEBUG_tor + if (gCMSOutputProfile) + fprintf(stderr, + "ICM profile read from %s successfully\n", + fname.get()); +#endif + } + } + + if (!gCMSOutputProfile) { + gCMSOutputProfile = + gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfile(); + } + + if (!gCMSOutputProfile) { + gCMSOutputProfile = cmsCreate_sRGBProfile(); + } + } + + return gCMSOutputProfile; +} + +cmsHTRANSFORM +gfxPlatform::GetCMSRGBTransform() +{ + if (!gCMSRGBTransform) { + cmsHPROFILE inProfile, outProfile; + outProfile = GetCMSOutputProfile(); + inProfile = cmsCreate_sRGBProfile(); + + if (!inProfile || !outProfile) + return nsnull; + + gCMSRGBTransform = cmsCreateTransform(inProfile, TYPE_RGB_8, + outProfile, TYPE_RGB_8, + INTENT_PERCEPTUAL, 0); + } + + return gCMSRGBTransform; +} + +cmsHTRANSFORM +gfxPlatform::GetCMSRGBATransform() +{ + if (!gCMSRGBATransform) { + cmsHPROFILE inProfile, outProfile; + outProfile = GetCMSOutputProfile(); + inProfile = cmsCreate_sRGBProfile(); + + if (!inProfile || !outProfile) + return nsnull; + + gCMSRGBATransform = cmsCreateTransform(inProfile, TYPE_RGBA_8, + outProfile, TYPE_RGBA_8, + INTENT_PERCEPTUAL, 0); + } + + return gCMSRGBATransform; +} diff --git a/gfx/thebes/src/gfxPlatformGtk.cpp b/gfx/thebes/src/gfxPlatformGtk.cpp index 03b5cb1147..4d5f4e06e3 100644 --- a/gfx/thebes/src/gfxPlatformGtk.cpp +++ b/gfx/thebes/src/gfxPlatformGtk.cpp @@ -68,6 +68,8 @@ #include "nsMathUtils.h" +#include "lcms.h" + PRInt32 gfxPlatformGtk::sDPI = -1; gfxFontconfigUtils *gfxPlatformGtk::sFontconfigUtils = nsnull; @@ -365,3 +367,130 @@ gfxPlatformGtk::InitDPI() } } } + +cmsHPROFILE +gfxPlatformGtk::GetPlatformCMSOutputProfile() +{ + const char EDID1_ATOM_NAME[] = "XFree86_DDC_EDID1_RAWDATA"; + const char ICC_PROFILE_ATOM_NAME[] = "_ICC_PROFILE"; + + Atom edidAtom, iccAtom; + Display *dpy = GDK_DISPLAY(); + Window root = gdk_x11_get_default_root_xwindow(); + + Atom retAtom; + int retFormat; + unsigned long retLength, retAfter; + unsigned char *retProperty ; + + iccAtom = XInternAtom(dpy, ICC_PROFILE_ATOM_NAME, TRUE); + if (iccAtom) { + // read once to get size, once for the data + if (Success == XGetWindowProperty(dpy, root, iccAtom, + 0, 0 /* length */, + False, AnyPropertyType, + &retAtom, &retFormat, &retLength, + &retAfter, &retProperty)) { + XGetWindowProperty(dpy, root, iccAtom, + 0, retLength, + False, AnyPropertyType, + &retAtom, &retFormat, &retLength, + &retAfter, &retProperty); + + cmsHPROFILE profile = + cmsOpenProfileFromMem(retProperty, retLength); + + XFree(retProperty); + + if (profile) { +#ifdef DEBUG_tor + fprintf(stderr, + "ICM profile read from %s successfully\n", + ICC_PROFILE_ATOM_NAME); +#endif + return profile; + } + } + } + + edidAtom = XInternAtom(dpy, EDID1_ATOM_NAME, TRUE); + if (edidAtom) { + if (Success == XGetWindowProperty(dpy, root, edidAtom, 0, 32, + False, AnyPropertyType, + &retAtom, &retFormat, &retLength, + &retAfter, &retProperty)) { + double gamma; + cmsCIExyY whitePoint; + cmsCIExyYTRIPLE primaries; + + if (retLength != 128) { +#ifdef DEBUG_tor + fprintf(stderr, "Short EDID data\n"); +#endif + return nsnull; + } + + // Format documented in "VESA E-EDID Implementation Guide" + + gamma = (100 + retProperty[0x17]) / 100.0; + whitePoint.x = ((retProperty[0x21] << 2) | + (retProperty[0x1a] >> 2 & 3)) / 1024.0; + whitePoint.y = ((retProperty[0x22] << 2) | + (retProperty[0x1a] >> 0 & 3)) / 1024.0; + whitePoint.Y = 1.0; + + primaries.Red.x = ((retProperty[0x1b] << 2) | + (retProperty[0x19] >> 6 & 3)) / 1024.0; + primaries.Red.y = ((retProperty[0x1c] << 2) | + (retProperty[0x19] >> 4 & 3)) / 1024.0; + primaries.Red.Y = 1.0; + + primaries.Green.x = ((retProperty[0x1d] << 2) | + (retProperty[0x19] >> 2 & 3)) / 1024.0; + primaries.Green.y = ((retProperty[0x1e] << 2) | + (retProperty[0x19] >> 0 & 3)) / 1024.0; + primaries.Green.Y = 1.0; + + primaries.Blue.x = ((retProperty[0x1f] << 2) | + (retProperty[0x1a] >> 6 & 3)) / 1024.0; + primaries.Blue.y = ((retProperty[0x20] << 2) | + (retProperty[0x1a] >> 4 & 3)) / 1024.0; + primaries.Blue.Y = 1.0; + + XFree(retProperty); + +#ifdef DEBUG_tor + fprintf(stderr, "EDID gamma: %f\n", gamma); + fprintf(stderr, "EDID whitepoint: %f %f %f\n", + whitePoint.x, whitePoint.y, whitePoint.Y); + fprintf(stderr, "EDID primaries: [%f %f %f] [%f %f %f] [%f %f %f]\n", + primaries.Red.x, primaries.Red.y, primaries.Red.Y, + primaries.Green.x, primaries.Green.y, primaries.Green.Y, + primaries.Blue.x, primaries.Blue.y, primaries.Blue.Y); +#endif + + LPGAMMATABLE gammaTable[3]; + gammaTable[0] = gammaTable[1] = gammaTable[2] = + cmsBuildGamma(256, gamma); + + if (!gammaTable[0]) + return nsnull; + + cmsHPROFILE profile = + cmsCreateRGBProfile(&whitePoint, &primaries, gammaTable); + + cmsFreeGamma(gammaTable[0]); + +#ifdef DEBUG_tor + if (profile) { + fprintf(stderr, + "ICM profile read from %s successfully\n", + EDID1_ATOM_NAME); + } +#endif + + return profile; + } + } + return nsnull; +} diff --git a/gfx/thebes/src/gfxPlatformMac.cpp b/gfx/thebes/src/gfxPlatformMac.cpp index b569c0a465..6c76425f2e 100644 --- a/gfx/thebes/src/gfxPlatformMac.cpp +++ b/gfx/thebes/src/gfxPlatformMac.cpp @@ -49,6 +49,8 @@ #include "glitz-agl.h" #endif +#include "lcms.h" + gfxPlatformMac::gfxPlatformMac() { #ifdef MOZ_ENABLE_GLITZ @@ -174,3 +176,50 @@ gfxPlatformMac::UpdateFontList() gfxQuartzFontCache::SharedFontCache()->UpdateFontList(); return NS_OK; } + +cmsHPROFILE +gfxPlatformMac::GetPlatformCMSOutputProfile() +{ + CMProfileLocation device; + CMError err = CMGetDeviceProfile(cmDisplayDeviceClass, + cmDefaultDeviceID, + cmDefaultProfileID, + &device); + if (err != noErr) + return nsnull; + + cmsHPROFILE profile = nsnull; + switch (device.locType) { + case cmFileBasedProfile: { + FSRef fsRef; + if (!FSpMakeFSRef(&device.u.fileLoc.spec, &fsRef)) { + char path[512]; + if (!FSRefMakePath(&fsRef, (UInt8*)(path), sizeof(path))) { + profile = cmsOpenProfileFromFile(path, "r"); +#ifdef DEBUG_tor + if (profile) + fprintf(stderr, + "ICM profile read from %s fileLoc successfully\n", path); +#endif + } + } + break; + } + case cmPathBasedProfile: + profile = cmsOpenProfileFromFile(device.u.pathLoc.path, "r"); +#ifdef DEBUG_tor + if (profile) + fprintf(stderr, + "ICM profile read from %s pathLoc successfully\n", + device.u.pathLoc.path); +#endif + break; + default: +#ifdef DEBUG_tor + fprintf(stderr, "Unhandled ColorSync profile location\n"); +#endif + break; + } + + return profile; +} diff --git a/gfx/thebes/src/gfxWindowsPlatform.cpp b/gfx/thebes/src/gfxWindowsPlatform.cpp index d897d3f67f..5053a69485 100644 --- a/gfx/thebes/src/gfxWindowsPlatform.cpp +++ b/gfx/thebes/src/gfxWindowsPlatform.cpp @@ -54,6 +54,8 @@ #include +#include "lcms.h" + //#define DEBUG_CMAP_SIZE 1 /* Define this if we want to update the unicode range bitsets based @@ -742,3 +744,24 @@ gfxWindowsPlatform::FindFontEntry(const nsAString& aName) } return fe.get(); } + +cmsHPROFILE +gfxWindowsPlatform::GetPlatformCMSOutputProfile() +{ + WCHAR str[1024+1]; + DWORD size = 1024; + + HDC dc = GetDC(nsnull); + GetICMProfileW(dc, &size, (LPWSTR)&str); + ReleaseDC(nsnull, dc); + + cmsHPROFILE profile = + cmsOpenProfileFromFile(NS_ConvertUTF16toUTF8(str).get(), "r"); +#ifdef DEBUG_tor + if (profile) + fprintf(stderr, + "ICM profile read from %s successfully\n", + NS_ConvertUTF16toUTF8(str).get()); +#endif + return profile; +} diff --git a/modules/libimg/png/mozpngconf.h b/modules/libimg/png/mozpngconf.h index 6dbb19aa1d..9e6770e95e 100644 --- a/modules/libimg/png/mozpngconf.h +++ b/modules/libimg/png/mozpngconf.h @@ -57,9 +57,7 @@ #define PNG_NO_READ_RGB_TO_GRAY #define PNG_NO_READ_USER_TRANSFORM #define PNG_NO_READ_bKGD -#define PNG_NO_READ_cHRM #define PNG_NO_READ_hIST -#define PNG_NO_READ_iCCP #define PNG_NO_READ_pCAL #define PNG_NO_READ_pHYs #define PNG_NO_READ_sBIT diff --git a/modules/libpr0n/build/Makefile.in b/modules/libpr0n/build/Makefile.in index aff43d6bf9..4a851f2c7b 100644 --- a/modules/libpr0n/build/Makefile.in +++ b/modules/libpr0n/build/Makefile.in @@ -61,6 +61,7 @@ REQUIRES = xpcom \ $(JPEG_REQUIRES) \ $(PNG_REQUIRES) \ $(ZLIB_REQUIRES) \ + $(LCMS_REQUIRES) \ $(NULL) CPPSRCS = \ @@ -96,6 +97,7 @@ EXTRA_DSO_LDOPTS = \ $(LIBS_DIR) \ $(JPEG_LIBS) \ $(PNG_LIBS) $(ZLIB_LIBS) \ + $(LCMS_LIBS) \ $(EXTRA_DSO_LIBS) \ $(MOZ_COMPONENT_LIBS) \ $(NULL) diff --git a/modules/libpr0n/decoders/gif/Makefile.in b/modules/libpr0n/decoders/gif/Makefile.in index 4b0947ff35..c4452e533f 100644 --- a/modules/libpr0n/decoders/gif/Makefile.in +++ b/modules/libpr0n/decoders/gif/Makefile.in @@ -54,6 +54,7 @@ REQUIRES = xpcom \ thebes \ cairo \ imglib2 \ + $(LCMS_REQUIRES) \ $(NULL) CPPSRCS = nsGIFDecoder2.cpp diff --git a/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp b/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp index 1a7f05f104..f576baa481 100644 --- a/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp +++ b/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp @@ -86,6 +86,9 @@ mailing address. #include "imgContainer.h" +#include "gfxPlatform.h" +#include "lcms.h" + /* * GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's' * @@ -817,17 +820,29 @@ nsresult nsGIFDecoder2::GifWrite(const PRUint8 *buf, PRUint32 len) GETN(size, gif_global_colormap); break; } - // Copy everything and directly go to gif_lzw_start + // Copy everything, go to colormap state to do CMS correction memcpy(mGIFStruct.global_colormap, buf, size); buf += size; len -= size; + GETN(0, gif_global_colormap); + break; } GETN(1, gif_image_start); break; case gif_global_colormap: - // Everything is already copied into global_colormap + if (gfxPlatform::IsCMSEnabled()) { + // Everything is already copied into global_colormap + cmsHTRANSFORM transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { + cmsDoTransform(transform, + mGIFStruct.global_colormap, + mGIFStruct.global_colormap, + mGIFStruct.global_colormap_size); + } + } + GETN(1, gif_image_start); break; @@ -1069,10 +1084,12 @@ nsresult nsGIFDecoder2::GifWrite(const PRUint8 *buf, PRUint32 len) GETN(size, gif_image_colormap); break; } - // Copy everything and directly go to gif_lzw_start + // Copy everything, go to colormap state to do CMS correction memcpy(mGIFStruct.local_colormap, buf, size); buf += size; len -= size; + GETN(0, gif_image_colormap); + break; } else { /* Switch back to the global palette */ mGIFStruct.is_local_colormap_defined = PR_FALSE; @@ -1081,7 +1098,17 @@ nsresult nsGIFDecoder2::GifWrite(const PRUint8 *buf, PRUint32 len) break; case gif_image_colormap: - // Everything is already copied into local_colormap + if (gfxPlatform::IsCMSEnabled()) { + // Everything is already copied into local_colormap + cmsHTRANSFORM transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { + cmsDoTransform(transform, + mGIFStruct.local_colormap, + mGIFStruct.local_colormap, + mGIFStruct.local_colormap_size); + } + } + GETN(1, gif_lzw_start); break; diff --git a/modules/libpr0n/decoders/jpeg/Makefile.in b/modules/libpr0n/decoders/jpeg/Makefile.in index ad9bde9132..76246da8e6 100644 --- a/modules/libpr0n/decoders/jpeg/Makefile.in +++ b/modules/libpr0n/decoders/jpeg/Makefile.in @@ -54,9 +54,12 @@ REQUIRES = xpcom \ thebes \ imglib2 \ $(JPEG_REQUIRES) \ + $(LCMS_REQUIRES) \ $(NULL) CPPSRCS = nsJPEGDecoder.cpp +CSRCS = iccjpeg.c + include $(topsrcdir)/config/rules.mk diff --git a/modules/libpr0n/decoders/jpeg/iccjpeg.c b/modules/libpr0n/decoders/jpeg/iccjpeg.c new file mode 100644 index 0000000000..aa96c8bebf --- /dev/null +++ b/modules/libpr0n/decoders/jpeg/iccjpeg.c @@ -0,0 +1,248 @@ +/* + * iccprofile.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. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. If you need to do that, + * change all the "unsigned int" variables to "INT32". You'll also need + * to find a malloc() replacement that can allocate more than 64K. + */ + +#include "iccjpeg.h" +#include /* define malloc() */ + + +/* + * 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 +write_icc_profile (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int 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++; + } +} + + +/* + * Prepare for reading an ICC profile + */ + +void +setup_read_icc_profile (j_decompress_ptr cinfo) +{ + /* Tell the library to keep any APP2 data it may find */ + jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF); +} + + +/* + * 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. + * + * 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.) + * + * NOTE: if the file contains invalid ICC APP2 markers, we just silently + * return FALSE. You might want to issue an error message instead. + */ + +boolean +read_icc_profile (j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *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 = (JOCTET *) 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/modules/libpr0n/decoders/jpeg/iccjpeg.h b/modules/libpr0n/decoders/jpeg/iccjpeg.h new file mode 100644 index 0000000000..5e1888d9ef --- /dev/null +++ b/modules/libpr0n/decoders/jpeg/iccjpeg.h @@ -0,0 +1,73 @@ +/* + * iccprofile.h + * + * 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. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. See iccprofile.c + * for details. + */ + +#include /* needed to define "FILE", "NULL" */ +#include "jpeglib.h" + + +/* + * 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.) + */ + +extern void write_icc_profile JPP((j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int 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(), + * call setup_read_icc_profile(). This routine tells the IJG library + * to save in memory any APP2 markers it may find in the file. + * + * 2. After jpeg_read_header(), call read_icc_profile() to find out + * whether there was a profile and obtain it if so. + */ + + +/* + * Prepare for reading an ICC profile + */ + +extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo)); + + +/* + * 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.) + */ + +extern boolean read_icc_profile JPP((j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len)); diff --git a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp index c5616a2c1e..5d582baa20 100644 --- a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp +++ b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp @@ -53,6 +53,12 @@ #include "jerror.h" +#include "gfxPlatform.h" + +extern "C" { +#include "iccjpeg.h" +} + NS_IMPL_ISUPPORTS1(nsJPEGDecoder, imgIDecoder) #if defined(PR_LOGGING) @@ -89,12 +95,19 @@ nsJPEGDecoder::nsJPEGDecoder() mBackBuffer = nsnull; mBackBufferLen = mBackBufferSize = mBackBufferUnreadLen = 0; + + mInProfile = nsnull; + mTransform = nsnull; } nsJPEGDecoder::~nsJPEGDecoder() { PR_FREEIF(mBuffer); PR_FREEIF(mBackBuffer); + if (mTransform) + cmsDeleteTransform(mTransform); + if (mInProfile) + cmsCloseProfile(mInProfile); } @@ -132,6 +145,10 @@ NS_IMETHODIMP nsJPEGDecoder::Init(imgILoad *aLoad) mSourceMgr.resync_to_restart = jpeg_resync_to_restart; mSourceMgr.term_source = term_source; + /* Record app markers for ICC data */ + for (PRUint32 m = 0; m < 16; m++) + jpeg_save_markers(&mInfo, JPEG_APP0 + m, 0xFFFF); + return NS_OK; } @@ -223,18 +240,107 @@ NS_IMETHODIMP nsJPEGDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count, PR if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) return NS_OK; /* I/O suspension */ - /* let libjpeg take care of gray->RGB and YCbCr->RGB conversions */ - switch (mInfo.jpeg_color_space) { + JOCTET *profile; + PRUint32 profileLength; + + if (gfxPlatform::IsCMSEnabled() && + read_icc_profile(&mInfo, &profile, &profileLength) && + (mInProfile = cmsOpenProfileFromMem(profile, profileLength)) != NULL) { + free(profile); + + PRUint32 profileSpace = cmsGetColorSpace(mInProfile); + PRBool mismatch = PR_FALSE; + +#ifdef DEBUG_tor + fprintf(stderr, "JPEG profileSpace: 0x%08X\n", profileSpace); +#endif + switch (mInfo.jpeg_color_space) { case JCS_GRAYSCALE: + if (profileSpace == icSigRgbData) + mInfo.out_color_space = JCS_RGB; + else if (profileSpace != icSigGrayData) + mismatch = PR_TRUE; + break; case JCS_RGB: + if (profileSpace != icSigRgbData) + mismatch = PR_TRUE; + break; case JCS_YCbCr: - mInfo.out_color_space = JCS_RGB; + if (profileSpace == icSigRgbData) + mInfo.out_color_space = JCS_RGB; + else if (profileSpace != icSigYCbCrData) + mismatch = PR_TRUE; break; case JCS_CMYK: case JCS_YCCK: + if (profileSpace == icSigCmykData) + mInfo.out_color_space = JCS_CMYK; + else + mismatch = PR_TRUE; + break; + default: + mState = JPEG_ERROR; + return NS_ERROR_UNEXPECTED; + } + + if (!mismatch) { + PRUint32 space, channels; + switch (mInfo.out_color_space) { + case JCS_GRAYSCALE: + space = PT_GRAY; + channels = 1; + break; + case JCS_RGB: + space = PT_RGB; + channels = 3; + break; + case JCS_YCbCr: + space = PT_YCbCr; + channels = 3; + case JCS_CMYK: + space = PT_CMYK; + channels = 4; + break; + default: + mState = JPEG_ERROR; + return NS_ERROR_UNEXPECTED; + } + + PRUint32 type = + COLORSPACE_SH(space) | + CHANNELS_SH(channels) | + BYTES_SH(1); + + /* Adobe Photoshop writes CMYK files with inverted data */ + if (mInfo.jpeg_color_space == JCS_CMYK) + type |= FLAVOR_SH(mInfo.saw_Adobe_marker ? 1 : 0); + + if (gfxPlatform::GetCMSOutputProfile()) + mTransform = cmsCreateTransform(mInProfile, + type, + gfxPlatform::GetCMSOutputProfile(), + TYPE_RGB_8, + cmsTakeRenderingIntent(mInProfile), + 0); + } else { +#ifdef DEBUG_tor + fprintf(stderr, "ICM profile colorspace mismatch\n"); +#endif + } + } + + if (!mTransform) { + switch (mInfo.jpeg_color_space) { + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + mInfo.out_color_space = JCS_RGB; + break; default: mState = JPEG_ERROR; return NS_ERROR_UNEXPECTED; + break; + } } /* @@ -479,6 +585,30 @@ nsJPEGDecoder::OutputScanlines() break; } + if (mTransform) { + if (mInfo.out_color_space == JCS_GRAYSCALE) { + /* move gray data to end of mSample array so + cmsDoTransform can do in-place transform */ + memcpy(mSamples[0] + 2 * mInfo.output_width, + mSamples[0], + mInfo.output_width); + cmsDoTransform(mTransform, + mSamples[0] + 2 * mInfo.output_width, mSamples[0], + mInfo.output_width); + } else + cmsDoTransform(mTransform, + mSamples[0], mSamples[0], + mInfo.output_width); + } else { + /* No embedded ICC profile - treat as sRGB */ + cmsHTRANSFORM transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { + cmsDoTransform(transform, + mSamples[0], mSamples[0], + mInfo.output_width); + } + } + // offset is in Cairo pixels (PRUint32) PRUint32 offset = (mInfo.output_scanline - 1) * mInfo.output_width; PRUint32 *ptrOutputBuf = ((PRUint32*)imageData) + offset; diff --git a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h index 05e75821c9..83bb818898 100644 --- a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h +++ b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h @@ -50,6 +50,7 @@ #include "imgILoad.h" #include "nsIInputStream.h" #include "nsIPipe.h" +#include "lcms.h" extern "C" { #include "jpeglib.h" @@ -118,6 +119,12 @@ public: PRUint32 mBackBufferSize; // size in bytes what mBackBuffer was created with PRUint32 mBackBufferUnreadLen; // amount of data currently in mBackBuffer + JOCTET *mProfile; + PRUint32 mProfileLength; + + cmsHPROFILE mInProfile; + cmsHTRANSFORM mTransform; + PRPackedBool mReading; }; diff --git a/modules/libpr0n/decoders/png/Makefile.in b/modules/libpr0n/decoders/png/Makefile.in index c1eda5c490..06bf44af95 100644 --- a/modules/libpr0n/decoders/png/Makefile.in +++ b/modules/libpr0n/decoders/png/Makefile.in @@ -57,6 +57,7 @@ REQUIRES = xpcom \ imglib2 \ $(PNG_REQUIRES) \ $(ZLIB_REQUIRES) \ + $(LCMS_REQUIRES) \ $(NULL) CPPSRCS = nsPNGDecoder.cpp diff --git a/modules/libpr0n/decoders/png/nsPNGDecoder.cpp b/modules/libpr0n/decoders/png/nsPNGDecoder.cpp index 8be507173a..85f0216e98 100644 --- a/modules/libpr0n/decoders/png/nsPNGDecoder.cpp +++ b/modules/libpr0n/decoders/png/nsPNGDecoder.cpp @@ -56,6 +56,8 @@ #include "nspr.h" #include "png.h" +#include "gfxPlatform.h" + // for nsPNGDecoder.apngFlags enum { FRAME_HIDDEN = 0x01 @@ -77,16 +79,25 @@ NS_IMPL_ISUPPORTS1(nsPNGDecoder, imgIDecoder) nsPNGDecoder::nsPNGDecoder() : mPNG(nsnull), mInfo(nsnull), - apngFlags(0), - interlacebuf(nsnull), ibpr(0), - mError(PR_FALSE) + mCMSLine(nsnull), interlacebuf(nsnull), + mInProfile(nsnull), mTransform(nsnull), + ibpr(0), apngFlags(0), mChannels(0), mError(PR_FALSE) { } nsPNGDecoder::~nsPNGDecoder() { + if (mCMSLine) + nsMemory::Free(mCMSLine); if (interlacebuf) nsMemory::Free(interlacebuf); + if (mInProfile) { + cmsCloseProfile(mInProfile); + + /* mTransform belongs to us only if mInProfile is non-null */ + if (mTransform) + cmsDeleteTransform(mTransform); + } } // CreateFrame() is used for both simple and animated images @@ -153,9 +164,7 @@ NS_IMETHODIMP nsPNGDecoder::Init(imgILoad *aLoad) #if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED) static png_byte unused_chunks[]= { 98, 75, 71, 68, '\0', /* bKGD */ - 99, 72, 82, 77, '\0', /* cHRM */ 104, 73, 83, 84, '\0', /* hIST */ - 105, 67, 67, 80, '\0', /* iCCP */ 105, 84, 88, 116, '\0', /* iTXt */ 111, 70, 70, 115, '\0', /* oFFs */ 112, 67, 65, 76, '\0', /* pCAL */ @@ -266,6 +275,119 @@ NS_IMETHODIMP nsPNGDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRU return rv; } +// Adapted from http://www.littlecms.com/pngchrm.c example code +static cmsHPROFILE +PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr, + int color_type, PRUint32 *inType, PRUint32 *intent) +{ + cmsHPROFILE profile = nsnull; + *intent = INTENT_PERCEPTUAL; // XXX: should this be the default? + + // First try to see if iCCP chunk is present + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { + png_uint_32 profileLen; + char *profileData, *profileName; + int compression; + + png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, + &profileData, &profileLen); + + profile = cmsOpenProfileFromMem(profileData, profileLen); + PRUint32 profileSpace = cmsGetColorSpace(profile); + +#ifdef DEBUG_tor + fprintf(stderr, "PNG profileSpace: 0x%08X\n", profileSpace); +#endif + + PRBool mismatch = PR_FALSE; + if (color_type & PNG_COLOR_MASK_COLOR) { + if (profileSpace != icSigRgbData) + mismatch = PR_TRUE; + } else { + if (profileSpace == icSigRgbData) + png_set_gray_to_rgb(png_ptr); + else if (profileSpace != icSigGrayData) + mismatch = PR_TRUE; + } + + if (mismatch) { + cmsCloseProfile(profile); + profile = nsnull; + } else { + *intent = cmsTakeRenderingIntent(profile); + } + } + + // Check sRGB chunk + if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { + profile = cmsCreate_sRGBProfile(); + + if (profile) { + int fileIntent; + png_get_sRGB(png_ptr, info_ptr, &fileIntent); + PRUint32 map[] = { INTENT_PERCEPTUAL, INTENT_RELATIVE_COLORIMETRIC, + INTENT_SATURATION, INTENT_ABSOLUTE_COLORIMETRIC }; + *intent = map[fileIntent]; + } + } + + // Check gAMA/cHRM chunks + if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA)) { + cmsCIExyY whitePoint = {0.3127, 0.3290, 1.0}; // D65 + cmsCIExyYTRIPLE primaries = { + {0.6400, 0.3300, 1.0}, + {0.3000, 0.6000, 1.0}, + {0.1500, 0.0600, 1.0} + }; + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) { + png_get_cHRM(png_ptr, info_ptr, + &whitePoint.x, &whitePoint.y, + &primaries.Red.x, &primaries.Red.y, + &primaries.Green.x, &primaries.Green.y, + &primaries.Blue.x, &primaries.Blue.y); + + whitePoint.Y = + primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0; + } + + double gammaOfFile; + LPGAMMATABLE gammaTable[3]; + + png_get_gAMA(png_ptr, info_ptr, &gammaOfFile); + + gammaTable[0] = gammaTable[1] = gammaTable[2] = + cmsBuildGamma(256, 1/gammaOfFile); + + if (!gammaTable[0]) + return nsnull; + + profile = cmsCreateRGBProfile(&whitePoint, &primaries, gammaTable); + + if (profile && !(color_type & PNG_COLOR_MASK_COLOR)) + png_set_gray_to_rgb(png_ptr); + + cmsFreeGamma(gammaTable[0]); + } + + if (profile) { + PRUint32 profileSpace = cmsGetColorSpace(profile); + if (profileSpace == icSigGrayData) { + if (color_type & PNG_COLOR_MASK_ALPHA) + *inType = TYPE_GRAYA_8; + else + *inType = TYPE_GRAY_8; + } else { + if (color_type & PNG_COLOR_MASK_ALPHA || + png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + *inType = TYPE_RGBA_8; + else + *inType = TYPE_RGB_8; + } + } + + return profile; +} void info_callback(png_structp png_ptr, png_infop info_ptr) @@ -305,6 +427,53 @@ info_callback(png_structp png_ptr, png_infop info_ptr) if (bit_depth == 16) png_set_strip_16(png_ptr); + PRUint32 inType, intent; + if (gfxPlatform::IsCMSEnabled()) { + decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, + color_type, &inType, &intent); + } + if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { + PRUint32 outType; + + if (color_type & PNG_COLOR_MASK_ALPHA || trans) + outType = TYPE_RGBA_8; + else + outType = TYPE_RGB_8; + + decoder->mTransform = cmsCreateTransform(decoder->mInProfile, + inType, + gfxPlatform::GetCMSOutputProfile(), + outType, + intent, + 0); + } else { + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + if (gfxPlatform::IsCMSEnabled()) { + if (color_type & PNG_COLOR_MASK_ALPHA || trans) + decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); + else + decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); + } + } + + if (!decoder->mTransform) { + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) { + if ((aGamma <= 0.0) || (aGamma > 21474.83)) { + aGamma = 0.45455; + png_set_gAMA(png_ptr, info_ptr, aGamma); + } + png_set_gamma(png_ptr, 2.2, aGamma); + } + else + png_set_gamma(png_ptr, 2.2, 0.45455); + } + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); @@ -328,8 +497,7 @@ info_callback(png_structp png_ptr, png_infop info_ptr) /* now all of those things we set above are used to update various struct * members and whatnot, after which we can get channels, rowbytes, etc. */ png_read_update_info(png_ptr, info_ptr); - channels = png_get_channels(png_ptr, info_ptr); - PR_ASSERT(channels == 3 || channels == 4); + decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); /*---------------------------------------------------------------*/ /* copy PNG info into imagelib structs (formerly png_set_dims()) */ @@ -337,7 +505,7 @@ info_callback(png_structp png_ptr, png_infop info_ptr) PRInt32 alpha_bits = 1; - if (channels > 3) { + if (channels == 2 || channels == 4) { /* check if alpha is coming from a tRNS chunk and is binary */ if (num_trans) { /* if it's not a indexed color image, tRNS means binary */ @@ -368,9 +536,9 @@ info_callback(png_structp png_ptr, png_infop info_ptr) if (decoder->mObserver) decoder->mObserver->OnStartContainer(nsnull, decoder->mImage); - if (channels == 3) { + if (channels == 1 || channels == 3) { decoder->format = gfxIFormats::RGB; - } else if (channels > 3) { + } else if (channels == 2 || channels == 4) { if (alpha_bits == 8) { decoder->mImage->GetPreferredAlphaChannelFormat(&(decoder->format)); } else if (alpha_bits == 1) { @@ -398,11 +566,17 @@ info_callback(png_structp png_ptr, png_infop info_ptr) PRUint32 bpr; decoder->mFrame->GetImageBytesPerRow(&bpr); + if (decoder->mTransform && + (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) { + PRUint32 bpp[] = { 0, 3, 4, 3, 4 }; + decoder->mCMSLine = + (PRUint8 *)nsMemory::Alloc(bpp[channels] * width); + if (!decoder->mCMSLine) + longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY + } + if (interlace_type == PNG_INTERLACE_ADAM7) { - if (channels > 3) - decoder->ibpr = channels*width; - else - decoder->ibpr = bpr; + decoder->ibpr = channels * width; decoder->interlacebuf = (PRUint8 *)nsMemory::Alloc(decoder->ibpr*height); if (!decoder->interlacebuf) { longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY @@ -475,6 +649,21 @@ row_callback(png_structp png_ptr, png_bytep new_row, decoder->mFrame->GetImageData(&imageData, &imageDataLength); PRUint32 *cptr32 = (PRUint32*)(imageData + (row_num*bpr)); + if (decoder->mTransform) { + if (decoder->mCMSLine) { + cmsDoTransform(decoder->mTransform, line, decoder->mCMSLine, iwidth); + /* copy alpha over */ + PRUint32 channels = decoder->mChannels; + if (channels == 2 || channels == 4) { + for (PRUint32 i = 0; i < iwidth; i++) + decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1]; + } + line = decoder->mCMSLine; + } else { + cmsDoTransform(decoder->mTransform, line, line, iwidth); + } + } + switch (format) { case gfxIFormats::RGB: case gfxIFormats::BGR: diff --git a/modules/libpr0n/decoders/png/nsPNGDecoder.h b/modules/libpr0n/decoders/png/nsPNGDecoder.h index 3294217c7d..9fc3f15da2 100644 --- a/modules/libpr0n/decoders/png/nsPNGDecoder.h +++ b/modules/libpr0n/decoders/png/nsPNGDecoder.h @@ -52,6 +52,8 @@ #include "png.h" +#include "lcms.h" + #define NS_PNGDECODER_CID \ { /* 36fa00c2-1dd2-11b2-be07-d16eeb4c50ed */ \ 0x36fa00c2, \ @@ -81,10 +83,15 @@ public: png_structp mPNG; png_infop mInfo; - gfx_format format; - PRUint8 apngFlags; + PRUint8 *mCMSLine; PRUint8 *interlacebuf; + cmsHPROFILE mInProfile; + cmsHTRANSFORM mTransform; + PRUint32 ibpr; + gfx_format format; + PRUint8 apngFlags; + PRUint8 mChannels; PRPackedBool mError; }; diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index e57c37daa8..911546cadc 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -118,6 +118,9 @@ pref("browser.chrome.image_icons.max_size", 1024); pref("browser.triple_click_selects_paragraph", true); +pref("gfx.color_management.enabled", false); +pref("gfx.color_management.display_profile", ""); + pref("accessibility.browsewithcaret", false); pref("accessibility.warn_on_browsewithcaret", true); diff --git a/toolkit/toolkit-tiers.mk b/toolkit/toolkit-tiers.mk index 521604b085..5f3a98db8c 100644 --- a/toolkit/toolkit-tiers.mk +++ b/toolkit/toolkit-tiers.mk @@ -68,6 +68,10 @@ tier_external_dirs += modules/libbz2 tier_external_dirs += modules/libmar endif +ifndef MOZ_NATIVE_LCMS +tier_external_dirs += modules/lcms +endif + # # tier "gecko" - core components # -- 2.11.4.GIT