mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
888bca7387 | ||
|
e3aefd2aa1 | ||
|
aa8134ee0d | ||
|
9f09473aa0 | ||
|
62eb1d28cb | ||
|
15bece40ec | ||
|
1b3d2afbe3 | ||
|
850068c1dc | ||
|
6bf38ea638 | ||
|
2adca7c0ca | ||
|
dd69fdaea9 | ||
|
ae62ea3dfc | ||
|
9c47845f15 | ||
|
7c7fa73020 | ||
|
92e4271e84 | ||
|
6f588c6fd3 | ||
|
a182478e2c | ||
|
4026f41890 | ||
|
bef2b9168f | ||
|
473f5d9847 | ||
|
9bee29cc01 | ||
|
cdf3be3af1 | ||
|
752b18a42c | ||
|
97a1ea181c | ||
|
64a43fb04f | ||
|
6821c29819 | ||
|
e4d95c03fa | ||
|
afa8ed1a5d | ||
|
245c835d92 | ||
|
b2663d2651 |
@ -7,5 +7,5 @@ Dependencies:
|
|||||||
Options:
|
Options:
|
||||||
test-before-installing: True
|
test-before-installing: True
|
||||||
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
|
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
|
per-test-timeout: 90
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
set(KF_VERSION "6.12.0") # handled by release scripts
|
set(KF_VERSION "6.15.0") # handled by release scripts
|
||||||
set(KF_DEP_VERSION "6.12.0") # handled by release scripts
|
set(KF_DEP_VERSION "6.14.0") # handled by release scripts
|
||||||
project(KImageFormats VERSION ${KF_VERSION})
|
project(KImageFormats VERSION ${KF_VERSION})
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
find_package(ECM 6.12.0 NO_MODULE)
|
find_package(ECM 6.14.0 NO_MODULE)
|
||||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ include(ECMDeprecationSettings)
|
|||||||
include(CheckIncludeFiles)
|
include(CheckIncludeFiles)
|
||||||
include(FindPkgConfig)
|
include(FindPkgConfig)
|
||||||
|
|
||||||
set(REQUIRED_QT_VERSION 6.6.0)
|
set(REQUIRED_QT_VERSION 6.7.0)
|
||||||
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
||||||
|
|
||||||
find_package(KF6Archive ${KF_DEP_VERSION})
|
find_package(KF6Archive ${KF_DEP_VERSION})
|
||||||
@ -99,8 +99,8 @@ endif()
|
|||||||
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
|
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
|
||||||
|
|
||||||
ecm_set_disabled_deprecation_versions(
|
ecm_set_disabled_deprecation_versions(
|
||||||
QT 6.8.0
|
QT 6.9.0
|
||||||
KF 6.11.0
|
KF 6.13.0
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
@ -109,6 +109,28 @@ if (BUILD_TESTING)
|
|||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6ImageFormats")
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
write_basic_package_version_file(
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/KF6ImageFormatsConfigVersion.cmake"
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
COMPATIBILITY AnyNewerVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_package_config_file(
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/KF6ImageFormatsConfig.cmake.in"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/KF6ImageFormatsConfig.cmake"
|
||||||
|
INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
install(FILES
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/KF6ImageFormatsConfig.cmake"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/KF6ImageFormatsConfigVersion.cmake"
|
||||||
|
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
|
||||||
|
COMPONENT Devel
|
||||||
|
)
|
||||||
|
|
||||||
include(ECMFeatureSummary)
|
include(ECMFeatureSummary)
|
||||||
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
|
5
KF6ImageFormatsConfig.cmake.in
Normal file
5
KF6ImageFormatsConfig.cmake.in
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2025 Xaver Hugl <xaver.hugl@gmail.com>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
@PACKAGE_INIT@
|
||||||
|
# empty, because this is plugins for Qt instead of a library to link against
|
@ -53,8 +53,10 @@ willing to sign the Qt Project contributor agreement, it may be better to
|
|||||||
submit the plugin directly to the Qt Project.
|
submit the plugin directly to the Qt Project.
|
||||||
|
|
||||||
To be accepted, contributions must:
|
To be accepted, contributions must:
|
||||||
- Contain the test images needed to verify that the changes work correctly
|
- Contain the test images needed to verify that the changes work correctly.
|
||||||
- Pass the tests successfully
|
- Pass the tests successfully.
|
||||||
|
|
||||||
|
For more info about tests, see also [Autotests README](autotests/README.md).
|
||||||
|
|
||||||
## Duplicated Plugins
|
## Duplicated Plugins
|
||||||
|
|
||||||
|
@ -122,9 +122,12 @@ if (LibHeif_FOUND)
|
|||||||
kimageformats_read_tests(FUZZ 1
|
kimageformats_read_tests(FUZZ 1
|
||||||
hej2
|
hej2
|
||||||
)
|
)
|
||||||
|
kimageformats_write_tests(FUZZ 1
|
||||||
|
hej2-nodatacheck-lossless
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.19.0")
|
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.19.6")
|
||||||
kimageformats_read_tests(FUZZ 4
|
kimageformats_read_tests(FUZZ 4
|
||||||
avci
|
avci
|
||||||
)
|
)
|
||||||
|
253
autotests/README.md
Normal file
253
autotests/README.md
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
# Autotests
|
||||||
|
|
||||||
|
Automated testing for plugins to allow
|
||||||
|
[`QImage`](https://doc.qt.io/qt-6/qimage.html) to support
|
||||||
|
extra file formats.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The testing part is essential for the correct functioning of the plugins.
|
||||||
|
There are generic read/write tests and specific tests for the plugins that
|
||||||
|
require them.
|
||||||
|
|
||||||
|
## Read tests
|
||||||
|
|
||||||
|
The generic reading tests are contained in the `read` folder. Inside the
|
||||||
|
`read` folder, there are one or more folders for each plugin to be tested.
|
||||||
|
A plugin can support multiple image file types, and if you need different
|
||||||
|
parameters for each type, you need to create a folder for each type supported
|
||||||
|
by the plugin (see e.g. HEIF plugin). If all formats supported by the plugin
|
||||||
|
do not require different parameters, a single folder approach is simpler (see
|
||||||
|
e.g. PSD plugin).
|
||||||
|
|
||||||
|
The reading tests are mainly based on comparing the image read by the plugin
|
||||||
|
with a template in a known and working format. For this reason, the template
|
||||||
|
formats chosen are those distributed by the Qt project: PNG in the first
|
||||||
|
instance and TIFF for image formats not supported by PNG (e.g. CMYK images).
|
||||||
|
|
||||||
|
Some image options such as `QImageIOHandler::Size`,
|
||||||
|
`QImageIOHandler::ImageFormat` and `QImageIOHandler::ImageTransformation` are
|
||||||
|
also checked. If supported by the plugin, the resulting image is checked to
|
||||||
|
see if it is compatible with the option's specification.
|
||||||
|
|
||||||
|
Optionally, for each image, you can also create a JSON file to modify the test
|
||||||
|
behavior and/or verify data other than image pixels, such as metadata.
|
||||||
|
|
||||||
|
Finally, two tests are run for each test case:
|
||||||
|
- On a random access device: this test must not fail.
|
||||||
|
- On a sequential access device: a plugin may not support sequential operation.
|
||||||
|
In this case, the test is skipped. However, if an image is returned, the test
|
||||||
|
must succeed.
|
||||||
|
|
||||||
|
### The readtest command
|
||||||
|
|
||||||
|
To start a test, run the `readtest` command with the format to test as an
|
||||||
|
argument. The format is one of those supported by plugins and a folder with
|
||||||
|
the name of the format must be present inside the `read` folder.
|
||||||
|
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).
|
||||||
|
- `--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
|
||||||
|
in very different RGB values. Since the alpha is small visually there is no
|
||||||
|
difference (so it is not considered an error).
|
||||||
|
- `--skip-optional-tests`: Used to skip the optional test such as metadata
|
||||||
|
and resolution tests.
|
||||||
|
|
||||||
|
Note that some tests may fail if the correct options are not used. The correct
|
||||||
|
options for each test are defined in [CMakeLists.txt](CMakeLists.txt).
|
||||||
|
See also [Add a test to CMakeLists.txt](#add-a-test-to-cmakeliststxt).
|
||||||
|
|
||||||
|
### Test image nomenclature
|
||||||
|
|
||||||
|
Each test consists of the image to test, the verification image(s) (template)
|
||||||
|
and, optionally, the additional JSON file.
|
||||||
|
To be a test, the names of these files must be the same (except for
|
||||||
|
the extension). A test for a JXL image would be, for example, composed like
|
||||||
|
this:
|
||||||
|
- `testRGB.jxl`: The image to test.
|
||||||
|
- `testRGB.jxl.json`: The test behavior modifier (note that it must contain
|
||||||
|
the double extension).
|
||||||
|
- `testRGB.png`: How the image should look (template). The template name
|
||||||
|
can be different if specified in the JSON file.
|
||||||
|
|
||||||
|
Although there is no precise rule for the name of a test, it is good to have
|
||||||
|
a name that is explanatory.
|
||||||
|
|
||||||
|
### JSON behavior file
|
||||||
|
|
||||||
|
The behavior file was initially introduced to solve compatibility issues
|
||||||
|
between different versions of Qt supported by the framework. It was later
|
||||||
|
extended to also check image metadata.
|
||||||
|
|
||||||
|
The JSON file consists of an array of JSON objects. The objects in the array
|
||||||
|
are iterated sequentially and the first object that matches the requirements
|
||||||
|
is used for testing (successes are ignored).
|
||||||
|
|
||||||
|
Supported values for JSON objects:
|
||||||
|
- `comment`: Type string. A string shown by the test when a condition occurs.
|
||||||
|
- `description`: Type string. A description of the object. Not used by the
|
||||||
|
test.
|
||||||
|
- `disableAutoTransform`: Type boolean. By default, tests are run with
|
||||||
|
autotransform enabled (i.e. rotation is applied if the plugin supports it).
|
||||||
|
Set to `true` to disable autotransform.
|
||||||
|
- `filename`: Type string. Name of the template file to use. E.g.
|
||||||
|
"testRGB_Qt_6_2.png".
|
||||||
|
- `fuzziness`: Type integer. Set the fuzziness only if not already set on the
|
||||||
|
command line. The value set on the command line wins over the one in the JSON
|
||||||
|
file.
|
||||||
|
- `maxQtVersion`: Type string. Maximum Qt version this object is compatible
|
||||||
|
with (if not set means all). E.g. "6.2.99".
|
||||||
|
- `metadata`: Type Array. An array of key/value objects (string type)
|
||||||
|
containing the image metadata as returned by `QImage::text`.
|
||||||
|
- `minQtVersion`: Type string. Minimum Qt version this object is compatible
|
||||||
|
with (if not set means all). E.g. "6.2.0".
|
||||||
|
- `perceptiveFuzziness` Type boolean. Set the perceptive fuzziness only if not
|
||||||
|
already set on the command line. The value set on the command line wins over
|
||||||
|
the one in the JSON file.
|
||||||
|
- `resolution`: Type object. An object with the `dotsPerMeterX` and
|
||||||
|
`dotsPerMeterY` (integer) values of the image.
|
||||||
|
- `seeAlso`: Type string. More info about the object. Normally used to point
|
||||||
|
to bug reports. Not used by the test.
|
||||||
|
- `unsupportedFormat`: Type `boolean`. When true, the test is skipped.
|
||||||
|
|
||||||
|
Some examples:
|
||||||
|
- Example 1: [Runs only on Qt without alpha bug on float formats](read/jxl/testcard_rgba_fp16.jxl.json)
|
||||||
|
- Example 2: [Rotation disabled](read/jxl/orientation6_notranfs.jxl.json)
|
||||||
|
- Example 3: [Metadata](read/psd/metadata.psd.json)
|
||||||
|
- Example 4: [Check Qt version, resolution and metadata](read/psd/mch-16bits.psd.json)
|
||||||
|
- Example 5: [Fuzziness setting](read/xcf/birthday16.xcf.json)
|
||||||
|
|
||||||
|
These are just a few examples. More examples can be found in the test folders.
|
||||||
|
|
||||||
|
## Write tests
|
||||||
|
|
||||||
|
The generic writing tests are contained in the `write/basic` and
|
||||||
|
`write/format` folders. Similar to the read tests, they verify the written
|
||||||
|
(and then reread) image with a template.
|
||||||
|
|
||||||
|
The write test is composed of several phases:
|
||||||
|
- Basic test: Uses the `write/basic` folder and checks the most common images
|
||||||
|
and, optionally, metadata and resolution via a JSON properties file.
|
||||||
|
- Format test: Uses the `write/format` folder and checks that all QImage image
|
||||||
|
formats are written correctly.
|
||||||
|
- Dimensional test: Uses the `write/format` folder and check images of
|
||||||
|
different sizes (odd numbers, prime numbers, etc.) to verify internal
|
||||||
|
alignments.
|
||||||
|
- Null device test: Verify that there are no crashes if the device is null.
|
||||||
|
|
||||||
|
### The writetest command
|
||||||
|
|
||||||
|
To start a test, run the `writetest` command with the format to test as an
|
||||||
|
argument. The format is one of those supported by plugins, a folder with
|
||||||
|
the name of the format must be present inside the `write/format` folder and
|
||||||
|
may need a template image in `write/basic`.
|
||||||
|
Depending on the format, you can specify the following additional options.
|
||||||
|
|
||||||
|
- `--help`: Displays help on commandline options.
|
||||||
|
- `--create-format-templates`: Create template images for all formats
|
||||||
|
supported by the QImage in `write/format`. Command to simplify the creation of
|
||||||
|
format test images when adding a new plugin or modifying an old one. This
|
||||||
|
command is not intended to be used from the CMakeLists file as it must be used
|
||||||
|
manually and the generated images must be verified one by one.
|
||||||
|
- `--fuzz <max>`: The fuzziness. Used to add some deviation in ARGB data
|
||||||
|
(nornally used on lossy codec).
|
||||||
|
- `--lossless`: Check that reading back the data gives the same image.
|
||||||
|
- `--no-data-check`: Don't check that write data is exactly the same.
|
||||||
|
- `--skip-optional-tests`: Skip optional data tests (metadata, resolution,
|
||||||
|
etc...).
|
||||||
|
|
||||||
|
Note that some tests may fail if the correct options are not used. The correct
|
||||||
|
options for each test are defined in [CMakeLists.txt](CMakeLists.txt).
|
||||||
|
See also [Add a test to CMakeLists.txt](#add-a-test-to-cmakeliststxt).
|
||||||
|
|
||||||
|
### JSON properties file
|
||||||
|
|
||||||
|
The properties file must be located in `write/basic` and must have the name
|
||||||
|
of the file format (e.g. jxl.json). It is a JSON object composed of the
|
||||||
|
following values:
|
||||||
|
- `format`: Type string. The format tested.
|
||||||
|
- `metadata`: Type Array. An array of key/value objects (string type)
|
||||||
|
containing the image metadata as returned by `QImage::text`.
|
||||||
|
- `resolution`: Type object. An object with the `dotsPerMeterX` and `
|
||||||
|
dotsPerMeterY` (integer) values of the image.
|
||||||
|
|
||||||
|
[This is an example](write/basic/jxl.json) of property file.
|
||||||
|
|
||||||
|
|
||||||
|
## Custom tests
|
||||||
|
|
||||||
|
If the generic read/write tests do not meet the requirements of a plugin,
|
||||||
|
it is possible to write a custom test.
|
||||||
|
In general it makes sense to write a dedicated test for a format if and
|
||||||
|
only if you are testing unique features not present in other plugins.
|
||||||
|
|
||||||
|
### The PIC test
|
||||||
|
|
||||||
|
The PIC test is generated using Qt Test class. For more information
|
||||||
|
see [Qt Test](https://doc.qt.io/qt-6/qttest-index.html).
|
||||||
|
|
||||||
|
### The ANI test
|
||||||
|
|
||||||
|
The ANI test is generated using Qt Test class. For more information
|
||||||
|
see [Qt Test](https://doc.qt.io/qt-6/qttest-index.html).
|
||||||
|
|
||||||
|
|
||||||
|
## Add a test to CMakeLists.txt
|
||||||
|
|
||||||
|
To add a test to CMake use the `kimageformats_read_tests` and
|
||||||
|
`kimageformats_write_tests` functions. For example, to add the read
|
||||||
|
tests for the PSD you just write `kimageformats_read_tests(psd)`.
|
||||||
|
|
||||||
|
It is also possible to pass command line arguments to the test by
|
||||||
|
appropriately composing the string passed to the test functions.
|
||||||
|
For boolean parameters you need to add a string starting with '-'
|
||||||
|
after the image format. For example, to pass `--skip-optional-tests`
|
||||||
|
to the PSD plugin write `kimageformats_read_tests(psd-skipoptional)`.
|
||||||
|
|
||||||
|
To add a fuzziness of 4, you must first set it as follows:
|
||||||
|
`kimageformats_read_tests(FUZZ 4 psd-skipoptional)`.
|
||||||
|
|
||||||
|
The possible modifiers for `kimageformats_read_tests` are as follows:
|
||||||
|
- `-skipoptional`: Add the `--skip-optional-tests` command line parameter.
|
||||||
|
|
||||||
|
The possible modifiers for `kimageformats_write_tests` are as follows:
|
||||||
|
- `-lossless`: Add the `--lossless` command line parameter.
|
||||||
|
- `-nodatacheck`: Add the `--no-data-check` command line parameter.
|
||||||
|
- `-skipoptional`: Add the `--skip-optional-tests` command line parameter.
|
||||||
|
|
||||||
|
To set multiple parameters, you can enter multiple modifiers. For example:
|
||||||
|
```
|
||||||
|
kimageformats_write_tests(FUZZ 1
|
||||||
|
hej2-nodatacheck-lossless
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## OSS-Fuzz
|
||||||
|
|
||||||
|
Plugins are also tested with [OSS-Fuzz](https://google.github.io/oss-fuzz/)
|
||||||
|
project to identify possible security issues. When adding a new plugin it is
|
||||||
|
also necessary to add it to the test in the [kimageformats
|
||||||
|
project](https://github.com/google/oss-fuzz/tree/master/projects/kimageformats)
|
||||||
|
on OSS-Fuzz.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
List of tests not implemented or only partially implemented.
|
||||||
|
|
||||||
|
### Color Profiles Test
|
||||||
|
|
||||||
|
Many plugins support color profiles via [`QColorSpace`](https://doc.qt.io/qt-6/qcolorspace.html).
|
||||||
|
Checking for correct color management is increasingly necessary especially now
|
||||||
|
that monitors are HDR and other than RGB color spaces have been added to Qt.
|
||||||
|
|
||||||
|
Furthermore, lossy plugins often have different color management behaviors
|
||||||
|
depending on how the image is saved.
|
||||||
|
|
||||||
|
### Animations Test
|
||||||
|
|
||||||
|
Few plugins support animations. There are currently no plans for
|
||||||
|
implementation.
|
@ -258,6 +258,15 @@ int main(int argc, char **argv)
|
|||||||
});
|
});
|
||||||
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
|
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();
|
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
|
// 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) {
|
for (int seq = 0; seq < 2; ++seq) {
|
||||||
@ -323,7 +332,12 @@ int main(int argc, char **argv)
|
|||||||
OptionTest optionTest;
|
OptionTest optionTest;
|
||||||
if (!optionTest.store(&inputReader)) {
|
if (!optionTest.store(&inputReader)) {
|
||||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key" : "Description",
|
"key" : "Description",
|
||||||
"value" : "TV broadcast test image."
|
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key" : "Latitude",
|
"key" : "Latitude",
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key" : "Description",
|
"key" : "Description",
|
||||||
"value" : "TV broadcast test image."
|
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key" : "Latitude",
|
"key" : "Latitude",
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key" : "Description",
|
"key" : "Description",
|
||||||
"value" : "TV broadcast test image."
|
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key" : "Latitude",
|
"key" : "Latitude",
|
||||||
|
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.
@ -370,7 +370,12 @@ int formatTest(const QString &suffix, bool createTemplates)
|
|||||||
QBuffer buffer(&ba);
|
QBuffer buffer(&ba);
|
||||||
auto writtenImage = QImageReader(&buffer, suffix.toLatin1()).read();
|
auto writtenImage = QImageReader(&buffer, suffix.toLatin1()).read();
|
||||||
if (writtenImage.isNull()) {
|
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";
|
QTextStream(stdout) << "FAIL : error while reading the image " << formatName << "\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -622,8 +627,28 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run test
|
|
||||||
auto suffix = args.at(0);
|
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);
|
auto ret = basicTest(suffix, parser.isSet(lossless), parser.isSet(ignoreDataCheck), parser.isSet(skipOptTest), fuzzarg);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
ret = formatTest(suffix, parser.isSet(createFormatTempates));
|
ret = formatTest(suffix, parser.isSet(createFormatTempates));
|
||||||
|
@ -15,6 +15,16 @@ function(kimageformats_add_plugin plugin)
|
|||||||
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
|
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
|
||||||
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
|
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
|
||||||
target_link_libraries(${plugin} PRIVATE Qt6::Gui)
|
target_link_libraries(${plugin} PRIVATE Qt6::Gui)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
# Plugins should be named with lib prefix on Android
|
||||||
|
# Working name: libplugins_imageformats_kimg_avif_armeabi-v7a.so
|
||||||
|
# Doesn't work: plugins_imageformats_kimg_avif_armeabi-v7a.so
|
||||||
|
if(NOT ${CMAKE_SHARED_LIBRARY_PREFIX} STREQUAL "")
|
||||||
|
set_target_properties(${plugin} PROPERTIES PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
|
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
@ -2557,3 +2557,5 @@ QImageIOHandler *QDDSPlugin::create(QIODevice *device, const QByteArray &format)
|
|||||||
handler->setFormat(format);
|
handler->setFormat(format);
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "moc_dds_p.cpp"
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QPointF>
|
#include <QPointF>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
|
#include <cstring>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifndef HEIF_MAX_METADATA_SIZE
|
#ifndef HEIF_MAX_METADATA_SIZE
|
||||||
/*!
|
/*!
|
||||||
@ -26,12 +26,12 @@
|
|||||||
#define HEIF_MAX_METADATA_SIZE (4 * 1024 * 1024)
|
#define HEIF_MAX_METADATA_SIZE (4 * 1024 * 1024)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
size_t HEIFHandler::m_initialized_count = 0;
|
size_t HEIFHandler::m_initialized_count = 0;
|
||||||
bool HEIFHandler::m_plugins_queried = false;
|
bool HEIFHandler::m_plugins_queried = false;
|
||||||
bool HEIFHandler::m_heif_decoder_available = false;
|
bool HEIFHandler::m_heif_decoder_available = false;
|
||||||
bool HEIFHandler::m_heif_encoder_available = false;
|
bool HEIFHandler::m_heif_encoder_available = false;
|
||||||
bool HEIFHandler::m_hej2_decoder_available = false;
|
bool HEIFHandler::m_hej2_decoder_available = false;
|
||||||
|
bool HEIFHandler::m_hej2_encoder_available = false;
|
||||||
bool HEIFHandler::m_avci_decoder_available = false;
|
bool HEIFHandler::m_avci_decoder_available = false;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -155,6 +155,14 @@ bool HEIFHandler::write_helper(const QImage &image)
|
|||||||
break;
|
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;
|
heif_chroma chroma;
|
||||||
if (save_depth > 8) {
|
if (save_depth > 8) {
|
||||||
if (save_alpha) {
|
if (save_alpha) {
|
||||||
@ -287,7 +295,7 @@ bool HEIFHandler::write_helper(const QImage &image)
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct heif_encoder *encoder = nullptr;
|
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) {
|
if (err.code) {
|
||||||
qWarning() << "Unable to get an encoder instance:" << err.message;
|
qWarning() << "Unable to get an encoder instance:" << err.message;
|
||||||
heif_image_release(h_image);
|
heif_image_release(h_image);
|
||||||
@ -314,7 +322,7 @@ bool HEIFHandler::write_helper(const QImage &image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct heif_image_handle* handle;
|
struct heif_image_handle *handle;
|
||||||
err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle);
|
err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle);
|
||||||
|
|
||||||
// exif metadata
|
// exif metadata
|
||||||
@ -330,7 +338,7 @@ bool HEIFHandler::write_helper(const QImage &image)
|
|||||||
auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE));
|
auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE));
|
||||||
if (!xmp.isEmpty()) {
|
if (!xmp.isEmpty()) {
|
||||||
auto ba = xmp.toUtf8();
|
auto ba = xmp.toUtf8();
|
||||||
err = heif_context_add_XMP_metadata2(context, handle, ba.constData(), ba.size(), heif_metadata_compression_off);
|
err = heif_context_add_XMP_metadata(context, handle, ba.constData(), ba.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,7 +562,7 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
|
|
||||||
QImage::Format target_image_format;
|
QImage::Format target_image_format;
|
||||||
|
|
||||||
if (bit_depth == 10 || bit_depth == 12) {
|
if (bit_depth == 10 || bit_depth == 12 || bit_depth == 16) {
|
||||||
if (hasAlphaChannel) {
|
if (hasAlphaChannel) {
|
||||||
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
|
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
|
||||||
target_image_format = QImage::Format_RGBA64;
|
target_image_format = QImage::Format_RGBA64;
|
||||||
@ -646,6 +654,35 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (bit_depth) {
|
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:
|
case 12:
|
||||||
if (hasAlphaChannel) {
|
if (hasAlphaChannel) {
|
||||||
for (int y = 0; y < imageHeight; y++) {
|
for (int y = 0; y < imageHeight; y++) {
|
||||||
@ -991,6 +1028,13 @@ bool HEIFHandler::isHej2DecoderAvailable()
|
|||||||
return m_hej2_decoder_available;
|
return m_hej2_decoder_available;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HEIFHandler::isHej2EncoderAvailable()
|
||||||
|
{
|
||||||
|
HEIFHandler::queryHeifLib();
|
||||||
|
|
||||||
|
return m_hej2_encoder_available;
|
||||||
|
}
|
||||||
|
|
||||||
bool HEIFHandler::isAVCIDecoderAvailable()
|
bool HEIFHandler::isAVCIDecoderAvailable()
|
||||||
{
|
{
|
||||||
HEIFHandler::queryHeifLib();
|
HEIFHandler::queryHeifLib();
|
||||||
@ -1013,8 +1057,9 @@ void HEIFHandler::queryHeifLib()
|
|||||||
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
|
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
|
||||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||||
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
|
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
|
#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);
|
m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC);
|
||||||
#endif
|
#endif
|
||||||
m_plugins_queried = true;
|
m_plugins_queried = true;
|
||||||
@ -1081,6 +1126,9 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
|
|||||||
if (HEIFHandler::isHej2DecoderAvailable()) {
|
if (HEIFHandler::isHej2DecoderAvailable()) {
|
||||||
format_cap |= CanRead;
|
format_cap |= CanRead;
|
||||||
}
|
}
|
||||||
|
if (HEIFHandler::isHej2EncoderAvailable()) {
|
||||||
|
format_cap |= CanWrite;
|
||||||
|
}
|
||||||
return format_cap;
|
return format_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1110,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;
|
cap |= CanWrite;
|
||||||
}
|
}
|
||||||
return cap;
|
return cap;
|
||||||
|
@ -31,6 +31,7 @@ public:
|
|||||||
static bool isHeifDecoderAvailable();
|
static bool isHeifDecoderAvailable();
|
||||||
static bool isHeifEncoderAvailable();
|
static bool isHeifEncoderAvailable();
|
||||||
static bool isHej2DecoderAvailable();
|
static bool isHej2DecoderAvailable();
|
||||||
|
static bool isHej2EncoderAvailable();
|
||||||
static bool isAVCIDecoderAvailable();
|
static bool isAVCIDecoderAvailable();
|
||||||
|
|
||||||
static bool isSupportedBMFFType(const QByteArray &header);
|
static bool isSupportedBMFFType(const QByteArray &header);
|
||||||
@ -62,6 +63,7 @@ private:
|
|||||||
static bool m_heif_decoder_available;
|
static bool m_heif_decoder_available;
|
||||||
static bool m_heif_encoder_available;
|
static bool m_heif_encoder_available;
|
||||||
static bool m_hej2_decoder_available;
|
static bool m_hej2_decoder_available;
|
||||||
|
static bool m_hej2_encoder_available;
|
||||||
static bool m_avci_decoder_available;
|
static bool m_avci_decoder_available;
|
||||||
|
|
||||||
static QMutex &getHEIFHandlerMutex();
|
static QMutex &getHEIFHandlerMutex();
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
// Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575)
|
// Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575)
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 7) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) || (QT_VERSION >= QT_VERSION_CHECK(6, 7, 3))
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 3)
|
||||||
#ifndef JXL_QT_AUTOTRANSFORM
|
#ifndef JXL_QT_AUTOTRANSFORM
|
||||||
#define JXL_QT_AUTOTRANSFORM
|
#define JXL_QT_AUTOTRANSFORM
|
||||||
#endif
|
#endif
|
||||||
|
@ -243,6 +243,24 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief initForReadingAndRollBack
|
||||||
|
* Initialize the device for reading and rollback the device to start position.
|
||||||
|
* \param device The source device.
|
||||||
|
* \return True on success, otherwise false.
|
||||||
|
*/
|
||||||
|
bool initForReadingAndRollBack(QIODevice *device)
|
||||||
|
{
|
||||||
|
if (device) {
|
||||||
|
device->startTransaction();
|
||||||
|
}
|
||||||
|
auto ok = initForReading(device);
|
||||||
|
if (device) {
|
||||||
|
device->rollbackTransaction();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief jxrFormat
|
* \brief jxrFormat
|
||||||
* \return The JXR format.
|
* \return The JXR format.
|
||||||
@ -720,11 +738,11 @@ public:
|
|||||||
|
|
||||||
auto exif = MicroExif::fromImage(image);
|
auto exif = MicroExif::fromImage(image);
|
||||||
if (!exif.isEmpty()) {
|
if (!exif.isEmpty()) {
|
||||||
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian);
|
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian, MicroExif::V2);
|
||||||
if (auto err = PKImageEncode_SetEXIFMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(exifIfd.constData()), exifIfd.size())) {
|
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;
|
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting EXIF data:" << err;
|
||||||
}
|
}
|
||||||
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian);
|
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian, MicroExif::V2);
|
||||||
if (auto err = PKImageEncode_SetGPSInfoMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(gpsIfd.constData()), gpsIfd.size())) {
|
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;
|
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting GPS data:" << err;
|
||||||
}
|
}
|
||||||
@ -991,7 +1009,7 @@ bool JXRHandler::read(QImage *outImage)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (qint32 y = 0, h = img.height(); y < h; ++y) {
|
for (qint32 y = 0, h = img.height(); y < h; ++y) {
|
||||||
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, img.bytesPerLine()));
|
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, qint64(img.bytesPerLine())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PKFormatConverter_Release(&pConverter);
|
PKFormatConverter_Release(&pConverter);
|
||||||
@ -1153,7 +1171,7 @@ QVariant JXRHandler::option(ImageOption option) const
|
|||||||
QVariant v;
|
QVariant v;
|
||||||
|
|
||||||
if (option == QImageIOHandler::Size) {
|
if (option == QImageIOHandler::Size) {
|
||||||
if (d->initForReading(device())) {
|
if (d->initForReadingAndRollBack(device())) {
|
||||||
auto size = d->imageSize();
|
auto size = d->imageSize();
|
||||||
if (size.isValid()) {
|
if (size.isValid()) {
|
||||||
v = QVariant::fromValue(size);
|
v = QVariant::fromValue(size);
|
||||||
@ -1162,7 +1180,7 @@ QVariant JXRHandler::option(ImageOption option) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (option == QImageIOHandler::ImageFormat) {
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
if (d->initForReading(device())) {
|
if (d->initForReadingAndRollBack(device())) {
|
||||||
v = QVariant::fromValue(d->imageFormat());
|
v = QVariant::fromValue(d->imageFormat());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1173,7 +1191,7 @@ QVariant JXRHandler::option(ImageOption option) const
|
|||||||
|
|
||||||
if (option == QImageIOHandler::ImageTransformation) {
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
// ignore result: I might want to read the value set in writing
|
// ignore result: I might want to read the value set in writing
|
||||||
d->initForReading(device());
|
d->initForReadingAndRollBack(device());
|
||||||
v = int(d->transformation());
|
v = int(d->transformation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QStringDecoder>
|
||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
|
||||||
// TIFF 6 specs
|
// TIFF 6 specs
|
||||||
@ -111,17 +112,17 @@ static const KnownTags staticTagTypes = {
|
|||||||
TagInfo(TIFF_IMAGEWIDTH, ExifTagType::Long),
|
TagInfo(TIFF_IMAGEWIDTH, ExifTagType::Long),
|
||||||
TagInfo(TIFF_IMAGEHEIGHT, ExifTagType::Long),
|
TagInfo(TIFF_IMAGEHEIGHT, ExifTagType::Long),
|
||||||
TagInfo(TIFF_BITSPERSAMPLE, ExifTagType::Short),
|
TagInfo(TIFF_BITSPERSAMPLE, ExifTagType::Short),
|
||||||
TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Ascii),
|
TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Utf8),
|
||||||
TagInfo(TIFF_MAKE, ExifTagType::Ascii),
|
TagInfo(TIFF_MAKE, ExifTagType::Utf8),
|
||||||
TagInfo(TIFF_MODEL, ExifTagType::Ascii),
|
TagInfo(TIFF_MODEL, ExifTagType::Utf8),
|
||||||
TagInfo(TIFF_ORIENT, ExifTagType::Short),
|
TagInfo(TIFF_ORIENT, ExifTagType::Short),
|
||||||
TagInfo(TIFF_XRES, ExifTagType::Rational),
|
TagInfo(TIFF_XRES, ExifTagType::Rational),
|
||||||
TagInfo(TIFF_YRES, ExifTagType::Rational),
|
TagInfo(TIFF_YRES, ExifTagType::Rational),
|
||||||
TagInfo(TIFF_URES, ExifTagType::Short),
|
TagInfo(TIFF_URES, ExifTagType::Short),
|
||||||
TagInfo(TIFF_SOFTWARE, ExifTagType::Ascii),
|
TagInfo(TIFF_SOFTWARE, ExifTagType::Utf8),
|
||||||
TagInfo(TIFF_ARTIST, ExifTagType::Ascii),
|
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
|
||||||
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
||||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Ascii),
|
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
|
||||||
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
||||||
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
|
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
|
||||||
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
|
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
|
||||||
@ -134,10 +135,10 @@ static const KnownTags staticTagTypes = {
|
|||||||
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
|
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
|
||||||
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
|
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
|
||||||
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
|
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
|
||||||
TagInfo(EXIF_LENSMAKE, ExifTagType::Ascii),
|
TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8),
|
||||||
TagInfo(EXIF_LENSMODEL, ExifTagType::Ascii),
|
TagInfo(EXIF_LENSMODEL, ExifTagType::Utf8),
|
||||||
TagInfo(EXIF_LENSSERIALNUMBER, ExifTagType::Ascii),
|
TagInfo(EXIF_LENSSERIALNUMBER, ExifTagType::Ascii),
|
||||||
TagInfo(EXIF_IMAGETITLE, ExifTagType::Ascii),
|
TagInfo(EXIF_IMAGETITLE, ExifTagType::Utf8),
|
||||||
TagInfo(EXIF_EXIFVERSION, ExifTagType::Undefined)
|
TagInfo(EXIF_EXIFVERSION, ExifTagType::Undefined)
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
@ -367,6 +368,28 @@ static void writeData(QDataStream &ds, const QVariant &value, const ExifTagType&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ExifTagType updateDataType(const ExifTagType &dataType, const QVariant &value, const MicroExif::Version &ver)
|
||||||
|
{
|
||||||
|
if (dataType != ExifTagType::Utf8)
|
||||||
|
return dataType;
|
||||||
|
|
||||||
|
if (ver == MicroExif::V2)
|
||||||
|
return ExifTagType::Ascii;
|
||||||
|
|
||||||
|
// Note that in EXIF specs, UTF-8 is backward compatible with ASCII: all UTF-8 tags can also be ASCII.
|
||||||
|
// To maximize compatibility, I check if the string can be encoded in ASCII.
|
||||||
|
auto txt = value.toString();
|
||||||
|
|
||||||
|
// Exif ASCII data type allow only values up to 127 (7-bit ASCII).
|
||||||
|
auto u8 = txt.toUtf8();
|
||||||
|
for (auto &&c : u8) {
|
||||||
|
if (uchar(c) > 127)
|
||||||
|
return dataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExifTagType::Ascii;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief writeIfd
|
* \brief writeIfd
|
||||||
* \param ds The stream.
|
* \param ds The stream.
|
||||||
@ -375,7 +398,12 @@ static void writeData(QDataStream &ds, const QVariant &value, const ExifTagType&
|
|||||||
* \param knownTags List of known and supported tags.
|
* \param knownTags List of known and supported tags.
|
||||||
* \return True on success, otherwise false.
|
* \return True on success, otherwise false.
|
||||||
*/
|
*/
|
||||||
static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &positions, quint32 pos = 0, const KnownTags &knownTags = staticTagTypes)
|
static bool writeIfd(QDataStream &ds,
|
||||||
|
const MicroExif::Version &ver,
|
||||||
|
const MicroExif::Tags &tags,
|
||||||
|
TagPos &positions,
|
||||||
|
quint32 pos = 0,
|
||||||
|
const KnownTags &knownTags = staticTagTypes)
|
||||||
{
|
{
|
||||||
if (tags.isEmpty())
|
if (tags.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
@ -390,7 +418,7 @@ static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &posit
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto value = tags.value(key);
|
auto value = tags.value(key);
|
||||||
auto dataType = knownTags.value(key);
|
auto dataType = updateDataType(knownTags.value(key), value, ver);
|
||||||
auto count = countBytes(dataType, value);
|
auto count = countBytes(dataType, value);
|
||||||
|
|
||||||
ds << quint16(key);
|
ds << quint16(key);
|
||||||
@ -412,9 +440,8 @@ static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &posit
|
|||||||
if (!knownTags.contains(key)) {
|
if (!knownTags.contains(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto value = tags.value(key);
|
auto value = tags.value(key);
|
||||||
auto dataType = knownTags.value(key);
|
auto dataType = updateDataType(knownTags.value(key), value, ver);
|
||||||
auto count = countBytes(dataType, value);
|
auto count = countBytes(dataType, value);
|
||||||
auto valueSize = count * EXIF_TAG_SIZEOF(dataType);
|
auto valueSize = count * EXIF_TAG_SIZEOF(dataType);
|
||||||
if (valueSize <= 4)
|
if (valueSize <= 4)
|
||||||
@ -534,8 +561,16 @@ static bool readIfd(QDataStream &ds, MicroExif::Tags &tags, quint32 pos = 0, con
|
|||||||
|
|
||||||
if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Ascii) || dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8)) {
|
if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Ascii) || dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8)) {
|
||||||
auto l = readBytes(ds, count, true);
|
auto l = readBytes(ds, count, true);
|
||||||
if (!l.isEmpty())
|
if (!l.isEmpty()) {
|
||||||
tags.insert(tagId, dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8) ? QString::fromUtf8(l) : QString::fromLatin1(l));
|
// It seems that converting to Latin 1 never detects errors so, using UTF-8.
|
||||||
|
// Note that if the dataType is ASCII, by EXIF specification, it must use only the
|
||||||
|
// first 128 values so the UTF-8 conversion is correct.
|
||||||
|
auto dec = QStringDecoder(QStringDecoder::Utf8);
|
||||||
|
// QStringDecoder raise an error only after converting to QString
|
||||||
|
auto ut8 = QString(dec(l));
|
||||||
|
// If there are errors in the conversion to UTF-8, then I try with latin1 (extended ASCII)
|
||||||
|
tags.insert(tagId, dec.hasError() ? QString::fromLatin1(l) : ut8);
|
||||||
|
}
|
||||||
} else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Undefined)) {
|
} else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Undefined)) {
|
||||||
auto l = readBytes(ds, count, false);
|
auto l = readBytes(ds, count, false);
|
||||||
if (!l.isEmpty())
|
if (!l.isEmpty())
|
||||||
@ -1032,7 +1067,7 @@ void MicroExif::setImageDirection(double degree, bool isMagnetic)
|
|||||||
m_gpsTags.insert(GPS_IMGDIRECTION, degree);
|
m_gpsTags.insert(GPS_IMGDIRECTION, degree);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||||
{
|
{
|
||||||
QByteArray ba;
|
QByteArray ba;
|
||||||
{
|
{
|
||||||
@ -1043,16 +1078,16 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
|||||||
return ba;
|
return ba;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
|
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||||
{
|
{
|
||||||
QByteArray ba;
|
QByteArray ba;
|
||||||
{
|
{
|
||||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||||
ds.setByteOrder(byteOrder);
|
ds.setByteOrder(byteOrder);
|
||||||
auto exifTags = m_exifTags;
|
auto exifTags = m_exifTags;
|
||||||
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
|
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232"));
|
||||||
TagPos positions;
|
TagPos positions;
|
||||||
if (!writeIfd(ds, exifTags, positions))
|
if (!writeIfd(ds, version, exifTags, positions))
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return ba;
|
return ba;
|
||||||
@ -1065,7 +1100,7 @@ bool MicroExif::setExifIfdByteArray(const QByteArray &ba, const QDataStream::Byt
|
|||||||
return readIfd(ds, m_exifTags, 0, staticTagTypes);
|
return readIfd(ds, m_exifTags, 0, staticTagTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
|
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||||
{
|
{
|
||||||
QByteArray ba;
|
QByteArray ba;
|
||||||
{
|
{
|
||||||
@ -1074,7 +1109,7 @@ QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) c
|
|||||||
auto gpsTags = m_gpsTags;
|
auto gpsTags = m_gpsTags;
|
||||||
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
||||||
TagPos positions;
|
TagPos positions;
|
||||||
if (!writeIfd(ds, gpsTags, positions, 0, staticGpsTagTypes))
|
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
|
||||||
return {};
|
return {};
|
||||||
return ba;
|
return ba;
|
||||||
}
|
}
|
||||||
@ -1087,7 +1122,7 @@ bool MicroExif::setGpsIfdByteArray(const QByteArray &ba, const QDataStream::Byte
|
|||||||
return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
|
return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder) const
|
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||||
{
|
{
|
||||||
if (device == nullptr || device->isSequential() || isEmpty())
|
if (device == nullptr || device->isSequential() || isEmpty())
|
||||||
return false;
|
return false;
|
||||||
@ -1096,7 +1131,7 @@ bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder
|
|||||||
ds.setByteOrder(byteOrder);
|
ds.setByteOrder(byteOrder);
|
||||||
if (!writeHeader(ds))
|
if (!writeHeader(ds))
|
||||||
return false;
|
return false;
|
||||||
if (!writeIfds(ds))
|
if (!writeIfds(ds, version))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
device->close();
|
device->close();
|
||||||
@ -1327,30 +1362,30 @@ bool MicroExif::writeHeader(QDataStream &ds) const
|
|||||||
return ds.status() == QDataStream::Ok;
|
return ds.status() == QDataStream::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MicroExif::writeIfds(QDataStream &ds) const
|
bool MicroExif::writeIfds(QDataStream &ds, const Version &version) const
|
||||||
{
|
{
|
||||||
auto tiffTags = m_tiffTags;
|
auto tiffTags = m_tiffTags;
|
||||||
auto exifTags = m_exifTags;
|
auto exifTags = m_exifTags;
|
||||||
auto gpsTags = m_gpsTags;
|
auto gpsTags = m_gpsTags;
|
||||||
updateTags(tiffTags, exifTags, gpsTags);
|
updateTags(tiffTags, exifTags, gpsTags, version);
|
||||||
|
|
||||||
TagPos positions;
|
TagPos positions;
|
||||||
if (!writeIfd(ds, tiffTags, positions))
|
if (!writeIfd(ds, version, tiffTags, positions))
|
||||||
return false;
|
return false;
|
||||||
if (!writeIfd(ds, exifTags, positions, positions.value(EXIF_EXIFIFD)))
|
if (!writeIfd(ds, version, exifTags, positions, positions.value(EXIF_EXIFIFD)))
|
||||||
return false;
|
return false;
|
||||||
if (!writeIfd(ds, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes))
|
if (!writeIfd(ds, version, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags) const
|
void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const
|
||||||
{
|
{
|
||||||
if (exifTags.isEmpty()) {
|
if (exifTags.isEmpty()) {
|
||||||
tiffTags.remove(EXIF_EXIFIFD);
|
tiffTags.remove(EXIF_EXIFIFD);
|
||||||
} else {
|
} else {
|
||||||
tiffTags.insert(EXIF_EXIFIFD, quint32());
|
tiffTags.insert(EXIF_EXIFIFD, quint32());
|
||||||
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
|
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232"));
|
||||||
}
|
}
|
||||||
if (gpsTags.isEmpty()) {
|
if (gpsTags.isEmpty()) {
|
||||||
tiffTags.remove(EXIF_GPSIFD);
|
tiffTags.remove(EXIF_GPSIFD);
|
||||||
|
@ -37,6 +37,15 @@ class MicroExif
|
|||||||
public:
|
public:
|
||||||
using Tags = QMap<quint16, QVariant>;
|
using Tags = QMap<quint16, QVariant>;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The Version enum
|
||||||
|
* Exif specs version used when writing.
|
||||||
|
*/
|
||||||
|
enum Version {
|
||||||
|
V2, // V2.xx
|
||||||
|
V3 // V3.xx, use of UTF-8 data type (default)
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief MicroExif
|
* \brief MicroExif
|
||||||
* Constructs an empty class.
|
* Constructs an empty class.
|
||||||
@ -265,18 +274,20 @@ public:
|
|||||||
* - EXIF IFD
|
* - EXIF IFD
|
||||||
* - GPS IFD
|
* - GPS IFD
|
||||||
* \param byteOrder Sets the serialization byte order for EXIF data.
|
* \param byteOrder Sets the serialization byte order for EXIF data.
|
||||||
|
* \param version The EXIF specs version to use.
|
||||||
* \return A byte array containing the serialized data.
|
* \return A byte array containing the serialized data.
|
||||||
* \sa write
|
* \sa write
|
||||||
*/
|
*/
|
||||||
QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief exifIfdByteArray
|
* \brief exifIfdByteArray
|
||||||
* Convert the EXIF IFD only to RAW data. Useful when you want to add EXIF data to an existing TIFF container.
|
* 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.
|
* \param byteOrder Sets the serialization byte order for the data.
|
||||||
|
* \param version The EXIF specs version to use.
|
||||||
* \return A byte array containing the serialized data.
|
* \return A byte array containing the serialized data.
|
||||||
*/
|
*/
|
||||||
QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||||
/*!
|
/*!
|
||||||
* \brief setExifIfdByteArray
|
* \brief setExifIfdByteArray
|
||||||
* \param ba The RAW data of EXIF IFD.
|
* \param ba The RAW data of EXIF IFD.
|
||||||
@ -289,9 +300,10 @@ public:
|
|||||||
* \brief gpsIfdByteArray
|
* \brief gpsIfdByteArray
|
||||||
* Convert the GPS IFD only to RAW data. Useful when you want to add GPS data to an existing TIFF container.
|
* 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.
|
* \param byteOrder Sets the serialization byte order for the data.
|
||||||
|
* \param version The EXIF specs version to use.
|
||||||
* \return A byte array containing the serialized data.
|
* \return A byte array containing the serialized data.
|
||||||
*/
|
*/
|
||||||
QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||||
/*!
|
/*!
|
||||||
* \brief setGpsIfdByteArray
|
* \brief setGpsIfdByteArray
|
||||||
* \param ba The RAW data of GPS IFD.
|
* \param ba The RAW data of GPS IFD.
|
||||||
@ -309,10 +321,11 @@ public:
|
|||||||
* - GPS IFD
|
* - GPS IFD
|
||||||
* \param device A random access device.
|
* \param device A random access device.
|
||||||
* \param byteOrder Sets the serialization byte order for EXIF data.
|
* \param byteOrder Sets the serialization byte order for EXIF data.
|
||||||
|
* \param version The EXIF specs version to use.
|
||||||
* \return True on success, otherwise false.
|
* \return True on success, otherwise false.
|
||||||
* \sa toByteArray
|
* \sa toByteArray
|
||||||
*/
|
*/
|
||||||
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief updateImageMetadata
|
* \brief updateImageMetadata
|
||||||
@ -373,8 +386,8 @@ private:
|
|||||||
void setGpsString(quint16 tagId, const QString& s);
|
void setGpsString(quint16 tagId, const QString& s);
|
||||||
QString gpsString(quint16 tagId) const;
|
QString gpsString(quint16 tagId) const;
|
||||||
bool writeHeader(QDataStream &ds) const;
|
bool writeHeader(QDataStream &ds) const;
|
||||||
bool writeIfds(QDataStream &ds) const;
|
bool writeIfds(QDataStream &ds, const Version &version) const;
|
||||||
void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags) const;
|
void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const;
|
||||||
|
|
||||||
static void setString(Tags &tags, quint16 tagId, const QString &s);
|
static void setString(Tags &tags, quint16 tagId, const QString &s);
|
||||||
static QString string(const Tags &tags, quint16 tagId);
|
static QString string(const Tags &tags, quint16 tagId);
|
||||||
|
@ -1504,7 +1504,18 @@ bool PSDHandler::read(QImage *image)
|
|||||||
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||||
#endif
|
#endif
|
||||||
} else if (!setColorSpace(img, irs)) {
|
} 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
|
// XMP data
|
||||||
|
@ -457,3 +457,4 @@ QImageIOHandler *ScitexPlugin::create(QIODevice *device, const QByteArray &forma
|
|||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "moc_sct_p.cpp"
|
||||||
|
@ -962,11 +962,7 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
|
|||||||
case PROP_PARASITES:
|
case PROP_PARASITES:
|
||||||
while (!property.atEnd()) {
|
while (!property.atEnd()) {
|
||||||
char *tag;
|
char *tag;
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
|
||||||
quint32 size;
|
|
||||||
#else
|
|
||||||
qint64 size;
|
qint64 size;
|
||||||
#endif
|
|
||||||
|
|
||||||
property.readBytes(tag, size);
|
property.readBytes(tag, size);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user