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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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