blob: 2cb55d2f04d0f18dfc666d7ad074e9379a532612 [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"
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 }
Matt Sarett62bb2802017-01-23 12:28:02 -050088 case kGray_8_SkColorType:
Matt Sarett62bb2802017-01-23 12:28:02 -050089 return transform_scanline_gray;
Matt Sarett55213562017-01-23 19:37:37 -050090 case kRGBA_F16_SkColorType:
Matt Sarett55213562017-01-23 19:37:37 -050091 switch (info.alphaType()) {
92 case kOpaque_SkAlphaType:
93 case kUnpremul_SkAlphaType:
94 return transform_scanline_F16_to_8888;
95 case kPremul_SkAlphaType:
96 return transform_scanline_F16_premul_to_8888;
97 default:
98 return nullptr;
99 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000100 default:
halcanary96fcdcc2015-08-27 07:41:13 -0700101 return nullptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000102 }
103}
104
105static int stream_writer(const uint8_t* data, size_t data_size,
106 const WebPPicture* const picture) {
107 SkWStream* const stream = (SkWStream*)picture->custom_ptr;
108 return stream->write(data, data_size) ? 1 : 0;
109}
110
Matt Sarettd5a16912017-05-16 17:06:52 -0400111bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
Brian Osmane1adc3a2018-06-04 09:21:17 -0400112 if (!SkPixmapIsValid(pixmap)) {
Matt Sarett04c37312017-05-05 14:02:13 -0400113 return false;
Matt Sarett55213562017-01-23 19:37:37 -0500114 }
115
Matt Sarett7abfb5e2017-04-05 17:36:04 -0400116 const transform_scanline_proc proc = choose_proc(pixmap.info(), opts.fUnpremulBehavior);
Matt Sarett62bb2802017-01-23 12:28:02 -0500117 if (!proc) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000118 return false;
119 }
Matt Sarett55213562017-01-23 19:37:37 -0500120
121 int bpp;
122 if (kRGBA_F16_SkColorType == pixmap.colorType()) {
123 bpp = 4;
124 } else {
125 bpp = pixmap.isOpaque() ? 3 : 4;
126 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000127
Hal Canary1fcc4042016-11-30 17:07:59 -0500128 if (nullptr == pixmap.addr()) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000129 return false;
130 }
131
Matt Sarett62bb2802017-01-23 12:28:02 -0500132 const SkPMColor* colors = nullptr;
Matt Sarett62bb2802017-01-23 12:28:02 -0500133
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000134 WebPConfig webp_config;
Matt Sarett04c37312017-05-05 14:02:13 -0400135 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000136 return false;
137 }
138
139 WebPPicture pic;
140 WebPPictureInit(&pic);
Matt Sarett46a45ba2017-04-06 20:34:38 +0000141 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
Hal Canary1fcc4042016-11-30 17:07:59 -0500142 pic.width = pixmap.width();
143 pic.height = pixmap.height();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000144 pic.writer = stream_writer;
Matt Sarett46a45ba2017-04-06 20:34:38 +0000145
Matt Sarett2f687872017-05-19 19:12:54 -0400146 // Set compression, method, and pixel format.
147 // libwebp recommends using BGRA for lossless and YUV for lossy.
148 // The choices of |webp_config.method| currently just match Chrome's defaults. We
149 // could potentially expose this decision to the client.
150 if (Compression::kLossy == opts.fCompression) {
151 webp_config.lossless = 0;
152#ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD
153 webp_config.method = 3;
154#endif
155 pic.use_argb = 0;
156 } else {
157 webp_config.lossless = 1;
158 webp_config.method = 0;
159 pic.use_argb = 1;
160 }
161
Matt Sarett46a45ba2017-04-06 20:34:38 +0000162 // If there is no need to embed an ICC profile, we write directly to the input stream.
163 // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp
164 // forces us to have an encoded image before we can add a profile.
Matt Sarett1950e0a2017-06-12 16:17:30 -0400165 sk_sp<SkData> icc = icc_from_color_space(pixmap.info());
Matt Sarett46a45ba2017-04-06 20:34:38 +0000166 SkDynamicMemoryWStream tmp;
167 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000168
Hal Canary1fcc4042016-11-30 17:07:59 -0500169 const uint8_t* src = (uint8_t*)pixmap.addr();
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000170 const int rgbStride = pic.width * bpp;
Hal Canary1fcc4042016-11-30 17:07:59 -0500171 const size_t rowBytes = pixmap.rowBytes();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000172
173 // Import (for each scanline) the bit-map image (in appropriate color-space)
174 // to RGB color space.
Hal Canary1fcc4042016-11-30 17:07:59 -0500175 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000176 for (int y = 0; y < pic.height; ++y) {
Matt Sarett62bb2802017-01-23 12:28:02 -0500177 proc((char*) &rgb[y * rgbStride], (const char*) &src[y * rowBytes], pic.width, bpp, colors);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000178 }
179
Matt Sarett46a45ba2017-04-06 20:34:38 +0000180 auto importProc = WebPPictureImportRGB;
181 if (3 != bpp) {
Matt Sarett55213562017-01-23 19:37:37 -0500182 if (pixmap.isOpaque()) {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000183 importProc = WebPPictureImportRGBX;
Matt Sarett55213562017-01-23 19:37:37 -0500184 } else {
Matt Sarett46a45ba2017-04-06 20:34:38 +0000185 importProc = WebPPictureImportRGBA;
Matt Sarett55213562017-01-23 19:37:37 -0500186 }
commit-bot@chromium.org5007aab2014-02-26 21:35:17 +0000187 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000188
Matt Sarett46a45ba2017-04-06 20:34:38 +0000189 if (!importProc(&pic, &rgb[0], rgbStride)) {
190 return false;
191 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000192
Matt Sarett46a45ba2017-04-06 20:34:38 +0000193 if (!WebPEncode(&webp_config, &pic)) {
194 return false;
195 }
196
197 if (icc) {
198 sk_sp<SkData> encodedData = tmp.detachAsData();
199 WebPData encoded = { encodedData->bytes(), encodedData->size() };
200 WebPData iccChunk = { icc->bytes(), icc->size() };
201
202 SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
203 if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
204 return false;
205 }
206
207 if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
208 return false;
209 }
210
211 WebPData assembled;
212 if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
213 return false;
214 }
215
216 stream->write(assembled.bytes, assembled.size);
217 WebPDataClear(&assembled);
218 }
219
220 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000221}
Matt Sarett55213562017-01-23 19:37:37 -0500222
Hal Canary1fcc4042016-11-30 17:07:59 -0500223#endif