Files
kimageformats/src/imageformats/ras.cpp
Alex Merry c9ca1f1862 Rename headers to end with _p.h
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).
2014-08-03 18:08:49 +01:00

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;
}