• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/core/SkTypes.h"
9 
10 #ifdef SK_ENCODE_WEBP
11 
12 #include "include/core/SkAlphaType.h"
13 #include "include/core/SkBitmap.h"
14 #include "include/core/SkColorType.h"
15 #include "include/core/SkData.h"
16 #include "include/core/SkImageInfo.h"
17 #include "include/core/SkPixmap.h"
18 #include "include/core/SkRefCnt.h"
19 #include "include/core/SkSpan.h"
20 #include "include/core/SkStream.h"
21 #include "include/encode/SkEncoder.h"
22 #include "include/encode/SkWebpEncoder.h"
23 #include "include/private/base/SkTemplates.h"
24 #include "src/core/SkImageInfoPriv.h"
25 #include "src/encode/SkImageEncoderFns.h"
26 #include "src/encode/SkImageEncoderPriv.h"
27 
28 #include <cstddef>
29 #include <cstdint>
30 #include <memory>
31 
32 // A WebP encoder only, on top of (subset of) libwebp
33 // For more information on WebP image format, and libwebp library, see:
34 //   http://code.google.com/speed/webp/
35 //   http://www.webmproject.org/code/#libwebp_webp_image_decoder_library
36 //   http://review.webmproject.org/gitweb?p=libwebp.git
37 
38 extern "C" {
39 // If moving libwebp out of skia source tree, path for webp headers must be
40 // updated accordingly. Here, we enforce using local copy in webp sub-directory.
41 #include "webp/encode.h"
42 #include "webp/mux.h"
43 #include "webp/mux_types.h"
44 }
45 
stream_writer(const uint8_t * data,size_t data_size,const WebPPicture * const picture)46 static int stream_writer(const uint8_t* data, size_t data_size,
47                          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 
Encode(SkWStream * stream,const SkPixmap & pixmap,const Options & opts)128 bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
129     if (!stream) {
130         return false;
131     }
132 
133     WebPConfig webp_config;
134     if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
135         return false;
136     }
137 
138     WebPPicture pic;
139     WebPPictureInit(&pic);
140     SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
141 
142     if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) {
143         return false;
144     }
145 
146     // If there is no need to embed an ICC profile, we write directly to the input stream.
147     // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk.  libwebp
148     // forces us to have an encoded image before we can add a profile.
149     sk_sp<SkData> icc =
150             icc_from_color_space(pixmap.info(), opts.fICCProfile, opts.fICCProfileDescription);
151     SkDynamicMemoryWStream tmp;
152     pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
153     pic.writer = stream_writer;
154 
155     if (!WebPEncode(&webp_config, &pic)) {
156         return false;
157     }
158 
159     if (icc) {
160         sk_sp<SkData> encodedData = tmp.detachAsData();
161         WebPData encoded = { encodedData->bytes(), encodedData->size() };
162         WebPData iccChunk = { icc->bytes(), icc->size() };
163 
164         SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
165         if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
166             return false;
167         }
168 
169         if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
170             return false;
171         }
172 
173         WebPData assembled;
174         if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
175             return false;
176         }
177 
178         stream->write(assembled.bytes, assembled.size);
179         WebPDataClear(&assembled);
180     }
181 
182     return true;
183 }
184 
EncodeAnimated(SkWStream * stream,SkSpan<const SkEncoder::Frame> frames,const Options & opts)185 bool SkWebpEncoder::EncodeAnimated(SkWStream* stream,
186                                    SkSpan<const SkEncoder::Frame> frames,
187                                    const Options& opts) {
188     if (!stream || !frames.size()) {
189         return false;
190     }
191 
192     const int canvasWidth = frames.front().pixmap.width();
193     const int canvasHeight = frames.front().pixmap.height();
194     int timestamp = 0;
195 
196     std::unique_ptr<WebPAnimEncoder, void (*)(WebPAnimEncoder*)> enc(
197             WebPAnimEncoderNew(canvasWidth, canvasHeight, nullptr), WebPAnimEncoderDelete);
198     if (!enc) {
199         return false;
200     }
201 
202     for (const auto& frame : frames) {
203         const auto& pixmap = frame.pixmap;
204 
205         if (pixmap.width() != canvasWidth || pixmap.height() != canvasHeight) {
206             return false;
207         }
208 
209         WebPConfig webp_config;
210         if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
211             return false;
212         }
213 
214         WebPPicture pic;
215         WebPPictureInit(&pic);
216         SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
217 
218         if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) {
219             return false;
220         }
221 
222         if (!WebPEncode(&webp_config, &pic)) {
223             return false;
224         }
225 
226         if (!WebPAnimEncoderAdd(enc.get(), &pic, timestamp, &webp_config)) {
227             return false;
228         }
229 
230         timestamp += frame.duration;
231     }
232 
233     // Add a last fake frame to signal the last duration.
234     if (!WebPAnimEncoderAdd(enc.get(), nullptr, timestamp, nullptr)) {
235         return false;
236     }
237 
238     WebPData assembled;
239     SkAutoTCallVProc<WebPData, WebPDataClear> autoWebPData(&assembled);
240     if (!WebPAnimEncoderAssemble(enc.get(), &assembled)) {
241         return false;
242     }
243 
244     enc.reset();
245 
246     return stream->write(assembled.bytes, assembled.size);
247 }
248 
249 #endif
250