From patchwork Thu Sep 28 18:55:36 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrey Konovalov X-Patchwork-Id: 19097 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 CB366C0F2A for ; Thu, 28 Sep 2023 18:57:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8A34862968; Thu, 28 Sep 2023 20:57:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1695927432; bh=zDUi3LpHVJzJwwbZMHnvqrjZ4wAnwzhd1Wy4V3S/DJg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=Fs0mL9zqwc8yFaRPhDl0M2Y5qQc/Z2ePtCciT67oJENj1FtDcRqQCOUJdtMUrfLY5 s6yXqXuQlVQY9mHlC5TUZRDfsxL9ZbZ/pt0s+2IKBvq3Y3MLTaYQ7ptqMqd23seKM8 023dop3db7645LBU/sgwni2undw/fk9/XWYG0P3dhTKMLh3pvWnl5eAHrwBbLy9h6p MrWrdjs1m/uDD++pclLMkyOqCz0f7AuKI2F7a2NOltEHf/gj70ab92H3wVNqz/jw+6 BOac0sCKJE4UHkfJJSqK0TYxgP7V1utVuzaSe3n0dI5rjxC6ZGLJ8Qg4ihP3eCk/Q3 l4jZNHrOp4/VA== Received: from mail-wr1-x432.google.com (mail-wr1-x432.google.com [IPv6:2a00:1450:4864:20::432]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7751962963 for ; Thu, 28 Sep 2023 20:57:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=linaro.org header.i=@linaro.org header.b="Ddd6DS7u"; dkim-atps=neutral Received: by mail-wr1-x432.google.com with SMTP id ffacd0b85a97d-3232be274a0so5450048f8f.1 for ; Thu, 28 Sep 2023 11:57:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1695927431; x=1696532231; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=eMsETHaSws+NBrGN8EhisryMSR4UFOOVrra7FOgytfw=; b=Ddd6DS7uFqtfUXIq1d2JqubbWaC+7FY1G96tJ6zhjpk3w9ciL0RXebierdNpqDH6a2 Cr+y7v0kFHf7L+zX9VztgfZwyo/17SnaJIPz8G/yB8A7mJ0W5ihBAreKo4SWPWOPyGIr aoMWk8Qa0UYBjDyoammAQYW4jgYWg2K/pAyqBhA3oDF/nk/yIhMX7EzAUvgdybX48glW KsOk/a2rcmXHfRgodAi1sYr5VdYQjmmm8sWUmaIhbpXBu9/XgtkK1weA92e8RqvjXU18 NjZQvoIM+GuNZvq5V2ymGGNCqIQEQwArQaUpldxHnNv+SKCn/S5IspIC/9rWqu2KARYm wJFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1695927431; x=1696532231; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=eMsETHaSws+NBrGN8EhisryMSR4UFOOVrra7FOgytfw=; b=msJ7qYcezXmqdW4Jq9F5xPf+eYejA+ubuUdthj9rnsUS6KkQxw3vulDcOU/SK205x+ ZgUVKrf9sIV6Mse+BVso8Ez0+RLQO68fsl6egFcCPwEvmPfE1/VX//pw66BkWbVFe12A BQ8cRA8Blq07MmRXLp0kNjc4ETzhSuHBI1NXvWyY+GM8M6nUDdP/ZRZ0bo6krcsPpdYg p/c5whM6Ug5Mgx6awlCvlmg9Bj3uUNdUqi0xRlCOJsezc2mAWNYdEXIaf4Znqk48CN8t EVGeWgQMIN6wKZDGKjpNjUxvwtaPAAz6HJ7aUbUNn0ymy/VfxLUnuq2FSMeh4Nf8fJUM XB/w== X-Gm-Message-State: AOJu0YwM02D00uag34rzEzmdIFYF2pFyFsiqZQsuJbVuFuaDBLV38hD+ fhMpsZKIqoAZgsKokx2B63n7c2hKI8/3No7FmIQ= X-Google-Smtp-Source: AGHT+IFXbWgnM8eWvD8W8DCdpsm4j3/z6ysDs9jyDb/iXGGagJbxjQUwAnucP+IMM5zZ5SMo+NyQnw== X-Received: by 2002:adf:e68a:0:b0:321:57a5:6e6c with SMTP id r10-20020adfe68a000000b0032157a56e6cmr1980795wrm.34.1695927431131; Thu, 28 Sep 2023 11:57:11 -0700 (PDT) Received: from Lat-5310.. ([87.116.164.210]) by smtp.gmail.com with ESMTPSA id u1-20020adfed41000000b003247d3e5d99sm890842wro.55.2023.09.28.11.57.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 Sep 2023 11:57:10 -0700 (PDT) To: libcamera-devel@lists.libcamera.org Date: Thu, 28 Sep 2023 21:55:36 +0300 Message-Id: <20230928185537.20178-5-andrey.konovalov@linaro.org> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230928185537.20178-1-andrey.konovalov@linaro.org> References: <20230928185537.20178-1-andrey.konovalov@linaro.org> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC PATCH v3 4/5] libcamera: converter: add software converter 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: , X-Patchwork-Original-From: Andrey Konovalov via libcamera-devel From: Andrey Konovalov Reply-To: Andrey Konovalov Cc: jacopo.mondi@ideasonboard.com, bryan.odonoghue@linaro.org, srinivas.kandagatla@linaro.org Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Use the Converter interface to implement bilinear Bayer demosaic filtering and very simple AWB on CPU. The only output format currently supported is RGB888. Signed-off-by: Andrey Konovalov --- .../internal/converter/converter_softw.h | 100 ++++ .../libcamera/internal/converter/meson.build | 1 + src/libcamera/converter/converter_softw.cpp | 445 ++++++++++++++++++ src/libcamera/converter/meson.build | 3 +- 4 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 include/libcamera/internal/converter/converter_softw.h create mode 100644 src/libcamera/converter/converter_softw.cpp diff --git a/include/libcamera/internal/converter/converter_softw.h b/include/libcamera/internal/converter/converter_softw.h new file mode 100644 index 00000000..daa2d6da --- /dev/null +++ b/include/libcamera/internal/converter/converter_softw.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * converter_softw.h - interface of software converter (runs 100% on CPU) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "libcamera/internal/converter.h" + +namespace libcamera { + +class FrameBuffer; +class MediaDevice; +class Size; +class SizeRange; +struct StreamConfiguration; + +class SwConverter : public Converter +{ +public: + SwConverter(MediaDevice *media); + + int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } + bool isValid() const { return isp_ != nullptr; } + + std::vector formats(PixelFormat input); + SizeRange sizes(const Size &input); + + std::tuple + strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size); + + int configure(const StreamConfiguration &inputCfg, + const std::vector> &outputCfg); + int exportBuffers(unsigned int ouput, unsigned int count, + std::vector> *buffers); + + void process(FrameBuffer *input, FrameBuffer *output); + int start(); + void stop(); + + int queueBuffers(FrameBuffer *input, + const std::map &outputs); + +private: + class Isp : public Object + { + public: + Isp(SwConverter *converter); + ~Isp(); + + int configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg); + int exportBuffers(unsigned int count, + std::vector> *buffers); + void process(FrameBuffer *input, FrameBuffer *output); + int start(); + void stop(); + void waitForIdle(); + + private: + void debayer(uint8_t *dst, const uint8_t *src); + + SwConverter *converter_; + + Thread thread_; + + libcamera::Mutex idleMutex_; + libcamera::ConditionVariable idleCV_; + bool idle_ LIBCAMERA_TSA_GUARDED_BY(idleMutex_); + + unsigned int width_; + unsigned int height_; + unsigned int stride_; + Point red_shift_; + + unsigned long rNumerat_, rDenomin_; /* red gain for AWB */ + unsigned long bNumerat_, bDenomin_; /* blue gain for AWB */ + unsigned long gNumerat_, gDenomin_; /* green gain for AWB */ + }; + + std::unique_ptr isp_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build index 891e79e7..843a0483 100644 --- a/include/libcamera/internal/converter/meson.build +++ b/include/libcamera/internal/converter/meson.build @@ -1,5 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 libcamera_internal_headers += files([ + 'converter_softw.h', 'converter_v4l2_m2m.h', ]) diff --git a/src/libcamera/converter/converter_softw.cpp b/src/libcamera/converter/converter_softw.cpp new file mode 100644 index 00000000..67245715 --- /dev/null +++ b/src/libcamera/converter/converter_softw.cpp @@ -0,0 +1,445 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * converter_softw.h - interface of software converter (runs 100% on CPU) + */ + +#include "libcamera/internal/converter/converter_softw.h" + +#include +#include +#include + +#include +#include + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/mapped_framebuffer.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Converter) + +SwConverter::SwConverter([[maybe_unused]] MediaDevice *media) + : Converter() +{ + isp_ = std::make_unique(this); +} + +std::vector SwConverter::formats(PixelFormat input) +{ + BayerFormat inputFormat = BayerFormat::fromPixelFormat(input); + + /* Only RAW10P is currently supported */ + if (inputFormat.bitDepth != 10 || + inputFormat.packing != BayerFormat::Packing::CSI2) { + LOG(Converter, Info) + << "Unsupported input format " << input.toString(); + return {}; + } + + return { formats::RGB888 }; +} + +SizeRange SwConverter::sizes(const Size &input) +{ + if (input.width < 2 || input.height < 2) { + LOG(Converter, Error) + << "Input format size too small: " << input.toString(); + return {}; + } + + return SizeRange(Size(input.width - 2, input.height - 2)); +} + +std::tuple +SwConverter::strideAndFrameSize(const PixelFormat &pixelFormat, + const Size &size) +{ + /* Only RGB888 output is currently supported */ + if (pixelFormat != formats::RGB888) + return {}; + + unsigned int stride = size.width * 3; + return std::make_tuple(stride, stride * (size.height)); +} + +int SwConverter::configure(const StreamConfiguration &inputCfg, + const std::vector> &outputCfgs) +{ + if (outputCfgs.size() != 1) { + LOG(Converter, Error) + << "Unsupported number of output streams: " + << outputCfgs.size(); + return -EINVAL; + } + + return isp_->invokeMethod(&SwConverter::Isp::configure, + ConnectionTypeBlocking, inputCfg, outputCfgs[0]); +} + +SwConverter::Isp::Isp(SwConverter *converter) + : converter_(converter) +{ + moveToThread(&thread_); + thread_.start(); +} + +SwConverter::Isp::~Isp() +{ + thread_.exit(); + thread_.wait(); +} + +int SwConverter::Isp::configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg) +{ + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + width_ = inputCfg.size.width; + height_ = inputCfg.size.height; + stride_ = inputCfg.stride; + + if (bayerFormat.bitDepth != 10 || + bayerFormat.packing != BayerFormat::Packing::CSI2 || + width_ < 2 || height_ < 2) { + LOG(Converter, Error) << "Input format " + << inputCfg.size << "-" + << inputCfg.pixelFormat + << "not supported"; + return -EINVAL; + } + + switch (bayerFormat.order) { + case BayerFormat::BGGR: + red_shift_ = Point(0, 0); + break; + case BayerFormat::GBRG: + red_shift_ = Point(1, 0); + break; + case BayerFormat::GRBG: + red_shift_ = Point(0, 1); + break; + case BayerFormat::RGGB: + default: + red_shift_ = Point(1, 1); + break; + } + + if (outputCfg.size.width != width_ - 2 || + outputCfg.size.height != height_ - 2 || + outputCfg.stride != (width_ - 2) * 3 || + outputCfg.pixelFormat != formats::RGB888) { + LOG(Converter, Error) + << "Output format not supported"; + return -EINVAL; + } + + LOG(Converter, Info) << "SwConverter configuration: " + << inputCfg.size << "-" << inputCfg.pixelFormat + << " -> " + << outputCfg.size << "-" << outputCfg.pixelFormat; + + /* set r/g/b gains to 1.0 until frame data collected */ + rNumerat_ = rDenomin_ = 1; + bNumerat_ = bDenomin_ = 1; + gNumerat_ = gDenomin_ = 1; + + return 0; +} + +int SwConverter::exportBuffers(unsigned int output, unsigned int count, + std::vector> *buffers) +{ + /* single output for now */ + if (output >= 1) + return -EINVAL; + + return isp_->invokeMethod(&SwConverter::Isp::exportBuffers, + ConnectionTypeBlocking, count, buffers); +} + +int SwConverter::Isp::exportBuffers(unsigned int count, + std::vector> *buffers) +{ + /* V4L2_PIX_FMT_BGR24 aka 'BGR3' for output: */ + unsigned int bufSize = (height_ - 2) * (width_ - 2) * 3; + + for (unsigned int i = 0; i < count; i++) { + std::string name = "frame-" + std::to_string(i); + + const int ispFd = memfd_create(name.c_str(), 0); + int ret = ftruncate(ispFd, bufSize); + if (ret < 0) { + LOG(Converter, Error) << "ftruncate() for memfd failed " + << strerror(-ret); + return ret; + } + + FrameBuffer::Plane outPlane; + outPlane.fd = SharedFD(std::move(ispFd)); + outPlane.offset = 0; + outPlane.length = bufSize; + + std::vector planes{ outPlane }; + buffers->emplace_back(std::make_unique(std::move(planes))); + } + + return count; +} + +int SwConverter::Isp::start() +{ + return 0; +} + +void SwConverter::Isp::stop() +{ +} + +void SwConverter::Isp::waitForIdle() +{ + MutexLocker locker(idleMutex_); + + idleCV_.wait(locker, [&]() LIBCAMERA_TSA_REQUIRES(idleMutex_) { + return idle_; + }); +} + +int SwConverter::start() +{ + return isp_->invokeMethod(&SwConverter::Isp::start, + ConnectionTypeBlocking); +} + +void SwConverter::stop() +{ + isp_->invokeMethod(&SwConverter::Isp::stop, + ConnectionTypeBlocking); + isp_->invokeMethod(&SwConverter::Isp::waitForIdle, + ConnectionTypeDirect); +} + +int SwConverter::queueBuffers(FrameBuffer *input, + const std::map &outputs) +{ + unsigned int mask = 0; + + /* + * Validate the outputs as a sanity check: at least one output is + * required, all outputs must reference a valid stream and no two + * outputs can reference the same stream. + */ + if (outputs.empty()) + return -EINVAL; + + for (auto [index, buffer] : outputs) { + if (!buffer) + return -EINVAL; + if (index >= 1) /* only single stream atm */ + return -EINVAL; + if (mask & (1 << index)) + return -EINVAL; + + mask |= 1 << index; + } + + process(input, outputs.at(0)); + + return 0; +} + +void SwConverter::process(FrameBuffer *input, FrameBuffer *output) +{ + isp_->invokeMethod(&SwConverter::Isp::process, + ConnectionTypeQueued, input, output); +} + +void SwConverter::Isp::process(FrameBuffer *input, FrameBuffer *output) +{ + { + MutexLocker locker(idleMutex_); + idle_ = false; + } + + /* Copy metadata from the input buffer */ + FrameMetadata &metadata = output->_d()->metadata(); + metadata.status = input->metadata().status; + metadata.sequence = input->metadata().sequence; + metadata.timestamp = input->metadata().timestamp; + + MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read); + MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write); + if (!in.isValid() || !out.isValid()) { + LOG(Converter, Error) << "mmap-ing buffer(s) failed"; + metadata.status = FrameMetadata::FrameError; + converter_->outputBufferReady.emit(output); + converter_->inputBufferReady.emit(input); + return; + } + + debayer(out.planes()[0].data(), in.planes()[0].data()); + metadata.planes()[0].bytesused = out.planes()[0].size(); + + converter_->outputBufferReady.emit(output); + converter_->inputBufferReady.emit(input); + + { + MutexLocker locker(idleMutex_); + idle_ = true; + } + idleCV_.notify_all(); +} + +void SwConverter::Isp::debayer(uint8_t *dst, const uint8_t *src) +{ + /* RAW10P input format is assumed */ + + /* output buffer is in BGR24 format and is of (width-2)*(height-2) */ + + int w_out = width_ - 2; + int h_out = height_ - 2; + + unsigned long sumR = 0; + unsigned long sumB = 0; + unsigned long sumG = 0; + + for (int y = 0; y < h_out; y++) { + const uint8_t *pin_base = src + (y + 1) * stride_; + uint8_t *pout = dst + y * w_out * 3; + int phase_y = (y + red_shift_.y) % 2; + + for (int x = 0; x < w_out; x++) { + int phase_x = (x + red_shift_.x) % 2; + int phase = 2 * phase_y + phase_x; + + /* x part of the offset in the input buffer: */ + int x_m1 = x + x / 4; /* offset for (x-1) */ + int x_0 = x + 1 + (x + 1) / 4; /* offset for x */ + int x_p1 = x + 2 + (x + 2) / 4; /* offset for (x+1) */ + /* the colour component value to write to the output */ + unsigned val; + + switch (phase) { + case 0: /* at R pixel */ + /* blue: ((-1,-1)+(1,-1)+(-1,1)+(1,1)) / 4 */ + val = ( *(pin_base + x_m1 - stride_) + + *(pin_base + x_p1 - stride_) + + *(pin_base + x_m1 + stride_) + + *(pin_base + x_p1 + stride_) ) >> 2; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: ((0,-1)+(-1,0)+(1,0)+(0,1)) / 4 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_p1) + + *(pin_base + x_m1) + + *(pin_base + x_0 + stride_) ) >> 2; + val = val * gNumerat_ / gDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: (0,0) */ + val = *(pin_base + x_0); + sumR += val; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + break; + case 1: /* at Gr pixel */ + /* blue: ((0,-1) + (0,1)) / 2 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_0 + stride_) ) >> 1; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: (0,0) */ + val = *(pin_base + x_0); + sumG += val; + val = val * gNumerat_ / gDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: ((-1,0) + (1,0)) / 2 */ + val = ( *(pin_base + x_m1) + + *(pin_base + x_p1) ) >> 1; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + break; + case 2: /* at Gb pixel */ + /* blue: ((-1,0) + (1,0)) / 2 */ + val = ( *(pin_base + x_m1) + + *(pin_base + x_p1) ) >> 1; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: (0,0) */ + val = *(pin_base + x_0); + sumG += val; + val = val * gNumerat_ / gDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: ((0,-1) + (0,1)) / 2 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_0 + stride_) ) >> 1; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + break; + default: /* at B pixel */ + /* blue: (0,0) */ + val = *(pin_base + x_0); + sumB += val; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: ((0,-1)+(-1,0)+(1,0)+(0,1)) / 4 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_p1) + + *(pin_base + x_m1) + + *(pin_base + x_0 + stride_) ) >> 2; + val = val * gNumerat_ / gDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: ((-1,-1)+(1,-1)+(-1,1)+(1,1)) / 4 */ + val = ( *(pin_base + x_m1 - stride_) + + *(pin_base + x_p1 - stride_) + + *(pin_base + x_m1 + stride_) + + *(pin_base + x_p1 + stride_) ) >> 2; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + } + } + } + + /* calculate red and blue gains for simple AWB */ + LOG(Converter, Debug) << "sumR = " << sumR + << ", sumB = " << sumB << ", sumG = " << sumG; + + sumG /= 2; /* the number of G pixels is twice as big vs R and B ones */ + + /* normalize red, blue, and green sums to fit into 22-bit value */ + unsigned long fRed = sumR / 0x400000; + unsigned long fBlue = sumB / 0x400000; + unsigned long fGreen = sumG / 0x400000; + unsigned long fNorm = std::max({ 1UL, fRed, fBlue, fGreen }); + sumR /= fNorm; + sumB /= fNorm; + sumG /= fNorm; + + LOG(Converter, Debug) << "fNorm = " << fNorm; + LOG(Converter, Debug) << "Normalized: sumR = " << sumR + << ", sumB= " << sumB << ", sumG = " << sumG; + + /* make sure red/blue gains never exceed approximately 256 */ + unsigned long minDenom; + rNumerat_ = (sumR + sumB + sumG) / 3; + minDenom = rNumerat_ / 0x100; + rDenomin_ = std::max(minDenom, sumR); + bNumerat_ = rNumerat_; + bDenomin_ = std::max(minDenom, sumB); + gNumerat_ = rNumerat_; + gDenomin_ = std::max(minDenom, sumG); + + LOG(Converter, Debug) << "rGain = [ " + << rNumerat_ << " / " << rDenomin_ + << " ], bGain = [ " << bNumerat_ << " / " << bDenomin_ + << " ], gGain = [ " << gNumerat_ << " / " << gDenomin_ + << " (minDenom = " << minDenom << ")"; +} + +static std::initializer_list compatibles = {}; + +REGISTER_CONVERTER("linaro-sw-converter", SwConverter, compatibles) + +} /* namespace libcamera */ diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build index 2aa72fe4..1f1e0ea4 100644 --- a/src/libcamera/converter/meson.build +++ b/src/libcamera/converter/meson.build @@ -1,5 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 libcamera_sources += files([ - 'converter_v4l2_m2m.cpp' + 'converter_softw.cpp', + 'converter_v4l2_m2m.cpp', ])