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
29
README.md
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
BIN
autotests/read/tga/bottom_left.tga
Normal file
After Width: | Height: | Size: 226 KiB |
5
autotests/read/tga/bottom_left.tga.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/tga/bottom_right.tga
Normal file
After Width: | Height: | Size: 226 KiB |
5
autotests/read/tga/bottom_right.tga.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/tga/devarea.tga
Normal file
After Width: | Height: | Size: 80 KiB |
56
autotests/read/tga/devarea.tga.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
BIN
autotests/read/tga/extarea.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/tga/extarea.tga
Normal file
32
autotests/read/tga/extarea.tga.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
BIN
autotests/read/tga/orientation.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/tga/rgb16.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
autotests/read/tga/rgb16.tga
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/tga/top_left.tga
Normal file
After Width: | Height: | Size: 226 KiB |
5
autotests/read/tga/top_left.tga.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/tga/top_right.tga
Normal file
After Width: | Height: | Size: 226 KiB |
5
autotests/read/tga/top_right.tga.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation.png"
|
||||
}
|
||||
]
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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).
|
||||
|
62
autotests/write/basic/tga.json
Normal 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
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
##################################
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|