quantize method

  1. @override
QuantizerResult quantize(
  1. List<int> inputPixels,
  2. int maxColors, {
  3. List<int> startingClusters = const [],
})
override

Implementation

@override
QuantizerResult quantize(
  List<int> inputPixels,
  int maxColors, {
  List<int> startingClusters = const [],
}) {
  final random = math.Random(0x42688);

  final pixelToCount = <int, int>{};
  final List<List<double>> points = List.generate(
    inputPixels.length,
    (index) => [],
  );
  final pixels = List<int>.filled(inputPixels.length, 0);
  const pointProvider = PointProviderLab();

  var pointCount = 0;
  for (final inputPixel in inputPixels) {
    pixelToCount.update(
      inputPixel,
      (value) => value + 1,
      ifAbsent: () {
        points[pointCount] = pointProvider.fromInt(inputPixel);
        pixels[pointCount] = inputPixel;
        pointCount++;
        return 1;
      },
    );
  }

  final counts = List<int>.filled(pointCount, 0);
  for (var i = 0; i < pointCount; i++) {
    final pixel = pixels[i];
    final count = pixelToCount[pixel];
    assert(count != null);
    counts[i] = count!;
  }

  var clusterCount = math.min(maxColors, pointCount);
  if (startingClusters.isNotEmpty) {
    clusterCount = math.min(clusterCount, startingClusters.length);
  }

  final clusters = startingClusters
      .map((startingCluster) => pointProvider.fromInt(startingCluster))
      .toList();
  final clustersCreated = clusters.length;

  final additionalClustersNeeded = clusterCount - clustersCreated;
  if (additionalClustersNeeded > 0) {
    for (var i = 0; i < additionalClustersNeeded; i++) {
      // TODO(deminearchiver): implement (Java and Kotlin implementation missing)
    }
  }

  final clusterIndices = List<int>.generate(
    pointCount,
    (_) => random.nextInt(clusterCount),
  );

  final indexMatrix = List.generate(
    clusterCount,
    (index) => List<int>.filled(clusterCount, 0),
  );

  final List<List<_Distance>> distanceToIndexMatrix = List.generate(
    clusterCount,
    (index) => List.generate(clusterCount, (_) => _Distance()),
  );

  List<int> pixelCountSums = [];
  for (var iteration = 0; iteration < _maxIterations; iteration++) {
    for (var i = 0; i < clusterCount; i++) {
      for (var j = i + 1; j < clusterCount; j++) {
        final distance = pointProvider.distance(clusters[i], clusters[j]);
        distanceToIndexMatrix[j][i].distance = distance;
        distanceToIndexMatrix[j][i].index = i;
        distanceToIndexMatrix[i][j].distance = distance;
        distanceToIndexMatrix[i][j].index = j;
      }
      distanceToIndexMatrix[i].sort();
      for (var j = 0; j < clusterCount; j++) {
        indexMatrix[i][j] = distanceToIndexMatrix[i][j].index;
      }
    }

    var pointsMoved = 0;
    for (var i = 0; i < pointCount; i++) {
      final point = points[i];
      final previousClusterIndex = clusterIndices[i];
      final previousCluster = clusters[previousClusterIndex];
      final previousDistance = pointProvider.distance(point, previousCluster);

      var minimumDistance = previousDistance;
      var newClusterIndex = -1;
      for (var j = 0; j < clusterCount; j++) {
        if (distanceToIndexMatrix[previousClusterIndex][j].distance >=
            4 * previousDistance) {
          continue;
        }
        final distance = pointProvider.distance(point, clusters[j]);
        if (distance < minimumDistance) {
          minimumDistance = distance;
          newClusterIndex = j;
        }
      }
      if (newClusterIndex != -1) {
        final distanceChange =
            (math.sqrt(minimumDistance) - math.sqrt(previousDistance)).abs();
        if (distanceChange > _minMovementDistance) {
          pointsMoved++;
          clusterIndices[i] = newClusterIndex;
        }
      }
    }
    if (pointsMoved == 0 && iteration != 0) {
      break;
    }

    final componentASums = List<double>.filled(clusterCount, 0.0);
    final componentBSums = List<double>.filled(clusterCount, 0.0);
    final componentCSums = List<double>.filled(clusterCount, 0.0);
    pixelCountSums = List.filled(clusterCount, 0);
    for (var i = 0; i < pointCount; i++) {
      final clusterIndex = clusterIndices[i];
      final point = points[i];
      final count = counts[i];
      pixelCountSums[clusterIndex] += count;
      componentASums[clusterIndex] += (point[0] * count);
      componentBSums[clusterIndex] += (point[1] * count);
      componentCSums[clusterIndex] += (point[2] * count);
    }

    for (var i = 0; i < clusterCount; i++) {
      final count = pixelCountSums[i];
      if (count == 0) {
        clusters[i] = [0.0, 0.0, 0.0];
        continue;
      }
      final a = componentASums[i] / count;
      final b = componentBSums[i] / count;
      final c = componentCSums[i] / count;
      clusters[i][0] = a;
      clusters[i][1] = b;
      clusters[i][2] = c;
    }
  }
  final argbToPopulation = <int, int>{};
  for (var i = 0; i < clusterCount; i++) {
    final count = pixelCountSums[i];
    if (count == 0) {
      continue;
    }

    final possibleNewCluster = pointProvider.toInt(clusters[i]);
    if (argbToPopulation.containsKey(possibleNewCluster)) {
      continue;
    }

    argbToPopulation[possibleNewCluster] = count;
  }
  return QuantizerResult(colorToCount: argbToPopulation);
}