mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-15 11:14:18 -04:00
Compare commits
34 Commits
v6.11.0-rc
...
v6.13.0
Author | SHA1 | Date | |
---|---|---|---|
6f588c6fd3 | |||
a182478e2c | |||
4026f41890 | |||
bef2b9168f | |||
473f5d9847 | |||
9bee29cc01 | |||
cdf3be3af1 | |||
752b18a42c | |||
97a1ea181c | |||
64a43fb04f | |||
6821c29819 | |||
e4d95c03fa | |||
afa8ed1a5d | |||
245c835d92 | |||
b2663d2651 | |||
35ab37c628 | |||
b28baa4a1e | |||
5d2540c135 | |||
25cc8bc262 | |||
7742537f8c | |||
d3386bbf50 | |||
e77986c7e0 | |||
c0261f4926 | |||
e5cf9caac5 | |||
90d4256f3d | |||
bb1c6aab9e | |||
74a734efed | |||
e9fa4b6610 | |||
36a6ef7d78 | |||
9fd6896cec | |||
9b14e752db | |||
90a2e3b412 | |||
b28cf4c352 | |||
b536ec4a5e |
@ -7,5 +7,5 @@ Dependencies:
|
||||
Options:
|
||||
test-before-installing: True
|
||||
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
|
||||
cmake-options: "-DKIMAGEFORMATS_DDS=ON -DKIMAGEFORMATS_JXR=ON"
|
||||
cmake-options: "-DKIMAGEFORMATS_DDS=ON -DKIMAGEFORMATS_JXR=ON -DKIMAGEFORMATS_HEIF=ON"
|
||||
per-test-timeout: 90
|
||||
|
@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(KF_VERSION "6.11.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.11.0") # handled by release scripts
|
||||
set(KF_VERSION "6.13.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.13.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 6.11.0 NO_MODULE)
|
||||
find_package(ECM 6.13.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)
|
||||
|
||||
@ -99,8 +99,8 @@ endif()
|
||||
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
|
||||
|
||||
ecm_set_disabled_deprecation_versions(
|
||||
QT 6.8.0
|
||||
KF 6.10.0
|
||||
QT 6.9.0
|
||||
KF 6.12.0
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
16
README.md
16
README.md
@ -125,9 +125,12 @@ About the image:
|
||||
example a verbal description of the image.
|
||||
- `Copyright`: Copyright notice of the person or organization that claims
|
||||
the copyright to the image.
|
||||
- `CreationDate`: Creation date and time in ISO 8601 format without
|
||||
milliseconds (e.g. 2024-03-23T15:30:43).
|
||||
- `CreationDate`: When the image was created or captured. Date and time in
|
||||
ISO 8601 format without milliseconds (e.g. 2024-03-23T15:30:43). This value
|
||||
should be kept unchanged when present.
|
||||
- `Description`: A string that describes the subject of the image.
|
||||
- `Direction`: Floating-point number indicating the direction of the image
|
||||
when it was captured in degrees (e.g. 123.3).
|
||||
- `DocumentName`: The name of the document from which this image was
|
||||
scanned.
|
||||
- `HostComputer`: The computer and/or operating system in use at the time
|
||||
@ -136,6 +139,9 @@ About the image:
|
||||
north of the equator (e.g. 27.717).
|
||||
- `Longitude`: Floating-point number indicating the longitude in degrees
|
||||
east of Greenwich (e.g. 85.317).
|
||||
- `ModificationDate`: Last modification date and time in ISO 8601 format
|
||||
without milliseconds (e.g. 2024-03-23T15:30:43). This value should be
|
||||
updated every time the image is saved.
|
||||
- `Owner`: Name of the owner of the image.
|
||||
- `Software`: Name and version number of the software package(s) used to
|
||||
create the image.
|
||||
@ -189,6 +195,7 @@ are created automatically:
|
||||
- `Software`: Created using `applicationName` and `applicationVersion` methods
|
||||
of [`QCoreApplication`](https://doc.qt.io/qt-6/qcoreapplication.html).
|
||||
- `CreationDate`: Set to current time and date.
|
||||
- `ModificationDate`: Set to current time and date.
|
||||
|
||||
### ICC profile support
|
||||
|
||||
@ -213,7 +220,7 @@ plugin ('n/a' means no limit, i.e. the limit depends on the format encoding).
|
||||
- EPS: n/a
|
||||
- HDR: n/a (large image)
|
||||
- HEIF: n/a
|
||||
- JP2: 300,000 x 300,000 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
|
||||
- JXR: n/a, in any case no larger than 4 GB
|
||||
- KRA: same size as Qt's PNG plugin
|
||||
@ -258,7 +265,7 @@ been used or the maximum size of the image that can be saved has been limited.
|
||||
PSD plugin loads CMYK, Lab and Multichannel images and converts them to RGB
|
||||
without using the ICC profile.
|
||||
|
||||
JP2, JXR, PSD and SCT plugins natively support 4-channel CMYK images when
|
||||
JP2, JXL, JXR, PSD and SCT plugins natively support 4-channel CMYK images when
|
||||
compiled with Qt 6.8+.
|
||||
|
||||
### The DDS plugin
|
||||
@ -314,6 +321,7 @@ in your cmake options.**
|
||||
JP2 plugin has the following limitations due to the lack of support by OpenJPEG:
|
||||
- Metadata are not supported.
|
||||
- Image resolution is not supported.
|
||||
- To write ICC profiles you need OpenJPEG V2.5.4 or higher
|
||||
|
||||
### The JXL plugin
|
||||
|
||||
|
@ -122,9 +122,12 @@ if (LibHeif_FOUND)
|
||||
kimageformats_read_tests(FUZZ 1
|
||||
hej2
|
||||
)
|
||||
kimageformats_write_tests(FUZZ 1
|
||||
hej2-nodatacheck-lossless
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.19.0")
|
||||
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.19.6")
|
||||
kimageformats_read_tests(FUZZ 4
|
||||
avci
|
||||
)
|
||||
|
BIN
autotests/read/avif/metadata.avif
Normal file
BIN
autotests/read/avif/metadata.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
59
autotests/read/avif/metadata.avif.json
Normal file
59
autotests/read/avif/metadata.avif.json
Normal file
@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "metadata.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-19T08:27:22+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "GIMP 3.0.0-RC3"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Copyright",
|
||||
"value" : "@2025 KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
},
|
||||
{
|
||||
"key" : "LensManufacturer",
|
||||
"value" : "KDE Glasses"
|
||||
},
|
||||
{
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
},
|
||||
{
|
||||
"key" : "Manufacturer",
|
||||
"value" : "KFramework"
|
||||
},
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 11811,
|
||||
"dotsPerMeterY" : 5905
|
||||
}
|
||||
}
|
||||
]
|
BIN
autotests/read/avif/metadata.png
Normal file
BIN
autotests/read/avif/metadata.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
autotests/read/heif/metadata.heif
Normal file
BIN
autotests/read/heif/metadata.heif
Normal file
Binary file not shown.
59
autotests/read/heif/metadata.heif.json
Normal file
59
autotests/read/heif/metadata.heif.json
Normal file
@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "metadata.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-26T16:52:06Z"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10 (Linux)"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Copyright",
|
||||
"value" : "@2025 KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
},
|
||||
{
|
||||
"key" : "LensManufacturer",
|
||||
"value" : "KDE Glasses"
|
||||
},
|
||||
{
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
},
|
||||
{
|
||||
"key" : "Manufacturer",
|
||||
"value" : "KFramework"
|
||||
},
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 11811,
|
||||
"dotsPerMeterY" : 5905
|
||||
}
|
||||
}
|
||||
]
|
BIN
autotests/read/heif/metadata.png
Normal file
BIN
autotests/read/heif/metadata.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -3,7 +3,7 @@
|
||||
"fileName" : "gimp_exif.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-01-05T10:18:16"
|
||||
},
|
||||
{
|
||||
|
BIN
autotests/read/jxr/metadata.jxr
Normal file
BIN
autotests/read/jxr/metadata.jxr
Normal file
Binary file not shown.
59
autotests/read/jxr/metadata.jxr.json
Normal file
59
autotests/read/jxr/metadata.jxr.json
Normal file
@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "metadata.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-01-14T10:34:51Z"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Copyright",
|
||||
"value" : "@2025 KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
},
|
||||
{
|
||||
"key" : "LensManufacturer",
|
||||
"value" : "KDE Glasses"
|
||||
},
|
||||
{
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
},
|
||||
{
|
||||
"key" : "Manufacturer",
|
||||
"value" : "KFramework"
|
||||
},
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 11811,
|
||||
"dotsPerMeterY" : 5906
|
||||
}
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/metadata.png
Normal file
BIN
autotests/read/jxr/metadata.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/jxr/orientation1.jxr
Normal file
BIN
autotests/read/jxr/orientation1.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation1.jxr.json
Normal file
11
autotests/read/jxr/orientation1.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation2.jxr
Normal file
BIN
autotests/read/jxr/orientation2.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation2.jxr.json
Normal file
11
autotests/read/jxr/orientation2.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation3.jxr
Normal file
BIN
autotests/read/jxr/orientation3.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation3.jxr.json
Normal file
11
autotests/read/jxr/orientation3.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation4.jxr
Normal file
BIN
autotests/read/jxr/orientation4.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation4.jxr.json
Normal file
11
autotests/read/jxr/orientation4.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation5.jxr
Normal file
BIN
autotests/read/jxr/orientation5.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation5.jxr.json
Normal file
11
autotests/read/jxr/orientation5.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation6.jxr
Normal file
BIN
autotests/read/jxr/orientation6.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation6.jxr.json
Normal file
11
autotests/read/jxr/orientation6.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation7.jxr
Normal file
BIN
autotests/read/jxr/orientation7.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation7.jxr.json
Normal file
11
autotests/read/jxr/orientation7.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation8.jxr
Normal file
BIN
autotests/read/jxr/orientation8.jxr
Normal file
Binary file not shown.
11
autotests/read/jxr/orientation8.jxr.json
Normal file
11
autotests/read/jxr/orientation8.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/orientation_all.png
Normal file
BIN
autotests/read/jxr/orientation_all.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@ -8,7 +8,7 @@
|
||||
},
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2022-11-11T14:27:52"
|
||||
},
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2022-11-11T14:27:39"
|
||||
},
|
||||
{
|
||||
|
@ -3,7 +3,7 @@
|
||||
"fileName" : "metadata.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-01-14T13:53:32+01:00"
|
||||
},
|
||||
{
|
||||
|
@ -258,6 +258,15 @@ int main(int argc, char **argv)
|
||||
});
|
||||
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
|
||||
|
||||
if (!formats.contains(format)) {
|
||||
if (format == "avci" || format == "heif" || format == "hej2") {
|
||||
QTextStream(stdout) << "WARNING : " << suffix << " is not supported with current libheif configuration!\n"
|
||||
<< "********* "
|
||||
<< "Finished basic read tests for " << suffix << " images *********\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
||||
// Launch 2 runs for each test: first run on a random access device, second run on a sequential access device
|
||||
for (int seq = 0; seq < 2; ++seq) {
|
||||
@ -323,7 +332,12 @@ int main(int argc, char **argv)
|
||||
OptionTest optionTest;
|
||||
if (!optionTest.store(&inputReader)) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
|
||||
++failed;
|
||||
if (format == "heif") {
|
||||
// libheif + ffmpeg decoder is unable to load all HEIF files.
|
||||
++skipped;
|
||||
} else {
|
||||
++failed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
65
autotests/write/basic/avif.json
Normal file
65
autotests/write/basic/avif.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"format" : "avif",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-01-14T13:53:32+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Direction",
|
||||
"value" : "123.7"
|
||||
},
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-14T15:58:44+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 26.2 (Windows)"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Copyright",
|
||||
"value" : "@2025 KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
},
|
||||
{
|
||||
"key" : "LensManufacturer",
|
||||
"value" : "KDE Glasses"
|
||||
},
|
||||
{
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
},
|
||||
{
|
||||
"key" : "Manufacturer",
|
||||
"value" : "KFramework"
|
||||
},
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 11811,
|
||||
"dotsPerMeterY" : 11812
|
||||
}
|
||||
}
|
65
autotests/write/basic/heif.json
Normal file
65
autotests/write/basic/heif.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"format" : "heif",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-01-14T13:53:32+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Direction",
|
||||
"value" : "123.7"
|
||||
},
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-14T15:58:44+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 26.2 (Windows)"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Copyright",
|
||||
"value" : "@2025 KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
},
|
||||
{
|
||||
"key" : "LensManufacturer",
|
||||
"value" : "KDE Glasses"
|
||||
},
|
||||
{
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
},
|
||||
{
|
||||
"key" : "Manufacturer",
|
||||
"value" : "KFramework"
|
||||
},
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 11811,
|
||||
"dotsPerMeterY" : 11812
|
||||
}
|
||||
}
|
65
autotests/write/basic/hej2.json
Normal file
65
autotests/write/basic/hej2.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"format" : "hej2",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-01-14T13:53:32+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Direction",
|
||||
"value" : "123.7"
|
||||
},
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-14T15:58:44+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 26.2 (Windows)"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Copyright",
|
||||
"value" : "@2025 KDE Project"
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
},
|
||||
{
|
||||
"key" : "LensManufacturer",
|
||||
"value" : "KDE Glasses"
|
||||
},
|
||||
{
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
},
|
||||
{
|
||||
"key" : "Manufacturer",
|
||||
"value" : "KFramework"
|
||||
},
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 11811,
|
||||
"dotsPerMeterY" : 11812
|
||||
}
|
||||
}
|
@ -5,6 +5,14 @@
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-01-14T13:53:32+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Direction",
|
||||
"value" : "123.7"
|
||||
},
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-14T15:58:44+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 26.2 (Windows)"
|
||||
|
@ -5,6 +5,18 @@
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-01-14T13:53:32+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Direction",
|
||||
"value" : "123.7"
|
||||
},
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-14T15:58:44+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
@ -17,6 +29,22 @@
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
},
|
||||
{
|
||||
"key" : "LensManufacturer",
|
||||
"value" : "KDE Glasses"
|
||||
},
|
||||
{
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
},
|
||||
{
|
||||
"key" : "Manufacturer",
|
||||
"value" : "KFramework"
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
autotests/write/format/hej2/Format_A2BGR30_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_A2BGR30_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_A2RGB30_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_A2RGB30_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_ARGB32.hej2
Normal file
BIN
autotests/write/format/hej2/Format_ARGB32.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_ARGB32_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_ARGB32_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_ARGB4444_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_ARGB4444_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_ARGB6666_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_ARGB6666_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_ARGB8555_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_ARGB8555_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_ARGB8565_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_ARGB8565_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_BGR30.hej2
Normal file
BIN
autotests/write/format/hej2/Format_BGR30.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_BGR888.hej2
Normal file
BIN
autotests/write/format/hej2/Format_BGR888.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_CMYK8888.hej2
Normal file
BIN
autotests/write/format/hej2/Format_CMYK8888.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_Grayscale16.hej2
Normal file
BIN
autotests/write/format/hej2/Format_Grayscale16.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_Grayscale8.hej2
Normal file
BIN
autotests/write/format/hej2/Format_Grayscale8.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_Indexed8.hej2
Normal file
BIN
autotests/write/format/hej2/Format_Indexed8.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_Mono.hej2
Normal file
BIN
autotests/write/format/hej2/Format_Mono.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_MonoLSB.hej2
Normal file
BIN
autotests/write/format/hej2/Format_MonoLSB.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGB16.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGB16.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGB30.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGB30.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGB32.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGB32.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGB444.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGB444.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGB555.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGB555.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGB666.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGB666.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGB888.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGB888.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA16FPx4.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA16FPx4.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA16FPx4_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA16FPx4_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA32FPx4.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA32FPx4.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA32FPx4_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA32FPx4_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA64.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA64.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA64_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA64_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA8888.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA8888.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBA8888_Premultiplied.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBA8888_Premultiplied.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBX16FPx4.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBX16FPx4.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBX32FPx4.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBX32FPx4.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBX64.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBX64.hej2
Normal file
Binary file not shown.
BIN
autotests/write/format/hej2/Format_RGBX8888.hej2
Normal file
BIN
autotests/write/format/hej2/Format_RGBX8888.hej2
Normal file
Binary file not shown.
Binary file not shown.
@ -46,11 +46,11 @@ QJsonObject readOptionalInfo(const QString &suffix)
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
void setOptionalInfo(QImage& image, const QString &suffix)
|
||||
void setOptionalInfo(QImage &image, const QString &suffix)
|
||||
{
|
||||
auto obj = readOptionalInfo(suffix);
|
||||
if (obj.isEmpty()) {
|
||||
return ;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set resolution
|
||||
@ -70,7 +70,7 @@ void setOptionalInfo(QImage& image, const QString &suffix)
|
||||
}
|
||||
}
|
||||
|
||||
bool checkOptionalInfo(QImage& image, const QString &suffix)
|
||||
bool checkOptionalInfo(QImage &image, const QString &suffix)
|
||||
{
|
||||
auto obj = readOptionalInfo(suffix);
|
||||
if (obj.isEmpty()) {
|
||||
@ -258,7 +258,6 @@ QImage formatSourceImage(const QImage::Format &format)
|
||||
auto folder = QStringLiteral("%1/format/_images").arg(IMAGEDIR);
|
||||
|
||||
switch (format) {
|
||||
|
||||
case QImage::Format_MonoLSB:
|
||||
case QImage::Format_Mono:
|
||||
image = QImage(QStringLiteral("%1/mono.png").arg(folder));
|
||||
@ -371,7 +370,12 @@ int formatTest(const QString &suffix, bool createTemplates)
|
||||
QBuffer buffer(&ba);
|
||||
auto writtenImage = QImageReader(&buffer, suffix.toLatin1()).read();
|
||||
if (writtenImage.isNull()) {
|
||||
++failed;
|
||||
if (suffix.toLatin1() == "heif") {
|
||||
// libheif + ffmpeg decoder is unable to load all HEIF files.
|
||||
++skipped;
|
||||
} else {
|
||||
++failed;
|
||||
}
|
||||
QTextStream(stdout) << "FAIL : error while reading the image " << formatName << "\n";
|
||||
continue;
|
||||
}
|
||||
@ -403,8 +407,16 @@ int formatTest(const QString &suffix, bool createTemplates)
|
||||
// checking the format: must be the same
|
||||
if (writtenImage.format() != tmplImage.format()) {
|
||||
++failed;
|
||||
auto writtenformatName = QString(QMetaEnum::fromType<QImage::Format>().valueToKey(writtenImage.format()));
|
||||
auto tmplformatName = QString(QMetaEnum::fromType<QImage::Format>().valueToKey(tmplImage.format()));
|
||||
QTextStream(stdout) << "FAIL : format mismatch " << formatName << " != " << tmplformatName << "\n";
|
||||
QTextStream(stdout) << "FAIL : format mismatch " << formatName << " (";
|
||||
if (format == writtenImage.format()) {
|
||||
QTextStream(stdout) << "template image: " << tmplformatName << ")\n";
|
||||
} else if (format == tmplImage.format()) {
|
||||
QTextStream(stdout) << "written image: " << writtenformatName << ")\n";
|
||||
} else {
|
||||
QTextStream(stdout) << writtenformatName << " != " << tmplformatName << ")\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -554,7 +566,7 @@ int nullDeviceTest(const QString &suffix)
|
||||
writer.optimizedWrite();
|
||||
writer.progressiveScanWrite();
|
||||
|
||||
if (failed == 0) {// success
|
||||
if (failed == 0) { // success
|
||||
++passed;
|
||||
}
|
||||
|
||||
@ -586,8 +598,7 @@ int main(int argc, char **argv)
|
||||
QStringLiteral("max"));
|
||||
QCommandLineOption createFormatTempates({QStringLiteral("create-format-templates")},
|
||||
QStringLiteral("Create template images for all formats supported by QImage."));
|
||||
QCommandLineOption skipOptTest({QStringLiteral("skip-optional-tests")},
|
||||
QStringLiteral("Skip optional data tests (metadata, resolution, etc.)."));
|
||||
QCommandLineOption skipOptTest({QStringLiteral("skip-optional-tests")}, QStringLiteral("Skip optional data tests (metadata, resolution, etc.)."));
|
||||
|
||||
parser.addOption(lossless);
|
||||
parser.addOption(ignoreDataCheck);
|
||||
@ -616,8 +627,28 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
// run test
|
||||
auto suffix = args.at(0);
|
||||
|
||||
// skip test if libheif configuration is obviously incomplete
|
||||
QByteArray format = suffix.toLatin1();
|
||||
const QList<QByteArray> read_formats = QImageReader::supportedImageFormats();
|
||||
const QList<QByteArray> write_formats = QImageWriter::supportedImageFormats();
|
||||
|
||||
if (!read_formats.contains(format)) {
|
||||
if (format == "heif" || format == "hej2") {
|
||||
QTextStream(stdout) << "WARNING : libheif configuration is missing necessary decoder(s)!\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!write_formats.contains(format)) {
|
||||
if (format == "heif" || format == "hej2") {
|
||||
QTextStream(stdout) << "WARNING : libheif configuration is missing necessary encoder(s)!\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// run test
|
||||
auto ret = basicTest(suffix, parser.isSet(lossless), parser.isSet(ignoreDataCheck), parser.isSet(skipOptTest), fuzzarg);
|
||||
if (ret == 0) {
|
||||
ret = formatTest(suffix, parser.isSet(createFormatTempates));
|
||||
|
@ -25,7 +25,7 @@ kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
|
||||
##################################
|
||||
|
||||
if (TARGET avif)
|
||||
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
||||
kimageformats_add_plugin(kimg_avif SOURCES avif.cpp microexif.cpp)
|
||||
target_link_libraries(kimg_avif PRIVATE "avif")
|
||||
endif()
|
||||
|
||||
@ -68,7 +68,7 @@ kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
|
||||
##################################
|
||||
|
||||
if (LibHeif_FOUND)
|
||||
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
||||
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp microexif.cpp)
|
||||
target_link_libraries(kimg_heif PRIVATE PkgConfig::LibHeif)
|
||||
endif()
|
||||
|
||||
@ -140,7 +140,7 @@ endif()
|
||||
##################################
|
||||
|
||||
if (LibJXR_FOUND)
|
||||
kimageformats_add_plugin(kimg_jxr SOURCES jxr.cpp)
|
||||
kimageformats_add_plugin(kimg_jxr SOURCES jxr.cpp microexif.cpp)
|
||||
kde_enable_exceptions()
|
||||
target_include_directories(kimg_jxr PRIVATE ${LIBJXR_INCLUDE_DIRS})
|
||||
target_link_libraries(kimg_jxr PRIVATE ${LIBJXR_LIBRARIES})
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QColorSpace>
|
||||
|
||||
#include "avif_p.h"
|
||||
#include "microexif_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <cfloat>
|
||||
@ -151,9 +152,6 @@ bool QAVIFHandler::ensureDecoder()
|
||||
|
||||
m_decoder = avifDecoderCreate();
|
||||
|
||||
m_decoder->ignoreExif = AVIF_TRUE;
|
||||
m_decoder->ignoreXMP = AVIF_TRUE;
|
||||
|
||||
#if AVIF_VERSION >= 80400
|
||||
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
||||
#endif
|
||||
@ -534,12 +532,49 @@ bool QAVIFHandler::decode_one_frame()
|
||||
|
||||
m_current_image.setColorSpace(colorspace);
|
||||
|
||||
if (m_decoder->image->exif.size) {
|
||||
auto exif = MicroExif::fromRawData(reinterpret_cast<const char *>(m_decoder->image->exif.data), m_decoder->image->exif.size);
|
||||
exif.updateImageResolution(m_current_image);
|
||||
exif.updateImageMetadata(m_current_image);
|
||||
}
|
||||
|
||||
if (m_decoder->image->xmp.size) {
|
||||
auto ba = QByteArray::fromRawData(reinterpret_cast<const char *>(m_decoder->image->xmp.data), m_decoder->image->xmp.size);
|
||||
m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromUtf8(ba));
|
||||
}
|
||||
|
||||
m_estimated_dimensions = m_current_image.size();
|
||||
|
||||
m_must_jump_to_next_image = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setMetadata(avifImage *avif, const QImage& image)
|
||||
{
|
||||
auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE)).toUtf8();
|
||||
if (!xmp.isEmpty()) {
|
||||
#if AVIF_VERSION >= 1000000
|
||||
auto res = avifImageSetMetadataXMP(avif, reinterpret_cast<const uint8_t *>(xmp.constData()), xmp.size());
|
||||
if (res != AVIF_RESULT_OK) {
|
||||
qWarning("ERROR in avifImageSetMetadataXMP: %s", avifResultToString(res));
|
||||
}
|
||||
#else
|
||||
avifImageSetMetadataXMP(avif, reinterpret_cast<const uint8_t *>(xmp.constData()), xmp.size());
|
||||
#endif
|
||||
}
|
||||
auto exif = MicroExif::fromImage(image).toByteArray();
|
||||
if (!exif.isEmpty()) {
|
||||
#if AVIF_VERSION >= 1000000
|
||||
auto res = avifImageSetMetadataExif(avif, reinterpret_cast<const uint8_t *>(exif.constData()), exif.size());
|
||||
if (res != AVIF_RESULT_OK) {
|
||||
qWarning("ERROR in avifImageSetMetadataExif: %s", avifResultToString(res));
|
||||
}
|
||||
#else
|
||||
avifImageSetMetadataExif(avif, reinterpret_cast<const uint8_t *>(exif.constData()), exif.size());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool QAVIFHandler::read(QImage *image)
|
||||
{
|
||||
if (!ensureOpened()) {
|
||||
@ -689,6 +724,8 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
#else
|
||||
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
||||
#endif
|
||||
// set EXIF and XMP metadata
|
||||
setMetadata(avif, tmpgrayimage);
|
||||
|
||||
if (tmpgrayimage.colorSpace().isValid()) {
|
||||
avif->colorPrimaries = (avifColorPrimaries)1;
|
||||
@ -915,6 +952,9 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
avif->colorPrimaries = primaries_to_save;
|
||||
avif->transferCharacteristics = transfer_to_save;
|
||||
|
||||
// set EXIF and XMP metadata
|
||||
setMetadata(avif, tmpcolorimage);
|
||||
|
||||
if (iccprofile.size() > 0) {
|
||||
#if AVIF_VERSION >= 1000000
|
||||
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||
|
@ -2557,3 +2557,5 @@ QImageIOHandler *QDDSPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_dds_p.cpp"
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "heif_p.h"
|
||||
#include "microexif_p.h"
|
||||
#include "util_p.h"
|
||||
#include <libheif/heif.h>
|
||||
|
||||
@ -15,14 +16,22 @@
|
||||
#include <QDebug>
|
||||
#include <QPointF>
|
||||
#include <QSysInfo>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef HEIF_MAX_METADATA_SIZE
|
||||
/*!
|
||||
* XMP and EXIF maximum size.
|
||||
*/
|
||||
#define HEIF_MAX_METADATA_SIZE (4 * 1024 * 1024)
|
||||
#endif
|
||||
|
||||
size_t HEIFHandler::m_initialized_count = 0;
|
||||
bool HEIFHandler::m_plugins_queried = false;
|
||||
bool HEIFHandler::m_heif_decoder_available = false;
|
||||
bool HEIFHandler::m_heif_encoder_available = false;
|
||||
bool HEIFHandler::m_hej2_decoder_available = false;
|
||||
bool HEIFHandler::m_hej2_encoder_available = false;
|
||||
bool HEIFHandler::m_avci_decoder_available = false;
|
||||
|
||||
extern "C" {
|
||||
@ -146,6 +155,14 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
break;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (save_alpha) {
|
||||
@ -278,7 +295,7 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
}
|
||||
|
||||
struct heif_encoder *encoder = nullptr;
|
||||
err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder);
|
||||
err = heif_context_get_encoder_for_format(context, encoder_codec, &encoder);
|
||||
if (err.code) {
|
||||
qWarning() << "Unable to get an encoder instance:" << err.message;
|
||||
heif_image_release(h_image);
|
||||
@ -305,7 +322,25 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr);
|
||||
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 (!exif.isEmpty()) {
|
||||
auto ba = exif.toByteArray();
|
||||
err = heif_context_add_exif_metadata(context, handle, ba.constData(), ba.size());
|
||||
}
|
||||
}
|
||||
// xmp metadata
|
||||
if (err.code == heif_error_Ok) {
|
||||
auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE));
|
||||
if (!xmp.isEmpty()) {
|
||||
auto ba = xmp.toUtf8();
|
||||
err = heif_context_add_XMP_metadata(context, handle, ba.constData(), ba.size());
|
||||
}
|
||||
}
|
||||
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
@ -527,7 +562,7 @@ bool HEIFHandler::ensureDecoder()
|
||||
|
||||
QImage::Format target_image_format;
|
||||
|
||||
if (bit_depth == 10 || bit_depth == 12) {
|
||||
if (bit_depth == 10 || bit_depth == 12 || bit_depth == 16) {
|
||||
if (hasAlphaChannel) {
|
||||
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
|
||||
target_image_format = QImage::Format_RGBA64;
|
||||
@ -619,6 +654,35 @@ bool HEIFHandler::ensureDecoder()
|
||||
}
|
||||
|
||||
switch (bit_depth) {
|
||||
case 16:
|
||||
if (hasAlphaChannel) {
|
||||
for (int y = 0; y < imageHeight; y++) {
|
||||
memcpy(m_current_image.scanLine(y), src + (y * stride), 8 * size_t(imageWidth));
|
||||
}
|
||||
} else { // no alpha channel
|
||||
for (int y = 0; y < imageHeight; y++) {
|
||||
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
|
||||
uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
|
||||
for (int x = 0; x < imageWidth; x++) {
|
||||
// R
|
||||
*dest_data = *src_word;
|
||||
src_word++;
|
||||
dest_data++;
|
||||
// G
|
||||
*dest_data = *src_word;
|
||||
src_word++;
|
||||
dest_data++;
|
||||
// B
|
||||
*dest_data = *src_word;
|
||||
src_word++;
|
||||
dest_data++;
|
||||
// X = 0xffff
|
||||
*dest_data = 0xffff;
|
||||
dest_data++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if (hasAlphaChannel) {
|
||||
for (int y = 0; y < imageHeight; y++) {
|
||||
@ -794,10 +858,40 @@ bool HEIFHandler::ensureDecoder()
|
||||
if (err.code) {
|
||||
qWarning() << "icc profile loading failed";
|
||||
} else {
|
||||
m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba));
|
||||
if (!m_current_image.colorSpace().isValid()) {
|
||||
QColorSpace colorspace = QColorSpace::fromIccProfile(ba);
|
||||
if (!colorspace.isValid()) {
|
||||
qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!";
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
|
||||
else if (colorspace.colorModel() == QColorSpace::ColorModel::Cmyk) {
|
||||
qWarning("CMYK ICC profile is not expected for HEIF, discarding the ICCprofile!");
|
||||
colorspace = QColorSpace();
|
||||
} else if (colorspace.colorModel() == QColorSpace::ColorModel::Gray) {
|
||||
if (hasAlphaChannel) {
|
||||
QPointF gray_whitePoint = colorspace.whitePoint();
|
||||
if (gray_whitePoint.isNull()) {
|
||||
gray_whitePoint = QPointF(0.3127f, 0.329f);
|
||||
}
|
||||
|
||||
const QPointF redP(0.64f, 0.33f);
|
||||
const QPointF greenP(0.3f, 0.6f);
|
||||
const QPointF blueP(0.15f, 0.06f);
|
||||
|
||||
QColorSpace::TransferFunction trc_new = colorspace.transferFunction();
|
||||
float gamma_new = colorspace.gamma();
|
||||
if (trc_new == QColorSpace::TransferFunction::Custom) {
|
||||
trc_new = QColorSpace::TransferFunction::SRgb;
|
||||
}
|
||||
colorspace = QColorSpace(gray_whitePoint, redP, greenP, blueP, trc_new, gamma_new);
|
||||
if (!colorspace.isValid()) {
|
||||
qWarning("HEIF plugin created invalid QColorSpace!");
|
||||
}
|
||||
} else { // no alpha channel
|
||||
m_current_image.convertTo(bit_depth > 8 ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
m_current_image.setColorSpace(colorspace);
|
||||
}
|
||||
} else {
|
||||
qWarning() << "icc profile is empty or above limits";
|
||||
@ -874,6 +968,38 @@ bool HEIFHandler::ensureDecoder()
|
||||
m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
|
||||
// read metadata
|
||||
if (auto numMetadata = heif_image_handle_get_number_of_metadata_blocks(handle, nullptr)) {
|
||||
QVector<heif_item_id> ids(numMetadata);
|
||||
heif_image_handle_get_list_of_metadata_block_IDs(handle, nullptr, ids.data(), numMetadata);
|
||||
for (int n = 0; n < numMetadata; ++n) {
|
||||
auto itemtype = heif_image_handle_get_metadata_type(handle, ids[n]);
|
||||
auto contenttype = heif_image_handle_get_metadata_content_type(handle, ids[n]);
|
||||
auto isExif = !std::strcmp(itemtype, "Exif");
|
||||
auto isXmp = !std::strcmp(contenttype, "application/rdf+xml");
|
||||
if (isExif || isXmp) {
|
||||
auto sz = heif_image_handle_get_metadata_size(handle, ids[n]);
|
||||
if (sz == 0 || sz >= HEIF_MAX_METADATA_SIZE)
|
||||
continue;
|
||||
QByteArray ba(sz, char());
|
||||
auto err = heif_image_handle_get_metadata(handle, ids[n], ba.data());
|
||||
if (err.code != heif_error_Ok) {
|
||||
qWarning() << "Error while reading metadata" << err.message;
|
||||
continue;
|
||||
}
|
||||
if (isXmp) {
|
||||
m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromUtf8(ba));
|
||||
} else if (isExif) {
|
||||
auto exif = MicroExif::fromByteArray(ba, true);
|
||||
if (!exif.isEmpty()) {
|
||||
exif.updateImageResolution(m_current_image);
|
||||
exif.updateImageMetadata(m_current_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heif_image_release(img);
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
@ -902,6 +1028,13 @@ bool HEIFHandler::isHej2DecoderAvailable()
|
||||
return m_hej2_decoder_available;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isHej2EncoderAvailable()
|
||||
{
|
||||
HEIFHandler::queryHeifLib();
|
||||
|
||||
return m_hej2_encoder_available;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isAVCIDecoderAvailable()
|
||||
{
|
||||
HEIFHandler::queryHeifLib();
|
||||
@ -924,8 +1057,9 @@ void HEIFHandler::queryHeifLib()
|
||||
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, 0)
|
||||
#if LIBHEIF_HAVE_VERSION(1, 19, 6)
|
||||
m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC);
|
||||
#endif
|
||||
m_plugins_queried = true;
|
||||
@ -992,6 +1126,9 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
|
||||
if (HEIFHandler::isHej2DecoderAvailable()) {
|
||||
format_cap |= CanRead;
|
||||
}
|
||||
if (HEIFHandler::isHej2EncoderAvailable()) {
|
||||
format_cap |= CanWrite;
|
||||
}
|
||||
return format_cap;
|
||||
}
|
||||
|
||||
@ -1021,7 +1158,7 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
|
||||
}
|
||||
}
|
||||
|
||||
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
|
||||
if (device->isWritable() && (HEIFHandler::isHeifEncoderAvailable() || HEIFHandler::isHej2EncoderAvailable())) {
|
||||
cap |= CanWrite;
|
||||
}
|
||||
return cap;
|
||||
|
@ -31,6 +31,7 @@ public:
|
||||
static bool isHeifDecoderAvailable();
|
||||
static bool isHeifEncoderAvailable();
|
||||
static bool isHej2DecoderAvailable();
|
||||
static bool isHej2EncoderAvailable();
|
||||
static bool isAVCIDecoderAvailable();
|
||||
|
||||
static bool isSupportedBMFFType(const QByteArray &header);
|
||||
@ -62,6 +63,7 @@ private:
|
||||
static bool m_heif_decoder_available;
|
||||
static bool m_heif_encoder_available;
|
||||
static bool m_hej2_decoder_available;
|
||||
static bool m_hej2_encoder_available;
|
||||
static bool m_avci_decoder_available;
|
||||
|
||||
static QMutex &getHEIFHandlerMutex();
|
||||
|
@ -27,6 +27,13 @@
|
||||
#define JP2_MAX_IMAGE_HEIGHT JP2_MAX_IMAGE_WIDTH
|
||||
#endif
|
||||
|
||||
/* *** JP2_MAX_IMAGE_PIXELS ***
|
||||
* OpenJPEG seems limited to an image of 2 gigapixel size.
|
||||
*/
|
||||
#ifndef JP2_MAX_IMAGE_PIXELS
|
||||
#define JP2_MAX_IMAGE_PIXELS std::numeric_limits<qint32>::max()
|
||||
#endif
|
||||
|
||||
/* *** JP2_ENABLE_HDR ***
|
||||
* Enable float image formats. Disabled by default
|
||||
* due to lack of test images.
|
||||
@ -101,11 +108,20 @@ public:
|
||||
JP2HandlerPrivate()
|
||||
: m_jp2_stream(nullptr)
|
||||
, m_jp2_image(nullptr)
|
||||
, m_jp2_version(0)
|
||||
, m_jp2_codec(nullptr)
|
||||
, m_quality(-1)
|
||||
, m_subtype(JP2_SUBTYPE)
|
||||
{
|
||||
|
||||
auto sver = QString::fromLatin1(QByteArray(opj_version())).split(QChar(u'.'));
|
||||
if (sver.size() == 3) {
|
||||
bool ok1, ok2, ok3;
|
||||
auto v1 = sver.at(0).toInt(&ok1);
|
||||
auto v2 = sver.at(1).toInt(&ok2);
|
||||
auto v3 = sver.at(2).toInt(&ok3);
|
||||
if (ok1 && ok2 && ok3)
|
||||
m_jp2_version = QT_VERSION_CHECK(v1, v2, v3);
|
||||
}
|
||||
}
|
||||
~JP2HandlerPrivate()
|
||||
{
|
||||
@ -323,6 +339,11 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (qint64(width) * qint64(height) > JP2_MAX_IMAGE_PIXELS) {
|
||||
qCritical() << "Maximum image size is limited to" << JP2_MAX_IMAGE_PIXELS << "pixels";
|
||||
return false;
|
||||
}
|
||||
|
||||
// OpenJPEG uses a shadow copy @32-bit/channel so we need to do a check
|
||||
auto maxBytes = qint64(QImageReader::allocationLimit()) * 1024 * 1024;
|
||||
auto neededBytes = qint64(width) * height * nchannels * 4;
|
||||
@ -363,6 +384,17 @@ public:
|
||||
prec = 0;
|
||||
}
|
||||
auto jp2cs = m_jp2_image->color_space;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
if (jp2cs == OPJ_CLRSPC_UNKNOWN || jp2cs == OPJ_CLRSPC_UNSPECIFIED) {
|
||||
auto cs = colorSpace();
|
||||
if (cs.colorModel() == QColorSpace::ColorModel::Cmyk)
|
||||
jp2cs = OPJ_CLRSPC_CMYK;
|
||||
else if (cs.colorModel() == QColorSpace::ColorModel::Rgb)
|
||||
jp2cs = OPJ_CLRSPC_SRGB;
|
||||
else if (cs.colorModel() == QColorSpace::ColorModel::Gray)
|
||||
jp2cs = OPJ_CLRSPC_GRAY;
|
||||
}
|
||||
#endif
|
||||
if (jp2cs == OPJ_CLRSPC_UNKNOWN || jp2cs == OPJ_CLRSPC_UNSPECIFIED) {
|
||||
if (m_jp2_image->numcomps == 1)
|
||||
jp2cs = OPJ_CLRSPC_GRAY;
|
||||
@ -453,13 +485,28 @@ public:
|
||||
return subType() == J2K_SUBTYPE ? OPJ_CODEC_J2K : OPJ_CODEC_JP2;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief opjVersion
|
||||
* \return The runtime library version.
|
||||
*/
|
||||
qint32 opjVersion() const
|
||||
{
|
||||
return m_jp2_version;
|
||||
}
|
||||
|
||||
bool imageToJp2(const QImage &image)
|
||||
{
|
||||
auto ncomp = image.hasAlphaChannel() ? 4 : 3;
|
||||
auto prec = 8;
|
||||
auto cs = OPJ_CLRSPC_SRGB;
|
||||
auto convFormat = image.format();
|
||||
auto isFloat = false;
|
||||
auto cs = OPJ_CLRSPC_SRGB;
|
||||
if (opjVersion() >= QT_VERSION_CHECK(2, 5, 4)) {
|
||||
auto ics = image.colorSpace();
|
||||
if (!(ics.isValid() && ics.primaries() == QColorSpace::Primaries::SRgb && ics.transferFunction() == QColorSpace::TransferFunction::SRgb)) {
|
||||
cs = OPJ_CLRSPC_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
switch (image.format()) {
|
||||
case QImage::Format_Mono:
|
||||
@ -527,7 +574,7 @@ public:
|
||||
break;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
case QImage::Format_CMYK8888: // requires OpenJPEG 2.5.3+
|
||||
if (strcmp(opj_version(), "2.5.3") >= 0) {
|
||||
if (opjVersion() >= QT_VERSION_CHECK(2, 5, 3)) {
|
||||
ncomp = 4;
|
||||
cs = OPJ_CLRSPC_CMYK;
|
||||
break;
|
||||
@ -613,8 +660,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// With SRGB, Gray and CMYK, writing the colorspace gives an assert
|
||||
if (cs == OPJ_CLRSPC_UNKNOWN || cs == OPJ_CLRSPC_UNSPECIFIED) {
|
||||
if (opjVersion() >= QT_VERSION_CHECK(2, 5, 4)) {
|
||||
auto colorSpace = scl.targetColorSpace().iccProfile();
|
||||
if (!colorSpace.isEmpty()) {
|
||||
m_jp2_image->icc_profile_buf = reinterpret_cast<OPJ_BYTE *>(malloc(colorSpace.size()));
|
||||
@ -674,6 +720,8 @@ private:
|
||||
|
||||
opj_image_t *m_jp2_image;
|
||||
|
||||
qint32 m_jp2_version;
|
||||
|
||||
// read
|
||||
opj_codec_t *m_jp2_codec;
|
||||
|
||||
|
@ -791,13 +791,8 @@ bool QJpegXLHandler::decode_one_frame()
|
||||
|
||||
if (!m_exif.isEmpty()) {
|
||||
auto exif = MicroExif::fromByteArray(m_exif);
|
||||
// set image resolution
|
||||
if (exif.horizontalResolution() > 0)
|
||||
m_current_image.setDotsPerMeterX(qRound(exif.horizontalResolution() / 25.4 * 1000));
|
||||
if (exif.verticalResolution() > 0)
|
||||
m_current_image.setDotsPerMeterY(qRound(exif.verticalResolution() / 25.4 * 1000));
|
||||
// set image metadata
|
||||
exif.toImageMetadata(m_current_image);
|
||||
exif.updateImageResolution(m_current_image);
|
||||
exif.updateImageMetadata(m_current_image);
|
||||
}
|
||||
|
||||
m_next_image_delay = m_framedelays[m_currentimage_index];
|
||||
@ -1329,7 +1324,10 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
output_info.uses_original_profile = JXL_FALSE;
|
||||
|
||||
if (tmpimage.colorSpace().isValid()) {
|
||||
const QPointF whiteP = image.colorSpace().whitePoint();
|
||||
QPointF whiteP(0.3127f, 0.329f);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
whiteP = image.colorSpace().whitePoint();
|
||||
#endif
|
||||
|
||||
switch (tmpimage.colorSpace().primaries()) {
|
||||
case QColorSpace::Primaries::SRgb:
|
||||
@ -1358,6 +1356,9 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
break;
|
||||
case QColorSpace::Primaries::ProPhotoRgb:
|
||||
color_profile.white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
|
||||
whiteP = QPointF(0.3457f, 0.3585f);
|
||||
#endif
|
||||
color_profile.white_point_xy[0] = whiteP.x();
|
||||
color_profile.white_point_xy[1] = whiteP.y();
|
||||
color_profile.primaries = JXL_PRIMARIES_CUSTOM;
|
||||
@ -1368,6 +1369,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
color_profile.primaries_blue_xy[0] = 0.0366;
|
||||
color_profile.primaries_blue_xy[1] = 0.0001;
|
||||
break;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
case QColorSpace::Primaries::Bt2020:
|
||||
color_profile.white_point = JXL_WHITE_POINT_D65;
|
||||
color_profile.primaries = JXL_PRIMARIES_2100;
|
||||
@ -1378,6 +1380,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
color_profile.primaries_blue_xy[0] = 0.131;
|
||||
color_profile.primaries_blue_xy[1] = 0.046;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
if (is_gray && !whiteP.isNull()) {
|
||||
color_profile.white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "jxr_p.h"
|
||||
#include "microexif_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
@ -83,9 +84,12 @@ Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
|
||||
class JXRHandlerPrivate : public QSharedData
|
||||
{
|
||||
private:
|
||||
QSharedPointer<QTemporaryDir> tempDir;
|
||||
mutable QSharedPointer<QFile> jxrFile;
|
||||
mutable QHash<QString, QString> txtMeta;
|
||||
QSharedPointer<QTemporaryDir> m_tempDir;
|
||||
QSharedPointer<QFile> m_jxrFile;
|
||||
MicroExif m_exif;
|
||||
qint32 m_quality;
|
||||
QImageIOHandler::Transformations m_transformations;
|
||||
mutable QHash<QString, QString> m_txtMeta;
|
||||
|
||||
public:
|
||||
PKFactory *pFactory = nullptr;
|
||||
@ -94,8 +98,10 @@ public:
|
||||
PKImageEncode *pEncoder = nullptr;
|
||||
|
||||
JXRHandlerPrivate()
|
||||
: m_quality(-1)
|
||||
, m_transformations(QImageIOHandler::TransformationNone)
|
||||
{
|
||||
tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
|
||||
m_tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
|
||||
if (PKCreateFactory(&pFactory, PK_SDK_VERSION) == WMP_errSuccess) {
|
||||
PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION);
|
||||
}
|
||||
@ -123,9 +129,101 @@ public:
|
||||
|
||||
QString fileName() const
|
||||
{
|
||||
return jxrFile->fileName();
|
||||
return m_jxrFile->fileName();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief setQuality
|
||||
* Set the image quality (write only)
|
||||
* \param q
|
||||
*/
|
||||
void setQuality(qint32 q)
|
||||
{
|
||||
m_quality = q;
|
||||
}
|
||||
qint32 quality() const
|
||||
{
|
||||
return m_quality;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief setTransformation
|
||||
* Set the image transformation (read/write)
|
||||
* \param t
|
||||
*/
|
||||
void setTransformation(const QImageIOHandler::Transformations& t)
|
||||
{
|
||||
m_transformations = t;
|
||||
}
|
||||
QImageIOHandler::Transformations transformation() const
|
||||
{
|
||||
return m_transformations;
|
||||
}
|
||||
|
||||
static QImageIOHandler::Transformations orientationToTransformation(const ORIENTATION& o)
|
||||
{
|
||||
auto v = QImageIOHandler::TransformationNone;
|
||||
switch (o) {
|
||||
case O_FLIPV:
|
||||
v = QImageIOHandler::TransformationFlip;
|
||||
break;
|
||||
case O_FLIPH:
|
||||
v = QImageIOHandler::TransformationMirror;
|
||||
break;
|
||||
case O_FLIPVH:
|
||||
v = QImageIOHandler::TransformationRotate180;
|
||||
break;
|
||||
case O_RCW:
|
||||
v = QImageIOHandler::TransformationRotate90;
|
||||
break;
|
||||
case O_RCW_FLIPH:
|
||||
v = QImageIOHandler::TransformationFlipAndRotate90;
|
||||
break;
|
||||
case O_RCW_FLIPV:
|
||||
v = QImageIOHandler::TransformationMirrorAndRotate90;
|
||||
break;
|
||||
case O_RCW_FLIPVH:
|
||||
v = QImageIOHandler::TransformationRotate270;
|
||||
break;
|
||||
default:
|
||||
v = QImageIOHandler::TransformationNone;
|
||||
break;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
static ORIENTATION transformationToOrientation(const QImageIOHandler::Transformations& t)
|
||||
{
|
||||
auto v = O_NONE;
|
||||
switch (t) {
|
||||
case QImageIOHandler::TransformationFlip:
|
||||
v = O_FLIPV;
|
||||
break;
|
||||
case QImageIOHandler::TransformationMirror:
|
||||
v = O_FLIPH;
|
||||
break;
|
||||
case QImageIOHandler::TransformationRotate180:
|
||||
v = O_FLIPVH;
|
||||
break;
|
||||
case QImageIOHandler::TransformationRotate90:
|
||||
v = O_RCW;
|
||||
break;
|
||||
case QImageIOHandler::TransformationFlipAndRotate90:
|
||||
v = O_RCW_FLIPH;
|
||||
break;
|
||||
case QImageIOHandler::TransformationMirrorAndRotate90:
|
||||
v = O_RCW_FLIPV;
|
||||
break;
|
||||
case QImageIOHandler::TransformationRotate270:
|
||||
v = O_RCW_FLIPVH;
|
||||
break;
|
||||
default:
|
||||
v = O_NONE;
|
||||
break;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/* *** READ *** */
|
||||
|
||||
/*!
|
||||
@ -318,11 +416,20 @@ public:
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief setTextMetadata
|
||||
* Set the text metadata into \a image
|
||||
* \brief exifData
|
||||
* \return The EXIF data.
|
||||
*/
|
||||
MicroExif exifData() const
|
||||
{
|
||||
return m_exif;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief setMetadata
|
||||
* Set the metadata into \a image
|
||||
* \param image Image on which to write metadata
|
||||
*/
|
||||
void setTextMetadata(QImage& image)
|
||||
void setMetadata(QImage& image)
|
||||
{
|
||||
auto xmp = xmpData();
|
||||
if (!xmp.isEmpty()) {
|
||||
@ -344,10 +451,6 @@ public:
|
||||
if (!model.isEmpty()) {
|
||||
image.setText(QStringLiteral(META_KEY_MODEL), model);
|
||||
}
|
||||
auto cDate = dateTime();
|
||||
if (!cDate.isEmpty()) {
|
||||
image.setText(QStringLiteral(META_KEY_CREATIONDATE), cDate);
|
||||
}
|
||||
auto author = artist();
|
||||
if (!author.isEmpty()) {
|
||||
image.setText(QStringLiteral(META_KEY_AUTHOR), author);
|
||||
@ -368,20 +471,23 @@ public:
|
||||
if (!docn.isEmpty()) {
|
||||
image.setText(QStringLiteral(META_KEY_DOCUMENTNAME), docn);
|
||||
}
|
||||
auto exif = exifData();
|
||||
if (!exif.isEmpty()) {
|
||||
exif.updateImageMetadata(image);
|
||||
}
|
||||
}
|
||||
|
||||
#define META_TEXT(name, key) \
|
||||
QString name() const \
|
||||
{ \
|
||||
readTextMeta(); \
|
||||
return txtMeta.value(QStringLiteral(key)); \
|
||||
return m_txtMeta.value(QStringLiteral(key)); \
|
||||
}
|
||||
|
||||
META_TEXT(description, META_KEY_DESCRIPTION)
|
||||
META_TEXT(cameraMake, META_KEY_MANUFACTURER)
|
||||
META_TEXT(cameraModel, META_KEY_MODEL)
|
||||
META_TEXT(software, META_KEY_SOFTWARE)
|
||||
META_TEXT(dateTime, META_KEY_CREATIONDATE)
|
||||
META_TEXT(artist, META_KEY_AUTHOR)
|
||||
META_TEXT(copyright, META_KEY_COPYRIGHT)
|
||||
META_TEXT(caption, META_KEY_TITLE)
|
||||
@ -400,9 +506,9 @@ public:
|
||||
bool initForWriting()
|
||||
{
|
||||
// I have to use QFile because, on Windows, the QTemporary file is locked (even if I close it)
|
||||
auto fileName = QStringLiteral("%1.jxr").arg(tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
|
||||
auto fileName = QStringLiteral("%1.jxr").arg(m_tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
|
||||
QSharedPointer<QFile> file(new QFile(fileName));
|
||||
jxrFile = file;
|
||||
m_jxrFile = file;
|
||||
return initEncoder();
|
||||
}
|
||||
|
||||
@ -422,7 +528,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!deviceCopy(device, jxrFile.data())) {
|
||||
if (!deviceCopy(device, m_jxrFile.data())) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while writing in the target device";
|
||||
return false;
|
||||
}
|
||||
@ -545,6 +651,9 @@ public:
|
||||
wmiSCP->cNumOfSliceMinus1H = wmiSCP->cNumOfSliceMinus1V = 0;
|
||||
wmiSCP->sbSubband = SB_ALL;
|
||||
wmiSCP->uAlphaMode = image.hasAlphaChannel() ? 2 : 0;
|
||||
if (quality() > -1) {
|
||||
wmiSCP->uiDefaultQPIndex = qBound(0, 100 - quality(), 100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -580,7 +689,6 @@ public:
|
||||
META_CTEXT(META_KEY_MODEL, pvarCameraModel)
|
||||
META_CTEXT(META_KEY_AUTHOR, pvarArtist)
|
||||
META_CTEXT(META_KEY_COPYRIGHT, pvarCopyright)
|
||||
META_CTEXT(META_KEY_CREATIONDATE, pvarDateTime)
|
||||
META_CTEXT(META_KEY_DOCUMENTNAME, pvarDocumentName)
|
||||
META_CTEXT(META_KEY_HOSTCOMPUTER, pvarHostComputer)
|
||||
META_WTEXT(META_KEY_TITLE, pvarCaption)
|
||||
@ -595,12 +703,33 @@ public:
|
||||
meta.pvarSoftware.VT.pszVal = software.data();
|
||||
}
|
||||
|
||||
// Date and Time (TIFF format)
|
||||
auto cDate = QDateTime::fromString(image.text(QStringLiteral(META_KEY_MODIFICATIONDATE)), Qt::ISODate);
|
||||
auto sDate = cDate.isValid() ? cDate.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toLatin1() : QByteArray();
|
||||
if (!sDate.isEmpty()) {
|
||||
meta.pvarDateTime.vt = DPKVT_LPSTR;
|
||||
meta.pvarDateTime.VT.pszVal = sDate.data();
|
||||
}
|
||||
|
||||
auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE)).toUtf8();
|
||||
if (!xmp.isNull()) {
|
||||
if (auto err = PKImageEncode_SetXMPMetadata_WMP(pEncoder, reinterpret_cast<quint8 *>(xmp.data()), xmp.size())) {
|
||||
if (auto err = PKImageEncode_SetXMPMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(xmp.constData()), xmp.size())) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting XMP data:" << err;
|
||||
}
|
||||
}
|
||||
|
||||
auto exif = MicroExif::fromImage(image);
|
||||
if (!exif.isEmpty()) {
|
||||
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian);
|
||||
if (auto err = PKImageEncode_SetEXIFMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(exifIfd.constData()), exifIfd.size())) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting EXIF data:" << err;
|
||||
}
|
||||
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian);
|
||||
if (auto err = PKImageEncode_SetGPSInfoMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(gpsIfd.constData()), gpsIfd.size())) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting GPS data:" << err;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto err = pEncoder->SetDescriptiveMetadata(pEncoder, &meta)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting descriptive data:" << err;
|
||||
}
|
||||
@ -710,11 +839,11 @@ private:
|
||||
if (device == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!jxrFile.isNull()) {
|
||||
if (!m_jxrFile.isNull()) {
|
||||
return true;
|
||||
}
|
||||
// I have to use QFile because, on Windows, the QTemporary file is locked (even if I close it)
|
||||
auto fileName = QStringLiteral("%1.jxr").arg(tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
|
||||
auto fileName = QStringLiteral("%1.jxr").arg(m_tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
|
||||
QSharedPointer<QFile> file(new QFile(fileName));
|
||||
if (!file->open(QFile::WriteOnly)) {
|
||||
return false;
|
||||
@ -724,7 +853,8 @@ private:
|
||||
return false;
|
||||
}
|
||||
file->close();
|
||||
jxrFile = file;
|
||||
m_exif = MicroExif::fromDevice(file.data());
|
||||
m_jxrFile = file;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -740,6 +870,8 @@ private:
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::initDecoder() unable to create decoder:" << err;
|
||||
return false;
|
||||
}
|
||||
setTransformation(JXRHandlerPrivate::orientationToTransformation(pDecoder->WMP.wmiI.oOrientation));
|
||||
pDecoder->WMP.wmiI.oOrientation = O_NONE; // disable the library rotation application
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -755,6 +887,7 @@ private:
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::initEncoder() unable to create encoder:" << err;
|
||||
return false;
|
||||
}
|
||||
pEncoder->WMP.oOrientation = JXRHandlerPrivate::transformationToOrientation(transformation());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -762,7 +895,7 @@ private:
|
||||
if (pDecoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!txtMeta.isEmpty()) {
|
||||
if (!m_txtMeta.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -773,15 +906,14 @@ private:
|
||||
|
||||
#define META_TEXT(name, field) \
|
||||
if (meta.field.vt == DPKVT_LPSTR) \
|
||||
txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
|
||||
m_txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
|
||||
else if (meta.field.vt == DPKVT_LPWSTR) \
|
||||
txtMeta.insert(QStringLiteral(name), QString::fromUtf16(reinterpret_cast<char16_t *>(meta.field.VT.pwszVal)));
|
||||
m_txtMeta.insert(QStringLiteral(name), QString::fromUtf16(reinterpret_cast<char16_t *>(meta.field.VT.pwszVal)));
|
||||
|
||||
META_TEXT(META_KEY_DESCRIPTION, pvarImageDescription)
|
||||
META_TEXT(META_KEY_MANUFACTURER, pvarCameraMake)
|
||||
META_TEXT(META_KEY_MODEL, pvarCameraModel)
|
||||
META_TEXT(META_KEY_SOFTWARE, pvarSoftware)
|
||||
META_TEXT(META_KEY_CREATIONDATE, pvarDateTime)
|
||||
META_TEXT(META_KEY_AUTHOR, pvarArtist)
|
||||
META_TEXT(META_KEY_COPYRIGHT, pvarCopyright)
|
||||
META_TEXT(META_KEY_TITLE, pvarCaption)
|
||||
@ -867,7 +999,7 @@ bool JXRHandler::read(QImage *outImage)
|
||||
|
||||
// Metadata (e.g.: icc profile, description, etc...)
|
||||
img.setColorSpace(d->colorSpace());
|
||||
d->setTextMetadata(img);
|
||||
d->setMetadata(img);
|
||||
|
||||
#ifndef JXR_DENY_FLOAT_IMAGE
|
||||
// JXR float are stored in scRGB.
|
||||
@ -921,6 +1053,10 @@ bool JXRHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
#ifndef JXR_DISABLE_BGRA_HACK
|
||||
if (IsEqualGUID(jxlfmt, GUID_PKPixelFormat32bppRGB)) {
|
||||
jxlfmt = GUID_PKPixelFormat32bppBGR;
|
||||
qi.rgbSwap();
|
||||
}
|
||||
if (IsEqualGUID(jxlfmt, GUID_PKPixelFormat32bppRGBA)) {
|
||||
jxlfmt = GUID_PKPixelFormat32bppBGRA;
|
||||
qi.rgbSwap();
|
||||
@ -937,10 +1073,6 @@ bool JXRHandler::write(const QImage &image)
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() something wrong when calculating encoder parameters for" << qi.format();
|
||||
return false;
|
||||
}
|
||||
if (m_quality > -1) {
|
||||
wmiSCP.uiDefaultQPIndex = qBound(0, 100 - m_quality, 100);
|
||||
}
|
||||
|
||||
if (auto err = d->pEncoder->Initialize(d->pEncoder, pEncodeStream, &wmiSCP, sizeof(wmiSCP))) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while initializing the encoder:" << err;
|
||||
return false;
|
||||
@ -983,10 +1115,18 @@ bool JXRHandler::write(const QImage &image)
|
||||
void JXRHandler::setOption(ImageOption option, const QVariant &value)
|
||||
{
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
bool ok = false;
|
||||
auto ok = false;
|
||||
auto q = value.toInt(&ok);
|
||||
if (ok) {
|
||||
m_quality = q;
|
||||
d->setQuality(q);
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageTransformation) {
|
||||
auto ok = false;
|
||||
auto t = value.toInt(&ok);
|
||||
if (ok) {
|
||||
d->setTransformation(QImageIOHandler::Transformation(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1003,7 +1143,7 @@ bool JXRHandler::supportsOption(ImageOption option) const
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageTransformation) {
|
||||
return false; // disabled because test cases are missing
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1028,39 +1168,13 @@ QVariant JXRHandler::option(ImageOption option) const
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
v = m_quality;
|
||||
v = d->quality();
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageTransformation) {
|
||||
// TODO: rotation info (test case needed)
|
||||
if (d->initForReading(device())) {
|
||||
switch (d->pDecoder->WMP.oOrientationFromContainer) {
|
||||
case O_FLIPV:
|
||||
v = int(QImageIOHandler::TransformationFlip);
|
||||
break;
|
||||
case O_FLIPH:
|
||||
v = int(QImageIOHandler::TransformationMirror);
|
||||
break;
|
||||
case O_FLIPVH:
|
||||
v = int(QImageIOHandler::TransformationRotate180);
|
||||
break;
|
||||
case O_RCW:
|
||||
v = int(QImageIOHandler::TransformationRotate90);
|
||||
break;
|
||||
case O_RCW_FLIPV:
|
||||
v = int(QImageIOHandler::TransformationFlipAndRotate90);
|
||||
break;
|
||||
case O_RCW_FLIPH:
|
||||
v = int(QImageIOHandler::TransformationMirrorAndRotate90);
|
||||
break;
|
||||
case O_RCW_FLIPVH:
|
||||
v = int(QImageIOHandler::TransformationRotate270);
|
||||
break;
|
||||
default:
|
||||
v = int(QImageIOHandler::TransformationNone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ignore result: I might want to read the value set in writing
|
||||
d->initForReading(device());
|
||||
v = int(d->transformation());
|
||||
}
|
||||
|
||||
return v;
|
||||
@ -1068,7 +1182,6 @@ QVariant JXRHandler::option(ImageOption option) const
|
||||
|
||||
JXRHandler::JXRHandler()
|
||||
: d(new JXRHandlerPrivate)
|
||||
, m_quality(-1)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,6 @@ public:
|
||||
|
||||
private:
|
||||
mutable QSharedDataPointer<JXRHandlerPrivate> d;
|
||||
|
||||
qint32 m_quality;
|
||||
};
|
||||
|
||||
class JXRPlugin : public QImageIOPlugin
|
||||
|
@ -37,7 +37,12 @@
|
||||
// EXIF 3 specs
|
||||
#define EXIF_EXIFIFD 0x8769
|
||||
#define EXIF_GPSIFD 0x8825
|
||||
#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_COLORSPACE 0xA001
|
||||
#define EXIF_PIXELXDIM 0xA002
|
||||
#define EXIF_PIXELYDIM 0xA003
|
||||
@ -47,7 +52,6 @@
|
||||
#define EXIF_LENSMODEL 0xA434
|
||||
#define EXIF_LENSSERIALNUMBER 0xA435
|
||||
#define EXIF_IMAGETITLE 0xA436
|
||||
#define EXIF_EXIFVERSION 0x9000
|
||||
|
||||
#define EXIF_VAL_COLORSPACE_SRGB 1
|
||||
#define EXIF_VAL_COLORSPACE_UNCAL 0xFFFF
|
||||
@ -59,7 +63,8 @@
|
||||
#define GPS_LONGITUDE 4
|
||||
#define GPS_ALTITUDEREF 5
|
||||
#define GPS_ALTITUDE 6
|
||||
|
||||
#define GPS_IMGDIRECTIONREF 16
|
||||
#define GPS_IMGDIRECTION 17
|
||||
#define EXIF_TAG_VALUE(n, byteSize) (((n) << 6) | ((byteSize) & 0x3F))
|
||||
#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
|
||||
#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
|
||||
@ -119,7 +124,11 @@ static const KnownTags staticTagTypes = {
|
||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIME, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEORIGINAL, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_COLORSPACE, ExifTagType::Short),
|
||||
TagInfo(EXIF_PIXELXDIM, ExifTagType::Long),
|
||||
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
|
||||
@ -144,7 +153,9 @@ static const KnownTags staticGpsTagTypes = {
|
||||
TagInfo(GPS_LONGITUDEREF, ExifTagType::Ascii),
|
||||
TagInfo(GPS_LONGITUDE, ExifTagType::Rational),
|
||||
TagInfo(GPS_ALTITUDEREF, ExifTagType::Byte),
|
||||
TagInfo(GPS_ALTITUDE, ExifTagType::Rational)
|
||||
TagInfo(GPS_ALTITUDE, ExifTagType::Rational),
|
||||
TagInfo(GPS_IMGDIRECTIONREF, ExifTagType::Ascii),
|
||||
TagInfo(GPS_IMGDIRECTION, ExifTagType::Rational)
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@ -230,8 +241,8 @@ static bool checkHeader(QDataStream &ds)
|
||||
|
||||
quint16 version;
|
||||
ds >> version;
|
||||
if (version != 0x2A)
|
||||
return false;
|
||||
if (version != 0x002A && version != 0x01BC)
|
||||
return false; // not TIFF or JXR
|
||||
|
||||
quint32 offset;
|
||||
ds >> offset;
|
||||
@ -852,6 +863,46 @@ void MicroExif::setDateTime(const QDateTime &dt)
|
||||
setExifString(EXIF_OFFSETTIME, timeOffset(dt.offsetFromUtc() / 60));
|
||||
}
|
||||
|
||||
QDateTime MicroExif::dateTimeOriginal() const
|
||||
{
|
||||
auto dt = QDateTime::fromString(exifString(EXIF_DATETIMEORIGINAL), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||
auto ofTag = exifString(EXIF_OFFSETTIMEORIGINAL);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
}
|
||||
|
||||
void MicroExif::setDateTimeOriginal(const QDateTime &dt)
|
||||
{
|
||||
if (!dt.isValid()) {
|
||||
m_exifTags.remove(EXIF_DATETIMEORIGINAL);
|
||||
m_exifTags.remove(EXIF_OFFSETTIMEORIGINAL);
|
||||
return;
|
||||
}
|
||||
setExifString(EXIF_DATETIMEORIGINAL, dt.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")));
|
||||
setExifString(EXIF_OFFSETTIMEORIGINAL, timeOffset(dt.offsetFromUtc() / 60));
|
||||
}
|
||||
|
||||
QDateTime MicroExif::dateTimeDigitized() const
|
||||
{
|
||||
auto dt = QDateTime::fromString(exifString(EXIF_DATETIMEDIGITIZED), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||
auto ofTag = exifString(EXIF_OFFSETTIMEDIGITIZED);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
}
|
||||
|
||||
void MicroExif::setDateTimeDigitized(const QDateTime &dt)
|
||||
{
|
||||
if (!dt.isValid()) {
|
||||
m_exifTags.remove(EXIF_DATETIMEDIGITIZED);
|
||||
m_exifTags.remove(EXIF_OFFSETTIMEDIGITIZED);
|
||||
return;
|
||||
}
|
||||
setExifString(EXIF_DATETIMEDIGITIZED, dt.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")));
|
||||
setExifString(EXIF_OFFSETTIMEDIGITIZED, timeOffset(dt.offsetFromUtc() / 60));
|
||||
}
|
||||
|
||||
QString MicroExif::title() const
|
||||
{
|
||||
return exifString(EXIF_IMAGETITLE);
|
||||
@ -896,6 +947,10 @@ double MicroExif::latitude() const
|
||||
|
||||
void MicroExif::setLatitude(double degree)
|
||||
{
|
||||
if (qIsNaN(degree)) {
|
||||
m_gpsTags.remove(GPS_LATITUDEREF);
|
||||
m_gpsTags.remove(GPS_LATITUDE);
|
||||
}
|
||||
if (degree < -90.0 || degree > 90.0)
|
||||
return; // invalid latitude
|
||||
auto adeg = qAbs(degree);
|
||||
@ -921,6 +976,10 @@ double MicroExif::longitude() const
|
||||
|
||||
void MicroExif::setLongitude(double degree)
|
||||
{
|
||||
if (qIsNaN(degree)) {
|
||||
m_gpsTags.remove(GPS_LONGITUDEREF);
|
||||
m_gpsTags.remove(GPS_LONGITUDE);
|
||||
}
|
||||
if (degree < -180.0 || degree > 180.0)
|
||||
return; // invalid longitude
|
||||
auto adeg = qAbs(degree);
|
||||
@ -935,16 +994,44 @@ double MicroExif::altitude() const
|
||||
auto ref = m_gpsTags.value(GPS_ALTITUDEREF);
|
||||
if (ref.isNull())
|
||||
return qQNaN();
|
||||
if (!m_gpsTags.contains(GPS_ALTITUDE))
|
||||
return qQNaN();
|
||||
auto alt = m_gpsTags.value(GPS_ALTITUDE).toDouble();
|
||||
return (ref.toInt() == 0 || ref.toInt() == 2) ? alt : -alt;
|
||||
}
|
||||
|
||||
void MicroExif::setAltitude(double meters)
|
||||
{
|
||||
if (qIsNaN(meters)) {
|
||||
m_gpsTags.remove(GPS_ALTITUDEREF);
|
||||
m_gpsTags.remove(GPS_ALTITUDE);
|
||||
}
|
||||
m_gpsTags.insert(GPS_ALTITUDEREF, quint8(meters < 0 ? 1 : 0));
|
||||
m_gpsTags.insert(GPS_ALTITUDE, meters);
|
||||
}
|
||||
|
||||
double MicroExif::imageDirection(bool *isMagnetic) const
|
||||
{
|
||||
auto tmp = false;
|
||||
if (isMagnetic == nullptr)
|
||||
isMagnetic = &tmp;
|
||||
if (!m_gpsTags.contains(GPS_IMGDIRECTION))
|
||||
return qQNaN();
|
||||
auto ref = gpsString(GPS_IMGDIRECTIONREF).toUpper();
|
||||
*isMagnetic = (ref == QStringLiteral("M"));
|
||||
return m_gpsTags.value(GPS_IMGDIRECTION).toDouble();
|
||||
}
|
||||
|
||||
void MicroExif::setImageDirection(double degree, bool isMagnetic)
|
||||
{
|
||||
if (qIsNaN(degree)) {
|
||||
m_gpsTags.remove(GPS_IMGDIRECTIONREF);
|
||||
m_gpsTags.remove(GPS_IMGDIRECTION);
|
||||
}
|
||||
m_gpsTags.insert(GPS_IMGDIRECTIONREF, isMagnetic ? QStringLiteral("M") : QStringLiteral("T"));
|
||||
m_gpsTags.insert(GPS_IMGDIRECTION, degree);
|
||||
}
|
||||
|
||||
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
{
|
||||
QByteArray ba;
|
||||
@ -956,6 +1043,50 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
return ba;
|
||||
}
|
||||
|
||||
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
{
|
||||
QByteArray ba;
|
||||
{
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
ds.setByteOrder(byteOrder);
|
||||
auto exifTags = m_exifTags;
|
||||
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
|
||||
TagPos positions;
|
||||
if (!writeIfd(ds, exifTags, positions))
|
||||
return {};
|
||||
}
|
||||
return ba;
|
||||
}
|
||||
|
||||
bool MicroExif::setExifIfdByteArray(const QByteArray &ba, const QDataStream::ByteOrder &byteOrder)
|
||||
{
|
||||
QDataStream ds(ba);
|
||||
ds.setByteOrder(byteOrder);
|
||||
return readIfd(ds, m_exifTags, 0, staticTagTypes);
|
||||
}
|
||||
|
||||
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
{
|
||||
QByteArray ba;
|
||||
{
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
ds.setByteOrder(byteOrder);
|
||||
auto gpsTags = m_gpsTags;
|
||||
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
||||
TagPos positions;
|
||||
if (!writeIfd(ds, gpsTags, positions, 0, staticGpsTagTypes))
|
||||
return {};
|
||||
return ba;
|
||||
}
|
||||
}
|
||||
|
||||
bool MicroExif::setGpsIfdByteArray(const QByteArray &ba, const QDataStream::ByteOrder &byteOrder)
|
||||
{
|
||||
QDataStream ds(ba);
|
||||
ds.setByteOrder(byteOrder);
|
||||
return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
|
||||
}
|
||||
|
||||
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder) const
|
||||
{
|
||||
if (device == nullptr || device->isSequential() || isEmpty())
|
||||
@ -972,7 +1103,7 @@ bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder
|
||||
return true;
|
||||
}
|
||||
|
||||
void MicroExif::toImageMetadata(QImage &targetImage, bool replaceExisting) const
|
||||
void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) const
|
||||
{
|
||||
// set TIFF strings
|
||||
for (auto &&p : tiffStrMap) {
|
||||
@ -993,8 +1124,13 @@ void MicroExif::toImageMetadata(QImage &targetImage, bool replaceExisting) const
|
||||
}
|
||||
|
||||
// set date and time
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_CREATIONDATE)).isEmpty()) {
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_MODIFICATIONDATE)).isEmpty()) {
|
||||
auto dt = dateTime();
|
||||
if (dt.isValid())
|
||||
targetImage.setText(QStringLiteral(META_KEY_MODIFICATIONDATE), dt.toString(Qt::ISODate));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_CREATIONDATE)).isEmpty()) {
|
||||
auto dt = dateTimeOriginal();
|
||||
if (dt.isValid())
|
||||
targetImage.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate));
|
||||
}
|
||||
@ -1015,15 +1151,47 @@ void MicroExif::toImageMetadata(QImage &targetImage, bool replaceExisting) const
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_LONGITUDE), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_DIRECTION)).isEmpty()) {
|
||||
auto v = imageDirection();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_DIRECTION), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
}
|
||||
|
||||
MicroExif MicroExif::fromByteArray(const QByteArray &ba)
|
||||
void MicroExif::updateImageResolution(QImage &targetImage)
|
||||
{
|
||||
if (horizontalResolution() > 0)
|
||||
targetImage.setDotsPerMeterX(qRound(horizontalResolution() / 25.4 * 1000));
|
||||
if (verticalResolution() > 0)
|
||||
targetImage.setDotsPerMeterY(qRound(verticalResolution() / 25.4 * 1000));
|
||||
}
|
||||
|
||||
MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader)
|
||||
{
|
||||
auto ba0(ba);
|
||||
if (searchHeader) {
|
||||
auto idxLE = ba0.indexOf(QByteArray("II"));
|
||||
auto idxBE = ba0.indexOf(QByteArray("MM"));
|
||||
auto idx = -1;
|
||||
if (idxLE > -1 && idxBE > -1)
|
||||
idx = std::min(idxLE, idxBE);
|
||||
else
|
||||
idx = idxLE > -1 ? idxLE : idxBE;
|
||||
if(idx > 0)
|
||||
ba0 = ba0.mid(idx);
|
||||
}
|
||||
QBuffer buf;
|
||||
buf.setData(ba);
|
||||
buf.setData(ba0);
|
||||
return fromDevice(&buf);
|
||||
}
|
||||
|
||||
MicroExif MicroExif::fromRawData(const char *data, size_t size, bool searchHeader)
|
||||
{
|
||||
if (data == nullptr || size == 0)
|
||||
return {};
|
||||
return fromByteArray(QByteArray::fromRawData(data, size), searchHeader);
|
||||
}
|
||||
|
||||
MicroExif MicroExif::fromDevice(QIODevice *device)
|
||||
{
|
||||
if (device == nullptr || device->isSequential())
|
||||
@ -1088,12 +1256,18 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
exif.setSoftware(sw.trimmed());
|
||||
}
|
||||
|
||||
// TIFF Creation date and time
|
||||
auto dt = QDateTime::fromString(image.text(QStringLiteral(META_KEY_CREATIONDATE)), Qt::ISODate);
|
||||
// TIFF date and time
|
||||
auto dt = QDateTime::fromString(image.text(QStringLiteral(META_KEY_MODIFICATIONDATE)), Qt::ISODate);
|
||||
if (!dt.isValid())
|
||||
dt = QDateTime::currentDateTime();
|
||||
exif.setDateTime(dt);
|
||||
|
||||
// EXIF original date and time
|
||||
dt = QDateTime::fromString(image.text(QStringLiteral(META_KEY_CREATIONDATE)), Qt::ISODate);
|
||||
if (!dt.isValid())
|
||||
dt = QDateTime::currentDateTime();
|
||||
exif.setDateTimeOriginal(dt);
|
||||
|
||||
// GPS Info
|
||||
auto ok = false;
|
||||
auto alt = image.text(QStringLiteral(META_KEY_ALTITUDE)).toDouble(&ok);
|
||||
@ -1105,6 +1279,9 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
auto lon = image.text(QStringLiteral(META_KEY_LONGITUDE)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setLongitude(lon);
|
||||
auto dir = image.text(QStringLiteral(META_KEY_DIRECTION)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setImageDirection(dir);
|
||||
|
||||
return exif;
|
||||
}
|
||||
|
@ -31,8 +31,6 @@
|
||||
*
|
||||
* This class is a partial (or rather minimal) implementation and is only used
|
||||
* to avoid including external libraries when only a few tags are needed.
|
||||
*
|
||||
* It reads/writes the main IFD only.
|
||||
*/
|
||||
class MicroExif
|
||||
{
|
||||
@ -201,6 +199,20 @@ public:
|
||||
QDateTime dateTime() const;
|
||||
void setDateTime(const QDateTime& dt);
|
||||
|
||||
/*!
|
||||
* \brief dateTimeOriginal
|
||||
* \return The date and time when the original image data was generated.
|
||||
*/
|
||||
QDateTime dateTimeOriginal() const;
|
||||
void setDateTimeOriginal(const QDateTime& dt);
|
||||
|
||||
/*!
|
||||
* \brief dateTimeDigitized
|
||||
* \return The date and time when the image was stored as digital data.
|
||||
*/
|
||||
QDateTime dateTimeDigitized() const;
|
||||
void setDateTimeDigitized(const QDateTime& dt);
|
||||
|
||||
/*!
|
||||
* \brief title
|
||||
* \return The title of the image.
|
||||
@ -237,37 +249,106 @@ public:
|
||||
double altitude() const;
|
||||
void setAltitude(double meters);
|
||||
|
||||
/*!
|
||||
* \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.
|
||||
* \return Floating-point number indicating the direction of the image when it was captured. The range of values is from 0.00 to 359.99 or NaN if not set.
|
||||
*/
|
||||
double imageDirection(bool *isMagnetic = nullptr) const;
|
||||
void setImageDirection(double degree, bool isMagnetic = false);
|
||||
|
||||
/*!
|
||||
* \brief toByteArray
|
||||
* Converts the class to RAW data. The raw data contains:
|
||||
* - TIFF header
|
||||
* - MAIN IFD
|
||||
* - EXIF IFD
|
||||
* - GPS IFD
|
||||
* \param byteOrder Sets the serialization byte order for EXIF data.
|
||||
* \return A byte array containing the serialized data.
|
||||
* \sa write
|
||||
*/
|
||||
QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
|
||||
/*!
|
||||
* \brief exifIfdByteArray
|
||||
* Convert the EXIF IFD only to RAW data. Useful when you want to add EXIF data to an existing TIFF container.
|
||||
* \param byteOrder Sets the serialization byte order for the data.
|
||||
* \return A byte array containing the serialized data.
|
||||
*/
|
||||
QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
/*!
|
||||
* \brief setExifIfdByteArray
|
||||
* \param ba The RAW data of EXIF IFD.
|
||||
* \param byteOrder Sets the serialization byte order of the data.
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
bool setExifIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER);
|
||||
|
||||
/*!
|
||||
* \brief gpsIfdByteArray
|
||||
* Convert the GPS IFD only to RAW data. Useful when you want to add GPS data to an existing TIFF container.
|
||||
* \param byteOrder Sets the serialization byte order for the data.
|
||||
* \return A byte array containing the serialized data.
|
||||
*/
|
||||
QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
/*!
|
||||
* \brief setGpsIfdByteArray
|
||||
* \param ba The RAW data of GPS IFD.
|
||||
* \param byteOrder Sets the serialization byte order of the data.
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
bool setGpsIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER);
|
||||
|
||||
/*!
|
||||
* \brief write
|
||||
* Serialize the class on a device.
|
||||
* Serialize the class on a device. The serialized data contains:
|
||||
* - TIFF header
|
||||
* - MAIN IFD
|
||||
* - EXIF IFD
|
||||
* - GPS IFD
|
||||
* \param device A random access device.
|
||||
* \param byteOrder Sets the serialization byte order for EXIF data.
|
||||
* \return True on success, otherwise false.
|
||||
* \sa toByteArray
|
||||
*/
|
||||
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
|
||||
/*!
|
||||
* \brief toImageMetadata
|
||||
* Helper to set metadata in an image.
|
||||
* \brief updateImageMetadata
|
||||
* Helper to set EXIF metadata to the image.
|
||||
* \param targetImage The image to set metadata on.
|
||||
* \param replaceExisting Replaces any existing metadata.
|
||||
*/
|
||||
void toImageMetadata(QImage& targetImage, bool replaceExisting = false) const;
|
||||
void updateImageMetadata(QImage &targetImage, bool replaceExisting = false) const;
|
||||
|
||||
/*!
|
||||
* \brief updateImageResolution
|
||||
* Helper to set the EXIF resolution to the image. Resolution is set only if valid.
|
||||
* \param targetImage The image to set resolution on.
|
||||
*/
|
||||
void updateImageResolution(QImage &targetImage);
|
||||
|
||||
/*!
|
||||
* \brief fromByteArray
|
||||
* Creates the class from RAW EXIF data.
|
||||
* \param ba Raw data containing EXIF data.
|
||||
* \param searchHeader If true, the EXIF header is searched within the data. If false, the data must begin with the EXIF header.
|
||||
* \return The created class (empty on error).
|
||||
* \sa isEmpty
|
||||
*/
|
||||
static MicroExif fromByteArray(const QByteArray &ba);
|
||||
static MicroExif fromByteArray(const QByteArray &ba, bool searchHeader = false);
|
||||
|
||||
/*!
|
||||
* \brief fromRawData
|
||||
* Creates the class from RAW EXIF data.
|
||||
* \param data Raw data containing EXIF data.
|
||||
* \param size The size of \a data.
|
||||
* \param searchHeader If true, the EXIF header is searched within the data. If false, the data must begin with the EXIF header.
|
||||
* \return The created class (empty on error).
|
||||
* \sa isEmpty, fromByteArray
|
||||
*/
|
||||
static MicroExif fromRawData(const char *data, size_t size, bool searchHeader = false);
|
||||
|
||||
/*!
|
||||
* \brief fromDevice
|
||||
|
@ -545,7 +545,7 @@ static bool setExifData(QImage &img, const MicroExif &exif)
|
||||
{
|
||||
if (exif.isEmpty())
|
||||
return false;
|
||||
exif.toImageMetadata(img);
|
||||
exif.updateImageMetadata(img);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1504,7 +1504,18 @@ bool PSDHandler::read(QImage *image)
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
#endif
|
||||
} else if (!setColorSpace(img, irs)) {
|
||||
// qDebug() << "No colorspace info set!";
|
||||
// Float images are used by Photoshop as linear: if no color space
|
||||
// is present, a linear one should be chosen.
|
||||
if (header.color_mode == CM_RGB && header.depth == 32) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
if (header.color_mode == CM_GRAYSCALE && header.depth == 32) {
|
||||
auto qs = QColorSpace(QPointF(0.3127, 0.3291), QColorSpace::TransferFunction::Linear);
|
||||
qs.setDescription(QStringLiteral("Linear grayscale"));
|
||||
img.setColorSpace(qs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// XMP data
|
||||
|
@ -207,10 +207,10 @@ public:
|
||||
auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok);
|
||||
if (ok && v > 0) {
|
||||
if (m_pb._unitsOfMeasurement) { // Inches
|
||||
return qRound(width() / v / 25.4 * 1000);
|
||||
return qRoundOrZero(width() / v / 25.4 * 1000);
|
||||
}
|
||||
// Millimeters
|
||||
return qRound(width() / v * 1000);
|
||||
return qRoundOrZero(width() / v * 1000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -221,10 +221,10 @@ public:
|
||||
auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok);
|
||||
if (ok && v > 0) {
|
||||
if (m_pb._unitsOfMeasurement) { // Inches
|
||||
return qRound(width() / v / 25.4 * 1000);
|
||||
return qRoundOrZero(height() / v / 25.4 * 1000);
|
||||
}
|
||||
// Millimeters
|
||||
return qRound(width() / v * 1000);
|
||||
return qRoundOrZero(height() / v * 1000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -457,3 +457,4 @@ QImageIOHandler *ScitexPlugin::create(QIODevice *device, const QByteArray &forma
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_sct_p.cpp"
|
||||
|
@ -20,10 +20,12 @@
|
||||
#define META_KEY_COPYRIGHT "Copyright"
|
||||
#define META_KEY_CREATIONDATE "CreationDate"
|
||||
#define META_KEY_DESCRIPTION "Description"
|
||||
#define META_KEY_DIRECTION "Direction"
|
||||
#define META_KEY_DOCUMENTNAME "DocumentName"
|
||||
#define META_KEY_HOSTCOMPUTER "HostComputer"
|
||||
#define META_KEY_LATITUDE "Latitude"
|
||||
#define META_KEY_LONGITUDE "Longitude"
|
||||
#define META_KEY_MODIFICATIONDATE "ModificationDate"
|
||||
#define META_KEY_OWNER "Owner"
|
||||
#define META_KEY_SOFTWARE "Software"
|
||||
#define META_KEY_TITLE "Title"
|
||||
@ -59,4 +61,13 @@ inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &form
|
||||
return imageAlloc(QSize(width, height), format);
|
||||
}
|
||||
|
||||
inline double qRoundOrZero(double d)
|
||||
{
|
||||
// If the value d is outside the range of int, the behavior is undefined.
|
||||
if (d > std::numeric_limits<int>::max()) {
|
||||
return 0;
|
||||
}
|
||||
return qRound(d);
|
||||
}
|
||||
|
||||
#endif // UTIL_P_H
|
||||
|
Reference in New Issue
Block a user