Show a patch.

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

{
    "id": 26486,
    "url": "https://patchwork.libcamera.org/api/patches/26486/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/26486/",
    "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": "<20260407-kbingham-awb-split-v1-3-a39af3f4dc20@ideasonboard.com>",
    "date": "2026-04-07T22:01:06",
    "name": "[03/13] libcamera: software_isp: Fix black level handling in CPU ISP",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "f0ce09466c6e79fd778eead03c1eb9d543b22433",
    "submitter": {
        "id": 4,
        "url": "https://patchwork.libcamera.org/api/people/4/?format=api",
        "name": "Kieran Bingham",
        "email": "kieran.bingham@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/26486/mbox/",
    "series": [
        {
            "id": 5874,
            "url": "https://patchwork.libcamera.org/api/series/5874/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5874",
            "date": "2026-04-07T22:01:03",
            "name": "ipa: simple: Convert to libipa AWB implementation",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/5874/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/26486/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/26486/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 2299DBDCBD\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  7 Apr 2026 22:02:56 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id EC80C62DD2;\n\tWed,  8 Apr 2026 00:02:46 +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 5CC7A62D66\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  8 Apr 2026 00:02:41 +0200 (CEST)",
            "from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A037F1C25;\n\tWed,  8 Apr 2026 00:01:13 +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=\"Cvu7Co7p\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1775599273;\n\tbh=bY67C6uJP8ZsZbZQ9qGjWmYA523zk4Q6DpGuKKMIBSs=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=Cvu7Co7pFaVmjRwO1K5a+ZdV5/vMWFR3+10IWGIBpdApCFy1HYI/qy79L4NXhNs25\n\tRvH4lSwByAAC0JiCFZ6T2XHA1c+N43E7h4RLqmQHoa4kFqtecy2riz3KgrK0ICQh10\n\tY6b6KJx8A3+0I1JRz7hgB5GhskhArC6OuVd1+FsA=",
        "From": "Kieran Bingham <kieran.bingham@ideasonboard.com>",
        "Date": "Tue, 07 Apr 2026 23:01:06 +0100",
        "Subject": "[PATCH 03/13] libcamera: software_isp: Fix black level handling in\n\tCPU ISP",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"utf-8\"",
        "Content-Transfer-Encoding": "7bit",
        "Message-Id": "<20260407-kbingham-awb-split-v1-3-a39af3f4dc20@ideasonboard.com>",
        "References": "<20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com>",
        "In-Reply-To": "<20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Kieran Bingham <kieran.bingham@ideasonboard.com>, \n\tMilan Zamazal <mzamazal@redhat.com>",
        "X-Mailer": "b4 0.14.2",
        "X-Developer-Signature": "v=1; a=ed25519-sha256; t=1775599359; l=6327;\n\ti=kieran.bingham@ideasonboard.com; s=20260204;\n\th=from:subject:message-id; \n\tbh=J/NGwZlg7OEyKZmBQ8iQMgpXJzyjsJs2TFIf1nVIDU8=;\n\tb=lTGEh4dP6hVFLsdZ3k99gmJ0DVtzp9WZI7s4RG3EB8OPfaKZ50ZlghugbECadLtXYKlrHZVag\n\tVfr41dYU7xFDd0m+QLv+vNLhSmxw/uHFlShdNYCDIbjSLRrPA43vPIo",
        "X-Developer-Key": "i=kieran.bingham@ideasonboard.com; a=ed25519;\n\tpk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY=",
        "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": "From: Milan Zamazal <mzamazal@redhat.com>\n\nThe 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 dd0fff8714144417467bac16a1055d1185782d14..5356d6bbec11c30fa0b05659d44a91e69e2b79d0 100644\n--- a/src/libcamera/software_isp/debayer_cpu.cpp\n+++ b/src/libcamera/software_isp/debayer_cpu.cpp\n@@ -879,15 +879,12 @@ void DebayerCpuThread::process4(uint32_t frame, const uint8_t *src, uint8_t *dst\n \n void DebayerCpu::updateGammaTable(const DebayerParams &params)\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@@ -897,14 +894,6 @@ void DebayerCpu::updateGammaTable(const DebayerParams &params)\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 &params)\n@@ -916,11 +905,13 @@ void DebayerCpu::updateLookupTables(const DebayerParams &params)\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@@ -930,17 +921,21 @@ void DebayerCpu::updateLookupTables(const DebayerParams &params)\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@@ -949,12 +944,17 @@ void DebayerCpu::updateLookupTables(const DebayerParams &params)\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": [
        "03/13"
    ]
}