[{"id":13462,"web_url":"https://patchwork.libcamera.org/comment/13462/","msgid":"<20201023233614.GN5979@pendragon.ideasonboard.com>","date":"2020-10-23T23:36:14","subject":"Re: [libcamera-devel] [PATCH v5 4/5] libcamera: Add geometry helper\n\tfunctions","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi David,\n\nThank you for the patch.\n\nOn Fri, Oct 23, 2020 at 11:21:58AM +0100, David Plowman wrote:\n> These functions are aimed at making it easier to calculate cropping\n> rectangles, particularly in order to implement digital zoom.\n> \n> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> ---\n>  include/libcamera/geometry.h |  52 ++++++\n>  src/libcamera/geometry.cpp   | 307 +++++++++++++++++++++++++++++++++++\n\nWould you be able to write a test cases for these helpers ? They can go\nin a separate patch. I can also try to help if needed.\n\n>  2 files changed, 359 insertions(+)\n> \n> diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h\n> index 02fb63c0..3fa9e926 100644\n> --- a/include/libcamera/geometry.h\n> +++ b/include/libcamera/geometry.h\n> @@ -13,6 +13,30 @@\n>  \n>  namespace libcamera {\n>  \n> +class Rectangle;\n> +\n> +class Point\n> +{\n> +public:\n> +\tconstexpr Point()\n> +\t\t: x(0), y(0)\n> +\t{\n> +\t}\n> +\n> +\tconstexpr Point(int xpos, int ypos)\n> +\t\t: x(xpos), y(ypos)\n> +\t{\n> +\t}\n> +\n> +\tint x;\n> +\tint y;\n> +\n> +\tconstexpr Point operator-() const\n> +\t{\n> +\t\treturn { -x, -y };\n> +\t}\n> +};\n> +\n>  class Size\n>  {\n>  public:\n> @@ -31,6 +55,7 @@ public:\n>  \n>  \tbool isNull() const { return !width && !height; }\n>  \tconst std::string toString() const;\n> +\tPoint center() const;\n\nThe concept of the centre of a size seems a bit awkward to me. Should we\nreplace\n\n\tSize size = ...;\n\tPoint p = size.center();\n\nwith\n\n\tSize size = ...;\n\tPoint p = Rectangle(size).center();\n\nand drop this function ?\n\n>  \n>  \tSize &alignDownTo(unsigned int hAlignment, unsigned int vAlignment)\n>  \t{\n> @@ -93,6 +118,17 @@ public:\n>  \t\t\tstd::max(height, expand.height)\n>  \t\t};\n>  \t}\n> +\n> +\tSize boundedToAspectRatio(const Size &ratio) const;\n> +\tSize expandedToAspectRatio(const Size &ratio) const;\n> +\n> +\tRectangle centeredTo(const Point &center) const;\n> +\n> +\tSize operator*(float factor) const;\n> +\tSize operator/(float factor) const;\n> +\n> +\tSize &operator*=(float factor);\n> +\tSize &operator/=(float factor);\n>  };\n>  \n>  bool operator==(const Size &lhs, const Size &rhs);\n> @@ -176,6 +212,11 @@ public:\n>  \t{\n>  \t}\n>  \n> +\tconstexpr explicit Rectangle(const Size &size)\n> +\t\t: x(0), y(0), width(size.width), height(size.height)\n> +\t{\n> +\t}\n> +\n>  \tint x;\n>  \tint y;\n>  \tunsigned int width;\n> @@ -183,6 +224,17 @@ public:\n>  \n>  \tbool isNull() const { return !width && !height; }\n>  \tconst std::string toString() const;\n> +\tPoint center() const;\n> +\tSize size() const;\n> +\tPoint topLeft() const;\n\nI'd make the last two functions inline, as they're just accessors for\nfields that are public.\n\n> +\n> +\tRectangle &scaleBy(const Size &numerator, const Size &denominator);\n> +\tRectangle &translateBy(const Point &point);\n> +\n> +\tRectangle boundedTo(const Rectangle &bound) const;\n> +\tRectangle enclosedIn(const Rectangle &boundary) const;\n> +\tRectangle scaledBy(const Size &numerator, const Size &denominator) const;\n> +\tRectangle translatedBy(const Point &point) const;\n>  };\n>  \n>  bool operator==(const Rectangle &lhs, const Rectangle &rhs);\n> diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp\n> index b12e1a62..63a49e9a 100644\n> --- a/src/libcamera/geometry.cpp\n> +++ b/src/libcamera/geometry.cpp\n> @@ -17,6 +17,42 @@\n>  \n>  namespace libcamera {\n>  \n> +/**\n> + * \\class Point\n> + * \\brief Describe a point in two-dimensional space\n> + *\n> + * The Point structure defines a point in two-dimensional space with integer\n> + * precision. The coordinates of a Point may be negative as well as positive.\n> + */\n> +\n> +/**\n> + * \\fn Point::Point()\n> + * \\brief Construct a Point with x and y set to 0\n> + */\n> +\n> +/**\n> + * \\fn Point::Point(int xpos, int ypos)\n> + * \\brief Construct a Point at given \\a x and \\a y values\n\ns/x/xpos/ and s/y/ypos/\n\n> + * \\param[in] xpos The x-coordinate\n> + * \\param[in] ypos The y-coordinate\n> + */\n> +\n> +/**\n> + * \\var Point::x\n> + * \\brief The x-coordinate of the Point\n> + */\n> +\n> +/**\n> + * \\var Point::y\n> + * \\brief The y-coordinate of the Point\n> + */\n> +\n> +/**\n> + * \\fn Point Point::operator-() const\n> + * \\brief Negate a Point by negating both its x and y coordinates\n> + * \\return The negated point\n> + */\n> +\n>  /**\n>   * \\struct Size\n>   * \\brief Describe a two-dimensional size\n> @@ -61,6 +97,15 @@ const std::string Size::toString() const\n>  \treturn std::to_string(width) + \"x\" + std::to_string(height);\n>  }\n>  \n> +/**\n> + * \\brief Retrieve the center point of this size\n> + * \\return The center Point\n> + */\n> +Point Size::center() const\n> +{\n> +\treturn { static_cast<int>(width / 2), static_cast<int>(height / 2) };\n> +}\n> +\n>  /**\n>   * \\fn Size::alignDownTo(unsigned int hAlignment, unsigned int vAlignment)\n>   * \\brief Align the size down horizontally and vertically in place\n> @@ -143,6 +188,105 @@ const std::string Size::toString() const\n>   * height of this size and the \\a expand size\n>   */\n>  \n> +/**\n> + * \\brief Bound the size down to match the aspect ratio given by \\a ratio\n> + * \\param[in] ratio The size whose aspect ratio must be matched\n> + * \\return A Size whose width and height are equal to the width and height\n> + * of this Size aligned down to the aspect ratio of \\a ratio\n> + */\n> +Size Size::boundedToAspectRatio(const Size &ratio) const\n> +{\n> +\tuint64_t ratio1 = static_cast<uint64_t>(width) *\n> +\t\t\t  static_cast<uint64_t>(ratio.height);\n> +\tuint64_t ratio2 = static_cast<uint64_t>(ratio.width) *\n> +\t\t\t  static_cast<uint64_t>(height);\n> +\n> +\tif (ratio1 > ratio2)\n> +\t\treturn { static_cast<unsigned int>(ratio2 / ratio.height), height };\n> +\telse\n> +\t\treturn { width, static_cast<unsigned int>(ratio1 / ratio.width) };\n> +}\n> +\n> +/**\n> + * \\brief Expand the size to match the aspect ratio given by \\a ratio\n> + * \\param[in] ratio The size whose aspect ratio must be matched\n\nI would either add here\n\n\"The behaviour of this function is undefined if either the width or the\nheight of the \\a ratio is zero.\"\n\nShould we have a special case below to return *this when !ratio.width ||\n!ratio.height, to avoid crashing ? Or an ASSERT() to catch this ? I'm\nconsidering this in the context of patch 5/5. If we decide to accept all\ncrop values and clamp them to what can be achieved, the call to\nexpandedToAspectRatio() could (I think) lead to the right result with\nthe crop.width && crop.height check dropped. But maybe we shouldn't rely\non how corner cases are implemented in geometry helpers, and handle them\nexplictly in the callers. Still, not crashing may be a good option.\n\n> + * \\return A Size whose width and height are equal to the width and height\n> + * of this Size expanded up to the aspect ratio of \\a ratio\n> + */\n> +Size Size::expandedToAspectRatio(const Size &ratio) const\n> +{\n> +\tuint64_t ratio1 = static_cast<uint64_t>(width) *\n> +\t\t\t  static_cast<uint64_t>(ratio.height);\n> +\tuint64_t ratio2 = static_cast<uint64_t>(ratio.width) *\n> +\t\t\t  static_cast<uint64_t>(height);\n> +\n> +\tif (ratio1 < ratio2)\n> +\t\treturn { static_cast<unsigned int>(ratio2 / ratio.height), height };\n> +\telse\n> +\t\treturn { width, static_cast<unsigned int>(ratio1 / ratio.width) };\n> +}\n> +\n> +/**\n> + * \\brief Center a rectangle of this size at a given Point\n> + * \\param[in] center The center point the Rectangle is to have\n> + *\n> + * A Rectangle of this object's size is positioned so that its center\n> + * is at the given Point.\n> + *\n> + * \\return A Rectangle of this size, centered at the given Point.\n> + */\n> +Rectangle Size::centeredTo(const Point &center) const\n> +{\n> +\tint x = center.x - width / 2;\n> +\tint y = center.y - height / 2;\n> +\n> +\treturn { x, y, width, height };\n> +}\n> +\n> +/**\n> + * \\brief Scale size up by the given factor\n> + * \\param[in] factor The factor\n> + * \\return The scaled Size\n> + */\n> +Size Size::operator*(float factor) const\n> +{\n> +\treturn Size(width * factor, height * factor);\n> +}\n> +\n> +/**\n> + * \\brief Scale size down by the given factor\n> + * \\param[in] factor The factor\n> + * \\return The scaled Size\n> + */\n> +Size Size::operator/(float factor) const\n> +{\n> +\treturn Size(width / factor, height / factor);\n> +}\n> +\n> +/**\n> + * \\brief Scale this size up by the given factor in place\n> + * \\param[in] factor The factor\n> + * \\return A reference to this object\n> + */\n> +Size &Size::operator*=(float factor)\n> +{\n> +\twidth *= factor;\n> +\theight *= factor;\n> +\treturn *this;\n> +}\n> +\n> +/**\n> + * \\brief Scale this size down by the given factor in place\n> + * \\param[in] factor The factor\n> + * \\return A reference to this object\n> + */\n> +Size &Size::operator/=(float factor)\n> +{\n> +\twidth /= factor;\n> +\theight /= factor;\n> +\treturn *this;\n> +}\n> +\n>  /**\n>   * \\brief Compare sizes for equality\n>   * \\return True if the two sizes are equal, false otherwise\n> @@ -365,6 +509,13 @@ bool operator==(const SizeRange &lhs, const SizeRange &rhs)\n>   * \\param[in] height The height\n>   */\n>  \n> +/**\n> + * \\fn Rectangle::Rectangle(const Size &size)\n> + * \\brief Construct a Rectangle of \\a size with its top left corner located\n> + * at (0,0).\n\ns/\\.//\n\n> + * \\param[in] size The desired Rectangle size\n> + */\n> +\n>  /**\n>   * \\var Rectangle::x\n>   * \\brief The horizontal coordinate of the rectangle's top-left corner\n> @@ -404,6 +555,162 @@ const std::string Rectangle::toString() const\n>  \treturn ss.str();\n>  }\n>  \n> +/**\n> + * \\brief Retrieve the center point of this rectangle\n> + * \\return The center Point\n> + */\n> +Point Rectangle::center() const\n> +{\n> +\treturn { x + static_cast<int>(width / 2), y + static_cast<int>(height / 2) };\n> +}\n> +\n> +/**\n> + * \\brief Retrieve the size of this rectangle\n> + * \\return The Rectangle size\n> + */\n> +Size Rectangle::size() const\n> +{\n> +\treturn Size(width, height);\n> +}\n> +\n> +/**\n> + * \\brief Retrieve the coordinates of the top left corner of this Rectangle\n> + * \\return The Rectangle's top left corner\n> + */\n> +Point Rectangle::topLeft() const\n> +{\n> +\treturn Point(x, y);\n> +}\n> +\n> +/**\n> + * \\brief Apply a non-uniform rational scaling in place to this Rectangle\n> + * \\param[in] numerator The numerators of the x and y scaling factors\n> + * \\param[in] denominator The denominators of the x and y scaling factors\n> + *\n> + * A non-uniform scaling is applied in place such the the resulting x\n> + * coordinates are multiplied by numerator.width / denominator.width,\n> + * and similarly for the y coordinates (using height in place of width).\n> + *\n> + * \\return A reference to this object\n> + */\n> +Rectangle &Rectangle::scaleBy(const Size &numerator, const Size &denominator)\n> +{\n> +\tx = static_cast<int64_t>(x) * numerator.width / denominator.width;\n> +\ty = static_cast<int64_t>(y) * numerator.height / denominator.height;\n> +\twidth = static_cast<uint64_t>(width) * numerator.width / denominator.width;\n> +\theight = static_cast<uint64_t>(height) * numerator.height / denominator.height;\n> +\n> +\treturn *this;\n> +}\n> +\n> +/**\n> + * \\brief Translate this Rectangle in place by the given Point\n> + * \\param[in] point The amount to translate the Rectangle by\n> + *\n> + * The Rectangle is translated in the x-direction by the point's x coordinate\n> + * and in the y-direction by the point's y coordinate.\n> + *\n> + * \\return A reference to this object\n> + */\n> +Rectangle &Rectangle::translateBy(const Point &point)\n> +{\n> +\tx += point.x;\n> +\ty += point.y;\n> +\n> +\treturn *this;\n> +}\n> +\n> +/**\n> + * \\brief Calculate the intersection of this Rectangle with another\n> + * \\param[in] bound The Rectangle that is intersected with this Rectangle\n> + *\n> + * This method calculates the standard intersection of two rectangles. If the\n> + * rectangles do not overlap in either the x or y direction, then the size\n> + * of that dimension in the result (its width or height) is set to zero. Even\n> + * when one dimension is set to zero, note that the other dimension may still\n> + * have a positive value if there was some overlap.\n> + *\n> + * \\return A Rectangle that is the intersection of the input rectangles\n> + */\n> +Rectangle Rectangle::boundedTo(const Rectangle &bound) const\n> +{\n> +\tint topLeftX = std::max(x, bound.x);\n> +\tint topLeftY = std::max(y, bound.y);\n> +\tint bottomRightX = std::min<int>(x + width, bound.x + bound.width);\n> +\tint bottomRightY = std::min<int>(y + height, bound.y + bound.height);\n> +\n> +\tunsigned int newWidth = std::max(bottomRightX - topLeftX, 0);\n> +\tunsigned int newHeight = std::max(bottomRightY - topLeftY, 0);\n> +\n> +\treturn { topLeftX, topLeftY, newWidth, newHeight };\n> +}\n> +\n> +/**\n> + * \\brief Enclose a Rectangle so as not to exceed another Rectangle\n> + * \\param[in] boundary The limit that the returned Rectangle will not exceed\n> + *\n> + * The Rectangle is modified so that it does not exceeed the given \\a boundary.\n> + * This process involves translating the Rectangle if any of its edges\n> + * lie beyond \\a boundary, so that those edges then lie along the boundary\n> + * instead.\n> + *\n> + * If either width or height are larger than \\a boundary, then the returned\n> + * Rectangle is clipped to be no larger. But other than this, the\n> + * Rectangle is not clipped or reduced in size, merely translated.\n> + *\n> + * Note that this is not a conventional Rectangle intersection function\n> + * which is provided by Rectangle::boundedTo.\n\ns/Rectangle::boundedTo/boundedTo()/ should be enough to please doxygen.\n\n> + *\n> + * \\return A Rectangle that does not extend beyond a boundary Rectangle\n> + */\n> +Rectangle Rectangle::enclosedIn(const Rectangle &boundary) const\n> +{\n> +\t/* We can't be bigger than the boundary rectangle. */\n> +\tRectangle result = boundedTo(Rectangle{ x, y, boundary.size() });\n> +\n> +\tresult.x = std::clamp<int>(result.x, boundary.x,\n> +\t\t\t\t   boundary.x + boundary.width - result.width);\n> +\tresult.y = std::clamp<int>(result.y, boundary.y,\n> +\t\t\t\t   boundary.y + boundary.height - result.height);\n> +\n> +\treturn result;\n> +}\n> +\n> +/**\n> + * \\brief Apply a non-uniform rational scaling to this Rectangle\n> + * \\param[in] numerator The numerators of the x and y scaling factors\n> + * \\param[in] denominator The denominators of the x and y scaling factors\n> + *\n> + * A non-uniform scaling is applied such the the resulting x\n> + * coordinates are multiplied by numerator.width / denominator.width,\n> + * and similarly for the y coordinates (using height in place of width).\n> + *\n> + * \\return The non-uniformly scaled Rectangle\n> + */\n> +Rectangle Rectangle::scaledBy(const Size &numerator, const Size &denominator) const\n> +{\n> +\tint scaledX = static_cast<int64_t>(x) * numerator.width / denominator.width;\n> +\tint scaledY = static_cast<int64_t>(y) * numerator.height / denominator.height;\n> +\tunsigned int scaledWidth = static_cast<uint64_t>(width) * numerator.width / denominator.width;\n> +\tunsigned int scaledHeight = static_cast<uint64_t>(height) * numerator.height / denominator.height;\n> +\n> +\treturn { scaledX, scaledY, scaledWidth, scaledHeight };\n> +}\n> +\n> +/**\n> + * \\brief Translate a Rectangle by the given amounts\n> + * \\param[in] point The amount to translate the Rectangle by\n> + *\n> + * The Rectangle is translated in the x-direction by the point's x coordinate\n> + * and in the y-direction by the point's y coordinate.\n> + *\n> + * \\return The translated Rectangle\n> + */\n> +Rectangle Rectangle::translatedBy(const Point &point) const\n> +{\n> +\treturn { x + point.x, y + point.y, width, height };\n> +}\n> +\n>  /**\n>   * \\brief Compare rectangles for equality\n>   * \\return True if the two rectangles are equal, false otherwise","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 46D5DBDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 23 Oct 2020 23:37:03 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id CF61961C92;\n\tSat, 24 Oct 2020 01:37:02 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4FA35615D2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 24 Oct 2020 01:37:01 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id CE13F9BF;\n\tSat, 24 Oct 2020 01:37:00 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"NLHdjFTi\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1603496221;\n\tbh=oZ0p1/4pSzXsYbqTw38VdOaXMUP9sk9Qp4xx5g3ak6g=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=NLHdjFTioMOReUdfNajyiPz9YgyGFGiq/8XxS+J5VJ2serReG7HlX7rBXcaWGfmnB\n\tsIvgTDbLksmFJH5xeFvoyB7XX4llF5BixklG1JEbwSoNYYx8gL8JiRHAVPhXK2kDhc\n\tJE0+mDFfVvy7zCpfr6PWjz0ZSyCMTZr+vw3EJroA=","Date":"Sat, 24 Oct 2020 02:36:14 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"David Plowman <david.plowman@raspberrypi.com>","Message-ID":"<20201023233614.GN5979@pendragon.ideasonboard.com>","References":"<20201023102159.26274-1-david.plowman@raspberrypi.com>\n\t<20201023102159.26274-5-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201023102159.26274-5-david.plowman@raspberrypi.com>","Subject":"Re: [libcamera-devel] [PATCH v5 4/5] libcamera: Add geometry helper\n\tfunctions","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13469,"web_url":"https://patchwork.libcamera.org/comment/13469/","msgid":"<20201024172459.2iepnaf5blrhvpn6@uno.localdomain>","date":"2020-10-24T17:24:59","subject":"Re: [libcamera-devel] [PATCH v5 4/5] libcamera: Add geometry helper\n\tfunctions","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi David,\n\nOn Fri, Oct 23, 2020 at 11:21:58AM +0100, David Plowman wrote:\n> These functions are aimed at making it easier to calculate cropping\n> rectangles, particularly in order to implement digital zoom.\n>\n> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n\nThis looks very good, just two minor documentation comments below\n\nFor the patch\nReviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\n> ---\n>  include/libcamera/geometry.h |  52 ++++++\n>  src/libcamera/geometry.cpp   | 307 +++++++++++++++++++++++++++++++++++\n>  2 files changed, 359 insertions(+)\n>\n> diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h\n> index 02fb63c0..3fa9e926 100644\n> --- a/include/libcamera/geometry.h\n> +++ b/include/libcamera/geometry.h\n> @@ -13,6 +13,30 @@\n>\n>  namespace libcamera {\n>\n> +class Rectangle;\n> +\n> +class Point\n> +{\n> +public:\n> +\tconstexpr Point()\n> +\t\t: x(0), y(0)\n> +\t{\n> +\t}\n> +\n> +\tconstexpr Point(int xpos, int ypos)\n> +\t\t: x(xpos), y(ypos)\n> +\t{\n> +\t}\n> +\n> +\tint x;\n> +\tint y;\n> +\n> +\tconstexpr Point operator-() const\n> +\t{\n> +\t\treturn { -x, -y };\n> +\t}\n> +};\n> +\n>  class Size\n>  {\n>  public:\n> @@ -31,6 +55,7 @@ public:\n>\n>  \tbool isNull() const { return !width && !height; }\n>  \tconst std::string toString() const;\n> +\tPoint center() const;\n>\n>  \tSize &alignDownTo(unsigned int hAlignment, unsigned int vAlignment)\n>  \t{\n> @@ -93,6 +118,17 @@ public:\n>  \t\t\tstd::max(height, expand.height)\n>  \t\t};\n>  \t}\n> +\n> +\tSize boundedToAspectRatio(const Size &ratio) const;\n> +\tSize expandedToAspectRatio(const Size &ratio) const;\n> +\n> +\tRectangle centeredTo(const Point &center) const;\n> +\n> +\tSize operator*(float factor) const;\n> +\tSize operator/(float factor) const;\n> +\n> +\tSize &operator*=(float factor);\n> +\tSize &operator/=(float factor);\n>  };\n>\n>  bool operator==(const Size &lhs, const Size &rhs);\n> @@ -176,6 +212,11 @@ public:\n>  \t{\n>  \t}\n>\n> +\tconstexpr explicit Rectangle(const Size &size)\n> +\t\t: x(0), y(0), width(size.width), height(size.height)\n> +\t{\n> +\t}\n> +\n>  \tint x;\n>  \tint y;\n>  \tunsigned int width;\n> @@ -183,6 +224,17 @@ public:\n>\n>  \tbool isNull() const { return !width && !height; }\n>  \tconst std::string toString() const;\n> +\tPoint center() const;\n> +\tSize size() const;\n> +\tPoint topLeft() const;\n> +\n> +\tRectangle &scaleBy(const Size &numerator, const Size &denominator);\n> +\tRectangle &translateBy(const Point &point);\n> +\n> +\tRectangle boundedTo(const Rectangle &bound) const;\n> +\tRectangle enclosedIn(const Rectangle &boundary) const;\n> +\tRectangle scaledBy(const Size &numerator, const Size &denominator) const;\n> +\tRectangle translatedBy(const Point &point) const;\n>  };\n>\n>  bool operator==(const Rectangle &lhs, const Rectangle &rhs);\n> diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp\n> index b12e1a62..63a49e9a 100644\n> --- a/src/libcamera/geometry.cpp\n> +++ b/src/libcamera/geometry.cpp\n> @@ -17,6 +17,42 @@\n>\n>  namespace libcamera {\n>\n> +/**\n> + * \\class Point\n> + * \\brief Describe a point in two-dimensional space\n> + *\n> + * The Point structure defines a point in two-dimensional space with integer\n> + * precision. The coordinates of a Point may be negative as well as positive.\n> + */\n> +\n> +/**\n> + * \\fn Point::Point()\n> + * \\brief Construct a Point with x and y set to 0\n> + */\n> +\n> +/**\n> + * \\fn Point::Point(int xpos, int ypos)\n> + * \\brief Construct a Point at given \\a x and \\a y values\n> + * \\param[in] xpos The x-coordinate\n> + * \\param[in] ypos The y-coordinate\n> + */\n> +\n> +/**\n> + * \\var Point::x\n> + * \\brief The x-coordinate of the Point\n> + */\n> +\n> +/**\n> + * \\var Point::y\n> + * \\brief The y-coordinate of the Point\n> + */\n> +\n> +/**\n> + * \\fn Point Point::operator-() const\n> + * \\brief Negate a Point by negating both its x and y coordinates\n> + * \\return The negated point\n> + */\n> +\n>  /**\n>   * \\struct Size\n>   * \\brief Describe a two-dimensional size\n> @@ -61,6 +97,15 @@ const std::string Size::toString() const\n>  \treturn std::to_string(width) + \"x\" + std::to_string(height);\n>  }\n>\n> +/**\n> + * \\brief Retrieve the center point of this size\n> + * \\return The center Point\n> + */\n> +Point Size::center() const\n> +{\n> +\treturn { static_cast<int>(width / 2), static_cast<int>(height / 2) };\n> +}\n> +\n>  /**\n>   * \\fn Size::alignDownTo(unsigned int hAlignment, unsigned int vAlignment)\n>   * \\brief Align the size down horizontally and vertically in place\n> @@ -143,6 +188,105 @@ const std::string Size::toString() const\n>   * height of this size and the \\a expand size\n>   */\n>\n> +/**\n> + * \\brief Bound the size down to match the aspect ratio given by \\a ratio\n> + * \\param[in] ratio The size whose aspect ratio must be matched\n> + * \\return A Size whose width and height are equal to the width and height\n> + * of this Size aligned down to the aspect ratio of \\a ratio\n> + */\n> +Size Size::boundedToAspectRatio(const Size &ratio) const\n> +{\n> +\tuint64_t ratio1 = static_cast<uint64_t>(width) *\n> +\t\t\t  static_cast<uint64_t>(ratio.height);\n> +\tuint64_t ratio2 = static_cast<uint64_t>(ratio.width) *\n> +\t\t\t  static_cast<uint64_t>(height);\n> +\n> +\tif (ratio1 > ratio2)\n> +\t\treturn { static_cast<unsigned int>(ratio2 / ratio.height), height };\n> +\telse\n> +\t\treturn { width, static_cast<unsigned int>(ratio1 / ratio.width) };\n> +}\n> +\n> +/**\n> + * \\brief Expand the size to match the aspect ratio given by \\a ratio\n> + * \\param[in] ratio The size whose aspect ratio must be matched\n> + * \\return A Size whose width and height are equal to the width and height\n> + * of this Size expanded up to the aspect ratio of \\a ratio\n> + */\n> +Size Size::expandedToAspectRatio(const Size &ratio) const\n> +{\n> +\tuint64_t ratio1 = static_cast<uint64_t>(width) *\n> +\t\t\t  static_cast<uint64_t>(ratio.height);\n> +\tuint64_t ratio2 = static_cast<uint64_t>(ratio.width) *\n> +\t\t\t  static_cast<uint64_t>(height);\n> +\n> +\tif (ratio1 < ratio2)\n> +\t\treturn { static_cast<unsigned int>(ratio2 / ratio.height), height };\n> +\telse\n> +\t\treturn { width, static_cast<unsigned int>(ratio1 / ratio.width) };\n> +}\n> +\n> +/**\n> + * \\brief Center a rectangle of this size at a given Point\n> + * \\param[in] center The center point the Rectangle is to have\n> + *\n> + * A Rectangle of this object's size is positioned so that its center\n> + * is at the given Point.\n> + *\n> + * \\return A Rectangle of this size, centered at the given Point.\n> + */\n> +Rectangle Size::centeredTo(const Point &center) const\n> +{\n> +\tint x = center.x - width / 2;\n> +\tint y = center.y - height / 2;\n> +\n> +\treturn { x, y, width, height };\n> +}\n> +\n> +/**\n> + * \\brief Scale size up by the given factor\n> + * \\param[in] factor The factor\n> + * \\return The scaled Size\n> + */\n> +Size Size::operator*(float factor) const\n> +{\n> +\treturn Size(width * factor, height * factor);\n> +}\n> +\n> +/**\n> + * \\brief Scale size down by the given factor\n> + * \\param[in] factor The factor\n> + * \\return The scaled Size\n> + */\n> +Size Size::operator/(float factor) const\n> +{\n> +\treturn Size(width / factor, height / factor);\n> +}\n> +\n> +/**\n> + * \\brief Scale this size up by the given factor in place\n> + * \\param[in] factor The factor\n> + * \\return A reference to this object\n> + */\n> +Size &Size::operator*=(float factor)\n> +{\n> +\twidth *= factor;\n> +\theight *= factor;\n> +\treturn *this;\n> +}\n> +\n> +/**\n> + * \\brief Scale this size down by the given factor in place\n> + * \\param[in] factor The factor\n> + * \\return A reference to this object\n> + */\n> +Size &Size::operator/=(float factor)\n> +{\n> +\twidth /= factor;\n> +\theight /= factor;\n> +\treturn *this;\n> +}\n> +\n>  /**\n>   * \\brief Compare sizes for equality\n>   * \\return True if the two sizes are equal, false otherwise\n> @@ -365,6 +509,13 @@ bool operator==(const SizeRange &lhs, const SizeRange &rhs)\n>   * \\param[in] height The height\n>   */\n>\n> +/**\n> + * \\fn Rectangle::Rectangle(const Size &size)\n> + * \\brief Construct a Rectangle of \\a size with its top left corner located\n> + * at (0,0).\n> + * \\param[in] size The desired Rectangle size\n> + */\n> +\n>  /**\n>   * \\var Rectangle::x\n>   * \\brief The horizontal coordinate of the rectangle's top-left corner\n> @@ -404,6 +555,162 @@ const std::string Rectangle::toString() const\n>  \treturn ss.str();\n>  }\n>\n> +/**\n> + * \\brief Retrieve the center point of this rectangle\n> + * \\return The center Point\n> + */\n> +Point Rectangle::center() const\n> +{\n> +\treturn { x + static_cast<int>(width / 2), y + static_cast<int>(height / 2) };\n> +}\n> +\n> +/**\n> + * \\brief Retrieve the size of this rectangle\n> + * \\return The Rectangle size\n> + */\n> +Size Rectangle::size() const\n> +{\n> +\treturn Size(width, height);\n> +}\n> +\n> +/**\n> + * \\brief Retrieve the coordinates of the top left corner of this Rectangle\n> + * \\return The Rectangle's top left corner\n> + */\n> +Point Rectangle::topLeft() const\n> +{\n> +\treturn Point(x, y);\n> +}\n> +\n> +/**\n> + * \\brief Apply a non-uniform rational scaling in place to this Rectangle\n> + * \\param[in] numerator The numerators of the x and y scaling factors\n> + * \\param[in] denominator The denominators of the x and y scaling factors\n> + *\n> + * A non-uniform scaling is applied in place such the the resulting x\n> + * coordinates are multiplied by numerator.width / denominator.width,\n> + * and similarly for the y coordinates (using height in place of width).\n> + *\n> + * \\return A reference to this object\n> + */\n> +Rectangle &Rectangle::scaleBy(const Size &numerator, const Size &denominator)\n> +{\n> +\tx = static_cast<int64_t>(x) * numerator.width / denominator.width;\n> +\ty = static_cast<int64_t>(y) * numerator.height / denominator.height;\n> +\twidth = static_cast<uint64_t>(width) * numerator.width / denominator.width;\n> +\theight = static_cast<uint64_t>(height) * numerator.height / denominator.height;\n> +\n> +\treturn *this;\n> +}\n> +\n> +/**\n> + * \\brief Translate this Rectangle in place by the given Point\n> + * \\param[in] point The amount to translate the Rectangle by\n> + *\n> + * The Rectangle is translated in the x-direction by the point's x coordinate\n> + * and in the y-direction by the point's y coordinate.\n> + *\n> + * \\return A reference to this object\n> + */\n> +Rectangle &Rectangle::translateBy(const Point &point)\n> +{\n> +\tx += point.x;\n> +\ty += point.y;\n> +\n> +\treturn *this;\n> +}\n> +\n> +/**\n> + * \\brief Calculate the intersection of this Rectangle with another\n> + * \\param[in] bound The Rectangle that is intersected with this Rectangle\n> + *\n> + * This method calculates the standard intersection of two rectangles. If the\n> + * rectangles do not overlap in either the x or y direction, then the size\n> + * of that dimension in the result (its width or height) is set to zero. Even\n> + * when one dimension is set to zero, note that the other dimension may still\n> + * have a positive value if there was some overlap.\n> + *\n> + * \\return A Rectangle that is the intersection of the input rectangles\n> + */\n> +Rectangle Rectangle::boundedTo(const Rectangle &bound) const\n> +{\n> +\tint topLeftX = std::max(x, bound.x);\n> +\tint topLeftY = std::max(y, bound.y);\n> +\tint bottomRightX = std::min<int>(x + width, bound.x + bound.width);\n> +\tint bottomRightY = std::min<int>(y + height, bound.y + bound.height);\n> +\n> +\tunsigned int newWidth = std::max(bottomRightX - topLeftX, 0);\n> +\tunsigned int newHeight = std::max(bottomRightY - topLeftY, 0);\n> +\n> +\treturn { topLeftX, topLeftY, newWidth, newHeight };\n> +}\n> +\n> +/**\n> + * \\brief Enclose a Rectangle so as not to exceed another Rectangle\n> + * \\param[in] boundary The limit that the returned Rectangle will not exceed\n> + *\n> + * The Rectangle is modified so that it does not exceeed the given \\a boundary.\n\ns/exceeed/exceed/\n\n> + * This process involves translating the Rectangle if any of its edges\n> + * lie beyond \\a boundary, so that those edges then lie along the boundary\n> + * instead.\n> + *\n> + * If either width or height are larger than \\a boundary, then the returned\n> + * Rectangle is clipped to be no larger. But other than this, the\n> + * Rectangle is not clipped or reduced in size, merely translated.\n> + *\n> + * Note that this is not a conventional Rectangle intersection function\n> + * which is provided by Rectangle::boundedTo.\n> + *\n> + * \\return A Rectangle that does not extend beyond a boundary Rectangle\n> + */\n> +Rectangle Rectangle::enclosedIn(const Rectangle &boundary) const\n> +{\n> +\t/* We can't be bigger than the boundary rectangle. */\n> +\tRectangle result = boundedTo(Rectangle{ x, y, boundary.size() });\n> +\n> +\tresult.x = std::clamp<int>(result.x, boundary.x,\n> +\t\t\t\t   boundary.x + boundary.width - result.width);\n> +\tresult.y = std::clamp<int>(result.y, boundary.y,\n> +\t\t\t\t   boundary.y + boundary.height - result.height);\n> +\n> +\treturn result;\n> +}\n> +\n> +/**\n> + * \\brief Apply a non-uniform rational scaling to this Rectangle\n> + * \\param[in] numerator The numerators of the x and y scaling factors\n> + * \\param[in] denominator The denominators of the x and y scaling factors\n> + *\n> + * A non-uniform scaling is applied such the the resulting x\n\ns/the the/the/\n\n> + * coordinates are multiplied by numerator.width / denominator.width,\n> + * and similarly for the y coordinates (using height in place of width).\n> + *\n> + * \\return The non-uniformly scaled Rectangle\n> + */\n> +Rectangle Rectangle::scaledBy(const Size &numerator, const Size &denominator) const\n> +{\n> +\tint scaledX = static_cast<int64_t>(x) * numerator.width / denominator.width;\n> +\tint scaledY = static_cast<int64_t>(y) * numerator.height / denominator.height;\n> +\tunsigned int scaledWidth = static_cast<uint64_t>(width) * numerator.width / denominator.width;\n> +\tunsigned int scaledHeight = static_cast<uint64_t>(height) * numerator.height / denominator.height;\n> +\n> +\treturn { scaledX, scaledY, scaledWidth, scaledHeight };\n> +}\n> +\n> +/**\n> + * \\brief Translate a Rectangle by the given amounts\n> + * \\param[in] point The amount to translate the Rectangle by\n> + *\n> + * The Rectangle is translated in the x-direction by the point's x coordinate\n> + * and in the y-direction by the point's y coordinate.\n> + *\n> + * \\return The translated Rectangle\n> + */\n> +Rectangle Rectangle::translatedBy(const Point &point) const\n> +{\n> +\treturn { x + point.x, y + point.y, width, height };\n> +}\n> +\n>  /**\n>   * \\brief Compare rectangles for equality\n>   * \\return True if the two rectangles are equal, false otherwise\n> --\n> 2.20.1\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id DCD72C3B5C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSat, 24 Oct 2020 17:25:03 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4974F61D64;\n\tSat, 24 Oct 2020 19:25:03 +0200 (CEST)","from relay10.mail.gandi.net (relay10.mail.gandi.net\n\t[217.70.178.230])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 300FE605BF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 24 Oct 2020 19:25:02 +0200 (CEST)","from uno.localdomain (2-224-242-101.ip172.fastwebnet.it\n\t[2.224.242.101]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay10.mail.gandi.net (Postfix) with ESMTPSA id 5C66D240006;\n\tSat, 24 Oct 2020 17:25:00 +0000 (UTC)"],"Date":"Sat, 24 Oct 2020 19:24:59 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"David Plowman <david.plowman@raspberrypi.com>","Message-ID":"<20201024172459.2iepnaf5blrhvpn6@uno.localdomain>","References":"<20201023102159.26274-1-david.plowman@raspberrypi.com>\n\t<20201023102159.26274-5-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201023102159.26274-5-david.plowman@raspberrypi.com>","Subject":"Re: [libcamera-devel] [PATCH v5 4/5] libcamera: Add geometry helper\n\tfunctions","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]