blob: 96bdc4681a4eefaff5917bf6235ccbf8c3fa8cb4 [file] [log] [blame]
/*
* Copyright 2022 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/codec/SkJpegRCodec.h"
#ifdef SK_CODEC_DECODES_JPEGR
#include <array>
#include <csetjmp>
#include <cstdlib>
#include <cstring>
#include <utility>
#include "include/android/SkAndroidFrameworkUtils.h"
#include "include/codec/SkCodec.h"
#include "include/core/SkAlphaType.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/SkStream.h"
#include "include/core/SkTypes.h"
#include "include/core/SkYUVAInfo.h"
#include "include/private/base/SkMalloc.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/base/SkTo.h"
#include "modules/skcms/skcms.h"
#include "src/codec/SkCodecPriv.h"
#include "src/codec/SkParseEncodedOrigin.h"
#include "src/codec/SkSwizzler.h"
#include "src/core/SkStreamPriv.h"
static SkEncodedOrigin get_orientation(const std::vector<uint8_t>& exifData) {
SkEncodedOrigin orientation = kDefault_SkEncodedOrigin;
if (exifData.size() > 6) {
constexpr size_t kOffset = 6;
const size_t exifSize = exifData.size();
const uint8_t* data = exifData.data();
SkParseEncodedOrigin(data + kOffset, exifSize - kOffset, &orientation);
}
return orientation;
}
bool SkJpegRCodec::IsJpegR(const void* buffer, size_t bytesRead) {
// We expect JPEGR file will have EXIF packet frist and custom
// 'JR' EXIF tag first in this EXIF packet. 32 bytes is not enough to
// parse the whole EXIF (or even the first TAG), but enough to detect
// this TAG.
constexpr uint8_t jpegSig[] = {0xFF, 0xD8, 0xFF, 0xE1};
constexpr uint8_t exifSig[]{'E', 'x', 'i', 'f', '\0', '\0'};
if (bytesRead < 32 || memcmp(buffer, jpegSig, sizeof(jpegSig))) {
return false;
}
const uint8_t* dataPtr = static_cast<const uint8_t*>(buffer) + 4;
const uint16_t exifSize = dataPtr[1] | (dataPtr[0] << 8);
if (exifSize < 26) {
return false;
}
dataPtr += 2;
if (memcmp(dataPtr, exifSig, sizeof(exifSig))) {
return false;
}
dataPtr += sizeof(exifSig);
bool isBigEndian = false;
if (dataPtr[0] == 0x49 && dataPtr[1] == 0x49) {
isBigEndian = false;
} else if (dataPtr[0] == 0x4d && dataPtr[1] == 0x4d) {
isBigEndian = true;
} else {
// Wrong EXIF format
return false;
}
dataPtr += 8; // points to the TAG number
uint16_t tagNum;
if (isBigEndian) {
tagNum = (dataPtr[0] << 8) | dataPtr[1];
} else {
tagNum = (dataPtr[1] << 8) | dataPtr[0];
}
if (tagNum < 1) {
return false;
}
dataPtr += 2;
if (dataPtr[0] != 0x4a || dataPtr[1] != 0x52) {
// Wrong TAG. JPEGR shall start with 0x4a 0x52 TAG
return false;
}
return true;
}
SkCodec::Result SkJpegRCodec::ReadHeader(SkStream* stream,
SkCodec** codecOut,
RecoveryMap** recoveryMapOut) {
// Create a RecoveryMap object to own all of the decompress information
std::unique_ptr<RecoveryMap> recoveryMap = std::make_unique<RecoveryMap>();
jpegr_info_struct jpegRInfo;
if (codecOut) {
// Get the encoded color type
std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr;
sk_sp<SkData> data = nullptr;
if (stream->getMemoryBase()) {
// It is safe to make without copy because we'll hold onto the stream.
data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength());
} else {
data = SkCopyStreamToData(stream);
// We don't need to hold the stream anymore
stream = nullptr;
}
jpegr_compressed_struct compressedImage;
if (data->data() == nullptr || data->size() == 0) {
return kIncompleteInput;
}
compressedImage.data = (void*)data->data();
compressedImage.length = data->size();
std::vector<uint8_t> exifData;
std::vector<uint8_t> iccData;
jpegRInfo.exifData = &exifData;
jpegRInfo.iccData = &iccData;
if (recoveryMap->getJPEGRInfo(&compressedImage, &jpegRInfo) != 0) {
return kInvalidInput;
}
// JPEGR always report 10-bit color depth
const uint8_t colorDepth = 10;
const int bitsPerComponent = 8;
// iccSkData will outlive iccData as it is passed to profile, hence we need a copy
sk_sp<SkData> iccSkData = SkData::MakeWithCopy(iccData.data(), iccData.size());
profile = SkEncodedInfo::ICCProfile::Make(std::move(iccSkData));
// TODO: Figure out if we need to expose default profile for JPEGR and what it should be
SkEncodedInfo info = SkEncodedInfo::Make(jpegRInfo.width,
jpegRInfo.height,
SkEncodedInfo::kRGBA_Color,
SkEncodedInfo::kOpaque_Alpha,
bitsPerComponent,
std::move(profile),
colorDepth);
SkEncodedOrigin orientation = get_orientation(exifData);
SkJpegRCodec* codec = new SkJpegRCodec(std::move(info),
std::unique_ptr<SkStream>(stream),
recoveryMap.release(),
orientation,
std::move(data));
*codecOut = codec;
} else {
SkASSERT(nullptr != recoveryMap);
*recoveryMapOut = recoveryMap.release();
}
return kSuccess;
}
std::unique_ptr<SkCodec> SkJpegRCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* result) {
SkCodec* codec = nullptr;
*result = ReadHeader(stream.get(), &codec, nullptr);
if (kSuccess == *result) {
// Codec has taken ownership of the stream, we do not need to delete it
SkASSERT(codec);
stream.release();
return std::unique_ptr<SkCodec>(codec);
}
return nullptr;
}
SkJpegRCodec::SkJpegRCodec(SkEncodedInfo&& info,
std::unique_ptr<SkStream> stream,
RecoveryMap* recoveryMap,
SkEncodedOrigin origin,
sk_sp<SkData> data)
: SkCodec(std::move(info), skcms_PixelFormat_RGBA_1010102, std::move(stream), origin)
, fRecoveryMap(recoveryMap)
, fData(std::move(data)) {}
SkJpegRCodec::~SkJpegRCodec() = default;
bool SkJpegRCodec::conversionSupported(const SkImageInfo& dstInfo,
bool srcIsOpaque,
bool needsColorXform) {
SkASSERT(srcIsOpaque);
// TODO: Implement color XForm
SkASSERT(needsColorXform == false);
if (kUnknown_SkAlphaType == dstInfo.alphaType()) {
return false;
}
if (kOpaque_SkAlphaType != dstInfo.alphaType()) {
SkCodecPrintf(
"Warning: an opaque image should be decoded as opaque "
"- it is being decoded as non-opaque, which will draw slower\n");
}
switch (dstInfo.colorType()) {
case kRGBA_1010102_SkColorType:
this->setSrcXformFormat(skcms_PixelFormat_RGBA_1010102);
return true;
case kRGBA_8888_SkColorType:
this->setSrcXformFormat(skcms_PixelFormat_RGBA_8888);
return true;
default:
return false;
}
}
void SkJpegRCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options) {
const int srcBPP = 4;
fSwizzler = SkSwizzler::MakeSimple(srcBPP, dstInfo, options);
SkASSERT(fSwizzler);
}
void SkJpegRCodec::allocateStorage(const SkImageInfo& dstInfo) {
int dstWidth = dstInfo.width();
size_t swizzleBytes = 0;
const SkEncodedInfo& encodedInfo = this->getEncodedInfo();
if (fSwizzler) {
const int srcBPP = encodedInfo.bitsPerPixel() / 8;
swizzleBytes = srcBPP * encodedInfo.width();
dstWidth = fSwizzler->swizzleWidth();
SkASSERT(!this->colorXform() || SkIsAlign4(swizzleBytes));
}
size_t totalBytes = swizzleBytes;
fStorage.reset(totalBytes);
if (totalBytes > 0) {
fSwizzleSrcRow = (swizzleBytes > 0) ? fStorage.get() : nullptr;
}
}
SkSampler* SkJpegRCodec::getSampler(bool createIfNecessary) {
if (!createIfNecessary || fSwizzler) {
SkASSERT(!fSwizzler || (fSwizzleSrcRow && fStorage.get() == fSwizzleSrcRow));
return fSwizzler.get();
}
this->initializeSwizzler(this->dstInfo(), this->options());
this->allocateStorage(this->dstInfo());
return fSwizzler.get();
}
SkCodec::Result SkJpegRCodec::decodeImage(const SkImageInfo& dstInfo, void* dst) {
const SkColorType dstColorType = dstInfo.colorType();
if (dstColorType != kRGBA_1010102_SkColorType && dstColorType != kRGBA_8888_SkColorType) {
// We only support RGBA1010102 and RGBA8888 colors
return kUnimplemented;
}
const bool decodeSDR = (dstColorType == kRGBA_8888_SkColorType);
jpegr_compressed_struct compressedImage;
jpegr_uncompressed_struct decompressedImage;
compressedImage.data = (void*)fData->data();
compressedImage.length = fData->size();
// Decode to intermediate buffer
if (dst == nullptr) {
const SkEncodedInfo& encodedInfo = this->getEncodedInfo();
fDecodedImage.reset(encodedInfo.width() * encodedInfo.height() *
encodedInfo.bitsPerPixel() / 8);
decompressedImage.data = fDecodedImage.get();
} else {
decompressedImage.data = dst;
}
if (fRecoveryMap->decodeJPEGR(&compressedImage, &decompressedImage, nullptr, decodeSDR) != 0) {
return kInternalError;
}
return kSuccess;
}
SkCodec::Result SkJpegRCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
const Options& options) {
// We need to decode the whole image.
// Decode it and put to a temporary storage
SkCodec::Result result = kSuccess;
if ((result = this->decodeImage(dstInfo, nullptr)) != kSuccess) {
return result;
}
if (options.fSubset) {
this->initializeSwizzler(dstInfo, options);
} else {
fSwizzler.reset(nullptr);
}
this->allocateStorage(dstInfo);
return kSuccess;
}
int SkJpegRCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) {
uint8_t* decodeDst = (uint8_t*)dst;
uint32_t* swizzleDst = (uint32_t*)dst;
size_t decodeDstRowBytes = dstRowBytes;
size_t swizzleDstRowBytes = dstRowBytes;
int dstWidth =
this->options().fSubset ? this->options().fSubset->width() : this->dstInfo().width();
if (fDecodedImage.get() == nullptr) {
return 0;
}
if (fSwizzleSrcRow) {
decodeDst = fSwizzleSrcRow;
decodeDstRowBytes = 0;
dstWidth = fSwizzler->swizzleWidth();
}
const int currentLine = this->nextScanline();
const int decodeWidth = this->dstInfo().width();
const int bpp = this->getEncodedInfo().bitsPerPixel() / 8;
const int decodeStride = decodeWidth * bpp;
for (int y = 0; y < count; y++) {
memcpy(decodeDst, fDecodedImage.get() + decodeStride * (y + currentLine), decodeStride);
if (fSwizzler) {
fSwizzler->swizzle(swizzleDst, decodeDst);
}
decodeDst = SkTAddOffset<uint8_t>(decodeDst, decodeDstRowBytes);
swizzleDst = SkTAddOffset<uint32_t>(swizzleDst, swizzleDstRowBytes);
}
return count;
}
bool SkJpegRCodec::onSkipScanlines(int count) { return true; }
/*
* Performs the JpegR decode
*/
SkCodec::Result SkJpegRCodec::onGetPixels(const SkImageInfo& dstInfo,
void* dst,
size_t dstRowBytes,
const Options& options,
int* rowsDecoded) {
if (options.fSubset) {
return kUnimplemented;
}
if (this->dimensions() != dstInfo.dimensions()) {
// No Scaling
return kUnimplemented;
}
if (dstRowBytes != static_cast<size_t>(this->dimensions().width()) * 4) {
// TODO: Add stride handling
return kUnimplemented;
}
SkCodec::Result result = kSuccess;
if ((result = this->decodeImage(dstInfo, dst)) != kSuccess) {
return result;
}
*rowsDecoded = dstInfo.height();
return kSuccess;
}
#endif // SK_CODEC_DECODES_JPEGR