adding color management capabilities -- preffed off. bug 16769. patch from tor. r=bsmedberg, sr=me
authorpavlov@pavlov.net
Mon, 23 Jul 2007 15:02:17 -0700
changeset 3787 85237d95c40f064992150a006c01b400c6ae5a84
parent 3786 51c6136ac549e266dde3c340ce7cc9594b178055
child 3788 00c4544780ce89e5c9a2ff220658b9710792538a
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, me
bugs16769
milestone1.9a7pre
adding color management capabilities -- preffed off. bug 16769. patch from tor. r=bsmedberg, sr=me
allmakefiles.sh
config/autoconf.mk.in
config/static-config.mk
config/system-headers
configure.in
gfx/thebes/public/gfxPlatform.h
gfx/thebes/public/gfxPlatformGtk.h
gfx/thebes/public/gfxPlatformMac.h
gfx/thebes/public/gfxWindowsPlatform.h
gfx/thebes/src/Makefile.in
gfx/thebes/src/gfxContext.cpp
gfx/thebes/src/gfxPlatform.cpp
gfx/thebes/src/gfxPlatformGtk.cpp
gfx/thebes/src/gfxPlatformMac.cpp
gfx/thebes/src/gfxWindowsPlatform.cpp
modules/libimg/png/mozpngconf.h
modules/libpr0n/build/Makefile.in
modules/libpr0n/decoders/gif/Makefile.in
modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp
modules/libpr0n/decoders/jpeg/Makefile.in
modules/libpr0n/decoders/jpeg/iccjpeg.c
modules/libpr0n/decoders/jpeg/iccjpeg.h
modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp
modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h
modules/libpr0n/decoders/png/Makefile.in
modules/libpr0n/decoders/png/nsPNGDecoder.cpp
modules/libpr0n/decoders/png/nsPNGDecoder.h
modules/libpref/src/init/all.js
toolkit/toolkit-tiers.mk
--- a/allmakefiles.sh
+++ b/allmakefiles.sh
@@ -171,16 +171,24 @@ gfx/cairo/libpixman/src/Makefile
 gfx/cairo/cairo/src/Makefile
 gfx/cairo/cairo/src/cairo-features.h
 gfx/cairo/glitz/src/Makefile
 gfx/cairo/glitz/src/glx/Makefile
 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
 parser/htmlparser/robot/test/Makefile
 parser/htmlparser/public/Makefile
 parser/htmlparser/src/Makefile
 parser/htmlparser/tests/Makefile
 parser/htmlparser/tests/grabpage/Makefile
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -258,17 +258,17 @@ CROSS_COMPILE   = @CROSS_COMPILE@
 OS_CPPFLAGS	= @CPPFLAGS@
 OS_CFLAGS	= $(OS_CPPFLAGS) @CFLAGS@
 OS_CXXFLAGS	= $(OS_CPPFLAGS) @CXXFLAGS@
 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@
 
 MOZ_OPTIMIZE	= @MOZ_OPTIMIZE@
 MOZ_OPTIMIZE_FLAGS = @MOZ_OPTIMIZE_FLAGS@
 MOZ_OPTIMIZE_LDFLAGS = @MOZ_OPTIMIZE_LDFLAGS@
 
 MOZ_RTTI_FLAGS_ON = @_MOZ_RTTI_FLAGS_ON@
@@ -401,16 +401,25 @@ PNG_CFLAGS	= @PNG_CFLAGS@
 PNG_LIBS	= @PNG_LIBS@
 PNG_REQUIRES	=
 else
 PNG_CFLAGS	= @MOZ_PNG_CFLAGS@
 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@
 
 NSS_CONFIG	= @NSS_CONFIG@
 NSS_CFLAGS	= @NSS_CFLAGS@
 NSS_LIBS	= @NSS_LIBS@
 NSS_DEP_LIBS	= @NSS_DEP_LIBS@
