Skip to content

Instantly share code, notes, and snippets.

@long1eu
Created January 14, 2019 17:44
Show Gist options
  • Save long1eu/5187b17f039fa3dae484640448771009 to your computer and use it in GitHub Desktop.
Save long1eu/5187b17f039fa3dae484640448771009 to your computer and use it in GitHub Desktop.
ColorMatrix
import 'dart:math' as math;
import 'package:collection/collection.dart';
/// 4x5 matrix for transforming the color and alpha components of a Bitmap.
/// The matrix can be passed as single array, and is treated as follows:
///
/// <pre>
/// [ a, b, c, d, e,
/// f, g, h, i, j,
/// k, l, m, n, o,
/// p, q, r, s, t ]</pre>
///
/// <p>
/// When applied to a color <code>[R, G, B, A]</code>, the resulting color
/// is computed as:
/// </p>
///
/// <pre>
/// R&rsquo; = a*R + b*G + c*B + d*A + e;
/// G&rsquo; = f*R + g*G + h*B + i*A + j;
/// B&rsquo; = k*R + l*G + m*B + n*A + o;
/// A&rsquo; = p*R + q*G + r*B + s*A + t;</pre>
///
/// <p>
/// That resulting color <code>[R&rsquo;, G&rsquo;, B&rsquo;, A&rsquo;]</code>
/// then has each channel clamped to the <code>0</code> to <code>255</code>
/// range.
/// </p>
///
/// <p>
/// The sample ColorMatrix below inverts incoming colors by scaling each
/// channel by <code>-1</code>, and then shifting the result up by
/// <code>255</code> to remain in the standard color space.
/// </p>
///
/// <pre>
/// [ -1, 0, 0, 0, 255,
/// 0, -1, 0, 0, 255,
/// 0, 0, -1, 0, 255,
/// 0, 0, 0, 1, 0 ]</pre>
class ColorMatrix {
ColorMatrix([List<double> src]) : _list = src != null ? src : List<double>(20);
final List<double> _list;
List<double> get data => _list.toList(growable: false);
/// Set this colormatrix to identity:
/// <pre>
/// [ 1 0 0 0 0 - red vector
/// 0 1 0 0 0 - green vector
/// 0 0 1 0 0 - blue vector
/// 0 0 0 1 0 ] - alpha vector
/// </pre>
void reset() {
for (int i = 0; i < _list.length; i++) {
_list[i] = 0;
}
_list[0] = _list[6] = _list[12] = _list[18] = 1;
}
/// Assign the array of doubles into this matrix, copying all of its values.
void set(List<double> src) {
for (int i = 0; i < _list.length; i++) {
_list[i] = src[i];
}
}
/// Set this colormatrix to scale by the specified values.
void setScale(double rScale, double gScale, double bScale, double aScale) {
final List<double> a = _list;
for (int i = 19; i > 0; --i) {
a[i] = 0;
}
a[0] = rScale;
a[6] = gScale;
a[12] = bScale;
a[18] = aScale;
}
/// Set the rotation on a color axis by the specified values.
/// <p>
/// <code>axis=0</code> correspond to a rotation around the RED color
/// <code>axis=1</code> correspond to a rotation around the GREEN color
/// <code>axis=2</code> correspond to a rotation around the BLUE color
/// </p>
void setRotate(int axis, double degrees) {
reset();
final double radians = degrees * math.pi / 180;
final double cosine = math.cos(radians);
final double sine = math.sin(radians);
switch (axis) {
// Rotation around the red color
case 0:
_list[6] = _list[12] = cosine;
_list[7] = sine;
_list[11] = -sine;
break;
// Rotation around the green color
case 1:
_list[0] = _list[12] = cosine;
_list[2] = -sine;
_list[10] = sine;
break;
// Rotation around the blue color
case 2:
_list[0] = _list[6] = cosine;
_list[1] = sine;
_list[5] = -sine;
break;
default:
throw StateError('');
}
}
/// Set this colormatrix to the concatenation of the two specified
/// colormatrices, such that the resulting colormatrix has the same effect
/// as applying matB and then applying matA.
/// <p>
/// It is legal for either matA or matB to be the same colormatrix as this.
/// </p>
void setConcat(ColorMatrix matA, ColorMatrix matB) {
List<double> tmp;
if (matA == this || matB == this) {
tmp = List<double>(20);
} else {
tmp = _list;
}
final List<double> a = matA._list;
final List<double> b = matB._list;
int index = 0;
for (int j = 0; j < 20; j += 5) {
for (int i = 0; i < 4; i++) {
tmp[index++] = a[j + 0] * b[i + 0] + a[j + 1] * b[i + 5] + a[j + 2] * b[i + 10] + a[j + 3] * b[i + 15];
}
tmp[index++] = a[j + 0] * b[4] + a[j + 1] * b[9] + a[j + 2] * b[14] + a[j + 3] * b[19] + a[j + 4];
}
if (tmp != _list) {
set(tmp);
}
}
/// Concat this colormatrix with the specified prematrix.
/// <p>
/// This is logically the same as calling setConcat(this, prematrix);
/// </p>
void preConcat(ColorMatrix prematrix) {
setConcat(this, prematrix);
}
/// Concat this colormatrix with the specified postmatrix.
/// <p>
/// This is logically the same as calling setConcat(postmatrix, this);
/// </p>
void postConcat(ColorMatrix postmatrix) {
setConcat(postmatrix, this);
}
///////////////////////////////////////////////////////////////////////////
/// Set the matrix to affect the saturation of colors.
/// @param sat A value of 0 maps the color to gray-scale. 1 is identity.
void setSaturation(double sat) {
reset();
final List<double> m = _list;
final double invSat = 1 - sat;
final double R = 0.213 * invSat;
final double G = 0.715 * invSat;
final double B = 0.072 * invSat;
m[0] = R + sat;
m[1] = G;
m[2] = B;
m[5] = R;
m[6] = G + sat;
m[7] = B;
m[10] = R;
m[11] = G;
m[12] = B + sat;
}
/// Set the matrix to convert RGB to YUV
void setRGB2YUV() {
reset();
final List<double> m = _list;
// these coefficients match those in libjpeg
m[0] = 0.299;
m[1] = 0.587;
m[2] = 0.114;
m[5] = -0.16874;
m[6] = -0.33126;
m[7] = 0.5;
m[10] = 0.5;
m[11] = -0.41869;
m[12] = -0.08131;
}
/// Set the matrix to convert from YUV to RGB
void setYUV2RGB() {
reset();
final List<double> m = _list;
// these coefficients match those in libjpeg
m[2] = 1.402;
m[5] = 1;
m[6] = -0.34414;
m[7] = -0.71414;
m[10] = 1;
m[11] = 1.772;
m[12] = 0;
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ColorMatrix &&
runtimeType == other.runtimeType &&
const ListEquality<double>().equals(_list, other._list);
@override
int get hashCode => const ListEquality<double>().hash(_list);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment