blob: 7d050b97040cc8117a51e8cbadfc6f0c912a6c92 [file] [log] [blame]
* Copyright 2020 Google LLC
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
#include "include/core/SkImageGenerator.h"
#include "include/core/SkImageInfo.h"
#include "include/ports/SkImageGeneratorNDK.h"
#include "src/ports/SkNDKConversions.h"
#include <android/bitmap.h>
#include <android/data_space.h>
#include <android/imagedecoder.h>
namespace {
class ImageGeneratorNDK : public SkImageGenerator {
ImageGeneratorNDK(const SkImageInfo&, sk_sp<SkData>, AImageDecoder*);
~ImageGeneratorNDK() override;
sk_sp<SkData> onRefEncodedData() override;
bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
const Options& opts) override;
sk_sp<SkData> fData;
AImageDecoder* fDecoder;
// Setting the ADataSpace is sticky - it is set for all future decodes
// until it is set again. But as of R there is no way to reset it to
// ADATASPACE_UNKNOWN to skip color correction. If the client requests
// skipping correction after having set it to something else, we need
// to recreate the AImageDecoder.
bool fPreviouslySetADataSpace;
using INHERITED = SkImageGenerator;
} // anonymous namespace
static bool ok(int result) {
static bool set_android_bitmap_format(AImageDecoder* decoder, SkColorType colorType) {
auto format = SkNDKConversions::toAndroidBitmapFormat(colorType);
return ok(AImageDecoder_setAndroidBitmapFormat(decoder, format));
static SkColorType colorType(AImageDecoder* decoder, const AImageDecoderHeaderInfo* headerInfo) {
// AImageDecoder never defaults to gray, but allows setting it if the image is 8 bit gray.
if (set_android_bitmap_format(decoder, kGray_8_SkColorType)) {
return kGray_8_SkColorType;
auto format = static_cast<AndroidBitmapFormat>(
return SkNDKConversions::toColorType(format);
static sk_sp<SkColorSpace> get_default_colorSpace(const AImageDecoderHeaderInfo* headerInfo) {
auto dataSpace = static_cast<ADataSpace>(AImageDecoderHeaderInfo_getDataSpace(headerInfo));
if (auto cs = SkNDKConversions::toColorSpace(dataSpace)) {
return cs;
return SkColorSpace::MakeSRGB();
std::unique_ptr<SkImageGenerator> SkImageGeneratorNDK::MakeFromEncodedNDK(sk_sp<SkData> data) {
if (!data) return nullptr;
AImageDecoder* rawDecoder;
if (!ok(AImageDecoder_createFromBuffer(data->data(), data->size(), &rawDecoder))) {
return nullptr;
const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(rawDecoder);
int32_t width = AImageDecoderHeaderInfo_getWidth(headerInfo);
int32_t height = AImageDecoderHeaderInfo_getHeight(headerInfo);
SkColorType ct = colorType(rawDecoder, headerInfo);
// Although the encoded data stores unpremultiplied pixels, AImageDecoder defaults to premul
// (if the image may have alpha).
SkAlphaType at = AImageDecoderHeaderInfo_getAlphaFlags(headerInfo)
== ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
auto imageInfo = SkImageInfo::Make(width, height, ct, at, get_default_colorSpace(headerInfo));
return std::unique_ptr<SkImageGenerator>(
new ImageGeneratorNDK(imageInfo, std::move(data), rawDecoder));
ImageGeneratorNDK::ImageGeneratorNDK(const SkImageInfo& info, sk_sp<SkData> data,
AImageDecoder* decoder)
, fData(std::move(data))
, fDecoder(decoder)
, fPreviouslySetADataSpace(false)
ImageGeneratorNDK::~ImageGeneratorNDK() {
static bool set_target_size(AImageDecoder* decoder, const SkISize& size, const SkISize targetSize) {
if (size != targetSize) {
// AImageDecoder will scale to arbitrary sizes. Only support a size if it's supported by the
// underlying library.
const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(decoder);
const char* mimeType = AImageDecoderHeaderInfo_getMimeType(headerInfo);
if (0 == strcmp(mimeType, "image/jpeg")) {
bool supported = false;
for (int sampleSize : { 2, 4, 8 }) {
int32_t width;
int32_t height;
if (ok(AImageDecoder_computeSampledSize(decoder, sampleSize, &width, &height))
&& targetSize == SkISize::Make(width, height)) {
supported = true;
if (!supported) return false;
} else if (0 == strcmp(mimeType, "image/webp")) {
// libwebp supports arbitrary downscaling.
if (targetSize.width() > size.width() || targetSize.height() > size.height()) {
return false;
} else {
return false;
return ok(AImageDecoder_setTargetSize(decoder, targetSize.width(), targetSize.height()));
bool ImageGeneratorNDK::onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
const Options& opts) {
if (auto* cs = info.colorSpace()) {
if (!ok(AImageDecoder_setDataSpace(fDecoder, SkNDKConversions::toDataSpace(cs)))) {
return false;
fPreviouslySetADataSpace = true;
} else {
// If the requested SkColorSpace is null, the client wants the "raw" colors, without color
// space transformations applied. (This is primarily useful for a client that wants to do
// their own color transformations.) This is AImageDecoder's default, but if a previous call
// set an ADataSpace, AImageDecoder is no longer using its default, so we need to set it
// back.
if (fPreviouslySetADataSpace) {
// AImageDecoderHeaderInfo_getDataSpace always returns the same value for the same
// image, regardless of prior calls to AImageDecoder_setDataSpace. Check if it's
// ADATASPACE_UNKNOWN, which needs to be handled specially.
const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(fDecoder);
const auto defaultDataSpace = AImageDecoderHeaderInfo_getDataSpace(headerInfo);
if (defaultDataSpace == ADATASPACE_UNKNOWN) {
// As of R, there's no way to reset AImageDecoder to ADATASPACE_UNKNOWN, so
// create a new one.
AImageDecoder* decoder;
if (!ok(AImageDecoder_createFromBuffer(fData->data(), fData->size(), &decoder))) {
return false;
fDecoder = decoder;
} else {
if (!ok(AImageDecoder_setDataSpace(fDecoder, defaultDataSpace))) {
return false;
// Whether by recreating AImageDecoder or calling AImageDecoder_setDataSpace, the
// AImageDecoder is back to its default, so if the next call has a null SkColorSpace, it
// does not need to reset it again.
fPreviouslySetADataSpace = false;
if (!set_android_bitmap_format(fDecoder, info.colorType())) {
return false;
switch (info.alphaType()) {
case kUnknown_SkAlphaType:
return false;
case kOpaque_SkAlphaType:
if (this->getInfo().alphaType() != kOpaque_SkAlphaType) {
return false;
case kUnpremul_SkAlphaType:
if (!ok(AImageDecoder_setUnpremultipliedRequired(fDecoder, true))) {
return false;
case kPremul_SkAlphaType:
if (!set_target_size(fDecoder, getInfo().dimensions(), info.dimensions())) {
return false;
auto byteSize = info.computeByteSize(rowBytes);
switch (AImageDecoder_decodeImage(fDecoder, pixels, rowBytes, byteSize)) {
// The image was partially decoded, but the input was truncated. The client may be
// happy with the partial image.
// Similarly, the image was partially decoded, but the input had an error. The client
// may be happy with the partial image.
return true;
return false;
sk_sp<SkData> ImageGeneratorNDK::onRefEncodedData() {
return fData;