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