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 test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31
32
33#include <QDebug>
34#include <QFile>
35#include <QImage>
36#include <QImageReader>
37#include <QImageWriter>
38#include <QPainter>
39#include <QSet>
40#include <QTemporaryDir>
41
42#ifdef Q_OS_UNIX // for geteuid()
43# include <sys/types.h>
44# include <unistd.h>
45#endif
46
47#include <algorithm>
48
49typedef QMap<QString, QString> QStringMap;
50typedef QVector<int> QIntList;
51Q_DECLARE_METATYPE(QImageWriter::ImageWriterError)
52Q_DECLARE_METATYPE(QImage::Format)
53
54class tst_QImageWriter : public QObject
55{
56 Q_OBJECT
57
58public slots:
59 void initTestCase();
60
61private slots:
62 void getSetCheck();
63 void writeImage_data();
64 void writeImage();
65 void writeImage2_data();
66 void writeImage2();
67 void supportedFormats();
68 void supportedMimeTypes();
69
70 void writeToInvalidDevice();
71 void testCanWrite();
72
73 void supportsOption_data();
74 void supportsOption();
75
76 void saveWithNoFormat_data();
77 void saveWithNoFormat();
78
79 void saveToTemporaryFile();
80
81 void writeEmpty();
82
83private:
84 QTemporaryDir m_temporaryDir;
85 QString prefix;
86 QString writePrefix;
87};
88
89// helper to skip an autotest when the given image format is not supported
90#define SKIP_IF_UNSUPPORTED(format) do { \
91 if (!QByteArray(format).isEmpty() && !QImageReader::supportedImageFormats().contains(format)) \
92 QSKIP('"' + QByteArray(format) + "\" images are not supported"); \
93} while (0)
94
95static void initializePadding(QImage *image)
96{
97 int effectiveBytesPerLine = (image->width() * image->depth() + 7) / 8;
98 int paddingBytes = image->bytesPerLine() - effectiveBytesPerLine;
99 if (paddingBytes == 0)
100 return;
101 for (int y = 0; y < image->height(); ++y) {
102 memset(s: image->scanLine(y) + effectiveBytesPerLine, c: 0, n: paddingBytes);
103 }
104}
105
106void tst_QImageWriter::initTestCase()
107{
108 QVERIFY(m_temporaryDir.isValid());
109 prefix = QFINDTESTDATA("images/");
110 if (prefix.isEmpty())
111 QFAIL("Can't find images directory!");
112 writePrefix = m_temporaryDir.path() + QLatin1Char('/');
113}
114
115// Testing get/set functions
116void tst_QImageWriter::getSetCheck()
117{
118 QImageWriter obj1;
119 // QIODevice * QImageWriter::device()
120 // void QImageWriter::setDevice(QIODevice *)
121 QFile *var1 = new QFile;
122 obj1.setDevice(var1);
123
124 QCOMPARE((QIODevice *) var1, obj1.device());
125 // The class should possibly handle a 0-pointer as a device, since
126 // there is a default contructor, so it's "handling" a 0 device by default.
127 // For example: QMovie::setDevice(0) works just fine
128 obj1.setDevice((QIODevice *)0);
129 QCOMPARE((QIODevice *) 0, obj1.device());
130 delete var1;
131
132 // int QImageWriter::quality()
133 // void QImageWriter::setQuality(int)
134 obj1.setQuality(0);
135 QCOMPARE(0, obj1.quality());
136 obj1.setQuality(INT_MIN);
137 QCOMPARE(INT_MIN, obj1.quality());
138 obj1.setQuality(INT_MAX);
139 QCOMPARE(INT_MAX, obj1.quality());
140
141 // int QImageWriter::compression()
142 // void QImageWriter::setCompression(int)
143 obj1.setCompression(0);
144 QCOMPARE(0, obj1.compression());
145 obj1.setCompression(INT_MIN);
146 QCOMPARE(INT_MIN, obj1.compression());
147 obj1.setCompression(INT_MAX);
148 QCOMPARE(INT_MAX, obj1.compression());
149
150 // float QImageWriter::gamma()
151 // void QImageWriter::setGamma(float)
152 obj1.setGamma(0.0f);
153 QCOMPARE(0.0f, obj1.gamma());
154 obj1.setGamma(1.1f);
155 QCOMPARE(1.1f, obj1.gamma());
156}
157
158void tst_QImageWriter::writeImage_data()
159{
160 QTest::addColumn<QString>(name: "fileName");
161 QTest::addColumn<bool>(name: "lossy");
162 QTest::addColumn<QByteArray>(name: "format");
163
164 QTest::newRow(dataTag: "BMP: colorful") << QString("colorful.bmp") << false << QByteArray("bmp");
165 QTest::newRow(dataTag: "BMP: font") << QString("font.bmp") << false << QByteArray("bmp");
166 QTest::newRow(dataTag: "XPM: marble") << QString("marble.xpm") << false << QByteArray("xpm");
167 QTest::newRow(dataTag: "PNG: kollada") << QString("kollada.png") << false << QByteArray("png");
168 QTest::newRow(dataTag: "PPM: teapot") << QString("teapot.ppm") << false << QByteArray("ppm");
169 QTest::newRow(dataTag: "PBM: ship63") << QString("ship63.pbm") << true << QByteArray("pbm");
170 QTest::newRow(dataTag: "XBM: gnus") << QString("gnus.xbm") << false << QByteArray("xbm");
171 QTest::newRow(dataTag: "JPEG: beavis") << QString("beavis.jpg") << true << QByteArray("jpeg");
172 QTest::newRow(dataTag: "ICO: App") << QString("App.ico") << true << QByteArray("ico");
173}
174
175void tst_QImageWriter::writeImage()
176{
177 QFETCH(QString, fileName);
178 QFETCH(bool, lossy);
179 QFETCH(QByteArray, format);
180
181 SKIP_IF_UNSUPPORTED(format);
182
183 QImage image;
184 {
185 QImageReader reader(prefix + fileName);
186 image = reader.read();
187 QVERIFY2(!image.isNull(), qPrintable(reader.errorString()));
188 }
189 {
190 QImageWriter writer(writePrefix + "gen-" + fileName, format);
191 QVERIFY(writer.write(image));
192 }
193
194 {
195 bool skip = false;
196#if defined(Q_OS_UNIX)
197 if (::geteuid() == 0)
198 skip = true;
199#endif
200 if (!skip) {
201 // Shouldn't be able to write to read-only file
202 QFile sourceFile(writePrefix + "gen-" + fileName);
203 QFile::Permissions permissions = sourceFile.permissions();
204 QVERIFY(sourceFile.setPermissions(QFile::ReadOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther));
205
206 QImageWriter writer(writePrefix + "gen-" + fileName, format);
207 QVERIFY(!writer.write(image));
208
209 QVERIFY(sourceFile.setPermissions(permissions));
210 }
211 }
212
213 QImage image2;
214 {
215 QImageReader reader(writePrefix + "gen-" + fileName);
216 image2 = reader.read();
217 QVERIFY(!image2.isNull());
218 }
219 if (!lossy) {
220 QCOMPARE(image, image2);
221 } else {
222 QCOMPARE(image.format(), image2.format());
223 QCOMPARE(image.depth(), image2.depth());
224 }
225}
226
227void tst_QImageWriter::writeImage2_data()
228{
229 QTest::addColumn<QString>(name: "fileName");
230 QTest::addColumn<QByteArray>(name: "format");
231 QTest::addColumn<QImage>(name: "image");
232
233 static const QLatin1String formats[] = {
234 QLatin1String("bmp"),
235 QLatin1String("xpm"),
236 QLatin1String("png"),
237 QLatin1String("ppm"),
238 QLatin1String("ico"),
239 // QLatin1String("jpeg"),
240 };
241
242 QImage image0(70, 70, QImage::Format_RGB32);
243 image0.fill(pixel: QColor(Qt::red).rgb());
244
245 QImage::Format imgFormat = QImage::Format_Mono;
246 while (imgFormat != QImage::NImageFormats) {
247 QImage image = image0.convertToFormat(f: imgFormat);
248 initializePadding(image: &image);
249 for (QLatin1String format : formats) {
250 const QString fileName = QLatin1String("solidcolor_")
251 + QString::number(imgFormat) + QLatin1Char('.') + format;
252 QTest::newRow(dataTag: fileName.toLatin1()) << writePrefix + fileName
253 << QByteArray(format.data(), format.size())
254 << image;
255 }
256 imgFormat = QImage::Format(int(imgFormat) + 1);
257 }
258}
259
260/*
261 Workaround for the equality operator for indexed formats
262 (which fails if the colortables are different).
263
264 Images must have the same format and size.
265*/
266static bool equalImageContents(const QImage &image1, const QImage &image2)
267{
268 switch (image1.format()) {
269 case QImage::Format_Mono:
270 case QImage::Format_Indexed8:
271 for (int y = 0; y < image1.height(); ++y)
272 for (int x = 0; x < image1.width(); ++x)
273 if (image1.pixel(x, y) != image2.pixel(x, y))
274 return false;
275 return true;
276 default:
277 return (image1 == image2);
278 }
279}
280
281void tst_QImageWriter::writeImage2()
282{
283 QFETCH(QString, fileName);
284 QFETCH(QByteArray, format);
285 QFETCH(QImage, image);
286
287 //we reduce the scope of writer so that it closes the associated file
288 // and QFile::remove can actually work
289 {
290 QImageWriter writer(fileName, format);
291 QVERIFY(writer.write(image));
292 }
293
294 QImage written;
295
296 //we reduce the scope of reader so that it closes the associated file
297 // and QFile::remove can actually work
298 {
299 QImageReader reader(fileName, format);
300 QVERIFY(reader.read(&written));
301 }
302
303 written = written.convertToFormat(f: image.format());
304 if (!equalImageContents(image1: written, image2: image)) {
305 qDebug() << "image" << image.format() << image.width()
306 << image.height() << image.depth()
307 << image.pixelColor(x: 0, y: 0);
308 qDebug() << "written" << written.format() << written.width()
309 << written.height() << written.depth()
310 << written.pixelColor(x: 0, y: 0);
311 }
312 QVERIFY(equalImageContents(written, image));
313
314 QVERIFY(QFile::remove(fileName));
315}
316
317namespace {
318// C++98-library version of C++11 std::is_sorted
319template <typename C>
320bool is_sorted(const C &c)
321{
322 return std::adjacent_find(c.begin(), c.end(), std::greater_equal<typename C::value_type>()) == c.end();
323}
324}
325
326void tst_QImageWriter::supportedFormats()
327{
328 QList<QByteArray> formats = QImageWriter::supportedImageFormats();
329
330 // check that the list is sorted
331 QVERIFY(is_sorted(formats));
332
333 // check that the list does not contain duplicates
334 QVERIFY(std::unique(formats.begin(), formats.end()) == formats.end());
335}
336
337void tst_QImageWriter::supportedMimeTypes()
338{
339 QList<QByteArray> mimeTypes = QImageWriter::supportedMimeTypes();
340
341 // check that the list is sorted
342 QVERIFY(is_sorted(mimeTypes));
343
344 // check the list as a minimum contains image/bmp
345 QVERIFY(mimeTypes.contains("image/bmp"));
346
347 // check that the list does not contain duplicates
348 QVERIFY(std::unique(mimeTypes.begin(), mimeTypes.end()) == mimeTypes.end());
349}
350
351void tst_QImageWriter::writeToInvalidDevice()
352{
353 QLatin1String fileName("/these/directories/do/not/exist/001.png");
354 {
355 QImageWriter writer(fileName);
356 QVERIFY(!writer.canWrite());
357 QCOMPARE(writer.error(), QImageWriter::DeviceError);
358 }
359 {
360 QImageWriter writer(fileName);
361 writer.setFormat("png");
362 QVERIFY(!writer.canWrite());
363 QCOMPARE(writer.error(), QImageWriter::DeviceError);
364 }
365 {
366 QImageWriter writer(fileName);
367 QImage im(10, 10, QImage::Format_ARGB32);
368 QVERIFY(!writer.write(im));
369 QCOMPARE(writer.error(), QImageWriter::DeviceError);
370 }
371 {
372 QImageWriter writer(fileName);
373 writer.setFormat("png");
374 QImage im(10, 10, QImage::Format_ARGB32);
375 QVERIFY(!writer.write(im));
376 QCOMPARE(writer.error(), QImageWriter::DeviceError);
377 }
378}
379
380void tst_QImageWriter::testCanWrite()
381{
382 {
383 // device is not set
384 QImageWriter writer;
385 QVERIFY(!writer.canWrite());
386 QCOMPARE(writer.error(), QImageWriter::DeviceError);
387 }
388
389 {
390 // check if canWrite won't leave an empty file
391 QTemporaryDir dir;
392 QVERIFY2(dir.isValid(), qPrintable(dir.errorString()));
393 QString fileName(dir.path() + QLatin1String("/001.garble"));
394 QVERIFY(!QFileInfo(fileName).exists());
395 QImageWriter writer(fileName);
396 QVERIFY(!writer.canWrite());
397 QCOMPARE(writer.error(), QImageWriter::UnsupportedFormatError);
398 QVERIFY(!QFileInfo(fileName).exists());
399 }
400}
401
402void tst_QImageWriter::supportsOption_data()
403{
404 QTest::addColumn<QString>(name: "fileName");
405 QTest::addColumn<QIntList>(name: "options");
406
407 QTest::newRow(dataTag: "png") << QString("gen-black.png")
408 << (QIntList() << QImageIOHandler::Gamma
409 << QImageIOHandler::Description
410 << QImageIOHandler::Quality
411 << QImageIOHandler::CompressionRatio
412 << QImageIOHandler::Size
413 << QImageIOHandler::ScaledSize);
414}
415
416void tst_QImageWriter::supportsOption()
417{
418 SKIP_IF_UNSUPPORTED(QTest::currentDataTag());
419
420 QFETCH(QString, fileName);
421 QFETCH(QIntList, options);
422
423 static Q_CONSTEXPR QImageIOHandler::ImageOption allOptions[] = {
424 QImageIOHandler::Size,
425 QImageIOHandler::ClipRect,
426 QImageIOHandler::Description,
427 QImageIOHandler::ScaledClipRect,
428 QImageIOHandler::ScaledSize,
429 QImageIOHandler::CompressionRatio,
430 QImageIOHandler::Gamma,
431 QImageIOHandler::Quality,
432 QImageIOHandler::Name,
433 QImageIOHandler::SubType,
434 QImageIOHandler::IncrementalReading,
435 QImageIOHandler::Endianness,
436 QImageIOHandler::Animation,
437 QImageIOHandler::BackgroundColor,
438 };
439
440 QImageWriter writer(writePrefix + fileName);
441 for (auto option : allOptions) {
442 QCOMPARE(writer.supportsOption(option), options.contains(option));
443 }
444}
445
446void tst_QImageWriter::saveWithNoFormat_data()
447{
448 QTest::addColumn<QString>(name: "fileName");
449 QTest::addColumn<QByteArray>(name: "format");
450 QTest::addColumn<QImageWriter::ImageWriterError>(name: "error");
451
452 QTest::newRow(dataTag: "garble") << writePrefix + QString("gen-out.garble") << QByteArray("jpeg") << QImageWriter::UnsupportedFormatError;
453 QTest::newRow(dataTag: "bmp") << writePrefix + QString("gen-out.bmp") << QByteArray("bmp") << QImageWriter::ImageWriterError(0);
454 QTest::newRow(dataTag: "xbm") << writePrefix + QString("gen-out.xbm") << QByteArray("xbm") << QImageWriter::ImageWriterError(0);
455 QTest::newRow(dataTag: "xpm") << writePrefix + QString("gen-out.xpm") << QByteArray("xpm") << QImageWriter::ImageWriterError(0);
456 QTest::newRow(dataTag: "png") << writePrefix + QString("gen-out.png") << QByteArray("png") << QImageWriter::ImageWriterError(0);
457 QTest::newRow(dataTag: "ppm") << writePrefix + QString("gen-out.ppm") << QByteArray("ppm") << QImageWriter::ImageWriterError(0);
458 QTest::newRow(dataTag: "pbm") << writePrefix + QString("gen-out.pbm") << QByteArray("pbm") << QImageWriter::ImageWriterError(0);
459}
460
461void tst_QImageWriter::saveWithNoFormat()
462{
463 QFETCH(QString, fileName);
464 QFETCH(QByteArray, format);
465 QFETCH(QImageWriter::ImageWriterError, error);
466
467 SKIP_IF_UNSUPPORTED(format);
468
469 QImage niceImage(64, 64, QImage::Format_ARGB32);
470 memset(s: niceImage.bits(), c: 0, n: niceImage.sizeInBytes());
471
472 QImageWriter writer(fileName /* , 0 - no format! */);
473 if (error != 0) {
474 QVERIFY(!writer.write(niceImage));
475 QCOMPARE(writer.error(), error);
476 return;
477 }
478
479 QVERIFY2(writer.write(niceImage), qPrintable(writer.errorString()));
480
481 QImageReader reader(fileName);
482 QCOMPARE(reader.format(), format);
483
484 QVERIFY(reader.canRead());
485
486 QImage outImage = reader.read();
487 QVERIFY2(!outImage.isNull(), qPrintable(reader.errorString()));
488}
489
490void tst_QImageWriter::saveToTemporaryFile()
491{
492 QImage image(prefix + "kollada.png");
493 QVERIFY(!image.isNull());
494
495 {
496 // 1) Via QImageWriter's API, with a standard temp file name
497 QTemporaryFile file;
498 QVERIFY2(file.open(), qPrintable(file.errorString()));
499 QImageWriter writer(&file, "PNG");
500 if (writer.canWrite())
501 QVERIFY(writer.write(image));
502 else
503 qWarning() << file.errorString();
504 QCOMPARE(QImage(writer.fileName()), image);
505 }
506 {
507 // 2) Via QImage's API, with a standard temp file name
508 QTemporaryFile file;
509 QVERIFY2(file.open(), qPrintable(file.errorString()));
510 QVERIFY(image.save(&file, "PNG"));
511 file.reset();
512 QImage tmp;
513 QVERIFY(tmp.load(&file, "PNG"));
514 QCOMPARE(tmp, image);
515 }
516 {
517 // 3) Via QImageWriter's API, with a named temp file
518 QTemporaryFile file(writePrefix + QLatin1String("tempXXXXXX"));
519 QVERIFY2(file.open(), qPrintable(file.errorString()));
520 QImageWriter writer(&file, "PNG");
521 QVERIFY(writer.write(image));
522 QCOMPARE(QImage(writer.fileName()), image);
523 }
524 {
525 // 4) Via QImage's API, with a named temp file
526 QTemporaryFile file(writePrefix + QLatin1String("tempXXXXXX"));
527 QVERIFY2(file.open(), qPrintable(file.errorString()));
528 QVERIFY(image.save(&file, "PNG"));
529 file.reset();
530 QImage tmp;
531 QVERIFY(tmp.load(&file, "PNG"));
532 QCOMPARE(tmp, image);
533 }
534}
535
536void tst_QImageWriter::writeEmpty()
537{
538 // check writing a null QImage errors gracefully
539 QTemporaryDir dir;
540 QVERIFY2(dir.isValid(), qPrintable(dir.errorString()));
541 QString fileName(dir.path() + QLatin1String("/testimage.bmp"));
542 QVERIFY(!QFileInfo(fileName).exists());
543 QImageWriter writer(fileName);
544 QVERIFY(!writer.write(QImage()));
545 QCOMPARE(writer.error(), QImageWriter::InvalidImageError);
546 QVERIFY(!QFileInfo(fileName).exists());
547}
548
549QTEST_MAIN(tst_QImageWriter)
550#include "tst_qimagewriter.moc"
551

source code of qtbase/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp