diff options
author | Leon Scroggins III <scroggo@google.com> | 2017-08-17 15:13:20 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-08-18 14:40:29 +0000 |
commit | 04be2b54ba07a076d59bea7dcbd063641010d77d (patch) | |
tree | 0b06118f8d83cc15dce38d97576b8bf8b7399f34 /src/codec | |
parent | 20af6d12eefd8d937bc13bdb7fa0a9ac86b699af (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.cpp | 3 | ||||
-rw-r--r-- | src/codec/SkCodec.cpp | 16 | ||||
-rw-r--r-- | src/codec/SkHeifCodec.cpp | 379 | ||||
-rw-r--r-- | src/codec/SkHeifCodec.h | 87 | ||||
-rw-r--r-- | src/codec/SkWebpCodec.h | 2 |
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. |