From 2532d56fea5b6b321a0d123d9c956c3ab5a10d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Novomesk=C3=BD?= Date: Wed, 6 Dec 2023 20:54:58 +0100 Subject: [PATCH] Support for HEJ2 format --- src/heif.cpp | 102 ++++++++++++++++++++++++++++++++++++++++++-------- src/heif.json | 4 +- src/heif_p.h | 8 ++-- 3 files changed, 93 insertions(+), 21 deletions(-) diff --git a/src/heif.cpp b/src/heif.cpp index bc48381..a32aa7e 100644 --- a/src/heif.cpp +++ b/src/heif.cpp @@ -22,6 +22,7 @@ size_t HEIFHandler::m_initialized_count = 0; bool HEIFHandler::m_plugins_queried = false; bool HEIFHandler::m_heif_decoder_available = false; bool HEIFHandler::m_heif_encoder_available = false; +bool HEIFHandler::m_hej2_decoder_available = false; extern "C" { static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata) @@ -59,12 +60,25 @@ HEIFHandler::HEIFHandler() bool HEIFHandler::canRead() const { - if (m_parseState == ParseHeicNotParsed && !canRead(device())) { + if (m_parseState == ParseHeicNotParsed) { + QIODevice *dev = device(); + if (dev) { + const QByteArray header = dev->peek(28); + + if (HEIFHandler::isSupportedBMFFType(header)) { + setFormat("heif"); + return true; + } + + if (HEIFHandler::isSupportedHEJ2(header)) { + setFormat("hej2"); + return true; + } + } return false; } if (m_parseState != ParseHeicError) { - setFormat("heif"); return true; } return false; @@ -300,17 +314,6 @@ bool HEIFHandler::write_helper(const QImage &image) return true; } -bool HEIFHandler::canRead(QIODevice *device) -{ - if (!device) { - qWarning("HEIFHandler::canRead() called with no device"); - return false; - } - - const QByteArray header = device->peek(28); - return HEIFHandler::isSupportedBMFFType(header); -} - bool HEIFHandler::isSupportedBMFFType(const QByteArray &header) { if (header.size() < 28) { @@ -350,6 +353,22 @@ bool HEIFHandler::isSupportedBMFFType(const QByteArray &header) return false; } +bool HEIFHandler::isSupportedHEJ2(const QByteArray &header) +{ + if (header.size() < 28) { + return false; + } + + const char *buffer = header.constData(); + if (qstrncmp(buffer + 4, "ftyp", 4) == 0) { + if (qstrncmp(buffer + 8, "j2ki", 4) == 0) { + return true; + } + } + + return false; +} + QVariant HEIFHandler::option(ImageOption option) const { if (option == Quality) { @@ -425,7 +444,7 @@ bool HEIFHandler::ensureDecoder() } const QByteArray buffer = device()->readAll(); - if (!HEIFHandler::isSupportedBMFFType(buffer)) { + if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer)) { m_parseState = ParseHeicError; return false; } @@ -814,6 +833,9 @@ bool HEIFHandler::isHeifDecoderAvailable() } #endif +#if LIBHEIF_HAVE_VERSION(1, 13, 0) + m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); +#endif m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); m_plugins_queried = true; @@ -839,6 +861,9 @@ bool HEIFHandler::isHeifEncoderAvailable() } #endif +#if LIBHEIF_HAVE_VERSION(1, 13, 0) + m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); +#endif m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); m_plugins_queried = true; @@ -853,6 +878,34 @@ bool HEIFHandler::isHeifEncoderAvailable() return m_heif_encoder_available; } +bool HEIFHandler::isHej2DecoderAvailable() +{ + QMutexLocker locker(&getHEIFHandlerMutex()); + + if (!m_plugins_queried) { +#if LIBHEIF_HAVE_VERSION(1, 13, 0) + if (m_initialized_count == 0) { + heif_init(nullptr); + } +#endif + + m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); + m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); +#if LIBHEIF_HAVE_VERSION(1, 13, 0) + m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); +#endif + m_plugins_queried = true; + +#if LIBHEIF_HAVE_VERSION(1, 13, 0) + if (m_initialized_count == 0) { + heif_deinit(); + } +#endif + } + + return m_hej2_decoder_available; +} + void HEIFHandler::startHeifLib() { #if LIBHEIF_HAVE_VERSION(1, 13, 0) @@ -901,6 +954,15 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q } return format_cap; } + + if (format == "hej2") { + Capabilities format_cap; + if (HEIFHandler::isHej2DecoderAvailable()) { + format_cap |= CanRead; + } + return format_cap; + } + if (!format.isEmpty()) { return {}; } @@ -909,8 +971,16 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q } Capabilities cap; - if (device->isReadable() && HEIFHandler::canRead(device) && HEIFHandler::isHeifDecoderAvailable()) { - cap |= CanRead; + if (device->isReadable()) { + const QByteArray header = device->peek(28); + + if (HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable()) { + cap |= CanRead; + } + + if (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable()) { + cap |= CanRead; + } } if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) { diff --git a/src/heif.json b/src/heif.json index 02307c2..fbc2cca 100644 --- a/src/heif.json +++ b/src/heif.json @@ -1,4 +1,4 @@ { - "Keys": [ "heif", "heic" ], - "MimeTypes": [ "image/heif", "image/heif" ] + "Keys": [ "heif", "heic", "hej2" ], + "MimeTypes": [ "image/heif", "image/heif", "image/hej2k" ] } diff --git a/src/heif_p.h b/src/heif_p.h index 9fbe0ea..8bfafff 100644 --- a/src/heif_p.h +++ b/src/heif_p.h @@ -24,17 +24,18 @@ class HEIFHandler : public QImageIOHandler bool read(QImage *image) override; bool write(const QImage &image) override; - static bool canRead(QIODevice *device); - QVariant option(ImageOption option) const override; void setOption(ImageOption option, const QVariant &value) override; bool supportsOption(ImageOption option) const override; static bool isHeifDecoderAvailable(); static bool isHeifEncoderAvailable(); + static bool isHej2DecoderAvailable(); -private: static bool isSupportedBMFFType(const QByteArray &header); + static bool isSupportedHEJ2(const QByteArray &header); + +private: bool ensureParsed() const; bool ensureDecoder(); @@ -57,6 +58,7 @@ class HEIFHandler : public QImageIOHandler static bool m_plugins_queried; static bool m_heif_decoder_available; static bool m_heif_encoder_available; + static bool m_hej2_decoder_available; static QMutex &getHEIFHandlerMutex(); };