import 'dart:math' as Math; | |
import 'dart:typed_data'; | |
import '../color.dart'; | |
import '../image.dart'; | |
import '../internal/clamp.dart'; | |
/** | |
* Adjust the color of the [src] image using various color transformations. | |
* | |
* [blacks] defines the black level of the image, as a color. | |
* | |
* [whites] defines the white level of the image, as a color. | |
* | |
* [mids] defines the mid level of hte image, as a color. | |
* | |
* [contrast] increases (> 1) / decreases (< 1) the contrast of the image by | |
* pushing colors away/toward neutral gray, where at 0.0 the image is entirely | |
* neutral gray (0 contrast), 1.0, the image is not adjusted and > 1.0 the | |
* image increases contrast. | |
* | |
* [saturation] increases (> 1) / decreases (< 1) the saturation of the image | |
* by pushing colors away/toward their grayscale value, where 0.0 is grayscale | |
* and 1.0 is the original image, and > 1.0 the image becomes more saturated. | |
* | |
* [brightness] is a constant scalar of the image colors. At 0 the image | |
* is black, 1.0 unmodified, and > 1.0 the image becomes brighter. | |
* | |
* [gamma] is an exponential scalar of the image colors. At < 1.0 the image | |
* becomes brighter, and > 1.0 the image becomes darker. A [gamma] of 1/2.2 | |
* will convert the image colors to linear color space. | |
* | |
* [exposure] is an exponential scalar of the image as rgb * pow(2, exposure). | |
* At 0, the image is unmodified; as the exposure increases, the image | |
* brightens. | |
* | |
* [hue] shifts the hue component of the image colors in degrees. A [hue] of | |
* 0 will have no affect, and a [hue] of 45 will shift the hue of all colors | |
* by 45 degrees. | |
* | |
* [amount] controls how much affect this filter has on the [src] image, where | |
* 0.0 has no effect and 1.0 has full effect. | |
*/ | |
Image adjustColor(Image src, {int blacks, int whites, int mids, | |
double contrast, double saturation, double brightness, | |
double gamma, double exposure, double hue, | |
double amount}) { | |
if (amount == 0.0) { | |
return src; | |
} | |
const double DEG_TO_RAD = 0.0174532925; | |
const double avgLumR = 0.5; | |
const double avgLumG = 0.5; | |
const double avgLumB = 0.5; | |
const double lumCoeffR = 0.2125; | |
const double lumCoeffG = 0.7154; | |
const double lumCoeffB = 0.0721; | |
double br, bg, bb; | |
double wr, wg, wb; | |
double mr, mg, mb; | |
if (blacks != null || whites != null || mids != null) { | |
br = blacks != null ? getRed(blacks) / 255.0 : 0.0; | |
bg = blacks != null ? getGreen(blacks) / 255.0 : 0.0; | |
bb = blacks != null ? getBlue(blacks) / 255.0 : 0.0; | |
wr = whites != null ? getRed(whites) / 255.0 : 1.0; | |
wg = whites != null ? getGreen(whites) / 255.0 : 1.0; | |
wb = whites != null ? getBlue(whites) / 255.0 : 1.0; | |
mr = mids != null ? getRed(mids) / 255.0 : 0.5; | |
mg = mids != null ? getGreen(mids) / 255.0 : 0.5; | |
mb = mids != null ? getBlue(mids) / 255.0 : 0.5; | |
mr = 1.0 / (1.0 + 2.0 * (mr - 0.5)); | |
mg = 1.0 / (1.0 + 2.0 * (mg - 0.5)); | |
mb = 1.0 / (1.0 + 2.0 * (mb - 0.5)); | |
} | |
double invSaturation = saturation != null ? 1.0 - saturation : 0.0; | |
double invContrast = contrast != null ? 1.0 - contrast : 0.0; | |
if (exposure != null) { | |
exposure = Math.pow(2.0, exposure); | |
} | |
double hueR; | |
double hueG; | |
double hueB; | |
if (hue != null) { | |
hue *= DEG_TO_RAD; | |
double s = Math.sin(hue); | |
double c = Math.cos(hue); | |
hueR = (2.0 * c) / 3.0; | |
hueG = (-Math.sqrt(3.0) * s - c) / 3.0; | |
hueB = ((Math.sqrt(3.0) * s - c) + 1.0) / 3.0; | |
} | |
double invAmount = amount != null ? 1.0 - amount : 0.0; | |
Uint8List pixels = src.getBytes(); | |
for (int i = 0, len = pixels.length; i < len; i += 4) { | |
double or = pixels[i] / 255.0; | |
double og = pixels[i + 1] / 255.0; | |
double ob = pixels[i + 2] / 255.0; | |
double r = or; | |
double g = og; | |
double b = ob; | |
if (br != null) { | |
r = Math.pow((r + br) * wr, mr); | |
g = Math.pow((g + bg) * wg, mg); | |
b = Math.pow((b + bb) * wb, mb); | |
} | |
if (brightness != null && brightness != 1.0) { | |
r *= brightness; | |
g *= brightness; | |
b *= brightness; | |
} | |
if (saturation != null) { | |
double lum = r * lumCoeffR + g * lumCoeffG + b * lumCoeffB; | |
r = lum * invSaturation + r * saturation; | |
g = lum * invSaturation + g * saturation; | |
b = lum * invSaturation + b * saturation; | |
} | |
if (contrast != null) { | |
r = avgLumR * invContrast + r * contrast; | |
g = avgLumG * invContrast + g * contrast; | |
b = avgLumB * invContrast + b * contrast; | |
} | |
if (gamma != null) { | |
r = Math.pow(r, gamma); | |
g = Math.pow(g, gamma); | |
b = Math.pow(b, gamma); | |
} | |
if (exposure != null) { | |
r = r * exposure; | |
g = g * exposure; | |
b = b * exposure; | |
} | |
if (hue != null && hue != 0.0) { | |
double hr = r * hueR + g * hueG + b * hueB; | |
double hg = r * hueB + g * hueR + b * hueG; | |
double hb = r * hueG + g * hueB + b * hueR; | |
r = hr; | |
g = hg; | |
b = hb; | |
} | |
if (amount != null) { | |
r = r * amount + or * invAmount; | |
g = g * amount + og * invAmount; | |
b = b * amount + ob * invAmount; | |
} | |
pixels[i] = clamp255((r * 255.0).toInt()); | |
pixels[i + 1] = clamp255((g * 255.0).toInt()); | |
pixels[i + 2] = clamp255((b * 255.0).toInt()); | |
} | |
return src; | |
} |