/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.image;
import javax.imageio.
ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.
AffineTransform;
import java.awt.geom.
Point2D;
import java.awt.geom.
Rectangle2D;
import java.awt.image.*;
import java.io.
File;
import java.io.
IOException;
/**
* AreaAverageOp
*
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/AreaAverageOp.java#2 $
*/
public class
AreaAverageOp implements
BufferedImageOp,
RasterOp {
final private int
width;
final private int
height;
private
Rectangle sourceRegion;
public
AreaAverageOp(final int
pWidth, final int
pHeight) {
width =
pWidth;
height =
pHeight;
}
public
Rectangle getSourceRegion() {
if (
sourceRegion == null) {
return null;
}
return new
Rectangle(
sourceRegion);
}
public void
setSourceRegion(final
Rectangle pSourceRegion) {
if (
pSourceRegion == null) {
sourceRegion = null;
}
else {
if (
sourceRegion == null) {
sourceRegion = new
Rectangle(
pSourceRegion);
}
else {
sourceRegion.
setBounds(
pSourceRegion);
}
}
}
public
BufferedImage filter(
BufferedImage src,
BufferedImage dest) {
BufferedImage result =
dest != null ?
dest :
createCompatibleDestImage(
src, null);
// TODO: src and dest can't be the same
// TODO: Do some type checking here..
// Should work with
// * all BYTE types, unless sub-byte packed rasters/IndexColorModel
// * all INT types (even custom, as long as they use 8bit/componnet)
// * all USHORT types (even custom)
// TODO: Also check if the images are really compatible!?
long
start =
System.
currentTimeMillis();
// Straight-forward version
//Image scaled = src.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
//ImageUtil.drawOnto(result, scaled);
//result = new BufferedImageFactory(scaled).getBufferedImage();
/*
// Try: Use bilinear/bicubic and half the image down until it's less than
// twice as big, then use bicubic for the last step?
BufferedImage temp = null;
AffineTransform xform = null;
int w = src.getWidth();
int h = src.getHeight();
while (w / 2 > width && h / 2 > height) {
w /= 2;
h /= 2;
if (temp == null) {
xform = AffineTransform.getScaleInstance(.5, .5);
ColorModel cm = src.getColorModel();
temp = new BufferedImage(cm,
ImageUtil.createCompatibleWritableRaster(src, cm, w, h),
cm.isAlphaPremultiplied(), null);
resample(src, temp, xform);
}
else {
resample(temp, temp, xform);
}
System.out.println("w: " + w);
System.out.println("h: " + h);
}
if (temp != null) {
src = temp.getSubimage(0, 0, w, h);
}
resample(src, result, AffineTransform.getScaleInstance(width / (double) w, height / (double) h));
*/
// The real version
filterImpl(
src.
getRaster(),
result.
getRaster());
long
time =
System.
currentTimeMillis() -
start;
System.
out.
println("time: " +
time);
return
result;
}
private void
resample(final
BufferedImage pSrc, final
BufferedImage pDest, final
AffineTransform pXform) {
Graphics2D d =
pDest.
createGraphics();
d.
setRenderingHint(
RenderingHints.
KEY_INTERPOLATION,
RenderingHints.
VALUE_INTERPOLATION_BILINEAR);
try {
d.
drawImage(
pSrc,
pXform, null);
}
finally {
d.
dispose();
}
}
public
WritableRaster filter(
Raster src,
WritableRaster dest) {
WritableRaster result =
dest != null ?
dest :
createCompatibleDestRaster(
src);
return
filterImpl(
src,
result);
}
private
WritableRaster filterImpl(
Raster src,
WritableRaster dest) {
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
if (
sourceRegion != null) {
int
cx =
sourceRegion.
x;
int
cy =
sourceRegion.
y;
int
cw =
sourceRegion.
width;
int
ch =
sourceRegion.
height;
boolean
same =
src ==
dest;
dest =
dest.
createWritableChild(
cx,
cy,
cw,
ch, 0, 0, null);
src =
same ?
dest :
src.
createChild(
cx,
cy,
cw,
ch, 0, 0, null);
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
}
final int
width =
src.
getWidth();
final int
height =
src.
getHeight();
// TODO: This don't work too well..
// The thing is that the step length and the scan length will vary, for
// non-even (1/2, 1/4, 1/8 etc) resampling
int
widthSteps = (
width + this.
width - 1) / this.
width;
int
heightSteps = (
height + this.
height - 1) / this.
height;
final boolean
oddX =
width % this.
width != 0;
final boolean
oddY =
height % this.
height != 0;
final int
dataElements =
src.
getNumDataElements();
final int
bands =
src.
getNumBands();
final int
dataType =
src.
getTransferType();
Object data = null;
int
scanW;
int
scanH;
// TYPE_USHORT setup
int[]
bitMasks = null;
int[]
bitOffsets = null;
if (
src.
getTransferType() ==
DataBuffer.
TYPE_USHORT) {
if (
src.
getSampleModel() instanceof
SinglePixelPackedSampleModel) {
// DIRECT
SinglePixelPackedSampleModel sampleModel = (
SinglePixelPackedSampleModel)
src.
getSampleModel();
bitMasks =
sampleModel.
getBitMasks();
bitOffsets =
sampleModel.
getBitOffsets();
}
else {
// GRAY
bitMasks = new int[]{0xffff};
bitOffsets = new int[]{0};
}
}
for (int
y = 0;
y < this.
height;
y++) {
if (!
oddY ||
y < this.
height) {
scanH =
heightSteps;
}
else {
scanH =
height - (
y *
heightSteps);
}
for (int
x = 0;
x < this.
width;
x++) {
if (!
oddX ||
x < this.
width) {
scanW =
widthSteps;
}
else {
scanW =
width - (
x *
widthSteps);
}
final int
pixelCount =
scanW *
scanH;
final int
pixelLength =
pixelCount *
dataElements;
try {
data =
src.
getDataElements(
x *
widthSteps,
y *
heightSteps,
scanW,
scanH,
data);
}
catch (
IndexOutOfBoundsException e) {
// TODO: FixMe!
// The bug is in the steps...
//System.err.println("x: " + x);
//System.err.println("y: " + y);
//System.err.println("widthSteps: " + widthSteps);
//System.err.println("heightSteps: " + heightSteps);
//System.err.println("scanW: " + scanW);
//System.err.println("scanH: " + scanH);
//
//System.err.println("width: " + width);
//System.err.println("height: " + height);
//System.err.println("width: " + width);
//System.err.println("height: " + height);
//
//e.printStackTrace();
continue;
}
// TODO: Might need more channels... Use an array?
// NOTE: These are not neccessarily ARGB..
double
valueA = 0.0;
double
valueR = 0.0;
double
valueG = 0.0;
double
valueB = 0.0;
switch (
dataType) {
case
DataBuffer.
TYPE_BYTE:
// TODO: Doesn't hold for index color models...
// For index color, the best bet is probably convert to
// true color, then convert back to the same index color
// model
byte[]
bytePixels = (byte[])
data;
for (int
i = 0;
i <
pixelLength;
i +=
dataElements) {
valueA +=
bytePixels[
i] & 0xff;
if (
bands > 1) {
valueR +=
bytePixels[
i + 1] & 0xff;
valueG +=
bytePixels[
i + 2] & 0xff;
if (
bands > 3) {
valueB +=
bytePixels[
i + 3] & 0xff;
}
}
}
// Average
valueA /=
pixelCount;
if (
bands > 1) {
valueR /=
pixelCount;
valueG /=
pixelCount;
if (
bands > 3) {
valueB /=
pixelCount;
}
}
//for (int i = 0; i < pixelLength; i += dataElements) {
bytePixels[0] = (byte)
clamp((int)
valueA);
if (
bands > 1) {
bytePixels[1] = (byte)
clamp((int)
valueR);
bytePixels[2] = (byte)
clamp((int)
valueG);
if (
bands > 3) {
bytePixels[3] = (byte)
clamp((int)
valueB);
}
}
//}
break;
case
DataBuffer.
TYPE_INT:
int[]
intPixels = (int[])
data;
// TODO: Rewrite to use bit offsets and masks from
// color model (see TYPE_USHORT) in case of a non-
// 888 or 8888 colormodel?
for (int
i = 0;
i <
pixelLength;
i +=
dataElements) {
valueA += (
intPixels[
i] & 0xff000000) >> 24;
valueR += (
intPixels[
i] & 0xff0000) >> 16;
valueG += (
intPixels[
i] & 0xff00) >> 8;
valueB += (
intPixels[
i] & 0xff);
}
// Average
valueA /=
pixelCount;
valueR /=
pixelCount;
valueG /=
pixelCount;
valueB /=
pixelCount;
//for (int i = 0; i < pixelLength; i += dataElements) {
intPixels[0] =
clamp((int)
valueA) << 24;
intPixels[0] |=
clamp((int)
valueR) << 16;
intPixels[0] |=
clamp((int)
valueG) << 8;
intPixels[0] |=
clamp((int)
valueB);
//}
break;
case
DataBuffer.
TYPE_USHORT:
if (
bitMasks != null) {
short[]
shortPixels = (short[])
data;
for (int
i = 0;
i <
pixelLength;
i +=
dataElements)
{
valueA += (
shortPixels[
i] &
bitMasks[0]) >>
bitOffsets[0];
if (
bitMasks.length > 1) {
valueR += (
shortPixels[
i] &
bitMasks[1]) >>
bitOffsets[1];
valueG += (
shortPixels[
i] &
bitMasks[2]) >>
bitOffsets[2];
if (
bitMasks.length > 3) {
valueB += (
shortPixels[
i] &
bitMasks[3]) >>
bitOffsets[3];
}
}
}
// Average
valueA /=
pixelCount;
valueR /=
pixelCount;
valueG /=
pixelCount;
valueB /=
pixelCount;
//for (int i = 0; i < pixelLength; i += dataElements) {
shortPixels[0] = (short) (((int)
valueA <<
bitOffsets[0]) &
bitMasks[0]);
if (
bitMasks.length > 1) {
shortPixels[0] |= (short) (((int)
valueR <<
bitOffsets[1]) &
bitMasks[1]);
shortPixels[0] |= (short) (((int)
valueG <<
bitOffsets[2]) &
bitMasks[2]);
if (
bitMasks.length > 3) {
shortPixels[0] |= (short) (((int)
valueB <<
bitOffsets[3]) &
bitMasks[3]);
}
}
//}
break;
}
default:
throw new
IllegalArgumentException("TransferType not supported: " +
dataType);
}
dest.
setDataElements(
x,
y, 1, 1,
data);
}
}
return
dest;
}
private static int
clamp(final int
pValue) {
return
pValue > 255 ? 255 :
pValue;
}
public
RenderingHints getRenderingHints() {
return null;
}
// TODO: Refactor boilerplate to AbstractBufferedImageOp or use a delegate?
// Delegate is maybe better as we won't always implement both BIOp and RasterOP
// (but are there ever any time we want to implemnet RasterOp and not BIOp?)
public
BufferedImage createCompatibleDestImage(
BufferedImage src,
ColorModel destCM) {
ColorModel cm =
destCM != null ?
destCM :
src.
getColorModel();
return new
BufferedImage(
cm,
ImageUtil.
createCompatibleWritableRaster(
src,
cm,
width,
height),
cm.
isAlphaPremultiplied(), null);
}
public
WritableRaster createCompatibleDestRaster(
Raster src) {
return
src.
createCompatibleWritableRaster(
width,
height);
}
public
Rectangle2D getBounds2D(
Raster src) {
return new
Rectangle(
width,
height);
}
public
Rectangle2D getBounds2D(
BufferedImage src) {
return new
Rectangle(
width,
height);
}
public
Point2D getPoint2D(
Point2D srcPt,
Point2D dstPt) {
// TODO: This is wrong!
if (
dstPt == null) {
if (
srcPt instanceof
Point2D.
Double) {
dstPt = new
Point2D.
Double();
}
else {
dstPt = new
Point2D.
Float();
}
}
dstPt.
setLocation(
srcPt);
return
dstPt;
}
public static void
main(
String[]
pArgs) throws
IOException {
BufferedImage image =
ImageIO.
read(new
File("2006-Lamborghini-Gallardo-Spyder-Y-T-1600x1200.png"));
//BufferedImage image = ImageIO.read(new File("focus-rs.jpg"));
//BufferedImage image = ImageIO.read(new File("blauesglas_16_bitmask444.bmp"));
//image = ImageUtil.toBuffered(image, BufferedImage.TYPE_USHORT_GRAY);
for (int
i = 0;
i < 100;
i++) {
//new PixelizeOp(10).filter(image, null);
//new AffineTransformOp(AffineTransform.getScaleInstance(.1, .1), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
//ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_BOX).filter(image, null);
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_QUADRATIC).filter(image, null);
//new AreaAverageOp(image.getWidth() / 10, image.getHeight() / 10).filter(image, null);
}
long
start =
System.
currentTimeMillis();
//PixelizeOp pixelizer = new PixelizeOp(image.getWidth() / 10, 1);
//pixelizer.setSourceRegion(new Rectangle(0, 2 * image.getHeight() / 3, image.getWidth(), image.getHeight() / 4));
//PixelizeOp pixelizer = new PixelizeOp(4);
//image = pixelizer.filter(image, image); // Filter in place, that's cool
//image = new AffineTransformOp(AffineTransform.getScaleInstance(.25, .25), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
//image = ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_BOX).filter(image, null);
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_QUADRATIC).filter(image, null);
//image = new AreaAverageOp(image.getWidth() / 7, image.getHeight() / 4).filter(image, null);
image = new
AreaAverageOp(500, 600).
filter(
image, null);
//image = new ResampleOp(500, 600, ResampleOp.FILTER_BOX).filter(image, null);
long
time =
System.
currentTimeMillis() -
start;
System.
out.
println("time: " +
time + " ms");
JFrame frame = new
JFrame("Test");
frame.
setDefaultCloseOperation(
JFrame.
EXIT_ON_CLOSE);
frame.
setContentPane(new
JScrollPane(new
JLabel(new
BufferedImageIcon(
image))));
frame.
pack();
frame.
setVisible(true);
}
}