1 /*
2 * Copyright 2010, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "SkImageEncoderPriv.h"
18
19 #ifdef SK_HAS_WEBP_LIBRARY
20
21 #include "SkBitmap.h"
22 #include "SkColorData.h"
23 #include "SkImageEncoderFns.h"
24 #include "SkStream.h"
25 #include "SkTemplates.h"
26 #include "SkUnPreMultiply.h"
27 #include "SkUTF.h"
28 #include "SkWebpEncoder.h"
29
30 // A WebP encoder only, on top of (subset of) libwebp
31 // For more information on WebP image format, and libwebp library, see:
32 // http://code.google.com/speed/webp/
33 // http://www.webmproject.org/code/#libwebp_webp_image_decoder_library
34 // http://review.webmproject.org/gitweb?p=libwebp.git
35
36 #include <stdio.h>
37 extern "C" {
38 // If moving libwebp out of skia source tree, path for webp headers must be
39 // updated accordingly. Here, we enforce using local copy in webp sub-directory.
40 #include "webp/encode.h"
41 #include "webp/mux.h"
42 }
43
choose_proc(const SkImageInfo & info)44 static transform_scanline_proc choose_proc(const SkImageInfo& info) {
45 switch (info.colorType()) {
46 case kRGBA_8888_SkColorType:
47 switch (info.alphaType()) {
48 case kOpaque_SkAlphaType:
49 return transform_scanline_RGBX;
50 case kUnpremul_SkAlphaType:
51 return transform_scanline_memcpy;
52 case kPremul_SkAlphaType:
53 return transform_scanline_rgbA;
54 default:
55 return nullptr;
56 }
57 case kBGRA_8888_SkColorType:
58 switch (info.alphaType()) {
59 case kOpaque_SkAlphaType:
60 return transform_scanline_BGRX;
61 case kUnpremul_SkAlphaType:
62 return transform_scanline_BGRA;
63 case kPremul_SkAlphaType:
64 return transform_scanline_bgrA;
65 default:
66 return nullptr;
67 }
68 case kRGB_565_SkColorType:
69 if (!info.isOpaque()) {
70 return nullptr;
71 }
72
73 return transform_scanline_565;
74 case kARGB_4444_SkColorType:
75 switch (info.alphaType()) {
76 case kOpaque_SkAlphaType:
77 return transform_scanline_444;
78 case kPremul_SkAlphaType:
79 return transform_scanline_4444;
80 default:
81 return nullptr;
82 }
83 case kGray_8_SkColorType:
84 return transform_scanline_gray;
85 case kRGBA_F16_SkColorType:
86 switch (info.alphaType()) {
87 case kOpaque_SkAlphaType:
88 case kUnpremul_SkAlphaType:
89 return transform_scanline_F16_to_8888;
90 case kPremul_SkAlphaType:
91 return transform_scanline_F16_premul_to_8888;
92 default:
93 return nullptr;
94 }
95 default:
96 return nullptr;
97 }
98 }
99
stream_writer(const uint8_t * data,size_t data_size,const WebPPicture * const picture)100 static int stream_writer(const uint8_t* data, size_t data_size,
101 const WebPPicture* const picture) {
102 SkWStream* const stream = (SkWStream*)picture->custom_ptr;
103 return stream->write(data, data_size) ? 1 : 0;
104 }
105
Encode(SkWStream * stream,const SkPixmap & pixmap,const Options & opts)106 bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
107 if (!SkPixmapIsValid(pixmap)) {
108 return false;
109 }
110
111 const transform_scanline_proc proc = choose_proc(pixmap.info());
112 if (!proc) {
113 return false;
114 }
115
116 int bpp;
117 if (kRGBA_F16_SkColorType == pixmap.colorType()) {
118 bpp = 4;
119 } else {
120 bpp = pixmap.isOpaque() ? 3 : 4;
121 }
122
123 if (nullptr == pixmap.addr()) {
124 return false;
125 }
126
127 WebPConfig webp_config;
128 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
129 return false;
130 }
131
132 WebPPicture pic;
133 WebPPictureInit(&pic);
134 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
135 pic.width = pixmap.width();
136 pic.height = pixmap.height();
137 pic.writer = stream_writer;
138
139 // Set compression, method, and pixel format.
140 // libwebp recommends using BGRA for lossless and YUV for lossy.
141 // The choices of |webp_config.method| currently just match Chrome's defaults. We
142 // could potentially expose this decision to the client.
143 if (Compression::kLossy == opts.fCompression) {
144 webp_config.lossless = 0;
145 #ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD
146 webp_config.method = 3;
147 #endif
148 pic.use_argb = 0;
149 } else {
150 webp_config.lossless = 1;
151 webp_config.method = 0;
152 pic.use_argb = 1;
153 }
154
155 // If there is no need to embed an ICC profile, we write directly to the input stream.
156 // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp
157 // forces us to have an encoded image before we can add a profile.
158 sk_sp<SkData> icc = icc_from_color_space(pixmap.info());
159 SkDynamicMemoryWStream tmp;
160 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
161
162 const uint8_t* src = (uint8_t*)pixmap.addr();
163 const int rgbStride = pic.width * bpp;
164 const size_t rowBytes = pixmap.rowBytes();
165
166 // Import (for each scanline) the bit-map image (in appropriate color-space)
167 // to RGB color space.
168 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]);
169 for (int y = 0; y < pic.height; ++y) {
170 proc((char*) &rgb[y * rgbStride],
171 (const char*) &src[y * rowBytes],
172 pic.width,
173 bpp);
174 }
175
176 auto importProc = WebPPictureImportRGB;
177 if (3 != bpp) {
178 if (pixmap.isOpaque()) {
179 importProc = WebPPictureImportRGBX;
180 } else {
181 importProc = WebPPictureImportRGBA;
182 }
183 }
184
185 if (!importProc(&pic, &rgb[0], rgbStride)) {
186 return false;
187 }
188
189 if (!WebPEncode(&webp_config, &pic)) {
190 return false;
191 }
192
193 if (icc) {
194 sk_sp<SkData> encodedData = tmp.detachAsData();
195 WebPData encoded = { encodedData->bytes(), encodedData->size() };
196 WebPData iccChunk = { icc->bytes(), icc->size() };
197
198 SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
199 if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
200 return false;
201 }
202
203 if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
204 return false;
205 }
206
207 WebPData assembled;
208 if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
209 return false;
210 }
211
212 stream->write(assembled.bytes, assembled.size);
213 WebPDataClear(&assembled);
214 }
215
216 return true;
217 }
218
219 #endif
220