HEIF plug-in extended to support HEJ2 format

HEJ2 is JPEG 2000 codec encapsulated in HEIF container.
Only HEJ2 reading is implemented.
This commit is contained in:
Daniel Novomeský 2023-12-07 12:58:14 +01:00
parent e90bca4924
commit 43c80793ac
8 changed files with 99 additions and 21 deletions

View File

@ -99,6 +99,12 @@ if (LibHeif_FOUND)
kimageformats_write_tests(FUZZ 1 kimageformats_write_tests(FUZZ 1
heif-nodatacheck-lossless heif-nodatacheck-lossless
) )
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.17.0")
kimageformats_read_tests(FUZZ 1
hej2
)
endif()
endif() endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND) if (LibJXL_FOUND AND LibJXLThreads_FOUND)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -22,6 +22,7 @@ size_t HEIFHandler::m_initialized_count = 0;
bool HEIFHandler::m_plugins_queried = false; bool HEIFHandler::m_plugins_queried = false;
bool HEIFHandler::m_heif_decoder_available = false; bool HEIFHandler::m_heif_decoder_available = false;
bool HEIFHandler::m_heif_encoder_available = false; bool HEIFHandler::m_heif_encoder_available = false;
bool HEIFHandler::m_hej2_decoder_available = false;
extern "C" { extern "C" {
static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata) 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 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; return false;
} }
if (m_parseState != ParseHeicError) { if (m_parseState != ParseHeicError) {
setFormat("heif");
return true; return true;
} }
return false; return false;
@ -300,17 +314,6 @@ bool HEIFHandler::write_helper(const QImage &image)
return true; 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) bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
{ {
if (header.size() < 28) { if (header.size() < 28) {
@ -350,6 +353,22 @@ bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
return false; 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 QVariant HEIFHandler::option(ImageOption option) const
{ {
if (option == Quality) { if (option == Quality) {
@ -425,7 +444,7 @@ bool HEIFHandler::ensureDecoder()
} }
const QByteArray buffer = device()->readAll(); const QByteArray buffer = device()->readAll();
if (!HEIFHandler::isSupportedBMFFType(buffer)) { if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer)) {
m_parseState = ParseHeicError; m_parseState = ParseHeicError;
return false; return false;
} }
@ -814,6 +833,9 @@ bool HEIFHandler::isHeifDecoderAvailable()
} }
#endif #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_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_plugins_queried = true; m_plugins_queried = true;
@ -839,6 +861,9 @@ bool HEIFHandler::isHeifEncoderAvailable()
} }
#endif #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_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_plugins_queried = true; m_plugins_queried = true;
@ -853,6 +878,34 @@ bool HEIFHandler::isHeifEncoderAvailable()
return m_heif_encoder_available; 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() void HEIFHandler::startHeifLib()
{ {
#if LIBHEIF_HAVE_VERSION(1, 13, 0) #if LIBHEIF_HAVE_VERSION(1, 13, 0)
@ -901,6 +954,15 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
} }
return format_cap; return format_cap;
} }
if (format == "hej2") {
Capabilities format_cap;
if (HEIFHandler::isHej2DecoderAvailable()) {
format_cap |= CanRead;
}
return format_cap;
}
if (!format.isEmpty()) { if (!format.isEmpty()) {
return {}; return {};
} }
@ -909,8 +971,16 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
} }
Capabilities cap; Capabilities cap;
if (device->isReadable() && HEIFHandler::canRead(device) && HEIFHandler::isHeifDecoderAvailable()) { if (device->isReadable()) {
cap |= CanRead; 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()) { if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {

View File

@ -1,4 +1,4 @@
{ {
"Keys": [ "heif", "heic" ], "Keys": [ "heif", "heic", "hej2" ],
"MimeTypes": [ "image/heif", "image/heif" ] "MimeTypes": [ "image/heif", "image/heif", "image/hej2k" ]
} }

View File

@ -24,17 +24,18 @@ public:
bool read(QImage *image) override; bool read(QImage *image) override;
bool write(const QImage &image) override; bool write(const QImage &image) override;
static bool canRead(QIODevice *device);
QVariant option(ImageOption option) const override; QVariant option(ImageOption option) const override;
void setOption(ImageOption option, const QVariant &value) override; void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption(ImageOption option) const override; bool supportsOption(ImageOption option) const override;
static bool isHeifDecoderAvailable(); static bool isHeifDecoderAvailable();
static bool isHeifEncoderAvailable(); static bool isHeifEncoderAvailable();
static bool isHej2DecoderAvailable();
static bool isSupportedBMFFType(const QByteArray &header);
static bool isSupportedHEJ2(const QByteArray &header);
private: private:
static bool isSupportedBMFFType(const QByteArray &header);
bool ensureParsed() const; bool ensureParsed() const;
bool ensureDecoder(); bool ensureDecoder();
@ -57,6 +58,7 @@ private:
static bool m_plugins_queried; static bool m_plugins_queried;
static bool m_heif_decoder_available; static bool m_heif_decoder_available;
static bool m_heif_encoder_available; static bool m_heif_encoder_available;
static bool m_hej2_decoder_available;
static QMutex &getHEIFHandlerMutex(); static QMutex &getHEIFHandlerMutex();
}; };