KImageFormats

ras.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 2003 Dominik Seichter <[email protected]>
4  SPDX-FileCopyrightText: 2004 Ignacio CastaƱo <[email protected]>
5  SPDX-FileCopyrightText: 2010 Troy Unrau <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "ras_p.h"
11 
12 #include <QDataStream>
13 #include <QDebug>
14 #include <QImage>
15 
16 namespace // Private.
17 {
18 // format info from http://www.fileformat.info/format/sunraster/egff.htm
19 
20 // Header format of saved files.
21 quint32 rasMagicBigEndian = 0x59a66a95;
22 // quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files
23 
24 enum RASType {
25  RAS_TYPE_OLD = 0x0,
26  RAS_TYPE_STANDARD = 0x1,
27  RAS_TYPE_BYTE_ENCODED = 0x2,
28  RAS_TYPE_RGB_FORMAT = 0x3,
29  RAS_TYPE_TIFF_FORMAT = 0x4,
30  RAS_TYPE_IFF_FORMAT = 0x5,
31  RAS_TYPE_EXPERIMENTAL = 0xFFFF,
32 };
33 
34 enum RASColorMapType {
35  RAS_COLOR_MAP_TYPE_NONE = 0x0,
36  RAS_COLOR_MAP_TYPE_RGB = 0x1,
37  RAS_COLOR_MAP_TYPE_RAW = 0x2,
38 };
39 
40 struct RasHeader {
41  quint32 MagicNumber;
42  quint32 Width;
43  quint32 Height;
44  quint32 Depth;
45  quint32 Length;
46  quint32 Type;
47  quint32 ColorMapType;
48  quint32 ColorMapLength;
49  enum {
50  SIZE = 32,
51  }; // 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) || head.Depth == 24 || head.Depth == 32)) {
85  return false;
86  }
87  // the Type field adds support for RLE(BGR), RGB and other encodings
88  // we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY!
89  // TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF
90  if (!(head.Type == 1 || head.Type == 3)) {
91  return false;
92  }
93  // Old files didn't have Length set - reject them for now
94  // TODO: add length recalculation to support old files
95  if (!head.Length) {
96  return false;
97  }
98  return true;
99 }
100 
101 static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
102 {
103  s.device()->seek(RasHeader::SIZE);
104 
105  // QVector uses some extra space for stuff, hence the 32 here suggested by thiago
106  if (ras.ColorMapLength > std::numeric_limits<int>::max() - 32) {
107  qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
108  return false;
109  }
110 
111  // Read palette if needed.
112  QVector<quint8> palette(ras.ColorMapLength);
113  if (ras.ColorMapType == 1) {
114  for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
115  s >> palette[i];
116  }
117  }
118 
119  const int bpp = ras.Depth / 8;
120  if (ras.Height == 0) {
121  return false;
122  }
123  if (bpp == 0) {
124  return false;
125  }
126  if (ras.Length / ras.Height / bpp < ras.Width) {
127  qWarning() << "LoadRAS() mistmatch between height and width" << ras.Width << ras.Height << ras.Length << ras.Depth;
128  return false;
129  }
130  // QVector uses some extra space for stuff, hence the 32 here suggested by thiago
131  if (ras.Length > std::numeric_limits<int>::max() - 32) {
132  qWarning() << "LoadRAS() unsupported image length in file header" << ras.Length;
133  return false;
134  }
135 
136  // each line must be a factor of 16 bits, so they may contain padding
137  // this will be 1 if padding required, 0 otherwise
138  const int paddingrequired = (ras.Width * bpp % 2);
139 
140  // qDebug() << "paddingrequired: " << paddingrequired;
141  // don't trust ras.Length
142  QVector<quint8> input(ras.Length);
143 
144  int i = 0;
145  while (!s.atEnd() && i < input.size()) {
146  s >> input[i];
147  // I guess we need to find out if we're at the end of a line
148  if (paddingrequired && i != 0 && !(i % (ras.Width * bpp))) {
149  s >> input[i];
150  }
151  i++;
152  }
153 
154  // Allocate image
155  img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
156 
157  if (img.isNull()) {
158  return false;
159  }
160 
161  // Reconstruct image from RGB palette if we have a palette
162  // TODO: make generic so it works with 24bit or 32bit palettes
163  if (ras.ColorMapType == 1 && ras.Depth == 8) {
164  quint8 red;
165  quint8 green;
166  quint8 blue;
167  for (quint32 y = 0; y < ras.Height; y++) {
168  for (quint32 x = 0; x < ras.Width; x++) {
169  red = palette.value((int)input[y * ras.Width + x]);
170  green = palette.value((int)input[y * ras.Width + x] + (ras.ColorMapLength / 3));
171  blue = palette.value((int)input[y * ras.Width + x] + 2 * (ras.ColorMapLength / 3));
172  img.setPixel(x, y, qRgb(red, green, blue));
173  }
174  }
175  }
176 
177  if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
178  quint8 red;
179  quint8 green;
180  quint8 blue;
181  for (quint32 y = 0; y < ras.Height; y++) {
182  for (quint32 x = 0; x < ras.Width; x++) {
183  red = input[y * 3 * ras.Width + x * 3 + 2];
184  green = input[y * 3 * ras.Width + x * 3 + 1];
185  blue = input[y * 3 * ras.Width + x * 3];
186  img.setPixel(x, y, qRgb(red, green, blue));
187  }
188  }
189  }
190 
191  if (ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
192  quint8 red;
193  quint8 green;
194  quint8 blue;
195  for (quint32 y = 0; y < ras.Height; y++) {
196  for (quint32 x = 0; x < ras.Width; x++) {
197  red = input[y * 3 * ras.Width + x * 3];
198  green = input[y * 3 * ras.Width + x * 3 + 1];
199  blue = input[y * 3 * ras.Width + x * 3 + 2];
200  img.setPixel(x, y, qRgb(red, green, blue));
201  }
202  }
203  }
204 
205  if (ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
206  quint8 red;
207  quint8 green;
208  quint8 blue;
209  for (quint32 y = 0; y < ras.Height; y++) {
210  for (quint32 x = 0; x < ras.Width; x++) {
211  red = input[y * 4 * ras.Width + x * 4 + 3];
212  green = input[y * 4 * ras.Width + x * 4 + 2];
213  blue = input[y * 4 * ras.Width + x * 4 + 1];
214  img.setPixel(x, y, qRgb(red, green, blue));
215  }
216  }
217  }
218 
219  if (ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3) {
220  quint8 red;
221  quint8 green;
222  quint8 blue;
223  for (quint32 y = 0; y < ras.Height; y++) {
224  for (quint32 x = 0; x < ras.Width; x++) {
225  red = input[y * 4 * ras.Width + x * 4 + 1];
226  green = input[y * 4 * ras.Width + x * 4 + 2];
227  blue = input[y * 4 * ras.Width + x * 4 + 3];
228  img.setPixel(x, y, qRgb(red, green, blue));
229  }
230  }
231  }
232 
233  return true;
234 }
235 } // namespace
236 
237 RASHandler::RASHandler()
238 {
239 }
240 
241 bool RASHandler::canRead() const
242 {
243  if (canRead(device())) {
244  setFormat("ras");
245  return true;
246  }
247  return false;
248 }
249 
250 bool RASHandler::canRead(QIODevice *device)
251 {
252  if (!device) {
253  qWarning("RASHandler::canRead() called with no device");
254  return false;
255  }
256 
257  if (device->isSequential()) {
258  // qWarning("Reading ras files from sequential devices not supported");
259  return false;
260  }
261 
262  qint64 oldPos = device->pos();
263  QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
264  int readBytes = head.size(); // this should always be 32 bytes
265 
266  device->seek(oldPos);
267 
268  if (readBytes < RasHeader::SIZE) {
269  return false;
270  }
271 
272  QDataStream stream(head);
273  stream.setByteOrder(QDataStream::BigEndian);
274  RasHeader ras;
275  stream >> ras;
276  return IsSupported(ras);
277 }
278 
279 bool RASHandler::read(QImage *outImage)
280 {
281  QDataStream s(device());
283 
284  // Read image header.
285  RasHeader ras;
286  s >> ras;
287 
288  if (ras.ColorMapLength > std::numeric_limits<int>::max()) {
289  return false;
290  }
291 
292  // TODO: add support for old versions of RAS where Length may be zero in header
293  s.device()->seek(RasHeader::SIZE + ras.Length + ras.ColorMapLength);
294 
295  // Check image file format. Type 2 is RLE, which causing seeking to be silly.
296  if (!s.atEnd() && ras.Type != 2) {
297  // qDebug() << "This RAS file is not valid, or an older version of the format.";
298  return false;
299  }
300 
301  // Check supported file types.
302  if (!IsSupported(ras)) {
303  // qDebug() << "This RAS file is not supported.";
304  return false;
305  }
306 
307  QImage img;
308  bool result = LoadRAS(s, ras, img);
309 
310  if (result == false) {
311  // qDebug() << "Error loading RAS file.";
312  return false;
313  }
314 
315  *outImage = img;
316  return true;
317 }
318 
319 QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
320 {
321  if (format == "ras") {
322  return Capabilities(CanRead);
323  }
324  if (!format.isEmpty()) {
325  return {};
326  }
327  if (!device->isOpen()) {
328  return {};
329  }
330 
331  Capabilities cap;
332  if (device->isReadable() && RASHandler::canRead(device)) {
333  cap |= CanRead;
334  }
335  return cap;
336 }
337 
338 QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
339 {
340  QImageIOHandler *handler = new RASHandler;
341  handler->setDevice(device);
342  handler->setFormat(format);
343  return handler;
344 }
virtual bool seek(qint64 pos)
QDataStream & operator>>(QDataStream &in, KDateTime::Spec &spec)
void setPixel(int x, int y, uint index_or_rgb)
bool isEmpty() const const
bool isNull() const const
bool isReadable() const const
virtual bool isSequential() const const
virtual qint64 pos() const const
void setDevice(QIODevice *device)
qint64 read(char *data, qint64 maxSize)
bool isOpen() const const
bool atEnd() const const
Width
void setByteOrder(QDataStream::ByteOrder bo)
typedef Capabilities
void setFormat(const QByteArray &format)
Height
QIODevice * device() const const
int size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 22:45:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.