Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/rawspeed-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
if ($null -eq $exe) {
throw "rawspeed-cli.exe not found under build/"
}

$output = (& $exe.FullName 2>&1 | Out-String)
$exitCode = $LASTEXITCODE
"exe: $($exe.FullName)" | Add-Content $env:GITHUB_STEP_SUMMARY
Expand All @@ -63,6 +64,75 @@ jobs:
throw "rawspeed-cli smoke test failed: exe=$($exe.FullName) exit=$exitCode output=[$output]"
}
Write-Output "smoke test ok: $($exe.FullName)"

$w = 8
$h = 8
$pixels = [System.IO.MemoryStream]::new()
for ($y = 0; $y -lt $h; $y++) {
for ($x = 0; $x -lt $w; $x++) {
$b = [BitConverter]::GetBytes([uint16](($y * $w + $x) * 500 + 1000))
$pixels.Write($b, 0, 2)
}
}
$pixels = $pixels.ToArray()
$make = [System.Text.Encoding]::ASCII.GetBytes("rawspeed`0")
$model = [System.Text.Encoding]::ASCII.GetBytes("test`0")
$ucm = [System.Text.Encoding]::ASCII.GetBytes("rawspeed test`0")
$entryCount = 16
$extOffset = 8 + (2 + $entryCount * 12 + 4)
$makeOff = $extOffset
$modelOff = $makeOff + $make.Length
$ucmOff = $modelOff + $model.Length
$pixelOff = $ucmOff + $ucm.Length
$ms = [System.IO.MemoryStream]::new()
$bw = [System.IO.BinaryWriter]::new($ms)
$bw.Write([byte[]]@(0x49, 0x49, 0x2A, 0x00))
$bw.Write([uint32]8)
$bw.Write([uint16]$entryCount)
foreach ($e in @(
@(254, 4, 1, 0), @(256, 4, 1, $w), @(257, 4, 1, $h),
@(258, 3, 1, 16), @(259, 3, 1, 1), @(262, 3, 1, 32803),
@(271, 2, $make.Length, $makeOff), @(272, 2, $model.Length, $modelOff),
@(273, 4, 1, $pixelOff), @(277, 3, 1, 1), @(278, 4, 1, $h),
@(279, 4, 1, $pixels.Length))) {
$bw.Write([uint16]$e[0]); $bw.Write([uint16]$e[1])
$bw.Write([uint32]$e[2]); $bw.Write([uint32]$e[3])
}
$bw.Write([uint16]33421); $bw.Write([uint16]3); $bw.Write([uint32]2)
$bw.Write([byte[]]@(2, 0, 2, 0))
$bw.Write([uint16]33422); $bw.Write([uint16]1); $bw.Write([uint32]4)
$bw.Write([byte[]]@(0, 1, 1, 2))
$bw.Write([uint16]50706); $bw.Write([uint16]1); $bw.Write([uint32]4)
$bw.Write([byte[]]@(1, 4, 0, 0))
$bw.Write([uint16]50708); $bw.Write([uint16]2)
$bw.Write([uint32]$ucm.Length); $bw.Write([uint32]$ucmOff)
$bw.Write([uint32]0)
$bw.Write($make)
$bw.Write($model)
$bw.Write($ucm)
$bw.Write($pixels)
$bw.Flush()
$dng = Join-Path $env:RUNNER_TEMP "tiny.dng"
[System.IO.File]::WriteAllBytes($dng, $ms.ToArray())

$ppm = Join-Path $env:RUNNER_TEMP "tiny.ppm"
& $exe.FullName $dng $ppm 2>&1 | Out-String | Write-Output
$decodeExit = $LASTEXITCODE
if ($decodeExit -ne 0) {
throw "rawspeed-cli decode smoke test failed: exit=$decodeExit"
}
$ppmBytes = [System.IO.File]::ReadAllBytes($ppm)
$expected = "P6`n$w $h`n65535`n"
$expectedBytes = [System.Text.Encoding]::ASCII.GetBytes($expected)
if ($ppmBytes.Length -ne ($expectedBytes.Length + $w * $h * 6)) {
throw "unexpected PPM size: $($ppmBytes.Length)"
}
for ($i = 0; $i -lt $expectedBytes.Length; $i++) {
if ($ppmBytes[$i] -ne $expectedBytes[$i]) {
throw "unexpected PPM header"
}
}
Write-Output "decode smoke test ok: $dng -> $ppm ($($ppmBytes.Length) bytes)"
exit 0

- name: Stage and checksum
Expand Down
68 changes: 68 additions & 0 deletions docs/rawspeed-cli-v1.0.1-regression-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
rawspeed-cli v1.0.1 regression report
=====================================

Symptom
-------

rawspeed-cli v1.0.1 fails on ARW/CR2/PEF inputs with::

rawspeed: unsupported CFA pattern
exit 1

v1.0.0 decoded the same files.

Root cause
----------

``main.cpp`` only calls ``decoder->decodeRaw()`` and never
``decoder->decodeMetaData()``. For ARW/CR2/PEF the CFA pattern is set
in the metadata pass, not in the raw pass:

