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 "SkUTF.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> 37 extern "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 44 static transform_scanline_proc choose_proc(const SkImageInfo& info) { 45 switch (info.colorType()) { 46 case kRGBA_8888_SkColorType: 47 switch (info.alphaType()) { 48 case kOpaque_SkAlphaType: 49 return transform_scanline_RGBX; 50 case kUnpremul_SkAlphaType: 51 return transform_scanline_memcpy; 52 case kPremul_SkAlphaType: 53 return transform_scanline_rgbA; 54 default: 55 return nullptr; 56 } 57 case kBGRA_8888_SkColorType: 58 switch (info.alphaType()) { 59 case kOpaque_SkAlphaType: 60 return transform_scanline_BGRX; 61 case kUnpremul_SkAlphaType: 62 return transform_scanline_BGRA; 63 case kPremul_SkAlphaType: 64 return transform_scanline_bgrA; 65 default: 66 return nullptr; 67 } 68 case kRGB_565_SkColorType: 69 if (!info.isOpaque()) { 70 return nullptr; 71 } 72 73 return transform_scanline_565; 74 case kARGB_4444_SkColorType: 75 switch (info.alphaType()) { 76 case kOpaque_SkAlphaType: 77 return transform_scanline_444; 78 case kPremul_SkAlphaType: 79 return transform_scanline_4444; 80 default: 81 return nullptr; 82 } 83 case kGray_8_SkColorType: 84 return transform_scanline_gray; 85 case kRGBA_F16_SkColorType: 86 switch (info.alphaType()) { 87 case kOpaque_SkAlphaType: 88 case kUnpremul_SkAlphaType: 89 return transform_scanline_F16_to_8888; 90 case kPremul_SkAlphaType: 91 return transform_scanline_F16_premul_to_8888; 92 default: 93 return nullptr; 94 } 95 default: 96 return nullptr; 97 } 98 } 99 100 static int stream_writer(const uint8_t* data, size_t data_size, 101 const WebPPicture* const picture) { 102 SkWStream* const stream = (SkWStream*)picture->custom_ptr; 103 return stream->write(data, data_size) ? 1 : 0; 104 } 105 106 bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) { 107 if (!SkPixmapIsValid(pixmap)) { 108 return false; 109 } 110 111 const transform_scanline_proc proc = choose_proc(pixmap.info()); 112 if (!proc) { 113 return false; 114 } 115 116 int bpp; 117 if (kRGBA_F16_SkColorType == pixmap.colorType()) { 118 bpp = 4; 119 } else { 120 bpp = pixmap.isOpaque() ? 3 : 4; 121 } 122 123 if (nullptr == pixmap.addr()) { 124 return false; 125 } 126 127 WebPConfig webp_config; 128 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) { 129 return false; 130 } 131 132 WebPPicture pic; 133 WebPPictureInit(&pic); 134 SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic); 135 pic.width = pixmap.width(); 136 pic.height = pixmap.height(); 137 pic.writer = stream_writer; 138 139 // Set compression, method, and pixel format. 140 // libwebp recommends using BGRA for lossless and YUV for lossy. 141 // The choices of |webp_config.method| currently just match Chrome's defaults. We 142 // could potentially expose this decision to the client. 143 if (Compression::kLossy == opts.fCompression) { 144 webp_config.lossless = 0; 145 #ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD 146 webp_config.method = 3; 147 #endif 148 pic.use_argb = 0; 149 } else { 150 webp_config.lossless = 1; 151 webp_config.method = 0; 152 pic.use_argb = 1; 153 } 154 155 // If there is no need to embed an ICC profile, we write directly to the input stream. 156 // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp 157 // forces us to have an encoded image before we can add a profile. 158 sk_sp<SkData> icc = icc_from_color_space(pixmap.info()); 159 SkDynamicMemoryWStream tmp; 160 pic.custom_ptr = icc ? (void*)&tmp : (void*)stream; 161 162 const uint8_t* src = (uint8_t*)pixmap.addr(); 163 const int rgbStride = pic.width * bpp; 164 const size_t rowBytes = pixmap.rowBytes(); 165 166 // Import (for each scanline) the bit-map image (in appropriate color-space) 167 // to RGB color space. 168 std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]); 169 for (int y = 0; y < pic.height; ++y) { 170 proc((char*) &rgb[y * rgbStride], 171 (const char*) &src[y * rowBytes], 172 pic.width, 173 bpp); 174 } 175 176 auto importProc = WebPPictureImportRGB; 177 if (3 != bpp) { 178 if (pixmap.isOpaque()) { 179 importProc = WebPPictureImportRGBX; 180 } else { 181 importProc = WebPPictureImportRGBA; 182 } 183 } 184 185 if (!importProc(&pic, &rgb[0], rgbStride)) { 186 return false; 187 } 188 189 if (!WebPEncode(&webp_config, &pic)) { 190 return false; 191 } 192 193 if (icc) { 194 sk_sp<SkData> encodedData = tmp.detachAsData(); 195 WebPData encoded = { encodedData->bytes(), encodedData->size() }; 196 WebPData iccChunk = { icc->bytes(), icc->size() }; 197 198 SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew()); 199 if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) { 200 return false; 201 } 202 203 if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) { 204 return false; 205 } 206 207 WebPData assembled; 208 if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) { 209 return false; 210 } 211 212 stream->write(assembled.bytes, assembled.size); 213 WebPDataClear(&assembled); 214 } 215 216 return true; 217 } 218 219 #endif 220