Compare commits

..

16 Commits

Author SHA1 Message Date
Nicolas Fella
2791c2543b Update dependency version to 6.27.0 2026-06-05 17:33:32 +02:00
Mirco Miranda
a2a8c955df Updated documentation on memory usage 2026-06-04 07:17:54 +02:00
Mirco Miranda
f450e5c9a9 JXR: add a memory allocation barrier in jxrlib 2026-05-27 10:37:09 +02:00
Mirco Miranda
9dfcf67ea9 EXR: reject files with dimensions exceeding 300kx300k pixels 2026-05-25 14:13:15 +02:00
Mirco Miranda
6017099044 OSS Fuzz: set image allocation limit to 2000 MiB 2026-05-23 05:59:01 +02:00
Mirco Miranda
9ddad16767 Improve size limits for AVIF, HEIF, and RAW plugins 2026-05-15 12:13:12 +02:00
Mirco Miranda
0e2b137b32 IFF: fix byte swapping in 16-bit DEEP images 2026-05-12 10:31:43 +02:00
Mirco Miranda
6d5e61f0b0 Add Farbfeld read only support 2026-05-11 07:38:28 +02:00
Mirco Miranda
d7c3174fb6 DDS: fix mime type 2026-05-08 04:58:39 +02:00
Mirco Miranda
49a8fd38c8 IFF: DEEP image support 2026-05-08 01:29:54 +02:00
Mirco Miranda
1206598337 imageAlloc: add image initialization support 2026-05-07 08:41:10 +02:00
Mirco Miranda
3488077d8d Fix uninitialized value 2026-05-04 08:51:34 +02:00
Laurent Montel
ea8a4dccdc GIT_SILENT: Bump kf ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without kf 6.26 deprecated methods 2026-05-03 08:41:08 +02:00
Mirco Miranda
8048279473 HEIF: image transformation support 2026-05-03 00:00:11 +02:00
Nicolas Fella
18d0f93d60 Update CI image 2026-05-02 15:30:10 +02:00
Nicolas Fella
497e612ad3 Update version to 6.27.0 2026-05-01 22:24:21 +02:00
66 changed files with 1630 additions and 122 deletions

View File

@@ -16,7 +16,7 @@ include:
image_json_validate:
stage: validate
image: invent-registry.kde.org/sysadmin/ci-images/suse-qt69:latest
image: invent-registry.kde.org/sysadmin/ci-images/suse-qt611:latest
tags:
- Linux
script:

View File

@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.29)
set(KF_VERSION "6.26.0") # handled by release scripts
set(KF_DEP_VERSION "6.26.0") # handled by release scripts
set(KF_VERSION "6.27.0") # handled by release scripts
set(KF_DEP_VERSION "6.27.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.26.0 NO_MODULE)
find_package(ECM 6.27.0 NO_MODULE)
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)
@@ -72,7 +72,7 @@ set_property(CACHE KIMAGEFORMATS_HEJ2_TEST PROPERTY STRINGS "OFF" "READ_ONLY" "A
set(KIMAGEFORMATS_AVCI_TEST "ALL" CACHE STRING "Enable AVCI tests: OFF, ALL")
set_property(CACHE KIMAGEFORMATS_AVCI_TEST PROPERTY STRINGS "OFF" "ALL")
if(KIMAGEFORMATS_HEIF)
pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0)
pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.17.0)
endif()
add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/HEIC images")
@@ -107,7 +107,7 @@ add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR
ecm_set_disabled_deprecation_versions(
QT 6.11.0
KF 6.23.0
KF 6.26.0
)
add_subdirectory(src)

View File

