2 Gwenview: an image viewer
3 Copyright 2007 Aurélien Gâteau <aurelien.gateau@free.fr>
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include <QConicalGradient>
27 #include <kio/netaccess.h>
28 #include <qtest_kde.h>
31 #include "../lib/abstractimageoperation.h"
32 #include "../lib/document/abstractdocumenteditor.h"
33 #include "../lib/document/documentfactory.h"
34 #include "../lib/imagemetainfomodel.h"
35 #include "../lib/imageutils.h"
36 #include "testutils.h"
38 #include <exiv2/exif.hpp>
40 #include "documenttest.moc"
42 QTEST_KDEMAIN( DocumentTest
, GUI
)
44 using namespace Gwenview
;
47 static void waitUntilMetaInfoLoaded(Document::Ptr doc
) {
48 while (doc
->loadingState() < Document::MetaInfoLoaded
) {
54 void DocumentTest::initTestCase() {
55 qRegisterMetaType
<KUrl
>("KUrl");
59 void DocumentTest::init() {
60 DocumentFactory::instance()->clearCache();
64 void DocumentTest::testLoad() {
65 QFETCH(QString
, fileName
);
66 QFETCH(QByteArray
, expectedFormat
);
67 QFETCH(int, expectedKindInt
);
68 QFETCH(bool, expectedIsAnimated
);
69 QFETCH(QImage
, expectedImage
);
70 MimeTypeUtils::Kind expectedKind
= MimeTypeUtils::Kind(expectedKindInt
);
72 KUrl url
= urlForTestFile(fileName
);
73 QVERIFY2(!expectedImage
.isNull(), "Could not load test image");
75 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
76 QSignalSpy
spy(doc
.data(), SIGNAL(isAnimatedUpdated()));
78 doc
->waitUntilLoaded();
79 QCOMPARE(doc
->loadingState(), Document::Loaded
);
81 QCOMPARE(expectedKind
, doc
->kind());
82 QCOMPARE(expectedIsAnimated
, doc
->isAnimated());
83 QCOMPARE(spy
.count(), doc
->isAnimated() ? 1 : 0);
84 if (doc
->kind() == MimeTypeUtils::KIND_RASTER_IMAGE
) {
85 QCOMPARE(expectedImage
, doc
->image());
86 QCOMPARE(expectedFormat
, doc
->format());
90 #define NEW_ROW(fileName, format, kind, isAnimated) QTest::newRow(fileName) << fileName << QByteArray(format) << int(kind) << isAnimated << QImage(pathForTestFile(fileName))
91 void DocumentTest::testLoad_data() {
92 QTest::addColumn
<QString
>("fileName");
93 QTest::addColumn
<QByteArray
>("expectedFormat");
94 QTest::addColumn
<int>("expectedKindInt");
95 QTest::addColumn
<bool>("expectedIsAnimated");
96 QTest::addColumn
<QImage
>("expectedImage");
99 "png", MimeTypeUtils::KIND_RASTER_IMAGE
, false);
100 NEW_ROW("160216_no_size_before_decoding.eps",
101 "eps", MimeTypeUtils::KIND_RASTER_IMAGE
, false);
102 NEW_ROW("160382_corrupted.jpeg",
103 "jpeg", MimeTypeUtils::KIND_RASTER_IMAGE
, false);
105 "", MimeTypeUtils::KIND_SVG_IMAGE
, false);
108 "png", MimeTypeUtils::KIND_RASTER_IMAGE
, false);
110 "jpeg", MimeTypeUtils::KIND_RASTER_IMAGE
, false);
111 NEW_ROW("4frames.gif",
112 "gif", MimeTypeUtils::KIND_RASTER_IMAGE
, true);
116 void DocumentTest::testLoadTwoPasses() {
117 KUrl url
= urlForTestFile("test.png");
119 bool ok
= image
.load(url
.path());
120 QVERIFY2(ok
, "Could not load 'test.png'");
121 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
122 waitUntilMetaInfoLoaded(doc
);
123 QVERIFY2(doc
->image().isNull(), "Image shouldn't have been loaded at this time");
124 QCOMPARE(doc
->format().data(), "png");
125 doc
->loadFullImage();
126 doc
->waitUntilLoaded();
127 QCOMPARE(image
, doc
->image());
130 void DocumentTest::testLoadEmpty() {
131 KUrl url
= urlForTestFile("empty.png");
132 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
133 while (doc
->loadingState() == Document::Loading
) {
136 QCOMPARE(doc
->loadingState(), Document::LoadingFailed
);
139 #define NEW_ROW(fileName) QTest::newRow(fileName) << fileName
140 void DocumentTest::testLoadDownSampled_data() {
141 QTest::addColumn
<QString
>("fileName");
143 NEW_ROW("orient6.jpg");
144 NEW_ROW("1x10k.jpg");
148 void DocumentTest::testLoadDownSampled() {
149 // Note: for now we only support down sampling on jpeg, do not use test.png
151 QFETCH(QString
, fileName
);
152 KUrl url
= urlForTestFile(fileName
);
154 bool ok
= image
.load(url
.path());
155 QVERIFY2(ok
, "Could not load test image");
156 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
158 QSignalSpy
downSampledImageReadySpy(doc
.data(), SIGNAL(downSampledImageReady()));
159 QSignalSpy
loadingFailedSpy(doc
.data(), SIGNAL(loadingFailed(const KUrl
&)));
160 QSignalSpy
loadedSpy(doc
.data(), SIGNAL(loaded(const KUrl
&)));
161 bool ready
= doc
->prepareDownSampledImageForZoom(0.2);
162 QVERIFY2(!ready
, "There should not be a down sampled image at this point");
164 while (downSampledImageReadySpy
.count() == 0 && loadingFailedSpy
.count() == 0 && loadedSpy
.count() == 0) {
167 QImage downSampledImage
= doc
->downSampledImageForZoom(0.2);
168 QVERIFY2(!downSampledImage
.isNull(), "Down sampled image should not be null");
170 QCOMPARE(downSampledImage
.size(), doc
->size() / 4);
173 void DocumentTest::testLoadRemote() {
174 QString testTarGzPath
= pathForTestFile("test.tar.gz");
176 url
.setProtocol("tar");
177 url
.setPath(testTarGzPath
+ "/test.png");
179 QVERIFY2(KIO::NetAccess::exists(url
, KIO::NetAccess::SourceSide
, 0), "test archive not found");
181 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
182 doc
->loadFullImage();
183 doc
->waitUntilLoaded();
184 QImage image
= doc
->image();
185 QCOMPARE(image
.width(), 300);
186 QCOMPARE(image
.height(), 200);
189 void DocumentTest::testLoadAnimated() {
190 KUrl srcUrl
= urlForTestFile("40frames.gif");
191 Document::Ptr doc
= DocumentFactory::instance()->load(srcUrl
);
192 QSignalSpy
spy(doc
.data(), SIGNAL(imageRectUpdated(const QRect
&)));
193 doc
->loadFullImage();
194 doc
->waitUntilLoaded();
195 QVERIFY(doc
->isAnimated());
197 // Test we receive only one imageRectUpdated() until animation is started
198 // (the imageRectUpdated() is triggered by the loading of the first image)
200 QCOMPARE(spy
.count(), 1);
202 // Test we now receive some imageRectUpdated()
203 doc
->startAnimation();
205 int count
= spy
.count();
206 doc
->stopAnimation();
207 QVERIFY2(count
> 0, "No imageRectUpdated() signal received");
209 // Test we do not receive imageRectUpdated() anymore
211 QCOMPARE(count
, spy
.count());
213 // Start again, we should receive imageRectUpdated() again
214 doc
->startAnimation();
216 QVERIFY2(spy
.count() > count
, "No imageRectUpdated() signal received after restarting");
219 void DocumentTest::testSaveRemote() {
220 KUrl srcUrl
= urlForTestFile("test.png");
221 Document::Ptr doc
= DocumentFactory::instance()->load(srcUrl
);
222 doc
->loadFullImage();
223 doc
->waitUntilLoaded();
226 dstUrl
.setProtocol("trash");
227 dstUrl
.setPath("/test.png");
228 QVERIFY(!doc
->save(dstUrl
, "png"));
230 if (qgetenv("REMOTE_SFTP_TEST").isEmpty()) {
231 kWarning() << "*** Define the environment variable REMOTE_SFTP_TEST to try saving an image to sftp://localhost/tmp/test.png";
233 dstUrl
.setProtocol("sftp");
234 dstUrl
.setHost("localhost");
235 dstUrl
.setPath("/tmp/test.png");
236 QVERIFY(doc
->save(dstUrl
, "png"));
241 * Check that deleting a document while it is loading does not crash
243 void DocumentTest::testDeleteWhileLoading() {
245 KUrl url
= urlForTestFile("test.png");
247 bool ok
= image
.load(url
.path());
248 QVERIFY2(ok
, "Could not load 'test.png'");
249 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
251 DocumentFactory::instance()->clearCache();
252 // Wait two seconds. If the test fails we will get a segfault while waiting
256 void DocumentTest::testLoadRotated() {
257 KUrl url
= urlForTestFile("orient6.jpg");
259 bool ok
= image
.load(url
.path());
260 QVERIFY2(ok
, "Could not load 'orient6.jpg'");
261 QMatrix matrix
= ImageUtils::transformMatrix(ROT_90
);
262 image
= image
.transformed(matrix
);
264 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
265 doc
->loadFullImage();
266 doc
->waitUntilLoaded();
267 QCOMPARE(image
, doc
->image());
271 * Checks that asking the DocumentFactory the same document twice in a row does
274 void DocumentTest::testMultipleLoads() {
275 KUrl url
= urlForTestFile("orient6.jpg");
276 Document::Ptr doc1
= DocumentFactory::instance()->load(url
);
277 Document::Ptr doc2
= DocumentFactory::instance()->load(url
);
279 QCOMPARE(doc1
.data(), doc2
.data());
282 void DocumentTest::testSave() {
283 KUrl url
= urlForTestFile("orient6.jpg");
284 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
285 doc
->loadFullImage();
287 KUrl destUrl
= urlForTestOutputFile("result.png");
288 QVERIFY(doc
->save(destUrl
, "png"));
289 QCOMPARE(doc
->format().data(), "png");
291 QVERIFY2(doc
->loadingState() == Document::Loaded
,
292 "Document is supposed to finish loading before saving"
295 QImage
image("result.png", "png");
296 QCOMPARE(doc
->image(), image
);
299 void DocumentTest::testLosslessSave() {
300 KUrl url1
= urlForTestFile("orient6.jpg");
301 Document::Ptr doc
= DocumentFactory::instance()->load(url1
);
302 doc
->loadFullImage();
304 KUrl url2
= urlForTestOutputFile("orient1.jpg");
305 QVERIFY(doc
->save(url2
, "jpeg"));
308 QVERIFY(image1
.load(url1
.path()));
311 QVERIFY(image2
.load(url2
.path()));
313 QCOMPARE(image1
, image2
);
316 void DocumentTest::testLosslessRotate() {
317 // Generate test image
318 QImage
image1(200, 96, QImage::Format_RGB32
);
320 QPainter
painter(&image1
);
321 QConicalGradient
gradient(QPointF(100, 48), 100);
322 gradient
.setColorAt(0, Qt::white
);
323 gradient
.setColorAt(1, Qt::blue
);
324 painter
.fillRect(image1
.rect(), gradient
);
327 KUrl url1
= urlForTestOutputFile("lossless1.jpg");
328 QVERIFY(image1
.save(url1
.path(), "jpeg"));
330 // Load it as a Gwenview document
331 Document::Ptr doc
= DocumentFactory::instance()->load(url1
);
332 doc
->loadFullImage();
333 doc
->waitUntilLoaded();
336 QVERIFY(doc
->editor());
337 doc
->editor()->applyTransformation(ROT_90
);
340 KUrl url2
= urlForTestOutputFile("lossless2.jpg");
341 doc
->save(url2
, "jpeg");
343 // Load the saved image
344 doc
= DocumentFactory::instance()->load(url2
);
345 doc
->loadFullImage();
346 doc
->waitUntilLoaded();
348 // Rotate the other way
349 QVERIFY(doc
->editor());
350 doc
->editor()->applyTransformation(ROT_270
);
351 doc
->save(url2
, "jpeg");
353 // Compare the saved images
354 QVERIFY(image1
.load(url1
.path()));
356 QVERIFY(image2
.load(url2
.path()));
358 QCOMPARE(image1
, image2
);
361 void DocumentTest::testModify() {
362 class TestOperation
: public AbstractImageOperation
{
365 QImage
image(10, 10, QImage::Format_ARGB32
);
366 image
.fill(QColor(Qt::white
).rgb());
367 document()->editor()->setImage(image
);
371 KUrl url
= urlForTestFile("orient6.jpg");
372 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
373 doc
->loadFullImage();
374 doc
->waitUntilLoaded();
375 QVERIFY(!doc
->isModified());
377 QVERIFY(doc
->editor());
378 TestOperation
* op
= new TestOperation
;
379 op
->setDocument(doc
);
380 doc
->undoStack()->push(op
);
381 QVERIFY(doc
->isModified());
383 KUrl destUrl
= urlForTestOutputFile("modify.png");
384 QVERIFY(doc
->save(destUrl
, "png"));
386 QVERIFY(!doc
->isModified());
389 void DocumentTest::testMetaInfoJpeg() {
390 KUrl url
= urlForTestFile("orient6.jpg");
391 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
393 // We cleared the cache, so the document should not be loaded
394 Q_ASSERT(doc
->loadingState() == Document::Loading
);
396 // Wait until we receive the metaInfoUpdated() signal
397 QSignalSpy
metaInfoUpdatedSpy(doc
.data(), SIGNAL(metaInfoUpdated()));
398 while (metaInfoUpdatedSpy
.count() == 0) {
402 // Extract an exif key
403 QString value
= doc
->metaInfo()->getValueForKey("Exif.Image.Make");
404 QCOMPARE(value
, QString::fromUtf8("Canon"));
408 void DocumentTest::testMetaInfoBmp() {
409 KUrl url
= urlForTestOutputFile("metadata.bmp");
410 const int width
= 200;
411 const int height
= 100;
412 QImage
image(width
, height
, QImage::Format_ARGB32
);
413 image
.fill(Qt::black
);
414 image
.save(url
.path(), "BMP");
416 Document::Ptr doc
= DocumentFactory::instance()->load(url
);
417 QSignalSpy
metaInfoUpdatedSpy(doc
.data(), SIGNAL(metaInfoUpdated()));
418 waitUntilMetaInfoLoaded(doc
);
420 Q_ASSERT(metaInfoUpdatedSpy
.count() >= 1);
422 QString value
= doc
->metaInfo()->getValueForKey("General.ImageSize");
423 QString expectedValue
= QString("%1x%2").arg(width
).arg(height
);
424 QCOMPARE(value
, expectedValue
);