blob: 38e6773d92a1f969546453cb22032d5a667485c6 [file] [log] [blame]
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001/*
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
Hal Canarydb683012016-11-23 08:55:18 -070017#include "SkImageEncoderPriv.h"
Hal Canary1fcc4042016-11-30 17:07:59 -050018
19#ifdef SK_HAS_WEBP_LIBRARY
20
21#include "SkBitmap.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000022#include "SkColorPriv.h"
Matt Sarett5df93de2017-03-22 21:52:47 +000023#include "SkImageEncoderFns.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000024#include "SkStream.h"
25#include "SkTemplates.h"
Hal Canary1fcc4042016-11-30 17:07:59 -050026#include "SkUnPreMultiply.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000027#include "SkUtils.h"
Matt Sarett04c37312017-05-05 14:02:13 -040028#include "SkWebpEncoder.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000029
Matt Sarett46a45ba2017-04-06 20:34:38 +000030// A WebP encoder only, on top of (subset of) libwebp
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000031// 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.
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000040#include "webp/encode.h"
Matt Sarett46a45ba2017-04-06 20:34:38 +000041#include "webp/mux.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000042}
43
Matt Sarett7abfb5e2017-04-05 17:36:04 -040044static transform_scanline_proc choose_proc(const SkImageInfo& info,
45 SkTransferFunctionBehavior unpremulBehavior) {
46 const bool isSRGBTransferFn =
47 (SkTransferFunctionBehavior::kRespect == unpremulBehavior) && info.gammaCloseToSRGB();
Matt Sarett62bb2802017-01-23 12:28:02 -050048 switch (info.colorType()) {
49 case kRGBA_8888_SkColorType:
50 switch (info.alphaType()) {
51 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050052 return transform_scanline_RGBX;
53 case kUnpremul_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050054 return transform_scanline_memcpy;
55 case kPremul_SkAlphaType:
Matt Sarett7abfb5e2017-04-05 17:36:04 -040056 return isSRGBTransferFn ? transform_scanline_srgbA :
57 transform_scanline_rgbA;
Matt Sarett62bb2802017-01-23 12:28:02 -050058 default:
59 return nullptr;
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +000060 }
Matt Sarett62bb2802017-01-23 12:28:02 -050061 case kBGRA_8888_SkColorType:
62 switch (info.alphaType()) {
63 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050064 return transform_scanline_BGRX;
65 case kUnpremul_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050066 return transform_scanline_BGRA;
67 case kPremul_SkAlphaType:
Matt Sarett7abfb5e2017-04-05 17:36:04 -040068 return isSRGBTransferFn ? transform_scanline_sbgrA :
69 transform_scanline_bgrA;
Matt Sarett62bb2802017-01-23 12:28:02 -050070 default:
71 return nullptr;
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +000072 }
reed0689d7b2014-06-14 05:30:20 -070073 case kRGB_565_SkColorType:
Matt Sarett55213562017-01-23 19:37:37 -050074 if (!info.isOpaque()) {
75 return nullptr;
76 }
77
Matt Sarett62bb2802017-01-23 12:28:02 -050078 return transform_scanline_565;
79 case kARGB_4444_SkColorType:
80 switch (info.alphaType()) {
81 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050082 return transform_scanline_444;
83 case kPremul_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050084 return transform_scanline_4444;
85 default:
86 return nullptr;
87 }
reed0689d7b2014-06-14 05:30:20 -070088 case kIndex_8_SkColorType:
Matt Sarett62bb2802017-01-23 12:28:02 -050089 switch (info.alphaType()) {
90 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050091 return transform_scanline_index8_opaque;
92 case kUnpremul_SkAlphaType:
93 case kPremul_SkAlphaType:
94 // If the color table is premultiplied, we'll fix it before calling the
95 // scanline proc.
Matt Sarett62bb2802017-01-23 12:28:02 -050096 return transform_scanline_index8_unpremul;
97 default:
98 return nullptr;
99 }
100 case kGray_8_SkColorType:
Matt Sarett62bb2802017-01-23 12:28:02 -0500101 return transform_scanline_gray;
Matt Sarett55213562017-01-23 19:37:37 -0500102 case kRGBA_F16_SkColorType:
103 if (!info.colorSpace() || !info.colorSpace()->gammaIsLinear()) {
104 return nullptr;
105 }
106
107 switch (info.alphaType()) {
108 case kOpaque_SkAlphaType:
109 case kUnpremul_SkAlphaType:
110 return transform_scanline_F16_to_8888;
111 case kPremul_SkAlphaType:
112 return transform_scanline_F16_premul_to_8888;
113 default:
114 return nullptr;
115 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000116 default:
halcanary96fcdcc2015-08-27 07:41:13 -0700117 return nullptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000118 }
119}
120
121static int stream_writer(const uint8_t* data, size_t data_size,
122 const WebPPicture* const picture) {
123 SkWStream* const stream = (SkWStream*)picture->custom_ptr;
124 return stream->write(data, data_size) ? 1 : 0;
125}
126
Matt Sarettd5a16912017-05-16 17:06:52 -0400127bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
Matt Sarett04c37312017-05-05 14:02:13 -0400128 if (!SkPixmapIsValid(pixmap, opts.fUnpremulBehavior)) {
129 return false;
Matt Sarett55213562017-01-23 19:37:37 -0500130 }
131
Matt Sarett7abfb5e2017-04-05 17:36:04 -0400132 const transform_scanline_proc proc = choose_proc(pixmap.info(), opts.fUnpremulBehavior);
Matt Sarett62bb2802017-01-23 12:28:02 -0500133 if (!proc) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000134 return false;
135 }
Matt Sarett55213562017-01-23 19:37:37 -0500136
137 int bpp;
138 if (kRGBA_F16_SkColorType == pixmap.colorType()) {
139 bpp = 4;
140 } else {
141 bpp = pixmap.isOpaque() ? 3 : 4;
142 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000143
Hal Canary1fcc4042016-11-30 17:07:59 -0500144 if (nullptr == pixmap.addr()) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000145 return false;
146 }
147
Matt Sarett62bb2802017-01-23 12:28:02 -0500148 const SkPMColor* colors = nullptr;
149 SkPMColor storage[256];
150 if (kIndex_8_SkColorType == pixmap.colorType()) {
151 if (!pixmap.ctable()) {
152 return false;
153 }
154
155 colors = pixmap.ctable()->readColors();
156 if (kPremul_SkAlphaType == pixmap.alphaType()) {
Matt Sarett55213562017-01-23 19:37:37 -0500157 // Unpremultiply the colors.
158 const SkImageInfo rgbaInfo = pixmap.info().makeColorType(kRGBA_8888_SkColorType);
Matt Sarett7abfb5e2017-04-05 17:36:04 -0400159 transform_scanline_proc proc = choose_proc(rgbaInfo, opts.fUnpremulBehavior);
Matt Sarett55213562017-01-23 19:37:37 -0500160 proc((char*) storage, (const char*) colors, pixmap.ctable()->count(), 4, nullptr);
Matt Sarett62bb2802017-01-23 12:28:02 -0500161 colors = storage;
162 }
163 }
164
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000165 WebPConfig webp_config;
Matt Sarett04c37312017-05-05 14:02:13 -0400166 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000167 return false;
168 }
169
170 WebPPicture pic;
171 WebPPictureInit(&pic);
Matt Sarett46a45ba2017-04-06 20:34:38 +0000172 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
Hal Canary1fcc4042016-11-30 17:07:59 -0500173 pic.width = pixmap.width();
174 pic.height = pixmap.height();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000175 pic.writer = stream_writer;
Matt Sarett46a45ba2017-04-06 20:34:38 +0000176
Matt Sarett2f687872017-05-19 19:12:54 -0400177 // Set compression, method, and pixel format.
178 // libwebp recommends using BGRA for lossless and YUV for lossy.
179 // The choices of |webp_config.method| currently just match Chrome's defaults. We
180 // could potentially expose this decision to the client.
181 if (Compression::kLossy == opts.fCompression) {
182 webp_config.lossless = 0;
183#ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD
184 webp_config.method = 3;
185#endif
186 pic.use_argb = 0;
187 } else {
188 webp_config.lossless = 1;
189 webp_config.method = 0;
190 pic.use_argb = 1;
191 }
192
Matt Sarett46a45ba2017-04-06 20:34:38 +0000193 // If there is no need to embed an ICC profile, we write directly to the input stream.
194 // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp
195 // forces us to have an encoded image before we can add a profile.
196 sk_sp<SkData> icc = pixmap.colorSpace() ? icc_from_color_space(*pixmap.colorSpace()) : nullptr;
197 SkDynamicMemoryWStream tmp;
198 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000199
Hal Canary1fcc4042016-11-30 17:07:59 -0500200 const uint8_t* src = (uint8_t*)pixmap.addr();
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000201 const int rgbStride = pic.width * bpp;
Hal Canary1fcc4042016-11-30 17:07:59 -0500202 const size_t rowBytes = pixmap.rowBytes();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000203
204 // Import (for each scanline) the bit-map image (in appropriate color-space)
205 // to RGB color space.
Hal Canary1fcc4042016-11-30 17:07:59 -0500206 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000207 for (int y = 0; y < pic.height; ++y) {
Matt Sarett62bb2802017-01-23 12:28:02 -0500208 proc((char*) &rgb[y * rgbStride], (const char*) &src[y * rowBytes], pic.width, bpp, colors);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000209 }
210
Matt Sarett46a45ba2017-04-06 20:34:38 +0000211 auto importProc = WebPPictureImportRGB;
212 if (3 != bpp) {
Matt Sarett55213562017-01-23 19:37:37 -0500213 if (pixmap.isOpaque()) {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000214 importProc = WebPPictureImportRGBX;
Matt Sarett55213562017-01-23 19:37:37 -0500215 } else {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000216 importProc = WebPPictureImportRGBA;
Matt Sarett55213562017-01-23 19:37:37 -0500217 }
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000218 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000219
Matt Sarett46a45ba2017-04-06 20:34:38 +0000220 if (!importProc(&pic, &rgb[0], rgbStride)) {
221 return false;
222 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000223
Matt Sarett46a45ba2017-04-06 20:34:38 +0000224 if (!WebPEncode(&webp_config, &pic)) {
225 return false;
226 }
227
228 if (icc) {
229 sk_sp<SkData> encodedData = tmp.detachAsData();
230 WebPData encoded = { encodedData->bytes(), encodedData->size() };
231 WebPData iccChunk = { icc->bytes(), icc->size() };
232
233 SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
234 if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
235 return false;
236 }
237
238 if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
239 return false;
240 }
241
242 WebPData assembled;
243 if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
244 return false;
245 }
246
247 stream->write(assembled.bytes, assembled.size);
248 WebPDataClear(&assembled);
249 }
250
251 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000252}
Matt Sarett55213562017-01-23 19:37:37 -0500253
Hal Canary1fcc4042016-11-30 17:07:59 -0500254#endif