@@ -16,6 +16,7 @@ The following image formats have read-only support:
- Animated Windows cursors (ani)
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
- Farbfeld (ff)
- Gimp (xcf)
- Interchange Format Files (iff, ilbm, lbm)
- Krita (kra)
@@ -243,21 +244,23 @@ RGB.
Where possible, plugins support large images. By convention, many of the
large image plugins are limited to a maximum of 300,000 x 300,000 pixels.
Anyway, all plugins are also limited by the
`QImageIOReader::allocationLimit()`.
`QImageReader::allocationLimit()`.
> [!note]
> You can change the maximum limit of 300000 pixels by setting the constant
> `KIF_LARGE_IMAGE_PIXEL_LIMIT` to the desired value in the cmake file.
> `KIF_LARGE_IMAGE_PIXEL_LIMIT` to the desired value in the cmake file. It
> cannot be less than 65536.
Below are the maximum sizes for each plugin ('n/a' means no limit, i.e. the
limit depends on the format encoding).
- ANI: n/a
- ANI: same size as Qt's ICO plugin
- AVIF: 32,768 x 32,768 pixels, in any case no larger than 256 megapixels
- DDS: 300,000 x 300,000 pixels
- EXR: 300,000 x 300,000 pixels
- EPS: same size as Qt's JPG plugin
- FF: 300,000 x 300,000 pixels
- HDR: 300,000 x 300,000 pixels
- HEIF: n/a
- HEIF: 65,535 x 65,535 pixels
- IFF: 65,535 x 65,535 pixels
- JP2: 300,000 x 300,000 pixels, in any case no larger than 2 gigapixels
- JXL: 262,144 x 262,144 pixels, in any case no larger than 256 megapixels
@@ -297,8 +300,18 @@ consumption proportional to the size of the image to be saved.
Normally this is not a source of problems because the affected plugins
are limited to maximum images of 2GiB or less.
Note that the value of `QImageReader::allocationLimit()` is only used when
allocating a new `QImage`. Since this parameter was created to limit damage
caused by corrupted files, any conversion of `QImage` (for example, with
`QImage::convertTo()`) is not subject to this limit.
On plugins for formats that support large images, progressive conversion has
been used or the maximum size of the image that can be saved has been limited.
Plugins that use external libraries don't always allow progressive decoding
(e.g., the JPEG series). In these cases, the memory required for reading
may be much larger than the entire decoded image. When the external library has
a maximum memory limit function, the value of `QImageReader::allocationLimit()`
is set.
### Non-RGB formats
@@ -333,6 +346,15 @@ distributions. In particular, it is necessary that the HEIF library has
support for HEVC codec. If HEVC codec is not available the plugin
will compile but will fail the tests.
The following defines can be defined in cmake to modify the behavior of the
plugin:
- `HEIF_DISABLE_QT_TRANSFORMATION`: HEIF transformations, in addition to
rotations and reflections, also support image cropping. Consequently, the
Qt plugin, must also honor the crop. This define is useful in case
of problems: activating it disables Qt's support for transformations,
delegating them to the HEIF libraries (which will therefore always apply
them regardless of what is requested from Qt).
**If you are interested in compiling the plugin without running the tests,
also use the following string options:**
- `KIMAGEFORMATS_HEIF_TEST` to change the behaviour of HEIF tests. Set to
@@ -394,6 +416,7 @@ The plugin supports the following image data:
- FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7
and DYuv formats.
- FORM RGFX: It supports uncompressed images only.
- FORM DEEP: It supports uncompressed, RLE and TVDC images.
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
RGBA images.
@@ -436,8 +459,13 @@ plugin:
> [!caution]
> The plugin disabled by default due to security issues in [jxrlib](https://github.com/4creators/jxrlib):
> the upstream jxrlib is dead and there is no "hope" they will fix the issues.
>
> **You should not enable it unless you know what you are doing.**
> [!note]
> Security issues in the jxrlib discovered by the [KImageFormats OSS-Fuzz project](https://github.com/google/oss-fuzz/tree/master/projects/kimageformats)
> should be fixed in this [jxrlib fork](https://github.com/mircomir/jxrlib).
The following defines can be defined in cmake to modify the behavior of the
plugin:
- `JXR_DENY_FLOAT_IMAGE`: disables the use of float images and consequently
@@ -450,9 +478,6 @@ plugin:
it only wants (P)BGRA32bpp files (a format not supported by Qt). Only for
this format an hack is activated to guarantee total compatibility of the
plugin with Windows.
- `JXR_ENABLE_ADVANCED_METADATA`: enable metadata support (e.g. XMP). Some
distributions use an incomplete JXR library that does not allow reading
metadata, causing compilation errors.
### The KRA plugin

View File

@@ -76,6 +76,7 @@ endmacro()
# Loads each <format> image in read/<format>/, and compares the
# result against the data read from the corresponding png file
kimageformats_read_tests(
ff
hdr
iff
pcx

View File

@@ -162,6 +162,7 @@ HANDLER_TYPES="ANIHandler ani
QAVIFHandler avif
QDDSHandler dds
EXRHandler exr
FFHandler ff
HDRHandler hdr
HEIFHandler heif
IFFHandler iff

View File

@@ -23,17 +23,19 @@
Usage:
python infra/helper.py build_image kimageformats
python infra/helper.py build_fuzzers --sanitizer undefined|address|memory kimageformats
python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tim|tga|xcf]_fuzzer
python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|ff|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tim|tga|xcf]_fuzzer
*/
#include <QBuffer>
#include <QCoreApplication>
#include <QImage>
#include <QImageReader>
#include "ani_p.h"
#include "avif_p.h"
#include "dds_p.h"
#include "exr_p.h"
#include "ff_p.h"
#include "hdr_p.h"
#include "heif_p.h"
#include "iff_p.h"
@@ -61,6 +63,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
int argc = 0;
QCoreApplication a(argc, nullptr);
QImageReader::setAllocationLimit(512);
QImageIOHandler* handler = new HANDLER();
QImage i;

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

View File

@@ -0,0 +1,17 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "GIMP built-in sRGB",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "orientation_all.png",
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "orientation_all.png",
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "orientation_all.png",
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "orientation_all.png",
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "orientation_all.png",
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "orientation_all.png",
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "orientation_all.png",
"metadata" : [
{
"key" : "Software" ,
"value" : "LIFE Pro 2.20.35 (Linux)"
}
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -73,6 +73,10 @@ endif()
##################################
kimageformats_add_plugin(kimg_ff SOURCES ff.cpp)
##################################
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
##################################

View File

@@ -49,6 +49,16 @@ Quality range - compression/subsampling
#define KIMG_AVIF_QUALITY_LOW 51
#endif
/* *** AVIF_MAX_IMAGE_WIDTH and AVIF_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef AVIF_MAX_IMAGE_WIDTH
#define AVIF_MAX_IMAGE_WIDTH KIF_64K_IMAGE_PIXEL_LIMIT
#endif
#ifndef AVIF_MAX_IMAGE_HEIGHT
#define AVIF_MAX_IMAGE_HEIGHT AVIF_MAX_IMAGE_WIDTH
#endif
QAVIFHandler::QAVIFHandler()
: m_parseState(ParseAvifNotParsed)
, m_quality(KIMG_AVIF_DEFAULT_QUALITY)
@@ -168,7 +178,7 @@ bool QAVIFHandler::ensureDecoder()
#endif
#if AVIF_VERSION >= 110000
m_decoder->imageDimensionLimit = 65535;
m_decoder->imageDimensionLimit = std::max(AVIF_MAX_IMAGE_WIDTH, AVIF_MAX_IMAGE_HEIGHT) - 1;
#endif
avifResult decodeResult;
@@ -196,7 +206,7 @@ bool QAVIFHandler::ensureDecoder()
m_container_width = m_decoder->image->width;
m_container_height = m_decoder->image->height;
if ((m_container_width > 65535) || (m_container_height > 65535)) {
if ((m_container_width >= AVIF_MAX_IMAGE_WIDTH) || (m_container_height >= AVIF_MAX_IMAGE_HEIGHT)) {
qCWarning(LOG_AVIFPLUGIN, "AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
m_parseState = ParseAvifError;
return false;
@@ -605,7 +615,7 @@ bool QAVIFHandler::write(const QImage &image)
}
if ((image.width() > 0) && (image.height() > 0)) {
if ((image.width() > 65535) || (image.height() > 65535)) {
if ((image.width() >= AVIF_MAX_IMAGE_WIDTH) || (image.height() >= AVIF_MAX_IMAGE_HEIGHT)) {
qCWarning(LOG_AVIFPLUGIN, "Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}

View File

@@ -295,6 +295,14 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new CTBLChunk());
} else if (cid == DATE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
} else if (cid == DBOD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DBODChunk());
} else if (cid == DGBL_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DGBLChunk());
} else if (cid == DLOC_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DLOCChunk());
} else if (cid == DPEL_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DPELChunk());
} else if (cid == DPI__CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DPIChunk());
} else if (cid == EXIF_CHUNK) {
@@ -341,6 +349,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new SHAMChunk());
} else if (cid == TBHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
} else if (cid == TVDC_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new TVDCChunk());
} else if (cid == VDAT_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new VDATChunk());
} else if (cid == VERS_CHUNK) {
@@ -1468,6 +1478,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == RGFX_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
}
return ok;
}
@@ -1585,6 +1597,72 @@ QImage::Format FORMChunk::rgfxFormat() const
return QImage::Format_Invalid;
}
QImage::Format FORMChunk::deepFormat() const
{
auto pels = IFFChunk::searchT<DPELChunk>(chunks());
if (pels.isEmpty()) {
return QImage::Format_Invalid;
}
auto list = pels.first()->elements();
// support for same depth on all elements
auto depth = -1;
for (auto &&el : list) {
if (depth < 0)
depth = el.depth;
if (depth != el.depth)
return QImage::Format_Invalid;
}
// calculate the image format
if (list.size() == 4) {
if (list.at(0).type == DPELChunk::Red &&
list.at(1).type == DPELChunk::Green &&
list.at(2).type == DPELChunk::Blue &&
list.at(3).type == DPELChunk::Alpha) {
if (depth == 8)
return FORMAT_RGBA_8BIT;
else if (depth == 16)
return QImage::Format_RGBA64;
} else if (list.at(0).type == DPELChunk::Cyan &&
list.at(1).type == DPELChunk::Magenta &&
list.at(2).type == DPELChunk::Yellow &&
list.at(3).type == DPELChunk::Black) {
if (depth == 8)
return QImage::Format_CMYK8888;
} else if (list.at(0).type == DPELChunk::Red &&
list.at(1).type == DPELChunk::Green &&
list.at(2).type == DPELChunk::Blue) {
// unknown type of channel 4 -> ignoring it
if (depth == 8)
return QImage::Format_RGBX8888;
else if (depth == 16)
return QImage::Format_RGBX64;
}
} else if (list.size() == 3) {
if (list.at(0).type == DPELChunk::Red &&
list.at(1).type == DPELChunk::Green &&
list.at(2).type == DPELChunk::Blue) {
if (depth == 8)
return FORMAT_RGB_8BIT;
} else if (list.at(0).type == DPELChunk::Blue &&
list.at(1).type == DPELChunk::Green &&
list.at(2).type == DPELChunk::Red) {
if (depth == 8)
return QImage::Format_BGR888;
}
} else if (list.size() == 1) {
if (depth == 1)
return QImage::Format_Mono;
else if (depth == 8)
return QImage::Format_Grayscale8;
else if (depth == 16)
return QImage::Format_Grayscale16;
}
return QImage::Format_Invalid;
}
QByteArray FORMChunk::formType() const
{
return _type;
@@ -1596,6 +1674,8 @@ QImage::Format FORMChunk::format() const
return cdiFormat();
} else if (formType() == RGFX_FORM_TYPE) {
return rgfxFormat();
} else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) {
return deepFormat();
}
return iffFormat();
}
@@ -1612,6 +1692,15 @@ QSize FORMChunk::size() const
if (!rghds.isEmpty()) {
return rghds.first()->size();
}
} else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) {
auto dlocs = IFFChunk::searchT<DLOCChunk>(chunks());
if (!dlocs.isEmpty()) {
return dlocs.first()->size();
}
auto dgbls = IFFChunk::searchT<DGBLChunk>(chunks());
if (!dgbls.isEmpty()) {
return dgbls.first()->size();
}
} else {
auto bmhds = IFFChunk::searchT<BMHDChunk>(chunks());
if (!bmhds.isEmpty()) {
@@ -1735,6 +1824,8 @@ bool CATChunk::innerReadStructure(QIODevice *d)
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == RGFX_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
}
return ok;
}
@@ -3574,6 +3665,438 @@ quint32 RBODChunk::strideSize(const RGHDChunk *header) const
}
/* ******************
* *** DGBL Chunk ***
* ****************** */
DGBLChunk::~DGBLChunk()
{
}
DGBLChunk::DGBLChunk()
: IFFChunk()
{
}
DGBLChunk::Compression DGBLChunk::compression() const
{
if (!isValid()) {
return Compression::Uncompressed;
}
return Compression(ui16(data(), 4));
}
qint32 DGBLChunk::width() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data(), 0));
}
qint32 DGBLChunk::height() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data(), 2));
}
quint8 DGBLChunk::xAspectRatio() const
{
if (!isValid()) {
return 0;
}
return quint8(data().at(6));
}
quint8 DGBLChunk::yAspectRatio() const
{
if (!isValid()) {
return 0;
}
return quint8(data().at(7));
}
bool DGBLChunk::isValid() const
{
if (dataBytes() < 8) {
return false;
}
return chunkId() == DGBLChunk::defaultChunkId();
}
bool DGBLChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** DPEL Chunk ***
* ****************** */
DPELChunk::~DPELChunk()
{
}
DPELChunk::DPELChunk()
: IFFChunk()
{
}
qint32 DPELChunk::count() const
{
if (dataBytes() < 4) {
return 0;
}
auto cnt = i32(data(), 0);
if (cnt < 0 || cnt > 128) {
// an image should have 3, 4 or 5 channels:
// 128 is enough to give an error.
cnt = 0;
}
return cnt;
}
qint32 DPELChunk::depth() const
{
auto depth = 0;
auto list = elements();
for (auto &&el : list) {
depth += el.depth;
}
return depth;
}
QList<DPELChunk::Element> DPELChunk::elements() const
{
QList<DPELChunk::Element> list;
if (isValid()) {
for (auto n = count(), i = 0; i < n; ++i) {
auto idx = 4 + i * 4;
list << DPELChunk::Element(DataType(ui16(data(), idx)),
ui16(data(), idx + 2));
}
}
return list;
}
bool DPELChunk::isValid() const
{
if (dataBytes() < quint32(4 + count() * 4)) {
return false;
}
return chunkId() == DPELChunk::defaultChunkId();
}
bool DPELChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** DLOC Chunk ***
* ****************** */
DLOCChunk::~DLOCChunk()
{
}
DLOCChunk::DLOCChunk()
: IFFChunk()
{
}
qint32 DLOCChunk::width() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data(), 0));
}
qint32 DLOCChunk::height() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data(), 2));
}
QSize DLOCChunk::size() const
{
return QSize(width(), height());
}
qint32 DLOCChunk::xOffset() const
{
if (!isValid()) {
return 0;
}
return qint32(i16(data(), 4));
}
qint32 DLOCChunk::yOffset() const
{
if (!isValid()) {
return 0;
}
return qint32(i16(data(), 6));
}
bool DLOCChunk::isValid() const
{
if (dataBytes() < 8) {
return false;
}
return chunkId() == DLOCChunk::defaultChunkId();
}
bool DLOCChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** TVDC Chunk ***
* ****************** */
TVDCChunk::~TVDCChunk()
{
}
TVDCChunk::TVDCChunk()
: IFFChunk()
{
}
qint32 TVDCChunk::count() const
{
if (!isValid()) {
return 0;
}
return dataBytes() / 2;
}
QList<quint16> TVDCChunk::table() const
{
QList<quint16> list;
if (isValid()) {
for (auto n = count(), i = 0; i < n; ++i) {
list << ui16(data(), i * 2);
}
}
return list;
}
bool TVDCChunk::isValid() const
{
if (dataBytes() < 32) {
return false;
}
return chunkId() == TVDCChunk::defaultChunkId();
}
bool TVDCChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** DBOD Chunk ***
* ****************** */
DBODChunk::~DBODChunk()
{
}
DBODChunk::DBODChunk()
: IFFChunk()
{
}
bool DBODChunk::isValid() const
{
return chunkId() == DBODChunk::defaultChunkId();
}
/*!
* \brief rleDeepDecompress
* Each run contains a pixel (all components)
*/
inline qint64 rleDeepDecompress(QIODevice *input, char *output, qint64 olen, qint32 pixelBytes)
{
qint64 j = 0;
pixelBytes = std::max(1, pixelBytes);
for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
char n;
// check the output buffer space for the next run
if (available < 129 * pixelBytes) {
if (input->peek(&n, 1) != 1) { // end of data (or error)
break;
}
if ((static_cast<signed char>(n) >= 0 ? qint64(n) + 1 : qint64(1 - n)) * pixelBytes > available)
break;
}
// decompress
if (input->read(&n, 1) != 1) { // end of data (or error)
break;
}
if (static_cast<signed char>(n) >= 0) {
rr = input->read(output + j, (qint64(n) + 1) * pixelBytes);
if (rr == -1) {
return -1;
}
}
else {
auto buf = input->read(pixelBytes);
if (buf.size() != pixelBytes) {
break;
}
rr = qint64(1 - static_cast<signed char>(n));
for (qint64 i = 0; i < rr; ++i) {
std::memcpy(output + j + i * pixelBytes, buf.data(), buf.size());
}
rr *= pixelBytes;
}
j += rr;
}
return j;
}
/*!
* \brief tvdcDeepDecompress
* the compression is made line by line for each elementof the chunk DPEL.
* For RGBA for example we have a Red line, a Green line, and so on.
*/
inline qint64 tvdcDeepDecompress(QIODevice *input, char *output, qint64 olen, const QList<quint16>& table)
{
if (table.size() != 16) {
return -1;
}
quint8 v = 0;
quint8 last = 0;
for (qint64 i = 0, pos = 0, lastRead = -1; i < olen; ++i) {
if ((pos >> 1) != lastRead) {
char n;
if (input->read(&n, 1) != 1) {
return -1;
}
lastRead = (pos >> 1);
last = quint8(n);
}
quint8 d = last;
if (pos++ & 1) {
d &= 0xf;
} else {
d >>= 4;
}
v += table.at(d);
output[i] = char(v);
if (!table.at(d)) {
if ((pos >> 1) != lastRead) {
char n;
if (input->read(&n, 1) != 1) {
return -1;
}
lastRead = (pos >> 1);
last = quint8(n);
}
d = last;
if (pos++ & 1) {
d &= 0xf;
} else {
d >>= 4;
}
while (d--) {
if (i < olen - 1) {
output[++i] = char(v);
continue;
}
return -1;
}
}
}
return olen;
}
QByteArray DBODChunk::strideRead(QIODevice *d, qint32, const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc, const TVDCChunk *tvdc) const
{
auto size = strideSize(header, pel, loc);
if (size == 0) {
return {};
}
qint64 rr = 0;
QByteArray planes(size, char());
if (header->compression() == DGBLChunk::Compression::Uncompressed) {
rr = d->read(planes.data(), planes.size());
} else if (header->compression() == DGBLChunk::Compression::Rle) {
rr = rleDeepDecompress(d, planes.data(), planes.size(), pel->depth() / 8);
} else if (header->compression() == DGBLChunk::Compression::TvDeepCompression) {
if (tvdc) { // TVDC is planar, so I have to convert to chunky
auto table = tvdc->table();
for (auto i = 0, n = pel->count(); i < n; ++i) {
QByteArray ba(size / n, char());
rr += tvdcDeepDecompress(d, ba.data(), ba.size(), tvdc->table());
for (auto j = 0, m = int(ba.size()); j < m; ++j) {
planes[i + j * n] = ba.at(j);
}
}
}
} else {
qCDebug(LOG_IFFPLUGIN) << "DBODChunk::strideRead(): unknown compression" << header->compression();
}
// Uncompressed, Rle and TvDeepCompression: one line at a time.
if (rr != size) {
return {};
}
// byte swap
if (auto count = pel->count()) {
if (pel->depth() / count == 16) {
for (auto x = 0, w = qint32(planes.size()) - 1; x < w; x += 2) {
std::swap(planes[x], planes[x + 1]);
}
}
}
return planes;
}
bool DBODChunk::resetStrideRead(QIODevice *d) const
{
return seek(d);
}
quint32 DBODChunk::strideSize(const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc) const
{
auto width = loc ? loc->width() : header->width();
return (width * pel->depth() + 7) / 8;
}
/* ******************
* *** BEAM Chunk ***
* ****************** */

