aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/codec
diff options
context:
space:
mode:
authorGravatar Leon Scroggins III <scroggo@google.com>2017-08-17 15:13:20 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-08-18 14:40:29 +0000
commit04be2b54ba07a076d59bea7dcbd063641010d77d (patch)
tree0b06118f8d83cc15dce38d97576b8bf8b7399f34 /src/codec
parent20af6d12eefd8d937bc13bdb7fa0a9ac86b699af (diff)
Reland "skia: add heif decoding support"
This reverts commit db68a426b6ba3a0fa1cace25ac306037eb7413a6. Fixes errors in Android and Google3 Bug: b/64077740 Change-Id: I3d2bb1223e4d8ba912ea2b88144aeecc487fce1a Reviewed-on: https://skia-review.googlesource.com/35701 Commit-Queue: Leon Scroggins <scroggo@google.com> Reviewed-by: Chong Zhang <chz@google.com> Reviewed-by: Mike Klein <mtklein@google.com> Reviewed-by: Derek Sollenberger <djsollen@google.com>
Diffstat (limited to 'src/codec')
-rw-r--r--src/codec/SkAndroidCodec.cpp3
-rw-r--r--src/codec/SkCodec.cpp16
-rw-r--r--src/codec/SkHeifCodec.cpp379
-rw-r--r--src/codec/SkHeifCodec.h87
-rw-r--r--src/codec/SkWebpCodec.h2
5 files changed, 477 insertions, 10 deletions
diff --git a/src/codec/SkAndroidCodec.cpp b/src/codec/SkAndroidCodec.cpp
index d004eb8326..f5c93f2445 100644
--- a/src/codec/SkAndroidCodec.cpp
+++ b/src/codec/SkAndroidCodec.cpp
@@ -89,6 +89,9 @@ std::unique_ptr<SkAndroidCodec> SkAndroidCodec::MakeFromStream(std::unique_ptr<S
case SkEncodedImageFormat::kGIF:
case SkEncodedImageFormat::kBMP:
case SkEncodedImageFormat::kWBMP:
+#ifdef SK_HAS_HEIF_LIBRARY
+ case SkEncodedImageFormat::kHEIF:
+#endif
return skstd::make_unique<SkSampledCodec>(codec.release());
#ifdef SK_HAS_WEBP_LIBRARY
case SkEncodedImageFormat::kWEBP:
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 84af2271f7..2168ac3521 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -14,6 +14,9 @@
#include "SkFrameHolder.h"
#include "SkGifCodec.h"
#include "SkHalf.h"
+#ifdef SK_HAS_HEIF_LIBRARY
+#include "SkHeifCodec.h"
+#endif
#include "SkIcoCodec.h"
#include "SkJpegCodec.h"
#ifdef SK_HAS_PNG_LIBRARY
@@ -41,13 +44,12 @@ static const DecoderProc gDecoderProcs[] = {
{ SkIcoCodec::IsIco, SkIcoCodec::MakeFromStream },
#endif
{ SkBmpCodec::IsBmp, SkBmpCodec::MakeFromStream },
- { SkWbmpCodec::IsWbmp, SkWbmpCodec::MakeFromStream }
+ { SkWbmpCodec::IsWbmp, SkWbmpCodec::MakeFromStream },
+#ifdef SK_HAS_HEIF_LIBRARY
+ { SkHeifCodec::IsHeif, SkHeifCodec::MakeFromStream },
+#endif
};
-size_t SkCodec::MinBufferedBytesNeeded() {
- return WEBP_VP8_HEADER_SIZE;
-}
-
std::unique_ptr<SkCodec> SkCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* outResult, SkPngChunkReader* chunkReader) {
Result resultStorage;
@@ -60,9 +62,7 @@ std::unique_ptr<SkCodec> SkCodec::MakeFromStream(std::unique_ptr<SkStream> strea
return nullptr;
}
- // 14 is enough to read all of the supported types.
- const size_t bytesToRead = 14;
- SkASSERT(bytesToRead <= MinBufferedBytesNeeded());
+ constexpr size_t bytesToRead = MinBufferedBytesNeeded();
char buffer[bytesToRead];
size_t bytesRead = stream->peek(buffer, bytesToRead);
diff --git a/src/codec/SkHeifCodec.cpp b/src/codec/SkHeifCodec.cpp
new file mode 100644
index 0000000000..5ccb52a01f
--- /dev/null
+++ b/src/codec/SkHeifCodec.cpp
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#ifdef SK_HAS_HEIF_LIBRARY
+#include "SkCodec.h"
+#include "SkCodecPriv.h"
+#include "SkColorPriv.h"
+#include "SkColorSpace_Base.h"
+#include "SkEndian.h"
+#include "SkStream.h"
+#include "SkHeifCodec.h"
+
+#define FOURCC(c1, c2, c3, c4) \
+ ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4))
+
+bool SkHeifCodec::IsHeif(const void* buffer, size_t bytesRead) {
+ // Parse the ftyp box up to bytesRead to determine if this is HEIF.
+ // Any valid ftyp box should have at least 8 bytes.
+ if (bytesRead < 8) {
+ return false;
+ }
+
+ uint32_t* ptr = (uint32_t*)buffer;
+ uint64_t chunkSize = SkEndian_SwapBE32(ptr[0]);
+ uint32_t chunkType = SkEndian_SwapBE32(ptr[1]);
+
+ if (chunkType != FOURCC('f', 't', 'y', 'p')) {
+ return false;
+ }
+
+ off64_t offset = 8;
+ if (chunkSize == 1) {
+ // This indicates that the next 8 bytes represent the chunk size,
+ // and chunk data comes after that.
+ if (bytesRead < 16) {
+ return false;
+ }
+ auto* chunkSizePtr = SkTAddOffset<const uint64_t>(buffer, offset);
+ chunkSize = SkEndian_SwapBE64(*chunkSizePtr);
+ if (chunkSize < 16) {
+ // The smallest valid chunk is 16 bytes long in this case.
+ return false;
+ }
+ offset += 8;
+ } else if (chunkSize < 8) {
+ // The smallest valid chunk is 8 bytes long.
+ return false;
+ }
+
+ if (chunkSize > bytesRead) {
+ chunkSize = bytesRead;
+ }
+ off64_t chunkDataSize = chunkSize - offset;
+ // It should at least have major brand (4-byte) and minor version (4-bytes).
+ // The rest of the chunk (if any) is a list of (4-byte) compatible brands.
+ if (chunkDataSize < 8) {
+ return false;
+ }
+
+ uint32_t numCompatibleBrands = (chunkDataSize - 8) / 4;
+ for (size_t i = 0; i < numCompatibleBrands + 2; ++i) {
+ if (i == 1) {
+ // Skip this index, it refers to the minorVersion,
+ // not a brand.
+ continue;
+ }
+ auto* brandPtr = SkTAddOffset<const uint32_t>(buffer, offset + 4 * i);
+ uint32_t brand = SkEndian_SwapBE32(*brandPtr);
+ if (brand == FOURCC('m', 'i', 'f', '1') || brand == FOURCC('h', 'e', 'i', 'c')) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static SkCodec::Origin get_orientation(const HeifFrameInfo& frameInfo) {
+ switch (frameInfo.mRotationAngle) {
+ case 0: return SkCodec::kTopLeft_Origin;
+ case 90: return SkCodec::kRightTop_Origin;
+ case 180: return SkCodec::kBottomRight_Origin;
+ case 270: return SkCodec::kLeftBottom_Origin;
+ }
+ return SkCodec::kDefault_Origin;
+}
+
+struct SkHeifStreamWrapper : public HeifStream {
+ SkHeifStreamWrapper(SkStream* stream) : fStream(stream) {}
+
+ ~SkHeifStreamWrapper() override {}
+
+ size_t read(void* buffer, size_t size) override {
+ return fStream->read(buffer, size);
+ }
+
+ bool rewind() override {
+ return fStream->rewind();
+ }
+
+ bool seek(size_t position) override {
+ return fStream->seek(position);
+ }
+
+ bool hasLength() const override {
+ return fStream->hasLength();
+ }
+
+ size_t getLength() const override {
+ return fStream->getLength();
+ }
+
+private:
+ std::unique_ptr<SkStream> fStream;
+};
+
+std::unique_ptr<SkCodec> SkHeifCodec::MakeFromStream(
+ std::unique_ptr<SkStream> stream, Result* result) {
+ std::unique_ptr<HeifDecoder> heifDecoder(createHeifDecoder());
+ if (heifDecoder.get() == nullptr) {
+ *result = kInternalError;
+ return nullptr;
+ }
+
+ HeifFrameInfo frameInfo;
+ if (!heifDecoder->init(new SkHeifStreamWrapper(stream.release()),
+ &frameInfo)) {
+ *result = kInvalidInput;
+ return nullptr;
+ }
+
+ SkEncodedInfo info = SkEncodedInfo::Make(
+ SkEncodedInfo::kYUV_Color, SkEncodedInfo::kOpaque_Alpha, 8);
+
+ Origin orientation = get_orientation(frameInfo);
+
+ sk_sp<SkColorSpace> colorSpace = nullptr;
+ if ((frameInfo.mIccSize > 0) && (frameInfo.mIccData != nullptr)) {
+ SkColorSpace_Base::ICCTypeFlag iccType = SkColorSpace_Base::kRGB_ICCTypeFlag;
+ colorSpace = SkColorSpace_Base::MakeICC(
+ frameInfo.mIccData.get(), frameInfo.mIccSize, iccType);
+ }
+ if (!colorSpace) {
+ colorSpace = SkColorSpace::MakeSRGB();
+ }
+
+ *result = kSuccess;
+ return std::unique_ptr<SkCodec>(new SkHeifCodec(frameInfo.mWidth, frameInfo.mHeight,
+ info, heifDecoder.release(), std::move(colorSpace), orientation));
+}
+
+SkHeifCodec::SkHeifCodec(int width, int height, const SkEncodedInfo& info,
+ HeifDecoder* heifDecoder, sk_sp<SkColorSpace> colorSpace, Origin origin)
+ : INHERITED(width, height, info, SkColorSpaceXform::kRGBA_8888_ColorFormat,
+ nullptr, std::move(colorSpace), origin)
+ , fHeifDecoder(heifDecoder)
+ , fSwizzleSrcRow(nullptr)
+ , fColorXformSrcRow(nullptr)
+{}
+
+/*
+ * Checks if the conversion between the input image and the requested output
+ * image has been implemented
+ * Sets the output color format
+ */
+bool SkHeifCodec::setOutputColorFormat(const SkImageInfo& dstInfo) {
+ 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_8888_SkColorType:
+ return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888);
+
+ case kBGRA_8888_SkColorType:
+ return fHeifDecoder->setOutputColor(kHeifColorFormat_BGRA_8888);
+
+ case kRGB_565_SkColorType:
+ if (this->colorXform()) {
+ return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888);
+ } else {
+ return fHeifDecoder->setOutputColor(kHeifColorFormat_RGB565);
+ }
+
+ case kRGBA_F16_SkColorType:
+ SkASSERT(this->colorXform());
+
+ if (!dstInfo.colorSpace()->gammaIsLinear()) {
+ return false;
+ }
+ return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888);
+
+ default:
+ return false;
+ }
+}
+
+int SkHeifCodec::readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count,
+ const Options& opts) {
+ // When fSwizzleSrcRow is non-null, it means that we need to swizzle. In this case,
+ // we will always decode into fSwizzlerSrcRow before swizzling into the next buffer.
+ // We can never swizzle "in place" because the swizzler may perform sampling and/or
+ // subsetting.
+ // When fColorXformSrcRow is non-null, it means that we need to color xform and that
+ // we cannot color xform "in place" (many times we can, but not when the dst is F16).
+ // In this case, we will color xform from fColorXformSrcRow into the dst.
+ uint8_t* decodeDst = (uint8_t*) dst;
+ uint32_t* swizzleDst = (uint32_t*) dst;
+ size_t decodeDstRowBytes = rowBytes;
+ size_t swizzleDstRowBytes = rowBytes;
+ int dstWidth = opts.fSubset ? opts.fSubset->width() : dstInfo.width();
+ if (fSwizzleSrcRow && fColorXformSrcRow) {
+ decodeDst = fSwizzleSrcRow;
+ swizzleDst = fColorXformSrcRow;
+ decodeDstRowBytes = 0;
+ swizzleDstRowBytes = 0;
+ dstWidth = fSwizzler->swizzleWidth();
+ } else if (fColorXformSrcRow) {
+ decodeDst = (uint8_t*) fColorXformSrcRow;
+ swizzleDst = fColorXformSrcRow;
+ decodeDstRowBytes = 0;
+ swizzleDstRowBytes = 0;
+ } else if (fSwizzleSrcRow) {
+ decodeDst = fSwizzleSrcRow;
+ decodeDstRowBytes = 0;
+ dstWidth = fSwizzler->swizzleWidth();
+ }
+
+ for (int y = 0; y < count; y++) {
+ if (!fHeifDecoder->getScanline(decodeDst)) {
+ return y;
+ }
+
+ if (fSwizzler) {
+ fSwizzler->swizzle(swizzleDst, decodeDst);
+ }
+
+ if (this->colorXform()) {
+ this->applyColorXform(dst, swizzleDst, dstWidth, kOpaque_SkAlphaType);
+ dst = SkTAddOffset<void>(dst, rowBytes);
+ }
+
+ decodeDst = SkTAddOffset<uint8_t>(decodeDst, decodeDstRowBytes);
+ swizzleDst = SkTAddOffset<uint32_t>(swizzleDst, swizzleDstRowBytes);
+ }
+
+ return count;
+}
+
+/*
+ * Performs the heif decode
+ */
+SkCodec::Result SkHeifCodec::onGetPixels(const SkImageInfo& dstInfo,
+ void* dst, size_t dstRowBytes,
+ const Options& options,
+ int* rowsDecoded) {
+ if (options.fSubset) {
+ // Not supporting subsets on this path for now.
+ // TODO: if the heif has tiles, we can support subset here, but
+ // need to retrieve tile config from metadata retriever first.
+ return kUnimplemented;
+ }
+
+ if (!this->initializeColorXform(dstInfo, options.fPremulBehavior)) {
+ return kInvalidConversion;
+ }
+
+ // Check if we can decode to the requested destination and set the output color space
+ if (!this->setOutputColorFormat(dstInfo)) {
+ return kInvalidConversion;
+ }
+
+ if (!fHeifDecoder->decode(&fFrameInfo)) {
+ return kInvalidInput;
+ }
+
+ this->allocateStorage(dstInfo);
+
+ int rows = this->readRows(dstInfo, dst, dstRowBytes, dstInfo.height(), options);
+ if (rows < dstInfo.height()) {
+ *rowsDecoded = rows;
+ return kIncompleteInput;
+ }
+
+ return kSuccess;
+}
+
+void SkHeifCodec::allocateStorage(const SkImageInfo& dstInfo) {
+ int dstWidth = dstInfo.width();
+
+ size_t swizzleBytes = 0;
+ if (fSwizzler) {
+ swizzleBytes = fFrameInfo.mBytesPerPixel * fFrameInfo.mWidth;
+ dstWidth = fSwizzler->swizzleWidth();
+ SkASSERT(!this->colorXform() || SkIsAlign4(swizzleBytes));
+ }
+
+ size_t xformBytes = 0;
+ if (this->colorXform() && (kRGBA_F16_SkColorType == dstInfo.colorType() ||
+ kRGB_565_SkColorType == dstInfo.colorType())) {
+ xformBytes = dstWidth * sizeof(uint32_t);
+ }
+
+ size_t totalBytes = swizzleBytes + xformBytes;
+ fStorage.reset(totalBytes);
+ if (totalBytes > 0) {
+ fSwizzleSrcRow = (swizzleBytes > 0) ? fStorage.get() : nullptr;
+ fColorXformSrcRow = (xformBytes > 0) ?
+ SkTAddOffset<uint32_t>(fStorage.get(), swizzleBytes) : nullptr;
+ }
+}
+
+void SkHeifCodec::initializeSwizzler(
+ const SkImageInfo& dstInfo, const Options& options) {
+ SkEncodedInfo swizzlerInfo = this->getEncodedInfo();
+
+ SkImageInfo swizzlerDstInfo = dstInfo;
+ if (this->colorXform()) {
+ // The color xform will be expecting RGBA 8888 input.
+ swizzlerDstInfo = swizzlerDstInfo.makeColorType(kRGBA_8888_SkColorType);
+ }
+
+ fSwizzler.reset(SkSwizzler::CreateSwizzler(swizzlerInfo, nullptr,
+ swizzlerDstInfo, options, nullptr, true));
+ SkASSERT(fSwizzler);
+}
+
+SkSampler* SkHeifCodec::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 SkHeifCodec::onStartScanlineDecode(
+ const SkImageInfo& dstInfo, const Options& options) {
+ if (!this->initializeColorXform(dstInfo, options.fPremulBehavior)) {
+ return kInvalidConversion;
+ }
+
+ // Check if we can decode to the requested destination and set the output color space
+ if (!this->setOutputColorFormat(dstInfo)) {
+ return kInvalidConversion;
+ }
+
+ // TODO: For now, just decode the whole thing even when there is a subset.
+ // If the heif image has tiles, we could potentially do this much faster,
+ // but the tile configuration needs to be retrieved from the metadata.
+ if (!fHeifDecoder->decode(&fFrameInfo)) {
+ return kInvalidInput;
+ }
+
+ this->allocateStorage(dstInfo);
+
+ return kSuccess;
+}
+
+int SkHeifCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) {
+ return this->readRows(this->dstInfo(), dst, dstRowBytes, count, this->options());
+}
+
+bool SkHeifCodec::onSkipScanlines(int count) {
+ return count == (int) fHeifDecoder->skipScanlines(count);
+}
+
+#endif // SK_HAS_HEIF_LIBRARY
diff --git a/src/codec/SkHeifCodec.h b/src/codec/SkHeifCodec.h
new file mode 100644
index 0000000000..86188f50ea
--- /dev/null
+++ b/src/codec/SkHeifCodec.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkHeifCodec_DEFINED
+#define SkHeifCodec_DEFINED
+
+#include "SkCodec.h"
+#include "SkColorSpace.h"
+#include "SkColorSpaceXform.h"
+#include "SkImageInfo.h"
+#include "SkSwizzler.h"
+#include "SkStream.h"
+#include "HeifDecoderAPI.h"
+
+class SkHeifCodec : public SkCodec {
+public:
+ static bool IsHeif(const void*, size_t);
+
+ /*
+ * Assumes IsHeif was called and returned true.
+ */
+ static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*);
+
+protected:
+
+ Result onGetPixels(
+ const SkImageInfo& dstInfo,
+ void* dst, size_t dstRowBytes,
+ const Options& options,
+ int* rowsDecoded) override;
+
+ SkEncodedImageFormat onGetEncodedFormat() const override {
+ return SkEncodedImageFormat::kHEIF;
+ }
+
+ bool conversionSupported(const SkImageInfo&, SkEncodedInfo::Color, bool,
+ const SkColorSpace*) const override {
+ // This class checks for conversion after creating colorXform.
+ return true;
+ }
+
+private:
+ /*
+ * Creates an instance of the decoder
+ * Called only by NewFromStream
+ */
+ SkHeifCodec(int width, int height, const SkEncodedInfo&,
+ HeifDecoder*, sk_sp<SkColorSpace>, Origin);
+
+ /*
+ * Checks if the conversion between the input image and the requested output
+ * image has been implemented.
+ *
+ * Sets the output color format.
+ */
+ bool setOutputColorFormat(const SkImageInfo& dst);
+
+ void initializeSwizzler(const SkImageInfo& dstInfo, const Options& options);
+ void allocateStorage(const SkImageInfo& dstInfo);
+ int readRows(const SkImageInfo& dstInfo, void* dst,
+ size_t rowBytes, int count, const Options&);
+
+ /*
+ * 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<HeifDecoder> fHeifDecoder;
+ HeifFrameInfo fFrameInfo;
+ SkAutoTMalloc<uint8_t> fStorage;
+ uint8_t* fSwizzleSrcRow;
+ uint32_t* fColorXformSrcRow;
+
+ std::unique_ptr<SkSwizzler> fSwizzler;
+
+ typedef SkCodec INHERITED;
+};
+
+#endif // SkHeifCodec_DEFINED
diff --git a/src/codec/SkWebpCodec.h b/src/codec/SkWebpCodec.h
index 60bff61220..134dafa3d5 100644
--- a/src/codec/SkWebpCodec.h
+++ b/src/codec/SkWebpCodec.h
@@ -23,8 +23,6 @@ extern "C" {
void WebPDemuxDelete(WebPDemuxer* dmux);
}
-static const size_t WEBP_VP8_HEADER_SIZE = 30;
-
class SkWebpCodec final : public SkCodec {
public:
// Assumes IsWebp was called and returned true.