• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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