From patchwork Thu Oct 23 14:48:24 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 24750 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 82BADC3340 for ; Thu, 23 Oct 2025 14:49:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 080EE6083D; Thu, 23 Oct 2025 16:49:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vZWi5pnd"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A560E6082F for ; Thu, 23 Oct 2025 16:49:55 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:7328:357b:4ce1:72b6]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id B0182177F; Thu, 23 Oct 2025 16:48:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761230890; bh=8ze/wVtYSCNyH2pXLXiMKgc7r4dl8SlTkahzl1pW36g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vZWi5pndber5GuXZapm/DJ7qOCHUti03yyWnUq4N1TnSocbAcic9z9/TrpOEBAEaV PwNVUb0lKtXfg722rQ6gpBcaHFyux+jrJM5v8wwJa7qCWJgS9I+w1I/AsbEGbs27dJ njAz1QEazRV+kUYkv375Jwp1X0aDq4NYvvX4qltg= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v2 23/35] libcamera: rkisp1: Use vertex map to implement ScalerCrop 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 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 input crop implemented in the dw100 kernel driver is quite limited in that it doesn't allow arbitrary crop rectangles but only scale factors quantized to the underlying fixed point representation and only aspect ratio preserving crops. The vertex map based implementation allows for pixel perfect crops. The only downside is that ScalerCrop can no longer be set dynamically on older kernels. Warn if the user tries to set ScalerCrop on such a setup. As the vertex map is now set on start() it no longer needs to be updated for frame==0. Signed-off-by: Stefan Klug --- Changes in 0.9 - Code cleanup - Update vertex map before start() to partially support old kernels Changes in 0.7: - Removed double call to applyVertexMap() --- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 115 ++++++++++++----------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index e22f05408931..0b31b8077c8d 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -235,9 +235,6 @@ private: RkISP1SelfPath selfPath_; std::unique_ptr dewarper_; - Rectangle scalerMaxCrop_; - - std::optional activeCrop_; /* Internal buffers used when dewarper is being used */ std::vector> mainPathBuffers_; @@ -969,13 +966,22 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) return ret; /* - * Calculate the crop rectangle of the data - * flowing into the dewarper in sensor - * coordinates. + * Apply the actual sensor crop, for proper + * dewarp map calculation + */ + Rectangle sensorCrop = outputCrop.transformedBetween( + inputCrop, sensorInfo.analogCrop); + auto &vertexMap = dewarper_->vertexMap(cfg.stream()); + vertexMap.setSensorCrop(sensorCrop); + data->properties_.set(properties::ScalerCropMaximum, sensorCrop); + + /* + * Apply a default sensor crop that keeps the + * aspect ratio. */ - scalerMaxCrop_ = - outputCrop.transformedBetween(inputCrop, - sensorInfo.analogCrop); + Size crop = format.size.boundedToAspectRatio(cfg.size); + vertexMap.setScalerCrop(crop.centeredTo( + Rectangle(format.size).center())); } ret = mainPath_.configure(ispCfg, format); @@ -1186,6 +1192,18 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL actions += [&]() { stat_->streamOff(); }; if (data->usesDewarper_) { + /* + * Apply the vertex map before start to partially + * support ScalerCrop on kernels with a dw100 driver + * that has no dynamic vertex map/requests support. + */ + if (controls && controls->contains(controls::ScalerCrop.id())) { + const auto &crop = controls->get(controls::ScalerCrop); + auto &vertexMap = dewarper_->vertexMap(&data->mainPathStream_); + vertexMap.setScalerCrop(*crop); + } + dewarper_->applyVertexMap(&data->mainPathStream_); + ret = dewarper_->start(); if (ret) { LOG(RkISP1, Error) << "Failed to start dewarper"; @@ -1338,25 +1356,31 @@ int PipelineHandlerRkISP1::updateControls(RkISP1CameraData *data) if (data->usesDewarper_) { std::pair cropLimits; - if (dewarper_->isConfigured(&data->mainPathStream_)) - cropLimits = dewarper_->inputCropBounds(&data->mainPathStream_); - else + if (dewarper_->isConfigured(&data->mainPathStream_)) { + auto &vertexMap = dewarper_->vertexMap(&data->mainPathStream_); + vertexMap.applyLimits(); + cropLimits = vertexMap.scalerCropBounds(); + controls[&controls::ScalerCrop] = ControlInfo(cropLimits.first, + cropLimits.second, + vertexMap.effectiveScalerCrop()); + } else { + /* This happens before configure() has run. Maybe we need a better solution.*/ + /* + * ScalerCrop is specified to be in Sensor coordinates. + * So we need to transform the limits to sensor coordinates. + * We can safely assume that the maximum crop limit contains the + * full fov of the dewarper. + */ cropLimits = dewarper_->inputCropBounds(); + Rectangle maxCrop = Rectangle(data->sensor_->resolution()); + Rectangle min = cropLimits.first.transformedBetween(cropLimits.second, + maxCrop); + + controls[&controls::ScalerCrop] = ControlInfo(min, + maxCrop, + maxCrop); + } - /* - * ScalerCrop is specified to be in Sensor coordinates. - * So we need to transform the limits to sensor coordinates. - * We can safely assume that the maximum crop limit contains the - * full fov of the dewarper. - */ - Rectangle min = cropLimits.first.transformedBetween(cropLimits.second, - scalerMaxCrop_); - - controls[&controls::ScalerCrop] = ControlInfo(min, - scalerMaxCrop_, - scalerMaxCrop_); - data->properties_.set(properties::ScalerCropMaximum, scalerMaxCrop_); - activeCrop_ = scalerMaxCrop_; } /* Add the IPA registered controls to list of camera controls. */ @@ -1397,8 +1421,6 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); - scalerMaxCrop_ = Rectangle(data->sensor_->resolution()); - const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays(); std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, @@ -1632,36 +1654,17 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) availableDewarpRequests_.pop(); } + auto &vertexMap = dewarper_->vertexMap(&data->mainPathStream_); + /* Handle scaler crop control. */ const auto &crop = request->controls().get(controls::ScalerCrop); if (crop) { - Rectangle rect = crop.value(); - - /* - * ScalerCrop is specified to be in Sensor coordinates. - * So we need to transform it into dewarper coordinates. - * We can safely assume that the maximum crop limit contains the - * full fov of the dewarper. - */ - std::pair cropLimits = - dewarper_->inputCropBounds(&data->mainPathStream_); - - rect = rect.transformedBetween(scalerMaxCrop_, cropLimits.second); - int ret = dewarper_->setInputCrop(&data->mainPathStream_, - &rect); - rect = rect.transformedBetween(cropLimits.second, scalerMaxCrop_); - if (!ret && rect != crop.value()) { - /* - * If the rectangle is changed by setInputCrop on the - * dewarper, log a debug message and cache the actual - * applied rectangle for metadata reporting. - */ - LOG(RkISP1, Debug) - << "Applied rectangle " << rect.toString() - << " differs from requested " << crop.value().toString(); - } - - activeCrop_ = rect; + if (!dewarper_->supportsRequests()) + LOG(RkISP1, Error) + << "Dynamically setting ScalerCrop requires a " + "dw100 driver with requests support"; + vertexMap.setScalerCrop(*crop); + dewarper_->applyVertexMap(&data->mainPathStream_, dewarpRequest); } /* @@ -1695,7 +1698,7 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) } } - request->metadata().set(controls::ScalerCrop, activeCrop_.value()); + request->metadata().set(controls::ScalerCrop, vertexMap.effectiveScalerCrop()); } void PipelineHandlerRkISP1::dewarpRequestReady(V4L2Request *request)