Make PNGImageDecoder::rowAvailable auto-vectorizable
[WebKit-https.git] / Source / WebCore / platform / image-decoders / png / PNGImageDecoder.cpp
index 815b096..776acce 100644 (file)
 #include "config.h"
 #include "PNGImageDecoder.h"
 
+#include "Color.h"
+#include "PlatformInstrumentation.h"
 #include "png.h"
+#include <wtf/OwnArrayPtr.h>
 #include <wtf/PassOwnPtr.h>
 
-#if PLATFORM(CHROMIUM)
-#include "TraceEvent.h"
+#if USE(QCMSLIB)
+#include "qcms.h"
 #endif
 
 #if defined(PNG_LIBPNG_VER_MAJOR) && defined(PNG_LIBPNG_VER_MINOR) && (PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 4))
@@ -102,15 +105,19 @@ static void PNGAPI pngComplete(png_structp png, png_infop)
     static_cast<PNGImageDecoder*>(png_get_progressive_ptr(png))->pngComplete();
 }
 
-class PNGImageReader
-{
+class PNGImageReader {
+    WTF_MAKE_FAST_ALLOCATED;
 public:
     PNGImageReader(PNGImageDecoder* decoder)
         : m_readOffset(0)
+        , m_currentBufferSize(0)
         , m_decodingSizeOnly(false)
-        , m_interlaceBuffer(0)
         , m_hasAlpha(false)
-        , m_currentBufferSize(0)
+        , m_interlaceBuffer(0)
+#if USE(QCMSLIB)
+        , m_transform(0)
+        , m_rowBuffer()
+#endif
     {
         m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, decodingFailed, decodingWarning);
         m_info = png_create_info_struct(m_png);
@@ -127,13 +134,16 @@ public:
         if (m_png && m_info)
             // This will zero the pointers.
             png_destroy_read_struct(&m_png, &m_info, 0);
+#if USE(QCMSLIB)
+        if (m_transform)
+            qcms_transform_release(m_transform);
+        m_transform = 0;
+#endif
         delete[] m_interlaceBuffer;
         m_interlaceBuffer = 0;
         m_readOffset = 0;
     }
 
-    unsigned currentBufferSize() const { return m_currentBufferSize; }
-
     bool decode(const SharedBuffer& data, bool sizeOnly)
     {
         m_decodingSizeOnly = sizeOnly;
@@ -157,25 +167,57 @@ public:
         return false;
     }
 
-    bool decodingSizeOnly() const { return m_decodingSizeOnly; }
     png_structp pngPtr() const { return m_png; }
     png_infop infoPtr() const { return m_info; }
-    png_bytep interlaceBuffer() const { return m_interlaceBuffer; }
-    bool hasAlpha() const { return m_hasAlpha; }
 
     void setReadOffset(unsigned offset) { m_readOffset = offset; }
-    void setHasAlpha(bool b) { m_hasAlpha = b; }
+    unsigned currentBufferSize() const { return m_currentBufferSize; }
+    bool decodingSizeOnly() const { return m_decodingSizeOnly; }
+    void setHasAlpha(bool hasAlpha) { m_hasAlpha = hasAlpha; }
+    bool hasAlpha() const { return m_hasAlpha; }
 
+    png_bytep interlaceBuffer() const { return m_interlaceBuffer; }
     void createInterlaceBuffer(int size) { m_interlaceBuffer = new png_byte[size]; }
+#if USE(QCMSLIB)
+    png_bytep rowBuffer() const { return m_rowBuffer.get(); }
+    void createRowBuffer(int size) { m_rowBuffer = adoptArrayPtr(new png_byte[size]); }
+    qcms_transform* colorTransform() const { return m_transform; }
+
+    void createColorTransform(const ColorProfile& colorProfile, bool hasAlpha)
+    {
+        if (m_transform)
+            qcms_transform_release(m_transform);
+        m_transform = 0;
+
+        if (colorProfile.isEmpty())
+            return;
+        qcms_profile* deviceProfile = ImageDecoder::qcmsOutputDeviceProfile();
+        if (!deviceProfile)
+            return;
+        qcms_profile* inputProfile = qcms_profile_from_memory(colorProfile.data(), colorProfile.size());
+        if (!inputProfile)
+            return;
+        // We currently only support color profiles for RGB and RGBA images.
+        ASSERT(icSigRgbData == qcms_profile_get_color_space(inputProfile));
+        qcms_data_type dataFormat = hasAlpha ? QCMS_DATA_RGBA_8 : QCMS_DATA_RGB_8;
+        // FIXME: Don't force perceptual intent if the image profile contains an intent.
+        m_transform = qcms_transform_create(inputProfile, dataFormat, deviceProfile, dataFormat, QCMS_INTENT_PERCEPTUAL);
+        qcms_profile_release(inputProfile);
+    }
+#endif
 
 private:
-    unsigned m_readOffset;
-    bool m_decodingSizeOnly;
     png_structp m_png;
     png_infop m_info;
-    png_bytep m_interlaceBuffer;
-    bool m_hasAlpha;
+    unsigned m_readOffset;
     unsigned m_currentBufferSize;
+    bool m_decodingSizeOnly;
+    bool m_hasAlpha;
+    png_bytep m_interlaceBuffer;
+#if USE(QCMSLIB)
+    qcms_transform* m_transform;
+    OwnArrayPtr<png_byte> m_rowBuffer;
+#endif
 };
 
 PNGImageDecoder::PNGImageDecoder(ImageSource::AlphaOption alphaOption,
@@ -217,8 +259,11 @@ ImageFrame* PNGImageDecoder::frameBufferAtIndex(size_t index)
     }
 
     ImageFrame& frame = m_frameBufferCache[0];
