/* * Copyright 2010 The Android Open Source Project * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/encode/SkWebpEncoder.h" #include "include/core/SkAlphaType.h" #include "include/core/SkBitmap.h" #include "include/core/SkColorType.h" #include "include/core/SkData.h" #include "include/core/SkImageInfo.h" #include "include/core/SkPixmap.h" #include "include/core/SkRefCnt.h" #include "include/core/SkSpan.h" #include "include/core/SkStream.h" #include "include/encode/SkEncoder.h" #include "include/private/base/SkTemplates.h" #include "src/core/SkImageInfoPriv.h" #include "src/encode/SkImageEncoderFns.h" #include "src/encode/SkImageEncoderPriv.h" #include "src/image/SkImage_Base.h" #include #include #include class GrDirectContext; class SkImage; // 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 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" // NO_G3_REWRITE #include "webp/mux.h" // NO_G3_REWRITE #include "webp/mux_types.h" // NO_G3_REWRITE } 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; } using WebPPictureImportProc = int (*)(WebPPicture* picture, const uint8_t* pixels, int stride); static bool preprocess_webp_picture(WebPPicture* pic, WebPConfig* webp_config, const SkPixmap& pixmap, const SkWebpEncoder::Options& opts) { if (!SkPixmapIsValid(pixmap)) { return false; } if (SkColorTypeIsAlphaOnly(pixmap.colorType())) { // Maintain the existing behavior of not supporting encoding alpha-only images. // TODO: Support encoding alpha only to an image with alpha but no color? return false; } if (nullptr == pixmap.addr()) { return false; } pic->width = pixmap.width(); pic->height = pixmap.height(); // 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 (SkWebpEncoder::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; } { const SkColorType ct = pixmap.colorType(); const bool premul = pixmap.alphaType() == kPremul_SkAlphaType; SkBitmap tmpBm; WebPPictureImportProc importProc = nullptr; const SkPixmap* src = &pixmap; if (ct == kRGB_888x_SkColorType) { importProc = WebPPictureImportRGBX; } else if (!premul && ct == kRGBA_8888_SkColorType) { importProc = WebPPictureImportRGBA; } #ifdef WebPPictureImportBGRA else if (!premul && ct == kBGRA_8888_SkColorType) { importProc = WebPPictureImportBGRA; } #endif else { importProc = WebPPictureImportRGBA; auto info = pixmap.info() .makeColorType(kRGBA_8888_SkColorType) .makeAlphaType(kUnpremul_SkAlphaType); if (!tmpBm.tryAllocPixels(info) || !pixmap.readPixels(tmpBm.info(), tmpBm.getPixels(), tmpBm.rowBytes())) { return false; } src = &tmpBm.pixmap(); } if (!importProc(pic, reinterpret_cast(src->addr()), src->rowBytes())) { return false; } } return true; } namespace SkWebpEncoder { bool Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) { if (!stream) { return false; } WebPConfig webp_config; if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) { return false; } WebPPicture pic; if (!WebPPictureInit(&pic)) { return false; } SkAutoTCallVProc autoPic(&pic); if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) { return false; } // 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 icc = icc_from_color_space(pixmap.info(), opts.fICCProfile, opts.fICCProfileDescription); SkDynamicMemoryWStream tmp; pic.custom_ptr = icc ? (void*)&tmp : (void*)stream; pic.writer = stream_writer; if (!WebPEncode(&webp_config, &pic)) { return false; } if (icc) { sk_sp encodedData = tmp.detachAsData(); WebPData encoded = {encodedData->bytes(), encodedData->size()}; WebPData iccChunk = {icc->bytes(), icc->size()}; SkAutoTCallVProc 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; SkAutoTCallVProc autoWebPData(&assembled); if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) { return false; } if (!stream->write(assembled.bytes, assembled.size)) { return false; } } return true; } bool EncodeAnimated(SkWStream* stream, SkSpan frames, const Options& opts) { if (!stream || frames.empty()) { return false; } const int canvasWidth = frames.front().pixmap.width(); const int canvasHeight = frames.front().pixmap.height(); int timestamp = 0; std::unique_ptr enc( WebPAnimEncoderNew(canvasWidth, canvasHeight, nullptr), WebPAnimEncoderDelete); if (!enc) { return false; } for (const auto& frame : frames) { const auto& pixmap = frame.pixmap; if (pixmap.width() != canvasWidth || pixmap.height() != canvasHeight) { return false; } WebPConfig webp_config; if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) { return false; } WebPPicture pic; if (!WebPPictureInit(&pic)) { return false; } SkAutoTCallVProc autoPic(&pic); if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) { return false; } if (!WebPEncode(&webp_config, &pic)) { return false; } if (!WebPAnimEncoderAdd(enc.get(), &pic, timestamp, &webp_config)) { return false; } timestamp += frame.duration; } // Add a last fake frame to signal the last duration. if (!WebPAnimEncoderAdd(enc.get(), nullptr, timestamp, nullptr)) { return false; } WebPData assembled; SkAutoTCallVProc autoWebPData(&assembled); if (!WebPAnimEncoderAssemble(enc.get(), &assembled)) { return false; } enc.reset(); return stream->write(assembled.bytes, assembled.size); } sk_sp Encode(GrDirectContext* ctx, const SkImage* img, const Options& options) { if (!img) { return nullptr; } SkBitmap bm; if (!as_IB(img)->getROPixels(ctx, &bm)) { return nullptr; } SkDynamicMemoryWStream stream; if (Encode(&stream, bm.pixmap(), options)) { return stream.detachAsData(); } return nullptr; } } // namespace SkWebpEncoder