1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <qplatformdefs.h>
41#include "private/qxbmhandler_p.h"
42
43#ifndef QT_NO_IMAGEFORMAT_XBM
44
45#include <qimage.h>
46#include <qiodevice.h>
47#include <qregexp.h>
48#include <qvariant.h>
49
50#include <stdio.h>
51#include <ctype.h>
52
53QT_BEGIN_NAMESPACE
54
55/*****************************************************************************
56 X bitmap image read/write functions
57 *****************************************************************************/
58
59static inline int hex2byte(char *p)
60{
61 return ((isdigit((uchar) *p) ? *p - '0' : toupper(c: (uchar) *p) - 'A' + 10) << 4) |
62 (isdigit((uchar) *(p+1)) ? *(p+1) - '0' : toupper(c: (uchar) *(p+1)) - 'A' + 10);
63}
64
65static bool read_xbm_header(QIODevice *device, int& w, int& h)
66{
67 const int buflen = 300;
68 const int maxlen = 4096;
69 char buf[buflen + 1];
70 QRegExp r1(QLatin1String("^#define[ \t]+[a-zA-Z0-9._]+[ \t]+"));
71 QRegExp r2(QLatin1String("[0-9]+"));
72
73 qint64 readBytes = 0;
74 qint64 totalReadBytes = 0;
75
76 buf[0] = '\0';
77
78 // skip initial comment, if any
79 while (buf[0] != '#') {
80 readBytes = device->readLine(data: buf, maxlen: buflen);
81
82 // if readBytes >= buflen, it's very probably not a C file
83 if (readBytes <= 0 || readBytes >= buflen -1)
84 return false;
85
86 // limit xbm headers to the first 4k in the file to prevent
87 // excessive reads on non-xbm files
88 totalReadBytes += readBytes;
89 if (totalReadBytes >= maxlen)
90 return false;
91 }
92
93 buf[readBytes - 1] = '\0';
94 QString sbuf;
95 sbuf = QString::fromLatin1(str: buf);
96
97 // "#define .._width <num>"
98 if (r1.indexIn(str: sbuf) == 0 &&
99 r2.indexIn(str: sbuf, offset: r1.matchedLength()) == r1.matchedLength())
100 w = QByteArray(&buf[r1.matchedLength()]).trimmed().toInt();
101 else
102 return false;
103
104 // "#define .._height <num>"
105 readBytes = device->readLine(data: buf, maxlen: buflen);
106 if (readBytes <= 0)
107 return false;
108 buf[readBytes - 1] = '\0';
109
110 sbuf = QString::fromLatin1(str: buf);
111
112 if (r1.indexIn(str: sbuf) == 0 &&
113 r2.indexIn(str: sbuf, offset: r1.matchedLength()) == r1.matchedLength())
114 h = QByteArray(&buf[r1.matchedLength()]).trimmed().toInt();
115 else
116 return false;
117
118 // format error
119 if (w <= 0 || w > 32767 || h <= 0 || h > 32767)
120 return false;
121
122 return true;
123}
124
125static bool read_xbm_body(QIODevice *device, int w, int h, QImage *outImage)
126{
127 const int buflen = 300;
128 char buf[buflen + 1];
129
130 qint64 readBytes = 0;
131
132 char *p;
133
134 // scan for database
135 do {
136 if ((readBytes = device->readLine(data: buf, maxlen: buflen)) <= 0) {
137 // end of file
138 return false;
139 }
140
141 buf[readBytes] = '\0';
142 p = strstr(haystack: buf, needle: "0x");
143 } while (!p);
144
145 if (outImage->size() != QSize(w, h) || outImage->format() != QImage::Format_MonoLSB) {
146 *outImage = QImage(w, h, QImage::Format_MonoLSB);
147 if (outImage->isNull())
148 return false;
149 }
150
151 outImage->fill(color: Qt::color0); // in case the image data does not cover the full image
152
153 outImage->setColorCount(2);
154 outImage->setColor(i: 0, c: qRgb(r: 255,g: 255,b: 255)); // white
155 outImage->setColor(i: 1, c: qRgb(r: 0,g: 0,b: 0)); // black
156
157 int x = 0, y = 0;
158 uchar *b = outImage->scanLine(0);
159 w = (w+7)/8; // byte width
160
161 while (y < h) { // for all encoded bytes...
162 if (p && p < (buf + readBytes - 3)) { // p = "0x.."
163 if (!isxdigit(p[2]) || !isxdigit(p[3]))
164 return false;
165 *b++ = hex2byte(p: p+2);
166 p += 2;
167 if (++x == w && ++y < h) {
168 b = outImage->scanLine(y);
169 x = 0;
170 }
171 p = strstr(haystack: p, needle: "0x");
172 } else { // read another line
173 if ((readBytes = device->readLine(data: buf,maxlen: buflen)) <= 0) // EOF ==> truncated image
174 break;
175 buf[readBytes] = '\0';
176 p = strstr(haystack: buf, needle: "0x");
177 }
178 }
179
180 return true;
181}
182
183static bool read_xbm_image(QIODevice *device, QImage *outImage)
184{
185 int w = 0, h = 0;
186 if (!read_xbm_header(device, w, h))
187 return false;
188 return read_xbm_body(device, w, h, outImage);
189}
190
191static bool write_xbm_image(const QImage &sourceImage, QIODevice *device, const QString &fileName)
192{
193 QImage image = sourceImage;
194 int w = image.width();
195 int h = image.height();
196 int i;
197 QString s = fileName; // get file base name
198 int msize = s.length() + 100;
199 char *buf = new char[msize];
200
201 qsnprintf(str: buf, n: msize, fmt: "#define %s_width %d\n", s.toUtf8().data(), w);
202 device->write(data: buf, len: qstrlen(str: buf));
203 qsnprintf(str: buf, n: msize, fmt: "#define %s_height %d\n", s.toUtf8().data(), h);
204 device->write(data: buf, len: qstrlen(str: buf));
205 qsnprintf(str: buf, n: msize, fmt: "static char %s_bits[] = {\n ", s.toUtf8().data());
206 device->write(data: buf, len: qstrlen(str: buf));
207
208 if (image.format() != QImage::Format_MonoLSB)
209 image = image.convertToFormat(f: QImage::Format_MonoLSB);
210
211 bool invert = qGray(rgb: image.color(i: 0)) < qGray(rgb: image.color(i: 1));
212 char hexrep[16];
213 for (i=0; i<10; i++)
214 hexrep[i] = '0' + i;
215 for (i=10; i<16; i++)
216 hexrep[i] = 'a' -10 + i;
217 if (invert) {
218 char t;
219 for (i=0; i<8; i++) {
220 t = hexrep[15-i];
221 hexrep[15-i] = hexrep[i];
222 hexrep[i] = t;
223 }
224 }
225 int bcnt = 0;
226 char *p = buf;
227 int bpl = (w+7)/8;
228 for (int y = 0; y < h; ++y) {
229 const uchar *b = image.constScanLine(y);
230 for (i = 0; i < bpl; ++i) {
231 *p++ = '0'; *p++ = 'x';
232 *p++ = hexrep[*b >> 4];
233 *p++ = hexrep[*b++ & 0xf];
234
235 if (i < bpl - 1 || y < h - 1) {
236 *p++ = ',';
237 if (++bcnt > 14) {
238 *p++ = '\n';
239 *p++ = ' ';
240 *p = '\0';
241 if ((int)qstrlen(str: buf) != device->write(data: buf, len: qstrlen(str: buf))) {
242 delete [] buf;
243 return false;
244 }
245 p = buf;
246 bcnt = 0;
247 }
248 }
249 }
250 }
251#ifdef Q_CC_MSVC
252 strcpy_s(p, sizeof(" };\n"), " };\n");
253#else
254 strcpy(dest: p, src: " };\n");
255#endif
256 if ((int)qstrlen(str: buf) != device->write(data: buf, len: qstrlen(str: buf))) {
257 delete [] buf;
258 return false;
259 }
260
261 delete [] buf;
262 return true;
263}
264
265QXbmHandler::QXbmHandler()
266 : state(Ready)
267{
268}
269
270bool QXbmHandler::readHeader()
271{
272 state = Error;
273 if (!read_xbm_header(device: device(), w&: width, h&: height))
274 return false;
275 state = ReadHeader;
276 return true;
277}
278
279bool QXbmHandler::canRead() const
280{
281 if (state == Ready && !canRead(device: device()))
282 return false;
283
284 if (state != Error) {
285 setFormat("xbm");
286 return true;
287 }
288
289 return false;
290}
291
292bool QXbmHandler::canRead(QIODevice *device)
293{
294 QImage image;
295
296 // it's impossible to tell whether we can load an XBM or not when
297 // it's from a sequential device, as the only way to do it is to
298 // attempt to parse the whole image.
299 if (device->isSequential())
300 return false;
301
302 qint64 oldPos = device->pos();
303 bool success = read_xbm_image(device, outImage: &image);
304 device->seek(pos: oldPos);
305
306 return success;
307}
308
309bool QXbmHandler::read(QImage *image)
310{
311 if (state == Error)
312 return false;
313
314 if (state == Ready && !readHeader()) {
315 state = Error;
316 return false;
317 }
318
319 if (!read_xbm_body(device: device(), w: width, h: height, outImage: image)) {
320 state = Error;
321 return false;
322 }
323
324 state = Ready;
325 return true;
326}
327
328bool QXbmHandler::write(const QImage &image)
329{
330 return write_xbm_image(sourceImage: image, device: device(), fileName);
331}
332
333bool QXbmHandler::supportsOption(ImageOption option) const
334{
335 return option == Name
336 || option == Size
337 || option == ImageFormat;
338}
339
340QVariant QXbmHandler::option(ImageOption option) const
341{
342 if (option == Name) {
343 return fileName;
344 } else if (option == Size) {
345 if (state == Error)
346 return QVariant();
347 if (state == Ready && !const_cast<QXbmHandler*>(this)->readHeader())
348 return QVariant();
349 return QSize(width, height);
350 } else if (option == ImageFormat) {
351 return QImage::Format_MonoLSB;
352 }
353 return QVariant();
354}
355
356void QXbmHandler::setOption(ImageOption option, const QVariant &value)
357{
358 if (option == Name)
359 fileName = value.toString();
360}
361
362QT_END_NAMESPACE
363
364#endif // QT_NO_IMAGEFORMAT_XBM
365

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