33// found in the LICENSE file.
44
55import 'dart:async' ;
6+ import 'dart:collection' ;
67import 'dart:math' as math;
78import 'dart:typed_data' ;
89import 'dart:ui' as ui;
@@ -838,7 +839,7 @@ class _ColorVolumeBox {
838839 _fitMinimumBox ();
839840 }
840841
841- final Map < Color , int > histogram;
842+ final _ColorHistogram histogram;
842843 final List <Color > colors;
843844
844845 // The lower and upper index are inclusive.
@@ -883,7 +884,7 @@ class _ColorVolumeBox {
883884 int count = 0 ;
884885 for (int i = _lowerIndex; i <= _upperIndex; i++ ) {
885886 final Color color = colors[i];
886- count += histogram[color];
887+ count += histogram[color].value ;
887888 if (color.red > maxRed) {
888889 maxRed = color.red;
889890 }
@@ -981,7 +982,7 @@ class _ColorVolumeBox {
981982 colors.replaceRange (_lowerIndex, _upperIndex + 1 , colorSubset);
982983 final int median = (_population / 2 ).round ();
983984 for (int i = 0 , count = 0 ; i <= colorSubset.length; i++ ) {
984- count += histogram[colorSubset[i]];
985+ count += histogram[colorSubset[i]].value ;
985986 if (count >= median) {
986987 // We never want to split on the upperIndex, as this will result in the
987988 // same box.
@@ -998,7 +999,7 @@ class _ColorVolumeBox {
998999 int totalPopulation = 0 ;
9991000 for (int i = _lowerIndex; i <= _upperIndex; i++ ) {
10001001 final Color color = colors[i];
1001- final int colorPopulation = histogram[color];
1002+ final int colorPopulation = histogram[color].value ;
10021003 totalPopulation += colorPopulation;
10031004 redSum += colorPopulation * color.red;
10041005 greenSum += colorPopulation * color.green;
@@ -1014,6 +1015,77 @@ class _ColorVolumeBox {
10141015 }
10151016}
10161017
1018+ /// Holds mutable count for a color.
1019+ // Using a mutable count rather than replacing value in the histogram
1020+ // in the _ColorCutQuantizer speeds up building the histogram significantly.
1021+ class _ColorCount {
1022+ int value = 0 ;
1023+ }
1024+
1025+ class _ColorHistogram {
1026+ final Map <int , Map <int , Map <int , _ColorCount >>> _hist =
1027+ < int , Map <int , Map <int , _ColorCount >>> {};
1028+ final DoubleLinkedQueue <Color > _keys = DoubleLinkedQueue <Color >();
1029+
1030+ _ColorCount operator [](Color color) {
1031+ final Map <int , Map <int , _ColorCount >> redMap = _hist[color.red];
1032+ if (redMap == null ) {
1033+ return null ;
1034+ }
1035+ final Map <int , _ColorCount > blueMap = redMap[color.blue];
1036+ if (blueMap == null ) {
1037+ return null ;
1038+ }
1039+ return blueMap[color.green];
1040+ }
1041+
1042+ void operator []= (Color key, _ColorCount value) {
1043+ final int red = key.red;
1044+ final int blue = key.blue;
1045+ final int green = key.green;
1046+
1047+ bool newColor = false ;
1048+
1049+ Map <int , Map <int , _ColorCount >> redMap = _hist[red];
1050+ if (redMap == null ) {
1051+ _hist[red] = redMap = < int , Map <int , _ColorCount >> {};
1052+ newColor = true ;
1053+ }
1054+
1055+ Map <int , _ColorCount > blueMap = redMap[blue];
1056+ if (blueMap == null ) {
1057+ redMap[blue] = blueMap = < int , _ColorCount > {};
1058+ newColor = true ;
1059+ }
1060+
1061+ if (blueMap[green] == null ) {
1062+ newColor = true ;
1063+ }
1064+ blueMap[green] = value;
1065+
1066+ if (newColor) {
1067+ _keys.add (key);
1068+ }
1069+ }
1070+
1071+ void removeWhere (bool predicate (Color key)) {
1072+ for (Color key in _keys) {
1073+ if (predicate (key)) {
1074+ _hist[key.red][key.blue][key.green] = null ;
1075+ }
1076+ }
1077+ _keys.removeWhere ((Color color) => predicate (color));
1078+ }
1079+
1080+ Iterable <Color > get keys {
1081+ return _keys;
1082+ }
1083+
1084+ int get length {
1085+ return _keys.length;
1086+ }
1087+ }
1088+
10171089class _ColorCutQuantizer {
10181090 _ColorCutQuantizer (
10191091 this .image, {
@@ -1108,28 +1180,39 @@ class _ColorCutQuantizer {
11081180 await image.toByteData (format: ui.ImageByteFormat .rawRgba);
11091181 final Iterable <Color > pixels =
11101182 _getImagePixels (imageData, image.width, image.height, region: region);
1111- final Map <Color , int > hist = < Color , int > {};
1183+ final _ColorHistogram hist = _ColorHistogram ();
1184+ Color currentColor;
1185+ _ColorCount currentColorCount;
1186+
11121187 for (Color pixel in pixels) {
11131188 // Update the histogram, but only for non-zero alpha values, and for the
11141189 // ones we do add, make their alphas opaque so that we can use a Color as
11151190 // the histogram key.
11161191 final Color quantizedColor = quantizeColor (pixel);
11171192 final Color colorKey = quantizedColor.withAlpha (0xff );
11181193 // Skip pixels that are entirely transparent.
1119- if (quantizedColor.alpha != 0x0 ) {
1120- hist[colorKey] = (hist[colorKey] ?? 0 ) + 1 ;
1194+ if (quantizedColor.alpha == 0x0 ) {
1195+ continue ;
1196+ }
1197+ if (currentColor != colorKey) {
1198+ currentColor = colorKey;
1199+ currentColorCount = hist[colorKey];
1200+ if (currentColorCount == null ) {
1201+ hist[colorKey] = currentColorCount = _ColorCount ();
1202+ }
11211203 }
1204+ currentColorCount.value = currentColorCount.value + 1 ;
11221205 }
11231206 // Now let's remove any colors that the filters want to ignore.
1124- hist.removeWhere ((Color color, int _ ) {
1207+ hist.removeWhere ((Color color) {
11251208 return _shouldIgnoreColor (color);
11261209 });
11271210 if (hist.length <= maxColors) {
11281211 // The image has fewer colors than the maximum requested, so just return
11291212 // the colors.
11301213 _paletteColors.clear ();
11311214 for (Color color in hist.keys) {
1132- _paletteColors.add (PaletteColor (color, hist[color]));
1215+ _paletteColors.add (PaletteColor (color, hist[color].value ));
11331216 }
11341217 } else {
11351218 // We need use quantization to reduce the number of colors
@@ -1141,7 +1224,7 @@ class _ColorCutQuantizer {
11411224
11421225 List <PaletteColor > _quantizePixels (
11431226 int maxColors,
1144- Map < Color , int > histogram,
1227+ _ColorHistogram histogram,
11451228 ) {
11461229 int volumeComparator (_ColorVolumeBox a, _ColorVolumeBox b) {
11471230 return b.getVolume ().compareTo (a.getVolume ());
0 commit comments