encodeRgba4Bpp method

Uint8List encodeRgba4Bpp(
  1. Image bitmap
)

Implementation

Uint8List encodeRgba4Bpp(Image bitmap) {
  if (bitmap.width != bitmap.height) {
    throw ImageException('PVRTC requires a square image.');
  }

  if (!BitUtility.isPowerOf2(bitmap.width)) {
    throw ImageException('PVRTC requires a power-of-two sized image.');
  }

  final size = bitmap.width;
  final blocks = size ~/ 4;
  final blockMask = blocks - 1;

  final bitmapData = bitmap.getBytes();

  // Allocate enough data for encoding the image.
  final outputData = Uint8List((bitmap.width * bitmap.height) ~/ 2);
  final packet = PvrtcPacket(outputData);
  final p0 = PvrtcPacket(outputData);
  final p1 = PvrtcPacket(outputData);
  final p2 = PvrtcPacket(outputData);
  final p3 = PvrtcPacket(outputData);

  for (var y = 0; y < blocks; ++y) {
    for (var x = 0; x < blocks; ++x) {
      packet.setBlock(x, y);
      packet.usePunchthroughAlpha = 0;
      final cbb = _calculateBoundingBoxRgba(bitmap, x, y);
      packet.setColorRgbaA(cbb.min as PvrtcColorRgba);
      packet.setColorRgbaB(cbb.max as PvrtcColorRgba);
    }
  }

  const factors = PvrtcPacket.BILINEAR_FACTORS;

  for (var y = 0; y < blocks; ++y) {
    for (var x = 0; x < blocks; ++x) {
      var factorIndex = 0;
      final pixelIndex = (y * 4 * size + x * 4) * 4;

      var modulationData = 0;

      for (var py = 0; py < 4; ++py) {
        final yOffset = (py < 2) ? -1 : 0;
        final y0 = (y + yOffset) & blockMask;
        final y1 = (y0 + 1) & blockMask;

        for (var px = 0; px < 4; ++px) {
          final xOffset = (px < 2) ? -1 : 0;
          final x0 = (x + xOffset) & blockMask;
          final x1 = (x0 + 1) & blockMask;

          p0.setBlock(x0, y0);
          p1.setBlock(x1, y0);
          p2.setBlock(x0, y1);
          p3.setBlock(x1, y1);

          final ca = p0.getColorRgbaA() * factors[factorIndex][0] +
              p1.getColorRgbaA() * factors[factorIndex][1] +
              p2.getColorRgbaA() * factors[factorIndex][2] +
              p3.getColorRgbaA() * factors[factorIndex][3];

          final cb = p0.getColorRgbaB() * factors[factorIndex][0] +
              p1.getColorRgbaB() * factors[factorIndex][1] +
              p2.getColorRgbaB() * factors[factorIndex][2] +
              p3.getColorRgbaB() * factors[factorIndex][3];

          final pi = pixelIndex + ((py * size + px) * 4);
          final r = bitmapData[pi];
          final g = bitmapData[pi + 1];
          final b = bitmapData[pi + 2];
          final a = bitmapData[pi + 3];

          final d = cb - ca;
          final p = PvrtcColorRgba(r * 16, g * 16, b * 16, a * 16);
          final v = p - ca;

          // PVRTC uses weightings of 0, 3/8, 5/8 and 1
          // The boundaries for these are 3/16, 1/2 (=8/16), 13/16
          final projection = v.dotProd(d) * 16;
          final lengthSquared = d.dotProd(d);

          if (projection > 3 * lengthSquared) {
            modulationData++;
          }
          if (projection > 8 * lengthSquared) {
            modulationData++;
          }
          if (projection > 13 * lengthSquared) {
            modulationData++;
          }

          modulationData = BitUtility.rotateRight(modulationData, 2);

          factorIndex++;
        }
      }

      packet.setBlock(x, y);
      packet.modulationData = modulationData;
    }
  }

  return outputData;
}