Show a patch.

GET /api/1.1/patches/24750/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 24750,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/24750/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/24750/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20251023144841.403689-24-stefan.klug@ideasonboard.com>",
    "date": "2025-10-23T14:48:24",
    "name": "[v2,23/35] libcamera: rkisp1: Use vertex map to implement ScalerCrop",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "39c56db03627930627f51d694c3b76131127ab9f",
    "submitter": {
        "id": 184,
        "url": "https://patchwork.libcamera.org/api/1.1/people/184/?format=api",
        "name": "Stefan Klug",
        "email": "stefan.klug@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/24750/mbox/",
    "series": [
        {
            "id": 5520,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5520/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5520",
            "date": "2025-10-23T14:48:01",
            "name": "Full dewarper support on imx8mp",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/5520/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/24750/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/24750/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 82BADC3340\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 23 Oct 2025 14:49:57 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 080EE6083D;\n\tThu, 23 Oct 2025 16:49:57 +0200 (CEST)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A560E6082F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 23 Oct 2025 16:49:55 +0200 (CEST)",
            "from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:7328:357b:4ce1:72b6])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id B0182177F; \n\tThu, 23 Oct 2025 16:48:10 +0200 (CEST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"vZWi5pnd\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1761230890;\n\tbh=8ze/wVtYSCNyH2pXLXiMKgc7r4dl8SlTkahzl1pW36g=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=vZWi5pndber5GuXZapm/DJ7qOCHUti03yyWnUq4N1TnSocbAcic9z9/TrpOEBAEaV\n\tPwNVUb0lKtXfg722rQ6gpBcaHFyux+jrJM5v8wwJa7qCWJgS9I+w1I/AsbEGbs27dJ\n\tnjAz1QEazRV+kUYkv375Jwp1X0aDq4NYvvX4qltg=",
        "From": "Stefan Klug <stefan.klug@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Stefan Klug <stefan.klug@ideasonboard.com>",
        "Subject": "[PATCH v2 23/35] libcamera: rkisp1: Use vertex map to implement\n\tScalerCrop",
        "Date": "Thu, 23 Oct 2025 16:48:24 +0200",
        "Message-ID": "<20251023144841.403689-24-stefan.klug@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.48.1",
        "In-Reply-To": "<20251023144841.403689-1-stefan.klug@ideasonboard.com>",
        "References": "<20251023144841.403689-1-stefan.klug@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "The input crop implemented in the dw100 kernel driver is quite limited\nin that it doesn't allow arbitrary crop rectangles but only scale\nfactors quantized to the underlying fixed point representation and only\naspect ratio preserving crops.\n\nThe vertex map based implementation allows for pixel perfect crops. The\nonly downside is that ScalerCrop can no longer be set dynamically on\nolder kernels. Warn if the user tries to set ScalerCrop on such a setup.\n\nAs the vertex map is now set on start() it no longer needs to be updated\nfor frame==0.\n\nSigned-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\n---\n\nChanges in 0.9\n- Code cleanup\n- Update vertex map before start() to partially support old kernels\n\nChanges in 0.7:\n- Removed double call to applyVertexMap()\n---\n src/libcamera/pipeline/rkisp1/rkisp1.cpp | 115 ++++++++++++-----------\n 1 file changed, 59 insertions(+), 56 deletions(-)",
    "diff": "diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\nindex e22f05408931..0b31b8077c8d 100644\n--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n@@ -235,9 +235,6 @@ private:\n \tRkISP1SelfPath selfPath_;\n \n \tstd::unique_ptr<ConverterDW100> dewarper_;\n-\tRectangle scalerMaxCrop_;\n-\n-\tstd::optional<Rectangle> activeCrop_;\n \n \t/* Internal buffers used when dewarper is being used */\n \tstd::vector<std::unique_ptr<FrameBuffer>> mainPathBuffers_;\n@@ -969,13 +966,22 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n \t\t\t\t\treturn ret;\n \n \t\t\t\t/*\n-\t\t\t\t * Calculate the crop rectangle of the data\n-\t\t\t\t * flowing into the dewarper in sensor\n-\t\t\t\t * coordinates.\n+\t\t\t\t * Apply the actual sensor crop, for proper\n+\t\t\t\t * dewarp map calculation\n+\t\t\t\t */\n+\t\t\t\tRectangle sensorCrop = outputCrop.transformedBetween(\n+\t\t\t\t\tinputCrop, sensorInfo.analogCrop);\n+\t\t\t\tauto &vertexMap = dewarper_->vertexMap(cfg.stream());\n+\t\t\t\tvertexMap.setSensorCrop(sensorCrop);\n+\t\t\t\tdata->properties_.set(properties::ScalerCropMaximum, sensorCrop);\n+\n+\t\t\t\t/*\n+\t\t\t\t * Apply a default sensor crop that keeps the\n+\t\t\t\t * aspect ratio.\n \t\t\t\t */\n-\t\t\t\tscalerMaxCrop_ =\n-\t\t\t\t\toutputCrop.transformedBetween(inputCrop,\n-\t\t\t\t\t\t\t\t      sensorInfo.analogCrop);\n+\t\t\t\tSize crop = format.size.boundedToAspectRatio(cfg.size);\n+\t\t\t\tvertexMap.setScalerCrop(crop.centeredTo(\n+\t\t\t\t\tRectangle(format.size).center()));\n \t\t\t}\n \n \t\t\tret = mainPath_.configure(ispCfg, format);\n@@ -1186,6 +1192,18 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL\n \t\tactions += [&]() { stat_->streamOff(); };\n \n \t\tif (data->usesDewarper_) {\n+\t\t\t/*\n+\t\t\t * Apply the vertex map before start to partially\n+\t\t\t * support ScalerCrop on kernels with a dw100 driver\n+\t\t\t * that has no dynamic vertex map/requests support.\n+\t\t\t */\n+\t\t\tif (controls && controls->contains(controls::ScalerCrop.id())) {\n+\t\t\t\tconst auto &crop = controls->get(controls::ScalerCrop);\n+\t\t\t\tauto &vertexMap = dewarper_->vertexMap(&data->mainPathStream_);\n+\t\t\t\tvertexMap.setScalerCrop(*crop);\n+\t\t\t}\n+\t\t\tdewarper_->applyVertexMap(&data->mainPathStream_);\n+\n \t\t\tret = dewarper_->start();\n \t\t\tif (ret) {\n \t\t\t\tLOG(RkISP1, Error) << \"Failed to start dewarper\";\n@@ -1338,25 +1356,31 @@ int PipelineHandlerRkISP1::updateControls(RkISP1CameraData *data)\n \n \tif (data->usesDewarper_) {\n \t\tstd::pair<Rectangle, Rectangle> cropLimits;\n-\t\tif (dewarper_->isConfigured(&data->mainPathStream_))\n-\t\t\tcropLimits = dewarper_->inputCropBounds(&data->mainPathStream_);\n-\t\telse\n+\t\tif (dewarper_->isConfigured(&data->mainPathStream_)) {\n+\t\t\tauto &vertexMap = dewarper_->vertexMap(&data->mainPathStream_);\n+\t\t\tvertexMap.applyLimits();\n+\t\t\tcropLimits = vertexMap.scalerCropBounds();\n+\t\t\tcontrols[&controls::ScalerCrop] = ControlInfo(cropLimits.first,\n+\t\t\t\t\t\t\t\t      cropLimits.second,\n+\t\t\t\t\t\t\t\t      vertexMap.effectiveScalerCrop());\n+\t\t} else {\n+\t\t\t/* This happens before configure() has run. Maybe we need a better solution.*/\n+\t\t\t/*\n+\t\t\t* ScalerCrop is specified to be in Sensor coordinates.\n+\t\t\t* So we need to transform the limits to sensor coordinates.\n+\t\t\t* We can safely assume that the maximum crop limit contains the\n+\t\t\t* full fov of the dewarper.\n+\t\t\t*/\n \t\t\tcropLimits = dewarper_->inputCropBounds();\n+\t\t\tRectangle maxCrop = Rectangle(data->sensor_->resolution());\n+\t\t\tRectangle min = cropLimits.first.transformedBetween(cropLimits.second,\n+\t\t\t\t\t\t\t\t\t    maxCrop);\n+\n+\t\t\tcontrols[&controls::ScalerCrop] = ControlInfo(min,\n+\t\t\t\t\t\t\t\t      maxCrop,\n+\t\t\t\t\t\t\t\t      maxCrop);\n+\t\t}\n \n-\t\t/*\n-\t\t * ScalerCrop is specified to be in Sensor coordinates.\n-\t\t * So we need to transform the limits to sensor coordinates.\n-\t\t * We can safely assume that the maximum crop limit contains the\n-\t\t * full fov of the dewarper.\n-\t\t */\n-\t\tRectangle min = cropLimits.first.transformedBetween(cropLimits.second,\n-\t\t\t\t\t\t\t\t    scalerMaxCrop_);\n-\n-\t\tcontrols[&controls::ScalerCrop] = ControlInfo(min,\n-\t\t\t\t\t\t\t      scalerMaxCrop_,\n-\t\t\t\t\t\t\t      scalerMaxCrop_);\n-\t\tdata->properties_.set(properties::ScalerCropMaximum, scalerMaxCrop_);\n-\t\tactiveCrop_ = scalerMaxCrop_;\n \t}\n \n \t/* Add the IPA registered controls to list of camera controls. */\n@@ -1397,8 +1421,6 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)\n \t/* Initialize the camera properties. */\n \tdata->properties_ = data->sensor_->properties();\n \n-\tscalerMaxCrop_ = Rectangle(data->sensor_->resolution());\n-\n \tconst CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays();\n \tstd::unordered_map<uint32_t, DelayedControls::ControlParams> params = {\n \t\t{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },\n@@ -1632,36 +1654,17 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer)\n \t\tavailableDewarpRequests_.pop();\n \t}\n \n+\tauto &vertexMap = dewarper_->vertexMap(&data->mainPathStream_);\n+\n \t/* Handle scaler crop control. */\n \tconst auto &crop = request->controls().get(controls::ScalerCrop);\n \tif (crop) {\n-\t\tRectangle rect = crop.value();\n-\n-\t\t/*\n-\t\t * ScalerCrop is specified to be in Sensor coordinates.\n-\t\t * So we need to transform it into dewarper coordinates.\n-\t\t * We can safely assume that the maximum crop limit contains the\n-\t\t * full fov of the dewarper.\n-\t\t */\n-\t\tstd::pair<Rectangle, Rectangle> cropLimits =\n-\t\t\tdewarper_->inputCropBounds(&data->mainPathStream_);\n-\n-\t\trect = rect.transformedBetween(scalerMaxCrop_, cropLimits.second);\n-\t\tint ret = dewarper_->setInputCrop(&data->mainPathStream_,\n-\t\t\t\t\t\t  &rect);\n-\t\trect = rect.transformedBetween(cropLimits.second, scalerMaxCrop_);\n-\t\tif (!ret && rect != crop.value()) {\n-\t\t\t/*\n-\t\t\t * If the rectangle is changed by setInputCrop on the\n-\t\t\t * dewarper, log a debug message and cache the actual\n-\t\t\t * applied rectangle for metadata reporting.\n-\t\t\t */\n-\t\t\tLOG(RkISP1, Debug)\n-\t\t\t\t<< \"Applied rectangle \" << rect.toString()\n-\t\t\t\t<< \" differs from requested \" << crop.value().toString();\n-\t\t}\n-\n-\t\tactiveCrop_ = rect;\n+\t\tif (!dewarper_->supportsRequests())\n+\t\t\tLOG(RkISP1, Error)\n+\t\t\t\t<< \"Dynamically setting ScalerCrop requires a \"\n+\t\t\t\t   \"dw100 driver with requests support\";\n+\t\tvertexMap.setScalerCrop(*crop);\n+\t\tdewarper_->applyVertexMap(&data->mainPathStream_, dewarpRequest);\n \t}\n \n \t/*\n@@ -1695,7 +1698,7 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer)\n \t\t}\n \t}\n \n-\trequest->metadata().set(controls::ScalerCrop, activeCrop_.value());\n+\trequest->metadata().set(controls::ScalerCrop, vertexMap.effectiveScalerCrop());\n }\n \n void PipelineHandlerRkISP1::dewarpRequestReady(V4L2Request *request)\n",
    "prefixes": [
        "v2",
        "23/35"
    ]
}