From patchwork Mon Oct 26 17:19: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: 10257 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 D987BBDB13 for ; Mon, 26 Oct 2020 17:19:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E703E6203F; Mon, 26 Oct 2020 18:19:17 +0100 (CET) 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="Lqhj0d8k"; 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 D0D4660361 for ; Mon, 26 Oct 2020 18:19:16 +0100 (CET) Received: by mail-wr1-x444.google.com with SMTP id j7so13529281wrt.9 for ; Mon, 26 Oct 2020 10:19:16 -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=iP5cVs05jjf8HaSDi6lKi+9ku2xFcrV5am2f7dn/CrU=; b=Lqhj0d8krBIW0JjZPlnccN5STOSYa7uWRiUz4Vs/3bOoJzzBz2T0GO4FPd0vZ12kA7 EaPMS7QdNH70Fqn7LFyf8aVVXvdhFGETS1Z5/vf01GGmxQBvKwVEFUOSTQYaJ3NPGe24 0LR6XBaiycz+KOv+PwsgZHaRbUe6gCjn+pBS+tA1hnfgXM1C8ohiF0tsCIpjZTpgtr5m 2lNs6SLYkSSAZ5FAokm8NKa5zvcnfRMD1JHZ61/SGzNFUthjtlfQlg6xLINHTOJ9nQDo 5OAL4WrrK24S5XCo8rqVIioBy8a2JJ7yT8+Kj+dKTMWl+tLk/pD9XLnwTc5RMF6sNgpi +Lcw== 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=iP5cVs05jjf8HaSDi6lKi+9ku2xFcrV5am2f7dn/CrU=; b=mSdcucfKTKZwG3NIeOH0anCfcNP9oIrNcuCWNW0ljkpiBctN8Mc6g2KUW5OcsPvdxR e4+nzIqNca+dJiillTF1s8J1iU9gZNSkehbyR/Y3vh2Z04+3wsKnSOoURKYxaAViIBBs 6Nh6P4s0o64PjIZIcrBJyFfxRy/jsdtRMnD6ROi+TI3RCDsoeY0sUmNmbOPfve9pxQKs q2ffaSKu4I5mmXig60DUGHIDGx10fVpqrQ6BTfTw9hTIy/tE++rP/f9thVdz4rfYFrRX QuqGTtL4N3ANw7f/g6ab2d8T3w6Wtf4lh8XgVhSCHEtko9BZfk/kPKD+ddBQ25dBXKq1 g0Rw== X-Gm-Message-State: AOAM533a5DDvwH6f7oiF2MxT2fQieun2Kd/U88ZCMg1Ve3hTGIKnGXLO k3BBW2sZgzcMU7HUaZWe9vKffxbzmKtmqg== X-Google-Smtp-Source: ABdhPJyzi7zlahXcw23lxtwuf/bDwffLzQUj+TXd5pt4XGJiYd2uOOVTnzR4GGAlyAXLZd/P/Y0g4Q== X-Received: by 2002:adf:e54b:: with SMTP id z11mr20020400wrm.128.1603732756288; Mon, 26 Oct 2020 10:19:16 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id o63sm20955295wmo.2.2020.10.26.10.19.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Oct 2020 10:19:15 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Mon, 26 Oct 2020 17:19:03 +0000 Message-Id: <20201026171908.21463-2-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201026171908.21463-1-david.plowman@raspberrypi.com> References: <20201026171908.21463-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 1/6] libcamera: Add ScalerCrop 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 ScalerCrop control selects how much of the sensor's active pixel area will be scaled to form the final output image. It can be used to implement digital zoom. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart --- src/libcamera/control_ids.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml index 4c415545..b4f76570 100644 --- a/src/libcamera/control_ids.yaml +++ b/src/libcamera/control_ids.yaml @@ -284,4 +284,17 @@ controls: order in an array of 9 floating point values. size: [3x3] + + - ScalerCrop: + type: Rectangle + description: | + Sets the image portion that will be scaled to form the whole of + the final output image. The (x,y) location of this rectangle is + relative to the PixelArrayActiveArea that is being used. The units + remain native sensor pixels, even if the sensor is being used in + a binning or skipping mode. + + This control is only present when the pipeline supports scaling. Its + maximum valid value is given by the properties::ScalerCropMaximum + property, and the two can be used to implement digital zoom. ... From patchwork Mon Oct 26 17:19: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: 10261 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 85561BDB13 for ; Mon, 26 Oct 2020 17:19:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 513C062040; Mon, 26 Oct 2020 18:19:25 +0100 (CET) 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="hRB5nTjZ"; 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 1F54F62037 for ; Mon, 26 Oct 2020 18:19:21 +0100 (CET) Received: by mail-wm1-x32a.google.com with SMTP id v5so12464890wmh.1 for ; Mon, 26 Oct 2020 10:19: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=DlOT1G0P1YZwReDuJ5vNb83iKivxweypgDohTHSrlUA=; b=hRB5nTjZoFiAlhghMnoFFbQa9aLmrXn+L/SYzxshwXcWjL6+SzmSwJ+IdktRqgbz52 ol3ApB17YlQT7JDypGcD7byQEbNaB0kX06oK5BJeN6MryvAr36XrTZ9Nb5gllsUl/CKE en75m0gTy7eDyqCkuGjx6sExwsj7+g1w11+950tejpZB2vegbsHomZYFHNPi75sh1z5B To3KLJKit2cx4QJ92wYmb1n/8HVrl9RwmfMZRbOwnLGXty1hbP22SN5bNHOMQWVmuIbL rICBG1De4x8NvIbyv4g+ABnXmdYaSvPJo/mjXXGQtN7ayjgbYttTOwA97GkttBDEj9h8 cHpg== 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=DlOT1G0P1YZwReDuJ5vNb83iKivxweypgDohTHSrlUA=; b=FlZsBnTbPJClbvVosFqHPqYOJyFAgCp19En2Xtw2qrSMYWRWf4GVEPKijsvxqPCdhe UvnoMtLjFF5YHoG0mnX1gAjgsFCvemsr+ywtAVaCIXD+AHFE4S7AN6ZrvL/ced7bdNuj 05GeBuTv10Pg992GQNlsRuX4MiJTrGMjJY0fxVcyFOyLaKyQx7vJF+iDHASa23KmWf2E /ywT88UnbebuDWYfskoll5UJEmsVce+TBFXsVou3c0obkXjHFKyTsbbcE1YL17YAt7JK 1z2FWwIJxg88fwyvCUAdm5U3tZ2LCftaCCexbT5jjcg9xRK/FewGjhUSRveReDpV7vfi UKOA== X-Gm-Message-State: AOAM533qysikA5oa1+uk+D78aggG98Z8DTC27SycaN1vULpS0AOz6C/s ZVXs6yEGBysY9vNxTPNFkpO1AXSeV7Qh5g== X-Google-Smtp-Source: ABdhPJy+Y5Yx1U80jE+4w4Y2NIZPDE4+E6xvvP9UVp9FXq6Kc4XrqB+sJnALFHqiOQdlPTr34LVQNg== X-Received: by 2002:a1c:2ed3:: with SMTP id u202mr17379357wmu.85.1603732757218; Mon, 26 Oct 2020 10:19:17 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id o63sm20955295wmo.2.2020.10.26.10.19.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Oct 2020 10:19:16 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Mon, 26 Oct 2020 17:19:04 +0000 Message-Id: <20201026171908.21463-3-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201026171908.21463-1-david.plowman@raspberrypi.com> References: <20201026171908.21463-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 2/6] libcamera: Add SensorCropMaximum property 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 SensorCropMaximum camera property reports the location of that part of the image sensor array that can be scaled to produce the output images, given in native sensor pixels. It will normally change when a new camera mode is selected, and can be used to implement digital zoom. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- src/libcamera/property_ids.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/libcamera/property_ids.yaml b/src/libcamera/property_ids.yaml index 7261263a..ccb88d28 100644 --- a/src/libcamera/property_ids.yaml +++ b/src/libcamera/property_ids.yaml @@ -663,4 +663,19 @@ controls: \todo Rename this property to ActiveAreas once we will have property categories (i.e. Properties::PixelArray::ActiveAreas) + - ScalerCropMaximum: + type: Rectangle + description: | + The maximum valid rectangle for the controls::ScalerCrop control. This + reflects the minimum mandatory cropping applied in the camera sensor and + the rest of the pipeline. Just as the ScalerCrop control, it defines a + rectangle taken from the sensor's active pixel area. + + This property is valid only after the camera has been successfully + configured and its value may change whenever a new configuration is + applied. + + \todo Turn this property into a "maximum control value" for the + ScalerCrop control once "dynamic" controls have been implemented. + ... From patchwork Mon Oct 26 17:19: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: 10258 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 9A2F6BDB13 for ; Mon, 26 Oct 2020 17:19:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 468CD62048; Mon, 26 Oct 2020 18:19:21 +0100 (CET) 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="q0QvjBnK"; dkim-atps=neutral Received: from mail-wm1-x330.google.com (mail-wm1-x330.google.com [IPv6:2a00:1450:4864:20::330]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6C80B62037 for ; Mon, 26 Oct 2020 18:19:18 +0100 (CET) Received: by mail-wm1-x330.google.com with SMTP id c194so12464353wme.2 for ; Mon, 26 Oct 2020 10:19:18 -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=zoc4yH1tGrvtNaaXgulQvSPpAXt0a3cFREKS4h0ENaQ=; b=q0QvjBnKs1pbcSVvnpxQFeRdXm/i4LNaSrdbast+2I9/1blbSAPdDyMw+pGqSw6/Yu 1+K/fwJSUYcz9inZ/+4qixsFzSiQti6KvmotLePUlUXIFkdgsrmBlCtOh4ZtOR4wPGHH 03VLAsDaFBavme2HzUfiWK56C6iL7oKeCWl1OBPLGtdUl79QSwHX541RsFNcJ8YwmZUH SF4rQ0P/jzA8GSwcANMkyYjr+5mkphKdRFuSI8zUPsNOZUzBTU+y7VXlNpkv/J0KNzyh fDOc4PKZgNNQ81SqoDqczii93NIEGK4GTerri2t9ZvCnrUN1AfYPMlElqpjLSKGO0KSY 0r9Q== 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=zoc4yH1tGrvtNaaXgulQvSPpAXt0a3cFREKS4h0ENaQ=; b=AQaIcKW/CIq7XKscMy6aSQY/nl+SM63MPzUem7fEX2LQYAugkhK3srJE5P1TrimkRb yLoa3WfPt048pKqgdF/JkkpTFkmbsOmvnu7MBI01TF4zq92GYHtuoSLBTD5gLwwR0ua3 ekG0TR+EOZ9lmHzdSfwHqd5JkIhk9cvwET4yvKShUIQkWg2u3mkcdG4sHoNZ3Gnssb/+ baXrg5fFT0IHTDu2j1L8eReyPTXcaevIynVoWiV+ye9++zwXdO5tKyK5dsqLRW6DXOda BTmXQd/CtNFqd8dQeUN5wFBVVfR6h0QhgQPK7O0uIFMlJ6pgq5rPsvzINTkrzDUv2SYi vOow== X-Gm-Message-State: AOAM531FdlW1cAhNYLIhi8uOFufwGKkETajp1n08xYYJI8XblQVl/JAX 4pMHOiM8XLwu82fXxNfDGWsJdrhydCk28g== X-Google-Smtp-Source: ABdhPJzMzaCPNeX+P78W1O38Va/Pg8wbMEWYPch1aeZJoV9V5iivk0mErMljZtI78ivrVREShdgFvA== X-Received: by 2002:a1c:254:: with SMTP id 81mr12627092wmc.21.1603732757980; Mon, 26 Oct 2020 10:19:17 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id o63sm20955295wmo.2.2020.10.26.10.19.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Oct 2020 10:19:17 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Mon, 26 Oct 2020 17:19:05 +0000 Message-Id: <20201026171908.21463-4-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201026171908.21463-1-david.plowman@raspberrypi.com> References: <20201026171908.21463-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 3/6] libcamera: raspberrypi: Initialise the SensorCropMaximum property 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" Initialise it to show we support its use, however, set it to all zeroes to indicate that it's not meaningful yet. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart --- src/libcamera/pipeline/raspberrypi/raspberrypi.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index dd62dfc7..763451a8 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -926,6 +926,13 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator) /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); + /* + * Set a default value for the ScalerCropMaximum property to show + * that we support its use, however, initialise it to zero because + * it's not meaningful until a camera mode has been chosen. + */ + data->properties_.set(properties::ScalerCropMaximum, Rectangle{}); + /* * We cache three things about the sensor in relation to transforms * (meaning horizontal and vertical flips). From patchwork Mon Oct 26 17:19: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: 10259 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 3A988BDB13 for ; Mon, 26 Oct 2020 17:19:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 090B56204A; Mon, 26 Oct 2020 18:19:22 +0100 (CET) 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="eIvjs6xB"; dkim-atps=neutral Received: from mail-wr1-x431.google.com (mail-wr1-x431.google.com [IPv6:2a00:1450:4864:20::431]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A735E62037 for ; Mon, 26 Oct 2020 18:19:19 +0100 (CET) Received: by mail-wr1-x431.google.com with SMTP id n15so13594150wrq.2 for ; Mon, 26 Oct 2020 10:19: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=+mUU3LGIBJG/X8d7IBu44bzlYXP7jfilBN5urrKRpgI=; b=eIvjs6xBcCO8OkdTS3dpaMOCeuUoOPAZEEW1P6WlDkfETHgPxSu69zm6J1VKFYKDdG yVWKoOrCK36mltiXsHMJz1FQUX3fgGpWjVyrn7mCLXbyvJ+L0ZQaxnLj4c2xaNMVdVUU 6llATlDB/GhYlicnRkn2UnbVQNs3gYDMmifZkE60+yKxnznHZS/uPMsRTyppx7CVMq5X jHUene/neXcsYBujh2qx18Q4XpXNNkBvSTkq6SIzblqwSInxZ+DGEycCv4WVk65kImoM OuiWyEgCKavuqqnQ1RZfL0omR1Zh9Q/rekuVunUsZSxE43uWshtRo5l7BlkOb5QdgzY+ AZrA== 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=+mUU3LGIBJG/X8d7IBu44bzlYXP7jfilBN5urrKRpgI=; b=S8jC6CYvoo7IhR43x0QQSOZO/OTHMOAqCJf8wA+RviqlNVP8zget8hX78A2wZkSBGH uyzV8bnELntxPVBY/jNcjE3nqpPqNQVjWCYI5NlIh9Hz/t74DKzYJ4TeCPMzQgIGK+An U4PWmc5VsIItljyEhqM3OZXGPHTAcL5IQQ/j5PO6ECWZ2ew4g0ZnKPRNOOg+Wz21vvsx 5Vw23sEmvHnKZH/6iR6M/O9znxgQHZ/3AHqZ50o9edoHxgitdDA6qUnPgl3SFLSiqEfs jaUCKG4R/IikF9C1XT9nX2p8yl7aItjbyZ7Lu+nHM2ezcJ0ZqlBwWto0wHZ+GO+M6z1H uM5g== X-Gm-Message-State: AOAM532XxbRMjQ8MH+L71Y1HV1DX5ZyyrLw3JCCldPNNM/8qr32/mQqh yrcoulHU9x8l0yPC/bctB+BBguSK/0VuAg== X-Google-Smtp-Source: ABdhPJw0F4RakDYwAJsu39bAbz9KsTwAWczm6ztdXFLmkig0jatZDuZqTbjp6DWc0XYj50IRu4u3WA== X-Received: by 2002:a5d:4b49:: with SMTP id w9mr20040710wrs.41.1603732758869; Mon, 26 Oct 2020 10:19:18 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id o63sm20955295wmo.2.2020.10.26.10.19.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Oct 2020 10:19:18 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Mon, 26 Oct 2020 17:19:06 +0000 Message-Id: <20201026171908.21463-5-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201026171908.21463-1-david.plowman@raspberrypi.com> References: <20201026171908.21463-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 4/6] libcamera: Add geometry helper functions X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" These functions are aimed at making it easier to calculate cropping rectangles, particularly in order to implement digital zoom. Signed-off-by: David Plowman Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- include/libcamera/geometry.h | 67 +++++++ src/libcamera/geometry.cpp | 328 +++++++++++++++++++++++++++++++++++ 2 files changed, 395 insertions(+) diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h index 02fb63c0..ca5687a7 100644 --- a/include/libcamera/geometry.h +++ b/include/libcamera/geometry.h @@ -13,6 +13,38 @@ namespace libcamera { +class Rectangle; + +class Point +{ +public: + constexpr Point() + : x(0), y(0) + { + } + + constexpr Point(int xpos, int ypos) + : x(xpos), y(ypos) + { + } + + int x; + int y; + + const std::string toString() const; + + constexpr Point operator-() const + { + return { -x, -y }; + } +}; + +bool operator==(const Point &lhs, const Point &rhs); +static inline bool operator!=(const Point &lhs, const Point &rhs) +{ + return !(lhs == rhs); +} + class Size { public: @@ -93,6 +125,17 @@ public: std::max(height, expand.height) }; } + + Size boundedToAspectRatio(const Size &ratio) const; + Size expandedToAspectRatio(const Size &ratio) const; + + Rectangle centeredTo(const Point ¢er) const; + + Size operator*(float factor) const; + Size operator/(float factor) const; + + Size &operator*=(float factor); + Size &operator/=(float factor); }; bool operator==(const Size &lhs, const Size &rhs); @@ -176,6 +219,11 @@ public: { } + constexpr explicit Rectangle(const Size &size) + : x(0), y(0), width(size.width), height(size.height) + { + } + int x; int y; unsigned int width; @@ -183,6 +231,25 @@ public: bool isNull() const { return !width && !height; } const std::string toString() const; + Point center() const; + + Size size() const + { + return { width, height }; + } + + Point topLeft() const + { + return { x, y }; + } + + Rectangle &scaleBy(const Size &numerator, const Size &denominator); + Rectangle &translateBy(const Point &point); + + Rectangle boundedTo(const Rectangle &bound) const; + Rectangle enclosedIn(const Rectangle &boundary) const; + Rectangle scaledBy(const Size &numerator, const Size &denominator) const; + Rectangle translatedBy(const Point &point) const; }; bool operator==(const Rectangle &lhs, const Rectangle &rhs); diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp index b12e1a62..438bf90b 100644 --- a/src/libcamera/geometry.cpp +++ b/src/libcamera/geometry.cpp @@ -10,6 +10,8 @@ #include #include +#include "libcamera/internal/log.h" + /** * \file geometry.h * \brief Data structures related to geometric objects @@ -17,6 +19,64 @@ namespace libcamera { +/** + * \class Point + * \brief Describe a point in two-dimensional space + * + * The Point structure defines a point in two-dimensional space with integer + * precision. The coordinates of a Point may be negative as well as positive. + */ + +/** + * \fn Point::Point() + * \brief Construct a Point with x and y set to 0 + */ + +/** + * \fn Point::Point(int xpos, int ypos) + * \brief Construct a Point at given \a xpos and \a ypos values + * \param[in] xpos The x-coordinate + * \param[in] ypos The y-coordinate + */ + +/** + * \var Point::x + * \brief The x-coordinate of the Point + */ + +/** + * \var Point::y + * \brief The y-coordinate of the Point + */ + +/** + * \brief Assemble and return a string describing the point + * \return A string describing the point + */ +const std::string Point::toString() const +{ + std::stringstream ss; + + ss << "(" << x << "," << y << ")"; + + return ss.str(); +} + +/** + * \fn Point Point::operator-() const + * \brief Negate a Point by negating both its x and y coordinates + * \return The negated point + */ + +/** + * \brief Compare points for equality + * \return True if the two points are equal, false otherwise + */ +bool operator==(const Point &lhs, const Point &rhs) +{ + return lhs.x == rhs.x && lhs.y == rhs.y; +} + /** * \struct Size * \brief Describe a two-dimensional size @@ -143,6 +203,117 @@ const std::string Size::toString() const * height of this size and the \a expand size */ +/** + * \brief Bound the size down to match the aspect ratio given by \a ratio + * \param[in] ratio The size whose aspect ratio must be matched + * + * The behaviour of this function is undefined if either the width or the + * height of the \a ratio is zero. + * + * \return A Size whose width and height are equal to the width and height + * of this Size aligned down to the aspect ratio of \a ratio + */ +Size Size::boundedToAspectRatio(const Size &ratio) const +{ + ASSERT(ratio.width && ratio.height); + + uint64_t ratio1 = static_cast(width) * + static_cast(ratio.height); + uint64_t ratio2 = static_cast(ratio.width) * + static_cast(height); + + if (ratio1 > ratio2) + return { static_cast(ratio2 / ratio.height), height }; + else + return { width, static_cast(ratio1 / ratio.width) }; +} + +/** + * \brief Expand the size to match the aspect ratio given by \a ratio + * \param[in] ratio The size whose aspect ratio must be matched + * + * The behaviour of this function is undefined if either the width or the + * height of the \a ratio is zero. + * + * \return A Size whose width and height are equal to the width and height + * of this Size expanded up to the aspect ratio of \a ratio + */ +Size Size::expandedToAspectRatio(const Size &ratio) const +{ + ASSERT(ratio.width && ratio.height); + + uint64_t ratio1 = static_cast(width) * + static_cast(ratio.height); + uint64_t ratio2 = static_cast(ratio.width) * + static_cast(height); + + if (ratio1 < ratio2) + return { static_cast(ratio2 / ratio.height), height }; + else + return { width, static_cast(ratio1 / ratio.width) }; +} + +/** + * \brief Center a rectangle of this size at a given Point + * \param[in] center The center point the Rectangle is to have + * + * A Rectangle of this object's size is positioned so that its center + * is at the given Point. + * + * \return A Rectangle of this size, centered at the given Point. + */ +Rectangle Size::centeredTo(const Point ¢er) const +{ + int x = center.x - width / 2; + int y = center.y - height / 2; + + return { x, y, width, height }; +} + +/** + * \brief Scale size up by the given factor + * \param[in] factor The factor + * \return The scaled Size + */ +Size Size::operator*(float factor) const +{ + return Size(width * factor, height * factor); +} + +/** + * \brief Scale size down by the given factor + * \param[in] factor The factor + * \return The scaled Size + */ +Size Size::operator/(float factor) const +{ + return Size(width / factor, height / factor); +} + +/** + * \brief Scale this size up by the given factor in place + * \param[in] factor The factor + * \return A reference to this object + */ +Size &Size::operator*=(float factor) +{ + width *= factor; + height *= factor; + return *this; +} + +/** + * \brief Scale this size down by the given factor in place + * \param[in] factor The factor + * \return A reference to this object + */ +Size &Size::operator/=(float factor) +{ + width /= factor; + height /= factor; + return *this; +} + /** * \brief Compare sizes for equality * \return True if the two sizes are equal, false otherwise @@ -365,6 +536,13 @@ bool operator==(const SizeRange &lhs, const SizeRange &rhs) * \param[in] height The height */ +/** + * \fn Rectangle::Rectangle(const Size &size) + * \brief Construct a Rectangle of \a size with its top left corner located + * at (0,0) + * \param[in] size The desired Rectangle size + */ + /** * \var Rectangle::x * \brief The horizontal coordinate of the rectangle's top-left corner @@ -404,6 +582,156 @@ const std::string Rectangle::toString() const return ss.str(); } +/** + * \brief Retrieve the center point of this rectangle + * \return The center Point + */ +Point Rectangle::center() const +{ + return { x + static_cast(width / 2), y + static_cast(height / 2) }; +} + +/** + * \fn Size Rectangle::size() const + * \brief Retrieve the size of this rectangle + * \return The Rectangle size + */ + +/** + * \fn Point Rectangle::topLeft() const + * \brief Retrieve the coordinates of the top left corner of this Rectangle + * \return The Rectangle's top left corner + */ + +/** + * \brief Apply a non-uniform rational scaling in place to this Rectangle + * \param[in] numerator The numerators of the x and y scaling factors + * \param[in] denominator The denominators of the x and y scaling factors + * + * A non-uniform scaling is applied in place such the resulting x + * coordinates are multiplied by numerator.width / denominator.width, + * and similarly for the y coordinates (using height in place of width). + * + * \return A reference to this object + */ +Rectangle &Rectangle::scaleBy(const Size &numerator, const Size &denominator) +{ + x = static_cast(x) * numerator.width / denominator.width; + y = static_cast(y) * numerator.height / denominator.height; + width = static_cast(width) * numerator.width / denominator.width; + height = static_cast(height) * numerator.height / denominator.height; + + return *this; +} + +/** + * \brief Translate this Rectangle in place by the given Point + * \param[in] point The amount to translate the Rectangle by + * + * The Rectangle is translated in the x-direction by the point's x coordinate + * and in the y-direction by the point's y coordinate. + * + * \return A reference to this object + */ +Rectangle &Rectangle::translateBy(const Point &point) +{ + x += point.x; + y += point.y; + + return *this; +} + +/** + * \brief Calculate the intersection of this Rectangle with another + * \param[in] bound The Rectangle that is intersected with this Rectangle + * + * This method calculates the standard intersection of two rectangles. If the + * rectangles do not overlap in either the x or y direction, then the size + * of that dimension in the result (its width or height) is set to zero. Even + * when one dimension is set to zero, note that the other dimension may still + * have a positive value if there was some overlap. + * + * \return A Rectangle that is the intersection of the input rectangles + */ +Rectangle Rectangle::boundedTo(const Rectangle &bound) const +{ + int topLeftX = std::max(x, bound.x); + int topLeftY = std::max(y, bound.y); + int bottomRightX = std::min(x + width, bound.x + bound.width); + int bottomRightY = std::min(y + height, bound.y + bound.height); + + unsigned int newWidth = std::max(bottomRightX - topLeftX, 0); + unsigned int newHeight = std::max(bottomRightY - topLeftY, 0); + + return { topLeftX, topLeftY, newWidth, newHeight }; +} + +/** + * \brief Enclose a Rectangle so as not to exceed another Rectangle + * \param[in] boundary The limit that the returned Rectangle will not exceed + * + * The Rectangle is modified so that it does not exceed the given \a boundary. + * This process involves translating the Rectangle if any of its edges + * lie beyond \a boundary, so that those edges then lie along the boundary + * instead. + * + * If either width or height are larger than \a boundary, then the returned + * Rectangle is clipped to be no larger. But other than this, the + * Rectangle is not clipped or reduced in size, merely translated. + * + * Note that this is not a conventional Rectangle intersection function + * which is provided by boundedTo(). + * + * \return A Rectangle that does not extend beyond a boundary Rectangle + */ +Rectangle Rectangle::enclosedIn(const Rectangle &boundary) const +{ + /* We can't be bigger than the boundary rectangle. */ + Rectangle result = boundedTo(Rectangle{ x, y, boundary.size() }); + + result.x = std::clamp(result.x, boundary.x, + boundary.x + boundary.width - result.width); + result.y = std::clamp(result.y, boundary.y, + boundary.y + boundary.height - result.height); + + return result; +} + +/** + * \brief Apply a non-uniform rational scaling to this Rectangle + * \param[in] numerator The numerators of the x and y scaling factors + * \param[in] denominator The denominators of the x and y scaling factors + * + * A non-uniform scaling is applied such the resulting x + * coordinates are multiplied by numerator.width / denominator.width, + * and similarly for the y coordinates (using height in place of width). + * + * \return The non-uniformly scaled Rectangle + */ +Rectangle Rectangle::scaledBy(const Size &numerator, const Size &denominator) const +{ + int scaledX = static_cast(x) * numerator.width / denominator.width; + int scaledY = static_cast(y) * numerator.height / denominator.height; + unsigned int scaledWidth = static_cast(width) * numerator.width / denominator.width; + unsigned int scaledHeight = static_cast(height) * numerator.height / denominator.height; + + return { scaledX, scaledY, scaledWidth, scaledHeight }; +} + +/** + * \brief Translate a Rectangle by the given amounts + * \param[in] point The amount to translate the Rectangle by + * + * The Rectangle is translated in the x-direction by the point's x coordinate + * and in the y-direction by the point's y coordinate. + * + * \return The translated Rectangle + */ +Rectangle Rectangle::translatedBy(const Point &point) const +{ + return { x + point.x, y + point.y, width, height }; +} + /** * \brief Compare rectangles for equality * \return True if the two rectangles are equal, false otherwise From patchwork Mon Oct 26 17:19: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: 10260 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 D7A56BDB13 for ; Mon, 26 Oct 2020 17:19:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 91ADB62046; Mon, 26 Oct 2020 18:19:22 +0100 (CET) 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="YxhUgAbz"; 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 789E362035 for ; Mon, 26 Oct 2020 18:19:20 +0100 (CET) Received: by mail-wr1-x42d.google.com with SMTP id y12so13542365wrp.6 for ; Mon, 26 Oct 2020 10:19: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=JQzXIXipEcVEl2Rq6HsEkqgFc5bdahjLLiZ2swiWWEk=; b=YxhUgAbzsZBqLJftaJHoxUCN5LQJd7XgD7jgyal8SST55sorW9D5gfgWUvfuuriBiE wVFPsREkCQI8A8Vsr8dkGwlANg74uZAVqORTrD7vNiQ26EjlUqNsiO4JW5Uwbgj3UGal DDOU0DN+AkzdGFlkZAbaj9tSutXrxeZWAwyv+dfMRXlOwPVY3TDB26SZcpIAm2ILTSEy bJJB1QHQuO6QSOTo/MbVADxaWtDUUYqmIencWhoUEC5l5sPl79C1aj7QVsZAi7cYQgcY ENhETujpxYlhOedZPg7+4Meqjpzb1kS7hIvViOupooMvMwiGfIJJ6137YeL9rNSvYUKi lSiA== 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=JQzXIXipEcVEl2Rq6HsEkqgFc5bdahjLLiZ2swiWWEk=; b=Rkpf1zIVfllHmGPPVylEALTvDT1QyAolavC4STgOumondbwFzRIPYpqgk3mCYF7Ocs vDUhWuZGTAXSBJyeHcRd7eLVSh7xZ9EDiu2zQmpxhrfyOiKnScB3JRQcTPqRcrcEzaFY yy8mS7zKZLl5x7tC22bMNaUL1eEkWkwRNvzwAHyXrPFkyeWrlBx3ATV3OHee1KI41v0I EnCpk5AjHN2CuX9pF1Kb3Pr60XHLkw+nDu3BDY794yy8RQuEiYMiiR7qa9AK7ibAoeCw 9KoWNo3y4gpWGT+3Gs5AxcM+gt86787mcshx8YK0AtCzrazFiKxCRzJR0PeYUq3xWgp/ 4NBA== X-Gm-Message-State: AOAM530LoQv6Qnmz3Z2/sBRolhj7HidsxHMlAn7u+kdOY6vMoQNTpadK lmav2LXxVFKCC0TInmYtctvu3FaQJxAmMA== X-Google-Smtp-Source: ABdhPJy+hB6jdkJBI+3wkxUC/T2x7hMTDz61G2mKSMULAm+jf6dCx51pFw0yrbPsNhZ8dP3ptVNhig== X-Received: by 2002:a5d:4001:: with SMTP id n1mr19564163wrp.426.1603732759694; Mon, 26 Oct 2020 10:19:19 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id o63sm20955295wmo.2.2020.10.26.10.19.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Oct 2020 10:19:19 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Mon, 26 Oct 2020 17:19:07 +0000 Message-Id: <20201026171908.21463-6-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201026171908.21463-1-david.plowman@raspberrypi.com> References: <20201026171908.21463-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 5/6] libcamera: pipeline: raspberrypi: Implementation of digital zoom 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" During configure() we update the ScalerCropMaximum to the correct value for this camera mode and work out the minimum crop size allowed by the ISP. Whenever a new ScalerCrop request is received we check it's valid and apply it to the ISP V4L2 device. When the IPA returns its metadata to us we add the ScalerCrop information, rescaled to sensor native pixels. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart --- include/libcamera/ipa/raspberrypi.h | 1 + src/ipa/raspberrypi/raspberrypi.cpp | 5 + .../pipeline/raspberrypi/raspberrypi.cpp | 92 +++++++++++++++---- 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h index b23baf2f..ff2faf86 100644 --- a/include/libcamera/ipa/raspberrypi.h +++ b/include/libcamera/ipa/raspberrypi.h @@ -62,6 +62,7 @@ static const ControlInfoMap Controls = { { &controls::Saturation, ControlInfo(0.0f, 32.0f) }, { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) }, + { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, }; } /* namespace RPi */ diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp index 1c255aa2..f338ff8b 100644 --- a/src/ipa/raspberrypi/raspberrypi.cpp +++ b/src/ipa/raspberrypi/raspberrypi.cpp @@ -699,6 +699,11 @@ void IPARPi::queueRequest(const ControlList &controls) break; } + case controls::SCALER_CROP: { + /* We do nothing with this, but should avoid the warning below. */ + break; + } + default: LOG(IPARPI, Warning) << "Ctrl " << controls::controls.at(ctrl.first)->name() diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index 763451a8..b9d74a81 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -135,7 +135,7 @@ public: RPiCameraData(PipelineHandler *pipe) : CameraData(pipe), sensor_(nullptr), state_(State::Stopped), supportsFlips_(false), flipsAlterBayerOrder_(false), - dropFrameCount_(0), ispOutputCount_(0) + updateScalerCrop_(true), dropFrameCount_(0), ispOutputCount_(0) { } @@ -193,6 +193,13 @@ public: bool flipsAlterBayerOrder_; BayerFormat::Order nativeBayerOrder_; + /* For handling digital zoom. */ + CameraSensorInfo sensorInfo_; + Rectangle ispCrop_; /* crop in ISP (camera mode) pixels */ + Rectangle scalerCrop_; /* crop in sensor native pixels */ + bool updateScalerCrop_; + Size ispMinCropSize_; + unsigned int dropFrameCount_; private: @@ -677,26 +684,31 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) return ret; } - /* Adjust aspect ratio by providing crops on the input image. */ - Rectangle crop{ 0, 0, sensorFormat.size }; - - int ar = maxSize.height * sensorFormat.size.width - maxSize.width * sensorFormat.size.height; - if (ar > 0) - crop.width = maxSize.width * sensorFormat.size.height / maxSize.height; - else if (ar < 0) - crop.height = maxSize.height * sensorFormat.size.width / maxSize.width; + /* Figure out the smallest selection the ISP will allow. */ + Rectangle testCrop(0, 0, 1, 1); + data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop); + data->ispMinCropSize_ = testCrop.size(); - crop.width &= ~1; - crop.height &= ~1; + /* Adjust aspect ratio by providing crops on the input image. */ + Size size = sensorFormat.size.boundedToAspectRatio(maxSize); + Rectangle crop = size.centeredTo(Rectangle(sensorFormat.size).center()); + data->ispCrop_ = crop; - crop.x = (sensorFormat.size.width - crop.width) >> 1; - crop.y = (sensorFormat.size.height - crop.height) >> 1; data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop); ret = data->configureIPA(config); if (ret) LOG(RPI, Error) << "Failed to configure the IPA: " << ret; + /* + * Update the ScalerCropMaximum to the correct value for this camera mode. + * For us, it's the same as the "analogue crop". + * + * \todo Make this property the ScalerCrop maximum value when dynamic + * controls are available and set it at validate() time + */ + data->properties_.set(properties::ScalerCropMaximum, data->sensorInfo_.analogCrop); + return ret; } @@ -1154,8 +1166,8 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) ipaConfig.data.push_back(static_cast(lsTable_.fd())); } - CameraSensorInfo sensorInfo = {}; - int ret = sensor_->sensorInfo(&sensorInfo); + /* We store the CameraSensorInfo for digital zoom calculations. */ + int ret = sensor_->sensorInfo(&sensorInfo_); if (ret) { LOG(RPI, Error) << "Failed to retrieve camera sensor info"; return ret; @@ -1164,7 +1176,7 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) /* Ready the IPA - it must know about the sensor resolution. */ IPAOperationData result; - ipa_->configure(sensorInfo, streamConfig, entityControls, ipaConfig, + ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig, &result); unsigned int resultIdx = 0; @@ -1243,8 +1255,26 @@ void RPiCameraData::queueFrameAction([[maybe_unused]] unsigned int frame, FrameBuffer *buffer = isp_[Isp::Stats].getBuffers().at(bufferId); handleStreamBuffer(buffer, &isp_[Isp::Stats]); + /* Fill the Request metadata buffer with what the IPA has provided */ - requestQueue_.front()->metadata() = std::move(action.controls[0]); + Request *request = requestQueue_.front(); + request->metadata() = std::move(action.controls[0]); + + /* + * Also update the ScalerCrop in the metadata with what we actually + * used. But we must first rescale that from ISP (camera mode) pixels + * back into sensor native pixels. + * + * Sending this information on every frame may be helpful. + */ + if (updateScalerCrop_) { + updateScalerCrop_ = false; + scalerCrop_ = ispCrop_.scaledBy(sensorInfo_.analogCrop.size(), + sensorInfo_.outputSize); + scalerCrop_.translateBy(sensorInfo_.analogCrop.topLeft()); + } + request->metadata().set(controls::ScalerCrop, scalerCrop_); + state_ = State::IpaComplete; break; } @@ -1595,6 +1625,34 @@ void RPiCameraData::tryRunPipeline() /* Take the first request from the queue and action the IPA. */ Request *request = requestQueue_.front(); + if (request->controls().contains(controls::ScalerCrop)) { + Rectangle crop = request->controls().get(controls::ScalerCrop); + + if (crop.width && crop.height) { + /* First scale the crop from sensor native to camera mode pixels. */ + crop.translateBy(-sensorInfo_.analogCrop.topLeft()); + crop.scaleBy(sensorInfo_.outputSize, sensorInfo_.analogCrop.size()); + + /* + * The crop that we set must be: + * 1. At least as big as ispMinCropSize_, once that's been + * enlarged to the same aspect ratio. + * 2. With the same mid-point, if possible. + * 3. But it can't go outside the sensor area. + */ + Size minSize = ispMinCropSize_.expandedToAspectRatio(crop.size()); + Size size = crop.size().expandedTo(minSize); + crop = size.centeredTo(crop.center()).enclosedIn(Rectangle(sensorInfo_.outputSize)); + + if (crop != ispCrop_) { + isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop); + ispCrop_ = crop; + /* queueFrameAction will have to update its scalerCrop_ */ + updateScalerCrop_ = true; + } + } + } + /* * Process all the user controls by the IPA. Once this is complete, we * queue the ISP output buffer listed in the request to start the HW From patchwork Mon Oct 26 17:19: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: 10262 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 E897AC3B5F for ; Mon, 26 Oct 2020 17:19:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A7A3A62051; Mon, 26 Oct 2020 18:19:25 +0100 (CET) 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="MokD/Gv7"; dkim-atps=neutral Received: from mail-wm1-x329.google.com (mail-wm1-x329.google.com [IPv6:2a00:1450:4864:20::329]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C32CE62035 for ; Mon, 26 Oct 2020 18:19:23 +0100 (CET) Received: by mail-wm1-x329.google.com with SMTP id 13so12460634wmf.0 for ; Mon, 26 Oct 2020 10:19:23 -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=HuQ/45j10rn7cV+oW6EnXl0vLVq6yUYLvkNJnvlm9Zo=; b=MokD/Gv7W9KS+Ssvxa6VMAulCSJ8EGd40m9XAR0mc3kqLeCbMC9M9k8jDz/H3p0ZwS O/a1p4xYXc07UvhT7x9FYojQA/cDOwj8PlgsH3SVVQjn1H4W0JU8GuX3MJmJ3sz5Vn4x +FY8jEzDWjHcAdK5UvHgOdkUmGa5Mp7cWrEuqSsmGHuOfzR4N8Rp1G9lkWeXDT1ICYyM d7Rvylp5nYqkXUrxWGDvL54cLM5vn8znHN2lKObUgslivpCWM+RTzToCQ7cQXadsqjer +iRfzjoYjjPWHzP/58sW/7RQy7gI8GYtyOPxdLGS26uSc+fqpSPSGXVG/PGTohLY+3WL /uZw== 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=HuQ/45j10rn7cV+oW6EnXl0vLVq6yUYLvkNJnvlm9Zo=; b=GLfXEbNSFR3DlkiNSDV0Fp9MIMhcGNlgJYd/vQL78zFl6xGoSbgkgDAeJd7pyWO7pL c48hmTxEVo4q4ItzLsMPKMa/+JJSc/qbAhxpIhC4DfYtyLZK9fYtc5SYTDuM6Ya7EYmJ 9BaecjRY4laBeE8glqV0kf7QHgE6yekwc/59isgZu+87drvukgJGbsNo8wNjh+pp1Y+y SMY5MtcVHt/NTVSUmUwNiKUJy544Ys8g3GNaiQgv58AOWwlYgrNUK562oU4WE/g63DVT DKDctrD1mbAs7pOPxVD02FENsju85yhtOpT7L+DMWaO7nJgkS92cBcvGBx2aVMX/xQlx 1KgA== X-Gm-Message-State: AOAM530t5ula6FjAf82r0VHACzA+5Rn/kA+Lqx5L/1zVJcGBMQb0RBZ6 FJ1LDlmqQZhjPISyMS+u1Po4Pm7278CnmQ== X-Google-Smtp-Source: ABdhPJy71ns1HZNXXjQizd5ouXf9HM7R3BpqwMsDsIJJ7nwwqDokZzRehBuNy07nMVjhgnqqfsfshg== X-Received: by 2002:a1c:9d94:: with SMTP id g142mr12748774wme.66.1603732760803; Mon, 26 Oct 2020 10:19:20 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id o63sm20955295wmo.2.2020.10.26.10.19.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Oct 2020 10:19:20 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Mon, 26 Oct 2020 17:19:08 +0000 Message-Id: <20201026171908.21463-7-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201026171908.21463-1-david.plowman@raspberrypi.com> References: <20201026171908.21463-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 6/6] test: geometry: Add unit tests for new geometry helper functions X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart --- test/geometry.cpp | 258 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 254 insertions(+), 4 deletions(-) diff --git a/test/geometry.cpp b/test/geometry.cpp index 08e268c9..175221f9 100644 --- a/test/geometry.cpp +++ b/test/geometry.cpp @@ -17,16 +17,17 @@ using namespace libcamera; class GeometryTest : public Test { protected: - bool compare(const Size &lhs, const Size &rhs, - bool (*op)(const Size &lhs, const Size &rhs), + template + bool compare(const T &lhs, const T &rhs, + bool (*op)(const T &lhs, const T &rhs), const char *opName, bool expect) { bool result = op(lhs, rhs); if (result != expect) { - cout << "Size(" << lhs.width << ", " << lhs.height << ") " + cout << lhs.toString() << opName << " " - << "Size(" << rhs.width << ", " << rhs.height << ") " + << rhs.toString() << "test failed" << std::endl; return false; } @@ -36,6 +37,63 @@ protected: int run() { + /* + * Point tests + */ + + /* Equality */ + if (!compare(Point(50, 100), Point(50, 100), &operator==, "==", true)) + return TestFail; + + if (!compare(Point(-50, 100), Point(-50, 100), &operator==, "==", true)) + return TestFail; + + if (!compare(Point(50, -100), Point(50, -100), &operator==, "==", true)) + return TestFail; + + if (!compare(Point(-50, -100), Point(-50, -100), &operator==, "==", true)) + return TestFail; + + /* Inequality */ + if (!compare(Point(50, 100), Point(50, 100), &operator!=, "!=", false)) + return TestFail; + + if (!compare(Point(-50, 100), Point(-50, 100), &operator!=, "!=", false)) + return TestFail; + + if (!compare(Point(50, -100), Point(50, -100), &operator!=, "!=", false)) + return TestFail; + + if (!compare(Point(-50, -100), Point(-50, -100), &operator!=, "!=", false)) + return TestFail; + + if (!compare(Point(-50, 100), Point(50, 100), &operator!=, "!=", true)) + return TestFail; + + if (!compare(Point(50, -100), Point(50, 100), &operator!=, "!=", true)) + return TestFail; + + if (!compare(Point(-50, -100), Point(50, 100), &operator!=, "!=", true)) + return TestFail; + + /* Negation */ + if (Point(50, 100) != -Point(-50, -100) || + Point(50, 100) == -Point(50, -100) || + Point(50, 100) == -Point(-50, 100)) { + cout << "Point negation test failed" << endl; + return TestFail; + } + + /* Default constructor */ + if (Point() != Point(0, 0)) { + cout << "Default constructor test failed" << endl; + return TestFail; + } + + /* + * Size tests + */ + if (!Size().isNull() || !Size(0, 0).isNull()) { cout << "Null size incorrectly reported as not null" << endl; return TestFail; @@ -109,6 +167,76 @@ protected: return TestFail; } + /* Aspect ratio tests */ + if (Size(0, 0).boundedToAspectRatio(Size(4, 3)) != Size(0, 0) || + Size(1920, 1440).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) || + Size(1920, 1440).boundedToAspectRatio(Size(65536, 36864)) != Size(1920, 1080) || + Size(1440, 1920).boundedToAspectRatio(Size(9, 16)) != Size(1080, 1920) || + Size(1920, 1080).boundedToAspectRatio(Size(4, 3)) != Size(1440, 1080) || + Size(1920, 1080).boundedToAspectRatio(Size(65536, 49152)) != Size(1440, 1080) || + Size(1024, 1024).boundedToAspectRatio(Size(1, 1)) != Size(1024, 1024) || + Size(1920, 1080).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) || + Size(200, 100).boundedToAspectRatio(Size(16, 9)) != Size(177, 100) || + Size(300, 200).boundedToAspectRatio(Size(16, 9)) != Size(300, 168)) { + cout << "Size::boundedToAspectRatio test failed" << endl; + return TestFail; + } + + if (Size(0, 0).expandedToAspectRatio(Size(4, 3)) != Size(0, 0) || + Size(1920, 1440).expandedToAspectRatio(Size(16, 9)) != Size(2560, 1440) || + Size(1920, 1440).expandedToAspectRatio(Size(65536, 36864)) != Size(2560, 1440) || + Size(1440, 1920).expandedToAspectRatio(Size(9, 16)) != Size(1440, 2560) || + Size(1920, 1080).expandedToAspectRatio(Size(4, 3)) != Size(1920, 1440) || + + Size(1920, 1080).expandedToAspectRatio(Size(65536, 49152)) != Size(1920, 1440) || + Size(1024, 1024).expandedToAspectRatio(Size(1, 1)) != Size(1024, 1024) || + Size(1920, 1080).expandedToAspectRatio(Size(16, 9)) != Size(1920, 1080) || + Size(200, 100).expandedToAspectRatio(Size(16, 9)) != Size(200, 112) || + Size(300, 200).expandedToAspectRatio(Size(16, 9)) != Size(355, 200)) { + cout << "Size::expandedToAspectRatio test failed" << endl; + return TestFail; + } + + /* Size::centeredTo tests */ + if (Size(0, 0).centeredTo(Point(50, 100)) != Rectangle(50, 100, 0, 0) || + Size(0, 0).centeredTo(Point(-50, -100)) != Rectangle(-50, -100, 0, 0) || + Size(100, 200).centeredTo(Point(50, 100)) != Rectangle(0, 0, 100, 200) || + Size(100, 200).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 100, 200) || + Size(101, 201).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 101, 201) || + Size(101, 201).centeredTo(Point(-51, -101)) != Rectangle(-101, -201, 101, 201)) { + cout << "Size::centeredTo test failed" << endl; + return TestFail; + } + + /* Scale a size by a float */ + if (Size(1000, 2000) * 2.0 != Size(2000, 4000) || + Size(300, 100) * 0.5 != Size(150, 50) || + Size(1, 2) * 1.6 != Size(1, 3)) { + cout << "Size::operator* failed" << endl; + return TestFail; + } + + if (Size(1000, 2000) / 2.0 != Size(500, 1000) || + Size(300, 100) / 0.5 != Size(600, 200) || + Size(1000, 2000) / 3.0 != Size(333, 666)) { + cout << "Size::operator* failed" << endl; + return TestFail; + } + + s = Size(300, 100); + s *= 0.3333; + if (s != Size(99, 33)) { + cout << "Size::operator* test failed" << endl; + return TestFail; + } + + s = Size(300, 100); + s /= 3; + if (s != Size(100, 33)) { + cout << "Size::operator* test failed" << endl; + return TestFail; + } + /* Test Size equality and inequality. */ if (!compare(Size(100, 100), Size(100, 100), &operator==, "==", true)) return TestFail; @@ -182,6 +310,10 @@ protected: if (!compare(Size(200, 100), Size(100, 200), &operator>=, ">=", true)) return TestFail; + /* + * Rectangle tests + */ + /* Test Rectangle::isNull(). */ if (!Rectangle(0, 0, 0, 0).isNull() || !Rectangle(1, 1, 0, 0).isNull()) { @@ -196,6 +328,124 @@ protected: return TestFail; } + /* Rectangle::size, Rectangle::topLeft and Rectangle::center tests */ + if (Rectangle(-1, -2, 3, 4).size() != Size(3, 4) || + Rectangle(0, 0, 100000, 200000).size() != Size(100000, 200000)) { + cout << "Rectangle::size test failed" << endl; + return TestFail; + } + + if (Rectangle(1, 2, 3, 4).topLeft() != Point(1, 2) || + Rectangle(-1, -2, 3, 4).topLeft() != Point(-1, -2)) { + cout << "Rectangle::topLeft test failed" << endl; + return TestFail; + } + + if (Rectangle(0, 0, 300, 400).center() != Point(150, 200) || + Rectangle(-1000, -2000, 300, 400).center() != Point(-850, -1800) || + Rectangle(10, 20, 301, 401).center() != Point(160, 220) || + Rectangle(11, 21, 301, 401).center() != Point(161, 221) || + Rectangle(-1011, -2021, 301, 401).center() != Point(-861, -1821)) { + cout << "Rectangle::center test failed" << endl; + return TestFail; + } + + /* Rectangle::boundedTo (intersection function) */ + if (Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) != + Rectangle(0, 0, 1000, 2000) || + Rectangle(-500, -1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) != + Rectangle(0, 0, 500, 1000) || + Rectangle(500, 1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) != + Rectangle(500, 1000, 500, 1000) || + Rectangle(300, 400, 50, 100).boundedTo(Rectangle(0, 0, 1000, 2000)) != + Rectangle(300, 400, 50, 100) || + Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(300, 400, 50, 100)) != + Rectangle(300, 400, 50, 100) || + Rectangle(0, 0, 100, 100).boundedTo(Rectangle(50, 100, 100, 100)) != + Rectangle(50, 100, 50, 0) || + Rectangle(0, 0, 100, 100).boundedTo(Rectangle(100, 50, 100, 100)) != + Rectangle(100, 50, 0, 50) || + Rectangle(-10, -20, 10, 20).boundedTo(Rectangle(10, 20, 100, 100)) != + Rectangle(10, 20, 0, 0)) { + cout << "Rectangle::boundedTo test failed" << endl; + return TestFail; + } + + /* Rectangle::enclosedIn tests */ + if (Rectangle(10, 20, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != + Rectangle(10, 20, 300, 400) || + Rectangle(-100, -200, 3000, 4000).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != + Rectangle(-10, -20, 1300, 1400) || + Rectangle(-100, -200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != + Rectangle(-10, -20, 300, 400) || + Rectangle(5100, 6200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != + Rectangle(990, 980, 300, 400) || + Rectangle(100, -300, 150, 200).enclosedIn(Rectangle(50, 0, 200, 300)) != + Rectangle(100, 0, 150, 200) || + Rectangle(100, -300, 150, 1200).enclosedIn(Rectangle(50, 0, 200, 300)) != + Rectangle(100, 0, 150, 300) || + Rectangle(-300, 100, 200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) != + Rectangle(0, 100, 200, 150) || + Rectangle(-300, 100, 1200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) != + Rectangle(0, 100, 300, 150)) { + cout << "Rectangle::enclosedIn test failed" << endl; + return TestFail; + } + + /* Rectange::scaledBy tests */ + if (Rectangle(10, 20, 300, 400).scaledBy(Size(0, 0), Size(1, 1)) != + Rectangle(0, 0, 0, 0) || + Rectangle(10, -20, 300, 400).scaledBy(Size(32768, 65536), Size(32768, 32768)) != + Rectangle(10, -40, 300, 800) || + Rectangle(-30000, 10000, 20000, 20000).scaledBy(Size(7, 7), Size(7, 7)) != + Rectangle(-30000, 10000, 20000, 20000) || + Rectangle(-20, -30, 320, 240).scaledBy(Size(1280, 960), Size(640, 480)) != + Rectangle(-40, -60, 640, 480) || + Rectangle(1, 1, 2026, 1510).scaledBy(Size(4056, 3024), Size(2028, 1512)) != + Rectangle(2, 2, 4052, 3020)) { + cout << "Rectangle::scaledBy test failed" << endl; + return TestFail; + } + + /* Rectangle::translatedBy tests */ + if (Rectangle(10, -20, 300, 400).translatedBy(Point(-30, 40)) != + Rectangle(-20, 20, 300, 400) || + Rectangle(-10, 20, 400, 300).translatedBy(Point(50, -60)) != + Rectangle(40, -40, 400, 300)) { + cout << "Rectangle::translatedBy test failed" << endl; + return TestFail; + } + + /* Rectangle::scaleBy tests */ + Rectangle r(-20, -30, 320, 240); + r.scaleBy(Size(1280, 960), Size(640, 480)); + if (r != Rectangle(-40, -60, 640, 480)) { + cout << "Rectangle::scaleBy test failed" << endl; + return TestFail; + } + + r = Rectangle(1, 1, 2026, 1510); + r.scaleBy(Size(4056, 3024), Size(2028, 1512)); + if (r != Rectangle(2, 2, 4052, 3020)) { + cout << "Rectangle::scaleBy test failed" << endl; + return TestFail; + } + + /* Rectangle::translateBy tests */ + r = Rectangle(10, -20, 300, 400); + r.translateBy(Point(-30, 40)); + if (r != Rectangle(-20, 20, 300, 400)) { + cout << "Rectangle::translateBy test failed" << endl; + return TestFail; + } + + r = Rectangle(-10, 20, 400, 300); + r.translateBy(Point(50, -60)); + if (r != Rectangle(40, -40, 400, 300)) { + cout << "Rectangle::translateBy test failed" << endl; + return TestFail; + } + return TestPass; } };