/*
* @(#)ColorAdjustCodec.java 1.0 2012-01-16
*
* Copyright (c) 2012 Werner Randelshofer, Goldau, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*/
package org.monte.media.converter;
import org.monte.media.BezierInterpolator;
import org.monte.media.SplineInterpolator;
import java.awt.Image;
import java.awt.image.BufferedImage;
import org.monte.media.AbstractVideoCodec;
import org.monte.media.Buffer;
import org.monte.media.Format;
import org.monte.media.image.ColorModels;
import org.monte.media.image.Images;
import static org.monte.media.VideoFormatKeys.*;
import static org.monte.media.BufferFlag.*;
import static java.lang.Math.*;
/**
* Adjusts the colors of a buffered image.
*
* @author Werner Randelshofer
* @version 1.0 2012-01-16 Created.
*/
public class ColorAdjustCodec extends AbstractVideoCodec {
private ColorAdjustModel model = new DefaultColorAdjustModel();
public ColorAdjustCodec() {
super(new Format[]{
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
EncodingKey, ENCODING_BUFFERED_IMAGE), //
},
new Format[]{
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
EncodingKey, ENCODING_BUFFERED_IMAGE), //
}//
);
name = "Adjust Color";
}
@Override
public Format setInputFormat(Format f) {
Format fNew = super.setInputFormat(f);
outputFormat = fNew;
return fNew;
}
@Override
public int process(Buffer in, Buffer out) {
out.setMetaTo(in);
out.format = outputFormat;
if (in.isFlag(DISCARD)) {
return CODEC_OK;
}
BufferedImage imgIn = (BufferedImage) in.data;
if (imgIn == null || model == null) {
out.setFlag(DISCARD);
return CODEC_FAILED;
}
BufferedImage imgOut = null;
if (out.data instanceof BufferedImage) {
imgOut = (BufferedImage) out.data;
if (imgOut.getWidth() != imgIn.getWidth()//
|| imgOut.getHeight() != imgIn.getHeight()//
|| imgOut.getType()!=BufferedImage.TYPE_INT_RGB) {
imgOut = null;
}
}
if (imgOut == null) {
imgOut = new BufferedImage(imgIn.getWidth(), imgIn.getHeight(), BufferedImage.TYPE_INT_RGB);
}
out.data = imgOut;
int[] rgbIn = Images.toPixels(imgIn);
int[] rgbOut = Images.toPixels(imgOut);
float whitePoint = max(model.getWhitePoint(), model.getBlackPoint());
float midPoint = model.getMidPoint();
float blackPoint = min(model.getBlackPoint(), model.getWhitePoint());
float invLevelsExtent = 1f / (whitePoint - blackPoint);
if (whitePoint == 1 && blackPoint == 0 || whitePoint - blackPoint == 0) {
invLevelsExtent = -1;
}
float saturation = model.getSaturation() * 2;
float invsqrt2 = (float) (1.0 / sqrt(2.0));
float cbShift, crShift;
boolean TT = model.isWhiteBalanceTTEnabled();
if (TT) {
cbShift = (-model.getTemperature() - model.getTint()) * invsqrt2;
crShift = (model.getTemperature() - model.getTint()) * invsqrt2;
//System.out.println("ColorAdjustCodec tmp,tnt="+model.getTemperature()+","+model.getTint());
//System.out.println("ColorAdjustCodec cb,cr="+cbShift+","+crShift);
} else {
cbShift = crShift = 0;
}
SplineInterpolator sint;
sint = new SplineInterpolator(0.5f - model.getShadows() * 0.5f, 0.5f,//
0.5f + model.getHighlights() * 0.5f, 0.5f);
BezierInterpolator hilightsAndShadows;
if (model.getShadows() == 0 && model.getHighlights() == 0) {
hilightsAndShadows = null;
} else {
hilightsAndShadows = new BezierInterpolator(new double[][]{//
{0, 0},//
{0.5f - model.getShadows() * 0.5f,//
0.5f},//
{0.5, 0.5},
{0.5f + model.getHighlights() * 0.5f,//
0.5f}, //
{1, 1}//
});
}
float brightness = model.getBrightness();
float exposure = 1f + model.getExposure();
exposure *= exposure;
float contrast = 1f + model.getContrast();
boolean becAdjust = (model.getBrightness() != 0 || model.getExposure() != 0 || model.getContrast() != 0);
float[] ycc = new float[3];
float[] rgb = new float[3];
boolean QM = model.isWhiteBalanceQMEnabled();
float[] wbqm = (QM) ? model.getWhiteBalanceQM() : new float[]{0, 1, 0, 1};
float rmu = wbqm[0];
float rnu = wbqm[1];
float bmu = wbqm[2];
float bnu = wbqm[3];
/*if (QM) {
System.out.println("ColorAdjustCodec mur=" + rmu + " nur=" + rnu + " mub=" + bmu + " nub=" + bnu);
}*/
for (int i = 0; i < rgbIn.length; i++) {
int p = rgbIn[i];
rgb[0] = (p & 0xff0000) >>> 16;
rgb[1] = (p & 0xff00) >> 8;
rgb[2] = (p & 0xff);
if (QM) {
// Note: QM operates on rgb values in the range [0,255]
float r = rgb[0], b = rgb[2];
rgb[0] = r * r * rmu + r * rnu;
rgb[2] = b * b * bmu + b * bnu;
}
// From now on, we work with values in the range [0,1].
rgb[0] *= 1f / 255f;
rgb[1] *= 1f / 255f;
rgb[2] *= 1f / 255f;
if (TT) {
ColorModels.RGBtoYCC(rgb, ycc);
ycc[1] = (ycc[1] + cbShift) * saturation;
ycc[2] = (ycc[2] + crShift) * saturation;
ColorModels.YCCtoRGB(ycc, rgb);
}
if (hilightsAndShadows != null) {
rgb[0] = hilightsAndShadows.getFraction(rgb[0]);
rgb[1] = hilightsAndShadows.getFraction(rgb[1]);
rgb[2] = hilightsAndShadows.getFraction(rgb[2]);
}
if (becAdjust) {
rgb[0] = max(0, min(1, ((rgb[0] - 0.5f) * contrast + 0.5f) * exposure + brightness));
rgb[1] = max(0, min(1, ((rgb[1] - 0.5f) * contrast + 0.5f) * exposure + brightness));
rgb[2] = max(0, min(1, ((rgb[2] - 0.5f) * contrast + 0.5f) * exposure + brightness));
}
if (invLevelsExtent != -1) {
rgb[0] = (max(min(rgb[0], whitePoint), blackPoint) - blackPoint) * invLevelsExtent;
rgb[1] = (max(min(rgb[1], whitePoint), blackPoint) - blackPoint) * invLevelsExtent;
rgb[2] = (max(min(rgb[2], whitePoint), blackPoint) - blackPoint) * invLevelsExtent;
}
rgbOut[i] = ((int) (rgb[0] * 255) << 16)
| ((int) (rgb[1] * 255) << 8)
| ((int) (rgb[2] * 255) << 0);
}
return CODEC_OK;
}
public ColorAdjustModel getModel() {
return model;
}
public void setModel(ColorAdjustModel newValue) {
this.model = newValue;
}
}