| /* | |
| * 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/SkTemplates.h" | |
| #include "src/core/SkImageInfoPriv.h" | |
| #include "src/encode/SkImageEncoderFns.h" | |
| #include "src/encode/SkImageEncoderPriv.h" | |
| #include "src/image/SkImage_Base.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| 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<const uint8_t*>(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<WebPPicture, WebPPictureFree> 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<SkData> icc = icc_from_color_space(pixmap.info()); | |
| 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<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; | |
| SkAutoTCallVProc<WebPData, WebPDataClear> autoWebPData(&assembled); | |
| if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) { | |
| return false; | |
| } | |
| if (!stream->write(assembled.bytes, assembled.size)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| sk_sp<SkData> Encode(const SkPixmap& pixmap, const Options& opts) { | |
| SkDynamicMemoryWStream stream; | |
| return Encode(&stream, pixmap, opts) ? stream.detachAsData() : nullptr; | |
| } | |
| bool EncodeAnimated(SkWStream* stream, SkSpan<const SkEncoder::Frame> 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<WebPAnimEncoder, void (*)(WebPAnimEncoder*)> 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<WebPPicture, WebPPictureFree> 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<WebPData, WebPDataClear> autoWebPData(&assembled); | |
| if (!WebPAnimEncoderAssemble(enc.get(), &assembled)) { | |
| return false; | |
| } | |
| enc.reset(); | |
| return stream->write(assembled.bytes, assembled.size); | |
| } | |
| sk_sp<SkData> Encode(GrDirectContext* ctx, const SkImage* img, const Options& options) { | |
| if (!img) { | |
| return nullptr; | |
| } | |
| SkBitmap bm; | |
| if (!as_IB(img)->getROPixels(ctx, &bm)) { | |
| return nullptr; | |
| } | |
| return Encode(bm.pixmap(), options); | |
| } | |
| } // namespace SkWebpEncoder |