optimizedCachedBlurhashDecode function

Future<Uint8List> optimizedCachedBlurhashDecode({
  1. required String blurHash,
  2. required int width,
  3. required int height,
  4. double punch = 1.0,
  5. CachedBlurhashOptimizationMode optimizationMode = CachedBlurhashOptimizationMode.standard,
})

Implementation

Future<Uint8List> optimizedCachedBlurhashDecode({
  required String blurHash,
  required int width,
  required int height,
  double punch = 1.0,
  CachedBlurhashOptimizationMode optimizationMode = CachedBlurhashOptimizationMode.standard,
}) {
  _validateBlurHash(blurHash);

  final sizeFlag = _decode83(blurHash[0]);
  final numY = (sizeFlag / 9).floor() + 1;
  final numX = (sizeFlag % 9) + 1;

  final quantisedMaximumValue = _decode83(blurHash[1]);
  final maximumValue = (quantisedMaximumValue + 1) / 166;

  // Preallocate colors array with fixed size
  final colors = List<List<double>>.filled(numX * numY, [0, 0, 0]);

  // Decode DC component (first component)
  final dcValue = _decode83(blurHash.substring(2, 6));
  colors[0] = _decodeDC(dcValue);

  // Decode AC components (remaining components)
  final adjustedPunch = maximumValue * punch;
  for (var i = 1; i < colors.length; i++) {
    final value = _decode83(blurHash.substring(4 + i * 2, 6 + i * 2));
    colors[i] = _decodeAC(value, adjustedPunch);
  }

  // Precalculate cosine values for x and y
  final cosinesX = List<List<double>>.generate(
    numX,
    (i) => List<double>.generate(
      width,
      (x) => cos((pi * x * i) / width),
    ),
  );

  final cosinesY = List<List<double>>.generate(
    numY,
    (j) => List<double>.generate(
      height,
      (y) => cos((pi * y * j) / height),
    ),
  );

  final bytesPerRow = width * 4;
  final pixels = Uint8List(bytesPerRow * height);

  // Process image in chunks to improve cache locality
  const chunkSize = 32;

  // Process the image in tiles for better cache performance
  for (int yChunk = 0; yChunk < height; yChunk += chunkSize) {
    final yEnd = min(yChunk + chunkSize, height);

    for (int xChunk = 0; xChunk < width; xChunk += chunkSize) {
      final xEnd = min(xChunk + chunkSize, width);

      for (int y = yChunk; y < yEnd; y++) {
        int p = (y * width + xChunk) * 4;

        for (int x = xChunk; x < xEnd; x++) {
          var r = 0.0, g = 0.0, b = 0.0;

          // Use precalculated cosine values
          for (int j = 0; j < numY; j++) {
            final cosY = cosinesY[j][y];

            for (int i = 0; i < numX; i++) {
              final basis = cosinesX[i][x] * cosY;
              final color = colors[i + j * numX];

              r += color[0] * basis;
              g += color[1] * basis;
              b += color[2] * basis;
            }
          }

          // Convert linear RGB to sRGB space based on optimization mode
          switch (optimizationMode) {
            case CachedBlurhashOptimizationMode.approximation:
              pixels[p++] = _approximatedLinearTosRGB(r);
              pixels[p++] = _approximatedLinearTosRGB(g);
              pixels[p++] = _approximatedLinearTosRGB(b);
              break;
            case CachedBlurhashOptimizationMode.standard:
            case CachedBlurhashOptimizationMode.none:
              pixels[p++] = _linearTosRGB(r);
              pixels[p++] = _linearTosRGB(g);
              pixels[p++] = _linearTosRGB(b);
              break;
          }

          pixels[p++] = 255; // Alpha is always 255
        }
      }
    }
  }

  return Future.value(pixels);
}