com.twelvemonkeys.image.PixelizeOp Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of common-image Show documentation
Show all versions of common-image Show documentation
TwelveMonkeys Common image support classes.
The newest version!
/*
* 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.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
/**
* PixelizeOp
*
* @author Harald Kuhr
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/PixelizeOp.java#2 $
*/
public class PixelizeOp implements BufferedImageOp, RasterOp {
// TODO: support more raster types/color models
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
final private int pixelSizeX;
final private int pixelSizeY;
private Rectangle sourceRegion;
public PixelizeOp(final int pPixelSize) {
this(pPixelSize, pPixelSize);
}
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
pixelSizeX = pPixelSizeX;
pixelSizeY = pPixelSizeY;
}
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: 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!?
filterImpl(src.getRaster(), result.getRaster());
return result;
}
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();
int w = (width + pixelSizeX - 1) / pixelSizeX;
int h = (height + pixelSizeY - 1) / pixelSizeY;
final boolean oddX = width % w != 0;
final boolean oddY = height % h != 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 < h; y++) {
if (!oddY || y + 1 < h) {
scanH = pixelSizeY;
}
else {
scanH = height - (y * pixelSizeY);
}
for (int x = 0; x < w; x++) {
if (!oddX || x + 1 < w) {
scanW = pixelSizeX;
}
else {
scanW = width - (x * pixelSizeX);
}
final int pixelCount = scanW * scanH;
final int pixelLength = pixelCount * dataElements;
data = src.getDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
// 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...
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[i] = (byte) clamp((int) valueA);
if (bands > 1) {
bytePixels[i + 1] = (byte) clamp((int) valueR);
bytePixels[i + 2] = (byte) clamp((int) valueG);
if (bands > 3) {
bytePixels[i + 3] = (byte) clamp((int) valueB);
}
}
}
break;
case DataBuffer.TYPE_INT:
int[] intPixels = (int[]) data;
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[i] = clamp((int) valueA) << 24;
intPixels[i] |= clamp((int) valueR) << 16;
intPixels[i] |= clamp((int) valueG) << 8;
intPixels[i] |= 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[i] = (short) (((int) valueA << bitOffsets[0]) & bitMasks[0]);
if (bitMasks.length > 1) {
shortPixels[i] |= (short) (((int) valueR << bitOffsets[1]) & bitMasks[1]);
shortPixels[i] |= (short) (((int) valueG << bitOffsets[2]) & bitMasks[2]);
if (bitMasks.length > 3) {
shortPixels[i] |= (short) (((int) valueB << bitOffsets[3]) & bitMasks[3]);
}
}
}
break;
}
default:
throw new IllegalArgumentException("TransferType not supported: " + dataType);
}
dest.setDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
}
}
/*/
// This is a very naive way of pixelizing (but it works)...
// Thanks to the awsome speed of AffineTransformOp, it's also fast
double sx = w / (double) src.getWidth();
double sy = h / (double) src.getHeight();
WritableRaster temp = src.createCompatibleWritableRaster(w, h);
new AffineTransformOp(AffineTransform.getScaleInstance(sx, sy), 3)
.filter(src, temp);
new AffineTransformOp(AffineTransform.getScaleInstance(1 / sx, 1 / sy),
AffineTransformOp.TYPE_NEAREST_NEIGHBOR)
.filter(temp, dest);
//*/
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, src.getWidth(), src.getHeight()),
cm.isAlphaPremultiplied(), null);
}
public WritableRaster createCompatibleDestRaster(Raster src) {
return src.createCompatibleWritableRaster();
}
public Rectangle2D getBounds2D(Raster src) {
return new Rectangle(src.getWidth(), src.getHeight());
}
public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(src.getWidth(), src.getHeight());
}
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
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 < 10; 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);
}
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);
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);
}
}