From patchwork Mon Oct 19 12:51:55 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 10098 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id B1AA4BDB1F for ; Mon, 19 Oct 2020 12:52:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7DB2D610C4; Mon, 19 Oct 2020 14:52:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="cQDY6znX"; dkim-atps=neutral Received: from mail-wm1-x330.google.com (mail-wm1-x330.google.com [IPv6:2a00:1450:4864:20::330]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D2C54613AD for ; Mon, 19 Oct 2020 14:52:08 +0200 (CEST) Received: by mail-wm1-x330.google.com with SMTP id k18so12708412wmj.5 for ; Mon, 19 Oct 2020 05:52:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=FlJbCPowPNQgdLxZw1xW4nGcr1UMbOQEpUkrtChTo/Y=; b=cQDY6znXifRUJfx8/gn21swSQSrs4eJmxuNiZUcoHTIcz5dlTM78pG6PtPh2aBGexK Te8j8+Zc4N95myGbICIAZ54L3MEF+iDNKTmAS0ibTn93dwNnMBIHzh3LBvtVN+Q2oCNy XuzVcFWUNWp4H+pEtD1DqPg2UDomhwgNr7Xji98F9Csh/+JOxy/IQxl9Qwrfjau2TI/h PNXmlv2UowWWdrWBfncMrSJvw+7Lkdqgz23bYBUdTlak6aIfy0olj25gV8DDkCF9fhtD 1QTgFMK7nMPRiNh4PakCDjdKL073btKudyhJsxGNhY8pFPBGDjY3vunxuQKUAkU5QOVB sSFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=FlJbCPowPNQgdLxZw1xW4nGcr1UMbOQEpUkrtChTo/Y=; b=PQoya/HWZ5n1dXSRw8eiv2EI1DHaaDQ4t3nRMSwKVMWQJvtMYIi1fAdbp0eopqsw58 0mNSRZQclOhc42N1SmSCxMY2VzP6PCTzRJEPqR/sATNLDbp8qMPJMT9W4WIQ6qpawAmx RJj1T0yTHGXUaMGxoedkJKSRMDB/60k0xNlPmSr0X6BNcth7wN5EFCZ+mZak/BpnQ47E OTVbQYU8KBwY5Ios7lvekge1yrmoFFWiRGNvAcANBu89OYNAuPfE7I5gPZo8Q37tyoRO 8fAN4a0bhFBc/BQH2iIh/6ybERQetdJiEK1XApk71odLUyMXa9/QvCsud9oT6O1PRJx4 nEoQ== X-Gm-Message-State: AOAM530/es67v83i9sl8OWF7Q0nNiIF0gWvmO1rScLvdW0z1PIm8iMUt e4WCIOOWdopzpnXpzfBoSQcIWVn/Jq/FYQ== X-Google-Smtp-Source: ABdhPJwN22WS7uqT48E/38ccVfOMnkv41Qmow706CosCE5vDEwmXCBFP+pjRSb6W+l7j6zmDNDqrLQ== X-Received: by 2002:a1c:791a:: with SMTP id l26mr18363116wme.163.1603111928059; Mon, 19 Oct 2020 05:52:08 -0700 (PDT) Received: from pi4-davidp.pitowers.org ([2a00:1098:3142:14:1ce1:9965:4328:89c4]) by smtp.gmail.com with ESMTPSA id q6sm16634335wma.0.2020.10.19.05.52.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Oct 2020 05:52:07 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Mon, 19 Oct 2020 13:51:55 +0100 Message-Id: <20201019125156.26751-5-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201019125156.26751-1-david.plowman@raspberrypi.com> References: <20201019125156.26751-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 4/5] libcamera: Add geometry helper functions X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" These functions are aimed at making it easier to calculate cropping rectangles, particularly in order to implement digital zoom. Signed-off-by: David Plowman --- include/libcamera/geometry.h | 53 +++++++ src/libcamera/geometry.cpp | 296 +++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h index 02fb63c0..5935cc63 100644 --- a/include/libcamera/geometry.h +++ b/include/libcamera/geometry.h @@ -13,6 +13,25 @@ namespace libcamera { +class Point +{ +public: + constexpr Point() + : x(0), y(0) + { + } + + constexpr Point(int xpos, int ypos) + : x(xpos), y(ypos) + { + } + + int x; + int y; +}; + +class Rectangle; + class Size { public: @@ -32,6 +51,8 @@ public: bool isNull() const { return !width && !height; } const std::string toString() const; + Point center() const; + Size &alignDownTo(unsigned int hAlignment, unsigned int vAlignment) { width = width / hAlignment * hAlignment; @@ -93,8 +114,19 @@ public: std::max(height, expand.height) }; } + + Size boundedToAspectRatio(const Size &ratio) const; + Size expandedToAspectRatio(const Size &ratio) const; + + Rectangle centeredTo(const Point ¢er) const; }; +Size operator*(const Size &size, float factor); +Size operator/(const Size &size, float factor); + +Size &operator*=(Size &size, float factor); +Size &operator/=(Size &size, float factor); + bool operator==(const Size &lhs, const Size &rhs); bool operator<(const Size &lhs, const Size &rhs); @@ -176,6 +208,11 @@ public: { } + constexpr explicit Rectangle(const Size &size) + : x(0), y(0), width(size.width), height(size.height) + { + } + int x; int y; unsigned int width; @@ -183,6 +220,22 @@ public: bool isNull() const { return !width && !height; } const std::string toString() const; + + Point center() const; + + Size size() const; + + Rectangle &translateBy(int dx, int dy); + + Rectangle &rescaleTo(const Size &from, const Size &to); + + Rectangle translatedBy(int dx, int dy) const; + + Rectangle rescaledTo(const Size &from, const Size &to) const; + + Rectangle boundedTo(const Rectangle &bound) const; + + Rectangle clampedTo(const Rectangle &boundary) const; }; bool operator==(const Rectangle &lhs, const Rectangle &rhs); diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp index b12e1a62..75741c1e 100644 --- a/src/libcamera/geometry.cpp +++ b/src/libcamera/geometry.cpp @@ -17,6 +17,36 @@ namespace libcamera { +/** + * \struct Point + * \brief Describe a point in two-dimensional space + * + * The Point structure defines a point in two-dimensional space with integer + * precision. The coordinates of a Point may be negative as well as positive. + */ + +/** + * \fn Point::Point() + * \brief Construct a Point with x and y set to 0 + */ + +/** + * \fn Point::Point(int x, int y) + * \brief Construct a Point at given \a x and \a y values + * \param[in] xpos The x-coordinate + * \param[in] ypos The y-coordinate + */ + +/** + * \var Point::x + * \brief The x-coordinate of the Point + */ + +/** + * \var Point::y + * \brief The y-coordinate of the Point + */ + /** * \struct Size * \brief Describe a two-dimensional size @@ -61,6 +91,15 @@ const std::string Size::toString() const return std::to_string(width) + "x" + std::to_string(height); } +/** + * \brief Retrieve the center point of this size + * \return The center Point + */ +Point Size::center() const +{ + return { static_cast(width / 2), static_cast(height / 2) }; +} + /** * \fn Size::alignDownTo(unsigned int hAlignment, unsigned int vAlignment) * \brief Align the size down horizontally and vertically in place @@ -143,6 +182,101 @@ const std::string Size::toString() const * height of this size and the \a expand size */ +/** + * \brief Bound the size down to match the aspect ratio given by \a ratio + * \param[in] ratio The size whose aspect ratio must be matched + * \return A Size whose width and height are equal to the width and height + * of this Size aligned down to the aspect ratio of \a ratio + */ +Size Size::boundedToAspectRatio(const Size &ratio) const +{ + uint64_t ratio1 = static_cast(width) * + static_cast(ratio.height); + uint64_t ratio2 = static_cast(ratio.width) * + static_cast(height); + + if (ratio1 > ratio2) + return { static_cast(ratio2 / ratio.height), height }; + else + return { width, static_cast(ratio1 / ratio.width) }; +} + +/** + * \brief Expand the size to match the aspect ratio given by \a ratio + * \param[in] ratio The size whose aspect ratio must be matched + * \return A Size whose width and height are equal to the width and height + * of this Size expanded up to the aspect ratio of \a ratio + */ +Size Size::expandedToAspectRatio(const Size &ratio) const +{ + uint64_t ratio1 = static_cast(width) * + static_cast(ratio.height); + uint64_t ratio2 = static_cast(ratio.width) * + static_cast(height); + + if (ratio1 < ratio2) + return { static_cast(ratio2 / ratio.height), height }; + else + return { width, static_cast(ratio1 / ratio.width) }; +} + +/** + * \brief Center a rectangle of this size at a given Point + * \param[in] center The center point the Rectangle is to have + * + * A Rectangle of this object's size is positioned so that its center + * is at the given Point. + * + * \return A Rectangle of this size, centered at the given Point. + */ +Rectangle Size::centeredTo(const Point ¢er) const +{ + int x = center.x - width / 2; + int y = center.y - height / 2; + + return { x, y, width, height }; +} + +/** + * \brief Scale size up by the given factor + * \return The scaled Size + */ +Size operator*(const Size &size, float factor) +{ + return Size(size.width * factor, size.height * factor); +} + +/** + * \brief Scale size down by the given factor + * \return The scaled Size + */ +Size operator/(const Size &size, float factor) +{ + return Size(size.width / factor, size.height / factor); +} + +/** + * \brief Scale size up by the given factor in place + * \return The scaled Size + */ +Size &operator*=(Size &size, float factor) +{ + size.width *= factor; + size.height *= factor; + return size; +} + +/** + * \brief Scale size down by the given factor in place + * \return The scaled Size + */ +Size &operator/=(Size &size, float factor) +{ + size.width /= factor; + size.height /= factor; + return size; +} + /** * \brief Compare sizes for equality * \return True if the two sizes are equal, false otherwise @@ -365,6 +499,13 @@ bool operator==(const SizeRange &lhs, const SizeRange &rhs) * \param[in] height The height */ +/** + * \fn Rectangle::Rectangle(const Size &size) + * \brief Construct a Rectangle of \a size with its top left corner located + * at (0,0). + * \param[in] size The desired Rectangle size + */ + /** * \var Rectangle::x * \brief The horizontal coordinate of the rectangle's top-left corner @@ -404,6 +545,161 @@ const std::string Rectangle::toString() const return ss.str(); } +/** + * \brief Retrieve the center point of this rectangle + * \return The center Point + */ +Point Rectangle::center() const +{ + return { x + static_cast(width / 2), y + static_cast(height / 2) }; +} + +/** + * \brief Retrieve the size of this rectangle + * \return The Rectangle size + */ +Size Rectangle::size() const +{ + return Size(width, height); +} + +/** + * \brief Translate this Rectangle by the given amounts in place + * \param[in] dx The amount to translate the Rectangle in the x direction + * \param[in] dy The amount to translate the Rectangle in the y direction + * + * The Rectangle is translated by the given amounts \a dx and \a dy. + * + * \return A reference to this object + */ +Rectangle &Rectangle::translateBy(int dx, int dy) +{ + x += dx; + y += dy; + + return *this; +} + +/** + * \brief Rescale this Rectangle in place from one coordinate frame to another + * \param[in] from The current coordinate frame of the Rectangle + * \param[in] to The new coordinate frame for the Rectangle + * + * The Rectangle can be regarded as being embedded within a coordinate frame + * given by \a from. It is rescaled so as to occupy the equivalent part of the + * new coordinate frame given by \a to. + * + * This is purely a scaling operation, and the Rectangle does not need to lie + * inside the coordinate frames. + * + * \return A reference to this object + */ +Rectangle &Rectangle::rescaleTo(const Size &from, const Size &to) +{ + x = static_cast(x) * to.width / from.width; + y = static_cast(y) * to.height / from.height; + width = static_cast(width) * to.width / from.width; + height = static_cast(height) * to.height / from.height; + + return *this; +} + +/** + * \brief Translate a Rectangle by the given amounts + * \param[in] dx The amount to translate the Rectangle in the x direction + * \param[in] dy The amount to translate the Rectangle in the y direction + * + * The Rectangle is translated by the given amounts \a dx and \a dy. + * + * \return The translated Rectangle + */ +Rectangle Rectangle::translatedBy(int dx, int dy) const +{ + return { x + dx, y + dy, width, height }; +} + +/** + * \brief Rescale a Rectangle from one coordinate frame to another + * \param[in] from The coordinate frame of the given Rectangle + * \param[in] to The new coordinate frame for the new Rectangle + * + * The Rectangle can be regarded as being embedded within a coordinate frame + * given by \a from. A new Rectangle, rescaled so as to occupy the equivalent + * part of the new coordinate frame given by \a to, is created and returned. + * + * This is purely a scaling operation, and the Rectangle does not need to lie + * inside the coordinate frames. + * + * \return The rescaled Rectangle + */ +Rectangle Rectangle::rescaledTo(const Size &from, const Size &to) const +{ + int scaledX = static_cast(x) * to.width / from.width; + int scaledY = static_cast(y) * to.height / from.height; + unsigned int scaledWidth = static_cast(width) * to.width / from.width; + unsigned int scaledHeight = static_cast(height) * to.height / from.height; + + return { scaledX, scaledY, scaledWidth, scaledHeight }; +} + +/** + * \brief Calculate the intersection of this Rectangle with another + * \param[in] bound The Rectangle that is intersected with this Rectangle + * + * This method calculates the standard intersection of two Rectangles. If the + * Rectangles do not overlap in either the x or y direction, then the size + * of that dimension in the result (its width or height) is set to zero. Even + * when one dimension is set to zero, note that the other dimension may still + * have a positive value if there was some overlap. + * + * \return A Rectangle that is the intersection of the input Rectangles + */ +Rectangle Rectangle::boundedTo(const Rectangle &bound) const +{ + int topLeftX = std::max(x, bound.x); + int topLeftY = std::max(y, bound.y); + int bottomRightX = std::min(x + width, bound.x + bound.width); + int bottomRightY = std::min(y + height, bound.y + bound.height); + + int newWidth = bottomRightX - topLeftX; + int newHeight = bottomRightY - topLeftY; + + return { topLeftX, topLeftY, + static_cast(std::max(newWidth, 0)), + static_cast(std::max(newHeight, 0)) }; +} + +/** + * \brief Clamp a Rectangle so as not to exceed another Rectangle + * \param[in] boundary The limit that the returned Rectangle will not exceed + * + * The Rectangle is modified so that it does not exceeed the given \a boundary. + * This process involves translating the Rectangle if any of its edges + * lie beyond \a boundary, so that those edges then lie along the boundary + * instead. + * + * If either width or height are larger than \a boundary, then the returned + * Rectangle is clipped to be no larger. But other than this, the + * Rectangle is not clipped or reduced in size, merely translated. + * + * We note that this is not a conventional Rectangle intersection function + * \sa Rectangle::boundedTo + * + * \return A Rectangle that does not extend beyond a boundary Rectangle + */ +Rectangle Rectangle::clampedTo(const Rectangle &boundary) const +{ + /* We can't be bigger than the boundary rectangle. */ + Rectangle result = boundedTo(Rectangle{ x, y, boundary.size() }); + + result.x = std::clamp(result.x, boundary.x, + boundary.x + boundary.width - result.width); + result.y = std::clamp(result.y, boundary.y, + boundary.y + boundary.height - result.height); + + return result; +} + /** * \brief Compare rectangles for equality * \return True if the two rectangles are equal, false otherwise