diff --git a/src/common/guac_rect.c b/src/common/guac_rect.c index 5d778b0a..22c3af2c 100644 --- a/src/common/guac_rect.c +++ b/src/common/guac_rect.c @@ -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; +} diff --git a/src/common/guac_rect.h b/src/common/guac_rect.h index f6e00ee6..2bb70845 100644 --- a/src/common/guac_rect.h +++ b/src/common/guac_rect.h @@ -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); +/** + * 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 * 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); +/** + * 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 diff --git a/tests/Makefile.am b/tests/Makefile.am index 0007ef02..b9a2bf4d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -40,6 +40,7 @@ test_libguac_SOURCES = \ common/common_suite.c \ common/guac_iconv.c \ common/guac_string.c \ + common/guac_rect.c \ protocol/suite.c \ protocol/base64_decode.c \ protocol/instruction_parse.c \ diff --git a/tests/common/common_suite.c b/tests/common/common_suite.c index 936ec3e8..5494aa83 100644 --- a/tests/common/common_suite.c +++ b/tests/common/common_suite.c @@ -48,6 +48,7 @@ int register_common_suite() { if ( 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-rect", test_guac_rect) == NULL ) { CU_cleanup_registry(); return CU_get_error(); diff --git a/tests/common/common_suite.h b/tests/common/common_suite.h index b078b20b..6ed50d92 100644 --- a/tests/common/common_suite.h +++ b/tests/common/common_suite.h @@ -49,5 +49,10 @@ void test_guac_string(); */ void test_guac_iconv(); +/** + * Unit test for rectangle calculation functions. + */ +void test_guac_rect(); + #endif diff --git a/tests/common/guac_rect.c b/tests/common/guac_rect.c new file mode 100644 index 00000000..df41ddad --- /dev/null +++ b/tests/common/guac_rect.c @@ -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 +#include +#include + +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); + +} +