Marble

TileCreator.cpp
1// SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
2// SPDX-FileCopyrightText: 2007-2008 Inge Wallin <ingwa@kde.org>
3// SPDX-FileCopyrightText: 2011 Niko Sams <niko.sams@gmail.com>
4//
5// SPDX-License-Identifier: LGPL-2.1-or-later
6
7#include "TileCreator.h"
8
9#include <cmath>
10
11#include <QApplication>
12#include <QDir>
13#include <QImage>
14#include <QList>
15#include <QPainter>
16#include <QRect>
17#include <QSize>
18
19#include "MarbleDebug.h"
20#include "MarbleDirs.h"
21#include "MarbleGlobal.h"
22#include "TileLoaderHelper.h"
23
24namespace Marble
25{
26
27class TileCreatorPrivate
28{
29public:
30 TileCreatorPrivate(TileCreatorSource *source, const QString &dem, const QString &targetDir = QString())
31 : m_dem(dem)
32 , m_targetDir(targetDir)
33 , m_cancelled(false)
34 , m_tileFormat(QStringLiteral("jpg"))
35 , m_resume(false)
36 , m_verify(false)
37 , m_source(source)
38 {
39 if (m_dem == QLatin1StringView("true")) {
40 m_tileQuality = 70;
41 } else {
42 m_tileQuality = 85;
43 }
44 }
45
46 ~TileCreatorPrivate()
47 {
48 delete m_source;
49 }
50
51public:
52 QString m_dem;
53 QString m_targetDir;
54 bool m_cancelled;
55 QString m_tileFormat;
56 int m_tileQuality;
57 bool m_resume;
58 bool m_verify;
59
60 TileCreatorSource *m_source;
61};
62
63class TileCreatorSourceImage : public TileCreatorSource
64{
65public:
66 explicit TileCreatorSourceImage(const QString &sourcePath)
67 : m_sourceImage(QImage(sourcePath))
68 , m_cachedRowNum(-1)
69 {
70 }
71
72 QSize fullImageSize() const override
73 {
74 if (m_sourceImage.size().width() > 21600 || m_sourceImage.height() > 10800) {
75 qDebug("Install map too large!");
76 return {};
77 }
78 return m_sourceImage.size();
79 }
80
81 QImage tile(int n, int m, int maxTileLevel) override
82 {
83 int mmax = TileLoaderHelper::levelToColumn(defaultLevelZeroColumns, maxTileLevel);
84 int nmax = TileLoaderHelper::levelToRow(defaultLevelZeroRows, maxTileLevel);
85
86 int imageHeight = m_sourceImage.height();
87 int imageWidth = m_sourceImage.width();
88
89 // If the image size of the image source does not match the expected
90 // geometry we need to smooth-scale the image in advance to match
91 // the required size
92 bool needsScaling = (imageWidth != 2 * nmax * (int)(c_defaultTileSize) || imageHeight != nmax * (int)(c_defaultTileSize));
93
94 if (needsScaling)
95 mDebug() << "Image Size doesn't match 2*n*TILEWIDTH x n*TILEHEIGHT geometry. Scaling ...";
96
97 int stdImageWidth = 2 * nmax * c_defaultTileSize;
98 if (stdImageWidth == 0)
99 stdImageWidth = 2 * c_defaultTileSize;
100
101 int stdImageHeight = nmax * c_defaultTileSize;
102 if (stdImageWidth != imageWidth) {
103 mDebug()
104 << QStringLiteral("TileCreator::createTiles() The size of the final image will measure %1 x %2 pixels").arg(stdImageWidth).arg(stdImageHeight);
105 }
106
107 QImage row;
108
109 if (m_cachedRowNum == n) {
110 row = m_rowCache;
111
112 } else {
113 QRect sourceRowRect(0, (int)((qreal)(n * imageHeight) / (qreal)(nmax)), imageWidth, (int)((qreal)(imageHeight) / (qreal)(nmax)));
114
115 row = m_sourceImage.copy(sourceRowRect);
116
117 if (needsScaling) {
118 // Pick the current row and smooth scale it
119 // to make it match the expected size
120 QSize destSize(stdImageWidth, c_defaultTileSize);
122 }
123
124 m_cachedRowNum = n;
125 m_rowCache = row;
126 }
127
128 if (row.isNull()) {
129 mDebug() << "Read-Error! Null QImage!";
130 return {};
131 }
132
133 QImage tile = row.copy(m * stdImageWidth / mmax, 0, c_defaultTileSize, c_defaultTileSize);
134
135 return tile;
136 }
137
138private:
139 QImage m_sourceImage;
140
141 QImage m_rowCache;
142 int m_cachedRowNum;
143};
144
145TileCreator::TileCreator(const QString &sourceDir, const QString &installMap, const QString &dem, const QString &targetDir)
146 : QThread(nullptr)
147 , d(new TileCreatorPrivate(nullptr, dem, targetDir))
148
149{
150 mDebug() << "Prefix: " << sourceDir << "installmap:" << installMap;
151
152 QString sourcePath;
153
154 // If the sourceDir starts with a '/' assume an absolute path.
155 // Otherwise assume a relative marble data path
156 if (QDir::isAbsolutePath(sourceDir)) {
157 sourcePath = sourceDir + QLatin1Char('/') + installMap;
158 mDebug() << "Trying absolute path*:" << sourcePath;
159 } else {
160 sourcePath = MarbleDirs::path(QLatin1StringView("maps/") + sourceDir + QLatin1Char('/') + installMap);
161 mDebug() << "Trying relative path*:" << QLatin1StringView("maps/") + sourceDir + QLatin1Char('/') + installMap;
162 }
163
164 mDebug() << "Creating tiles from*: " << sourcePath;
165
166 d->m_source = new TileCreatorSourceImage(sourcePath);
167
168 if (d->m_targetDir.isNull())
169 d->m_targetDir = MarbleDirs::localPath() + QLatin1StringView("/maps/") + sourcePath.section(QLatin1Char('/'), -3, -2) + QLatin1Char('/');
170
171 setTerminationEnabled(true);
172}
173
174TileCreator::TileCreator(TileCreatorSource *source, const QString &dem, const QString &targetDir)
175 : QThread(nullptr)
176 , d(new TileCreatorPrivate(source, dem, targetDir))
177{
178 setTerminationEnabled(true);
179}
180
181TileCreator::~TileCreator()
182{
183 delete d;
184}
185
186void TileCreator::cancelTileCreation()
187{
188 d->m_cancelled = true;
189}
190
191void TileCreator::run()
192{
193 if (d->m_resume && d->m_tileFormat == QLatin1StringView("jpg") && d->m_tileQuality != 100) {
194 qWarning() << "Resuming jpegs is only supported with tileQuality 100";
195 return;
196 }
197
198 if (!d->m_targetDir.endsWith(QLatin1Char('/')))
199 d->m_targetDir += QLatin1Char('/');
200
201 mDebug() << "Installing tiles to: " << d->m_targetDir;
202
203 QList<QRgb> grayScalePalette;
204 for (int cnt = 0; cnt <= 255; ++cnt) {
205 grayScalePalette.insert(cnt, qRgb(cnt, cnt, cnt));
206 }
207
208 QSize fullImageSize = d->m_source->fullImageSize();
209 int imageWidth = fullImageSize.width();
210 int imageHeight = fullImageSize.height();
211
212 mDebug() << QStringLiteral("TileCreator::createTiles() image dimensions %1 x %2").arg(imageWidth).arg(imageHeight);
213
214 if (imageWidth < 1 || imageHeight < 1) {
215 qDebug("Invalid imagemap!");
216 return;
217 }
218
219 // Calculating Maximum Tile Level
220 float approxMaxTileLevel = std::log(imageWidth / (2.0 * c_defaultTileSize)) / std::log(2.0);
221
222 int maxTileLevel = 0;
223 if (approxMaxTileLevel == int(approxMaxTileLevel))
224 maxTileLevel = static_cast<int>(approxMaxTileLevel);
225 else
226 maxTileLevel = static_cast<int>(approxMaxTileLevel + 1);
227
228 if (maxTileLevel < 0) {
229 mDebug() << QStringLiteral("TileCreator::createTiles(): Invalid Maximum Tile Level: %1").arg(maxTileLevel);
230 }
231 mDebug() << "Maximum Tile Level: " << maxTileLevel;
232
233 if (!QDir(d->m_targetDir).exists())
234 (QDir::root()).mkpath(d->m_targetDir);
235
236 // Counting total amount of tiles to be generated for the progressbar
237 // to prevent compiler warnings this var should
238 // match the type of maxTileLevel
239 int tileLevel = 0;
240 int totalTileCount = 0;
241
242 while (tileLevel <= maxTileLevel) {
243 totalTileCount += (TileLoaderHelper::levelToRow(defaultLevelZeroRows, tileLevel) * TileLoaderHelper::levelToColumn(defaultLevelZeroColumns, tileLevel));
244 tileLevel++;
245 }
246
247 mDebug() << totalTileCount << " tiles to be created in total.";
248
249 int mmax = TileLoaderHelper::levelToColumn(defaultLevelZeroColumns, maxTileLevel);
250 int nmax = TileLoaderHelper::levelToRow(defaultLevelZeroRows, maxTileLevel);
251
252 // Loading each row at highest spatial resolution and cropping tiles
253 int percentCompleted = 0;
254 int createdTilesCount = 0;
255 QString tileName;
256
257 // Creating directory structure for the highest level
258 QString dirName(d->m_targetDir + QStringLiteral("%1").arg(maxTileLevel));
259 if (!QDir(dirName).exists())
260 (QDir::root()).mkpath(dirName);
261
262 for (int n = 0; n < nmax; ++n) {
263 QString dirName(d->m_targetDir + QStringLiteral("%1/%2").arg(maxTileLevel).arg(n, tileDigits, 10, QLatin1Char('0')));
264 if (!QDir(dirName).exists())
265 (QDir::root()).mkpath(dirName);
266 }
267
268 for (int n = 0; n < nmax; ++n) {
269 for (int m = 0; m < mmax; ++m) {
270 mDebug() << "** tile" << m << "x" << n;
271
272 if (d->m_cancelled)
273 return;
274
275 tileName = d->m_targetDir
276 + QStringLiteral("%1/%2/%2_%3.%4")
277 .arg(maxTileLevel)
278 .arg(n, tileDigits, 10, QLatin1Char('0'))
279 .arg(m, tileDigits, 10, QLatin1Char('0'))
280 .arg(d->m_tileFormat);
281
282 if (QFile::exists(tileName) && d->m_resume) {
283 // mDebug() << tileName << "exists already";
284
285 } else {
286 QImage tile = d->m_source->tile(n, m, maxTileLevel);
287
288 if (tile.isNull()) {
289 mDebug() << "Read-Error! Null QImage!";
290 return;
291 }
292
293 if (d->m_dem == QLatin1StringView("true")) {
294 tile = tile.convertToFormat(QImage::Format_Indexed8, grayScalePalette, Qt::ThresholdDither);
295 }
296
297 bool ok = tile.save(tileName, d->m_tileFormat.toLatin1().data(), d->m_tileFormat == QLatin1StringView("jpg") ? 100 : d->m_tileQuality);
298 if (!ok)
299 mDebug() << "Error while writing Tile: " << tileName;
300
301 mDebug() << tileName << "size" << QFile(tileName).size();
302
303 if (d->m_verify) {
304 QImage writtenTile(tileName);
305 Q_ASSERT(writtenTile.size() == tile.size());
306 for (int i = 0; i < writtenTile.size().width(); ++i) {
307 for (int j = 0; j < writtenTile.size().height(); ++j) {
308 if (writtenTile.pixel(i, j) != tile.pixel(i, j)) {
309 unsigned int pixel = tile.pixel(i, j);
310 unsigned int writtenPixel = writtenTile.pixel(i, j);
311 qWarning() << "***** pixel" << i << j << "is off by" << (pixel - writtenPixel) << "pixel" << pixel << "writtenPixel"
312 << writtenPixel;
313 QByteArray baPixel((char *)&pixel, sizeof(unsigned int));
314 qWarning() << "pixel" << baPixel.size() << "0x" << baPixel.toHex();
315 QByteArray baWrittenPixel((char *)&writtenPixel, sizeof(unsigned int));
316 qWarning() << "writtenPixel" << baWrittenPixel.size() << "0x" << baWrittenPixel.toHex();
317 Q_ASSERT(false);
318 }
319 }
320 }
321 }
322 }
323
324 percentCompleted = (int)(90 * (qreal)(createdTilesCount) / (qreal)(totalTileCount));
325 createdTilesCount++;
326
327 mDebug() << "percentCompleted" << percentCompleted;
328 Q_EMIT progress(percentCompleted);
329 }
330 }
331
332 mDebug() << "tileLevel: " << maxTileLevel << " successfully created.";
333
334 tileLevel = maxTileLevel;
335
336 // Now that we have the tiles at the highest resolution lets build
337 // them together four by four.
338
339 while (tileLevel > 0) {
340 tileLevel--;
341
342 int nmaxit = TileLoaderHelper::levelToRow(defaultLevelZeroRows, tileLevel);
343
344 for (int n = 0; n < nmaxit; ++n) {
345 QString dirName(d->m_targetDir + QStringLiteral("%1/%2").arg(tileLevel).arg(n, tileDigits, 10, QLatin1Char('0')));
346
347 // mDebug() << "dirName: " << dirName;
348 if (!QDir(dirName).exists())
349 (QDir::root()).mkpath(dirName);
350
351 int mmaxit = TileLoaderHelper::levelToColumn(defaultLevelZeroColumns, tileLevel);
352 for (int m = 0; m < mmaxit; ++m) {
353 if (d->m_cancelled)
354 return;
355
356 QString newTileName = d->m_targetDir
357 + QStringLiteral("%1/%2/%2_%3.%4")
358 .arg(tileLevel)
359 .arg(n, tileDigits, 10, QLatin1Char('0'))
360 .arg(m, tileDigits, 10, QLatin1Char('0'))
361 .arg(d->m_tileFormat);
362
363 if (QFile::exists(newTileName) && d->m_resume) {
364 // mDebug() << newTileName << "exists already";
365 } else {
366 tileName = d->m_targetDir
367 + QStringLiteral("%1/%2/%2_%3.%4")
368 .arg(tileLevel + 1)
369 .arg(2 * n, tileDigits, 10, QLatin1Char('0'))
370 .arg(2 * m, tileDigits, 10, QLatin1Char('0'))
371 .arg(d->m_tileFormat);
372 QImage img_topleft(tileName);
373
374 tileName = d->m_targetDir
375 + QStringLiteral("%1/%2/%2_%3.%4")
376 .arg(tileLevel + 1)
377 .arg(2 * n, tileDigits, 10, QLatin1Char('0'))
378 .arg(2 * m + 1, tileDigits, 10, QLatin1Char('0'))
379 .arg(d->m_tileFormat);
380 QImage img_topright(tileName);
381
382 tileName = d->m_targetDir
383 + QStringLiteral("%1/%2/%2_%3.%4")
384 .arg(tileLevel + 1)
385 .arg(2 * n + 1, tileDigits, 10, QLatin1Char('0'))
386 .arg(2 * m, tileDigits, 10, QLatin1Char('0'))
387 .arg(d->m_tileFormat);
388 QImage img_bottomleft(tileName);
389
390 tileName = d->m_targetDir
391 + QStringLiteral("%1/%2/%2_%3.%4")
392 .arg(tileLevel + 1)
393 .arg(2 * n + 1, tileDigits, 10, QLatin1Char('0'))
394 .arg(2 * m + 1, tileDigits, 10, QLatin1Char('0'))
395 .arg(d->m_tileFormat);
396 QImage img_bottomright(tileName);
397
398 QSize const expectedSize(c_defaultTileSize, c_defaultTileSize);
399 if (img_topleft.size() != expectedSize || img_topright.size() != expectedSize || img_bottomleft.size() != expectedSize
400 || img_bottomright.size() != expectedSize) {
401 mDebug() << "Tile write failure. Missing write permissions?";
402 Q_EMIT progress(100);
403 return;
404 }
405 QImage tile = img_topleft;
406
407 if (d->m_dem == QLatin1StringView("true")) {
408 tile.setColorTable(grayScalePalette);
409 uchar *destLine;
410
411 for (uint y = 0; y < c_defaultTileSize / 2; ++y) {
412 destLine = tile.scanLine(y);
413 const uchar *srcLine = img_topleft.scanLine(2 * y);
414 for (uint x = 0; x < c_defaultTileSize / 2; ++x)
415 destLine[x] = srcLine[2 * x];
416 }
417 for (uint y = 0; y < c_defaultTileSize / 2; ++y) {
418 destLine = tile.scanLine(y);
419 const uchar *srcLine = img_topright.scanLine(2 * y);
420 for (uint x = c_defaultTileSize / 2; x < c_defaultTileSize; ++x)
421 destLine[x] = srcLine[2 * (x - c_defaultTileSize / 2)];
422 }
423 for (uint y = c_defaultTileSize / 2; y < c_defaultTileSize; ++y) {
424 destLine = tile.scanLine(y);
425 const uchar *srcLine = img_bottomleft.scanLine(2 * (y - c_defaultTileSize / 2));
426 for (uint x = 0; x < c_defaultTileSize / 2; ++x)
427 destLine[x] = srcLine[2 * x];
428 }
429 for (uint y = c_defaultTileSize / 2; y < c_defaultTileSize; ++y) {
430 destLine = tile.scanLine(y);
431 const uchar *srcLine = img_bottomright.scanLine(2 * (y - c_defaultTileSize / 2));
432 for (uint x = c_defaultTileSize / 2; x < c_defaultTileSize; ++x)
433 destLine[x] = srcLine[2 * (x - c_defaultTileSize / 2)];
434 }
435 } else {
436 // tile.depth() != 8
437
438 img_topleft = img_topleft.convertToFormat(QImage::Format_ARGB32);
439 img_topright = img_topright.convertToFormat(QImage::Format_ARGB32);
440 img_bottomleft = img_bottomleft.convertToFormat(QImage::Format_ARGB32);
441 img_bottomright = img_bottomright.convertToFormat(QImage::Format_ARGB32);
442 tile = img_topleft;
443
444 QRgb *destLine;
445
446 for (uint y = 0; y < c_defaultTileSize / 2; ++y) {
447 destLine = (QRgb *)tile.scanLine(y);
448 const QRgb *srcLine = (QRgb *)img_topleft.scanLine(2 * y);
449 for (uint x = 0; x < c_defaultTileSize / 2; ++x)
450 destLine[x] = srcLine[2 * x];
451 }
452 for (uint y = 0; y < c_defaultTileSize / 2; ++y) {
453 destLine = (QRgb *)tile.scanLine(y);
454 const QRgb *srcLine = (QRgb *)img_topright.scanLine(2 * y);
455 for (uint x = c_defaultTileSize / 2; x < c_defaultTileSize; ++x)
456 destLine[x] = srcLine[2 * (x - c_defaultTileSize / 2)];
457 }
458 for (uint y = c_defaultTileSize / 2; y < c_defaultTileSize; ++y) {
459 destLine = (QRgb *)tile.scanLine(y);
460 const QRgb *srcLine = (QRgb *)img_bottomleft.scanLine(2 * (y - c_defaultTileSize / 2));
461 for (uint x = 0; x < c_defaultTileSize / 2; ++x)
462 destLine[x] = srcLine[2 * x];
463 }
464 for (uint y = c_defaultTileSize / 2; y < c_defaultTileSize; ++y) {
465 destLine = (QRgb *)tile.scanLine(y);
466 const QRgb *srcLine = (QRgb *)img_bottomright.scanLine(2 * (y - c_defaultTileSize / 2));
467 for (uint x = c_defaultTileSize / 2; x < c_defaultTileSize; ++x)
468 destLine[x] = srcLine[2 * (x - c_defaultTileSize / 2)];
469 }
470 }
471
472 mDebug() << newTileName;
473
474 // Saving at 100% JPEG quality to have a high-quality
475 // version to create the remaining needed tiles from.
476 bool ok = tile.save(newTileName, d->m_tileFormat.toLatin1().data(), d->m_tileFormat == QLatin1StringView("jpg") ? 100 : d->m_tileQuality);
477 if (!ok)
478 mDebug() << "Error while writing Tile: " << newTileName;
479 }
480
481 percentCompleted = (int)(90 * (qreal)(createdTilesCount) / (qreal)(totalTileCount));
482 createdTilesCount++;
483
484 Q_EMIT progress(percentCompleted);
485 mDebug() << "percentCompleted" << percentCompleted;
486 }
487 }
488 mDebug() << "tileLevel: " << tileLevel << " successfully created.";
489 }
490 mDebug() << "Tile creation completed.";
491
492 if (d->m_tileFormat == QLatin1StringView("jpg") && d->m_tileQuality != 100) {
493 // Applying correct lower JPEG compression now that we created all tiles
494 int savedTilesCount = 0;
495
496 tileLevel = 0;
497 while (tileLevel <= maxTileLevel) {
498 int nmaxit = TileLoaderHelper::levelToRow(defaultLevelZeroRows, tileLevel);
499 for (int n = 0; n < nmaxit; ++n) {
500 int mmaxit = TileLoaderHelper::levelToColumn(defaultLevelZeroColumns, tileLevel);
501 for (int m = 0; m < mmaxit; ++m) {
502 if (d->m_cancelled)
503 return;
504
505 savedTilesCount++;
506
507 tileName = d->m_targetDir
508 + QStringLiteral("%1/%2/%2_%3.%4")
509 .arg(tileLevel)
510 .arg(n, tileDigits, 10, QLatin1Char('0'))
511 .arg(m, tileDigits, 10, QLatin1Char('0'))
512 .arg(d->m_tileFormat);
513 QImage tile(tileName);
514
515 bool ok;
516
517 ok = tile.save(tileName, d->m_tileFormat.toLatin1().data(), d->m_tileQuality);
518
519 if (!ok)
520 mDebug() << "Error while writing Tile: " << tileName;
521 // Don't exceed 99% as this would cancel the thread unexpectedly
522 percentCompleted = 90 + (int)(9 * (qreal)(savedTilesCount) / (qreal)(totalTileCount));
523 Q_EMIT progress(percentCompleted);
524 mDebug() << "percentCompleted" << percentCompleted;
525 // mDebug() << "Saving Tile #" << savedTilesCount
526 // << " of " << totalTileCount
527 // << " Percent: " << percentCompleted;
528 }
529 }
530 tileLevel++;
531 }
532 }
533
534 percentCompleted = 100;
535 Q_EMIT progress(percentCompleted);
536
537 mDebug() << "percentCompleted: " << percentCompleted;
538}
539
540void TileCreator::setTileFormat(const QString &format)
541{
542 d->m_tileFormat = format;
543}
544
545QString TileCreator::tileFormat() const
546{
547 return d->m_tileFormat;
548}
549
550void TileCreator::setTileQuality(int quality)
551{
552 d->m_tileQuality = quality;
553}
554
555int TileCreator::tileQuality() const
556{
557 return d->m_tileQuality;
558}
559
560void TileCreator::setResume(bool resume)
561{
562 d->m_resume = resume;
563}
564
565bool TileCreator::resume() const
566{
567 return d->m_resume;
568}
569
570void TileCreator::setVerifyExactResult(bool verify)
571{
572 d->m_verify = verify;
573}
574
575bool TileCreator::verifyExactResult() const
576{
577 return d->m_verify;
578}
579
580}
581
582#include "moc_TileCreator.cpp"
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
Binds a QML item to a specific geodetic location in screen coordinates.
bool exists() const const
bool isAbsolutePath(const QString &path)
QDir root()
bool exists() const const
virtual qint64 size() const const override
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
QImage copy(const QRect &rectangle) const const
int height() const const
bool isNull() const const
QRgb pixel(const QPoint &position) const const
bool save(QIODevice *device, const char *format, int quality) const const
QImage scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
uchar * scanLine(int i)
void setColorTable(const QList< QRgb > &colors)
QSize size() const const
int width() const const
iterator insert(const_iterator before, parameter_type value)
int height() const const
int width() const const
QString arg(Args &&... args) const const
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
IgnoreAspectRatio
ThresholdDither
SmoothTransformation
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.