jpegr codec: Implement scanline decoding and sampling
Add functionality needed for scanline decoding, region decoding and
sampling.
Test: atest BitmapFactoryTest#testDecodeJpegr BitmapRegionDecoderTest#testJpegr
Change-Id: I1557d9ee4a45802700b241f05fddaf50168512cb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/631162
Commit-Queue: Fyodor Kyslov <kyslov@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/client_utils/android/BitmapRegionDecoder.cpp b/client_utils/android/BitmapRegionDecoder.cpp
index c5262b8..067a76f 100644
--- a/client_utils/android/BitmapRegionDecoder.cpp
+++ b/client_utils/android/BitmapRegionDecoder.cpp
@@ -23,6 +23,9 @@
switch (codec->getEncodedFormat()) {
case SkEncodedImageFormat::kJPEG:
+#ifdef SK_CODEC_DECODES_JPEGR
+ case SkEncodedImageFormat::kJPEGR:
+#endif
case SkEncodedImageFormat::kPNG:
case SkEncodedImageFormat::kWEBP:
case SkEncodedImageFormat::kHEIF:
diff --git a/src/codec/SkAndroidCodec.cpp b/src/codec/SkAndroidCodec.cpp
index 11eceaa..dd4d582 100644
--- a/src/codec/SkAndroidCodec.cpp
+++ b/src/codec/SkAndroidCodec.cpp
@@ -107,6 +107,9 @@
#ifndef SK_CODEC_DECODES_AVIF
case SkEncodedImageFormat::kAVIF:
#endif
+#ifdef SK_CODEC_DECODES_JPEGR
+ case SkEncodedImageFormat::kJPEGR:
+#endif
return std::make_unique<SkSampledCodec>(codec.release());
#ifdef SK_HAS_WUFFS_LIBRARY
case SkEncodedImageFormat::kGIF:
@@ -120,9 +123,6 @@
#ifdef SK_CODEC_DECODES_AVIF
case SkEncodedImageFormat::kAVIF:
#endif
-#ifdef SK_CODEC_DECODES_JPEGR
- case SkEncodedImageFormat::kJPEGR:
-#endif
#if defined(SK_CODEC_DECODES_WEBP) || defined(SK_CODEC_DECODES_RAW) || \
defined(SK_HAS_WUFFS_LIBRARY) || defined(SK_CODEC_DECODES_AVIF)
return std::make_unique<SkAndroidCodecAdapter>(codec.release());
diff --git a/src/codec/SkJpegRCodec.cpp b/src/codec/SkJpegRCodec.cpp
index 5532f5b..96bdc46 100644
--- a/src/codec/SkJpegRCodec.cpp
+++ b/src/codec/SkJpegRCodec.cpp
@@ -24,22 +24,22 @@
#include "include/core/SkStream.h"
#include "include/core/SkTypes.h"
#include "include/core/SkYUVAInfo.h"
-#include "include/private/base/SkTemplates.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) {
+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);
+ const uint8_t* data = exifData.data();
+ SkParseEncodedOrigin(data + kOffset, exifSize - kOffset, &orientation);
}
return orientation;
}
@@ -135,6 +135,7 @@
// 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());
@@ -145,8 +146,9 @@
jpegRInfo.height,
SkEncodedInfo::kRGBA_Color,
SkEncodedInfo::kOpaque_Alpha,
- colorDepth,
- std::move(profile));
+ bitsPerComponent,
+ std::move(profile),
+ colorDepth);
SkEncodedOrigin orientation = get_orientation(exifData);
@@ -216,6 +218,128 @@
}
}
+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
*/
@@ -227,12 +351,6 @@
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;
@@ -243,16 +361,9 @@
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;
+ SkCodec::Result result = kSuccess;
+ if ((result = this->decodeImage(dstInfo, dst)) != kSuccess) {
+ return result;
}
*rowsDecoded = dstInfo.height();
diff --git a/src/codec/SkJpegRCodec.h b/src/codec/SkJpegRCodec.h
index a3ab47a..bc36cee 100644
--- a/src/codec/SkJpegRCodec.h
+++ b/src/codec/SkJpegRCodec.h
@@ -32,6 +32,7 @@
#include <memory>
class SkStream;
+class SkSwizzler;
struct SkImageInfo;
/*
@@ -109,9 +110,29 @@
SkEncodedOrigin origin,
sk_sp<SkData> data);
+ void initializeSwizzler(const SkImageInfo& dstInfo, const Options& options);
+ void allocateStorage(const SkImageInfo& dstInfo);
+
+ // If "dst" is nullptr decoded image will be placed to fDecodedImage
+ Result decodeImage(const SkImageInfo& dstInfo, void* dst);
+
+ /*
+ * Scanline decoding.
+ */
+ SkSampler* getSampler(bool createIfNecessary) override;
+ Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options) override;
+ int onGetScanlines(void* dst, int count, size_t rowBytes) override;
+ bool onSkipScanlines(int count) override;
+
std::unique_ptr<RecoveryMap> fRecoveryMap;
sk_sp<SkData> fData;
+ skia_private::AutoTMalloc<uint8_t> fStorage;
+ skia_private::AutoTMalloc<uint8_t> fDecodedImage;
+ uint8_t* fSwizzleSrcRow = nullptr;
+
+ std::unique_ptr<SkSwizzler> fSwizzler;
+
#endif // SK_CODEC_DECODES_JPEGR
};