mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-15 11:14:18 -04:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
4be09ba419 | |||
6cbdf9cf54 | |||
7d6de20e8c | |||
b37c991e39 | |||
249046f25d | |||
9f7b1b8dee | |||
f065104b72 | |||
f34185197a | |||
9f24023ca7 | |||
8d1ef536be | |||
da8ed31aec | |||
ce8383e5fd |
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
project(KImageFormats)
|
project(KImageFormats)
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
find_package(ECM 5.240.0 NO_MODULE)
|
find_package(ECM 6.0.0 NO_MODULE)
|
||||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
@ -91,6 +91,7 @@ if (BUILD_TESTING)
|
|||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
include(ECMFeatureSummary)
|
||||||
|
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
|
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
|
||||||
|
@ -97,6 +97,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)
|
||||||
|
BIN
autotests/read/exr/gray.exr
Normal file
BIN
autotests/read/exr/gray.exr
Normal file
Binary file not shown.
BIN
autotests/read/exr/gray.png
Normal file
BIN
autotests/read/exr/gray.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
autotests/read/hej2/rgb_kcolorchooser.hej2
Normal file
BIN
autotests/read/hej2/rgb_kcolorchooser.hej2
Normal file
Binary file not shown.
BIN
autotests/read/hej2/rgb_kcolorchooser.png
Normal file
BIN
autotests/read/hej2/rgb_kcolorchooser.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
autotests/read/hej2/rgba_kolourpaint.hej2
Normal file
BIN
autotests/read/hej2/rgba_kolourpaint.hej2
Normal file
Binary file not shown.
BIN
autotests/read/hej2/rgba_kolourpaint.png
Normal file
BIN
autotests/read/hej2/rgba_kolourpaint.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -16,9 +16,34 @@
|
|||||||
|
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Quality range - compression/subsampling
|
||||||
|
100 - lossless RGB compression
|
||||||
|
< KIMG_AVIF_QUALITY_BEST, 100 ) - YUV444 color subsampling
|
||||||
|
< KIMG_AVIF_QUALITY_HIGH, KIMG_AVIF_QUALITY_BEST ) - YUV422 color subsampling
|
||||||
|
< 0, KIMG_AVIF_QUALITY_HIGH ) - YUV420 color subsampling
|
||||||
|
< 0, KIMG_AVIF_QUALITY_LOW ) - lossy compression of alpha channel
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KIMG_AVIF_DEFAULT_QUALITY
|
||||||
|
#define KIMG_AVIF_DEFAULT_QUALITY 68
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef KIMG_AVIF_QUALITY_BEST
|
||||||
|
#define KIMG_AVIF_QUALITY_BEST 90
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef KIMG_AVIF_QUALITY_HIGH
|
||||||
|
#define KIMG_AVIF_QUALITY_HIGH 80
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef KIMG_AVIF_QUALITY_LOW
|
||||||
|
#define KIMG_AVIF_QUALITY_LOW 51
|
||||||
|
#endif
|
||||||
|
|
||||||
QAVIFHandler::QAVIFHandler()
|
QAVIFHandler::QAVIFHandler()
|
||||||
: m_parseState(ParseAvifNotParsed)
|
: m_parseState(ParseAvifNotParsed)
|
||||||
, m_quality(52)
|
, m_quality(KIMG_AVIF_DEFAULT_QUALITY)
|
||||||
, m_container_width(0)
|
, m_container_width(0)
|
||||||
, m_container_height(0)
|
, m_container_height(0)
|
||||||
, m_rawAvifData(AVIF_DATA_EMPTY)
|
, m_rawAvifData(AVIF_DATA_EMPTY)
|
||||||
@ -519,9 +544,17 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_quality > 100) {
|
||||||
|
m_quality = 100;
|
||||||
|
} else if (m_quality < 0) {
|
||||||
|
m_quality = KIMG_AVIF_DEFAULT_QUALITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if AVIF_VERSION < 1000000
|
||||||
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
|
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
|
||||||
int minQuantizer = 0;
|
int minQuantizer = 0;
|
||||||
int maxQuantizerAlpha = 0;
|
int maxQuantizerAlpha = 0;
|
||||||
|
#endif
|
||||||
avifResult res;
|
avifResult res;
|
||||||
|
|
||||||
bool save_grayscale; // true - monochrome, false - colors
|
bool save_grayscale; // true - monochrome, false - colors
|
||||||
@ -567,13 +600,15 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// quality settings
|
#if AVIF_VERSION < 1000000
|
||||||
|
// deprecated quality settings
|
||||||
if (maxQuantizer > 20) {
|
if (maxQuantizer > 20) {
|
||||||
minQuantizer = maxQuantizer - 20;
|
minQuantizer = maxQuantizer - 20;
|
||||||
if (maxQuantizer > 40) { // we decrease quality of alpha channel here
|
if (maxQuantizer > 40) { // we decrease quality of alpha channel here
|
||||||
maxQuantizerAlpha = maxQuantizer - 40;
|
maxQuantizerAlpha = maxQuantizer - 40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
|
if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
|
||||||
if (save_depth > 8) {
|
if (save_depth > 8) {
|
||||||
@ -646,8 +681,8 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
QImage tmpcolorimage = image.convertToFormat(tmpformat);
|
QImage tmpcolorimage = image.convertToFormat(tmpformat);
|
||||||
|
|
||||||
avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
|
avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
|
||||||
if (maxQuantizer < 20) {
|
if (m_quality >= KIMG_AVIF_QUALITY_HIGH) {
|
||||||
if (maxQuantizer < 10) {
|
if (m_quality >= KIMG_AVIF_QUALITY_BEST) {
|
||||||
pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
|
pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
|
||||||
} else {
|
} else {
|
||||||
pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
|
pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
|
||||||
@ -807,6 +842,8 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
avifRWData raw = AVIF_DATA_EMPTY;
|
avifRWData raw = AVIF_DATA_EMPTY;
|
||||||
avifEncoder *encoder = avifEncoderCreate();
|
avifEncoder *encoder = avifEncoderCreate();
|
||||||
encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
||||||
|
|
||||||
|
#if AVIF_VERSION < 1000000
|
||||||
encoder->minQuantizer = minQuantizer;
|
encoder->minQuantizer = minQuantizer;
|
||||||
encoder->maxQuantizer = maxQuantizer;
|
encoder->maxQuantizer = maxQuantizer;
|
||||||
|
|
||||||
@ -814,6 +851,17 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
|
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
|
||||||
encoder->maxQuantizerAlpha = maxQuantizerAlpha;
|
encoder->maxQuantizerAlpha = maxQuantizerAlpha;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
encoder->quality = m_quality;
|
||||||
|
|
||||||
|
if (image.hasAlphaChannel()) {
|
||||||
|
if (m_quality >= KIMG_AVIF_QUALITY_LOW) {
|
||||||
|
encoder->qualityAlpha = 100;
|
||||||
|
} else {
|
||||||
|
encoder->qualityAlpha = 100 - (KIMG_AVIF_QUALITY_LOW - m_quality) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
encoder->speed = 6;
|
encoder->speed = 6;
|
||||||
|
|
||||||
@ -870,7 +918,7 @@ void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
|
|||||||
if (m_quality > 100) {
|
if (m_quality > 100) {
|
||||||
m_quality = 100;
|
m_quality = 100;
|
||||||
} else if (m_quality < 0) {
|
} else if (m_quality < 0) {
|
||||||
m_quality = 52;
|
m_quality = KIMG_AVIF_DEFAULT_QUALITY;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
|
|
||||||
// Allow the code to works on all QT versions supported by KDE
|
// Allow the code to works on all QT versions supported by KDE
|
||||||
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
|
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
|
||||||
#if (QT_VERSION_MAJOR >= 6) && !defined(EXR_USE_LEGACY_CONVERSIONS)
|
#if !defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||||
// If uncommented, the image is rendered in a float16 format, the result is very precise
|
// If uncommented, the image is rendered in a float16 format, the result is very precise
|
||||||
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
||||||
#endif
|
#endif
|
||||||
@ -621,7 +621,14 @@ bool EXRHandler::write(const QImage &image)
|
|||||||
|
|
||||||
// write the EXR
|
// write the EXR
|
||||||
K_OStream ostr(device(), QByteArray());
|
K_OStream ostr(device(), QByteArray());
|
||||||
Imf::RgbaOutputFile file(ostr, header, image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB);
|
auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
|
||||||
|
if (image.format() == QImage::Format_Mono ||
|
||||||
|
image.format() == QImage::Format_MonoLSB ||
|
||||||
|
image.format() == QImage::Format_Grayscale16 ||
|
||||||
|
image.format() == QImage::Format_Grayscale8) {
|
||||||
|
channelsType = Imf::RgbaChannels::WRITE_Y;
|
||||||
|
}
|
||||||
|
Imf::RgbaOutputFile file(ostr, header, channelsType);
|
||||||
Imf::Array2D<Imf::Rgba> pixels;
|
Imf::Array2D<Imf::Rgba> pixels;
|
||||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||||
|
|
||||||
|
@ -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,10 +971,18 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
|
|||||||
}
|
}
|
||||||
|
|
||||||
Capabilities cap;
|
Capabilities cap;
|
||||||
if (device->isReadable() && HEIFHandler::canRead(device) && HEIFHandler::isHeifDecoderAvailable()) {
|
if (device->isReadable()) {
|
||||||
|
const QByteArray header = device->peek(28);
|
||||||
|
|
||||||
|
if (HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable()) {
|
||||||
cap |= CanRead;
|
cap |= CanRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable()) {
|
||||||
|
cap |= CanRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
|
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
|
||||||
cap |= CanWrite;
|
cap |= CanWrite;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"Keys": [ "heif", "heic" ],
|
"Keys": [ "heif", "heic", "hej2" ],
|
||||||
"MimeTypes": [ "image/heif", "image/heif" ]
|
"MimeTypes": [ "image/heif", "image/heif", "image/hej2k" ]
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
@ -631,7 +631,7 @@ static bool IsValid(const PSDHeader &header)
|
|||||||
qDebug() << "PSD header: invalid color mode" << header.color_mode;
|
qDebug() << "PSD header: invalid color mode" << header.color_mode;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Specs tells: "Supported range is 1 to 56" but the limit is 57:
|
// Specs tells: "Supported range is 1 to 56" but when the alpha channel is present the limit is 57:
|
||||||
// Photoshop does not make you add more (see also 53alphas.psd test case).
|
// Photoshop does not make you add more (see also 53alphas.psd test case).
|
||||||
if (header.channel_count < 1 || header.channel_count > 57) {
|
if (header.channel_count < 1 || header.channel_count > 57) {
|
||||||
qDebug() << "PSD header: invalid number of channels" << header.channel_count;
|
qDebug() << "PSD header: invalid number of channels" << header.channel_count;
|
||||||
|
@ -60,20 +60,33 @@ const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y)
|
|||||||
}
|
}
|
||||||
if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) {
|
if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) {
|
||||||
_tmpBuffer = QImage(image.width(), 1, image.format());
|
_tmpBuffer = QImage(image.width(), 1, image.format());
|
||||||
|
_tmpBuffer.setColorTable(image.colorTable());
|
||||||
}
|
}
|
||||||
if (_tmpBuffer.isNull()) {
|
if (_tmpBuffer.isNull()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine()));
|
std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine()));
|
||||||
|
auto tmp = _tmpBuffer;
|
||||||
if (colorSpaceConversion) {
|
if (colorSpaceConversion) {
|
||||||
auto cs = image.colorSpace();
|
auto cs = image.colorSpace();
|
||||||
if (!cs.isValid()) {
|
if (!cs.isValid()) {
|
||||||
cs = _defaultColorSpace;
|
cs = _defaultColorSpace;
|
||||||
}
|
}
|
||||||
_tmpBuffer.setColorSpace(cs);
|
if (tmp.depth() < 24) {
|
||||||
_tmpBuffer.convertToColorSpace(_colorSpace);
|
tmp.convertTo(tmp.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||||
}
|
}
|
||||||
_convBuffer = _tmpBuffer.convertToFormat(_targetFormat);
|
tmp.setColorSpace(cs);
|
||||||
|
tmp.convertToColorSpace(_colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Work Around for wrong RGBA64 -> 16FPx4/32FPx4 conversion on Intel architecture.
|
||||||
|
* Luckily convertTo() works fine with 16FPx4 images so I can use it instead convertToFormat().
|
||||||
|
* See also: https://bugreports.qt.io/browse/QTBUG-120614
|
||||||
|
*/
|
||||||
|
tmp.convertTo(_targetFormat);
|
||||||
|
_convBuffer = tmp;
|
||||||
|
|
||||||
if (_convBuffer.isNull()) {
|
if (_convBuffer.isNull()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -90,9 +103,6 @@ qsizetype ScanLineConverter::bytesPerLine() const
|
|||||||
|
|
||||||
bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace, const QColorSpace &defaultColorSpace)
|
bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace, const QColorSpace &defaultColorSpace)
|
||||||
{
|
{
|
||||||
if (image.depth() < 24) { // RGB 8 bit or grater only
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto sourceColorSpace = image.colorSpace();
|
auto sourceColorSpace = image.colorSpace();
|
||||||
if (!sourceColorSpace.isValid()) {
|
if (!sourceColorSpace.isValid()) {
|
||||||
sourceColorSpace = defaultColorSpace;
|
sourceColorSpace = defaultColorSpace;
|
||||||
|
@ -960,7 +960,11 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
|
|||||||
case PROP_PARASITES:
|
case PROP_PARASITES:
|
||||||
while (!property.atEnd()) {
|
while (!property.atEnd()) {
|
||||||
char *tag;
|
char *tag;
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||||
quint32 size;
|
quint32 size;
|
||||||
|
#else
|
||||||
|
qsizetype size;
|
||||||
|
#endif
|
||||||
|
|
||||||
property.readBytes(tag, size);
|
property.readBytes(tag, size);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user