-    if (frame.status() != ImageFrame::FrameComplete)
+    if (frame.status() != ImageFrame::FrameComplete) {
+        PlatformInstrumentation::willDecodeImage("PNG");
         decode(false);
+        PlatformInstrumentation::didDecodeImage();
+    }
     return &frame;
 }
 
@@ -290,16 +335,6 @@ void PNGImageDecoder::headerAvailable()
     int bitDepth, colorType, interlaceType, compressionType, filterType, channels;
     png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, &compressionType, &filterType);
 
-    if ((colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) && !m_ignoreGammaAndColorProfile) {
-        // We currently support color profiles only for RGB and RGBA PNGs.  Supporting
-        // color profiles for gray-scale images is slightly tricky, at least using the
-        // CoreGraphics ICC library, because we expand gray-scale images to RGB but we
-        // don't similarly transform the color profile.  We'd either need to transform
-        // the color profile or we'd need to decode into a gray-scale image buffer and
-        // hand that to CoreGraphics.
-        readColorProfile(png, info, m_colorProfile);
-    }
-
     // The options we set here match what Mozilla does.
 
     // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA.
@@ -319,6 +354,21 @@ void PNGImageDecoder::headerAvailable()
     if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
         png_set_gray_to_rgb(png);
 
+    if ((colorType & PNG_COLOR_MASK_COLOR) && !m_ignoreGammaAndColorProfile) {
+        // We only support color profiles for color PALETTE and RGB[A] PNG. Supporting
+        // color profiles for gray-scale images is slightly tricky, at least using the
+        // CoreGraphics ICC library, because we expand gray-scale images to RGB but we
+        // do not similarly transform the color profile. We'd either need to transform
+        // the color profile or we'd need to decode into a gray-scale image buffer and
+        // hand that to CoreGraphics.
+        readColorProfile(png, info, m_colorProfile);
+#if USE(QCMSLIB)
+        bool decodedImageHasAlpha = (colorType & PNG_COLOR_MASK_ALPHA) || trnsCount;
+        m_reader->createColorTransform(m_colorProfile, decodedImageHasAlpha);
+        m_colorProfile.clear();
+#endif
+    }
+
     // Deal with gamma and keep it under our control.
     double gamma;
     if (!m_ignoreGammaAndColorProfile && png_get_gAMA(png, info, &gamma)) {
@@ -353,7 +403,30 @@ void PNGImageDecoder::headerAvailable()
     }
 }
 
-void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int interlacePass)
+static inline void setPixelRGB(ImageFrame::PixelData* dest, png_bytep pixel)
+{
+    *dest = 0xFF000000U | pixel[0] << 16 | pixel[1] << 8 | pixel[2];
+}
+
+static inline void setPixelRGBA(ImageFrame::PixelData* dest, png_bytep pixel, unsigned char& nonTrivialAlphaMask)
+{
+    unsigned char a = pixel[3];
+    *dest = a << 24 | pixel[0] << 16 | pixel[1] << 8 | pixel[2];
+    nonTrivialAlphaMask |= (255 - a);
+}
+
+static inline void setPixelPremultipliedRGBA(ImageFrame::PixelData* dest, png_bytep pixel, unsigned char& nonTrivialAlphaMask)
+{
+    unsigned char a = pixel[3];
+    unsigned char r = fastDivideBy255(pixel[0] * a);
+    unsigned char g = fastDivideBy255(pixel[1] * a);
+    unsigned char b = fastDivideBy255(pixel[2] * a);
+
+    *dest = a << 24 | r << 16 | g << 8 | b;
+    nonTrivialAlphaMask |= (255 - a);
+}
+
+void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int)
 {
     if (m_frameBufferCache.isEmpty())
         return;
@@ -367,8 +440,8 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex,
             return;
         }
 
