Compare commits
29 Commits
v6.25.0-rc
...
v6.27.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2791c2543b | ||
|
|
a2a8c955df | ||
|
|
f450e5c9a9 | ||
|
|
9dfcf67ea9 | ||
|
|
6017099044 | ||
|
|
9ddad16767 | ||
|
|
0e2b137b32 | ||
|
|
6d5e61f0b0 | ||
|
|
d7c3174fb6 | ||
|
|
49a8fd38c8 | ||
|
|
1206598337 | ||
|
|
3488077d8d | ||
|
|
ea8a4dccdc | ||
|
|
8048279473 | ||
|
|
18d0f93d60 | ||
|
|
497e612ad3 | ||
|
|
84f28e8bc9 | ||
|
|
a936927ec1 | ||
|
|
191e5e6a69 | ||
|
|
51db11eefc | ||
|
|
bc398382ac | ||
|
|
d5e5012cfb | ||
|
|
1b3f32a332 | ||
|
|
7cf60da031 | ||
|
|
d160f268e7 | ||
|
|
276338199a | ||
|
|
742b5097f6 | ||
|
|
2d2ee68cc0 | ||
|
|
d15c3c679d |
@@ -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:
|
||||
|
||||
@@ -7,5 +7,5 @@ Dependencies:
|
||||
Options:
|
||||
test-before-installing: True
|
||||
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
|
||||
cmake-options: "-DKIMAGEFORMATS_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
|
||||
cmake-options: "-DKIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
|
||||
per-test-timeout: 90
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
|
||||
set(KF_VERSION "6.25.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.25.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.25.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")
|
||||
|
||||
@@ -97,16 +97,17 @@ set_package_properties(LibRaw PROPERTIES
|
||||
PURPOSE "Required for the QImage plugin for RAW images"
|
||||
)
|
||||
|
||||
# JXR plugin disabled by default due to security issues
|
||||
option(KIMAGEFORMATS_JXR "Enable plugin for JPEG XR format" OFF)
|
||||
if(KIMAGEFORMATS_JXR)
|
||||
# JXR plugin disabled by default due to security issues.
|
||||
# You should not enable it unless you know what you are doing.
|
||||
option(KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR "Enable plugin for JPEG XR format" OFF)
|
||||
if(KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR)
|
||||
find_package(LibJXR)
|
||||
endif()
|
||||
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
|
||||
|
||||
ecm_set_disabled_deprecation_versions(
|
||||
QT 6.11.0
|
||||
KF 6.23.0
|
||||
KF 6.26.0
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
90
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)
|
||||
@@ -155,8 +156,31 @@ About the image:
|
||||
- `Owner`: Name of the owner of the image.
|
||||
- `Software`: Name and version number of the software package(s) used to
|
||||
create the image.
|
||||
- `Speed`: Floating-point number indicating the speed of GPS receiver
|
||||
movement in Km/h (e.g. 30.2).
|
||||
- `Title`: The title of the image.
|
||||
|
||||
About the shot:
|
||||
- `DigitalZoomRatio`: Floating-point number indicating the digital zoom ratio
|
||||
when the image was shot.
|
||||
- `ExposureMode`: Integer number indicating the exposure mode set when the
|
||||
image was shot as reported in the EXIF specifications.
|
||||
- `ExposureProgram`: Integer number indicating the class of the program used
|
||||
by the camera to set exposure when the picture is taken as reported in the
|
||||
EXIF specifications.
|
||||
- `ExposureTime`: Floating-point number indicating the exposure time,
|
||||
given in seconds (s).
|
||||
- `Flash`: Integer number indicating the status of flash when the image
|
||||
was shot as reported in the EXIF specifications.
|
||||
- `FNumber`: Floating-point number indicating the F number.
|
||||
- `FocalLength`: Floating-point number indicating the actual focal length
|
||||
of the lens, in millimeters (mm).
|
||||
- `ISOSpeedRatings`: Integer number indicating the sensitivity of the camera
|
||||
or input device when the image was shot as reported in the EXIF
|
||||
specifications.
|
||||
- `WhiteBalance`: Integer number indicating the white balance mode set when
|
||||
the image was shot as reported in the EXIF specifications.
|
||||
|
||||
About the camera:
|
||||
- `Manufacturer`: The manufacturer of the recording equipment.
|
||||
- `Model`: The model name or model number of the recording equipment.
|
||||
@@ -220,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
|
||||
@@ -274,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
|
||||
|
||||
@@ -295,6 +331,11 @@ plugin:
|
||||
- `DDS_DISABLE_STRIDE_ALIGNMENT`: disable the stride alignment based on DDS
|
||||
pitch: it is known that some writers do not set it correctly.
|
||||
|
||||
When writing, it is possible to set which pixel format to use by setting the
|
||||
subtypes. The default is `Automatic` which chooses the most appropriate format
|
||||
based on the image. For a complete list of subformats, please use the
|
||||
appropriate [`QImageWriter`](https://doc.qt.io/qt-6/qimagewriter.html) APIs.
|
||||
|
||||
### The HEIF plugin
|
||||
|
||||
**This plugin is disabled by default. It can be enabled by settings
|
||||
@@ -305,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
|
||||
@@ -324,6 +374,10 @@ plugin:
|
||||
attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable
|
||||
writes it as well.
|
||||
|
||||
The plugin can set the following additional metadata:
|
||||
- `EXRLayerName`: A string containing the name of the EXR layer used to decode
|
||||
the image.
|
||||
|
||||
### The EPS plugin
|
||||
|
||||
The plugin uses `Ghostscript` to convert the raster image. When reading it
|
||||
@@ -362,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.
|
||||
|
||||
@@ -379,6 +434,11 @@ JP2 plugin has the following limitations due to the lack of support by OpenJPEG:
|
||||
- Image resolution is not supported.
|
||||
- To write ICC profiles you need OpenJPEG V2.5.4 or higher
|
||||
|
||||
When writing, it is possible to set which format to use by setting the
|
||||
following subtypes:
|
||||
- `JP2` (default): Save data using the JP2 container.
|
||||
- `J2K`: Save only the compressed codestream.
|
||||
|
||||
### The JXL plugin
|
||||
|
||||
**The current version of the plugin limits the image size to 256 megapixels
|
||||
@@ -394,7 +454,17 @@ plugin:
|
||||
### The JXR plugin
|
||||
|
||||
**This plugin is disabled by default. It can be enabled by settings
|
||||
`KIMAGEFORMATS_JXR` to `ON` in your cmake options.**
|
||||
`KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR` to `ON` in your cmake options.**
|
||||
|
||||
> [!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:
|
||||
@@ -408,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
|
||||
|
||||
@@ -443,6 +510,13 @@ plugin:
|
||||
- `PSD_NATIVE_CMYK_SUPPORT_DISABLED`: disable native support for CMYK images
|
||||
when compiled with Qt 6.8+
|
||||
|
||||
The plugin can set the following additional metadata:
|
||||
- `PSDDuotoneOptions`: Byte array in hexadecimal format of color data of the
|
||||
duotone specification (the format of which is not documented). From the PSD
|
||||
specification: *"Other applications that read Photoshop files can treat a
|
||||
duotone image as a gray image, and just preserve the contents of the duotone
|
||||
information when reading and writing the file."*
|
||||
|
||||
### The RAW plugin
|
||||
|
||||
Loading RAW images always requires a conversion. To allow the user to
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,7 +48,7 @@ Depending on the format, you can specify the following additional options.
|
||||
|
||||
- `--help`: Displays help on commandline options.
|
||||
- `--fuzz <max>`: The fuzziness. Used to add some deviation in ARGB data
|
||||
(nornally used on lossy codec).
|
||||
(normally used on lossy codec).
|
||||
- `--perceptive-fuzz`: Used to scale dynamically the fuzziness based on
|
||||
the alpha channel value. This is useful on images with pre-multiplied and
|
||||
small alphas. Qt can use different roundings based on optimizations resulting
|
||||
|
||||
@@ -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/exr/ps2026_testcard_rgb.exr
Normal file
15
autotests/read/exr/ps2026_testcard_rgb.exr.json
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "ps2026_testcard_rgb.png",
|
||||
"colorSpace" : {
|
||||
"description" : "sRGB build-in (Profilo RGB lineare)",
|
||||
"primaries" : "SRgb",
|
||||
"transferFunction" : "Linear",
|
||||
"gamma" : 1
|
||||
},
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 3937,
|
||||
"dotsPerMeterY" : 3937
|
||||
}
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/exr/ps2026_testcard_rgb.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"fileName" : "rgb-gimp.png",
|
||||
"colorSpace" : {
|
||||
"description" : "",
|
||||
"description" : "Embedded RGB (linear)",
|
||||
"primaries" : "Custom",
|
||||
"transferFunction" : "Linear",
|
||||
"gamma" : 1
|
||||
|
||||
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 |
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
"value" : "LIFE Pro 2.20.35"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
@@ -32,6 +32,10 @@
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "HostComputer",
|
||||
"value" : "Windows 11 Enterprise (25H2)"
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
|
||||
@@ -13,14 +13,18 @@
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-14T15:58:44+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 26.2 (Windows)"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Title",
|
||||
"value" : "A test"
|
||||
},
|
||||
{
|
||||
"key" : "Software",
|
||||
"value" : "KImageFormats write test"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
@@ -45,6 +49,10 @@
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "LensSerialNumber",
|
||||
"value" : "S/N:1234567"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
@@ -56,6 +64,50 @@
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
},
|
||||
{
|
||||
"key" : "SerialNumber",
|
||||
"value" : "S/N:7654321"
|
||||
},
|
||||
{
|
||||
"key" : "Speed",
|
||||
"value" : "13.2"
|
||||
},
|
||||
{
|
||||
"key" : "DigitalZoomRatio",
|
||||
"value" : "3.4"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureMode",
|
||||
"value" : "2"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureProgram",
|
||||
"value" : "6"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureTime",
|
||||
"value" : "0.004"
|
||||
},
|
||||
{
|
||||
"key" : "Flash",
|
||||
"value" : "16"
|
||||
},
|
||||
{
|
||||
"key" : "FNumber",
|
||||
"value" : "1.6"
|
||||
},
|
||||
{
|
||||
"key" : "FocalLength",
|
||||
"value" : "5.96"
|
||||
},
|
||||
{
|
||||
"key" : "ISOSpeedRatings",
|
||||
"value" : "50"
|
||||
},
|
||||
{
|
||||
"key" : "WhiteBalance",
|
||||
"value" : "1"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "LensSerialNumber",
|
||||
"value" : "S/N:1234567"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
@@ -52,6 +56,50 @@
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
},
|
||||
{
|
||||
"key" : "SerialNumber",
|
||||
"value" : "S/N:7654321"
|
||||
},
|
||||
{
|
||||
"key" : "Speed",
|
||||
"value" : "13.2"
|
||||
},
|
||||
{
|
||||
"key" : "DigitalZoomRatio",
|
||||
"value" : "3.4"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureMode",
|
||||
"value" : "2"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureProgram",
|
||||
"value" : "6"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureTime",
|
||||
"value" : "0.004"
|
||||
},
|
||||
{
|
||||
"key" : "Flash",
|
||||
"value" : "16"
|
||||
},
|
||||
{
|
||||
"key" : "FNumber",
|
||||
"value" : "1.6"
|
||||
},
|
||||
{
|
||||
"key" : "FocalLength",
|
||||
"value" : "5.96"
|
||||
},
|
||||
{
|
||||
"key" : "ISOSpeedRatings",
|
||||
"value" : "50"
|
||||
},
|
||||
{
|
||||
"key" : "WhiteBalance",
|
||||
"value" : "1"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
|
||||
@@ -62,7 +62,7 @@ void setOptionalInfo(QImage &image, const QString &suffix)
|
||||
|
||||
// Set metadata
|
||||
auto meta = obj.value("metadata").toArray();
|
||||
for (auto jv : meta) {
|
||||
for (auto &&jv : meta) {
|
||||
auto obj = jv.toObject();
|
||||
auto key = obj.value("key").toString();
|
||||
auto val = obj.value("value").toString();
|
||||
@@ -106,7 +106,7 @@ bool checkOptionalInfo(QImage &image, const QString &suffix)
|
||||
|
||||
// Test metadata
|
||||
auto meta = obj.value("metadata").toArray();
|
||||
for (auto jv : meta) {
|
||||
for (auto &&jv : meta) {
|
||||
auto obj = jv.toObject();
|
||||
auto key = obj.value("key").toString();
|
||||
auto val = obj.value("value").toString();
|
||||
|
||||
@@ -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" ]
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include <ImathBox.h>
|
||||
#include <ImfArray.h>
|
||||
#include <ImfBoxAttribute.h>
|
||||
#include <ImfOpaqueAttribute.h>
|
||||
#include <ImfChannelListAttribute.h>
|
||||
#include <ImfCompressionAttribute.h>
|
||||
#include <ImfConvert.h>
|
||||
@@ -208,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
|
||||
@@ -227,25 +230,57 @@ static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||
|
||||
/*!
|
||||
* \brief viewList
|
||||
* \param header
|
||||
* \param header The image header.
|
||||
* \return The list of available views.
|
||||
* \note This plugin does not support compositing layers which are returned as single images.
|
||||
*/
|
||||
static QStringList viewList(const Imf::Header &h)
|
||||
{
|
||||
QStringList l;
|
||||
if (auto views = h.findTypedAttribute<Imf::StringVectorAttribute>("multiView")) {
|
||||
// Internally OpenEXR first checks if the multiView attribute is present:
|
||||
// if present, I have no other layers.
|
||||
for (auto &&v : views->value()) {
|
||||
l << QString::fromStdString(v);
|
||||
}
|
||||
} else {
|
||||
// Recent versions of Photoshop save images by setting the layer.
|
||||
// Channels are named Layer 1.A, Layer 1.B, etc., so I have to set
|
||||
// the layer or the images will appear black.
|
||||
auto channels = h.channels();
|
||||
for (auto i = channels.begin(); i != channels.end(); ++i) {
|
||||
auto name = QString::fromLatin1(i.name(), -1);
|
||||
auto idx = name.indexOf(QChar(u'.'));
|
||||
if (idx > -1)
|
||||
l << name.left(idx);
|
||||
}
|
||||
l.removeDuplicates();
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static QString setLayerName(Imf::RgbaInputFile &file, qint32 imageNumber = -1)
|
||||
{
|
||||
// set the image to load
|
||||
QString layerName;
|
||||
auto &&header = file.header();
|
||||
if (imageNumber > -1) {
|
||||
auto views = viewList(header);
|
||||
if (imageNumber < views.count())
|
||||
layerName = views.at(imageNumber);
|
||||
}
|
||||
// set the layer name
|
||||
if (!layerName.isEmpty()) {
|
||||
file.setLayerName(layerName.toStdString());
|
||||
}
|
||||
return layerName;
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
static void printAttributes(const Imf::Header &h)
|
||||
{
|
||||
for (auto i = h.begin(); i != h.end(); ++i) {
|
||||
qCDebug(LOG_EXRPLUGIN) << i.name();
|
||||
qCDebug(LOG_EXRPLUGIN) << i.name() << i.attribute().typeName();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -340,15 +375,29 @@ static void readColorSpace(const Imf::Header &header, QImage &image)
|
||||
{
|
||||
// final color operations
|
||||
QColorSpace cs;
|
||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||
auto &&v = chroma->value();
|
||||
cs = QColorSpace(QPointF(v.white.x, v.white.y),
|
||||
QPointF(v.red.x, v.red.y),
|
||||
QPointF(v.green.x, v.green.y),
|
||||
QPointF(v.blue.x, v.blue.y),
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
|
||||
// Photoshop 2026 allow to save the ICC profile as "iccProfile" attribute
|
||||
if (auto iccProfile = header.findTypedAttribute<Imf::OpaqueAttribute>("iccProfile")) {
|
||||
auto &&v = iccProfile->data();
|
||||
cs = QColorSpace::fromIccProfile(QByteArray::fromRawData(v, v.size()));
|
||||
}
|
||||
|
||||
if (!cs.isValid()) {
|
||||
// Creating the ICC profile from Chromaticities
|
||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||
auto &&v = chroma->value();
|
||||
cs = QColorSpace(QPointF(v.white.x, v.white.y),
|
||||
QPointF(v.red.x, v.red.y),
|
||||
QPointF(v.green.x, v.green.y),
|
||||
QPointF(v.blue.x, v.blue.y),
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
if (cs.isValid())
|
||||
cs.setDescription(QStringLiteral("Embedded RGB (linear)"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!cs.isValid()) {
|
||||
// Use a linear profile
|
||||
cs = QColorSpace(QColorSpace::SRgbLinear);
|
||||
}
|
||||
image.setColorSpace(cs);
|
||||
@@ -377,12 +426,7 @@ bool EXRHandler::read(QImage *outImage)
|
||||
auto &&header = file.header();
|
||||
|
||||
// set the image to load
|
||||
if (m_imageNumber > -1) {
|
||||
auto views = viewList(header);
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
auto layerName = setLayerName(file, m_imageNumber);
|
||||
|
||||
// get image info
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
@@ -401,6 +445,9 @@ bool EXRHandler::read(QImage *outImage)
|
||||
qCWarning(LOG_EXRPLUGIN) << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||
return false;
|
||||
}
|
||||
if (!layerName.isEmpty()) {
|
||||
image.setText(QStringLiteral("EXRLayerName"), layerName);
|
||||
}
|
||||
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||
@@ -688,12 +735,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
try {
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
if (m_imageNumber > -1) { // set the image to read
|
||||
auto views = viewList(file.header());
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
setLayerName(file, m_imageNumber);
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
v = QVariant(QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
|
||||
} catch (const std::exception &) {
|
||||
@@ -713,6 +755,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
try {
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
setLayerName(file, m_imageNumber);
|
||||
v = QVariant::fromValue(imageFormat(file));
|
||||
} catch (const std::exception &) {
|
||||
// broken file or unsupported version
|
||||
@@ -787,12 +830,9 @@ bool EXRHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if OPENEXR_VERSION_MAJOR == 3 && OPENEXR_VERSION_MINOR > 2
|
||||
// openexpr >= 3.3 uses seek and tell extensively
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
const QByteArray head = device->peek(4);
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ private:
|
||||
* - 7: lossy 4-by-4 pixel block compression, fields are compressed more
|
||||
* - 8: lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access.
|
||||
* - 9: lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION.
|
||||
* - 10: High-Throughput JPEG2000 (HTJ2K), 256 scanlines (requires OpenEXR 3.4+).
|
||||
* - 11: High-Throughput JPEG2000 (HTJ2K), 32 scanlines (requires OpenEXR 3.4+).
|
||||
*/
|
||||
qint32 m_compressionRatio;
|
||||
|
||||
|
||||
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
|
||||
@@ -54,10 +54,10 @@ public:
|
||||
{
|
||||
return width() > 0 && height() > 0 && width() <= HDR_MAX_IMAGE_WIDTH && height() <= HDR_MAX_IMAGE_HEIGHT;
|
||||
}
|
||||
qint32 width() const { return(m_size.width()); }
|
||||
qint32 height() const { return(m_size.height()); }
|
||||
QString software() const { return(m_software); }
|
||||
QImageIOHandler::Transformations transformation() const { return(m_transformation); }
|
||||
qint32 width() const { return m_size.width(); }
|
||||
qint32 height() const { return m_size.height(); }
|
||||
QString software() const { return m_software; }
|
||||
QImageIOHandler::Transformations transformation() const { return m_transformation; }
|
||||
|
||||
/*!
|
||||
* \brief colorSpace
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
* 0.600 0.150 0.060 0.333 0.333" for red, green, blue
|
||||
* and white, respectively.
|
||||
*/
|
||||
QColorSpace colorSpace() const { return(m_colorSpace); }
|
||||
QColorSpace colorSpace() const { return m_colorSpace; }
|
||||
|
||||
/*!
|
||||
* \brief exposure
|
||||
@@ -247,7 +247,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
s >> image[2];
|
||||
s >> image[3];
|
||||
|
||||
if (s.atEnd()) {
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -340,20 +340,24 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
|
||||
|
||||
// determine scanline type
|
||||
if ((width < MINELEN) || (MAXELEN < width)) {
|
||||
Read_Old_Line(image, width, s);
|
||||
if (!Read_Old_Line(image, width, s)) {
|
||||
return false;
|
||||
}
|
||||
RGBE_To_QRgbLine(image, scanline, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
s >> val;
|
||||
|
||||
if (s.atEnd()) {
|
||||
return true;
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (val != 2) {
|
||||
s.device()->ungetChar(val);
|
||||
Read_Old_Line(image, width, s);
|
||||
if (!Read_Old_Line(image, width, s)) {
|
||||
return false;
|
||||
}
|
||||
RGBE_To_QRgbLine(image, scanline, h);
|
||||
continue;
|
||||
}
|
||||
@@ -362,13 +366,15 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
|
||||
s >> image[2];
|
||||
s >> image[3];
|
||||
|
||||
if (s.atEnd()) {
|
||||
return true;
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((image[1] != 2) || (image[2] & 128)) {
|
||||
image[0] = 2;
|
||||
Read_Old_Line(image + 4, width - 1, s);
|
||||
if (!Read_Old_Line(image + 4, width - 1, s)) {
|
||||
return false;
|
||||
}
|
||||
RGBE_To_QRgbLine(image, scanline, h);
|
||||
continue;
|
||||
}
|
||||
@@ -382,7 +388,7 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
|
||||
for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
|
||||
for (int j = 0; j < width;) {
|
||||
s >> code;
|
||||
if (s.atEnd()) {
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
qCDebug(HDRPLUGIN) << "Truncated HDR file";
|
||||
return false;
|
||||
}
|
||||
@@ -510,7 +516,7 @@ bool HDRHandler::canRead(QIODevice *device)
|
||||
}
|
||||
|
||||
// the .pic taken from official test cases does not start with this string but can be loaded.
|
||||
if(device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
|
||||
if (device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -35,44 +35,47 @@
|
||||
#include <cstring>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(LOG_JXRPLUGIN)
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtDebugMsg)
|
||||
#else
|
||||
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.
|
||||
*/
|
||||
// #define JXR_DENY_FLOAT_IMAGE
|
||||
#ifndef JXR_DENY_FLOAT_IMAGE
|
||||
// #define JXR_DENY_FLOAT_IMAGE // default commented
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* Remove the needs of additional memory by disabling the conversion between
|
||||
* \brief JXR_DISABLE_DEPTH_CONVERSION
|
||||
*
|
||||
* When defined, removes the needs of additional memory by disabling the conversion between
|
||||
* different color depths (e.g. RGBA64bpp to RGBA32bpp).
|
||||
*
|
||||
* NOTE: Leaving deptch conversion enabled (default) ensures maximum read compatibility.
|
||||
* \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.
|
||||
@@ -84,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:
|
||||
@@ -112,28 +127,35 @@ public:
|
||||
, m_transformations(QImageIOHandler::TransformationNone)
|
||||
{
|
||||
m_tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
|
||||
if (PKCreateFactory(&pFactory, PK_SDK_VERSION) == WMP_errSuccess) {
|
||||
PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION);
|
||||
}
|
||||
if (pFactory == nullptr || pCodecFactory == nullptr) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() initialization error of JXR library!";
|
||||
if (auto err = PKCreateFactory(&pFactory, PK_SDK_VERSION)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while initializing the JXR factory:" << err;
|
||||
} else if (auto err = PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while initializing the JXR codec factory:" << err;
|
||||
}
|
||||
}
|
||||
JXRHandlerPrivate(const JXRHandlerPrivate &other) = default;
|
||||
|
||||
~JXRHandlerPrivate()
|
||||
{
|
||||
if (pCodecFactory) {
|
||||
PKCreateCodecFactory_Release(&pCodecFactory);
|
||||
}
|
||||
if (pFactory) {
|
||||
PKCreateFactory_Release(&pFactory);
|
||||
}
|
||||
if (pDecoder) {
|
||||
PKImageDecode_Release(&pDecoder);
|
||||
if (auto err = pDecoder->Release(&pDecoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the decoder:" << err;
|
||||
}
|
||||
}
|
||||
if (pEncoder) {
|
||||
PKImageEncode_Release(&pEncoder);
|
||||
if (auto err = pEncoder->Release(&pEncoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the encoder:" << err;
|
||||
}
|
||||
}
|
||||
if (pCodecFactory) {
|
||||
if (auto err = pCodecFactory->Release(&pCodecFactory)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the codec factory:" << err;
|
||||
}
|
||||
}
|
||||
if (pFactory) {
|
||||
if (auto err = pFactory->Release(&pFactory)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the factory:" << err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,8 +300,12 @@ public:
|
||||
PKPixelFormatGUID jxrFormat() const
|
||||
{
|
||||
PKPixelFormatGUID pixelFormatGUID = GUID_PKPixelFormatUndefined;
|
||||
if (pDecoder) {
|
||||
pDecoder->GetPixelFormat(pDecoder, &pixelFormatGUID);
|
||||
if (pDecoder == nullptr) {
|
||||
return pixelFormatGUID;
|
||||
}
|
||||
if (auto err = pDecoder->GetPixelFormat(pDecoder, &pixelFormatGUID)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::jxrFormat() error while getting pixel format:" << err;
|
||||
return GUID_PKPixelFormatUndefined;
|
||||
}
|
||||
return pixelFormatGUID;
|
||||
}
|
||||
@@ -303,6 +329,12 @@ public:
|
||||
return qtFormat;
|
||||
}
|
||||
|
||||
// *** MCH could be RGB, CMYK ***
|
||||
qtFormat = multichannelFormat(jxrfmt, colorSpace());
|
||||
if (qtFormat != QImage::Format_Invalid) {
|
||||
return qtFormat;
|
||||
}
|
||||
|
||||
// *** CONVERSION WITH THE SAME DEPTH ***
|
||||
// IMPORTANT: For supported conversions see JXRGluePFC.c
|
||||
|
||||
@@ -393,17 +425,20 @@ public:
|
||||
*/
|
||||
QSize imageSize() const
|
||||
{
|
||||
if (pDecoder) {
|
||||
qint32 w, h;
|
||||
pDecoder->GetSize(pDecoder, &w, &h);
|
||||
if (w > JXR_MAX_IMAGE_WIDTH || h > JXR_MAX_IMAGE_HEIGHT || w < 1 || h < 1) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() Maximum image size is limited to" << JXR_MAX_IMAGE_WIDTH << "x"
|
||||
<< JXR_MAX_IMAGE_HEIGHT << "pixels";
|
||||
return {};
|
||||
}
|
||||
return QSize(w, h);
|
||||
if (pDecoder == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
qint32 w = 0, h = 0;
|
||||
if (auto err = pDecoder->GetSize(pDecoder, &w, &h)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() error while getting the image size:" << err;
|
||||
return {};
|
||||
}
|
||||
if (w > JXR_MAX_IMAGE_WIDTH || h > JXR_MAX_IMAGE_HEIGHT || w < 1 || h < 1) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() Maximum image size is limited to" << JXR_MAX_IMAGE_WIDTH << "x"
|
||||
<< JXR_MAX_IMAGE_HEIGHT << "pixels";
|
||||
return {};
|
||||
}
|
||||
return QSize(w, h);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -416,8 +451,8 @@ public:
|
||||
if (pDecoder == nullptr) {
|
||||
return cs;
|
||||
}
|
||||
quint32 size;
|
||||
if (!pDecoder->GetColorContext(pDecoder, nullptr, &size) && size) {
|
||||
quint32 size = 0;
|
||||
if (!pDecoder->GetColorContext(pDecoder, nullptr, &size) && size > 0 && size < kMaxQVectorSize) {
|
||||
QByteArray ba(size, 0);
|
||||
if (!pDecoder->GetColorContext(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
|
||||
cs = QColorSpace::fromIccProfile(ba);
|
||||
@@ -436,8 +471,8 @@ public:
|
||||
if (pDecoder == nullptr) {
|
||||
return xmp;
|
||||
}
|
||||
#ifdef JXR_ENABLE_ADVANCED_METADATA
|
||||
quint32 size;
|
||||
#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);
|
||||
if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
|
||||
@@ -498,7 +533,7 @@ public:
|
||||
}
|
||||
auto host = hostComputer();
|
||||
if (!host.isEmpty()) {
|
||||
image.setText(QStringLiteral(META_KEY_HOSTCOMPUTER), capt);
|
||||
image.setText(QStringLiteral(META_KEY_HOSTCOMPUTER), host);
|
||||
}
|
||||
auto docn = documentName();
|
||||
if (!docn.isEmpty()) {
|
||||
@@ -556,7 +591,11 @@ public:
|
||||
if (device == nullptr || pEncoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (auto err = PKImageEncode_Release(&pEncoder)) {
|
||||
if (auto err = pEncoder->Terminate(pEncoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while terminating the encoder:" << err;
|
||||
return false;
|
||||
}
|
||||
if (auto err = pEncoder->Release(&pEncoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while releasing the encoder:" << err;
|
||||
return false;
|
||||
}
|
||||
@@ -796,6 +835,47 @@ public:
|
||||
return GUID_PKPixelFormatUndefined;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief multichannelFormat
|
||||
* I can only decide how to interpret multichannels by checking the color profile.
|
||||
* If it's not present, I assume CMYK for 4 channels and RGB for 3 channels (like
|
||||
* Windows does).
|
||||
* \param jxrFormat Format to be converted.
|
||||
* \param cs The color space of the image.
|
||||
* \return A valid Qt format or QImage::Format_Invalid if there is no match
|
||||
*/
|
||||
static QImage::Format multichannelFormat(const PKPixelFormatGUID &jxrFormat, const QColorSpace& cs)
|
||||
{
|
||||
auto model = QColorSpace::ColorModel::Undefined;
|
||||
if (cs.isValid()) {
|
||||
model = cs.colorModel();
|
||||
} else if (!cs.iccProfile().isEmpty()) {
|
||||
model = QColorSpace::ColorModel::Gray; // means invalid
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat24bpp3Channels, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_RGB888;
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat32bpp4Channels, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Cmyk || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_CMYK8888;
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat32bpp3ChannelsAlpha, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_RGBA8888;
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat64bpp3ChannelsAlpha, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_RGBA64;
|
||||
}
|
||||
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
private:
|
||||
static QList<std::pair<QImage::Format, PKPixelFormatGUID>> exactMatchingFormats()
|
||||
{
|
||||
@@ -893,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;
|
||||
@@ -926,7 +1012,7 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
DESCRIPTIVEMETADATA meta;
|
||||
DESCRIPTIVEMETADATA meta = {};
|
||||
if (pDecoder->GetDescriptiveMetadata(pDecoder, &meta)) {
|
||||
return false;
|
||||
}
|
||||
@@ -961,13 +1047,13 @@ 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;
|
||||
}
|
||||
|
||||
// resolution
|
||||
float hres, vres;
|
||||
float hres = 0, vres = 0;
|
||||
if (auto err = d->pDecoder->GetResolution(d->pDecoder, &hres, &vres)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err;
|
||||
} else {
|
||||
@@ -999,14 +1085,18 @@ bool JXRHandler::read(QImage *outImage)
|
||||
return false;
|
||||
}
|
||||
if (auto err = pConverter->Initialize(pConverter, d->pDecoder, nullptr, convFmt)) {
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to initialize the converter:" << err;
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (d->pDecoder->WMP.wmiI.cBitsPerUnit == size_t(img.depth())) { // in place conversion
|
||||
if (auto err = pConverter->Copy(pConverter, &rect, img.bits(), img.bytesPerLine())) {
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else { // additional buffer needed
|
||||
@@ -1015,19 +1105,26 @@ bool JXRHandler::read(QImage *outImage)
|
||||
qint64 limit = QImageReader::allocationLimit();
|
||||
if (limit && (buffSize + img.sizeInBytes()) > limit * 1024 * 1024) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to covert due to allocation limit set:" << limit << "MiB";
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
QVector<quint8> ba(buffSize);
|
||||
if (auto err = pConverter->Copy(pConverter, &rect, ba.data(), convStrideSize)) {
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (qint32 y = 0, h = img.height(); y < h; ++y) {
|
||||
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, qint64(img.bytesPerLine())));
|
||||
}
|
||||
}
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata (e.g.: icc profile, description, etc...)
|
||||
|
||||
@@ -36,17 +36,26 @@
|
||||
#define TIFF_VAL_URES_CENTIMETER 3
|
||||
|
||||
// EXIF 3 specs
|
||||
#define EXIF_EXPOSURETIME 0x829A
|
||||
#define EXIF_FNUMBER 0x829D
|
||||
#define EXIF_EXIFIFD 0x8769
|
||||
#define EXIF_EXPOSUREPROGRAM 0x8822
|
||||
#define EXIF_GPSIFD 0x8825
|
||||
#define EXIF_ISOSPEEDRATINGS 0x8827
|
||||
#define EXIF_EXIFVERSION 0x9000
|
||||
#define EXIF_DATETIMEORIGINAL 0x9003
|
||||
#define EXIF_DATETIMEDIGITIZED 0x9004
|
||||
#define EXIF_OFFSETTIME 0x9010
|
||||
#define EXIF_OFFSETTIMEORIGINAL 0x9011
|
||||
#define EXIF_OFFSETTIMEDIGITIZED 0x9012
|
||||
#define EXIF_FLASH 0x9209
|
||||
#define EXIF_FOCALLENGTH 0x920A
|
||||
#define EXIF_COLORSPACE 0xA001
|
||||
#define EXIF_PIXELXDIM 0xA002
|
||||
#define EXIF_PIXELYDIM 0xA003
|
||||
#define EXIF_EXPOSUREMODE 0xA402
|
||||
#define EXIF_WHITEBALANCE 0xA403
|
||||
#define EXIF_DIGITALZOOMRATIO 0xA404
|
||||
#define EXIF_IMAGEUNIQUEID 0xA420
|
||||
#define EXIF_BODYSERIALNUMBER 0xA431
|
||||
#define EXIF_LENSMAKE 0xA433
|
||||
@@ -64,6 +73,8 @@
|
||||
#define GPS_LONGITUDE 4
|
||||
#define GPS_ALTITUDEREF 5
|
||||
#define GPS_ALTITUDE 6
|
||||
#define GPS_SPEEDREF 12
|
||||
#define GPS_SPEED 13
|
||||
#define GPS_IMGDIRECTIONREF 16
|
||||
#define GPS_IMGDIRECTION 17
|
||||
#define EXIF_TAG_VALUE(n, byteSize) (((n) << 6) | ((byteSize) & 0x3F))
|
||||
@@ -123,16 +134,25 @@ static const KnownTags staticTagTypes = {
|
||||
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
|
||||
TagInfo(EXIF_EXPOSURETIME, ExifTagType::Rational),
|
||||
TagInfo(EXIF_FNUMBER, ExifTagType::Rational),
|
||||
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_EXPOSUREPROGRAM, ExifTagType::Short),
|
||||
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_ISOSPEEDRATINGS, ExifTagType::Short),
|
||||
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_DATETIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIME, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEORIGINAL, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_FLASH, ExifTagType::Short),
|
||||
TagInfo(EXIF_FOCALLENGTH, ExifTagType::Rational),
|
||||
TagInfo(EXIF_COLORSPACE, ExifTagType::Short),
|
||||
TagInfo(EXIF_PIXELXDIM, ExifTagType::Long),
|
||||
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
|
||||
TagInfo(EXIF_EXPOSUREMODE, ExifTagType::Short),
|
||||
TagInfo(EXIF_WHITEBALANCE, ExifTagType::Short),
|
||||
TagInfo(EXIF_DIGITALZOOMRATIO, ExifTagType::Rational),
|
||||
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8),
|
||||
@@ -155,6 +175,8 @@ static const KnownTags staticGpsTagTypes = {
|
||||
TagInfo(GPS_LONGITUDE, ExifTagType::Rational),
|
||||
TagInfo(GPS_ALTITUDEREF, ExifTagType::Byte),
|
||||
TagInfo(GPS_ALTITUDE, ExifTagType::Rational),
|
||||
TagInfo(GPS_SPEEDREF, ExifTagType::Ascii),
|
||||
TagInfo(GPS_SPEED, ExifTagType::Rational),
|
||||
TagInfo(GPS_IMGDIRECTIONREF, ExifTagType::Ascii),
|
||||
TagInfo(GPS_IMGDIRECTION, ExifTagType::Rational)
|
||||
};
|
||||
@@ -317,7 +339,7 @@ static void writeList(QDataStream &ds, const QVariant &value)
|
||||
inline qint32 rationalPrecision(double v)
|
||||
{
|
||||
v = qAbs(v);
|
||||
return 8 - qBound(0, v < 1 ? 8 : int(std::log10(v)), 8);
|
||||
return v < 1 ? 8 : 8 - qBound(0, int(std::log10(v)), 8);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
@@ -722,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:
|
||||
@@ -745,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);
|
||||
@@ -884,7 +908,7 @@ QDateTime MicroExif::dateTime() const
|
||||
auto ofTag = exifString(EXIF_OFFSETTIME);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
return dt;
|
||||
}
|
||||
|
||||
void MicroExif::setDateTime(const QDateTime &dt)
|
||||
@@ -904,7 +928,7 @@ QDateTime MicroExif::dateTimeOriginal() const
|
||||
auto ofTag = exifString(EXIF_OFFSETTIMEORIGINAL);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
return dt;
|
||||
}
|
||||
|
||||
void MicroExif::setDateTimeOriginal(const QDateTime &dt)
|
||||
@@ -924,7 +948,7 @@ QDateTime MicroExif::dateTimeDigitized() const
|
||||
auto ofTag = exifString(EXIF_OFFSETTIMEDIGITIZED);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
return dt;
|
||||
}
|
||||
|
||||
void MicroExif::setDateTimeDigitized(const QDateTime &dt)
|
||||
@@ -966,6 +990,138 @@ void MicroExif::setUniqueId(const QUuid &uuid)
|
||||
setExifString(EXIF_IMAGEUNIQUEID, uuid.toString(QUuid::WithoutBraces).replace(QStringLiteral("-"), QString()));
|
||||
}
|
||||
|
||||
double MicroExif::digitalZoomRatio() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_DIGITALZOOMRATIO))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_DIGITALZOOMRATIO).toDouble();
|
||||
|
||||
}
|
||||
|
||||
void MicroExif::setDigitalZoomRatio(double zoom)
|
||||
{
|
||||
if (qIsNaN(zoom))
|
||||
m_exifTags.remove(EXIF_DIGITALZOOMRATIO);
|
||||
else
|
||||
m_exifTags.insert(EXIF_DIGITALZOOMRATIO, zoom);
|
||||
}
|
||||
|
||||
quint16 MicroExif::isoSpeedRatings() const
|
||||
{
|
||||
return quint16(m_exifTags.value(EXIF_ISOSPEEDRATINGS).toUInt());
|
||||
}
|
||||
|
||||
void MicroExif::setIsoSpeedRatings(quint16 iso)
|
||||
{
|
||||
if (iso == 0)
|
||||
m_exifTags.remove(EXIF_ISOSPEEDRATINGS);
|
||||
else
|
||||
m_exifTags.insert(EXIF_ISOSPEEDRATINGS, iso);
|
||||
}
|
||||
|
||||
ExposureMode MicroExif::exposureMode() const
|
||||
{
|
||||
auto ok = false;
|
||||
auto v = m_exifTags.value(EXIF_EXPOSUREMODE).toUInt(&ok);
|
||||
return ok ? ExposureMode(v) : ExposureMode::NotSet;
|
||||
}
|
||||
|
||||
void MicroExif::setExposureMode(const ExposureMode &em)
|
||||
{
|
||||
if (em == ExposureMode::NotSet)
|
||||
m_exifTags.remove(EXIF_EXPOSUREMODE);
|
||||
else
|
||||
m_exifTags.insert(EXIF_EXPOSUREMODE, quint16(em));
|
||||
}
|
||||
|
||||
ExposureProgram MicroExif::exposureProgram() const
|
||||
{
|
||||
auto ok = false;
|
||||
auto v = m_exifTags.value(EXIF_EXPOSUREPROGRAM).toUInt(&ok);
|
||||
return ok ? ExposureProgram(v) : ExposureProgram::NotSet;
|
||||
}
|
||||
|
||||
void MicroExif::setExposureProgram(const ExposureProgram &ep)
|
||||
{
|
||||
if (ep == ExposureProgram::NotSet)
|
||||
m_exifTags.remove(EXIF_EXPOSUREPROGRAM);
|
||||
else
|
||||
m_exifTags.insert(EXIF_EXPOSUREPROGRAM, quint16(ep));
|
||||
}
|
||||
|
||||
double MicroExif::exposureTime() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_EXPOSURETIME))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_EXPOSURETIME).toDouble();
|
||||
}
|
||||
|
||||
void MicroExif::setExposureTime(double et)
|
||||
{
|
||||
if (qIsNaN(et))
|
||||
m_exifTags.remove(EXIF_EXPOSURETIME);
|
||||
else
|
||||
m_exifTags.insert(EXIF_EXPOSURETIME, et);
|
||||
}
|
||||
|
||||
double MicroExif::fNumber() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_FNUMBER))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_FNUMBER).toDouble();
|
||||
}
|
||||
|
||||
void MicroExif::setFNumber(double f)
|
||||
{
|
||||
if (qIsNaN(f))
|
||||
m_exifTags.remove(EXIF_FNUMBER);
|
||||
else
|
||||
m_exifTags.insert(EXIF_FNUMBER, f);
|
||||
}
|
||||
|
||||
double MicroExif::focalLength() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_FOCALLENGTH))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_FOCALLENGTH).toDouble();
|
||||
}
|
||||
|
||||
void MicroExif::setFocalLength(double fl)
|
||||
{
|
||||
if (qIsNaN(fl))
|
||||
m_exifTags.remove(EXIF_FOCALLENGTH);
|
||||
else
|
||||
m_exifTags.insert(EXIF_FOCALLENGTH, fl);
|
||||
}
|
||||
|
||||
FlashFlags MicroExif::flash() const
|
||||
{
|
||||
return FlashFlags(m_exifTags.value(EXIF_FLASH).toUInt());
|
||||
}
|
||||
|
||||
void MicroExif::setFlash(const FlashFlags &flash)
|
||||
{
|
||||
if (flash == Flash::NotSet)
|
||||
m_exifTags.remove(EXIF_FLASH);
|
||||
else
|
||||
m_exifTags.insert(EXIF_FLASH, quint16(flash));
|
||||
}
|
||||
|
||||
WhiteBalance MicroExif::whiteBalance() const
|
||||
{
|
||||
auto ok = false;
|
||||
auto v = m_exifTags.value(EXIF_WHITEBALANCE).toUInt(&ok);
|
||||
return ok ? WhiteBalance(v) : WhiteBalance::NotSet;
|
||||
}
|
||||
|
||||
void MicroExif::setWhiteBalance(const WhiteBalance &wb)
|
||||
{
|
||||
if (wb == WhiteBalance::NotSet)
|
||||
m_exifTags.remove(EXIF_WHITEBALANCE);
|
||||
else
|
||||
m_exifTags.insert(EXIF_WHITEBALANCE, quint16(wb));
|
||||
}
|
||||
|
||||
double MicroExif::latitude() const
|
||||
{
|
||||
auto ref = gpsString(GPS_LATITUDEREF).toUpper();
|
||||
@@ -1045,6 +1201,30 @@ void MicroExif::setAltitude(double meters)
|
||||
m_gpsTags.insert(GPS_ALTITUDE, meters);
|
||||
}
|
||||
|
||||
double MicroExif::imageSpeed() const
|
||||
{
|
||||
if (!m_gpsTags.contains(GPS_SPEED))
|
||||
return qQNaN();
|
||||
auto ref = gpsString(GPS_SPEEDREF).toUpper();
|
||||
auto speed = m_gpsTags.value(GPS_SPEED).toDouble();
|
||||
if (ref == QStringLiteral("M"))
|
||||
speed *= 1.60934;
|
||||
else if (ref == QStringLiteral("N"))
|
||||
speed *= 1.852;
|
||||
return speed;
|
||||
}
|
||||
|
||||
void MicroExif::setImageSpeed(double kmh)
|
||||
{
|
||||
if (qIsNaN(kmh)) {
|
||||
m_gpsTags.remove(GPS_SPEEDREF);
|
||||
m_gpsTags.remove(GPS_SPEED);
|
||||
return;
|
||||
}
|
||||
m_gpsTags.insert(GPS_SPEEDREF, QStringLiteral("K"));
|
||||
m_gpsTags.insert(GPS_SPEED, kmh);
|
||||
}
|
||||
|
||||
double MicroExif::imageDirection(bool *isMagnetic) const
|
||||
{
|
||||
auto tmp = false;
|
||||
@@ -1191,6 +1371,58 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_DIRECTION), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_SPEED)).isEmpty()) {
|
||||
auto v = imageSpeed();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_SPEED), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
|
||||
// shot info
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_DIGITALZOOMRATIO)).isEmpty()) {
|
||||
auto v = digitalZoomRatio();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_DIGITALZOOMRATIO), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSUREMODE)).isEmpty()) {
|
||||
auto v = exposureMode();
|
||||
if (v != ExposureMode::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_EXPOSUREMODE), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSUREPROGRAM)).isEmpty()) {
|
||||
auto v = exposureProgram();
|
||||
if (v != ExposureProgram::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_EXPOSUREPROGRAM), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSURETIME)).isEmpty()) {
|
||||
auto v = exposureTime();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_EXPOSURETIME), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FLASH)).isEmpty()) {
|
||||
auto v = flash();
|
||||
if (v != Flash::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_FLASH), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FNUMBER)).isEmpty()) {
|
||||
auto v = fNumber();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_FNUMBER), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FOCALLENGTH)).isEmpty()) {
|
||||
auto v = focalLength();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_FOCALLENGTH), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_ISOSPEEDRATINGS)).isEmpty()) {
|
||||
auto v = isoSpeedRatings();
|
||||
if (v != 0)
|
||||
targetImage.setText(QStringLiteral(META_KEY_ISOSPEEDRATINGS), QStringLiteral("%1").arg(v));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_WHITEBALANCE)).isEmpty()) {
|
||||
auto v = whiteBalance();
|
||||
if (v != WhiteBalance::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_WHITEBALANCE), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
}
|
||||
|
||||
bool MicroExif::updateImageResolution(QImage &targetImage)
|
||||
@@ -1217,7 +1449,7 @@ MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader)
|
||||
idx = std::min(idxLE, idxBE);
|
||||
else
|
||||
idx = idxLE > -1 ? idxLE : idxBE;
|
||||
if(idx > 0)
|
||||
if (idx > 0)
|
||||
ba0 = ba0.mid(idx);
|
||||
}
|
||||
QBuffer buf;
|
||||
@@ -1308,7 +1540,7 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
dt = QDateTime::currentDateTime();
|
||||
exif.setDateTimeOriginal(dt);
|
||||
|
||||
// GPS Info
|
||||
// GPS info
|
||||
auto ok = false;
|
||||
auto alt = image.text(QStringLiteral(META_KEY_ALTITUDE)).toDouble(&ok);
|
||||
if (ok)
|
||||
@@ -1322,6 +1554,38 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
auto dir = image.text(QStringLiteral(META_KEY_DIRECTION)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setImageDirection(dir);
|
||||
auto spd = image.text(QStringLiteral(META_KEY_SPEED)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setImageSpeed(spd);
|
||||
|
||||
// EXIF shot info
|
||||
auto zoom = image.text(QStringLiteral(META_KEY_DIGITALZOOMRATIO)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setDigitalZoomRatio(zoom);
|
||||
auto expm = image.text(QStringLiteral(META_KEY_EXPOSUREMODE)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setExposureMode(ExposureMode(expm));
|
||||
auto expp = image.text(QStringLiteral(META_KEY_EXPOSUREPROGRAM)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setExposureProgram(ExposureProgram(expp));
|
||||
auto expt = image.text(QStringLiteral(META_KEY_EXPOSURETIME)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setExposureTime(expt);
|
||||
auto flsh = image.text(QStringLiteral(META_KEY_FLASH)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setFlash(FlashFlags(flsh));
|
||||
auto fnum = image.text(QStringLiteral(META_KEY_FNUMBER)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setFNumber(fnum);
|
||||
auto flen = image.text(QStringLiteral(META_KEY_FOCALLENGTH)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setFocalLength(flen);
|
||||
auto isos = image.text(QStringLiteral(META_KEY_ISOSPEEDRATINGS)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setIsoSpeedRatings(isos);
|
||||
auto whtb = image.text(QStringLiteral(META_KEY_WHITEBALANCE)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setWhiteBalance(WhiteBalance(whtb));
|
||||
|
||||
return exif;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,85 @@
|
||||
#define EXIF_DEFAULT_BYTEORDER QDataStream::BigEndian
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief The Flash enum
|
||||
*/
|
||||
enum class Flash : quint16 {
|
||||
NotSet = 0,
|
||||
|
||||
// Values for bit 0 indicating whether the flash fired.
|
||||
// 0b = Flash did not fire.
|
||||
// 1b = Flash fired.
|
||||
Fired = 1,
|
||||
|
||||
// Values for bits 1 and 2 indicating the status of returned light.
|
||||
// 00b = No strobe return detection function
|
||||
// 01b = reserved
|
||||
// 10b = Strobe return light not detected.
|
||||
// 11b = Strobe return light detected.
|
||||
ReturnLightNotDetected = 2 << 1,
|
||||
ReturnLightDetected = 3 << 1,
|
||||
|
||||
// Values for bits 3 and 4 indicating the camera's flash mode.
|
||||
// 00b = unknown
|
||||
// 01b = Compulsory flash firing
|
||||
// 10b = Compulsory flash suppression
|
||||
// 11b = Auto mode
|
||||
CompulsoryFiring = 1 << 3,
|
||||
CompulsorySuppression = 2 << 3,
|
||||
AutoMode = 3 << 3,
|
||||
|
||||
// Values for bit 5 indicating the presence of a flash function.
|
||||
// 0b = Flash function present
|
||||
// 1b = No flash function
|
||||
FlashNotAvailable = 1 << 5,
|
||||
|
||||
// Values for bit 6 indicating the camera's red-eye mode.
|
||||
// 0b = No red-eye reduction mode or unknown
|
||||
// 1b = Red-eye reduction supported
|
||||
RedEyeReductionSupported = 1 << 6,
|
||||
};
|
||||
Q_DECLARE_FLAGS(FlashFlags, Flash)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(FlashFlags)
|
||||
|
||||
/*!
|
||||
* \brief The ExposureMode enum
|
||||
*/
|
||||
enum class ExposureMode : quint16 {
|
||||
Auto,
|
||||
Manual,
|
||||
AutoBracket,
|
||||
|
||||
NotSet = 65535
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The ExposureProgram enum
|
||||
*/
|
||||
enum class ExposureProgram : quint16 {
|
||||
NotDefined,
|
||||
Manual,
|
||||
Normal,
|
||||
AperturePriority,
|
||||
ShutterPriority,
|
||||
Creative,
|
||||
Action,
|
||||
PortraitMode,
|
||||
LandscapeMode,
|
||||
|
||||
NotSet = 65535
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The WhiteBalance enum
|
||||
*/
|
||||
enum class WhiteBalance : quint16 {
|
||||
Auto,
|
||||
Manual,
|
||||
|
||||
NotSet = 65535
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The MicroExif class
|
||||
* Class to extract / write minimal EXIF data (e.g. resolution, rotation,
|
||||
@@ -131,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.
|
||||
@@ -236,6 +328,69 @@ public:
|
||||
QUuid uniqueId() const;
|
||||
void setUniqueId(const QUuid &uuid);
|
||||
|
||||
/*!
|
||||
* \brief digitalZoomRatio
|
||||
* \return The digital zoom ratio when the image was shot or NaN if not set.
|
||||
*/
|
||||
double digitalZoomRatio() const;
|
||||
void setDigitalZoomRatio(double zoom);
|
||||
|
||||
/*!
|
||||
* \brief exposureMode
|
||||
* \return The exposure mode set when the image was shot. In auto-bracketing mode, the camera shoots a series of frames of the same scene at different exposure settings.
|
||||
*/
|
||||
ExposureMode exposureMode() const;
|
||||
void setExposureMode(const ExposureMode& em);
|
||||
|
||||
/*!
|
||||
* \brief exposureProgram
|
||||
* \return The class of the program used by the camera to set exposure when the picture is taken.
|
||||
*/
|
||||
ExposureProgram exposureProgram() const;
|
||||
void setExposureProgram(const ExposureProgram& ep);
|
||||
|
||||
/*!
|
||||
* \brief exposureTime
|
||||
* \return Exposure time, given in seconds (sec) or NaN if not set.
|
||||
*/
|
||||
double exposureTime() const;
|
||||
void setExposureTime(double et);
|
||||
|
||||
/*!
|
||||
* \brief fNumber
|
||||
* \return The F number or NaN if not set.
|
||||
*/
|
||||
double fNumber() const;
|
||||
void setFNumber(double f);
|
||||
|
||||
/*!
|
||||
* \brief focalLength
|
||||
* \return The actual focal length of the lens, in mm.
|
||||
*/
|
||||
double focalLength() const;
|
||||
void setFocalLength(double fl);
|
||||
|
||||
/*!
|
||||
* \brief flash
|
||||
* \return The status of flash when the image was shot.
|
||||
*/
|
||||
FlashFlags flash() const;
|
||||
void setFlash(const FlashFlags& flash);
|
||||
|
||||
/*!
|
||||
* \brief isoSpeedRatings
|
||||
* \return The sensitivity of the camera or input device when the image was shot.
|
||||
*/
|
||||
quint16 isoSpeedRatings() const;
|
||||
void setIsoSpeedRatings(quint16 iso);
|
||||
|
||||
/*!
|
||||
* \brief whiteBalance
|
||||
* \return The white balance mode set when the image was shot.
|
||||
*/
|
||||
WhiteBalance whiteBalance() const;
|
||||
void setWhiteBalance(const WhiteBalance& wb);
|
||||
|
||||
/*!
|
||||
* \brief latitude
|
||||
* \return Floating-point number indicating the latitude in degrees north of the equator (e.g. 27.717) or NaN if not set.
|
||||
@@ -258,6 +413,13 @@ public:
|
||||
double altitude() const;
|
||||
void setAltitude(double meters);
|
||||
|
||||
/*!
|
||||
* \brief imageSpeed
|
||||
* \return The speed in Km/h or NaN if not set.
|
||||
*/
|
||||
double imageSpeed() const;
|
||||
void setImageSpeed(double kmh);
|
||||
|
||||
/*!
|
||||
* \brief imageDirection
|
||||
* \param isMagnetic Set to true if the direction is relative to magnetic north, false if it is relative to true north. Leave nullptr if is not of interest.
|
||||
|
||||
@@ -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"
|
||||
@@ -34,10 +43,22 @@
|
||||
#define META_KEY_MODIFICATIONDATE "ModificationDate"
|
||||
#define META_KEY_OWNER "Owner"
|
||||
#define META_KEY_SOFTWARE "Software"
|
||||
#define META_KEY_SPEED "Speed"
|
||||
#define META_KEY_TITLE "Title"
|
||||
#define META_KEY_XML_GIMP "XML:org.gimp.xml"
|
||||
#define META_KEY_XMP_ADOBE "XML:com.adobe.xmp"
|
||||
|
||||
// Shot info metadata keys
|
||||
#define META_KEY_DIGITALZOOMRATIO "DigitalZoomRatio"
|
||||
#define META_KEY_EXPOSUREMODE "ExposureMode"
|
||||
#define META_KEY_EXPOSUREPROGRAM "ExposureProgram"
|
||||
#define META_KEY_EXPOSURETIME "ExposureTime"
|
||||
#define META_KEY_FLASH "Flash"
|
||||
#define META_KEY_FNUMBER "FNumber"
|
||||
#define META_KEY_FOCALLENGTH "FocalLength"
|
||||
#define META_KEY_ISOSPEEDRATINGS "ISOSpeedRatings"
|
||||
#define META_KEY_WHITEBALANCE "WhiteBalance"
|
||||
|
||||
// Camera info metadata keys
|
||||
#define META_KEY_MANUFACTURER "Manufacturer"
|
||||
#define META_KEY_MODEL "Model"
|
||||
@@ -51,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
|
||||
|
||||