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 Computer, 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#include "PlatformInstrumentation.h" 35 36#ifdef QCMS_WEBP_COLOR_CORRECTION 37#include "qcms.h" 38#include "webp/demux.h" 39#else 40#undef ICCP_FLAG 41#define ICCP_FLAG 0 42#endif 43 44// Backward emulation for earlier versions than 0.1.99. 45#if (WEBP_DECODER_ABI_VERSION < 0x0163) 46#define MODE_rgbA MODE_RGBA 47#define MODE_bgrA MODE_BGRA 48#endif 49 50#if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN) 51inline WEBP_CSP_MODE outputMode(bool hasAlpha) { return hasAlpha ? MODE_rgbA : MODE_RGBA; } 52#else // LITTLE_ENDIAN, output BGRA pixels. 53inline WEBP_CSP_MODE outputMode(bool hasAlpha) { return hasAlpha ? MODE_bgrA : MODE_BGRA; } 54#endif 55 56namespace WebCore { 57 58WEBPImageDecoder::WEBPImageDecoder(ImageSource::AlphaOption alphaOption, 59 ImageSource::GammaAndColorProfileOption gammaAndColorProfileOption) 60 : ImageDecoder(alphaOption, gammaAndColorProfileOption) 61 , m_decoder(0) 62 , m_hasAlpha(false) 63 , m_formatFlags(0) 64#ifdef QCMS_WEBP_COLOR_CORRECTION 65 , m_haveReadProfile(false) 66 , m_transform(0) 67 , m_decodedHeight(0) 68#endif 69{ 70} 71 72WEBPImageDecoder::~WEBPImageDecoder() 73{ 74 clear(); 75} 76 77void WEBPImageDecoder::clear() 78{ 79#ifdef QCMS_WEBP_COLOR_CORRECTION 80 if (m_transform) 81 qcms_transform_release(m_transform); 82 m_transform = 0; 83#endif 84 if (m_decoder) 85 WebPIDelete(m_decoder); 86 m_decoder = 0; 87} 88 89bool WEBPImageDecoder::isSizeAvailable() 90{ 91 if (!ImageDecoder::isSizeAvailable()) 92 decode(true); 93 94 return ImageDecoder::isSizeAvailable(); 95} 96 97ImageFrame* WEBPImageDecoder::frameBufferAtIndex(size_t index) 98{ 99 if (index) 100 return 0; 101 102 if (m_frameBufferCache.isEmpty()) { 103 m_frameBufferCache.resize(1); 104 m_frameBufferCache[0].setPremultiplyAlpha(m_premultiplyAlpha); 105 } 106 107 ImageFrame& frame = m_frameBufferCache[0]; 108 if (frame.status() != ImageFrame::FrameComplete) { 109 PlatformInstrumentation::willDecodeImage("WEBP"); 110 decode(false); 111 PlatformInstrumentation::didDecodeImage(); 112 } 113 return &frame; 114} 115 116#ifdef QCMS_WEBP_COLOR_CORRECTION 117 118void WEBPImageDecoder::createColorTransform(const char* data, size_t size) 119{ 120 if (m_transform) 121 qcms_transform_release(m_transform); 122 m_transform = 0; 123 124 qcms_profile* deviceProfile = ImageDecoder::qcmsOutputDeviceProfile(); 125 if (!deviceProfile) 126 return; 127 qcms_profile* inputProfile = qcms_profile_from_memory(data, size); 128 if (!inputProfile) 129 return; 130 131 // We currently only support color profiles for RGB profiled images. 132 ASSERT(icSigRgbData == qcms_profile_get_color_space(inputProfile)); 133 // The input image pixels are RGBA format. 134 qcms_data_type format = QCMS_DATA_RGBA_8; 135 // FIXME: Don't force perceptual intent if the image profile contains an intent. 136 m_transform = qcms_transform_create(inputProfile, format, deviceProfile, QCMS_DATA_RGBA_8, QCMS_INTENT_PERCEPTUAL); 137 138 qcms_profile_release(inputProfile); 139} 140 141void WEBPImageDecoder::readColorProfile(const uint8_t* data, size_t size) 142{ 143 WebPChunkIterator chunkIterator; 144 WebPData inputData = { data, size }; 145 WebPDemuxState state; 146 147 WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &state); 148 if (!WebPDemuxGetChunk(demuxer, "ICCP", 1, &chunkIterator)) { 149 WebPDemuxReleaseChunkIterator(&chunkIterator); 150 WebPDemuxDelete(demuxer); 151 return; 152 } 153 154 const char* profileData = reinterpret_cast<const char*>(chunkIterator.chunk.bytes); 155 size_t profileSize = chunkIterator.chunk.size; 156 157 // Only accept RGB color profiles from input class devices. 158 bool ignoreProfile = false; 159 if (profileSize < ImageDecoder::iccColorProfileHeaderLength) 160 ignoreProfile = true; 161 else if (!ImageDecoder::rgbColorProfile(profileData, profileSize)) 162 ignoreProfile = true; 163 else if (!ImageDecoder::inputDeviceColorProfile(profileData, profileSize)) 164 ignoreProfile = true; 165 166 if (!ignoreProfile) 167 createColorTransform(profileData, profileSize); 168 169 WebPDemuxReleaseChunkIterator(&chunkIterator); 170 WebPDemuxDelete(demuxer); 171} 172 173void WEBPImageDecoder::applyColorProfile(const uint8_t* data, size_t size, ImageFrame& buffer) 174{ 175 int width; 176 int decodedHeight; 177 if (!WebPIDecGetRGB(m_decoder, &decodedHeight, &width, 0, 0)) 178 return; // See also https://bugs.webkit.org/show_bug.cgi?id=74062 179 if (decodedHeight <= 0) 180 return; 181 182 if (!m_haveReadProfile) { 183 readColorProfile(data, size); 184 m_haveReadProfile = true; 185 } 186 187 ASSERT(width == scaledSize().width()); 188 ASSERT(decodedHeight <= scaledSize().height()); 189 190 for (int y = m_decodedHeight; y < decodedHeight; ++y) { 191 uint8_t* row = reinterpret_cast<uint8_t*>(buffer.getAddr(0, y)); 192 if (qcms_transform* transform = colorTransform()) 193 qcms_transform_data_type(transform, row, row, width, QCMS_OUTPUT_RGBX); 194 uint8_t* pixel = row; 195 for (int x = 0; x < width; ++x, pixel += 4) 196 buffer.setRGBA(x, y, pixel[0], pixel[1], pixel[2], pixel[3]); 197 } 198 199 m_decodedHeight = decodedHeight; 200} 201 202#endif // QCMS_WEBP_COLOR_CORRECTION 203 204bool WEBPImageDecoder::decode(bool onlySize) 205{ 206 if (failed()) 207 return false; 208 209 const uint8_t* dataBytes = reinterpret_cast<const uint8_t*>(m_data->data()); 210 const size_t dataSize = m_data->size(); 211 212 if (!ImageDecoder::isSizeAvailable()) { 213 static const size_t imageHeaderSize = 30; 214 if (dataSize < imageHeaderSize) 215 return false; 216 int width, height; 217#ifdef QCMS_WEBP_COLOR_CORRECTION 218 WebPData inputData = { dataBytes, dataSize }; 219 WebPDemuxState state; 220 WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &state); 221 if (!demuxer) 222 return setFailed(); 223 224 width = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); 225 height = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); 226 m_formatFlags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS); 227 m_hasAlpha = !!(m_formatFlags & ALPHA_FLAG); 228 229 WebPDemuxDelete(demuxer); 230 if (state <= WEBP_DEMUX_PARSING_HEADER) 231 return false; 232#elif (WEBP_DECODER_ABI_VERSION >= 0x0163) 233 WebPBitstreamFeatures features; 234 if (WebPGetFeatures(dataBytes, dataSize, &features) != VP8_STATUS_OK) 235 return setFailed(); 236 width = features.width; 237 height = features.height; 238 m_hasAlpha = features.has_alpha; 239#else 240 // Earlier version won't be able to display WebP files with alpha. 241 if (!WebPGetInfo(dataBytes, dataSize, &width, &height)) 242 return setFailed(); 243 m_hasAlpha = false; 244#endif 245 if (!setSize(width, height)) 246 return setFailed(); 247 } 248 249 ASSERT(ImageDecoder::isSizeAvailable()); 250 if (onlySize) 251 return true; 252 253 ASSERT(!m_frameBufferCache.isEmpty()); 254 ImageFrame& buffer = m_frameBufferCache[0]; 255 ASSERT(buffer.status() != ImageFrame::FrameComplete); 256 257 if (buffer.status() == ImageFrame::FrameEmpty) { 258 if (!buffer.setSize(size().width(), size().height())) 259 return setFailed(); 260 buffer.setStatus(ImageFrame::FramePartial); 261 buffer.setHasAlpha(m_hasAlpha); 262 buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); 263 } 264 265 if (!m_decoder) { 266 WEBP_CSP_MODE mode = outputMode(m_hasAlpha); 267 if (!m_premultiplyAlpha) 268 mode = outputMode(false); 269 if ((m_formatFlags & ICCP_FLAG) && !ignoresGammaAndColorProfile()) 270 mode = MODE_RGBA; // Decode to RGBA for input to libqcms. 271 int rowStride = size().width() * sizeof(ImageFrame::PixelData); 272 uint8_t* output = reinterpret_cast<uint8_t*>(buffer.getAddr(0, 0)); 273 int outputSize = size().height() * rowStride; 274 m_decoder = WebPINewRGB(mode, output, outputSize, rowStride); 275 if (!m_decoder) 276 return setFailed(); 277 } 278 279 switch (WebPIUpdate(m_decoder, dataBytes, dataSize)) { 280 case VP8_STATUS_OK: 281 if ((m_formatFlags & ICCP_FLAG) && !ignoresGammaAndColorProfile()) 282 applyColorProfile(dataBytes, dataSize, buffer); 283 buffer.setStatus(ImageFrame::FrameComplete); 284 clear(); 285 return true; 286 case VP8_STATUS_SUSPENDED: 287 if ((m_formatFlags & ICCP_FLAG) && !ignoresGammaAndColorProfile()) 288 applyColorProfile(dataBytes, dataSize, buffer); 289 return false; 290 default: 291 clear(); 292 return setFailed(); 293 } 294} 295 296} // namespace WebCore 297 298#endif 299