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 | |
19 | namespace { // 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 { |
43 | quint32 ; |
44 | quint32 ; |
45 | quint32 ; |
46 | quint32 ; |
47 | quint32 ; |
48 | quint32 ; |
49 | quint32 ; |
50 | quint32 ; |
51 | enum { = 32 }; // 8 fields of four bytes each |
52 | }; |
53 | |
54 | static QDataStream & ( 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 ( 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 ( 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 | |
199 | RASHandler::RASHandler() |
200 | { |
201 | } |
202 | |
203 | QByteArray RASHandler::name() const |
204 | { |
205 | return "ras" ; |
206 | } |
207 | |
208 | bool RASHandler::canRead() const |
209 | { |
210 | if (canRead(device())) { |
211 | setFormat("ras" ); |
212 | return true; |
213 | } |
214 | return false; |
215 | } |
216 | |
217 | bool 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 | |
246 | bool 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 | /* |
281 | bool RASHandler::write(const QImage &image){ |
282 | return false; |
283 | }*/ |
284 | |
285 | class RASPlugin : public QImageIOPlugin |
286 | { |
287 | public: |
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 | |
293 | QStringList RASPlugin::keys() const |
294 | { |
295 | return QStringList() << "ras" << "RAS" ; |
296 | } |
297 | |
298 | QImageIOPlugin::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 | |
317 | QImageIOHandler *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 | |
326 | Q_EXPORT_STATIC_PLUGIN(RASPlugin) |
327 | Q_EXPORT_PLUGIN2(ras, RASPlugin) |
328 | |