| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui show Image, Codec, FrameInfo, instantiateImageCodec; |
| |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/foundation.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:palette_generator/palette_generator.dart'; |
| |
| /// An image provider implementation for testing that takes a pre-loaded image. |
| /// This avoids handling asynchronous I/O in the test zone, which is |
| /// problematic. |
| class FakeImageProvider extends ImageProvider<FakeImageProvider> { |
| const FakeImageProvider(this._image, {this.scale = 1.0}); |
| |
| final ui.Image _image; |
| |
| /// The scale to place in the [ImageInfo] object of the image. |
| final double scale; |
| |
| @override |
| Future<FakeImageProvider> obtainKey(ImageConfiguration configuration) { |
| return SynchronousFuture<FakeImageProvider>(this); |
| } |
| |
| @override |
| ImageStreamCompleter load(FakeImageProvider key, DecoderCallback decode) { |
| assert(key == this); |
| return OneFrameImageStreamCompleter( |
| SynchronousFuture<ImageInfo>( |
| ImageInfo(image: _image, scale: scale), |
| ), |
| ); |
| } |
| } |
| |
| Future<ImageProvider> loadImage(String name) async { |
| File imagePath = File(path.joinAll(<String>['assets', name])); |
| if (path.split(Directory.current.absolute.path).last != 'test') { |
| imagePath = File(path.join('test', imagePath.path)); |
| } |
| final Uint8List data = Uint8List.fromList(imagePath.readAsBytesSync()); |
| final ui.Codec codec = await ui.instantiateImageCodec(data); |
| final ui.FrameInfo frameInfo = await codec.getNextFrame(); |
| return FakeImageProvider(frameInfo.image); |
| } |
| |
| Future<void> main() async { |
| // Load the images outside of the test zone so that IO doesn't get |
| // complicated. |
| final List<String> imageNames = <String>[ |
| 'tall_blue', |
| 'wide_red', |
| 'dominant', |
| 'landscape' |
| ]; |
| final Map<String, ImageProvider> testImages = <String, ImageProvider>{}; |
| for (String name in imageNames) { |
| testImages[name] = await loadImage('$name.png'); |
| } |
| |
| testWidgets('Initialize the image cache', (WidgetTester tester) async { |
| // We need to have a testWidgets test in order to initialize the image |
| // cache for the other tests, but they timeout if they too are testWidgets |
| // tests. |
| tester.pumpWidget(const Placeholder()); |
| }); |
| |
| test('PaletteGenerator works on 1-pixel wide blue image', () async { |
| final PaletteGenerator palette = |
| await PaletteGenerator.fromImageProvider(testImages['tall_blue']); |
| expect(palette.paletteColors.length, equals(1)); |
| expect(palette.paletteColors[0].color, |
| within<Color>(distance: 8, from: const Color(0xff0000ff))); |
| }); |
| |
| test('PaletteGenerator works on 1-pixel high red image', () async { |
| final PaletteGenerator palette = |
| await PaletteGenerator.fromImageProvider(testImages['wide_red']); |
| expect(palette.paletteColors.length, equals(1)); |
| expect(palette.paletteColors[0].color, |
| within<Color>(distance: 8, from: const Color(0xffff0000))); |
| }); |
| |
| test('PaletteGenerator finds dominant color and text colors', () async { |
| final PaletteGenerator palette = |
| await PaletteGenerator.fromImageProvider(testImages['dominant']); |
| expect(palette.paletteColors.length, equals(3)); |
| expect(palette.dominantColor.color, |
| within<Color>(distance: 8, from: const Color(0xff0000ff))); |
| expect(palette.dominantColor.titleTextColor, |
| within<Color>(distance: 8, from: const Color(0x8affffff))); |
| expect(palette.dominantColor.bodyTextColor, |
| within<Color>(distance: 8, from: const Color(0xb2ffffff))); |
| }); |
| |
| test('PaletteGenerator works with regions', () async { |
| 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( |
| imageProvider, |
| region: region, |
| size: size); |
| expect(palette.paletteColors.length, equals(3)); |
| expect(palette.dominantColor.color, |
| within<Color>(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, |
| within<Color>(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, |
| within<Color>(distance: 8, from: const Color(0xff00ff00))); |
| }); |
| |
| test('PaletteGenerator works as expected on a real image', () async { |
| final PaletteGenerator palette = |
| await PaletteGenerator.fromImageProvider(testImages['landscape']); |
| final List<PaletteColor> expectedSwatches = <PaletteColor>[ |
| PaletteColor(const Color(0xff3f630c), 10137), |
| PaletteColor(const Color(0xff3c4b2a), 4773), |
| PaletteColor(const Color(0xff81b2e9), 4762), |
| PaletteColor(const Color(0xffc0d6ec), 4714), |
| PaletteColor(const Color(0xff4c4f50), 2465), |
| PaletteColor(const Color(0xff5c635b), 2463), |
| PaletteColor(const Color(0xff6e80a2), 2421), |
| PaletteColor(const Color(0xff9995a3), 1214), |
| PaletteColor(const Color(0xff676c4d), 1213), |
| PaletteColor(const Color(0xffc4b2b2), 1173), |
| PaletteColor(const Color(0xff445166), 1040), |
| PaletteColor(const Color(0xff475d83), 1019), |
| PaletteColor(const Color(0xff7e7360), 589), |
| PaletteColor(const Color(0xfff6b835), 286), |
| PaletteColor(const Color(0xffb9983d), 152), |
| PaletteColor(const Color(0xffe3ab35), 149), |
| ]; |
| final Iterable<Color> expectedColors = |
| expectedSwatches.map<Color>((PaletteColor swatch) => swatch.color); |
| expect(palette.paletteColors, containsAll(expectedSwatches)); |
| expect(palette.vibrantColor.color, |
| within<Color>(distance: 8, from: const Color(0xfff6b835))); |
| expect(palette.lightVibrantColor.color, |
| within<Color>(distance: 8, from: const Color(0xff82b2e9))); |
| expect(palette.darkVibrantColor.color, |
| within<Color>(distance: 8, from: const Color(0xff3f630c))); |
| expect(palette.mutedColor.color, |
| within<Color>(distance: 8, from: const Color(0xff6c7fa2))); |
| expect(palette.lightMutedColor.color, |
| within<Color>(distance: 8, from: const Color(0xffc4b2b2))); |
| expect(palette.darkMutedColor.color, |
| within<Color>(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']; |
| PaletteGenerator palette = await PaletteGenerator.fromImageProvider( |
| imageProvider, |
| maximumColorCount: 32); |
| expect(palette.paletteColors.length, equals(31)); |
| palette = await PaletteGenerator.fromImageProvider(imageProvider, |
| maximumColorCount: 1); |
| expect(palette.paletteColors.length, equals(1)); |
| palette = await PaletteGenerator.fromImageProvider(imageProvider, |
| maximumColorCount: 15); |
| expect(palette.paletteColors.length, equals(15)); |
| }); |
| |
| test('PaletteGenerator Filters work', () async { |
| final ImageProvider imageProvider = testImages['landscape']; |
| // First, test that supplying the default filter is the same as not supplying one. |
| List<PaletteFilter> filters = <PaletteFilter>[ |
| avoidRedBlackWhitePaletteFilter |
| ]; |
| PaletteGenerator palette = await PaletteGenerator.fromImageProvider( |
| imageProvider, |
| filters: filters); |
| final List<PaletteColor> expectedSwatches = <PaletteColor>[ |
| PaletteColor(const Color(0xff3f630c), 10137), |
| PaletteColor(const Color(0xff3c4b2a), 4773), |
| PaletteColor(const Color(0xff81b2e9), 4762), |
| PaletteColor(const Color(0xffc0d6ec), 4714), |
| PaletteColor(const Color(0xff4c4f50), 2465), |
| PaletteColor(const Color(0xff5c635b), 2463), |
| PaletteColor(const Color(0xff6e80a2), 2421), |
| PaletteColor(const Color(0xff9995a3), 1214), |
| PaletteColor(const Color(0xff676c4d), 1213), |
| PaletteColor(const Color(0xffc4b2b2), 1173), |
| PaletteColor(const Color(0xff445166), 1040), |
| PaletteColor(const Color(0xff475d83), 1019), |
| PaletteColor(const Color(0xff7e7360), 589), |
| PaletteColor(const Color(0xfff6b835), 286), |
| PaletteColor(const Color(0xffb9983d), 152), |
| PaletteColor(const Color(0xffe3ab35), 149), |
| ]; |
| final Iterable<Color> expectedColors = |
| expectedSwatches.map<Color>((PaletteColor swatch) => swatch.color); |
| expect(palette.paletteColors, containsAll(expectedSwatches)); |
| expect(palette.dominantColor.color, |
| within<Color>(distance: 8, from: const Color(0xff3f630c))); |
| expect(palette.colors, containsAllInOrder(expectedColors)); |
| |
| // A non-default filter works (and the default filter isn't applied too). |
| filters = <PaletteFilter>[onlyBluePaletteFilter]; |
| palette = await PaletteGenerator.fromImageProvider(imageProvider, |
| filters: filters); |
| final List<PaletteColor> blueSwatches = <PaletteColor>[ |
| PaletteColor(const Color(0xff4c5c75), 1515), |
| PaletteColor(const Color(0xff7483a1), 1505), |
| PaletteColor(const Color(0xff515661), 1476), |
| PaletteColor(const Color(0xff769dd4), 1470), |
| PaletteColor(const Color(0xff3e4858), 777), |
| PaletteColor(const Color(0xff98a3bc), 760), |
| PaletteColor(const Color(0xffb4c7e0), 760), |
| PaletteColor(const Color(0xff99bbe5), 742), |
| PaletteColor(const Color(0xffcbdef0), 701), |
| PaletteColor(const Color(0xff1c212b), 429), |
| PaletteColor(const Color(0xff393c46), 417), |
| PaletteColor(const Color(0xff526483), 394), |
| PaletteColor(const Color(0xff61708b), 372), |
| PaletteColor(const Color(0xff5e8ccc), 345), |
| PaletteColor(const Color(0xff587ab4), 194), |
| PaletteColor(const Color(0xff5584c8), 182), |
| ]; |
| final Iterable<Color> expectedBlues = |
| blueSwatches.map<Color>((PaletteColor swatch) => swatch.color); |
| |
| expect(palette.paletteColors, containsAll(blueSwatches)); |
| expect(palette.dominantColor.color, |
| within<Color>(distance: 8, from: const Color(0xff4c5c75))); |
| expect(palette.colors, containsAllInOrder(expectedBlues)); |
| |
| // More than one filter is the intersection of the two filters. |
| filters = <PaletteFilter>[onlyBluePaletteFilter, onlyCyanPaletteFilter]; |
| palette = await PaletteGenerator.fromImageProvider(imageProvider, |
| filters: filters); |
| final List<PaletteColor> blueGreenSwatches = <PaletteColor>[ |
| PaletteColor(const Color(0xffc8e8f8), 87), |
| PaletteColor(const Color(0xff5c6c74), 73), |
| PaletteColor(const Color(0xff6f8088), 49), |
| PaletteColor(const Color(0xff687880), 49), |
| PaletteColor(const Color(0xff506068), 45), |
| PaletteColor(const Color(0xff485860), 39), |
| PaletteColor(const Color(0xff405058), 21), |
| PaletteColor(const Color(0xffd6ebf3), 11), |
| PaletteColor(const Color(0xff2f3f47), 7), |
| PaletteColor(const Color(0xff0f1f27), 6), |
| PaletteColor(const Color(0xffc0e0f0), 6), |
| PaletteColor(const Color(0xff203038), 3), |
| PaletteColor(const Color(0xff788890), 2), |
| PaletteColor(const Color(0xff384850), 2), |
| PaletteColor(const Color(0xff98a8b0), 1), |
| PaletteColor(const Color(0xffa8b8c0), 1), |
| ]; |
| final Iterable<Color> expectedBlueGreens = |
| blueGreenSwatches.map<Color>((PaletteColor swatch) => swatch.color); |
| |
| expect(palette.paletteColors, containsAll(blueGreenSwatches)); |
| expect(palette.dominantColor.color, |
| within<Color>(distance: 8, from: const Color(0xffc8e8f8))); |
| expect(palette.colors, containsAllInOrder(expectedBlueGreens)); |
| |
| // Mutually exclusive filters return an empty palette. |
| filters = <PaletteFilter>[onlyBluePaletteFilter, onlyGreenPaletteFilter]; |
| palette = await PaletteGenerator.fromImageProvider(imageProvider, |
| filters: filters); |
| expect(palette.paletteColors, isEmpty); |
| expect(palette.dominantColor, isNull); |
| expect(palette.colors, isEmpty); |
| }); |
| |
| test('PaletteGenerator targets work', () async { |
| 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( |
| imageProvider, |
| targets: <PaletteTarget>[]); |
| expect(palette.selectedSwatches, isNotEmpty); |
| expect(palette.vibrantColor, isNotNull); |
| expect(palette.lightVibrantColor, isNotNull); |
| expect(palette.darkVibrantColor, isNotNull); |
| expect(palette.mutedColor, isNotNull); |
| expect(palette.lightMutedColor, isNotNull); |
| expect(palette.darkMutedColor, isNotNull); |
| |
| // Passing targets augments the baseTargets, and those targets are found. |
| final List<PaletteTarget> saturationExtremeTargets = <PaletteTarget>[ |
| PaletteTarget(minimumSaturation: 0.85), |
| PaletteTarget(maximumSaturation: .25), |
| ]; |
| palette = await PaletteGenerator.fromImageProvider(imageProvider, |
| targets: saturationExtremeTargets); |
| 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.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))); |
| }); |
| |
| test('PaletteGenerator produces consistent results', () async { |
| final ImageProvider imageProvider = testImages['landscape']; |
| |
| PaletteGenerator lastPalette = |
| await PaletteGenerator.fromImageProvider(imageProvider); |
| for (int i = 0; i < 5; ++i) { |
| final PaletteGenerator palette = |
| await PaletteGenerator.fromImageProvider(imageProvider); |
| expect(palette.paletteColors.length, lastPalette.paletteColors.length); |
| expect(palette.vibrantColor, equals(lastPalette.vibrantColor)); |
| expect(palette.lightVibrantColor, equals(lastPalette.lightVibrantColor)); |
| expect(palette.darkVibrantColor, equals(lastPalette.darkVibrantColor)); |
| expect(palette.mutedColor, equals(lastPalette.mutedColor)); |
| expect(palette.lightMutedColor, equals(lastPalette.lightMutedColor)); |
| expect(palette.darkMutedColor, equals(lastPalette.darkMutedColor)); |
| expect(palette.dominantColor.color, |
| within<Color>(distance: 8, from: lastPalette.dominantColor.color)); |
| lastPalette = palette; |
| } |
| }); |
| } |
| |
| bool onlyBluePaletteFilter(HSLColor hslColor) { |
| const double blueLineMinHue = 185.0; |
| const double blueLineMaxHue = 260.0; |
| const double blueLineMaxSaturation = 0.82; |
| return hslColor.hue >= blueLineMinHue && |
| hslColor.hue <= blueLineMaxHue && |
| hslColor.saturation <= blueLineMaxSaturation; |
| } |
| |
| bool onlyCyanPaletteFilter(HSLColor hslColor) { |
| const double cyanLineMinHue = 165.0; |
| const double cyanLineMaxHue = 200.0; |
| const double cyanLineMaxSaturation = 0.82; |
| return hslColor.hue >= cyanLineMinHue && |
| hslColor.hue <= cyanLineMaxHue && |
| hslColor.saturation <= cyanLineMaxSaturation; |
| } |
| |
| bool onlyGreenPaletteFilter(HSLColor hslColor) { |
| const double greenLineMinHue = 80.0; |
| const double greenLineMaxHue = 165.0; |
| const double greenLineMaxSaturation = 0.82; |
| return hslColor.hue >= greenLineMinHue && |
| hslColor.hue <= greenLineMaxHue && |
| hslColor.saturation <= greenLineMaxSaturation; |
| } |