1 /* ============================================================
2 *
3 * This file is a part of KDE project
4 *
5 *
6 * Date : 2007-02-13
7 * Description : Layouting photos on a page
8 *
9 * Copyright 2007-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10 *
11 * This program is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General
13 * Public License as published by the Free Software Foundation;
14 * either version 2, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * ============================================================ */
22
23 #include "layouttree.h"
24
25 // C++ includes
26
27 #include <cmath>
28
29 // Qt includes
30
31 #include <QList>
32
33 namespace KIPIPrintImagesPlugin
34 {
35
36 /*
37 Some comments refer to the paper mentioned in the header file
38 */
39
LayoutNode(double aspectRatio,double relativeArea,int index)40 LayoutNode::LayoutNode(double aspectRatio, double relativeArea, int index)
41 : m_a(aspectRatio),
42 m_e(relativeArea),
43 m_division(0),
44 m_type(TerminalNode),
45 m_index(index),
46 m_leftChild(nullptr),
47 m_rightChild(nullptr)
48 {
49 }
50
LayoutNode(LayoutNode * const subtree,LayoutNode * const terminalChild,bool horizontal,int index)51 LayoutNode::LayoutNode(LayoutNode* const subtree, LayoutNode* const terminalChild, bool horizontal, int index)
52 : m_a(0),
53 m_e(0),
54 m_division(0),
55 m_type(horizontal ? HorizontalDivision : VerticalDivision),
56 m_index(index),
57 m_leftChild(subtree),
58 m_rightChild(terminalChild)
59 {
60 }
61
LayoutNode(const LayoutNode & other)62 LayoutNode::LayoutNode(const LayoutNode& other)
63 {
64 (*this) = other;
65 }
66
~LayoutNode()67 LayoutNode::~LayoutNode()
68 {
69 delete m_leftChild;
70 delete m_rightChild;
71 }
72
operator =(const LayoutNode & other)73 LayoutNode &LayoutNode::operator=(const LayoutNode& other)
74 {
75 m_a = other.m_a;
76 m_e = other.m_e;
77 m_division = other.m_division;
78 m_type = other.m_type;
79 m_index = other.m_index;
80 m_leftChild = other.m_leftChild ? new LayoutNode(*other.m_leftChild) : nullptr;
81 m_rightChild = other.m_rightChild ? new LayoutNode(*other.m_rightChild) : nullptr;
82
83 return *this;
84 }
85
86 // replace one child with a new one
takeAndSetChild(LayoutNode * oldChild,LayoutNode * newChild)87 void LayoutNode::takeAndSetChild(LayoutNode* oldChild, LayoutNode* newChild)
88 {
89 if (m_leftChild == oldChild)
90 m_leftChild = newChild;
91 else if (m_rightChild == oldChild)
92 m_rightChild = newChild;
93 }
94
95 // retrieve the node which has the given index in the hierarchy of this node
nodeForIndex(int index)96 LayoutNode* LayoutNode::nodeForIndex(int index)
97 {
98 if (m_index == index)
99 return this;
100
101 if (m_type == TerminalNode)
102 return nullptr;
103
104 LayoutNode* const fromLeft = m_leftChild->nodeForIndex(index);
105
106 if (fromLeft)
107 return fromLeft;
108
109 return m_rightChild->nodeForIndex(index);
110 }
111
112 // retrieve the parent node of the given child in the hierarchy of this node
parentOf(LayoutNode * child)113 LayoutNode* LayoutNode::parentOf(LayoutNode* child)
114 {
115 if (m_type == TerminalNode)
116 return nullptr;
117
118 if (m_leftChild == child || m_rightChild == child)
119 return this;
120
121 LayoutNode* const fromLeft = m_leftChild->parentOf(child);
122
123 if (fromLeft)
124 return fromLeft;
125
126 return m_rightChild->parentOf(child);
127 }
128
129 // compute the "aspect ratio" (a) and "relative size" (e) parameters
130 // Section 2.2.1, (1)-(4)
computeRelativeSizes()131 void LayoutNode::computeRelativeSizes()
132 {
133 if (m_type == TerminalNode)
134 return;
135
136 m_leftChild->computeRelativeSizes();
137 m_rightChild->computeRelativeSizes();
138
139 double leftProductRoot = std::sqrt(m_leftChild->m_a * m_leftChild->m_e);
140 double rightProductRoot = std::sqrt(m_rightChild->m_a * m_rightChild->m_e);
141 double maxProductRoot = leftProductRoot > rightProductRoot ? leftProductRoot : rightProductRoot;
142
143 double leftDivisionRoot = std::sqrt(m_leftChild->m_e / m_leftChild->m_a);
144 double rightDivisionRoot = std::sqrt(m_rightChild->m_e / m_rightChild->m_a);
145 double maxDivisionRoot = leftDivisionRoot > rightDivisionRoot ? leftDivisionRoot : rightDivisionRoot;
146
147 if (m_type == VerticalDivision) // side by side
148 {
149 m_a = maxProductRoot / (leftDivisionRoot + rightDivisionRoot);
150 m_e = maxProductRoot * (leftDivisionRoot + rightDivisionRoot);
151 }
152 else if (m_type == HorizontalDivision) // one on top of the other
153 {
154 m_a = (leftProductRoot + rightProductRoot) / maxDivisionRoot;
155 m_e = maxDivisionRoot * (leftProductRoot + rightProductRoot);
156 }
157 }
158
159 // Section 2.2.2
computeDivisions()160 void LayoutNode::computeDivisions()
161 {
162 if (m_type == TerminalNode)
163 return;
164
165 m_leftChild->computeDivisions();
166 m_rightChild->computeDivisions();
167
168 if (m_type == VerticalDivision) // side by side
169 {
170 double leftDivisionRoot = std::sqrt(m_leftChild->m_e / m_leftChild->m_a);
171 double rightDivisionRoot = std::sqrt(m_rightChild->m_e / m_rightChild->m_a);
172
173 m_division = leftDivisionRoot / (leftDivisionRoot + rightDivisionRoot);
174 }
175 else if (m_type == HorizontalDivision) // one on top of the other
176 {
177 // left child is topmost
178 double leftProductRoot = std::sqrt(m_leftChild->m_a * m_leftChild->m_e);
179 double rightProductRoot = std::sqrt(m_rightChild->m_a * m_rightChild->m_e);
180
181 // the term in the paper takes 0 = bottom, we use 0 = top
182 m_division = 1 - (rightProductRoot / (rightProductRoot + leftProductRoot));
183 }
184 }
185
186 // --------------------------------------------- //
187
LayoutTree(double aspectRatioPage,double absoluteAreaPage)188 LayoutTree::LayoutTree(double aspectRatioPage, double absoluteAreaPage)
189 : m_root(nullptr),
190 m_count(0),
191 m_aspectRatioPage(aspectRatioPage),
192 m_absoluteAreaPage(absoluteAreaPage)
193 {
194 }
195
LayoutTree(const LayoutTree & other)196 LayoutTree::LayoutTree(const LayoutTree& other)
197 {
198 (*this) = other;
199 }
200
operator =(const LayoutTree & other)201 LayoutTree &LayoutTree::operator=(const LayoutTree& other)
202 {
203 delete m_root;
204 m_root = new LayoutNode(*(other.m_root));
205 m_count = other.m_count;
206 m_aspectRatioPage = other.m_aspectRatioPage;
207 m_absoluteAreaPage = other.m_absoluteAreaPage;
208
209 return *this;
210 }
211
~LayoutTree()212 LayoutTree::~LayoutTree()
213 {
214 delete m_root;
215 }
216
addImage(double aspectRatio,double relativeArea)217 int LayoutTree::addImage(double aspectRatio, double relativeArea)
218 {
219 int index = m_count;
220
221 if (!m_root)
222 {
223 m_root = new LayoutNode(aspectRatio, relativeArea, index);
224 m_count++;
225 return index;
226 }
227
228 // Section 2.1
229 LayoutNode* bestTree = nullptr;
230 double highScore = 0;
231
232 for (int i=0; i< m_count; ++i)
233 {
234 for (int horizontal=0; horizontal<2; ++horizontal)
235 {
236 // create temporary tree
237 LayoutNode* candidateTree = new LayoutNode(*m_root);
238
239 // select the subtree which will be replace by a new internal node
240 LayoutNode* const candidateSubtree = candidateTree->nodeForIndex(i);
241
242 // find parent node
243 LayoutNode* const parentNode = candidateTree->parentOf(candidateSubtree);
244
245 // create new terminal node
246 LayoutNode* const newTerminalNode = new LayoutNode(aspectRatio, relativeArea, index);
247
248 // create new internal node
249 LayoutNode* const newInternalNode = new LayoutNode(candidateSubtree, newTerminalNode, horizontal, index+1);
250
251 // replace in tree
252 if (parentNode)
253 parentNode->takeAndSetChild(candidateSubtree, newInternalNode);
254 else // candidateTree is candidateSubtree is root
255 candidateTree = newInternalNode;
256
257 // recompute sizes
258 candidateTree->computeRelativeSizes();
259
260 double candidateScore = score(candidateTree, m_count+2);
261
262 if (candidateScore > highScore)
263 {
264 highScore = candidateScore;
265 delete bestTree;
266 bestTree = candidateTree;
267 }
268 else
269 {
270 delete candidateTree;
271 }
272 }
273 }
274
275 delete m_root;
276 m_root = bestTree;
277
278 if (m_root)
279 m_root->computeDivisions();
280
281 m_count += 2;
282 return index;
283 }
284
285 // Section 2.2.1
score(LayoutNode * root,int nodeCount)286 double LayoutTree::score(LayoutNode* root, int nodeCount)
287 {
288 if (!root)
289 return 0;
290
291 double areaSum = 0;
292
293 for (int i = 0; i<nodeCount; ++i)
294 {
295 LayoutNode* node = root->nodeForIndex(i);
296
297 if (node->type() == LayoutNode::TerminalNode)
298 areaSum += node->relativeArea();
299 }
300
301 double minRatioPage = root->aspectRatio() < m_aspectRatioPage ? root->aspectRatio() : m_aspectRatioPage;
302 double maxRatioPage = root->aspectRatio() > m_aspectRatioPage ? root->aspectRatio() : m_aspectRatioPage;
303
304 return G() * (areaSum / root->relativeArea()) * (minRatioPage / maxRatioPage);
305 }
306
307 // Section 2.2.2
G() const308 double LayoutTree::G() const
309 {
310 return 0.95 * 0.95;
311 }
312
313 // Section 2.2.2
absoluteArea(LayoutNode * node)314 double LayoutTree::absoluteArea(LayoutNode* node)
315 {
316 // min(a_pbb, a_page), max(a_pbb, a_page)
317 double minRatioPage = m_root->aspectRatio() < m_aspectRatioPage ? m_root->aspectRatio() : m_aspectRatioPage;
318 double maxRatioPage = m_root->aspectRatio() > m_aspectRatioPage ? m_root->aspectRatio() : m_aspectRatioPage;
319
320 // A_pbb
321 double absoluteAreaRoot = m_absoluteAreaPage * minRatioPage / maxRatioPage;
322
323 if (node == m_root)
324 return absoluteAreaRoot;
325
326 // A_i
327 return G() * node->relativeArea() / m_root->relativeArea() * absoluteAreaRoot;
328 }
329
drawingArea(int index,const QRectF & absoluteRectPage)330 QRectF LayoutTree::drawingArea(int index, const QRectF& absoluteRectPage)
331 {
332 LayoutNode* const node = m_root->nodeForIndex(index);
333
334 if (!node)
335 return QRectF();
336
337 // find out the "line of ancestry" of the node
338 QList<LayoutNode*> treePath;
339 LayoutNode* parent = node;
340
341 while (parent)
342 {
343 treePath.prepend(parent);
344 parent = m_root->parentOf(parent);
345 }
346
347 // find out the rect of the page bounding box (the rect of the root node in the page rect)
348 QRectF absoluteRect = rectInRect(absoluteRectPage, m_root->aspectRatio(), absoluteArea(m_root));
349
350 // go along the line of ancestry and narrow down the bounding rectangle,
351 // as described in section 2.2.2
352 for (int i=0; i<treePath.count() - 1; ++i)
353 {
354 LayoutNode* const parent = treePath[i];
355 LayoutNode* const child = treePath[i+1]; // only iterating to count-1
356
357 if (parent->type() == LayoutNode::VerticalDivision) // side by side
358 {
359 double leftWidth = absoluteRect.width() * parent->division();
360
361 if (child == parent->leftChild())
362 {
363 absoluteRect.setWidth(leftWidth);
364 }
365 else // right child
366 {
367 double rightWidth = absoluteRect.width() - leftWidth;
368 absoluteRect.setWidth(rightWidth);
369 absoluteRect.translate(leftWidth, 0);
370 }
371 }
372 else // horizontal division: one on top of the other
373 {
374 // left child is topmost
375 double upperHeight = absoluteRect.height() * parent->division();
376
377 if (child == parent->leftChild())
378 {
379 absoluteRect.setHeight(upperHeight);
380 }
381 else // right child
382 {
383 double lowerHeight = absoluteRect.height() - upperHeight;
384 absoluteRect.setHeight(lowerHeight);
385 absoluteRect.translate(0, upperHeight);
386 }
387 }
388 }
389
390 return rectInRect(absoluteRect, node->aspectRatio(), absoluteArea(node));
391 }
392
393 // lays out a rectangle with given aspect ratio and absolute area inside the given larger rectangle
394 // (not in the paper)
rectInRect(const QRectF & rect,double aspectRatio,double absoluteArea)395 QRectF LayoutTree::rectInRect(const QRectF &rect, double aspectRatio, double absoluteArea)
396 {
397 double width = std::sqrt(absoluteArea / aspectRatio);
398 double height = std::sqrt(absoluteArea * aspectRatio);
399 double x = rect.x() + (rect.width() - width) / 2;
400 double y = rect.y() + (rect.height() - height) / 2;
401 return QRectF(x,y,width, height);
402 }
403
404 } // NameSpace KIPIPrintImagesPlugin
405