/*
* #%L
* BroadleafCommerce Open Admin Platform
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.broadleafcommerce.openadmin.server.service.artifact.image.effects.chain.filter;
import org.broadleafcommerce.openadmin.server.service.artifact.image.Operation;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Map;
/**
* This filter is based conceptually on the auto-levels feature of Photoshop and functions in the same way.
* The filter automatically adjusts tonal range for problem photographs, making sure the tones are
* equally distributed from black to white. Note, a marginal clipping does occur at the highs and lows to account
* for aberrant pixels in those quadrants that might erroneously effect the calculation.
*
* @author jfischer
*
*/
public class AutoLevelsRGB extends BaseFilter {
private static final double TOPCLIP = 0.01D;
private static final double BOTTOMCLIP = 0.01D;
public static void main(String[] args) {
try {
BufferedImage image = ImageIO.read(new File("C:/temp/test.jpg"));
AutoLevelsRGB levels = new AutoLevelsRGB(null);
BufferedImage newImage = levels.filter(image, null);
ImageIO.write(newImage, "jpg", new File("C:/temp/test2.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
private RenderingHints hints;
public AutoLevelsRGB() {
//do nothing
}
public AutoLevelsRGB(RenderingHints hints) {
this.hints = hints;
}
@Override
public Operation buildOperation(Map<String, String> parameterMap, InputStream artifactStream, String mimeType) {
String key = FilterTypeEnum.AUTOLEVELSRGB.toString().toLowerCase();
if (!containsMyFilterParams(key, parameterMap)) {
return null;
}
Operation operation = new Operation();
operation.setName(key);
return operation;
}
/* (non-Javadoc)
* @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, java.awt.image.BufferedImage)
*/
public BufferedImage filter(BufferedImage src, BufferedImage dst) {
if (src == null) {
throw new NullPointerException("src image is null");
}
if (src == dst) {
throw new IllegalArgumentException("src image cannot be the same as the dst image");
}
boolean needToConvert = false;
ColorModel srcCM = src.getColorModel();
ColorModel dstCM;
BufferedImage origDst = dst;
if (srcCM instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel) srcCM;
src = icm.convertToIntDiscrete(src.getRaster(), false);
srcCM = src.getColorModel();
}
if (dst == null) {
dst = createCompatibleDestImage(src, null);
dstCM = srcCM;
origDst = dst;
}
else {
dstCM = dst.getColorModel();
if (srcCM.getColorSpace().getType() !=
dstCM.getColorSpace().getType())
{
needToConvert = true;
dst = createCompatibleDestImage(src, null);
dstCM = dst.getColorModel();
}
else if (dstCM instanceof IndexColorModel) {
dst = createCompatibleDestImage(src, null);
dstCM = dst.getColorModel();
}
}
int[] originalPixels = ImageConverter.getPixels(src);
int imageWidth = dst.getWidth();
int imageHeight = dst.getHeight();
/*
* Sort all the red pixels from low to high and establish
* the clipping regions. We also note the delta from the
* lowest and highest leftover pixels to black and white,
* respectively.
*/
int[] redPixels = new int[originalPixels.length];
for (int j=0;j<originalPixels.length;j++){
redPixels[j] = (originalPixels[j] >> 16) & 0xff;
}
Arrays.sort(redPixels);
int redStart = redPixels[(int) (redPixels.length * BOTTOMCLIP )];
int redEnd = redPixels[redPixels.length-(int) (redPixels.length * TOPCLIP)];
int redEndDelta = 255 - redEnd;
int redStartDelta = redStart;
int[] greenPixels = new int[originalPixels.length];
for (int j=0;j<originalPixels.length;j++){
greenPixels[j] = (originalPixels[j] >> 8) & 0xff;
}
Arrays.sort(greenPixels);
int greenStart = greenPixels[(int) (greenPixels.length * BOTTOMCLIP )];
int greenEnd = greenPixels[greenPixels.length-(int) (greenPixels.length * TOPCLIP)];
int greenEndDelta = 255 - greenEnd;
int greenStartDelta = greenStart;
int[] bluePixels = new int[originalPixels.length];
for (int j=0;j<originalPixels.length;j++){
bluePixels[j] = (originalPixels[j] >> 0) & 0xff;
}
Arrays.sort(bluePixels);
int blueStart = bluePixels[(int) (bluePixels.length * BOTTOMCLIP )];
int blueEnd = bluePixels[bluePixels.length-(int) (bluePixels.length * TOPCLIP)];
int blueEndDelta = 255 - blueEnd;
int blueStartDelta = blueStart;
int r=0;
int g=0;
int b=0;
int index=0;
for (int y=0;y<imageHeight;y++){
for (int x=0;x<imageWidth;x++){
r = (originalPixels[index] >> 16) & 0xff;
g = (originalPixels[index] >> 8) & 0xff;
b = (originalPixels[index] >> 0) & 0xff;
if (r > redStart && r < redEnd) {
if (redEndDelta > 0) {
if (redEnd - r == 0) {
r = 255;
} else {
/*
* If there was a white shift, distribute all the pixels proportionally up
*/
r = redEnd + redEndDelta - (((redEnd - r) * (redEnd + redEndDelta)) / redEnd);
}
}
if (redStartDelta > 0) {
if (r - redStartDelta == 0) {
r = 0;
} else {
/*
* If there was a black shift, distribute all the pixels proportionally down
*/
r = redEnd - (((redEnd -(redStart - redStartDelta)) * (redEnd - r))/(redEnd - redStart));
}
}
} else if (r <= redStart) {
r = 0;
} else {
r = 255;
}
if (g > greenStart && g < greenEnd) {
if (greenEndDelta > 0){
if (greenEnd - g == 0) {
g = 255;
} else {
g = greenEnd + greenEndDelta - (((greenEnd - g) * (greenEnd + greenEndDelta)) / greenEnd);
}
}
if (greenStartDelta > 0){
if (g - greenStartDelta == 0) {
g = 0;
} else {
g = greenEnd - (((greenEnd -(greenStart - greenStartDelta)) * (greenEnd - g))/(greenEnd - greenStart));
}
}
} else if (g <= greenStart) {
g = 0;
} else {
g = 255;
}
if (b > blueStart && b < blueEnd) {
if (blueEndDelta > 0){
if (blueEnd - b == 0) {
b = 255;
} else {
b = blueEnd + blueEndDelta - (((blueEnd - b) * (blueEnd + blueEndDelta)) / blueEnd);
}
}
if (blueStartDelta > 0){
if (b - blueStartDelta == 0) {
b = 0;
} else {
b = blueEnd - (((blueEnd -(blueStart - blueStartDelta)) * (blueEnd - b))/(blueEnd - blueStart));
}
}
} else if (b <= blueStart) {
b = 0;
} else {
b = 255;
}
// fix overflows
if (r > 255) r = 255;
if (r < 0) r = 0;
if (g > 255) g = 255;
if (g < 0) g = 0;
if (b > 255) b = 255;
if (b < 0) b = 0;
originalPixels[index] = (originalPixels[index] & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
index++;
}
}
dst = ImageConverter.getImage(originalPixels, imageWidth, imageHeight);
if (needToConvert) {
ColorConvertOp ccop = new ColorConvertOp(hints);
ccop.filter(dst, origDst);
}
else if (origDst != dst) {
java.awt.Graphics2D g2 = origDst.createGraphics();
try {
g2.drawImage(dst, 0, 0, null);
} finally {
g2.dispose();
}
}
return origDst;
}
}