- ``ArwDecoder::decodeMetaDataInternal`` (ArwDecoder.cpp:501) calls
``mRaw->cfa.setCFA(2x2, R, G, G, B)`` before consulting the camera
database; the same holds for ``Cr2Decoder`` and ``PefDecoder``.
- ``render()`` then reads ``raw->cfa`` with its default size (0, 0)
and fails at the ``cfaSize != 2x2`` check.

The old v1.0.0 CLI only worked because it unconditionally called
``raw->cfa.setCFA(2x2, R,G,G,B)`` in ``render()``. That call looked
like dead code and was removed during the clang-tidy cleanup - it was
load-bearing.

Fix (v1.0.2)
------------

1. After ``decodeRaw()``, call ``decoder->decodeMetaData(&meta)``.
``cameras.xml`` is resolved like ``rawspeed-identify`` does
(``RS_CAMERAS_XML_PATH``, ``<bindir>/../share/darktable/rawspeed/
cameras.xml``, then ``RAWSPEED_SOURCE_DIR/data/cameras.xml`` for
standalone builds). If it cannot be found or parsed, an empty
``CameraMetaData{}`` is used: ``ArwDecoder`` (and Cr2/Pef) set the
RGGB CFA before consulting the database, and with
``failOnUnknown = false`` an unknown camera only logs a warning and
returns early, so decoding works without ``cameras.xml``. A failing
metadata pass is logged and skipped rather than aborting the
decode.
2. ``render()`` is hardened: if the reported CFA size is smaller than
2x2, it defaults to RGGB instead of failing. Larger CFA patterns
(e.g. X-Trans) still fail as unsupported. The existing 4-Bayer
pattern mapping is kept - it is correct once the CFA is real.
3. The publish workflow's smoke test now decodes a real image (a
synthetic 8x8 uncompressed CFA DNG generated in the runner) and
verifies the PPM output, instead of only checking the usage path
which cannot catch this class of regression.

Verification
------------

- synthetic 8x8 CFA DNG decodes: exit 0, ``P6 8 8 65535``, 397 bytes;
8-bit variant: 203 bytes
- usage path still exits 2 with usage text
- clang-format 18.1.8 idempotent, clang -Weverything -Werror clean,
clang-tidy (CI check set) clean, MSVC Release build green

AI note
-------

This report and the fix were prepared with AI assistance (opencode),
per AGENTS.md disclosure rules.
50 changes: 49 additions & 1 deletion src/utilities/rawspeed-cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#include <cstdint>
#include <cstdio>
#include <exception>
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
Expand All @@ -23,6 +25,27 @@ using rawspeed::CFAColor;

namespace {

std::string find_cameras_xml(const char* argv0) {
#ifdef RS_CAMERAS_XML_PATH
if (std::filesystem::exists(RS_CAMERAS_XML_PATH))
return RS_CAMERAS_XML_PATH;
#endif
const std::string self(argv0);
const std::size_t lastslash = self.find_last_of(R"(/\)");
const std::string bindir = lastslash == std::string::npos
? std::string(".")
: self.substr(0, lastslash);
std::string camfile = bindir + "/../share/darktable/rawspeed/cameras.xml";
if (std::filesystem::exists(camfile))
return camfile;
#ifdef RAWSPEED_STANDALONE_BUILD
camfile = std::string(RAWSPEED_SOURCE_DIR "/data/cameras.xml");
if (std::filesystem::exists(camfile))
return camfile;
#endif
return {};
}

struct DemosaicJob {
rawspeed::Array2DRef<uint16_t> mosaic;
rawspeed::Array1DRef<uint16_t> rgb;
Expand Down Expand Up @@ -121,7 +144,11 @@ bool render(const rawspeed::RawImage& raw, std::vector<uint16_t>& rgbOut,
}

const auto cfaSize = raw->cfa.getSize();
if (cfaSize.x != 2 || cfaSize.y != 2) {
if (cfaSize.x < 2 || cfaSize.y < 2) {
raw->cfa.setCFA(rawspeed::iPoint2D(2, 2), CFAColor::RED, CFAColor::GREEN,
CFAColor::GREEN, CFAColor::BLUE);
}
if (raw->cfa.getSize().x != 2 || raw->cfa.getSize().y != 2) {
std::fprintf(stderr, "rawspeed: unsupported CFA pattern\n");
return false;
}
Expand Down Expand Up @@ -230,6 +257,27 @@ int main(int argc_, char** argv_) {

rawspeed::RawImage raw = decoder->decodeRaw();

auto meta = std::make_unique<rawspeed::CameraMetaData>();
#ifdef HAVE_PUGIXML
const std::string camfile = find_cameras_xml(argv(0));
if (!camfile.empty()) {
try {
meta = std::make_unique<rawspeed::CameraMetaData>(camfile.c_str());
} catch (const std::exception& e) {
std::fprintf(stderr,
"rawspeed: cameras.xml unusable ('%s'), continuing "
"without it\n",
e.what());
}
}
#endif
try {
decoder->decodeMetaData(meta.get());
} catch (const std::exception& e) {
std::fprintf(stderr, "rawspeed: metadata pass failed, continuing: %s\n",
e.what());
}

std::vector<uint16_t> rgb;
uint32_t w = 0;
uint32_t h = 0;
Expand Down
Loading