skia2/src/codec/SkWebpCodec.cpp
scroggo e7fc14b55b Move all knowledge of X sampling into SkScaledCodec
Prior to this CL, each SkCodec subclass that allows sampling does an
extra check in onStartScanlineDecode to determine whether the X dimension
is supported for sampling. Remove this check, and provide a way for
SkScaledCodec to directly access the SkSwizzler, and update it to do
sampling. This way, the SkCodec knows nothing of sampling, but we can
still save the extra step of sampling afterwards.

FIXME: SkBmpRLECodec still calls SkScaledCodec::DimensionsSupported. It
seems like it could directly support sampling, rather than dealing with
SkScaledCodec (partially).

Add a new base class for SkSwizzler. It allows updating the swizzler
after it was created, which is done by SkScaledCodec. Modify SkSwizzler's
constructor/factory function to stop taking any info about sampling,
assume the sample size is one, and move modifying that into a virtual
function overridden from the base class.

BUG=skia:4284

Review URL: https://codereview.chromium.org/1372973002
2015-10-02 13:14:46 -07:00

257 lines
9.1 KiB
C++

/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkCodecPriv.h"
#include "SkWebpCodec.h"
#include "SkTemplates.h"
// A WebP decoder on top of (subset of) libwebp
// For more information on WebP image format, and libwebp library, see:
// https://code.google.com/speed/webp/
// http://www.webmproject.org/code/#libwebp-webp-image-library
// https://chromium.googlesource.com/webm/libwebp
// 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/decode.h"
#include "webp/encode.h"
bool SkWebpCodec::IsWebp(SkStream* stream) {
// WEBP starts with the following:
// RIFFXXXXWEBPVP
// Where XXXX is unspecified.
const char LENGTH = 14;
char bytes[LENGTH];
if (stream->read(&bytes, LENGTH) != LENGTH) {
return false;
}
return !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
}
static const size_t WEBP_VP8_HEADER_SIZE = 30;
// Parse headers of RIFF container, and check for valid Webp (VP8) content.
// NOTE: This calls peek instead of read, since onGetPixels will need these
// bytes again.
static bool webp_parse_header(SkStream* stream, SkImageInfo* info) {
unsigned char buffer[WEBP_VP8_HEADER_SIZE];
if (!stream->peek(buffer, WEBP_VP8_HEADER_SIZE)) {
return false;
}
WebPBitstreamFeatures features;
VP8StatusCode status = WebPGetFeatures(buffer, WEBP_VP8_HEADER_SIZE, &features);
if (VP8_STATUS_OK != status) {
return false; // Invalid WebP file.
}
// sanity check for image size that's about to be decoded.
{
const int64_t size = sk_64_mul(features.width, features.height);
if (!sk_64_isS32(size)) {
return false;
}
// now check that if we are 4-bytes per pixel, we also don't overflow
if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) {
return false;
}
}
if (info) {
// FIXME: Is N32 the right type?
// Is unpremul the right type? Clients of SkCodec may assume it's the
// best type, when Skia currently cannot draw unpremul (and raster is faster
// with premul).
*info = SkImageInfo::Make(features.width, features.height, kN32_SkColorType,
SkToBool(features.has_alpha) ? kUnpremul_SkAlphaType
: kOpaque_SkAlphaType);
}
return true;
}
SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
SkAutoTDelete<SkStream> streamDeleter(stream);
SkImageInfo info;
if (webp_parse_header(stream, &info)) {
return new SkWebpCodec(info, streamDeleter.detach());
}
return nullptr;
}
// This version is slightly different from SkCodecPriv's version of conversion_possible. It
// supports both byte orders for 8888.
static bool webp_conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) {
if (dst.profileType() != src.profileType()) {
return false;
}
if (!valid_alpha(dst.alphaType(), src.alphaType())) {
return false;
}
switch (dst.colorType()) {
// Both byte orders are supported.
case kBGRA_8888_SkColorType:
case kRGBA_8888_SkColorType:
return true;
case kRGB_565_SkColorType:
return src.alphaType() == kOpaque_SkAlphaType;
default:
return false;
}
}
SkISize SkWebpCodec::onGetScaledDimensions(float desiredScale) const {
SkISize dim = this->getInfo().dimensions();
// SkCodec treats zero dimensional images as errors, so the minimum size
// that we will recommend is 1x1.
dim.fWidth = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fWidth));
dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight));
return dim;
}
bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) {
const SkImageInfo& info = this->getInfo();
return dim.width() >= 1 && dim.width() <= info.width()
&& dim.height() >= 1 && dim.height() <= info.height();
}
static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) {
switch (ct) {
case kBGRA_8888_SkColorType:
return premultiply ? MODE_bgrA : MODE_BGRA;
case kRGBA_8888_SkColorType:
return premultiply ? MODE_rgbA : MODE_RGBA;
case kRGB_565_SkColorType:
return MODE_RGB_565;
default:
return MODE_LAST;
}
}
// The WebP decoding API allows us to incrementally pass chunks of bytes as we receive them to the
// decoder with WebPIAppend. In order to do so, we need to read chunks from the SkStream. This size
// is arbitrary.
static const size_t BUFFER_SIZE = 4096;
bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
if (!desiredSubset) {
return false;
}
SkIRect bounds = SkIRect::MakeSize(this->getInfo().dimensions());
if (!desiredSubset->intersect(bounds)) {
return false;
}
// As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we
// decode this exact subset.
// Leave right and bottom unmodified, so we suggest a slightly larger subset than requested.
desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1;
desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1;
return true;
}
SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
const Options& options, SkPMColor*, int*) {
if (!webp_conversion_possible(dstInfo, this->getInfo())) {
return kInvalidConversion;
}
WebPDecoderConfig config;
if (0 == WebPInitDecoderConfig(&config)) {
// ABI mismatch.
// FIXME: New enum for this?
return kInvalidInput;
}
// Free any memory associated with the buffer. Must be called last, so we declare it first.
SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output));
SkIRect bounds = SkIRect::MakeSize(this->getInfo().dimensions());
if (options.fSubset) {
// Caller is requesting a subset.
if (!bounds.contains(*options.fSubset)) {
// The subset is out of bounds.
return kInvalidParameters;
}
bounds = *options.fSubset;
// This is tricky. libwebp snaps the top and left to even values. We could let libwebp
// do the snap, and return a subset which is a different one than requested. The problem
// with that approach is that the caller may try to stitch subsets together, and if we
// returned different subsets than requested, there would be artifacts at the boundaries.
// Instead, we report that we cannot support odd values for top and left..
if (!SkIsAlign2(bounds.fLeft) || !SkIsAlign2(bounds.fTop)) {
return kInvalidParameters;
}
#ifdef SK_DEBUG
{
// Make a copy, since getValidSubset can change its input.
SkIRect subset(bounds);
// That said, getValidSubset should *not* change its input, in this case; otherwise
// getValidSubset does not match the actual subsets we can do.
SkASSERT(this->getValidSubset(&subset) && subset == bounds);
}
#endif
config.options.use_cropping = 1;
config.options.crop_left = bounds.fLeft;
config.options.crop_top = bounds.fTop;
config.options.crop_width = bounds.width();
config.options.crop_height = bounds.height();
}
SkISize dstDimensions = dstInfo.dimensions();
if (bounds.size() != dstDimensions) {
// Caller is requesting scaling.
config.options.use_scaling = 1;
config.options.scaled_width = dstDimensions.width();
config.options.scaled_height = dstDimensions.height();
}
config.output.colorspace = webp_decode_mode(dstInfo.colorType(),
dstInfo.alphaType() == kPremul_SkAlphaType);
config.output.u.RGBA.rgba = (uint8_t*) dst;
config.output.u.RGBA.stride = (int) rowBytes;
config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes);
config.output.is_external_memory = 1;
SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config));
if (!idec) {
return kInvalidInput;
}
SkAutoMalloc storage(BUFFER_SIZE);
uint8_t* buffer = static_cast<uint8_t*>(storage.get());
while (true) {
const size_t bytesRead = stream()->read(buffer, BUFFER_SIZE);
if (0 == bytesRead) {
// FIXME: Maybe this is an incomplete image? How to decide? Based
// on the number of rows decoded? We can know the number of rows
// decoded using WebPIDecGetRGB.
return kInvalidInput;
}
switch (WebPIAppend(idec, buffer, bytesRead)) {
case VP8_STATUS_OK:
return kSuccess;
case VP8_STATUS_SUSPENDED:
// Break out of the switch statement. Continue the loop.
break;
default:
return kInvalidInput;
}
}
}
SkWebpCodec::SkWebpCodec(const SkImageInfo& info, SkStream* stream)
: INHERITED(info, stream) {}