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 <qloggingcategory.h>
48#include <qvariant.h>
49
50#include <stdio.h>
51#include <ctype.h>
52
53QT_BEGIN_NAMESPACE
54
55Q_DECLARE_LOGGING_CATEGORY(lcImageIo)
56
57/*****************************************************************************
58 X bitmap image read/write functions
59 *****************************************************************************/
60
61static inline int hex2byte(char *p)
62{
63 return ((isdigit((uchar) *p) ? *p - '0' : toupper((uchar) *p) - 'A' + 10) << 4) |
64 (isdigit((uchar) *(p+1)) ? *(p+1) - '0' : toupper((uchar) *(p+1)) - 'A' + 10);
65}
66
67static bool read_xbm_header(QIODevice *device, int& w, int& h)
68{
69 const int buflen = 300;
70 const int maxlen = 4096;
71 char buf[buflen + 1];
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(buf, 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 auto parseDefine = [] (const char *buf, int len) -> int {
94 auto isAsciiLetterOrNumber = [] (char ch) -> bool {
95 return (ch >= '0' && ch <= '9') ||
96 (ch >= 'A' && ch <= 'Z') ||
97 (ch >= 'a' && ch <= 'z') ||
98 ch == '_' || ch == '.';
99 };
100 auto isAsciiSpace = [] (char ch) -> bool {
101 return ch == ' ' || ch == '\t';
102 };
103 const char define[] = "#define";
104 constexpr size_t defineLen = sizeof(define) - 1;
105 if (strncmp(buf, define, defineLen) != 0)
106 return 0;
107 int index = defineLen;
108 while (buf[index] && isAsciiSpace(buf[index]))
109 ++index;
110 while (buf[index] && isAsciiLetterOrNumber(buf[index]))
111 ++index;
112 while (buf[index] && isAsciiSpace(buf[index]))
113 ++index;
114
115 return QByteArray(buf + index, len - index).toInt();
116 };
117
118
119 // "#define .._width <num>"
120 w = parseDefine(buf, readBytes - 1);
121
122 readBytes = device->readLine(buf, buflen);
123 // "#define .._height <num>"
124 h = parseDefine(buf, readBytes - 1);
125
126 // format error
127 if (w <= 0 || w > 32767 || h <= 0 || h > 32767)
128 return false;
129
130 return true;
131}
132
133static bool read_xbm_body(QIODevice *device, int w, int h, QImage *outImage)
134{
135 const int buflen = 300;
136 char buf[buflen + 1];
137
138 qint64 readBytes = 0;
139
140 char *p;
141
142 // scan for database
143 do {
144 if ((readBytes = device->readLine(buf, buflen)) <= 0) {
145 // end of file
146 return false;
147 }
148
149 buf[readBytes] = '\0';
150 p = strstr(buf, "0x");
151 } while (!p);
152
153 if (!QImageIOHandler::allocateImage(QSize(w, h), QImage::Format_MonoLSB, outImage))
154 return false;
155
156 outImage->fill(Qt::color0); // in case the image data does not cover the full image
157
158 outImage->setColorCount(2);
159 outImage->setColor(0, qRgb(255,255,255)); // white
160 outImage->setColor(1, qRgb(0,0,0)); // black
161
162 int x = 0, y = 0;
163 uchar *b = outImage->scanLine(0);
164 w = (w+7)/8; // byte width
165
166 while (y < h) { // for all encoded bytes...
167 if (p && p < (buf + readBytes - 3)) { // p = "0x.."
168 if (!isxdigit(p[2]) || !isxdigit(p[3]))
169 return false;
170 *b++ = hex2byte(p+2);
171 p += 2;
172 if (++x == w && ++y < h) {
173 b = outImage->scanLine(y);
174 x = 0;
175 }
176 p = strstr(p, "0x");
177 } else { // read another line
178 if ((readBytes = device->readLine(buf,buflen)) <= 0) // EOF ==> truncated image
179 break;
180 buf[readBytes] = '\0';
181 p = strstr(buf, "0x");
182 }
183 }
184
185 return true;
186}
187
188static bool read_xbm_image(QIODevice *device, QImage *outImage)
189{
190 int w = 0, h = 0;
191 if (!read_xbm_header(device, w, h))
192 return false;
193 return read_xbm_body(device, w, h, outImage);
194}
195
196static bool write_xbm_image(const QImage &sourceImage, QIODevice *device, const QString &fileName)
197{
198 QImage image = sourceImage;
199 int w = image.width();
200 int h = image.height();
201 int i;
202 QString s = fileName; // get file base name
203 int msize = s.length() + 100;
204 char *buf = new char[msize];
205
206 qsnprintf(buf, msize, "#define %s_width %d\n", s.toUtf8().data(), w);
207 device->write(buf, qstrlen(buf));
208 qsnprintf(buf, msize, "#define %s_height %d\n", s.toUtf8().data(), h);
209 device->write(buf, qstrlen(buf));
210 qsnprintf(buf, msize, "static char %s_bits[] = {\n ", s.toUtf8().data());
211 device->write(buf, qstrlen(buf));
212
213 if (image.format() != QImage::Format_MonoLSB)
214 image = image.convertToFormat(QImage::Format_MonoLSB);
215
216 bool invert = qGray(image.color(0)) < qGray(image.color(1));
217 char hexrep[16];
218 for (i=0; i<10; i++)
219 hexrep[i] = '0' + i;
220 for (i=10; i<16; i++)
221 hexrep[i] = 'a' -10 + i;
222 if (invert) {
223 char t;
224 for (i=0; i<8; i++) {
225 t = hexrep[15-i];
226 hexrep[15-i] = hexrep[i];
227 hexrep[i] = t;
228 }
229 }
230 int bcnt = 0;
231 char *p = buf;
232 int bpl = (w+7)/8;
233 for (int y = 0; y < h; ++y) {
234 const uchar *b = image.constScanLine(y);
235 for (i = 0; i < bpl; ++i) {
236 *p++ = '0'; *p++ = 'x';
237 *p++ = hexrep[*b >> 4];
238 *p++ = hexrep[*b++ & 0xf];
239
240 if (i < bpl - 1 || y < h - 1) {
241 *p++ = ',';
242 if (++bcnt > 14) {
243 *p++ = '\n';
244 *p++ = ' ';
245 *p = '\0';
246 if ((int)qstrlen(buf) != device->write(buf, qstrlen(buf))) {
247 delete [] buf;
248 return false;
249 }
250 p = buf;
251 bcnt = 0;
252 }
253 }
254 }
255 }
256#ifdef Q_CC_MSVC
257 strcpy_s(p, sizeof(" };\n"), " };\n");
258#else
259 strcpy(p, " };\n");
260#endif
261 if ((int)qstrlen(buf) != device->write(buf, qstrlen(buf))) {
262 delete [] buf;
263 return false;
264 }
265
266 delete [] buf;
267 return true;
268}
269
270QXbmHandler::QXbmHandler()
271 : state(Ready)
272{
273}
274
275bool QXbmHandler::readHeader()
276{
277 state = Error;
278 if (!read_xbm_header(device(), width, height))
279 return false;
280 state = ReadHeader;
281 return true;
282}
283
284bool QXbmHandler::canRead() const
285{
286 if (state == Ready && !canRead(device()))
287 return false;
288
289 if (state != Error) {
290 setFormat("xbm");
291 return true;
292 }
293
294 return false;
295}
296
297bool QXbmHandler::canRead(QIODevice *device)
298{
299 if (!device) {
300 qCWarning(lcImageIo, "QXbmHandler::canRead() called with no device");
301 return false;
302 }
303
304 // it's impossible to tell whether we can load an XBM or not when
305 // it's from a sequential device, as the only way to do it is to
306 // attempt to parse the whole image.
307 if (device->isSequential())
308 return false;
309
310 QImage image;
311 qint64 oldPos = device->pos();
312 bool success = read_xbm_image(device, &image);
313 device->seek(oldPos);
314
315 return success;
316}
317
318bool QXbmHandler::read(QImage *image)
319{
320 if (state == Error)
321 return false;
322
323 if (state == Ready && !readHeader()) {
324 state = Error;
325 return false;
326 }
327
328 if (!read_xbm_body(device(), width, height, image)) {
329 state = Error;
330 return false;
331 }
332
333 state = Ready;
334 return true;
335}
336
337bool QXbmHandler::write(const QImage &image)
338{
339 return write_xbm_image(image, device(), fileName);
340}
341
342bool QXbmHandler::supportsOption(ImageOption option) const
343{
344 return option == Name
345 || option == Size
346 || option == ImageFormat;
347}
348
349QVariant QXbmHandler::option(ImageOption option) const
350{
351 if (option == Name) {
352 return fileName;
353 } else if (option == Size) {
354 if (state == Error)
355 return QVariant();
356 if (state == Ready && !const_cast<QXbmHandler*>(this)->readHeader())
357 return QVariant();
358 return QSize(width, height);
359 } else if (option == ImageFormat) {
360 return QImage::Format_MonoLSB;
361 }
362 return QVariant();
363}
364
365void QXbmHandler::setOption(ImageOption option, const QVariant &value)
366{
367 if (option == Name)
368 fileName = value.toString();
369}
370
371QT_END_NAMESPACE
372
373#endif // QT_NO_IMAGEFORMAT_XBM
374