/* * Copyright 2010, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "SkImageEncoderPriv.h" #ifdef SK_HAS_WEBP_LIBRARY #include "SkBitmap.h" #include "SkColorData.h" #include "SkImageEncoderFns.h" #include "SkStream.h" #include "SkTemplates.h" #include "SkUnPreMultiply.h" #include "SkUtils.h" #include "SkWebpEncoder.h" // A WebP encoder only, on top of (subset of) libwebp // For more information on WebP image format, and libwebp library, see: // http://code.google.com/speed/webp/ // http://www.webmproject.org/code/#libwebp_webp_image_decoder_library // http://review.webmproject.org/gitweb?p=libwebp.git #include <stdio.h> extern "C" { // If moving libwebp out of skia source tree, path for webp headers must be // updated accordingly. Here, we enforce using local copy in webp sub-directory. #include "webp/encode.h" #include "webp/mux.h" } static transform_scanline_proc choose_proc(const SkImageInfo& info, SkTransferFunctionBehavior unpremulBehavior) { const bool isSRGBTransferFn = (SkTransferFunctionBehavior::kRespect == unpremulBehavior) && info.gammaCloseToSRGB(); switch (info.colorType()) { case kRGBA_8888_SkColorType: switch (info.alphaType()) { case kOpaque_SkAlphaType: return transform_scanline_RGBX; case kUnpremul_SkAlphaType: return transform_scanline_memcpy; case kPremul_SkAlphaType: return isSRGBTransferFn ? transform_scanline_srgbA : transform_scanline_rgbA; default: return nullptr; } case kBGRA_8888_SkColorType: switch (info.alphaType()) { case kOpaque_SkAlphaType: return transform_scanline_BGRX; case kUnpremul_SkAlphaType: return transform_scanline_BGRA; case kPremul_SkAlphaType: return isSRGBTransferFn ? transform_scanline_sbgrA : transform_scanline_bgrA; default: return nullptr; } case kRGB_565_SkColorType: if (!info.isOpaque()) { return nullptr; } return transform_scanline_565; case kARGB_4444_SkColorType: switch (info.alphaType()) { case kOpaque_SkAlphaType: return transform_scanline_444; case kPremul_SkAlphaType: return transform_scanline_4444; default: return nullptr; } case kGray_8_SkColorType: return transform_scanline_gray; case kRGBA_F16_SkColorType: if (!info.colorSpace() || !info.colorSpace()->gammaIsLinear()) { return nullptr; } switch (info.alphaType()) { case kOpaque_SkAlphaType: case kUnpremul_SkAlphaType: return transform_scanline_F16_to_8888; case kPremul_SkAlphaType: return transform_scanline_F16_premul_to_8888; default: return nullptr; } default: return nullptr; } } static int stream_writer(const uint8_t* data, size_t data_size, const WebPPicture* const picture) { SkWStream* const stream = (SkWStream*)picture->custom_ptr; return stream->write(data, data_size) ? 1 : 0; } bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) { if (!SkPixmapIsValid(pixmap, opts.fUnpremulBehavior)) { return false; } const transform_scanline_proc proc = choose_proc(pixmap.info(), opts.fUnpremulBehavior); if (!proc) { return false; } int bpp; if (kRGBA_F16_SkColorType == pixmap.colorType()) { bpp = 4; } else { bpp = pixmap.isOpaque() ? 3 : 4; } if (nullptr == pixmap.addr()) { return false; } const SkPMColor* colors = nullptr; WebPConfig webp_config; if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) { return false; } WebPPicture pic; WebPPictureInit(&pic); SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic); pic.width = pixmap.width(); pic.height = pixmap.height(); pic.writer = stream_writer; // Set compression, method, and pixel format. // libwebp recommends using BGRA for lossless and YUV for lossy. // The choices of |webp_config.method| currently just match Chrome's defaults. We // could potentially expose this decision to the client. if (Compression::kLossy == opts.fCompression) { webp_config.lossless = 0; #ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD webp_config.method = 3; #endif pic.use_argb = 0; } else { webp_config.lossless = 1; webp_config.method = 0; pic.use_argb = 1; } // If there is no need to embed an ICC profile, we write directly to the input stream. // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp // forces us to have an encoded image before we can add a profile. sk_sp<SkData> icc = icc_from_color_space(pixmap.info()); SkDynamicMemoryWStream tmp; pic.custom_ptr = icc ? (void*)&tmp : (void*)stream; const uint8_t* src = (uint8_t*)pixmap.addr(); const int rgbStride = pic.width * bpp; const size_t rowBytes = pixmap.rowBytes(); // Import (for each scanline) the bit-map image (in appropriate color-space) // to RGB color space. std::unique_ptr<uint8_t[]> rgb(new uint8_t[rgbStride * pic.height]); for (int y = 0; y < pic.height; ++y) { proc((char*) &rgb[y * rgbStride], (const char*) &src[y * rowBytes], pic.width, bpp, colors); } auto importProc = WebPPictureImportRGB; if (3 != bpp) { if (pixmap.isOpaque()) { importProc = WebPPictureImportRGBX; } else { importProc = WebPPictureImportRGBA; } } if (!importProc(&pic, &rgb[0], rgbStride)) { return false; } if (!WebPEncode(&webp_config, &pic)) { return false; } if (icc) { sk_sp<SkData> encodedData = tmp.detachAsData(); WebPData encoded = { encodedData->bytes(), encodedData->size() }; WebPData iccChunk = { icc->bytes(), icc->size() }; SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew()); if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) { return false; } if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) { return false; } WebPData assembled; if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) { return false; } stream->write(assembled.bytes, assembled.size); WebPDataClear(&assembled); } return true; } #endif