GUAC-240: New rectangle helper methods and utilities which will be used by adaptive encoding. Rectangle helper unit tests.

This commit is contained in:
Frode Langelo 2015-08-06 07:59:51 -07:00
parent 0d1396c815
commit 385808c7a1
6 changed files with 547 additions and 0 deletions

View File

@ -80,3 +80,190 @@ void guac_common_rect_constrain(guac_common_rect* rect, const guac_common_rect*
} }
int guac_common_rect_expand_to_grid(int cell_size, guac_common_rect* rect,
const guac_common_rect* max_rect) {
/* Invalid cell_size received */
if (cell_size <= 0)
return -1;
/* Nothing to do */
if (cell_size == 1)
return 0;
/* Calculate how much the rectangle must be adjusted to fit within the
* given cell size. */
int dw = cell_size - rect->width % cell_size;
int dh = cell_size - rect->height % cell_size;
int dx = dw / 2;
int dy = dh / 2;
/* Set initial extents of adjusted rectangle. */
int top = rect->y - dy;
int left = rect->x - dx;
int bottom = top + rect->height + dh;
int right = left + rect->width + dw;
/* The max rectangle */
int max_left = max_rect->x;
int max_top = max_rect->y;
int max_right = max_left + max_rect->width;
int max_bottom = max_top + max_rect->height;
/* If the adjusted rectangle has sides beyond the max rectangle, or is larger
* in any direction; shift or adjust the rectangle while trying to fit in
* the grid */
/* Adjust left/right */
if (right > max_right) {
/* shift to left */
dw = right - max_right;
right -= dw;
left -= dw;
/* clamp left if too far */
if (left < max_left) {
left = max_left;
}
}
else if (left < max_left) {
/* shift to right */
dw = max_left - left;
left += dw;
right += dw;
/* clamp right if too far */
if (right > max_right) {
right = max_right;
}
}
/* Adjust top/bottom */
if (bottom > max_bottom) {
/* shift up */
dh = bottom - max_bottom;
bottom -= dh;
top -= dh;
/* clamp top if too far */
if (top < max_top) {
top = max_top;
}
}
else if (top < max_top) {
/* shift down */
dh = max_top - top;
top += dh;
bottom += dh;
/* clamp bottom if too far */
if (bottom > max_bottom) {
bottom = max_bottom;
}
}
/* Commit rect */
guac_common_rect_init(rect, left, top, right - left, bottom - top);
return 0;
}
int guac_common_rect_intersects(const guac_common_rect* rect,
const guac_common_rect* other) {
/* Empty (no intersection) */
if (other->x + other->width < rect->x || rect->x + rect->width < other->x ||
other->y + other->height < rect->y || rect->y + rect->height < other->y) {
return 0;
}
/* Complete */
else if (other->x <= rect->x && (other->x + other->width) >= (rect->x + rect->width) &&
other->y <= rect->y && (other->y + other->height) >= (rect->y + rect->height)) {
return 2;
}
/* Partial intersection */
return 1;
}
int guac_common_rect_clip_and_split(guac_common_rect* rect,
const guac_common_rect* hole, guac_common_rect* split_rect) {
/* Only continue if the rectangles intersects */
if (!guac_common_rect_intersects(rect, hole))
return 0;
int top, left, bottom, right;
/* Clip and split top */
if (rect->y < hole->y) {
top = rect->y;
left = rect->x;
bottom = hole->y;
right = rect->x + rect->width;
guac_common_rect_init(split_rect, left, top, right - left, bottom - top);
/* Re-initialize original rect */
top = hole->y;
bottom = rect->y + rect->height;
guac_common_rect_init(rect, left, top, right - left, bottom - top);
return 1;
}
/* Clip and split left */
else if (rect->x < hole->x) {
top = rect->y;
left = rect->x;
bottom = rect->y + rect->height;
right = hole->x;
guac_common_rect_init(split_rect, left, top, right - left, bottom - top);
/* Re-initialize original rect */
left = hole->x;
right = rect->x + rect->width;
guac_common_rect_init(rect, left, top, right - left, bottom - top);
return 1;
}
/* Clip and split bottom */
else if (rect->y + rect->height > hole->y + hole->height) {
top = hole->y + hole->height;
left = rect->x;
bottom = rect->y + rect->height;
right = rect->x + rect->width;
guac_common_rect_init(split_rect, left, top, right - left, bottom - top);
/* Re-initialize original rect */
top = rect->y;
bottom = hole->y + hole->height;
guac_common_rect_init(rect, left, top, right - left, bottom - top);
return 1;
}
/* Clip and split right */
else if (rect->x + rect->width > hole->x + hole->width) {
top = rect->y;
left = hole->x + hole->width;
bottom = rect->y + rect->height;
right = rect->x + rect->width;
guac_common_rect_init(split_rect, left, top, right - left, bottom - top);
/* Re-initialize original rect */
left = rect->x;
right = hole->x + hole->width;
guac_common_rect_init(rect, left, top, right - left, bottom - top);
return 1;
}
return 0;
}

