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 |