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