View File

@ -63,6 +63,27 @@ typedef struct guac_common_rect {
*/ */
void guac_common_rect_init(guac_common_rect* rect, int x, int y, int width, int height); void guac_common_rect_init(guac_common_rect* rect, int x, int y, int width, int height);
/**
* Expand the rectangle to fit an NxN grid.
*
* The rectangle will be shifted to the left and up, expanded and adjusted to
* fit within the max bounding rect.
*
* @param cell_size
* The (NxN) grid cell size.
*
* @param rect
* The rectangle to adjust.
*
* @param max_rect
* The bounding area in which the given rect can exist.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_common_rect_expand_to_grid(int cell_size, guac_common_rect* rect,
const guac_common_rect* max_rect);
/** /**
* Extend the given rect such that it contains at least the specified minimum * Extend the given rect such that it contains at least the specified minimum
* rect. * rect.
@ -81,5 +102,45 @@ void guac_common_rect_extend(guac_common_rect* rect, const guac_common_rect* min
*/ */
void guac_common_rect_constrain(guac_common_rect* rect, const guac_common_rect* max); void guac_common_rect_constrain(guac_common_rect* rect, const guac_common_rect* max);
/**
* Check whether a rectangle intersects another.
*
* @param rect
* Rectangle to check for intersection.
*
* @param other
* The other rectangle.
*
* @return
* Zero if no intersection, 1 if partial intersection,
* 2 if first rect is completely inside the other.
*/
int guac_common_rect_intersects(const guac_common_rect* rect,
const guac_common_rect* other);
/**
* Clip and split a rectangle into rectangles which are not covered by the
* hole rectangle.
*
* This function will clip and split single edges when executed and must be
* invoked until it returns zero. The edges are handled counter-clockwise
* starting at the top edge.
*
* @param rect
* The rectangle to be split. This rectangle will be clipped by the
* split_rect.
*
* @param hole
* The rectangle which represents the hole.
*
* @param split_rect
* Resulting split rectangle.
*
* @return
* Zero when no splits were done, non-zero when the rectangle was split.
*/
int guac_common_rect_clip_and_split(guac_common_rect* rect,
const guac_common_rect* hole, guac_common_rect* split_rect);
#endif #endif

View File

@ -40,6 +40,7 @@ test_libguac_SOURCES = \
common/common_suite.c \ common/common_suite.c \
common/guac_iconv.c \ common/guac_iconv.c \
common/guac_string.c \ common/guac_string.c \
common/guac_rect.c \
protocol/suite.c \ protocol/suite.c \
protocol/base64_decode.c \ protocol/base64_decode.c \
protocol/instruction_parse.c \ protocol/instruction_parse.c \

View File

@ -48,6 +48,7 @@ int register_common_suite() {
if ( if (
CU_add_test(suite, "guac-iconv", test_guac_iconv) == NULL CU_add_test(suite, "guac-iconv", test_guac_iconv) == NULL
|| CU_add_test(suite, "guac-string", test_guac_string) == NULL || CU_add_test(suite, "guac-string", test_guac_string) == NULL
|| CU_add_test(suite, "guac-rect", test_guac_rect) == NULL
) { ) {
CU_cleanup_registry(); CU_cleanup_registry();
return CU_get_error(); return CU_get_error();

View File

@ -49,5 +49,10 @@ void test_guac_string();
*/ */
void test_guac_iconv(); void test_guac_iconv();
/**
* Unit test for rectangle calculation functions.
*/
void test_guac_rect();
#endif #endif

292
tests/common/guac_rect.c Normal file
View File

@ -0,0 +1,292 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "common_suite.h"
#include "guac_rect.h"
#include <stdlib.h>
#include <stdio.h>
#include <CUnit/Basic.h>
void test_guac_rect() {
guac_common_rect max;
/*
* Test init method
*/
guac_common_rect_init(&max, 0, 0, 100, 100);
CU_ASSERT_EQUAL(0, max.x);
CU_ASSERT_EQUAL(0, max.y);
CU_ASSERT_EQUAL(100, max.width);
CU_ASSERT_EQUAL(100, max.height);
/*
* Test constrain method
*/
guac_common_rect rect;
guac_common_rect_init(&rect, -10, -10, 110, 110);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_constrain(&rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(100, rect.width);
CU_ASSERT_EQUAL(100, rect.height);
/*
* Test extend method
*/
guac_common_rect_init(&rect, 10, 10, 90, 90);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_extend(&rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(100, rect.width);
CU_ASSERT_EQUAL(100, rect.height);
/*
* Test adjust method
*/
int cell_size = 16;
/* Simple adjustment */
guac_common_rect_init(&rect, 0, 0, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
/* Adjustment with moving of rect */
guac_common_rect_init(&rect, 75, 75, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(max.width - 32, rect.x);
CU_ASSERT_EQUAL(max.height - 32, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
guac_common_rect_init(&rect, -5, -5, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
/* Adjustment with moving and clamping of rect */
guac_common_rect_init(&rect, 0, 0, 25, 15);
guac_common_rect_init(&max, 0, 5, 32, 15);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(max.x, rect.x);
CU_ASSERT_EQUAL(max.y, rect.y);
CU_ASSERT_EQUAL(max.width, rect.width);
CU_ASSERT_EQUAL(max.height, rect.height);
/*
* Rectangle intersection tests
*/
guac_common_rect min;
guac_common_rect_init(&min, 10, 10, 10, 10);
/* Rectangle intersection - empty
* rectangle is outside */
guac_common_rect_init(&rect, 25, 25, 5, 5);
int res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(0, res);
/* Rectangle intersection - complete
* rectangle is completely inside */
guac_common_rect_init(&rect, 11, 11, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects UL */
guac_common_rect_init(&rect, 8, 8, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - partial
* rectangle intersects LR */
guac_common_rect_init(&rect, 18, 18, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - complete
* rect intersects along UL but inside */
guac_common_rect_init(&rect, 10, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects along L but outside */
guac_common_rect_init(&rect, 5, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - complete
* rectangle intersects along LR but rest is inside */
guac_common_rect_init(&rect, 15, 15, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects along R but rest is outside */
guac_common_rect_init(&rect, 20, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - partial
* rectangle encloses min; which is a partial intersection */
guac_common_rect_init(&rect, 5, 5, 20, 20);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/*
* Basic test of clip and split method
*/
guac_common_rect_init(&min, 10, 10, 10, 10);
guac_common_rect cut;
/* Clip top */
guac_common_rect_init(&rect, 10, 5, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(5, cut.y);
CU_ASSERT_EQUAL(10, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(5, rect.height);
/* Clip bottom */
guac_common_rect_init(&rect, 10, 15, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(20, cut.y);
CU_ASSERT_EQUAL(10, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(15, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(5, rect.height);
/* Clip left */
guac_common_rect_init(&rect, 5, 10, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(5, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Clip right */
guac_common_rect_init(&rect, 15, 10, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(20, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(15, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(5, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/*
* Test a rectangle which completely covers the hole.
* Clip and split until done.
*/
guac_common_rect_init(&rect, 5, 5, 20, 20);
/* Clip top */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(5, cut.y);
CU_ASSERT_EQUAL(20, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(5, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(20, rect.width);
CU_ASSERT_EQUAL(15, rect.height);
/* Clip left */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(15, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(15, rect.width);
CU_ASSERT_EQUAL(15, rect.height);
/* Clip bottom */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(20, cut.y);
CU_ASSERT_EQUAL(15, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(15, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Clip right */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(20, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Make sure nothing is left to do */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(0, res);
}