ADR-1483: a subsampled chroma plane of an odd-sized picture is rounded up; upstream rounds down, and chroma metrics on odd sizes differ by up to 0.83 dB¶
- Status: Accepted
- Date: 2026-10-02
- Deciders: lusoris
- Tags:
upstream-divergence,core,picture,chroma,odd-dimensions,correctness,rc3,fork-local
Context¶
The reference for code the fork inherited from Netflix/vmaf is Netflix's source; a difference needs an ADR (see References). The upstream parity audit of 2026-10-02 found this difference without one: the commit that made it called it a bug fix.
Upstream, libvmaf/src/picture.c:74 and :76 at Netflix 9e48141b: pic->w[1] = pic->w[2] = w >> ss_hor; and pic->h[1] = pic->h[2] = h >> ss_ver;. A 4:2:0 picture of 19x19 has chroma planes of 9x9. A raw 4:2:0 or 4:2:2 file stores ceil(w / 2) chroma samples per row (and ceil(h / 2) rows for 4:2:0), so the last chroma column and row of an odd-sized frame are not part of the picture, and the rightmost luma column and the bottom luma row have no chroma sample of their own.
The fork rounds up: vmaf_chroma_extent() (core/src/picture_geometry.h), called by picture_compute_geometry() in core/src/picture.c and by every extractor and twin that sizes a chroma buffer itself. Commit 4f08d32b2 (2026-05-10) introduced it after a 577x323 4:2:0 input made ciede read one row past the chroma allocation (an ASan heap out-of-bounds read in scale_chroma_planes()). PR #1643 moved the definition into one helper and ADR-1398 made the CLI accept odd raw inputs with that geometry.
Decision¶
The fork keeps ceil for subsampled chroma extents: every luma sample of an odd-sized picture is covered by a chroma sample, and the picture holds the planes as a file stores them. Metrics that read chroma differ from upstream on odd sizes by design; luma metrics and even sizes are not affected.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Restore upstream's floor | Chroma metrics equal upstream on odd sizes | The last chroma row and column of the file are dropped; an extractor that upsamples chroma to the luma size has no sample for the last luma row and column, which is where ciede read out of bounds | The out-of-bounds read is what the change fixed |
Keep floor and fix each consumer to clamp | Same plane sizes as upstream | Every consumer and every GPU twin needs its own clamp; the dropped samples stay dropped | One definition in one helper is checked once |
Consequences¶
- Measured size (audit of 2026-10-02: Netflix
cea2b4d8, whosepicture.cis9e48141b's, against the fork with its unintended differences reverted; GCC 16.2.1, x86-64, C API at%.17g; the 4:2:0 pairs of 19x19, 17x17 and 12x9, 7 frames; the harness reads the file with rounded-up chroma rows and gives each tree what its picture holds):psnr_cbdiffers on 7 of 7 frames by up to 0.684 dB andpsnr_crby up to 0.826 dB (19x19:psnr_crupstream 33.759956236901679, fork 32.933835937080573);ciede2000differs on the same frames by up to 0.198.psnr_yand every other luma metric are identical on those frames, and the 573x163 4:4:4 pair shows no difference. - Upstream status: Netflix/vmaf#1604 (open, sent by the fork) makes upstream's tools read odd-sized files; it keeps
floorin the picture and drops the trailing chroma column and row in the reader. No upstream pull request changespicture.c. - Ends when upstream sizes subsampled chroma planes by rounding up. Until then the allowlist of the upstream parity guard lists the chroma metrics on odd-sized subsampled fixtures.
- Neutral:
core/test/test_picture.casserts the plane sizes for odd 4:2:0, 4:2:2 and 4:4:4 pictures. No Netflix golden fixture has an odd size.
References¶
- Fork commit
4f08d32b2(subject: "ceiling division for odd-dim YUV 4:2:0 chroma planes"); fork PR #1643 (c1a2b1914); ADR-1398. - Upstream:
libvmaf/src/picture.c:74,:76at Netflix9e48141b; Netflix/vmaf#1604. - Source:
req(popup answer, 2026-10-02): "Netflix's source, deviations only by ADR".