1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwbmphandler_p.h"
5
6/*!
7 \class QWbmpHandler
8 \since 5.0
9 \brief The QWbmpHandler class provides support for the WBMP image format.
10 \internal
11*/
12
13#include <qimage.h>
14#include <qvariant.h>
15
16QT_BEGIN_NAMESPACE
17
18// This struct represents header of WBMP image file
19struct WBMPHeader
20{
21 quint8 type; // Type of WBMP image (always equal to 0)
22 quint8 format; // Format of WBMP image
23 quint32 width; // Width of the image already decoded from multibyte integer
24 quint32 height; // Height of the image already decoded from multibyte integer
25};
26#define WBMPFIXEDHEADER_SIZE 2
27
28// Data renderers and writers which takes care of data alignment endiness and stuff
29static bool readMultiByteInt(QIODevice *iodev, quint32 *num)
30{
31 quint32 res = 0;
32
33 quint8 c;
34 unsigned int count = 0;
35 do {
36 // Do not allow to read longer
37 // then we can store in num
38 if (++count > sizeof(*num))
39 return false;
40
41 if (!iodev->getChar(c: reinterpret_cast<char *>(&c)))
42 return false;
43
44 res = (res << 7) | (c & 0x7F);
45
46 } while (c & 0x80);
47
48 *num = res;
49 return true;
50}
51
52static bool writeMultiByteInt(QIODevice *iodev, quint32 num)
53{
54 quint64 tmp = num & 0x7F;
55 num >>= 7;
56
57 while (num) {
58 quint8 c = num & 0x7F;
59 num = num >> 7;
60 tmp = (tmp << 8) | (c | 0x80);
61 }
62
63 while (tmp) {
64 quint8 c = tmp & 0xFF;
65 if (!iodev->putChar(c))
66 return false;
67 tmp >>= 8;
68 }
69 return true;
70}
71
72static bool readWBMPHeader(QIODevice *iodev, WBMPHeader *hdr)
73{
74 if (!iodev)
75 return false;
76
77 uchar tmp[WBMPFIXEDHEADER_SIZE];
78 if (iodev->read(data: reinterpret_cast<char *>(tmp), WBMPFIXEDHEADER_SIZE) == WBMPFIXEDHEADER_SIZE) {
79 hdr->type = tmp[0];
80 hdr->format = tmp[1];
81 } else {
82 return false;
83 }
84
85 if (readMultiByteInt(iodev, num: &hdr->width)
86 && readMultiByteInt(iodev, num: &hdr->height)) {
87 return true;
88 }
89 return false;
90}
91
92static bool writeWBMPHeader(QIODevice *iodev, const WBMPHeader &hdr)
93{
94 if (iodev) {
95 uchar tmp[WBMPFIXEDHEADER_SIZE];
96 tmp[0] = hdr.type;
97 tmp[1] = hdr.format;
98 if (iodev->write(data: reinterpret_cast<char *>(tmp), WBMPFIXEDHEADER_SIZE) != WBMPFIXEDHEADER_SIZE)
99 return false;
100
101 if (writeMultiByteInt(iodev, num: hdr.width) &&
102 writeMultiByteInt(iodev, num: hdr.height))
103 return true;
104 }
105 return false;
106}
107
108static bool writeWBMPData(QIODevice *iodev, const QImage &image)
109{
110 if (iodev) {
111 int h = image.height();
112 int bpl = (image.width() + 7) / 8;
113
114 for (int l=0; l<h; l++) {
115 if (iodev->write(data: reinterpret_cast<const char *>(image.constScanLine(l)), len: bpl) != bpl)
116 return false;
117 }
118 return true;
119 }
120 return false;
121}
122
123static bool readWBMPData(QIODevice *iodev, QImage &image)
124{
125 if (iodev) {
126 int h = image.height();
127 int bpl = (image.width() + 7) / 8;
128
129 for (int l = 0; l < h; l++) {
130 if (iodev->read(data: reinterpret_cast<char *>(image.scanLine(l)), maxlen: bpl) != bpl)
131 return false;
132 }
133 return true;
134 }
135 return false;
136}
137
138class WBMPReader
139{
140public:
141 WBMPReader(QIODevice *iodevice);
142
143 QImage readImage();
144 bool writeImage(QImage image);
145
146 static bool canRead(QIODevice *iodevice);
147
148private:
149 QIODevice *iodev;
150 WBMPHeader hdr;
151};
152
153// WBMP common reader and writer implementation
154WBMPReader::WBMPReader(QIODevice *iodevice) : iodev(iodevice)
155{
156 memset(s: &hdr, c: 0, n: sizeof(hdr));
157}
158
159QImage WBMPReader::readImage()
160{
161 if (!readWBMPHeader(iodev, hdr: &hdr))
162 return QImage();
163
164 QImage image;
165 if (!QImageIOHandler::allocateImage(size: QSize(hdr.width, hdr.height), format: QImage::Format_Mono, image: &image))
166 return QImage();
167 if (!readWBMPData(iodev, image))
168 return QImage();
169
170 return image;
171}
172
173bool WBMPReader::writeImage(QImage image)
174{
175 if (image.format() != QImage::Format_Mono)
176 image = image.convertToFormat(f: QImage::Format_Mono);
177
178 if (image.colorTable().at(i: 0) == image.colorTable().at(i: 1)) {
179 // degenerate image: actually blank.
180 image.fill(pixel: (qGray(rgb: image.colorTable().at(i: 0)) < 128) ? 0 : 1);
181 } else if (qGray(rgb: image.colorTable().at(i: 0)) > qGray(rgb: image.colorTable().at(i: 1))) {
182 // Conform to WBMP's convention about black and white
183 image.invertPixels();
184 }
185
186 hdr.type = 0;
187 hdr.format = 0;
188 hdr.width = image.width();
189 hdr.height = image.height();
190
191 if (!writeWBMPHeader(iodev, hdr))
192 return false;
193
194 if (!writeWBMPData(iodev, image))
195 return false;
196
197 return true;
198}
199
200bool WBMPReader::canRead(QIODevice *device)
201{
202 if (device) {
203
204 if (device->isSequential())
205 return false;
206
207 // Save previous position
208 qint64 oldPos = device->pos();
209
210 WBMPHeader hdr;
211 if (readWBMPHeader(iodev: device, hdr: &hdr)) {
212 if ((hdr.type == 0) && (hdr.format == 0)) {
213 const qint64 imageSize = hdr.height * ((qint64(hdr.width) + 7) / 8);
214 qint64 available = device->bytesAvailable();
215 device->seek(pos: oldPos);
216 return (imageSize == available);
217 }
218 }
219 device->seek(pos: oldPos);
220 }
221 return false;
222}
223
224/*!
225 Constructs an instance of QWbmpHandler initialized to use \a device.
226*/
227QWbmpHandler::QWbmpHandler(QIODevice *device) :
228 m_reader(new WBMPReader(device))
229{
230}
231
232/*!
233 Destructor for QWbmpHandler.
234*/
235QWbmpHandler::~QWbmpHandler()
236{
237 delete m_reader;
238}
239
240/*!
241 * Verifies if some values (magic bytes) are set as expected in the header of the file.
242 * If the magic bytes were found, it is assumed that the QWbmpHandler can read the file.
243 */
244bool QWbmpHandler::canRead() const
245{
246 bool bCanRead = false;
247
248 QIODevice *device = QImageIOHandler::device();
249 if (device) {
250 bCanRead = QWbmpHandler::canRead(device);
251 if (bCanRead)
252 setFormat("wbmp");
253
254 } else {
255 qWarning(msg: "QWbmpHandler::canRead() called with no device");
256 }
257
258 return bCanRead;
259}
260
261/*! \reimp
262*/
263bool QWbmpHandler::read(QImage *image)
264{
265 bool bSuccess = false;
266 QImage img = m_reader->readImage();
267
268 if (!img.isNull()) {
269 bSuccess = true;
270 *image = img;
271 }
272
273 return bSuccess;
274}
275
276/*! \reimp
277*/
278bool QWbmpHandler::write(const QImage &image)
279{
280 if (image.isNull())
281 return false;
282
283 return m_reader->writeImage(image);
284}
285
286/*!
287 Only Size option is supported
288*/
289QVariant QWbmpHandler::option(ImageOption option) const
290{
291 if (option == QImageIOHandler::Size) {
292 QIODevice *device = QImageIOHandler::device();
293 if (device->isSequential())
294 return QVariant();
295
296 // Save old position
297 qint64 oldPos = device->pos();
298
299 WBMPHeader hdr;
300 if (readWBMPHeader(iodev: device, hdr: &hdr)) {
301 device->seek(pos: oldPos);
302 return QSize(hdr.width, hdr.height);
303 }
304
305 device->seek(pos: oldPos);
306
307 } else if (option == QImageIOHandler::ImageFormat) {
308 return QVariant(QImage::Format_Mono);
309 }
310
311 return QVariant();
312}
313
314bool QWbmpHandler::supportsOption(ImageOption option) const
315{
316 return (option == QImageIOHandler::Size) ||
317 (option == QImageIOHandler::ImageFormat);
318}
319
320bool QWbmpHandler::canRead(QIODevice *device)
321{
322 return WBMPReader::canRead(device);
323}
324
325QT_END_NAMESPACE
326

source code of qtimageformats/src/plugins/imageformats/wbmp/qwbmphandler.cpp