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#include <private/qtools_p.h>
14
15#include <stdio.h>
16
17QT_BEGIN_NAMESPACE
18
19using namespace QtMiscUtils;
20
21Q_DECLARE_LOGGING_CATEGORY(lcImageIo)
22
23/*****************************************************************************
24 X bitmap image read/write functions
25 *****************************************************************************/
26
27static inline int hex2byte(char *p)
28{
29 return QtMiscUtils::fromHex(c: p[0]) * 16 | QtMiscUtils::fromHex(c: p[1]);
30}
31
32static bool read_xbm_header(QIODevice *device, int& w, int& h)
33{
34 const int buflen = 300;
35 const int maxlen = 4096;
36 char buf[buflen + 1];
37
38 qint64 readBytes = 0;
39 qint64 totalReadBytes = 0;
40
41 buf[0] = '\0';
42
43 // skip initial comment, if any
44 while (buf[0] != '#') {
45 readBytes = device->readLine(data: buf, maxlen: buflen);
46
47 // if readBytes >= buflen, it's very probably not a C file
48 if (readBytes <= 0 || readBytes >= buflen -1)
49 return false;
50
51 // limit xbm headers to the first 4k in the file to prevent
52 // excessive reads on non-xbm files
53 totalReadBytes += readBytes;
54 if (totalReadBytes >= maxlen)
55 return false;
56 }
57
58 auto parseDefine = [] (const char *buf, int len) -> int {
59 auto checkChar = [] (char ch) -> bool {
60 return isAsciiLetterOrNumber(c: ch)
61 || ch == '_' || ch == '.';
62 };
63 auto isAsciiSpace = [] (char ch) -> bool {
64 return ch == ' ' || ch == '\t';
65 };
66 const char define[] = "#define";
67 constexpr size_t defineLen = sizeof(define) - 1;
68 if (strncmp(s1: buf, s2: define, n: defineLen) != 0)
69 return 0;
70 int index = defineLen;
71 while (buf[index] && isAsciiSpace(buf[index]))
72 ++index;
73 while (buf[index] && checkChar(buf[index]))
74 ++index;
75 while (buf[index] && isAsciiSpace(buf[index]))
76 ++index;
77
78 return QByteArray(buf + index, len - index).toInt();
79 };
80
81
82 // "#define .._width <num>"
83 w = parseDefine(buf, readBytes - 1);
84
85 readBytes = device->readLine(data: buf, maxlen: buflen);
86 // "#define .._height <num>"
87 h = parseDefine(buf, readBytes - 1);
88
89 // format error
90 if (w <= 0 || w > 32767 || h <= 0 || h > 32767)
91 return false;
92
93 return true;
94}
95
96static bool read_xbm_body(QIODevice *device, int w, int h, QImage *outImage)
97{
98 const int buflen = 300;
99 char buf[buflen + 1];
100
101 qint64 readBytes = 0;
102
103 char *p;
104
105 // scan for database
106 do {
107 if ((readBytes = device->readLine(data: buf, maxlen: buflen)) <= 0) {
108 // end of file
109 return false;
110 }
111
112 buf[readBytes] = '\0';
113 p = strstr(haystack: buf, needle: "0x");
114 } while (!p);
115
116 if (!QImageIOHandler::allocateImage(size: QSize(w, h), format: QImage::Format_MonoLSB, image: outImage))
117 return false;
118
119 outImage->fill(color: Qt::color0); // in case the image data does not cover the full image
120
121 outImage->setColorCount(2);
122 outImage->setColor(i: 0, c: qRgb(r: 255,g: 255,b: 255)); // white
123 outImage->setColor(i: 1, c: qRgb(r: 0,g: 0,b: 0)); // black
124
125 int x = 0, y = 0;
126 uchar *b = outImage->scanLine(0);
127 w = (w+7)/8; // byte width
128
129 while (y < h) { // for all encoded bytes...
130 if (p && p < (buf + readBytes - 3)) { // p = "0x.."
131 const int byte = hex2byte(p: p + 2);
132 if (byte < 0) // non-hex char encountered
133 return false;
134 *b++ = byte;
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