| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "SkCodecPriv.h" | 8 #include "SkCodecPriv.h" |
| 9 #include "SkWebpCodec.h" | 9 #include "SkWebpCodec.h" |
| 10 #include "SkStreamPriv.h" |
| 10 #include "SkTemplates.h" | 11 #include "SkTemplates.h" |
| 11 | 12 |
| 12 // A WebP decoder on top of (subset of) libwebp | 13 // A WebP decoder on top of (subset of) libwebp |
| 13 // For more information on WebP image format, and libwebp library, see: | 14 // For more information on WebP image format, and libwebp library, see: |
| 14 // https://code.google.com/speed/webp/ | 15 // https://code.google.com/speed/webp/ |
| 15 // http://www.webmproject.org/code/#libwebp-webp-image-library | 16 // http://www.webmproject.org/code/#libwebp-webp-image-library |
| 16 // https://chromium.googlesource.com/webm/libwebp | 17 // https://chromium.googlesource.com/webm/libwebp |
| 17 | 18 |
| 18 // If moving libwebp out of skia source tree, path for webp headers must be | 19 // If moving libwebp out of skia source tree, path for webp headers must be |
| 19 // updated accordingly. Here, we enforce using local copy in webp sub-directory. | 20 // updated accordingly. Here, we enforce using local copy in webp sub-directory. |
| 20 #include "webp/decode.h" | 21 #include "webp/decode.h" |
| 21 #include "webp/demux.h" | 22 #include "webp/demux.h" |
| 22 #include "webp/encode.h" | 23 #include "webp/encode.h" |
| 23 | 24 |
| 24 bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) { | 25 bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) { |
| 25 // WEBP starts with the following: | 26 // WEBP starts with the following: |
| 26 // RIFFXXXXWEBPVP | 27 // RIFFXXXXWEBPVP |
| 27 // Where XXXX is unspecified. | 28 // Where XXXX is unspecified. |
| 28 const char* bytes = static_cast<const char*>(buf); | 29 const char* bytes = static_cast<const char*>(buf); |
| 29 return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "W
EBPVP", 6); | 30 return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "W
EBPVP", 6); |
| 30 } | 31 } |
| 31 | 32 |
| 32 // Parse headers of RIFF container, and check for valid Webp (VP8) content. | 33 // Parse headers of RIFF container, and check for valid Webp (VP8) content. |
| 33 // NOTE: This calls peek instead of read, since onGetPixels will need these | 34 // NOTE: This calls peek instead of read, since onGetPixels will need these |
| 34 // bytes again. | 35 // bytes again. |
| 35 // Returns an SkWebpCodec on success; | 36 // Returns an SkWebpCodec on success; |
| 36 SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { | 37 SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { |
| 37 SkAutoTDelete<SkStream> streamDeleter(stream); | 38 SkAutoTDelete<SkStream> streamDeleter(stream); |
| 38 | 39 |
| 39 unsigned char buffer[WEBP_VP8_HEADER_SIZE]; | 40 // Webp demux needs a contiguous data buffer. |
| 40 SkASSERT(WEBP_VP8_HEADER_SIZE <= SkCodec::MinBufferedBytesNeeded()); | 41 sk_sp<SkData> data = nullptr; |
| 42 if (stream->getMemoryBase()) { |
| 43 // It is safe to make without copy because we'll hold onto the stream. |
| 44 data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLengt
h()); |
| 45 } else { |
| 46 data = SkCopyStreamToData(stream); |
| 41 | 47 |
| 42 const size_t bytesPeeked = stream->peek(buffer, WEBP_VP8_HEADER_SIZE); | 48 // If we are forced to copy the stream to a data, we can go ahead and de
lete the stream. |
| 43 if (bytesPeeked != WEBP_VP8_HEADER_SIZE) { | 49 streamDeleter.reset(nullptr); |
| 44 // Use read + rewind as a backup | 50 } |
| 45 if (stream->read(buffer, WEBP_VP8_HEADER_SIZE) != WEBP_VP8_HEADER_SIZE | 51 |
| 46 || !stream->rewind()) | 52 // It's a little strange that the |demux| will outlive |webpData|, though it
needs the |
| 53 // pointer in |webpData| to remain valid. This works because the pointer re
mains valid |
| 54 // until the SkData is freed. |
| 55 WebPData webpData = { data->bytes(), data->size() }; |
| 56 SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&webpD
ata, nullptr)); |
| 57 if (nullptr == demux) { |
| 47 return nullptr; | 58 return nullptr; |
| 48 } | 59 } |
| 49 | 60 |
| 50 WebPBitstreamFeatures features; | 61 WebPChunkIterator chunkIterator; |
| 51 VP8StatusCode status = WebPGetFeatures(buffer, WEBP_VP8_HEADER_SIZE, &featur
es); | 62 SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&c
hunkIterator); |
| 52 if (VP8_STATUS_OK != status) { | 63 sk_sp<SkColorSpace> colorSpace = nullptr; |
| 53 return nullptr; // Invalid WebP file. | 64 if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { |
| 65 colorSpace = SkColorSpace::NewICC(chunkIterator.chunk.bytes, chunkIterat
or.chunk.size); |
| 54 } | 66 } |
| 55 | 67 |
| 56 // sanity check for image size that's about to be decoded. | 68 if (!colorSpace) { |
| 69 colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); |
| 70 } |
| 71 |
| 72 // Since we do not yet support animation, we get the |width|, |height|, |col
or|, and |alpha| |
| 73 // from the first frame. It's the only frame we will decode. |
| 74 // |
| 75 // TODO: |
| 76 // When we support animation, we'll want to report the canvas width and canv
as height instead. |
| 77 // We can get these from the |demux| directly. |
| 78 // What |color| and |alpha| will we want to report though? WebP allows diff
erent frames |
| 79 // to be encoded in different ways, making the encoded format difficult to d
escribe. |
| 80 WebPIterator frame; |
| 81 SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); |
| 82 if (!WebPDemuxGetFrame(demux, 1, &frame)) { |
| 83 return nullptr; |
| 84 } |
| 85 |
| 86 // Sanity check for image size that's about to be decoded. |
| 57 { | 87 { |
| 58 const int64_t size = sk_64_mul(features.width, features.height); | 88 const int64_t size = sk_64_mul(frame.width, frame.height); |
| 59 if (!sk_64_isS32(size)) { | 89 if (!sk_64_isS32(size)) { |
| 60 return nullptr; | 90 return nullptr; |
| 61 } | 91 } |
| 62 // now check that if we are 4-bytes per pixel, we also don't overflow | 92 // now check that if we are 4-bytes per pixel, we also don't overflow |
| 63 if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) { | 93 if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) { |
| 64 return nullptr; | 94 return nullptr; |
| 65 } | 95 } |
| 66 } | 96 } |
| 67 | 97 |
| 98 // TODO: |
| 99 // The only reason we actually need to call WebPGetFeatures() is to get the
|features.format|. |
| 100 // This call actually re-reads the frame header. Should we suggest that lib
webp expose |
| 101 // the format on the |frame|? |
| 102 WebPBitstreamFeatures features; |
| 103 VP8StatusCode status = WebPGetFeatures(frame.fragment.bytes, frame.fragment.
size, &features); |
| 104 if (VP8_STATUS_OK != status) { |
| 105 return nullptr; |
| 106 } |
| 107 |
| 68 SkEncodedInfo::Color color; | 108 SkEncodedInfo::Color color; |
| 69 SkEncodedInfo::Alpha alpha; | 109 SkEncodedInfo::Alpha alpha; |
| 70 switch (features.format) { | 110 switch (features.format) { |
| 71 case 0: | 111 case 0: |
| 72 // This indicates a "mixed" format. We would see this for | 112 // This indicates a "mixed" format. We would see this for |
| 73 // animated webps or for webps encoded in multiple fragments. | 113 // animated webps or for webps encoded in multiple fragments. |
| 74 // I believe that this is a rare case. | 114 // I believe that this is a rare case. |
| 75 // We could also guess kYUV here, but I think it makes more | 115 // We could also guess kYUV here, but I think it makes more |
| 76 // sense to guess kBGRA which is likely closer to the final | 116 // sense to guess kBGRA which is likely closer to the final |
| 77 // output. Otherwise, we might end up converting | 117 // output. Otherwise, we might end up converting |
| (...skipping 13 matching lines...) Expand all Loading... |
| 91 break; | 131 break; |
| 92 case 2: | 132 case 2: |
| 93 // This is the lossless format (BGRA). | 133 // This is the lossless format (BGRA). |
| 94 color = SkEncodedInfo::kBGRA_Color; | 134 color = SkEncodedInfo::kBGRA_Color; |
| 95 alpha = SkEncodedInfo::kUnpremul_Alpha; | 135 alpha = SkEncodedInfo::kUnpremul_Alpha; |
| 96 break; | 136 break; |
| 97 default: | 137 default: |
| 98 return nullptr; | 138 return nullptr; |
| 99 } | 139 } |
| 100 | 140 |
| 101 // FIXME (msarett): | |
| 102 // Temporary strategy for getting ICC profiles from webps. Once the increme
ntal decoding | |
| 103 // API lands, we will use the WebPDemuxer to manage the entire decode. | |
| 104 sk_sp<SkColorSpace> colorSpace = nullptr; | |
| 105 const void* memory = stream->getMemoryBase(); | |
| 106 if (memory) { | |
| 107 WebPData data = { (const uint8_t*) memory, stream->getLength() }; | |
| 108 WebPDemuxState state; | |
| 109 SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&d
ata, &state)); | |
| 110 | |
| 111 WebPChunkIterator chunkIterator; | |
| 112 SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoC
I(&chunkIterator); | |
| 113 if (demux && WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { | |
| 114 colorSpace = SkColorSpace::NewICC(chunkIterator.chunk.bytes, chunkIt
erator.chunk.size); | |
| 115 } | |
| 116 } | |
| 117 | |
| 118 if (!colorSpace) { | |
| 119 colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); | |
| 120 } | |
| 121 | |
| 122 SkEncodedInfo info = SkEncodedInfo::Make(color, alpha, 8); | 141 SkEncodedInfo info = SkEncodedInfo::Make(color, alpha, 8); |
| 123 return new SkWebpCodec(features.width, features.height, info, colorSpace, | 142 return new SkWebpCodec(features.width, features.height, info, std::move(colo
rSpace), |
| 124 streamDeleter.release()); | 143 streamDeleter.release(), demux.release(), std::move(d
ata)); |
| 125 } | 144 } |
| 126 | 145 |
| 127 // This version is slightly different from SkCodecPriv's version of conversion_p
ossible. It | 146 // This version is slightly different from SkCodecPriv's version of conversion_p
ossible. It |
| 128 // supports both byte orders for 8888. | 147 // supports both byte orders for 8888. |
| 129 static bool webp_conversion_possible(const SkImageInfo& dst, const SkImageInfo&
src) { | 148 static bool webp_conversion_possible(const SkImageInfo& dst, const SkImageInfo&
src) { |
| 130 if (!valid_alpha(dst.alphaType(), src.alphaType())) { | 149 if (!valid_alpha(dst.alphaType(), src.alphaType())) { |
| 131 return false; | 150 return false; |
| 132 } | 151 } |
| 133 | 152 |
| 134 switch (dst.colorType()) { | 153 switch (dst.colorType()) { |
| (...skipping 16 matching lines...) Expand all Loading... |
| 151 dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight)); | 170 dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight)); |
| 152 return dim; | 171 return dim; |
| 153 } | 172 } |
| 154 | 173 |
| 155 bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) { | 174 bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) { |
| 156 const SkImageInfo& info = this->getInfo(); | 175 const SkImageInfo& info = this->getInfo(); |
| 157 return dim.width() >= 1 && dim.width() <= info.width() | 176 return dim.width() >= 1 && dim.width() <= info.width() |
| 158 && dim.height() >= 1 && dim.height() <= info.height(); | 177 && dim.height() >= 1 && dim.height() <= info.height(); |
| 159 } | 178 } |
| 160 | 179 |
| 161 | |
| 162 static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) { | 180 static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) { |
| 163 switch (ct) { | 181 switch (ct) { |
| 164 case kBGRA_8888_SkColorType: | 182 case kBGRA_8888_SkColorType: |
| 165 return premultiply ? MODE_bgrA : MODE_BGRA; | 183 return premultiply ? MODE_bgrA : MODE_BGRA; |
| 166 case kRGBA_8888_SkColorType: | 184 case kRGBA_8888_SkColorType: |
| 167 return premultiply ? MODE_rgbA : MODE_RGBA; | 185 return premultiply ? MODE_rgbA : MODE_RGBA; |
| 168 case kRGB_565_SkColorType: | 186 case kRGB_565_SkColorType: |
| 169 return MODE_RGB_565; | 187 return MODE_RGB_565; |
| 170 default: | 188 default: |
| 171 return MODE_LAST; | 189 return MODE_LAST; |
| 172 } | 190 } |
| 173 } | 191 } |
| 174 | 192 |
| 175 // The WebP decoding API allows us to incrementally pass chunks of bytes as we r
eceive them to the | |
| 176 // decoder with WebPIAppend. In order to do so, we need to read chunks from the
SkStream. This size | |
| 177 // is arbitrary. | |
| 178 static const size_t BUFFER_SIZE = 4096; | |
| 179 | |
| 180 bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { | 193 bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { |
| 181 if (!desiredSubset) { | 194 if (!desiredSubset) { |
| 182 return false; | 195 return false; |
| 183 } | 196 } |
| 184 | 197 |
| 185 SkIRect dimensions = SkIRect::MakeSize(this->getInfo().dimensions()); | 198 SkIRect dimensions = SkIRect::MakeSize(this->getInfo().dimensions()); |
| 186 if (!dimensions.contains(*desiredSubset)) { | 199 if (!dimensions.contains(*desiredSubset)) { |
| 187 return false; | 200 return false; |
| 188 } | 201 } |
| 189 | 202 |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 256 config.options.scaled_height = dstDimensions.height(); | 269 config.options.scaled_height = dstDimensions.height(); |
| 257 } | 270 } |
| 258 | 271 |
| 259 config.output.colorspace = webp_decode_mode(dstInfo.colorType(), | 272 config.output.colorspace = webp_decode_mode(dstInfo.colorType(), |
| 260 dstInfo.alphaType() == kPremul_SkAlphaType); | 273 dstInfo.alphaType() == kPremul_SkAlphaType); |
| 261 config.output.u.RGBA.rgba = (uint8_t*) dst; | 274 config.output.u.RGBA.rgba = (uint8_t*) dst; |
| 262 config.output.u.RGBA.stride = (int) rowBytes; | 275 config.output.u.RGBA.stride = (int) rowBytes; |
| 263 config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes); | 276 config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes); |
| 264 config.output.is_external_memory = 1; | 277 config.output.is_external_memory = 1; |
| 265 | 278 |
| 279 WebPIterator frame; |
| 280 SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); |
| 281 // If this succeeded in NewFromStream(), it should succeed again here. |
| 282 SkAssertResult(WebPDemuxGetFrame(fDemux, 1, &frame)); |
| 283 |
| 266 SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &co
nfig)); | 284 SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &co
nfig)); |
| 267 if (!idec) { | 285 if (!idec) { |
| 268 return kInvalidInput; | 286 return kInvalidInput; |
| 269 } | 287 } |
| 270 | 288 |
| 271 SkAutoTMalloc<uint8_t> storage(BUFFER_SIZE); | 289 switch (WebPIUpdate(idec, frame.fragment.bytes, frame.fragment.size)) { |
| 272 uint8_t* buffer = storage.get(); | 290 case VP8_STATUS_OK: |
| 273 while (true) { | 291 return kSuccess; |
| 274 const size_t bytesRead = stream()->read(buffer, BUFFER_SIZE); | 292 case VP8_STATUS_SUSPENDED: |
| 275 if (0 == bytesRead) { | 293 WebPIDecGetRGB(idec, rowsDecoded, nullptr, nullptr, nullptr); |
| 276 WebPIDecGetRGB(idec, rowsDecoded, NULL, NULL, NULL); | |
| 277 return kIncompleteInput; | 294 return kIncompleteInput; |
| 278 } | 295 default: |
| 279 | 296 return kInvalidInput; |
| 280 switch (WebPIAppend(idec, buffer, bytesRead)) { | |
| 281 case VP8_STATUS_OK: | |
| 282 return kSuccess; | |
| 283 case VP8_STATUS_SUSPENDED: | |
| 284 // Break out of the switch statement. Continue the loop. | |
| 285 break; | |
| 286 default: | |
| 287 return kInvalidInput; | |
| 288 } | |
| 289 } | 297 } |
| 290 } | 298 } |
| 291 | 299 |
| 292 SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info, | 300 SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info, |
| 293 sk_sp<SkColorSpace> colorSpace, SkStream* stream) | 301 sk_sp<SkColorSpace> colorSpace, SkStream* stream, WebPD
emuxer* demux, |
| 294 : INHERITED(width, height, info, stream, colorSpace) | 302 sk_sp<SkData> data) |
| 303 : INHERITED(width, height, info, stream, std::move(colorSpace)) |
| 304 , fDemux(demux) |
| 305 , fData(std::move(data)) |
| 295 {} | 306 {} |
| OLD | NEW |