encodeAstc4x4 function

Uint8List encodeAstc4x4(
  1. Rgba8Image image
)

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;
}