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