From 85573b7bf052746b4a21e2835d77b1b68dc16d83 Mon Sep 17 00:00:00 2001 From: Renan Borges Date: Mon, 21 Dec 2020 17:36:45 +0100 Subject: [PATCH] Initial commit --- .gitignore | 13 ++ CHANGELOG.md | 3 + LICENSE | 21 ++ README.md | 265 +++++++++++++++++++++ analysis_options.yaml | 14 ++ example/kandinsky_example.dart | 15 ++ lib/kandinsky.dart | 3 + lib/src/kandinsky_base.dart | 272 +++++++++++++++++++++ pubspec.yaml | 14 ++ test/kandinsky_test.dart | 416 +++++++++++++++++++++++++++++++++ 10 files changed, 1036 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 example/kandinsky_example.dart create mode 100644 lib/kandinsky.dart create mode 100644 lib/src/kandinsky_base.dart create mode 100644 pubspec.yaml create mode 100644 test/kandinsky_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c44ab0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Files and directories created by pub +.dart_tool/ +.packages + +# Omit commiting pubspec.lock for library packages: +# https://dart.dev/guides/libraries/private-files#pubspeclock +pubspec.lock + +# Conventional directory for build outputs +build/ + +# Directory created by dartdoc +doc/api/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..532bcd2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c03abdd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Renan Garcia Borges + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfb1b80 --- /dev/null +++ b/README.md @@ -0,0 +1,265 @@ +# kandinsky-dart + +Full ported version of [francisrstokes/kandinsky-js] + +## Usage + +A simple usage example: + +```dart +import 'package:kandinsky/kandinsky.dart' as kandinsky; + +main() { + var myDarkenHex = kandinsky.darkenHex(0.5, '#6699CC'); +} +``` + +## API + +### __rgb2hsl(rgbArray)__ + +> returns a hsl array + +--- +```dart +List rgb2hsl(List color) +``` +--- + +### __hsl2rgb(hslArray)__ + +> returns an rgb array + +--- +```dart +List hsl2rgb(List color) +``` +--- + +### __hex2rgb(hexString)__ + +> returns an rgb array + +--- +```dart +List hex2rgb(String hex) +``` +--- + +### __rgb2hex(rgbArray)__ + +> returns a hex string + +--- +```dart +String rgb2hex(List rgb) +``` +--- + +### __hex2hsl(hexString)__ + +> returns a hsl array + +--- +```dart +List hex2hsl(String hex) +``` +--- + +### __hsl2hex(hslArray)__ + +> returns a hex string + +--- +```dart +String hsl2hex(List color) +``` +--- + +### __darkenRgb(amount, rgbArray)__ + +> returns a darkened rgb array. `amount` is a value in the range `[0, 1]` + +--- +```dart +List darkenRgb(num amount, List rgb) +``` +--- + +### __lightenRgb(amount, rgbArray)__ + +> returns a lightened rgb array. `amount` is a value in the range `[0, 1]` + +--- +```dart +List lightenRgb(num amount, List rgb) +``` +--- + +### __darkenHsl(amount, hslArray)__ + +> returns a darkened hsl array. `amount` is a value in the range `[0, 1]` + +--- +```dart +List darkenHsl(num amount, List color) +``` +--- + +### __lightenHsl(amount, hslArray)__ + +> returns a lightened hsl array. `amount` is a value in the range `[0, 1]` + +--- +```dart +List lightenHsl(num amount, List color) +``` +--- + +### __lightenHex(amount, hexString)__ + +> returns a lightened hex string. `amount` is a value in the range `[0, 1]` + +--- +```dart +String lightenHex(num amount, String hex) +``` +--- + +### __darkenHex(amount, hexString)__ + +> returns a darkened hex string. `amount` is a value in the range `[0, 1]` + +--- +```dart +String darkenHex(num amount, String hex) +``` +--- + +### __lerp3(t, c1, c2)__ + +> returns a Vector3 colour somewhere between `c1` and `c2`. `t` is the "time" value in the range `[0, 1]` + +--- +```dart +List lerp3(num t, List color1, List color2) +``` +--- + +### __linearGradient(n, c1, c2)__ + +> returns an length `n` array of Vector3 colours. colours are evenly spaced between `c1` and `c2`. + +--- +```dart +List> linearGradient(num n, List color1, List color2) +``` +--- + +### __gradient(easeFn, n, c1, c2)__ + +> returns an length `n` array of Vector3 colours. colours are between `color1` and `color2`, and are spaced according to the easing function `easeFn`. + +--- +```dart +List> gradient(Function ease, int n, List color1, List color2) +``` +--- + +### __multiGradient(n, [col1, col3, ..., colN])__ + +> returns a length `n` array of Vector3 colours. colours are the ones formed from the `linearGradient(n/(numColours-1), col1, col2)` for all colours `col1, col2, ..., colN` + +--- +```dart +List> multiGradient(num n, List> colors) +``` +--- + + +### __rLinearGradient(n, c1, c2)__ + +> returns a rounded, length `n` array of Vector3 colours. colours are evenly spaced between `color1` and `color2`. + +--- +```dart +List> rLinearGradient(num n, List color1, List color2) +``` +--- + +### __rGradient(easeFn, n, c1, c2)__ + +> returns a rounded, length `n` array of Vector3 colours. colours are between `color1` and `color2`, and are spaced according to the easing function `easeFn`. + +--- +```dart +List> rGradient(Function ease, num n, List color1, List color2) +``` +--- + +### __rMultiGradient(n, [col1, col3, ..., colN])__ + +> returns a rounded, length `n` array of Vector3 colours. colours are the ones formed from the `linearGradient(n/(numColours-1), col1, col2)` for all colours `col1, col2, ..., colN` + +--- +```dart +List> rMultiGradient(num n, List> colors) +``` +--- + +### __complimentHex(n, hexString)__ + +> returns an length `n` array of hex strings. The 0th color is the same as the input `hexString`, while the others are colours corresponding to an eve turn around the colour wheel. If `n` is 3 for example, the two other colours would represent a 1/3 and 2/3 rotation of the colour wheel. + +--- +```dart +List complimentHex(num n, String hex) +``` +--- + +### __complimentHsl(n, hsl)__ + +> returns an length `n` array of hsl Vector3. The 0th color is the same as the input `hsl`, while the others are colours corresponding to an eve turn around the colour wheel. If `n` is 3 for example, the two other colours would represent a 1/3 and 2/3 rotation of the colour wheel. + +--- +```dart +List> complimentHsl(num n, List color) +``` +--- + +### __complimentRgb(n, rgb)__ + +> returns an length `n` array of rgb Vector3. The 0th color is the same as the input `rgb`, while the others are colours corresponding to an eve turn around the colour wheel. If `n` is 3 for example, the two other colours would represent a 1/3 and 2/3 rotation of the colour wheel. + +--- +```dart +List> complimentRgb(num n, List color) +``` +--- + +### __rgb2css(alpha, rgb)__ + +> returns an rgba css string like `rgba(255, 255, 255, 1)` from the rgb color and alpha value + +--- +```dart +String rgb2css(num alpha, List color) +``` +--- + +### __hsl2css(alpha, hsl)__ + +> returns an hsl css string like `hsl(222, 50%, 75%, 0.6)` from the hsl color and alpha value + +--- +```dart +String hsl2css(num alpha, List hsl) +``` +--- + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: https://github.com/renanborgez/kandinsky-dart/issues +[francisrstokes/kandinsky-js]: https://github.com/francisrstokes/kandinsky-js diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a686c1b --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,14 @@ +# Defines a default set of lint rules enforced for +# projects at Google. For details and rationale, +# see https://github.com/dart-lang/pedantic#enabled-lints. +include: package:pedantic/analysis_options.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. +# Uncomment to specify additional rules. +# linter: +# rules: +# - camel_case_types + +analyzer: +# exclude: +# - path/to/excluded/files/** diff --git a/example/kandinsky_example.dart b/example/kandinsky_example.dart new file mode 100644 index 0000000..6d11fb9 --- /dev/null +++ b/example/kandinsky_example.dart @@ -0,0 +1,15 @@ +import 'package:kandinsky/kandinsky.dart'; + +void main() { + var darkenHexColor = darkenHex(0.5, '#6699CC'); + print('my darken hex color: ${darkenHexColor}'); + + var lightenHexColor = lightenHex(0.5, '#06795C'); + print('my lighten hex color: ${lightenHexColor}'); + + var darkenRgbColor = darkenRgb(0.5, [180, 40, 20]); + print('my darken rgb color: ${darkenRgbColor}'); + + var lightenRgbColor = lightenRgb(0.5, [155, 90, 60]); + print('my lighten rgb color: ${lightenRgbColor}'); +} diff --git a/lib/kandinsky.dart b/lib/kandinsky.dart new file mode 100644 index 0000000..951336e --- /dev/null +++ b/lib/kandinsky.dart @@ -0,0 +1,3 @@ +library kandinsky_dart; + +export 'src/kandinsky_base.dart'; diff --git a/lib/src/kandinsky_base.dart b/lib/src/kandinsky_base.dart new file mode 100644 index 0000000..fc37872 --- /dev/null +++ b/lib/src/kandinsky_base.dart @@ -0,0 +1,272 @@ +import 'dart:math' as math; + +String padHex(String hexStr) => hexStr.length == 1 ? '0$hexStr' : hexStr; + +num _wrapValue(num m, num _m, num v) { + if (v < m) { + var diff = _m - v - 1; + return _wrapValue(m, _m, _m - diff); + } + if (v > _m) { + var diff = v - _m - 1; + return _wrapValue(m, _m, m + diff); + } + return v; +} + +num _wrapNorm(num n) => _wrapValue(0, 1, n); + +num _clamp(num min, num max, num n) { + if (n < min) { + return min; + } + if (n > max) { + return max; + } + return n; +} + +num _clampNorm(num n) => _clamp(0, 1, n); + +List rgb2hsl(List color) { + var r = color[0]; + var g = color[1]; + var b = color[2]; + + var nr = r / 255; + var ng = g / 255; + var nb = b / 255; + + var max = [nr, ng, nb].reduce(math.max); + var min = [nr, ng, nb].reduce(math.min); + var h; + var s; + var l = (max + min) / 2; + + if (max == min) { + h = 0; + s = 0; + } else { + var d = max - min; + s = (l > 0.5) ? d / (2 - max - min) : d / (max + min); + + if (max == nr) { + h = (ng - nb) / d + (ng < nb ? 6 : 0); + } + if (max == ng) { + h = (nb - nr) / d + 2; + } + if (max == nb) { + h = (nr - ng) / d + 4; + } + + h /= 6; + } + return [h, s, l]; +} + +num hue2rgb(num p, num q, num t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + + return p; +} + +List hsl2rgb(List color) { + var h = color[0]; + var s = color[1]; + var l = color[2]; + + var r, g, b; + + if (s == 0) { + r = g = b = l; + } else { + var q = (l < 0.5) ? l * (1 + s) : l + s - l * s; + + var p = 2 * l - q; + + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [(r * 255).round(), (g * 255).round(), (b * 255).round()]; +} + +List hex2rgb(String hex) { + var hs = hex[0] == '#' ? hex.substring(1) : hex; + return [ + int.parse(hs[0] + hs[1], radix: 16), + int.parse(hs[2] + hs[3], radix: 16), + int.parse(hs[4] + hs[5], radix: 16) + ]; +} + +String rgb2hex(List rgb) => + rgb.fold('#', (hex, c) => hex + padHex(c.round().toRadixString(16))); + +List hex2hsl(String hex) => rgb2hsl(hex2rgb(hex)); + +String hsl2hex(List color) => rgb2hex(hsl2rgb(color)); + +List darkenRgb(num amount, List rgb) { + return rgb + .map((c) => [ + [0, c + (c * -amount)].reduce(math.max), + 255 + ].reduce(math.min).round()) + .toList(); +} + +List lightenRgb(num amount, List rgb) => rgb + .map((c) => [ + [0, c + (c * amount)].reduce(math.max), + 255 + ].reduce(math.min).round()) + .toList(); + +List darkenHsl(num amount, List color) { + var h = color[0]; + var s = color[1]; + var l = color[2]; + return [h, s, _clampNorm(l - (l * amount))]; +} + +List lightenHsl(num amount, List color) { + var h = color[0]; + var s = color[1]; + var l = color[2]; + return [h, s, _clampNorm(l + (l * amount))]; +} + +String lightenHex(num amount, String hex) { + var rgb = hex2rgb(hex); + var ligthen = lightenRgb(amount, rgb); + + return rgb2hex(ligthen); +} + +String darkenHex(num amount, String hex) { + var rgb = hex2rgb(hex); + var ligthen = darkenRgb(amount, rgb); + + return rgb2hex(ligthen); +} + +List lerp3(num t, List color1, List color2) { + var r1 = color1[0]; + var g1 = color1[1]; + var b1 = color1[2]; + + var r2 = color2[0]; + var g2 = color2[1]; + var b2 = color2[2]; + + return [ + r1 + (t * (r2 - r1)), + g1 + (t * (g2 - g1)), + b1 + (t * (b2 - b1)), + ].toList(); +} + +List> linearGradient( + num n, + List color1, + List color2, +) { + var d = (n - 1 != 0) ? n - 1 : 1; + var result = List.generate(n, (i) => lerp3(i / d, color1, color2)).toList(); + return result; +} + +List> gradient( + Function ease, int n, List color1, List color2) { + var d = (n - 1 != 0) ? n - 1 : 1; + var result = + List.generate(n, (i) => lerp3(ease(i / d), color1, color2)).toList(); + return result; +} + +List> multiGradient(num n, List> colors) { + var i = -1; + return colors.fold([], (grad, color) { + i = i + 1; + if (i == 0) { + return grad; + } + var color1 = colors[i - 1]; + var color2 = color; + + var values = (n / (colors.length - 1)).round(); + if (i == colors.length - 1 || i == 1) { + values = (n / (colors.length - 1)).ceil(); + } + + return [...grad, ...linearGradient(values, color1, color2)]; + }); +} + +List> rMultiGradient(num n, List> colors) { + return multiGradient(n, colors) + .map((color) => color.map((c) => c.round()).toList()) + .toList(); +} + +List> rGradient( + Function ease, num n, List color1, List color2) => + gradient(ease, n, color1, color2) + .map((color) => color.map((c) => c.round()).toList()) + .toList(); + +List> rLinearGradient(num n, List color1, List color2) { + return linearGradient(n, color1, color2) + .map((color) => color.map((c) => c.round()).toList()) + .toList(); +} + +List> complimentHsl(num n, List color) { + var h = color[0]; + var s = color[1]; + var l = color[2]; + return List.generate(n, (i) => [_wrapNorm(h - (i / n)), s, l]); +} + +List> complimentRgb(num n, List color) { + return complimentHsl(n, rgb2hsl(color)).map(hsl2rgb).toList(); +} + +List complimentHex(num n, String hex) { + var hsl = hex2hsl(hex); + return complimentHsl(n, hsl).map(hsl2hex).toList(); +} + +String rgb2css(num alpha, List color) { + var rgb = color.map((e) => _clamp(0, 255, e.round())).toList(); + var r = rgb[0]; + var g = rgb[1]; + var b = rgb[2]; + + return 'rgba($r, $g, $b, ${_clampNorm(alpha)})'; +} + +String hsl2css(num alpha, List hsl) { + var h = hsl[0]; + var s = hsl[1]; + var l = hsl[2]; + + var _h = (h * 360).round(); + var _s = (s * 100).round(); + var _l = (l * 100).round(); + return 'hsl($_h, $_s%, $_l%, ${_clampNorm(alpha)})'; +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..7188003 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,14 @@ +name: kandinsky +description: A tiny color library +# version: 1.0.0 +# homepage: https://github.com/renanborgez/kandinsky-dart + +environment: + sdk: '>=2.10.0 <3.0.0' + +#dependencies: +# path: ^1.7.0 + +dev_dependencies: + pedantic: ^1.9.0 + test: ^1.15.7 diff --git a/test/kandinsky_test.dart b/test/kandinsky_test.dart new file mode 100644 index 0000000..20113c8 --- /dev/null +++ b/test/kandinsky_test.dart @@ -0,0 +1,416 @@ +import 'dart:math' as math; +import 'package:test/test.dart'; + +import 'package:kandinsky/kandinsky.dart'; + +const errorMargin = 255 / 100; +const hslErrorMargin = 360 / 100; +num errorCorrect(num n) => num.parse(n.toStringAsFixed(2)); + +void main() { + test('convert a rgb array to a hsl array', () { + var rgb = [175, 103, 31]; + var expected = [30 / 360, 70 / 100, 40 / 100].map((e) => e.round()); + + expect(rgb2hsl(rgb).map((e) => e.round()), expected); + }); + + test('convert a hsl array to a rgb array', () { + const hsl = [30 / 360, 70 / 100, 40 / 100]; + const expected = [175, 103, 31]; + + var converted = hsl2rgb(hsl); + + var diff = [ + [converted[0], expected[0]].reduce(math.max) - + [converted[0], expected[0]].reduce(math.min), + [converted[1], expected[1]].reduce(math.max) - + [converted[1], expected[1]].reduce(math.min), + [converted[2], expected[2]].reduce(math.max) - + [converted[2], expected[2]].reduce(math.min), + ]; + + for (var i = 0; i < diff.length; i++) { + expect(diff[i] < errorMargin, true); + } + }); + + test('linear interpolate a vector3', () { + const a = [0.0, 0.0, 0.0]; + const b = [255.0, 255.0, 255.0]; + + const expected1 = a; + const expected2 = [127.5, 127.5, 127.5]; + const expected3 = b; + + var converted1 = lerp3(0, a, b); + var converted2 = lerp3(0.5, a, b); + var converted3 = lerp3(1, a, b); + + expect(converted1, expected1); + expect(converted2, expected2); + expect(converted3, expected3); + }); + + test('create a linear gradient between two vector3 colors', () { + const a = [0.0, 0.0, 0.0]; + const b = [255.0, 255.0, 255.0]; + const n = 10; + + var out = linearGradient(10, a, b); + + var expected = List.generate(n, (i) => lerp3(i / (n - 1), a, b)); + expect(out, expected); + }); + + test('create a rounded linear gradient between two vector3 colors', () { + const a = [0.0, 0.0, 0.0]; + const b = [255.0, 255.0, 255.0]; + const n = 10; + + var out = rLinearGradient(10, a, b); + + var expected = + List.generate(n, (i) => lerp3(i / (n - 1), a, b).map((e) => e.round())); + expect(out, expected); + }); + + test('create a linear gradient of multiple vector3 colors', () { + const a = [0.0, 0.0, 0.0]; + const b = [255.0, 255.0, 255.0]; + const c = [23.0, 45.0, 67.0]; + const d = [99.0, 101.0, 222.0]; + const n = 10; + + for (var i = 1; i < 101; i += 1) { + var mg2 = multiGradient(i, [a, b]); + expect(mg2.length, i); + } + + var g1 = [ + ...linearGradient((n / 3).ceil(), a, b), + ...linearGradient((n / 3).round(), b, c), + ...linearGradient((n / 3).ceil(), c, d), + ]; + + var g2 = multiGradient(n, [a, b, c, d]); + + expect(g1, g2); + }); + + test('create a rounded linear gradient of multiple vector3 colors', () { + const a = [0.0, 0.0, 0.0]; + const b = [255.0, 255.0, 255.0]; + const c = [23.0, 45.0, 67.0]; + const d = [99.0, 101.0, 222.0]; + const n = 10; + + for (var i = 1; i < 101; i += 1) { + var mg2 = multiGradient(i, [a, b]); + expect(mg2.length, i); + } + + var g1 = [ + ...linearGradient((n / 3).ceil(), a, b), + ...linearGradient((n / 3).round(), b, c), + ...linearGradient((n / 3).ceil(), c, d), + ].map((color) => color.map((x) => x.round())); + + var g2 = rMultiGradient(n, [a, b, c, d]); + + expect(g1, g2); + }); + + test('create a nonlinear gradient between two vector3 colors', () { + const a = [0, 0, 0]; + const b = [255, 255, 255]; + const n = 10; + + num easeFn(num t) => math.pow(t, 2); + + var out = gradient(easeFn, 10, a, b); + var expected = List.generate(n, (i) => lerp3(easeFn(i / (n - 1)), a, b)); + + expect(out, expected); + }); + + test('create a rounded nonlinear gradient between two vector3 colors', () { + const a = [0, 0, 0]; + const b = [255, 255, 255]; + const n = 10; + + num easeFn(num t) => math.pow(t, 2); + + var out = rGradient(easeFn, 10, a, b); + var expected = List.generate( + n, (i) => lerp3(easeFn(i / (n - 1)), a, b).map((e) => e.round())); + + expect(out, expected); + }); + + test('convert a hex code to a rgb array', () { + const hex = '#FF0000'; + const hexNoHash = 'FF0000'; + const expected = [0xFF, 0, 0]; + + expect(hex2rgb(hex), expected); + expect(hex2rgb(hexNoHash), expected); + }); + + test('convert a rgb array to a hex code', () { + const expected = '#ff0000'; + const rgb = [0xFF, 0.0, 0.0]; + + expect(rgb2hex(rgb), expected); + }); + + test('convert a hex code to a hsl array', () { + const hex = '#af671f'; + var expected = [30 / 360, 70 / 100, 40 / 100].map(errorCorrect); + + expect(hex2hsl(hex).map(errorCorrect), expected); + }); + + test('convert a hsl array to a hex code', () { + var expected = hex2rgb('#af671f'); + const hsl = [30 / 360, 70 / 100, 40 / 100]; + var converted = hex2rgb(hsl2hex(hsl)); + + var diff = [ + [converted[0], expected[0]].reduce(math.max) - + [converted[0], expected[0]].reduce(math.min), + [converted[1], expected[1]].reduce(math.max) - + [converted[1], expected[1]].reduce(math.min), + [converted[2], expected[2]].reduce(math.max) - + [converted[2], expected[2]].reduce(math.min), + ]; + + for (var i = 0; i < diff.length; i++) { + expect(diff[i] < errorMargin, true); + } + }); + + test('darken an rgb color', () { + const rgb = [0x66, 0x99, 0xCC]; + const expected = [0x52, 0x7a, 0xa3]; + var converted = darkenRgb(0.2, rgb); + + expect(converted, expected); + }); + + test('lighten an rgb color', () { + const rgb = [0x66, 0x99, 0xCC]; + const expected = [0x7a, 0xb8, 0xf5]; + var converted = lightenRgb(0.2, rgb); + + expect(converted, expected); + }); + + test('darken a hsl color', () { + var hsl = [210 / 360, 0.5, 0.6].map(errorCorrect).toList(); + var expected = + [210 / 360, 0.5, 0.6 - (0.6 * 0.2)].map(errorCorrect).toList(); + var converted = darkenHsl(0.2, hsl); + + expect(converted, expected); + }); + + test('lighten a hsl color', () { + var hsl = [210 / 360, 0.5, 0.6].map(errorCorrect).toList(); + var expected = + [210 / 360, 0.5, 0.6 + (0.6 * 0.2)].map(errorCorrect).toList(); + var converted = lightenHsl(0.2, hsl); + + expect(converted, expected); + }); + + test('lighten a hex code', () { + const hex = '#6699CC'; + const expected = '#7ab8f5'; + var converted = lightenHex(0.2, hex); + + expect(converted, expected); + }); + + test('darken a hex code', () { + const hex = '#6699CC'; + const expected = '#527aa3'; + var converted = darkenHex(0.2, hex); + + expect(converted, expected); + }); + + test('create a hsl complimentary colour pallete', () { + var c = hex2hsl('#ff0000'); + var expected2 = ['#ff0000', '#00ffff'] + .map(hex2hsl) + .map((c) => c.map(errorCorrect).toList()) + .toList(); + var result2 = complimentHsl(2, c); + + var diffs2 = List.generate( + expected2.length, + (i) => [ + [result2[i][0], expected2[i][0]].reduce(math.max) - + [result2[i][0], expected2[i][0]].reduce(math.min), + [result2[i][1], expected2[i][1]].reduce(math.max) - + [result2[i][1], expected2[i][1]].reduce(math.min), + [result2[i][2], expected2[i][2]].reduce(math.max) - + [result2[i][2], expected2[i][2]].reduce(math.min), + ]).toList(); + + for (var i = 0; i < diffs2.length; i++) { + for (var j = 0; j < diffs2[i].length; j++) { + expect(diffs2[i][j] < hslErrorMargin, true); + } + } + + var expected3 = ['#ff0000', '#0000ff', '#00ff00'] + .map(hex2hsl) + .map((c) => c.map(errorCorrect).toList()) + .toList(); + var result3 = + complimentHsl(3, c).map((c) => c.map(errorCorrect).toList()).toList(); + + var diffs3 = List.generate( + expected3.length, + (i) => [ + [result3[i][0], expected3[i][0]].reduce(math.max) - + [result3[i][0], expected3[i][0]].reduce(math.min), + [result3[i][1], expected3[i][1]].reduce(math.max) - + [result3[i][1], expected3[i][1]].reduce(math.min), + [result3[i][2], expected3[i][2]].reduce(math.max) - + [result3[i][2], expected3[i][2]].reduce(math.min), + ]).toList(); + + for (var i = 0; i < diffs3.length; i++) { + for (var j = 0; j < diffs3[i].length; j++) { + expect(diffs3[i][j] < hslErrorMargin, true); + } + } + + var expected4 = ['#ff0000', '#7f00ff', '#00ffff', '#80ff00'] + .map(hex2hsl) + .map((c) => c.map(errorCorrect).toList()) + .toList(); + var result4 = + complimentHsl(4, c).map((c) => c.map(errorCorrect).toList()).toList(); + var diffs4 = List.generate( + expected4.length, + (i) => [ + [result4[i][0], expected4[i][0]].reduce(math.max) - + [result4[i][0], expected4[i][0]].reduce(math.min), + [result4[i][1], expected4[i][1]].reduce(math.max) - + [result4[i][1], expected4[i][1]].reduce(math.min), + [result4[i][2], expected4[i][2]].reduce(math.max) - + [result4[i][2], expected4[i][2]].reduce(math.min), + ]); + + for (var i = 0; i < diffs4.length; i++) { + for (var j = 0; j < diffs4[i].length; j++) { + expect(diffs4[i][j] < hslErrorMargin, true); + } + } + }); + + test('create a rgb complimentary colour pallete', () { + var c = hex2rgb('#ff0000'); + var expected2 = ['#ff0000', '#00ffff'].map(hex2rgb).toList(); + var result2 = complimentRgb(2, c); + + var diffs2 = List.generate( + expected2.length, + (i) => [ + [result2[i][0], expected2[i][0]].reduce(math.max) - + [result2[i][0], expected2[i][0]].reduce(math.min), + [result2[i][1], expected2[i][1]].reduce(math.max) - + [result2[i][1], expected2[i][1]].reduce(math.min), + [result2[i][2], expected2[i][2]].reduce(math.max) - + [result2[i][2], expected2[i][2]].reduce(math.min), + ]); + + for (var i = 0; i < diffs2.length; i++) { + for (var j = 0; j < diffs2[i].length; j++) { + expect(diffs2[i][j] < hslErrorMargin, true); + } + } + + var expected3 = ['#ff0000', '#0000ff', '#00ff00'].map(hex2rgb).toList(); + var result3 = complimentRgb(3, c); + + var diffs3 = List.generate( + expected3.length, + (i) => [ + [result3[i][0], expected3[i][0]].reduce(math.max) - + [result3[i][0], expected3[i][0]].reduce(math.min), + [result3[i][1], expected3[i][1]].reduce(math.max) - + [result3[i][1], expected3[i][1]].reduce(math.min), + [result3[i][2], expected3[i][2]].reduce(math.max) - + [result3[i][2], expected3[i][2]].reduce(math.min), + ]); + + for (var i = 0; i < diffs3.length; i++) { + for (var j = 0; j < diffs3[i].length; j++) { + expect(diffs3[i][j] < hslErrorMargin, true); + } + } + + var expected4 = + ['#ff0000', '#7f00ff', '#00ffff', '#80ff00'].map(hex2rgb).toList(); + var result4 = complimentRgb(4, c); + + var diffs4 = List.generate( + expected4.length, + (i) => [ + [result4[i][0], expected4[i][0]].reduce(math.max) - + [result4[i][0], expected4[i][0]].reduce(math.min), + [result4[i][1], expected4[i][1]].reduce(math.max) - + [result4[i][1], expected4[i][1]].reduce(math.min), + [result4[i][2], expected4[i][2]].reduce(math.max) - + [result4[i][2], expected4[i][2]].reduce(math.min), + ]); + + for (var i = 0; i < diffs4.length; i++) { + for (var j = 0; j < diffs4[i].length; j++) { + expect(diffs4[i][j] < hslErrorMargin, true); + } + } + }); + + test('create a hex complimentary colour pallete', () { + const c = '#ff0000'; + const expected2 = ['#ff0000', '#00ffff']; + var result2 = complimentHex(2, c); + expect(result2, expected2); + + const expected3 = ['#ff0000', '#0000ff', '#00ff00']; + var result3 = complimentHex(3, c); + expect(result3, expected3); + + const expected4 = ['#ff0000', '#7f00ff', '#00ffff', '#80ff00']; + var result4 = complimentHex(4, c); + expect(expected4, result4); + }); + + test('get a css string from an rgb color', () { + const expected1 = 'rgba(255, 128, 64, 1)'; + expect(rgb2css(1, [255, 128, 64]), expected1); + + const expected2 = 'rgba(255, 128, 64, 1)'; + expect(rgb2css(1.5, [255, 128, 64]), expected2); + + const expected3 = 'rgba(255, 128, 64, 0.3)'; + expect(rgb2css(0.3, [255, 128, 64]), expected3); + }); + + test('get a css string from an hsl color', () { + const expected1 = 'hsl(101, 62%, 41%, 1)'; + expect(hsl2css(1, [0.28, 0.62, 0.414]), expected1); + + const expected2 = 'hsl(101, 62%, 41%, 0.12)'; + expect(hsl2css(0.12, [0.28, 0.62, 0.414]), expected2); + + const expected3 = 'hsl(101, 62%, 41%, 1)'; + expect(hsl2css(1.5, [0.28, 0.62, 0.414]), expected3); + }); +}