blob: 81ed7e184532c6b932230f50f362fd7f1b3805ec [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"
Mike Klein5e819ca2017-06-12 13:07:22 -040023#include "SkColorSpace_Base.h"
Matt Sarett5df93de2017-03-22 21:52:47 +000024#include "SkImageEncoderFns.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000025#include "SkStream.h"
26#include "SkTemplates.h"
Hal Canary1fcc4042016-11-30 17:07:59 -050027#include "SkUnPreMultiply.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000028#include "SkUtils.h"
Matt Sarett04c37312017-05-05 14:02:13 -040029#include "SkWebpEncoder.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000030
Matt Sarett46a45ba2017-04-06 20:34:38 +000031// A WebP encoder only, on top of (subset of) libwebp
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000032// 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.
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000041#include "webp/encode.h"
Matt Sarett46a45ba2017-04-06 20:34:38 +000042#include "webp/mux.h"
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000043}
44
Matt Sarett7abfb5e2017-04-05 17:36:04 -040045static transform_scanline_proc choose_proc(const SkImageInfo& info,
46 SkTransferFunctionBehavior unpremulBehavior) {
47 const bool isSRGBTransferFn =
48 (SkTransferFunctionBehavior::kRespect == unpremulBehavior) && info.gammaCloseToSRGB();
Matt Sarett62bb2802017-01-23 12:28:02 -050049 switch (info.colorType()) {
50 case kRGBA_8888_SkColorType:
51 switch (info.alphaType()) {
52 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050053 return transform_scanline_RGBX;
54 case kUnpremul_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050055 return transform_scanline_memcpy;
56 case kPremul_SkAlphaType:
Matt Sarett7abfb5e2017-04-05 17:36:04 -040057 return isSRGBTransferFn ? transform_scanline_srgbA :
58 transform_scanline_rgbA;
Matt Sarett62bb2802017-01-23 12:28:02 -050059 default:
60 return nullptr;
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +000061 }
Matt Sarett62bb2802017-01-23 12:28:02 -050062 case kBGRA_8888_SkColorType:
63 switch (info.alphaType()) {
64 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050065 return transform_scanline_BGRX;
66 case kUnpremul_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050067 return transform_scanline_BGRA;
68 case kPremul_SkAlphaType:
Matt Sarett7abfb5e2017-04-05 17:36:04 -040069 return isSRGBTransferFn ? transform_scanline_sbgrA :
70 transform_scanline_bgrA;
Matt Sarett62bb2802017-01-23 12:28:02 -050071 default:
72 return nullptr;
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +000073 }
reed0689d7b2014-06-14 05:30:20 -070074 case kRGB_565_SkColorType:
Matt Sarett55213562017-01-23 19:37:37 -050075 if (!info.isOpaque()) {
76 return nullptr;
77 }
78
Matt Sarett62bb2802017-01-23 12:28:02 -050079 return transform_scanline_565;
80 case kARGB_4444_SkColorType:
81 switch (info.alphaType()) {
82 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050083 return transform_scanline_444;
84 case kPremul_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050085 return transform_scanline_4444;
86 default:
87 return nullptr;
88 }
reed0689d7b2014-06-14 05:30:20 -070089 case kIndex_8_SkColorType:
Matt Sarett62bb2802017-01-23 12:28:02 -050090 switch (info.alphaType()) {
91 case kOpaque_SkAlphaType:
Matt Sarett62bb2802017-01-23 12:28:02 -050092 return transform_scanline_index8_opaque;
93 case kUnpremul_SkAlphaType:
94 case kPremul_SkAlphaType:
95 // If the color table is premultiplied, we'll fix it before calling the
96 // scanline proc.
Matt Sarett62bb2802017-01-23 12:28:02 -050097 return transform_scanline_index8_unpremul;
98 default:
99 return nullptr;
100 }
101 case kGray_8_SkColorType:
Matt Sarett62bb2802017-01-23 12:28:02 -0500102 return transform_scanline_gray;
Matt Sarett55213562017-01-23 19:37:37 -0500103 case kRGBA_F16_SkColorType:
104 if (!info.colorSpace() || !info.colorSpace()->gammaIsLinear()) {
105 return nullptr;
106 }
107
108 switch (info.alphaType()) {
109 case kOpaque_SkAlphaType:
110 case kUnpremul_SkAlphaType:
111 return transform_scanline_F16_to_8888;
112 case kPremul_SkAlphaType:
113 return transform_scanline_F16_premul_to_8888;
114 default:
115 return nullptr;
116 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000117 default:
halcanary96fcdcc2015-08-27 07:41:13 -0700118 return nullptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000119 }
120}
121
122static int stream_writer(const uint8_t* data, size_t data_size,
123 const WebPPicture* const picture) {
124 SkWStream* const stream = (SkWStream*)picture->custom_ptr;
125 return stream->write(data, data_size) ? 1 : 0;
126}
127
Matt Sarettd5a16912017-05-16 17:06:52 -0400128bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
Matt Sarett04c37312017-05-05 14:02:13 -0400129 if (!SkPixmapIsValid(pixmap, opts.fUnpremulBehavior)) {
130 return false;
Matt Sarett55213562017-01-23 19:37:37 -0500131 }
132
Matt Sarett7abfb5e2017-04-05 17:36:04 -0400133 const transform_scanline_proc proc = choose_proc(pixmap.info(), opts.fUnpremulBehavior);
Matt Sarett62bb2802017-01-23 12:28:02 -0500134 if (!proc) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000135 return false;
136 }
Matt Sarett55213562017-01-23 19:37:37 -0500137
138 int bpp;
139 if (kRGBA_F16_SkColorType == pixmap.colorType()) {
140 bpp = 4;
141 } else {
142 bpp = pixmap.isOpaque() ? 3 : 4;
143 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000144
Hal Canary1fcc4042016-11-30 17:07:59 -0500145 if (nullptr == pixmap.addr()) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000146 return false;
147 }
148
Matt Sarett62bb2802017-01-23 12:28:02 -0500149 const SkPMColor* colors = nullptr;
150 SkPMColor storage[256];
151 if (kIndex_8_SkColorType == pixmap.colorType()) {
152 if (!pixmap.ctable()) {
153 return false;
154 }
155
156 colors = pixmap.ctable()->readColors();
157 if (kPremul_SkAlphaType == pixmap.alphaType()) {
Matt Sarett55213562017-01-23 19:37:37 -0500158 // Unpremultiply the colors.
159 const SkImageInfo rgbaInfo = pixmap.info().makeColorType(kRGBA_8888_SkColorType);
Matt Sarett7abfb5e2017-04-05 17:36:04 -0400160 transform_scanline_proc proc = choose_proc(rgbaInfo, opts.fUnpremulBehavior);
Matt Sarett55213562017-01-23 19:37:37 -0500161 proc((char*) storage, (const char*) colors, pixmap.ctable()->count(), 4, nullptr);
Matt Sarett62bb2802017-01-23 12:28:02 -0500162 colors = storage;
163 }
164 }
165
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000166 WebPConfig webp_config;
Matt Sarett04c37312017-05-05 14:02:13 -0400167 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000168 return false;
169 }
170
171 WebPPicture pic;
172 WebPPictureInit(&pic);
Matt Sarett46a45ba2017-04-06 20:34:38 +0000173 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
Hal Canary1fcc4042016-11-30 17:07:59 -0500174 pic.width = pixmap.width();
175 pic.height = pixmap.height();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000176 pic.writer = stream_writer;
Matt Sarett46a45ba2017-04-06 20:34:38 +0000177
Matt Sarett2f687872017-05-19 19:12:54 -0400178 // Set compression, method, and pixel format.
179 // libwebp recommends using BGRA for lossless and YUV for lossy.
180 // The choices of |webp_config.method| currently just match Chrome's defaults. We
181 // could potentially expose this decision to the client.
182 if (Compression::kLossy == opts.fCompression) {
183 webp_config.lossless = 0;
184#ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD
185 webp_config.method = 3;
186#endif
187 pic.use_argb = 0;
188 } else {
189 webp_config.lossless = 1;
190 webp_config.method = 0;
191 pic.use_argb = 1;
192 }
193
Matt Sarett46a45ba2017-04-06 20:34:38 +0000194 // If there is no need to embed an ICC profile, we write directly to the input stream.
195 // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp
196 // forces us to have an encoded image before we can add a profile.
Mike Klein5e819ca2017-06-12 13:07:22 -0400197 sk_sp<SkData> icc;
198 if (SkColorSpace* cs = pixmap.colorSpace()) {
199 sk_sp<SkColorSpace> owned;
200 if (pixmap.colorType() == kRGBA_F16_SkColorType) {
201 // We'll be converting to 8-bit sRGB, so we'd better tag it that way.
202 owned = as_CSB(pixmap.colorSpace())->makeSRGBGamma();
203 cs = owned.get();
204 }
205 icc = icc_from_color_space(*cs);
206 }
Matt Sarett46a45ba2017-04-06 20:34:38 +0000207 SkDynamicMemoryWStream tmp;
208 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000209
Hal Canary1fcc4042016-11-30 17:07:59 -0500210 const uint8_t* src = (uint8_t*)pixmap.addr();
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000211 const int rgbStride = pic.width * bpp;
Hal Canary1fcc4042016-11-30 17:07:59 -0500212 const size_t rowBytes = pixmap.rowBytes();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000213
214 // Import (for each scanline) the bit-map image (in appropriate color-space)
215 // to RGB color space.
Hal Canary1fcc4042016-11-30 17:07:59 -0500216 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000217 for (int y = 0; y < pic.height; ++y) {
Matt Sarett62bb2802017-01-23 12:28:02 -0500218 proc((char*) &rgb[y * rgbStride], (const char*) &src[y * rowBytes], pic.width, bpp, colors);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000219 }
220
Matt Sarett46a45ba2017-04-06 20:34:38 +0000221 auto importProc = WebPPictureImportRGB;
222 if (3 != bpp) {
Matt Sarett55213562017-01-23 19:37:37 -0500223 if (pixmap.isOpaque()) {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000224 importProc = WebPPictureImportRGBX;
Matt Sarett55213562017-01-23 19:37:37 -0500225 } else {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000226 importProc = WebPPictureImportRGBA;
Matt Sarett55213562017-01-23 19:37:37 -0500227 }
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000228 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000229
Matt Sarett46a45ba2017-04-06 20:34:38 +0000230 if (!importProc(&pic, &rgb[0], rgbStride)) {
231 return false;
232 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000233
Matt Sarett46a45ba2017-04-06 20:34:38 +0000234 if (!WebPEncode(&webp_config, &pic)) {
235 return false;
236 }
237
238 if (icc) {
239 sk_sp<SkData> encodedData = tmp.detachAsData();
240 WebPData encoded = { encodedData->bytes(), encodedData->size() };
241 WebPData iccChunk = { icc->bytes(), icc->size() };
242
243 SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
244 if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
245 return false;
246 }
247
248 if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
249 return false;
250 }
251
252 WebPData assembled;
253 if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
254 return false;
255 }
256
257 stream->write(assembled.bytes, assembled.size);
258 WebPDataClear(&assembled);
259 }
260
261 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000262}
Matt Sarett55213562017-01-23 19:37:37 -0500263
Hal Canary1fcc4042016-11-30 17:07:59 -0500264#endif