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 <qplatformdefs.h>
5#include "private/qxbmhandler_p.h"
6
7#ifndef QT_NO_IMAGEFORMAT_XBM
8
9#include <qimage.h>
10#include <qiodevice.h>
11#include <qloggingcategory.h>
12#include <qvariant.h>
13
14#include <stdio.h>
15#include <ctype.h>
16
17QT_BEGIN_NAMESPACE
18
19Q_DECLARE_LOGGING_CATEGORY(lcImageIo)
20
21/*****************************************************************************
22 X bitmap image read/write functions
23 *****************************************************************************/
24
25static inline int hex2byte(char *p)
26{
27 return ((isdigit((uchar) *p) ? *p - '0' : toupper(c: (uchar) *p) - 'A' + 10) << 4) |
28 (isdigit((uchar) *(p+1)) ? *(p+1) - '0' : toupper(c: (uchar) *(p+1)) - 'A' + 10);
29}
30
31static bool read_xbm_header(QIODevice *device, int& w, int& h)
32{
33 const int buflen = 300;
34 const int maxlen = 4096;
35 char buf[buflen + 1];
36
37 qint64 readBytes = 0;
38 qint64 totalReadBytes = 0;
39
40 buf[0] = '\0';
41
42 // skip initial comment, if any
43 while (buf[0] != '#') {
44 readBytes = device->readLine(data: buf, maxlen: buflen);
45
46 // if readBytes >= buflen, it's very probably not a C file
47 if (readBytes <= 0 || readBytes >= buflen -1)
48 return false;
49
50 // limit xbm headers to the first 4k in the file to prevent
51 // excessive reads on non-xbm files
52 totalReadBytes += readBytes;
53 if (totalReadBytes >= maxlen)
54 return false;
55 }
56
57 auto parseDefine = [] (const char *buf, int len) -> int {
58 auto isAsciiLetterOrNumber = [] (char ch) -> bool {
59 return (ch >= '0' && ch <= '9') ||
60 (ch >= 'A' && ch <= 'Z') ||
61 (ch >= 'a' && ch <= 'z') ||
62 ch == '_' || ch == '.';
63 };
64 auto isAsciiSpace = [] (char ch) -> bool {
65 return ch == ' ' || ch == '\t';
66 };
67 const char define[] = "#define";
68 constexpr size_t defineLen = sizeof(define) - 1;
69 if (strncmp(s1: buf, s2: define, n: defineLen) != 0)
70 return 0;
71 int index = defineLen;
72 while (buf[index] && isAsciiSpace(buf[index]))
73 ++index;
74 while (buf[index] && isAsciiLetterOrNumber(buf[index]))
75 ++index;
76 while (buf[index] && isAsciiSpace(buf[index]))
77 ++index;
78
79 return QByteArray(buf + index, len - index).toInt();
80 };
81
82
83 // "#define .._width <num>"
84 w = parseDefine(buf, readBytes - 1);
85
86 readBytes = device->readLine(data: buf, maxlen: buflen);
87 // "#define .._height <num>"
88 h = parseDefine(buf, readBytes - 1);
89
90 // format error
91 if (w <= 0 || w > 32767 || h <= 0 || h > 32767)
92 return false;
93
94 return true;
95}
96
97static bool read_xbm_body(QIODevice *device, int w, int h, QImage *outImage)
98{
99 const int buflen = 300;
100 char buf[buflen + 1];
101
102 qint64 readBytes = 0;
103
104 char *p;
105
106 // scan for database
107 do {
108 if ((readBytes = device->readLine(data: buf, maxlen: buflen)) <= 0) {
109 // end of file
110 return false;
111 }
112
113 buf[readBytes] = '\0';
114 p = strstr(haystack: buf, needle: "0x");
115 } while (!p);
116
117 if (!QImageIOHandler::allocateImage(size: QSize(w, h), format: QImage::Format_MonoLSB, image: outImage))
118 return false;
119
120 outImage->fill(color: Qt::color0); // in case the image data does not cover the full image
121
122 outImage->setColorCount(2);
123 outImage->setColor(i: 0, c: qRgb(r: 255,g: 255,b: 255)); // white
124 outImage->setColor(i: 1, c: qRgb(r: 0,g: 0,b: 0)); // black
125
126 int x = 0, y = 0;
127 uchar *b = outImage->scanLine(0);
128 w = (w+7)/8; // byte width
129
130 while (y < h) { // for all encoded bytes...
131 if (p && p < (buf + readBytes - 3)) { // p = "0x.."
132 if (!isxdigit(p[2]) || !isxdigit(p[3]))
133 return false;
134 *b++ = hex2byte(p: p+2);
135 p += 2;
136 if (++x == w && ++y < h) {
137 b = outImage->scanLine(y);
138 x = 0;
139 }
140 p = strstr(haystack: p, needle: "0x");
141 } else { // read another line
142 if ((readBytes = device->readLine(data: buf,maxlen: buflen)) <= 0) // EOF ==> truncated image
143 break;
144 buf[readBytes] = '\0';
145 p = strstr(haystack: buf, needle: "0x");
146 }
147 }
148
149 return true;
150}
151
152static bool read_xbm_image(QIODevice *device, QImage *outImage)
153{
154 int w = 0, h = 0;
155 if (!read_xbm_header(device, w, h))
156 return false;
157 return read_xbm_body(device, w, h, outImage);
158}
159
160static bool write_xbm_image(const QImage &sourceImage, QIODevice *device, const QString &fileName)
161{
162 QImage image = sourceImage;
163 int w = image.width();
164 int h = image.height();
165 int i;
166 QString s = fileName; // get file base name
167 int msize = s.size() + 100;
168 char *buf = new char[msize];
169
170 qsnprintf(str: buf, n: msize, fmt: "#define %s_width %d\n", s.toUtf8().data(), w);
171 device->write(data: buf, len: qstrlen(str: buf));
172 qsnprintf(str: buf, n: msize, fmt: "#define %s_height %d\n", s.toUtf8().data(), h);
173 device->write(data: buf, len: qstrlen(str: buf));
174 qsnprintf(str: buf, n: msize, fmt: "static char %s_bits[] = {\n ", s.toUtf8().data());
175 device->write(data: buf, len: qstrlen(str: buf));
176
177 if (image.format() != QImage::Format_MonoLSB)
178 image = image.convertToFormat(f: QImage::Format_MonoLSB);
179
180 bool invert = qGray(rgb: image.color(i: 0)) < qGray(rgb: image.color(i: 1));
181 char hexrep[16];
182 for (i=0; i<10; i++)
183 hexrep[i] = '0' + i;
184 for (i=10; i<16; i++)
185 hexrep[i] = 'a' -10 + i;
186 if (invert) {
187 char t;
188 for (i=0; i<8; i++) {
189 t = hexrep[15-i];
190 hexrep[15-i] = hexrep[i];
191 hexrep[i] = t;
192 }
193 }
194 int bcnt = 0;
195 char *p = buf;
196 int bpl = (w+7)/8;
197 for (int y = 0; y < h; ++y) {
198 const uchar *b = image.constScanLine(y);
199 for (i = 0; i < bpl; ++i) {
200 *p++ = '0'; *p++ = 'x';
201 *p++ = hexrep[*b >> 4];
202 *p++ = hexrep[*b++ & 0xf];
203
204 if (i < bpl - 1 || y < h - 1) {
205 *p++ = ',';
206 if (++bcnt > 14) {
207 *p++ = '\n';
208 *p++ = ' ';
209 *p = '\0';
210 if ((int)qstrlen(str: buf) != device->write(data: buf, len: qstrlen(str: buf))) {
211 delete [] buf;
212 return false;
213 }
214 p = buf;
215 bcnt = 0;
216 }
217 }
218 }
219 }
220#ifdef Q_CC_MSVC
221 strcpy_s(p, sizeof(" };\n"), " };\n");
222#else
223 strcpy(dest: p, src: " };\n");
224#endif
225 if ((int)qstrlen(str: buf) != device->write(data: buf, len: qstrlen(str: buf))) {
226 delete [] buf;
227 return false;
228 }
229
230 delete [] buf;
231 return true;
232}
233
234QXbmHandler::QXbmHandler()
235 : state(Ready)
236{
237}
238
239bool QXbmHandler::readHeader()
240{
241 state = Error;
242 if (!read_xbm_header(device: device(), w&: width, h&: height))
243 return false;
244 state = ReadHeader;
245 return true;
246}
247
248bool QXbmHandler::canRead() const
249{
250 if (state == Ready && !canRead(device: device()))
251 return false;
252
253 if (state != Error) {
254 setFormat("xbm");
255 return true;
256 }
257
258 return false;
259}
260
261bool QXbmHandler::canRead(QIODevice *device)
262{
263 if (!device) {
264 qCWarning(lcImageIo, "QXbmHandler::canRead() called with no device");
265 return false;
266 }
267
268 // it's impossible to tell whether we can load an XBM or not when
269 // it's from a sequential device, as the only way to do it is to
270 // attempt to parse the whole image.
271 if (device->isSequential())
272 return false;
273
274 QImage image;
275 qint64 oldPos = device->pos();
276 bool success = read_xbm_image(device, outImage: &image);
277 device->seek(pos: oldPos);
278
279 return success;
280}
281
282bool QXbmHandler::read(QImage *image)
283{
284 if (state == Error)
285 return false;
286
287 if (state == Ready && !readHeader()) {
288 state = Error;
289 return false;
290 }
291
292 if (!read_xbm_body(device: device(), w: width, h: height, outImage: image)) {
293 state = Error;
294 return false;
295 }
296
297 state = Ready;
298 return true;
299}
300
301bool QXbmHandler::write(const QImage &image)
302{
303 return write_xbm_image(sourceImage: image, device: device(), fileName);
304}
305
306bool QXbmHandler::supportsOption(ImageOption option) const
307{
308 return option == Name
309 || option == Size
310 || option == ImageFormat;
311}
312
313QVariant QXbmHandler::option(ImageOption option) const
314{
315 if (option == Name) {
316 return fileName;
317 } else if (option == Size) {
318 if (state == Error)
319 return QVariant();
320 if (state == Ready && !const_cast<QXbmHandler*>(this)->readHeader())
321 return QVariant();
322 return QSize(width, height);
323 } else if (option == ImageFormat) {
324 return QImage::Format_MonoLSB;
325 }
326 return QVariant();
327}
328
329void QXbmHandler::setOption(ImageOption option, const QVariant &value)
330{
331 if (option == Name)
332 fileName = value.toString();
333}
334
335QT_END_NAMESPACE
336
337#endif // QT_NO_IMAGEFORMAT_XBM
338

source code of qtbase/src/gui/image/qxbmhandler.cpp