mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-06-03 17:08:08 -04:00
ras: code revamped
- Progressive load from file - Added support for 1-bit image - Images with palette are now Index (instead of RGB32) - Added options support (Size, Format) - Added some test cases - Improved performance by directly accessing the scanline - Support for more RAS extension (taken from GIMP) The code should works "as is" also on KF5.
This commit is contained in:
parent
7d63a1d8fa
commit
79e8e183eb
BIN
autotests/read/ras/rgb-gimp-2.10-1bit.png
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-1bit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 192 B |
BIN
autotests/read/ras/rgb-gimp-2.10-1bit.ras
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-1bit.ras
Normal file
Binary file not shown.
BIN
autotests/read/ras/rgb-gimp-2.10-gray.png
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-gray.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 470 B |
BIN
autotests/read/ras/rgb-gimp-2.10-gray.ras
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-gray.ras
Normal file
Binary file not shown.
BIN
autotests/read/ras/rgb-gimp-2.10-pal16.png
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-pal16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 338 B |
BIN
autotests/read/ras/rgb-gimp-2.10-pal16.ras
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-pal16.ras
Normal file
Binary file not shown.
BIN
autotests/read/ras/rgb24.png
Normal file
BIN
autotests/read/ras/rgb24.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
BIN
autotests/read/ras/rgb24.ras
Normal file
BIN
autotests/read/ras/rgb24.ras
Normal file
Binary file not shown.
@ -3,6 +3,7 @@
|
||||
SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
|
||||
SPDX-FileCopyrightText: 2004 Ignacio Castaño <castano@ludicon.com>
|
||||
SPDX-FileCopyrightText: 2010 Troy Unrau <troy@kde.org>
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
@ -80,152 +81,161 @@ static bool IsSupported(const RasHeader &head)
|
||||
return false;
|
||||
}
|
||||
// check for an appropriate depth
|
||||
// we support 8bit+palette, 24bit and 32bit ONLY!
|
||||
// TODO: add support for 1bit
|
||||
if (!((head.Depth == 8 && head.ColorMapType == 1) || head.Depth == 24 || head.Depth == 32)) {
|
||||
if (head.Depth != 1 && head.Depth != 8 && head.Depth != 24 && head.Depth != 32) {
|
||||
return false;
|
||||
}
|
||||
if (head.Width == 0 || head.Height == 0) {
|
||||
return false;
|
||||
}
|
||||
// the Type field adds support for RLE(BGR), RGB and other encodings
|
||||
// we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY!
|
||||
// TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF
|
||||
if (!(head.Type == 1 || head.Type == 3)) {
|
||||
return false;
|
||||
}
|
||||
// Old files didn't have Length set - reject them for now
|
||||
// TODO: add length recalculation to support old files
|
||||
if (!head.Length) {
|
||||
if (!(head.Type == RAS_TYPE_STANDARD || head.Type == RAS_TYPE_RGB_FORMAT)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static QImage::Format imageFormat(const RasHeader &header)
|
||||
{
|
||||
if (header.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
|
||||
return QImage::Format_Indexed8;
|
||||
}
|
||||
if (header.Depth == 8 && header.ColorMapType == RAS_COLOR_MAP_TYPE_NONE) {
|
||||
return QImage::Format_Grayscale8;
|
||||
}
|
||||
if (header.Depth == 1) {
|
||||
return QImage::Format_Mono;
|
||||
}
|
||||
return QImage::Format_RGB32;
|
||||
}
|
||||
|
||||
static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
{
|
||||
s.device()->seek(RasHeader::SIZE);
|
||||
|
||||
if (ras.ColorMapLength > kMaxQVectorSize) {
|
||||
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
|
||||
// The width of a scan line is always a multiple of 16 bits, padded when necessary.
|
||||
auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8;
|
||||
if (rasLineSize & 1)
|
||||
++rasLineSize;
|
||||
if (rasLineSize > kMaxQVectorSize) {
|
||||
qWarning() << "LoadRAS() unsupported line size" << rasLineSize;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read palette if needed.
|
||||
QVector<quint8> palette(ras.ColorMapLength);
|
||||
if (ras.ColorMapType == 1) {
|
||||
for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
|
||||
s >> palette[i];
|
||||
}
|
||||
}
|
||||
|
||||
const int bpp = ras.Depth / 8;
|
||||
if (ras.Height == 0) {
|
||||
return false;
|
||||
}
|
||||
if (bpp == 0) {
|
||||
return false;
|
||||
}
|
||||
if (ras.Length / ras.Height / bpp < ras.Width) {
|
||||
qWarning() << "LoadRAS() mistmatch between height and width" << ras.Width << ras.Height << ras.Length << ras.Depth;
|
||||
return false;
|
||||
}
|
||||
if (ras.Length > kMaxQVectorSize) {
|
||||
qWarning() << "LoadRAS() unsupported image length in file header" << ras.Length;
|
||||
return false;
|
||||
}
|
||||
|
||||
// each line must be a factor of 16 bits, so they may contain padding
|
||||
// this will be 1 if padding required, 0 otherwise
|
||||
const int paddingrequired = (ras.Width * bpp % 2);
|
||||
|
||||
// qDebug() << "paddingrequired: " << paddingrequired;
|
||||
// don't trust ras.Length
|
||||
QVector<quint8> input(ras.Length);
|
||||
|
||||
int i = 0;
|
||||
while (!s.atEnd() && i < input.size()) {
|
||||
s >> input[i];
|
||||
// I guess we need to find out if we're at the end of a line
|
||||
if (paddingrequired && i != 0 && !(i % (ras.Width * bpp))) {
|
||||
s >> input[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// Allocate image
|
||||
img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32);
|
||||
img = imageAlloc(ras.Width, ras.Height, imageFormat(ras));
|
||||
if (img.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reconstruct image from RGB palette if we have a palette
|
||||
// TODO: make generic so it works with 24bit or 32bit palettes
|
||||
if (ras.ColorMapType == 1 && ras.Depth == 8) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
for (quint32 y = 0; y < ras.Height; y++) {
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = palette.value((int)input[y * ras.Width + x]);
|
||||
green = palette.value((int)input[y * ras.Width + x] + (ras.ColorMapLength / 3));
|
||||
blue = palette.value((int)input[y * ras.Width + x] + 2 * (ras.ColorMapLength / 3));
|
||||
img.setPixel(x, y, qRgb(red, green, blue));
|
||||
}
|
||||
// Read palette if needed.
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
|
||||
QVector<quint8> palette(ras.ColorMapLength);
|
||||
for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
|
||||
s >> palette[i];
|
||||
}
|
||||
QList<QRgb> colorTable;
|
||||
for (quint32 i = 0, n = ras.ColorMapLength / 3; i < n; ++i) {
|
||||
colorTable << qRgb(palette.at(i), palette.at(i + n), palette.at(i + 2 * n));
|
||||
}
|
||||
for (; colorTable.size() < 256;) {
|
||||
colorTable << qRgb(255, 255, 255);
|
||||
}
|
||||
img.setColorTable(colorTable);
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
for (quint32 y = 0; y < ras.Height; y++) {
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = input[y * 3 * ras.Width + x * 3 + 2];
|
||||
green = input[y * 3 * ras.Width + x * 3 + 1];
|
||||
blue = input[y * 3 * ras.Width + x * 3];
|
||||
img.setPixel(x, y, qRgb(red, green, blue));
|
||||
}
|
||||
QByteArray rasLine(rasLineSize, 0);
|
||||
auto bytesPerLine = std::min(img.bytesPerLine(), rasLine.size());
|
||||
for (quint32 y = 0; y < ras.Height; ++y) {
|
||||
auto read = s.readRawData(rasLine.data(), rasLine.size());
|
||||
if (read != rasLine.size()) {
|
||||
qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
for (quint32 y = 0; y < ras.Height; y++) {
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = input[y * 3 * ras.Width + x * 3];
|
||||
green = input[y * 3 * ras.Width + x * 3 + 1];
|
||||
blue = input[y * 3 * ras.Width + x * 3 + 2];
|
||||
img.setPixel(x, y, qRgb(red, green, blue));
|
||||
// Grayscale 1-bit / Grayscale 8-bit (never seen)
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && (ras.Depth == 1 || ras.Depth == 8)) {
|
||||
for (auto &&b : rasLine) {
|
||||
b = ~b;
|
||||
}
|
||||
std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
for (quint32 y = 0; y < ras.Height; y++) {
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = input[y * 4 * ras.Width + x * 4 + 3];
|
||||
green = input[y * 4 * ras.Width + x * 4 + 2];
|
||||
blue = input[y * 4 * ras.Width + x * 4 + 1];
|
||||
img.setPixel(x, y, qRgb(red, green, blue));
|
||||
}
|
||||
// Image with palette
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB && (ras.Depth == 1 || ras.Depth == 8)) {
|
||||
std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
for (quint32 y = 0; y < ras.Height; y++) {
|
||||
// BGR 24-bit
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = input[y * 4 * ras.Width + x * 4 + 1];
|
||||
green = input[y * 4 * ras.Width + x * 4 + 2];
|
||||
blue = input[y * 4 * ras.Width + x * 4 + 3];
|
||||
img.setPixel(x, y, qRgb(red, green, blue));
|
||||
red = rasLine.at(x * 3 + 2);
|
||||
green = rasLine.at(x * 3 + 1);
|
||||
blue = rasLine.at(x * 3);
|
||||
*(scanLine + x) = qRgb(red, green, blue);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// RGB 24-bit
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && ras.Type == RAS_TYPE_RGB_FORMAT) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = rasLine.at(x * 3);
|
||||
green = rasLine.at(x * 3 + 1);
|
||||
blue = rasLine.at(x * 3 + 2);
|
||||
*(scanLine + x) = qRgb(red, green, blue);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// BGR 32-bit (not tested: test case missing)
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = rasLine.at(x * 4 + 3);
|
||||
green = rasLine.at(x * 4 + 2);
|
||||
blue = rasLine.at(x * 4 + 1);
|
||||
*(scanLine + x) = qRgb(red, green, blue);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// RGB 32-bit (tested: test case missing due to image too large)
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && ras.Type == RAS_TYPE_RGB_FORMAT) {
|
||||
quint8 red;
|
||||
quint8 green;
|
||||
quint8 blue;
|
||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
for (quint32 x = 0; x < ras.Width; x++) {
|
||||
red = rasLine.at(x * 4 + 1);
|
||||
green = rasLine.at(x * 4 + 2);
|
||||
blue = rasLine.at(x * 4 + 3);
|
||||
*(scanLine + x) = qRgb(red, green, blue);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
qWarning() << "LoadRAS() unsupported format!"
|
||||
<< "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -283,16 +293,8 @@ bool RASHandler::read(QImage *outImage)
|
||||
RasHeader ras;
|
||||
s >> ras;
|
||||
|
||||
if (ras.ColorMapLength > std::numeric_limits<int>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: add support for old versions of RAS where Length may be zero in header
|
||||
s.device()->seek(RasHeader::SIZE + ras.Length + ras.ColorMapLength);
|
||||
|
||||
// Check image file format. Type 2 is RLE, which causing seeking to be silly.
|
||||
if (!s.atEnd() && ras.Type != 2) {
|
||||
// qDebug() << "This RAS file is not valid, or an older version of the format.";
|
||||
if (ras.ColorMapLength > kMaxQVectorSize) {
|
||||
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -314,9 +316,65 @@ bool RASHandler::read(QImage *outImage)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RASHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant RASHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(RasHeader::SIZE);
|
||||
d->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
RasHeader header;
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(RasHeader::SIZE);
|
||||
d->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
RasHeader header;
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "ras") {
|
||||
if (format == "im1" || format == "im8" || format == "im24" || format == "im32" || format == "ras" || format == "sun") {
|
||||
return Capabilities(CanRead);
|
||||
}
|
||||
if (!format.isEmpty()) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"Keys": [ "ras" ],
|
||||
"MimeTypes": [ "image/x-sun-raster" ]
|
||||
"Keys": [ "im1", "im8", "im24", "im32", "ras", "sun" ],
|
||||
"MimeTypes": [ "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster" ]
|
||||
}
|
||||
|
@ -19,6 +19,9 @@ public:
|
||||
bool canRead() const override;
|
||||
bool read(QImage *image) override;
|
||||
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user