All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twelvemonkeys.image.AreaAverageOp Maven / Gradle / Ivy

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.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 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/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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy