diff --git a/aports/device/soc-fairphone-fp6-audio/APKBUILD b/aports/device/soc-fairphone-fp6-audio/APKBUILD index a0eb5f0..2b0ba5c 100644 --- a/aports/device/soc-fairphone-fp6-audio/APKBUILD +++ b/aports/device/soc-fairphone-fp6-audio/APKBUILD @@ -3,21 +3,14 @@ # branch fp6), the AW88261 amp config blob, and the UCM profile. Files are # byte-identical to the ones deployed and tested on the development FP6. # The tplg/UCM target filenames contain spaces, hence the safe source names. -# -# Licensing: the topology (built from BSD-3-Clause audioreach-topology -# sources) and the UCM/pipewire configs are BSD-3-Clause. aw88261_acf.bin -# is a verbatim proprietary Awinic amp-config blob extracted from the -# FP6's stock vendor partition; its redistribution status is unresolved -# (candidate for FairBlobs adoption or install-time on-device extraction -# from the stock vendor partition, which pmOS installs leave in place). maintainer="Jorijn van der Graaf " pkgname=soc-fairphone-fp6-audio pkgver=3 -pkgrel=1 +pkgrel=0 pkgdesc="Fairphone 6 audio topology, amp firmware and UCM profile" url="https://forgejo.catcrafts.net/Catcrafts/audioreach-topology" arch="noarch" -license="BSD-3-Clause AND proprietary" +license="BSD-3-Clause" options="!check" source=" fp6-tplg.bin diff --git a/aports/temp/libcamera/0016-ipa-simple-awb-Gate-the-gray-world-estimate-on-near-.patch b/aports/temp/libcamera/0016-ipa-simple-awb-Gate-the-gray-world-estimate-on-near-.patch deleted file mode 100644 index 39d4033..0000000 --- a/aports/temp/libcamera/0016-ipa-simple-awb-Gate-the-gray-world-estimate-on-near-.patch +++ /dev/null @@ -1,365 +0,0 @@ -From 3e109c99ec651719311580f574d8a152a5a09680 Mon Sep 17 00:00:00 2001 -From: Jorijn van der Graaf -Date: Sun, 23 Aug 2026 22:32:48 +0200 -Subject: [PATCH] ipa: simple: awb: Gate the gray-world estimate on - near-neutral zones - -A single large coloured area - typically a coloured light source, e.g. -an RGB keyboard backlight filling the bottom of the frame - defeats -whole-frame gray world by design: the frame average it produces is -statistically identical to that of a neutral scene under some other -illuminant, so the locus-clamped estimate settles confidently at the -wrong end of the locus (measured 2026-08-17: a cyan-backlit keyboard -renders the ambient-lit desk red; stable, uniform and wrong). - -Divide the statistics window into 16x12 zones and collect per-zone RGB -sums alongside the existing whole-frame sums, at the same sampling -density. In the Awb algorithm, when a white point locus is tuned, vote -only with the zones whose implied gains lie within zoneGateMargin of -the locus - zones some plausible illuminant could render gray - and -fall back to the whole-frame sums when fewer than four zones qualify. -Zones that are too dark, clipped, or carry too few samples are -excluded from the vote. - -The gate runs ahead of the existing locus clamp and damping, which are -unchanged. Without whitePoints tuning the zone statistics are still -collected but the algorithm behaves exactly as before. - -Assisted-by: Claude:claude-fable-5 -Signed-off-by: Jorijn van der Graaf ---- - .../internal/software_isp/swisp_stats.h | 28 +++++ - src/ipa/simple/algorithms/awb.cpp | 119 +++++++++++++++++- - src/ipa/simple/algorithms/awb.h | 4 + - src/libcamera/software_isp/swstats_cpu.cpp | 47 ++++++- - 4 files changed, 193 insertions(+), 5 deletions(-) - -diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h -index c2ec20e..22cdd0e 100644 ---- a/include/libcamera/internal/software_isp/swisp_stats.h -+++ b/include/libcamera/internal/software_isp/swisp_stats.h -@@ -47,6 +47,34 @@ struct SwIspStats { - * \brief Holds the sharpness of an image - */ - uint64_t sharpness; -+ /** -+ * \brief Number of AWB statistics zone columns -+ */ -+ static constexpr unsigned int kAwbZoneCols = 16; -+ /** -+ * \brief Number of AWB statistics zone rows -+ */ -+ static constexpr unsigned int kAwbZoneRows = 12; -+ /** -+ * \brief Per-zone colour channel sums and sample count -+ * -+ * The sums are in the same scale as sum_. uint32_t is large enough: -+ * a zone of a 12-bit 8192x6144 frame at the stats sampling density -+ * sums to at most ~2^26. -+ */ -+ struct AwbZone { -+ uint32_t sumR; -+ uint32_t sumG; -+ uint32_t sumB; -+ uint32_t count; -+ }; -+ /** -+ * \brief Colour channel sums of the sampled pixels per image zone -+ * -+ * The statistics window is divided into kAwbZoneCols x kAwbZoneRows -+ * equally sized zones, stored in row-major order. -+ */ -+ std::array awbZones; - }; - - } /* namespace libcamera */ -diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp -index 83dcece..e47db76 100644 ---- a/src/ipa/simple/algorithms/awb.cpp -+++ b/src/ipa/simple/algorithms/awb.cpp -@@ -43,6 +43,22 @@ constexpr unsigned int kFastConvergeFrames = 3; - /* Start from the white point closest to daylight. */ - constexpr double kInitialCT = 5000.0; - -+/* -+ * Zone gating thresholds. A zone votes only when it carries enough samples -+ * to trust its means (kZoneMinSamples), no channel mean is close to clipping -+ * (kZoneClipMean, in the 8-bit scale of the statistics), and the green mean -+ * clears the black level by at least kZoneMinSignal. The implied per-zone -+ * gains are capped at kZoneMaxGain to keep the locus distance finite for -+ * almost-black channels. Fewer than kZoneMinIncluded voting zones means the -+ * scene carries no usable spatial signal and the whole-frame estimate is -+ * used instead. -+ */ -+constexpr unsigned int kZoneMinSamples = 16; -+constexpr double kZoneClipMean = 250.0; -+constexpr double kZoneMinSignal = 2.0; -+constexpr double kZoneMaxGain = 8.0; -+constexpr unsigned int kZoneMinIncluded = 4; -+ - } /* namespace */ - - namespace libcamera { -@@ -70,6 +86,18 @@ namespace ipa::soft::algorithms { - * temperature fed to the Ccm algorithm is interpolated from the applied - * gains' position along the locus. Without whitePoints the plain - * gray-world algorithm runs unchanged. -+ * -+ * With whitePoints two further keys enable spatial (zone-gated) AWB: -+ * -+ * zoneGating: true # vote with near-neutral zones only -+ * zoneGateMargin: 0.35 # max locus distance for a zone to vote -+ * -+ * The gray-world sums are then taken from the statistics zones whose -+ * implied gains lie within zoneGateMargin (default clampMargin) of the -+ * locus, i.e. zones some plausible illuminant could render gray, so a -+ * large coloured area - typically a coloured light source - no longer -+ * drags the white balance. When too few zones qualify the whole-frame -+ * sums are used as before. - */ - int Awb::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) - { -@@ -120,11 +148,21 @@ int Awb::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) - return -EINVAL; - } - -+ zoneGating_ = tuningData["zoneGating"].get(true); -+ zoneGateMargin_ = tuningData["zoneGateMargin"].get(clampMargin_); -+ if (zoneGateMargin_ <= 0.0) { -+ LOG(IPASoftAwb, Error) << "zoneGateMargin must be positive"; -+ whitePoints_.clear(); -+ return -EINVAL; -+ } -+ - LOG(IPASoftAwb, Info) - << "White point locus: " << whitePoints_.size() << " points, " - << whitePoints_.front().ct << "-" << whitePoints_.back().ct - << " K, clamp margin " << clampMargin_ -- << ", damping " << damping_; -+ << ", damping " << damping_ -+ << ", zone gating " << (zoneGating_ ? "on" : "off") -+ << " margin " << zoneGateMargin_; - - return 0; - } -@@ -202,6 +240,73 @@ Awb::LocusPosition Awb::closestOnLocus(double rGain, double bGain) const - return best; - } - -+/* -+ * Compute the gray-world sums from the near-neutral image zones only. -+ * -+ * A large coloured area - typically a coloured light source such as an RGB -+ * keyboard backlight - defeats whole-frame averaging by design: the frame -+ * average it produces is statistically identical to that of a neutral scene -+ * under some other illuminant, so the estimate is confidently wrong. Per -+ * zone the distinction is visible: a zone votes only when the gains that -+ * would render it gray lie close to the calibrated white point locus, i.e. -+ * when some plausible illuminant could have produced it from a neutral -+ * surface. Zones that are too dark, clipped or strongly coloured are -+ * excluded. Returns false when too few zones vote; the caller then falls -+ * back to the whole-frame estimate. -+ */ -+bool Awb::gatedEstimate(const SwIspStats &stats, double blackLevel, -+ double &rTarget, double &bTarget) const -+{ -+ double sumR = 0.0, sumG = 0.0, sumB = 0.0; -+ unsigned int eligible = 0, included = 0; -+ -+ for (const SwIspStats::AwbZone &zone : stats.awbZones) { -+ if (zone.count < kZoneMinSamples) -+ continue; -+ -+ const double meanR = static_cast(zone.sumR) / zone.count; -+ const double meanG = static_cast(zone.sumG) / zone.count; -+ const double meanB = static_cast(zone.sumB) / zone.count; -+ -+ /* A clipped channel no longer measures colour. */ -+ if (meanR > kZoneClipMean || meanG > kZoneClipMean || -+ meanB > kZoneClipMean) -+ continue; -+ -+ const double r = meanR - blackLevel; -+ const double g = meanG - blackLevel; -+ const double b = meanB - blackLevel; -+ if (g < kZoneMinSignal) -+ continue; -+ -+ eligible++; -+ -+ const double gR = g / std::max(r, g / kZoneMaxGain); -+ const double gB = g / std::max(b, g / kZoneMaxGain); -+ if (closestOnLocus(gR, gB).distance > zoneGateMargin_) -+ continue; -+ -+ included++; -+ sumR += r * zone.count; -+ sumG += g * zone.count; -+ sumB += b * zone.count; -+ } -+ -+ if (included < kZoneMinIncluded || sumR <= 0.0 || sumB <= 0.0) { -+ LOG(IPASoftAwb, Debug) -+ << "zone gate: " << included << "/" << eligible -+ << " zones, falling back to whole-frame"; -+ return false; -+ } -+ -+ LOG(IPASoftAwb, Debug) -+ << "zone gate: " << included << "/" << eligible << " zones vote"; -+ -+ rTarget = sumG / sumR; -+ bTarget = sumG / sumB; -+ return true; -+} -+ - void Awb::process(IPAContext &context, - [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, -@@ -258,11 +363,17 @@ void Awb::process(IPAContext &context, - } - - /* -- * Gray-world estimate. The sums are at least minValid, so the -+ * Gray-world estimate, from the near-neutral zones when enough of -+ * them pass the chromaticity gate, from the whole-frame sums -+ * otherwise. The whole-frame sums are at least minValid, so the - * divisions are safe; the locus clamp below bounds the result. - */ -- double rTarget = static_cast(sum.g()) / sum.r(); -- double bTarget = static_cast(sum.g()) / sum.b(); -+ double rTarget, bTarget; -+ if (!zoneGating_ || -+ !gatedEstimate(*stats, blackLevel, rTarget, bTarget)) { -+ rTarget = static_cast(sum.g()) / sum.r(); -+ bTarget = static_cast(sum.g()) / sum.b(); -+ } - - /* Clamp the estimate to within clampMargin_ of the locus. */ - const LocusPosition projection = closestOnLocus(rTarget, bTarget); -diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h -index ef4e4f3..f86b31f 100644 ---- a/src/ipa/simple/algorithms/awb.h -+++ b/src/ipa/simple/algorithms/awb.h -@@ -49,6 +49,8 @@ private: - }; - - LocusPosition closestOnLocus(double rGain, double bGain) const; -+ bool gatedEstimate(const SwIspStats &stats, double blackLevel, -+ double &rTarget, double &bTarget) const; - - /* - * Calibrated white point locus in (R gain, B gain) space, ordered by -@@ -58,6 +60,8 @@ private: - std::vector whitePoints_; - double clampMargin_ = 0.0; - double damping_ = 0.0; -+ bool zoneGating_ = false; -+ double zoneGateMargin_ = 0.0; - unsigned int fastFrames_ = 0; - }; - -diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp -index 2cbdbef..ed32c47 100644 ---- a/src/libcamera/software_isp/swstats_cpu.cpp -+++ b/src/libcamera/software_isp/swstats_cpu.cpp -@@ -11,6 +11,8 @@ - - #include "libcamera/internal/software_isp/swstats_cpu.h" - -+#include -+ - #include - - #include -@@ -192,7 +194,19 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ - const unsigned int sharpXBegin = (lineLength) * 2 / 5; \ - const unsigned int sharpXEnd = (lineLength) * 3 / 5; \ - const bool sharpRow = y >= window_.height * 2 / 5 && \ -- y < window_.height * 3 / 5; -+ y < window_.height * 3 / 5; \ -+ uint32_t zSumR = 0; \ -+ uint32_t zSumG = 0; \ -+ uint32_t zSumB = 0; \ -+ uint32_t zCount = 0; \ -+ unsigned int zCol = 0; \ -+ const unsigned int zLineLength = (lineLength); \ -+ unsigned int zEndX = zLineLength / SwIspStats::kAwbZoneCols; \ -+ SwIspStats::AwbZone *zRow = &stats.awbZones[ \ -+ std::min(y * SwIspStats::kAwbZoneRows / \ -+ static_cast(window_.height), \ -+ SwIspStats::kAwbZoneRows - 1) * \ -+ SwIspStats::kAwbZoneCols]; - - /* - * Sharpness is the sum of the squared differences between green samples two -@@ -205,6 +219,20 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ - sumG += g; \ - sumB += b; \ - \ -+ while (x >= zEndX && zCol + 1 < SwIspStats::kAwbZoneCols) { \ -+ zRow[zCol].sumR += zSumR; \ -+ zRow[zCol].sumG += zSumG; \ -+ zRow[zCol].sumB += zSumB; \ -+ zRow[zCol].count += zCount; \ -+ zSumR = zSumG = zSumB = zCount = 0; \ -+ zCol++; \ -+ zEndX = (zCol + 1) * zLineLength / SwIspStats::kAwbZoneCols; \ -+ } \ -+ zSumR += r; \ -+ zSumG += g; \ -+ zSumB += b; \ -+ zCount++; \ -+ \ - yVal = r * kRedYMul; \ - yVal += g * kGreenYMul; \ - yVal += b * kBlueYMul; \ -@@ -227,6 +255,10 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ - stats.sum_.r() += sumR; \ - stats.sum_.g() += sumG; \ - stats.sum_.b() += sumB; \ -+ zRow[zCol].sumR += zSumR; \ -+ zRow[zCol].sumG += zSumG; \ -+ zRow[zCol].sumB += zSumB; \ -+ zRow[zCol].count += zCount; \ - if (sharpCount) { \ - const uint64_t meanG = sharpSumG / sharpCount; \ - if (meanG) \ -@@ -432,6 +464,7 @@ void SwStatsCpu::startFrame(uint32_t frame) - s.sum_ = RGB({ 0, 0, 0 }); - s.yHistogram.fill(0); - s.sharpness = 0; -+ s.awbZones.fill({ 0, 0, 0, 0 }); - } - } - -@@ -450,14 +483,26 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) - sharedStats_->sum_ = RGB({ 0, 0, 0 }); - sharedStats_->sharpness = 0; - sharedStats_->yHistogram.fill(0); -+ sharedStats_->awbZones.fill({ 0, 0, 0, 0 }); - for (const auto &s : stats_) { - sharedStats_->sharpness += s.sharpness; - sharedStats_->sum_ += s.sum_; - for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++) - sharedStats_->yHistogram[j] += s.yHistogram[j]; -+ for (unsigned int j = 0; j < sharedStats_->awbZones.size(); j++) { -+ sharedStats_->awbZones[j].sumR += s.awbZones[j].sumR; -+ sharedStats_->awbZones[j].sumG += s.awbZones[j].sumG; -+ sharedStats_->awbZones[j].sumB += s.awbZones[j].sumB; -+ sharedStats_->awbZones[j].count += s.awbZones[j].count; -+ } - } - - sharedStats_->sum_ >>= sumShift_; -+ for (SwIspStats::AwbZone &zone : sharedStats_->awbZones) { -+ zone.sumR >>= sumShift_; -+ zone.sumG >>= sumShift_; -+ zone.sumB >>= sumShift_; -+ } - } - - sharedStats_->valid = valid; --- -2.55.0 - diff --git a/aports/temp/libcamera/APKBUILD b/aports/temp/libcamera/APKBUILD index adb297d..8b17811 100644 --- a/aports/temp/libcamera/APKBUILD +++ b/aports/temp/libcamera/APKBUILD @@ -3,7 +3,7 @@ maintainer="Robert Mader " pkgname=libcamera _pkgver=0.7.2 pkgver=9999$_pkgver -pkgrel=9 +pkgrel=8 pkgdesc="Linux camera framework" url="https://libcamera.org/" arch="all" @@ -75,7 +75,6 @@ source="libcamera-v$_pkgver.tar.gz 0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch 0014-libcamera-software_isp-af-Re-baseline-the-scene-refe.patch 0015-ipa-simple-awb-Add-optional-white-point-locus-clamp-.patch - 0016-ipa-simple-awb-Gate-the-gray-world-estimate-on-near-.patch qcam.desktop $_tuning_files " @@ -204,7 +203,6 @@ c8ddc64ab943d9b215f4c997f2629e4403744c0ddb5de88cd42d384506a27c0762191eddd4d13559 dce81c2863ff6b9374115325f4af14ac5d396a6e2aee12f87d55a47d1d0bda860ae2af5a9d6885b126e20a12b7f3f40e49a2b9d738f94c04177bff6149da64e1 0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch 18ed6a03fbe3bcd750fac280831d1e62f2a2e35f76880535f2dc16ff196308512535e077ab0d8a923ad241090713edb53d1777e0dd6e5dcc10de495db1277052 0014-libcamera-software_isp-af-Re-baseline-the-scene-refe.patch b01deba75c093ad4312ffa829ddbe5d745f2dfb199a3f00cdd52a8cb972fbe58bfb7cd94fa825b30d65047ac0c371ecf3ec0d0e356f090119d11086552f514be 0015-ipa-simple-awb-Add-optional-white-point-locus-clamp-.patch -a2dd2d360607c198022fd3353b5e9b4dcb0712abe3f0a6f60de138839b255aedea6a414760e29aa1cab82d0a84633755684d2f28ea0b15541241e51d7f5bd03e 0016-ipa-simple-awb-Gate-the-gray-world-estimate-on-near-.patch 22167a4eceb6d1b40b0b7c45fdf116c71684f5340de7f767535cb8e160ad9d2ae0f00cb3d461f73a344520a48a4641cf46226841d78bee06bfbfd2a91337f754 qcam.desktop 2ee566653b17d565d2c0de67cbe6ebad25df5aac6051cad78e6bae52016fa2ca0247e964d735ac776b3e0bd447ead9c4fcb26cd7629228cdba3ceb91b46f378f hi846.yaml 55dab9dcb9b1982143b9d63e4e51138c5bffd7c8f4a8e4e122750b3ad9d475446956bc297d9f564819ade4be75a575d06633a064e14f8ba3ef77986783c5f067 imx355.yaml