Chromium Code Reviews| 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 |