View File

@@ -55,6 +55,10 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CAMG_CHUNK QByteArray("CAMG")
#define CMAP_CHUNK QByteArray("CMAP")
#define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK
#define DBOD_CHUNK QByteArray("DBOD")
#define DGBL_CHUNK QByteArray("DGBL")
#define DLOC_CHUNK QByteArray("DLOC")
#define DPEL_CHUNK QByteArray("DPEL")
#define DPI__CHUNK QByteArray("DPI ")
#define IDAT_CHUNK QByteArray("IDAT")
#define IHDR_CHUNK QByteArray("IHDR")
@@ -65,6 +69,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define RFLG_CHUNK QByteArray("RFLG")
#define RGHD_CHUNK QByteArray("RGHD")
#define RSCM_CHUNK QByteArray("RSCM")
#define TVDC_CHUNK QByteArray("TVDC")
#define XBMI_CHUNK QByteArray("XBMI")
#define YUVS_CHUNK QByteArray("YUVS")
@@ -96,12 +101,14 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
// FORM types
#define ACBM_FORM_TYPE QByteArray("ACBM")
#define DEEP_FORM_TYPE QByteArray("DEEP")
#define ILBM_FORM_TYPE QByteArray("ILBM")
#define IMAG_FORM_TYPE QByteArray("IMAG")
#define PBM__FORM_TYPE QByteArray("PBM ")
#define RGB8_FORM_TYPE QByteArray("RGB8")
#define RGBN_FORM_TYPE QByteArray("RGBN")
#define RGFX_FORM_TYPE QByteArray("RGFX")
#define TVPP_FORM_TYPE QByteArray("TVPP") // same as DEEP
#define CIMG_FOR4_TYPE QByteArray("CIMG")
#define TBMP_FOR4_TYPE QByteArray("TBMP")
@@ -642,11 +649,23 @@ class CAMGChunk : public IFFChunk
{
public:
enum ModeId {
LoResLace = 0x0004,
GenLockVideo = 0x0002,
InterlacedDisplay = 0x0004,
DoubleScan = 0x0008,
SuperHighResolution = 0x0020,
PlayfieldBitplaneAdjust = 0x0040,
HalfBrite = 0x0080,
LoResDpf = 0x0400,
Ham = 0x0800,
HiRes = 0x8000
GenLockAudio = 0x0100,
DualPlayfield = 0x0400,
HoldAndModify = 0x0800,
ExtendedMode = 0x1000,
ViewPortHide = 0x2000,
Sprites = 0x4000,
HighResolution = 0x8000,
// aliases
Lace = InterlacedDisplay,
Ham = HoldAndModify
};
Q_DECLARE_FLAGS(ModeIds, ModeId)
@@ -946,6 +965,8 @@ private:
QImage::Format cdiFormat() const;
QImage::Format rgfxFormat() const;
QImage::Format deepFormat() const;
};
@@ -1946,6 +1967,266 @@ private:
};
/*!
* *** DEEP IFF CHUNKS ***
*/
/*!
* \brief The DGBLChunk class
*/
class DGBLChunk : public IFFChunk
{
public:
enum Compression : quint16 {
Uncompressed = 0,
Rle = 1,
Huffman = 2,
DynamicHuffman = 3,
Jpeg = 4,
TvDeepCompression = 5
};
virtual ~DGBLChunk() override;
DGBLChunk();
DGBLChunk(const DGBLChunk& other) = default;
DGBLChunk& operator =(const DGBLChunk& other) = default;
/*!
* \brief compression
* \return The type of compression used.
*/
Compression compression() const;
/*!
* \brief width
* \return Width of the source display in pixels.
*/
qint32 width() const;
/*!
* \brief height
* \return Height of the source display in pixels.
*/
qint32 height() const;
/*!
* \brief size
* \return Size of the source display in pixels.
*/
QSize size() const
{
return QSize(width(), height());
}
/*!
* \brief xAspectRatio
* \return X pixel aspect.
*/
quint8 xAspectRatio() const;
/*!
* \brief yAspectRatio
* \return Y pixel aspect.
*/
quint8 yAspectRatio() const;
virtual bool isValid() const override;
CHUNKID_DEFINE(DGBL_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The DPELChunk class
*/
class DPELChunk : public IFFChunk
{
public:
enum DataType : quint16 {
Invalid = 0,
Red = 1,
Green = 2,
Blue = 3,
Alpha = 4,
Yellow = 5,
Cyan = 6,
Magenta = 7,
Black = 8,
Mask = 9,
ZBuffer = 10,
Opacity = 11,
LinearKey = 12,
BinaryKey = 13
};
struct Element {
Element(const DataType& t = DataType::Invalid, quint16 d = 0) : type(t), depth(d) {}
DataType type;
quint16 depth;
};
virtual ~DPELChunk() override;
DPELChunk();
DPELChunk(const DPELChunk& other) = default;
DPELChunk& operator =(const DPELChunk& other) = default;
/*!
* \brief count
* \return The number of elements
*/
qint32 count() const;
/*!
* \brief depth
* \return The pixel depth.
*/
qint32 depth() const;
/*!
* \brief elements
* Elements needed to identify the content of every pixel.
* Pixels will always be padded to byte boundaries.
* \return The list of elements. An empty list on error.
*/
QList<Element> elements() const;
virtual bool isValid() const override;
CHUNKID_DEFINE(DPEL_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The DLOCChunk class
*/
class DLOCChunk : public IFFChunk
{
public:
virtual ~DLOCChunk() override;
DLOCChunk();
DLOCChunk(const DLOCChunk& other) = default;
DLOCChunk& operator =(const DLOCChunk& other) = default;
/*!
* \brief width
* \return Width of the bitmap in pixels.
*/
qint32 width() const;
/*!
* \brief height
* \return Height of the bitmap in pixels.
*/
qint32 height() const;
/*!
* \brief size
* \return Size in pixels.
*/
QSize size() const;
/*!
* \brief xOffset
* X offset of origin in source image.
*/
qint32 xOffset() const;
/*!
* \brief yOffset
* \returnX offset of origin in source image.
*/
qint32 yOffset() const;
virtual bool isValid() const override;
CHUNKID_DEFINE(DLOC_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The TVDCChunk class
*/
class TVDCChunk : public IFFChunk
{
public:
virtual ~TVDCChunk() override;
TVDCChunk();
TVDCChunk(const TVDCChunk& other) = default;
TVDCChunk& operator =(const TVDCChunk& other) = default;
qint32 count() const;
QList<quint16> table() const;
virtual bool isValid() const override;
CHUNKID_DEFINE(TVDC_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The DBODChunk class
*/
class DBODChunk : public IFFChunk
{
public:
virtual ~DBODChunk() override;
DBODChunk();
DBODChunk(const DBODChunk& other) = default;
DBODChunk& operator =(const DBODChunk& other) = default;
virtual bool isValid() const override;
CHUNKID_DEFINE(DBOD_CHUNK)
/*!
* \brief readStride
* \param d The device.
* \param y The current scanline.
* \param header The bitmap header.
* \param pel The pixel elements info.
* \param loc The display location (optional).
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
*/
QByteArray strideRead(QIODevice *d,
qint32 y,
const DGBLChunk *header,
const DPELChunk *pel,
const DLOCChunk *loc = nullptr,
const TVDCChunk *tvdc = nullptr) const;
/*!
* \brief resetStrideRead
* Reset the stride read set the position at the beginning of the data and reset all buffers.
* \param d The device.
* \return True on success, otherwise false.
* \sa strideRead
* \note Must be called once before strideRead().
*/
bool resetStrideRead(QIODevice *d) const;
protected:
/*!
* \brief strideSize
* \return The size of data to have to decode an image row.
*/
quint32 strideSize(const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc = nullptr) const;
};
/*!
* *** UNDOCUMENTED CHUNKS ***
*/

View File

@@ -1,4 +1,4 @@
{
"Keys": [ "dds" ],
"MimeTypes": [ "image/x-dds" ]
"Keys": [ "dds", "dds" ],
"MimeTypes": [ "image/vnd.ms-dds", "image/x-dds" ]
}

View File

@@ -209,6 +209,8 @@ EXRHandler::EXRHandler()
{
// Set the number of threads to use (0 is allowed)
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
// Set maximum image size
Imf::Header::setMaxImageSize(EXR_MAX_IMAGE_WIDTH, EXR_MAX_IMAGE_HEIGHT);
}
bool EXRHandler::canRead() const

259
src/imageformats/ff.cpp Normal file
View File

@@ -0,0 +1,259 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2026 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// Specs: https://tools.suckless.org/farbfeld/
#include "ff_p.h"
#include "util_p.h"
#include <QColorSpace>
#include <QIODevice>
#include <QImage>
#include <QLoggingCategory>
#include <QtEndian>
Q_DECLARE_LOGGING_CATEGORY(LOG_FFPLUGIN)
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_FFPLUGIN, "kf.imageformats.plugins.ff", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_FFPLUGIN, "kf.imageformats.plugins.ff", QtWarningMsg)
#endif
/* *** FF_MAX_IMAGE_WIDTH and FF_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef FF_MAX_IMAGE_WIDTH
#define FF_MAX_IMAGE_WIDTH KIF_LARGE_IMAGE_PIXEL_LIMIT
#endif
#ifndef FF_MAX_IMAGE_HEIGHT
#define FF_MAX_IMAGE_HEIGHT FF_MAX_IMAGE_WIDTH
#endif
#define HEADER_SIZE 16
class FFHeader
{
private:
QByteArray m_rawHeader;
public:
FFHeader()
{
}
bool isValid() const
{
if (m_rawHeader.size() < HEADER_SIZE) {
return false;
}
return m_rawHeader.startsWith(QByteArray::fromRawData("farbfeld", 8));
}
bool isSupported() const
{
auto w = width();
auto h = height();
if (w < 1 || w > FF_MAX_IMAGE_WIDTH || h < 1 || h > FF_MAX_IMAGE_HEIGHT) {
return false;
}
return format() != QImage::Format_Invalid;
}
qint32 width() const
{
if (!isValid()) {
return 0;
}
return qFromBigEndian<qint32>(m_rawHeader.data() + 8);
}
qint32 height() const
{
if (!isValid()) {
return 0;
}
return qFromBigEndian<qint32>(m_rawHeader.data() + 12);
}
QSize size() const
{
return QSize(width(), height());
}
QImage::Format format() const
{
if (!isValid()) {
return QImage::Format_Invalid;
}
return QImage::Format_RGBA64;
}
bool read(QIODevice *d)
{
m_rawHeader = d->read(HEADER_SIZE);
if (m_rawHeader.size() != HEADER_SIZE) {
return false;
}
return isValid();
}
bool peek(QIODevice *d)
{
m_rawHeader = d->peek(HEADER_SIZE);
if (m_rawHeader.size() != HEADER_SIZE) {
return false;
}
return isValid();
}
};
class FFHandlerPrivate
{
public:
FFHandlerPrivate() {}
~FFHandlerPrivate() {}
FFHeader m_header;
};
FFHandler::FFHandler()
: QImageIOHandler()
, d(new FFHandlerPrivate)
{
}
bool FFHandler::canRead() const
{
if (canRead(device())) {
setFormat("ff");
return true;
}
return false;
}
bool FFHandler::canRead(QIODevice *device)
{
if (!device) {
qCWarning(LOG_FFPLUGIN) << "FFHandler::canRead() called with no device";
return false;
}
FFHeader h;
if (!h.peek(device)) {
return false;
}
return h.isSupported();
}
bool FFHandler::read(QImage *image)
{
auto&& header = d->m_header;
if (!header.read(device())) {
qCWarning(LOG_FFPLUGIN) << "FFHandler::read() invalid header";
return false;
}
auto img = imageAlloc(header.size(), header.format());
if (img.isNull()) {
qCWarning(LOG_FFPLUGIN) << "FFHandler::read() error while allocating the image";
return false;
}
auto d = device();
auto size = img.bytesPerLine();
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
if (d->read(line, size) != size) {
qCWarning(LOG_FFPLUGIN) << "FFHandler::read() error while reading image scanline";
return false;
}
#if Q_LITTLE_ENDIAN
for (auto i = 0; i < size; i += 2) {
std::swap(line[i], line[i + 1]);
}
#endif
}
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
*image = img;
return true;
}
bool FFHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
return false;
}
QVariant FFHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
auto&& h = d->m_header;
if (h.isValid()) {
v = QVariant::fromValue(h.size());
} else if (auto d = device()) {
if (h.peek(d)) {
v = QVariant::fromValue(h.size());
}
}
}
if (option == QImageIOHandler::ImageFormat) {
auto&& h = d->m_header;
if (h.isValid()) {
v = QVariant::fromValue(h.format());
} else if (auto d = device()) {
if (h.peek(d)) {
v = QVariant::fromValue(h.format());
}
}
}
return v;
}
QImageIOPlugin::Capabilities FFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "ff") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && FFHandler::canRead(device)) {
cap |= CanRead;
}
return cap;
}
QImageIOHandler *FFPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new FFHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}
#include "moc_ff_p.cpp"

4
src/imageformats/ff.json Normal file
View File

@@ -0,0 +1,4 @@
{
"Keys": [ "ff" ],
"MimeTypes": [ "image/x-farbfeld" ]
}

42
src/imageformats/ff_p.h Normal file
View File

@@ -0,0 +1,42 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2026 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIMG_FF_P_H
#define KIMG_FF_P_H
#include <QImageIOPlugin>
#include <QScopedPointer>
class FFHandlerPrivate;
class FFHandler : public QImageIOHandler
{
public:
FFHandler();
bool canRead() const override;
bool read(QImage *image) override;
bool supportsOption(QImageIOHandler::ImageOption option) const override;
QVariant option(QImageIOHandler::ImageOption option) const override;
static bool canRead(QIODevice *device);
private:
const QScopedPointer<FFHandlerPrivate> d;
};
class FFPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "ff.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_FF_P_H

View File

@@ -11,6 +11,7 @@
#include "microexif_p.h"
#include "util_p.h"
#include <libheif/heif.h>
#include <libheif/heif_properties.h>
#include <QColorSpace>
#include <QLoggingCategory>
@@ -33,6 +34,28 @@ Q_LOGGING_CATEGORY(LOG_HEIFPLUGIN, "kf.imageformats.plugins.heif", QtWarningMsg)
#define HEIF_MAX_METADATA_SIZE (4 * 1024 * 1024)
#endif
#ifndef HEIF_DISABLE_QT_TRANSFORMATION
/*!
* HEIF transformations, in addition to rotations and reflections,
* also support image cropping. Consequently, the Qt plugin, must
* also honor the crop. This define is useful in case of problems:
* activating it disables Qt's support for transformations,
* delegating them to the HEIF libraries (which will therefore
* always apply them regardless of what is requested from Qt).
*/
// #define HEIF_DISABLE_QT_TRANSFORMATION
#endif
/* *** HEIF_MAX_IMAGE_WIDTH and HEIF_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef HEIF_MAX_IMAGE_WIDTH
#define HEIF_MAX_IMAGE_WIDTH KIF_64K_IMAGE_PIXEL_LIMIT
#endif
#ifndef HEIF_MAX_IMAGE_HEIGHT
#define HEIF_MAX_IMAGE_HEIGHT HEIF_MAX_IMAGE_WIDTH
#endif
size_t HEIFHandler::m_initialized_count = 0;
bool HEIFHandler::m_plugins_queried = false;
bool HEIFHandler::m_heif_decoder_available = false;
@@ -72,6 +95,7 @@ static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx
HEIFHandler::HEIFHandler()
: m_parseState(ParseHeicNotParsed)
, m_quality(100)
, m_orientation(0)
{
}
@@ -123,15 +147,16 @@ bool HEIFHandler::write(const QImage &image)
return false;
}
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (image.width() >= HEIF_MAX_IMAGE_WIDTH || image.height() >= HEIF_MAX_IMAGE_HEIGHT) {
qCWarning(LOG_HEIFPLUGIN) << "Image size invalid:" << image.width() << "x" << image.height();
return false;
}
startHeifLib();
#endif
bool success = write_helper(image);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
finishHeifLib();
#endif
return success;
}
@@ -163,12 +188,10 @@ bool HEIFHandler::write_helper(const QImage &image)
}
heif_compression_format encoder_codec = heif_compression_HEVC;
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (format() == "hej2") {
encoder_codec = heif_compression_JPEG2000;
save_depth = 8; // for compatibility reasons
}
#endif
heif_chroma chroma;
if (save_depth > 8) {
@@ -325,12 +348,21 @@ bool HEIFHandler::write_helper(const QImage &image)
}
}
if (m_orientation >= 1 && m_orientation <= 8) {
// Function available from HEIF v1.14
encoder_options->image_orientation = heif_orientation(m_orientation);
}
struct heif_image_handle *handle;
err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle);
// exif metadata
if (err.code == heif_error_Ok) {
auto exif = MicroExif::fromImage(tmpimage);
if (m_orientation >= 1 && m_orientation <= 8) {
// EXIF orientation must be coherent with HEIF orientation
exif.setOrientation(m_orientation);
}
if (!exif.isEmpty()) {
auto ba = exif.toByteArray();
err = heif_context_add_exif_metadata(context, handle, ba.constData(), ba.size());
@@ -376,6 +408,74 @@ bool HEIFHandler::write_helper(const QImage &image)
return true;
}
bool HEIFHandler::read_orientation_helper(void *heif_handle, const void *heif_ctx)
{
if (heif_handle == nullptr || heif_ctx == nullptr) {
return false;
}
auto handle = reinterpret_cast<heif_image_handle *>(heif_handle);
auto ctx = reinterpret_cast<const heif_context *>(heif_ctx);
auto item_id = heif_image_handle_get_item_id(handle);
// get the properties
heif_transform_mirror_direction mirror = heif_transform_mirror_direction::heif_transform_mirror_direction_invalid;
heif_property_id mir_id;
if (heif_item_get_properties_of_type(ctx, item_id, heif_item_property_type_transform_mirror, &mir_id, 1) > 0) {
mirror = heif_item_get_property_transform_mirror(ctx, item_id, mir_id);
if (mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_invalid)
return false;
}
int rotation_ccw = -1;
heif_property_id rot_id;
if (heif_item_get_properties_of_type(ctx, item_id, heif_item_property_type_transform_rotation, &rot_id, 1) > 0) {
rotation_ccw = heif_item_get_property_transform_rotation_ccw(ctx, item_id, rot_id);
if (rotation_ccw == -1)
return false;
}
if (rotation_ccw == -1 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_invalid) {
m_orientation = 0;
} else if (rotation_ccw == 0 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_invalid) {
m_orientation = 1;
} else if (rotation_ccw <= 0 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_horizontal) {
m_orientation = 2;
} else if (rotation_ccw == 180 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_invalid) {
m_orientation = 3;
} else if (rotation_ccw <= 0 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_vertical) {
m_orientation = 4;
} else if (rotation_ccw == 270 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_horizontal) {
m_orientation = 5;
} else if (rotation_ccw == 270 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_invalid) {
m_orientation = 6;
} else if (rotation_ccw == 270 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_vertical) {
m_orientation = 7;
} else if (rotation_ccw == 90 && mirror == heif_transform_mirror_direction::heif_transform_mirror_direction_invalid) {
m_orientation = 8;
}
return true;
}
bool HEIFHandler::read_crop(void *heif_handle, const void *heif_ctx, const QSize &size, QRect &crop)
{
if (heif_handle == nullptr || heif_ctx == nullptr) {
return false;
}
auto handle = reinterpret_cast<heif_image_handle *>(heif_handle);
auto ctx = reinterpret_cast<const heif_context *>(heif_ctx);
auto item_id = heif_image_handle_get_item_id(handle);
heif_property_id crop_id;
if (heif_item_get_properties_of_type(ctx, item_id, heif_item_property_type_transform_crop, &crop_id, 1) > 0) {
int l = 0, t = 0, r = 0, b = 0;
heif_item_get_property_transform_crop_borders(ctx, item_id, crop_id, size.width(), size.height(), &l, &t, &r, &b);
crop = QRect(QPoint(t, l), size - QSize(b + t, r + l));
}
return crop.isValid();
}
bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
{
if (header.size() < 28) {
@@ -456,10 +556,10 @@ QVariant HEIFHandler::option(ImageOption option) const
switch (option) {
case Size:
return m_current_image.size();
break;
case ImageTransformation:
return int(MicroExif::orientationToTransformation(m_orientation));
default:
return QVariant();
break;
}
}
@@ -474,6 +574,9 @@ void HEIFHandler::setOption(ImageOption option, const QVariant &value)
m_quality = 100;
}
break;
case ImageTransformation:
m_orientation = MicroExif::transformationToOrientation(QImageIOHandler::Transformation(value.toUInt()));
break;
default:
QImageIOHandler::setOption(option, value);
break;
@@ -482,7 +585,11 @@ void HEIFHandler::setOption(ImageOption option, const QVariant &value)
bool HEIFHandler::supportsOption(ImageOption option) const
{
return option == Quality || option == Size;
auto ok = option == Quality || option == Size;
#ifndef HEIF_DISABLE_QT_TRANSFORMATION
ok = ok || option == ImageTransformation;
#endif
return ok;
}
bool HEIFHandler::ensureParsed() const
@@ -496,15 +603,12 @@ bool HEIFHandler::ensureParsed() const
HEIFHandler *that = const_cast<HEIFHandler *>(this);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
startHeifLib();
#endif
bool success = that->ensureDecoder();
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
finishHeifLib();
#endif
return success;
}
@@ -589,16 +693,19 @@ bool HEIFHandler::ensureDecoder()
return false;
}
bool ignore_transformations = false;
struct heif_decoding_options *decoder_option = heif_decoding_options_alloc();
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
decoder_option->strict_decoding = 1;
#ifdef HEIF_DISABLE_QT_TRANSFORMATION
decoder_option->ignore_transformations = ignore_transformations;
#else
decoder_option->ignore_transformations = ignore_transformations = read_orientation_helper(handle, ctx);
#endif
struct heif_image *img = nullptr;
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr && buffer.contains("Xiaomi")) {
qCWarning(LOG_HEIFPLUGIN) << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!";
@@ -606,7 +713,6 @@ bool HEIFHandler::ensureDecoder()
decoder_option->strict_decoding = 0;
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
}
#endif
if (decoder_option) {
heif_decoding_options_free(decoder_option);
@@ -623,14 +729,17 @@ bool HEIFHandler::ensureDecoder()
const int imageWidth = heif_image_get_width(img, heif_channel_interleaved);
const int imageHeight = heif_image_get_height(img, heif_channel_interleaved);
QSize imageSize(imageWidth, imageHeight);
QSize imageSize;
if (imageWidth < HEIF_MAX_IMAGE_WIDTH && imageHeight < HEIF_MAX_IMAGE_HEIGHT) {
imageSize = QSize(imageWidth, imageHeight);
}
if (!imageSize.isValid()) {
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qCWarning(LOG_HEIFPLUGIN) << "HEIC image size invalid:" << imageSize;
qCWarning(LOG_HEIFPLUGIN) << "HEIC image size invalid:" << imageWidth << "x" << imageHeight;
return false;
}
@@ -852,6 +961,12 @@ bool HEIFHandler::ensureDecoder()
break;
}
if (ignore_transformations) {
QRect crop_rect;
if (read_crop(handle, ctx, m_current_image.size(), crop_rect))
m_current_image = m_current_image.copy(crop_rect);
}
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle);
if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle);
@@ -1045,34 +1160,27 @@ void HEIFHandler::queryHeifLib()
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);
m_hej2_encoder_available = heif_have_encoder_for_format(heif_compression_JPEG2000);
#endif
#if LIBHEIF_HAVE_VERSION(1, 19, 6)
m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC);
#endif
m_plugins_queried = true;
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_deinit();
}
#endif
}
}
void HEIFHandler::startHeifLib()
{
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
QMutexLocker locker(&getHEIFHandlerMutex());
if (m_initialized_count == 0) {
@@ -1080,12 +1188,10 @@ void HEIFHandler::startHeifLib()
}
m_initialized_count++;
#endif
}
void HEIFHandler::finishHeifLib()
{
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
QMutexLocker locker(&getHEIFHandlerMutex());
if (m_initialized_count == 0) {
@@ -1096,8 +1202,6 @@ void HEIFHandler::finishHeifLib()
if (m_initialized_count == 0) {
heif_deinit();
}
#endif
}
QMutex &HEIFHandler::getHEIFHandlerMutex()

View File

@@ -51,9 +51,24 @@ private:
ParseHeicState m_parseState;
int m_quality;
QImage m_current_image;
quint16 m_orientation;
bool write_helper(const QImage &image);
/*!
* \brief heif_orientation_helper
* Read the transform_mirror and transform_rotation_ccw properties and set \a m_orientation
* \return True on success, otherwise false.
*/
bool read_orientation_helper(void *heif_handle, const void *heif_ctx);
/*!
* \brief read_crop
* Read the crop information.
* \return True on success, otherwise false.
*/
bool read_crop(void *heif_handle, const void *heif_ctx, const QSize& size, QRect &crop);
static void startHeifLib();
static void finishHeifLib();
static void queryHeifLib();

View File

@@ -607,6 +607,69 @@ bool IFFHandler::readRGFXImage(QImage *image)
return true;
}
bool IFFHandler::readDEEPImage(QImage *image)
{
auto forms = d->searchForms<FORMChunk>();
if (forms.isEmpty()) {
return false;
}
auto cin = qBound(0, currentImageNumber(), int(forms.size() - 1));
auto &&form = forms.at(cin);
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<DGBLChunk>(form);
if (headers.isEmpty()) {
return false;
}
// create the image
auto &&header = headers.first();
auto size = header->size();
auto locs = IFFChunk::searchT<DLOCChunk>(form);
if (!locs.isEmpty()) {
size = locs.first()->size();
}
auto img = imageAlloc(size, form->format());
if (img.isNull()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while allocating the image";
return false;
}
// decoding the image
auto bodies = IFFChunk::searchT<DBODChunk>(form);
if (bodies.isEmpty()) {
img.fill(0);
} else {
auto &&body = bodies.first();
if (!body->resetStrideRead(device())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while reading image data";
return false;
}
auto pels = IFFChunk::searchT<DPELChunk>(form);
if (pels.isEmpty()) {
// DPEL is used to calculate the image format, so here should not be empty.
return false;
}
auto tvdcs = IFFChunk::searchT<TVDCChunk>(form);
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), y, header, pels.first(),
locs.isEmpty() ? nullptr : locs.first(),
tvdcs.isEmpty() ? nullptr : tvdcs.first());
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while reading image scanline";
return false;
}
memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size()));
}
}
// set metadata (including image resolution)
addMetadata(img, form);
*image = img;
return true;
}
bool IFFHandler::read(QImage *image)
{
if (!d->readStructure(device())) {
@@ -630,6 +693,10 @@ bool IFFHandler::read(QImage *image)
return true;
}
if (readDEEPImage(image)) {
return true;
}
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found";
return false;
}

