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