--- a/config/static-config.mk
+++ b/config/static-config.mk
@@ -64,16 +64,17 @@ STATIC_EXTRA_DEPS	+= \
 ifdef MOZ_PSM
 STATIC_EXTRA_DEPS	+= $(NSS_DEP_LIBS)
 endif
 
 STATIC_EXTRA_LIBS	+= \
 		$(PNG_LIBS) \
 		$(JPEG_LIBS) \
 		$(ZLIB_LIBS) \
+		$(LCMS_LIBS) \
 		$(NULL)
 
 ifdef MOZ_PSM
 STATIC_EXTRA_LIBS	+= \
 		$(NSS_LIBS) \
 		$(NULL)
 endif
 
--- a/config/system-headers
+++ b/config/system-headers
@@ -932,8 +932,11 @@ plarenas.h
 plarena.h
 plhash.h
 #if MOZ_NATIVE_PNG==1
 png.h
 #endif
 #if MOZ_NATIVE_ZLIB==1
 zlib.h
 #endif
+#if MOZ_TREE_LCMS==1
+lcms.h
+#endif
--- a/configure.in
+++ b/configure.in
@@ -121,16 +121,17 @@ MAKE_VERSION=3.78
 WINDRES_VERSION=2.14.90
 W32API_VERSION=3.8
 GNOMEVFS_VERSION=2.0
 GNOMEUI_VERSION=2.2.0
 GCONF_VERSION=1.2.1
 LIBGNOME_VERSION=2.0
 STARTUP_NOTIFICATION_VERSION=0.8
 DBUS_VERSION=0.60
+LCMS_VERSION=1.17
 
 MSMANIFEST_TOOL=
 
 dnl Set various checks
 dnl ========================================================
 MISSING_X=
 AC_PROG_AWK
 
@@ -7045,16 +7046,45 @@ if test "$MOZ_SVG" -o "$MOZ_ENABLE_CANVA
    fi
 fi
 
 AC_SUBST(MOZ_TREE_CAIRO)
 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,
 [  --disable-xul           Disable XUL],
     MOZ_XUL= )
 if test "$MOZ_XUL"; then
   AC_DEFINE(MOZ_XUL)
 else
--- a/gfx/thebes/public/gfxPlatform.h
+++ b/gfx/thebes/public/gfxPlatform.h
@@ -40,16 +40,19 @@
 #define GFX_PLATFORM_H
 
 #include "prtypes.h"
 #include "nsVoidArray.h"
 
 #include "gfxTypes.h"
 #include "gfxASurface.h"
 
+typedef void* cmsHPROFILE;
+typedef void* cmsHTRANSFORM;
+
 class gfxImageSurface;
 class gfxFontGroup;
 struct gfxFontStyle;
 
 class THEBES_API gfxPlatform {
 public:
     /**
      * Return a pointer to the current active platform.
@@ -130,15 +133,37 @@ public:
     /* Returns PR_TRUE if the given block of ARGB32 data really has alpha, otherwise PR_FALSE */
     static PRBool DoesARGBImageDataHaveAlpha(PRUint8* data,
                                              PRUint32 width,
                                              PRUint32 height,
                                              PRUint32 stride);
 
     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 */
--- a/gfx/thebes/public/gfxPlatformGtk.h
+++ b/gfx/thebes/public/gfxPlatformGtk.h
@@ -83,11 +83,14 @@ public:
         return sDPI;
     }
 
 protected:
     static void InitDPI();
 
     static PRInt32 sDPI;
     static gfxFontconfigUtils *sFontconfigUtils;
+
+private:
+    virtual cmsHPROFILE GetPlatformCMSOutputProfile();
 };
 
 #endif /* GFX_PLATFORM_GTK_H */
--- a/gfx/thebes/public/gfxPlatformMac.h
+++ b/gfx/thebes/public/gfxPlatformMac.h
@@ -58,11 +58,14 @@ public:
 
     gfxFontGroup *CreateFontGroup(const nsAString &aFamilies,
                                   const gfxFontStyle *aStyle);
 
     nsresult GetFontList(const nsACString& aLangGroup,
                          const nsACString& aGenericFamily,
                          nsStringArray& aListOfFonts);
     nsresult UpdateFontList();
