[{"id":35455,"web_url":"https://patchwork.libcamera.org/comment/35455/","msgid":"<0b407f0c-da46-4260-bb20-f6aa210aa264@ideasonboard.com>","date":"2025-08-15T16:14:06","subject":"Re: [PATCH v3 09/19] libipa: exposure_mode_helper: Calculate\n\tquantization gain in splitExposure()","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/people/156/","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"content":"Hi Stefan - thanks - I think this looks ok now\n\nOn 15/08/2025 11:29, Stefan Klug wrote:\n> Calculate the error introduced by quantization as \"quantization gain\"\n> and return it separately from splitExposure(). It is not included in the\n> digital gain, to not silently ignore the limits imposed by the AGC\n> configuration.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\nReviewed-by: Daniel Scally <dan.scally@ideasonboard.com>\n> \n> ---\n> \n> Changes in v3:\n> - Fixed mali-c55 build\n> - Fixed return of splitExposure in case of gain & exposure time fixed\n> - Calculate quantizationGain for exposure and gain separately and do not feed it into the next stage\n> ---\n>   src/ipa/ipu3/algorithms/agc.cpp         |  4 +-\n>   src/ipa/libipa/agc_mean_luminance.cpp   |  7 ++--\n>   src/ipa/libipa/agc_mean_luminance.h     |  2 +-\n>   src/ipa/libipa/exposure_mode_helper.cpp | 51 +++++++++++++++++--------\n>   src/ipa/libipa/exposure_mode_helper.h   |  2 +-\n>   src/ipa/mali-c55/algorithms/agc.cpp     |  4 +-\n>   src/ipa/rkisp1/algorithms/agc.cpp       |  9 +++--\n>   7 files changed, 51 insertions(+), 28 deletions(-)\n> \n> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n> index 39d0aebb0838..da045640d569 100644\n> --- a/src/ipa/ipu3/algorithms/agc.cpp\n> +++ b/src/ipa/ipu3/algorithms/agc.cpp\n> @@ -222,8 +222,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>   \tutils::Duration effectiveExposureValue = exposureTime * analogueGain;\n>   \n>   \tutils::Duration newExposureTime;\n> -\tdouble aGain, dGain;\n> -\tstd::tie(newExposureTime, aGain, dGain) =\n> +\tdouble aGain, qGain, dGain;\n> +\tstd::tie(newExposureTime, aGain, qGain, dGain) =\n>   \t\tcalculateNewEv(context.activeState.agc.constraintMode,\n>   \t\t\t       context.activeState.agc.exposureMode, hist,\n>   \t\t\t       effectiveExposureValue);\n> diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp\n> index 1cbb3a0f9818..6c7cf0038b16 100644\n> --- a/src/ipa/libipa/agc_mean_luminance.cpp\n> +++ b/src/ipa/libipa/agc_mean_luminance.cpp\n> @@ -566,11 +566,12 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n>    *\n>    * Calculate a new exposure value to try to obtain the target. The calculated\n>    * exposure value is filtered to prevent rapid changes from frame to frame, and\n> - * divided into exposure time, analogue and digital gain.\n> + * divided into exposure time, analogue, quantization and digital gain.\n>    *\n> - * \\return Tuple of exposure time, analogue gain, and digital gain\n> + * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n> + * gain\n>    */\n> -std::tuple<utils::Duration, double, double>\n> +std::tuple<utils::Duration, double, double, double>\n>   AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,\n>   \t\t\t\t uint32_t exposureModeIndex,\n>   \t\t\t\t const Histogram &yHist,\n> diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h\n> index 49985481ac51..fbb526f6ae8e 100644\n> --- a/src/ipa/libipa/agc_mean_luminance.h\n> +++ b/src/ipa/libipa/agc_mean_luminance.h\n> @@ -68,7 +68,7 @@ public:\n>   \t\treturn controls_;\n>   \t}\n>   \n> -\tstd::tuple<utils::Duration, double, double>\n> +\tstd::tuple<utils::Duration, double, double, double>\n>   \tcalculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex,\n>   \t\t       const Histogram &yHist, utils::Duration effectiveExposureValue);\n>   \n> diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp\n> index 21efc4356afa..6c10aa757866 100644\n> --- a/src/ipa/libipa/exposure_mode_helper.cpp\n> +++ b/src/ipa/libipa/exposure_mode_helper.cpp\n> @@ -178,14 +178,24 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n>    * required exposure, the helper falls-back to simply maximising the exposure\n>    * time first, followed by analogue gain, followed by digital gain.\n>    *\n> - * \\return Tuple of exposure time, analogue gain, and digital gain\n> + * During the calculations the gain missed due to quantization is recorded and\n> + * returned as quantization gain. The quantization gain is not included in the\n> + * digital gain. So to exactly apply the given exposure, both quantization gain\n> + * and digital gain must be applied.\n> + *\n> + * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n> + * gain\n>    */\n> -std::tuple<utils::Duration, double, double>\n> +std::tuple<utils::Duration, double, double, double>\n>   ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>   {\n>   \tASSERT(maxExposureTime_);\n>   \tASSERT(maxGain_);\n>   \n> +\tutils::Duration exposureTime;\n> +\tdouble gain;\n> +\tdouble quantGain;\n> +\tdouble quantGain2;\n>   \tbool gainFixed = minGain_ == maxGain_;\n>   \tbool exposureTimeFixed = minExposureTime_ == maxExposureTime_;\n>   \n> @@ -193,16 +203,21 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>   \t * There's no point entering the loop if we cannot change either gain\n>   \t * nor exposure time anyway.\n>   \t */\n> -\tif (exposureTimeFixed && gainFixed)\n> -\t\treturn { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) };\n> +\tif (exposureTimeFixed && gainFixed) {\n> +\t\texposureTime = clampExposureTime(minExposureTime_, &quantGain);\n> +\t\tgain = clampGain(minGain_, &quantGain2);\n> +\t\tquantGain *= quantGain2;\n> +\n> +\t\treturn { exposureTime, gain, quantGain,\n> +\t\t\t exposure / (exposureTime * gain * quantGain) };\n> +\t}\n>   \n> -\tutils::Duration exposureTime;\n>   \tdouble stageGain = clampGain(1.0);\n>   \tdouble lastStageGain = stageGain;\n> -\tdouble gain;\n>   \n>   \tfor (unsigned int stage = 0; stage < gains_.size(); stage++) {\n> -\t\tutils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]);\n> +\t\tutils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage],\n> +\t\t\t\t\t\t\t\t      &quantGain);\n>   \t\tstageGain = clampGain(gains_[stage]);\n>   \n>   \t\t/*\n> @@ -215,18 +230,22 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>   \n>   \t\t/* Clamp the gain to lastStageGain and regulate exposureTime. */\n>   \t\tif (stageExposureTime * lastStageGain >= exposure) {\n> -\t\t\texposureTime = clampExposureTime(exposure / lastStageGain);\n> -\t\t\tgain = clampGain(exposure / exposureTime);\n> +\t\t\texposureTime = clampExposureTime(exposure / lastStageGain, &quantGain);\n> +\t\t\tgain = clampGain(exposure / exposureTime, &quantGain2);\n> +\t\t\tquantGain *= quantGain2;\n>   \n> -\t\t\treturn { exposureTime, gain, exposure / (exposureTime * gain) };\n> +\t\t\treturn { exposureTime, gain, quantGain,\n> +\t\t\t\t exposure / (exposureTime * gain * quantGain) };\n>   \t\t}\n>   \n>   \t\t/* Clamp the exposureTime to stageExposureTime and regulate gain. */\n>   \t\tif (stageExposureTime * stageGain >= exposure) {\n>   \t\t\texposureTime = stageExposureTime;\n> -\t\t\tgain = clampGain(exposure / exposureTime);\n> +\t\t\tgain = clampGain(exposure / exposureTime, &quantGain2);\n> +\t\t\tquantGain *= quantGain2;\n>   \n> -\t\t\treturn { exposureTime, gain, exposure / (exposureTime * gain) };\n> +\t\t\treturn { exposureTime, gain, quantGain,\n> +\t\t\t\t exposure / (exposureTime * gain * quantGain) };\n>   \t\t}\n>   \n>   \t\tlastStageGain = stageGain;\n> @@ -239,10 +258,12 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>   \t * stages to use then the default stageGain of 1.0 is used so that\n>   \t * exposure time is maxed before gain is touched at all.\n>   \t */\n> -\texposureTime = clampExposureTime(exposure / stageGain);\n> -\tgain = clampGain(exposure / exposureTime);\n> +\texposureTime = clampExposureTime(exposure / stageGain, &quantGain);\n> +\tgain = clampGain(exposure / exposureTime, &quantGain2);\n> +\tquantGain *= quantGain2;\n>   \n> -\treturn { exposureTime, gain, exposure / (exposureTime * gain) };\n> +\treturn { exposureTime, gain, quantGain,\n> +\t\t exposure / (exposureTime * gain * quantGain) };\n>   }\n>   \n>   /**\n> diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h\n> index ac7e8da95c6c..968192ddc5af 100644\n> --- a/src/ipa/libipa/exposure_mode_helper.h\n> +++ b/src/ipa/libipa/exposure_mode_helper.h\n> @@ -30,7 +30,7 @@ public:\n>   \tvoid setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,\n>   \t\t       double minGain, double maxGain);\n>   \n> -\tstd::tuple<utils::Duration, double, double>\n> +\tstd::tuple<utils::Duration, double, double, double>\n>   \tsplitExposure(utils::Duration exposure) const;\n>   \n>   \tutils::Duration minExposureTime() const { return minExposureTime_; }\n> diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\n> index 15963994b2d6..88f8664c9823 100644\n> --- a/src/ipa/mali-c55/algorithms/agc.cpp\n> +++ b/src/ipa/mali-c55/algorithms/agc.cpp\n> @@ -381,8 +381,8 @@ void Agc::process(IPAContext &context,\n>   \tutils::Duration effectiveExposureValue = currentShutter * totalGain;\n>   \n>   \tutils::Duration shutterTime;\n> -\tdouble aGain, dGain;\n> -\tstd::tie(shutterTime, aGain, dGain) =\n> +\tdouble aGain, qGain, dGain;\n> +\tstd::tie(shutterTime, aGain, qGain, dGain) =\n>   \t\tcalculateNewEv(activeState.agc.constraintMode,\n>   \t\t\t       activeState.agc.exposureMode, statistics_.yHist,\n>   \t\t\t       effectiveExposureValue);\n> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\n> index 35440b67e999..0a29326841fb 100644\n> --- a/src/ipa/rkisp1/algorithms/agc.cpp\n> +++ b/src/ipa/rkisp1/algorithms/agc.cpp\n> @@ -567,15 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>   \tsetExposureCompensation(pow(2.0, frameContext.agc.exposureValue));\n>   \n>   \tutils::Duration newExposureTime;\n> -\tdouble aGain, dGain;\n> -\tstd::tie(newExposureTime, aGain, dGain) =\n> +\tdouble aGain, qGain, dGain;\n> +\tstd::tie(newExposureTime, aGain, qGain, dGain) =\n>   \t\tcalculateNewEv(frameContext.agc.constraintMode,\n>   \t\t\t       frameContext.agc.exposureMode,\n>   \t\t\t       hist, effectiveExposureValue);\n>   \n>   \tLOG(RkISP1Agc, Debug)\n> -\t\t<< \"Divided up exposure time, analogue gain and digital gain are \"\n> -\t\t<< newExposureTime << \", \" << aGain << \" and \" << dGain;\n> +\t\t<< \"Divided up exposure time, analogue gain, quantization gain\"\n> +\t\t<< \" and digital gain are \" << newExposureTime << \", \" << aGain\n> +\t\t<< \", \" << qGain << \" and \" << dGain;\n>   \n>   \tIPAActiveState &activeState = context.activeState;\n>   \t/* Update the estimated exposure and gain. */","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 D0DAABDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 15 Aug 2025 16:14:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 90CE869257;\n\tFri, 15 Aug 2025 18:14:11 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C4EA461443\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 15 Aug 2025 18:14:09 +0200 (CEST)","from [192.168.0.43]\n\t(cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A000856D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 15 Aug 2025 18:13:14 +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=\"tkLXkY0p\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1755274394;\n\tbh=kD/WGe5XPhtbovneDygAiiVdTEYyIOe+HM2QZSupEyk=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=tkLXkY0pF8StyBN4Tkn2GpjQgHQacpRq+hfoPYDt3H7flYpRCr8/+K8DvOyWk3fi/\n\t6DbJ9RU9QykttrPN1b2mPMIDBzS3scw5Xurd0lGAqq1Nll6CaPHitaNhqJtWY4PbmC\n\t1NAuvUsGsC4oPS4wdMkdRH9a8YDaGv/xUThLgOBY=","Message-ID":"<0b407f0c-da46-4260-bb20-f6aa210aa264@ideasonboard.com>","Date":"Fri, 15 Aug 2025 17:14:06 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 09/19] libipa: exposure_mode_helper: Calculate\n\tquantization gain in splitExposure()","To":"libcamera-devel@lists.libcamera.org","References":"<20250815102945.1602071-1-stefan.klug@ideasonboard.com>\n\t<20250815102945.1602071-10-stefan.klug@ideasonboard.com>","Content-Language":"en-US","From":"Dan Scally <dan.scally@ideasonboard.com>","In-Reply-To":"<20250815102945.1602071-10-stefan.klug@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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>"}},{"id":35706,"web_url":"https://patchwork.libcamera.org/comment/35706/","msgid":"<175706897713.1787083.16844193513192419837@neptunite.rasen.tech>","date":"2025-09-05T10:42:57","subject":"Re: [PATCH v3 09/19] libipa: exposure_mode_helper: Calculate\n\tquantization gain in splitExposure()","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Quoting Stefan Klug (2025-08-15 19:29:29)\n> Calculate the error introduced by quantization as \"quantization gain\"\n> and return it separately from splitExposure(). It is not included in the\n> digital gain, to not silently ignore the limits imposed by the AGC\n> configuration.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\nLooks good to me.\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> \n> ---\n> \n> Changes in v3:\n> - Fixed mali-c55 build\n> - Fixed return of splitExposure in case of gain & exposure time fixed\n> - Calculate quantizationGain for exposure and gain separately and do not feed it into the next stage\n> ---\n>  src/ipa/ipu3/algorithms/agc.cpp         |  4 +-\n>  src/ipa/libipa/agc_mean_luminance.cpp   |  7 ++--\n>  src/ipa/libipa/agc_mean_luminance.h     |  2 +-\n>  src/ipa/libipa/exposure_mode_helper.cpp | 51 +++++++++++++++++--------\n>  src/ipa/libipa/exposure_mode_helper.h   |  2 +-\n>  src/ipa/mali-c55/algorithms/agc.cpp     |  4 +-\n>  src/ipa/rkisp1/algorithms/agc.cpp       |  9 +++--\n>  7 files changed, 51 insertions(+), 28 deletions(-)\n> \n> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n> index 39d0aebb0838..da045640d569 100644\n> --- a/src/ipa/ipu3/algorithms/agc.cpp\n> +++ b/src/ipa/ipu3/algorithms/agc.cpp\n> @@ -222,8 +222,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>         utils::Duration effectiveExposureValue = exposureTime * analogueGain;\n>  \n>         utils::Duration newExposureTime;\n> -       double aGain, dGain;\n> -       std::tie(newExposureTime, aGain, dGain) =\n> +       double aGain, qGain, dGain;\n> +       std::tie(newExposureTime, aGain, qGain, dGain) =\n>                 calculateNewEv(context.activeState.agc.constraintMode,\n>                                context.activeState.agc.exposureMode, hist,\n>                                effectiveExposureValue);\n> diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp\n> index 1cbb3a0f9818..6c7cf0038b16 100644\n> --- a/src/ipa/libipa/agc_mean_luminance.cpp\n> +++ b/src/ipa/libipa/agc_mean_luminance.cpp\n> @@ -566,11 +566,12 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n>   *\n>   * Calculate a new exposure value to try to obtain the target. The calculated\n>   * exposure value is filtered to prevent rapid changes from frame to frame, and\n> - * divided into exposure time, analogue and digital gain.\n> + * divided into exposure time, analogue, quantization and digital gain.\n>   *\n> - * \\return Tuple of exposure time, analogue gain, and digital gain\n> + * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n> + * gain\n>   */\n> -std::tuple<utils::Duration, double, double>\n> +std::tuple<utils::Duration, double, double, double>\n>  AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,\n>                                  uint32_t exposureModeIndex,\n>                                  const Histogram &yHist,\n> diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h\n> index 49985481ac51..fbb526f6ae8e 100644\n> --- a/src/ipa/libipa/agc_mean_luminance.h\n> +++ b/src/ipa/libipa/agc_mean_luminance.h\n> @@ -68,7 +68,7 @@ public:\n>                 return controls_;\n>         }\n>  \n> -       std::tuple<utils::Duration, double, double>\n> +       std::tuple<utils::Duration, double, double, double>\n>         calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex,\n>                        const Histogram &yHist, utils::Duration effectiveExposureValue);\n>  \n> diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp\n> index 21efc4356afa..6c10aa757866 100644\n> --- a/src/ipa/libipa/exposure_mode_helper.cpp\n> +++ b/src/ipa/libipa/exposure_mode_helper.cpp\n> @@ -178,14 +178,24 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n>   * required exposure, the helper falls-back to simply maximising the exposure\n>   * time first, followed by analogue gain, followed by digital gain.\n>   *\n> - * \\return Tuple of exposure time, analogue gain, and digital gain\n> + * During the calculations the gain missed due to quantization is recorded and\n> + * returned as quantization gain. The quantization gain is not included in the\n> + * digital gain. So to exactly apply the given exposure, both quantization gain\n> + * and digital gain must be applied.\n> + *\n> + * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n> + * gain\n>   */\n> -std::tuple<utils::Duration, double, double>\n> +std::tuple<utils::Duration, double, double, double>\n>  ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>  {\n>         ASSERT(maxExposureTime_);\n>         ASSERT(maxGain_);\n>  \n> +       utils::Duration exposureTime;\n> +       double gain;\n> +       double quantGain;\n> +       double quantGain2;\n>         bool gainFixed = minGain_ == maxGain_;\n>         bool exposureTimeFixed = minExposureTime_ == maxExposureTime_;\n>  \n> @@ -193,16 +203,21 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>          * There's no point entering the loop if we cannot change either gain\n>          * nor exposure time anyway.\n>          */\n> -       if (exposureTimeFixed && gainFixed)\n> -               return { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) };\n> +       if (exposureTimeFixed && gainFixed) {\n> +               exposureTime = clampExposureTime(minExposureTime_, &quantGain);\n> +               gain = clampGain(minGain_, &quantGain2);\n> +               quantGain *= quantGain2;\n> +\n> +               return { exposureTime, gain, quantGain,\n> +                        exposure / (exposureTime * gain * quantGain) };\n> +       }\n>  \n> -       utils::Duration exposureTime;\n>         double stageGain = clampGain(1.0);\n>         double lastStageGain = stageGain;\n> -       double gain;\n>  \n>         for (unsigned int stage = 0; stage < gains_.size(); stage++) {\n> -               utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]);\n> +               utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage],\n> +                                                                     &quantGain);\n>                 stageGain = clampGain(gains_[stage]);\n>  \n>                 /*\n> @@ -215,18 +230,22 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>  \n>                 /* Clamp the gain to lastStageGain and regulate exposureTime. */\n>                 if (stageExposureTime * lastStageGain >= exposure) {\n> -                       exposureTime = clampExposureTime(exposure / lastStageGain);\n> -                       gain = clampGain(exposure / exposureTime);\n> +                       exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain);\n> +                       gain = clampGain(exposure / exposureTime, &quantGain2);\n> +                       quantGain *= quantGain2;\n>  \n> -                       return { exposureTime, gain, exposure / (exposureTime * gain) };\n> +                       return { exposureTime, gain, quantGain,\n> +                                exposure / (exposureTime * gain * quantGain) };\n>                 }\n>  \n>                 /* Clamp the exposureTime to stageExposureTime and regulate gain. */\n>                 if (stageExposureTime * stageGain >= exposure) {\n>                         exposureTime = stageExposureTime;\n> -                       gain = clampGain(exposure / exposureTime);\n> +                       gain = clampGain(exposure / exposureTime, &quantGain2);\n> +                       quantGain *= quantGain2;\n>  \n> -                       return { exposureTime, gain, exposure / (exposureTime * gain) };\n> +                       return { exposureTime, gain, quantGain,\n> +                                exposure / (exposureTime * gain * quantGain) };\n>                 }\n>  \n>                 lastStageGain = stageGain;\n> @@ -239,10 +258,12 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>          * stages to use then the default stageGain of 1.0 is used so that\n>          * exposure time is maxed before gain is touched at all.\n>          */\n> -       exposureTime = clampExposureTime(exposure / stageGain);\n> -       gain = clampGain(exposure / exposureTime);\n> +       exposureTime = clampExposureTime(exposure / stageGain, &quantGain);\n> +       gain = clampGain(exposure / exposureTime, &quantGain2);\n> +       quantGain *= quantGain2;\n>  \n> -       return { exposureTime, gain, exposure / (exposureTime * gain) };\n> +       return { exposureTime, gain, quantGain,\n> +                exposure / (exposureTime * gain * quantGain) };\n>  }\n>  \n>  /**\n> diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h\n> index ac7e8da95c6c..968192ddc5af 100644\n> --- a/src/ipa/libipa/exposure_mode_helper.h\n> +++ b/src/ipa/libipa/exposure_mode_helper.h\n> @@ -30,7 +30,7 @@ public:\n>         void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,\n>                        double minGain, double maxGain);\n>  \n> -       std::tuple<utils::Duration, double, double>\n> +       std::tuple<utils::Duration, double, double, double>\n>         splitExposure(utils::Duration exposure) const;\n>  \n>         utils::Duration minExposureTime() const { return minExposureTime_; }\n> diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\n> index 15963994b2d6..88f8664c9823 100644\n> --- a/src/ipa/mali-c55/algorithms/agc.cpp\n> +++ b/src/ipa/mali-c55/algorithms/agc.cpp\n> @@ -381,8 +381,8 @@ void Agc::process(IPAContext &context,\n>         utils::Duration effectiveExposureValue = currentShutter * totalGain;\n>  \n>         utils::Duration shutterTime;\n> -       double aGain, dGain;\n> -       std::tie(shutterTime, aGain, dGain) =\n> +       double aGain, qGain, dGain;\n> +       std::tie(shutterTime, aGain, qGain, dGain) =\n>                 calculateNewEv(activeState.agc.constraintMode,\n>                                activeState.agc.exposureMode, statistics_.yHist,\n>                                effectiveExposureValue);\n> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\n> index 35440b67e999..0a29326841fb 100644\n> --- a/src/ipa/rkisp1/algorithms/agc.cpp\n> +++ b/src/ipa/rkisp1/algorithms/agc.cpp\n> @@ -567,15 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>         setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));\n>  \n>         utils::Duration newExposureTime;\n> -       double aGain, dGain;\n> -       std::tie(newExposureTime, aGain, dGain) =\n> +       double aGain, qGain, dGain;\n> +       std::tie(newExposureTime, aGain, qGain, dGain) =\n>                 calculateNewEv(frameContext.agc.constraintMode,\n>                                frameContext.agc.exposureMode,\n>                                hist, effectiveExposureValue);\n>  \n>         LOG(RkISP1Agc, Debug)\n> -               << \"Divided up exposure time, analogue gain and digital gain are \"\n> -               << newExposureTime << \", \" << aGain << \" and \" << dGain;\n> +               << \"Divided up exposure time, analogue gain, quantization gain\"\n> +               << \" and digital gain are \" << newExposureTime << \", \" << aGain\n> +               << \", \" << qGain << \" and \" << dGain;\n>  \n>         IPAActiveState &activeState = context.activeState;\n>         /* Update the estimated exposure and gain. */\n> -- \n> 2.48.1\n>","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 0F2B4C328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  5 Sep 2025 10:43:13 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E852969364;\n\tFri,  5 Sep 2025 12:43:12 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3B0BA69356\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  5 Sep 2025 12:43:04 +0200 (CEST)","from neptunite.rasen.tech (unknown\n\t[IPv6:2404:7a81:160:2100:dbfb:387c:7405:52bc])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 327451340; \n\tFri,  5 Sep 2025 12:41:52 +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=\"bFYC69uq\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1757068913;\n\tbh=7CiWGe2L9rJFsohn439YHx6mgNTfeULR7z9F2bsz7gA=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=bFYC69uqacbGl2iPaIyf+ckyDEGWMtWfyD4viojBvkHg1SLQlrYRDRhtXfA1F9Dl9\n\tBeni8IKegUqjIEofMty3YhEao5dDnMKFgkAC2AI408viy1SqmFRcJeXh/8m0iStCHq\n\t14SsN7i+vvqb3i0N06W3L2/W1LAomdOg9f3kNX18=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250815102945.1602071-10-stefan.klug@ideasonboard.com>","References":"<20250815102945.1602071-1-stefan.klug@ideasonboard.com>\n\t<20250815102945.1602071-10-stefan.klug@ideasonboard.com>","Subject":"Re: [PATCH v3 09/19] libipa: exposure_mode_helper: Calculate\n\tquantization gain in splitExposure()","From":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Fri, 05 Sep 2025 19:42:57 +0900","Message-ID":"<175706897713.1787083.16844193513192419837@neptunite.rasen.tech>","User-Agent":"alot/0.0.0","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>"}}]