TGA: Support for TGA specification 2.0

Adds TGA 2.0 compliance:
- Support for Extension Area, Developer Area and Footer (metadata support)
- Support for 15-bit and 16-bit per pixel images (both RGB and Indexed)
- Full support for rotation on reading (we cannot use Qt transformations because only a subset is part of the TGA specification)
- When writing you can choose the supported version (subType)
- Improved writing speed (approximately 10 times) and removed whole image conversions (significant memory savings)

It pass the [TrueVision TGA 2.0 conformance suite](https://github.com/zigimg/test-suite/tree/master/fixtures/tga).

Test changes:
- Read test: added ability to skip a specific test on sequential devices (via JSON behavior file)
- Write test: added the ability to set the subType when writing (via JSON properties file)

Closes #37
This commit is contained in:
Mirco Miranda
2025-08-23 11:20:09 +02:00
committed by Albert Astals Cid
parent f933cbe12d
commit 9c6c0c01ae
31 changed files with 1054 additions and 200 deletions

View File

@ -56,11 +56,17 @@ submit the plugin directly to the Qt Project.
To be accepted, contributions must:
- Contain the test images needed to verify that the changes work correctly.
- Pass the tests successfully.
- Use Qt logging categories for Debug messages.
For more info about tests, see also [Autotests README](autotests/README.md).
## Duplicated Plugins
> [!important]
> To ensure you are using the correct plugin, the unwanted one should be
renamed or deleted. If several plugins support the same capability, Qt will
select one arbitrarily.
### The TGA plugin
The TGA plugin supports more formats than Qt's own TGA plugin;
@ -340,10 +346,6 @@ The plugin supports the following image data:
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
RGBA images.
The plugin does not load images with non-standard SHAM/CTBL chunks due to the
lack of clear specifications.
### The JP2 plugin
**This plugin can be disabled by setting `KIMAGEFORMATS_JP2` to `OFF`
@ -426,6 +428,25 @@ selectively change the conversion (see also [raw_p.h](./src/imageformats/raw_p.h
The default setting tries to balance quality and conversion speed.
### The TGA plugin
TGA plugin supports both version 1 and version 2 of TGA files. When writing,
it is possible to force which version to use by setting the following subtypes:
- `TGAv1`: force TGA v1.0. No metadata.
- `TGAv2` (default): force TGA v2.0 (strict). Adds the TGA Extension Area.
- `TGAv2E`: force TGA v2.0 (enhanced). Same as TGA v2.0 (strict) but with the
addition of the TGA v2.0 Developer Area with info like, for e.g., Exif data,
XMP packet and the ICC profile.
They are all TGA specs compliant. While for versions 1 and 2 (strict) it is
possible to decode all the information with the TGA specification alone, for
version 2 (enhanced) it is necessary to know how the additional data is
encoded.
The following defines can be defined in cmake to modify the behavior of the
plugin:
- `TGA_V2E_AS_DEFAULT`: change the default version of the plugin to `TGAv2E`.
### The XCF plugin
XCF support has the following limitations:

View File

@ -189,7 +189,7 @@ kimageformats_write_tests(
pic-lossless
qoi-lossless
rgb-lossless
tga # fixme: the alpha images appear not to be written properly
tga-nodatacheck
)
# EPS read tests depend on the vagaries of GhostScript

View File

@ -88,30 +88,33 @@ 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
- `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
- `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.
- `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
- `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
- `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)
- `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
- `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
- `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
- `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
- `seeAlso`: Type `string`. More info about the object. Normally used to point
to bug reports. Not used by the test.
- `skipSequential`: Type `boolean`. Skip the test on sequential access device.
Some plugins may have limited functionality on sequential devices (e.g.,
not reading metadata).
- `unsupportedFormat`: Type `boolean`. When true, the test is skipped.
Some examples:
@ -169,11 +172,12 @@ See also [Add a test to CMakeLists.txt](#add-a-test-to-cmakeliststxt).
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)
- `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 `
- `resolution`: Type `object`. An object with the `dotsPerMeterX` and `
dotsPerMeterY` (integer) values of the image.
- `subType`: type `string`. The image writer subtype to set when testing.
[This is an example](write/basic/jxl.json) of property file.

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -0,0 +1,56 @@
[
{
"fileName" : "extarea.png",
"skipSequential" : true,
"metadata" : [
{
"key" : "Author",
"value" : "KDE Project"
},
{
"key" : "Comment",
"value" : "TV broadcast test image."
},
{
"key" : "Altitude",
"value" : "34"
},
{
"key" : "Copyright",
"value" : "@2025 KDE Project"
},
{
"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"
},
{
"key" : "Software",
"value" : "LIFE Pro 2.18.30 (Linux)"
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 5906
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

View File

@ -0,0 +1,32 @@
[
{
"fileName" : "extarea.png",
"skipSequential" : true,
"metadata" : [
{
"key" : "Title",
"value" : "Test Card"
},
{
"key" : "Author",
"value" : "KDE Project"
},
{
"key" : "ModificationDate",
"value" : "2025-08-21T07:32:45"
},
{
"key" : "Comment",
"value" : "TV broadcast test image."
},
{
"key" : "Software",
"value" : "LIFE Pro 2.18.31 (Linux)"
}
],
"resolution" : {
"dotsPerMeterX" : 3937,
"dotsPerMeterY" : 3937
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

View File

@ -281,6 +281,12 @@ int main(int argc, char **argv)
continue;
}
if (seq && timg.skipSequentialDeviceTest()) {
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": marked to be skipped on a sequential device (don't worry, it's ok)\n";
++skipped;
continue;
}
TemplateImage::TestFlags flags = TemplateImage::None;
QString comment;
QFileInfo expFileInfo = timg.compareImage(flags, comment);

View File

@ -76,6 +76,15 @@ bool TemplateImage::isLicense() const
return !m_fi.suffix().compare(QStringLiteral("license"), Qt::CaseInsensitive);
}
bool TemplateImage::skipSequentialDeviceTest() const
{
auto obj = searchObject(m_fi);
if (obj.isEmpty()) {
return false;
}
return obj.value("skipSequential").toBool();
}
QFileInfo TemplateImage::compareImage(TestFlags &flags, QString& comment) const
{
auto fi = jsonImage(flags, comment);

View File

@ -54,6 +54,12 @@ public:
*/
bool isLicense() const;
/*!
* \brief skipSequentialDeviceTest
* \return tre it the sequential test should be skipped.
*/
bool skipSequentialDeviceTest() const;
/*!
* \brief compareImage
* \param flags Flags for modifying test behavior (e.g. image format not supported by current Qt version).

View File

@ -0,0 +1,62 @@
{
"format" : "tga",
"subType" : "TGAv2E",
"metadata" : [
{
"key" : "CreationDate",
"value" : "2025-01-14T13:53:32+01:00"
},
{
"key" : "Direction",
"value" : "123.7"
},
{
"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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -70,6 +70,15 @@ void setOptionalInfo(QImage &image, const QString &suffix)
}
}
QByteArray readSubType(const QString &suffix)
{
auto obj = readOptionalInfo(suffix);
if (obj.isEmpty()) {
return {};
}
return obj.value("subType").toString().toLatin1();
}
bool checkOptionalInfo(QImage &image, const QString &suffix)
{
auto obj = readOptionalInfo(suffix);
@ -157,6 +166,9 @@ int basicTest(const QString &suffix, bool lossless, bool ignoreDataCheck, bool s
{
QBuffer buffer(&writtenData);
QImageWriter imgWriter(&buffer, format.constData());
auto subType = readSubType(suffix);
if (!subType.isEmpty())
imgWriter.setSubType(subType);
if (lossless) {
imgWriter.setQuality(100);
}

View File

@ -137,7 +137,7 @@ kimageformats_add_plugin(kimg_sct SOURCES sct.cpp)
##################################
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp microexif.cpp scanlineconverter.cpp)
##################################

File diff suppressed because it is too large Load Diff

View File

@ -22,17 +22,20 @@ public:
bool write(const QImage &image) override;
bool supportsOption(QImageIOHandler::ImageOption option) const override;
void setOption(ImageOption option, const QVariant &value) override;
QVariant option(QImageIOHandler::ImageOption option) const override;
static bool canRead(QIODevice *device);
private:
bool writeIndexed(const QImage &image);
bool writeGrayscale(const QImage &image);
bool writeRGB555(const QImage &image);
bool writeRGBA(const QImage &image);
bool writeMetadata(const QImage &image);
bool readMetadata(QImage &image);
const QScopedPointer<TGAHandlerPrivate> d;
};