blob: 1c5986c59f86d8bc24dc9d98f16520bc22316535 [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 <android/bitmap.h>
#include <android/data_space.h>
#include <android/imagedecoder.h>
namespace {
class ImageGeneratorNDK : public SkImageGenerator {
public:
ImageGeneratorNDK(const SkImageInfo&, sk_sp<SkData>, AImageDecoder*);
~ImageGeneratorNDK() override;
protected:
sk_sp<SkData> onRefEncodedData() override;
bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
const Options& opts) override;
private:
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;
typedef SkImageGenerator INHERITED;
};
} // anonymous namespace
static bool ok(int result) {
return result == ANDROID_IMAGE_DECODER_SUCCESS;
}
namespace {
static const struct {
SkColorType colorType;
AndroidBitmapFormat format;
} gColorTypeTable[] = {
{ kRGBA_8888_SkColorType, ANDROID_BITMAP_FORMAT_RGBA_8888 },
{ kRGBA_F16_SkColorType, ANDROID_BITMAP_FORMAT_RGBA_F16 },
{ kRGB_565_SkColorType, ANDROID_BITMAP_FORMAT_RGB_565 },
// Android allows using its alpha 8 format to get 8 bit gray pixels.
{ kGray_8_SkColorType, ANDROID_BITMAP_FORMAT_A_8 },
};
} // anonymous namespace
static bool set_android_bitmap_format(AImageDecoder* decoder, SkColorType colorType) {
for (const auto& entry : gColorTypeTable) {
if (entry.colorType == colorType) {
return ok(AImageDecoder_setAndroidBitmapFormat(decoder, entry.format));
}
}
return false;
}
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;
}
const auto format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(headerInfo);
for (const auto& entry : gColorTypeTable) {
if (format == entry.format) {
return entry.colorType;
}
}
SkUNREACHABLE;
}
static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
static constexpr skcms_Matrix3x3 kDCIP3 = {{
{0.486143, 0.323835, 0.154234},
{0.226676, 0.710327, 0.0629966},
{0.000800549, 0.0432385, 0.78275},
}};
namespace {
static const struct {
ADataSpace dataSpace;
skcms_TransferFunction transferFunction;
skcms_Matrix3x3 gamut;
} gColorSpaceTable[] = {
// Note: ADATASPACE_SCRGB would look the same as ADATASPACE_SRGB. Leaving it out of the table is
// fine, since users of the table will will still use SRGB.
{ ADATASPACE_SRGB, SkNamedTransferFn::kSRGB, SkNamedGamut::kSRGB },
{ ADATASPACE_SCRGB_LINEAR, SkNamedTransferFn::kLinear, SkNamedGamut::kSRGB },
{ ADATASPACE_SRGB_LINEAR, SkNamedTransferFn::kLinear, SkNamedGamut::kSRGB },
{ ADATASPACE_ADOBE_RGB, SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB },
{ ADATASPACE_DISPLAY_P3, SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3 },
{ ADATASPACE_BT2020, SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020 },
{ ADATASPACE_BT709, SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB },
{ ADATASPACE_DCI_P3, k2Dot6, kDCIP3 },
};
} // anonymous namespace
static sk_sp<SkColorSpace> get_default_colorSpace(const AImageDecoderHeaderInfo* headerInfo) {
auto dataSpace = AImageDecoderHeaderInfo_getDataSpace(headerInfo);
for (const auto& entry : gColorSpaceTable) {
if (entry.dataSpace == dataSpace) {
return SkColorSpace::MakeRGB(entry.transferFunction, entry.gamut);
}
}
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)
: INHERITED(info)
, fData(std::move(data))
, fDecoder(decoder)
, fPreviouslySetADataSpace(false)
{
SkASSERT(fDecoder);
}
ImageGeneratorNDK::~ImageGeneratorNDK() {
AImageDecoder_delete(fDecoder);
}
static bool nearly_equal(float a, float b) {
return fabs(a - b) < .002f;
}
static bool nearly_equal(const skcms_TransferFunction& x, const skcms_TransferFunction& y) {
return nearly_equal(x.g, y.g)
&& nearly_equal(x.a, y.a)
&& nearly_equal(x.b, y.b)
&& nearly_equal(x.c, y.c)
&& nearly_equal(x.d, y.d)
&& nearly_equal(x.e, y.e)
&& nearly_equal(x.f, y.f);
}
static bool nearly_equal(const skcms_Matrix3x3& a, const skcms_Matrix3x3& b) {
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) {
if (!nearly_equal(a.vals[i][j], b.vals[i][j])) return false;
}
return true;
}
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;
break;
}
}
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()) {
skcms_TransferFunction fn;
skcms_Matrix3x3 gamut;
if (!cs->isNumericalTransferFn(&fn) || !cs->toXYZD50(&gamut)) {
return false;
}
ADataSpace dataSpace = ADATASPACE_UNKNOWN;
for (const auto& entry : gColorSpaceTable) {
if (nearly_equal(gamut, entry.gamut) && nearly_equal(fn, entry.transferFunction)) {
dataSpace = entry.dataSpace;
}
}
if (!ok(AImageDecoder_setDataSpace(fDecoder, dataSpace))) {
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;
}
AImageDecoder_delete(fDecoder);
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;
}
break;
case kUnpremul_SkAlphaType:
if (!ok(AImageDecoder_setUnpremultipliedRequired(fDecoder, true))) {
return false;
}
break;
case kPremul_SkAlphaType:
break;
}
if (!set_target_size(fDecoder, getInfo().dimensions(), info.dimensions())) {
return false;
}
auto byteSize = info.computeByteSize(rowBytes);
switch (AImageDecoder_decodeImage(fDecoder, pixels, rowBytes, byteSize)) {
case ANDROID_IMAGE_DECODER_INCOMPLETE:
// The image was partially decoded, but the input was truncated. The client may be
// happy with the partial image.
case ANDROID_IMAGE_DECODER_ERROR:
// Similarly, the image was partially decoded, but the input had an error. The client
// may be happy with the partial image.
case ANDROID_IMAGE_DECODER_SUCCESS:
return true;
default:
return false;
}
}
sk_sp<SkData> ImageGeneratorNDK::onRefEncodedData() {
return fData;
}