1/* This file is part of the KDE project
2 Copyright (C) 2003 Dominik Seichter <domseichter@web.de>
3 Copyright (C) 2004 Ignacio CastaƱo <castano@ludicon.com>
4 Copyright (C) 2010 Troy Unrau <troy@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the Lesser GNU General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10*/
11
12#include "ras.h"
13
14#include <QtGui/QImage>
15#include <QtCore/QDataStream>
16
17#include <kdebug.h>
18
19namespace { // Private.
20 // format info from http://www.fileformat.info/format/sunraster/egff.htm
21
22 // Header format of saved files.
23 quint32 rasMagicBigEndian = 0x59a66a95;
24 // quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files
25
26 enum RASType {
27 RAS_TYPE_OLD = 0x0,
28 RAS_TYPE_STANDARD = 0x1,
29 RAS_TYPE_BYTE_ENCODED = 0x2,
30 RAS_TYPE_RGB_FORMAT = 0x3,
31 RAS_TYPE_TIFF_FORMAT = 0x4,
32 RAS_TYPE_IFF_FORMAT = 0x5,
33 RAS_TYPE_EXPERIMENTAL = 0xFFFF
34 };
35
36 enum RASColorMapType {
37 RAS_COLOR_MAP_TYPE_NONE = 0x0,
38 RAS_COLOR_MAP_TYPE_RGB = 0x1,
39 RAS_COLOR_MAP_TYPE_RAW = 0x2
40 };
41
42 struct RasHeader {
43 quint32 MagicNumber;
44 quint32 Width;
45 quint32 Height;
46 quint32 Depth;
47 quint32 Length;
48 quint32 Type;
49 quint32 ColorMapType;
50 quint32 ColorMapLength;
51 enum { SIZE = 32 }; // 8 fields of four bytes each
52 };
53
54 static QDataStream & operator>> ( QDataStream & s, RasHeader & head )
55 {
56 s >> head.MagicNumber;
57 s >> head.Width;
58 s >> head.Height;
59 s >> head.Depth;
60 s >> head.Length;
61 s >> head.Type;
62 s >> head.ColorMapType;
63 s >> head.ColorMapLength;
64 /*qDebug() << "MagicNumber: " << head.MagicNumber
65 << "Width: " << head.Width
66 << "Height: " << head.Height
67 << "Depth: " << head.Depth
68 << "Length: " << head.Length
69 << "Type: " << head.Type
70 << "ColorMapType: " << head.ColorMapType
71 << "ColorMapLength: " << head.ColorMapLength;*/
72 return s;
73 }
74
75 static bool IsSupported( const RasHeader & head )
76 {
77 // check magic number
78 if ( head.MagicNumber != rasMagicBigEndian) {
79 return false;
80 }
81 // check for an appropriate depth
82 // we support 8bit+palette, 24bit and 32bit ONLY!
83 // TODO: add support for 1bit
84 if ( ! ((head.Depth == 8 && head.ColorMapType == 1)
85 || head.Depth == 24 || head.Depth == 32) ){
86 return false;
87 }
88 // the Type field adds support for RLE(BGR), RGB and other encodings
89 // we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY!
90 // TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF
91 if ( ! (head.Type == 1 || head.Type == 3) ){
92 return false;
93 }
94 // Old files didn't have Length set - reject them for now
95 // TODO: add length recalculation to support old files
96 if ( !head.Length ) {
97 return false;
98 }
99 return true;
100 }
101
102 static bool LoadRAS( QDataStream & s, const RasHeader & ras, QImage &img )
103 {
104 s.device()->seek(RasHeader::SIZE);
105 // Read palette if needed.
106 QVector<quint8> palette(ras.ColorMapLength);
107 if ( ras.ColorMapType == 1 ) {
108 for (quint32 i = 0; i < ras.ColorMapLength; ++i) { s >> palette[i]; }
109 }
110
111 // each line must be a factor of 16 bits, so they may contain padding
112 // this will be 1 if padding required, 0 otherwise
113 int paddingrequired = (ras.Width*(ras.Depth/8) % 2);
114
115 // qDebug() << "paddingrequired: " << paddingrequired;
116 // don't trust ras.Length
117 QVector<quint8> input(ras.Length);
118
119 int i = 0;
120 while ( ! s.atEnd()) {
121 s >> input[i];
122 // I guess we need to find out if we're at the end of a line
123 if ( paddingrequired && i != 0 && !(i % (ras.Width*(ras.Depth/8))) ) {
124 s >> input[i];
125 }
126 i++;
127 }
128
129 // Allocate image
130 img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
131
132 // Reconstruct image from RGB palette if we have a palette
133 // TODO: make generic so it works with 24bit or 32bit palettes
134 if ( ras.ColorMapType == 1 && ras.Depth == 8) {
135 quint8 red, green, blue;
136 for ( quint32 y = 0; y < ras.Height; y++ ){
137 for ( quint32 x = 0; x < ras.Width; x++ ) {
138 red = palette[(int)input[y*ras.Width + x]];
139 green = palette[(int)input[y*ras.Width + x] + (ras.ColorMapLength/3)];
140 blue = palette[(int)input[y*ras.Width + x] + 2*(ras.ColorMapLength/3)];
141 img.setPixel(x, y, qRgb(red, green, blue));
142 }
143 }
144
145 }
146
147 if ( ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
148 quint8 red, green, blue;
149 for ( quint32 y = 0; y < ras.Height; y++ ){
150 for ( quint32 x = 0; x < ras.Width; x++ ) {
151 red = input[y*3*ras.Width + x*3 + 2];
152 green = input[y*3*ras.Width + x*3 + 1];
153 blue = input[y*3*ras.Width + x*3];
154 img.setPixel(x, y, qRgb(red, green, blue));
155 }
156 }
157 }
158
159 if ( ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
160 quint8 red, green, blue;
161 for ( quint32 y = 0; y < ras.Height; y++ ){
162 for ( quint32 x = 0; x < ras.Width; x++ ) {
163 red = input[y*3*ras.Width + x*3];
164 green = input[y*3*ras.Width + x*3 + 1];
165 blue = input[y*3*ras.Width + x*3 + 2];
166 img.setPixel(x, y, qRgb(red, green, blue));
167 }
168 }
169 }
170
171 if ( ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
172 quint8 red, green, blue;
173 for ( quint32 y = 0; y < ras.Height; y++ ){
174 for ( quint32 x = 0; x < ras.Width; x++ ) {
175 red = input[y*4*ras.Width + x*4 + 3];
176 green = input[y*4*ras.Width + x*4 + 2];
177 blue = input[y*4*ras.Width + x*4 + 1];
178 img.setPixel(x, y, qRgb(red, green, blue));
179 }
180 }
181 }
182
183 if ( ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3 ) {
184 quint8 red, green, blue;
185 for ( quint32 y = 0; y < ras.Height; y++ ){
186 for ( quint32 x = 0; x < ras.Width; x++ ) {
187 red = input[y*4*ras.Width + x*4 + 1];
188 green = input[y*4*ras.Width + x*4 + 2];
189 blue = input[y*4*ras.Width + x*4 + 3];
190 img.setPixel(x, y, qRgb(red, green, blue));
191 }
192 }
193 }
194
195 return true;
196 }
197} // namespace
198
199RASHandler::RASHandler()
200{
201}
202
203QByteArray RASHandler::name() const
204{
205 return "ras";
206}
207
208bool RASHandler::canRead() const
209{
210 if (canRead(device())) {
211 setFormat("ras");
212 return true;
213 }
214 return false;
215}
216
217bool RASHandler::canRead(QIODevice *device)
218{
219 if (!device) {
220 qWarning("RASHandler::canRead() called with no device");
221 return false;
222 }
223
224 if (device->isSequential()) {
225 qWarning("Reading ras files from sequential devices not supported");
226 return false;
227 }
228
229 qint64 oldPos = device->pos();
230 QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
231 int readBytes = head.size(); // this should always be 32 bytes
232
233 device->seek(oldPos);
234
235 if (readBytes < RasHeader::SIZE) {
236 return false;
237 }
238
239 QDataStream stream(head);
240 stream.setByteOrder(QDataStream::BigEndian);
241 RasHeader ras;
242 stream >> ras;
243 return IsSupported(ras);
244}
245
246bool RASHandler::read(QImage *outImage)
247{
248 QDataStream s( device() );
249 s.setByteOrder( QDataStream::BigEndian );
250
251 // Read image header.
252 RasHeader ras;
253 s >> ras;
254 // TODO: add support for old versions of RAS where Length may be zero in header
255 s.device()->seek( RasHeader::SIZE + ras.Length + ras.ColorMapLength );
256
257 // Check image file format. Type 2 is RLE, which causing seeking to be silly.
258 if( !s.atEnd() && ras.Type != 2) {
259 kDebug(399) << "This RAS file is not valid, or an older version of the format.";
260 return false;
261 }
262
263 // Check supported file types.
264 if( !IsSupported(ras) ) {
265 kDebug(399) << "This RAS file is not supported.";
266 return false;
267 }
268
269 QImage img;
270 bool result = LoadRAS(s, ras, img);
271
272 if( result == false ) {
273 kDebug(399) << "Error loading RAS file.";
274 return false;
275 }
276
277 *outImage = img;
278 return true;
279}
280/*
281bool RASHandler::write(const QImage &image){
282 return false;
283}*/
284
285class RASPlugin : public QImageIOPlugin
286{
287public:
288 QStringList keys() const;
289 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
290 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
291};
292
293QStringList RASPlugin::keys() const
294{
295 return QStringList() << "ras" << "RAS";
296}
297
298QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
299{
300
301 if (format == "ras" || format == "RAS")
302 return Capabilities(CanRead);
303// return Capabilities(CanRead | CanWrite);
304 if (!format.isEmpty())
305 return 0;
306 if (!device->isOpen())
307 return 0;
308
309 Capabilities cap;
310 if (device->isReadable() && RASHandler::canRead(device))
311 cap |= CanRead;
312 if (device->isWritable())
313 cap |= CanWrite;
314 return cap;
315}
316
317QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
318{
319 QImageIOHandler *handler = new RASHandler;
320 handler->setDevice(device);
321 handler->setFormat(format);
322 return handler;
323}
324
325
326Q_EXPORT_STATIC_PLUGIN(RASPlugin)
327Q_EXPORT_PLUGIN2(ras, RASPlugin)
328