Compare commits
7 Commits
v6.28.0-rc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c21b50d699 | ||
|
|
a0825fe5cb | ||
|
|
ad0fb9dde4 | ||
|
|
2faf524b85 | ||
|
|
c748c6c2c6 | ||
|
|
7e14d5616f | ||
|
|
215305fa4b |
@@ -7,5 +7,5 @@ Dependencies:
|
||||
Options:
|
||||
test-before-installing: True
|
||||
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
|
||||
cmake-options: "-DKIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
|
||||
cmake-options: "-DKIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIC_TEST:STRING=OFF -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
|
||||
per-test-timeout: 90
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
|
||||
set(KF_VERSION "6.28.0") # handled by release scripts
|
||||
set(KF_VERSION "6.29.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.28.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
@@ -65,8 +65,10 @@ set_package_properties(libavif PROPERTIES
|
||||
option(KIMAGEFORMATS_DDS "Enable plugin for DDS format" ON)
|
||||
|
||||
option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF)
|
||||
set(KIMAGEFORMATS_HEIF_TEST "ALL" CACHE STRING "Enable HEIF tests: OFF, READ_ONLY, ALL")
|
||||
set_property(CACHE KIMAGEFORMATS_HEIF_TEST PROPERTY STRINGS "OFF" "READ_ONLY" "ALL")
|
||||
set(KIMAGEFORMATS_HEIC_TEST "ALL" CACHE STRING "Enable HEIC tests: OFF, READ_ONLY, ALL")
|
||||
set_property(CACHE KIMAGEFORMATS_HEIC_TEST PROPERTY STRINGS "OFF" "READ_ONLY" "ALL")
|
||||
set(KIMAGEFORMATS_HEIF_TEST "ALL" CACHE STRING "Enable HEIF tests: OFF, ALL")
|
||||
set_property(CACHE KIMAGEFORMATS_HEIF_TEST PROPERTY STRINGS "OFF" "ALL")
|
||||
set(KIMAGEFORMATS_HEJ2_TEST "ALL" CACHE STRING "Enable HEJ2 tests: OFF, READ_ONLY, ALL")
|
||||
set_property(CACHE KIMAGEFORMATS_HEJ2_TEST PROPERTY STRINGS "OFF" "READ_ONLY" "ALL")
|
||||
set(KIMAGEFORMATS_AVCI_TEST "ALL" CACHE STRING "Enable AVCI tests: OFF, ALL")
|
||||
|
||||
13
README.md
@@ -146,6 +146,7 @@ About the image:
|
||||
scanned.
|
||||
- `HostComputer`: The computer and/or operating system in use at the time
|
||||
of image creation.
|
||||
- `Keywords`: Keywords, separated by semicolons, that represent the image.
|
||||
- `Latitude`: Floating-point number indicating the latitude in degrees
|
||||
north of the equator (e.g. 27.717).
|
||||
- `Longitude`: Floating-point number indicating the longitude in degrees
|
||||
@@ -154,6 +155,8 @@ About the image:
|
||||
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.
|
||||
- `Rating`: Integer number indicating the image rating (usually between 1
|
||||
and 5).
|
||||
- `Software`: Name and version number of the software package(s) used to
|
||||
create the image.
|
||||
- `Speed`: Floating-point number indicating the speed of GPS receiver
|
||||
@@ -357,12 +360,18 @@ plugin:
|
||||
|
||||
**If you are interested in compiling the plugin without running the tests,
|
||||
also use the following string options:**
|
||||
- `KIMAGEFORMATS_HEIF_TEST` to change the behaviour of HEIF tests. Set to
|
||||
- `KIMAGEFORMATS_HEIC_TEST` to change the behaviour of HEIC tests. Set to
|
||||
`"OFF"` (no test at all) or `"READ_ONLY"` (run read tests only).
|
||||
Required codecs: HEVC / H.265.
|
||||
- `KIMAGEFORMATS_HEIF_TEST` to change the behaviour of HEIF tests. Set to
|
||||
`"OFF"` (no test at all).
|
||||
Required codecs: uncompressed, deflate, zlib, brotli, jpeg.
|
||||
- `KIMAGEFORMATS_HEJ2_TEST` to change the behaviour of HEJ2 tests. Set to
|
||||
`"OFF"` (no test at all) or `"READ_ONLY"` (run read tests only).
|
||||
Required codecs: JPEG 2000.
|
||||
- `KIMAGEFORMATS_AVCI_TEST` to change the behaviour of AVCI tests. Set to
|
||||
`"OFF"` (no test at all).
|
||||
Required codecs: Advanced Video Coding / H.264.
|
||||
|
||||
### The EXR plugin
|
||||
|
||||
@@ -415,7 +424,7 @@ The plugin supports the following image data:
|
||||
with color map only.
|
||||
- FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7
|
||||
and DYuv formats.
|
||||
- FORM RGFX: It supports uncompressed images only.
|
||||
- FORM RGFX: It supports uncompressed and ZIP compressed images.
|
||||
- FORM DEEP: It supports uncompressed, RLE and TVDC images.
|
||||
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
|
||||
RGBA images.
|
||||
|
||||
@@ -119,15 +119,21 @@ if (TARGET avif)
|
||||
endif()
|
||||
|
||||
if (LibHeif_FOUND)
|
||||
if (KIMAGEFORMATS_HEIF_TEST STREQUAL "ALL" OR KIMAGEFORMATS_HEIF_TEST STREQUAL "READ_ONLY")
|
||||
if (KIMAGEFORMATS_HEIC_TEST STREQUAL "ALL" OR KIMAGEFORMATS_HEIC_TEST STREQUAL "READ_ONLY")
|
||||
kimageformats_read_tests(
|
||||
heif
|
||||
heic
|
||||
)
|
||||
endif()
|
||||
if (KIMAGEFORMATS_HEIF_TEST STREQUAL "ALL")
|
||||
if (KIMAGEFORMATS_HEIC_TEST STREQUAL "ALL")
|
||||
# because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference.
|
||||
kimageformats_write_tests(FUZZ 1
|
||||
heif-nodatacheck-lossless
|
||||
heic-nodatacheck-lossless
|
||||
)
|
||||
endif()
|
||||
|
||||
if (KIMAGEFORMATS_HEIF_TEST STREQUAL "ALL")
|
||||
kimageformats_read_tests(
|
||||
heif
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
BIN
autotests/read/heif/brotli.heif
Normal file
BIN
autotests/read/heif/brotli.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
autotests/read/heif/deflate.heif
Normal file
BIN
autotests/read/heif/deflate.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
autotests/read/heif/jpeg.heif
Normal file
7
autotests/read/heif/jpeg.heif.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "jpeg.png",
|
||||
"fuzziness" : 1,
|
||||
"description" : "Minimum fuzziness value to pass the test on all architectures."
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/heif/jpeg.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
autotests/read/heif/uncompressed.heif
Normal file
BIN
autotests/read/heif/uncompressed.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
autotests/read/heif/zlib.heif
Normal file
BIN
autotests/read/heif/zlib.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
autotests/read/iff/sv5_idx8z_rgx.iff
Normal file
5
autotests/read/iff/sv5_idx8z_rgx.iff.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "sv5_idx8_rgx.png"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/iff/sv5_rgb16z_rgx.iff
Normal file
5
autotests/read/iff/sv5_rgb16z_rgx.iff.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "sv5_rgb16_rgx.png"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/iff/sv5_rgb32z_rgx.iff
Normal file
5
autotests/read/iff/sv5_rgb32z_rgx.iff.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "sv5_rgb32_rgx.png"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/iff/sv5_rgb8z_rgx.iff
Normal file
5
autotests/read/iff/sv5_rgb8z_rgx.iff.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "sv5_rgb8_rgx.png"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/iff/sv5_rgba16z_rgx.iff
Normal file
5
autotests/read/iff/sv5_rgba16z_rgx.iff.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "sv5_rgba16_rgx.png"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/iff/sv5_rgba32z_rgx.iff
Normal file
5
autotests/read/iff/sv5_rgba32z_rgx.iff.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "sv5_rgba32_rgx.png"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/iff/sv5_rgba8z_rgx.iff
Normal file
5
autotests/read/iff/sv5_rgba8z_rgx.iff.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "sv5_rgba8_rgx.png"
|
||||
}
|
||||
]
|
||||
@@ -12,6 +12,14 @@
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-01-05T10:18:16"
|
||||
},
|
||||
{
|
||||
"key" : "Keywords",
|
||||
"value" : "kde;w11"
|
||||
},
|
||||
{
|
||||
"key" : "Rating",
|
||||
"value" : "4"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "GIMP 3.0.0-RC2"
|
||||
|
||||
@@ -59,6 +59,14 @@
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
},
|
||||
{
|
||||
"key" : "Keywords",
|
||||
"value" : "broacast;test;kde"
|
||||
},
|
||||
{
|
||||
"key" : "Rating",
|
||||
"value" : "5"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
|
||||
@@ -37,6 +37,14 @@
|
||||
"key" : "Description",
|
||||
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||
},
|
||||
{
|
||||
"key" : "Keywords",
|
||||
"value" : "kde;test"
|
||||
},
|
||||
{
|
||||
"key" : "Rating",
|
||||
"value" : "5"
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "Rating",
|
||||
"value" : "4"
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
|
||||
@@ -3501,6 +3501,17 @@ QByteArray RBODChunk::strideRead(QIODevice *d, qint32 y, const RGHDChunk *header
|
||||
for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && planes.size() < readSize;) {
|
||||
if (header->compression() == RGHDChunk::Compression::Uncompressed) {
|
||||
planes = d->read(readSize);
|
||||
} else if (header->compression() == RGHDChunk::Compression::Zip) {
|
||||
if (_readBuffer.isEmpty()) {
|
||||
auto size = nextPos - d->pos();
|
||||
if (size < kMaxQVectorSize) {
|
||||
// I have no control over qUncompress() so, I have to decompress the whole chunk
|
||||
_readBuffer = qUncompress(deviceRead(d, size));
|
||||
// Reset the position to avoid falling into the `d->pos() < nextPos` check
|
||||
seek(d, 0);
|
||||
}
|
||||
}
|
||||
planes = _readBuffer.mid(y * readSize, readSize);
|
||||
} else {
|
||||
qCDebug(LOG_IFFPLUGIN) << "RBODChunk::strideRead(): unknown compression" << header->compression();
|
||||
}
|
||||
@@ -3514,6 +3525,7 @@ QByteArray RBODChunk::strideRead(QIODevice *d, qint32 y, const RGHDChunk *header
|
||||
|
||||
bool RBODChunk::resetStrideRead(QIODevice *d) const
|
||||
{
|
||||
_readBuffer.clear();
|
||||
return seek(d);
|
||||
}
|
||||
|
||||
|
||||
@@ -1964,6 +1964,9 @@ private:
|
||||
QByteArray deinterleave(const QByteArray &planes, qint32 y, const RGHDChunk *header, const RSCMChunk *rcsm = nullptr, const RCOLChunk *rcol = nullptr) const;
|
||||
|
||||
quint32 strideSize(const RGHDChunk *header) const;
|
||||
|
||||
private:
|
||||
mutable QByteArray _readBuffer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ 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;
|
||||
bool HEIFHandler::m_avci_encoder_available = false;
|
||||
bool HEIFHandler::m_jpeg_decoder_available = false;
|
||||
|
||||
/*!
|
||||
* \brief create_heif_reader_for_qiodevice
|
||||
@@ -76,14 +78,14 @@ static heif_reader create_heif_reader_for_qiodevice()
|
||||
|
||||
reader.reader_api_version = 1;
|
||||
|
||||
reader.get_position = [](void* userdata) -> int64_t {
|
||||
QIODevice* device = static_cast<QIODevice*>(userdata);
|
||||
reader.get_position = [](void *userdata) -> int64_t {
|
||||
QIODevice *device = static_cast<QIODevice *>(userdata);
|
||||
return device->pos();
|
||||
};
|
||||
|
||||
reader.read = [](void* data, size_t size, void* userdata) -> int {
|
||||
QIODevice* device = static_cast<QIODevice*>(userdata);
|
||||
qint64 bytesRead = device->read(static_cast<char*>(data), size);
|
||||
reader.read = [](void *data, size_t size, void *userdata) -> int {
|
||||
QIODevice *device = static_cast<QIODevice *>(userdata);
|
||||
qint64 bytesRead = device->read(static_cast<char *>(data), size);
|
||||
if (bytesRead == -1) {
|
||||
return -1; // Error
|
||||
}
|
||||
@@ -95,13 +97,13 @@ static heif_reader create_heif_reader_for_qiodevice()
|
||||
return 0; // Success
|
||||
};
|
||||
|
||||
reader.seek = [](int64_t position, void* userdata) -> int {
|
||||
QIODevice* device = static_cast<QIODevice*>(userdata);
|
||||
reader.seek = [](int64_t position, void *userdata) -> int {
|
||||
QIODevice *device = static_cast<QIODevice *>(userdata);
|
||||
return device->seek(position) ? 0 : -1;
|
||||
};
|
||||
|
||||
reader.wait_for_file_size = [](int64_t target_size, void* userdata) -> heif_reader_grow_status {
|
||||
QIODevice* device = static_cast<QIODevice*>(userdata);
|
||||
reader.wait_for_file_size = [](int64_t target_size, void *userdata) -> heif_reader_grow_status {
|
||||
QIODevice *device = static_cast<QIODevice *>(userdata);
|
||||
if (target_size <= device->size()) {
|
||||
return heif_reader_grow_status_size_reached;
|
||||
}
|
||||
@@ -159,7 +161,7 @@ bool HEIFHandler::canRead() const
|
||||
if (dev) {
|
||||
const QByteArray header = dev->peek(28);
|
||||
|
||||
if (HEIFHandler::isSupportedBMFFType(header)) {
|
||||
if (HEIFHandler::isSupportedBMFFType(header) || HEIFHandler::isSupportedJPEG(header)) {
|
||||
setFormat("heif");
|
||||
return true;
|
||||
}
|
||||
@@ -245,6 +247,12 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
encoder_codec = heif_compression_JPEG2000;
|
||||
save_depth = 8; // for compatibility reasons
|
||||
}
|
||||
#if LIBHEIF_HAVE_VERSION(1, 21, 0)
|
||||
if (format() == "avci") {
|
||||
encoder_codec = heif_compression_AVC;
|
||||
save_depth = 8; // for compatibility reasons
|
||||
}
|
||||
#endif
|
||||
|
||||
heif_chroma chroma;
|
||||
if (save_depth > 8) {
|
||||
@@ -382,13 +390,22 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (format() == "avci") { // workaround for limited h264 decoders
|
||||
if (m_quality >= 98) { // OpenH264 will fail to decode quality 99 and 100
|
||||
heif_encoder_set_lossy_quality(encoder, 98);
|
||||
} else {
|
||||
heif_encoder_set_lossy_quality(encoder, m_quality);
|
||||
}
|
||||
} else {
|
||||
heif_encoder_set_lossy_quality(encoder, m_quality);
|
||||
if (m_quality > 90) {
|
||||
if (m_quality == 100) {
|
||||
heif_encoder_set_lossless(encoder, true);
|
||||
}
|
||||
|
||||
heif_encoder_set_parameter_string(encoder, "chroma", "444");
|
||||
}
|
||||
}
|
||||
|
||||
struct heif_encoding_options *encoder_options = heif_encoding_options_alloc();
|
||||
encoder_options->save_alpha_channel = save_alpha;
|
||||
@@ -406,29 +423,96 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
encoder_options->image_orientation = heif_orientation(m_orientation);
|
||||
}
|
||||
|
||||
struct heif_image_handle *handle;
|
||||
err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle);
|
||||
struct heif_image_handle *handle = nullptr;
|
||||
|
||||
// exif metadata
|
||||
if (err.code == heif_error_Ok) {
|
||||
auto exif = MicroExif::fromImage(tmpimage);
|
||||
if (m_orientation >= 1 && m_orientation <= 8) {
|
||||
// EXIF orientation must be coherent with HEIF orientation
|
||||
exif.setOrientation(m_orientation);
|
||||
if (format() == "avci" && (tmpimage.width() > 3840 || tmpimage.height() > 2160)) {
|
||||
#if LIBHEIF_HAVE_VERSION(1, 21, 0)
|
||||
/* encode as grid so that OpenH264 can decode */
|
||||
const uint32_t nColumns = (tmpimage.width() + 2047) / 2048;
|
||||
const uint32_t nRows = (tmpimage.height() + 2047) / 2048;
|
||||
|
||||
err = heif_context_add_grid_image(context, tmpimage.width(), tmpimage.height(), nColumns, nRows, encoder_options, &handle);
|
||||
if (err.code) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "heif_context_add_grid_image failed:" << err.message;
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
}
|
||||
if (!exif.isEmpty()) {
|
||||
auto ba = exif.toByteArray();
|
||||
err = heif_context_add_exif_metadata(context, handle, ba.constData(), ba.size());
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
heif_security_limits *limits = heif_context_get_security_limits(context);
|
||||
|
||||
for (uint32_t rY = 0; rY < nRows; rY++) {
|
||||
for (uint32_t rX = 0; rX < nColumns; rX++) {
|
||||
heif_image *current_tile = nullptr;
|
||||
|
||||
err = heif_image_extract_area(h_image, rX * 2048, rY * 2048, 2048, 2048, limits, ¤t_tile);
|
||||
if (err.code) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "heif_image_extract_area failed:" << err.message;
|
||||
heif_image_handle_release(handle);
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
}
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
err = heif_image_extend_to_size_fill_with_zero(current_tile, 2048, 2048);
|
||||
if (err.code) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "heif_image_extend_to_size_fill_with_zero failed:" << err.message;
|
||||
heif_image_release(current_tile);
|
||||
heif_image_handle_release(handle);
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
}
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (iccprofile.size() > 0) {
|
||||
heif_image_set_raw_color_profile(current_tile, "prof", iccprofile.constData(), iccprofile.size());
|
||||
}
|
||||
|
||||
err = heif_context_add_image_tile(context, handle, rX, rY, current_tile, encoder);
|
||||
heif_image_release(current_tile);
|
||||
if (err.code) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "heif_context_add_image_tile failed:" << err.message;
|
||||
heif_image_handle_release(handle);
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
}
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 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());
|
||||
}
|
||||
|
||||
heif_context_set_primary_image(context, handle);
|
||||
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
}
|
||||
#else
|
||||
qCWarning(LOG_HEIFPLUGIN) << "Cannot encode AVCI image, libheif is too old!";
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
}
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle);
|
||||
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
@@ -441,6 +525,40 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// exif metadata
|
||||
auto exif = MicroExif::fromImage(tmpimage);
|
||||
if (m_orientation >= 1 && m_orientation <= 8) {
|
||||
// EXIF orientation must be coherent with HEIF orientation
|
||||
exif.setOrientation(m_orientation);
|
||||
}
|
||||
if (!exif.isEmpty()) {
|
||||
auto ba = exif.toByteArray();
|
||||
err = heif_context_add_exif_metadata(context, handle, ba.constData(), ba.size());
|
||||
if (err.code) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "heif_context_add_exif_metadata failed:" << err.message;
|
||||
heif_image_handle_release(handle);
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// xmp metadata
|
||||
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 (err.code) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "heif_context_add_XMP_metadata failed:" << err.message;
|
||||
heif_image_handle_release(handle);
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct heif_writer writer;
|
||||
writer.writer_api_version = 1;
|
||||
@@ -448,6 +566,7 @@ bool HEIFHandler::write_helper(const QImage &image)
|
||||
|
||||
err = heif_context_write(context, &writer, device());
|
||||
|
||||
heif_image_handle_release(handle);
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
|
||||
@@ -596,6 +715,20 @@ bool HEIFHandler::isSupportedAVCI(const QByteArray &header)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isSupportedJPEG(const QByteArray &header)
|
||||
{
|
||||
if (header.size() < 28) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *buffer = header.constData();
|
||||
if (memcmp(buffer + 4, "ftypjpeg", 8) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant HEIFHandler::option(ImageOption option) const
|
||||
{
|
||||
if (option == Quality) {
|
||||
@@ -682,7 +815,8 @@ bool HEIFHandler::ensureDecoder()
|
||||
}
|
||||
|
||||
QByteArray buffer = dev->peek(28);
|
||||
if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer) && !HEIFHandler::isSupportedAVCI(buffer)) {
|
||||
if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer) && !HEIFHandler::isSupportedAVCI(buffer)
|
||||
&& !HEIFHandler::isSupportedJPEG(buffer)) {
|
||||
m_parseState = ParseHeicError;
|
||||
return false;
|
||||
}
|
||||
@@ -694,6 +828,11 @@ bool HEIFHandler::ensureDecoder()
|
||||
if (auto heif_limits = heif_context_get_security_limits(ctx)) {
|
||||
heif_limits->max_image_size_pixels = quint64(HEIF_MAX_IMAGE_WIDTH) * HEIF_MAX_IMAGE_HEIGHT;
|
||||
heif_limits->max_memory_block_size = quint64(QImageReader::allocationLimit()) * 1024 * 1024;
|
||||
|
||||
static constexpr uint32_t relaxed_childboxes_limit = 360;
|
||||
if (heif_limits->max_children_per_box > 0 && heif_limits->max_children_per_box < relaxed_childboxes_limit) {
|
||||
heif_limits->max_children_per_box = relaxed_childboxes_limit;
|
||||
}
|
||||
#if LIBHEIF_HAVE_VERSION(1, 20, 0)
|
||||
heif_limits->max_total_memory = heif_limits->max_memory_block_size;
|
||||
#endif
|
||||
@@ -711,7 +850,8 @@ bool HEIFHandler::ensureDecoder()
|
||||
buffer = deviceRead(dev, kMaxQVectorSize);
|
||||
err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
|
||||
} else {
|
||||
heif_reader reader = create_heif_reader_for_qiodevice();
|
||||
// Must not be destroyed until decoding ends
|
||||
static const heif_reader reader = create_heif_reader_for_qiodevice();
|
||||
err = heif_context_read_from_reader(ctx, &reader, dev, nullptr);
|
||||
}
|
||||
if (err.code) {
|
||||
@@ -1134,8 +1274,7 @@ bool HEIFHandler::ensureDecoder()
|
||||
break;
|
||||
default:
|
||||
qCWarning(LOG_HEIFPLUGIN) << "CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet."
|
||||
<< nclx->color_primaries
|
||||
<< nclx->transfer_characteristics;
|
||||
<< nclx->color_primaries << nclx->transfer_characteristics;
|
||||
q_trc = QColorSpace::TransferFunction::SRgb;
|
||||
break;
|
||||
}
|
||||
@@ -1239,6 +1378,20 @@ bool HEIFHandler::isAVCIDecoderAvailable()
|
||||
return m_avci_decoder_available;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isAVCIEncoderAvailable()
|
||||
{
|
||||
HEIFHandler::queryHeifLib();
|
||||
|
||||
return m_avci_encoder_available;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isJPEGDecoderAvailable()
|
||||
{
|
||||
HEIFHandler::queryHeifLib();
|
||||
|
||||
return m_jpeg_decoder_available;
|
||||
}
|
||||
|
||||
void HEIFHandler::queryHeifLib()
|
||||
{
|
||||
QMutexLocker locker(&getHEIFHandlerMutex());
|
||||
@@ -1255,6 +1408,11 @@ void HEIFHandler::queryHeifLib()
|
||||
#if LIBHEIF_HAVE_VERSION(1, 19, 6)
|
||||
m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC);
|
||||
#endif
|
||||
#if LIBHEIF_HAVE_VERSION(1, 21, 0)
|
||||
m_avci_encoder_available = heif_have_encoder_for_format(heif_compression_AVC);
|
||||
#endif
|
||||
m_jpeg_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG);
|
||||
|
||||
m_plugins_queried = true;
|
||||
|
||||
if (m_initialized_count == 0) {
|
||||
@@ -1323,6 +1481,9 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
|
||||
if (HEIFHandler::isAVCIDecoderAvailable()) {
|
||||
format_cap |= CanRead;
|
||||
}
|
||||
if (HEIFHandler::isAVCIEncoderAvailable()) {
|
||||
format_cap |= CanWrite;
|
||||
}
|
||||
return format_cap;
|
||||
}
|
||||
|
||||
@@ -1339,12 +1500,13 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
|
||||
|
||||
if ((HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable())
|
||||
|| (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable())
|
||||
|| (HEIFHandler::isSupportedAVCI(header) && HEIFHandler::isAVCIDecoderAvailable())) {
|
||||
|| (HEIFHandler::isSupportedAVCI(header) && HEIFHandler::isAVCIDecoderAvailable())
|
||||
|| (HEIFHandler::isSupportedJPEG(header) && HEIFHandler::isJPEGDecoderAvailable())) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
if (device->isWritable() && (HEIFHandler::isHeifEncoderAvailable() || HEIFHandler::isHej2EncoderAvailable())) {
|
||||
if (device->isWritable() && (HEIFHandler::isHeifEncoderAvailable() || HEIFHandler::isHej2EncoderAvailable() || HEIFHandler::isAVCIEncoderAvailable())) {
|
||||
cap |= CanWrite;
|
||||
}
|
||||
return cap;
|
||||
|
||||
@@ -33,10 +33,13 @@ public:
|
||||
static bool isHej2DecoderAvailable();
|
||||
static bool isHej2EncoderAvailable();
|
||||
static bool isAVCIDecoderAvailable();
|
||||
static bool isAVCIEncoderAvailable();
|
||||
static bool isJPEGDecoderAvailable();
|
||||
|
||||
static bool isSupportedBMFFType(const QByteArray &header);
|
||||
static bool isSupportedHEJ2(const QByteArray &header);
|
||||
static bool isSupportedAVCI(const QByteArray &header);
|
||||
static bool isSupportedJPEG(const QByteArray &header);
|
||||
|
||||
private:
|
||||
bool ensureParsed() const;
|
||||
@@ -67,7 +70,7 @@ private:
|
||||
* Read the crop information.
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
bool read_crop(void *heif_handle, const void *heif_ctx, const QSize& size, QRect &crop);
|
||||
bool read_crop(void *heif_handle, const void *heif_ctx, const QSize &size, QRect &crop);
|
||||
|
||||
static void startHeifLib();
|
||||
static void finishHeifLib();
|
||||
@@ -80,6 +83,8 @@ private:
|
||||
static bool m_hej2_decoder_available;
|
||||
static bool m_hej2_encoder_available;
|
||||
static bool m_avci_decoder_available;
|
||||
static bool m_avci_encoder_available;
|
||||
static bool m_jpeg_decoder_available;
|
||||
|
||||
static QMutex &getHEIFHandlerMutex();
|
||||
};
|
||||
|
||||
@@ -739,6 +739,13 @@ public:
|
||||
DESCRIPTIVEMETADATA meta;
|
||||
memset(&meta, 0, sizeof(meta));
|
||||
|
||||
auto ok = false;
|
||||
auto rating = image.text(QStringLiteral(META_KEY_RATING)).toUInt(&ok);
|
||||
if (ok) {
|
||||
meta.pvarRatingStars.vt = DPKVT_UI2;
|
||||
meta.pvarRatingStars.VT.uiVal = quint16(rating);
|
||||
}
|
||||
|
||||
#define META_CTEXT(name, field) \
|
||||
auto field = image.text(QStringLiteral(name)).toUtf8(); \
|
||||
if (!field.isEmpty()) { \
|
||||
@@ -1017,6 +1024,10 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (meta.pvarRatingStars.vt == DPKVT_UI2) {
|
||||
m_txtMeta.insert(QStringLiteral(META_KEY_RATING), QStringLiteral("%1").arg(meta.pvarRatingStars.VT.uiVal));
|
||||
}
|
||||
|
||||
#define META_TEXT(name, field) \
|
||||
if (meta.field.vt == DPKVT_LPSTR) \
|
||||
m_txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
#define TIFF_ARTIST 0x013B
|
||||
#define TIFF_DATETIME 0x0132
|
||||
#define TIFF_COPYRIGHT 0x8298
|
||||
#define TIFF_XPRATING 0x4746 // added by Windows Explorer Image Properties
|
||||
#define TIFF_XPTITLE 0x9C9B // added by Windows Explorer Image Properties
|
||||
#define TIFF_XPKEYWORDS 0x9C9E // added by Windows Explorer Image Properties
|
||||
|
||||
#define TIFF_VAL_URES_NOABSOLUTE 1
|
||||
#define TIFF_VAL_URES_INCH 2
|
||||
@@ -134,6 +137,9 @@ static const KnownTags staticTagTypes = {
|
||||
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_XPRATING, ExifTagType::Short),
|
||||
TagInfo(TIFF_XPTITLE, ExifTagType::Byte),
|
||||
TagInfo(TIFF_XPKEYWORDS, ExifTagType::Byte),
|
||||
TagInfo(EXIF_EXPOSURETIME, ExifTagType::Rational),
|
||||
TagInfo(EXIF_FNUMBER, ExifTagType::Rational),
|
||||
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
||||
@@ -206,8 +212,8 @@ static const QList<std::pair<quint16, QString>> exifStrMap = {
|
||||
std::pair<quint16, QString>(EXIF_BODYSERIALNUMBER, QStringLiteral(META_KEY_SERIALNUMBER)),
|
||||
std::pair<quint16, QString>(EXIF_LENSMAKE, QStringLiteral(META_KEY_LENS_MANUFACTURER)),
|
||||
std::pair<quint16, QString>(EXIF_LENSMODEL, QStringLiteral(META_KEY_LENS_MODEL)),
|
||||
std::pair<quint16, QString>(EXIF_LENSSERIALNUMBER, QStringLiteral(META_KEY_LENS_SERIALNUMBER)),
|
||||
std::pair<quint16, QString>(EXIF_IMAGETITLE, QStringLiteral(META_KEY_TITLE)),
|
||||
std::pair<quint16, QString>(EXIF_LENSSERIALNUMBER, QStringLiteral(META_KEY_LENS_SERIALNUMBER))
|
||||
// std::pair<quint16, QString>(EXIF_IMAGETITLE, QStringLiteral(META_KEY_TITLE)) // using functions due to XP title fallback
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -842,6 +848,32 @@ void MicroExif::setCopyright(const QString &s)
|
||||
setTiffString(TIFF_COPYRIGHT, s);
|
||||
}
|
||||
|
||||
QStringList MicroExif::keywords() const
|
||||
{
|
||||
auto val = utf16String(m_tiffTags, TIFF_XPKEYWORDS);
|
||||
if (val.isEmpty())
|
||||
return {};
|
||||
return val.split(QChar(u';'), Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
void MicroExif::setKeywords(const QStringList &k)
|
||||
{
|
||||
setUtf16String(m_tiffTags, TIFF_XPKEYWORDS, k.join(QChar(u';')));
|
||||
}
|
||||
|
||||
quint16 MicroExif::rating() const
|
||||
{
|
||||
return m_tiffTags.value(TIFF_XPRATING).toUInt();
|
||||
}
|
||||
|
||||
void MicroExif::setRating(quint16 rating)
|
||||
{
|
||||
if (rating == 0)
|
||||
m_tiffTags.remove(TIFF_XPRATING);
|
||||
else
|
||||
m_tiffTags.insert(TIFF_XPRATING, rating);
|
||||
}
|
||||
|
||||
QString MicroExif::make() const
|
||||
{
|
||||
return tiffString(TIFF_MAKE);
|
||||
@@ -964,12 +996,18 @@ void MicroExif::setDateTimeDigitized(const QDateTime &dt)
|
||||
|
||||
QString MicroExif::title() const
|
||||
{
|
||||
return exifString(EXIF_IMAGETITLE);
|
||||
auto s = exifString(EXIF_IMAGETITLE);
|
||||
if (s.isEmpty()) { // fall back to XP title
|
||||
s = utf16String(m_tiffTags, TIFF_XPTITLE);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void MicroExif::setImageTitle(const QString &s)
|
||||
void MicroExif::setTitle(const QString &s)
|
||||
{
|
||||
setExifString(EXIF_IMAGETITLE, s);
|
||||
// Since there is an official title tag, I never write the non-standard Microsoft one.
|
||||
setUtf16String(m_tiffTags, TIFF_XPTITLE, QString());
|
||||
}
|
||||
|
||||
QUuid MicroExif::uniqueId() const
|
||||
@@ -1337,6 +1375,11 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
|
||||
if (!s.isEmpty())
|
||||
targetImage.setText(p.second, s);
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_TITLE)).isEmpty()) {
|
||||
auto s = title();
|
||||
if (!s.isEmpty())
|
||||
targetImage.setText(QStringLiteral(META_KEY_TITLE), s);
|
||||
}
|
||||
|
||||
// set date and time
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_MODIFICATIONDATE)).isEmpty()) {
|
||||
@@ -1423,6 +1466,18 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
|
||||
if (v != WhiteBalance::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_WHITEBALANCE), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
|
||||
// set Microsoft tags
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_KEYWORDS)).isEmpty()) {
|
||||
auto keywords = this->keywords();
|
||||
if (!keywords.isEmpty())
|
||||
targetImage.setText(QStringLiteral(META_KEY_KEYWORDS), keywords.join(QChar(u';')));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_RATING)).isEmpty()) {
|
||||
auto v = rating();
|
||||
if (v != 0)
|
||||
targetImage.setText(QStringLiteral(META_KEY_RATING), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
}
|
||||
|
||||
bool MicroExif::updateImageResolution(QImage &targetImage)
|
||||
@@ -1518,6 +1573,7 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
for (auto &&p : exifStrMap) {
|
||||
exif.setExifString(p.first, image.text(p.second));
|
||||
}
|
||||
exif.setTitle(image.text(QStringLiteral(META_KEY_TITLE)));
|
||||
|
||||
// TIFF Software
|
||||
if (exif.software().isEmpty()) {
|
||||
@@ -1587,6 +1643,13 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
if (ok)
|
||||
exif.setWhiteBalance(WhiteBalance(whtb));
|
||||
|
||||
// Microsoft tags
|
||||
exif.setKeywords(image.text(QStringLiteral(META_KEY_KEYWORDS)).split(QChar(u';'), Qt::SkipEmptyParts));
|
||||
|
||||
auto rating = image.text(QStringLiteral(META_KEY_RATING)).toUInt(&ok);
|
||||
if (ok)
|
||||
exif.setRating(rating);
|
||||
|
||||
return exif;
|
||||
}
|
||||
|
||||
@@ -1676,3 +1739,29 @@ QString MicroExif::string(const Tags &tags, quint16 tagId)
|
||||
{
|
||||
return tags.value(tagId).toString();
|
||||
}
|
||||
|
||||
void MicroExif::setUtf16String(Tags &tags, quint16 tagId, const QString &s)
|
||||
{
|
||||
if (s.isEmpty()) {
|
||||
tags.remove(tagId);
|
||||
return;
|
||||
}
|
||||
auto s16 = s.toStdU16String();
|
||||
QList<quint8> uba;
|
||||
auto ba = QByteArrayView(reinterpret_cast<char*>(s16.data()), s.size() * 2);
|
||||
for(auto&& c : ba)
|
||||
uba.append(quint8(c));
|
||||
uba.append(quint8('\0'));
|
||||
uba.append(quint8('\0'));
|
||||
tags.insert(tagId, QVariant::fromValue(uba));
|
||||
}
|
||||
|
||||
QString MicroExif::utf16String(const Tags &tags, quint16 tagId)
|
||||
{
|
||||
auto ba = tags.value(tagId).value<QList<quint8>>();
|
||||
if (ba.isEmpty())
|
||||
return {};
|
||||
auto p16 = reinterpret_cast<char16_t*>(ba.data());
|
||||
auto sz = std::max(ba.size() / 2 - 1, qsizetype());
|
||||
return QString::fromUtf16(p16, sz);
|
||||
}
|
||||
|
||||
@@ -251,6 +251,24 @@ public:
|
||||
QString copyright() const;
|
||||
void setCopyright(const QString& s);
|
||||
|
||||
/*!
|
||||
* \brief keywords
|
||||
* Keywords tag used by Windows.
|
||||
* \note Non-standard, added by Windows Explorer.
|
||||
* \return The list of tags.
|
||||
*/
|
||||
QStringList keywords() const;
|
||||
void setKeywords(const QStringList& k);
|
||||
|
||||
/*!
|
||||
* \brief rating
|
||||
* Rating tag used by Windows.
|
||||
* \note Non-standard, added by Windows Explorer.
|
||||
* \return The rating. Usually between 0 (not set) and 5.
|
||||
*/
|
||||
quint16 rating() const;
|
||||
void setRating(quint16 rating);
|
||||
|
||||
/*!
|
||||
* \brief make
|
||||
* \return The manufacturer of the recording equipment.
|
||||
@@ -319,7 +337,7 @@ public:
|
||||
* \return The title of the image.
|
||||
*/
|
||||
QString title() const;
|
||||
void setImageTitle(const QString &s);
|
||||
void setTitle(const QString &s);
|
||||
|
||||
/*!
|
||||
* \brief uniqueId
|
||||
@@ -555,6 +573,9 @@ private:
|
||||
static void setString(Tags &tags, quint16 tagId, const QString &s);
|
||||
static QString string(const Tags &tags, quint16 tagId);
|
||||
|
||||
static void setUtf16String(Tags &tags, quint16 tagId, const QString &s);
|
||||
static QString utf16String(const Tags &tags, quint16 tagId);
|
||||
|
||||
private:
|
||||
Tags m_tiffTags;
|
||||
Tags m_exifTags;
|
||||
|
||||
@@ -44,10 +44,12 @@
|
||||
#define META_KEY_DIRECTION "Direction"
|
||||
#define META_KEY_DOCUMENTNAME "DocumentName"
|
||||
#define META_KEY_HOSTCOMPUTER "HostComputer"
|
||||
#define META_KEY_KEYWORDS "Keywords"
|
||||
#define META_KEY_LATITUDE "Latitude"
|
||||
#define META_KEY_LONGITUDE "Longitude"
|
||||
#define META_KEY_MODIFICATIONDATE "ModificationDate"
|
||||
#define META_KEY_OWNER "Owner"
|
||||
#define META_KEY_RATING "Rating"
|
||||
#define META_KEY_SOFTWARE "Software"
|
||||
#define META_KEY_SPEED "Speed"
|
||||
#define META_KEY_TITLE "Title"
|
||||
|
||||