Browse Source

Verify AV1 high-bit-depth source precision

pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
513c534081
  1. 6
      HEIF_IMPLEMENTATION_PLAN.md
  2. 66
      tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs

6
HEIF_IMPLEMENTATION_PLAN.md

@ -797,7 +797,7 @@ Writer primitives are not an encoder. The public encoder remains incomplete unti
- [x] Treat source pixel type, source alpha representation, and decoded source bit depth as conversion inputs, never as output-eligibility checks. Do not pre-scan pixels before encoding.
- [x] Resolve output configuration once from explicit encoder options, converted `HeifMetadata`, and AV1 defaults in that order. Sanitize only combinations that cannot describe a legal requested output, and never write resolved values back to source metadata.
- [ ] Finalize observable options for quality, effort, lossless mode, bit depth, chroma subsampling, alpha quality, metadata, and bounded sequences.
- [ ] Preserve high-bit-depth source precision through 16-bit RGB and native 10/12-bit component planes.
- [x] Preserve high-bit-depth source precision through 16-bit RGB and native 10/12-bit component planes.
- [ ] Reject only genuinely unsupported output combinations at the public boundary before writing output.
- [ ] Register only capabilities that the completed encoder proves.
@ -876,7 +876,7 @@ Encoder verification contract:
- [x] Candidate distortion now follows the reference separation between immutable source residuals and reconstructed-pixel error. The forward-transform boundary accepts a read-only residual, so all transform types for one prediction reuse that block directly instead of copying it into transform scratch before every trial. The shared residual API now measures strided source-versus-reconstruction squared error with documented Vector512, Vector256, Vector128, and scalar traversal through ImageSharp's vector-count helpers, eliminating the former residual destination write and second reduction pass. The same path covers ordinary intra, filter intra, palette, chroma-from-luma, split transforms, and intra-block copy at 8, 10, and 12 bits without an allocation. Independent scalar, stride, tail, intrinsic-tier, and zero-allocation coverage passes with the 140-case focused encoder set; the complete non-HEVC HEIF/AV1 namespace passes 9,301 of 9,301. Representative palette, filter-intra, and effort-eight output hashes remain byte-identical, and current-main `aomdec` accepts every checked stream.
- [x] Lossless still-image coding now follows current libaom's qindex-zero path without introducing a per-block allocation or a second frame buffer. The forward 4x4 Walsh-Hadamard transform and its transpose into the entropy pipeline's row-major coefficient order are allocation-free at Vector512, Vector256, Vector128, and scalar tiers; lossless quantization reconstructs the original transform coefficient exactly. The mode decision fixes lossless transforms to DCT-DCT syntax and four-by-four blocks, disables transform skip for nonzero residuals, and excludes the fixed-eight-by-eight intra-block-copy search that cannot represent the required lossless transform grid. The frame coefficient owner reserves the exact worst-case 2,048 transform states needed by both 128x128 4:4:4 chroma planes without adding an allocation. The color configuration derives its plane count from the monochrome flag, so high-bit-depth direct-frame writers and readers cannot retain contradictory mutable state. Public eight-bit color and auxiliary-alpha round trips are pixel exact; direct 10-bit and 12-bit 4:4:4 frame round trips are exact at native-plane precision. The Release build completes with zero errors, Roslynk reports zero compiler errors, and the complete non-HEVC HEIF/AV1 namespace passes 9,081 of 9,081 through one foreground VSTest run. An independently built generic `aomdec` from current official libaom `main` at `d565eec60f084421fa34fc0534b760c6452b6a6c` accepts all 55 payloads regenerated by that run, including the new public color, auxiliary alpha, 10-bit, and 12-bit lossless streams.
- [x] Lossless still-image coding now follows current libaom's qindex-zero path without introducing a per-block allocation or a second frame buffer. The forward 4x4 Walsh-Hadamard transform and its transpose into the entropy pipeline's row-major coefficient order are allocation-free at Vector512, Vector256, Vector128, and scalar tiers; lossless quantization reconstructs the original transform coefficient exactly. The mode decision fixes lossless transforms to DCT-DCT syntax and four-by-four blocks, disables transform skip for nonzero residuals, and excludes the fixed-eight-by-eight intra-block-copy search that cannot represent the required lossless transform grid. The frame coefficient owner reserves the exact worst-case 2,048 transform states needed by both 128x128 4:4:4 chroma planes without adding an allocation. The color configuration derives its plane count from the monochrome flag, so high-bit-depth direct-frame writers and readers cannot retain contradictory mutable state. Public 8-bit, 10-bit, and 12-bit color and auxiliary-alpha round trips are pixel exact on their native sample lattices; direct 10-bit and 12-bit 4:4:4 frame round trips are also exact at native-plane precision. The Release build completes with zero errors, Roslynk reports zero compiler errors, and the complete non-HEVC HEIF/AV1 namespace passes 9,081 of 9,081 through one foreground VSTest run. An independently built generic `aomdec` from current official libaom `main` at `d565eec60f084421fa34fc0534b760c6452b6a6c` accepts all 59 current payloads, including the public color and auxiliary-alpha lossless streams at every supported precision.
### 7. Write complete AVIF output
@ -897,7 +897,7 @@ Encoder verification contract:
Encoder exit gate:
- [x] Current-main libaom accepts every currently produced AV1 payload.
- [~] Lossless output is exact for public eight-bit pixels and direct 8-, 10-, and 12-bit native planes; preservation of high-bit-depth public source precision remains part of the encoder contract work.
- [x] Lossless output is exact at public pixel and direct native-plane precision for 8-, 10-, and 12-bit output.
- [ ] Lossy output demonstrates recorded quality and effort tradeoffs with absolute size, quality, timing, and allocation evidence.
- [ ] 8, 10, and 12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 outputs pass.
- [ ] Alpha, grids, metadata, color profiles, transforms, and bounded sequences pass.

