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 "SkColorPriv.h" 23 #include "SkColorSpace_Base.h" 24 #include "SkImageEncoderFns.h" 25 #include "SkStream.h" 26 #include "SkTemplates.h" 27 #include "SkUnPreMultiply.h" 28 #include "SkUtils.h" 29 #include "SkWebpEncoder.h" 30 31 // A WebP encoder only, on top of (subset of) libwebp 32 // 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> 38 extern "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. 41 #include "webp/encode.h" 42 #include "webp/mux.h" 43 } 44 45 static transform_scanline_proc choose_proc(const SkImageInfo& info, 46 SkTransferFunctionBehavior unpremulBehavior) { 47 const bool isSRGBTransferFn = 48 (SkTransferFunctionBehavior::kRespect == unpremulBehavior) && info.gammaCloseToSRGB(); 49 switch (info.colorType()) { 50 case kRGBA_8888_SkColorType: 51 switch (info.alphaType()) { 52 case kOpaque_SkAlphaType: 53 return transform_scanline_RGBX; 54 case kUnpremul_SkAlphaType: 55 return transform_scanline_memcpy; 56 case kPremul_SkAlphaType: 57 return isSRGBTransferFn ? transform_scanline_srgbA : 58 transform_scanline_rgbA; 59 default: 60 return nullptr; 61 } 62 case kBGRA_8888_SkColorType: 63 switch (info.alphaType()) { 64 case kOpaque_SkAlphaType: 65 return transform_scanline_BGRX; 66 case kUnpremul_SkAlphaType: 67 return transform_scanline_BGRA; 68 case kPremul_SkAlphaType: 69 return isSRGBTransferFn ? transform_scanline_sbgrA : 70 transform_scanline_bgrA; 71 default: 72 return nullptr; 73 } 74 case kRGB_565_SkColorType: 75 if (!info.isOpaque()) { 76 return nullptr; 77 } 78 79 return transform_scanline_565; 80 case kARGB_4444_SkColorType: 81 switch (info.alphaType()) { 82 case kOpaque_SkAlphaType: 83 return transform_scanline_444; 84 case kPremul_SkAlphaType: 85 return transform_scanline_4444; 86 default: 87 return nullptr; 88 } 89 case kGray_8_SkColorType: 90 return transform_scanline_gray; 91 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 } 105 default: 106 return nullptr; 107 } 108 } 109 110 static 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 116 bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) { 117 if (!SkPixmapIsValid(pixmap, opts.fUnpremulBehavior)) { 118 return false; 119 } 120 121 const transform_scanline_proc proc = choose_proc(pixmap.info(), opts.fUnpremulBehavior); 122 if (!proc) { 123 return false; 124 } 125 126 int bpp; 127 if (kRGBA_F16_SkColorType == pixmap.colorType()) { 128 bpp = 4; 129 } else { 130 bpp = pixmap.isOpaque() ? 3 : 4; 131 } 132 133 if (nullptr == pixmap.addr()) { 134 return false; 135 } 136 137 const SkPMColor* colors = nullptr; 138 139 WebPConfig webp_config; 140 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) { 141 return false; 142 } 143 144 WebPPicture pic; 145 WebPPictureInit(&pic); 146 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic); 147 pic.width = pixmap.width(); 148 pic.height = pixmap.height(); 149 pic.writer = stream_writer; 150 151 // 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 167 // 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. 170 sk_sp<SkData> icc = icc_from_color_space(pixmap.info()); 171 SkDynamicMemoryWStream tmp; 172 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream; 173 174 const uint8_t* src = (uint8_t*)pixmap.addr(); 175 const int rgbStride = pic.width * bpp; 176 const size_t rowBytes = pixmap.rowBytes(); 177 178 // Import (for each scanline) the bit-map image (in appropriate color-space) 179 // to RGB color space. 180 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]); 181 for (int y = 0; y < pic.height; ++y) { 182 proc((char*) &rgb[y * rgbStride], (const char*) &src[y * rowBytes], pic.width, bpp, colors); 183 } 184 185 auto importProc = WebPPictureImportRGB; 186 if (3 != bpp) { 187 if (pixmap.isOpaque()) { 188 importProc = WebPPictureImportRGBX; 189 } else { 190 importProc = WebPPictureImportRGBA; 191 } 192 } 193 194 if (!importProc(&pic, &rgb[0], rgbStride)) { 195 return false; 196 } 197 198 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; 226 } 227 228 #endif 229