From patchwork Fri Sep 4 10:30:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9491 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 6077EBE174 for ; Fri, 4 Sep 2020 10:30:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6B09A62B29; Fri, 4 Sep 2020 12:30:49 +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="YtpSf56c"; dkim-atps=neutral Received: from mail-wr1-x432.google.com (mail-wr1-x432.google.com [IPv6:2a00:1450:4864:20::432]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0E95A60371 for ; Fri, 4 Sep 2020 12:30:48 +0200 (CEST) Received: by mail-wr1-x432.google.com with SMTP id c18so6191174wrm.9 for ; Fri, 04 Sep 2020 03:30:48 -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=WGbtWdgkXnhzgLWEMiyouzRS4O02UfaVPvUia46yucQ=; b=YtpSf56c0u5jArBM3jlRVl0hl8kyzr0oZRuNYZOcoIUyrSUkxypt6pyZO6PQYLRN3y W4fBjCJ7KetLXRuG+6Pdt8xDjoVKSNDeVBmDAmhaEyfnOeYy+1z7gEK16uAemi+f0ZUf T5ufuXK7TF8+Jf+CGjjhZ+ySX6b7XtQf2H7LEkOuzwORGpe9xiJOJRqkEf4pU4orw3+x cMqk8dwv7SkvaVgNtwYGQOifIiVbJOCUgjoFAQ3K5y7rKdxrd1sC+94QGxFYiKrSvCyQ 6mpGE35IZcTGryKY0ktsNIRucWiWyaD93vVRom+JAuB0dRBU9lGoMHccJSFruwi2Uxi3 vRsw== 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=WGbtWdgkXnhzgLWEMiyouzRS4O02UfaVPvUia46yucQ=; b=UKqGwvPcURIqiuN7FQ0YKKBYRUNUJ68yDD5tq6f12L+IrV1uFNEN9JLBnGvcXpFqqS zaYkLRyfesZGSQH3Tl/v8bZVFmvRZUXiHA3ns2b0wdm8ELHU3FY14dCxntyG1t2552Rl bGlKKGnXHgKAWAp9Ix60qioPP6yoIrmO/CoaApgkVWwkfqkBKeataEj70eLxISbYNE/K P76iZbZw3mew0/j8gD2Gm44uTYUqq2aWhBr+gS5PoQcKeorkw18AqKP364CKzpDmZl13 8CC9R1BJLr/nIOhbamn/nNicxpW0IVMZOp4y9Ujk75Tu/pkYCOCRdQnrOlMfhS9bHo+o K6nQ== X-Gm-Message-State: AOAM530uQvXQPaD1qfWR4nuTE1kZBDN6+ru/jOo7IuAp2MwGl1YxVBhc MiEVA9U1i5Lmw8VSgTDwWIQrs28NuORa9w== X-Google-Smtp-Source: ABdhPJznHi5+h3Fzjidm0jyRZ1owLdCXs9KSvhHCSHIygxWpsuPr3TZA3jDM/yboKqHhZPKlOxG7Uw== X-Received: by 2002:adf:9d44:: with SMTP id o4mr6978623wre.361.1599215447393; Fri, 04 Sep 2020 03:30:47 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:46 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:35 +0100 Message-Id: <20200904103042.1593-2-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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 Reviewed-by: Laurent Pinchart --- 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 Fri Sep 4 10:30:36 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9492 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 DC3E5BE174 for ; Fri, 4 Sep 2020 10:30:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AE81F629E4; Fri, 4 Sep 2020 12:30:49 +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="r642ngf3"; 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 D09FC628EE for ; Fri, 4 Sep 2020 12:30:48 +0200 (CEST) Received: by mail-wr1-x443.google.com with SMTP id m6so6255821wrn.0 for ; Fri, 04 Sep 2020 03:30:48 -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=2r26cquJvFEGijK6A1Qt2id7HpC8ABt6M/5TpJ40vpQ=; b=r642ngf37LSt1rMNCTHXcsFSgpatXnqpsOPMm8SX5fkVqftbzI/gJ717t3LHd2dg2D XuhRiaUhwVJheVBqLkvOndgXIo6cA4esZGh1HNk4KmVKG3+BTcDVrmiBJYlb1t1DlJzM 3njAh0vOpMcGy1e+Stp3EwAaguUEMKzpfvTNd69G0V70/1BJUZhhFGpMpFeNxC4TDtO4 aH9i3sDtLAJWgMI11d3q+LvFRbiA8c7Fyuu7hZaZQNp6ZkZCPfeTtnlsXQYUEVrdeqBH JgT99GjUB25K5xA7e34cJJteAi7h2Q0JNW6qPJwrlRA5fiFc2d8s2C5YIugkitO0io56 LNYA== 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=2r26cquJvFEGijK6A1Qt2id7HpC8ABt6M/5TpJ40vpQ=; b=hd1KrTuZKX56atJsY7Eijf5c1YDPXoz6ikhXh4M2TI40ABeLHu1DR2wW/Yd3g9jAx9 E4NvoTsM+znhCOZx7nibtiUbpcx+wn0v9KddWvnBMgQrNFh9wy3JUBQJzPpCthNNUlRm FqmIs01MCQPy5R1R7UOy0D3ppVkkQfVs8hAsWeGeftLHTHPEPkI/j302JceSCz06u4sf FlzSUfaDO4r15alWSgAzUGTvfre3eNeIXwBRheh0/GddzaY4tRI3cd28cIHg1ybfyNo/ HgUyELe2aG/QLiB/9FjGxP36sJo9rCEj02P5HiOFUJG9EKK9qxWaozIyHOJ+qDRg8T2b OaMQ== X-Gm-Message-State: AOAM533x/Ezh9bcCp0+5ORlNRs3gl8StZpHFUVbuXzWEtXDRod7LZMbB Oh22rrYvxDtmlGX4orFYF9K+7moabaTstw== X-Google-Smtp-Source: ABdhPJyIHihT6aeCPunHX2VKJZJ+iA/ZRY70JdyjUw4Wr7kmw3lNG8AHDIsKhG3jxHfY2rSeuAM0gg== X-Received: by 2002:adf:fa02:: with SMTP id m2mr6898860wrr.273.1599215448275; Fri, 04 Sep 2020 03:30:48 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:47 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:36 +0100 Message-Id: <20200904103042.1593-3-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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::controlInfo 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 Fri Sep 4 10:30:37 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9493 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 3FFECBE174 for ; Fri, 4 Sep 2020 10:30:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0C8DE62B53; Fri, 4 Sep 2020 12:30:52 +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="LFrZsZxs"; dkim-atps=neutral Received: from mail-wr1-x42d.google.com (mail-wr1-x42d.google.com [IPv6:2a00:1450:4864:20::42d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 00A1560371 for ; Fri, 4 Sep 2020 12:30:50 +0200 (CEST) Received: by mail-wr1-x42d.google.com with SMTP id w5so6213268wrp.8 for ; Fri, 04 Sep 2020 03:30:49 -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=LFrZsZxss5Gke+pv36AFmhAfe3BtLDS5p81bE+A1MrkmEmNlVFGk8R1EpvbQSbFfFW Tte1TfCuW+ftDN5sG+UTomb3gfLtKLuFSEJt5Xvyp7gslt0Rso8GgJALRpyQ3umPLK34 Hm/sIzfGMdNFHkMEtQ86qj9v5gk0gF4ReLhC7ZpduH9tnQ/cEUQ/8vXVCDnqc4vBNSvY S8fSk9fG51u+ZAVXJ5N9Cejm+JbiSvZXaIP3PA53S/bRCCq75jFR5cDKKz9r/vS5sbos jOLBuKLIjr1epO83AiqT1Bi7Xvy1xNRGdfzAVkXW6F/rM/2V+dBWgQEAi0AwhtPK+kUm 0cpw== 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=ebavz6gkxaGIdDvmptPBxHEdABhLnbdlb3fClKfnuI/XsdVHx7TxjQnjuJZsKweDda AtSYQSBZuhfI9D5xR7I5e0FeCjAHOROxvABmXnRX7027yfbIfHg3Gjxc74sgeJG3hwS+ 2Z7aBLmOfwzR/JZcbhBd1PIZBK7/tzMFnMhNXAU5exCW9NIb7N6Sts8hbcdAESyxc90g H9EmzWGXx/fih7/6eFTyPsTrf0lCf4iroK8ueyMhD6vB185SmLCs31OmdNEVOycCvFJA rlunrzH+/d+q0GKVlK9o1LK36HS/qIoshTYE/hU8XVSIW/RyT0JizlD5uGCcPTDPkUhy WROQ== X-Gm-Message-State: AOAM533kj0ddcMGnPAxoB9YAz3E1vlFPiY3ohO6P9+nVsyvEQ1m/YQYa /hGCrQRFTEJvdDPKSPkx3P1DXidemBmuSg== X-Google-Smtp-Source: ABdhPJzeGIsWObGR0+3wA+vcYaq7GR+LnWTg4blh08jHZVnPHvVtcnPSil5mHz96DKuF+5h1rEGGBQ== X-Received: by 2002:adf:ed12:: with SMTP id a18mr7390140wro.178.1599215449154; Fri, 04 Sep 2020 03:30:49 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:48 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:37 +0100 Message-Id: <20200904103042.1593-4-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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 Fri Sep 4 10:30:38 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9494 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 AC840BE174 for ; Fri, 4 Sep 2020 10:30:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7585760371; Fri, 4 Sep 2020 12:30:52 +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="ksjb1mQ6"; 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 C57C762946 for ; Fri, 4 Sep 2020 12:30:50 +0200 (CEST) Received: by mail-wr1-x442.google.com with SMTP id c18so6191335wrm.9 for ; Fri, 04 Sep 2020 03:30:50 -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=gf20xRgK5cHl2EqM3znt2oz2qj8hNXxwCTdPCxaBoOg=; b=ksjb1mQ6lTRBNl3TjvARg18v6TrHDOP5pvqx562rKKBhQOmPwVV99dpkl2oTJaYy6V Sl6vBq9sitdkXUI3NuhVR4gojBq6D0wE/sfa34bD/pKnx4dupKzGiWIJaM5FiyOwtTRJ Rl/X5XcjCR8mNLTpV57plTck2UFopX6pSk9rirX2XlKYt9hLNBfE/ACsAhGTKh2pOZAY 63S7vFZhMMOBwoz45XtFRw8GcFPcfzGTtWtMmfv//IzO6XsZmRvJSYs4fMqpWmLG998D /Zs4rI6dNErbTep+n8+nZVzOdXPEVmVfj3eROxlu1zr0v0VvXS/HTZFFUryJm9aikHq9 t0RA== 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=gf20xRgK5cHl2EqM3znt2oz2qj8hNXxwCTdPCxaBoOg=; b=lKm0KbphGAiBVfppZ6ND8+rntvP06KFyqbSi1Q40neOFGhaJHn7gaPJu1/9qFtHo/V b/zyziKFfgsVnRyaujnCPXv0E/NnsWhwv7LWKqfTC++k3s32LiYqdSAvZ0N/THglGcv0 Xv8wNBUWqO1vri2xTka3VAAr+VdZTzqorHno/LQchmWJhfbXDKR+3S+qAHQUTmxqZ9YE tB4NaA2YLuJSMJEOiigtX9mFh03NTqVW2KTUY120ItxDQn7YpGGbeztfsOhg8KXwVeYd trcl53YToh4s5758Y8Rvmho4belJFIA3q6Vun1u1dWHAngER647bOBN1o2AaItY4Rn3p 7mng== X-Gm-Message-State: AOAM530pLkGWMFqG506Pxu3ynRt5726esI6IwJorFQdouUdgU2pg5AAU GAseXCYfujzMtbVKbivT4r0NinfNoeKZTw== X-Google-Smtp-Source: ABdhPJzbs79bSDy2zRLlq58cIzeaCSPiBgCyPMppv4GuTlbprZ6z2FyD93wSonsXqiFj635YnMwlDg== X-Received: by 2002:a5d:69c2:: with SMTP id s2mr6905975wrw.389.1599215449943; Fri, 04 Sep 2020 03:30:49 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:49 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:38 +0100 Message-Id: <20200904103042.1593-5-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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 | 60 ++++++ src/libcamera/bayer_format.cpp | 231 ++++++++++++++++++++++ src/libcamera/meson.build | 1 + 3 files changed, 292 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..deeef5b --- /dev/null +++ b/include/libcamera/internal/bayer_format.h @@ -0,0 +1,60 @@ +/* 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..3780d7d --- /dev/null +++ b/src/libcamera/bayer_format.cpp @@ -0,0 +1,231 @@ +/* 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 + * + * For example, performing a horizontal flip on the Bayer pattern RGGB causes + * the RG rows of pixels to become GR, and the GB rows become BG. So the final + * result in this case would be GRBG. + */ +BayerFormat BayerFormat::transform(Transform t) const +{ + BayerFormat result = *this; + + /* + * Observe that flipping bit 0 of the Order enum performs a horizontal + * mirror on the Bayer pattern (e.g. RGGB goes to GRBG). Similarly, + * flipping bit 1 performs a vertical mirror operation on it. Hence: + */ + 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 Fri Sep 4 10:30:39 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9495 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 57586BE174 for ; Fri, 4 Sep 2020 10:30:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 24CEB6037B; Fri, 4 Sep 2020 12:30:53 +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="j2B4xr1Z"; dkim-atps=neutral Received: from mail-wm1-x333.google.com (mail-wm1-x333.google.com [IPv6:2a00:1450:4864:20::333]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 51FA760371 for ; Fri, 4 Sep 2020 12:30:51 +0200 (CEST) Received: by mail-wm1-x333.google.com with SMTP id c19so7220372wmd.1 for ; Fri, 04 Sep 2020 03:30:51 -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=j2B4xr1ZKRRPNmvkvC+Cf0EJD4K1nRxIVrftFbvJons1xtX8gUbha1Mt0nrfHff25V lwfCumXWv2w3C4jVhIS9frh8Cj3PIic+TOBS+rJkNCQ/f2sD0hEHUROEn0H/gUJWdFJI 5O3SepGGfnVc0/BI0p5U/F6mxeFDVC0VBqtRDVx167ulzxUlFL19J64wumT9VoruM4C1 B3a5kI2huIZBBuC2D+yFbQg6vghvEVME3Do98/uOUFXNY6yeq1qQg2pPDxPytgL+dCQT OTTTtivY/OdPgHzUFdZ51lzylSgwTPzuOgVSD6opvrP7NMs7wdfADuJR5T3ODegqm8rS E53g== 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=h+9htlHm4nqYmSWOx8WCdv+TY/4DAlXcJAhWB9Ot6+8T4TfQR9sF8q+ffaOIjdse7H Zm2w29xnn1dmRE6JzT3JhyE6YkcxhKyJ8VdQP1LLCyrrLti/GLk6pkIfvMUDVmQDl+iH mcEh7o7gxiesx7R2MzgCsKXoiaKrVnnQmcCG5jz26QfBQJOrkENCAgAPdiLu0UTbuGOA IOHn9ATfKwGanYf/IIUbGDLOJcquARbYi/rJeEhvfL530E/FApI5qF7ztC27mNbbJ1+9 aVwXwC+SVKg/c40V3oxJepCMlKNK9qabnz304fGeD7SaSwwWOMmKFmnZYB4ncuVDFsMV iVGw== X-Gm-Message-State: AOAM532vnJSdJbQEiBiMj/Dw2iLaRPo8+VznCuS/jfGjwiQDFeMUKl08 Yl11apWpP1ZIPBNai43TNPRggdhEaxrdyQ== X-Google-Smtp-Source: ABdhPJyl6nyymJAQDlHqmC1j6HM1+eE8uhQAE3x3ObMggFn1gQv/rfJIOXKR6cL0CsYJCuIKhI8TiQ== X-Received: by 2002:a05:600c:214e:: with SMTP id v14mr7239840wml.118.1599215450688; Fri, 04 Sep 2020 03:30:50 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:50 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:39 +0100 Message-Id: <20200904103042.1593-6-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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 Fri Sep 4 10:30:40 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9496 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 028CABE174 for ; Fri, 4 Sep 2020 10:30:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C565862B2F; Fri, 4 Sep 2020 12:30:54 +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="UVaX58a2"; dkim-atps=neutral Received: from mail-wm1-x32a.google.com (mail-wm1-x32a.google.com [IPv6:2a00:1450:4864:20::32a]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6B80E62946 for ; Fri, 4 Sep 2020 12:30:52 +0200 (CEST) Received: by mail-wm1-x32a.google.com with SMTP id e11so7229143wme.0 for ; Fri, 04 Sep 2020 03:30:52 -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=ucqBoc3TMWZlEHWZuX4hDevZgdEe/QiSE87HzuaVTS4=; b=UVaX58a2Qi4oXwNuQzLmd9YGTP4dWsvoAKi4T77llwpgifJ5z+5LXwubmJfBfmHYn2 x6faXyyhi8E3AlN9BAkpSI8F58mw4/cAHMwJ+zEH/nLg8xURTcpvfidQBbsM3dlZJH5Q PUq6HiAiVeHkjNS0xnHAipbdPHZilGLVfKTfAFpNqpfzEvWXmgC/dbysXb3Pz97z/XGN 2SDbLr2YBZG6Ds6DE9Rswpl61ZQzEv2qWGHcLHZEOxXuB4ZvKhmDjfcXlFyrXN7jd9jI JmKcdvrw2wTqxZHJ4+WKKJ29HW1tGxCoIrhMxIOGP/e3NQA+jNlYwnBrll9Y9iGo2HfL sRNA== 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=ucqBoc3TMWZlEHWZuX4hDevZgdEe/QiSE87HzuaVTS4=; b=RqWntGKUGZWGOZDvyQrkcQ3ijptSLSFxtyRL5gcwgeMooAQSmdI6B5aX1C9IiPpxxF IMVvLYwpBbOl1gRLUmbVTJUgMog+G9MvoaxAfpm73+wb2v6D1zz55DA4QjupiMMgpxqZ CwA4IHpa8C4nDeUHt4EzE9fVf5GL4yeCTIkK4PzQ1I1HNXmg2LHPtcPFShH106UpHX/F 3o7AzmDKXUJ9qt3NnnEVT7dpS0mjLGDCaMwkaGM18bWIJjPjia9/zP8hxrVKf8mZ5CBn cLraudAHlbTONylLf2B2HrKzHXsrz2uTQ9WbK2HCvcpxcOSJ8IwKQ+XlAE7ysmBXAh5p d5Yw== X-Gm-Message-State: AOAM530CtZ6msoR0r4qzBU1+UOXJ7X9N3NqP+/9W3qCGOqMJvlhGLFR8 9VMm7mCothieKypDVomjGZvSgF/dMUwbuw== X-Google-Smtp-Source: ABdhPJzuDEhUive/YaUW0xyjqhlfuKMnGxxkN4QnmgRFpSVumDBm9G7i9gGBMpHd8Buv+CXm9SYc/Q== X-Received: by 2002:a1c:4d14:: with SMTP id o20mr6636238wmh.115.1599215451489; Fri, 04 Sep 2020 03:30:51 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:51 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:40 +0100 Message-Id: <20200904103042.1593-7-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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 Reviewed-by: Laurent Pinchart --- .../pipeline/raspberrypi/raspberrypi.cpp | 149 ++++++++++++++++-- 1 file changed, 137 insertions(+), 12 deletions(-) diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index 1087257..ee654d1 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) { } @@ -294,7 +296,7 @@ public: void frameStarted(uint32_t sequence); int loadIPA(); - int configureIPA(); + int configureIPA(const CameraConfiguration *config); void queueFrameAction(unsigned int frame, const IPAOperationData &action); @@ -335,6 +337,15 @@ public: std::queue embeddedQueue_; std::deque requestQueue_; + /* + * Manage horizontal and vertical flips supported (or not) by the + * sensor. Also store the "native" Bayer order (that is, with no + * transforms applied). + */ + bool supportsFlips_; + bool flipsAlterBayerOrder_; + BayerFormat::Order nativeBayerOrder_; + private: void checkRequestCompleted(); void tryRunPipeline(); @@ -352,6 +363,9 @@ public: Status validate() override; + /* Cache the combinedTransform_ that will be applied to the sensor */ + Transform combinedTransform_; + private: const RPiCameraData *data_; }; @@ -400,11 +414,54 @@ 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 the + * 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 to save us working it out again. + */ + combinedTransform_ = combined; + unsigned int rawCount = 0, outCount = 0, count = 0, maxIndex = 0; std::pair outSize[2]; Size maxSize; @@ -420,7 +477,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; @@ -756,7 +829,7 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) crop.y = (sensorFormat.size.height - crop.height) >> 1; data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop); - ret = data->configureIPA(); + ret = data->configureIPA(config); if (ret) LOG(RPI, Error) << "Failed to configure the IPA: " << ret; @@ -967,6 +1040,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! @@ -1114,8 +1229,12 @@ int RPiCameraData::loadIPA() return ipa_->init(settings); } -int RPiCameraData::configureIPA() +int RPiCameraData::configureIPA(const CameraConfiguration *config) { + /* We know config must be an RPiCameraConfiguration. */ + const RPiCameraConfiguration *rpiConfig = + static_cast(config); + std::map streamConfig; std::map entityControls; IPAOperationData ipaConfig = {}; @@ -1172,12 +1291,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(!!(rpiConfig->combinedTransform_ & Transform::HFlip))); + ctrls.set(V4L2_CID_VFLIP, + static_cast(!!(rpiConfig->combinedTransform_ & Transform::VFlip))); + unicam_[Unicam::Image].dev()->setControls(&ctrls); + } } if (result.operation & RPI_IPA_CONFIG_SENSOR) { From patchwork Fri Sep 4 10:30:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9497 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 7F0D0BE174 for ; Fri, 4 Sep 2020 10:30:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4388262B29; Fri, 4 Sep 2020 12:30:55 +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="swb1WyIi"; dkim-atps=neutral Received: from mail-wm1-x335.google.com (mail-wm1-x335.google.com [IPv6:2a00:1450:4864:20::335]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 03ED462B2F for ; Fri, 4 Sep 2020 12:30:53 +0200 (CEST) Received: by mail-wm1-x335.google.com with SMTP id c19so7220427wmd.1 for ; Fri, 04 Sep 2020 03:30:53 -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=VKXVTPrPfOszWgPiSbNWOqplkMpV4jozlxf9iUVZATU=; b=swb1WyIikQe5w5yuEMv1vCEvURHEu4Cxk7Om/0pBZ/GDEo3TtGspCnvmG7hN6fD2Sc YE1G+JxwIOgGxwFl932ZqvgXpZfxnntlesXmvAvNlQoqLh7maP2jOg59LIEOP5psXo3L ufXPXxukwDtzFeYRnAjjd0KTqd1aDqbihnf7bES8zMonPAVtUz3r1yTYYxa3IN7SQYzB lQrJJA50wT0Csi7xyJocBbVGPFmkfAS+AltgWlx1QMtGGR3vHP7ljGlHcYmXS70vg928 h03vT+bpvVP1BDQMgjFKJGzOZ73i+ehuZDE1grZeTmTGh+XHBvpfvV935y1jDSUYsPiB rCHA== 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=VKXVTPrPfOszWgPiSbNWOqplkMpV4jozlxf9iUVZATU=; b=d4Lj33zt4jZId5N7bP9JSXodeS9iZRXRIypzWnlEMFsoVzfIvpDDLXx2/pwH8ZZ6cC hBhBxt8+UbgPBXrKymvE9nMux08t7/QiXtCFHSvC8vL8yMtL3AtGzKN6e8zkwVO4q4Xb PsRVSi9wHdgXffPjvCKZ7oyD75Tggfmy0J0YnR+mpZaWCtmstFF/Zhmm29eOhyDveII+ 6hO+ms61VbstqS8gwh/vvZ0K6aI3v5ZEg1GBV2C14Bulmm8PdgD8LsEI3E8k2yQLAcfx ISJkR3ytt6o2mLhxH+6ewfvAiHBbiPiI3sJsIPPkNUvFDtR9V/u0BRSU2sxh1FM6y05r y9Lw== X-Gm-Message-State: AOAM532z4EDa5ZkP1FSVcrwe7yEsZ71aFcsVRTcrh6o5kTQVQK2aIJzm Adxafixd3tjQCJkfnzq54MZw6TK/DkOOlQ== X-Google-Smtp-Source: ABdhPJwACpHUbR7mqU9MZ2cmFhja0nRTjCeuZbLbXCeyg9cOejORkY0p+RWR0uZuPWFQdbJZE/gpxA== X-Received: by 2002:a1c:6385:: with SMTP id x127mr7252744wmb.95.1599215452324; Fri, 04 Sep 2020 03:30:52 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:51 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:41 +0100 Message-Id: <20200904103042.1593-8-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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 ee654d1..c8dc88f 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -1255,6 +1255,9 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) 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(config->transform) }; + /* Allocate the lens shading table via dmaHeap and pass to the IPA. */ if (!lsTable_.isValid()) { lsTable_ = dmaHeap_.alloc("ls_grid", MAX_LS_GRID_SIZE); @@ -1263,7 +1266,7 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) /* 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 Fri Sep 4 10:30:42 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 9498 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 DA152BF6D3 for ; Fri, 4 Sep 2020 10:30:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A07ED62B68; Fri, 4 Sep 2020 12:30:55 +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="PCm4yE49"; dkim-atps=neutral Received: from mail-wm1-x343.google.com (mail-wm1-x343.google.com [IPv6:2a00:1450:4864:20::343]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A409D62B2F for ; Fri, 4 Sep 2020 12:30:53 +0200 (CEST) Received: by mail-wm1-x343.google.com with SMTP id a9so5597610wmm.2 for ; Fri, 04 Sep 2020 03:30:53 -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=NxN5BIHNXBiSe01AANUlWSLW1QWfFBaoWgYdFWkMoL4=; b=PCm4yE49avK96QxjHNgOl3Of6MfscXKBm5hxXYBNrb94cF2TqGe0VosyKPVW2AzVe2 oHipLzahpvEOAA9HYqGHga6SS6may31mnR8zbm8hY2lBk9Zt18rd/L/4UhnkSQWY6WwR ypZl6R2nihtUnWtLEPDdD1aYaxUIX3LvroB8QwkwYqKMHoIHY1+w4aWm/5VwMgTaBskr TuDvjQJKQlY/PfNL7jvUugBu2vRdOlwJsjhRBWP+jgj8U2iIoUEIKG+REvN9MB9ItRUJ fKQkZUZd/grty00bQc5jDBR3oI/oFbFlj7Oy7uWl735NnLbWuMRZHst/N7cyr88MXDnj /P1A== 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=NxN5BIHNXBiSe01AANUlWSLW1QWfFBaoWgYdFWkMoL4=; b=jBljvPhufSnIbBk8MId++EXrJBLiZWiRucOTDBOGym/5rfSWXcUYcNfLYvTxiEq+Vl G123vPm2QrKBgi9tZE/CrTl4LKaNOcLGRGXNgTLz62bkZYKRDc9cX4vfM6Pa97JM4eO/ 8MnKxb1E5rqxyItO0Bg0dxkG4fl4g2MAAbn3KE5tO34FAFi5HIV92ax77Z6QU5mCae6K F9M2ENKg6EeiN2LZ3cERgIdq97uS5enTP0t8AMk/BXIrEeCLlE6CuDXxZ3/936idIP5g wtWEIkB5EZJ21cbMlYSnNOfschzUPWI5uLQOvsC31JijCW3O+1etFKwJ7eTifiN8goPh b+xg== X-Gm-Message-State: AOAM533h5gCHeDlWe/J07XY2VmUvbD//bPgG6DT3UwzH6RlbQB9y0QW/ FlHcbdsnJdirZadSsU2xSKt69Se80huJzw== X-Google-Smtp-Source: ABdhPJyS/eS/xyBTZIJ2l7mLvrfjsEICcdfqR4QzL42D7pGHa3lkvui0cWMbARoVIN7K1toaE+gX0g== X-Received: by 2002:a7b:c958:: with SMTP id i24mr7234760wml.50.1599215453141; Fri, 04 Sep 2020 03:30:53 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id q4sm10294542wru.65.2020.09.04.03.30.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 03:30:52 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 4 Sep 2020 11:30:42 +0100 Message-Id: <20200904103042.1593-9-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200904103042.1593-1-david.plowman@raspberrypi.com> References: <20200904103042.1593-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v7 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++) {