Patch Detail
Show a patch.
GET /api/patches/26375/?format=api
{ "id": 26375, "url": "https://patchwork.libcamera.org/api/patches/26375/?format=api", "web_url": "https://patchwork.libcamera.org/patch/26375/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/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": "<20260326143849.36096-1-mzamazal@redhat.com>", "date": "2026-03-26T14:38:49", "name": "[v2] libcamera: software_isp: Fix black level handling in CPU ISP", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "f0ce09466c6e79fd778eead03c1eb9d543b22433", "submitter": { "id": 177, "url": "https://patchwork.libcamera.org/api/people/177/?format=api", "name": "Milan Zamazal", "email": "mzamazal@redhat.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/26375/mbox/", "series": [ { "id": 5850, "url": "https://patchwork.libcamera.org/api/series/5850/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5850", "date": "2026-03-26T14:38:49", "name": "[v2] libcamera: software_isp: Fix black level handling in CPU ISP", "version": 2, "mbox": "https://patchwork.libcamera.org/series/5850/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/26375/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/26375/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 540C9BDCBD\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 26 Mar 2026 14:39:10 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 882576284B;\n\tThu, 26 Mar 2026 15:39:09 +0100 (CET)", "from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2BA2862781\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 26 Mar 2026 15:39:07 +0100 (CET)", "from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com\n\t(ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97])\n\tby relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n\tcipher=TLS_AES_256_GCM_SHA384) id us-mta-588--Sdz2x9vPuCRVLH1pf_gYQ-1;\n\tThu, 26 Mar 2026 10:39:03 -0400", "from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com\n\t(mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com\n\t[10.30.177.12])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTPS id B318518089B8; Thu, 26 Mar 2026 14:38:59 +0000 (UTC)", "from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.34.16])\n\tby mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTP id 203CD1955F2B; Thu, 26 Mar 2026 14:38:57 +0000 (UTC)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"Mb5r5qqi\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1774535945;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding;\n\tbh=ykKvzNOTZr1MPNFsNZimQIL2SQZjStTOx8c4DA/R0BY=;\n\tb=Mb5r5qqid71MHmFTDbWIiNv5mZVdzYrKWbARtdA1cAlZ+6WC9BLjCP12pgepvUzOHhC4Kr\n\tVTxOAlF3MnT+LKkjt0KS0dDiYzMcp1WIBhJou0+okR6bV1CjJXPHpmZ3+ZuxZ8inxpZ+gO\n\tF6ay5AmpCoioGoDiIcPDuX93HHvKm+Q=", "X-MC-Unique": "-Sdz2x9vPuCRVLH1pf_gYQ-1", "X-Mimecast-MFC-AGG-ID": "-Sdz2x9vPuCRVLH1pf_gYQ_1774535943", "From": "Milan Zamazal <mzamazal@redhat.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Milan Zamazal <mzamazal@redhat.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>", "Subject": "[PATCH v2] libcamera: software_isp: Fix black level handling in CPU\n\tISP", "Date": "Thu, 26 Mar 2026 15:38:49 +0100", "Message-ID": "<20260326143849.36096-1-mzamazal@redhat.com>", "MIME-Version": "1.0", "X-Scanned-By": "MIMEDefang 3.0 on 10.30.177.12", "X-Mimecast-Spam-Score": "0", "X-Mimecast-MFC-PROC-ID": "nSrzXQAQcPuLvOEUDyuR5kwbwtKeU-u2lj-8AkWLKlg_1774535943", "X-Mimecast-Originator": "redhat.com", "Content-Transfer-Encoding": "8bit", "content-type": "text/plain; charset=\"US-ASCII\"; x-default=true", "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 black level handling in CPU ISP has two flaws:\n\n- The black level is applied after white balance rather than before.\n\n- It doesn't handle black levels with different values for individual\n colour channels.\n\nThe flaws are in both CCM and non-CCM cases. The wrong black level and\nwhite balance application order is well visible when the white balance\ngains are significantly different from 1.0. Then the output differs\nsignificantly from GPU ISP output, which uses the correct order.\n\nThis patch changes the computations of the lookup tables in a way that\nfixes both the problems.\n\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\n---\n src/libcamera/software_isp/debayer_cpu.cpp | 60 +++++++++++-----------\n 1 file changed, 30 insertions(+), 30 deletions(-)", "diff": "diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\nindex e7b012105..05540c555 100644\n--- a/src/libcamera/software_isp/debayer_cpu.cpp\n+++ b/src/libcamera/software_isp/debayer_cpu.cpp\n@@ -751,15 +751,12 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst)\n \n void DebayerCpu::updateGammaTable(const DebayerParams ¶ms)\n {\n-\tconst RGB<float> blackLevel = params.blackLevel;\n-\t/* Take let's say the green channel black level */\n-\tconst unsigned int blackIndex = blackLevel[1] * gammaTable_.size();\n \tconst float gamma = params.gamma;\n \tconst float contrastExp = params.contrastExp;\n \n-\tconst float divisor = gammaTable_.size() - blackIndex - 1.0;\n-\tfor (unsigned int i = blackIndex; i < gammaTable_.size(); i++) {\n-\t\tfloat normalized = (i - blackIndex) / divisor;\n+\tconst float divisor = gammaTable_.size() - 1.0;\n+\tfor (unsigned int i = 0; i < gammaTable_.size(); i++) {\n+\t\tfloat normalized = i / divisor;\n \t\t/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */\n \t\t/* Apply simple S-curve */\n \t\tif (normalized < 0.5)\n@@ -769,14 +766,6 @@ void DebayerCpu::updateGammaTable(const DebayerParams ¶ms)\n \t\tgammaTable_[i] = UINT8_MAX *\n \t\t\t\t std::pow(normalized, gamma);\n \t}\n-\t/*\n-\t * Due to CCM operations, the table lookup may reach indices below the black\n-\t * level. Let's set the table values below black level to the minimum\n-\t * non-black value to prevent problems when the minimum value is\n-\t * significantly non-zero (for example, when the image should be all grey).\n-\t */\n-\tstd::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex,\n-\t\t gammaTable_[blackIndex]);\n }\n \n void DebayerCpu::updateLookupTables(const DebayerParams ¶ms)\n@@ -788,11 +777,13 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms)\n \tif (gammaUpdateNeeded)\n \t\tupdateGammaTable(params);\n \n+\t/* Processing order: black level -> gains -> gamma */\n \tauto matrixChanged = [](const Matrix<float, 3, 3> &m1, const Matrix<float, 3, 3> &m2) -> bool {\n \t\treturn !std::equal(m1.data().begin(), m1.data().end(), m2.data().begin());\n \t};\n \tconst unsigned int gammaTableSize = gammaTable_.size();\n-\tconst double div = static_cast<double>(kRGBLookupSize) / gammaTableSize;\n+\tRGB<float> blackIndex = params.blackLevel * kRGBLookupSize;\n+\n \tif (ccmEnabled_) {\n \t\tif (gammaUpdateNeeded ||\n \t\t matrixChanged(params.combinedMatrix, params_.combinedMatrix)) {\n@@ -802,17 +793,21 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms)\n \t\t\tconst unsigned int redIndex = swapRedBlueGains_ ? 2 : 0;\n \t\t\tconst unsigned int greenIndex = 1;\n \t\t\tconst unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2;\n+\t\t\tconst RGB<float> div =\n+\t\t\t\t(RGB<float>(kRGBLookupSize) - blackIndex).max(1.0) /\n+\t\t\t\tkRGBLookupSize;\n \t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n-\t\t\t\tred[i].r = std::round(i * params.combinedMatrix[redIndex][0]);\n-\t\t\t\tred[i].g = std::round(i * params.combinedMatrix[greenIndex][0]);\n-\t\t\t\tred[i].b = std::round(i * params.combinedMatrix[blueIndex][0]);\n-\t\t\t\tgreen[i].r = std::round(i * params.combinedMatrix[redIndex][1]);\n-\t\t\t\tgreen[i].g = std::round(i * params.combinedMatrix[greenIndex][1]);\n-\t\t\t\tgreen[i].b = std::round(i * params.combinedMatrix[blueIndex][1]);\n-\t\t\t\tblue[i].r = std::round(i * params.combinedMatrix[redIndex][2]);\n-\t\t\t\tblue[i].g = std::round(i * params.combinedMatrix[greenIndex][2]);\n-\t\t\t\tblue[i].b = std::round(i * params.combinedMatrix[blueIndex][2]);\n-\t\t\t\tgammaLut_[i] = gammaTable_[i / div];\n+\t\t\t\tconst RGB<float> rgb = ((RGB<float>(i) - blackIndex) / div).max(0.0);\n+\t\t\t\tred[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]);\n+\t\t\t\tred[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][0]);\n+\t\t\t\tred[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][0]);\n+\t\t\t\tgreen[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][1]);\n+\t\t\t\tgreen[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][1]);\n+\t\t\t\tgreen[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][1]);\n+\t\t\t\tblue[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][2]);\n+\t\t\t\tblue[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][2]);\n+\t\t\t\tblue[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][2]);\n+\t\t\t\tgammaLut_[i] = gammaTable_[i * gammaTableSize / kRGBLookupSize];\n \t\t\t}\n \t\t}\n \t} else {\n@@ -821,12 +816,17 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms)\n \t\t\tauto &red = swapRedBlueGains_ ? blue_ : red_;\n \t\t\tauto &green = green_;\n \t\t\tauto &blue = swapRedBlueGains_ ? red_ : blue_;\n+\t\t\tconst RGB<float> div =\n+\t\t\t\t(RGB<float>(kRGBLookupSize) - blackIndex).max(1.0) /\n+\t\t\t\tgammaTableSize;\n \t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n-\t\t\t\t/* Apply gamma after gain! */\n-\t\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n-\t\t\t\tred[i] = gammaTable_[static_cast<unsigned int>(lutGains.r())];\n-\t\t\t\tgreen[i] = gammaTable_[static_cast<unsigned int>(lutGains.g())];\n-\t\t\t\tblue[i] = gammaTable_[static_cast<unsigned int>(lutGains.b())];\n+\t\t\t\tconst RGB<float> lutGains =\n+\t\t\t\t\t(gains * (RGB<float>(i) - blackIndex) / div)\n+\t\t\t\t\t\t.min(gammaTableSize - 1)\n+\t\t\t\t\t\t.max(0.0);\n+\t\t\t\tred[i] = gammaTable_[lutGains.r()];\n+\t\t\t\tgreen[i] = gammaTable_[lutGains.g()];\n+\t\t\t\tblue[i] = gammaTable_[lutGains.b()];\n \t\t\t}\n \t\t}\n \t}\n", "prefixes": [ "v2" ] }