1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
|
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkCodecPriv.h"
#include "SkWebpCodec.h"
#include "SkTemplates.h"
// A WebP decoder on top of (subset of) libwebp
// For more information on WebP image format, and libwebp library, see:
// https://code.google.com/speed/webp/
// http://www.webmproject.org/code/#libwebp-webp-image-library
// https://chromium.googlesource.com/webm/libwebp
// If moving libwebp out of skia source tree, path for webp headers must be
// updated accordingly. Here, we enforce using local copy in webp sub-directory.
#include "webp/decode.h"
#include "webp/encode.h"
bool SkWebpCodec::IsWebp(SkStream* stream) {
// WEBP starts with the following:
// RIFFXXXXWEBPVP
// Where XXXX is unspecified.
const char LENGTH = 14;
char bytes[LENGTH];
if (stream->read(&bytes, LENGTH) != LENGTH) {
return false;
}
return !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
}
static const size_t WEBP_VP8_HEADER_SIZE = 30;
// Parse headers of RIFF container, and check for valid Webp (VP8) content.
// NOTE: This calls peek instead of read, since onGetPixels will need these
// bytes again.
static bool webp_parse_header(SkStream* stream, SkImageInfo* info) {
unsigned char buffer[WEBP_VP8_HEADER_SIZE];
if (!stream->peek(buffer, WEBP_VP8_HEADER_SIZE)) {
return false;
}
WebPBitstreamFeatures features;
VP8StatusCode status = WebPGetFeatures(buffer, WEBP_VP8_HEADER_SIZE, &features);
if (VP8_STATUS_OK != status) {
return false; // Invalid WebP file.
}
// sanity check for image size that's about to be decoded.
{
const int64_t size = sk_64_mul(features.width, features.height);
if (!sk_64_isS32(size)) {
return false;
}
// now check that if we are 4-bytes per pixel, we also don't overflow
if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) {
return false;
}
}
if (info) {
// FIXME: Is N32 the right type?
// Is unpremul the right type? Clients of SkCodec may assume it's the
// best type, when Skia currently cannot draw unpremul (and raster is faster
// with premul).
*info = SkImageInfo::Make(features.width, features.height, kN32_SkColorType,
SkToBool(features.has_alpha) ? kUnpremul_SkAlphaType
: kOpaque_SkAlphaType);
}
return true;
}
SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
SkAutoTDelete<SkStream> streamDeleter(stream);
SkImageInfo info;
if (webp_parse_header(stream, &info)) {
return new SkWebpCodec(info, streamDeleter.detach());
}
return nullptr;
}
// This version is slightly different from SkCodecPriv's version of conversion_possible. It
// supports both byte orders for 8888.
static bool webp_conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) {
if (dst.profileType() != src.profileType()) {
return false;
}
if (!valid_alpha(dst.alphaType(), src.alphaType())) {
return false;
}
switch (dst.colorType()) {
// Both byte orders are supported.
case kBGRA_8888_SkColorType:
case kRGBA_8888_SkColorType:
return true;
case kRGB_565_SkColorType:
return src.alphaType() == kOpaque_SkAlphaType;
default:
return false;
}
}
SkISize SkWebpCodec::onGetScaledDimensions(float desiredScale) const {
SkISize dim = this->getInfo().dimensions();
// SkCodec treats zero dimensional images as errors, so the minimum size
// that we will recommend is 1x1.
dim.fWidth = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fWidth));
dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight));
return dim;
}
static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) {
switch (ct) {
case kBGRA_8888_SkColorType:
return premultiply ? MODE_bgrA : MODE_BGRA;
case kRGBA_8888_SkColorType:
return premultiply ? MODE_rgbA : MODE_RGBA;
case kRGB_565_SkColorType:
return MODE_RGB_565;
default:
return MODE_LAST;
}
}
// The WebP decoding API allows us to incrementally pass chunks of bytes as we receive them to the
// decoder with WebPIAppend. In order to do so, we need to read chunks from the SkStream. This size
// is arbitrary.
static const size_t BUFFER_SIZE = 4096;
bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
if (!desiredSubset) {
return false;
}
SkIRect bounds = SkIRect::MakeSize(this->getInfo().dimensions());
if (!desiredSubset->intersect(bounds)) {
return false;
}
// As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we
// decode this exact subset.
// Leave right and bottom unmodified, so we suggest a slightly larger subset than requested.
desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1;
desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1;
return true;
}
SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
const Options& options, SkPMColor*, int*) {
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
if (!webp_conversion_possible(dstInfo, this->getInfo())) {
return kInvalidConversion;
}
WebPDecoderConfig config;
if (0 == WebPInitDecoderConfig(&config)) {
// ABI mismatch.
// FIXME: New enum for this?
return kInvalidInput;
}
// Free any memory associated with the buffer. Must be called last, so we declare it first.
SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output));
SkIRect bounds = SkIRect::MakeSize(this->getInfo().dimensions());
if (options.fSubset) {
// Caller is requesting a subset.
if (!bounds.contains(*options.fSubset)) {
// The subset is out of bounds.
return kInvalidParameters;
}
bounds = *options.fSubset;
// This is tricky. libwebp snaps the top and left to even values. We could let libwebp
// do the snap, and return a subset which is a different one than requested. The problem
// with that approach is that the caller may try to stitch subsets together, and if we
// returned different subsets than requested, there would be artifacts at the boundaries.
// Instead, we report that we cannot support odd values for top and left..
if (!SkIsAlign2(bounds.fLeft) || !SkIsAlign2(bounds.fTop)) {
return kInvalidParameters;
}
#ifdef SK_DEBUG
{
// Make a copy, since getValidSubset can change its input.
SkIRect subset(bounds);
// That said, getValidSubset should *not* change its input, in this case; otherwise
// getValidSubset does not match the actual subsets we can do.
SkASSERT(this->getValidSubset(&subset) && subset == bounds);
}
#endif
config.options.use_cropping = 1;
config.options.crop_left = bounds.fLeft;
config.options.crop_top = bounds.fTop;
config.options.crop_width = bounds.width();
config.options.crop_height = bounds.height();
}
SkISize dstDimensions = dstInfo.dimensions();
if (bounds.size() != dstDimensions) {
// Caller is requesting scaling.
config.options.use_scaling = 1;
config.options.scaled_width = dstDimensions.width();
config.options.scaled_height = dstDimensions.height();
}
config.output.colorspace = webp_decode_mode(dstInfo.colorType(),
dstInfo.alphaType() == kPremul_SkAlphaType);
config.output.u.RGBA.rgba = (uint8_t*) dst;
config.output.u.RGBA.stride = (int) rowBytes;
config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes);
config.output.is_external_memory = 1;
SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config));
if (!idec) {
return kInvalidInput;
}
SkAutoMalloc storage(BUFFER_SIZE);
uint8_t* buffer = static_cast<uint8_t*>(storage.get());
while (true) {
const size_t bytesRead = stream()->read(buffer, BUFFER_SIZE);
if (0 == bytesRead) {
// FIXME: Maybe this is an incomplete image? How to decide? Based
// on the number of rows decoded? We can know the number of rows
// decoded using WebPIDecGetRGB.
return kInvalidInput;
}
switch (WebPIAppend(idec, buffer, bytesRead)) {
case VP8_STATUS_OK:
return kSuccess;
case VP8_STATUS_SUSPENDED:
// Break out of the switch statement. Continue the loop.
break;
default:
return kInvalidInput;
}
}
}
SkWebpCodec::SkWebpCodec(const SkImageInfo& info, SkStream* stream)
: INHERITED(info, stream) {}
|