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