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 "SkColorPriv.h"
23 #include "SkImageEncoderFns.h"
24 #include "SkStream.h"
25 #include "SkTemplates.h"
26 #include "SkUnPreMultiply.h"
27 #include "SkUtils.h"
28
29 // A WebP encoder only, on top of (subset of) libwebp
30 // For more information on WebP image format, and libwebp library, see:
31 // http://code.google.com/speed/webp/
32 // http://www.webmproject.org/code/#libwebp_webp_image_decoder_library
33 // http://review.webmproject.org/gitweb?p=libwebp.git
34
35 #include <stdio.h>
36 extern "C" {
37 // If moving libwebp out of skia source tree, path for webp headers must be
38 // updated accordingly. Here, we enforce using local copy in webp sub-directory.
39 #include "webp/encode.h"
40 #include "webp/mux.h"
41 }
42
choose_proc(const SkImageInfo & info,SkTransferFunctionBehavior unpremulBehavior)43 static transform_scanline_proc choose_proc(const SkImageInfo& info,
44 SkTransferFunctionBehavior unpremulBehavior) {
45 const bool isSRGBTransferFn =
46 (SkTransferFunctionBehavior::kRespect == unpremulBehavior) && info.gammaCloseToSRGB();
47 switch (info.colorType()) {
48 case kRGBA_8888_SkColorType:
49 switch (info.alphaType()) {
50 case kOpaque_SkAlphaType:
51 return transform_scanline_RGBX;
52 case kUnpremul_SkAlphaType:
53 return transform_scanline_memcpy;
54 case kPremul_SkAlphaType:
55 return isSRGBTransferFn ? transform_scanline_srgbA :
56 transform_scanline_rgbA;
57 default:
58 return nullptr;
59 }
60 case kBGRA_8888_SkColorType:
61 switch (info.alphaType()) {
62 case kOpaque_SkAlphaType:
63 return transform_scanline_BGRX;
64 case kUnpremul_SkAlphaType:
65 return transform_scanline_BGRA;
66 case kPremul_SkAlphaType:
67 return isSRGBTransferFn ? transform_scanline_sbgrA :
68 transform_scanline_bgrA;
69 default:
70 return nullptr;
71 }
72 case kRGB_565_SkColorType:
73 if (!info.isOpaque()) {
74 return nullptr;
75 }
76
77 return transform_scanline_565;
78 case kARGB_4444_SkColorType:
79 switch (info.alphaType()) {
80 case kOpaque_SkAlphaType:
81 return transform_scanline_444;
82 case kPremul_SkAlphaType:
83 return transform_scanline_4444;
84 default:
85 return nullptr;
86 }
87 case kIndex_8_SkColorType:
88 switch (info.alphaType()) {
89 case kOpaque_SkAlphaType:
90 return transform_scanline_index8_opaque;
91 case kUnpremul_SkAlphaType:
92 case kPremul_SkAlphaType:
93 // If the color table is premultiplied, we'll fix it before calling the
94 // scanline proc.
95 return transform_scanline_index8_unpremul;
96 default:
97 return nullptr;
98 }
99 case kGray_8_SkColorType:
100 return transform_scanline_gray;
101 case kRGBA_F16_SkColorType:
102 if (!info.colorSpace() || !info.colorSpace()->gammaIsLinear()) {
103 return nullptr;
104 }
105
106 switch (info.alphaType()) {
107 case kOpaque_SkAlphaType:
108 case kUnpremul_SkAlphaType:
109 return transform_scanline_F16_to_8888;
110 case kPremul_SkAlphaType:
111 return transform_scanline_F16_premul_to_8888;
112 default:
113 return nullptr;
114 }
115 default:
116 return nullptr;
117 }
118 }
119
stream_writer(const uint8_t * data,size_t data_size,const WebPPicture * const picture)120 static int stream_writer(const uint8_t* data, size_t data_size,
121 const WebPPicture* const picture) {
122 SkWStream* const stream = (SkWStream*)picture->custom_ptr;
123 return stream->write(data, data_size) ? 1 : 0;
124 }
125
do_encode(SkWStream * stream,const SkPixmap & pixmap,const SkEncodeOptions & opts,int quality)126 static bool do_encode(SkWStream* stream, const SkPixmap& pixmap, const SkEncodeOptions& opts,
127 int quality) {
128 if (SkTransferFunctionBehavior::kRespect == opts.fUnpremulBehavior) {
129 if (!pixmap.colorSpace() || (!pixmap.colorSpace()->gammaCloseToSRGB() &&
130 !pixmap.colorSpace()->gammaIsLinear())) {
131 return false;
132 }
133 }
134
135 const transform_scanline_proc proc = choose_proc(pixmap.info(), opts.fUnpremulBehavior);
136 if (!proc) {
137 return false;
138 }
139
140 int bpp;
141 if (kRGBA_F16_SkColorType == pixmap.colorType()) {
142 bpp = 4;
143 } else {
144 bpp = pixmap.isOpaque() ? 3 : 4;
145 }
146
147 if (nullptr == pixmap.addr()) {
148 return false;
149 }
150
151 const SkPMColor* colors = nullptr;
152 SkPMColor storage[256];
153 if (kIndex_8_SkColorType == pixmap.colorType()) {
154 if (!pixmap.ctable()) {
155 return false;
156 }
157
158 colors = pixmap.ctable()->readColors();
159 if (kPremul_SkAlphaType == pixmap.alphaType()) {
160 // Unpremultiply the colors.
161 const SkImageInfo rgbaInfo = pixmap.info().makeColorType(kRGBA_8888_SkColorType);
162 transform_scanline_proc proc = choose_proc(rgbaInfo, opts.fUnpremulBehavior);
163 proc((char*) storage, (const char*) colors, pixmap.ctable()->count(), 4, nullptr);
164 colors = storage;
165 }
166 }
167
168 WebPConfig webp_config;
169 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, (float) quality)) {
170 return false;
171 }
172
173 WebPPicture pic;
174 WebPPictureInit(&pic);
175 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
176 pic.width = pixmap.width();
177 pic.height = pixmap.height();
178 pic.writer = stream_writer;
179
180 // If there is no need to embed an ICC profile, we write directly to the input stream.
181 // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp
182 // forces us to have an encoded image before we can add a profile.
183 sk_sp<SkData> icc = icc_from_color_space(pixmap.info());
184 SkDynamicMemoryWStream tmp;
185 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
186
187 const uint8_t* src = (uint8_t*)pixmap.addr();
188 const int rgbStride = pic.width * bpp;
189 const size_t rowBytes = pixmap.rowBytes();
190
191 // Import (for each scanline) the bit-map image (in appropriate color-space)
192 // to RGB color space.
193 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]);
194 for (int y = 0; y < pic.height; ++y) {
195 proc((char*) &rgb[y * rgbStride], (const char*) &src[y * rowBytes], pic.width, bpp, colors);
196 }
197
198 auto importProc = WebPPictureImportRGB;
199 if (3 != bpp) {
200 if (pixmap.isOpaque()) {
201 importProc = WebPPictureImportRGBX;
202 } else {
203 importProc = WebPPictureImportRGBA;
204 }
205 }
206
207 if (!importProc(&pic, &rgb[0], rgbStride)) {
208 return false;
209 }
210
211 if (!WebPEncode(&webp_config, &pic)) {
212 return false;
213 }
214
215 if (icc) {
216 sk_sp<SkData> encodedData = tmp.detachAsData();
217 WebPData encoded = { encodedData->bytes(), encodedData->size() };
218 WebPData iccChunk = { icc->bytes(), icc->size() };
219
220 SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
221 if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
222 return false;
223 }
224
225 if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
226 return false;
227 }
228
229 WebPData assembled;
230 if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
231 return false;
232 }
233
234 stream->write(assembled.bytes, assembled.size);
235 WebPDataClear(&assembled);
236 }
237
238 return true;
239 }
240
SkEncodeImageAsWEBP(SkWStream * stream,const SkPixmap & src,int quality)241 bool SkEncodeImageAsWEBP(SkWStream* stream, const SkPixmap& src, int quality) {
242 return do_encode(stream, src, SkEncodeOptions(), quality);
243 }
244
SkEncodeImageAsWEBP(SkWStream * stream,const SkPixmap & src,const SkEncodeOptions & opts)245 bool SkEncodeImageAsWEBP(SkWStream* stream, const SkPixmap& src, const SkEncodeOptions& opts) {
246 return do_encode(stream, src, opts, 100);
247 }
248
249 #endif
250