blob: ad6ac42496eb23606926a1f5ce498c2402b0acc5 [file] [log] [blame]
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +00001/* Copyright (C) 2004-2007 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00002 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18//
19// PollingManager.cxx
20//
21
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000022#include <stdio.h>
23#include <string.h>
24#include <time.h>
25#include <X11/Xlib.h>
26#include <rfb/LogWriter.h>
27#include <rfb/VNCServer.h>
28#include <rfb/Configuration.h>
29#include <rfb/ServerCore.h>
30
31#include <x0vncserver/PollingManager.h>
32
33static LogWriter vlog("PollingMgr");
34
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000035const int PollingManager::m_pollingOrder[32] = {
36 0, 16, 8, 24, 4, 20, 12, 28,
37 10, 26, 18, 2, 22, 6, 30, 14,
38 1, 17, 9, 25, 7, 23, 15, 31,
39 19, 3, 27, 11, 29, 13, 5, 21
40};
41
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +000042// FIXME: Check that the parameter's value is in the allowed range.
43// This applies to all other parameters as well.
Constantin Kaplinsky1d378022007-12-25 17:43:09 +000044IntParameter PollingManager::m_videoPriority("VideoPriority",
45 "Priority of sending updates for video area (0..8)", 2);
46
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000047//
48// Constructor.
49//
50// Note that dpy and image should remain valid during the object
51// lifetime, while factory is used only in the constructor itself.
52//
Constantin Kaplinsky936c3692007-12-27 08:42:53 +000053// FIXME: Pass XPixelBuffer* instead of Image*.
54//
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000055
56PollingManager::PollingManager(Display *dpy, Image *image,
57 ImageFactory *factory,
58 int offsetLeft, int offsetTop)
59 : m_dpy(dpy), m_server(0), m_image(image),
60 m_offsetLeft(offsetLeft), m_offsetTop(offsetTop),
Constantin Kaplinsky1d378022007-12-25 17:43:09 +000061 m_numVideoPasses(0),
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +000062 m_pollingStep(0)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000063{
64 // Save width and height of the screen (and the image).
65 m_width = m_image->xim->width;
66 m_height = m_image->xim->height;
67
68 // Compute width and height in 32x32 tiles.
69 m_widthTiles = (m_width + 31) / 32;
70 m_heightTiles = (m_height + 31) / 32;
71
72 // Get initial screen image.
73 m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop);
74
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +000075 // Create additional images used in polling algorithm, warn if
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000076 // underlying class names are different from the class name of the
77 // primary image.
78 m_rowImage = factory->newImage(m_dpy, m_width, 1);
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +000079 if (strcmp(m_image->className(), m_rowImage->className()) != 0) {
80 vlog.error("Image types do not match (%s)",
81 m_rowImage->className());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000082 }
83
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000084 int numTiles = m_widthTiles * m_heightTiles;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000085 m_rateMatrix = new char[numTiles];
86 m_videoFlags = new char[numTiles];
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000087 memset(m_rateMatrix, 0, numTiles);
88 memset(m_videoFlags, 0, numTiles);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000089}
90
91PollingManager::~PollingManager()
92{
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000093 delete[] m_videoFlags;
94 delete[] m_rateMatrix;
95
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000096 delete m_rowImage;
97}
98
99//
100// Register VNCServer object.
101//
102
103void PollingManager::setVNCServer(VNCServer *s)
104{
105 m_server = s;
106}
107
108//
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000109// DEBUG: Measuring time spent in the poll() function,
110// as well as time intervals between poll() calls.
111//
112
113#ifdef DEBUG
114void PollingManager::debugBeforePoll()
115{
116 TimeMillis timeNow;
117 int diff = timeNow.diffFrom(m_timeSaved);
118 fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32);
119 m_timeSaved = timeNow;
120}
121
122void PollingManager::debugAfterPoll()
123{
124 TimeMillis timeNow;
125 int diff = timeNow.diffFrom(m_timeSaved);
126 fprintf(stderr, "[poll%4dms]\n", diff);
127 m_timeSaved = timeNow;
128}
129
130#endif
131
132//
133// Search for changed rectangles on the screen.
134//
135
136void PollingManager::poll()
137{
138#ifdef DEBUG
139 debugBeforePoll();
140#endif
141
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +0000142 // Perform polling and try update clients if changes were detected.
143 if (pollScreen())
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000144 m_server->tryUpdate();
145
146#ifdef DEBUG
147 debugAfterPoll();
148#endif
149}
150
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +0000151bool PollingManager::pollScreen()
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000152{
153 if (!m_server)
154 return false;
155
Constantin Kaplinsky1d378022007-12-25 17:43:09 +0000156 // If video data should have higher priority, and video area was
157 // detected, perform special passes to send video data only. Such
158 // "video passes" will be performed between normal polling passes.
159 // No actual polling is performed in a video pass since we know that
160 // video is changing continuously.
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +0000161 //
162 // FIXME: Should we move this block into a separate function?
163 // FIXME: Giving higher priority to video area lengthens video
164 // detection cycles. Should we do something with that?
Constantin Kaplinsky1d378022007-12-25 17:43:09 +0000165 if ((int)m_videoPriority > 1 && !m_videoRect.is_empty()) {
166 if (m_numVideoPasses > 0) {
167 m_numVideoPasses--;
168 getScreenRect(m_videoRect);
169 return true; // we've got changes
170 } else {
171 // Normal pass now, but schedule video passes for next calls.
172 m_numVideoPasses = (int)m_videoPriority - 1;
173 }
174 }
175
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000176 // changeFlags[] array will hold boolean values corresponding to
177 // each 32x32 tile. If a value is true, then we've detected a change
178 // in that tile. Initially, we fill in the array with zero values.
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +0000179 //
180 // FIXME: Should we use a member variable in place of changeFlags?
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000181 bool *changeFlags = new bool[m_widthTiles * m_heightTiles];
182 memset(changeFlags, 0, m_widthTiles * m_heightTiles * sizeof(bool));
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000183
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000184 // First pass over the framebuffer. Here we scan 1/32 part of the
185 // framebuffer -- that is, one line in each (32 * m_width) stripe.
186 // We compare the pixels of that line with previous framebuffer
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000187 // contents and raise corresponding member values of changeFlags[].
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000188 int scanOffset = m_pollingOrder[m_pollingStep++ % 32];
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000189 bool *pChangeFlags = changeFlags;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000190 int nTilesChanged = 0;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000191 for (int y = scanOffset; y < m_height; y += 32) {
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000192 nTilesChanged += checkRow(0, y, m_width, pChangeFlags);
193 pChangeFlags += m_widthTiles;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000194 }
195
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000196 // Do the work related to video area detection.
Constantin Kaplinsky1d378022007-12-25 17:43:09 +0000197 bool haveVideoRect = false;
198 if ((int)m_videoPriority != 0)
199 haveVideoRect = handleVideo(changeFlags);
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000200
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000201 // Inform the server about the changes.
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +0000202 //
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000203 // FIXME: It's possible that (nTilesChanged != 0) but changeFlags[]
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000204 // array is empty. That's possible because handleVideo()
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000205 // modifies changeFlags[].
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000206 if (nTilesChanged)
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000207 sendChanges(changeFlags);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000208
209 // Cleanup.
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000210 delete[] changeFlags;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000211
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000212#ifdef DEBUG
213 if (nTilesChanged != 0) {
214 fprintf(stderr, "#%d# ", nTilesChanged);
215 }
216#endif
217
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000218 return (nTilesChanged != 0 || haveVideoRect);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000219}
220
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000221int PollingManager::checkRow(int x, int y, int w, bool *pChangeFlags)
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000222{
223 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
224 int bytesPerLine = m_image->xim->bytes_per_line;
225
Constantin Kaplinsky29d32052007-10-08 14:16:02 +0000226 if (x == 0 && w == m_width) {
227 getFullRow(y); // use more efficient method if possible
228 } else {
229 getRow(x, y, w);
230 }
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000231
232 char *ptr_old = m_image->xim->data + y * bytesPerLine + x * bytesPerPixel;
233 char *ptr_new = m_rowImage->xim->data;
234
235 int nTilesChanged = 0;
236 for (int i = 0; i < (w + 31) / 32; i++) {
237 int tile_w = (w - i * 32 >= 32) ? 32 : w - i * 32;
238 int nBytes = tile_w * bytesPerPixel;
239 if (memcmp(ptr_old, ptr_new, nBytes)) {
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000240 *pChangeFlags = true;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000241 nTilesChanged++;
242 }
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000243 pChangeFlags++;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000244 ptr_old += nBytes;
245 ptr_new += nBytes;
246 }
247
248 return nTilesChanged;
249}
250
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000251void PollingManager::sendChanges(bool *pChangeFlags)
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000252{
253 Rect rect;
254 for (int y = 0; y < m_heightTiles; y++) {
255 for (int x = 0; x < m_widthTiles; x++) {
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000256 if (*pChangeFlags++) {
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000257 // Count successive tiles marked as changed.
258 int count = 1;
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000259 while (x + count < m_widthTiles && *pChangeFlags++) {
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000260 count++;
261 }
262 // Compute the coordinates and the size of this band.
263 rect.setXYWH(x * 32, y * 32, count * 32, 32);
264 if (rect.br.x > m_width)
265 rect.br.x = m_width;
266 if (rect.br.y > m_height)
267 rect.br.y = m_height;
268 // Add to the changed region maintained by the server.
269 getScreenRect(rect);
270 m_server->add_changed(rect);
271 // Skip processed tiles.
272 x += count;
273 }
274 }
275 }
276}
277
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000278bool PollingManager::handleVideo(bool *pChangeFlags)
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000279{
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000280 // Update counters in m_rateMatrix.
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000281 int numTiles = m_heightTiles * m_widthTiles;
282 for (int i = 0; i < numTiles; i++)
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000283 m_rateMatrix[i] += (pChangeFlags[i] != false);
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000284
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000285 // Once per eight calls: detect video rectangle by examining
286 // m_rateMatrix[], then reset counters in m_rateMatrix[].
287 if (m_pollingStep % 8 == 0) {
288 detectVideo();
289 memset(m_rateMatrix, 0, numTiles);
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000290 }
291
Constantin Kaplinsky04e910b2007-12-25 18:10:35 +0000292 // FIXME: It looks like the code below rather belongs to
293 // pollScreen(). Perhaps handleVideo() should be merged back
294 // to pollScreen(), and then pollScreen() should be split in
295 // some better way, if needed at all.
296
Constantin Kaplinsky646998a2007-10-09 09:31:41 +0000297 // Grab the pixels of video area. Also, exclude video rectangle from
Constantin Kaplinsky808db552007-10-09 12:48:26 +0000298 // pChangeFlags[], to prevent grabbing the same pixels twice.
Constantin Kaplinsky646998a2007-10-09 09:31:41 +0000299 if (!m_videoRect.is_empty()) {
Constantin Kaplinskybd390352007-12-28 08:44:59 +0000300 flagVideoArea(pChangeFlags, false);
Constantin Kaplinsky646998a2007-10-09 09:31:41 +0000301 getScreenRect(m_videoRect);
302 return true; // we've got a video rectangle
303 }
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000304
Constantin Kaplinsky646998a2007-10-09 09:31:41 +0000305 return false; // video rectangle is empty
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000306}
307
Constantin Kaplinskybd390352007-12-28 08:44:59 +0000308void PollingManager::flagVideoArea(bool *pChangeFlags, bool value)
309{
310 Rect r(m_videoRect.tl.x / 32, m_videoRect.tl.y / 32,
311 m_videoRect.br.x / 32, m_videoRect.br.y / 32);
312
313 for (int y = r.tl.y; y < r.br.y; y++)
314 for (int x = r.tl.x; x < r.br.x; x++)
315 pChangeFlags[y * m_widthTiles + x] = value;
316}
317
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000318void
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000319PollingManager::detectVideo()
320{
321 // Configurable parameters.
322 const int VIDEO_THRESHOLD_0 = 3;
323 const int VIDEO_THRESHOLD_1 = 5;
324
Constantin Kaplinskybb563772007-12-25 11:25:07 +0000325 // In m_rateMatrix, clear counters corresponding to non-32x32 tiles.
326 // This will guarantee that the size of the video area is always a
327 // multiple of 32 pixels. This is important for hardware JPEG encoders.
328 int numTiles = m_heightTiles * m_widthTiles;
329 if (m_width % 32 != 0) {
330 for (int n = m_widthTiles - 1; n < numTiles; n += m_widthTiles)
331 m_rateMatrix[n] = 0;
332 }
333 if (m_height % 32 != 0) {
334 for (int n = numTiles - m_widthTiles; n < numTiles; n++)
335 m_rateMatrix[n] = 0;
336 }
337
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000338 // First, detect candidate region that looks like video. In other
339 // words, find a region that consists of continuously changing
340 // pixels. Save the result in m_videoFlags[].
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000341 for (int i = 0; i < numTiles; i++) {
342 if (m_rateMatrix[i] <= VIDEO_THRESHOLD_0) {
343 m_videoFlags[i] = 0;
344 } else if (m_rateMatrix[i] >= VIDEO_THRESHOLD_1) {
345 m_videoFlags[i] = 1;
346 }
347 }
348
349 // Now, choose the biggest rectangle from that candidate region.
350 Rect newRect;
351 getVideoAreaRect(&newRect);
352
353 // Does new rectangle differ from the previously detected one?
354 // If it does, save new rectangle and inform the server.
355 if (!newRect.equals(m_videoRect)) {
356 if (newRect.is_empty()) {
Constantin Kaplinskydab9a562007-10-10 01:33:39 +0000357 vlog.debug("No video detected");
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000358 } else {
Constantin Kaplinskydab9a562007-10-10 01:33:39 +0000359 vlog.debug("Detected video %dx%d at (%d,%d)",
360 newRect.width(), newRect.height(),
361 newRect.tl.x, newRect.tl.y);
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000362 }
363 m_videoRect = newRect;
364 m_server->set_video_area(newRect);
365 }
366}
367
368void
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000369PollingManager::getVideoAreaRect(Rect *result)
370{
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000371 int *mx_hlen, *mx_vlen;
372 constructLengthMatrices(&mx_hlen, &mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000373
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000374 int full_h = m_heightTiles;
375 int full_w = m_widthTiles;
376 int x, y;
377 Rect max_rect(0, 0, 0, 0);
378 Rect local_rect;
379
380 for (y = 0; y < full_h; y++) {
381 for (x = 0; x < full_w; x++) {
382 int max_w = mx_hlen[y * full_w + x];
383 int max_h = mx_vlen[y * full_w + x];
384 if (max_w > 2 && max_h > 1 && max_h * max_w > (int)max_rect.area()) {
385 local_rect.tl.x = x;
386 local_rect.tl.y = y;
387 findMaxLocalRect(&local_rect, mx_hlen, mx_vlen);
388 if (local_rect.area() > max_rect.area()) {
389 max_rect = local_rect;
390 }
391 }
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000392 }
393 }
394
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000395 destroyLengthMatrices(mx_hlen, mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000396
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000397 max_rect.tl.x *= 32;
398 max_rect.tl.y *= 32;
399 max_rect.br.x *= 32;
400 max_rect.br.y *= 32;
401 if (max_rect.br.x > m_width)
402 max_rect.br.x = m_width;
403 if (max_rect.br.y > m_height)
404 max_rect.br.y = m_height;
405 *result = max_rect;
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000406}
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000407
408void
409PollingManager::constructLengthMatrices(int **pmx_h, int **pmx_v)
410{
411 // Handy shortcuts.
412 int h = m_heightTiles;
413 int w = m_widthTiles;
414
415 // Allocate memory.
416 int *mx_h = new int[h * w];
417 memset(mx_h, 0, h * w * sizeof(int));
418 int *mx_v = new int[h * w];
419 memset(mx_v, 0, h * w * sizeof(int));
420
421 int x, y, len, i;
422
423 // Fill in horizontal length matrix.
424 for (y = 0; y < h; y++) {
425 for (x = 0; x < w; x++) {
426 len = 0;
427 while (x + len < w && m_videoFlags[y * w + x + len]) {
428 len++;
429 }
430 for (i = 0; i < len; i++) {
431 mx_h[y * w + x + i] = len - i;
432 }
433 x += len;
434 }
435 }
436
437 // Fill in vertical length matrix.
438 for (x = 0; x < w; x++) {
439 for (y = 0; y < h; y++) {
440 len = 0;
441 while (y + len < h && m_videoFlags[(y + len) * w + x]) {
442 len++;
443 }
444 for (i = 0; i < len; i++) {
445 mx_v[(y + i) * w + x] = len - i;
446 }
447 y += len;
448 }
449 }
450
451 *pmx_h = mx_h;
452 *pmx_v = mx_v;
453}
454
455void
456PollingManager::destroyLengthMatrices(int *mx_h, int *mx_v)
457{
458 delete[] mx_h;
459 delete[] mx_v;
460}
461
462// NOTE: This function assumes that current tile has non-zero in mx_h[],
463// otherwise we get division by zero.
464void
465PollingManager::findMaxLocalRect(Rect *r, int mx_h[], int mx_v[])
466{
467 int idx = r->tl.y * m_widthTiles + r->tl.x;
468
469 // NOTE: Rectangle's maximum width and height are 25 and 18
470 // (in tiles, where each tile is usually 32x32 pixels).
471 int max_w = mx_h[idx];
472 if (max_w > 25)
473 max_w = 25;
474 int cur_h = 18;
475
476 int best_w = max_w;
477 int best_area = 1 * best_w;
478
479 for (int i = 0; i < max_w; i++) {
480 int h = mx_v[idx + i];
481 if (h < cur_h) {
482 cur_h = h;
483 if (cur_h * max_w <= best_area)
484 break;
485 }
486 if (cur_h * (i + 1) > best_area) {
487 best_w = i + 1;
488 best_area = cur_h * best_w;
489 }
490 }
491
492 r->br.x = r->tl.x + best_w;
493 r->br.y = r->tl.y + best_area / best_w;
494}
495