blob: 8cfacc3fba10a876eea00cb89d79a2cb8126a00a [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkData.h"
#include "SkDecodingImageGenerator.h"
#include "SkImageDecoder.h"
#include "SkImageInfo.h"
#include "SkImageGenerator.h"
#include "SkImagePriv.h"
#include "SkStream.h"
#include "SkUtils.h"
namespace {
bool equal_modulo_alpha(const SkImageInfo& a, const SkImageInfo& b) {
return a.width() == b.width() && a.height() == b.height() &&
a.colorType() == b.colorType();
}
class DecodingImageGenerator : public SkImageGenerator {
public:
virtual ~DecodingImageGenerator();
SkData* fData;
SkAutoTDelete<SkStreamRewindable> fStream;
const SkImageInfo fInfo;
const int fSampleSize;
const bool fDitherImage;
DecodingImageGenerator(SkData* data,
SkStreamRewindable* stream,
const SkImageInfo& info,
int sampleSize,
bool ditherImage);
protected:
SkData* onRefEncodedData() override;
bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
SkPMColor ctable[], int* ctableCount) override;
bool onGetYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3],
SkYUVColorSpace* colorSpace) override;
private:
typedef SkImageGenerator INHERITED;
};
/**
* Special allocator used by getPixels(). Uses preallocated memory
* provided if possible, else fall-back on the default allocator
*/
class TargetAllocator : public SkBitmap::Allocator {
public:
TargetAllocator(const SkImageInfo& info,
void* target,
size_t rowBytes)
: fInfo(info)
, fTarget(target)
, fRowBytes(rowBytes)
{}
bool isReady() { return (fTarget != nullptr); }
virtual bool allocPixelRef(SkBitmap* bm, SkColorTable* ct) {
if (nullptr == fTarget || !equal_modulo_alpha(fInfo, bm->info())) {
// Call default allocator.
return bm->tryAllocPixels(nullptr, ct);
}
// TODO(halcanary): verify that all callers of this function
// will respect new RowBytes. Will be moot once rowbytes belongs
// to PixelRef.
bm->installPixels(fInfo, fTarget, fRowBytes, ct, nullptr, nullptr);
fTarget = nullptr; // never alloc same pixels twice!
return true;
}
private:
const SkImageInfo fInfo;
void* fTarget; // Block of memory to be supplied as pixel memory
// in allocPixelRef. Must be large enough to hold
// a bitmap described by fInfo and fRowBytes
const size_t fRowBytes; // rowbytes for the destination bitmap
typedef SkBitmap::Allocator INHERITED;
};
// TODO(halcanary): Give this macro a better name and move it into SkTypes.h
#ifdef SK_DEBUG
#define SkCheckResult(expr, value) SkASSERT((value) == (expr))
#else
#define SkCheckResult(expr, value) (void)(expr)
#endif
#ifdef SK_DEBUG
inline bool check_alpha(SkAlphaType reported, SkAlphaType actual) {
return ((reported == actual)
|| ((reported == kPremul_SkAlphaType)
&& (actual == kOpaque_SkAlphaType)));
}
#endif // SK_DEBUG
////////////////////////////////////////////////////////////////////////////////
DecodingImageGenerator::DecodingImageGenerator(
SkData* data,
SkStreamRewindable* stream,
const SkImageInfo& info,
int sampleSize,
bool ditherImage)
: INHERITED(info)
, fData(data)
, fStream(stream)
, fInfo(info)
, fSampleSize(sampleSize)
, fDitherImage(ditherImage)
{
SkASSERT(stream != nullptr);
SkSafeRef(fData); // may be nullptr.
}
DecodingImageGenerator::~DecodingImageGenerator() {
SkSafeUnref(fData);
}
SkData* DecodingImageGenerator::onRefEncodedData() {
// This functionality is used in `gm --serialize`
// Does not encode options.
if (nullptr == fData) {
// TODO(halcanary): SkStreamRewindable needs a refData() function
// which returns a cheap copy of the underlying data.
if (!fStream->rewind()) {
return nullptr;
}
size_t length = fStream->getLength();
if (length) {
fData = SkData::NewFromStream(fStream, length);
}
}
return SkSafeRef(fData);
}
bool DecodingImageGenerator::onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
SkPMColor ctableEntries[], int* ctableCount) {
if (fInfo != info) {
// The caller has specified a different info. This is an
// error for this kind of SkImageGenerator. Use the Options
// to change the settings.
return false;
}
SkAssertResult(fStream->rewind());
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(fStream));
if (nullptr == decoder.get()) {
return false;
}
decoder->setDitherImage(fDitherImage);
decoder->setSampleSize(fSampleSize);
decoder->setRequireUnpremultipliedColors(info.alphaType() == kUnpremul_SkAlphaType);
SkBitmap bitmap;
TargetAllocator allocator(fInfo, pixels, rowBytes);
decoder->setAllocator(&allocator);
const SkImageDecoder::Result decodeResult = decoder->decode(fStream, &bitmap, info.colorType(),
SkImageDecoder::kDecodePixels_Mode);
decoder->setAllocator(nullptr);
if (SkImageDecoder::kFailure == decodeResult) {
return false;
}
if (allocator.isReady()) { // Did not use pixels!
SkBitmap bm;
SkASSERT(bitmap.canCopyTo(info.colorType()));
bool copySuccess = bitmap.copyTo(&bm, info.colorType(), &allocator);
if (!copySuccess || allocator.isReady()) {
SkDEBUGFAIL("bitmap.copyTo(requestedConfig) failed.");
// Earlier we checked canCopyto(); we expect consistency.
return false;
}
SkASSERT(check_alpha(info.alphaType(), bm.alphaType()));
} else {
SkASSERT(check_alpha(info.alphaType(), bitmap.alphaType()));
}
if (kIndex_8_SkColorType == info.colorType()) {
if (kIndex_8_SkColorType != bitmap.colorType()) {
// they asked for Index8, but we didn't receive that from decoder
return false;
}
SkColorTable* ctable = bitmap.getColorTable();
if (nullptr == ctable) {
return false;
}
const int count = ctable->count();
memcpy(ctableEntries, ctable->readColors(), count * sizeof(SkPMColor));
*ctableCount = count;
}
return true;
}
bool DecodingImageGenerator::onGetYUV8Planes(SkISize sizes[3], void* planes[3],
size_t rowBytes[3], SkYUVColorSpace* colorSpace) {
if (!fStream->rewind()) {
return false;
}
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(fStream));
if (nullptr == decoder.get()) {
return false;
}
return decoder->decodeYUV8Planes(fStream, sizes, planes, rowBytes, colorSpace);
}
// A contructor-type function that returns nullptr on failure. This
// prevents the returned SkImageGenerator from ever being in a bad
// state. Called by both Create() functions
SkImageGenerator* CreateDecodingImageGenerator(
SkData* data,
SkStreamRewindable* stream,
const SkDecodingImageGenerator::Options& opts) {
SkASSERT(stream);
SkAutoTDelete<SkStreamRewindable> autoStream(stream); // always delete this
SkAssertResult(autoStream->rewind());
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(autoStream));
if (nullptr == decoder.get()) {
return nullptr;
}
SkBitmap bitmap;
decoder->setSampleSize(opts.fSampleSize);
decoder->setRequireUnpremultipliedColors(opts.fRequireUnpremul);
if (!decoder->decode(stream, &bitmap, SkImageDecoder::kDecodeBounds_Mode)) {
return nullptr;
}
if (kUnknown_SkColorType == bitmap.colorType()) {
return nullptr;
}
SkImageInfo info = bitmap.info();
if (opts.fUseRequestedColorType && (opts.fRequestedColorType != info.colorType())) {
if (!bitmap.canCopyTo(opts.fRequestedColorType)) {
SkASSERT(bitmap.colorType() != opts.fRequestedColorType);
return nullptr; // Can not translate to needed config.
}
info = info.makeColorType(opts.fRequestedColorType);
}
if (opts.fRequireUnpremul && info.alphaType() != kOpaque_SkAlphaType) {
info = info.makeAlphaType(kUnpremul_SkAlphaType);
}
SkAlphaType newAlphaType = info.alphaType();
if (!SkColorTypeValidateAlphaType(info.colorType(), info.alphaType(), &newAlphaType)) {
return nullptr;
}
return new DecodingImageGenerator(data, autoStream.detach(), info.makeAlphaType(newAlphaType),
opts.fSampleSize, opts.fDitherImage);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
SkImageGenerator* SkDecodingImageGenerator::Create(
SkData* data,
const SkDecodingImageGenerator::Options& opts) {
SkASSERT(data != nullptr);
if (nullptr == data) {
return nullptr;
}
SkStreamRewindable* stream = new SkMemoryStream(data);
SkASSERT(stream != nullptr);
return CreateDecodingImageGenerator(data, stream, opts);
}
SkImageGenerator* SkDecodingImageGenerator::Create(
SkStreamRewindable* stream,
const SkDecodingImageGenerator::Options& opts) {
SkASSERT(stream != nullptr);
if (stream == nullptr) {
return nullptr;
}
return CreateDecodingImageGenerator(nullptr, stream, opts);
}