View File

@@ -39,6 +39,8 @@ private:
bool readRGFXImage(QImage *image);
bool readDEEPImage(QImage *image);
private:
const QScopedPointer<IFFHandlerPrivate> d;
};

View File

@@ -43,41 +43,39 @@ Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
#endif
/*!
* Support for float images
* \brief JXR_DENY_FLOAT_IMAGE
*
* NOTE: Float images have values greater than 1 so they need an additional in place conversion.
* When defined, disables the support for float images.
* \note Float images have values greater than 1 so they need an additional in place conversion.
*/
#ifndef JXR_DENY_FLOAT_IMAGE
// #define JXR_DENY_FLOAT_IMAGE // default commented
#endif
/*!
* Remove the needs of additional memory by disabling the conversion between
* different color depths (e.g. RGBA64bpp to RGBA32bpp).
* \brief JXR_DISABLE_DEPTH_CONVERSION
*
* NOTE: Leaving deptch conversion enabled (default) ensures maximum read compatibility.
* When defined, removes the needs of additional memory by disabling the conversion between
* different color depths (e.g. RGBA64bpp to RGBA32bpp).
* \note Leaving depth conversion enabled (default) ensures maximum read compatibility.
*/
#ifndef JXR_DISABLE_DEPTH_CONVERSION
// #define JXR_DISABLE_DEPTH_CONVERSION // default commented
#endif
/*!
* \brief JXR_DISABLE_BGRA_HACK
*
* When defined, disables Windows compatibility for BGRs.
*
* Windows displays and opens JXR files correctly out of the box. Unfortunately it doesn't
* seem to open (P)RGBA @32bpp files as it only wants (P)BGRA32bpp files (a format not supported by Qt).
* Only for this format an hack is activated to guarantee total compatibility of the plugin with Windows.
* Only for this format, an hack is activated to guarantee total compatibility of the plugin with Windows,
* at the cost of some overhead.
*/
#ifndef JXR_DISABLE_BGRA_HACK
// #define JXR_DISABLE_BGRA_HACK // default commented
/*!
* The following functions are present in the Debian headers but not in the SUSE ones even if the source version is 1.0.1 on both.
*
* - ERR PKImageDecode_GetXMPMetadata_WMP(PKImageDecode *pID, U8 *pbXMPMetadata, U32 *pcbXMPMetadata);
* - ERR PKImageDecode_GetEXIFMetadata_WMP(PKImageDecode *pID, U8 *pbEXIFMetadata, U32 *pcbEXIFMetadata);
* - ERR PKImageDecode_GetGPSInfoMetadata_WMP(PKImageDecode *pID, U8 *pbGPSInfoMetadata, U32 *pcbGPSInfoMetadata);
* - ERR PKImageDecode_GetIPTCNAAMetadata_WMP(PKImageDecode *pID, U8 *pbIPTCNAAMetadata, U32 *pcbIPTCNAAMetadata);
* - ERR PKImageDecode_GetPhotoshopMetadata_WMP(PKImageDecode *pID, U8 *pbPhotoshopMetadata, U32 *pcbPhotoshopMetadata);
*
* As a result, their use is disabled by default. It is possible to activate their use by defining the
* JXR_ENABLE_ADVANCED_METADATA preprocessor directive
*/
// #define JXR_ENABLE_ADVANCED_METADATA
#endif
/* *** JXR_MAX_IMAGE_WIDTH and JXR_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
@@ -89,13 +87,25 @@ Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
#define JXR_MAX_IMAGE_HEIGHT JXR_MAX_IMAGE_WIDTH
#endif
#ifndef JXR_MAX_METADATA_SIZE
/*!
* XMP and EXIF maximum size.
* \brief JXR_MAX_METADATA_SIZE
*
* XMP and EXIF maximum size in bytes.
*/
#ifndef JXR_MAX_METADATA_SIZE
#define JXR_MAX_METADATA_SIZE (4 * 1024 * 1024)
#endif
/*
* Compatibility with older libraries
*/
#ifndef JXR_MAKEVERSION
#define JXR_MAKEVERSION(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))
#endif
#ifndef JXR_VERSION
#define JXR_VERSION JXR_MAKEVERSION(1, 1, 0)
#endif
class JXRHandlerPrivate : public QSharedData
{
private:
@@ -461,7 +471,7 @@ public:
if (pDecoder == nullptr) {
return xmp;
}
#ifdef JXR_ENABLE_ADVANCED_METADATA
#if JXR_VERSION >= JXR_MAKEVERSION(1, 4, 0)
quint32 size = 0;
if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, nullptr, &size) && size > 0 && size < JXR_MAX_METADATA_SIZE) {
QByteArray ba(size, 0);
@@ -963,6 +973,12 @@ private:
if (pCodecFactory == nullptr) {
return false;
}
#if JXR_VERSION >= JXR_MAKEVERSION(1, 4, 0)
// Prevents the library from making single large memory allocations.
// Note that it may still exceed it with multiple allocations.
PKAlloc_SetLimit(size_t(QImageReader::allocationLimit()) * 1024 * 1024);
#endif
if (auto err = pCodecFactory->CreateDecoderFromFile(qUtf8Printable(fileName()), &pDecoder)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::initDecoder() unable to create decoder:" << err;
return false;
@@ -996,7 +1012,7 @@ private:
return true;
}
DESCRIPTIVEMETADATA meta;
DESCRIPTIVEMETADATA meta = {};
if (pDecoder->GetDescriptiveMetadata(pDecoder, &meta)) {
return false;
}
@@ -1031,7 +1047,7 @@ bool JXRHandler::read(QImage *outImage)
PKPixelFormatGUID convFmt;
auto imageFmt = d->imageFormat(&convFmt);
auto img = imageAlloc(d->imageSize(), imageFmt);
auto img = imageAlloc(d->imageSize(), imageFmt, ImageInitToZero::FPOnly);
if (img.isNull()) {
return false;
}

View File

@@ -744,7 +744,42 @@ void MicroExif::setOrientation(quint16 orient)
QImageIOHandler::Transformation MicroExif::transformation() const
{
switch (orientation()) {
return orientationToTransformation(orientation());
}
void MicroExif::setTransformation(const QImageIOHandler::Transformation &t)
{
setOrientation(transformationToOrientation(t));
}
quint16 MicroExif::transformationToOrientation(const QImageIOHandler::Transformation &t)
{
switch (t) {
case QImageIOHandler::TransformationNone:
return 1;
case QImageIOHandler::TransformationMirror:
return 2;
case QImageIOHandler::TransformationRotate180:
return 3;
case QImageIOHandler::TransformationFlip:
return 4;
case QImageIOHandler::TransformationFlipAndRotate90:
return 5;
case QImageIOHandler::TransformationRotate90:
return 6;
case QImageIOHandler::TransformationMirrorAndRotate90:
return 7;
case QImageIOHandler::TransformationRotate270:
return 8;
default:
break;
}
return 0; // no orientation set
}
QImageIOHandler::Transformation MicroExif::orientationToTransformation(quint16 o)
{
switch (o) {
case 1:
return QImageIOHandler::TransformationNone;
case 2:
@@ -767,39 +802,6 @@ QImageIOHandler::Transformation MicroExif::transformation() const
return QImageIOHandler::TransformationNone;
}
void MicroExif::setTransformation(const QImageIOHandler::Transformation &t)
{
switch (t) {
case QImageIOHandler::TransformationNone:
setOrientation(1);
break;
case QImageIOHandler::TransformationMirror:
setOrientation(2);
break;
case QImageIOHandler::TransformationRotate180:
setOrientation(3);
break;
case QImageIOHandler::TransformationFlip:
setOrientation(4);
break;
case QImageIOHandler::TransformationFlipAndRotate90:
setOrientation(5);
break;
case QImageIOHandler::TransformationRotate90:
setOrientation(6);
break;
case QImageIOHandler::TransformationMirrorAndRotate90:
setOrientation(7);
break;
case QImageIOHandler::TransformationRotate270:
setOrientation(8);
break;
default:
break;
}
setOrientation(0); // no orientation set
}
QString MicroExif::software() const
{
return tiffString(TIFF_SOFTWARE);

View File

@@ -210,6 +210,19 @@ public:
QImageIOHandler::Transformation transformation() const;
void setTransformation(const QImageIOHandler::Transformation& t);
/*!
* \brief transformationToOrientation
* \param t The Qt transformation.
* \return The EXIF orientation value or 0 if none.
*/
static quint16 transformationToOrientation(const QImageIOHandler::Transformation& t);
/*!
* \brief orientationToTransformation
* \param o The EXIF orientation.
* \return The orientation converted in the equivalent Qt transformation.
*/
static QImageIOHandler::Transformation orientationToTransformation(quint16 o);
/*!
* \brief software
* \return Name and version number of the software package(s) used to create the image.

View File

@@ -65,11 +65,11 @@ public:
inline int width() const
{
return (XMax - XMin) + 1;
return int(XMax - XMin) + 1;
}
inline int height() const
{
return (YMax - YMin) + 1;
return int(YMax - YMin) + 1;
}
inline bool isCompressed() const
{

View File

@@ -35,7 +35,7 @@
* The maximum size in pixel allowed by the plugin.
*/
#ifndef RAW_MAX_IMAGE_WIDTH
#define RAW_MAX_IMAGE_WIDTH std::min(65535, KIF_LARGE_IMAGE_PIXEL_LIMIT)
#define RAW_MAX_IMAGE_WIDTH KIF_64K_IMAGE_PIXEL_LIMIT
#endif
#ifndef RAW_MAX_IMAGE_HEIGHT
#define RAW_MAX_IMAGE_HEIGHT RAW_MAX_IMAGE_WIDTH
@@ -759,8 +759,8 @@ bool LoadRAW(QImageIOHandler *handler, QImage &img)
// *** Limiting the maximum image size on a reasonable size
auto size = rawImageSize(rawProcessor.get());
if (size.width() > RAW_MAX_IMAGE_WIDTH || size.height() > RAW_MAX_IMAGE_HEIGHT) {
qCWarning(LOG_RAWPLUGIN) << "The maximum image size is limited to" << RAW_MAX_IMAGE_WIDTH << "x" << RAW_MAX_IMAGE_HEIGHT << "px";
if (size.width() >= RAW_MAX_IMAGE_WIDTH || size.height() >= RAW_MAX_IMAGE_HEIGHT) {
qCWarning(LOG_RAWPLUGIN) << "The maximum image size is limited to" << (RAW_MAX_IMAGE_WIDTH - 1) << "x" << (RAW_MAX_IMAGE_HEIGHT - 1) << "px";
return false;
}

View File

@@ -13,12 +13,21 @@
#include <QImage>
#include <QImageIOHandler>
#include <QIODevice>
#include <QPixelFormat>
// Default maximum width and height for the large image plugins.
#ifndef KIF_LARGE_IMAGE_PIXEL_LIMIT
#define KIF_LARGE_IMAGE_PIXEL_LIMIT 300000
#endif
// Maximum size for legacy image formats.
#define KIF_64K_IMAGE_PIXEL_LIMIT 65536
#if KIF_LARGE_IMAGE_PIXEL_LIMIT < KIF_64K_IMAGE_PIXEL_LIMIT
#undef KIF_LARGE_IMAGE_PIXEL_LIMIT
#define KIF_LARGE_IMAGE_PIXEL_LIMIT KIF_64K_IMAGE_PIXEL_LIMIT
#endif
// Image metadata keys to use in plugins (so they are consistent)
#define META_KEY_ALTITUDE "Altitude"
#define META_KEY_AUTHOR "Author"
@@ -63,20 +72,50 @@
// QList uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
// On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit()
// it is necessary to allocate the image with QImageIOHandler::allocateImage().
inline QImage imageAlloc(const QSize &size, const QImage::Format &format)
/*!
* \brief The ImageInitToZero enum
*/
enum class ImageInitToZero
{
None, /**< Do not initialize the image. */
All, /**< Initialize all images. */
PremulOnly, /**< Initialize only images with premultiplied alpha. */
FPOnly, /**< Initialize only images with floating point format. */
FPAndPremul /**< Initialize floating point and premultiplied formats. */
};
/*!
* \brief imageAlloc
* Helper function to initialize framework images.
* \param size The image size.
* \param format The image format,
* \param init Whether and which images should be initialized to zero.
* \return The allocated image or a null image on error.
*/
inline QImage imageAlloc(const QSize &size, const QImage::Format &format, const ImageInitToZero& init = ImageInitToZero::None)
{
QImage img;
if (!QImageIOHandler::allocateImage(size, format, &img)) {
img = QImage(); // paranoia
}
if (init != ImageInitToZero::None && !img.isNull()) {
auto pixelFormat = img.pixelFormat();
auto isFloat = pixelFormat.typeInterpretation() == QPixelFormat::FloatingPoint;
auto isPremul = pixelFormat.premultiplied();
if (init == ImageInitToZero::All) {
img.fill(0);
} else if (isFloat && (init == ImageInitToZero::FPOnly || init == ImageInitToZero::FPAndPremul)) {
img.fill(0);
} else if (isPremul && (init == ImageInitToZero::PremulOnly || init == ImageInitToZero::FPAndPremul)) {
img.fill(0);
}
}
return img;
}
inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format)
inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format, const ImageInitToZero& init = ImageInitToZero::None)
{
return imageAlloc(QSize(width, height), format);
return imageAlloc(QSize(width, height), format, init);
}
template<class TI, class SF> // SF = source FP, TI = target INT