1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
-311 | /* ============================================================
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733 | /* ============================================================
*
* This file is a part of digiKam project
* https://www.digikam.org
*
- * Date : 2007-02-13
- * Description : Layouting photos on a page
+ * Date : 2007-11-07
+ * Description : a tool to print images
*
- * Copyright (C) 2007-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
- * Copyright (C) 2006-2020 by Gilles Caulier <caulier dot gilles at gmail dot com>
- *
- * This program is free software; you can redistribute it
- * and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation;
- * either version 2, or (at your option) any later version.
+ * Copyright (C) 2017-2020 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, or (at your option)
+ * any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
-#include "atkinspagelayouttree.h"
+#include "advprinttask.h"
// C++ includes
#include <cmath>
// Qt includes
-#include <QList>
-
-// Local includes
-
-#include "atkinspagelayoutnode.h"
-
-namespace DigikamGenericPrintCreatorPlugin
-{
+#include <QImage>
+#include <QSize>
+#include <QPainter>
+#include <QFileInfo>
+
+// KDE includes
+
+#include <klocalizedstring.h>
-AtkinsPageLayoutTree::AtkinsPageLayoutTree(double aspectRatioPage,
- double absoluteAreaPage)
- : m_root(nullptr),
- m_count(0),
- m_aspectRatioPage(aspectRatioPage),
- m_absoluteAreaPage(absoluteAreaPage)
-{
-}
-
-AtkinsPageLayoutTree::AtkinsPageLayoutTree(const AtkinsPageLayoutTree& other)
- : m_root(nullptr)
-{
- (*this) = other;
-}
-
-AtkinsPageLayoutTree& AtkinsPageLayoutTree::operator=(const AtkinsPageLayoutTree& other)
-{
- if (this != &other)
- {
- delete m_root;
- m_root = new AtkinsPageLayoutNode(*(other.m_root));
- m_count = other.m_count;
- m_aspectRatioPage = other.m_aspectRatioPage;
- m_absoluteAreaPage = other.m_absoluteAreaPage;
- }
-
- return *this;
-}
+// Local includes
+
+#include "advprintwizard.h"
+#include "advprintphoto.h"
+#include "advprintcaptionpage.h"
+#include "dmetadata.h"
+#include "dfileoperations.h"
+#include "dimg.h"
+#include "digikam_debug.h"
+#include "digikam_config.h"
+
+namespace DigikamGenericPrintCreatorPlugin
+{
+
+class Q_DECL_HIDDEN AdvPrintTask::Private
+{
+public:
+
+ explicit Private()
+ : settings(nullptr),
+ mode(AdvPrintTask::PRINT),
+ sizeIndex(0)
+ {
+ }
+
+public:
+
+ AdvPrintSettings* settings;
-AtkinsPageLayoutTree::~AtkinsPageLayoutTree()
-{
- delete m_root;
-}
-
-int AtkinsPageLayoutTree::addImage(double aspectRatio,
- double relativeArea)
-{
- int index = m_count;
-
- if (!m_root)
- {
- m_root = new AtkinsPageLayoutNode(aspectRatio,
- relativeArea,
- index);
- m_count++;
- return index;
- }
-
- // Section 2.1
+ PrintMode mode;
+ QSize size;
+
+ int sizeIndex;
+};
+
+// -------------------------------------------------------
+
+AdvPrintTask::AdvPrintTask(AdvPrintSettings* const settings,
+ PrintMode mode,
+ const QSize& size,
+ int sizeIndex)
+ : ActionJob(),
+ d(new Private)
+{
+ d->settings = settings;
+ d->mode = mode;
+ d->size = size;
+ d->sizeIndex = sizeIndex;
+}
- AtkinsPageLayoutNode* bestTree = nullptr;
- double highScore = 0;
-
- for (int i = 0 ; i < m_count ; ++i)
- {
- for (int horizontal = 0 ; horizontal < 2 ; ++horizontal)
- {
- // create temporary tree
-
- AtkinsPageLayoutNode* candidateTree = new AtkinsPageLayoutNode(*m_root);
-
- // select the subtree which will be replace by a new internal node
-
- AtkinsPageLayoutNode* const candidateSubtree = candidateTree->nodeForIndex(i);
-
- // find parent node
+AdvPrintTask::~AdvPrintTask()
+{
+ cancel();
+ delete d;
+}
+
+void AdvPrintTask::run()
+{
+ switch (d->mode)
+ {
+ case PREPAREPRINT:
+
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Start prepare to print";
+ preparePrint();
+ emit signalDone(!m_cancel);
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Prepare to print is done";
- AtkinsPageLayoutNode* const parentNode = candidateTree->parentOf(candidateSubtree);
+ break;
- // create new terminal node
+ case PRINT:
- AtkinsPageLayoutNode* const newTerminalNode = new AtkinsPageLayoutNode(aspectRatio,
- relativeArea,
- index);
-
- // create new internal node
-
- AtkinsPageLayoutNode* const newInternalNode = new AtkinsPageLayoutNode(candidateSubtree,
- newTerminalNode,
- horizontal,
- index+1);
-
- // replace in tree
-
- if (parentNode)
- {
- // replace in tree
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Start to print";
+
+ if (d->settings->printerName != d->settings->outputName(AdvPrintSettings::FILES) &&
+ d->settings->printerName != d->settings->outputName(AdvPrintSettings::GIMP))
+ {
+ printPhotos();
+ emit signalDone(!m_cancel);
+ }
+ else
+ {
+ QStringList files = printPhotosToFile();
+
+ if (d->settings->printerName == d->settings->outputName(AdvPrintSettings::GIMP))
+ {
+ d->settings->gimpFiles << files;
+ }
- parentNode->takeAndSetChild(candidateSubtree, newInternalNode);
+ emit signalDone(!m_cancel && !files.isEmpty());
}
- else
- {
- // candidateTree is candidateSubtree is root
-
- candidateTree = newInternalNode;
- }
+
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Print is done";
+
+ break;
+
+ default: // PREVIEW
- // recompute sizes
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Start to compute preview";
- candidateTree->computeRelativeSizes();
-
- double candidateScore = score(candidateTree, m_count+2);
-
- if (candidateScore > highScore)
- {
- highScore = candidateScore;
- delete bestTree;
- bestTree = candidateTree;
- }
- else
- {
- delete candidateTree;
- }
- }
- }
-
- delete m_root;
- m_root = bestTree;
-
- if (m_root)
- {
- m_root->computeDivisions();
- }
-
- m_count += 2;
-
- return index;
-}
-
-/// Section 2.2.1
-double AtkinsPageLayoutTree::score(AtkinsPageLayoutNode* const root,
- int nodeCount)
-{
- if (!root)
- {
- return 0;
- }
-
- double areaSum = 0;
-
- for (int i = 0 ; i < nodeCount ; ++i)
- {
- AtkinsPageLayoutNode* const node = root->nodeForIndex(i);
-
- if (node->type() == AtkinsPageLayoutNode::TerminalNode)
- {
- areaSum += node->relativeArea();
- }
- }
+ QImage img(d->size, QImage::Format_ARGB32_Premultiplied);
+ QPainter p(&img);
+ p.setCompositionMode(QPainter::CompositionMode_Clear);
+ p.fillRect(img.rect(), Qt::color0);
+ p.setCompositionMode(QPainter::CompositionMode_SourceOver);
+ paintOnePage(p,
+ d->settings->photos,
+ d->settings->outputLayouts->m_layouts,
+ d->settings->currentPreviewPage,
+ d->settings->disableCrop,
+ true);
+ p.end();
+
+ if (!m_cancel)
+ emit signalPreview(img);
+
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Preview computation is done";
+
+ break;
+ }
+}
+
+void AdvPrintTask::preparePrint()
+{
+ int photoIndex = 0;
+
+ for (QList<AdvPrintPhoto*>::iterator it = d->settings->photos.begin() ;
+ it != d->settings->photos.end() ; ++it)
+ {
+ AdvPrintPhoto* const photo = static_cast<AdvPrintPhoto*>(*it);
+
+ if (photo && photo->m_cropRegion == QRect(-1, -1, -1, -1))
+ {
+ QRect* const curr = d->settings->getLayout(photoIndex, d->sizeIndex);
+
+ photo->updateCropRegion(curr->width(),
+ curr->height(),
+ d->settings->outputLayouts->m_autoRotate);
+ }
+
+ photoIndex++;
+ emit signalProgress(photoIndex);
+
+ if (m_cancel)
+ {
+ signalMessage(i18n("Printing canceled"), true);
+ return;
+ }
+ }
+}
- double minRatioPage = root->aspectRatio() < m_aspectRatioPage ? root->aspectRatio()
- : m_aspectRatioPage;
- double maxRatioPage = root->aspectRatio() > m_aspectRatioPage ? root->aspectRatio()
- : m_aspectRatioPage;
+void AdvPrintTask::printPhotos()
+{
+ AdvPrintPhotoSize* const layouts = d->settings->outputLayouts;
+ QPrinter* const printer = d->settings->outputPrinter;
- return G() * (areaSum / root->relativeArea()) * (minRatioPage / maxRatioPage);
-}
-
-/// Section 2.2.2
-double AtkinsPageLayoutTree::G() const
-{
- return 0.95 * 0.95;
-}
-
-/// Section 2.2.2
-double AtkinsPageLayoutTree::absoluteArea(AtkinsPageLayoutNode* const node)
-{
- // min(a_pbb, a_page), max(a_pbb, a_page)
-
- double minRatioPage = m_root->aspectRatio() < m_aspectRatioPage ? m_root->aspectRatio()
- : m_aspectRatioPage;
- double maxRatioPage = m_root->aspectRatio() > m_aspectRatioPage ? m_root->aspectRatio()
- : m_aspectRatioPage;
-
- // A_pbb
-
- double absoluteAreaRoot = m_absoluteAreaPage * minRatioPage / maxRatioPage;
-
- if (node == m_root)
- {
- return absoluteAreaRoot;
- }
-
- // A_i
- return (G() * node->relativeArea() / m_root->relativeArea() * absoluteAreaRoot);
-}
-
-QRectF AtkinsPageLayoutTree::drawingArea(int index, const QRectF& absoluteRectPage)
-{
- AtkinsPageLayoutNode* const node = m_root->nodeForIndex(index);
-
- if (!node)
- {
- return QRectF();
- }
+ Q_ASSERT(layouts);
+ Q_ASSERT(printer);
+ Q_ASSERT(layouts->m_layouts.count() > 1);
+
+ QList<AdvPrintPhoto*> photos = d->settings->photos;
+ QPainter p;
+ p.begin(printer);
+
+ int current = 0;
+ int pageCount = 1;
+ bool printing = true;
+
+ while (printing)
+ {
+ signalMessage(i18n("Processing page %1", pageCount), false);
+
+ printing = paintOnePage(p,
+ photos,
+ layouts->m_layouts,
+ current,
+ d->settings->disableCrop);
+
+ if (printing)
+ {
+ printer->newPage();
+ }
+
+ pageCount++;
+ emit signalProgress(current);
+
+ if (m_cancel)
+ {
+ printer->abort();
+ signalMessage(i18n("Printing canceled"), true);
+ return;
+ }
+ }
+
+ p.end();
+}
- // find out the "line of ancestry" of the node
-
- QList<AtkinsPageLayoutNode*> treePath;
- AtkinsPageLayoutNode* parent = node;<--- Shadowed declaration
-
- while (parent)
- {
- treePath.prepend(parent);
- parent = m_root->parentOf(parent);
- }
+QStringList AdvPrintTask::printPhotosToFile()
+{
+ AdvPrintPhotoSize* const layouts = d->settings->outputLayouts;
+ QString dir = d->settings->outputPath;
+
+ Q_ASSERT(layouts);
+ Q_ASSERT(!dir.isEmpty());
+ Q_ASSERT(layouts->m_layouts.count() > 1);
+
+ QList<AdvPrintPhoto*> photos = d->settings->photos;
- // find out the rect of the page bounding box (the rect of the root node in the page rect)
-
- QRectF absoluteRect = rectInRect(absoluteRectPage,
- m_root->aspectRatio(),
- absoluteArea(m_root));
+ QStringList files;
+ int current = 0;
+ int pageCount = 1;
+ bool printing = true;
+ QRect* const srcPage = layouts->m_layouts.at(0);
- // go along the line of ancestry and narrow down the bounding rectangle,
- // as described in section 2.2.2
-
- for (int i = 0 ; i < treePath.count() - 1 ; ++i)
- {
- AtkinsPageLayoutNode* const parent = treePath[i];<--- Shadow variable
- AtkinsPageLayoutNode* const child = treePath[i+1]; // only iterating to count-1
-
- if (parent->type() == AtkinsPageLayoutNode::VerticalDivision) // side by side
- {
- double leftWidth = absoluteRect.width() * parent->division();
-
- if (child == parent->leftChild())
- {
- absoluteRect.setWidth(leftWidth);
- }
- else // right child
- {
- double rightWidth = absoluteRect.width() - leftWidth;
- absoluteRect.setWidth(rightWidth);
- absoluteRect.translate(leftWidth, 0);
- }
- }
- else // horizontal division: one on top of the other
- {
- // left child is topmost
+ while (printing)
+ {
+ // make a pixmap to save to file. Make it just big enough to show the
+ // highest-dpi image on the page without losing data.
+
+ double dpi = layouts->m_dpi;
+
+ if (dpi == 0.0)
+ {
+ dpi = getMaxDPI(photos, layouts->m_layouts, current) * 1.1;
+ (void)dpi; // Remove clang warnings.
+ }
+
+ int w = AdvPrintWizard::normalizedInt(srcPage->width());
+ int h = AdvPrintWizard::normalizedInt(srcPage->height());
+
+ QImage image(w, h, QImage::Format_ARGB32_Premultiplied);
+ QPainter painter;
+ painter.begin(&image);
+
+ QString ext = d->settings->format();
+ QString name = QLatin1String("output");
+ QString filename = dir + QLatin1Char('/') +
+ name + QLatin1Char('_') +
+ QString::number(pageCount) +
+ QLatin1Char('.') + ext;
- double upperHeight = absoluteRect.height() * parent->division();
-
- if (child == parent->leftChild())
- {
- absoluteRect.setHeight(upperHeight);
- }
- else // right child
- {
- double lowerHeight = absoluteRect.height() - upperHeight;
- absoluteRect.setHeight(lowerHeight);
- absoluteRect.translate(0, upperHeight);
- }
- }
- }
-
- return rectInRect(absoluteRect, node->aspectRatio(), absoluteArea(node));
-}
-
-QRectF AtkinsPageLayoutTree::rectInRect(const QRectF &rect,
- double aspectRatio,
- double absoluteArea)
-{
- double width = std::sqrt(absoluteArea / aspectRatio);
- double height = std::sqrt(absoluteArea * aspectRatio);
- double x = rect.x() + (rect.width() - width) / 2;
- double y = rect.y() + (rect.height() - height) / 2;
+ if (QFile::exists(filename) &&
+ d->settings->conflictRule != FileSaveConflictBox::OVERWRITE)
+ {
+ filename = DFileOperations::getUniqueFileUrl(QUrl::fromLocalFile(filename)).toLocalFile();
+ }
+
+ signalMessage(i18n("Processing page %1", pageCount), false);
+
+ printing = paintOnePage(painter,
+ photos,
+ layouts->m_layouts,
+ current,
+ d->settings->disableCrop);
+
+ painter.end();
+
+ if (!image.save(filename, nullptr, 100))
+ {
+ signalMessage(i18n("Could not save file %1", filename), true);
+ break;
+ }
+ else
+ {
+ files.append(filename);
+ signalMessage(i18n("Page %1 saved as %2", pageCount, filename), false);
+ }
- return QRectF(x, y, width, height);
-}
+ pageCount++;
+ emit signalProgress(current);
-} // Namespace Digikam
+ if (m_cancel)
+ {
+ signalMessage(i18n("Printing canceled"), true);
+ break;
+ }
+ }
+
+ return files;
+}
+
+bool AdvPrintTask::paintOnePage(QPainter& p,
+ const QList<AdvPrintPhoto*>& photos,
+ const QList<QRect*>& layouts,
+ int& current,
+ bool cropDisabled,
+ bool useThumbnails)
+{
+ if (layouts.isEmpty())
+ {
+ qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Invalid layout content";
+ return true;
+ }
+
+ if (photos.count() == 0)
+ {
+ qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "no photo to print";
+
+ // no photos => last photo
+
+ return true;
+ }
+
+ QList<QRect*>::const_iterator it = layouts.begin();
+ QRect* const srcPage = static_cast<QRect*>(*it);
+ ++it;
+ QRect* layout = static_cast<QRect*>(*it);
+
+ // scale the page size to best fit the painter
+ // size the rectangle based on the minimum image dimension
+
+ int destW = p.window().width();
+ int destH = p.window().height();
+ int srcW = srcPage->width();
+ int srcH = srcPage->height();
+
+ if (destW < destH)
+ {
+ destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW));
+
+ if (destH > p.window().height())
+ {
+ destH = p.window().height();
+ destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH));
+ }
+ }
+ else
+ {
+ destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH));
+
+ if (destW > p.window().width())
+ {
+ destW = p.window().width();
+ destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW));
+ }
+ }
+
+ double xRatio = (double) destW / (double) srcPage->width();<--- Shadowed declaration
+ double yRatio = (double) destH / (double) srcPage->height();<--- Shadowed declaration
+ int left = (p.window().width() - destW) / 2;
+ int top = (p.window().height() - destH) / 2;
+
+ // FIXME: may not want to erase the background page
+
+ p.eraseRect(left, top,
+ AdvPrintWizard::normalizedInt((double) srcPage->width() * xRatio),
+ AdvPrintWizard::normalizedInt((double) srcPage->height() * yRatio));
+
+ for ( ; (current < photos.count()) && !m_cancel ; ++current)
+ {
+ AdvPrintPhoto* const photo = photos.at(current);
+
+ // crop
+
+ QImage img;
+
+ if (useThumbnails)
+ {
+ img = photo->thumbnail().copyQImage();
+ }
+ else
+ {
+ img = photo->loadPhoto().copyQImage();
+ }
+
+ // next, do we rotate?
+
+ if (photo->m_rotation != 0)
+ {
+ // rotate
+
+ QMatrix matrix;
+ matrix.rotate(photo->m_rotation);
+ img = img.transformed(matrix);
+ }
+
+ if (useThumbnails)
+ {
+ // scale the crop region to thumbnail coords
+
+ double xRatio = 0.0;<--- Shadow variable
+ double yRatio = 0.0;<--- Shadow variable
+
+ if (photo->thumbnail().width() != 0)
+ {
+ xRatio = (double)photo->thumbnail().width() / (double)photo->width();
+ }
+
+ if (photo->thumbnail().height() != 0)
+ {
+ yRatio = (double)photo->thumbnail().height() / (double)photo->height();
+ }
+
+ int x1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.left() * xRatio);
+ int y1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.top() * yRatio);
+ int w = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.width() * xRatio);
+ int h = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.height() * yRatio);
+ img = img.copy(QRect(x1, y1, w, h));
+ }
+ else if (!cropDisabled)
+ {
+ img = img.copy(photo->m_cropRegion);
+ }
+
+ int x1 = AdvPrintWizard::normalizedInt((double) layout->left() * xRatio);
+ int y1 = AdvPrintWizard::normalizedInt((double) layout->top() * yRatio);
+ int w = AdvPrintWizard::normalizedInt((double) layout->width() * xRatio);
+ int h = AdvPrintWizard::normalizedInt((double) layout->height() * yRatio);
+
+ QRect rectViewPort = p.viewport();
+ QRect newRectViewPort = QRect(x1 + left, y1 + top, w, h);
+ QSize imageSize = img.size();
+/*
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Image "
+ << photo->filename
+ << " size " << imageSize;
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "viewport size "
+ << newRectViewPort.size();
+*/
+ QPoint point;
+
+ if (cropDisabled)
+ {
+ imageSize.scale(newRectViewPort.size(), Qt::KeepAspectRatio);
+ int spaceLeft = (newRectViewPort.width() - imageSize.width()) / 2;
+ int spaceTop = (newRectViewPort.height() - imageSize.height()) / 2;
+ p.setViewport(spaceLeft + newRectViewPort.x(),
+ spaceTop + newRectViewPort.y(),
+ imageSize.width(),
+ imageSize.height());
+ point = QPoint(newRectViewPort.x() + spaceLeft + imageSize.width(),
+ newRectViewPort.y() + spaceTop + imageSize.height());
+ }
+ else
+ {
+ p.setViewport(newRectViewPort);
+ point = QPoint(x1 + left + w, y1 + top + w);
+ }
+
+ QRect rectWindow = p.window();
+ p.setWindow(img.rect());
+ p.drawImage(0, 0, img);
+ p.setViewport(rectViewPort);
+ p.setWindow(rectWindow);
+ p.setBrushOrigin(point);
+
+ if (photo->m_pAdvPrintCaptionInfo &&
+ (photo->m_pAdvPrintCaptionInfo->m_captionType != AdvPrintSettings::NONE))
+ {
+ p.save();
+ QString caption = AdvPrintCaptionPage::captionFormatter(photo);
+
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Caption for"
+ << photo->m_url
+ << ":"
+ << caption;
+
+ // draw the text at (0,0), but we will translate and rotate the world
+ // before drawing so the text will be in the correct location
+ // next, do we rotate?
+
+ int captionW = w - 2;
+ double ratio = photo->m_pAdvPrintCaptionInfo->m_captionSize * 0.01;
+ int captionH = (int)(qMin(w, h) * ratio);
+ int orientatation = photo->m_rotation;
+ int exifOrientation = DMetadata::ORIENTATION_NORMAL;
+ (void)exifOrientation; // prevent cppcheck warning.
+
+
+ if (photo->m_iface)
+ {
+ DItemInfo info(photo->m_iface->itemInfo(photo->m_url));
+ exifOrientation = info.orientation();
+ }
+ else
+ {
+ DMetadata meta(photo->m_url.toLocalFile());
+ exifOrientation = meta.getItemOrientation();
+ }
+
+ // ROT_90_HFLIP .. ROT_270
+
+ if (
+ (exifOrientation == DMetadata::ORIENTATION_ROT_90_HFLIP) ||
+ (exifOrientation == DMetadata::ORIENTATION_ROT_90) ||
+ (exifOrientation == DMetadata::ORIENTATION_ROT_90_VFLIP) ||
+ (exifOrientation == DMetadata::ORIENTATION_ROT_270)
+ )
+ {
+ orientatation = (photo->m_rotation + 270) % 360; // -90 degrees
+ }
+
+ if ((orientatation == 90) || (orientatation == 270))
+ {
+ captionW = h;
+ }
+
+ p.rotate(orientatation);
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "rotation "
+ << photo->m_rotation
+ << " orientation "
+ << orientatation;
+ int tx = left;
+ int ty = top;
+
+ switch (orientatation)
+ {
+ case 0:
+ {
+ tx += x1 + 1;
+ ty += y1 + (h - captionH - 1);
+ break;
+ }
+
+ case 90:
+ {
+ tx = top + y1 + 1;
+ ty = -left - x1 - captionH - 1;
+ break;
+ }
+
+ case 180:
+ {
+ tx = -left - x1 - w + 1;
+ ty = -top - y1 - (captionH + 1);
+ break;
+ }
+
+ case 270:
+ {
+ tx = -top - y1 - h + 1;
+ ty = left + x1 + (w - captionH) - 1;
+ break;
+ }
+ }
+
+ p.translate(tx, ty);
+ printCaption(p, photo, captionW, captionH, caption);
+ p.restore();
+ }
+
+ // iterate to the next position
+
+ ++it;
+ layout = (it == layouts.end()) ? nullptr : static_cast<QRect*>(*it);
+
+ if (layout == nullptr)
+ {
+ current++;
+ break;
+ }
+ }
+
+ // did we print the last photo?
+
+ return (current < photos.count());
+}
+
+double AdvPrintTask::getMaxDPI(const QList<AdvPrintPhoto*>& photos,
+ const QList<QRect*>& layouts,
+ int current)
+{
+ Q_ASSERT(layouts.count() > 1);
+
+ QList<QRect*>::const_iterator it = layouts.begin();
+ QRect* layout = static_cast<QRect*>(*it);
+ double maxDPI = 0.0;
+
+ for ( ; current < photos.count() ; ++current)
+ {
+ AdvPrintPhoto* const photo = photos.at(current);
+ double dpi = ((double) photo->m_cropRegion.width() +
+ (double) photo->m_cropRegion.height()) /
+ (((double) layout->width() / 1000.0) +
+ ((double) layout->height() / 1000.0));
+
+ if (dpi > maxDPI)
+ maxDPI = dpi;
+
+ // iterate to the next position
+
+ ++it;
+ layout = (it == layouts.end()) ? nullptr : static_cast<QRect*>(*it);
+
+ if (layout == nullptr)
+ {
+ break;
+ }
+ }
+
+ return maxDPI;
+}
+
+void AdvPrintTask::printCaption(QPainter& p,
+ AdvPrintPhoto* const photo,
+ int captionW,
+ int captionH,
+ const QString& caption)
+{
+ QStringList captionByLines;
+
+ int captionIndex = 0;
+
+ while (captionIndex < caption.length())
+ {
+ QString newLine;
+ bool breakLine = false; // End Of Line found
+ int currIndex; // Caption QString current index
+
+ // Check minimal lines dimension
+ // TODO: fix length, maybe useless
+
+ int captionLineLocalLength = 40;
+
+ for (currIndex = captionIndex ;
+ currIndex < caption.length() && !breakLine ; ++currIndex)
+ {
+ if ((caption[currIndex] == QLatin1Char('\n')) ||
+ caption[currIndex].isSpace())
+ {
+ breakLine = true;
+ }
+ }
+
+ if (captionLineLocalLength <= (currIndex - captionIndex))
+ {
+ captionLineLocalLength = (currIndex - captionIndex);
+ }
+
+ breakLine = false;
+
+ for (currIndex = captionIndex;
+ (currIndex <= captionIndex + captionLineLocalLength) &&
+ (currIndex < caption.length()) && !breakLine;
+ ++currIndex)
+ {
+ breakLine = (caption[currIndex] == QLatin1Char('\n')) ? true : false;
+
+ if (breakLine)
+ {
+ newLine.append(QLatin1Char(' '));
+ }
+ else
+ {
+ newLine.append(caption[currIndex]);
+ }
+ }
+
+ captionIndex = currIndex; // The line is ended
+
+ if (captionIndex != caption.length())
+ {
+ while (!newLine.endsWith(QLatin1Char(' ')))
+ {
+ newLine.truncate(newLine.length() - 1);
+ captionIndex--;
+ }
+ }
+
+ captionByLines.prepend(newLine.trimmed());
+ }
+
+ QFont font(photo->m_pAdvPrintCaptionInfo->m_captionFont);
+ font.setStyleHint(QFont::SansSerif);
+ font.setPixelSize((int)(captionH * 0.8F)); // Font height ratio
+ font.setWeight(QFont::Normal);
+
+ QFontMetrics fm(font);
+ int pixelsHigh = fm.height();
+
+ p.setFont(font);
+ p.setPen(photo->m_pAdvPrintCaptionInfo->m_captionColor);
+
+ qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Number of lines "
+ << (int) captionByLines.count() ;
+
+ // Now draw the caption
+ // TODO allow printing captions per photo and on top, bottom and vertically
+
+ for (int lineNumber = 0 ;
+ lineNumber < (int) captionByLines.count() ; ++lineNumber)
+ {
+ if (lineNumber > 0)
+ {
+ p.translate(0, - (int)(pixelsHigh));
+ }
+
+ QRect r(0, 0, captionW, captionH);
+
+ p.drawText(r, Qt::AlignLeft, captionByLines[lineNumber], &r);
+ }
+}
+
+} // namespace DigikamGenericPrintCreatorPlugin
|