1 | /* |
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 | |
8 | #include "src/codec/SkWebpCodec.h" |
9 | |
10 | #include "include/codec/SkCodecAnimation.h" |
11 | #include "include/core/SkBitmap.h" |
12 | #include "include/core/SkCanvas.h" |
13 | #include "include/private/SkTemplates.h" |
14 | #include "include/private/SkTo.h" |
15 | #include "src/codec/SkCodecAnimationPriv.h" |
16 | #include "src/codec/SkCodecPriv.h" |
17 | #include "src/codec/SkParseEncodedOrigin.h" |
18 | #include "src/codec/SkSampler.h" |
19 | #include "src/core/SkRasterPipeline.h" |
20 | #include "src/core/SkStreamPriv.h" |
21 | |
22 | // A WebP decoder on top of (subset of) libwebp |
23 | // For more information on WebP image format, and libwebp library, see: |
24 | // https://code.google.com/speed/webp/ |
25 | // http://www.webmproject.org/code/#libwebp-webp-image-library |
26 | // https://chromium.googlesource.com/webm/libwebp |
27 | |
28 | // If moving libwebp out of skia source tree, path for webp headers must be |
29 | // updated accordingly. Here, we enforce using local copy in webp sub-directory. |
30 | #include "webp/decode.h" |
31 | #include "webp/demux.h" |
32 | #include "webp/encode.h" |
33 | |
34 | bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) { |
35 | // WEBP starts with the following: |
36 | // RIFFXXXXWEBPVP |
37 | // Where XXXX is unspecified. |
38 | const char* bytes = static_cast<const char*>(buf); |
39 | return bytesRead >= 14 && !memcmp(bytes, "RIFF" , 4) && !memcmp(&bytes[8], "WEBPVP" , 6); |
40 | } |
41 | |
42 | // Parse headers of RIFF container, and check for valid Webp (VP8) content. |
43 | // Returns an SkWebpCodec on success |
44 | std::unique_ptr<SkCodec> SkWebpCodec::MakeFromStream(std::unique_ptr<SkStream> stream, |
45 | Result* result) { |
46 | // Webp demux needs a contiguous data buffer. |
47 | sk_sp<SkData> data = nullptr; |
48 | if (stream->getMemoryBase()) { |
49 | // It is safe to make without copy because we'll hold onto the stream. |
50 | data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength()); |
51 | } else { |
52 | data = SkCopyStreamToData(stream.get()); |
53 | |
54 | // If we are forced to copy the stream to a data, we can go ahead and delete the stream. |
55 | stream.reset(nullptr); |
56 | } |
57 | |
58 | // It's a little strange that the |demux| will outlive |webpData|, though it needs the |
59 | // pointer in |webpData| to remain valid. This works because the pointer remains valid |
60 | // until the SkData is freed. |
61 | WebPData webpData = { data->bytes(), data->size() }; |
62 | WebPDemuxState state; |
63 | SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&webpData, &state)); |
64 | switch (state) { |
65 | case WEBP_DEMUX_PARSE_ERROR: |
66 | *result = kInvalidInput; |
67 | return nullptr; |
68 | case WEBP_DEMUX_PARSING_HEADER: |
69 | *result = kIncompleteInput; |
70 | return nullptr; |
71 | case WEBP_DEMUX_PARSED_HEADER: |
72 | case WEBP_DEMUX_DONE: |
73 | SkASSERT(demux); |
74 | break; |
75 | } |
76 | |
77 | const int width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); |
78 | const int height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); |
79 | |
80 | // Sanity check for image size that's about to be decoded. |
81 | { |
82 | const int64_t size = sk_64_mul(width, height); |
83 | // now check that if we are 4-bytes per pixel, we also don't overflow |
84 | if (!SkTFitsIn<int32_t>(size) || SkTo<int32_t>(size) > (0x7FFFFFFF >> 2)) { |
85 | *result = kInvalidInput; |
86 | return nullptr; |
87 | } |
88 | } |
89 | |
90 | std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr; |
91 | { |
92 | WebPChunkIterator chunkIterator; |
93 | SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator); |
94 | if (WebPDemuxGetChunk(demux, "ICCP" , 1, &chunkIterator)) { |
95 | // FIXME: I think this could be MakeWithoutCopy |
96 | auto chunk = SkData::MakeWithCopy(chunkIterator.chunk.bytes, chunkIterator.chunk.size); |
97 | profile = SkEncodedInfo::ICCProfile::Make(std::move(chunk)); |
98 | } |
99 | if (profile && profile->profile()->data_color_space != skcms_Signature_RGB) { |
100 | profile = nullptr; |
101 | } |
102 | } |
103 | |
104 | SkEncodedOrigin origin = kDefault_SkEncodedOrigin; |
105 | { |
106 | WebPChunkIterator chunkIterator; |
107 | SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator); |
108 | if (WebPDemuxGetChunk(demux, "EXIF" , 1, &chunkIterator)) { |
109 | SkParseEncodedOrigin(chunkIterator.chunk.bytes, chunkIterator.chunk.size, &origin); |
110 | } |
111 | } |
112 | |
113 | // Get the first frame and its "features" to determine the color and alpha types. |
114 | WebPIterator frame; |
115 | SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); |
116 | if (!WebPDemuxGetFrame(demux, 1, &frame)) { |
117 | *result = kIncompleteInput; |
118 | return nullptr; |
119 | } |
120 | |
121 | WebPBitstreamFeatures features; |
122 | switch (WebPGetFeatures(frame.fragment.bytes, frame.fragment.size, &features)) { |
123 | case VP8_STATUS_OK: |
124 | break; |
125 | case VP8_STATUS_SUSPENDED: |
126 | case VP8_STATUS_NOT_ENOUGH_DATA: |
127 | *result = kIncompleteInput; |
128 | return nullptr; |
129 | default: |
130 | *result = kInvalidInput; |
131 | return nullptr; |
132 | } |
133 | |
134 | const bool hasAlpha = SkToBool(frame.has_alpha) |
135 | || frame.width != width || frame.height != height; |
136 | SkEncodedInfo::Color color; |
137 | SkEncodedInfo::Alpha alpha; |
138 | switch (features.format) { |
139 | case 0: |
140 | // This indicates a "mixed" format. We could see this for |
141 | // animated webps (multiple fragments). |
142 | // We could also guess kYUV here, but I think it makes more |
143 | // sense to guess kBGRA which is likely closer to the final |
144 | // output. Otherwise, we might end up converting |
145 | // BGRA->YUVA->BGRA. |
146 | // Fallthrough: |
147 | case 2: |
148 | // This is the lossless format (BGRA). |
149 | if (hasAlpha) { |
150 | color = SkEncodedInfo::kBGRA_Color; |
151 | alpha = SkEncodedInfo::kUnpremul_Alpha; |
152 | } else { |
153 | color = SkEncodedInfo::kBGRX_Color; |
154 | alpha = SkEncodedInfo::kOpaque_Alpha; |
155 | } |
156 | break; |
157 | case 1: |
158 | // This is the lossy format (YUV). |
159 | if (hasAlpha) { |
160 | color = SkEncodedInfo::kYUVA_Color; |
161 | alpha = SkEncodedInfo::kUnpremul_Alpha; |
162 | } else { |
163 | color = SkEncodedInfo::kYUV_Color; |
164 | alpha = SkEncodedInfo::kOpaque_Alpha; |
165 | } |
166 | break; |
167 | default: |
168 | *result = kInvalidInput; |
169 | return nullptr; |
170 | } |
171 | |
172 | |
173 | *result = kSuccess; |
174 | SkEncodedInfo info = SkEncodedInfo::Make(width, height, color, alpha, 8, std::move(profile)); |
175 | return std::unique_ptr<SkCodec>(new SkWebpCodec(std::move(info), std::move(stream), |
176 | demux.release(), std::move(data), origin)); |
177 | } |
178 | |
179 | static WEBP_CSP_MODE webp_decode_mode(SkColorType dstCT, bool premultiply) { |
180 | switch (dstCT) { |
181 | case kBGRA_8888_SkColorType: |
182 | return premultiply ? MODE_bgrA : MODE_BGRA; |
183 | case kRGBA_8888_SkColorType: |
184 | return premultiply ? MODE_rgbA : MODE_RGBA; |
185 | case kRGB_565_SkColorType: |
186 | return MODE_RGB_565; |
187 | default: |
188 | return MODE_LAST; |
189 | } |
190 | } |
191 | |
192 | SkWebpCodec::Frame* SkWebpCodec::FrameHolder::appendNewFrame(bool hasAlpha) { |
193 | const int i = this->size(); |
194 | fFrames.emplace_back(i, hasAlpha ? SkEncodedInfo::kUnpremul_Alpha |
195 | : SkEncodedInfo::kOpaque_Alpha); |
196 | return &fFrames[i]; |
197 | } |
198 | |
199 | bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { |
200 | if (!desiredSubset) { |
201 | return false; |
202 | } |
203 | |
204 | if (!this->bounds().contains(*desiredSubset)) { |
205 | return false; |
206 | } |
207 | |
208 | // As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we |
209 | // decode this exact subset. |
210 | // Leave right and bottom unmodified, so we suggest a slightly larger subset than requested. |
211 | desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1; |
212 | desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1; |
213 | return true; |
214 | } |
215 | |
216 | int SkWebpCodec::onGetRepetitionCount() { |
217 | auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); |
218 | if (!(flags & ANIMATION_FLAG)) { |
219 | return 0; |
220 | } |
221 | |
222 | int loopCount = WebPDemuxGetI(fDemux.get(), WEBP_FF_LOOP_COUNT); |
223 | if (0 == loopCount) { |
224 | return kRepetitionCountInfinite; |
225 | } |
226 | |
227 | #ifndef SK_LEGACY_WEBP_LOOP_COUNT |
228 | loopCount--; |
229 | #endif |
230 | return loopCount; |
231 | } |
232 | |
233 | int SkWebpCodec::onGetFrameCount() { |
234 | auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); |
235 | if (!(flags & ANIMATION_FLAG)) { |
236 | return 1; |
237 | } |
238 | |
239 | const uint32_t oldFrameCount = fFrameHolder.size(); |
240 | if (fFailed) { |
241 | return oldFrameCount; |
242 | } |
243 | |
244 | const uint32_t frameCount = WebPDemuxGetI(fDemux, WEBP_FF_FRAME_COUNT); |
245 | if (oldFrameCount == frameCount) { |
246 | // We have already parsed this. |
247 | return frameCount; |
248 | } |
249 | |
250 | fFrameHolder.reserve(frameCount); |
251 | |
252 | for (uint32_t i = oldFrameCount; i < frameCount; i++) { |
253 | WebPIterator iter; |
254 | SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoIter(&iter); |
255 | |
256 | if (!WebPDemuxGetFrame(fDemux.get(), i + 1, &iter)) { |
257 | fFailed = true; |
258 | break; |
259 | } |
260 | |
261 | // libwebp only reports complete frames of an animated image. |
262 | SkASSERT(iter.complete); |
263 | |
264 | Frame* frame = fFrameHolder.appendNewFrame(iter.has_alpha); |
265 | frame->setXYWH(iter.x_offset, iter.y_offset, iter.width, iter.height); |
266 | frame->setDisposalMethod(iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? |
267 | SkCodecAnimation::DisposalMethod::kRestoreBGColor : |
268 | SkCodecAnimation::DisposalMethod::kKeep); |
269 | frame->setDuration(iter.duration); |
270 | if (WEBP_MUX_BLEND != iter.blend_method) { |
271 | frame->setBlend(SkCodecAnimation::Blend::kBG); |
272 | } |
273 | fFrameHolder.setAlphaAndRequiredFrame(frame); |
274 | } |
275 | |
276 | return fFrameHolder.size(); |
277 | |
278 | } |
279 | |
280 | const SkFrame* SkWebpCodec::FrameHolder::onGetFrame(int i) const { |
281 | return static_cast<const SkFrame*>(this->frame(i)); |
282 | } |
283 | |
284 | const SkWebpCodec::Frame* SkWebpCodec::FrameHolder::frame(int i) const { |
285 | SkASSERT(i >= 0 && i < this->size()); |
286 | return &fFrames[i]; |
287 | } |
288 | |
289 | bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { |
290 | if (i >= fFrameHolder.size()) { |
291 | return false; |
292 | } |
293 | |
294 | const Frame* frame = fFrameHolder.frame(i); |
295 | if (!frame) { |
296 | return false; |
297 | } |
298 | |
299 | if (frameInfo) { |
300 | frameInfo->fRequiredFrame = frame->getRequiredFrame(); |
301 | frameInfo->fDuration = frame->getDuration(); |
302 | // libwebp only reports fully received frames for an |
303 | // animated image. |
304 | frameInfo->fFullyReceived = true; |
305 | frameInfo->fAlphaType = frame->hasAlpha() ? kUnpremul_SkAlphaType |
306 | : kOpaque_SkAlphaType; |
307 | frameInfo->fDisposalMethod = frame->getDisposalMethod(); |
308 | } |
309 | |
310 | return true; |
311 | } |
312 | |
313 | static bool is_8888(SkColorType colorType) { |
314 | switch (colorType) { |
315 | case kRGBA_8888_SkColorType: |
316 | case kBGRA_8888_SkColorType: |
317 | return true; |
318 | default: |
319 | return false; |
320 | } |
321 | } |
322 | |
323 | // Requires that the src input be unpremultiplied (or opaque). |
324 | static void blend_line(SkColorType dstCT, void* dst, |
325 | SkColorType srcCT, const void* src, |
326 | SkAlphaType dstAt, |
327 | bool srcHasAlpha, |
328 | int width) { |
329 | SkRasterPipeline_MemoryCtx dst_ctx = { (void*)dst, 0 }, |
330 | src_ctx = { (void*)src, 0 }; |
331 | |
332 | SkRasterPipeline_<256> p; |
333 | |
334 | p.append_load_dst(dstCT, &dst_ctx); |
335 | if (kUnpremul_SkAlphaType == dstAt) { |
336 | p.append(SkRasterPipeline::premul_dst); |
337 | } |
338 | |
339 | p.append_load(srcCT, &src_ctx); |
340 | if (srcHasAlpha) { |
341 | p.append(SkRasterPipeline::premul); |
342 | } |
343 | |
344 | p.append(SkRasterPipeline::srcover); |
345 | |
346 | if (kUnpremul_SkAlphaType == dstAt) { |
347 | p.append(SkRasterPipeline::unpremul); |
348 | } |
349 | p.append_store(dstCT, &dst_ctx); |
350 | |
351 | p.run(0,0, width,1); |
352 | } |
353 | |
354 | SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, |
355 | const Options& options, int* rowsDecodedPtr) { |
356 | const int index = options.fFrameIndex; |
357 | SkASSERT(0 == index || index < fFrameHolder.size()); |
358 | SkASSERT(0 == index || !options.fSubset); |
359 | |
360 | WebPDecoderConfig config; |
361 | if (0 == WebPInitDecoderConfig(&config)) { |
362 | // ABI mismatch. |
363 | // FIXME: New enum for this? |
364 | return kInvalidInput; |
365 | } |
366 | |
367 | // Free any memory associated with the buffer. Must be called last, so we declare it first. |
368 | SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output)); |
369 | |
370 | WebPIterator frame; |
371 | SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); |
372 | // If this succeeded in onGetFrameCount(), it should succeed again here. |
373 | SkAssertResult(WebPDemuxGetFrame(fDemux, index + 1, &frame)); |
374 | |
375 | const bool independent = index == 0 ? true : |
376 | (fFrameHolder.frame(index)->getRequiredFrame() == kNoFrame); |
377 | // Get the frameRect. libwebp will have already signaled an error if this is not fully |
378 | // contained by the canvas. |
379 | auto frameRect = SkIRect::MakeXYWH(frame.x_offset, frame.y_offset, frame.width, frame.height); |
380 | SkASSERT(this->bounds().contains(frameRect)); |
381 | const bool frameIsSubset = frameRect != this->bounds(); |
382 | if (independent && frameIsSubset) { |
383 | SkSampler::Fill(dstInfo, dst, rowBytes, options.fZeroInitialized); |
384 | } |
385 | |
386 | int dstX = frameRect.x(); |
387 | int dstY = frameRect.y(); |
388 | int subsetWidth = frameRect.width(); |
389 | int subsetHeight = frameRect.height(); |
390 | if (options.fSubset) { |
391 | SkIRect subset = *options.fSubset; |
392 | SkASSERT(this->bounds().contains(subset)); |
393 | SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); |
394 | SkASSERT(this->getValidSubset(&subset) && subset == *options.fSubset); |
395 | |
396 | if (!SkIRect::Intersects(subset, frameRect)) { |
397 | return kSuccess; |
398 | } |
399 | |
400 | int minXOffset = std::min(dstX, subset.x()); |
401 | int minYOffset = std::min(dstY, subset.y()); |
402 | dstX -= minXOffset; |
403 | dstY -= minYOffset; |
404 | frameRect.offset(-minXOffset, -minYOffset); |
405 | subset.offset(-minXOffset, -minYOffset); |
406 | |
407 | // Just like we require that the requested subset x and y offset are even, libwebp |
408 | // guarantees that the frame x and y offset are even (it's actually impossible to specify |
409 | // an odd frame offset). So we can still guarantee that the adjusted offsets are even. |
410 | SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); |
411 | |
412 | SkIRect intersection; |
413 | SkAssertResult(intersection.intersect(frameRect, subset)); |
414 | subsetWidth = intersection.width(); |
415 | subsetHeight = intersection.height(); |
416 | |
417 | config.options.use_cropping = 1; |
418 | config.options.crop_left = subset.x(); |
419 | config.options.crop_top = subset.y(); |
420 | config.options.crop_width = subsetWidth; |
421 | config.options.crop_height = subsetHeight; |
422 | } |
423 | |
424 | // Ignore the frame size and offset when determining if scaling is necessary. |
425 | int scaledWidth = subsetWidth; |
426 | int scaledHeight = subsetHeight; |
427 | SkISize srcSize = options.fSubset ? options.fSubset->size() : this->dimensions(); |
428 | if (srcSize != dstInfo.dimensions()) { |
429 | config.options.use_scaling = 1; |
430 | |
431 | if (frameIsSubset) { |
432 | float scaleX = ((float) dstInfo.width()) / srcSize.width(); |
433 | float scaleY = ((float) dstInfo.height()) / srcSize.height(); |
434 | |
435 | // We need to be conservative here and floor rather than round. |
436 | // Otherwise, we may find ourselves decoding off the end of memory. |
437 | dstX = scaleX * dstX; |
438 | scaledWidth = scaleX * scaledWidth; |
439 | dstY = scaleY * dstY; |
440 | scaledHeight = scaleY * scaledHeight; |
441 | if (0 == scaledWidth || 0 == scaledHeight) { |
442 | return kSuccess; |
443 | } |
444 | } else { |
445 | scaledWidth = dstInfo.width(); |
446 | scaledHeight = dstInfo.height(); |
447 | } |
448 | |
449 | config.options.scaled_width = scaledWidth; |
450 | config.options.scaled_height = scaledHeight; |
451 | } |
452 | |
453 | const bool blendWithPrevFrame = !independent && frame.blend_method == WEBP_MUX_BLEND |
454 | && frame.has_alpha; |
455 | |
456 | SkBitmap webpDst; |
457 | auto webpInfo = dstInfo; |
458 | if (!frame.has_alpha) { |
459 | webpInfo = webpInfo.makeAlphaType(kOpaque_SkAlphaType); |
460 | } |
461 | if (this->colorXform()) { |
462 | // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a |
463 | // color transform, we should decode to whatever is easiest for libwebp, and then let the |
464 | // color transform swizzle if necessary. |
465 | // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is |
466 | // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA. |
467 | webpInfo = webpInfo.makeColorType(kBGRA_8888_SkColorType); |
468 | |
469 | if (webpInfo.alphaType() == kPremul_SkAlphaType) { |
470 | webpInfo = webpInfo.makeAlphaType(kUnpremul_SkAlphaType); |
471 | } |
472 | } |
473 | |
474 | if ((this->colorXform() && !is_8888(dstInfo.colorType())) || blendWithPrevFrame) { |
475 | // We will decode the entire image and then perform the color transform. libwebp |
476 | // does not provide a row-by-row API. This is a shame particularly when we do not want |
477 | // 8888, since we will need to create another image sized buffer. |
478 | webpDst.allocPixels(webpInfo); |
479 | } else { |
480 | // libwebp can decode directly into the output memory. |
481 | webpDst.installPixels(webpInfo, dst, rowBytes); |
482 | } |
483 | |
484 | config.output.colorspace = webp_decode_mode(webpInfo.colorType(), |
485 | frame.has_alpha && dstInfo.alphaType() == kPremul_SkAlphaType && !this->colorXform()); |
486 | config.output.is_external_memory = 1; |
487 | |
488 | config.output.u.RGBA.rgba = reinterpret_cast<uint8_t*>(webpDst.getAddr(dstX, dstY)); |
489 | config.output.u.RGBA.stride = static_cast<int>(webpDst.rowBytes()); |
490 | config.output.u.RGBA.size = webpDst.computeByteSize(); |
491 | |
492 | SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config)); |
493 | if (!idec) { |
494 | return kInvalidInput; |
495 | } |
496 | |
497 | int rowsDecoded = 0; |
498 | SkCodec::Result result; |
499 | switch (WebPIUpdate(idec, frame.fragment.bytes, frame.fragment.size)) { |
500 | case VP8_STATUS_OK: |
501 | rowsDecoded = scaledHeight; |
502 | result = kSuccess; |
503 | break; |
504 | case VP8_STATUS_SUSPENDED: |
505 | if (!WebPIDecGetRGB(idec, &rowsDecoded, nullptr, nullptr, nullptr) |
506 | || rowsDecoded <= 0) { |
507 | return kInvalidInput; |
508 | } |
509 | *rowsDecodedPtr = rowsDecoded + dstY; |
510 | result = kIncompleteInput; |
511 | break; |
512 | default: |
513 | return kInvalidInput; |
514 | } |
515 | |
516 | const size_t dstBpp = dstInfo.bytesPerPixel(); |
517 | dst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY); |
518 | const size_t srcRowBytes = config.output.u.RGBA.stride; |
519 | |
520 | const auto dstCT = dstInfo.colorType(); |
521 | if (this->colorXform()) { |
522 | uint32_t* xformSrc = (uint32_t*) config.output.u.RGBA.rgba; |
523 | SkBitmap tmp; |
524 | void* xformDst; |
525 | |
526 | if (blendWithPrevFrame) { |
527 | // Xform into temporary bitmap big enough for one row. |
528 | tmp.allocPixels(dstInfo.makeWH(scaledWidth, 1)); |
529 | xformDst = tmp.getPixels(); |
530 | } else { |
531 | xformDst = dst; |
532 | } |
533 | |
534 | for (int y = 0; y < rowsDecoded; y++) { |
535 | this->applyColorXform(xformDst, xformSrc, scaledWidth); |
536 | if (blendWithPrevFrame) { |
537 | blend_line(dstCT, dst, dstCT, xformDst, |
538 | dstInfo.alphaType(), frame.has_alpha, scaledWidth); |
539 | dst = SkTAddOffset<void>(dst, rowBytes); |
540 | } else { |
541 | xformDst = SkTAddOffset<void>(xformDst, rowBytes); |
542 | } |
543 | xformSrc = SkTAddOffset<uint32_t>(xformSrc, srcRowBytes); |
544 | } |
545 | } else if (blendWithPrevFrame) { |
546 | const uint8_t* src = config.output.u.RGBA.rgba; |
547 | |
548 | for (int y = 0; y < rowsDecoded; y++) { |
549 | blend_line(dstCT, dst, webpDst.colorType(), src, |
550 | dstInfo.alphaType(), frame.has_alpha, scaledWidth); |
551 | src = SkTAddOffset<const uint8_t>(src, srcRowBytes); |
552 | dst = SkTAddOffset<void>(dst, rowBytes); |
553 | } |
554 | } |
555 | |
556 | return result; |
557 | } |
558 | |
559 | SkWebpCodec::SkWebpCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, |
560 | WebPDemuxer* demux, sk_sp<SkData> data, SkEncodedOrigin origin) |
561 | : INHERITED(std::move(info), skcms_PixelFormat_BGRA_8888, std::move(stream), |
562 | origin) |
563 | , fDemux(demux) |
564 | , fData(std::move(data)) |
565 | , fFailed(false) |
566 | { |
567 | const auto& eInfo = this->getEncodedInfo(); |
568 | fFrameHolder.setScreenSize(eInfo.width(), eInfo.height()); |
569 | } |
570 | |