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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | // This struct represents header of WBMP image file |
19 | struct |
20 | { |
21 | quint8 ; // Type of WBMP image (always equal to 0) |
22 | quint8 ; // Format of WBMP image |
23 | quint32 ; // Width of the image already decoded from multibyte integer |
24 | quint32 ; // Height of the image already decoded from multibyte integer |
25 | }; |
26 | #define 2 |
27 | |
28 | // Data renderers and writers which takes care of data alignment endiness and stuff |
29 | static 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 | |
52 | static 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 | |
72 | static bool (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 | |
92 | static bool (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 | |
108 | static 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 | |
123 | static 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 | |
138 | class WBMPReader |
139 | { |
140 | public: |
141 | WBMPReader(QIODevice *iodevice); |
142 | |
143 | QImage readImage(); |
144 | bool writeImage(QImage image); |
145 | |
146 | static bool canRead(QIODevice *iodevice); |
147 | |
148 | private: |
149 | QIODevice *iodev; |
150 | WBMPHeader hdr; |
151 | }; |
152 | |
153 | // WBMP common reader and writer implementation |
154 | WBMPReader::WBMPReader(QIODevice *iodevice) : iodev(iodevice) |
155 | { |
156 | memset(s: &hdr, c: 0, n: sizeof(hdr)); |
157 | } |
158 | |
159 | QImage 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 | |
173 | bool 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 | |
200 | bool 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 | */ |
227 | QWbmpHandler::QWbmpHandler(QIODevice *device) : |
228 | m_reader(new WBMPReader(device)) |
229 | { |
230 | } |
231 | |
232 | /*! |
233 | Destructor for QWbmpHandler. |
234 | */ |
235 | QWbmpHandler::~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 | */ |
244 | bool 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 | */ |
263 | bool 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 | */ |
278 | bool 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 | */ |
289 | QVariant 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 | |
314 | bool QWbmpHandler::supportsOption(ImageOption option) const |
315 | { |
316 | return (option == QImageIOHandler::Size) || |
317 | (option == QImageIOHandler::ImageFormat); |
318 | } |
319 | |
320 | bool QWbmpHandler::canRead(QIODevice *device) |
321 | { |
322 | return WBMPReader::canRead(device); |
323 | } |
324 | |
325 | QT_END_NAMESPACE |
326 | |