66
tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs

@ -247,6 +247,72 @@ public class HeifEncoderTests
File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-public-lossless-alpha.obu"), alphaPayload.ToArray());
}
[Theory]
[InlineData(HeifBitDepth.Bit10)]
[InlineData(HeifBitDepth.Bit12)]
public void Av1LosslessRoundTripPreservesHighBitDepthSourcePixels(HeifBitDepth bitDepth)
{
const int width = 8;
const int height = 8;
int codedMaximum = (1 << (int)bitDepth) - 1;
using Image<Rgba64> image = new(width, height);
for (int row = 0; row < height; row++)
{
Span<Rgba64> pixels = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row);
for (int column = 0; column < width; column++)
{
int red = ((column * 131) + (row * 37) + 1) & codedMaximum;
int green = ((column * 61) + (row * 173) + 3) & codedMaximum;
int blue = ((column * 211) + (row * 47) + 5) & codedMaximum;
int alpha = ((column * 127) + (row * 89)) & codedMaximum;
pixels[column] = new Rgba64(
ExpandToUShort(red, codedMaximum),
ExpandToUShort(green, codedMaximum),
ExpandToUShort(blue, codedMaximum),
ExpandToUShort(alpha, codedMaximum));
}
}
// Full-range identity 4:4:4 preserves the requested sample lattice, isolating source precision from a
// deliberately lossy color matrix or chroma subsampling step.
image.Metadata.CicpProfile = new CicpProfile(1, 13, 0, true);
using MemoryStream stream = new();
HeifEncoder encoder = new()
{
CompressionMethod = HeifCompressionMethod.Av1,
BitDepth = bitDepth,
ChromaSubsampling = HeifChromaSubsampling.Yuv444,
Lossless = true,
Effort = 0
};
image.Save(stream, encoder);
byte[] file = stream.ToArray();
Span<byte> colorPayload = GetItemPayload(file, 1);
Span<byte> alphaPayload = GetItemPayload(file, 2);
stream.Position = 0;
using Image<Rgba64> decoded = Image.Load<Rgba64>(stream);
Assert.Empty(ImageComparer.Exact.CompareImages(image, decoded));
string outputDirectory = Path.Combine(
TestEnvironment.ActualOutputDirectoryFullPath,
"Formats",
"Heif",
"Av1");
Directory.CreateDirectory(outputDirectory);
File.WriteAllBytes(
Path.Combine(outputDirectory, $"encoder-public-lossless-{(int)bitDepth}b-color.obu"),
colorPayload.ToArray());
File.WriteAllBytes(
Path.Combine(outputDirectory, $"encoder-public-lossless-{(int)bitDepth}b-alpha.obu"),
alphaPayload.ToArray());
}
private static ushort ExpandToUShort(int sample, int maximum)
=> (ushort)(((sample * (long)ushort.MaxValue) + (maximum / 2)) / maximum);
[Fact]
public void Av1RejectsImageSequenceBeforeWritingOutput()
{

Loading…
Cancel
Save