[{"id":13495,"web_url":"https://patchwork.libcamera.org/comment/13495/","msgid":"<20201026225107.GI3756@pendragon.ideasonboard.com>","date":"2020-10-26T22:51:07","subject":"Re: [libcamera-devel] [PATCH v6 4/6] 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 Mon, Oct 26, 2020 at 05:19:06PM +0000, 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> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n> ---\n>  include/libcamera/geometry.h |  67 +++++++\n>  src/libcamera/geometry.cpp   | 328 +++++++++++++++++++++++++++++++++++\n>  2 files changed, 395 insertions(+)\n> \n> diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h\n> index 02fb63c0..ca5687a7 100644\n> --- a/include/libcamera/geometry.h\n> +++ b/include/libcamera/geometry.h\n> @@ -13,6 +13,38 @@\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> +\tconst std::string toString() const;\n> +\n> +\tconstexpr Point operator-() const\n> +\t{\n> +\t\treturn { -x, -y };\n> +\t}\n> +};\n> +\n> +bool operator==(const Point &lhs, const Point &rhs);\n> +static inline bool operator!=(const Point &lhs, const Point &rhs)\n> +{\n> +\treturn !(lhs == rhs);\n> +}\n> +\n>  class Size\n>  {\n>  public:\n> @@ -93,6 +125,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 +219,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 +231,25 @@ public:\n>  \n>  \tbool isNull() const { return !width && !height; }\n>  \tconst std::string toString() const;\n> +\tPoint center() const;\n> +\n> +\tSize size() const\n> +\t{\n> +\t\treturn { width, height };\n> +\t}\n> +\n> +\tPoint topLeft() const\n> +\t{\n> +\t\treturn { x, y };\n> +\t}\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..438bf90b 100644\n> --- a/src/libcamera/geometry.cpp\n> +++ b/src/libcamera/geometry.cpp\n> @@ -10,6 +10,8 @@\n>  #include <sstream>\n>  #include <stdint.h>\n>  \n> +#include \"libcamera/internal/log.h\"\n> +\n>  /**\n>   * \\file geometry.h\n>   * \\brief Data structures related to geometric objects\n> @@ -17,6 +19,64 @@\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 xpos and \\a ypos 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> + * \\brief Assemble and return a string describing the point\n> + * \\return A string describing the point\n> + */\n> +const std::string Point::toString() const\n> +{\n> +\tstd::stringstream ss;\n> +\n> +\tss << \"(\" << x << \",\" << y << \")\";\n> +\n> +\treturn ss.str();\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> + * \\brief Compare points for equality\n> + * \\return True if the two points are equal, false otherwise\n> + */\n> +bool operator==(const Point &lhs, const Point &rhs)\n> +{\n> +\treturn lhs.x == rhs.x && lhs.y == rhs.y;\n> +}\n> +\n\nDocumentation for operator!= is missing. I'll add it when applying.\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\n>  /**\n>   * \\struct Size\n>   * \\brief Describe a two-dimensional size\n> @@ -143,6 +203,117 @@ 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> + *\n> + * The behaviour of this function is undefined if either the width or the\n> + * height of the \\a ratio is zero.\n> + *\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> +\tASSERT(ratio.width && ratio.height);\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> + *\n> + * The behaviour of this function is undefined if either the width or the\n> + * height of the \\a ratio is zero.\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> +\tASSERT(ratio.width && ratio.height);\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 +536,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 +582,156 @@ 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> + * \\fn Size Rectangle::size() const\n> + * \\brief Retrieve the size of this rectangle\n> + * \\return The Rectangle size\n> + */\n> +\n> +/**\n> + * \\fn Point Rectangle::topLeft() const\n> + * \\brief Retrieve the coordinates of the top left corner of this Rectangle\n> + * \\return The Rectangle's top left corner\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 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 exceed 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 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 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 E7747BDB1E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 26 Oct 2020 22:51:56 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5B00B62064;\n\tMon, 26 Oct 2020 23:51:56 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4948660350\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 26 Oct 2020 23:51:55 +0100 (CET)","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 B26EB527;\n\tMon, 26 Oct 2020 23:51:54 +0100 (CET)"],"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=\"rsy1prUv\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1603752714;\n\tbh=MqDowgVGJlm32W5mPMaF2DV8QpR0cBUck+Tpg5s3jT4=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=rsy1prUvL8bSOAd435qscGk4BRr0+Clq2Ah9g+vl6NGVt0eHXSZYE9j/jP9RpV0Xs\n\tGOaZDjWoDns+F0YPHHOvcu14/eSvMJ5/jsCktAOrru8AuOfK/nUAzvcfxVdOG95opg\n\tHs+1XQHgchKnUmRe9gMW/hbi9+HkxaWEA8dY11zs=","Date":"Tue, 27 Oct 2020 00:51:07 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"David Plowman <david.plowman@raspberrypi.com>","Message-ID":"<20201026225107.GI3756@pendragon.ideasonboard.com>","References":"<20201026171908.21463-1-david.plowman@raspberrypi.com>\n\t<20201026171908.21463-5-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201026171908.21463-5-david.plowman@raspberrypi.com>","Subject":"Re: [libcamera-devel] [PATCH v6 4/6] 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>"}}]