+
+private:
+    virtual cmsHPROFILE GetPlatformCMSOutputProfile();
 };
 
 #endif /* GFX_PLATFORM_MAC_H */
--- a/gfx/thebes/public/gfxWindowsPlatform.h
+++ b/gfx/thebes/public/gfxWindowsPlatform.h
@@ -102,15 +102,17 @@ private:
     static PLDHashOperator PR_CALLBACK HashEnumFunc(nsStringHashKey::KeyType aKey,
                                                     nsRefPtr<FontEntry>& aData,
                                                     void* userArg);
 
     static PLDHashOperator PR_CALLBACK FindFontForStringProc(nsStringHashKey::KeyType aKey,
                                                              nsRefPtr<FontEntry>& aFontEntry,
                                                              void* userArg);
 
+    virtual cmsHPROFILE GetPlatformCMSOutputProfile();
+
     nsDataHashtable<nsStringHashKey, nsRefPtr<FontEntry> > mFonts;
     nsDataHashtable<nsStringHashKey, nsRefPtr<FontEntry> > mFontAliases;
     nsDataHashtable<nsStringHashKey, nsRefPtr<FontEntry> > mFontSubstitutes;
     nsStringArray mNonExistingFonts;
 };
 
 #endif /* GFX_WINDOWS_PLATFORM_H */
--- a/gfx/thebes/src/Makefile.in
+++ b/gfx/thebes/src/Makefile.in
@@ -13,16 +13,17 @@ EXPORT_LIBRARY	= 1
 
 REQUIRES = \
 	cairo \
 	libpixman \
 	string \
 	pref \
 	xpcom \
 	unicharutil \
+	$(LCMS_REQUIRES) \
 	$(NULL)
 
 CPPSRCS	= \
 	gfxASurface.cpp \
 	gfxAlphaRecovery.cpp \
 	gfxContext.cpp \
 	gfxImageSurface.cpp \
 	gfxFont.cpp \
@@ -39,16 +40,17 @@ CPPSRCS	= \
 	$(NULL)
 
 EXTRA_DSO_LDOPTS += \
 	$(MOZ_CAIRO_LIBS) \
 	$(MOZ_UNICHARUTIL_LIBS) \
 	$(XPCOM_LIBS) \
 	$(NSPR_LIBS) \
 	$(ZLIB_LIBS) \
+	$(LCMS_LIBS) \
 	$(NULL)
 
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 CPPSRCS	+= 	gfxWindowsFonts.cpp \
 		gfxWindowsPlatform.cpp \
 		gfxWindowsSurface.cpp \
 		gfxWindowsNativeDrawing.cpp \
--- a/gfx/thebes/src/gfxContext.cpp
+++ b/gfx/thebes/src/gfxContext.cpp
@@ -41,24 +41,25 @@
 #endif
 #include <math.h>
 
 #ifndef M_PI
 #define M_PI 3.14159265358979323846
 #endif
 
 #include "cairo.h"
+#include "lcms.h"
 
 #include "gfxContext.h"
 
 #include "gfxColor.h"
 #include "gfxMatrix.h"
 #include "gfxASurface.h"
 #include "gfxPattern.h"
-
+#include "gfxPlatform.h"
 
 
 gfxContext::gfxContext(gfxASurface *surface) :
     mSurface(surface)
 {
     mCairo = cairo_create(surface->CairoSurface());
 }
 gfxContext::~gfxContext()
