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 "SkTemplates.h" | 10 #include "SkTemplates.h" |
(...skipping 18 matching lines...) Expand all Loading... | |
29 return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "W EBPVP", 6); | 29 return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "W EBPVP", 6); |
30 } | 30 } |
31 | 31 |
32 // Parse headers of RIFF container, and check for valid Webp (VP8) content. | 32 // 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 | 33 // NOTE: This calls peek instead of read, since onGetPixels will need these |
34 // bytes again. | 34 // bytes again. |
35 // Returns an SkWebpCodec on success; | 35 // Returns an SkWebpCodec on success; |
36 SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { | 36 SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { |
37 SkAutoTDelete<SkStream> streamDeleter(stream); | 37 SkAutoTDelete<SkStream> streamDeleter(stream); |
38 | 38 |
39 unsigned char buffer[WEBP_VP8_HEADER_SIZE]; | 39 // Webp demux needs a contiguous data buffer. |
40 SkASSERT(WEBP_VP8_HEADER_SIZE <= SkCodec::MinBufferedBytesNeeded()); | 40 sk_sp<SkData> data = nullptr; |
41 if (stream->getMemoryBase()) { | |
scroggo
2016/09/07 14:20:51
I think we have some common code to handle this ca
msarett
2016/09/07 16:47:10
Ahh yes. The common code always performs a copy,
| |
42 data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLengt h()); | |
scroggo
2016/09/07 14:20:51
Maybe add a comment here that this is safe because
msarett
2016/09/07 16:47:10
SGTM
| |
43 } else if (stream->hasLength()) { | |
44 size_t size = stream->getLength(); | |
45 data = SkData::MakeUninitialized(size); | |
46 stream->read(data->writable_data(), size); | |
41 | 47 |
42 const size_t bytesPeeked = stream->peek(buffer, WEBP_VP8_HEADER_SIZE); | 48 // Go ahead and delete the stream, we don't need it anymore. |
43 if (bytesPeeked != WEBP_VP8_HEADER_SIZE) { | 49 streamDeleter.reset(nullptr); |
44 // Use read + rewind as a backup | 50 } else { |
45 if (stream->read(buffer, WEBP_VP8_HEADER_SIZE) != WEBP_VP8_HEADER_SIZE | 51 SkDynamicMemoryWStream writeStream; |
46 || !stream->rewind()) | 52 static constexpr size_t kChunkSize = 8192; // Arbitrary |
53 uint8_t buffer[kChunkSize]; | |
54 size_t bytesRead; | |
55 do { | |
56 bytesRead = stream->read(buffer, kChunkSize); | |
57 writeStream.write(buffer, bytesRead); | |
58 } while (bytesRead == kChunkSize); | |
59 data.reset(writeStream.copyToData()); | |
60 | |
61 streamDeleter.reset(nullptr); | |
62 } | |
63 | |
64 WebPData webpData = { data->bytes(), data->size() }; | |
65 SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&webpD ata, nullptr)); | |
scroggo
2016/09/07 14:20:51
I think this is OK (in fact I think it's what chro
msarett
2016/09/07 16:47:10
Agree that this is weird... Don't think it's nice
| |
66 if (nullptr == demux) { | |
67 return nullptr; | |
68 } | |
69 | |
70 WebPChunkIterator chunkIterator; | |
71 SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&c hunkIterator); | |
72 sk_sp<SkColorSpace> colorSpace = nullptr; | |
73 if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { | |
74 colorSpace = SkColorSpace::NewICC(chunkIterator.chunk.bytes, chunkIterat or.chunk.size); | |
75 } | |
76 | |
77 if (!colorSpace) { | |
78 colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); | |
79 } | |
80 | |
81 WebPIterator frame; | |
82 SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); | |
83 if (!WebPDemuxGetFrame(demux, 1, &frame)) { | |
scroggo
2016/09/07 14:20:51
I forget, does this decode the frame? I guess not,
msarett
2016/09/07 16:47:10
Yes, this won't decode the image - just sets us up
| |
47 return nullptr; | 84 return nullptr; |
48 } | 85 } |
49 | 86 |
50 WebPBitstreamFeatures features; | 87 WebPBitstreamFeatures features; |
51 VP8StatusCode status = WebPGetFeatures(buffer, WEBP_VP8_HEADER_SIZE, &featur es); | 88 VP8StatusCode status = WebPGetFeatures(frame.fragment.bytes, frame.fragment. size, &features); |
52 if (VP8_STATUS_OK != status) { | 89 if (VP8_STATUS_OK != status) { |
53 return nullptr; // Invalid WebP file. | 90 return nullptr; |
54 } | 91 } |
55 | 92 |
56 // sanity check for image size that's about to be decoded. | 93 // Sanity check for image size that's about to be decoded. |
57 { | 94 { |
58 const int64_t size = sk_64_mul(features.width, features.height); | 95 const int64_t size = sk_64_mul(features.width, features.height); |
59 if (!sk_64_isS32(size)) { | 96 if (!sk_64_isS32(size)) { |
60 return nullptr; | 97 return nullptr; |
61 } | 98 } |
62 // now check that if we are 4-bytes per pixel, we also don't overflow | 99 // now check that if we are 4-bytes per pixel, we also don't overflow |
63 if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) { | 100 if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) { |
64 return nullptr; | 101 return nullptr; |
65 } | 102 } |
66 } | 103 } |
(...skipping 24 matching lines...) Expand all Loading... | |
91 break; | 128 break; |
92 case 2: | 129 case 2: |
93 // This is the lossless format (BGRA). | 130 // This is the lossless format (BGRA). |
94 color = SkEncodedInfo::kBGRA_Color; | 131 color = SkEncodedInfo::kBGRA_Color; |
95 alpha = SkEncodedInfo::kUnpremul_Alpha; | 132 alpha = SkEncodedInfo::kUnpremul_Alpha; |
96 break; | 133 break; |
97 default: | 134 default: |
98 return nullptr; | 135 return nullptr; |
99 } | 136 } |
100 | 137 |
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); | 138 SkEncodedInfo info = SkEncodedInfo::Make(color, alpha, 8); |
123 return new SkWebpCodec(features.width, features.height, info, colorSpace, | 139 return new SkWebpCodec(features.width, features.height, info, std::move(colo rSpace), |
124 streamDeleter.release()); | 140 streamDeleter.release(), demux.release(), std::move(d ata)); |
scroggo
2016/09/07 14:20:51
Sometimes streamDeleter will hold null. I forgot,
msarett
2016/09/07 16:47:10
I hadn't thought about this, but now that I look c
| |
125 } | 141 } |
126 | 142 |
127 // This version is slightly different from SkCodecPriv's version of conversion_p ossible. It | 143 // This version is slightly different from SkCodecPriv's version of conversion_p ossible. It |
128 // supports both byte orders for 8888. | 144 // supports both byte orders for 8888. |
129 static bool webp_conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) { | 145 static bool webp_conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) { |
130 if (!valid_alpha(dst.alphaType(), src.alphaType())) { | 146 if (!valid_alpha(dst.alphaType(), src.alphaType())) { |
131 return false; | 147 return false; |
132 } | 148 } |
133 | 149 |
134 switch (dst.colorType()) { | 150 switch (dst.colorType()) { |
(...skipping 16 matching lines...) Expand all Loading... | |
151 dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight)); | 167 dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight)); |
152 return dim; | 168 return dim; |
153 } | 169 } |
154 | 170 |
155 bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) { | 171 bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) { |
156 const SkImageInfo& info = this->getInfo(); | 172 const SkImageInfo& info = this->getInfo(); |
157 return dim.width() >= 1 && dim.width() <= info.width() | 173 return dim.width() >= 1 && dim.width() <= info.width() |
158 && dim.height() >= 1 && dim.height() <= info.height(); | 174 && dim.height() >= 1 && dim.height() <= info.height(); |
159 } | 175 } |
160 | 176 |
161 | |
162 static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) { | 177 static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) { |
163 switch (ct) { | 178 switch (ct) { |
164 case kBGRA_8888_SkColorType: | 179 case kBGRA_8888_SkColorType: |
165 return premultiply ? MODE_bgrA : MODE_BGRA; | 180 return premultiply ? MODE_bgrA : MODE_BGRA; |
166 case kRGBA_8888_SkColorType: | 181 case kRGBA_8888_SkColorType: |
167 return premultiply ? MODE_rgbA : MODE_RGBA; | 182 return premultiply ? MODE_rgbA : MODE_RGBA; |
168 case kRGB_565_SkColorType: | 183 case kRGB_565_SkColorType: |
169 return MODE_RGB_565; | 184 return MODE_RGB_565; |
170 default: | 185 default: |
171 return MODE_LAST; | 186 return MODE_LAST; |
172 } | 187 } |
173 } | 188 } |
174 | 189 |
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 { | 190 bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { |
181 if (!desiredSubset) { | 191 if (!desiredSubset) { |
182 return false; | 192 return false; |
183 } | 193 } |
184 | 194 |
185 SkIRect dimensions = SkIRect::MakeSize(this->getInfo().dimensions()); | 195 SkIRect dimensions = SkIRect::MakeSize(this->getInfo().dimensions()); |
186 if (!dimensions.contains(*desiredSubset)) { | 196 if (!dimensions.contains(*desiredSubset)) { |
187 return false; | 197 return false; |
188 } | 198 } |
189 | 199 |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
256 config.options.scaled_height = dstDimensions.height(); | 266 config.options.scaled_height = dstDimensions.height(); |
257 } | 267 } |
258 | 268 |
259 config.output.colorspace = webp_decode_mode(dstInfo.colorType(), | 269 config.output.colorspace = webp_decode_mode(dstInfo.colorType(), |
260 dstInfo.alphaType() == kPremul_SkAlphaType); | 270 dstInfo.alphaType() == kPremul_SkAlphaType); |
261 config.output.u.RGBA.rgba = (uint8_t*) dst; | 271 config.output.u.RGBA.rgba = (uint8_t*) dst; |
262 config.output.u.RGBA.stride = (int) rowBytes; | 272 config.output.u.RGBA.stride = (int) rowBytes; |
263 config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes); | 273 config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes); |
264 config.output.is_external_memory = 1; | 274 config.output.is_external_memory = 1; |
265 | 275 |
276 WebPIterator frame; | |
277 SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); | |
278 // If this succeeded in NewFromStream(), it should succeed again here. | |
279 SkAssertResult(WebPDemuxGetFrame(fDemux, 1, &frame)); | |
280 | |
266 SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &co nfig)); | 281 SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &co nfig)); |
267 if (!idec) { | 282 if (!idec) { |
268 return kInvalidInput; | 283 return kInvalidInput; |
269 } | 284 } |
270 | 285 |
271 SkAutoTMalloc<uint8_t> storage(BUFFER_SIZE); | 286 switch (WebPIUpdate(idec, frame.fragment.bytes, frame.fragment.size)) { |
msarett
2016/09/06 23:47:11
It's kind of ugly to create the WebPIDecoder with
scroggo
2016/09/07 14:20:51
It's the same as we did before though, right?
msarett
2016/09/07 16:47:10
Yes, true. Before it seemed to make a little more
| |
272 uint8_t* buffer = storage.get(); | 287 case VP8_STATUS_OK: |
273 while (true) { | 288 return kSuccess; |
274 const size_t bytesRead = stream()->read(buffer, BUFFER_SIZE); | 289 case VP8_STATUS_SUSPENDED: |
275 if (0 == bytesRead) { | 290 WebPIDecGetRGB(idec, rowsDecoded, nullptr, nullptr, nullptr); |
276 WebPIDecGetRGB(idec, rowsDecoded, NULL, NULL, NULL); | |
277 return kIncompleteInput; | 291 return kIncompleteInput; |
278 } | 292 default: |
279 | 293 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 } | 294 } |
290 } | 295 } |
291 | 296 |
292 SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info, | 297 SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info, |
293 sk_sp<SkColorSpace> colorSpace, SkStream* stream) | 298 sk_sp<SkColorSpace> colorSpace, SkStream* stream, WebPD emuxer* demux, |
294 : INHERITED(width, height, info, stream, colorSpace) | 299 sk_sp<SkData> data) |
300 : INHERITED(width, height, info, stream, std::move(colorSpace)) | |
301 , fDemux(demux) | |
302 , fData(std::move(data)) | |
295 {} | 303 {} |
OLD | NEW |