From patchwork Mon Oct 26 08:26:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28606 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 50D9EC3302 for ; Mon, 26 Oct 2026 08:27:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 009C168FD9; Mon, 26 Oct 2026 09:27:03 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="cIdlNxnO"; dkim-atps=neutral Received: from smtp-outgoing-1603.laposte.net (smtp-outgoing-1603.laposte.net [160.92.124.98]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EC7C968FBA for ; Mon, 26 Oct 2026 09:26:58 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv13Z2fzgY7m", "contextId": "e048617a-7252-40e2-82cd-89f1563f633f"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0104.laposte.net (SMTP Server) with ESMTP id 4jCmv13Z2fzgY7m; Mon, 26 Oct 2026 09:26:53 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv040qKzgY7R", "contextId": "fc0959fe-8552-4c62-9d93-f231a782fbdf"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian (unknown [212.133.41.21]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mlpnf0104.laposte.net (SMTP Server) with ESMTPSA id 4jCmv040qKzgY7R; Mon, 26 Oct 2026 09:26:52 +0100 (CET) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: bryan.odonoghue@linaro.org, barnabas.pocze@ideasonboard.com, mzamazal@redhat.com, johannes.goede@oss.qualcomm.com, laurent.pinchart@ideasonboard.com, jai.luthra@ideasonboard.com, =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v7 3/5] libcamera: software_isp: Add support for monochrome formats - CPU Date: Mon, 26 Oct 2026 09:26:00 +0100 Message-ID: <20261026082602.60417-4-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261026082602.60417-1-alain.cousinie@laposte.net> References: <20261026082602.60417-1-alain.cousinie@laposte.net> MIME-Version: 1.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=laposte.net; s=lpn-wlmd; t=1793003218; bh=Q7EGMRC444U2yopW18AoexmjK9uvZLxWv7mHeYhbVTI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=cIdlNxnO99rMiASmBcJKcVpBlUqItbideK2mjoao1R3SAl04ZFpVR+AE79J/FH/5Q6crpfoA7K7aZ5TisXPDjXyUKh/st7yf58dOfFrxU+3nicoAh90AiQwMaVNnSHI5DGYE32d8eKXVbIUZ74XP+rALvp/i+KjUQ5V1vvERkxfwmTfTXmRfJnB77mwJE13+8xI625kgHDNRV8XBsLGFynm1fkQeGQlJ7l5cB1vHWai3bbXMYu0pEfpeBwLM1iTj8ce2eVwv+D0/3BM1W+y1WqcZ1p+ABaI491Hud0u3HYcqbe9z2+XlYJIlfb93NlpQv6OBdxjDQnQ7MonuR9T2yw==; 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" Signed-off-by: Alain CousiniƩ --- src/libcamera/software_isp/debayer_cpu.cpp | 231 +++++++++++++++++++-- src/libcamera/software_isp/debayer_cpu.h | 25 +++ 2 files changed, 244 insertions(+), 12 deletions(-) -- 2.55.0 diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 91867baa6..a22582931 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -50,6 +50,7 @@ private: void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src); void memcpyNextLine(const uint8_t *linePointers[]); void process2(uint32_t frame, const uint8_t *src, uint8_t *dst); + void processMono2(uint32_t frame, const uint8_t *src, uint8_t *dst); void process4(uint32_t frame, const uint8_t *src, uint8_t *dst); /* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */ @@ -424,6 +425,123 @@ void DebayerCpu::debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]) } } +template +void DebayerCpu::debayer8_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t pixel_val = *cur++; + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + +template +void DebayerCpu::debayer10_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 2); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + + +template +void DebayerCpu::debayer12_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 4); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + +template +void DebayerCpu::debayer14_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 6); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + +template +void DebayerCpu::debayer10P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t gray0 = *cur++; + uint8_t gray1 = *cur++; + uint8_t gray2 = *cur++; + uint8_t gray3 = *cur++; + cur++; + + STORE_PIXEL(gray0, gray0, gray0) + STORE_PIXEL(gray1, gray1, gray1) + STORE_PIXEL(gray2, gray2, gray2) + STORE_PIXEL(gray3, gray3, gray3) + } +} + +template +void DebayerCpu::debayer12P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t gray0 = *cur++; + uint8_t gray1 = *cur++; + cur++; + + STORE_PIXEL(gray0, gray0, gray0) + STORE_PIXEL(gray1, gray1, gray1) + } +} + +template +void DebayerCpu::debayer14P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t gray0 = *cur++; + uint8_t gray1 = *cur++; + uint8_t gray2 = *cur++; + uint8_t gray3 = *cur++; + cur += 3; + + STORE_PIXEL(gray0, gray0, gray0) + STORE_PIXEL(gray1, gray1, gray1) + STORE_PIXEL(gray2, gray2, gray2) + STORE_PIXEL(gray3, gray3, gray3) + } +} + +template +void DebayerCpu::debayer16_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 8); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + /* * Setup the Debayer object according to the passed in parameters. * Return 0 on success, a negative errno value on failure @@ -433,6 +551,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf { BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + std::vector outputFormats = { formats::RGB888, formats::XRGB8888, formats::ARGB8888, @@ -440,6 +559,20 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf formats::XBGR8888, formats::ABGR8888 }; + if (bayerFormat.order == BayerFormat::Order::MONO) { + if (bayerFormat.packing == BayerFormat::Packing::None) { + config.bpp = (bayerFormat.bitDepth + 7) & ~7; + config.patternSize.width = 2; + config.patternSize.height = 2; + } else if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + config.bpp = bayerFormat.bitDepth; + config.patternSize.width = (bayerFormat.bitDepth == 12) ? 2 : 4; + config.patternSize.height = 2; + } + config.outputFormats = outputFormats; + return 0; + } + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && bayerFormat.packing == BayerFormat::Packing::None && isStandardBayerOrder(bayerFormat.order)) { @@ -513,8 +646,6 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat, bool ccmEnabled) { - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputFormat); bool addAlphaByte = false; xShift_ = 0; @@ -525,6 +656,48 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, return -EINVAL; }; + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + + if (bayerFormat.order == BayerFormat::Order::MONO) { + if (outputFormat == formats::XRGB8888 || outputFormat == formats::ARGB8888 || + outputFormat == formats::XBGR8888 || outputFormat == formats::ABGR8888) { + addAlphaByte = true; + } + if (bayerFormat.packing == BayerFormat::Packing::None) { + switch (bayerFormat.bitDepth) { + case 8: + SET_DEBAYER_METHODS(debayer8_MONO_BGR888, debayer8_MONO_BGR888) + return 0; + case 10: + SET_DEBAYER_METHODS(debayer10_MONO_BGR888, debayer10_MONO_BGR888) + return 0; + case 12: + SET_DEBAYER_METHODS(debayer12_MONO_BGR888, debayer12_MONO_BGR888) + return 0; + case 14: + SET_DEBAYER_METHODS(debayer14_MONO_BGR888, debayer14_MONO_BGR888) + return 0; + case 16: + SET_DEBAYER_METHODS(debayer16_MONO_BGR888, debayer16_MONO_BGR888) + return 0; + } + } + if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + switch (bayerFormat.bitDepth) { + case 10: + SET_DEBAYER_METHODS(debayer10P_MONO_BGR888, debayer10P_MONO_BGR888) + return 0; + case 12: + SET_DEBAYER_METHODS(debayer12P_MONO_BGR888, debayer12P_MONO_BGR888) + return 0; + case 14: + SET_DEBAYER_METHODS(debayer14P_MONO_BGR888, debayer14P_MONO_BGR888) + return 0; + } + } + return invalidFmt(); + } + switch (outputFormat) { case formats::XRGB8888: case formats::ARGB8888: @@ -633,6 +806,9 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg, return -EINVAL; } + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + isMono_ = (bayerFormat.order == BayerFormat::Order::MONO); + if (stats_->configure(inputCfg, threads_.size()) != 0) return -EINVAL; @@ -683,10 +859,16 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg, */ params_.gamma = 1.0; - window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & - ~(inputConfig_.patternSize.width - 1); - window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & - ~(inputConfig_.patternSize.height - 1); + /* Setup the window geometry according to the format type */ + if (isMono_) { + window_.x = 0; + window_.y = 0; + } else { + window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & + ~(inputConfig_.patternSize.width - 1); + window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & + ~(inputConfig_.patternSize.height - 1); + } window_.width = outputCfg.size.width; window_.height = outputCfg.size.height; @@ -828,7 +1010,9 @@ void DebayerCpuThread::process(uint32_t frame, const uint8_t *src, uint8_t *dst) /* Adjust dst for yStart_ */ dst += yStart_ * debayer_->outputConfig_.stride; - if (debayer_->inputConfig_.patternSize.height == 2) + if (debayer_->isMono_) + processMono2(frame, src, dst); + else if (debayer_->inputConfig_.patternSize.height == 2) process2(frame, src, dst); else process4(frame, src, dst); @@ -905,6 +1089,27 @@ void DebayerCpuThread::process2(uint32_t frame, const uint8_t *src, uint8_t *dst } } +void DebayerCpuThread::processMono2(uint32_t frame, const uint8_t *src, uint8_t *dst) +{ + unsigned int outputStride = debayer_->outputConfig_.stride; + unsigned int inputStride = debayer_->inputConfig_.stride; + /* Holds [0] previous- [1] current- [2] next-line */ + const uint8_t *linePointers[3]; + + for (unsigned int y = yStart_; y < yEnd_; y += 2) { + linePointers[1] = src; + debayer_->stats_->processLine0(frame, y, linePointers, threadIndex_); + debayer_->debayer0(dst, linePointers); + src += inputStride; + dst += outputStride; + + linePointers[1] = src; + debayer_->debayer1(dst, linePointers); + src += inputStride; + dst += outputStride; + } +} + void DebayerCpuThread::process4(uint32_t frame, const uint8_t *src, uint8_t *dst) { unsigned int outputStride = debayer_->outputConfig_.stride; @@ -1125,12 +1330,11 @@ void DebayerCpu::stop() SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize) { Size patternSize = this->patternSize(inputFormat); - unsigned int borderHeight = patternSize.height; - if (patternSize.isNull()) return {}; /* No need for top/bottom border with a pattern height of 2 */ + unsigned int borderHeight = patternSize.height; if (patternSize.height == 2) borderHeight = 0; @@ -1141,13 +1345,16 @@ SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize) if (inputSize.width < (3 * patternSize.width) || inputSize.height < (2 * borderHeight + patternSize.height)) { LOG(Debayer, Warning) - << "Input format size too small: " << inputSize.toString(); + << "Input format size too small: " << inputSize; return {}; } + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + bool localIsMono = (bayerFormat.order == BayerFormat::Order::MONO); + return SizeRange(Size(patternSize.width, patternSize.height), - Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1), - (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)), + Size((inputSize.width - (localIsMono ? 0 : 2 * patternSize.width)) & ~(patternSize.width - 1), + (inputSize.height - (localIsMono ? 0 : 2 * borderHeight)) & ~(patternSize.height - 1)), patternSize.width, patternSize.height); } diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h index ae570893b..0543d75dc 100644 --- a/src/libcamera/software_isp/debayer_cpu.h +++ b/src/libcamera/software_isp/debayer_cpu.h @@ -120,6 +120,30 @@ private: void debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); template void debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); + /* 8-bit raw monochrome format */ + template + void debayer8_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 10-bit raw monochrome format */ + template + void debayer10_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 12-bit raw monochrome format */ + template + void debayer12_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 14-bit raw monochrome format */ + template + void debayer14_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 10-bit raw monochrome format */ + template + void debayer10P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 12-bit raw monochrome format */ + template + void debayer12P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 14-bit raw monochrome format */ + template + void debayer14P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* Mono 16 bpp unpacked */ + template + void debayer16_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); int setupStandardBayerOrder(BayerFormat::Order order); @@ -147,6 +171,7 @@ private: std::array gammaTable_; LookupTable gammaLut_; bool ccmEnabled_; + bool isMono_; DebayerParams params_; debayerFn debayer0_;