diff --git a/packages/palette_generator/CHANGELOG.md b/packages/palette_generator/CHANGELOG.md
new file mode 100644
index 000000000000..5703fc8bcbb3
--- /dev/null
+++ b/packages/palette_generator/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1.0
+
+* Initial Open Source release.
diff --git a/packages/palette_generator/LICENSE b/packages/palette_generator/LICENSE
new file mode 100644
index 000000000000..8211a02c0646
--- /dev/null
+++ b/packages/palette_generator/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014 The Chromium Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/palette_generator/README.md b/packages/palette_generator/README.md
new file mode 100644
index 000000000000..a1bf087a1002
--- /dev/null
+++ b/packages/palette_generator/README.md
@@ -0,0 +1,21 @@
+# Palette Generator package
+
+[](
+https://pub.dartlang.org/packages/palette_generator)
+
+A Flutter package to extract prominent colors from an Image, typically used to
+find colors for a user interface.
+
+## Usage
+To use this package, add `palette_generator` as a
+[dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
+
+## Example
+
+Import the library via
+``` dart
+import 'package:palette_generator/palette_generator.dart';
+```
+
+Then use the `PaletteGenerator` Dart class in your code. To see how this is done,
+check out the [image_colors example app](example/image_colors/README.md).
diff --git a/packages/palette_generator/example/image_colors/.gitignore b/packages/palette_generator/example/image_colors/.gitignore
new file mode 100644
index 000000000000..dee655cc42ea
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+
+build/
+
+.flutter-plugins
diff --git a/packages/palette_generator/example/image_colors/.metadata b/packages/palette_generator/example/image_colors/.metadata
new file mode 100644
index 000000000000..96bf554252c0
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/.metadata
@@ -0,0 +1,8 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: bb23a110e0e4c2b378cec38fcd5d9b7bef10ec63
+ channel: unknown
diff --git a/packages/palette_generator/example/image_colors/README.md b/packages/palette_generator/example/image_colors/README.md
new file mode 100644
index 000000000000..cfba2a8538e7
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/README.md
@@ -0,0 +1,6 @@
+# image_colors
+
+A sample app for demonstrating the PaletteGenerator
+
+This app will show you what kinds of palettes the generator creates, and one
+way to create them from existing image providers.
\ No newline at end of file
diff --git a/packages/palette_generator/example/image_colors/android/.gitignore b/packages/palette_generator/example/image_colors/android/.gitignore
new file mode 100644
index 000000000000..65b7315af1b6
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/.gitignore
@@ -0,0 +1,10 @@
+*.iml
+*.class
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+GeneratedPluginRegistrant.java
diff --git a/packages/palette_generator/example/image_colors/android/app/build.gradle b/packages/palette_generator/example/image_colors/android/app/build.gradle
new file mode 100644
index 000000000000..e9cb5e84ea7a
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/build.gradle
@@ -0,0 +1,56 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.")
+}
+
+apply plugin: 'com.android.application'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+ compileSdkVersion 27
+
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "io.flutter.packages.palettegenerator.imagecolors"
+ minSdkVersion 16
+ targetSdkVersion 27
+ versionCode 1
+ versionName flutterVersionName
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/AndroidManifest.xml b/packages/palette_generator/example/image_colors/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000000..a3cd38c02f60
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/java/io/flutter/packages/palettegenerator/imagecolors/MainActivity.java b/packages/palette_generator/example/image_colors/android/app/src/main/java/io/flutter/packages/palettegenerator/imagecolors/MainActivity.java
new file mode 100644
index 000000000000..2ce2e49fea34
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/java/io/flutter/packages/palettegenerator/imagecolors/MainActivity.java
@@ -0,0 +1,13 @@
+package io.flutter.packages.palettegenerator.imagecolors;
+
+import android.os.Bundle;
+import io.flutter.app.FlutterActivity;
+import io.flutter.plugins.GeneratedPluginRegistrant;
+
+public class MainActivity extends FlutterActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ GeneratedPluginRegistrant.registerWith(this);
+ }
+}
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/res/drawable/launch_background.xml b/packages/palette_generator/example/image_colors/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 000000000000..304732f88420
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000000..db77bb4b7b09
Binary files /dev/null and b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000000..17987b79bb8a
Binary files /dev/null and b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000000..09d4391482be
Binary files /dev/null and b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000000..d5f1c8d34e7a
Binary files /dev/null and b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000000..4d6372eebdb2
Binary files /dev/null and b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/packages/palette_generator/example/image_colors/android/app/src/main/res/values/styles.xml b/packages/palette_generator/example/image_colors/android/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000000..00fa4417cfbe
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/android/build.gradle b/packages/palette_generator/example/image_colors/android/build.gradle
new file mode 100644
index 000000000000..d4225c7905bc
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.1.2'
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/packages/palette_generator/example/image_colors/android/gradle.properties b/packages/palette_generator/example/image_colors/android/gradle.properties
new file mode 100644
index 000000000000..8bd86f680510
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx1536M
diff --git a/packages/palette_generator/example/image_colors/android/gradle/wrapper/gradle-wrapper.jar b/packages/palette_generator/example/image_colors/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000000..13372aef5e24
Binary files /dev/null and b/packages/palette_generator/example/image_colors/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/packages/palette_generator/example/image_colors/android/gradle/wrapper/gradle-wrapper.properties b/packages/palette_generator/example/image_colors/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000000..9372d0f3f41a
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/packages/palette_generator/example/image_colors/android/gradlew b/packages/palette_generator/example/image_colors/android/gradlew
new file mode 100755
index 000000000000..9d82f7891513
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/packages/palette_generator/example/image_colors/android/gradlew.bat b/packages/palette_generator/example/image_colors/android/gradlew.bat
new file mode 100644
index 000000000000..8a0b282aa688
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/packages/palette_generator/example/image_colors/android/settings.gradle b/packages/palette_generator/example/image_colors/android/settings.gradle
new file mode 100644
index 000000000000..5a2f14fb18f6
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/settings.gradle
@@ -0,0 +1,15 @@
+include ':app'
+
+def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+
+def plugins = new Properties()
+def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
+if (pluginsFile.exists()) {
+ pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
+}
+
+plugins.each { name, path ->
+ def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
+ include ":$name"
+ project(":$name").projectDir = pluginDirectory
+}
diff --git a/packages/palette_generator/example/image_colors/assets/landscape.png b/packages/palette_generator/example/image_colors/assets/landscape.png
new file mode 100644
index 000000000000..815f5999f03c
Binary files /dev/null and b/packages/palette_generator/example/image_colors/assets/landscape.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/.gitignore b/packages/palette_generator/example/image_colors/ios/.gitignore
new file mode 100644
index 000000000000..79cc4da802e9
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/.gitignore
@@ -0,0 +1,45 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/app.flx
+/Flutter/app.zip
+/Flutter/flutter_assets/
+/Flutter/App.framework
+/Flutter/Flutter.framework
+/Flutter/Generated.xcconfig
+/ServiceDefinitions.json
+
+Pods/
+.symlinks/
diff --git a/packages/palette_generator/example/image_colors/ios/Flutter/AppFrameworkInfo.plist b/packages/palette_generator/example/image_colors/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 000000000000..9367d483e44e
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 8.0
+
+
diff --git a/packages/palette_generator/example/image_colors/ios/Flutter/Debug.xcconfig b/packages/palette_generator/example/image_colors/ios/Flutter/Debug.xcconfig
new file mode 100644
index 000000000000..592ceee85b89
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/palette_generator/example/image_colors/ios/Flutter/Release.xcconfig b/packages/palette_generator/example/image_colors/ios/Flutter/Release.xcconfig
new file mode 100644
index 000000000000..592ceee85b89
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/project.pbxproj b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 000000000000..d78d4890d2c8
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,438 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
+ 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
+ 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
+ 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
+ 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
+ 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
+ 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
+ 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
+ 3B80C3931E831B6300D905FE /* App.framework */,
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEBA1CF902C7004384FC /* Flutter.framework */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
+ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 97C146F11CF9000F007C117D /* Supporting Files */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ 97C146F11CF9000F007C117D /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146F21CF9000F007C117D /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0910;
+ ORGANIZATIONNAME = "The Chromium Authors";
+ TargetAttributes = {
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
+ 97C146F31CF9000F007C117D /* main.m in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.flutter.packages.palette_generator.imageColors;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.flutter.packages.palette_generator.imageColors;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000000..1d526a16ed0f
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 000000000000..1263ac84b105
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/palette_generator/example/image_colors/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000000..1d526a16ed0f
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/AppDelegate.h b/packages/palette_generator/example/image_colors/ios/Runner/AppDelegate.h
new file mode 100644
index 000000000000..36e21bbf9cf4
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/AppDelegate.h
@@ -0,0 +1,6 @@
+#import
+#import
+
+@interface AppDelegate : FlutterAppDelegate
+
+@end
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/AppDelegate.m b/packages/palette_generator/example/image_colors/ios/Runner/AppDelegate.m
new file mode 100644
index 000000000000..59a72e90be12
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/AppDelegate.m
@@ -0,0 +1,13 @@
+#include "AppDelegate.h"
+#include "GeneratedPluginRegistrant.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ [GeneratedPluginRegistrant registerWithRegistry:self];
+ // Override point for customization after application launch.
+ return [super application:application didFinishLaunchingWithOptions:launchOptions];
+}
+
+@end
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000000..d36b1fab2d9d
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 000000000000..3d43d11e66f4
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 000000000000..28c6bf03016f
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 000000000000..2ccbfd967d96
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 000000000000..f091b6b0bca8
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 000000000000..4cde12118dda
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 000000000000..d0ef06e7edb8
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 000000000000..dcdc2306c285
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 000000000000..2ccbfd967d96
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 000000000000..c8f9ed8f5cee
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 000000000000..a6d6b8609df0
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 000000000000..a6d6b8609df0
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 000000000000..75b2d164a5a9
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 000000000000..c4df70d39da7
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 000000000000..6a84f41e14e2
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 000000000000..d0e1f5853602
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 000000000000..0bedcf2fd467
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 000000000000..9da19eacad3b
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 000000000000..9da19eacad3b
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 000000000000..9da19eacad3b
Binary files /dev/null and b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 000000000000..89c2725b70f1
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 000000000000..f2e259c7c939
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/Main.storyboard b/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 000000000000..f3c28516fb38
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/Info.plist b/packages/palette_generator/example/image_colors/ios/Runner/Info.plist
new file mode 100644
index 000000000000..cdf4f69014e5
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Info.plist
@@ -0,0 +1,45 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ image_colors
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
+
diff --git a/packages/palette_generator/example/image_colors/ios/Runner/main.m b/packages/palette_generator/example/image_colors/ios/Runner/main.m
new file mode 100644
index 000000000000..dff6597e4513
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/main.m
@@ -0,0 +1,9 @@
+#import
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/packages/palette_generator/example/image_colors/lib/main.dart b/packages/palette_generator/example/image_colors/lib/main.dart
new file mode 100644
index 000000000000..c1ed3e081b5a
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/lib/main.dart
@@ -0,0 +1,305 @@
+// 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:math' as math;
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+
+import 'package:palette_generator/palette_generator.dart';
+
+void main() => runApp(new MyApp());
+
+const Color _kBackgroundColor = const Color(0xffa0a0a0);
+const Color _kSelectionRectangleBackground = const Color(0x15000000);
+const Color _kSelectionRectangleBorder = const Color(0x80000000);
+const Color _kPlaceholderColor = const Color(0x80404040);
+
+/// The main Application class.
+class MyApp extends StatelessWidget {
+ // This widget is the root of your application.
+ @override
+ Widget build(BuildContext context) {
+ return new MaterialApp(
+ title: 'Image Colors',
+ theme: new ThemeData(
+ primarySwatch: Colors.green,
+ ),
+ home: const ImageColors(
+ title: 'Image Colors',
+ image: const AssetImage('assets/landscape.png'),
+ imageSize: const Size(256.0, 170.0),
+ ),
+ );
+ }
+}
+
+/// The home page for this example app.
+@immutable
+class ImageColors extends StatefulWidget {
+ /// Creates the home page.
+ const ImageColors({
+ Key key,
+ this.title,
+ this.image,
+ this.imageSize,
+ }) : super(key: key);
+
+ /// The title that is shown at the top of the page.
+ 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;
+
+ @override
+ _ImageColorsState createState() {
+ return new _ImageColorsState();
+ }
+}
+
+class _ImageColorsState extends State {
+ Rect region;
+ Rect dragRegion;
+ Offset startDrag;
+ Offset currentDrag;
+ PaletteGenerator paletteGenerator;
+
+ final GlobalKey imageKey = new GlobalKey();
+
+ @override
+ void initState() {
+ super.initState();
+ region = Offset.zero & widget.imageSize;
+ _updatePaletteGenerator(region);
+ }
+
+ Future _updatePaletteGenerator(Rect newRegion) async {
+ paletteGenerator = await PaletteGenerator.fromImageProvider(
+ widget.image,
+ size: widget.imageSize,
+ region: newRegion,
+ maximumColorCount: 20,
+ );
+ setState(() {});
+ }
+
+ // Called when the user starts to drag
+ void _onPanDown(DragDownDetails details) {
+ final RenderBox box = imageKey.currentContext.findRenderObject();
+ final Offset localPosition = box.globalToLocal(details.globalPosition);
+ setState(() {
+ startDrag = localPosition;
+ currentDrag = startDrag;
+ dragRegion = new Rect.fromPoints(startDrag, currentDrag);
+ });
+ }
+
+ // Called as the user drags: just updates the region, not the colors.
+ void _onPanUpdate(DragUpdateDetails details) {
+ setState(() {
+ currentDrag += details.delta;
+ dragRegion = new Rect.fromPoints(startDrag, currentDrag);
+ });
+ }
+
+ // Called if the drag is canceled (e.g. by rotating the device or switching
+ // apps)
+ void _onPanCancel() {
+ setState(() {
+ dragRegion = null;
+ startDrag = null;
+ });
+ }
+
+ // Called when the drag ends. Sets the region, and updates the colors.
+ void _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;
+ }
+ await _updatePaletteGenerator(newRegion);
+ setState(() {
+ region = newRegion;
+ dragRegion = null;
+ startDrag = null;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return new Scaffold(
+ backgroundColor: _kBackgroundColor,
+ appBar: new AppBar(
+ title: new Text(widget.title),
+ ),
+ body: Column(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ new Padding(
+ padding: const EdgeInsets.all(20.0),
+ // GestureDetector is used to handle the selection rectangle.
+ child: new GestureDetector(
+ onPanDown: _onPanDown,
+ onPanUpdate: _onPanUpdate,
+ onPanCancel: _onPanCancel,
+ onPanEnd: _onPanEnd,
+ child: new Stack(children: [
+ new Image(
+ key: imageKey,
+ image: widget.image,
+ width: widget.imageSize.width,
+ height: widget.imageSize.height,
+ ),
+ // This is the selection rectangle.
+ new Positioned.fromRect(
+ rect: dragRegion ?? region ?? Rect.zero,
+ child: new Container(
+ decoration: new BoxDecoration(
+ color: _kSelectionRectangleBackground,
+ border: new Border.all(
+ width: 1.0,
+ color: _kSelectionRectangleBorder,
+ style: BorderStyle.solid,
+ )),
+ )),
+ ]),
+ ),
+ ),
+ // Use a FutureBuilder so that the palettes will be displayed when
+ // the palette generator is done generating its data.
+ new PaletteSwatches(generator: paletteGenerator),
+ ],
+ ),
+ );
+ }
+}
+
+/// A widget that draws the swatches for the [PaletteGenerator] it is given,
+/// and shows the selected target colors.
+class PaletteSwatches extends StatelessWidget {
+ /// Create a Palette swatch.
+ ///
+ /// 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);
+
+ /// The [PaletteGenerator] that contains all of the swatches that we're going
+ /// to display.
+ final PaletteGenerator generator;
+
+ @override
+ Widget build(BuildContext context) {
+ final List swatches = [];
+ if (generator == null || generator.colors.isEmpty) {
+ return new Container();
+ }
+ for (Color color in generator.colors) {
+ swatches.add(new PaletteSwatch(color: color));
+ }
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ new Wrap(
+ children: swatches,
+ ),
+ new Container(height: 30.0),
+ new PaletteSwatch(
+ label: 'Dominant', color: generator.dominantColor?.color),
+ new PaletteSwatch(
+ label: 'Light Vibrant', color: generator.lightVibrantColor?.color),
+ new PaletteSwatch(
+ label: 'Vibrant', color: generator.vibrantColor?.color),
+ new PaletteSwatch(
+ label: 'Dark Vibrant', color: generator.darkVibrantColor?.color),
+ new PaletteSwatch(
+ label: 'Light Muted', color: generator.lightMutedColor?.color),
+ new PaletteSwatch(label: 'Muted', color: generator.mutedColor?.color),
+ new PaletteSwatch(
+ label: 'Dark Muted', color: generator.darkMutedColor?.color),
+ ],
+ );
+ }
+}
+
+/// A small square of color with an optional label.
+@immutable
+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.
+ const PaletteSwatch({
+ Key key,
+ this.color,
+ this.label,
+ }) : super(key: key);
+
+ /// The color of the swatch. May be null.
+ final Color color;
+
+ /// The optional label to display next to the swatch.
+ final String label;
+
+ @override
+ Widget build(BuildContext context) {
+ // Compute the "distance" of the color swatch and the background color
+ // so that we can put a border around those color swatches that are too
+ // close to the background's saturation and lightness. We ignore hue for
+ // the comparison.
+ final HSLColor hslColor = HSLColor.fromColor(color ?? Colors.transparent);
+ final HSLColor backgroundAsHsl = HSLColor.fromColor(_kBackgroundColor);
+ final double colorDistance = math.sqrt(
+ math.pow(hslColor.saturation - backgroundAsHsl.saturation, 2.0) +
+ math.pow(hslColor.lightness - backgroundAsHsl.lightness, 2.0));
+
+ Widget swatch = Padding(
+ padding: const EdgeInsets.all(2.0),
+ child: color == null
+ ? const Placeholder(
+ fallbackWidth: 34.0,
+ fallbackHeight: 20.0,
+ color: const Color(0xff404040),
+ strokeWidth: 2.0,
+ )
+ : new Container(
+ decoration: new BoxDecoration(
+ color: color,
+ border: new Border.all(
+ width: 1.0,
+ color: _kPlaceholderColor,
+ style: colorDistance < 0.2
+ ? BorderStyle.solid
+ : BorderStyle.none,
+ )),
+ width: 34.0,
+ height: 20.0,
+ ),
+ );
+
+ if (label != null) {
+ swatch = ConstrainedBox(
+ constraints: const BoxConstraints(maxWidth: 130.0, minWidth: 130.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ swatch,
+ new Container(width: 5.0),
+ new Text(label),
+ ],
+ ),
+ );
+ }
+ return swatch;
+ }
+}
+
diff --git a/packages/palette_generator/example/image_colors/pubspec.yaml b/packages/palette_generator/example/image_colors/pubspec.yaml
new file mode 100644
index 000000000000..8f1eee305593
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/pubspec.yaml
@@ -0,0 +1,22 @@
+name: image_colors
+description: A simple example of how to use the PaletteGenerator to load the palette from an image file.
+
+version: 0.1.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ palette_generator:
+ path: ../..
+ cupertino_icons: ^0.1.2
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ uses-material-design: true
+ assets:
+ - assets/landscape.png
+
+
diff --git a/packages/palette_generator/lib/palette_generator.dart b/packages/palette_generator/lib/palette_generator.dart
new file mode 100644
index 000000000000..77954a66123f
--- /dev/null
+++ b/packages/palette_generator/lib/palette_generator.dart
@@ -0,0 +1,1198 @@
+// 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:math' as math;
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+import 'dart:ui' show Color;
+
+import 'package:collection/collection.dart'
+ show PriorityQueue, HeapPriorityQueue;
+import 'package:flutter/foundation.dart';
+import 'package:flutter/painting.dart';
+
+/// A class to extract prominent colors from an image for use as user interface
+/// colors.
+///
+/// To create a new [PaletteGenerator], use the asynchronous
+/// [PaletteGenerator.fromImage] static function.
+///
+/// A number of color paletteColors with different profiles are chosen from the
+/// image:
+///
+/// * [vibrantColor]
+/// * [darkVibrantColor]
+/// * [lightVibrantColor]
+/// * [mutedColor]
+/// * [darkMutedColor]
+/// * [lightMutedColor]
+///
+/// You may add your own target palette color types by supplying them to the
+/// `targets` parameter for [PaletteGenerator.fromImage].
+///
+/// In addition, the population-sorted list of discovered [colors] is available,
+/// and a [paletteColors] list providing contrasting title text and body text
+/// colors for each palette color.
+///
+/// The palette is created using a color quantizer based on the Median-cut
+/// algorithm, but optimized for picking out distinct colors rather than
+/// representative colors.
+///
+/// The color space is represented as a 3-dimensional cube with each dimension
+/// being one component of an RGB image. The cube is then repeatedly divided
+/// until the color space is reduced to the requested number of colors. An
+/// average color is then generated from each cube.
+///
+/// What makes this different from a median-cut algorithm is that median-cut
+/// divides cubes so that all of the cubes have roughly the same population,
+/// where the quantizer that is used to create the palette divides cubes based
+/// on their color volume. This means that the color space is divided into
+/// distinct colors, rather than representative colors.
+///
+/// See also:
+///
+/// * [PaletteColor], to contain various pieces of metadata about a chosen
+/// palette color.
+/// * [PaletteTarget], to be able to create your own target color types.
+/// * [PaletteFilter], a function signature for filtering the allowed colors
+/// in the palette.
+class PaletteGenerator extends Diagnosticable {
+ /// Create a [PaletteGenerator] from a set of paletteColors and targets.
+ ///
+ /// The usual way to create a [PaletteGenerator] is to use the asynchronous
+ /// [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 = {} {
+ _sortSwatches();
+ _selectSwatches();
+ }
+
+ /// Create a [PaletteGenerator] from an [dart:ui.Image] asynchronously.
+ ///
+ /// The [region] specifies the part of the image to inspect for color
+ /// candidates. By default it uses the entire image. Must not be equal to
+ /// [Rect.zero], and must not be larger than the image dimensions.
+ ///
+ /// The [maximumColorCount] sets the maximum number of colors that will be
+ /// returned in the [PaletteGenerator]. The default is 16 colors.
+ ///
+ /// The [filters] specify a lost of [PaletteFilter] instances that can be used
+ /// to include certain colors in the list of colors. The default filter is
+ /// an instance of [AvoidRedBlackWhitePaletteFilter], which stays away from
+ /// whites, blacks, and low-saturation reds.
+ ///
+ /// 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,
+ }) async {
+ assert(image != 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 ${image.width}x${image.height}');
+ assert(
+ region == null ||
+ (region.bottomRight.dx <= image.width &&
+ region.bottomRight.dy <= image.height),
+ 'Region $region is outside the image ${image.width}x${image.height}');
+
+ filters ??= [avoidRedBlackWhitePaletteFilter];
+ maximumColorCount ??= _defaultCalculateNumberColors;
+ final _ColorCutQuantizer quantizer = new _ColorCutQuantizer(
+ image,
+ maxColors: maximumColorCount,
+ filters: filters,
+ region: region,
+ );
+ final List colors = await quantizer.quantizedColors;
+ return new PaletteGenerator.fromColors(
+ colors,
+ targets: targets,
+ );
+ }
+
+ /// Create a [PaletteGenerator] from an [ImageProvider], like [FileImage], or
+ /// [AssetImage], asynchronously.
+ ///
+ /// The [size] is the desired size of the image. The image will be resized to
+ /// this size before creating the [PaletteGenerator] from it.
+ ///
+ /// The [region] specifies the part of the (resized) image to inspect for
+ /// color candidates. By default it uses the entire image. Must not be equal
+ /// to [Rect.zero], and must not be larger than the image dimensions.
+ ///
+ /// The [maximumColorCount] sets the maximum number of colors that will be
+ /// returned in the [PaletteGenerator]. The default is 16 colors.
+ ///
+ /// The [filters] specify a lost of [PaletteFilter] instances that can be used
+ /// to include certain colors in the list of colors. The default filter is
+ /// an instance of [AvoidRedBlackWhitePaletteFilter], which stays away from
+ /// whites, blacks, and low-saturation reds.
+ ///
+ /// 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 [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,
+ Duration timeout = const Duration(seconds: 15),
+ }) async {
+ assert(imageProvider != null);
+ assert(timeout != null);
+ assert(region == null || (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');
+ assert(
+ region == null ||
+ (region.bottomRight.dx <= size.width &&
+ region.bottomRight.dy <= size.height),
+ 'Region $region is outside the image $size');
+ final ImageStream stream = imageProvider.resolve(
+ new ImageConfiguration(size: size, devicePixelRatio: 1.0),
+ );
+ final Completer imageCompleter = new Completer();
+ Timer loadFailureTimeout;
+ void imageListener(ImageInfo info, bool synchronousCall) {
+ loadFailureTimeout?.cancel();
+ imageCompleter.complete(info.image);
+ }
+
+ if (timeout != Duration.zero) {
+ loadFailureTimeout = new Timer(timeout, () {
+ stream.removeListener(imageListener);
+ imageCompleter.completeError(
+ new TimeoutException(
+ 'Timeout occurred trying to load from $imageProvider'),
+ );
+ });
+ }
+ stream.addListener(imageListener);
+ return PaletteGenerator.fromImage(
+ await imageCompleter.future,
+ region: region,
+ maximumColorCount: maximumColorCount,
+ filters: filters,
+ targets: targets,
+ );
+ }
+
+ static const int _defaultCalculateNumberColors = 16;
+
+ /// Provides a map of the selected paletteColors for each target in [targets].
+ final Map selectedSwatches;
+
+ /// The list of [PaletteColor]s that make up the palette, sorted from most
+ /// dominant color to least dominant color.
+ final List paletteColors;
+
+ /// The list of targets that the palette uses for custom color selection.
+ ///
+ /// By default, this contains the entire list of predefined targets in
+ /// [PaletteTarget.baseTargets].
+ final List targets;
+
+ /// Returns a list of colors in the [paletteColors], sorted from most
+ /// dominant to least dominant color.
+ Iterable get colors sync* {
+ for (PaletteColor paletteColor in paletteColors) {
+ yield paletteColor.color;
+ }
+ }
+
+ /// 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];
+
+ /// Returns a light and vibrant color from the palette. Might be null if an
+ /// appropriate target color could not be found.
+ 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 =>
+ 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];
+
+ /// Returns a muted and light color from the palette. Might be null if an
+ /// appropriate target color could not be found.
+ 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];
+
+ /// The dominant color (the color with the largest population).
+ PaletteColor get dominantColor => _dominantColor;
+ PaletteColor _dominantColor;
+
+ void _sortSwatches() {
+ if (paletteColors.isEmpty) {
+ _dominantColor = null;
+ return;
+ }
+ // Sort from most common to least common.
+ paletteColors.sort((PaletteColor a, PaletteColor b) {
+ return b.population.compareTo(a.population);
+ });
+ _dominantColor = paletteColors[0];
+ }
+
+ void _selectSwatches() {
+ final Set allTargets = new Set.from(
+ (targets ?? []) + PaletteTarget.baseTargets);
+ final Set usedColors = new Set();
+ for (PaletteTarget target in allTargets) {
+ target._normalizeWeights();
+ selectedSwatches[target] = _generateScoredTarget(target, usedColors);
+ }
+ }
+
+ PaletteColor _generateScoredTarget(
+ PaletteTarget target, Set usedColors) {
+ 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
+ // used list.
+ usedColors.add(maxScoreSwatch.color);
+ }
+ return maxScoreSwatch;
+ }
+
+ PaletteColor _getMaxScoredSwatchForTarget(
+ PaletteTarget target, Set usedColors) {
+ double maxScore = 0.0;
+ PaletteColor maxScoreSwatch;
+ for (PaletteColor paletteColor in paletteColors) {
+ if (_shouldBeScoredForTarget(paletteColor, target, usedColors)) {
+ final double score = _generateScore(paletteColor, target);
+ if (maxScoreSwatch == null || score > maxScore) {
+ maxScoreSwatch = paletteColor;
+ maxScore = score;
+ }
+ }
+ }
+ return maxScoreSwatch;
+ }
+
+ bool _shouldBeScoredForTarget(
+ PaletteColor paletteColor, PaletteTarget target, Set usedColors) {
+ // Check whether the HSL lightness is within the correct range, and that
+ // this color hasn't been used yet.
+ final HSLColor hslColor = new HSLColor.fromColor(paletteColor.color);
+ return hslColor.saturation >= target.minimumSaturation &&
+ hslColor.saturation <= target.maximumSaturation &&
+ hslColor.lightness >= target.minimumLightness &&
+ hslColor.lightness <= target.maximumLightness &&
+ !usedColors.contains(paletteColor.color);
+ }
+
+ double _generateScore(PaletteColor paletteColor, PaletteTarget target) {
+ final HSLColor hslColor = new HSLColor.fromColor(paletteColor.color);
+
+ double saturationScore = 0.0;
+ double valueScore = 0.0;
+ double populationScore = 0.0;
+
+ if (target.saturationWeight > 0.0) {
+ saturationScore = target.saturationWeight *
+ (1.0 - (hslColor.saturation - target.targetSaturation).abs());
+ }
+ if (target.lightnessWeight > 0.0) {
+ valueScore = target.lightnessWeight *
+ (1.0 - (hslColor.lightness - target.targetLightness).abs());
+ }
+ if (target.populationWeight > 0.0) {
+ populationScore = target.populationWeight *
+ (paletteColor.population / _dominantColor.population);
+ }
+
+ return saturationScore + valueScore + populationScore;
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(new IterableProperty(
+ 'paletteColors', paletteColors,
+ defaultValue: []));
+ properties.add(new IterableProperty('targets', targets,
+ defaultValue: PaletteTarget.baseTargets));
+ }
+}
+
+/// A class which allows custom selection of colors when a [PaletteGenerator] is
+/// generated.
+///
+/// To add a target, supply it to the `targets` list in
+/// [PaletteGenerator.fromImage] or [PaletteGenerator..fromColors].
+///
+/// See also:
+///
+/// * [PaletteGenerator], a class for selecting color palettes from images.
+class PaletteTarget extends Diagnosticable {
+ /// Creates a [PaletteTarget] for custom palette selection.
+ ///
+ /// None of the arguments can be null.
+ PaletteTarget({
+ this.minimumSaturation = 0.0,
+ this.targetSaturation = 0.5,
+ this.maximumSaturation = 1.0,
+ this.minimumLightness = 0.0,
+ 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.
+ final double minimumSaturation;
+
+ /// The target saturation value for this target. Must not be null.
+ final double targetSaturation;
+
+ /// The maximum saturation value for this target. Must not be null.
+ final double maximumSaturation;
+
+ /// The minimum lightness value for this target. Must not be null.
+ final double minimumLightness;
+
+ /// The target lightness value for this target. Must not be null.
+ final double targetLightness;
+
+ /// The maximum lightness value for this target. Must not be null.
+ 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.
+ final bool isExclusive;
+
+ /// The weight of importance that a color's saturation value has on selection.
+ double saturationWeight = _weightSaturation;
+
+ /// The weight of importance that a color's lightness value has on selection.
+ double lightnessWeight = _weightLightness;
+
+ /// The weight of importance that a color's population value has on selection.
+ double populationWeight = _weightPopulation;
+
+ static const double _targetDarkLightness = 0.26;
+ static const double _maxDarkLightness = 0.45;
+
+ static const double _minLightLightness = 0.55;
+ static const double _targetLightLightness = 0.74;
+
+ static const double _minNormalLightness = 0.3;
+ static const double _targetNormalLightness = 0.5;
+ static const double _maxNormalLightness = 0.7;
+
+ static const double _targetMutedSaturation = 0.3;
+ static const double _maxMutedSaturation = 0.4;
+
+ static const double _targetVibrantSaturation = 1.0;
+ static const double _minVibrantSaturation = 0.35;
+
+ static const double _weightSaturation = 0.24;
+ static const double _weightLightness = 0.52;
+ static const double _weightPopulation = 0.24;
+
+ /// A target which has the characteristics of a vibrant color which is light
+ /// in luminance.
+ ///
+ /// One of the base set of `targets` for [PaletteGenerator.fromImage], in [baseTargets].
+ static final PaletteTarget lightVibrant = new PaletteTarget(
+ targetLightness: _targetLightLightness,
+ minimumLightness: _minLightLightness,
+ minimumSaturation: _minVibrantSaturation,
+ targetSaturation: _targetVibrantSaturation,
+ );
+
+ /// A target which has the characteristics of a vibrant color which is neither
+ /// light or dark.
+ ///
+ /// One of the base set of `targets` for [PaletteGenerator.fromImage], in [baseTargets].
+ static final PaletteTarget vibrant = new PaletteTarget(
+ minimumLightness: _minNormalLightness,
+ targetLightness: _targetNormalLightness,
+ maximumLightness: _maxNormalLightness,
+ minimumSaturation: _minVibrantSaturation,
+ targetSaturation: _targetVibrantSaturation,
+ );
+
+ /// A target which has the characteristics of a vibrant color which is dark in
+ /// luminance.
+ ///
+ /// One of the base set of `targets` for [PaletteGenerator.fromImage], in [baseTargets].
+ static final PaletteTarget darkVibrant = new PaletteTarget(
+ targetLightness: _targetDarkLightness,
+ maximumLightness: _maxDarkLightness,
+ minimumSaturation: _minVibrantSaturation,
+ targetSaturation: _targetVibrantSaturation,
+ );
+
+ /// A target which has the characteristics of a muted color which is light in
+ /// luminance.
+ ///
+ /// One of the base set of `targets` for [PaletteGenerator.fromImage], in [baseTargets].
+ static final PaletteTarget lightMuted = new PaletteTarget(
+ targetLightness: _targetLightLightness,
+ minimumLightness: _minLightLightness,
+ targetSaturation: _targetMutedSaturation,
+ maximumSaturation: _maxMutedSaturation,
+ );
+
+ /// A target which has the characteristics of a muted color which is neither
+ /// light or dark.
+ ///
+ /// One of the base set of `targets` for [PaletteGenerator.fromImage], in [baseTargets].
+ static final PaletteTarget muted = new PaletteTarget(
+ minimumLightness: _minNormalLightness,
+ targetLightness: _targetNormalLightness,
+ maximumLightness: _maxNormalLightness,
+ targetSaturation: _targetMutedSaturation,
+ maximumSaturation: _maxMutedSaturation,
+ );
+
+ /// A target which has the characteristics of a muted color which is dark in
+ /// luminance.
+ ///
+ /// One of the base set of `targets` for [PaletteGenerator.fromImage], in [baseTargets].
+ static final PaletteTarget darkMuted = new PaletteTarget(
+ targetLightness: _targetDarkLightness,
+ maximumLightness: _maxDarkLightness,
+ targetSaturation: _targetMutedSaturation,
+ maximumSaturation: _maxMutedSaturation,
+ );
+
+ /// A list of all the available predefined targets.
+ ///
+ /// The base set of `targets` for [PaletteGenerator.fromImage].
+ static final List baseTargets = [
+ lightVibrant,
+ vibrant,
+ darkVibrant,
+ lightMuted,
+ muted,
+ darkMuted,
+ ];
+
+ void _normalizeWeights() {
+ final double sum = saturationWeight + lightnessWeight + populationWeight;
+ if (sum != 0.0) {
+ saturationWeight /= sum;
+ lightnessWeight /= sum;
+ populationWeight /= sum;
+ }
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ return minimumSaturation == other.minimumSaturation &&
+ targetSaturation == other.targetSaturation &&
+ maximumSaturation == other.maximumSaturation &&
+ minimumLightness == other.minimumLightness &&
+ targetLightness == other.targetLightness &&
+ maximumLightness == other.maximumLightness &&
+ saturationWeight == other.saturationWeight &&
+ lightnessWeight == other.lightnessWeight &&
+ populationWeight == other.populationWeight;
+ }
+
+ @override
+ int get hashCode {
+ return hashValues(
+ minimumSaturation,
+ targetSaturation,
+ maximumSaturation,
+ minimumLightness,
+ targetLightness,
+ maximumLightness,
+ saturationWeight,
+ lightnessWeight,
+ populationWeight,
+ );
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ final PaletteTarget defaultTarget = new PaletteTarget();
+ properties.add(new DoubleProperty('minimumSaturation', minimumSaturation,
+ defaultValue: defaultTarget.minimumSaturation));
+ properties.add(new DoubleProperty('targetSaturation', targetSaturation,
+ defaultValue: defaultTarget.targetSaturation));
+ properties.add(new DoubleProperty('maximumSaturation', maximumSaturation,
+ defaultValue: defaultTarget.maximumSaturation));
+ properties.add(new DoubleProperty('minimumLightness', minimumLightness,
+ defaultValue: defaultTarget.minimumLightness));
+ properties.add(new DoubleProperty('targetLightness', targetLightness,
+ defaultValue: defaultTarget.targetLightness));
+ properties.add(new DoubleProperty('maximumLightness', maximumLightness,
+ defaultValue: defaultTarget.maximumLightness));
+ properties.add(new DoubleProperty('saturationWeight', saturationWeight,
+ defaultValue: defaultTarget.saturationWeight));
+ properties.add(new DoubleProperty('lightnessWeight', lightnessWeight,
+ defaultValue: defaultTarget.lightnessWeight));
+ properties.add(new DoubleProperty('populationWeight', populationWeight,
+ defaultValue: defaultTarget.populationWeight));
+ }
+}
+
+typedef _ContrastCalculator = double Function(Color a, Color b, int alpha);
+
+/// A color palette color generated by the [PaletteGenerator].
+///
+/// This palette color represents a dominant [color] in an image, and has a
+/// [population] of how many pixels in the source image it represents. It picks
+/// a [titleTextColor] and a [bodyTextColor] that contrast sufficiently with the
+/// source [color] for comfortable reading.
+///
+/// See also:
+///
+/// * [PaletteGenerator], a class for selecting color palettes from images.
+class PaletteColor extends Diagnosticable {
+ /// Generate a [PaletteColor].
+ ///
+ /// The `color` and `population` parameters must not be null.
+ PaletteColor(this.color, this.population)
+ : assert(color != null),
+ assert(population != null);
+ static const double _minContrastTitleText = 3.0;
+ static const double _minContrastBodyText = 4.5;
+
+ /// The color that this palette color represents.
+ final Color color;
+
+ /// The number of pixels in the source image that this palette color
+ /// represents.
+ final int population;
+
+ /// The color of title text for use with this palette color.
+ Color get titleTextColor {
+ if (_titleTextColor == null) {
+ _ensureTextColorsGenerated();
+ }
+ return _titleTextColor;
+ }
+
+ Color _titleTextColor;
+
+ /// The color of body text for use with this palette color.
+ Color get bodyTextColor {
+ if (_bodyTextColor == null) {
+ _ensureTextColorsGenerated();
+ }
+ return _bodyTextColor;
+ }
+
+ Color _bodyTextColor;
+
+ void _ensureTextColorsGenerated() {
+ if (_titleTextColor == null || _bodyTextColor == null) {
+ const Color white = const Color(0xffffffff);
+ const Color black = const Color(0xff000000);
+ // First check white, as most colors will be dark
+ final int lightBodyAlpha =
+ _calculateMinimumAlpha(white, color, _minContrastBodyText);
+ final int lightTitleAlpha =
+ _calculateMinimumAlpha(white, color, _minContrastTitleText);
+
+ if (lightBodyAlpha != null && lightTitleAlpha != null) {
+ // If we found valid light values, use them and return
+ _bodyTextColor = white.withAlpha(lightBodyAlpha);
+ _titleTextColor = white.withAlpha(lightTitleAlpha);
+ return;
+ }
+
+ final int darkBodyAlpha =
+ _calculateMinimumAlpha(black, color, _minContrastBodyText);
+ final int darkTitleAlpha =
+ _calculateMinimumAlpha(black, color, _minContrastTitleText);
+
+ if (darkBodyAlpha != null && darkBodyAlpha != null) {
+ // If we found valid dark values, use them and return
+ _bodyTextColor = black.withAlpha(darkBodyAlpha);
+ _titleTextColor = black.withAlpha(darkTitleAlpha);
+ return;
+ }
+
+ // 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 //
+ ? white.withAlpha(lightBodyAlpha)
+ : black.withAlpha(darkBodyAlpha);
+ _titleTextColor = lightTitleAlpha != null //
+ ? white.withAlpha(lightTitleAlpha)
+ : black.withAlpha(darkTitleAlpha);
+ }
+ }
+
+ /// Returns the contrast ratio between [foreground] and [background].
+ /// [background] must be opaque.
+ ///
+ /// Formula defined [here](http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef).
+ static double _calculateContrast(Color foreground, Color background) {
+ assert(background.alpha == 0xff,
+ 'background can not be translucent: $background.');
+ if (foreground.alpha < 0xff) {
+ // If the foreground is translucent, composite the foreground over the
+ // background
+ foreground = Color.alphaBlend(foreground, background);
+ }
+ final double lightness1 =
+ new HSLColor.fromColor(foreground).lightness + 0.05;
+ final double lightness2 =
+ new HSLColor.fromColor(background).lightness + 0.05;
+ return math.max(lightness1, lightness2) / math.min(lightness1, lightness2);
+ }
+
+ // Calculates the minimum alpha value which can be applied to foreground that
+ // would have a contrast value of at least [minContrastRatio] when compared to
+ // background.
+ //
+ // The background must be opaque (alpha of 255).
+ //
+ // Returns the alpha value in the range 0-255, or null if no value could be
+ // calculated.
+ 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) {
+ final Color testForeground = fg.withAlpha(alpha);
+ return _calculateContrast(testForeground, bg);
+ }
+
+ // First lets check that a fully opaque foreground has sufficient contrast
+ final double testRatio = contrastCalculator(foreground, background, 0xff);
+ if (testRatio < minContrastRatio) {
+ // Fully opaque foreground does not have sufficient contrast, return error
+ return null;
+ }
+ foreground = foreground.withAlpha(0xff);
+ return _binaryAlphaSearch(
+ foreground, background, minContrastRatio, contrastCalculator);
+ }
+
+ // Calculates the alpha value using binary search based on a given contrast
+ // evaluation function and target contrast that needs to be satisfied.
+ //
+ // The background must be opaque (alpha of 255).
+ //
+ // Returns the alpha value in the range [0, 255].
+ static int _binaryAlphaSearch(
+ Color foreground,
+ Color background,
+ double minContrastRatio,
+ _ContrastCalculator calculator,
+ ) {
+ assert(foreground != null);
+ assert(background != null);
+ assert(background.alpha == 0xff,
+ 'The background cannot be translucent: $background.');
+ const int minAlphaSearchMaxIterations = 10;
+ const int minAlphaSearchPrecision = 1;
+
+ // Binary search to find a value with the minimum value which provides
+ // sufficient contrast
+ int numIterations = 0;
+ int minAlpha = 0;
+ int maxAlpha = 0xff;
+ while (numIterations <= minAlphaSearchMaxIterations &&
+ (maxAlpha - minAlpha) > minAlphaSearchPrecision) {
+ final int testAlpha = (minAlpha + maxAlpha) ~/ 2;
+ final double testRatio = calculator(foreground, background, testAlpha);
+ if (testRatio < minContrastRatio) {
+ minAlpha = testAlpha;
+ } else {
+ maxAlpha = testAlpha;
+ }
+ numIterations++;
+ }
+ // Conservatively return the max of the range of possible alphas, which is
+ // known to pass.
+ return maxAlpha;
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(new DiagnosticsProperty('color', color));
+ properties
+ .add(new DiagnosticsProperty('titleTextColor', titleTextColor));
+ properties
+ .add(new DiagnosticsProperty('bodyTextColor', bodyTextColor));
+ properties.add(new IntProperty('population', population, defaultValue: 0));
+ }
+
+ @override
+ int get hashCode {
+ return hashValues(color, population);
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ return color == other.color && population == other.population;
+ }
+}
+
+/// Hook to allow clients to be able filter colors from selected in a
+/// [PaletteGenerator]. Returns true if the [color] is allowed.
+///
+/// See also:
+///
+/// * [PaletteGenerator.fromImage], which takes a list of these for its
+/// `filters` parameter.
+/// * [avoidRedBlackWhitePaletteFilter], the default filter for
+/// [PaletteGenerator].
+typedef PaletteFilter = bool Function(HSLColor color);
+
+/// A basic [PaletteFilter], which rejects colors near black, white and low
+/// saturation red.
+///
+/// Use this as an element in the `filters` list given to [PaletteGenerator.fromImage].
+///
+/// See also:
+/// * [PaletteGenerator], a class for selecting color palettes from images.
+bool avoidRedBlackWhitePaletteFilter(HSLColor color) {
+ bool _isBlack(HSLColor hslColor) {
+ const double _blackMaxLightness = 0.05;
+ return hslColor.lightness <= _blackMaxLightness;
+ }
+
+ bool _isWhite(HSLColor hslColor) {
+ const double _whiteMinLightness = 0.95;
+ return hslColor.lightness >= _whiteMinLightness;
+ }
+
+ // Returns true if the color is close to the red side of the I line.
+ bool _isNearRedILine(HSLColor hslColor) {
+ const double redLineMinHue = 10.0;
+ const double redLineMaxHue = 37.0;
+ const double redLineMaxSaturation = 0.82;
+ return hslColor.hue >= redLineMinHue &&
+ hslColor.hue <= redLineMaxHue &&
+ hslColor.saturation <= redLineMaxSaturation;
+ }
+
+ return !_isWhite(color) && !_isBlack(color) && !_isNearRedILine(color);
+}
+
+enum _ColorComponent {
+ red,
+ green,
+ blue,
+}
+
+/// A box that represents a volume in the RGB color space.
+class _ColorVolumeBox {
+ _ColorVolumeBox(int lowerIndex, int upperIndex, this.histogram, this.colors)
+ : assert(histogram != null),
+ assert(colors != null),
+ _lowerIndex = lowerIndex,
+ _upperIndex = upperIndex {
+ _fitMinimumBox();
+ }
+
+ final Map histogram;
+ final List colors;
+
+ // The lower and upper index are inclusive.
+ int _lowerIndex;
+ int _upperIndex;
+
+ // The population of colors within this box.
+ int _population;
+
+ // Bounds in each of the dimensions.
+ int _minRed;
+ int _maxRed;
+ int _minGreen;
+ int _maxGreen;
+ int _minBlue;
+ int _maxBlue;
+
+ int getVolume() {
+ return (_maxRed - _minRed + 1) *
+ (_maxGreen - _minGreen + 1) *
+ (_maxBlue - _minBlue + 1);
+ }
+
+ bool canSplit() {
+ return getColorCount() > 1;
+ }
+
+ int getColorCount() {
+ return 1 + _upperIndex - _lowerIndex;
+ }
+
+ /// Recomputes the boundaries of this box to tightly fit the colors within the
+ /// box.
+ void _fitMinimumBox() {
+ // Reset the min and max to opposite values
+ int minRed = 256;
+ int minGreen = 256;
+ int minBlue = 256;
+ int maxRed = -1;
+ int maxGreen = -1;
+ int maxBlue = -1;
+ int count = 0;
+ for (int i = _lowerIndex; i <= _upperIndex; i++) {
+ final Color color = colors[i];
+ count += histogram[color];
+ if (color.red > maxRed) {
+ maxRed = color.red;
+ }
+ if (color.red < minRed) {
+ minRed = color.red;
+ }
+ if (color.green > maxGreen) {
+ maxGreen = color.green;
+ }
+ if (color.green < minGreen) {
+ minGreen = color.green;
+ }
+ if (color.blue > maxBlue) {
+ maxBlue = color.blue;
+ }
+ if (color.blue < minBlue) {
+ minBlue = color.blue;
+ }
+ }
+ _minRed = minRed;
+ _maxRed = maxRed;
+ _minGreen = minGreen;
+ _maxGreen = maxGreen;
+ _minBlue = minBlue;
+ _maxBlue = maxBlue;
+ _population = count;
+ }
+
+ /// Split this color box at the mid-point along it's longest dimension
+ ///
+ /// Returns the new ColorBox
+ _ColorVolumeBox splitBox() {
+ assert(canSplit(), "Can't split a box with only 1 color");
+ // find median along the longest dimension
+ final int splitPoint = _findSplitPoint();
+ final _ColorVolumeBox newBox =
+ new _ColorVolumeBox(splitPoint + 1, _upperIndex, histogram, colors);
+ // Now change this box's upperIndex and recompute the color boundaries
+ _upperIndex = splitPoint;
+ _fitMinimumBox();
+ return newBox;
+ }
+
+ /// Returns the largest dimension of this color box.
+ _ColorComponent _getLongestColorDimension() {
+ final int redLength = _maxRed - _minRed;
+ final int greenLength = _maxGreen - _minGreen;
+ final int blueLength = _maxBlue - _minBlue;
+ if (redLength >= greenLength && redLength >= blueLength) {
+ return _ColorComponent.red;
+ } else if (greenLength >= redLength && greenLength >= blueLength) {
+ return _ColorComponent.green;
+ } else {
+ return _ColorComponent.blue;
+ }
+ }
+
+ // Finds where to split this box between _lowerIndex and _upperIndex.
+ //
+ // The split point is calculated by finding the longest color dimension, and
+ // then sorting the sub-array based on that dimension value in each color.
+ // The colors are then iterated over until a color is found with the
+ // midpoint closest to the whole box's dimension midpoint.
+ //
+ // Returns the index of the split point in the colors array.
+ int _findSplitPoint() {
+ final _ColorComponent longestDimension = _getLongestColorDimension();
+ int compareColors(Color a, Color b) {
+ int makeValue(int first, int second, int third) {
+ return first << 16 | second << 8 | third;
+ }
+
+ switch (longestDimension) {
+ case _ColorComponent.red:
+ final int aValue = makeValue(a.red, a.green, a.blue);
+ final int bValue = makeValue(b.red, b.green, b.blue);
+ return aValue.compareTo(bValue);
+ case _ColorComponent.green:
+ final int aValue = makeValue(a.green, a.red, a.blue);
+ final int bValue = makeValue(b.green, b.red, b.blue);
+ return aValue.compareTo(bValue);
+ case _ColorComponent.blue:
+ final int aValue = makeValue(a.blue, a.green, a.red);
+ 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
+ // dimension.
+ final List colorSubset =
+ colors.sublist(_lowerIndex, _upperIndex + 1);
+ colorSubset.sort(compareColors);
+ 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]];
+ if (count >= median) {
+ // We never want to split on the upperIndex, as this will result in the
+ // same box.
+ return math.min(_upperIndex - 1, i + _lowerIndex);
+ }
+ }
+ return _lowerIndex;
+ }
+
+ PaletteColor getAverageColor() {
+ int redSum = 0;
+ int greenSum = 0;
+ int blueSum = 0;
+ int totalPopulation = 0;
+ for (int i = _lowerIndex; i <= _upperIndex; i++) {
+ final Color color = colors[i];
+ final int colorPopulation = histogram[color];
+ totalPopulation += colorPopulation;
+ redSum += colorPopulation * color.red;
+ greenSum += colorPopulation * color.green;
+ blueSum += colorPopulation * color.blue;
+ }
+ final int redMean = (redSum / totalPopulation).round();
+ final int greenMean = (greenSum / totalPopulation).round();
+ final int blueMean = (blueSum / totalPopulation).round();
+ return new PaletteColor(
+ new Color.fromARGB(0xff, redMean, greenMean, blueMean),
+ totalPopulation,
+ );
+ }
+}
+
+class _ColorCutQuantizer {
+ _ColorCutQuantizer(
+ this.image, {
+ this.maxColors = PaletteGenerator._defaultCalculateNumberColors,
+ this.region,
+ this.filters,
+ }) : assert(image != null),
+ assert(maxColors != null),
+ assert(region == null || region != Rect.zero),
+ _paletteColors = [];
+
+ FutureOr> get quantizedColors async {
+ if (_paletteColors.isNotEmpty) {
+ return _paletteColors;
+ } else {
+ return _quantizeColors(image);
+ }
+ }
+
+ final ui.Image image;
+ final List _paletteColors;
+
+ final int maxColors;
+ final Rect region;
+ final List filters;
+
+ Iterable _getImagePixels(ByteData pixels, int width, int height,
+ {Rect region}) sync* {
+ final int rowStride = width * 4;
+ int rowStart;
+ int rowEnd;
+ int colStart;
+ int colEnd;
+ if (region != null) {
+ rowStart = region.top.floor();
+ rowEnd = region.bottom.floor();
+ colStart = region.left.floor();
+ colEnd = region.right.floor();
+ assert(rowStart >= 0);
+ assert(rowEnd <= height);
+ assert(colStart >= 0);
+ assert(colEnd <= width);
+ } else {
+ rowStart = 0;
+ rowEnd = height;
+ colStart = 0;
+ colEnd = width;
+ }
+ int byteCount = 0;
+ for (int row = rowStart; row < rowEnd; ++row) {
+ for (int col = colStart; col < colEnd; ++col) {
+ final int position = row * rowStride + col * 4;
+ // Convert from RGBA to ARGB.
+ final int pixel = pixels.getUint32(position);
+ final Color result = new Color((pixel << 24) | (pixel >> 8));
+ byteCount += 4;
+ yield result;
+ }
+ }
+ assert(byteCount == ((rowEnd - rowStart) * (colEnd - colStart) * 4));
+ }
+
+ bool _shouldIgnoreColor(Color color) {
+ final HSLColor hslColor = HSLColor.fromColor(color);
+ if (filters != null && filters.isNotEmpty) {
+ for (PaletteFilter filter in filters) {
+ if (!filter(hslColor)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ Future> _quantizeColors(ui.Image image) async {
+ const int quantizeWordWidth = 5;
+ const int quantizeChannelWidth = 8;
+ const int quantizeShift = quantizeChannelWidth - quantizeWordWidth;
+ const int quantizeWordMask =
+ ((1 << quantizeWordWidth) - 1) << quantizeShift;
+
+ Color quantizeColor(Color color) {
+ return new Color.fromARGB(
+ color.alpha,
+ color.red & quantizeWordMask,
+ color.green & quantizeWordMask,
+ color.blue & quantizeWordMask,
+ );
+ }
+
+ final ByteData imageData =
+ await image.toByteData(format: ui.ImageByteFormat.rawRgba);
+ final Iterable pixels =
+ _getImagePixels(imageData, image.width, image.height, region: region);
+ final Map hist = {};
+ for (Color pixel in pixels) {
+ // Update the histogram, but only for non-zero alpha values, and for the
+ // ones we do add, make their alphas opaque so that we can use a Color as
+ // the histogram key.
+ final Color quantizedColor = quantizeColor(pixel);
+ final Color colorKey = quantizedColor.withAlpha(0xff);
+ // Skip pixels that are entirely transparent.
+ if (quantizedColor.alpha != 0x0) {
+ hist[colorKey] = (hist[colorKey] ?? 0) + 1;
+ }
+ }
+ // Now let's remove any colors that the filters want to ignore.
+ hist.removeWhere((Color color, int _) {
+ return _shouldIgnoreColor(color);
+ });
+ if (hist.length <= maxColors) {
+ // The image has fewer colors than the maximum requested, so just return
+ // the colors.
+ _paletteColors.clear();
+ for (Color color in hist.keys) {
+ _paletteColors.add(new PaletteColor(color, hist[color]));
+ }
+ } else {
+ // We need use quantization to reduce the number of colors
+ _paletteColors.clear();
+ _paletteColors.addAll(_quantizePixels(maxColors, hist));
+ }
+ return _paletteColors;
+ }
+
+ List _quantizePixels(
+ int maxColors,
+ Map histogram,
+ ) {
+ int volumeComparator(_ColorVolumeBox a, _ColorVolumeBox b) {
+ return b.getVolume().compareTo(a.getVolume());
+ }
+
+ // Create the priority queue which is sorted by volume descending. This
+ // means we always split the largest box in the queue
+ final PriorityQueue<_ColorVolumeBox> priorityQueue =
+ new HeapPriorityQueue<_ColorVolumeBox>(volumeComparator);
+ // To start, offer a box which contains all of the colors
+ priorityQueue.add(new _ColorVolumeBox(
+ 0, histogram.length - 1, histogram, histogram.keys.toList()));
+ // Now go through the boxes, splitting them until we have reached maxColors
+ // or there are no more boxes to split
+ _splitBoxes(priorityQueue, maxColors);
+ // Finally, return the average colors of the color boxes.
+ return _generateAverageColors(priorityQueue);
+ }
+
+ // Iterate through the [PriorityQueue], popping [_ColorVolumeBox] objects
+ // from the queue and splitting them. Once split, the new box and the
+ // remaining box are offered back to the queue.
+ //
+ // The `maxSize` is the maximum number of boxes to split.
+ void _splitBoxes(PriorityQueue<_ColorVolumeBox> queue, final int maxSize) {
+ while (queue.length < maxSize) {
+ final _ColorVolumeBox colorVolumeBox = queue.removeFirst();
+ if (colorVolumeBox != null && colorVolumeBox.canSplit()) {
+ // First split the box, and offer the result
+ queue.add(colorVolumeBox.splitBox());
+ // Then offer the box back
+ queue.add(colorVolumeBox);
+ } else {
+ // If we get here then there are no more boxes to split, so return
+ return;
+ }
+ }
+ }
+
+ // Generates the average colors from each of the boxes in the queue.
+ List _generateAverageColors(
+ PriorityQueue<_ColorVolumeBox> colorVolumeBoxes) {
+ final List colors = [];
+ for (_ColorVolumeBox colorVolumeBox in colorVolumeBoxes.toList()) {
+ final PaletteColor paletteColor = colorVolumeBox.getAverageColor();
+ if (!_shouldIgnoreColor(paletteColor.color)) {
+ colors.add(paletteColor);
+ }
+ }
+ return colors;
+ }
+}
diff --git a/packages/palette_generator/palette_generator.iml b/packages/palette_generator/palette_generator.iml
new file mode 100644
index 000000000000..b5426556b10f
--- /dev/null
+++ b/packages/palette_generator/palette_generator.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/palette_generator/pubspec.yaml b/packages/palette_generator/pubspec.yaml
new file mode 100644
index 000000000000..81234d140b50
--- /dev/null
+++ b/packages/palette_generator/pubspec.yaml
@@ -0,0 +1,20 @@
+name: palette_generator
+description: Flutter package for generating palette colors from a source image.
+author: Flutter Team
+homepage: https://github.com/flutter/packages/tree/master/packages/palette_generator
+version: 0.1.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ collection: ^1.14.6
+ path: ^1.6.1
+
+dev_dependencies:
+ mockito: ^2.2.3
+ flutter_test:
+ sdk: flutter
+
+environment:
+ sdk: ">=2.0.0-dev.61.0 <3.0.0"
+ flutter: ">=0.1.4 <2.0.0"
diff --git a/packages/palette_generator/test/assets/dominant.png b/packages/palette_generator/test/assets/dominant.png
new file mode 100644
index 000000000000..ca545431ad76
Binary files /dev/null and b/packages/palette_generator/test/assets/dominant.png differ
diff --git a/packages/palette_generator/test/assets/landscape.png b/packages/palette_generator/test/assets/landscape.png
new file mode 100644
index 000000000000..815f5999f03c
Binary files /dev/null and b/packages/palette_generator/test/assets/landscape.png differ
diff --git a/packages/palette_generator/test/assets/tall_blue.png b/packages/palette_generator/test/assets/tall_blue.png
new file mode 100644
index 000000000000..6453d791d65e
Binary files /dev/null and b/packages/palette_generator/test/assets/tall_blue.png differ
diff --git a/packages/palette_generator/test/assets/wide_red.png b/packages/palette_generator/test/assets/wide_red.png
new file mode 100644
index 000000000000..5130b44286aa
Binary files /dev/null and b/packages/palette_generator/test/assets/wide_red.png differ
diff --git a/packages/palette_generator/test/palette_generator_test.dart b/packages/palette_generator/test/palette_generator_test.dart
new file mode 100644
index 000000000000..c777b0195950
--- /dev/null
+++ b/packages/palette_generator/test/palette_generator_test.dart
@@ -0,0 +1,362 @@
+// 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 {
+ 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 obtainKey(ImageConfiguration configuration) {
+ return new SynchronousFuture(this);
+ }
+
+ @override
+ ImageStreamCompleter load(FakeImageProvider key) {
+ assert(key == this);
+ return new OneFrameImageStreamCompleter(
+ new SynchronousFuture(
+ new ImageInfo(image: _image, scale: scale),
+ ),
+ );
+ }
+}
+
+Future loadImage(String name) async {
+ File imagePath = new File(path.joinAll(['assets', name]));
+ if (path.split(Directory.current.absolute.path).last != 'test') {
+ imagePath = new File(path.join('test', imagePath.path));
+ }
+ final Uint8List data = new Uint8List.fromList(imagePath.readAsBytesSync());
+ final ui.Codec codec = await ui.instantiateImageCodec(data);
+ final ui.FrameInfo frameInfo = await codec.getNextFrame();
+ return new FakeImageProvider(frameInfo.image);
+}
+
+void main() async {
+ // Load the images outside of the test zone so that IO doesn't get
+ // complicated.
+ final List imageNames = [
+ 'tall_blue',
+ 'wide_red',
+ 'dominant',
+ 'landscape'
+ ];
+ final Map testImages = {};
+ for (String name in imageNames) {
+ testImages[name] = await loadImage('$name.png');
+ }
+
+ testWidgets('Initialize the image cache', (WidgetTester tester) {
+ // 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(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(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(distance: 8, from: const Color(0xff0000ff)));
+ expect(palette.dominantColor.titleTextColor,
+ within(distance: 8, from: const Color(0xbc000000)));
+ expect(palette.dominantColor.bodyTextColor,
+ within(distance: 8, from: const Color(0xda000000)));
+ });
+
+ test('PaletteGenerator works with regions', () async {
+ final ImageProvider imageProvider = testImages['dominant'];
+ Rect region = new Rect.fromLTRB(0.0, 0.0, 100.0, 100.0);
+ const Size size = const 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(distance: 8, from: const Color(0xff0000ff)));
+
+ region = new 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(distance: 8, from: const Color(0xffff0000)));
+
+ region = new 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(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 expectedSwatches = [
+ new PaletteColor(const Color(0xff3f630c), 10137),
+ new PaletteColor(const Color(0xff3c4b2a), 4773),
+ new PaletteColor(const Color(0xff81b2e9), 4762),
+ new PaletteColor(const Color(0xffc0d6ec), 4714),
+ new PaletteColor(const Color(0xff4c4f50), 2465),
+ new PaletteColor(const Color(0xff5c635b), 2463),
+ new PaletteColor(const Color(0xff6e80a2), 2421),
+ new PaletteColor(const Color(0xff9995a3), 1214),
+ new PaletteColor(const Color(0xff676c4d), 1213),
+ new PaletteColor(const Color(0xffc4b2b2), 1173),
+ new PaletteColor(const Color(0xff445166), 1040),
+ new PaletteColor(const Color(0xff475d83), 1019),
+ new PaletteColor(const Color(0xff7e7360), 589),
+ new PaletteColor(const Color(0xfff6b835), 286),
+ new PaletteColor(const Color(0xffb9983d), 152),
+ new PaletteColor(const Color(0xffe3ab35), 149),
+ ];
+ final Iterable expectedColors =
+ expectedSwatches.map((PaletteColor swatch) => swatch.color);
+ expect(palette.paletteColors, containsAll(expectedSwatches));
+ expect(palette.vibrantColor.color,
+ within(distance: 8, from: const Color(0xfff6b835)));
+ expect(palette.lightVibrantColor.color,
+ within(distance: 8, from: const Color(0xff82b2e9)));
+ expect(palette.darkVibrantColor.color,
+ within(distance: 8, from: const Color(0xff3f630c)));
+ expect(palette.mutedColor.color,
+ within(distance: 8, from: const Color(0xff6c7fa2)));
+ expect(palette.lightMutedColor.color,
+ within(distance: 8, from: const Color(0xffc4b2b2)));
+ 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'];
+ 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 filters = [
+ avoidRedBlackWhitePaletteFilter
+ ];
+ PaletteGenerator palette =
+ await PaletteGenerator.fromImageProvider(imageProvider, filters: filters);
+ final List expectedSwatches = [
+ new PaletteColor(const Color(0xff3f630c), 10137),
+ new PaletteColor(const Color(0xff3c4b2a), 4773),
+ new PaletteColor(const Color(0xff81b2e9), 4762),
+ new PaletteColor(const Color(0xffc0d6ec), 4714),
+ new PaletteColor(const Color(0xff4c4f50), 2465),
+ new PaletteColor(const Color(0xff5c635b), 2463),
+ new PaletteColor(const Color(0xff6e80a2), 2421),
+ new PaletteColor(const Color(0xff9995a3), 1214),
+ new PaletteColor(const Color(0xff676c4d), 1213),
+ new PaletteColor(const Color(0xffc4b2b2), 1173),
+ new PaletteColor(const Color(0xff445166), 1040),
+ new PaletteColor(const Color(0xff475d83), 1019),
+ new PaletteColor(const Color(0xff7e7360), 589),
+ new PaletteColor(const Color(0xfff6b835), 286),
+ new PaletteColor(const Color(0xffb9983d), 152),
+ new PaletteColor(const Color(0xffe3ab35), 149),
+ ];
+ final Iterable expectedColors =
+ expectedSwatches.map((PaletteColor swatch) => swatch.color);
+ expect(palette.paletteColors, containsAll(expectedSwatches));
+ expect(palette.dominantColor.color,
+ within(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 = [onlyBluePaletteFilter];
+ palette = await PaletteGenerator.fromImageProvider(imageProvider, filters: filters);
+ final List blueSwatches = [
+ new PaletteColor(const Color(0xff4c5c75), 1515),
+ new PaletteColor(const Color(0xff7483a1), 1505),
+ new PaletteColor(const Color(0xff515661), 1476),
+ new PaletteColor(const Color(0xff769dd4), 1470),
+ new PaletteColor(const Color(0xff3e4858), 777),
+ new PaletteColor(const Color(0xff98a3bc), 760),
+ new PaletteColor(const Color(0xffb4c7e0), 760),
+ new PaletteColor(const Color(0xff99bbe5), 742),
+ new PaletteColor(const Color(0xffcbdef0), 701),
+ new PaletteColor(const Color(0xff1c212b), 429),
+ new PaletteColor(const Color(0xff393c46), 417),
+ new PaletteColor(const Color(0xff526483), 394),
+ new PaletteColor(const Color(0xff61708b), 372),
+ new PaletteColor(const Color(0xff5e8ccc), 345),
+ new PaletteColor(const Color(0xff587ab4), 194),
+ new PaletteColor(const Color(0xff5584c8), 182),
+ ];
+ final Iterable expectedBlues =
+ blueSwatches.map((PaletteColor swatch) => swatch.color);
+
+ expect(palette.paletteColors, containsAll(blueSwatches));
+ expect(palette.dominantColor.color,
+ within(distance: 8, from: const Color(0xff4c5c75)));
+ expect(palette.colors, containsAllInOrder(expectedBlues));
+
+ // More than one filter is the intersection of the two filters.
+ filters = [onlyBluePaletteFilter, onlyCyanPaletteFilter];
+ palette = await PaletteGenerator.fromImageProvider(imageProvider, filters: filters);
+ final List blueGreenSwatches = [
+ new PaletteColor(const Color(0xffc8e8f8), 87),
+ new PaletteColor(const Color(0xff5c6c74), 73),
+ new PaletteColor(const Color(0xff6f8088), 49),
+ new PaletteColor(const Color(0xff687880), 49),
+ new PaletteColor(const Color(0xff506068), 45),
+ new PaletteColor(const Color(0xff485860), 39),
+ new PaletteColor(const Color(0xff405058), 21),
+ new PaletteColor(const Color(0xffd6ebf3), 11),
+ new PaletteColor(const Color(0xff2f3f47), 7),
+ new PaletteColor(const Color(0xff0f1f27), 6),
+ new PaletteColor(const Color(0xffc0e0f0), 6),
+ new PaletteColor(const Color(0xff203038), 3),
+ new PaletteColor(const Color(0xff788890), 2),
+ new PaletteColor(const Color(0xff384850), 2),
+ new PaletteColor(const Color(0xff98a8b0), 1),
+ new PaletteColor(const Color(0xffa8b8c0), 1),
+ ];
+ final Iterable expectedBlueGreens =
+ blueGreenSwatches.map((PaletteColor swatch) => swatch.color);
+
+ expect(palette.paletteColors, containsAll(blueGreenSwatches));
+ expect(palette.dominantColor.color,
+ within(distance: 8, from: const Color(0xffc8e8f8)));
+ expect(palette.colors, containsAllInOrder(expectedBlueGreens));
+
+ // Mutually exclusive filters return an empty palette.
+ filters = [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: []);
+ 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 saturationExtremeTargets = [
+ new PaletteTarget(minimumSaturation: 0.85),
+ new 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(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;
+}