public class CurveFilter implements Filter {
/ The lookup table.
private final Mat mLUT = new MatOfInt();
public CurveFilter(
final double[] vValIn, final double[] vValOut,
final double[] rValIn, final double[] rValOut,
final double[] gValIn, final double[] gValOut,
final double[] bValIn, final double[] bValOut) {
/ Create the interpolation functions.
UnivariateFunction vFunc = newFunc(vValIn, vValOut);
UnivariateFunction rFunc = newFunc(rValIn, rValOut);
UnivariateFunction gFunc = newFunc(gValIn, gValOut);
UnivariateFunction bFunc = newFunc(bValIn, bValOut);
/ Create and populate the lookup table.
mLUT.create(256, 1, CvType.CV_8UC4);
for (int i = 0; i < 256; i++) {
final double v = vFunc.value(i);
final double r = rFunc.value(v);
final double g = gFunc.value(v);
final double b = bFunc.value(v);
mLUT.put(i, 0, r, g, b, i); / alpha is unchanged
}
}
@Override
public void apply(final Mat src, final Mat dst) {
/ Apply the lookup table.
Core.LUT(src, mLUT, dst);
}
private UnivariateFunction newFunc(final double[] valIn,
final double[] valOut) {
UnivariateInterpolator interpolator;
if (valIn.length > 2) {
interpolator = new SplineInterpolator();
} else {
interpolator = new LinearInterpolator();
}
return interpolator.interpolate(valIn, valOut);
}
}
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
CurveFilter stores the lookup table in a member variable. The constructor method populates the lookup table based on the four sets of control points that are taken as arguments. As well as a set of control points for each of the RGB channels, the constructor also takes a set of control points for the image's overall brightness, just for convenience. A helper method, newFunc, creates an appropriate interpolation function (linear or spline) for each set of control points. Then, we iterate over the possible input values and populate the lookup table.
The apply method is a one-liner. It simply uses the precomputed lookup table with the given source and destination matrices.
CurveFilter can be subclassed to define a filter with a specific set of control points. For example, let's open PortraCurveFilter.java and write the following code:
public class PortraCurveFilter extends CurveFilter {
public PortraCurveFilter() {
super(
new double[] { 0, 23, 157, 255 }, / vValIn
new double[] { 0, 20, 173, 255 }, / vValOut
new double[] { 0, 69, 213, 255 }, / rValIn
new double[] { 0, 69, 218, 255 }, / rValOut
new double[] { 0, 52, 189, 255 }, / gValIn
new double[] { 0, 47, 196, 255 }, / gValOut
new double[] { 0, 41, 231, 255 }, / bValIn
new double[] { 0, 46, 228, 255 }); / bValOut
}
}
This filter brightens the image, makes shadows cooler (more blue), and makes highlights warmer (more yellow). It produces flattering skin tones and tends to make things look sunnier and cleaner. It resembles the color characteristics of a brand of photo film called Kodak Portra, which was often used for portraits.
The code for our other three channel mixing filters is similar. The ProviaCurveFilter class uses the following arguments for its control points:
new double[] { 0, 255 }, / vValIn
new double[] { 0, 255 }, / vValOut
new double[] { 0, 59, 202, 255 }, / rValIn
new double[] { 0, 54, 210, 255 }, / rValOut
new double[] { 0, 27, 196, 255 }, / gValIn
new double[] { 0, 21, 207, 255 }, / gValOut
new double[] { 0, 35, 205, 255 }, / bValIn
new double[] { 0, 25, 227, 255 }); / bValOut
The effect is a strong, blue or greenish-blue tint in shadows and a strong, yellow or greenish-yellow tint in highlights. It resembles a film processing technique called cross-processing, which was sometimes used to produce grungy-looking photos of fashion models, pop stars, and so on.
For a good discussion of how to emulate various brands of photo film, see Petteri Sulonen's blog at http://www.prime-junta.net/pont/How_to/100_Curves_and_Films/_Curves_and_films.html. The control points that we use are based on examples given in this article.
Curve filters are a convenient tool for manipulating color and contrast, but they are limited insofar as each destination pixel is affected by only a single input pixel. Next, we will examine a more flexible family of filters, which enable each destination pixel to be affected by a neighborhood of input pixels.
Summary
In this article we learned how to make subtle color shifts with curves.
Resources for Article:
Further resources on this subject: