mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-23 00:00:05 -04:00
Core now moved to common.
This commit is contained in:
+453
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" 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 OWNER 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 mWidth;
|
||||
final private int mHeight;
|
||||
|
||||
private Rectangle mSourceRegion;
|
||||
|
||||
public AreaAverageOp(final int pWidth, final int pHeight) {
|
||||
mWidth = pWidth;
|
||||
mHeight = pHeight;
|
||||
}
|
||||
|
||||
public Rectangle getSourceRegion() {
|
||||
if (mSourceRegion == null) {
|
||||
return null;
|
||||
}
|
||||
return new Rectangle(mSourceRegion);
|
||||
}
|
||||
|
||||
public void setSourceRegion(final Rectangle pSourceRegion) {
|
||||
if (pSourceRegion == null) {
|
||||
mSourceRegion = null;
|
||||
}
|
||||
else {
|
||||
if (mSourceRegion == null) {
|
||||
mSourceRegion = new Rectangle(pSourceRegion);
|
||||
}
|
||||
else {
|
||||
mSourceRegion.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(mWidth, mHeight, 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 > mWidth && h / 2 > mHeight) {
|
||||
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(mWidth / (double) w, mHeight / (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 (mSourceRegion != null) {
|
||||
int cx = mSourceRegion.x;
|
||||
int cy = mSourceRegion.y;
|
||||
int cw = mSourceRegion.width;
|
||||
int ch = mSourceRegion.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 + mWidth - 1) / mWidth;
|
||||
int heightSteps = (height + mHeight - 1) / mHeight;
|
||||
|
||||
final boolean oddX = width % mWidth != 0;
|
||||
final boolean oddY = height % mHeight != 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 < mHeight; y++) {
|
||||
if (!oddY || y < mHeight) {
|
||||
scanH = heightSteps;
|
||||
}
|
||||
else {
|
||||
scanH = height - (y * heightSteps);
|
||||
}
|
||||
|
||||
for (int x = 0; x < mWidth; x++) {
|
||||
if (!oddX || x < mWidth) {
|
||||
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("mWidth: " + mWidth);
|
||||
//System.err.println("mHeight: " + mHeight);
|
||||
//
|
||||
//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, mWidth, mHeight),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
public WritableRaster createCompatibleDestRaster(Raster src) {
|
||||
return src.createCompatibleWritableRaster(mWidth, mHeight);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(Raster src) {
|
||||
return new Rectangle(mWidth, mHeight);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return new Rectangle(mWidth, mHeight);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user