blob: 3c08fdcfa41cc54c74158dd9b05e8050cda0f027 [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"
Cary Clarka4083c92017-09-15 11:59:23 -040022#include "SkColorData.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 }
Matt Sarett62bb2802017-01-23 12:28:02 -050089 case kGray_8_SkColorType:
Matt Sarett62bb2802017-01-23 12:28:02 -050090 return transform_scanline_gray;
Matt Sarett55213562017-01-23 19:37:37 -050091 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 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000105 default:
halcanary96fcdcc2015-08-27 07:41:13 -0700106 return nullptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000107 }
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
Matt Sarettd5a16912017-05-16 17:06:52 -0400116bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
Matt Sarett04c37312017-05-05 14:02:13 -0400117 if (!SkPixmapIsValid(pixmap, opts.fUnpremulBehavior)) {
118 return false;
Matt Sarett55213562017-01-23 19:37:37 -0500119 }
120
Matt Sarett7abfb5e2017-04-05 17:36:04 -0400121 const transform_scanline_proc proc = choose_proc(pixmap.info(), opts.fUnpremulBehavior);
Matt Sarett62bb2802017-01-23 12:28:02 -0500122 if (!proc) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000123 return false;
124 }
Matt Sarett55213562017-01-23 19:37:37 -0500125
126 int bpp;
127 if (kRGBA_F16_SkColorType == pixmap.colorType()) {
128 bpp = 4;
129 } else {
130 bpp = pixmap.isOpaque() ? 3 : 4;
131 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000132
Hal Canary1fcc4042016-11-30 17:07:59 -0500133 if (nullptr == pixmap.addr()) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000134 return false;
135 }
136
Matt Sarett62bb2802017-01-23 12:28:02 -0500137 const SkPMColor* colors = nullptr;
Matt Sarett62bb2802017-01-23 12:28:02 -0500138
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000139 WebPConfig webp_config;
Matt Sarett04c37312017-05-05 14:02:13 -0400140 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000141 return false;
142 }
143
144 WebPPicture pic;
145 WebPPictureInit(&pic);
Matt Sarett46a45ba2017-04-06 20:34:38 +0000146 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
Hal Canary1fcc4042016-11-30 17:07:59 -0500147 pic.width = pixmap.width();
148 pic.height = pixmap.height();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000149 pic.writer = stream_writer;
Matt Sarett46a45ba2017-04-06 20:34:38 +0000150
Matt Sarett2f687872017-05-19 19:12:54 -0400151 // 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
Matt Sarett46a45ba2017-04-06 20:34:38 +0000167 // 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.
Matt Sarett1950e0a2017-06-12 16:17:30 -0400170 sk_sp<SkData> icc = icc_from_color_space(pixmap.info());
Matt Sarett46a45ba2017-04-06 20:34:38 +0000171 SkDynamicMemoryWStream tmp;
172 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000173
Hal Canary1fcc4042016-11-30 17:07:59 -0500174 const uint8_t* src = (uint8_t*)pixmap.addr();
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000175 const int rgbStride = pic.width * bpp;
Hal Canary1fcc4042016-11-30 17:07:59 -0500176 const size_t rowBytes = pixmap.rowBytes();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000177
178 // Import (for each scanline) the bit-map image (in appropriate color-space)
179 // to RGB color space.
Hal Canary1fcc4042016-11-30 17:07:59 -0500180 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000181 for (int y = 0; y < pic.height; ++y) {
Matt Sarett62bb2802017-01-23 12:28:02 -0500182 proc((char*) &rgb[y * rgbStride], (const char*) &src[y * rowBytes], pic.width, bpp, colors);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000183 }
184
Matt Sarett46a45ba2017-04-06 20:34:38 +0000185 auto importProc = WebPPictureImportRGB;
186 if (3 != bpp) {
Matt Sarett55213562017-01-23 19:37:37 -0500187 if (pixmap.isOpaque()) {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000188 importProc = WebPPictureImportRGBX;
Matt Sarett55213562017-01-23 19:37:37 -0500189 } else {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000190 importProc = WebPPictureImportRGBA;
Matt Sarett55213562017-01-23 19:37:37 -0500191 }
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000192 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000193
Matt Sarett46a45ba2017-04-06 20:34:38 +0000194 if (!importProc(&pic, &rgb[0], rgbStride)) {
195 return false;
196 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000197
Matt Sarett46a45ba2017-04-06 20:34:38 +0000198 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;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000226}
Matt Sarett55213562017-01-23 19:37:37 -0500227
Hal Canary1fcc4042016-11-30 17:07:59 -0500228#endif