Merge pull request #2 from gspencergoog/palette_generator

This adds a palette generator that will take an image and generate a set of distinct, dominant colors from it. This is more or less a port of the Android Palette class, except that it doesn't automatically resize the image if it is too large.
diff --git a/packages/palette_generator/CHANGELOG.md b/packages/palette_generator/CHANGELOG.md
new file mode 100644
index 0000000..5703fc8
--- /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 0000000..8211a02
--- /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 0000000..a1bf087
--- /dev/null
+++ b/packages/palette_generator/README.md
@@ -0,0 +1,21 @@
+# Palette Generator package
+
+[![pub package](https://img.shields.io/pub/v/palette_generator.svg)](
+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 0000000..dee655c
--- /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 0000000..96bf554
--- /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 0000000..cfba2a8
--- /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 0000000..65b7315
--- /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 0000000..e9cb5e8
--- /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 0000000..a3cd38c
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.flutter.packages.palettegenerator.imagecolors">
+
+    <!-- The INTERNET permission is required for development. Specifically,
+         flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
+         calls FlutterMain.startInitialization(this); in its onCreate method.
+         In most cases you can leave this as-is, but you if you want to provide
+         additional functionality it is fine to subclass or reimplement
+         FlutterApplication and put your custom class here. -->
+    <application
+        android:name="io.flutter.app.FlutterApplication"
+        android:label="image_colors"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- This keeps the window background of the activity showing
+                 until Flutter renders its first frame. It can be removed if
+                 there is no splash screen (such as the default splash screen
+                 defined in @style/LaunchTheme). -->
+            <meta-data
+                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
+                android:value="true" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
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 0000000..2ce2e49
--- /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 0000000..304732f
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
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 0000000..db77bb4
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files 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 0000000..17987b7
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files 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 0000000..09d4391
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files 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 0000000..d5f1c8d
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files 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 0000000..4d6372e
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files 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 0000000..00fa441
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+</resources>
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 0000000..d4225c7
--- /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 0000000..8bd86f6
--- /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 0000000..13372ae
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/android/gradle/wrapper/gradle-wrapper.jar
Binary files 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 0000000..9372d0f
--- /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 0000000..9d82f78
--- /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 0000000..8a0b282
--- /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 0000000..5a2f14f
--- /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 0000000..815f599
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/assets/landscape.png
Binary files 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 0000000..79cc4da
--- /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 0000000..9367d48
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>MinimumOSVersion</key>
+  <string>8.0</string>
+</dict>
+</plist>
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 0000000..592ceee
--- /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 0000000..592ceee
--- /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 0000000..d78d489
--- /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 = "<group>"; };
+		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		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 = "<group>"; };
+		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
+		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 = "<group>"; };
+		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* 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 = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+				CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		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 = "<group>";
+		};
+		97C146F11CF9000F007C117D /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				97C146F21CF9000F007C117D /* main.m */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
+/* 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 = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* 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 0000000..1d526a1
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
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 0000000..1263ac8
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0910"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
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 0000000..1d526a1
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
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 0000000..36e21bb
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/AppDelegate.h
@@ -0,0 +1,6 @@
+#import <Flutter/Flutter.h>
+#import <UIKit/UIKit.h>
+
+@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 0000000..59a72e9
--- /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 0000000..d36b1fa
--- /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 0000000..3d43d11
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Binary files 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 0000000..28c6bf0
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
Binary files 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 0000000..2ccbfd9
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
Binary files 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 0000000..f091b6b
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
Binary files 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 0000000..4cde121
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
Binary files 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 0000000..d0ef06e
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
Binary files 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 0000000..dcdc230
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
Binary files 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 0000000..2ccbfd9
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
Binary files 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 0000000..c8f9ed8
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
Binary files 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 0000000..a6d6b86
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
Binary files 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 0000000..a6d6b86
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
Binary files 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 0000000..75b2d16
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
Binary files 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 0000000..c4df70d
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
Binary files 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 0000000..6a84f41
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
Binary files 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 0000000..d0e1f58
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
Binary files 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 0000000..0bedcf2
--- /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 0000000..9da19ea
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
Binary files 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 0000000..9da19ea
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
Binary files 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 0000000..9da19ea
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
Binary files 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 0000000..89c2725
--- /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 0000000..f2e259c
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>
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 0000000..f3c2851
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
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 0000000..cdf4f69
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/Info.plist
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>image_colors</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
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 0000000..dff6597
--- /dev/null
+++ b/packages/palette_generator/example/image_colors/ios/Runner/main.m
@@ -0,0 +1,9 @@
+#import <Flutter/Flutter.h>
+#import <UIKit/UIKit.h>
+#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 0000000..c1ed3e0
--- /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<ImageColors> {
+  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<void> _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: <Widget>[
+          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: <Widget>[
+                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<Widget> swatches = <Widget>[];
+    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: <Widget>[
+        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: <Widget>[
+            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 0000000..8f1eee3
--- /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 0000000..77954a6
--- /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 = <PaletteTarget, PaletteColor>{} {
+    _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<PaletteGenerator> fromImage(
+    ui.Image image, {
+    Rect region,
+    int maximumColorCount,
+    List<PaletteFilter> filters,
+    List<PaletteTarget> 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 ??= <PaletteFilter>[avoidRedBlackWhitePaletteFilter];
+    maximumColorCount ??= _defaultCalculateNumberColors;
+    final _ColorCutQuantizer quantizer = new _ColorCutQuantizer(
+      image,
+      maxColors: maximumColorCount,
+      filters: filters,
+      region: region,
+    );
+    final List<PaletteColor> 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<PaletteGenerator> fromImageProvider(
+    ImageProvider imageProvider, {
+    Size size,
+    Rect region,
+    int maximumColorCount,
+    List<PaletteFilter> filters,
+    List<PaletteTarget> 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<ui.Image> imageCompleter = new Completer<ui.Image>();
+    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<PaletteTarget, PaletteColor> selectedSwatches;
+
+  /// The list of [PaletteColor]s that make up the palette, sorted from most
+  /// dominant color to least dominant color.
+  final List<PaletteColor> 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<PaletteTarget> targets;
+
+  /// Returns a list of colors in the [paletteColors], sorted from most
+  /// dominant to least dominant color.
+  Iterable<Color> 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<PaletteTarget> allTargets = new Set<PaletteTarget>.from(
+        (targets ?? <PaletteTarget>[]) + PaletteTarget.baseTargets);
+    final Set<Color> usedColors = new Set<Color>();
+    for (PaletteTarget target in allTargets) {
+      target._normalizeWeights();
+      selectedSwatches[target] = _generateScoredTarget(target, usedColors);
+    }
+  }
+
+  PaletteColor _generateScoredTarget(
+      PaletteTarget target, Set<Color> 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<Color> 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<Color> 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<PaletteColor>(
+        'paletteColors', paletteColors,
+        defaultValue: <PaletteColor>[]));
+    properties.add(new IterableProperty<PaletteTarget>('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<PaletteTarget> baseTargets = <PaletteTarget>[
+    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', color));
+    properties
+        .add(new DiagnosticsProperty<Color>('titleTextColor', titleTextColor));
+    properties
+        .add(new DiagnosticsProperty<Color>('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<Color, int> histogram;
+  final List<Color> 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<Color> 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 = <PaletteColor>[];
+
+  FutureOr<List<PaletteColor>> get quantizedColors async {
+    if (_paletteColors.isNotEmpty) {
+      return _paletteColors;
+    } else {
+      return _quantizeColors(image);
+    }
+  }
+
+  final ui.Image image;
+  final List<PaletteColor> _paletteColors;
+
+  final int maxColors;
+  final Rect region;
+  final List<PaletteFilter> filters;
+
+  Iterable<Color> _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<List<PaletteColor>> _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<Color> pixels =
+        _getImagePixels(imageData, image.width, image.height, region: region);
+    final Map<Color, int> hist = <Color, int>{};
+    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<PaletteColor> _quantizePixels(
+    int maxColors,
+    Map<Color, int> 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<PaletteColor> _generateAverageColors(
+      PriorityQueue<_ColorVolumeBox> colorVolumeBoxes) {
+    final List<PaletteColor> colors = <PaletteColor>[];
+    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 0000000..b542655
--- /dev/null
+++ b/packages/palette_generator/palette_generator.iml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/.pub" />
+      <excludeFolder url="file://$MODULE_DIR$/build" />
+      <excludeFolder url="file://$MODULE_DIR$/example/image_colors/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/example/image_colors/.pub" />
+      <excludeFolder url="file://$MODULE_DIR$/example/image_colors/android/app/src/main/java/io/flutter/packages" />
+      <excludeFolder url="file://$MODULE_DIR$/example/image_colors/build" />
+      <excludeFolder url="file://$MODULE_DIR$/example/image_test/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/example/image_test/.pub" />
+      <excludeFolder url="file://$MODULE_DIR$/example/image_test/build" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Dart SDK" level="project" />
+  </component>
+</module>
\ 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 0000000..81234d1
--- /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 <flutter-dev@googlegroups.com>
+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 0000000..ca54543
--- /dev/null
+++ b/packages/palette_generator/test/assets/dominant.png
Binary files differ
diff --git a/packages/palette_generator/test/assets/landscape.png b/packages/palette_generator/test/assets/landscape.png
new file mode 100644
index 0000000..815f599
--- /dev/null
+++ b/packages/palette_generator/test/assets/landscape.png
Binary files 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 0000000..6453d79
--- /dev/null
+++ b/packages/palette_generator/test/assets/tall_blue.png
Binary files 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 0000000..5130b44
--- /dev/null
+++ b/packages/palette_generator/test/assets/wide_red.png
Binary files 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 0000000..c777b01
--- /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<FakeImageProvider> {
+  const FakeImageProvider(this._image, {this.scale = 1.0});
+
+  final ui.Image _image;
+
+  /// The scale to place in the [ImageInfo] object of the image.
+  final double scale;
+
+  @override
+  Future<FakeImageProvider> obtainKey(ImageConfiguration configuration) {
+    return new SynchronousFuture<FakeImageProvider>(this);
+  }
+
+  @override
+  ImageStreamCompleter load(FakeImageProvider key) {
+    assert(key == this);
+    return new OneFrameImageStreamCompleter(
+      new SynchronousFuture<ImageInfo>(
+        new ImageInfo(image: _image, scale: scale),
+      ),
+    );
+  }
+}
+
+Future<ImageProvider> loadImage(String name) async {
+  File imagePath = new File(path.joinAll(<String>['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<String> imageNames = <String>[
+    'tall_blue',
+    'wide_red',
+    'dominant',
+    'landscape'
+  ];
+  final Map<String, ImageProvider> testImages = <String, ImageProvider>{};
+  for (String name in imageNames) {
+    testImages[name] = await loadImage('$name.png');
+  }
+
+  testWidgets('Initialize the image cache', (WidgetTester tester) {
+    // We need to have a testWidgets test in order to initialize the image
+    // cache for the other tests, but they timeout if they too are testWidgets
+    // tests.
+    tester.pumpWidget(const Placeholder());
+  });
+
+  test('PaletteGenerator works on 1-pixel wide blue image', () async {
+    final PaletteGenerator palette =
+        await PaletteGenerator.fromImageProvider(testImages['tall_blue']);
+    expect(palette.paletteColors.length, equals(1));
+    expect(palette.paletteColors[0].color,
+        within<Color>(distance: 8, from: const Color(0xff0000ff)));
+  });
+
+  test('PaletteGenerator works on 1-pixel high red image', () async {
+    final PaletteGenerator palette =
+        await PaletteGenerator.fromImageProvider(testImages['wide_red']);
+    expect(palette.paletteColors.length, equals(1));
+    expect(palette.paletteColors[0].color,
+        within<Color>(distance: 8, from: const Color(0xffff0000)));
+  });
+
+  test('PaletteGenerator finds dominant color and text colors', () async {
+    final PaletteGenerator palette =
+        await PaletteGenerator.fromImageProvider(testImages['dominant']);
+    expect(palette.paletteColors.length, equals(3));
+    expect(palette.dominantColor.color,
+        within<Color>(distance: 8, from: const Color(0xff0000ff)));
+    expect(palette.dominantColor.titleTextColor,
+        within<Color>(distance: 8, from: const Color(0xbc000000)));
+    expect(palette.dominantColor.bodyTextColor,
+        within<Color>(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<Color>(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<Color>(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<Color>(distance: 8, from: const Color(0xff00ff00)));
+  });
+
+  test('PaletteGenerator works as expected on a real image', () async {
+    final PaletteGenerator palette =
+        await PaletteGenerator.fromImageProvider(testImages['landscape']);
+    final List<PaletteColor> expectedSwatches = <PaletteColor>[
+      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<Color> expectedColors =
+        expectedSwatches.map<Color>((PaletteColor swatch) => swatch.color);
+    expect(palette.paletteColors, containsAll(expectedSwatches));
+    expect(palette.vibrantColor.color,
+        within<Color>(distance: 8, from: const Color(0xfff6b835)));
+    expect(palette.lightVibrantColor.color,
+        within<Color>(distance: 8, from: const Color(0xff82b2e9)));
+    expect(palette.darkVibrantColor.color,
+        within<Color>(distance: 8, from: const Color(0xff3f630c)));
+    expect(palette.mutedColor.color,
+        within<Color>(distance: 8, from: const Color(0xff6c7fa2)));
+    expect(palette.lightMutedColor.color,
+        within<Color>(distance: 8, from: const Color(0xffc4b2b2)));
+    expect(palette.darkMutedColor.color,
+        within<Color>(distance: 8, from: const Color(0xff3c4b2a)));
+    expect(palette.colors, containsAllInOrder(expectedColors));
+    expect(palette.colors.length, equals(palette.paletteColors.length));
+  });
+
+  test('PaletteGenerator limits max colors', () async {
+    final ImageProvider imageProvider = testImages['landscape'];
+    PaletteGenerator palette =
+        await PaletteGenerator.fromImageProvider(imageProvider, maximumColorCount: 32);
+    expect(palette.paletteColors.length, equals(31));
+    palette =
+        await PaletteGenerator.fromImageProvider(imageProvider, maximumColorCount: 1);
+    expect(palette.paletteColors.length, equals(1));
+    palette =
+        await PaletteGenerator.fromImageProvider(imageProvider, maximumColorCount: 15);
+    expect(palette.paletteColors.length, equals(15));
+  });
+
+  test('PaletteGenerator Filters work', () async {
+    final ImageProvider imageProvider = testImages['landscape'];
+    // First, test that supplying the default filter is the same as not supplying one.
+    List<PaletteFilter> filters = <PaletteFilter>[
+      avoidRedBlackWhitePaletteFilter
+    ];
+    PaletteGenerator palette =
+        await PaletteGenerator.fromImageProvider(imageProvider, filters: filters);
+    final List<PaletteColor> expectedSwatches = <PaletteColor>[
+      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<Color> expectedColors =
+        expectedSwatches.map<Color>((PaletteColor swatch) => swatch.color);
+    expect(palette.paletteColors, containsAll(expectedSwatches));
+    expect(palette.dominantColor.color,
+        within<Color>(distance: 8, from: const Color(0xff3f630c)));
+    expect(palette.colors, containsAllInOrder(expectedColors));
+
+    // A non-default filter works (and the default filter isn't applied too).
+    filters = <PaletteFilter>[onlyBluePaletteFilter];
+    palette = await PaletteGenerator.fromImageProvider(imageProvider, filters: filters);
+    final List<PaletteColor> blueSwatches = <PaletteColor>[
+      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<Color> expectedBlues =
+        blueSwatches.map<Color>((PaletteColor swatch) => swatch.color);
+
+    expect(palette.paletteColors, containsAll(blueSwatches));
+    expect(palette.dominantColor.color,
+        within<Color>(distance: 8, from: const Color(0xff4c5c75)));
+    expect(palette.colors, containsAllInOrder(expectedBlues));
+
+    // More than one filter is the intersection of the two filters.
+    filters = <PaletteFilter>[onlyBluePaletteFilter, onlyCyanPaletteFilter];
+    palette = await PaletteGenerator.fromImageProvider(imageProvider, filters: filters);
+    final List<PaletteColor> blueGreenSwatches = <PaletteColor>[
+      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<Color> expectedBlueGreens =
+        blueGreenSwatches.map<Color>((PaletteColor swatch) => swatch.color);
+
+    expect(palette.paletteColors, containsAll(blueGreenSwatches));
+    expect(palette.dominantColor.color,
+        within<Color>(distance: 8, from: const Color(0xffc8e8f8)));
+    expect(palette.colors, containsAllInOrder(expectedBlueGreens));
+
+    // Mutually exclusive filters return an empty palette.
+    filters = <PaletteFilter>[onlyBluePaletteFilter, onlyGreenPaletteFilter];
+    palette = await PaletteGenerator.fromImageProvider(imageProvider, filters: filters);
+    expect(palette.paletteColors, isEmpty);
+    expect(palette.dominantColor, isNull);
+    expect(palette.colors, isEmpty);
+  });
+
+  test('PaletteGenerator targets work', () async {
+    final ImageProvider imageProvider = testImages['landscape'];
+    // Passing an empty set of targets works the same as passing a null targets
+    // list.
+    PaletteGenerator palette = await PaletteGenerator.fromImageProvider(imageProvider,
+        targets: <PaletteTarget>[]);
+    expect(palette.selectedSwatches, isNotEmpty);
+    expect(palette.vibrantColor, isNotNull);
+    expect(palette.lightVibrantColor, isNotNull);
+    expect(palette.darkVibrantColor, isNotNull);
+    expect(palette.mutedColor, isNotNull);
+    expect(palette.lightMutedColor, isNotNull);
+    expect(palette.darkMutedColor, isNotNull);
+
+    // Passing targets augments the baseTargets, and those targets are found.
+    final List<PaletteTarget> saturationExtremeTargets = <PaletteTarget>[
+      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<Color>(distance: 8, from: lastPalette.dominantColor.color));
+      lastPalette = palette;
+    }
+  });
+}
+
+bool onlyBluePaletteFilter(HSLColor hslColor) {
+  const double blueLineMinHue = 185.0;
+  const double blueLineMaxHue = 260.0;
+  const double blueLineMaxSaturation = 0.82;
+  return hslColor.hue >= blueLineMinHue &&
+      hslColor.hue <= blueLineMaxHue &&
+      hslColor.saturation <= blueLineMaxSaturation;
+}
+
+bool onlyCyanPaletteFilter(HSLColor hslColor) {
+  const double cyanLineMinHue = 165.0;
+  const double cyanLineMaxHue = 200.0;
+  const double cyanLineMaxSaturation = 0.82;
+  return hslColor.hue >= cyanLineMinHue &&
+      hslColor.hue <= cyanLineMaxHue &&
+      hslColor.saturation <= cyanLineMaxSaturation;
+}
+
+bool onlyGreenPaletteFilter(HSLColor hslColor) {
+  const double greenLineMinHue = 80.0;
+  const double greenLineMaxHue = 165.0;
+  const double greenLineMaxSaturation = 0.82;
+  return hslColor.hue >= greenLineMinHue &&
+      hslColor.hue <= greenLineMaxHue &&
+      hslColor.saturation <= greenLineMaxSaturation;
+}