blob: 0ee350381634093ac88cb1dec2ae288a9c8508fd [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/SkTemplates.h"
#include "include/private/base/SkMalloc.h"
#include "include/private/base/SkTo.h"
#include "modules/skcms/skcms.h"
#include "src/codec/SkCodecPriv.h"
#include "src/codec/SkParseEncodedOrigin.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;
jpegRInfo.exifData = &exifData;
jpegRInfo.iccData = nullptr;
if (recoveryMap->getJPEGRInfo(&compressedImage, &jpegRInfo) != 0) {
return kInvalidInput;
}
// JPEGR always report 10-bit color depth
const uint8_t colorDepth = 10;
// TODO: Create color profile from ICC data
SkEncodedInfo info = SkEncodedInfo::Make(jpegRInfo.width,
jpegRInfo.height,
SkEncodedInfo::kRGBA_Color,
SkEncodedInfo::kOpaque_Alpha,
colorDepth,
std::move(profile));
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;
}
}
/*
* 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;
}
const SkColorType dstColorType = dstInfo.colorType();
if (dstColorType != kRGBA_1010102_SkColorType && dstColorType != kRGBA_8888_SkColorType) {
// We only support RGBA1010102 and RGBA8888 colors
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;
}
const bool decodeSDR = (dstColorType == kRGBA_8888_SkColorType);
jpegr_compressed_struct compressedImage;
jpegr_uncompressed_struct decompressedImage;
compressedImage.data = (void*)fData->data();
compressedImage.length = fData->size();
decompressedImage.data = dst;
if (fRecoveryMap->decodeJPEGR(&compressedImage, &decompressedImage, nullptr, decodeSDR) != 0) {
return kInternalError;
}
*rowsDecoded = dstInfo.height();
return kSuccess;
}
#endif // SK_CODEC_DECODES_JPEGR