/* * Copyright (c) 2019 Jiri Svoboda * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup libgfx * @{ */ /** * @file Coordinates */ #include <gfx/coord.h> #include <macros.h> #include <stdbool.h> #include <stddef.h> /** Divide @a a by @a b and round towards negative numbers. * * Regular integer division always rounds towards zero. This is not useful * e.g. for scaling down, where we always need to round towards negative * numbers. * * @param a Dividend * @param b Divisor * @return Quotient */ gfx_coord_t gfx_coord_div_rneg(gfx_coord_t a, gfx_coord_t b) { if ((a > 0 && b > 0) || (a < 0 && b < 0)) { /* Result is non-negative, round towards zero */ return a / b; } else { /* Result is negative, round away from zero */ return (a - b + 1) / b; } } /** Add two vectors. * * @param a First vector * @param b Second vector * @param d Destination */ void gfx_coord2_add(gfx_coord2_t *a, gfx_coord2_t *b, gfx_coord2_t *d) { d->x = a->x + b->x; d->y = a->y + b->y; } /** Subtract two vectors. * * @param a First vector * @param b Second vector * @param d Destination */ void gfx_coord2_subtract(gfx_coord2_t *a, gfx_coord2_t *b, gfx_coord2_t *d) { d->x = a->x - b->x; d->y = a->y - b->y; } /** Clip point coordinates to be within a rectangle. * * @param a Pixel coordinates * @param clip Clipping rectangle * @param d Place to store clipped coordinates */ void gfx_coord2_clip(gfx_coord2_t *a, gfx_rect_t *clip, gfx_coord2_t *d) { gfx_rect_t sclip; gfx_coord2_t t; gfx_rect_points_sort(clip, &sclip); t.x = min(a->x, clip->p1.x - 1); t.y = min(a->y, clip->p1.y - 1); d->x = max(clip->p0.x, t.x); d->y = max(clip->p0.y, t.y); } /** Transform coordinates via rectangle to rectangle projection. * * Transform pixel coordinate via a projection that maps one rectangle * onto another rectangle. The source rectangle must have both dimensions * greater than one. * * @param a Pixel coordinates * @param srect Source rectangle * @param drect Destination rectangle * @param d Place to store resulting coordinates. */ void gfx_coord2_project(gfx_coord2_t *a, gfx_rect_t *srect, gfx_rect_t *drect, gfx_coord2_t *d) { gfx_rect_t sr; gfx_rect_t dr; gfx_rect_points_sort(srect, &sr); gfx_rect_points_sort(drect, &dr); d->x = dr.p0.x + (a->x - sr.p0.x) * (dr.p1.x - dr.p0.x - 1) / (sr.p1.x - sr.p0.x - 1); d->y = dr.p0.y + (a->y - sr.p0.y) * (dr.p1.y - dr.p0.y - 1) / (sr.p1.y - sr.p0.y - 1); } /** Sort points of a span. * * Sort the begin and end points so that the begin point has the lower * coordinate (i.e. if needed, the span is transposed, if not, it is simply * copied). * * @param s0 Source span start point * @param s1 Source span end point * @param d0 Destination span start point * @param d1 Destination span end point */ void gfx_span_points_sort(gfx_coord_t s0, gfx_coord_t s1, gfx_coord_t *d0, gfx_coord_t *d1) { if (s0 <= s1) { *d0 = s0; *d1 = s1; } else { *d0 = s1 + 1; *d1 = s0 + 1; } } /** Move (translate) rectangle. * * @param trans Translation offset * @param src Source rectangle * @param dest Destination rectangle */ void gfx_rect_translate(gfx_coord2_t *trans, gfx_rect_t *src, gfx_rect_t *dest) { gfx_coord2_add(&src->p0, trans, &dest->p0); gfx_coord2_add(&src->p1, trans, &dest->p1); } /** Reverse move (translate) rectangle. * * @param trans Translation offset * @param src Source rectangle * @param dest Destination rectangle */ void gfx_rect_rtranslate(gfx_coord2_t *trans, gfx_rect_t *src, gfx_rect_t *dest) { gfx_coord2_subtract(&src->p0, trans, &dest->p0); gfx_coord2_subtract(&src->p1, trans, &dest->p1); } /** Compute envelope of two rectangles. * * Envelope is the minimal rectangle covering all pixels of both rectangles. * * @param a First rectangle * @param b Second rectangle * @param dest Place to store enveloping rectangle */ void gfx_rect_envelope(gfx_rect_t *a, gfx_rect_t *b, gfx_rect_t *dest) { gfx_rect_t sa, sb; if (gfx_rect_is_empty(a)) { *dest = *b; return; } if (gfx_rect_is_empty(b)) { *dest = *a; return; } /* a and b are both non-empty */ gfx_rect_points_sort(a, &sa); gfx_rect_points_sort(b, &sb); dest->p0.x = min(sa.p0.x, sb.p0.x); dest->p0.y = min(sa.p0.y, sb.p0.y); dest->p1.x = max(sa.p1.x, sb.p1.x); dest->p1.y = max(sa.p1.y, sb.p1.y); } /** Compute intersection of two rectangles. * * If the two rectangles do not intersect, the result will be an empty * rectangle (check with gfx_rect_is_empty()). The resulting rectangle * is always sorted. If @a clip is NULL, no clipping is performed. * * @param rect Source rectangle * @param clip Clipping rectangle or @c NULL * @param dest Place to store clipped rectangle */ void gfx_rect_clip(gfx_rect_t *rect, gfx_rect_t *clip, gfx_rect_t *dest) { gfx_rect_t srect, sclip; if (clip == NULL) { *dest = *rect; return; } gfx_rect_points_sort(rect, &srect); gfx_rect_points_sort(clip, &sclip); dest->p0.x = min(max(srect.p0.x, sclip.p0.x), sclip.p1.x); dest->p0.y = min(max(srect.p0.y, sclip.p0.y), sclip.p1.y); dest->p1.x = max(sclip.p0.x, min(srect.p1.x, sclip.p1.x)); dest->p1.y = max(sclip.p0.y, min(srect.p1.y, sclip.p1.y)); } /** Center rectangle on rectangle. * * Translate rectangle @a a so that its center coincides with the * center of rectangle @a b, saving the result in @a dest. * * @param a Rectnagle to translate * @param b Rectangle on which to center * @param dest Place to store resulting rectangle */ void gfx_rect_ctr_on_rect(gfx_rect_t *a, gfx_rect_t *b, gfx_rect_t *dest) { gfx_coord2_t adim; gfx_coord2_t bdim; gfx_rect_dims(a, &adim); gfx_rect_dims(b, &bdim); dest->p0.x = b->p0.x + bdim.x / 2 - adim.x / 2; dest->p0.y = b->p0.y + bdim.y / 2 - adim.y / 2; dest->p1.x = dest->p0.x + adim.x; dest->p1.y = dest->p0.y + adim.y; } /** Sort points of a rectangle. * * Shuffle around coordinates of a rectangle so that p0.x < p1.x and * p0.y < p0.y. * * @param src Source rectangle * @param dest Destination (sorted) rectangle */ void gfx_rect_points_sort(gfx_rect_t *src, gfx_rect_t *dest) { gfx_span_points_sort(src->p0.x, src->p1.x, &dest->p0.x, &dest->p1.x); gfx_span_points_sort(src->p0.y, src->p1.y, &dest->p0.y, &dest->p1.y); } /** Determine if rectangle contains no pixels * * @param rect Rectangle * @return @c true iff rectangle contains no pixels */ bool gfx_rect_is_empty(gfx_rect_t *rect) { return rect->p0.x == rect->p1.x || rect->p0.y == rect->p1.y; } /** Determine if two rectangles share any pixels * * @param a First rectangle * @param b Second rectangle * @return @c true iff rectangles share any pixels */ bool gfx_rect_is_incident(gfx_rect_t *a, gfx_rect_t *b) { gfx_rect_t r; gfx_rect_clip(a, b, &r); return !gfx_rect_is_empty(&r); } /** Return true if rectangle @a a is contained in rectangle @a b. * * @param a Inside rectangle * @param b Outside rectangle * @return @c true iff @a a is (non-strictly) inside @a b */ bool gfx_rect_is_inside(gfx_rect_t *a, gfx_rect_t *b) { gfx_rect_t sa; gfx_rect_t sb; gfx_rect_points_sort(a, &sa); gfx_rect_points_sort(b, &sb); if (sa.p0.x < sb.p0.x || sa.p0.y < sb.p0.y) return false; if (sa.p1.x > sb.p1.x || sa.p1.y > sb.p1.y) return false; return true; } /** Get rectangle dimensions. * * Get a vector containing the x, y dimensions of a rectangle. These are * always nonnegative. * * @param rect Rectangle * @param dims Place to store dimensions */ void gfx_rect_dims(gfx_rect_t *rect, gfx_coord2_t *dims) { gfx_rect_t srect; gfx_rect_points_sort(rect, &srect); gfx_coord2_subtract(&srect.p1, &srect.p0, dims); } /** Return true if pixel at coordinate @a coord lies within rectangle @a rect. */ bool gfx_pix_inside_rect(gfx_coord2_t *coord, gfx_rect_t *rect) { gfx_rect_t sr; gfx_rect_points_sort(rect, &sr); if (coord->x < sr.p0.x || coord->y < sr.p0.y) return false; if (coord->x >= sr.p1.x || coord->y >= sr.p1.y) return false; return true; } /** @} */