1 // Copyright 2019 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "aemu/base/LayoutResolver.h"
16
17 #include <algorithm>
18 #include <cmath>
19 #include <stdint.h>
20 #include <vector>
21
22 namespace android {
23 namespace base {
24
25 /*
26 * An internal struct used for describing a rectangle that could contain a list
27 *of "children" rectangles that has to fit in the "parent" rectangle.
28 */
29 struct Rect {
30 uint32_t id;
31 uint32_t pos_x;
32 uint32_t pos_y;
33 uint32_t width;
34 uint32_t height;
35 // indicates that this rectangle is a child of some parent rectangle.
36 bool isChild;
37
38 std::vector<uint32_t> children;
Rectandroid::base::Rect39 Rect(uint32_t i, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
40 : id(i), pos_x(x), pos_y(y), width(w), height(h), isChild(false) {}
41 };
42
computeScore(const uint32_t combinedWidth,const uint32_t combinedHeight,const uint32_t firstRowWidth,const uint32_t secondRowWidth,const double monitorAspectRatio)43 static double computeScore(const uint32_t combinedWidth,
44 const uint32_t combinedHeight,
45 const uint32_t firstRowWidth,
46 const uint32_t secondRowWidth,
47 const double monitorAspectRatio) {
48 if (firstRowWidth == 0 || secondRowWidth == 0) {
49 return std::pow((double)combinedHeight / (double)combinedWidth -
50 monitorAspectRatio,
51 2);
52 } else {
53 return std::pow((double)firstRowWidth / (double)secondRowWidth - 1.0,
54 2) +
55 std::pow((double)combinedHeight / (double)combinedWidth -
56 monitorAspectRatio,
57 2);
58 }
59 }
60 /*
61 * Compute the coordinates for each rectangle using the lower left corner as
62 * origin. Save the coordinates in @param retVal using id as the key.
63 */
64 static std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>>
computeCoordinatesPerRow(std::vector<Rect> & row,uint32_t yOffset,std::unordered_map<uint32_t,std::pair<uint32_t,uint32_t>> & displays)65 computeCoordinatesPerRow(
66 std::vector<Rect>& row,
67 uint32_t yOffset,
68 std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>>& displays) {
69 std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> retVal;
70 std::stable_sort(row.begin(), row.end(), [](const Rect& a, const Rect& b) {
71 return a.height > b.height || (a.height == b.height && a.id < b.id);
72 });
73 uint32_t width = 0;
74 for (const auto& iter : row) {
75 uint32_t displayId = iter.id;
76 retVal[displayId] = std::make_pair(width, yOffset);
77 uint32_t cumulativeHeight = displays[displayId].second + yOffset;
78 for (auto id : iter.children) {
79 retVal[id] = std::make_pair(width, cumulativeHeight);
80 cumulativeHeight += displays[id].second;
81 }
82 width += iter.width;
83 }
84 return retVal;
85 }
86
resolveLayout(std::unordered_map<uint32_t,std::pair<uint32_t,uint32_t>> rect,const double monitorAspectRatio)87 std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> resolveLayout(
88 std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> rect,
89 const double monitorAspectRatio) {
90 // Combine smaller rectangles into bigger ones by trying every pair of
91 // rectangles but always maintain these two invariants. 1, Always start
92 // combining from rectangle with smaller width. 2, The height of combined
93 // rectangle should not exceed the max height of all the original
94 // rectangles. When two smaller rectangles form a new one, update the
95 // rectangle with bigger width as the newly-formed rectangle, the smaller
96 // one is marked as children.
97
98 std::vector<Rect> rectangles;
99 uint32_t maxHeight = 0;
100 for (const auto& iter : rect) {
101 uint32_t width = iter.second.first;
102 uint32_t height = iter.second.second;
103 rectangles.emplace_back(iter.first, 0, 0, width, height);
104 maxHeight = std::max(maxHeight, height);
105 }
106
107 std::stable_sort(
108 rectangles.begin(), rectangles.end(),
109 [](const Rect& a, const Rect& b) { return a.width < b.width || (a.width == b.width && a.id > b.id); });
110
111 for (int i = 0; i < rectangles.size(); i++) {
112 for (int j = i + 1; j < rectangles.size(); j++) {
113 if (rectangles[i].height + rectangles[j].height <= maxHeight) {
114 rectangles[i].isChild = true;
115 rectangles[j].children.push_back(rectangles[i].id);
116 if (!rectangles[i].children.empty()) {
117 for (uint32_t it : rectangles[i].children) {
118 rectangles[j].children.push_back(it);
119 }
120 }
121 rectangles[j].height += rectangles[i].height;
122 break;
123 }
124 }
125 }
126
127 // Delete rectangles that are chidlren of others.
128 for (auto it = rectangles.begin(); it != rectangles.end();) {
129 if (it->isChild) {
130 it = rectangles.erase(it);
131 } else {
132 ++it;
133 }
134 }
135
136 // Try every enumeration of placing a rectangle into either first or second
137 // row to find the best fit. We try to balance two factors: 1, the combined
138 // rectangle's aspect ratio will be close to the monitor screen's aspect
139 // ratio. 2, the combined width of the first row will be close to the
140 // combined width of the second row. After the rectangles are placed into
141 // two rows, in each row, the rectangles will be sorted based on height.
142 double bestScore = 0;
143 int bestScoreBitSet = -1;
144 for (int bitset = 0; bitset < 1 << (rectangles.size() - 1); bitset++) {
145 uint32_t firstRowWidth = 0;
146 uint32_t secondRowWidth = 0;
147 uint32_t firstRowHeight = 0;
148 uint32_t secondRowHeight = 0;
149 for (int idx = 0; idx < rectangles.size(); idx++) {
150 if ((1 << idx) & bitset) {
151 firstRowWidth += rectangles[idx].width;
152 firstRowHeight =
153 std::max(firstRowHeight, rectangles[idx].height);
154 } else {
155 secondRowWidth += rectangles[idx].width;
156 secondRowHeight =
157 std::max(secondRowHeight, rectangles[idx].height);
158 }
159 }
160 uint32_t combinedHeight = firstRowHeight + secondRowHeight;
161 uint32_t combinedWidth = std::max(firstRowWidth, secondRowWidth);
162 double score =
163 computeScore(combinedWidth, combinedHeight, firstRowWidth,
164 secondRowWidth, monitorAspectRatio);
165 if (bestScoreBitSet == -1 || score < bestScore) {
166 bestScoreBitSet = bitset;
167 bestScore = score;
168 }
169 }
170
171 std::vector<Rect> firstRow, secondRow;
172 for (int idx = 0; idx < rectangles.size(); idx++) {
173 if ((1 << idx) & bestScoreBitSet) {
174 firstRow.push_back(std::move(rectangles[idx]));
175 } else {
176 secondRow.push_back(std::move(rectangles[idx]));
177 }
178 }
179
180 std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> retVal;
181 uint32_t yOffset = 0;
182 auto resFirstRow = computeCoordinatesPerRow(firstRow, yOffset, rect);
183 retVal.insert(resFirstRow.begin(), resFirstRow.end());
184 uint32_t firstRowHeight = (firstRow.empty() ? 0 : firstRow[0].height);
185
186 yOffset = firstRowHeight;
187 auto resSecRow = computeCoordinatesPerRow(secondRow, yOffset, rect);
188 retVal.insert(resSecRow.begin(), resSecRow.end());
189
190 return retVal;
191 }
192
193 } // namespace base
194 } // namespace android
195