diff --git a/packages/palette_generator/CHANGELOG.md b/packages/palette_generator/CHANGELOG.md index 81ff9432f7a1..786dcc74246d 100644 --- a/packages/palette_generator/CHANGELOG.md +++ b/packages/palette_generator/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0 + +* Migrated to null safety. + ## 0.2.4+1 * Removed a `dart:async` import that isn't required for \>=Dart 2.1. diff --git a/packages/palette_generator/example/lib/main.dart b/packages/palette_generator/example/lib/main.dart index 5d14baabe6a0..e445e5b5fe1f 100644 --- a/packages/palette_generator/example/lib/main.dart +++ b/packages/palette_generator/example/lib/main.dart @@ -41,20 +41,20 @@ class MyApp extends StatelessWidget { class ImageColors extends StatefulWidget { /// Creates the home page. const ImageColors({ - Key key, + Key? key, this.title, - this.image, + required this.image, this.imageSize, }) : super(key: key); /// The title that is shown at the top of the page. - final String title; + final String? title; /// This is the image provider that is used to load the colors from. final ImageProvider image; /// The dimensions of the image. - final Size imageSize; + final Size? imageSize; @override _ImageColorsState createState() { @@ -63,22 +63,24 @@ class ImageColors extends StatefulWidget { } class _ImageColorsState extends State { - Rect region; - Rect dragRegion; - Offset startDrag; - Offset currentDrag; - PaletteGenerator paletteGenerator; + Rect? region; + Rect? dragRegion; + Offset? startDrag; + Offset? currentDrag; + PaletteGenerator? paletteGenerator; final GlobalKey imageKey = GlobalKey(); @override void initState() { super.initState(); - region = Offset.zero & widget.imageSize; + if (widget.imageSize != null) { + region = Offset.zero & widget.imageSize!; + } _updatePaletteGenerator(region); } - Future _updatePaletteGenerator(Rect newRegion) async { + Future _updatePaletteGenerator(Rect? newRegion) async { paletteGenerator = await PaletteGenerator.fromImageProvider( widget.image, size: widget.imageSize, @@ -90,20 +92,21 @@ class _ImageColorsState extends State { // Called when the user starts to drag void _onPanDown(DragDownDetails details) { - final RenderBox box = imageKey.currentContext.findRenderObject(); + final RenderBox box = + imageKey.currentContext!.findRenderObject() as RenderBox; final Offset localPosition = box.globalToLocal(details.globalPosition); setState(() { startDrag = localPosition; - currentDrag = startDrag; - dragRegion = Rect.fromPoints(startDrag, currentDrag); + currentDrag = localPosition; + dragRegion = Rect.fromPoints(localPosition, localPosition); }); } // Called as the user drags: just updates the region, not the colors. void _onPanUpdate(DragUpdateDetails details) { setState(() { - currentDrag += details.delta; - dragRegion = Rect.fromPoints(startDrag, currentDrag); + currentDrag = currentDrag! + details.delta; + dragRegion = Rect.fromPoints(startDrag!, currentDrag!); }); } @@ -118,11 +121,16 @@ class _ImageColorsState extends State { // Called when the drag ends. Sets the region, and updates the colors. Future _onPanEnd(DragEndDetails details) async { - Rect newRegion = - (Offset.zero & imageKey.currentContext.size).intersect(dragRegion); - if (newRegion.size.width < 4 && newRegion.size.width < 4) { - newRegion = Offset.zero & imageKey.currentContext.size; + final Size? imageSize = imageKey.currentContext?.size; + Rect? newRegion; + + if (imageSize != null) { + newRegion = (Offset.zero & imageSize).intersect(dragRegion!); + if (newRegion.size.width < 4 && newRegion.size.width < 4) { + newRegion = Offset.zero & imageSize; + } } + await _updatePaletteGenerator(newRegion); setState(() { region = newRegion; @@ -136,7 +144,7 @@ class _ImageColorsState extends State { return Scaffold( backgroundColor: _kBackgroundColor, appBar: AppBar( - title: Text(widget.title), + title: Text(widget.title ?? ''), ), body: Column( mainAxisSize: MainAxisSize.max, @@ -155,8 +163,8 @@ class _ImageColorsState extends State { Image( key: imageKey, image: widget.image, - width: widget.imageSize.width, - height: widget.imageSize.height, + width: widget.imageSize?.width, + height: widget.imageSize?.height, ), // This is the selection rectangle. Positioned.fromRect( @@ -189,19 +197,20 @@ class PaletteSwatches extends StatelessWidget { /// /// The [generator] is optional. If it is null, then the display will /// just be an empty container. - const PaletteSwatches({Key key, this.generator}) : super(key: key); + const PaletteSwatches({Key? key, this.generator}) : super(key: key); /// The [PaletteGenerator] that contains all of the swatches that we're going /// to display. - final PaletteGenerator generator; + final PaletteGenerator? generator; @override Widget build(BuildContext context) { final List swatches = []; - if (generator == null || generator.colors.isEmpty) { + final PaletteGenerator? paletteGen = generator; + if (paletteGen == null || paletteGen.colors.isEmpty) { return Container(); } - for (Color color in generator.colors) { + for (Color color in paletteGen.colors) { swatches.add(PaletteSwatch(color: color)); } return Column( @@ -213,17 +222,18 @@ class PaletteSwatches extends StatelessWidget { children: swatches, ), Container(height: 30.0), - PaletteSwatch(label: 'Dominant', color: generator.dominantColor?.color), PaletteSwatch( - label: 'Light Vibrant', color: generator.lightVibrantColor?.color), - PaletteSwatch(label: 'Vibrant', color: generator.vibrantColor?.color), + label: 'Dominant', color: paletteGen.dominantColor?.color), + PaletteSwatch( + label: 'Light Vibrant', color: paletteGen.lightVibrantColor?.color), + PaletteSwatch(label: 'Vibrant', color: paletteGen.vibrantColor?.color), PaletteSwatch( - label: 'Dark Vibrant', color: generator.darkVibrantColor?.color), + label: 'Dark Vibrant', color: paletteGen.darkVibrantColor?.color), PaletteSwatch( - label: 'Light Muted', color: generator.lightMutedColor?.color), - PaletteSwatch(label: 'Muted', color: generator.mutedColor?.color), + label: 'Light Muted', color: paletteGen.lightMutedColor?.color), + PaletteSwatch(label: 'Muted', color: paletteGen.mutedColor?.color), PaletteSwatch( - label: 'Dark Muted', color: generator.darkMutedColor?.color), + label: 'Dark Muted', color: paletteGen.darkMutedColor?.color), ], ); } @@ -234,19 +244,20 @@ class PaletteSwatches extends StatelessWidget { class PaletteSwatch extends StatelessWidget { /// Creates a PaletteSwatch. /// - /// If the [color] argument is omitted, then the swatch will show a - /// placeholder instead, to indicate that there is no color. + /// If the [paletteColor] has property `isTargetColorFound` as `false`, + /// then the swatch will show a placeholder instead, to indicate + /// that there is no color. const PaletteSwatch({ - Key key, + Key? key, this.color, this.label, }) : super(key: key); - /// The color of the swatch. May be null. - final Color color; + /// The color of the swatch. + final Color? color; /// The optional label to display next to the swatch. - final String label; + final String? label; @override Widget build(BuildContext context) { @@ -292,7 +303,7 @@ class PaletteSwatch extends StatelessWidget { children: [ swatch, Container(width: 5.0), - Text(label), + Text(label!), ], ), ); diff --git a/packages/palette_generator/example/pubspec.yaml b/packages/palette_generator/example/pubspec.yaml index 879d4a21aa9d..b2b0cbd63902 100644 --- a/packages/palette_generator/example/pubspec.yaml +++ b/packages/palette_generator/example/pubspec.yaml @@ -1,16 +1,16 @@ name: image_colors description: A simple example of how to use the PaletteGenerator to load the palette from an image file. - +publish_to: none version: 0.1.0 -publish_to: none +environment: + sdk: ">=2.12.0 <3.0.0" dependencies: - cupertino_icons: ^0.1.2 flutter: sdk: flutter palette_generator: - path: .. + path: ../ dev_dependencies: flutter_test: @@ -19,7 +19,4 @@ dev_dependencies: flutter: uses-material-design: true assets: - - assets/landscape.png - -environment: - sdk: ">=2.2.0 <3.0.0" + - assets/landscape.png diff --git a/packages/palette_generator/lib/palette_generator.dart b/packages/palette_generator/lib/palette_generator.dart index 02200cab44d0..2869155fec8d 100644 --- a/packages/palette_generator/lib/palette_generator.dart +++ b/packages/palette_generator/lib/palette_generator.dart @@ -66,11 +66,10 @@ class PaletteGenerator with Diagnosticable { /// [PaletteGenerator.fromImage] static function. This constructor is mainly /// used for cases when you have your own source of color information and /// would like to use the target selection and scoring methods here. - /// - /// The [paletteColors] argument must not be null. - PaletteGenerator.fromColors(this.paletteColors, {this.targets}) - : assert(paletteColors != null), - selectedSwatches = {} { + PaletteGenerator.fromColors( + this.paletteColors, { + this.targets = const [], + }) : selectedSwatches = {} { _sortSwatches(); _selectSwatches(); } @@ -92,16 +91,15 @@ class PaletteGenerator with Diagnosticable { /// The [targets] are a list of target color types, specified by creating /// custom [PaletteTarget]s. By default, this is the list of targets in /// [PaletteTarget.baseTargets]. - /// - /// The [image] must not be null. static Future fromImage( ui.Image image, { - Rect region, - int maximumColorCount, - List filters, - List targets, + Rect? region, + int maximumColorCount = _defaultCalculateNumberColors, + List filters = const [ + avoidRedBlackWhitePaletteFilter + ], + List targets = const [], }) async { - assert(image != null); assert(region == null || region != Rect.zero); assert( region == null || @@ -113,8 +111,6 @@ class PaletteGenerator with Diagnosticable { region.bottomRight.dy <= image.height), 'Region $region is outside the image ${image.width}x${image.height}'); - filters ??= [avoidRedBlackWhitePaletteFilter]; - maximumColorCount ??= _defaultCalculateNumberColors; final _ColorCutQuantizer quantizer = _ColorCutQuantizer( image, maxColors: maximumColorCount, @@ -153,38 +149,36 @@ class PaletteGenerator with Diagnosticable { /// The [timeout] describes how long to wait for the image to load before /// giving up on it. A value of Duration.zero implies waiting forever. The /// default timeout is 15 seconds. - /// - /// The [imageProvider] and [timeout] arguments must not be null. static Future fromImageProvider( ImageProvider imageProvider, { - Size size, - Rect region, - int maximumColorCount, - List filters, - List targets, + Size? size, + Rect? region, + int maximumColorCount = _defaultCalculateNumberColors, + List filters = const [ + avoidRedBlackWhitePaletteFilter + ], + List targets = const [], Duration timeout = const Duration(seconds: 15), }) async { - assert(imageProvider != null); - assert(timeout != null); - assert(region == null || (region != null && size != null)); + assert(region == null || size != null); assert(region == null || region != Rect.zero); assert( region == null || (region.topLeft.dx >= 0.0 && region.topLeft.dy >= 0.0), - 'Region $region is outside the image ${size.width}x${size.height}'); - assert(region == null || size.contains(region.topLeft), + 'Region $region is outside the image ${size!.width}x${size.height}'); + assert(region == null || size!.contains(region.topLeft), 'Region $region is outside the image $size'); assert( region == null || - (region.bottomRight.dx <= size.width && + (region.bottomRight.dx <= size!.width && region.bottomRight.dy <= size.height), 'Region $region is outside the image $size'); final ImageStream stream = imageProvider.resolve( ImageConfiguration(size: size, devicePixelRatio: 1.0), ); final Completer imageCompleter = Completer(); - Timer loadFailureTimeout; - ImageStreamListener listener; + Timer? loadFailureTimeout; + late ImageStreamListener listener; listener = ImageStreamListener((ImageInfo info, bool synchronousCall) { loadFailureTimeout?.cancel(); stream.removeListener(listener); @@ -202,7 +196,7 @@ class PaletteGenerator with Diagnosticable { } stream.addListener(listener); final ui.Image image = await imageCompleter.future; - ui.Rect newRegion = region; + ui.Rect? newRegion = region; if (size != null && region != null) { final double scale = image.width / size.width; newRegion = Rect.fromLTRB( @@ -246,34 +240,34 @@ class PaletteGenerator with Diagnosticable { /// Returns a vibrant color from the palette. Might be null if an appropriate /// target color could not be found. - PaletteColor get vibrantColor => selectedSwatches[PaletteTarget.vibrant]; + PaletteColor? get vibrantColor => selectedSwatches[PaletteTarget.vibrant]; /// Returns a light and vibrant color from the palette. Might be null if an /// appropriate target color could not be found. - PaletteColor get lightVibrantColor => + PaletteColor? get lightVibrantColor => selectedSwatches[PaletteTarget.lightVibrant]; /// Returns a dark and vibrant color from the palette. Might be null if an /// appropriate target color could not be found. - PaletteColor get darkVibrantColor => + PaletteColor? get darkVibrantColor => selectedSwatches[PaletteTarget.darkVibrant]; /// Returns a muted color from the palette. Might be null if an appropriate /// target color could not be found. - PaletteColor get mutedColor => selectedSwatches[PaletteTarget.muted]; + PaletteColor? get mutedColor => selectedSwatches[PaletteTarget.muted]; /// Returns a muted and light color from the palette. Might be null if an /// appropriate target color could not be found. - PaletteColor get lightMutedColor => + PaletteColor? get lightMutedColor => selectedSwatches[PaletteTarget.lightMuted]; /// Returns a muted and dark color from the palette. Might be null if an /// appropriate target color could not be found. - PaletteColor get darkMutedColor => selectedSwatches[PaletteTarget.darkMuted]; + PaletteColor? get darkMutedColor => selectedSwatches[PaletteTarget.darkMuted]; /// The dominant color (the color with the largest population). - PaletteColor get dominantColor => _dominantColor; - PaletteColor _dominantColor; + PaletteColor? get dominantColor => _dominantColor; + PaletteColor? _dominantColor; void _sortSwatches() { if (paletteColors.isEmpty) { @@ -288,18 +282,22 @@ class PaletteGenerator with Diagnosticable { } void _selectSwatches() { - final Set allTargets = Set.from( - (targets ?? []) + PaletteTarget.baseTargets); + final Set allTargets = + Set.from(targets + PaletteTarget.baseTargets); final Set usedColors = {}; for (PaletteTarget target in allTargets) { target._normalizeWeights(); - selectedSwatches[target] = _generateScoredTarget(target, usedColors); + final PaletteColor? targetScore = + _generateScoredTarget(target, usedColors); + if (targetScore != null) { + selectedSwatches[target] = targetScore; + } } } - PaletteColor _generateScoredTarget( + PaletteColor? _generateScoredTarget( PaletteTarget target, Set usedColors) { - final PaletteColor maxScoreSwatch = + final PaletteColor? maxScoreSwatch = _getMaxScoredSwatchForTarget(target, usedColors); if (maxScoreSwatch != null && target.isExclusive) { // If we have a color, and the target is exclusive, add the color to the @@ -309,10 +307,10 @@ class PaletteGenerator with Diagnosticable { return maxScoreSwatch; } - PaletteColor _getMaxScoredSwatchForTarget( + PaletteColor? _getMaxScoredSwatchForTarget( PaletteTarget target, Set usedColors) { double maxScore = 0.0; - PaletteColor maxScoreSwatch; + PaletteColor? maxScoreSwatch; for (PaletteColor paletteColor in paletteColors) { if (_shouldBeScoredForTarget(paletteColor, target, usedColors)) { final double score = _generateScore(paletteColor, target); @@ -352,9 +350,9 @@ class PaletteGenerator with Diagnosticable { valueScore = target.lightnessWeight * (1.0 - (hslColor.lightness - target.targetLightness).abs()); } - if (target.populationWeight > 0.0) { + if (_dominantColor != null && target.populationWeight > 0.0) { populationScore = target.populationWeight * - (paletteColor.population / _dominantColor.population); + (paletteColor.population / _dominantColor!.population); } return saturationScore + valueScore + populationScore; @@ -382,8 +380,6 @@ class PaletteGenerator with Diagnosticable { /// * [PaletteGenerator], a class for selecting color palettes from images. class PaletteTarget with Diagnosticable { /// Creates a [PaletteTarget] for custom palette selection. - /// - /// None of the arguments can be null. PaletteTarget({ this.minimumSaturation = 0.0, this.targetSaturation = 0.5, @@ -392,37 +388,31 @@ class PaletteTarget with Diagnosticable { this.targetLightness = 0.5, this.maximumLightness = 1.0, this.isExclusive = true, - }) : assert(minimumSaturation != null), - assert(targetSaturation != null), - assert(maximumSaturation != null), - assert(minimumLightness != null), - assert(targetLightness != null), - assert(maximumLightness != null), - assert(isExclusive != null); - - /// The minimum saturation value for this target. Must not be null. + }); + + /// The minimum saturation value for this target. final double minimumSaturation; - /// The target saturation value for this target. Must not be null. + /// The target saturation value for this target. final double targetSaturation; - /// The maximum saturation value for this target. Must not be null. + /// The maximum saturation value for this target. final double maximumSaturation; - /// The minimum lightness value for this target. Must not be null. + /// The minimum lightness value for this target. final double minimumLightness; - /// The target lightness value for this target. Must not be null. + /// The target lightness value for this target. final double targetLightness; - /// The maximum lightness value for this target. Must not be null. + /// The maximum lightness value for this target. final double maximumLightness; /// Returns whether any color selected for this target is exclusive for this /// target only. /// /// If false, then the color can also be selected for other targets. Defaults - /// to true. Must not be null. + /// to true. final bool isExclusive; /// The weight of importance that a color's saturation value has on selection. @@ -610,11 +600,8 @@ typedef _ContrastCalculator = double Function(Color a, Color b, int alpha); /// * [PaletteGenerator], a class for selecting color palettes from images. class PaletteColor with Diagnosticable { /// Generate a [PaletteColor]. - /// - /// The `color` and `population` parameters must not be null. - PaletteColor(this.color, this.population) - : assert(color != null), - assert(population != null); + PaletteColor(this.color, this.population); + static const double _minContrastTitleText = 3.0; static const double _minContrastBodyText = 4.5; @@ -630,29 +617,29 @@ class PaletteColor with Diagnosticable { if (_titleTextColor == null) { _ensureTextColorsGenerated(); } - return _titleTextColor; + return _titleTextColor!; } - Color _titleTextColor; + Color? _titleTextColor; /// The color of body text for use with this palette color. Color get bodyTextColor { if (_bodyTextColor == null) { _ensureTextColorsGenerated(); } - return _bodyTextColor; + return _bodyTextColor!; } - Color _bodyTextColor; + Color? _bodyTextColor; void _ensureTextColorsGenerated() { if (_titleTextColor == null || _bodyTextColor == null) { const Color white = Color(0xffffffff); const Color black = Color(0xff000000); // First check white, as most colors will be dark - final int lightBodyAlpha = + final int? lightBodyAlpha = _calculateMinimumAlpha(white, color, _minContrastBodyText); - final int lightTitleAlpha = + final int? lightTitleAlpha = _calculateMinimumAlpha(white, color, _minContrastTitleText); if (lightBodyAlpha != null && lightTitleAlpha != null) { @@ -662,12 +649,12 @@ class PaletteColor with Diagnosticable { return; } - final int darkBodyAlpha = + final int? darkBodyAlpha = _calculateMinimumAlpha(black, color, _minContrastBodyText); - final int darkTitleAlpha = + final int? darkTitleAlpha = _calculateMinimumAlpha(black, color, _minContrastTitleText); - if (darkBodyAlpha != null && darkBodyAlpha != null) { + if (darkBodyAlpha != null && darkTitleAlpha != null) { // If we found valid dark values, use them and return _bodyTextColor = black.withAlpha(darkBodyAlpha); _titleTextColor = black.withAlpha(darkTitleAlpha); @@ -676,12 +663,12 @@ class PaletteColor with Diagnosticable { // If we reach here then we can not find title and body values which use // the same lightness, we need to use mismatched values - _bodyTextColor = lightBodyAlpha != null // + _bodyTextColor = lightBodyAlpha != null ? white.withAlpha(lightBodyAlpha) - : black.withAlpha(darkBodyAlpha); - _titleTextColor = lightTitleAlpha != null // + : black.withAlpha(darkBodyAlpha ?? 255); + _titleTextColor = lightTitleAlpha != null ? white.withAlpha(lightTitleAlpha) - : black.withAlpha(darkTitleAlpha); + : black.withAlpha(darkTitleAlpha ?? 255); } } @@ -710,10 +697,8 @@ class PaletteColor with Diagnosticable { // // Returns the alpha value in the range 0-255, or null if no value could be // calculated. - static int _calculateMinimumAlpha( + static int? _calculateMinimumAlpha( Color foreground, Color background, double minContrastRatio) { - assert(foreground != null); - assert(background != null); assert(background.alpha == 0xff, 'The background cannot be translucent: $background.'); double contrastCalculator(Color fg, Color bg, int alpha) { @@ -744,8 +729,6 @@ class PaletteColor with Diagnosticable { double minContrastRatio, _ContrastCalculator calculator, ) { - assert(foreground != null); - assert(background != null); assert(background.alpha == 0xff, 'The background cannot be translucent: $background.'); const int minAlphaSearchMaxIterations = 10; @@ -844,9 +827,7 @@ enum _ColorComponent { /// A box that represents a volume in the RGB color space. class _ColorVolumeBox { _ColorVolumeBox( - this._lowerIndex, this._upperIndex, this.histogram, this.colors) - : assert(histogram != null), - assert(colors != null) { + this._lowerIndex, this._upperIndex, this.histogram, this.colors) { _fitMinimumBox(); } @@ -858,15 +839,15 @@ class _ColorVolumeBox { int _upperIndex; // The population of colors within this box. - int _population; + late int _population; // Bounds in each of the dimensions. - int _minRed; - int _maxRed; - int _minGreen; - int _maxGreen; - int _minBlue; - int _maxBlue; + late int _minRed; + late int _maxRed; + late int _minGreen; + late int _maxGreen; + late int _minBlue; + late int _maxBlue; int getVolume() { return (_maxRed - _minRed + 1) * @@ -895,7 +876,7 @@ class _ColorVolumeBox { int count = 0; for (int i = _lowerIndex; i <= _upperIndex; i++) { final Color color = colors[i]; - count += histogram[color].value; + count += histogram[color]!.value; if (color.red > maxRed) { maxRed = color.red; } @@ -982,7 +963,6 @@ class _ColorVolumeBox { final int bValue = makeValue(b.blue, b.green, b.red); return aValue.compareTo(bValue); } - return 0; } // We need to sort the colors in this box based on the longest color @@ -993,7 +973,7 @@ class _ColorVolumeBox { colors.replaceRange(_lowerIndex, _upperIndex + 1, colorSubset); final int median = (_population / 2).round(); for (int i = 0, count = 0; i <= colorSubset.length; i++) { - count += histogram[colorSubset[i]].value; + count += histogram[colorSubset[i]]!.value; if (count >= median) { // We never want to split on the upperIndex, as this will result in the // same box. @@ -1010,7 +990,7 @@ class _ColorVolumeBox { int totalPopulation = 0; for (int i = _lowerIndex; i <= _upperIndex; i++) { final Color color = colors[i]; - final int colorPopulation = histogram[color].value; + final int colorPopulation = histogram[color]!.value; totalPopulation += colorPopulation; redSum += colorPopulation * color.red; greenSum += colorPopulation * color.green; @@ -1038,12 +1018,12 @@ class _ColorHistogram { >>{}; final DoubleLinkedQueue _keys = DoubleLinkedQueue(); - _ColorCount operator [](Color color) { - final Map> redMap = _hist[color.red]; + _ColorCount? operator [](Color color) { + final Map>? redMap = _hist[color.red]; if (redMap == null) { return null; } - final Map blueMap = redMap[color.blue]; + final Map? blueMap = redMap[color.blue]; if (blueMap == null) { return null; } @@ -1057,13 +1037,13 @@ class _ColorHistogram { bool newColor = false; - Map> redMap = _hist[red]; + Map>? redMap = _hist[red]; if (redMap == null) { _hist[red] = redMap = >{}; newColor = true; } - Map blueMap = redMap[blue]; + Map? blueMap = redMap[blue]; if (blueMap == null) { redMap[blue] = blueMap = {}; newColor = true; @@ -1082,7 +1062,7 @@ class _ColorHistogram { void removeWhere(bool predicate(Color key)) { for (Color key in _keys) { if (predicate(key)) { - _hist[key.red][key.blue][key.green] = null; + _hist[key.red]?[key.blue]?.remove(key.green); } } _keys.removeWhere((Color color) => predicate(color)); @@ -1102,10 +1082,8 @@ class _ColorCutQuantizer { this.image, { this.maxColors = PaletteGenerator._defaultCalculateNumberColors, this.region, - this.filters, - }) : assert(image != null), - assert(maxColors != null), - assert(region == null || region != Rect.zero), + this.filters = const [avoidRedBlackWhitePaletteFilter], + }) : assert(region == null || region != Rect.zero), _paletteColors = []; FutureOr> get quantizedColors async { @@ -1120,11 +1098,11 @@ class _ColorCutQuantizer { final List _paletteColors; final int maxColors; - final Rect region; + final Rect? region; final List filters; Iterable _getImagePixels(ByteData pixels, int width, int height, - {Rect region}) sync* { + {Rect? region}) sync* { final int rowStride = width * 4; int rowStart; int rowEnd; @@ -1161,7 +1139,7 @@ class _ColorCutQuantizer { bool _shouldIgnoreColor(Color color) { final HSLColor hslColor = HSLColor.fromColor(color); - if (filters != null && filters.isNotEmpty) { + if (filters.isNotEmpty) { for (PaletteFilter filter in filters) { if (!filter(hslColor)) { return true; @@ -1187,13 +1165,16 @@ class _ColorCutQuantizer { ); } - final ByteData imageData = + final ByteData? imageData = await image.toByteData(format: ui.ImageByteFormat.rawRgba); + if (imageData == null) { + throw 'Failed to encode the image.'; + } final Iterable pixels = _getImagePixels(imageData, image.width, image.height, region: region); final _ColorHistogram hist = _ColorHistogram(); - Color currentColor; - _ColorCount currentColorCount; + Color? currentColor; + _ColorCount? currentColorCount; for (Color pixel in pixels) { // Update the histogram, but only for non-zero alpha values, and for the @@ -1212,7 +1193,7 @@ class _ColorCutQuantizer { hist[colorKey] = currentColorCount = _ColorCount(); } } - currentColorCount.value = currentColorCount.value + 1; + currentColorCount!.value = currentColorCount.value + 1; } // Now let's remove any colors that the filters want to ignore. hist.removeWhere((Color color) { @@ -1223,7 +1204,7 @@ class _ColorCutQuantizer { // the colors. _paletteColors.clear(); for (Color color in hist.keys) { - _paletteColors.add(PaletteColor(color, hist[color].value)); + _paletteColors.add(PaletteColor(color, hist[color]!.value)); } } else { // We need use quantization to reduce the number of colors @@ -1263,7 +1244,7 @@ class _ColorCutQuantizer { void _splitBoxes(PriorityQueue<_ColorVolumeBox> queue, final int maxSize) { while (queue.length < maxSize) { final _ColorVolumeBox colorVolumeBox = queue.removeFirst(); - if (colorVolumeBox != null && colorVolumeBox.canSplit()) { + if (colorVolumeBox.canSplit()) { // First split the box, and offer the result queue.add(colorVolumeBox.splitBox()); // Then offer the box back diff --git a/packages/palette_generator/pubspec.yaml b/packages/palette_generator/pubspec.yaml index 79ea89110c68..8bc401b776ce 100644 --- a/packages/palette_generator/pubspec.yaml +++ b/packages/palette_generator/pubspec.yaml @@ -1,19 +1,18 @@ name: palette_generator description: Flutter package for generating palette colors from a source image. homepage: https://github.com/flutter/packages/tree/master/packages/palette_generator -version: 0.2.4+1 +version: 0.3.0 + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.15.21" dependencies: - collection: ^1.14.6 + collection: ^1.15.0 flutter: sdk: flutter - path: ^1.6.1 + path: ^1.8.0 dev_dependencies: flutter_test: sdk: flutter - mockito: ^3.0.0 - -environment: - sdk: ">=2.2.0 <3.0.0" - flutter: ">=1.15.21" diff --git a/packages/palette_generator/test/palette_generator_test.dart b/packages/palette_generator/test/palette_generator_test.dart index e85fd6e3185f..336ec8cc4571 100644 --- a/packages/palette_generator/test/palette_generator_test.dart +++ b/packages/palette_generator/test/palette_generator_test.dart @@ -73,7 +73,7 @@ Future main() async { test('PaletteGenerator works on 1-pixel wide blue image', () async { final PaletteGenerator palette = - await PaletteGenerator.fromImageProvider(testImages['tall_blue']); + await PaletteGenerator.fromImageProvider(testImages['tall_blue']!); expect(palette.paletteColors.length, equals(1)); expect(palette.paletteColors[0].color, within(distance: 8, from: const Color(0xff0000ff))); @@ -81,7 +81,7 @@ Future main() async { test('PaletteGenerator works on 1-pixel high red image', () async { final PaletteGenerator palette = - await PaletteGenerator.fromImageProvider(testImages['wide_red']); + await PaletteGenerator.fromImageProvider(testImages['wide_red']!); expect(palette.paletteColors.length, equals(1)); expect(palette.paletteColors[0].color, within(distance: 8, from: const Color(0xffff0000))); @@ -89,18 +89,19 @@ Future main() async { test('PaletteGenerator finds dominant color and text colors', () async { final PaletteGenerator palette = - await PaletteGenerator.fromImageProvider(testImages['dominant']); + await PaletteGenerator.fromImageProvider(testImages['dominant']!); expect(palette.paletteColors.length, equals(3)); - expect(palette.dominantColor.color, + expect(palette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, within(distance: 8, from: const Color(0xff0000ff))); - expect(palette.dominantColor.titleTextColor, + expect(palette.dominantColor!.titleTextColor, within(distance: 8, from: const Color(0x8affffff))); - expect(palette.dominantColor.bodyTextColor, + expect(palette.dominantColor!.bodyTextColor, within(distance: 8, from: const Color(0xb2ffffff))); }); test('PaletteGenerator works with regions', () async { - final ImageProvider imageProvider = testImages['dominant']; + final ImageProvider imageProvider = testImages['dominant']!; Rect region = const Rect.fromLTRB(0.0, 0.0, 100.0, 100.0); const Size size = Size(100.0, 100.0); PaletteGenerator palette = await PaletteGenerator.fromImageProvider( @@ -108,27 +109,30 @@ Future main() async { region: region, size: size); expect(palette.paletteColors.length, equals(3)); - expect(palette.dominantColor.color, + expect(palette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, within(distance: 8, from: const Color(0xff0000ff))); region = const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); palette = await PaletteGenerator.fromImageProvider(imageProvider, region: region, size: size); expect(palette.paletteColors.length, equals(1)); - expect(palette.dominantColor.color, + expect(palette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, within(distance: 8, from: const Color(0xffff0000))); region = const Rect.fromLTRB(0.0, 0.0, 30.0, 20.0); palette = await PaletteGenerator.fromImageProvider(imageProvider, region: region, size: size); expect(palette.paletteColors.length, equals(3)); - expect(palette.dominantColor.color, + expect(palette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, within(distance: 8, from: const Color(0xff00ff00))); }); test('PaletteGenerator works as expected on a real image', () async { final PaletteGenerator palette = - await PaletteGenerator.fromImageProvider(testImages['landscape']); + await PaletteGenerator.fromImageProvider(testImages['landscape']!); final List expectedSwatches = [ PaletteColor(const Color(0xff3f630c), 10137), PaletteColor(const Color(0xff3c4b2a), 4773), @@ -150,24 +154,30 @@ Future main() async { final Iterable expectedColors = expectedSwatches.map((PaletteColor swatch) => swatch.color); expect(palette.paletteColors, containsAll(expectedSwatches)); - expect(palette.vibrantColor.color, + expect(palette.vibrantColor, isNotNull); + expect(palette.lightVibrantColor, isNotNull); + expect(palette.darkVibrantColor, isNotNull); + expect(palette.mutedColor, isNotNull); + expect(palette.lightMutedColor, isNotNull); + expect(palette.darkMutedColor, isNotNull); + expect(palette.vibrantColor!.color, within(distance: 8, from: const Color(0xfff6b835))); - expect(palette.lightVibrantColor.color, + expect(palette.lightVibrantColor!.color, within(distance: 8, from: const Color(0xff82b2e9))); - expect(palette.darkVibrantColor.color, + expect(palette.darkVibrantColor!.color, within(distance: 8, from: const Color(0xff3f630c))); - expect(palette.mutedColor.color, + expect(palette.mutedColor!.color, within(distance: 8, from: const Color(0xff6c7fa2))); - expect(palette.lightMutedColor.color, + expect(palette.lightMutedColor!.color, within(distance: 8, from: const Color(0xffc4b2b2))); - expect(palette.darkMutedColor.color, + expect(palette.darkMutedColor!.color, within(distance: 8, from: const Color(0xff3c4b2a))); expect(palette.colors, containsAllInOrder(expectedColors)); expect(palette.colors.length, equals(palette.paletteColors.length)); }); test('PaletteGenerator limits max colors', () async { - final ImageProvider imageProvider = testImages['landscape']; + final ImageProvider imageProvider = testImages['landscape']!; PaletteGenerator palette = await PaletteGenerator.fromImageProvider( imageProvider, maximumColorCount: 32); @@ -181,7 +191,7 @@ Future main() async { }); test('PaletteGenerator Filters work', () async { - final ImageProvider imageProvider = testImages['landscape']; + final ImageProvider imageProvider = testImages['landscape']!; // First, test that supplying the default filter is the same as not supplying one. List filters = [ avoidRedBlackWhitePaletteFilter @@ -210,7 +220,8 @@ Future main() async { final Iterable expectedColors = expectedSwatches.map((PaletteColor swatch) => swatch.color); expect(palette.paletteColors, containsAll(expectedSwatches)); - expect(palette.dominantColor.color, + expect(palette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, within(distance: 8, from: const Color(0xff3f630c))); expect(palette.colors, containsAllInOrder(expectedColors)); @@ -240,7 +251,8 @@ Future main() async { blueSwatches.map((PaletteColor swatch) => swatch.color); expect(palette.paletteColors, containsAll(blueSwatches)); - expect(palette.dominantColor.color, + expect(palette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, within(distance: 8, from: const Color(0xff4c5c75))); expect(palette.colors, containsAllInOrder(expectedBlues)); @@ -270,7 +282,8 @@ Future main() async { blueGreenSwatches.map((PaletteColor swatch) => swatch.color); expect(palette.paletteColors, containsAll(blueGreenSwatches)); - expect(palette.dominantColor.color, + expect(palette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, within(distance: 8, from: const Color(0xffc8e8f8))); expect(palette.colors, containsAllInOrder(expectedBlueGreens)); @@ -284,7 +297,7 @@ Future main() async { }); test('PaletteGenerator targets work', () async { - final ImageProvider imageProvider = testImages['landscape']; + final ImageProvider imageProvider = testImages['landscape']!; // Passing an empty set of targets works the same as passing a null targets // list. PaletteGenerator palette = await PaletteGenerator.fromImageProvider( @@ -313,14 +326,18 @@ Future main() async { expect(palette.darkMutedColor, isNotNull); expect(palette.selectedSwatches.length, equals(PaletteTarget.baseTargets.length + 2)); - expect(palette.selectedSwatches[saturationExtremeTargets[0]].color, - equals(const Color(0xfff6b835))); - expect(palette.selectedSwatches[saturationExtremeTargets[1]].color, - equals(const Color(0xff6e80a2))); + final PaletteColor? selectedSwatchesFirst = + palette.selectedSwatches[saturationExtremeTargets[0]]; + final PaletteColor? selectedSwatchesSecond = + palette.selectedSwatches[saturationExtremeTargets[1]]; + expect(selectedSwatchesFirst, isNotNull); + expect(selectedSwatchesSecond, isNotNull); + expect(selectedSwatchesFirst!.color, equals(const Color(0xfff6b835))); + expect(selectedSwatchesSecond!.color, equals(const Color(0xff6e80a2))); }); test('PaletteGenerator produces consistent results', () async { - final ImageProvider imageProvider = testImages['landscape']; + final ImageProvider imageProvider = testImages['landscape']!; PaletteGenerator lastPalette = await PaletteGenerator.fromImageProvider(imageProvider); @@ -334,8 +351,10 @@ Future main() async { expect(palette.mutedColor, equals(lastPalette.mutedColor)); expect(palette.lightMutedColor, equals(lastPalette.lightMutedColor)); expect(palette.darkMutedColor, equals(lastPalette.darkMutedColor)); - expect(palette.dominantColor.color, - within(distance: 8, from: lastPalette.dominantColor.color)); + expect(palette.dominantColor, isNotNull); + expect(lastPalette.dominantColor, isNotNull); + expect(palette.dominantColor!.color, + within(distance: 8, from: lastPalette.dominantColor!.color)); lastPalette = palette; } });