Home | History | Annotate | Download | only in images
      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