From patchwork Wed Sep 2 10:44:03 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9444 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 10A5CBF019 for ; Wed, 2 Sep 2020 10:44:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CFF3062984; Wed, 2 Sep 2020 12:44:18 +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="IgnkqGk1"; dkim-atps=neutral Received: from mail-wr1-x42e.google.com (mail-wr1-x42e.google.com [IPv6:2a00:1450:4864:20::42e]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0FE10628EE for ; Wed, 2 Sep 2020 12:44:17 +0200 (CEST) Received: by mail-wr1-x42e.google.com with SMTP id m6so4727658wrn.0 for ; Wed, 02 Sep 2020 03:44:17 -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=Wcr7IpixxCkOcntKY+WlarwGA5RJqYNWCxkC24oHDJM=; b=IgnkqGk1vGFy8GfppWbi3c5knHObxptJfNMjIP6Vqt1IFMMZDv9vPtL+my4sU4r8/D KNHXZzrIR6Qy89bQYZ6p5kRw0QACuIdqGVvYoKOxBTA6HS7Cz6g0y6dWBg/H0obRL51M Xt27lrI7o8P4xdTGYAEf4Hrur2ZTsnogkPU+RFw2DtEhX5BcXjkapeau42MGjQ2ucLgW uCa3goKi0qx2Rb0ihsA6lKvHbqMzI7fPrwzWxOwCT8vmG3NZDJAMGkyg9B4YQwM922d4 u8HghiSVZ8OlAuklvI8kH+C78om3v4h9OmXAcAKfu9kh/P2FdsC98vR+dwBMooMlvw9O dMxw== 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=Wcr7IpixxCkOcntKY+WlarwGA5RJqYNWCxkC24oHDJM=; b=BaKUV2Z6qvFUC2VkeUbvczs5UbNU60JZjBs0J6gc45XPojQwXUQ5QurluIb7ur2AcG nuUvAY4oj4kB6Y/fyXxtQjb1VwXZYYUYkJ5Aith3cwiZQ6kPXkGlZs6OLWeVyK0uQV7j mnCzDU1BNelA7brntG9x0HtdLe2ZP5rcTp0B4tE19lIcgAiQgdD/Sd/WkRDIxtJA4qCn VuW/r+cGzDhb30OR8nMd2UeZMwtSys0fCGVkGgyNDQgNUDslVLrSHM/Mdu7l4md5aUnc 3nr/0MlhsPZGxCUF2iN6Q6w/GdXNWNkqKowabvcVf9GlvpKbP19fdX9KL6ZbAVLl/2B/ z2Yw== X-Gm-Message-State: AOAM5338oCImUSkIgoGf0y4ho/vwhpPMCOl9l1Bon/UmK4SZj90/goE2 aWzCkX+HHnS4k9XpipKGGPnTzicMzIrWTA== X-Google-Smtp-Source: ABdhPJze67LxXGmKoYJwWq3dJQLLm7KqUP9un+DF84NgFyGWcPMsMc7lH2kb2n13Q/DyTj11dSMe2w== X-Received: by 2002:a5d:458e:: with SMTP id p14mr6631679wrq.61.1599043456463; Wed, 02 Sep 2020 03:44:16 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:16 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:03 +0100 Message-Id: <20200902104410.7569-2-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 1/8] libcamera: pipeline: raspberrypi: Revert "Set sensor default orientation before configure()" 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" This reverts commit 1e8c91b65695449c5246d17ba7dc439c8058b781. Now that we shall be implementing application-defined 2D transforms it's no longer possible to set the sensor orientation so early on. We have to wait until we have the CameraConfiguration object as that's where the application puts its choice of transform. Signed-off-by: David Plowman Reviewed-by: Kieran Bingham --- src/libcamera/pipeline/raspberrypi/raspberrypi.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index ce43af3..f78b73e 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -962,13 +962,6 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator) /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); - /* Configure the H/V flip controls based on the sensor rotation. */ - ControlList ctrls(data->unicam_[Unicam::Image].dev()->controls()); - int32_t rotation = data->properties_.get(properties::Rotation); - ctrls.set(V4L2_CID_HFLIP, static_cast(!!rotation)); - ctrls.set(V4L2_CID_VFLIP, static_cast(!!rotation)); - data->unicam_[Unicam::Image].dev()->setControls(&ctrls); - /* * List the available output streams. * Currently cannot do Unicam streams! @@ -1173,6 +1166,13 @@ int RPiCameraData::configureIPA() { V4L2_CID_EXPOSURE, result.data[1] } }); sensorMetadata_ = result.data[2]; } + + /* Configure the H/V flip controls based on the sensor rotation. */ + ControlList ctrls(unicam_[Unicam::Image].dev()->controls()); + int32_t rotation = sensor_->properties().get(properties::Rotation); + ctrls.set(V4L2_CID_HFLIP, static_cast(!!rotation)); + ctrls.set(V4L2_CID_VFLIP, static_cast(!!rotation)); + unicam_[Unicam::Image].dev()->setControls(&ctrls); } if (result.operation & RPI_IPA_CONFIG_SENSOR) { From patchwork Wed Sep 2 10:44:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9445 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 B1D12BF019 for ; Wed, 2 Sep 2020 10:44:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 785B3629AB; Wed, 2 Sep 2020 12:44:19 +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="bzOZhiaL"; dkim-atps=neutral Received: from mail-wr1-x443.google.com (mail-wr1-x443.google.com [IPv6:2a00:1450:4864:20::443]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BAEA362984 for ; Wed, 2 Sep 2020 12:44:17 +0200 (CEST) Received: by mail-wr1-x443.google.com with SMTP id g4so4214224wrs.5 for ; Wed, 02 Sep 2020 03:44:17 -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=+QpAyWOuwZpuzlhf2y2xR6YOBGKUy35hWiw41rtlaM4=; b=bzOZhiaLvX59LjMCx6KSSF4e0SRbQ8ng5N4MAWwT7R11z5MgsbWwtS5sD6uI/46et7 Hkfl60qmP+i083vbV6KhGN0SWgIB5HxuUQYXG8aZvrUrQjXDqyJuYH2Ge5+DnGnkXBJ+ PkArBZ3qC8sQLQN3Aebz0FX11omluZ5RTyerw3RiKFK7IVCll/h05L475HDI7IpsOL3H y9zEQiFILkSXSsH/FilOl2aV8uXV0eg/Ecutcx/kxms+GRQnh1G6zruBGldxCOnvWtll QtXZ2IYaKb+7oqggLreIZDbY8ZjoQVtHtKIv5egyjaL4C5MF4xrNBu22MZOUtH+ozUKI aCEw== 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=+QpAyWOuwZpuzlhf2y2xR6YOBGKUy35hWiw41rtlaM4=; b=oDKZcQHfIdv0CboNVzhSyJ5o0piQ4rZCrktGuVCJaEDoaFfs5pzuznJFIa/FS4jhSO 0m58rQ7oW97CxHbfA35VQxIrBeTYy+1vny8MYKmrAWKhlm4GJRdTEWHz04WJhteH9+7b z5/G4ESEsNAnwrkMf25gHPkScF+Swb+SJJZSzKmx3p3CdEV4Fujqw7HN2ztlZbN9iOdG vh+NtBLQdH5qr7aATfimjHUn+VF+OsIDHg19C1AwDM5avZyyuH/mf9x1w+uX8orYJ5/Z 9FdaKkYfVw3+urA3FPuOQ2qKsDXMTEXZ0WNPiZZv1bkpxPI+qN6W3zo7pMmCgYF6J86S Gd7A== X-Gm-Message-State: AOAM530zO0HsVzO+NmBS39lUowzft6FbRKthPHZjwrrFk8ka67uKne2U EGIIFZ0UhTcQb3+luxcWn9iIwxcYshXNpw== X-Google-Smtp-Source: ABdhPJy0kTcInu4z0U1m1jJgtwgRy9UOK9ka47dC/NDrCLx2x1qhvR3UwiTITcGL3pFE9nT4/Yio7g== X-Received: by 2002:a5d:4bc6:: with SMTP id l6mr7024182wrt.132.1599043457281; Wed, 02 Sep 2020 03:44:17 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:16 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:04 +0100 Message-Id: <20200902104410.7569-3-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 2/8] libcamera: Allow access to v4l2_query_ext_ctrl structure for a V4L2 control 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" The V4L2Device::controInfo method simply returns a pointer to the v4l2_query_ext_ctrl structure for the given control, which has already been retrieved and stored. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- include/libcamera/internal/v4l2_device.h | 2 ++ src/libcamera/v4l2_device.cpp | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 3b605aa..722fb72 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -29,6 +29,8 @@ public: ControlList getControls(const std::vector &ids); int setControls(ControlList *ctrls); + const struct v4l2_query_ext_ctrl *controlInfo(uint32_t id) const; + const std::string &deviceNode() const { return deviceNode_; } std::string devicePath() const; diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 65830d4..e8399c4 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -353,6 +353,21 @@ int V4L2Device::setControls(ControlList *ctrls) return ret; } +/** + * \brief Return the v4l2_query_ext_ctrl information for the given control. + * \param[in] id The V4L2 control id + * \return A pointer to the v4l2_query_ext_ctrl structure for the given + * control, or a null pointer if not found + */ +const struct v4l2_query_ext_ctrl *V4L2Device::controlInfo(uint32_t id) const +{ + const auto it = controlInfo_.find(id); + if (it == controlInfo_.end()) + return nullptr; + + return &it->second; +} + /** * \brief Retrieve the device path in sysfs * From patchwork Wed Sep 2 10:44:05 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9447 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 6EAAEBF6D3 for ; Wed, 2 Sep 2020 10:44:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 39CD2629C7; Wed, 2 Sep 2020 12:44:21 +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="sXHfbwMY"; dkim-atps=neutral Received: from mail-wr1-x434.google.com (mail-wr1-x434.google.com [IPv6:2a00:1450:4864:20::434]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EA949628EE for ; Wed, 2 Sep 2020 12:44:19 +0200 (CEST) Received: by mail-wr1-x434.google.com with SMTP id w5so4705633wrp.8 for ; Wed, 02 Sep 2020 03:44:19 -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=MWoVM8Ok4SRj4PL+Wv9SgyQ412qDxa867Pxlto/+NDQ=; b=sXHfbwMYnI0k2CKvv/Ag3N9Pa3JSR9HkpR+M09MFuRaUrkm3YcBYAmrKq1H0gN/QN9 r1GMkjGDuqd/42M+S8RFbI95KNFER4iAn6SNSarFueKotfhcdrba/vSbsEAOQtc830Vx kN4ANYjXqB7F/eXxNHMcZ9K9vcCKAudwVB3tnu7iEsqCu4DCmNXzHjmtisOicsAzLt45 pqgfL2jD5LRxtTnvm2MmQVK1pLwLh/G2vWxbL0fa7BpF9ljK9rH0YuY0/RWofp2sjPvs VahOnmAL+NS8jJG5pLr+qehAOnX3vGSXis5cWWM+QR/HTQ+qxZSPihrOJ0BBIT2nkFf/ XniA== 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=MWoVM8Ok4SRj4PL+Wv9SgyQ412qDxa867Pxlto/+NDQ=; b=Vm+Bofsq2pFudhGBjRbtNZZYB8jjVWHKY3/qqiZjT7bkw2ZDi+SQw6t9mpcAdz0Pwt 8s0dntuxZykSDQW7UiSJDvZTvSinQKfnvLCVoKL56dfNxl0iDkDW6suA0Pey9mBqaEj2 D2i4Llq8hf4v2EA5EzMf+lnnBHptMGT+OAQY8o0S3RTOsh5Do96VV+NE+2p4j25FhYWm 5FQRYZtTEXcnjLPeRVmaS1eSSphBG+0kOISKjVUPHTVJauWDSldx9y5kT5aLD3bRecIV 4HUGu2JnQpK51uun6236i9YVRdAOlT2hiKBIX7Z7jhb+SN9IM6BECndvGqKj76HuYm/Z jCVQ== X-Gm-Message-State: AOAM5337X/6YX8zMmVRMmbnlWGZSlSRWKs6CL0TZcIGRxIm0CMoF3IgD xs0+VqiU5cLhUgm4aSLLCjFsVXSu+l9p/Q== X-Google-Smtp-Source: ABdhPJz+37IAu2pth6jMz/PjRsSVgA8zCRfSxqMErWaSFPqmKu+OfRSLCt1fP1CsPfse6hA9E6eiLw== X-Received: by 2002:adf:e94a:: with SMTP id m10mr7099641wrn.249.1599043458107; Wed, 02 Sep 2020 03:44:18 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:17 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:05 +0100 Message-Id: <20200902104410.7569-4-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 3/8] libcamera: Add Transform enum to represent 2D plane transforms. 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" We implement 2D transforms as an enum class with 8 elements, consisting of the usual 2D plane transformations (flips, rotations etc.). The transform is made up of 3 bits, indicating whether the transform includes: a transpose, a horizontal flip (mirror) and a vertical flip. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- include/libcamera/meson.build | 1 + include/libcamera/transform.h | 78 ++++++++ src/libcamera/meson.build | 1 + src/libcamera/transform.cpp | 322 ++++++++++++++++++++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 include/libcamera/transform.h create mode 100644 src/libcamera/transform.cpp diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build index cdb8e03..7fae5e5 100644 --- a/include/libcamera/meson.build +++ b/include/libcamera/meson.build @@ -19,6 +19,7 @@ libcamera_public_headers = files([ 'span.h', 'stream.h', 'timer.h', + 'transform.h', ]) include_dir = join_paths(libcamera_include_dir, 'libcamera') diff --git a/include/libcamera/transform.h b/include/libcamera/transform.h new file mode 100644 index 0000000..71b43da --- /dev/null +++ b/include/libcamera/transform.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Limited + * + * transform.h - 2D plane transforms + */ + +#ifndef __LIBCAMERA_TRANSFORM_H__ +#define __LIBCAMERA_TRANSFORM_H__ + +#include + +namespace libcamera { + +enum class Transform : int { + Identity = 0, + Rot0 = Identity, + HFlip = 1, + VFlip = 2, + HVFlip = HFlip | VFlip, + Rot180 = HVFlip, + Transpose = 4, + Rot270 = HFlip | Transpose, + Rot90 = VFlip | Transpose, + Rot180Transpose = HFlip | VFlip | Transpose +}; + +constexpr Transform operator&(Transform t0, Transform t1) +{ + return static_cast(static_cast(t0) & static_cast(t1)); +} + +constexpr Transform operator|(Transform t0, Transform t1) +{ + return static_cast(static_cast(t0) | static_cast(t1)); +} + +constexpr Transform operator^(Transform t0, Transform t1) +{ + return static_cast(static_cast(t0) ^ static_cast(t1)); +} + +constexpr Transform &operator&=(Transform &t0, Transform t1) +{ + return t0 = t0 & t1; +} + +constexpr Transform &operator|=(Transform &t0, Transform t1) +{ + return t0 = t0 | t1; +} + +constexpr Transform &operator^=(Transform &t0, Transform t1) +{ + return t0 = t0 ^ t1; +} + +Transform operator*(Transform t0, Transform t1); + +Transform operator-(Transform t); + +constexpr bool operator!(Transform t) +{ + return t == Transform::Identity; +} + +constexpr Transform operator~(Transform t) +{ + return static_cast(~static_cast(t) & 7); +} + +Transform transformFromRotation(int angle, bool *success = nullptr); + +const char *transformToString(Transform t); + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_TRANSFORM_H__ */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index af2f3d9..edec55e 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -44,6 +44,7 @@ libcamera_sources = files([ 'sysfs.cpp', 'thread.cpp', 'timer.cpp', + 'transform.cpp', 'utils.cpp', 'v4l2_controls.cpp', 'v4l2_device.cpp', diff --git a/src/libcamera/transform.cpp b/src/libcamera/transform.cpp new file mode 100644 index 0000000..f3e37f3 --- /dev/null +++ b/src/libcamera/transform.cpp @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Limited + * + * transform.cpp - 2D plane transforms. + */ + +#include + +/** + * \file transform.h + * \brief Enum to represent and manipulate 2D plane transforms + */ + +namespace libcamera { + +/** + * \enum Transform + * \brief Enum to represent a 2D plane transform + * + * The Transform can take 8 distinct values, representing the usual 2D plane + * transforms listed below. Each of these transforms can be constructed + * out of 3 basic operations, namely a horizontal flip (mirror), a vertical + * flip, and a transposition (about the main diagonal). The transforms are + * encoded such that a single bit indicates the presence of each of the 3 + * basic operations: + * + * - bit 0 - presence of a horizontal flip + * - bit 1 - presence of a vertical flip + * - bit 2 - presence of a transposition. + * + * We regard these 3 basic operations as being applied in a specific order: + * first the two flip operations (actually they commute, so the order between + * them is unimportant) and finally any transpose operation. + * + * Functions are provided to manipulate directly the bits within the transform + * encoding, but there are also higher-level functions to invert and compose + * transforms. Transforms are composed according to the usual mathematical + * convention such that the right transform is applied first, and the left + * transform is applied second. + * + * Finally, we have a total of 8 distinct transformations, as follows (a + * couple of them have additional synonyms for convenience). We illustrate each + * with its nominal effect on a rectangle with vertices labelled A, B, C and D. + * + * **Identity** + * + * Identity transform. +~~~ + A-B A-B +Input image | | goes to output image | | + C-D C-D +~~~ + * Numeric value: 0 (no bits set). + * + * **Rot0** + * + * Synonym for `Identity` (zero degree rotation). + * + * **HFlip** + * + * Horizontal flip. +~~~ + A-B B-A +Input image | | goes to output image | | + C-D D-C +~~~ + * Numeric value: 1 (horizontal flip bit set only). + * + * **VFlip** + * + * Vertical flip. +~~~ + A-B C-D +Input image | | goes to output image | | + C-D A-B +~~~ + * Numeric value: 2 (vertical flip bit set only). + * + * **HVFlip** + * + * Horizontal and vertical flip (identical to a 180 degree rotation). +~~~ + A-B D-C +Input image | | goes to output image | | + C-D B-A +~~~ + * Numeric value: 3 (horizontal and vertical flip bits set). + * + * **Rot180** + * + * Synonym for `HVFlip` (180 degree rotation). + * + * **Transpose** + * + * Transpose (about the main diagonal). +~~~ + A-B A-C +Input image | | goes to output image | | + C-D B-D +~~~ + * Numeric value: 4 (transpose bit set only). + * + * **Rot270** + * + * Rotation by 270 degrees clockwise (90 degrees anticlockwise). +~~~ + A-B B-D +Input image | | goes to output image | | + C-D A-C +~~~ + * Numeric value: 5 (transpose and horizontal flip bits set). + * + * **Rot90** + * + * Rotation by 90 degrees clockwise (270 degrees anticlockwise). +~~~ + A-B C-A +Input image | | goes to output image | | + C-D D-B +~~~ + * Numeric value: 6 (transpose and vertical flip bits set). + * + * **Rot180Transpose** + * + * Rotation by 180 degrees followed by transpose (alternatively, transposition + * about the "opposite diagonal"). +~~~ + A-B D-B +Input image | | goes to output image | | + C-D C-A +~~~ + * Numeric value: 7 (all bits set). + * + * \sa https://en.wikipedia.org/wiki/Examples_of_groups#dihedral_group_of_order_8 + * + * The set of 2D plane transforms is also known as the symmetry group of a + * square, described in the link. Note that the group can be generated by + * only 2 elements (the horizontal flip and a 90 degree rotation, for + * example), however, the encoding used here makes the presence of the vertical + * flip explicit. + */ + +/** + * \fn operator &(Transform t0, Transform t1) + * \brief Apply bitwise AND operator between the bits in the two transforms + * \param[in] t0 The first transform + * \param[in] t1 The second transform + */ + +/** + * \fn operator |(Transform t0, Transform t1) + * \brief Apply bitwise OR operator between the bits in the two transforms + * \param[in] t0 The first transform + * \param[in] t1 The second transform + */ + +/** + * \fn operator ^(Transform t0, Transform t1) + * \brief Apply bitwise XOR operator between the bits in the two transforms + * \param[in] t0 The first transform + * \param[in] t1 The second transform + */ + +/** + * \fn operator &=(Transform &t0, Transform t1) + * \brief Apply bitwise AND-assignment operator between the bits in the two + * transforms + * \param[in] t0 The first transform + * \param[in] t1 The second transform + */ + +/** + * \fn operator |=(Transform &t0, Transform t1) + * \brief Apply bitwise OR-assignment operator between the bits in the two + * transforms + * \param[in] t0 The first transform + * \param[in] t1 The second transform + */ + +/** + * \fn operator ^=(Transform &t0, Transform t1) + * \brief Apply bitwise XOR-assignment operator between the bits in the two + * transforms + * \param[in] t0 The first transform + * \param[in] t1 The second transform + */ + +/** + * \brief Compose two transforms together + * \param[in] t1 The second transform + * \param[in] t0 The first transform + * + * Composing transforms follows the usual mathematical convention for + * composing functions. That is, when performing `t1 * t0`, \a t0 is applied + * first, and then \a t1. + * For example, `Transpose * HFlip` performs `HFlip` first and then the + * `Transpose` yielding `Rot270`, as shown below. +~~~ + A-B B-A B-D +Input image | | -> HFLip -> | | -> Transpose -> | | = Rot270 + C-D D-C A-C +~~~ + * Note that composition is generally non-commutative for Transforms, + * and not the same as XOR-ing the underlying bit representations. + */ +Transform operator*(Transform t1, Transform t0) +{ + /* + * Reorder the operations so that we imagine doing t0's transpose + * (if any) after t1's flips. The effect is to swap t1's hflips for + * vflips and vice versa, after which we can just xor all the bits. + */ + Transform reordered = t1; + if (!!(t0 & Transform::Transpose)) { + reordered = t1 & Transform::Transpose; + if (!!(t1 & Transform::HFlip)) + reordered |= Transform::VFlip; + if (!!(t1 & Transform::VFlip)) + reordered |= Transform::HFlip; + } + + return reordered ^ t0; +} + +/** + * \brief Invert a transform + * \param[in] t The transform to be inverted + * + * That is, we return the transform such that `t * (-t)` and `(-t) * t` both + * yield the identity transform. + */ +Transform operator-(Transform t) +{ + /* All are self-inverses, except for Rot270 and Rot90. */ + static const Transform inverses[] = { + Transform::Identity, + Transform::HFlip, + Transform::VFlip, + Transform::HVFlip, + Transform::Transpose, + Transform::Rot90, + Transform::Rot270, + Transform::Rot180Transpose + }; + + return inverses[static_cast(t)]; +} + +/** + * \fn operator!(Transform t) + * \brief Return `true` if the transform is the `Identity`, otherwise `false` + * \param[in] t The transform to be tested + */ + +/** + * \fn operator~(Transform t) + * \brief Return the transform with all the bits inverted individually + * \param[in] t The transform of which the bits will be inverted + * + * This inverts the bits that encode the transform in a bitwise manner. Note + * that this is not the proper inverse of transform \a t (for which use \a + * operator-). + */ + +/** + * \brief Return the transform representing a rotation of the given angle + * clockwise + * \param[in] angle The angle of rotation in a clockwise sense. Negative values + * can be used to represent anticlockwise rotations + * \param[out] success Set to `true` if the angle is a multiple of 90 degrees, + * otherwise `false` + * \return The transform corresponding to the rotation if \a success was set to + * `true`, otherwise the `Identity` transform + */ +Transform transformFromRotation(int angle, bool *success) +{ + angle = angle % 360; + if (angle < 0) + angle += 360; + + if (success != nullptr) + *success = true; + + switch (angle) { + case 0: + return Transform::Identity; + case 90: + return Transform::Rot90; + case 180: + return Transform::Rot180; + case 270: + return Transform::Rot270; + } + + if (success != nullptr) + *success = false; + + return Transform::Identity; +} + +/** + * \brief Return a character string describing the transform + * \param[in] t The transform to be described. + */ +const char *transformToString(Transform t) +{ + static const char *strings[] = { + "identity", + "hflip", + "vflip", + "hvflip", + "transpose", + "rot270", + "rot90", + "rot180transpose" + }; + + return strings[static_cast(t)]; +} + +} /* namespace libcamera */ From patchwork Wed Sep 2 10:44:06 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9446 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 10AA4BF019 for ; Wed, 2 Sep 2020 10:44:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CFDE062984; Wed, 2 Sep 2020 12:44:20 +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="SOws2v7h"; dkim-atps=neutral Received: from mail-wr1-x442.google.com (mail-wr1-x442.google.com [IPv6:2a00:1450:4864:20::442]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8AEEF629C7 for ; Wed, 2 Sep 2020 12:44:19 +0200 (CEST) Received: by mail-wr1-x442.google.com with SMTP id k15so4691530wrn.10 for ; Wed, 02 Sep 2020 03:44:19 -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=pNKjRwwRfo6GXLykvpBfguqZnWGomhKyzTnClISj5qE=; b=SOws2v7hw9Llzy8m/stOEGnm6/r+Fh10TellCJyopFPFRAv4/m8ihqrkxmiqL9T7j2 wGgFdNHRJyOY5mHO3oqkgE7x4svU8jUEk00gQexlPTo5YP3Bi7DL1NWBu6VMp7/nJ6Xx gThpEbY157nxoPXcrULpB4e9E5jRS9BJGE82g75sDoowTy+EZStUxlXA3X9GPwXMJZvI vgp93crfLJ3UwNZqSg0bvBUbgVBmY6hY4ubh99WtIEp6cD3MqS0sJKTvofTWiZ085klR eVbrmzNjnA0T9mmRqxLQlhW/yJUWP7NXLcu/ysVU7ze5jfrPyhLGxbpKk2yUWsk3AYrN lyWQ== 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=pNKjRwwRfo6GXLykvpBfguqZnWGomhKyzTnClISj5qE=; b=huJAl+RtG77t0/l8iwNljemlW/wjPPXhWBmJLXEkmnMmzbbBBW7jYJkwTrWAKnjPQ+ wnBO+XMmKK4QMSR6fMrL2OZvopq/AjrjZYwzggx2rl9Ygx0lzLe6Tig84FMDixpljpMb KxUs/MnqdL46h/mZfdMWXkNQDLHKvsw0TkrLz2TobV2IZKlmR0xOgU7kozGKT6VI86Xw rqSC+gDttgL62xsRCGSdALnOwqnN+xxLq/MWQWH/zDFHjyfUYtFhEU4VGCIZyLRA0FQ3 UAU6+0rOatkyIuQUBBU8gWVv9pBKTuEkyJDVonZQ3H3q3Awc5i1RaUlI5zsyujqefES3 vCnQ== X-Gm-Message-State: AOAM53130ODFAflI/j7N602ZkVWFcme4Tk6K2+ZfOlDRKJCVLsr50Ugk DmBWV9MjLGE4chwPUF0nLR7QtXOuRYkO2w== X-Google-Smtp-Source: ABdhPJxWaYiqTYDC+Z2YMzLeNEtd1+JSaj+cAaGz09M+9AsCzrzTGKrOp0YYg6sAQTTbix2k/3/khw== X-Received: by 2002:adf:f492:: with SMTP id l18mr1829981wro.280.1599043458812; Wed, 02 Sep 2020 03:44:18 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:18 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:06 +0100 Message-Id: <20200902104410.7569-5-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 4/8] libcamera: Add BayerFormat type 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" This type encodes BayerFormats in an explicit way, that makes them easier to use than some of the other more opaque type formats. This makes the BayerFormat useful for editing or manipulating Bayer types more easily. Signed-off-by: David Plowman Reviewed-by: Kieran Bingham --- include/libcamera/internal/bayer_format.h | 63 ++++++ src/libcamera/bayer_format.cpp | 223 ++++++++++++++++++++++ src/libcamera/meson.build | 1 + 3 files changed, 287 insertions(+) create mode 100644 include/libcamera/internal/bayer_format.h create mode 100644 src/libcamera/bayer_format.cpp diff --git a/include/libcamera/internal/bayer_format.h b/include/libcamera/internal/bayer_format.h new file mode 100644 index 0000000..3a0f5cb --- /dev/null +++ b/include/libcamera/internal/bayer_format.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. + * + * bayer_format.h - Bayer Pixel Format + */ +#ifndef __LIBCAMERA_INTERNAL_BAYER_FORMAT_H__ +#define __LIBCAMERA_INTERNAL_BAYER_FORMAT_H__ + +#include +#include + +#include "libcamera/internal/v4l2_pixelformat.h" + +namespace libcamera { + +enum class Transform; + +class BayerFormat +{ +public: + enum Order : uint8_t { + BGGR = 0, + GBRG = 1, + GRBG = 2, + RGGB = 3 + }; + + enum Modifier : uint16_t { + None = 0, + Packed = 1, + }; + + constexpr BayerFormat() + : order(Order::BGGR), bitDepth(0), modifiers(Modifier::None) + { + } + + constexpr BayerFormat(Order o, uint8_t b, Modifier m) + : order(o), bitDepth(b), modifiers(m) + { + } + + explicit BayerFormat(V4L2PixelFormat v4l2Format); + + bool isValid() const { return bitDepth != 0; } + + std::string toString() const; + + V4L2PixelFormat toV4L2PixelFormat() const; + + BayerFormat transform(Transform t) const; + + Order order; + + uint8_t bitDepth; + + Modifier modifiers; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_BAYER_FORMAT_H__ */ diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp new file mode 100644 index 0000000..cc8ed66 --- /dev/null +++ b/src/libcamera/bayer_format.cpp @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Limited + * + * bayer_format.cpp - class to represent Bayer formats + */ + +#include "libcamera/internal/bayer_format.h" + +#include + +#include + +/** + * \file bayer_format.h + * \brief Class to represent Bayer formats and manipulate them + */ + +namespace libcamera { + +/** + * \class BayerFormat + * \brief Class to represent a raw image Bayer format + * + * This class encodes the different Bayer formats in such a way that they can + * be easily manipulated. For example, the bit depth or Bayer order can be + * easily altered - the Bayer order can even be "transformed" in the same + * manner as happens in many sensors when their horizontal or vertical "flip" + * controls are set. + */ + +/** + * \enum BayerFormat::Order + * \brief The order of the colour channels in the Bayer pattern + * + * \var BayerFormat::BGGR + * \brief B then G on the first row, G then R on the second row. + * \var BayerFormat::GBRG + * \brief G then B on the first row, R then G on the second row. + * \var BayerFormat::GRBG + * \brief G then R on the first row, B then G on the second row. + * \var BayerFormat::RGGB + * \brief T then G on the first row, G then B on the second row. + */ + +/** + * \enum BayerFormat::Modifier + * \brief Modifiers that can apply to a BayerFormat + * + * \var BayerFormat::None + * \brief No modifiers + * \var BayerFormat::Packed + * \brief Format uses MIPI CSI-2 style packing + */ + +namespace { + +const std::map v4l2ToBayer{ + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8), { BayerFormat::BGGR, 8, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG8), { BayerFormat::GBRG, 8, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG8), { BayerFormat::GRBG, 8, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB8), { BayerFormat::RGGB, 8, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10), { BayerFormat::BGGR, 10, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10), { BayerFormat::GBRG, 10, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10), { BayerFormat::GRBG, 10, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10), { BayerFormat::RGGB, 10, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P), { BayerFormat::BGGR, 10, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10P), { BayerFormat::GBRG, 10, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10P), { BayerFormat::GRBG, 10, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P), { BayerFormat::RGGB, 10, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12), { BayerFormat::BGGR, 12, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12), { BayerFormat::GBRG, 12, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12), { BayerFormat::GRBG, 12, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12), { BayerFormat::RGGB, 12, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12P), { BayerFormat::BGGR, 12, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12P), { BayerFormat::GBRG, 12, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12P), { BayerFormat::GRBG, 12, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12P), { BayerFormat::RGGB, 12, BayerFormat::Packed } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR16), { BayerFormat::BGGR, 16, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG16), { BayerFormat::GBRG, 16, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG16), { BayerFormat::GRBG, 16, BayerFormat::None } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB16), { BayerFormat::RGGB, 16, BayerFormat::None } }, +}; + +/* Define a slightly arbitrary ordering so that we can use a std::map. */ +struct BayerFormatComparator { + bool operator()(const BayerFormat &left, const BayerFormat &right) const + { + return left.bitDepth > right.bitDepth || + (left.bitDepth == right.bitDepth && left.order > right.order) || + (left.bitDepth == right.bitDepth && left.order == right.order && + left.modifiers > right.modifiers); + } +}; + +const std::map bayerToV4l2{ + { { BayerFormat::BGGR, 8, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8) }, + { { BayerFormat::GBRG, 8, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG8) }, + { { BayerFormat::GRBG, 8, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG8) }, + { { BayerFormat::RGGB, 8, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB8) }, + { { BayerFormat::BGGR, 10, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10) }, + { { BayerFormat::GBRG, 10, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10) }, + { { BayerFormat::GRBG, 10, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10) }, + { { BayerFormat::RGGB, 10, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10) }, + { { BayerFormat::BGGR, 10, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P) }, + { { BayerFormat::GBRG, 10, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10P) }, + { { BayerFormat::GRBG, 10, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10P) }, + { { BayerFormat::RGGB, 10, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P) }, + { { BayerFormat::BGGR, 12, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12) }, + { { BayerFormat::GBRG, 12, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12) }, + { { BayerFormat::GRBG, 12, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12) }, + { { BayerFormat::RGGB, 12, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12) }, + { { BayerFormat::BGGR, 12, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12P) }, + { { BayerFormat::GBRG, 12, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12P) }, + { { BayerFormat::GRBG, 12, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12P) }, + { { BayerFormat::RGGB, 12, BayerFormat::Packed }, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12P) }, + { { BayerFormat::BGGR, 16, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR16) }, + { { BayerFormat::GBRG, 16, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG16) }, + { { BayerFormat::GRBG, 16, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG16) }, + { { BayerFormat::RGGB, 16, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB16) }, +}; + +} // namespace + +/** + * \fn BayerFormat::BayerFormat() + * \brief Construct an empty (and invalid) BayerFormat + */ + +/** + * \fn BayerFormat::BayerFormat(Order o, uint8_t b, Modifier m) + * \brief Construct a BayerFormat from explicit values + */ + +/** + * \brief Construct a BayerFormat from a V4L2PixelFormat + * \param[in] v4l2Format The raw format to convert into a BayerFormat + */ +BayerFormat::BayerFormat(V4L2PixelFormat v4l2Format) +{ + const auto it = v4l2ToBayer.find(v4l2Format); + if (it == v4l2ToBayer.end()) + bitDepth = 0; + else + *this = it->second; +} + +/** + * \fn BayerFormat::isValid() + * \brief Return whether a BayerFormat is valid + */ + +/** + * \brief Returns a readable string representation of the BayerFormat + */ +std::string BayerFormat::toString() const +{ + std::string result; + + static const char *orderStrings[] = { + "BGGR", + "GBRG", + "GRBG", + "RGGB" + }; + if (order <= RGGB) + result = orderStrings[order]; + else + result = "unknown"; + + result += "-" + std::to_string(bitDepth); + + if (modifiers & Packed) + result += "-P"; + + return result; +} + +/** + * \brief Convert a BayerFormat into the corresponding V4L2PixelFormat + */ +V4L2PixelFormat BayerFormat::toV4L2PixelFormat() const +{ + const auto it = bayerToV4l2.find(*this); + if (it != bayerToV4l2.end()) + return it->second; + + return V4L2PixelFormat(); +} + +/** + * \brief Apply a transform to this BayerFormat. + * \param[in] t The transform to apply + */ +BayerFormat BayerFormat::transform(Transform t) const +{ + BayerFormat result = *this; + + /* The order encoding is chosen to behave like this: */ + if (!!(t & Transform::HFlip)) + result.order = static_cast(result.order ^ 1); + if (!!(t & Transform::VFlip)) + result.order = static_cast(result.order ^ 2); + + return result; +} + +/** + * \var BayerFormat::order + * \brief The order of the colour channels in the Bayer pattern + */ + +/** + * \var BayerFormat::bitDepth + * \brief The bit depth of the samples in the Bayer pattern + */ + +/** + * \var BayerFormat::modifiers + * \brief Any modifier flags applied to this BayerFormat + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index edec55e..86c225f 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 libcamera_sources = files([ + 'bayer_format.cpp', 'bound_method.cpp', 'buffer.cpp', 'byte_stream_buffer.cpp', From patchwork Wed Sep 2 10:44:07 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9448 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 02335BF019 for ; Wed, 2 Sep 2020 10:44:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C4074629B8; Wed, 2 Sep 2020 12:44:21 +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="jyO4CIZ/"; dkim-atps=neutral Received: from mail-wr1-x435.google.com (mail-wr1-x435.google.com [IPv6:2a00:1450:4864:20::435]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 60648628EE for ; Wed, 2 Sep 2020 12:44:20 +0200 (CEST) Received: by mail-wr1-x435.google.com with SMTP id z4so4703430wrr.4 for ; Wed, 02 Sep 2020 03:44:20 -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=NoWPL5STjhHoP2Bx4FsXHaV0u34wvih5RQqaTMaxQmU=; b=jyO4CIZ/X/hIajrN5W297lrn8j0sFZFg86XCxn0GVAFQPphXRnbJyAP2j8oTEaBg8e +eWeH8vIj3mRTCszv6bcDw+Myjfx89K0NNlo7RoXkh/ktqZquH4xfn3LzJWIkQ7oHTXW qbdWWZhUd1qRxa7GzT69FmHIvvFbyTj34t7JAorFesy/aFfvPfkXAgbTpdAWIZqNsC3I eqBNu3TCqO4E2rVtmfuyA7wI5Y49c+DkX7trDtdWnBMZIpVp6B/FhzW9V9t1KbjvRe9P cJvxPUh6gBMlSv2dq3bIZ318Ub2n8ImPYdpr5LrcXTcXAVcSM/e3OCivdeWX9q4srxOI UI9w== 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=NoWPL5STjhHoP2Bx4FsXHaV0u34wvih5RQqaTMaxQmU=; b=VlDnIsD7rrfTwGVKutwYKspH04min3H6Am8aL0Lr5WMDkSceAM4b30c5Nerd3JNY+u FzUGOVnY38rfMbXO78lMo2qJ4OsZzPMXsmUp31utcIHB0FAAVCxI9tomUZ8IYL5CX629 6fhM44qclK1Pa3Ylp7eXAGKOoyP9KQf/GxNEiSlJJW9TkNEGXI2BJ1/W595lo8yHDKxK 76rBhRcFZkWXDgEQhg3BqR2E/3tp3+gTTbnffIYE25mI9BDCFHVXZO+JYB2O5ufNsCdb y8rT6IaFaFqoSelsoWa7mRTcjvddhGFQLj7a9cpaMupVbl4+TyJr/u5o1xwNN8+WPZJr hweQ== X-Gm-Message-State: AOAM531+SEECET1c4o1oP4z6/rFyaJg9Fybkc6luripH+ON4XMF6cP3B 90iIDnsyJWgeU00W4aKI450EgckkQrJD1Q== X-Google-Smtp-Source: ABdhPJz3HMwt1LDPLB7JNSPw5m6WZ2zGAK0JFztzZ58dQFBl/tm4Z8K2CwIeUIE0Xn++A31Kp4vpqw== X-Received: by 2002:a5d:4e0b:: with SMTP id p11mr6338690wrt.32.1599043459776; Wed, 02 Sep 2020 03:44:19 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:19 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:07 +0100 Message-Id: <20200902104410.7569-6-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 5/8] libcamera: Add user Transform to CameraConfiguration 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" Add a field to the CameraConfiguration (including the necessary documentation) to represent a 2D transform requested by the application. All pipeline handlers are amended to coerce this to the Identity, marking the configuration as "adjusted" if something different had been requested. Pipeline handlers that support Transforms can be amended subsequently. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- include/libcamera/camera.h | 3 +++ src/libcamera/camera.cpp | 16 +++++++++++++++- src/libcamera/pipeline/ipu3/ipu3.cpp | 5 +++++ .../pipeline/raspberrypi/raspberrypi.cpp | 5 +++++ src/libcamera/pipeline/rkisp1/rkisp1.cpp | 5 +++++ src/libcamera/pipeline/simple/simple.cpp | 5 +++++ src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 5 +++++ src/libcamera/pipeline/vimc/vimc.cpp | 5 +++++ 8 files changed, 48 insertions(+), 1 deletion(-) diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index 272c12c..a2ee4e7 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -17,6 +17,7 @@ #include #include #include +#include namespace libcamera { @@ -61,6 +62,8 @@ public: bool empty() const; std::size_t size() const; + Transform transform; + protected: CameraConfiguration(); diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 4a9c19c..b547ffe 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -93,7 +93,7 @@ LOG_DECLARE_CATEGORY(Camera) * \brief Create an empty camera configuration */ CameraConfiguration::CameraConfiguration() - : config_({}) + : transform(Transform::Identity), config_({}) { } @@ -250,6 +250,20 @@ std::size_t CameraConfiguration::size() const return config_.size(); } +/** + * \var CameraConfiguration::transform + * \brief User-specified transform to be applied to the image + * + * The transform is a user-specified 2D plane transform that will be applied + * to the camera images by the processing pipeline before being handed to + * the application. This is subsequent to any transform that is already + * required to fix up any platform-defined rotation. + * + * The usual 2D plane transforms are allowed here (horizontal/vertical + * flips, multiple of 90-degree rotations etc.), but the validate() function + * may adjust this field at its discretion if the selection is not supported. + */ + /** * \var CameraConfiguration::config_ * \brief The vector of stream configurations diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index 2d881fe..22b8825 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -138,6 +138,11 @@ CameraConfiguration::Status IPU3CameraConfiguration::validate() if (config_.empty()) return Invalid; + if (transform != Transform::Identity) { + transform = Transform::Identity; + status = Adjusted; + } + /* Cap the number of entries to the available streams. */ if (config_.size() > IPU3_MAX_STREAMS) { config_.resize(IPU3_MAX_STREAMS); diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index f78b73e..1087257 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -400,6 +400,11 @@ CameraConfiguration::Status RPiCameraConfiguration::validate() if (config_.empty()) return Invalid; + if (transform != Transform::Identity) { + transform = Transform::Identity; + status = Adjusted; + } + unsigned int rawCount = 0, outCount = 0, count = 0, maxIndex = 0; std::pair outSize[2]; Size maxSize; diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 4d89aab..6f53a1d 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -478,6 +478,11 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() if (config_.empty()) return Invalid; + if (transform != Transform::Identity) { + transform = Transform::Identity; + status = Adjusted; + } + /* Cap the number of entries to the available streams. */ if (config_.size() > 1) { config_.resize(1); diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index eb72e3b..10223a9 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -438,6 +438,11 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() if (config_.empty()) return Invalid; + if (transform != Transform::Identity) { + transform = Transform::Identity; + status = Adjusted; + } + /* Cap the number of entries to the available streams. */ if (config_.size() > 1) { config_.resize(1); diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index bafe6f1..ba0efc8 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -109,6 +109,11 @@ CameraConfiguration::Status UVCCameraConfiguration::validate() if (config_.empty()) return Invalid; + if (transform != Transform::Identity) { + transform = Transform::Identity; + status = Adjusted; + } + /* Cap the number of entries to the available streams. */ if (config_.size() > 1) { config_.resize(1); diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp index d192670..fc8085f 100644 --- a/src/libcamera/pipeline/vimc/vimc.cpp +++ b/src/libcamera/pipeline/vimc/vimc.cpp @@ -130,6 +130,11 @@ CameraConfiguration::Status VimcCameraConfiguration::validate() if (config_.empty()) return Invalid; + if (transform != Transform::Identity) { + transform = Transform::Identity; + status = Adjusted; + } + /* Cap the number of entries to the available streams. */ if (config_.size() > 1) { config_.resize(1); From patchwork Wed Sep 2 10:44:08 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9449 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 59F72BF019 for ; Wed, 2 Sep 2020 10:44:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1B2D1629DD; Wed, 2 Sep 2020 12:44:23 +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="cnjOQD7L"; dkim-atps=neutral Received: from mail-wm1-x32c.google.com (mail-wm1-x32c.google.com [IPv6:2a00:1450:4864:20::32c]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5D3D1629D3 for ; Wed, 2 Sep 2020 12:44:21 +0200 (CEST) Received: by mail-wm1-x32c.google.com with SMTP id b79so3992675wmb.4 for ; Wed, 02 Sep 2020 03:44:21 -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=9S57DD6Y8ShJRT8lIuO9PZgcgBB6SXr5dhY17Tdvgvg=; b=cnjOQD7Lm6zbgnTmdkNt05BiHONFYi349B6fxEj4N8ohnVLMXdSv6FawbsMwz6whMG sUSVQFz95tJTjiaNZWiJyreo8pdxk2529NrrgHeR+qb3a7SSup+wQ57tdV20itSq9/PI i8btAKgMpaZarfIzpaZRupP/cA3sGLVgNaJb/HLgMSMW4F+mdQwrx0BvRwajvbminL0/ RJJMIeTaDexySeZ14rSn6+Pf0c7a5fFmU4SWxMoECNgQT0kMYFi+oMLJI3tVlbA6CatS tYuISjJorOZr9bH0qF9Qc9U3Dz3hchyHdv6vDy0JyeGd3+3nj59PakfErcj2YxfII/SW e2Cw== 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=9S57DD6Y8ShJRT8lIuO9PZgcgBB6SXr5dhY17Tdvgvg=; b=EuBS4Eq7HDbntDQ7Pr3PRFU9sdslpuv0uUMH+isWjhuGh3iHSDk9QN/BUM7MkZPSkF sLDxQRX3uljEE8tzQMjdRSPoRQlmhcAKkuZbAZWgL0Bn2uWTyQmeVE2luM2Bk4SZvbVb P360eQnvFHSbO76Q2ze4d1DRKznwGTPAyXQie5wQlfi3DE5e90YvX5cojDBACG/7A0QB uJDo0YaZ2FKd4KJxi9sQtoxpoq2zY98cGaYxQflPVQUwQ0dTUzT+PyFlsw4xynKXWAx8 JLC7hEm4tPIoCVReSypjqZ/wi0hDQOCoEYjLTquPyIuRY3SXuA21cfTErbxKGudQVegj LiNA== X-Gm-Message-State: AOAM530khw/OLUrKYvQVsR5gcemW0lMULTyHfqgunVccgnp8H8C7Y+VS PqHOlciTk+NYlAYkgtuVnnumGVUJdlYiPg== X-Google-Smtp-Source: ABdhPJxv9Vgqw6gLWjTqpWyfKLDm0aOk34kCopxWVT8ErPEFYW43zqLS6kkPCvs8oq7T8GgTk7s7ew== X-Received: by 2002:a7b:cb4d:: with SMTP id v13mr55992wmj.56.1599043460572; Wed, 02 Sep 2020 03:44:20 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:20 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:08 +0100 Message-Id: <20200902104410.7569-7-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 6/8] libcamera: raspberrypi: Set camera flips correctly from user transform 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" The Raspberry Pi pipeline handler allows all transforms except those involving a transpose. The user transform is combined with any inherent rotation of the camera, and the camera's H and V flip bits are set accordingly. Note that the validate() method has to work out what the final Bayer order of any raw streams will be, before configure() actually applies the transform to the sensor. We make a note of the "native" (untransformed) Bayer order when the system starts, so that we can deduce transformed Bayer orders more easily. Signed-off-by: David Plowman Reviewed-by: Kieran Bingham --- .../pipeline/raspberrypi/raspberrypi.cpp | 146 ++++++++++++++++-- 1 file changed, 134 insertions(+), 12 deletions(-) diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index 1087257..29112da 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -23,6 +23,7 @@ #include +#include "libcamera/internal/bayer_format.h" #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/ipa_manager.h" @@ -287,6 +288,7 @@ class RPiCameraData : public CameraData public: RPiCameraData(PipelineHandler *pipe) : CameraData(pipe), sensor_(nullptr), state_(State::Stopped), + supportsFlips_(false), flipsAlterBayerOrder_(false), dropFrame_(false), ispOutputCount_(0) { } @@ -335,6 +337,17 @@ public: std::queue embeddedQueue_; std::deque requestQueue_; + /* + * The remaining field are for handling horizontal and vertical flips + * that sensors may support. + */ + bool supportsFlips_; + bool flipsAlterBayerOrder_; + BayerFormat::Order nativeBayerOrder_; + + Transform combinedTransform_; + Transform userTransform_; + private: void checkRequestCompleted(); void tryRunPipeline(); @@ -348,12 +361,12 @@ private: class RPiCameraConfiguration : public CameraConfiguration { public: - RPiCameraConfiguration(const RPiCameraData *data); + RPiCameraConfiguration(RPiCameraData *data); Status validate() override; private: - const RPiCameraData *data_; + RPiCameraData *data_; }; class PipelineHandlerRPi : public PipelineHandler @@ -388,7 +401,7 @@ private: MediaDevice *isp_; }; -RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data) +RPiCameraConfiguration::RPiCameraConfiguration(RPiCameraData *data) : CameraConfiguration(), data_(data) { } @@ -400,11 +413,56 @@ CameraConfiguration::Status RPiCameraConfiguration::validate() if (config_.empty()) return Invalid; - if (transform != Transform::Identity) { - transform = Transform::Identity; + /* + * What if the platform has a non-90 degree rotation? We can't even + * "adjust" the configuration and carry on. Alternatively, raising an + * error means the platform can never run. Let's just print a warning + * and continue regardless; the rotation is effectively set to zero. + */ + int32_t rotation = data_->sensor_->properties().get(properties::Rotation); + bool success; + Transform combined = transform * transformFromRotation(rotation, &success); + if (!success) + LOG(RPI, Warning) << "Invalid rotation of " << rotation + << " degrees - ignoring"; + + /* + * We combine the platform and user transform, but must "adjust away" + * any combined result that includes a transform, as we can't do those. + * In this case, flipping only the transpose bit is helpful to + * applications - they either get the transform they requested, or have + * to do a simple transpose themselves (they don't have to worry about + * the other possible cases). + */ + if (!!(combined & Transform::Transpose)) { + /* + * Flipping the transpose bit in "transform" flips it in + * combined result too (as it's the last thing that happens), + * which is of course clearing it. + */ + transform ^= Transform::Transpose; + combined &= ~Transform::Transpose; status = Adjusted; } + /* + * We also check if the sensor doesn't do h/vflips at all, in which + * case we clear them, and the application will have to do everything. + */ + if (!data_->supportsFlips_ && !!combined) { + transform &= Transform::Transpose; + combined = Transform::Identity; + status = Adjusted; + } + + /* + * Store the final combined transform that configure() will need to + * apply to the sensor, and the associated user transform that gives + * rise to it (which is what the IPAs will see). + */ + data_->combinedTransform_ = combined; + data_->userTransform_ = transform; + unsigned int rawCount = 0, outCount = 0, count = 0, maxIndex = 0; std::pair outSize[2]; Size maxSize; @@ -420,7 +478,23 @@ CameraConfiguration::Status RPiCameraConfiguration::validate() if (ret) return Invalid; - PixelFormat sensorPixFormat = sensorFormat.fourcc.toPixelFormat(); + /* + * Some sensors change their Bayer order when they are + * h-flipped or v-flipped, according to the transform. + * If this one does, we must advertise the transformed + * Bayer order in the raw stream. Note how we must + * fetch the "native" (i.e. untransformed) Bayer order, + * because the sensor may currently be flipped! + */ + V4L2PixelFormat fourcc = sensorFormat.fourcc; + if (data_->flipsAlterBayerOrder_) { + BayerFormat bayer(fourcc); + bayer.order = data_->nativeBayerOrder_; + bayer = bayer.transform(combined); + fourcc = bayer.toV4L2PixelFormat(); + } + + PixelFormat sensorPixFormat = fourcc.toPixelFormat(); if (cfg.size != sensorFormat.size || cfg.pixelFormat != sensorPixFormat) { cfg.size = sensorFormat.size; @@ -967,6 +1041,48 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator) /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); + /* + * We cache three things about the sensor in relation to transforms + * (meaning horizontal and vertical flips). + * + * Firstly, does it support them? + * Secondly, if you use them does it affect the Bayer ordering? + * Thirdly, what is the "native" Bayer order, when no transforms are + * applied? + * + * As part of answering the final question, we reset the camera to + * no transform at all. + */ + + V4L2VideoDevice *dev = data->unicam_[Unicam::Image].dev(); + const struct v4l2_query_ext_ctrl *hflipCtrl = dev->controlInfo(V4L2_CID_HFLIP); + if (hflipCtrl) { + /* We assume it will support vflips too... */ + data->supportsFlips_ = true; + data->flipsAlterBayerOrder_ = hflipCtrl->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + ControlList ctrls(dev->controls()); + ctrls.set(V4L2_CID_HFLIP, 0); + ctrls.set(V4L2_CID_VFLIP, 0); + dev->setControls(&ctrls); + } + + /* Look for a valid Bayer format. */ + V4L2VideoDevice::Formats fmts = dev->formats(); + BayerFormat bayerFormat; + for (const auto &iter : fmts) { + V4L2PixelFormat v4l2Format = iter.first; + bayerFormat = BayerFormat(v4l2Format); + if (bayerFormat.isValid()) + break; + } + + if (!bayerFormat.isValid()) { + LOG(RPI, Error) << "No Bayer format found"; + return false; + } + data->nativeBayerOrder_ = bayerFormat.order; + /* * List the available output streams. * Currently cannot do Unicam streams! @@ -1172,12 +1288,18 @@ int RPiCameraData::configureIPA() sensorMetadata_ = result.data[2]; } - /* Configure the H/V flip controls based on the sensor rotation. */ - ControlList ctrls(unicam_[Unicam::Image].dev()->controls()); - int32_t rotation = sensor_->properties().get(properties::Rotation); - ctrls.set(V4L2_CID_HFLIP, static_cast(!!rotation)); - ctrls.set(V4L2_CID_VFLIP, static_cast(!!rotation)); - unicam_[Unicam::Image].dev()->setControls(&ctrls); + /* + * Configure the H/V flip controls based on the combination of + * the sensor and user transform. + */ + if (supportsFlips_) { + ControlList ctrls(unicam_[Unicam::Image].dev()->controls()); + ctrls.set(V4L2_CID_HFLIP, + static_cast(!!(combinedTransform_ & Transform::HFlip))); + ctrls.set(V4L2_CID_VFLIP, + static_cast(!!(combinedTransform_ & Transform::VFlip))); + unicam_[Unicam::Image].dev()->setControls(&ctrls); + } } if (result.operation & RPI_IPA_CONFIG_SENSOR) { From patchwork Wed Sep 2 10:44:09 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9450 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 A81C7BF6D3 for ; Wed, 2 Sep 2020 10:44:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6B2CB629BF; Wed, 2 Sep 2020 12:44:23 +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="l8wFuPv7"; dkim-atps=neutral Received: from mail-wr1-x42b.google.com (mail-wr1-x42b.google.com [IPv6:2a00:1450:4864:20::42b]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DE695629E1 for ; Wed, 2 Sep 2020 12:44:21 +0200 (CEST) Received: by mail-wr1-x42b.google.com with SMTP id z4so4703502wrr.4 for ; Wed, 02 Sep 2020 03:44:21 -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=jW1QQnzuQ0Fne7ITcP+39LfIm90WZZddE0H4H+DyRQY=; b=l8wFuPv7Q/lq+BeGBABMIN/Qsrfyj5h+3Ho2UR+CEWuL3r0ALw6UeUz07n8APlaIh5 depL5opG3AEX9YcwkCIYa030aToFEtp3gf9xD+WnBMBDLxEkEqQ4wLp34fE3T/WP1yo8 S7HcQ+/sjgcXslDcFGwSss3P38FfjBHeH+X5eOvr1YS4QrE6wKngdwi+vOoJXUQzUPHP +s4H7yCrk0yhIuKZ17VP3PZBXHqi26mCoLjBXUX8K9btX0+aTJzbwIFAjQphq72nFGdW +6It2U6K4wkHqaIyf2Rk2tx09D4WZG5zafVUMVrPtXWq2KDy3txlGONFUA4qIPAxuvPB rksg== 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=jW1QQnzuQ0Fne7ITcP+39LfIm90WZZddE0H4H+DyRQY=; b=dTlzEgLMQoxHgbTk+mX5IolX2UuSKfdLWQzl0iPDED7Dq9vDVa4YZ4JkgNddwO/PGq XLeG1o7/JKVywbGEtnwM6C06vrK4gUT/mMDz4sKvBbsrLndHccjyCfkzd9NZr41gyhAQ SsaNP+i/oezdqiPGjqjXxzjSkEqg0sV4kCJgMwb+jWVvVUBgkvXSVdPNkyLSsu3QZifP KDB+0VrkOQya/yNL2audMcuSJXc82BNnEGqgDR8iZV34If4q5KRoXmg7XHHkSjrDDGWL oXSm+Egric9HSM9oRRQer55vJ3+KEKnknNkDzdRg1o1C2U7bD9EISHeCxjQq3rkicBZx HPSQ== X-Gm-Message-State: AOAM530klkIVjRavAGSH/DNh+EpZJziSCzSjDMnzTZXaTYwEujOKT1zq MtuS1lhTL/vCCILhCmCt8NKOUINvddtibQ== X-Google-Smtp-Source: ABdhPJzVZThlK5V5zD6vvYWvthEJVgOdBVXM1u/m3cyVUyj12kcnLpDRLDO/oexP3AO0FSa+ApW5Mg== X-Received: by 2002:adf:f812:: with SMTP id s18mr6778387wrp.96.1599043461287; Wed, 02 Sep 2020 03:44:21 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:20 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:09 +0100 Message-Id: <20200902104410.7569-8-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 7/8] libcamera: raspberrypi: Plumb user transform through to IPA 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" This commit plumbs the user transform from the Raspberry Pi pipeline handler through to the IPA. Note that the transform is actually handled in the sensor (by setting the h/v flip bits), so the IPAs need to understand the orientation of the image they receive. Once in the IPA we add it to the CameraMode description, so that it becomes automatically available to all the individual control algorithms. The IPA configure method has to be reordered just a little so as to fill in the transform in the camera mode before calling SwitchMode. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- src/ipa/raspberrypi/controller/camera_mode.h | 4 ++ src/ipa/raspberrypi/raspberrypi.cpp | 48 +++++++++++-------- .../pipeline/raspberrypi/raspberrypi.cpp | 5 +- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/ipa/raspberrypi/controller/camera_mode.h b/src/ipa/raspberrypi/controller/camera_mode.h index 875bab3..920f11b 100644 --- a/src/ipa/raspberrypi/controller/camera_mode.h +++ b/src/ipa/raspberrypi/controller/camera_mode.h @@ -6,6 +6,8 @@ */ #pragma once +#include + // Description of a "camera mode", holding enough information for control // algorithms to adapt their behaviour to the different modes of the camera, // including binning, scaling, cropping etc. @@ -33,6 +35,8 @@ struct CameraMode { double noise_factor; // line time in nanoseconds double line_length; + // any camera transform *not* reflected already in the camera tuning + libcamera::Transform transform; }; #ifdef __cplusplus diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp index 4557016..c730326 100644 --- a/src/ipa/raspberrypi/raspberrypi.cpp +++ b/src/ipa/raspberrypi/raspberrypi.cpp @@ -232,6 +232,33 @@ void IPARPi::configure(const CameraSensorInfo &sensorInfo, /* Re-assemble camera mode using the sensor info. */ setMode(sensorInfo); + /* + * The ipaConfig.data always gives us the user transform first. Note that + * this will always make the LS table pointer (if present) element 1. + */ + mode_.transform = static_cast(ipaConfig.data[0]); + + /* Store the lens shading table pointer and handle if available. */ + if (ipaConfig.operation & RPI_IPA_CONFIG_LS_TABLE) { + /* Remove any previous table, if there was one. */ + if (lsTable_) { + munmap(lsTable_, MAX_LS_GRID_SIZE); + lsTable_ = nullptr; + } + + /* Map the LS table buffer into user space (now element 1). */ + lsTableHandle_ = FileDescriptor(ipaConfig.data[1]); + if (lsTableHandle_.isValid()) { + lsTable_ = mmap(nullptr, MAX_LS_GRID_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, lsTableHandle_.fd(), 0); + + if (lsTable_ == MAP_FAILED) { + LOG(IPARPI, Error) << "dmaHeap mmap failure for LS table."; + lsTable_ = nullptr; + } + } + } + /* Pass the camera mode to the CamHelper to setup algorithms. */ helper_->SetCameraMode(mode_); @@ -280,27 +307,6 @@ void IPARPi::configure(const CameraSensorInfo &sensorInfo, } lastMode_ = mode_; - - /* Store the lens shading table pointer and handle if available. */ - if (ipaConfig.operation & RPI_IPA_CONFIG_LS_TABLE) { - /* Remove any previous table, if there was one. */ - if (lsTable_) { - munmap(lsTable_, MAX_LS_GRID_SIZE); - lsTable_ = nullptr; - } - - /* Map the LS table buffer into user space. */ - lsTableHandle_ = FileDescriptor(ipaConfig.data[0]); - if (lsTableHandle_.isValid()) { - lsTable_ = mmap(nullptr, MAX_LS_GRID_SIZE, PROT_READ | PROT_WRITE, - MAP_SHARED, lsTableHandle_.fd(), 0); - - if (lsTable_ == MAP_FAILED) { - LOG(IPARPI, Error) << "dmaHeap mmap failure for LS table."; - lsTable_ = nullptr; - } - } - } } void IPARPi::mapBuffers(const std::vector &buffers) diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index 29112da..728973a 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -1252,6 +1252,9 @@ int RPiCameraData::configureIPA() entityControls.emplace(0, unicam_[Unicam::Image].dev()->controls()); entityControls.emplace(1, isp_[Isp::Input].dev()->controls()); + /* Always send the user transform to the IPA. */ + ipaConfig.data = { static_cast(userTransform_) }; + /* Allocate the lens shading table via dmaHeap and pass to the IPA. */ if (!lsTable_.isValid()) { lsTable_ = dmaHeap_.alloc("ls_grid", MAX_LS_GRID_SIZE); @@ -1260,7 +1263,7 @@ int RPiCameraData::configureIPA() /* Allow the IPA to mmap the LS table via the file descriptor. */ ipaConfig.operation = RPI_IPA_CONFIG_LS_TABLE; - ipaConfig.data = { static_cast(lsTable_.fd()) }; + ipaConfig.data.push_back(static_cast(lsTable_.fd())); } CameraSensorInfo sensorInfo = {}; From patchwork Wed Sep 2 10:44:10 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9451 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 197C0BF019 for ; Wed, 2 Sep 2020 10:44:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D656C629D3; Wed, 2 Sep 2020 12:44:23 +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="ZwvaOowv"; dkim-atps=neutral Received: from mail-wr1-x444.google.com (mail-wr1-x444.google.com [IPv6:2a00:1450:4864:20::444]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C5F9B629AB for ; Wed, 2 Sep 2020 12:44:22 +0200 (CEST) Received: by mail-wr1-x444.google.com with SMTP id a17so4700181wrn.6 for ; Wed, 02 Sep 2020 03:44:22 -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=yqzzfDnOwH0Awzrjn/UOHYIemK1WmVNHkZJX33QDD+s=; b=ZwvaOowveWeD+SW6jS8jSoKJMigRiGrFJbRl5+53y8GVEBRjqCwazzM+yGhN+ao9l3 T+PFgOBLl5VpOLCIgd6hZfQApFoWCouqdQIeedP72r/4vwNwZfgMSMvqu3tO/LAHrg11 GLhJxiannlpUaNqgE3msAAUkDT1Kyk8Ncw29E7MJAAseFTzx1DpCqk4c3enDvbrd7LiN K93xx0HEp2cx9tzoFptnyeIkdoWefd3DwJ/HKaM7RRR0jAYcsEg4i07KSK40a75fAgNC u3EB4ojezGSO8s2myQM3+U61HCMp+caoSyZlgYm2RNhOISZ8yjfSELrpEpu5limXnfwf lmQg== 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=yqzzfDnOwH0Awzrjn/UOHYIemK1WmVNHkZJX33QDD+s=; b=AnYbihtSOaXrE6NEDEgkrkFqO1Xg3+Y8vtdAnkhfwhFmHHwDIRmyX4BQGyVws22WAj AhcElsInN45RPpOPXvzMtOvbrxdxTgoAf8zTsg3xzf8sBrPt/fDkfNJATzWc1A5bEoKG 6/irsq6reU4KhfZrkbjcZWNHpfWf2Bc4AC1raqtZ9354wcTwWWZeoZKbpkaHoZBFjnTQ PKDkN76qqmehhStrhF0FtWoi7S583FCG2JHbtAlKzCypvOgCyjnQZNF4XVpa9KpF2nmn iSuEIsaEZLlh8BZRfWW7/K+yKGRiRm5hX7TIg1v8MMfd1j7SANe/WRRrBON27zHpq6F8 5SbA== X-Gm-Message-State: AOAM530IaTkpfjMucfXfE0jknFJottRgZ/3FIMtPSmihwo4GFxDZAODA 0O1HHQScqES/mno7GnQJmR6H8tgmjAfpVg== X-Google-Smtp-Source: ABdhPJyS7lxt3LkKx6YKNLxxyydHJAPRPF+BTbO67IL1ApYB+ytEBN304gLik05wTL9oFyt45mMyLA== X-Received: by 2002:a5d:5106:: with SMTP id s6mr7036332wrt.166.1599043462076; Wed, 02 Sep 2020 03:44:22 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id m3sm5583062wmb.26.2020.09.02.03.44.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2020 03:44:21 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Wed, 2 Sep 2020 11:44:10 +0100 Message-Id: <20200902104410.7569-9-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902104410.7569-1-david.plowman@raspberrypi.com> References: <20200902104410.7569-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 8/8] libcamera: ipa: raspberrypi: ALSC: Handle user transform 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" Update ALSC (Auto Lens Shading Correction) to handle correctly the user transform now passed in the camera mode. The user transform is applied directly in the sensor so the image statistics already incorporate it, and the adaptive algorithm is entirely agnostic towards it, so all we have to do is flip the calibrated tables to match. (These tables will have been calibrated without the user transform.) Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- src/ipa/raspberrypi/controller/rpi/alsc.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp index 0d0e0b0..f610de2 100644 --- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp +++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp @@ -184,7 +184,10 @@ void Alsc::waitForAysncThread() static bool compare_modes(CameraMode const &cm0, CameraMode const &cm1) { - // Return true if the modes crop from the sensor significantly differently. + // Return true if the modes crop from the sensor significantly differently, + // or if the user transform has changed. + if (cm0.transform != cm1.transform) + return true; int left_diff = abs(cm0.crop_x - cm1.crop_x); int top_diff = abs(cm0.crop_y - cm1.crop_y); int right_diff = fabs(cm0.crop_x + cm0.scale_x * cm0.width - @@ -428,6 +431,10 @@ void resample_cal_table(double const cal_table_in[XY], xf[i] = x - x_lo[i]; x_hi[i] = std::min(x_lo[i] + 1, X - 1); x_lo[i] = std::max(x_lo[i], 0); + if (!!(camera_mode.transform & libcamera::Transform::HFlip)) { + x_lo[i] = X - 1 - x_lo[i]; + x_hi[i] = X - 1 - x_hi[i]; + } } // Now march over the output table generating the new values. double scale_y = camera_mode.sensor_height / @@ -440,6 +447,10 @@ void resample_cal_table(double const cal_table_in[XY], double yf = y - y_lo; int y_hi = std::min(y_lo + 1, Y - 1); y_lo = std::max(y_lo, 0); + if (!!(camera_mode.transform & libcamera::Transform::VFlip)) { + y_lo = Y - 1 - y_lo; + y_hi = Y - 1 - y_hi; + } double const *row_above = cal_table_in + X * y_lo; double const *row_below = cal_table_in + X * y_hi; for (int i = 0; i < X; i++) {