Compare commits
16 Commits
v6.26.0
...
v6.27.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2791c2543b | ||
|
|
a2a8c955df | ||
|
|
f450e5c9a9 | ||
|
|
9dfcf67ea9 | ||
|
|
6017099044 | ||
|
|
9ddad16767 | ||
|
|
0e2b137b32 | ||
|
|
6d5e61f0b0 | ||
|
|
d7c3174fb6 | ||
|
|
49a8fd38c8 | ||
|
|
1206598337 | ||
|
|
3488077d8d | ||
|
|
ea8a4dccdc | ||
|
|
8048279473 | ||
|
|
18d0f93d60 | ||
|
|
497e612ad3 |
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
39
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -162,6 +162,7 @@ HANDLER_TYPES="ANIHandler ani
|
||||
QAVIFHandler avif
|
||||
QDDSHandler dds
|
||||
EXRHandler exr
|
||||
FFHandler ff
|
||||
HDRHandler hdr
|
||||
HEIFHandler heif
|
||||
IFFHandler iff
|
||||
|
||||
@@ -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;
|
||||
|
||||
BIN
autotests/read/ff/testcard_rgba16.ff
Normal file
BIN
autotests/read/ff/testcard_rgba16.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
autotests/read/heif/crop_orientation1.heif
Normal file
BIN
autotests/read/heif/crop_orientation1.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
autotests/read/heif/crop_orientation2.heif
Normal file
BIN
autotests/read/heif/crop_orientation2.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
autotests/read/heif/crop_orientation3.heif
Normal file
BIN
autotests/read/heif/crop_orientation3.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/heif/crop_orientation4.heif
Normal file
BIN
autotests/read/heif/crop_orientation4.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
autotests/read/heif/crop_orientation5.heif
Normal file
BIN
autotests/read/heif/crop_orientation5.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
autotests/read/heif/crop_orientation6.heif
Normal file
BIN
autotests/read/heif/crop_orientation6.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
autotests/read/heif/crop_orientation7.heif
Normal file
BIN
autotests/read/heif/crop_orientation7.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/heif/crop_orientation8.heif
Normal file
BIN
autotests/read/heif/crop_orientation8.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
autotests/read/heif/orientation1.heif
Normal file
17
autotests/read/heif/orientation1.heif.json
Normal 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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation2.heif
Normal file
11
autotests/read/heif/orientation2.heif.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.20.35 (Linux)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation3.heif
Normal file
11
autotests/read/heif/orientation3.heif.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.20.35 (Linux)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation4.heif
Normal file
11
autotests/read/heif/orientation4.heif.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.20.35 (Linux)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation5.heif
Normal file
11
autotests/read/heif/orientation5.heif.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.20.35 (Linux)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation6.heif
Normal file
11
autotests/read/heif/orientation6.heif.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.20.35 (Linux)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation7.heif
Normal file
11
autotests/read/heif/orientation7.heif.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.20.35 (Linux)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation8.heif
Normal file
11
autotests/read/heif/orientation8.heif.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.20.35 (Linux)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/orientation_all.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/iff/sv5_unc_rgb8_deep.iff
Normal file
BIN
autotests/read/iff/sv5_unc_rgb8_deep.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
autotests/read/iff/sv5_unc_rgba16_deep.iff
Normal file
BIN
autotests/read/iff/sv5_unc_rgba16_deep.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/iff/sv5_unc_rgba8_deep.iff
Normal file
BIN
autotests/read/iff/sv5_unc_rgba8_deep.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
@@ -73,6 +73,10 @@ endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_ff SOURCES ff.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ***
|
||||
* ****************** */
|
||||
|
||||
@@ -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 ***
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"Keys": [ "dds" ],
|
||||
"MimeTypes": [ "image/x-dds" ]
|
||||
"Keys": [ "dds", "dds" ],
|
||||
"MimeTypes": [ "image/vnd.ms-dds", "image/x-dds" ]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Keys": [ "ff" ],
|
||||
"MimeTypes": [ "image/x-farbfeld" ]
|
||||
}
|
||||
42
src/imageformats/ff_p.h
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ private:
|
||||
|
||||
bool readRGFXImage(QImage *image);
|
||||
|
||||
bool readDEEPImage(QImage *image);
|
||||
|
||||
private:
|
||||
const QScopedPointer<IFFHandlerPrivate> d;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||