mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-17 11:44:16 -04:00
Frameworks have a convention of naming uninstalled headers in src/ with a _p at the end of the name, to make it clear they are not part of the API. None of the headers in KImageFormats are installed, so it is not really necessary to follow this convention, but we follow it anyway for the benefit of both humans and tools (like kapidox).
305 lines
9.0 KiB
C++
305 lines
9.0 KiB
C++
/* This file is part of the KDE project
|
|
Copyright (C) 2003 Dominik Seichter <domseichter@web.de>
|
|
Copyright (C) 2004 Ignacio Castaño <castano@ludicon.com>
|
|
Copyright (C) 2010 Troy Unrau <troy@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the Lesser GNU General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include "ras_p.h"
|
|
|
|
#include <QImage>
|
|
#include <QtCore/QDataStream>
|
|
// #include <QDebug>
|
|
|
|
namespace // Private.
|
|
{
|
|
// format info from http://www.fileformat.info/format/sunraster/egff.htm
|
|
|
|
// Header format of saved files.
|
|
quint32 rasMagicBigEndian = 0x59a66a95;
|
|
// quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files
|
|
|
|
enum RASType {
|
|
RAS_TYPE_OLD = 0x0,
|
|
RAS_TYPE_STANDARD = 0x1,
|
|
RAS_TYPE_BYTE_ENCODED = 0x2,
|
|
RAS_TYPE_RGB_FORMAT = 0x3,
|
|
RAS_TYPE_TIFF_FORMAT = 0x4,
|
|
RAS_TYPE_IFF_FORMAT = 0x5,
|
|
RAS_TYPE_EXPERIMENTAL = 0xFFFF
|
|
};
|
|
|
|
enum RASColorMapType {
|
|
RAS_COLOR_MAP_TYPE_NONE = 0x0,
|
|
RAS_COLOR_MAP_TYPE_RGB = 0x1,
|
|
RAS_COLOR_MAP_TYPE_RAW = 0x2
|
|
};
|
|
|
|
struct RasHeader {
|
|
quint32 MagicNumber;
|
|
quint32 Width;
|
|
quint32 Height;
|
|
quint32 Depth;
|
|
quint32 Length;
|
|
quint32 Type;
|
|
quint32 ColorMapType;
|
|
quint32 ColorMapLength;
|
|
enum { SIZE = 32 }; // 8 fields of four bytes each
|
|
};
|
|
|
|
static QDataStream &operator>> (QDataStream &s, RasHeader &head)
|
|
{
|
|
s >> head.MagicNumber;
|
|
s >> head.Width;
|
|
s >> head.Height;
|
|
s >> head.Depth;
|
|
s >> head.Length;
|
|
s >> head.Type;
|
|
s >> head.ColorMapType;
|
|
s >> head.ColorMapLength;
|
|
/*qDebug() << "MagicNumber: " << head.MagicNumber
|
|
<< "Width: " << head.Width
|
|
<< "Height: " << head.Height
|
|
<< "Depth: " << head.Depth
|
|
<< "Length: " << head.Length
|
|
<< "Type: " << head.Type
|
|
<< "ColorMapType: " << head.ColorMapType
|
|
<< "ColorMapLength: " << head.ColorMapLength;*/
|
|
return s;
|
|
}
|
|
|
|
static bool IsSupported(const RasHeader &head)
|
|
{
|
|
// check magic number
|
|
if (head.MagicNumber != rasMagicBigEndian) {
|
|
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)) {
|
|
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) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
|
{
|
|
s.device()->seek(RasHeader::SIZE);
|
|
// Read palette if needed.
|
|
QVector<quint8> palette(ras.ColorMapLength);
|
|
if (ras.ColorMapType == 1) {
|
|
for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
|
|
s >> palette[i];
|
|
}
|
|
}
|
|
|
|
// each line must be a factor of 16 bits, so they may contain padding
|
|
// this will be 1 if padding required, 0 otherwise
|
|
int paddingrequired = (ras.Width * (ras.Depth / 8) % 2);
|
|
|
|
// qDebug() << "paddingrequired: " << paddingrequired;
|
|
// don't trust ras.Length
|
|
QVector<quint8> input(ras.Length);
|
|
|
|
int i = 0;
|
|
while (! s.atEnd()) {
|
|
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 * (ras.Depth / 8)))) {
|
|
s >> input[i];
|
|
}
|
|
i++;
|
|
}
|
|
|
|
// Allocate image
|
|
img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
|
|
|
|
// 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, green, blue;
|
|
for (quint32 y = 0; y < ras.Height; y++) {
|
|
for (quint32 x = 0; x < ras.Width; x++) {
|
|
red = palette[(int)input[y * ras.Width + x]];
|
|
green = palette[(int)input[y * ras.Width + x] + (ras.ColorMapLength / 3)];
|
|
blue = palette[(int)input[y * ras.Width + x] + 2 * (ras.ColorMapLength / 3)];
|
|
img.setPixel(x, y, qRgb(red, green, blue));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
|
|
quint8 red, green, 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
|
|
quint8 red, green, 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
|
|
quint8 red, green, 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3) {
|
|
quint8 red, green, 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 + 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
RASHandler::RASHandler()
|
|
{
|
|
}
|
|
|
|
bool RASHandler::canRead() const
|
|
{
|
|
if (canRead(device())) {
|
|
setFormat("ras");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RASHandler::canRead(QIODevice *device)
|
|
{
|
|
if (!device) {
|
|
qWarning("RASHandler::canRead() called with no device");
|
|
return false;
|
|
}
|
|
|
|
if (device->isSequential()) {
|
|
qWarning("Reading ras files from sequential devices not supported");
|
|
return false;
|
|
}
|
|
|
|
qint64 oldPos = device->pos();
|
|
QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
|
|
int readBytes = head.size(); // this should always be 32 bytes
|
|
|
|
device->seek(oldPos);
|
|
|
|
if (readBytes < RasHeader::SIZE) {
|
|
return false;
|
|
}
|
|
|
|
QDataStream stream(head);
|
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
RasHeader ras;
|
|
stream >> ras;
|
|
return IsSupported(ras);
|
|
}
|
|
|
|
bool RASHandler::read(QImage *outImage)
|
|
{
|
|
QDataStream s(device());
|
|
s.setByteOrder(QDataStream::BigEndian);
|
|
|
|
// Read image header.
|
|
RasHeader ras;
|
|
s >> ras;
|
|
// 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.";
|
|
return false;
|
|
}
|
|
|
|
// Check supported file types.
|
|
if (!IsSupported(ras)) {
|
|
// qDebug() << "This RAS file is not supported.";
|
|
return false;
|
|
}
|
|
|
|
QImage img;
|
|
bool result = LoadRAS(s, ras, img);
|
|
|
|
if (result == false) {
|
|
// qDebug() << "Error loading RAS file.";
|
|
return false;
|
|
}
|
|
|
|
*outImage = img;
|
|
return true;
|
|
}
|
|
|
|
QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
|
|
if (format == "ras") {
|
|
return Capabilities(CanRead);
|
|
}
|
|
if (!format.isEmpty()) {
|
|
return 0;
|
|
}
|
|
if (!device->isOpen()) {
|
|
return 0;
|
|
}
|
|
|
|
Capabilities cap;
|
|
if (device->isReadable() && RASHandler::canRead(device)) {
|
|
cap |= CanRead;
|
|
}
|
|
return cap;
|
|
}
|
|
|
|
QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
QImageIOHandler *handler = new RASHandler;
|
|
handler->setDevice(device);
|
|
handler->setFormat(format);
|
|
return handler;
|
|
}
|