xref: /aosp_15_r20/external/skia/src/encode/SkWebpEncoderImpl.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2010 The Android Open Source Project
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 "include/encode/SkWebpEncoder.h"
9 
10 #include "include/core/SkAlphaType.h"
11 #include "include/core/SkBitmap.h"
12 #include "include/core/SkColorType.h"
13 #include "include/core/SkData.h"
14 #include "include/core/SkImageInfo.h"
15 #include "include/core/SkPixmap.h"
16 #include "include/core/SkRefCnt.h"
17 #include "include/core/SkSpan.h"
18 #include "include/core/SkStream.h"
19 #include "include/encode/SkEncoder.h"
20 #include "include/private/base/SkTemplates.h"
21 #include "src/core/SkImageInfoPriv.h"
22 #include "src/encode/SkImageEncoderFns.h"
23 #include "src/encode/SkImageEncoderPriv.h"
24 #include "src/image/SkImage_Base.h"
25 
26 #include <cstddef>
27 #include <cstdint>
28 #include <memory>
29 
30 class GrDirectContext;
31 class SkImage;
32 
33 // A WebP encoder only, on top of (subset of) libwebp
34 // For more information on WebP image format, and libwebp library, see:
35 //   http://code.google.com/speed/webp/
36 //   http://www.webmproject.org/code/#libwebp_webp_image_decoder_library
37 //   http://review.webmproject.org/gitweb?p=libwebp.git
38 
39 extern "C" {
40 // If moving libwebp out of skia source tree, path for webp headers must be
41 // updated accordingly. Here, we enforce using local copy in webp sub-directory.
42 #include "webp/encode.h"  // NO_G3_REWRITE
43 #include "webp/mux.h"  // NO_G3_REWRITE
44 #include "webp/mux_types.h"  // NO_G3_REWRITE
45 }
46 
stream_writer(const uint8_t * data,size_t data_size,const WebPPicture * const picture)47 static int stream_writer(const uint8_t* data, size_t data_size, const WebPPicture* const picture) {
48     SkWStream* const stream = (SkWStream*)picture->custom_ptr;
49     return stream->write(data, data_size) ? 1 : 0;
50 }
51 
52 using WebPPictureImportProc = int (*)(WebPPicture* picture, const uint8_t* pixels, int stride);
53 
preprocess_webp_picture(WebPPicture * pic,WebPConfig * webp_config,const SkPixmap & pixmap,const SkWebpEncoder::Options & opts)54 static bool preprocess_webp_picture(WebPPicture* pic,
55                                     WebPConfig* webp_config,
56                                     const SkPixmap& pixmap,
57                                     const SkWebpEncoder::Options& opts) {
58     if (!SkPixmapIsValid(pixmap)) {
59         return false;
60     }
61 
62     if (SkColorTypeIsAlphaOnly(pixmap.colorType())) {
63         // Maintain the existing behavior of not supporting encoding alpha-only images.
64         // TODO: Support encoding alpha only to an image with alpha but no color?
65         return false;
66     }
67 
68     if (nullptr == pixmap.addr()) {
69         return false;
70     }
71 
72     pic->width = pixmap.width();
73     pic->height = pixmap.height();
74 
75     // Set compression, method, and pixel format.
76     // libwebp recommends using BGRA for lossless and YUV for lossy.
77     // The choices of |webp_config.method| currently just match Chrome's defaults.  We
78     // could potentially expose this decision to the client.
79     if (SkWebpEncoder::Compression::kLossy == opts.fCompression) {
80         webp_config->lossless = 0;
81 #ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD
82         webp_config->method = 3;
83 #endif
84         pic->use_argb = 0;
85     } else {
86         webp_config->lossless = 1;
87         webp_config->method = 0;
88         pic->use_argb = 1;
89     }
90 
91     {
92         const SkColorType ct = pixmap.colorType();
93         const bool premul = pixmap.alphaType() == kPremul_SkAlphaType;
94 
95         SkBitmap tmpBm;
96         WebPPictureImportProc importProc = nullptr;
97         const SkPixmap* src = &pixmap;
98         if (ct == kRGB_888x_SkColorType) {
99             importProc = WebPPictureImportRGBX;
100         } else if (!premul && ct == kRGBA_8888_SkColorType) {
101             importProc = WebPPictureImportRGBA;
102         }
103 #ifdef WebPPictureImportBGRA
104         else if (!premul && ct == kBGRA_8888_SkColorType) {
105             importProc = WebPPictureImportBGRA;
106         }
107 #endif
108         else {
109             importProc = WebPPictureImportRGBA;
110             auto info = pixmap.info()
111                                 .makeColorType(kRGBA_8888_SkColorType)
112                                 .makeAlphaType(kUnpremul_SkAlphaType);
113             if (!tmpBm.tryAllocPixels(info) ||
114                 !pixmap.readPixels(tmpBm.info(), tmpBm.getPixels(), tmpBm.rowBytes())) {
115                 return false;
116             }
117             src = &tmpBm.pixmap();
118         }
119 
120         if (!importProc(pic, reinterpret_cast<const uint8_t*>(src->addr()), src->rowBytes())) {
121             return false;
122         }
123     }
124 
125     return true;
126 }
127 
128 namespace SkWebpEncoder {
129 
Encode(SkWStream * stream,const SkPixmap & pixmap,const Options & opts)130 bool Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
131     if (!stream) {
132         return false;
133     }
134 
135     WebPConfig webp_config;
136     if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
137         return false;
138     }
139 
140     WebPPicture pic;
141     if (!WebPPictureInit(&pic)) {
142         return false;
143     }
144     SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
145 
146     if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) {
147         return false;
148     }
149 
150     // If there is no need to embed an ICC profile, we write directly to the input stream.
151     // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk.  libwebp
152     // forces us to have an encoded image before we can add a profile.
153     sk_sp<SkData> icc =
154             icc_from_color_space(pixmap.info(), opts.fICCProfile, opts.fICCProfileDescription);
155     SkDynamicMemoryWStream tmp;
156     pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
157     pic.writer = stream_writer;
158 
159     if (!WebPEncode(&webp_config, &pic)) {
160         return false;
161     }
162 
163     if (icc) {
164         sk_sp<SkData> encodedData = tmp.detachAsData();
165         WebPData encoded = {encodedData->bytes(), encodedData->size()};
166         WebPData iccChunk = {icc->bytes(), icc->size()};
167 
168         SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
169         if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
170             return false;
171         }
172 
173         if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
174             return false;
175         }
176 
177         WebPData assembled;
178         SkAutoTCallVProc<WebPData, WebPDataClear> autoWebPData(&assembled);
179         if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
180             return false;
181         }
182 
183         if (!stream->write(assembled.bytes, assembled.size)) {
184             return false;
185         }
186     }
187 
188     return true;
189 }
190 
EncodeAnimated(SkWStream * stream,SkSpan<const SkEncoder::Frame> frames,const Options & opts)191 bool EncodeAnimated(SkWStream* stream, SkSpan<const SkEncoder::Frame> frames, const Options& opts) {
192     if (!stream || frames.empty()) {
193         return false;
194     }
195 
196     const int canvasWidth = frames.front().pixmap.width();
197     const int canvasHeight = frames.front().pixmap.height();
198     int timestamp = 0;
199 
200     std::unique_ptr<WebPAnimEncoder, void (*)(WebPAnimEncoder*)> enc(
201             WebPAnimEncoderNew(canvasWidth, canvasHeight, nullptr), WebPAnimEncoderDelete);
202     if (!enc) {
203         return false;
204     }
205 
206     for (const auto& frame : frames) {
207         const auto& pixmap = frame.pixmap;
208 
209         if (pixmap.width() != canvasWidth || pixmap.height() != canvasHeight) {
210             return false;
211         }
212 
213         WebPConfig webp_config;
214         if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
215             return false;
216         }
217 
218         WebPPicture pic;
219         if (!WebPPictureInit(&pic)) {
220             return false;
221         }
222         SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
223 
224         if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) {
225             return false;
226         }
227 
228         if (!WebPEncode(&webp_config, &pic)) {
229             return false;
230         }
231 
232         if (!WebPAnimEncoderAdd(enc.get(), &pic, timestamp, &webp_config)) {
233             return false;
234         }
235 
236         timestamp += frame.duration;
237     }
238 
239     // Add a last fake frame to signal the last duration.
240     if (!WebPAnimEncoderAdd(enc.get(), nullptr, timestamp, nullptr)) {
241         return false;
242     }
243 
244     WebPData assembled;
245     SkAutoTCallVProc<WebPData, WebPDataClear> autoWebPData(&assembled);
246     if (!WebPAnimEncoderAssemble(enc.get(), &assembled)) {
247         return false;
248     }
249 
250     enc.reset();
251 
252     return stream->write(assembled.bytes, assembled.size);
253 }
254 
Encode(GrDirectContext * ctx,const SkImage * img,const Options & options)255 sk_sp<SkData> Encode(GrDirectContext* ctx, const SkImage* img, const Options& options) {
256     if (!img) {
257         return nullptr;
258     }
259     SkBitmap bm;
260     if (!as_IB(img)->getROPixels(ctx, &bm)) {
261         return nullptr;
262     }
263     SkDynamicMemoryWStream stream;
264     if (Encode(&stream, bm.pixmap(), options)) {
265         return stream.detachAsData();
266     }
267     return nullptr;
268 }
269 
270 }  // namespace SkWebpEncoder
271