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