mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
327 lines
9.1 KiB
C++
327 lines
9.1 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.h"
|
|
|
|
#include <QtGui/QImage>
|
|
#include <QtCore/QDataStream>
|
|
|
|
#include <kdebug.h>
|
|
|
|
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()
|
|
{
|
|
}
|
|
|
|
QByteArray RASHandler::name() const
|
|
{
|
|
return "ras";
|
|
}
|
|
|
|
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) {
|
|
kDebug(399) << "This RAS file is not valid, or an older version of the format.";
|
|
return false;
|
|
}
|
|
|
|
// Check supported file types.
|
|
if( !IsSupported(ras) ) {
|
|
kDebug(399) << "This RAS file is not supported.";
|
|
return false;
|
|
}
|
|
|
|
QImage img;
|
|
bool result = LoadRAS(s, ras, img);
|
|
|
|
if( result == false ) {
|
|
kDebug(399) << "Error loading RAS file.";
|
|
return false;
|
|
}
|
|
|
|
*outImage = img;
|
|
return true;
|
|
}
|
|
/*
|
|
bool RASHandler::write(const QImage &image){
|
|
return false;
|
|
}*/
|
|
|
|
class RASPlugin : public QImageIOPlugin
|
|
{
|
|
public:
|
|
QStringList keys() const;
|
|
Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
|
|
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
|
|
};
|
|
|
|
QStringList RASPlugin::keys() const
|
|
{
|
|
return QStringList() << "ras" << "RAS";
|
|
}
|
|
|
|
QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
|
|
if (format == "ras" || format == "RAS")
|
|
return Capabilities(CanRead);
|
|
// return Capabilities(CanRead | CanWrite);
|
|
if (!format.isEmpty())
|
|
return 0;
|
|
if (!device->isOpen())
|
|
return 0;
|
|
|
|
Capabilities cap;
|
|
if (device->isReadable() && RASHandler::canRead(device))
|
|
cap |= CanRead;
|
|
if (device->isWritable())
|
|
cap |= CanWrite;
|
|
return cap;
|
|
}
|
|
|
|
QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
QImageIOHandler *handler = new RASHandler;
|
|
handler->setDevice(device);
|
|
handler->setFormat(format);
|
|
return handler;
|
|
}
|
|
|
|
|
|
Q_EXPORT_STATIC_PLUGIN(RASPlugin)
|
|
Q_EXPORT_PLUGIN2(ras, RASPlugin)
|