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