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 "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>
     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                                            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 
    109 static 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 
    115 bool 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