blob: dbc141ebd67d89b752a1d6503d5548a48cbd77bd [file] [log] [blame]
scroggo6f5e6192015-06-18 12:53:43 -07001/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
scroggocc2feb12015-08-14 08:32:46 -07008#include "SkCodecPriv.h"
msarette99883f2016-09-08 06:05:35 -07009#include "SkColorSpaceXform.h"
Robert Phillipsb3050b92017-02-06 13:12:18 +000010#include "SkWebpCodec.h"
msarettff2a6c82016-09-07 11:23:28 -070011#include "SkStreamPriv.h"
scroggo6f5e6192015-06-18 12:53:43 -070012#include "SkTemplates.h"
13
14// A WebP decoder on top of (subset of) libwebp
15// For more information on WebP image format, and libwebp library, see:
16// https://code.google.com/speed/webp/
17// http://www.webmproject.org/code/#libwebp-webp-image-library
18// https://chromium.googlesource.com/webm/libwebp
19
20// If moving libwebp out of skia source tree, path for webp headers must be
21// updated accordingly. Here, we enforce using local copy in webp sub-directory.
22#include "webp/decode.h"
msarett9d15dab2016-08-24 07:36:06 -070023#include "webp/demux.h"
scroggo6f5e6192015-06-18 12:53:43 -070024#include "webp/encode.h"
25
scroggodb30be22015-12-08 18:54:13 -080026bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) {
scroggo6f5e6192015-06-18 12:53:43 -070027 // WEBP starts with the following:
28 // RIFFXXXXWEBPVP
29 // Where XXXX is unspecified.
scroggodb30be22015-12-08 18:54:13 -080030 const char* bytes = static_cast<const char*>(buf);
31 return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
scroggo6f5e6192015-06-18 12:53:43 -070032}
33
scroggo6f5e6192015-06-18 12:53:43 -070034// Parse headers of RIFF container, and check for valid Webp (VP8) content.
35// NOTE: This calls peek instead of read, since onGetPixels will need these
36// bytes again.
msarettac6c7502016-04-25 09:30:24 -070037// Returns an SkWebpCodec on success;
38SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
Ben Wagner145dbcd2016-11-03 14:40:50 -040039 std::unique_ptr<SkStream> streamDeleter(stream);
msarettac6c7502016-04-25 09:30:24 -070040
msarettff2a6c82016-09-07 11:23:28 -070041 // Webp demux needs a contiguous data buffer.
42 sk_sp<SkData> data = nullptr;
43 if (stream->getMemoryBase()) {
44 // It is safe to make without copy because we'll hold onto the stream.
45 data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength());
46 } else {
47 data = SkCopyStreamToData(stream);
scroggodb30be22015-12-08 18:54:13 -080048
msarettff2a6c82016-09-07 11:23:28 -070049 // If we are forced to copy the stream to a data, we can go ahead and delete the stream.
50 streamDeleter.reset(nullptr);
51 }
52
53 // It's a little strange that the |demux| will outlive |webpData|, though it needs the
54 // pointer in |webpData| to remain valid. This works because the pointer remains valid
55 // until the SkData is freed.
56 WebPData webpData = { data->bytes(), data->size() };
57 SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&webpData, nullptr));
58 if (nullptr == demux) {
msarettac6c7502016-04-25 09:30:24 -070059 return nullptr;
scroggo6f5e6192015-06-18 12:53:43 -070060 }
61
msarettff2a6c82016-09-07 11:23:28 -070062 WebPChunkIterator chunkIterator;
63 SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator);
64 sk_sp<SkColorSpace> colorSpace = nullptr;
raftiasd737bee2016-12-08 10:53:24 -050065 bool unsupportedICC = false;
msarettff2a6c82016-09-07 11:23:28 -070066 if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) {
Brian Osman526972e2016-10-24 09:24:02 -040067 colorSpace = SkColorSpace::MakeICC(chunkIterator.chunk.bytes, chunkIterator.chunk.size);
raftiasd737bee2016-12-08 10:53:24 -050068 if (!colorSpace) {
69 unsupportedICC = true;
70 }
scroggo6f5e6192015-06-18 12:53:43 -070071 }
msarettff2a6c82016-09-07 11:23:28 -070072 if (!colorSpace) {
Brian Osman526972e2016-10-24 09:24:02 -040073 colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
msarettff2a6c82016-09-07 11:23:28 -070074 }
75
Robert Phillipsb3050b92017-02-06 13:12:18 +000076 // Since we do not yet support animation, we get the |width|, |height|, |color|, and |alpha|
77 // from the first frame. It's the only frame we will decode.
78 //
79 // TODO:
80 // When we support animation, we'll want to report the canvas width and canvas height instead.
81 // We can get these from the |demux| directly.
82 // What |color| and |alpha| will we want to report though? WebP allows different frames
83 // to be encoded in different ways, making the encoded format difficult to describe.
msarettff2a6c82016-09-07 11:23:28 -070084 WebPIterator frame;
85 SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
86 if (!WebPDemuxGetFrame(demux, 1, &frame)) {
87 return nullptr;
88 }
89
Robert Phillipsb3050b92017-02-06 13:12:18 +000090 // Sanity check for image size that's about to be decoded.
91 {
92 const int64_t size = sk_64_mul(frame.width, frame.height);
93 if (!sk_64_isS32(size)) {
94 return nullptr;
95 }
96 // now check that if we are 4-bytes per pixel, we also don't overflow
97 if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) {
98 return nullptr;
99 }
100 }
101
102 // TODO:
103 // The only reason we actually need to call WebPGetFeatures() is to get the |features.format|.
104 // This call actually re-reads the frame header. Should we suggest that libwebp expose
105 // the format on the |frame|?
msarettff2a6c82016-09-07 11:23:28 -0700106 WebPBitstreamFeatures features;
107 VP8StatusCode status = WebPGetFeatures(frame.fragment.bytes, frame.fragment.size, &features);
108 if (VP8_STATUS_OK != status) {
109 return nullptr;
110 }
111
msarettac6c7502016-04-25 09:30:24 -0700112 SkEncodedInfo::Color color;
113 SkEncodedInfo::Alpha alpha;
114 switch (features.format) {
115 case 0:
Robert Phillipsb3050b92017-02-06 13:12:18 +0000116 // This indicates a "mixed" format. We would see this for
117 // animated webps or for webps encoded in multiple fragments.
msarettac6c7502016-04-25 09:30:24 -0700118 // I believe that this is a rare case.
119 // We could also guess kYUV here, but I think it makes more
120 // sense to guess kBGRA which is likely closer to the final
121 // output. Otherwise, we might end up converting
122 // BGRA->YUVA->BGRA.
123 color = SkEncodedInfo::kBGRA_Color;
124 alpha = SkEncodedInfo::kUnpremul_Alpha;
125 break;
126 case 1:
127 // This is the lossy format (YUV).
Robert Phillipsb3050b92017-02-06 13:12:18 +0000128 if (SkToBool(features.has_alpha)) {
msarettac6c7502016-04-25 09:30:24 -0700129 color = SkEncodedInfo::kYUVA_Color;
msarettc30c4182016-04-20 11:53:35 -0700130 alpha = SkEncodedInfo::kUnpremul_Alpha;
msarettac6c7502016-04-25 09:30:24 -0700131 } else {
132 color = SkEncodedInfo::kYUV_Color;
133 alpha = SkEncodedInfo::kOpaque_Alpha;
134 }
135 break;
136 case 2:
137 // This is the lossless format (BGRA).
msarettac6c7502016-04-25 09:30:24 -0700138 color = SkEncodedInfo::kBGRA_Color;
139 alpha = SkEncodedInfo::kUnpremul_Alpha;
140 break;
141 default:
142 return nullptr;
scroggo6f5e6192015-06-18 12:53:43 -0700143 }
scroggo6f5e6192015-06-18 12:53:43 -0700144
msarettac6c7502016-04-25 09:30:24 -0700145 SkEncodedInfo info = SkEncodedInfo::Make(color, alpha, 8);
Robert Phillipsb3050b92017-02-06 13:12:18 +0000146 SkWebpCodec* codecOut = new SkWebpCodec(features.width, features.height, info,
147 std::move(colorSpace), streamDeleter.release(),
148 demux.release(), std::move(data));
raftiasd737bee2016-12-08 10:53:24 -0500149 codecOut->setUnsupportedICC(unsupportedICC);
150 return codecOut;
scroggo6f5e6192015-06-18 12:53:43 -0700151}
152
scroggo6f5e6192015-06-18 12:53:43 -0700153SkISize SkWebpCodec::onGetScaledDimensions(float desiredScale) const {
154 SkISize dim = this->getInfo().dimensions();
msaretta0c414d2015-06-19 07:34:30 -0700155 // SkCodec treats zero dimensional images as errors, so the minimum size
156 // that we will recommend is 1x1.
157 dim.fWidth = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fWidth));
158 dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight));
scroggo6f5e6192015-06-18 12:53:43 -0700159 return dim;
160}
161
scroggoe7fc14b2015-10-02 13:14:46 -0700162bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) {
163 const SkImageInfo& info = this->getInfo();
164 return dim.width() >= 1 && dim.width() <= info.width()
165 && dim.height() >= 1 && dim.height() <= info.height();
166}
167
scroggo6f5e6192015-06-18 12:53:43 -0700168static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) {
169 switch (ct) {
170 case kBGRA_8888_SkColorType:
171 return premultiply ? MODE_bgrA : MODE_BGRA;
172 case kRGBA_8888_SkColorType:
173 return premultiply ? MODE_rgbA : MODE_RGBA;
scroggo74992b52015-08-06 13:50:15 -0700174 case kRGB_565_SkColorType:
175 return MODE_RGB_565;
scroggo6f5e6192015-06-18 12:53:43 -0700176 default:
177 return MODE_LAST;
178 }
179}
180
scroggob636b452015-07-22 07:16:20 -0700181bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
182 if (!desiredSubset) {
183 return false;
184 }
185
msarettfdb47572015-10-13 12:50:14 -0700186 SkIRect dimensions = SkIRect::MakeSize(this->getInfo().dimensions());
187 if (!dimensions.contains(*desiredSubset)) {
scroggob636b452015-07-22 07:16:20 -0700188 return false;
189 }
190
191 // As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we
192 // decode this exact subset.
193 // Leave right and bottom unmodified, so we suggest a slightly larger subset than requested.
194 desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1;
195 desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1;
196 return true;
197}
198
scroggoeb602a52015-07-09 08:16:03 -0700199SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
msarette6dd0042015-10-09 11:07:34 -0700200 const Options& options, SkPMColor*, int*,
msarette99883f2016-09-08 06:05:35 -0700201 int* rowsDecodedPtr) {
msarett2ecc35f2016-09-08 11:55:16 -0700202 if (!conversion_possible(dstInfo, this->getInfo())) {
203 return kInvalidConversion;
204 }
msarette99883f2016-09-08 06:05:35 -0700205
Matt Sarett313c4632016-10-20 12:35:23 -0400206 if (!this->initializeColorXform(dstInfo)) {
207 return kInvalidConversion;
scroggo6f5e6192015-06-18 12:53:43 -0700208 }
209
210 WebPDecoderConfig config;
211 if (0 == WebPInitDecoderConfig(&config)) {
212 // ABI mismatch.
213 // FIXME: New enum for this?
214 return kInvalidInput;
215 }
216
217 // Free any memory associated with the buffer. Must be called last, so we declare it first.
218 SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output));
219
Robert Phillipsb3050b92017-02-06 13:12:18 +0000220 SkIRect bounds = SkIRect::MakeSize(this->getInfo().dimensions());
Matt Sarett0f339702017-02-03 15:24:19 -0500221 if (options.fSubset) {
Robert Phillipsb3050b92017-02-06 13:12:18 +0000222 // Caller is requesting a subset.
223 if (!bounds.contains(*options.fSubset)) {
224 // The subset is out of bounds.
225 return kInvalidParameters;
Matt Sarett0f339702017-02-03 15:24:19 -0500226 }
227
Robert Phillipsb3050b92017-02-06 13:12:18 +0000228 bounds = *options.fSubset;
Matt Sarett0f339702017-02-03 15:24:19 -0500229
Robert Phillipsb3050b92017-02-06 13:12:18 +0000230 // This is tricky. libwebp snaps the top and left to even values. We could let libwebp
231 // do the snap, and return a subset which is a different one than requested. The problem
232 // with that approach is that the caller may try to stitch subsets together, and if we
233 // returned different subsets than requested, there would be artifacts at the boundaries.
234 // Instead, we report that we cannot support odd values for top and left..
235 if (!SkIsAlign2(bounds.fLeft) || !SkIsAlign2(bounds.fTop)) {
236 return kInvalidParameters;
237 }
Matt Sarett0f339702017-02-03 15:24:19 -0500238
Robert Phillipsb3050b92017-02-06 13:12:18 +0000239#ifdef SK_DEBUG
240 {
241 // Make a copy, since getValidSubset can change its input.
242 SkIRect subset(bounds);
243 // That said, getValidSubset should *not* change its input, in this case; otherwise
244 // getValidSubset does not match the actual subsets we can do.
245 SkASSERT(this->getValidSubset(&subset) && subset == bounds);
246 }
247#endif
Matt Sarett0f339702017-02-03 15:24:19 -0500248
249 config.options.use_cropping = 1;
Robert Phillipsb3050b92017-02-06 13:12:18 +0000250 config.options.crop_left = bounds.fLeft;
251 config.options.crop_top = bounds.fTop;
252 config.options.crop_width = bounds.width();
253 config.options.crop_height = bounds.height();
Matt Sarett0f339702017-02-03 15:24:19 -0500254 }
255
Robert Phillipsb3050b92017-02-06 13:12:18 +0000256 SkISize dstDimensions = dstInfo.dimensions();
257 if (bounds.size() != dstDimensions) {
258 // Caller is requesting scaling.
scroggo6f5e6192015-06-18 12:53:43 -0700259 config.options.use_scaling = 1;
Robert Phillipsb3050b92017-02-06 13:12:18 +0000260 config.options.scaled_width = dstDimensions.width();
261 config.options.scaled_height = dstDimensions.height();
scroggo6f5e6192015-06-18 12:53:43 -0700262 }
263
msarettcf7b8772016-09-22 12:37:04 -0700264 // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a
265 // color transform, we should decode to whatever is easiest for libwebp, and then let the
266 // color transform swizzle if necessary.
267 // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is
268 // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
Matt Sarett313c4632016-10-20 12:35:23 -0400269 config.output.colorspace = this->colorXform() ? MODE_BGRA :
msarette99883f2016-09-08 06:05:35 -0700270 webp_decode_mode(dstInfo.colorType(), dstInfo.alphaType() == kPremul_SkAlphaType);
scroggo6f5e6192015-06-18 12:53:43 -0700271 config.output.is_external_memory = 1;
272
msarette99883f2016-09-08 06:05:35 -0700273 // We will decode the entire image and then perform the color transform. libwebp
274 // does not provide a row-by-row API. This is a shame particularly in the F16 case,
275 // where we need to allocate an extra image-sized buffer.
276 SkAutoTMalloc<uint32_t> pixels;
Robert Phillipsb3050b92017-02-06 13:12:18 +0000277 if (kRGBA_F16_SkColorType == dstInfo.colorType()) {
278 pixels.reset(dstDimensions.width() * dstDimensions.height());
279 config.output.u.RGBA.rgba = (uint8_t*) pixels.get();
280 config.output.u.RGBA.stride = (int) dstDimensions.width() * sizeof(uint32_t);
281 config.output.u.RGBA.size = config.output.u.RGBA.stride * dstDimensions.height();
282 } else {
283 config.output.u.RGBA.rgba = (uint8_t*) dst;
284 config.output.u.RGBA.stride = (int) rowBytes;
285 config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes);
286 }
msarette99883f2016-09-08 06:05:35 -0700287
Robert Phillipsb3050b92017-02-06 13:12:18 +0000288 WebPIterator frame;
289 SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
290 // If this succeeded in NewFromStream(), it should succeed again here.
291 SkAssertResult(WebPDemuxGetFrame(fDemux, 1, &frame));
msarettff2a6c82016-09-07 11:23:28 -0700292
halcanary96fcdcc2015-08-27 07:41:13 -0700293 SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config));
scroggo6f5e6192015-06-18 12:53:43 -0700294 if (!idec) {
295 return kInvalidInput;
296 }
297
msarette99883f2016-09-08 06:05:35 -0700298 int rowsDecoded;
299 SkCodec::Result result;
msarettff2a6c82016-09-07 11:23:28 -0700300 switch (WebPIUpdate(idec, frame.fragment.bytes, frame.fragment.size)) {
301 case VP8_STATUS_OK:
msarette99883f2016-09-08 06:05:35 -0700302 rowsDecoded = dstInfo.height();
303 result = kSuccess;
304 break;
msarettff2a6c82016-09-07 11:23:28 -0700305 case VP8_STATUS_SUSPENDED:
msarette99883f2016-09-08 06:05:35 -0700306 WebPIDecGetRGB(idec, rowsDecodedPtr, nullptr, nullptr, nullptr);
Robert Phillipsb3050b92017-02-06 13:12:18 +0000307 rowsDecoded = *rowsDecodedPtr;
msarette99883f2016-09-08 06:05:35 -0700308 result = kIncompleteInput;
309 break;
msarettff2a6c82016-09-07 11:23:28 -0700310 default:
311 return kInvalidInput;
scroggo6f5e6192015-06-18 12:53:43 -0700312 }
msarette99883f2016-09-08 06:05:35 -0700313
Matt Sarett313c4632016-10-20 12:35:23 -0400314 if (this->colorXform()) {
msarettcf7b8772016-09-22 12:37:04 -0700315 SkColorSpaceXform::ColorFormat dstColorFormat = select_xform_format(dstInfo.colorType());
msarettc0444612016-09-16 11:45:58 -0700316 SkAlphaType xformAlphaType = select_xform_alpha(dstInfo.alphaType(),
msarette99883f2016-09-08 06:05:35 -0700317 this->getInfo().alphaType());
318
Robert Phillipsb3050b92017-02-06 13:12:18 +0000319 uint32_t* src = (uint32_t*) config.output.u.RGBA.rgba;
msarette99883f2016-09-08 06:05:35 -0700320 size_t srcRowBytes = config.output.u.RGBA.stride;
Robert Phillipsb3050b92017-02-06 13:12:18 +0000321 for (int y = 0; y < rowsDecoded; y++) {
322 SkAssertResult(this->colorXform()->apply(dstColorFormat, dst,
323 SkColorSpaceXform::kBGRA_8888_ColorFormat, src, dstInfo.width(),
Matt Sarett313c4632016-10-20 12:35:23 -0400324 xformAlphaType));
Robert Phillipsb3050b92017-02-06 13:12:18 +0000325 dst = SkTAddOffset<void>(dst, rowBytes);
326 src = SkTAddOffset<uint32_t>(src, srcRowBytes);
msarette99883f2016-09-08 06:05:35 -0700327 }
328 }
329
330 return result;
scroggo6f5e6192015-06-18 12:53:43 -0700331}
332
msarett9d15dab2016-08-24 07:36:06 -0700333SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info,
msarettff2a6c82016-09-07 11:23:28 -0700334 sk_sp<SkColorSpace> colorSpace, SkStream* stream, WebPDemuxer* demux,
335 sk_sp<SkData> data)
336 : INHERITED(width, height, info, stream, std::move(colorSpace))
337 , fDemux(demux)
338 , fData(std::move(data))
msarett9d15dab2016-08-24 07:36:06 -0700339{}