@@ -601,16 +602,37 @@ gfxContext::GetClipExtents()
     return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
 }
 
 // rendering sources
 
 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);
 }
 
 PRBool
 gfxContext::GetColor(gfxRGBA& c)
 {
     return cairo_pattern_get_rgba(cairo_get_source(mCairo),
                                   &c.r,
--- a/gfx/thebes/src/gfxPlatform.cpp
+++ b/gfx/thebes/src/gfxPlatform.cpp
@@ -58,19 +58,26 @@
 #include "nsIPref.h"
 #include "nsServiceManagerUtils.h"
 
 #ifdef MOZ_ENABLE_GLITZ
 #include <stdlib.h>
 #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()
 {
     return gPlatform;
 }
 
 nsresult
@@ -291,8 +298,111 @@ gfxPlatform::GetPrefFonts(const char *aL
 {
     aFonts.Truncate();
 
     AppendGenericFontFromPref(aFonts, aLangGroup, nsnull);
     if (aAppendUnicode)
         AppendGenericFontFromPref(aFonts, "x-unicode", nsnull);
 }
 
+PRBool
+gfxPlatform::IsCMSEnabled()
+{
+    static PRBool sEnabled = -1;
+    if (sEnabled == -1) {
+        sEnabled = PR_TRUE;
+        nsCOMPtr<nsIPrefBranch> 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<nsIPrefBranch> 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;
+}
--- a/gfx/thebes/src/gfxPlatformGtk.cpp
+++ b/gfx/thebes/src/gfxPlatformGtk.cpp
@@ -63,16 +63,18 @@
 #ifndef THEBES_USE_PANGO_CAIRO
 #include <pango/pangoxft.h>
 #endif // THEBES_USE_PANGO_CAIRO
 
 #include <pango/pango-font.h>
 
 #include "nsMathUtils.h"
 
+#include "lcms.h"
+
 PRInt32 gfxPlatformGtk::sDPI = -1;
 gfxFontconfigUtils *gfxPlatformGtk::sFontconfigUtils = nsnull;
 
 static cairo_user_data_key_t cairo_gdk_window_key;
 static cairo_user_data_key_t cairo_gdk_pixmap_key;
 static void do_gdk_pixmap_unref (void *data)
 {
     GdkPixmap *pmap = (GdkPixmap*)data;
@@ -360,8 +362,135 @@ gfxPlatformGtk::InitDPI()
     if (sDPI <= 0) {
         sDPI = GetDPIFromPangoFont();
         if (sDPI <= 0) {
             // Fall back to something sane
             sDPI = 96;
         }
     }
 }
+
+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;
+}
--- a/gfx/thebes/src/gfxPlatformMac.cpp
+++ b/gfx/thebes/src/gfxPlatformMac.cpp
@@ -44,16 +44,18 @@
 #include "gfxQuartzFontCache.h"
 #include "gfxAtsuiFonts.h"
 
 #ifdef MOZ_ENABLE_GLITZ
 #include "gfxGlitzSurface.h"
 #include "glitz-agl.h"
 #endif
 
+#include "lcms.h"
+
 gfxPlatformMac::gfxPlatformMac()
 {
 #ifdef MOZ_ENABLE_GLITZ
     if (UseGlitz())
         glitz_agl_init();
 #endif
 }
 
@@ -169,8 +171,55 @@ gfxPlatformMac::GetFontList(const nsACSt
 }
 
 nsresult
 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;
+}
--- a/gfx/thebes/src/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/src/gfxWindowsPlatform.cpp
@@ -49,16 +49,18 @@
 #include "nsServiceManagerUtils.h"
 
 #include "nsIWindowsRegKey.h"
 
 #include "gfxWindowsFonts.h"
 
 #include <string>
 
+#include "lcms.h"
+
 //#define DEBUG_CMAP_SIZE 1
 
 /* Define this if we want to update the unicode range bitsets based
  * on the actual characters a font supports.
  *
  * Doing this can result in very large lists of fonts being returned.
  * Not doing this can let us prioritize fonts that do have the bit set
  * as they are more likely to provide better glyphs (in theory).
@@ -737,8 +739,29 @@ gfxWindowsPlatform::FindFontEntry(const 
     nsRefPtr<FontEntry> fe;
     if (!mFonts.Get(name, &fe) &&
         !mFontSubstitutes.Get(name, &fe) &&
         !mFontAliases.Get(name, &fe)) {
         return nsnull;
     }
     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;
+}
--- a/modules/libimg/png/mozpngconf.h
+++ b/modules/libimg/png/mozpngconf.h
@@ -52,19 +52,17 @@
 #define PNG_NO_READ_PACK
 #define PNG_NO_READ_PACKSWAP
 #define PNG_NO_READ_FILLER
 #define PNG_NO_READ_SWAP_ALPHA
 #define PNG_NO_READ_INVERT_ALPHA
 #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
 #define PNG_NO_READ_sCAL
 #define PNG_NO_READ_sPLT
 #define PNG_NO_READ_TEXT
 #define PNG_NO_READ_tIME
 #define PNG_NO_READ_UNKNOWN_CHUNKS
--- a/modules/libpr0n/build/Makefile.in
+++ b/modules/libpr0n/build/Makefile.in
@@ -56,16 +56,17 @@ REQUIRES	= xpcom \
 		  string \
 		  thebes \
 		  necko \
 		  nkcache \
 		  gfx \
 		  $(JPEG_REQUIRES) \
 		  $(PNG_REQUIRES) \
 		  $(ZLIB_REQUIRES) \
+		  $(LCMS_REQUIRES) \
 		  $(NULL)
 
 CPPSRCS = \
 		nsImageModule.cpp \
 		$(NULL)
 
 LOCAL_INCLUDES	= \
 		-I. \
@@ -91,16 +92,17 @@ EXTRA_DSO_LIBS	= \
 ifdef MOZ_ENABLE_CAIRO_GFX
 EXTRA_DSO_LIBS	+= thebes
 endif
 
 EXTRA_DSO_LDOPTS = \
 		$(LIBS_DIR) \
 		$(JPEG_LIBS) \
 		$(PNG_LIBS) $(ZLIB_LIBS) \
+		$(LCMS_LIBS) \
 		$(EXTRA_DSO_LIBS) \
 		$(MOZ_COMPONENT_LIBS) \
 		$(NULL)
 
 # Force a rebuild of nsImageModule when either of MOZ_IMG_{DE,EN}CODERS changes
 
 GARBAGE += _img_list nsImgBuildDefines.h
 
--- a/modules/libpr0n/decoders/gif/Makefile.in
+++ b/modules/libpr0n/decoders/gif/Makefile.in
@@ -49,16 +49,17 @@ MODULE_NAME	= nsGIFModule2
 LIBXUL_LIBRARY = 1
 
 REQUIRES	= xpcom \
 		  string \
 		  gfx \
 		  thebes \
 		  cairo \
 		  imglib2 \
+		  $(LCMS_REQUIRES) \
 		  $(NULL)
 
 CPPSRCS		= nsGIFDecoder2.cpp
 
 # nsGIFDecoder2.cpp includes imgContainer.h
 LOCAL_INCLUDES += -I$(topsrcdir)/modules/libpr0n/src
 
 include $(topsrcdir)/config/rules.mk
--- a/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp
+++ b/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp
@@ -81,16 +81,19 @@ mailing address.
 #include "nsIInputStream.h"
 #include "nsIComponentManager.h"
 #include "imgIContainerObserver.h"
 
 #include "imgILoad.h"
 
 #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'
  *
  * Note, the hold will never need to be bigger than 256 bytes to gather up in the hold,
  * as each GIF block (except colormaps) can never be bigger than 256 bytes.
  * Colormaps are directly copied in the resp. global_colormap or dynamically allocated local_colormap.
  * So a fixed buffer in gif_struct is good enough.
  * This buffer is only needed to copy left-over data from one GifWrite call to the next
@@ -812,27 +815,39 @@ nsresult nsGIFDecoder2::GifWrite(const P
       if (q[4] & 0x80) { /* global map */
         // Get the global colormap
         const PRUint32 size = 3*mGIFStruct.global_colormap_size;
         if (len < size) {
           // Use 'hold' pattern to get the global colormap
           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;
 
     case gif_image_start:
       switch (*q) {
         case ';':  /* terminator */
           mGIFStruct.state = gif_done;
           break;
@@ -1064,29 +1079,41 @@ nsresult nsGIFDecoder2::GifWrite(const P
         mGIFStruct.local_colormap_size = num_colors;
         mGIFStruct.is_local_colormap_defined = PR_TRUE;
 
         if (len < size) {
           // Use 'hold' pattern to get the image colormap
           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;
       }
       GETN(1, gif_lzw_start);
       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;
 
     case gif_sub_block:
       mGIFStruct.count = *q;
       if (mGIFStruct.count) {
         /* Still working on the same image: Process next LZW data block */
         /* Make sure there are still rows left. If the GIF data */
--- a/modules/libpr0n/decoders/jpeg/Makefile.in
+++ b/modules/libpr0n/decoders/jpeg/Makefile.in
@@ -49,14 +49,17 @@ MODULE_NAME	= nsJPEGDecoderModule
 LIBXUL_LIBRARY  = 1
 
 REQUIRES	= xpcom \
 		  string \
 		  gfx \
 		  thebes \
 		  imglib2 \
 		  $(JPEG_REQUIRES) \
+		  $(LCMS_REQUIRES) \
 		  $(NULL)
 
 CPPSRCS		= nsJPEGDecoder.cpp
 
+CSRCS		= iccjpeg.c
+
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /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 <stdlib.h>			/* 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;
+}
new file mode 100644
--- /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 <stdio.h>		/* 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));
--- a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp
+++ b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp
@@ -48,16 +48,22 @@
 #include "nsCRT.h"
 #include "ImageLogging.h"
 #include "nsIImage.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "gfxColor.h"
 
 #include "jerror.h"
 
+#include "gfxPlatform.h"
+
+extern "C" {
+#include "iccjpeg.h"
+}
+
 NS_IMPL_ISUPPORTS1(nsJPEGDecoder, imgIDecoder)
 
 #if defined(PR_LOGGING)
 PRLogModuleInfo *gJPEGlog = PR_NewLogModule("JPEGDecoder");
 #else
 #define gJPEGlog
 #endif
 
@@ -84,22 +90,29 @@ nsJPEGDecoder::nsJPEGDecoder()
   memset(&mSourceMgr, 0, sizeof(mSourceMgr));
   mInfo.client_data = (void*)this;
 
   mBuffer = nsnull;
   mBufferLen = mBufferSize = 0;
 
   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);
 }
 
 
 /** imgIDecoder methods **/
 
 /* void init (in imgILoad aLoad); */
 NS_IMETHODIMP nsJPEGDecoder::Init(imgILoad *aLoad)
 {
@@ -127,16 +140,20 @@ NS_IMETHODIMP nsJPEGDecoder::Init(imgILo
 
   /* Setup callback functions. */
   mSourceMgr.init_source = init_source;
   mSourceMgr.fill_input_buffer = fill_input_buffer;
   mSourceMgr.skip_input_data = skip_input_data;
   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;
 }
 
 
 /* void close (); */
 NS_IMETHODIMP nsJPEGDecoder::Close()
 {
   PR_LOG(gJPEGlog, PR_LOG_DEBUG,
@@ -218,28 +235,117 @@ NS_IMETHODIMP nsJPEGDecoder::WriteFrom(n
   case JPEG_HEADER:
   {
     LOG_SCOPE(gJPEGlog, "nsJPEGDecoder::WriteFrom -- entering JPEG_HEADER case");
 
     /* Step 3: read file parameters with jpeg_read_header() */
     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:
+        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;
-      case JCS_CMYK:
-      case JCS_YCCK:
       default:
         mState = JPEG_ERROR;
         return NS_ERROR_UNEXPECTED;
+        break;
+      }
     }
 
     /*
      * Don't allocate a giant and superfluous memory buffer
      * when the image is a sequential JPEG.
      */
     mInfo.buffered_image = jpeg_has_multiple_scans(&mInfo);
 
@@ -474,16 +580,40 @@ nsJPEGDecoder::OutputScanlines()
 
   while ((mInfo.output_scanline < mInfo.output_height)) {
       /* Request one scanline.  Returns 0 or 1 scanlines. */    
       if (jpeg_read_scanlines(&mInfo, mSamples, 1) != 1) {
         rv = PR_FALSE; /* suspend */
         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;
       JSAMPLE *j1 = mSamples[0];
       for (PRUint32 i=mInfo.output_width; i>0; --i) {
         *ptrOutputBuf++ = GFX_PACKED_PIXEL(0xFF, j1[0], j1[1], j1[2]);
         j1+=3;
       }
--- a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h
+++ b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h
@@ -45,16 +45,17 @@
 #include "nsCOMPtr.h"
 
 #include "imgIContainer.h"
 #include "gfxIImageFrame.h"
 #include "imgIDecoderObserver.h"
 #include "imgILoad.h"
 #include "nsIInputStream.h"
 #include "nsIPipe.h"
+#include "lcms.h"
 
 extern "C" {
 #include "jpeglib.h"
 }
 
 #include <setjmp.h>
 
 #define NS_JPEGDECODER_CID \
@@ -113,12 +114,18 @@ public:
   PRUint32 mBufferLen;  // amount of data currently in mBuffer
   PRUint32 mBufferSize; // size in bytes what mBuffer was created with
 
   JOCTET *mBackBuffer;
   PRUint32 mBackBufferLen; // Offset of end of active backtrack data
   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;
 };
 
 #endif // nsJPEGDecoder_h__
--- a/modules/libpr0n/decoders/png/Makefile.in
+++ b/modules/libpr0n/decoders/png/Makefile.in
@@ -52,14 +52,15 @@ EXTRA_DSO_LIBS	= gkgfx
 
 REQUIRES = xpcom \
            string \
            gfx \
            thebes \
            imglib2 \
            $(PNG_REQUIRES) \
            $(ZLIB_REQUIRES) \
+           $(LCMS_REQUIRES) \
            $(NULL)
 
 CPPSRCS		= nsPNGDecoder.cpp
 
 include $(topsrcdir)/config/rules.mk
 
--- a/modules/libpr0n/decoders/png/nsPNGDecoder.cpp
+++ b/modules/libpr0n/decoders/png/nsPNGDecoder.cpp
@@ -51,16 +51,18 @@
 #include "nsIInterfaceRequestorUtils.h"
 
 #include "gfxColor.h"
 #include "nsColor.h"
 
 #include "nspr.h"
 #include "png.h"
 
+#include "gfxPlatform.h"
+
 // for nsPNGDecoder.apngFlags
 enum { 
   FRAME_HIDDEN         = 0x01
 };
 
 static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr);
 static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row,
                            png_uint_32 row_num, int pass);
@@ -72,26 +74,35 @@ static void PNGAPI warning_callback(png_
 #ifdef PR_LOGGING
 PRLogModuleInfo *gPNGLog = PR_NewLogModule("PNGDecoder");
 #endif
 
 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
 void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, 
                                 PRInt32 width, PRInt32 height, gfx_format format)
 {
   mFrame = do_CreateInstance("@mozilla.org/gfx/image/frame;2");
   if (!mFrame)
@@ -148,19 +159,17 @@ void nsPNGDecoder::SetAnimFrameInfo()
 /** imgIDecoder methods **/
 
 /* void init (in imgILoad aLoad); */
 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 */
         115,  67,  65,  76, '\0',   /* sCAL */
         112,  72,  89, 115, '\0',   /* pHYs */
         115,  66,  73,  84, '\0',   /* sBIT */
         115,  80,  76,  84, '\0',   /* sPLT */
         116,  69,  88, 116, '\0',   /* tEXt */
@@ -261,16 +270,129 @@ NS_IMETHODIMP nsPNGDecoder::WriteFrom(ns
   if (mError) {
     *_retval = 0;
     rv = NS_ERROR_FAILURE;
   }
 
   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)
 {
 /*  int number_passes;   NOT USED  */
   png_uint_32 width, height;
   int bit_depth, color_type, interlace_type, compression_type, filter_type;
   int channels;
@@ -300,16 +422,63 @@ info_callback(png_structp png_ptr, png_i
   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
     png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
     png_set_expand(png_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);
 
   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);
@@ -323,26 +492,25 @@ info_callback(png_structp png_ptr, png_i
   if (interlace_type == PNG_INTERLACE_ADAM7) {
     /* number_passes = */
     png_set_interlace_handling(png_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()) */
   /*---------------------------------------------------------------*/
 
   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 */
       if (color_type == PNG_COLOR_TYPE_PALETTE) {
         for (int i=0; i<num_trans; i++) {
           if ((trans[i] != 0) && (trans[i] != 255)) {
             alpha_bits = 8;
             break;
@@ -363,19 +531,19 @@ info_callback(png_structp png_ptr, png_i
 
   decoder->mImageLoad->SetImage(decoder->mImage);
 
   decoder->mImage->Init(width, height, decoder->mObserver);
 
   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) {
       decoder->format = gfxIFormats::RGB_A1;
     }
   }
 
   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL))
@@ -393,21 +561,27 @@ info_callback(png_structp png_ptr, png_i
       longjmp(png_ptr->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY
   } else {
     decoder->CreateFrame(0, 0, width, height, decoder->format);
   }
   
   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
     }
   }
   
   if (png_get_first_frame_is_hidden(png_ptr, info_ptr))
     decoder->mFrame = nsnull;
@@ -470,16 +644,31 @@ row_callback(png_structp png_ptr, png_by
     decoder->mFrame->GetFormat(&format);
 
     // we're thebes. we can write stuff directly to the data
     PRUint8 *imageData;
     PRUint32 imageDataLength, bpr = width * sizeof(PRUint32);
     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:
       {
         for (PRUint32 x=iwidth; x>0; --x) {
           *cptr32++ = GFX_PACKED_PIXEL(0xFF, line[0], line[1], line[2]);
           line += 3;
         }
--- a/modules/libpr0n/decoders/png/nsPNGDecoder.h
+++ b/modules/libpr0n/decoders/png/nsPNGDecoder.h
@@ -47,16 +47,18 @@
 #include "gfxIImageFrame.h"
 #include "imgILoad.h"
 
 
 #include "nsCOMPtr.h"
 
 #include "png.h"
 
+#include "lcms.h"
+
 #define NS_PNGDECODER_CID \
 { /* 36fa00c2-1dd2-11b2-be07-d16eeb4c50ed */         \
      0x36fa00c2,                                     \
      0x1dd2,                                         \
      0x11b2,                                         \
     {0xbe, 0x07, 0xd1, 0x6e, 0xeb, 0x4c, 0x50, 0xed} \
 }
 
@@ -76,16 +78,21 @@ public:
 public:
   nsCOMPtr<imgIContainer> mImage;
   nsCOMPtr<gfxIImageFrame> mFrame;
   nsCOMPtr<imgILoad> mImageLoad;
   nsCOMPtr<imgIDecoderObserver> mObserver; // this is just qi'd from mRequest for speed
 
   png_structp mPNG;
   png_infop mInfo;
+  PRUint8 *mCMSLine;
+  PRUint8 *interlacebuf;
+  cmsHPROFILE mInProfile;
+  cmsHTRANSFORM mTransform;
+
+  PRUint32 ibpr;
   gfx_format format;
   PRUint8 apngFlags;
-  PRUint8 *interlacebuf;
-  PRUint32 ibpr;
+  PRUint8 mChannels;
   PRPackedBool mError;
 };
 
 #endif // nsPNGDecoder_h__
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -113,16 +113,19 @@ pref("browser.chrome.toolbar_tips",     
 // 0 = Pictures Only, 1 = Text Only, 2 = Pictures and Text
 pref("browser.chrome.toolbar_style",        2);
 // max image size for which it is placed in the tab icon for tabbrowser.
 // if 0, no images are used for tab icons for image documents.
 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);
 
 #ifndef XP_MACOSX
 // Tab focus model bit field:
 // 1 focuses text controls, 2 focuses other form elements, 4 adds links.
 // Most users will want 1, 3, or 7.
 // On OS X, we use Full Keyboard Access system preference,
--- a/toolkit/toolkit-tiers.mk
+++ b/toolkit/toolkit-tiers.mk
@@ -63,16 +63,20 @@ ifdef MOZ_INSTALLER
 tier_external_dirs	+= modules/zlib/standalone
 endif
 
 ifdef MOZ_UPDATER
 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
 #
 
 ifdef NS_TRACE_MALLOC
 tier_gecko_dirs += tools/trace-malloc/lib
 endif