encodeAstc4x4 function
Encodes image as ASTC 4×4 LDR (vkFormat.astc4x4UNormBlock): one
16-byte block per 4×4 tile — mat-30's own gap on top of fmt-22's
BC1/BC3/ETC2 trio, doc/asset-pipeline-plan.md's ap-07.
Single partition, single plane, a 4×4 weight grid — the ETC2-shaped
subset of ASTC, not the full format. Real ASTC's block-mode field packs
a whole table of weight-grid shapes (2×2 up to 12×12, non-square,
interpolated) and up to four colour partitions with dual-plane
(per-channel-independent weight) blocks on top of that; a full encoder
for all of it is the size of a small library. What is implemented here is
the same trade encodeEtc2Rgb8Block documents making: one shape (the
weight grid equals the 4×4 texel grid exactly, so every texel gets its
own weight with no interpolation to derive), one partition, one
colour-endpoint pair, LDR RGB Direct mode — a real GPU's ASTC decoder
accepts this shape as valid ASTC, since single-partition/single-plane is
one legal point in the format, not a reduced dialect of it.
The block-mode field is the specification's, checked against ARM's own
decoder — gfx-88n, 2026-09-18. Until that row this file wrote eleven
zero bits there and said so in this comment, calling the layout its own
because reproducing the real row-selection table with nothing to check
against risked a block that looks self-consistent and is not. The risk was
real and the check was a package away: astcenc installs from npm, and fed
a file this encoder wrote it returned (255, 0, 255) — ASTC's error colour
— for every block, because eleven zeros is a reserved encoding rather than
a 4×4 weight grid. What is written now is _kBlockMode, read out of
decode_block_mode_2d, and astc_conformance_test.dart pins both the bytes
astcenc was handed and what it gave back.
Endpoints from the same principal-axis fit encodeBc1Block uses, one
weight per texel from an exhaustive nearest-level search against the
endpoint line — not the two-endpoint interpolation table BC1 is stuck
with: eight independent levels per texel against BC1's four-entry palette,
which is where ASTC's quality advantage over block formats from the same era
actually comes from.
Implementation
Uint8List encodeAstc4x4(Rgba8Image image) {
requireWholeBlocks(image, 'encodeAstc4x4');
final blocksX = image.width ~/ 4;
final blocksY = image.height ~/ 4;
final out = Uint8List(blocksX * blocksY * 16);
var offset = 0;
for (var by = 0; by < blocksY; by++) {
for (var bx = 0; bx < blocksX; bx++) {
final block = encodeAstc4x4Block(readBlock(image, bx, by));
out.setRange(offset, offset + 16, block);
offset += 16;
}
}
return out;
}