+        unsigned colorChannels = m_reader->hasAlpha() ? 4 : 3;
         if (PNG_INTERLACE_ADAM7 == png_get_interlace_type(png, m_reader->infoPtr())) {
-            unsigned colorChannels = m_reader->hasAlpha() ? 4 : 3;
             m_reader->createInterlaceBuffer(colorChannels * size().width() * size().height());
             if (!m_reader->interlaceBuffer()) {
                 longjmp(JMPBUF(png), 1);
@@ -376,6 +449,15 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex,
             }
         }
 
+#if USE(QCMSLIB)
+        if (m_reader->colorTransform()) {
+            m_reader->createRowBuffer(colorChannels * size().width());
+            if (!m_reader->rowBuffer()) {
+                longjmp(JMPBUF(png), 1);
+                return;
+            }
+        }
+#endif
         buffer.setStatus(ImageFrame::FramePartial);
         buffer.setHasAlpha(false);
         buffer.setColorProfile(m_colorProfile);
@@ -433,29 +515,47 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex,
         png_progressive_combine_row(m_reader->pngPtr(), row, rowBuffer);
     }
 
+#if USE(QCMSLIB)
+    if (qcms_transform* transform = m_reader->colorTransform()) {
+        qcms_transform_data(transform, row, m_reader->rowBuffer(), size().width());
+        row = m_reader->rowBuffer();
+    }
+#endif
+
     // Write the decoded row pixels to the frame buffer.
+    ImageFrame::PixelData* address = buffer.getAddr(0, y);
     int width = scaledSize().width();
-    bool nonTrivialAlpha = false;
+    unsigned char nonTrivialAlphaMask = 0;
 
 #if ENABLE(IMAGE_DECODER_DOWN_SAMPLING)
-    for (int x = 0; x < width; ++x) {
-        png_bytep pixel = row + (m_scaled ? m_scaledColumns[x] : x) * colorChannels;
-        unsigned alpha = hasAlpha ? pixel[3] : 255;
-        buffer.setRGBA(x, y, pixel[0], pixel[1], pixel[2], alpha);
-        nonTrivialAlpha |= alpha < 255;
-    }
-#else
-    ASSERT(!m_scaled);
-    png_bytep pixel = row;
-    for (int x = 0; x < width; ++x, pixel += colorChannels) {
-        unsigned alpha = hasAlpha ? pixel[3] : 255;
-        buffer.setRGBA(x, y, pixel[0], pixel[1], pixel[2], alpha);
-        nonTrivialAlpha |= alpha < 255;
-    }
+    if (m_scaled) {
+        for (int x = 0; x < width; ++x) {
+            png_bytep pixel = row + m_scaledColumns[x] * colorChannels;
+            unsigned alpha = hasAlpha ? pixel[3] : 255;
+            buffer.setRGBA(address++, pixel[0], pixel[1], pixel[2], alpha);
+            nonTrivialAlphaMask |= (255 - alpha);
+        }
+    } else
 #endif
+    {
+        png_bytep pixel = row;
+        if (hasAlpha) {
+            if (buffer.premultiplyAlpha()) {
+                for (int x = 0; x < width; ++x, pixel += 4)
+                    setPixelPremultipliedRGBA(address++, pixel, nonTrivialAlphaMask);
+            } else {
+                for (int x = 0; x < width; ++x, pixel += 4)
+                    setPixelRGBA(address++, pixel, nonTrivialAlphaMask);
+            }
+        } else {
+            for (int x = 0; x < width; ++x, pixel += 3)
+                setPixelRGB(address++, pixel);
+        }
+    }
 
-    if (nonTrivialAlpha && !buffer.hasAlpha())
-        buffer.setHasAlpha(nonTrivialAlpha);
+
+    if (nonTrivialAlphaMask && !buffer.hasAlpha())
+        buffer.setHasAlpha(true);
 }
 
 void PNGImageDecoder::pngComplete()
@@ -466,9 +566,6 @@ void PNGImageDecoder::pngComplete()
 
 void PNGImageDecoder::decode(bool onlySize)
 {
-#if PLATFORM(CHROMIUM)
-    TRACE_EVENT("PNGImageDecoder::decode", this, 0);
-#endif
     if (failed())
         return;