blob: 475eb28df621635c582dea5a4923c37e9b577747 [file] [log] [blame]
/*
* 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/core/SkTypes.h"
#ifdef SK_ENCODE_WEBP
#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/encode/SkWebpEncoder.h"
#include "include/private/SkImageInfoPriv.h"
#include "include/private/SkTemplates.h"
#include "src/images/SkImageEncoderFns.h"
#include "src/images/SkImageEncoderPriv.h"
#include <cstddef>
#include <cstdint>
#include <memory>
// 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"
#include "webp/mux.h"
#include "webp/mux_types.h"
}
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;
}
bool SkWebpEncoder::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;
WebPPictureInit(&pic);
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(), 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<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;
}
bool SkWebpEncoder::EncodeAnimated(SkWStream* stream,
SkSpan<const SkEncoder::Frame> frames,
const Options& opts) {
if (!stream || !frames.size()) {
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;
WebPPictureInit(&pic);
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);
}
#endif