1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include "config.h" 30#include "WEBPImageDecoder.h" 31 32#if USE(WEBP) 33 34#ifdef QCMS_WEBP_COLOR_CORRECTION 35#include "qcms.h" 36#include "webp/demux.h" 37#else 38#undef ICCP_FLAG 39#define ICCP_FLAG 0 40#endif 41 42// Backward emulation for earlier versions than 0.1.99. 43#if (WEBP_DECODER_ABI_VERSION < 0x0163) 44#define MODE_rgbA MODE_RGBA 45#define MODE_bgrA MODE_BGRA 46#endif 47 48#if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN) 49inline WEBP_CSP_MODE outputMode(bool hasAlpha) { return hasAlpha ? MODE_rgbA : MODE_RGBA; } 50#else // LITTLE_ENDIAN, output BGRA pixels. 51inline WEBP_CSP_MODE outputMode(bool hasAlpha) { return hasAlpha ? MODE_bgrA : MODE_BGRA; } 52#endif 53 54namespace WebCore { 55 56WEBPImageDecoder::WEBPImageDecoder(ImageSource::AlphaOption alphaOption, 57 ImageSource::GammaAndColorProfileOption gammaAndColorProfileOption) 58 : ImageDecoder(alphaOption, gammaAndColorProfileOption) 59 , m_decoder(0) 60 , m_hasAlpha(false) 61 , m_formatFlags(0) 62#ifdef QCMS_WEBP_COLOR_CORRECTION 63 , m_haveReadProfile(false) 64 , m_transform(0) 65 , m_decodedHeight(0) 66#endif 67{ 68} 69 70WEBPImageDecoder::~WEBPImageDecoder() 71{ 72 clear(); 73} 74 75void WEBPImageDecoder::clear() 76{ 77#ifdef QCMS_WEBP_COLOR_CORRECTION 78 if (m_transform) 79 qcms_transform_release(m_transform); 80 m_transform = 0; 81#endif 82 if (m_decoder) 83 WebPIDelete(m_decoder); 84 m_decoder = 0; 85} 86 87bool WEBPImageDecoder::isSizeAvailable() 88{ 89 if (!ImageDecoder::isSizeAvailable()) 90 decode(true); 91 92 return ImageDecoder::isSizeAvailable(); 93} 94 95ImageFrame* WEBPImageDecoder::frameBufferAtIndex(size_t index) 96{ 97 if (index) 98 return 0; 99 100 if (m_frameBufferCache.isEmpty()) { 101 m_frameBufferCache.resize(1); 102 m_frameBufferCache[0].setPremultiplyAlpha(m_premultiplyAlpha); 103 } 104 105 ImageFrame& frame = m_frameBufferCache[0]; 106 if (frame.status() != ImageFrame::FrameComplete) 107 decode(false); 108 return &frame; 109} 110 111#ifdef QCMS_WEBP_COLOR_CORRECTION 112 113void WEBPImageDecoder::createColorTransform(const char* data, size_t size) 114{ 115 if (m_transform) 116 qcms_transform_release(m_transform); 117 m_transform = 0; 118 119 qcms_profile* deviceProfile = ImageDecoder::qcmsOutputDeviceProfile(); 120 if (!deviceProfile) 121 return; 122 qcms_profile* inputProfile = qcms_profile_from_memory(data, size); 123 if (!inputProfile) 124 return; 125 126 // We currently only support color profiles for RGB profiled images. 127 ASSERT(icSigRgbData == qcms_profile_get_color_space(inputProfile)); 128 // The input image pixels are RGBA format. 129 qcms_data_type format = QCMS_DATA_RGBA_8; 130 // FIXME: Don't force perceptual intent if the image profile contains an intent. 131 m_transform = qcms_transform_create(inputProfile, format, deviceProfile, QCMS_DATA_RGBA_8, QCMS_INTENT_PERCEPTUAL); 132 133 qcms_profile_release(inputProfile); 134} 135 136void WEBPImageDecoder::readColorProfile(const uint8_t* data, size_t size) 137{ 138 WebPChunkIterator chunkIterator; 139 WebPData inputData = { data, size }; 140 WebPDemuxState state; 141 142 WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &state); 143 if (!WebPDemuxGetChunk(demuxer, "ICCP", 1, &chunkIterator)) { 144 WebPDemuxReleaseChunkIterator(&chunkIterator); 145 WebPDemuxDelete(demuxer); 146 return; 147 } 148 149 const char* profileData = reinterpret_cast<const char*>(chunkIterator.chunk.bytes); 150 size_t profileSize = chunkIterator.chunk.size; 151 152 // Only accept RGB color profiles from input class devices. 153 bool ignoreProfile = false; 154 if (profileSize < ImageDecoder::iccColorProfileHeaderLength) 155 ignoreProfile = true; 156 else if (!ImageDecoder::rgbColorProfile(profileData, profileSize)) 157 ignoreProfile = true; 158 else if (!ImageDecoder::inputDeviceColorProfile(profileData, profileSize)) 159 ignoreProfile = true; 160 161 if (!ignoreProfile) 162 createColorTransform(profileData, profileSize); 163 164 WebPDemuxReleaseChunkIterator(&chunkIterator); 165 WebPDemuxDelete(demuxer); 166} 167 168void WEBPImageDecoder::applyColorProfile(const uint8_t* data, size_t size, ImageFrame& buffer) 169{ 170 int width; 171 int decodedHeight; 172 if (!WebPIDecGetRGB(m_decoder, &decodedHeight, &width, 0, 0)) 173 return; // See also https://bugs.webkit.org/show_bug.cgi?id=74062 174 if (decodedHeight <= 0) 175 return; 176 177 if (!m_haveReadProfile) { 178 readColorProfile(data, size); 179 m_haveReadProfile = true; 180 } 181 182 ASSERT(width == scaledSize().width()); 183 ASSERT(decodedHeight <= scaledSize().height()); 184 185 for (int y = m_decodedHeight; y < decodedHeight; ++y) { 186 uint8_t* row = reinterpret_cast<uint8_t*>(buffer.getAddr(0, y)); 187 if (qcms_transform* transform = colorTransform()) 188 qcms_transform_data_type(transform, row, row, width, QCMS_OUTPUT_RGBX); 189 uint8_t* pixel = row; 190 for (int x = 0; x < width; ++x, pixel += 4) 191 buffer.setRGBA(x, y, pixel[0], pixel[1], pixel[2], pixel[3]); 192 } 193 194 m_decodedHeight = decodedHeight; 195} 196 197#endif // QCMS_WEBP_COLOR_CORRECTION 198 199bool WEBPImageDecoder::decode(bool onlySize) 200{ 201 if (failed()) 202 return false; 203 204 const uint8_t* dataBytes = reinterpret_cast<const uint8_t*>(m_data->data()); 205 const size_t dataSize = m_data->size(); 206 207 if (!ImageDecoder::isSizeAvailable()) { 208 static const size_t imageHeaderSize = 30; 209 if (dataSize < imageHeaderSize) 210 return false; 211 int width, height; 212#ifdef QCMS_WEBP_COLOR_CORRECTION 213 WebPData inputData = { dataBytes, dataSize }; 214 WebPDemuxState state; 215 WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &state); 216 if (!demuxer) 217 return setFailed(); 218 219 width = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); 220 height = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); 221 m_formatFlags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS); 222 m_hasAlpha = !!(m_formatFlags & ALPHA_FLAG); 223 224 WebPDemuxDelete(demuxer); 225 if (state <= WEBP_DEMUX_PARSING_HEADER) 226 return false; 227#elif (WEBP_DECODER_ABI_VERSION >= 0x0163) 228 WebPBitstreamFeatures features; 229 if (WebPGetFeatures(dataBytes, dataSize, &features) != VP8_STATUS_OK) 230 return setFailed(); 231 width = features.width; 232 height = features.height; 233 m_hasAlpha = features.has_alpha; 234#else 235 // Earlier version won't be able to display WebP files with alpha. 236 if (!WebPGetInfo(dataBytes, dataSize, &width, &height)) 237 return setFailed(); 238 m_hasAlpha = false; 239#endif 240 if (!setSize(width, height)) 241 return setFailed(); 242 } 243 244 ASSERT(ImageDecoder::isSizeAvailable()); 245 if (onlySize) 246 return true; 247 248 ASSERT(!m_frameBufferCache.isEmpty()); 249 ImageFrame& buffer = m_frameBufferCache[0]; 250 ASSERT(buffer.status() != ImageFrame::FrameComplete); 251 252 if (buffer.status() == ImageFrame::FrameEmpty) { 253 if (!buffer.setSize(size().width(), size().height())) 254 return setFailed(); 255 buffer.setStatus(ImageFrame::FramePartial); 256 buffer.setHasAlpha(m_hasAlpha); 257 buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); 258 } 259 260 if (!m_decoder) { 261 WEBP_CSP_MODE mode = outputMode(m_hasAlpha); 262 if (!m_premultiplyAlpha) 263 mode = outputMode(false); 264 if ((m_formatFlags & ICCP_FLAG) && !ignoresGammaAndColorProfile()) 265 mode = MODE_RGBA; // Decode to RGBA for input to libqcms. 266 int rowStride = size().width() * sizeof(ImageFrame::PixelData); 267 uint8_t* output = reinterpret_cast<uint8_t*>(buffer.getAddr(0, 0)); 268 int outputSize = size().height() * rowStride; 269 m_decoder = WebPINewRGB(mode, output, outputSize, rowStride); 270 if (!m_decoder) 271 return setFailed(); 272 } 273 274 switch (WebPIUpdate(m_decoder, dataBytes, dataSize)) { 275 case VP8_STATUS_OK: 276 if ((m_formatFlags & ICCP_FLAG) && !ignoresGammaAndColorProfile()) 277 applyColorProfile(dataBytes, dataSize, buffer); 278 buffer.setStatus(ImageFrame::FrameComplete); 279 clear(); 280 return true; 281 case VP8_STATUS_SUSPENDED: 282 if ((m_formatFlags & ICCP_FLAG) && !ignoresGammaAndColorProfile()) 283 applyColorProfile(dataBytes, dataSize, buffer); 284 return false; 285 default: 286 clear(); 287 return setFailed(); 288 } 289} 290 291} // namespace WebCore 292 293#endif 294