From edbe9d655a7f303f6013d1e3e1c7c515c5423817 Mon Sep 17 00:00:00 2001 From: Paul Oliver Date: Mon, 27 Jul 2026 12:37:13 +1200 Subject: [PATCH 1/4] feat(sony): add Sony A7R VI (ILCE-7RM6) camera support Camera ID 0x19a (SonyModelID 410), FF 10240/10016 and APS-C 6656/6592 raw-width variants cropping to 9984x6656 / 6528x4352 per Adobe DNG Converter DefaultCropSize. D65 color matrix extracted from Adobe DNG Converter ColorMatrix2 on rawdb.dnglab.org CC0 samples (matches dnglab PR #796). Lossless frames decode via the existing LJpeg path; lossy/HQ "Compressed RAW 2" frames use the ARW6 decoder already on this branch. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S1mAhAaVQjGgRPsXSVVnxp --- internal/libraw_cameraids.h | 1 + src/metadata/identify.cpp | 25 +++++++++++++++++++++++++ src/metadata/normalize_model.cpp | 1 + src/metadata/sony.cpp | 3 +++ src/tables/cameralist.cpp | 1 + src/tables/colordata.cpp | 2 ++ 6 files changed, 33 insertions(+) diff --git a/internal/libraw_cameraids.h b/internal/libraw_cameraids.h index 210c28a3..03a7203d 100644 --- a/internal/libraw_cameraids.h +++ b/internal/libraw_cameraids.h @@ -344,4 +344,5 @@ it under the terms of the one of two licenses as you choose: #define SonyID_ZV_E10M2 0x18fULL #define SonyID_ILME_FX2 0x196ULL #define SonyID_ILCE_7M5 0x197ULL +#define SonyID_ILCE_7RM6 0x19aULL #endif diff --git a/src/metadata/identify.cpp b/src/metadata/identify.cpp index 1bd6545f..4dab56c9 100644 --- a/src/metadata/identify.cpp +++ b/src/metadata/identify.cpp @@ -3005,6 +3005,31 @@ void LibRaw::identify_finetune_dcr(char head[64], INT64 fsize, INT64 flen) imgdata.process_warnings |= LIBRAW_WARN_VENDOR_CROP_SUGGESTED; } + } + else if (unique_id == SonyID_ILCE_7RM6) + { + if (raw_width == 10240) // FF, Lossless compressed + { + width = 9984; + height = 6656; + } + else if (raw_width == 10016) // FF compressed (RAW 2) + { + width = 9984; + height = 6656; + } + else if (raw_width == 6656) // APS-C, Lossless compressed + { + width = 6528; + height = 4352; + } + else if (raw_width == 6592) // APS-C compressed (RAW 2) + { + width = 6528; + height = 4352; + } + else + imgdata.process_warnings |= LIBRAW_WARN_VENDOR_CROP_SUGGESTED; } else if (raw_width == 3984) { // Sony DSC-R1; diff --git a/src/metadata/normalize_model.cpp b/src/metadata/normalize_model.cpp index e852c4a9..5e138298 100644 --- a/src/metadata/normalize_model.cpp +++ b/src/metadata/normalize_model.cpp @@ -399,6 +399,7 @@ void LibRaw::GetNormalizedModel() { SonyID_ZV_E10M2, "ZV-E10M2"}, { SonyID_ILME_FX2, "ILME-FX2"}, { SonyID_ILCE_7M5, "ILCE-7M5"}, + { SonyID_ILCE_7RM6, "ILCE-7RM6"}, }; static const char *orig; diff --git a/src/metadata/sony.cpp b/src/metadata/sony.cpp index 497c477b..3eb52d58 100644 --- a/src/metadata/sony.cpp +++ b/src/metadata/sony.cpp @@ -338,6 +338,8 @@ void LibRaw::setSonyBodyFeatures(unsigned long long id) { LIBRAW_SONY_Tag2010None, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, {SonyID_ILCE_7M5, sbfILCE_FF, LIBRAW_SONY_Tag2010None, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, + {SonyID_ILCE_7RM6, sbfILCE_FF, + LIBRAW_SONY_Tag2010None, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, {SonyID_ILCE_7RM3A, sbfILCE_FF, LIBRAW_SONY_Tag2010i, 0x0320, 0x019f, 0x024b, 0x024c, 0x0208}, {SonyID_ILCE_7RM4A, sbfILCE_FF, @@ -449,6 +451,7 @@ void LibRaw::setSonyBodyFeatures(unsigned long long id) { break; case SonyID_ILME_FX2: case SonyID_ILCE_7M5: + case SonyID_ILCE_7RM6: case SonyID_ZV_E1: case SonyID_ILCE_6700: case SonyID_ILCE_7CR: diff --git a/src/tables/cameralist.cpp b/src/tables/cameralist.cpp index 75bf4f1f..575bf20f 100644 --- a/src/tables/cameralist.cpp +++ b/src/tables/cameralist.cpp @@ -1192,6 +1192,7 @@ static const char *static_camera_list[] = { "Sony ILCE-7RM4 (A7R IV)", "Sony ILCE-7RM4A (A7R IVA)", "Sony ILCE-7RM5 (A7R V)", + "Sony ILCE-7RM6 (A7R VI)", "Sony ILCE-7S (A7S)", "Sony ILCE-7SM2 (A7S II)", "Sony ILCE-7SM3 (A7S III)", diff --git a/src/tables/colordata.cpp b/src/tables/colordata.cpp index 0e38f4cf..c9f349a0 100644 --- a/src/tables/colordata.cpp +++ b/src/tables/colordata.cpp @@ -1747,6 +1747,8 @@ int LibRaw::adobe_coeff(unsigned make_idx, const char *t_model, { LIBRAW_CAMERAMAKER_Sony, "ILCE-7RM5", 0, 0, { 8200, -2976, -719, -4296, 12053, 2532, -429, 1282, 5774 } }, + { LIBRAW_CAMERAMAKER_Sony, "ILCE-7RM6", 0, 0, + { 11765,-5595,-1192,-3689,11507,2485,51,681,5731 } }, { LIBRAW_CAMERAMAKER_Sony, "ILCE-7RM4", 0, 0, // same CMs: ILCE-7RM4, ILCE-7RM4A { 7662, -2686,-660,-5240, 12965,2530, -796, 1508, 6167 } }, { LIBRAW_CAMERAMAKER_Sony, "ILCE-7RM3", 0, 0, // same CMs: ILCE-7RM3, ILCE-7RM3A From 9927835299b0bcd6e0acbc62b99b44d1177312b7 Mon Sep 17 00:00:00 2001 From: Paul Oliver Date: Mon, 27 Jul 2026 13:30:31 +1200 Subject: [PATCH 2/4] fix(sony): derive ARW6 vertical canvas geometry instead of special-casing ARW6 lays each tile's component rows on a shared vertical canvas whose origin (comp_top) varies with the tile height modulo 16 and drives every sub-band's per-chunk row windows and the per-level IDWT merge parity. The decoder hard-coded one aligned layout (comp_top 8) plus a guarded path that was only right for some non-aligned heights; ILCE-7RM6 APS-C frames (4372 rows, comp_top 7) decoded with globally wrong detail. Replace the per-case synthesis functions with computed lattice spans, a general slot extractor (chunk rows fill fixed record slots sequentially in HL/LH/HH order, so partial chunks shift rows across the linear planes; partial-window HH rows are stored at half amplitude), and a parity-aware vertical 5/3 lift. Validated bit-identical against Adobe DNG Converter raw output on all 12 ILCE-7RM6 rawdb samples (FF + APS-C x lossless/lossy/HQ x both orientations) and all 4 ILCE-7M5 pixls.us ARW6/LJpeg samples (no change to previously exact files). Canvas model cross-checked against the format description in abbradar/arw6_decode (LibRaw#828). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S1mAhAaVQjGgRPsXSVVnxp --- src/decoders/sony_arw6.cpp | 587 ++++++++++++++----------------------- 1 file changed, 214 insertions(+), 373 deletions(-) diff --git a/src/decoders/sony_arw6.cpp b/src/decoders/sony_arw6.cpp index db168c5a..b442616a 100644 --- a/src/decoders/sony_arw6.cpp +++ b/src/decoders/sony_arw6.cpp @@ -798,6 +798,63 @@ static int sony_arw6_expected_packet_rows(int group, int coded_height) return (padded_height / 16 + 1) * sony_arw6_row_multiplier(group); } +/* ---- ARW6 vertical canvas geometry ---- + + A tile's component rows (comp_height = tile height / 2) sit on a shared + 1-D "canvas" that is split into chunks of SONY_ARW6_CHUNK_ROWS canvas + rows starting at SONY_ARW6_CHUNK_ORIGIN. Every sub-band occupies a + lattice — a strided subset of canvas rows (step = canvas rows per lattice + row; even lattices at phase 0, odd ones at phase step/2). A chunk stores + each orientation's in-image lattice rows in orientation order (HL, LH, + HH), filling the record's fixed plane slots sequentially, so a partial + first/last chunk shifts rows across the linear planes. None of this is + stored in the file; it all derives from comp_height. Cross-validated + bit-exactly against Adobe DNG Converter output; the layout matches the + reverse-engineered format description in abbradar/arw6_decode. */ + +#define SONY_ARW6_CHUNK_ROWS 8 /* canvas rows per chunk (4 levels) */ +#define SONY_ARW6_CHUNK_ORIGIN 4 /* canvas row where chunk 0 begins */ + +static inline int sony_arw6_ceil_div(int a, int b) +{ + return (a + b - 1) / b; /* a >= 0, b > 0 */ +} + +/* First data row on the canvas: the chunk origin plus the offset that makes + the first and last chunks symmetric. */ +static int sony_arw6_comp_top(int comp_height) +{ + const int m = (comp_height / 2) % SONY_ARW6_CHUNK_ROWS; + return SONY_ARW6_CHUNK_ORIGIN + + (SONY_ARW6_CHUNK_ROWS - m) % SONY_ARW6_CHUNK_ROWS; +} + +static int sony_arw6_chunk_count(int comp_height) +{ + return sony_arw6_ceil_div(sony_arw6_comp_top(comp_height) + comp_height - + SONY_ARW6_CHUNK_ORIGIN, + SONY_ARW6_CHUNK_ROWS); +} + +struct SonyArw6Span +{ + int top; /* first in-image lattice row */ + int bottom; /* one past the last in-image lattice row */ + int base; /* lattice row at/after the chunk-0 origin */ + int rpc; /* lattice rows per chunk */ +}; + +static SonyArw6Span sony_arw6_make_span(int comp_top, int comp_height, + int step, int phase) +{ + SonyArw6Span s; + s.top = sony_arw6_ceil_div(comp_top - phase, step); + s.bottom = sony_arw6_ceil_div(comp_top + comp_height - phase, step); + s.base = sony_arw6_ceil_div(SONY_ARW6_CHUNK_ORIGIN - phase, step); + s.rpc = SONY_ARW6_CHUNK_ROWS / step; + return s; +} + static std::vector sony_arw6_parse_directory(const uchar *stream, uint32_t stream_size) { @@ -934,9 +991,9 @@ sony_arw6_decode_packet_arrays(const uchar *stream, uint32_t stream_size, const int expected_rows = sony_arw6_expected_packet_rows(group, coded_height); const int groups_per_row = (row_width + 3) / 4; sony_arw6_require(row_width > 0 && groups_per_row > 0 && rows > 0); - if (rows != expected_rows) - sony_arw6_require((coded_height & 15) && - expected_rows - rows == row_multiplier); + sony_arw6_require((coded_height & 1) == 0 && + p.block_count == sony_arw6_chunk_count(coded_height / 2)); + sony_arw6_require(rows <= expected_rows); sony_arw6_require(size_t(p.block_count) <= (stream_size / 16U + 1U)); /* weak corruption guard */ @@ -994,32 +1051,6 @@ static SonyArw6Plane sony_arw6_head_rows(const SonyArw6Plane &src, int rows) return out; } -static SonyArw6Plane sony_arw6_inv53_axis0(const SonyArw6Plane &low, - const SonyArw6Plane &high) -{ - sony_arw6_require(low.rows == high.rows && low.cols == high.cols); - SonyArw6Plane lo2(low.rows, low.cols); - for (int y = 0; y < low.rows; y++) - for (int x = 0; x < low.cols; x++) - { - const int32_t hp = high.at(y ? y - 1 : 0, x); - const int32_t hc = high.at(y, x); - lo2.at(y, x) = low.at(y, x) - sony_arw6_floor_shift(hp + hc + 2, 2); - } - - SonyArw6Plane out(low.rows * 2, low.cols); - for (int y = 0; y < low.rows; y++) - for (int x = 0; x < low.cols; x++) - { - const int32_t ln = lo2.at(y + 1 < low.rows ? y + 1 : y, x); - const int32_t hi2 = - high.at(y, x) + sony_arw6_floor_shift(lo2.at(y, x) + ln, 1); - out.at(y * 2, x) = lo2.at(y, x); - out.at(y * 2 + 1, x) = hi2; - } - return out; -} - static SonyArw6Plane sony_arw6_inv53_axis1(const SonyArw6Plane &low, const SonyArw6Plane &high) { @@ -1046,265 +1077,136 @@ static SonyArw6Plane sony_arw6_inv53_axis1(const SonyArw6Plane &low, return out; } -static SonyArw6Plane sony_arw6_inv53_axis0_high_leading( - const SonyArw6Plane &low, const SonyArw6Plane &high) +/* Copy orientation `oi`'s in-image lattice rows out of the fixed per-record + plane slots (slot s of chunk c = plane s/rm, plane row c*rm + s%rm). A + chunk's stored rows fill the slots sequentially in orientation order, so + partial first/last chunks shift rows across the linear planes. HH rows in + a partial chunk window are stored at half amplitude; `hh_edge` restores + them via sony_arw6_edge_detail(). */ +static SonyArw6Plane sony_arw6_extract_orientation( + const std::vector &planes, int row_multiplier, + int n_chunks, const SonyArw6Span *spans, int n_orient, int oi, + bool hh_edge, bool odd_edge_mode) { - sony_arw6_require(low.cols == high.cols); - sony_arw6_require(high.rows == low.rows || high.rows == low.rows + 1); - SonyArw6Plane lo2(low.rows, low.cols); - SonyArw6Plane hi2(high.rows, high.cols); - - if (high.rows == low.rows + 1) + const SonyArw6Span &sp = spans[oi]; + const int count = sp.bottom - sp.top; + sony_arw6_require(count > 0 && !planes.empty()); + const int cols = planes[0].cols; + const int slots_per_rec = int(planes.size()) * row_multiplier; + SonyArw6Plane out(count, cols); + for (int c = 0; c < n_chunks; c++) { - for (int y = 0; y < low.rows; y++) - for (int x = 0; x < low.cols; x++) - lo2.at(y, x) = - low.at(y, x) - - sony_arw6_floor_shift(high.at(y, x) + high.at(y + 1, x) + 2, 2); - - for (int x = 0; x < high.cols; x++) + int slot = 0; + for (int o = 0; o < n_orient; o++) { - hi2.at(0, x) = high.at(0, x) + lo2.at(0, x); - hi2.at(high.rows - 1, x) = high.at(high.rows - 1, x) + - lo2.at(low.rows - 1, x); - } - for (int y = 1; y < high.rows - 1; y++) - for (int x = 0; x < high.cols; x++) - hi2.at(y, x) = high.at(y, x) + - sony_arw6_floor_shift(lo2.at(y - 1, x) + - lo2.at(y, x), - 1); - - SonyArw6Plane out(low.rows * 2 + 1, low.cols); - for (int y = 0; y < low.rows; y++) - { - memcpy(out.row(y * 2), hi2.row(y), sizeof(int32_t) * size_t(low.cols)); - memcpy(out.row(y * 2 + 1), lo2.row(y), - sizeof(int32_t) * size_t(low.cols)); - } - memcpy(out.row(out.rows - 1), hi2.row(hi2.rows - 1), - sizeof(int32_t) * size_t(low.cols)); - return out; - } - - for (int y = 0; y < low.rows; y++) - for (int x = 0; x < low.cols; x++) - { - const int next_y = y + 1 < high.rows ? y + 1 : y; - lo2.at(y, x) = - low.at(y, x) - - sony_arw6_floor_shift(high.at(y, x) + high.at(next_y, x) + 2, 2); + const SonyArw6Span &osp = spans[o]; + int lo = osp.base + c * osp.rpc; + int hi = lo + osp.rpc; + if (lo < osp.top) + lo = osp.top; + if (hi > osp.bottom) + hi = osp.bottom; + const int window = hi - lo; + if (window <= 0) + continue; + if (o == oi) + { + const bool partial = window < osp.rpc; + for (int r = 0; r < window; r++) + { + const int s = slot + r; + sony_arw6_require(s < slots_per_rec); + const int plane_row = c * row_multiplier + s % row_multiplier; + sony_arw6_require(plane_row < planes[s / row_multiplier].rows); + const int32_t *src = planes[s / row_multiplier].row(plane_row); + int32_t *dst = out.row(lo + r - sp.top); + if (hh_edge && partial) + for (int x = 0; x < cols; x++) + dst[x] = sony_arw6_edge_detail(src[x], odd_edge_mode); + else + memcpy(dst, src, sizeof(int32_t) * size_t(cols)); + } + } + slot += window; } - for (int x = 0; x < high.cols; x++) - hi2.at(0, x) = high.at(0, x) + lo2.at(0, x); - for (int y = 1; y < high.rows; y++) - for (int x = 0; x < high.cols; x++) - hi2.at(y, x) = - high.at(y, x) + - sony_arw6_floor_shift(lo2.at(y - 1, x) + lo2.at(y, x), 1); - - SonyArw6Plane out(low.rows * 2, low.cols); - for (int y = 0; y < low.rows; y++) - { - memcpy(out.row(y * 2), hi2.row(y), sizeof(int32_t) * size_t(low.cols)); - memcpy(out.row(y * 2 + 1), lo2.row(y), - sizeof(int32_t) * size_t(low.cols)); } return out; } -static SonyArw6Plane sony_arw6_synthesize_level(const SonyArw6Plane &ll, - const SonyArw6Plane &sub0, - const SonyArw6Plane &sub1, - const SonyArw6Plane &sub2) +static inline int sony_arw6_clamp_index(int i, int n) { - const int h = ll.rows; - const int w = ll.cols; - sony_arw6_require(sub0.cols == w && sub1.cols == w && sub2.cols == w); - sony_arw6_require(sub0.rows >= h + 1 && sub1.rows >= h + 1 && - sub2.rows >= h + 1); - SonyArw6Plane lh(h, w), hh(h, w); - for (int y = 0; y < h - 1; y++) - for (int x = 0; x < w; x++) - { - lh.at(y, x) = sub1.at(y + 1, x); - hh.at(y, x) = sub2.at(y + 1, x); - } - for (int x = 0; x < w; x++) - { - lh.at(h - 1, x) = sub0.at(h, x); - hh.at(h - 1, x) = sub1.at(h, x); - } - SonyArw6Plane low_horizontal = sony_arw6_inv53_axis0(ll, lh); - SonyArw6Plane high_horizontal = - sony_arw6_inv53_axis0(sony_arw6_head_rows(sub0, h), hh); - return sony_arw6_inv53_axis1(low_horizontal, high_horizontal); + return i < 0 ? 0 : (i >= n ? n - 1 : i); } -static SonyArw6Plane sony_arw6_synthesize_guard_group1( - const SonyArw6Plane &ll, const SonyArw6Plane &sub0, - const SonyArw6Plane &sub1, const SonyArw6Plane &sub2) +/* One vertical inverse 5/3 lift interleaving the coarse and detail + sub-bands. `coarse_odd` puts the coarse samples on the odd output rows (the + parity of the merged span's top canvas coordinate). Row counts may differ + by one; a trailing sample past the grid edge is dropped. */ +static SonyArw6Plane sony_arw6_idwt53_vert(const SonyArw6Plane &low, + const SonyArw6Plane &high, + bool coarse_odd) { - const int h = ll.rows; - const int w = ll.cols; - sony_arw6_require(sub0.cols == w && sub1.cols == w && sub2.cols == w); - sony_arw6_require(sub0.rows >= h + 2 && sub1.rows >= h + 2 && - sub2.rows >= h + 2); - - SonyArw6Plane lh(h + 1, w), hh(h + 1, w), hl(h, w); - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - { - hl.at(y, x) = sub0.at(y + 1, x); - lh.at(y, x) = sub1.at(y + 1, x); - hh.at(y, x) = sub2.at(y + 1, x); - } - for (int x = 0; x < w; x++) + const int n_low = low.rows; + const int n_high = high.rows; + const int n_out = n_low + n_high; + sony_arw6_require(n_low > 0 && n_high > 0 && low.cols == high.cols && + n_low - n_high <= 1 && n_high - n_low <= 1); + const int w = low.cols; + const int c = coarse_odd ? 1 : 0; + SonyArw6Plane out(n_out, w); + for (int n = 0; n < n_low; n++) { - lh.at(h, x) = sub0.at(h + 1, x); - hh.at(h, x) = sub1.at(h + 1, x); + const int pos = 2 * n + c; + if (pos >= n_out) + continue; + const int h0 = sony_arw6_clamp_index(n - 1 + c, n_high); + const int h1 = sony_arw6_clamp_index(n + c, n_high); + for (int x = 0; x < w; x++) + out.at(pos, x) = + low.at(n, x) - + sony_arw6_floor_shift(high.at(h0, x) + high.at(h1, x) + 2, 2); } - - SonyArw6Plane low_horizontal = - sony_arw6_inv53_axis0_high_leading(ll, lh); - SonyArw6Plane high_horizontal = - sony_arw6_inv53_axis0_high_leading(hl, hh); - return sony_arw6_inv53_axis1(low_horizontal, high_horizontal); -} - -static SonyArw6Plane sony_arw6_synthesize_guard_group2( - const SonyArw6Plane &ll, const SonyArw6Plane &sub0, - const SonyArw6Plane &sub1, const SonyArw6Plane &sub2, - bool odd_edge_mode) -{ - const int h = ll.rows; - const int w = ll.cols; - sony_arw6_require(sub0.cols == w && sub1.cols == w && sub2.cols == w); - sony_arw6_require(sub0.rows >= h + 3 && sub1.rows >= h + 1 && - sub2.rows >= h + 1); - - SonyArw6Plane hl(h, w), lh(h, w), hh(h, w); - for (int x = 0; x < w; x++) + for (int n = 0; n < n_high; n++) { - lh.at(0, x) = sub0.at(0, x); - hh.at(0, x) = sony_arw6_edge_detail(sub0.at(1, x), odd_edge_mode); - } - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - hl.at(y, x) = sub0.at(y + 2, x); - for (int y = 1; y < h; y++) + const int pos = 2 * n + 1 - c; + if (pos >= n_out) + continue; + const int e0 = 2 * sony_arw6_clamp_index(n - c, n_low) + c; + const int e1 = 2 * sony_arw6_clamp_index(n + 1 - c, n_low) + c; for (int x = 0; x < w; x++) - { - lh.at(y, x) = sub1.at(y + 1, x); - hh.at(y, x) = sub2.at(y + 1, x); - } - - SonyArw6Plane low_horizontal = - sony_arw6_inv53_axis0_high_leading(ll, lh); - SonyArw6Plane high_horizontal = - sony_arw6_inv53_axis0_high_leading(hl, hh); - return sony_arw6_inv53_axis1(low_horizontal, high_horizontal); -} - -static SonyArw6Plane sony_arw6_synthesize_guard_group3( - const SonyArw6Plane &ll, const SonyArw6Plane &sub0, - const SonyArw6Plane &sub1, const SonyArw6Plane &sub2, - bool odd_edge_mode) -{ - const int h = ll.rows; - const int w = ll.cols; - sony_arw6_require(sub0.cols == w && sub1.cols == w && sub2.cols == w); - sony_arw6_require(sub0.rows >= h + 5 && sub1.rows >= h + 2 && - sub2.rows >= h + 2); - - SonyArw6Plane hl(h, w), lh(h, w), hh(h, w); - for (int x = 0; x < w; x++) - { - hl.at(0, x) = sub0.at(0, x); - lh.at(0, x) = sub0.at(1, x); - hh.at(0, x) = sony_arw6_edge_detail(sub0.at(2, x), odd_edge_mode); - lh.at(h - 1, x) = sub0.at(h + 3, x); - hh.at(h - 1, x) = - sony_arw6_edge_detail(sub0.at(h + 4, x), odd_edge_mode); + out.at(pos, x) = + high.at(n, x) + + sony_arw6_floor_shift(out.at(e0, x) + out.at(e1, x), 1); } - for (int y = 1; y < h; y++) - for (int x = 0; x < w; x++) - hl.at(y, x) = sub0.at(y + 3, x); - for (int y = 1; y < h - 1; y++) - for (int x = 0; x < w; x++) - { - lh.at(y, x) = sub1.at(y + 3, x); - hh.at(y, x) = sub2.at(y + 3, x); - } - - SonyArw6Plane low_horizontal = sony_arw6_inv53_axis0(ll, lh); - SonyArw6Plane high_horizontal = sony_arw6_inv53_axis0(hl, hh); - return sony_arw6_inv53_axis1(low_horizontal, high_horizontal); + return out; } -static SonyArw6Plane sony_arw6_synthesize_level_stride( - const SonyArw6Plane &ll, const SonyArw6Plane &sub0, - const SonyArw6Plane &sub1, const SonyArw6Plane &sub2, int edge_rows, - bool odd_edge_mode) +/* One inverse 2-D level: vertical lifts of the LL|LH and HL|HH column pairs + at the level's parity, then the horizontal lift (always standard phase). */ +static SonyArw6Plane sony_arw6_merge_level(const SonyArw6Plane &ll, + const SonyArw6Plane &hl, + const SonyArw6Plane &lh, + const SonyArw6Plane &hh, bool coarse_odd) { - if (edge_rows == 0) - return sony_arw6_synthesize_level(ll, sub0, sub1, sub2); - - const int h = ll.rows; - const int w = ll.cols; - sony_arw6_require(sub0.cols == w && sub1.cols == w && sub2.cols == w); - sony_arw6_require(sub0.rows >= h + edge_rows * 2 && - sub1.rows >= h + edge_rows && - sub2.rows >= h); - - SonyArw6Plane hl(h, w), lh(h, w), hh(h, w); - for (int y = 0; y < edge_rows; y++) - for (int x = 0; x < w; x++) - { - hl.at(y, x) = sub0.at(y, x); - lh.at(y, x) = sub0.at(y + edge_rows, x); - hh.at(y, x) = sony_arw6_edge_detail(sub1.at(y, x), odd_edge_mode); - } - for (int y = edge_rows; y < h - edge_rows; y++) - for (int x = 0; x < w; x++) - { - hl.at(y, x) = sub0.at(y + edge_rows, x); - lh.at(y, x) = sub1.at(y + edge_rows, x); - hh.at(y, x) = sub2.at(y + edge_rows, x); - } - for (int y = h - edge_rows; y < h; y++) - { - const int k = y - (h - edge_rows); - for (int x = 0; x < w; x++) - { - hl.at(y, x) = sub0.at(h + k, x); - lh.at(y, x) = sub0.at(h + edge_rows + k, x); - hh.at(y, x) = sony_arw6_edge_detail(sub1.at(h + k, x), odd_edge_mode); - } - } - - SonyArw6Plane low_horizontal = sony_arw6_inv53_axis0(ll, lh); - SonyArw6Plane high_horizontal = sony_arw6_inv53_axis0(hl, hh); - return sony_arw6_inv53_axis1(low_horizontal, high_horizontal); + SonyArw6Plane left = sony_arw6_idwt53_vert(ll, lh, coarse_odd); + SonyArw6Plane right = sony_arw6_idwt53_vert(hl, hh, coarse_odd); + const int rows = std::min(left.rows, right.rows); + if (left.rows != rows) + left = sony_arw6_head_rows(left, rows); + if (right.rows != rows) + right = sony_arw6_head_rows(right, rows); + return sony_arw6_inv53_axis1(left, right); } +/* Recombine the merged green_lo with the extracted green_hi rows: the + always-flipped horizontal green split (green_hi lands on the even output + columns). */ static SonyArw6Plane sony_arw6_final_green(const SonyArw6Plane &ll, - const SonyArw6Plane &detail, - int top_rows) + const SonyArw6Plane &selected) { const int h = ll.rows; const int w = ll.cols; - sony_arw6_require(top_rows >= 0 && top_rows <= 8); - sony_arw6_require(detail.cols == w && - detail.rows >= 8 + std::max(0, h - top_rows)); - - SonyArw6Plane selected(h, w); - for (int y = 0; y < h; y++) - { - const int src_y = y < top_rows ? y : y + 8 - top_rows; - for (int x = 0; x < w; x++) - selected.at(y, x) = detail.at(src_y, x); - } + sony_arw6_require(selected.cols == w && selected.rows >= h); SonyArw6Plane odd_green(h, w); for (int y = 0; y < h; y++) @@ -1391,131 +1293,70 @@ static SonyArw6DecodedTile sony_arw6_decode_stream_tile(const uchar *stream, const std::vector dir = sony_arw6_parse_directory(stream, stream_size); const int coded_height = header.logical_height; - sony_arw6_require(header.coded_width > 0 && coded_height > 0); - const int padded_height = sony_arw6_align_up(coded_height, 16); - const bool guarded_height = coded_height != padded_height; - const int low_rows = padded_height / 16; - const int low_start = guarded_height ? 1 : 0; - const int low_count = low_rows - low_start; - - std::vector g0 = - sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, 0, 0); - SonyArw6Plane green = - sony_arw6_integrate_type1(g0[0], low_start + low_count, 0); - if (low_start) - { - SonyArw6Plane cropped(low_count, green.cols); - for (int y = 0; y < low_count; y++) - memcpy(cropped.row(y), green.row(y + low_start), - sizeof(int32_t) * size_t(green.cols)); - green = cropped; - } - - std::vector r0 = - sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, 0, 1); - SonyArw6Plane red_residual = - sony_arw6_integrate_type1(r0[0], low_start + low_count, 0); - if (low_start) + sony_arw6_require(header.coded_width > 0 && coded_height > 0 && + (coded_height & 1) == 0); + const int comp_height = coded_height / 2; + const int comp_top = sony_arw6_comp_top(comp_height); + const int n_chunks = sony_arw6_chunk_count(comp_height); + + /* components 0..2 = green_lo, chroma_r, chroma_b */ + const SonyArw6Span ll_span = sony_arw6_make_span(comp_top, comp_height, 8, 0); + SonyArw6Plane comps[3]; + for (int comp = 0; comp < 3; comp++) { - SonyArw6Plane cropped(low_count, red_residual.cols); - for (int y = 0; y < low_count; y++) - memcpy(cropped.row(y), red_residual.row(y + low_start), - sizeof(int32_t) * size_t(red_residual.cols)); - red_residual = cropped; - } - - std::vector b0 = - sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, 0, 2); - SonyArw6Plane blue_residual = - sony_arw6_integrate_type1(b0[0], low_start + low_count, 0); - if (low_start) - { - SonyArw6Plane cropped(low_count, blue_residual.cols); - for (int y = 0; y < low_count; y++) - memcpy(cropped.row(y), blue_residual.row(y + low_start), - sizeof(int32_t) * size_t(blue_residual.cols)); - blue_residual = cropped; + std::vector p = + sony_arw6_decode_packet_arrays(stream, stream_size, dir, + header.coded_width, coded_height, 0, + comp); + SonyArw6Plane ll = sony_arw6_extract_orientation(p, 1, n_chunks, &ll_span, + 1, 0, false, false); + comps[comp] = sony_arw6_integrate_type1(ll, ll.rows, 0); } + /* detail groups, coarsest first */ for (int group = 1; group <= 3; group++) { - const int edge_rows = group == 1 ? 0 : (group == 2 ? 1 : 2); - const bool color_odd_edge = group == 3; - - std::vector planes = - sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, group, - 0); - if (guarded_height) - { - if (group == 1) - green = sony_arw6_synthesize_guard_group1(green, planes[0], - planes[1], planes[2]); - else if (group == 2) - green = sony_arw6_synthesize_guard_group2(green, planes[0], - planes[1], planes[2], false); - else - green = sony_arw6_synthesize_guard_group3(green, planes[0], - planes[1], planes[2], false); - } - else - green = sony_arw6_synthesize_level_stride(green, planes[0], planes[1], - planes[2], edge_rows, false); - - planes = sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, - group, 1); - if (guarded_height) + const int step = 8 >> (group - 1); + const int rm = sony_arw6_row_multiplier(group); + SonyArw6Span spans[3]; + spans[0] = sony_arw6_make_span(comp_top, comp_height, step, 0); /* HL */ + spans[1] = + sony_arw6_make_span(comp_top, comp_height, step, step / 2); /* LH */ + spans[2] = spans[1]; /* HH */ + /* The merged span's top canvas coordinate decides which output parity + the coarse rows land on. */ + const bool coarse_odd = (sony_arw6_ceil_div(comp_top, step / 2) & 1) != 0; + + for (int comp = 0; comp < 3; comp++) { - if (group == 1) - red_residual = sony_arw6_synthesize_guard_group1( - red_residual, planes[0], planes[1], planes[2]); - else if (group == 2) - red_residual = sony_arw6_synthesize_guard_group2( - red_residual, planes[0], planes[1], planes[2], false); - else - red_residual = sony_arw6_synthesize_guard_group3( - red_residual, planes[0], planes[1], planes[2], color_odd_edge); - } - else - red_residual = sony_arw6_synthesize_level_stride( - red_residual, planes[0], planes[1], planes[2], edge_rows, - color_odd_edge); - - planes = sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, - group, 2); - if (guarded_height) - { - if (group == 1) - blue_residual = sony_arw6_synthesize_guard_group1( - blue_residual, planes[0], planes[1], planes[2]); - else if (group == 2) - blue_residual = sony_arw6_synthesize_guard_group2( - blue_residual, planes[0], planes[1], planes[2], false); - else - blue_residual = sony_arw6_synthesize_guard_group3( - blue_residual, planes[0], planes[1], planes[2], color_odd_edge); + const bool odd_edge = group == 3 && comp != 0; + std::vector p = + sony_arw6_decode_packet_arrays(stream, stream_size, dir, + header.coded_width, coded_height, + group, comp); + SonyArw6Plane hl = sony_arw6_extract_orientation(p, rm, n_chunks, spans, + 3, 0, false, false); + SonyArw6Plane lh = sony_arw6_extract_orientation(p, rm, n_chunks, spans, + 3, 1, false, false); + SonyArw6Plane hh = sony_arw6_extract_orientation(p, rm, n_chunks, spans, + 3, 2, true, odd_edge); + comps[comp] = sony_arw6_merge_level(comps[comp], hl, lh, hh, coarse_odd); } - else - blue_residual = sony_arw6_synthesize_level_stride( - blue_residual, planes[0], planes[1], planes[2], edge_rows, - color_odd_edge); } + const SonyArw6Span hi_span = sony_arw6_make_span(comp_top, comp_height, 1, 0); std::vector g4 = sony_arw6_decode_packet_arrays(stream, stream_size, dir, header.coded_width, coded_height, 4, 0); - SonyArw6Plane full_green = - sony_arw6_final_green(green, g4[0], guarded_height ? 2 : 4); + SonyArw6Plane green_hi = sony_arw6_extract_orientation(g4, 8, n_chunks, + &hi_span, 1, 0, + false, false); + SonyArw6Plane full_green = sony_arw6_final_green(comps[0], green_hi); SonyArw6DecodedTile out; - out.green = green; - out.red_residual = red_residual; - out.blue_residual = blue_residual; + out.green = comps[0]; + out.red_residual = comps[1]; + out.blue_residual = comps[2]; out.full_green = full_green; return out; } From 5e832a4efb2239327522dfc8fe74e781438725c5 Mon Sep 17 00:00:00 2001 From: Paul Oliver Date: Mon, 27 Jul 2026 14:21:31 +1200 Subject: [PATCH 3/4] refactor(sony): tighten ARW6 decoder after geometry rewrite Post-rewrite cleanup, no decoded-byte changes (re-validated bit-exact against Adobe DNG Converter on all 16 A7R VI + A7 V sample pairs): - size packet planes to the validated block count and drop the vestigial padded-height row formula (sony_arw6_expected_packet_rows) and sony_arw6_align_up - pass the tile's chunk count into decode_packet_arrays instead of re-deriving geometry per packet; take comp_height straight from the stream header - drop integrate_type1's constant rows/dc_offset parameters and the extractor's row-multiplier parameter (always the span's rpc) - move the four result planes instead of deep-copying (~340 MB per full-frame tile) - const ints for the canvas constants, std::max/min window clamps Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S1mAhAaVQjGgRPsXSVVnxp --- src/decoders/sony_arw6.cpp | 98 +++++++++++++++----------------------- 1 file changed, 39 insertions(+), 59 deletions(-) diff --git a/src/decoders/sony_arw6.cpp b/src/decoders/sony_arw6.cpp index b442616a..eedce605 100644 --- a/src/decoders/sony_arw6.cpp +++ b/src/decoders/sony_arw6.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -26,11 +27,6 @@ const int SONY_ARW6_INTERNAL_BIAS = 2048; const size_t SONY_ARW6_MAX_PLANE_SAMPLES = size_t(128) * 1024 * 1024; const INT64 SONY_ARW6_WORKING_BYTES_PER_TILE_PIXEL = 24; -static inline int sony_arw6_align_up(int value, int multiple) -{ - return ((value + multiple - 1) / multiple) * multiple; -} - static inline INT64 sony_arw6_memory_limit_bytes(unsigned max_raw_memory_mb) { return INT64(max_raw_memory_mb) * INT64(1024 * 1024); @@ -790,14 +786,6 @@ static int sony_arw6_infer_packet_width(int group, int packet_type, throw LIBRAW_EXCEPTION_IO_CORRUPT; } -static int sony_arw6_expected_packet_rows(int group, int coded_height) -{ - sony_arw6_require(group >= 0 && group <= 4); - sony_arw6_require(coded_height > 0 && coded_height <= 0x7fff); - const int padded_height = sony_arw6_align_up(coded_height, 16); - return (padded_height / 16 + 1) * sony_arw6_row_multiplier(group); -} - /* ---- ARW6 vertical canvas geometry ---- A tile's component rows (comp_height = tile height / 2) sit on a shared @@ -812,8 +800,8 @@ static int sony_arw6_expected_packet_rows(int group, int coded_height) bit-exactly against Adobe DNG Converter output; the layout matches the reverse-engineered format description in abbradar/arw6_decode. */ -#define SONY_ARW6_CHUNK_ROWS 8 /* canvas rows per chunk (4 levels) */ -#define SONY_ARW6_CHUNK_ORIGIN 4 /* canvas row where chunk 0 begins */ +static const int SONY_ARW6_CHUNK_ROWS = 8; /* canvas rows per chunk (4 levels) */ +static const int SONY_ARW6_CHUNK_ORIGIN = 4; /* canvas row where chunk 0 begins */ static inline int sony_arw6_ceil_div(int a, int b) { @@ -979,7 +967,7 @@ static SonyArw6Packet sony_arw6_parse_packet(const uchar *data, uint32_t length) static std::vector sony_arw6_decode_packet_arrays(const uchar *stream, uint32_t stream_size, const std::vector &dir, - int mosaic_width, int coded_height, int group, + int mosaic_width, int n_blocks, int group, int index) { const SonyArw6DirectoryEntry e = sony_arw6_find_entry(dir, group, index); @@ -988,19 +976,16 @@ sony_arw6_decode_packet_arrays(const uchar *stream, uint32_t stream_size, const int row_width = sony_arw6_infer_packet_width(group, p.type, mosaic_width); const int row_multiplier = sony_arw6_row_multiplier(group); const int rows = p.block_count * row_multiplier; - const int expected_rows = sony_arw6_expected_packet_rows(group, coded_height); const int groups_per_row = (row_width + 3) / 4; sony_arw6_require(row_width > 0 && groups_per_row > 0 && rows > 0); - sony_arw6_require((coded_height & 1) == 0 && - p.block_count == sony_arw6_chunk_count(coded_height / 2)); - sony_arw6_require(rows <= expected_rows); + sony_arw6_require(p.block_count == n_blocks); sony_arw6_require(size_t(p.block_count) <= (stream_size / 16U + 1U)); /* weak corruption guard */ std::vector planes; planes.reserve(components); for (int c = 0; c < components; c++) - planes.push_back(SonyArw6Plane(expected_rows, row_width)); + planes.push_back(SonyArw6Plane(rows, row_width)); for (int ri = 0; ri < p.block_count; ri++) { @@ -1023,19 +1008,18 @@ sony_arw6_decode_packet_arrays(const uchar *stream, uint32_t stream_size, return planes; } -static SonyArw6Plane sony_arw6_integrate_type1(const SonyArw6Plane &coeffs, - int rows, int dc_offset) +static SonyArw6Plane sony_arw6_integrate_type1(const SonyArw6Plane &coeffs) { - sony_arw6_require(rows > 0 && rows <= coeffs.rows); - SonyArw6Plane out(rows, coeffs.cols); - for (int y = 0; y < rows; y++) + sony_arw6_require(coeffs.rows > 0); + SonyArw6Plane out(coeffs.rows, coeffs.cols); + for (int y = 0; y < coeffs.rows; y++) { int32_t acc = sony_arw6_sign16(coeffs.at(y, 0)) * 2; - out.at(y, 0) = acc / 2 + dc_offset; + out.at(y, 0) = acc / 2; for (int x = 1; x < coeffs.cols; x++) { acc += sony_arw6_sign16(coeffs.at(y, x)) * 2; - out.at(y, x) = acc / 2 + dc_offset; + out.at(y, x) = acc / 2; } } return out; @@ -1084,11 +1068,14 @@ static SonyArw6Plane sony_arw6_inv53_axis1(const SonyArw6Plane &low, a partial chunk window are stored at half amplitude; `hh_edge` restores them via sony_arw6_edge_detail(). */ static SonyArw6Plane sony_arw6_extract_orientation( - const std::vector &planes, int row_multiplier, - int n_chunks, const SonyArw6Span *spans, int n_orient, int oi, - bool hh_edge, bool odd_edge_mode) + const std::vector &planes, int n_chunks, + const SonyArw6Span *spans, int n_orient, int oi, bool hh_edge, + bool odd_edge_mode) { const SonyArw6Span &sp = spans[oi]; + /* Records hold rpc rows per plane slot; every span in a packet shares it + (step * rpc == SONY_ARW6_CHUNK_ROWS). */ + const int row_multiplier = sp.rpc; const int count = sp.bottom - sp.top; sony_arw6_require(count > 0 && !planes.empty()); const int cols = planes[0].cols; @@ -1100,12 +1087,8 @@ static SonyArw6Plane sony_arw6_extract_orientation( for (int o = 0; o < n_orient; o++) { const SonyArw6Span &osp = spans[o]; - int lo = osp.base + c * osp.rpc; - int hi = lo + osp.rpc; - if (lo < osp.top) - lo = osp.top; - if (hi > osp.bottom) - hi = osp.bottom; + const int lo = std::max(osp.base + c * osp.rpc, osp.top); + const int hi = std::min(osp.base + (c + 1) * osp.rpc, osp.bottom); const int window = hi - lo; if (window <= 0) continue; @@ -1292,10 +1275,8 @@ static SonyArw6DecodedTile sony_arw6_decode_stream_tile(const uchar *stream, sony_arw6_require(sony_arw6_parse_stream_header(stream, stream_size, header)); const std::vector dir = sony_arw6_parse_directory(stream, stream_size); - const int coded_height = header.logical_height; - sony_arw6_require(header.coded_width > 0 && coded_height > 0 && - (coded_height & 1) == 0); - const int comp_height = coded_height / 2; + const int comp_height = header.coded_half_height; + sony_arw6_require(header.coded_width > 0 && comp_height > 0); const int comp_top = sony_arw6_comp_top(comp_height); const int n_chunks = sony_arw6_chunk_count(comp_height); @@ -1306,18 +1287,17 @@ static SonyArw6DecodedTile sony_arw6_decode_stream_tile(const uchar *stream, { std::vector p = sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, 0, + header.coded_width, n_chunks, 0, comp); - SonyArw6Plane ll = sony_arw6_extract_orientation(p, 1, n_chunks, &ll_span, - 1, 0, false, false); - comps[comp] = sony_arw6_integrate_type1(ll, ll.rows, 0); + SonyArw6Plane ll = sony_arw6_extract_orientation(p, n_chunks, &ll_span, 1, + 0, false, false); + comps[comp] = sony_arw6_integrate_type1(ll); } /* detail groups, coarsest first */ for (int group = 1; group <= 3; group++) { const int step = 8 >> (group - 1); - const int rm = sony_arw6_row_multiplier(group); SonyArw6Span spans[3]; spans[0] = sony_arw6_make_span(comp_top, comp_height, step, 0); /* HL */ spans[1] = @@ -1332,14 +1312,14 @@ static SonyArw6DecodedTile sony_arw6_decode_stream_tile(const uchar *stream, const bool odd_edge = group == 3 && comp != 0; std::vector p = sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, + header.coded_width, n_chunks, group, comp); - SonyArw6Plane hl = sony_arw6_extract_orientation(p, rm, n_chunks, spans, - 3, 0, false, false); - SonyArw6Plane lh = sony_arw6_extract_orientation(p, rm, n_chunks, spans, - 3, 1, false, false); - SonyArw6Plane hh = sony_arw6_extract_orientation(p, rm, n_chunks, spans, - 3, 2, true, odd_edge); + SonyArw6Plane hl = sony_arw6_extract_orientation(p, n_chunks, spans, 3, + 0, false, false); + SonyArw6Plane lh = sony_arw6_extract_orientation(p, n_chunks, spans, 3, + 1, false, false); + SonyArw6Plane hh = sony_arw6_extract_orientation(p, n_chunks, spans, 3, + 2, true, odd_edge); comps[comp] = sony_arw6_merge_level(comps[comp], hl, lh, hh, coarse_odd); } } @@ -1347,17 +1327,17 @@ static SonyArw6DecodedTile sony_arw6_decode_stream_tile(const uchar *stream, const SonyArw6Span hi_span = sony_arw6_make_span(comp_top, comp_height, 1, 0); std::vector g4 = sony_arw6_decode_packet_arrays(stream, stream_size, dir, - header.coded_width, coded_height, 4, 0); - SonyArw6Plane green_hi = sony_arw6_extract_orientation(g4, 8, n_chunks, + header.coded_width, n_chunks, 4, 0); + SonyArw6Plane green_hi = sony_arw6_extract_orientation(g4, n_chunks, &hi_span, 1, 0, false, false); SonyArw6Plane full_green = sony_arw6_final_green(comps[0], green_hi); SonyArw6DecodedTile out; - out.green = comps[0]; - out.red_residual = comps[1]; - out.blue_residual = comps[2]; - out.full_green = full_green; + out.green = std::move(comps[0]); + out.red_residual = std::move(comps[1]); + out.blue_residual = std::move(comps[2]); + out.full_green = std::move(full_green); return out; } From 777e105130781344f739998c6c2fe33de00a4f74 Mon Sep 17 00:00:00 2001 From: Don Chin Date: Mon, 24 Aug 2026 10:18:43 +1200 Subject: [PATCH 4/4] fix(sony): don't emit a bogus InternalBodySerial for the A7 V The ILCE-7M5 is routed to the Tag9050d layout, which reads InternalBodySerial from six bytes at offset 0x38 of the 0x9050 block. Those bytes are not a body serial on this camera: the value changes from frame to frame, so anything keying on camera identity sees every frame as a different body. It split a 2-second burst into twelve separate groups in a scene-clustering consumer. Measured across two bodies and both shipped firmwares: - v1.01, one 12-frame burst: twelve distinct values (191100288e43, 180010000028, 44100000002, ...). Zero of the 256 raw bytes of the block are constant across the burst, so no offset in it holds a body serial for these files. The block does not carry the layout's five-zero-byte prefix and nothing else in it decodes either - SonyFNumber reads as 57511365 against a real f/2.8, ShutterCount ranges 320..8683552 within the burst. - v1.00, one session: three distinct values (2cff0000b108, e1fe0000e908, 37ff0000b908). Here the block does carry the prefix and its other fields decode correctly, but the six bytes at 0x38 still move between frames - they read XX ff 00 00 YY 08, a counter rather than an identity. So the field is wrong on v1.01 because the whole block is a different format, and wrong on v1.00 because 0x38 was never the serial. Exclude the camera from this read, in the same way the 9050a branch already excludes the NEX-5N, NEX-7 and NEX-VG20. Bodies that do have an identity report it in EXIF BodySerialNumber, which is read elsewhere: the v1.00 samples keep BodySerial 02052278 with this change. Scoped to the serial read alone, so CurAp, LensMount, LensType2 and ImageCount3 continue to decode from the block on firmwares where it is valid. Verified: InternalBodySerial is now empty on all 23 A7 V files across both bodies, BodySerial 02052278 is preserved on the six samples that carry it, and lens/aperture/ISO still decode on v1.00. Canon 5D Mark IV, 250D, 7D, Nikon D5100, Fuji X20 and DSLR-A390 fixtures are unchanged. The ILCE-7RM6 shares the Tag9050d routing and may well have the same problem, but is not excluded here - no sample was available to test it. --- src/metadata/sony.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/metadata/sony.cpp b/src/metadata/sony.cpp index 3eb52d58..8fd399fb 100644 --- a/src/metadata/sony.cpp +++ b/src/metadata/sony.cpp @@ -791,7 +791,8 @@ printf ("==>> Tag9050, len: 0x%04x, type: %s, model: =%s=, CamID: %llu, ARW vers sprintf(imgdata.shootinginfo.InternalBodySerial, "%06llx", (b88 << 40) + (b89 << 32) + (b8a << 24) + (b8b << 16) + (b8c << 8) + b8d); - } else if (imSony.group9050 == LIBRAW_SONY_Tag9050d) { + } else if ((imSony.group9050 == LIBRAW_SONY_Tag9050d) && + (id != SonyID_ILCE_7M5)) { unsigned start_InternalBodySerial = 0x38; if (len <= start_InternalBodySerial+5) return; unsigned long long b38 = SonySubstitution[buf[start_InternalBodySerial]];