encodeEtc2Rgb8 function

Uint8List encodeEtc2Rgb8(
  1. Rgba8Image image
)

Encodes image as ETC2 RGB8 (vkFormat.etc2R8g8b8UNormBlock): one 8-byte block per 4×4 tile.

The ETC1-compatible subset of ETC2's modes, not the full format. ETC2 added three block modes over ETC1 — "T", "H" and "planar" — each a different trade of precision for a specific kind of block (planar for a smooth gradient across all sixteen texels, T/H for a block with one colour cutting sharply through the rest). None is required: every ETC2 decoder — hardware or software — already accepts an ETC1 block as valid ETC2, because ETC2 is specified as a strict superset. What is given up is some quality on the specific block shapes those modes target, not correctness or conformance; picking the better of ETC1's own two modes (individual, differential) per block, done here, is most of the way there and a fraction of the code.

Individual or differential mode, chosen per block, never the flip bit's alternative split. Both encode two 4×2 sub-blocks stacked vertically (rows 0–1, rows 2–3) — flip = 0 throughout — rather than ever trying the 2×4 side-by-side split flip = 1 offers. A real encoder gets a little more from choosing per block; this one accepts the loss for the same reason it skips T/H/planar: the two vertical halves already carry two independent base colours and tables, which is most of what a smarter split would add.

Implementation

Uint8List encodeEtc2Rgb8(Rgba8Image image) {
  requireWholeBlocks(image, 'encodeEtc2Rgb8');
  final blocksX = image.width ~/ 4;
  final blocksY = image.height ~/ 4;
  final out = Uint8List(blocksX * blocksY * 8);
  var offset = 0;
  for (var by = 0; by < blocksY; by++) {
    for (var bx = 0; bx < blocksX; bx++) {
      final block = encodeEtc2Rgb8Block(readBlock(image, bx, by));
      out.setRange(offset, offset + 8, block);
      offset += 8;
    }
  }
  return out;
}