Latest Tweets

 

Java image processing code

Okay, this has to be the ugliest class in my private sprawling Java computer vision library (and that really is an achievement). Everything I want to forget about Java image processing is in this class. One day I might come back here and document it.

In practice, it’s incredibly useful and will repay any kindness you show it; it’s just very raw.

package com.tomgibara.vision.image;

import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;

public class ImageUtil {

    public enum IntensityModel {
        AVERAGE, RED, GREEN, BLUE;

        byte combine(int r, int g, int b) {
            switch (this) {
            case AVERAGE: return (byte) ((r + b + g) / 3);
            case RED: return (byte) r;
            case GREEN: return (byte) g;
            case BLUE: return (byte) b;
            default: throw new IllegalStateException();
            }
        }

        byte combine(byte r, byte g, byte b) {
            return combine( r & 0xff, g & 0xff, b & 0xff);
        }
    }

    public static byte[] toByteIntensity(BufferedImage image, IntensityModel model, byte[] bytes) {
        if (model == null) model = IntensityModel.AVERAGE;
        int width = image.getWidth();
        int height = image.getHeight();
        switch(image.getType()) {
        case BufferedImage.TYPE_BYTE_GRAY :
        {
            image.getData().getDataElements(0, 0, width, height, bytes);
            return bytes;
        }

        case BufferedImage.TYPE_3BYTE_BGR :
        {
            byte[] data = (byte[]) image.getData().getDataElements(0, 0, width, height, null);
            int i = 0;
            for (int j = 0; j < data.length; j += 3) {
                //TODO WTF!
                //work around for bug 4886732
                bytes[i++] = model.combine(data[j], data[j+1], data[j+2]);
                //bytes[i++] = model.combine(data[j+2], data[j+1], data[j]);
            }
            return bytes;
        }

        case BufferedImage.TYPE_4BYTE_ABGR:
        case BufferedImage.TYPE_4BYTE_ABGR_PRE:
        {
            byte[] data = (byte[]) image.getData().getDataElements(0, 0, width, height, null);
            int i = 0;
            for (int j = 0; j < data.length; j += 4) {
                bytes[i++] = model.combine(data[j+3], data[j+2], data[j+1]);
            }
            return bytes;
        }

        case BufferedImage.TYPE_INT_ARGB:
        case BufferedImage.TYPE_INT_ARGB_PRE:
        case BufferedImage.TYPE_INT_RGB:
        {
            int[] data = (int[]) image.getData().getDataElements(0, 0, width, height, null);
            for (int i = 0; i < data.length; i++) {
                int d = data[i];
                bytes[i] = model.combine((d >> 16) & 0xff, (d >> 8) & 0xff, d & 0xff);
            }
            return bytes;
        }

        case BufferedImage.TYPE_USHORT_GRAY:
        {
            short[] data = (short[]) image.getData().getDataElements(0, 0, width, height, null);
            for (int i = 0; i < data.length; i++) {
                bytes[i] = (byte) (data[i] >> 8);
            }
            return bytes;
        }

        default: throw new IllegalArgumentException("Unsupported image type: " + image.getType());
        }
    }

    public static byte[] toByteIntensity(BufferedImage image, IntensityModel model) {
        return toByteIntensity(image, model, new byte[image.getWidth() * image.getHeight()]);
    }

    public static BufferedImage fromByteIntensity(BufferedImage image, byte[] bytes) {
        int width = image.getWidth();
        int height = image.getHeight();
        switch(image.getType()) {
        case BufferedImage.TYPE_BYTE_BINARY:
        case BufferedImage.TYPE_BYTE_GRAY :
        {
            image.getWritableTile(0,0).setDataElements(0, 0, width, height, bytes);
            return image;
        }
        case BufferedImage.TYPE_INT_ARGB:
        case BufferedImage.TYPE_INT_ARGB_PRE:
        case BufferedImage.TYPE_INT_RGB:
        {
            int[] data = new int[width * height];
            for (int i = 0; i < bytes.length; i++) {
                int b = bytes[i] & 0xff;
                data[i] = (((((0xff << 8) | b) << 8) | b) << 8) | b; 
            }
            image.getWritableTile(0,0).setDataElements(0, 0, width, height, data);
            return image;
        }
        case BufferedImage.TYPE_3BYTE_BGR:
        {
            byte[] data = new byte[width * height * 3];
            int offset = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i]; 
                data[offset++] = b;
                data[offset++] = b;
                data[offset++] = b;
            }
            image.getWritableTile(0,0).setDataElements(0, 0, width, height, data);
            return image;
        }
        default: throw new IllegalArgumentException("Unsupported image type: " + image.getType());

        }
    }

    public static BufferedImage fromByteIntensity(int width, int height, int imageType, byte[] bytes) {
        BufferedImage image = new BufferedImage(width, height, imageType);
        return fromByteIntensity(image, bytes);
    }

    public static BufferedImage fromByteRGB(BufferedImage image, byte[] reds, byte[] greens, byte[] blues) {
        int width = image.getWidth();
        int height = image.getHeight();
        int size = width * height;
        if (image.getType() != BufferedImage.TYPE_3BYTE_BGR) throw new IllegalArgumentException();
        byte[] combined = new byte[3 * size];
        int offset = 0;
        for (int i = 0; i < size; i++) {
            //Work around for bug 4886732
            combined[offset++] = reds[i];
            combined[offset++] = greens[i];
            combined[offset++] = blues[i];
        }
//        System.arraycopy(reds, 0, combined, 0, size);
//        System.arraycopy(greens, 0, combined, size, size);
//        System.arraycopy(blues, 0, combined, 2*size, size);
        image.getWritableTile(0,0).setDataElements(0,0,width,height,combined);
        return image;
    }

    public static BufferedImage fromByteRGB(int width, int height, byte[] reds, byte[] greens, byte[] blues) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
        return fromByteRGB(image, reds, greens, blues);
    }

    public static int[] intFromByteRGB(byte[] reds, byte[] greens, byte[] blues) {
        int[] pixels = new int[reds.length];
        for (int i = 0; i < pixels.length; i++) {
            pixels[i] = ((reds[i] & 0xff) << 16) | ((greens[i] & 0xff) << 8) | (blues[i] & 0xff);
        }
        return pixels;
    }

    public static int[] toIntRGB(BufferedImage image, int[] ints) {
        int width = image.getWidth();
        int height = image.getHeight();
        switch(image.getType()) {
        case BufferedImage.TYPE_BYTE_GRAY :
        {
            byte[] data = (byte[]) image.getData().getDataElements(0, 0, width, height, null);
            for (int i = 0; i < data.length; i++) {
                int v = data[i] & 0xff;
                ints[i] = (v << 16) | (v << 8) | v;

            }
            return ints;
        }

        case BufferedImage.TYPE_3BYTE_BGR :
        {
            byte[] data = (byte[]) image.getData().getDataElements(0, 0, width, height, null);
            int i = 0;
            for (int j = 0; j < data.length; j += 3) {
                //TODO WTF!
                //work around for bug 4886732
                ints[i++] = ((data[j] & 0xff) << 16) | ((data[j+1] & 0xff) << 8) | (data[j+2] & 0xff);
            }
            return ints;
        }

        case BufferedImage.TYPE_4BYTE_ABGR:
        case BufferedImage.TYPE_4BYTE_ABGR_PRE:
        {
            byte[] data = (byte[]) image.getData().getDataElements(0, 0, width, height, null);
            int i = 0;
            for (int j = 0; j < data.length; j += 3) {
                //TODO WTF!
                //work around for bug 4886732
                ints[i++] = ((data[j+1] & 0xff) << 16) | ((data[j+2] & 0xff) << 8) | (data[j+3] & 0xff);
            }
            return ints;
        }

        case BufferedImage.TYPE_INT_ARGB:
        case BufferedImage.TYPE_INT_ARGB_PRE:
        case BufferedImage.TYPE_INT_RGB:
        {
            image.getData().getDataElements(0, 0, width, height, ints);
            return ints;
        }

        case BufferedImage.TYPE_USHORT_GRAY:
        {
            short[] data = (short[]) image.getData().getDataElements(0, 0, width, height, null);
            for (int i = 0; i < data.length; i++) {
                int v = (data[i] & 0xffff) >> 8;
                ints[i] = (v << 16) | (v << 8) | v;

            }
            return ints;
        }

        default: throw new IllegalArgumentException("Unsupported image type: " + image.getType());
        }
    }

    public static int[] toIntRGB(BufferedImage image) {
        return toIntRGB(image, new int[image.getWidth() * image.getHeight()]);
    }

    public static BufferedImage fromIntRGB(BufferedImage image, int[] rgb) {
        //TODO must support more types
        if (image.getType() != BufferedImage.TYPE_INT_RGB) throw new IllegalArgumentException();
        image.getWritableTile(0, 0).setDataElements(0, 0, image.getWidth(), image.getHeight(), rgb);
        return image;
    }

    public static BufferedImage fromIntRGB(int width, int height, int[] rgb) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        return fromIntRGB(image, rgb);
    }

    public static byte[][] splitIntRGB(int[] rgb) {
        byte[] rs = new byte[ rgb.length ];
        byte[] gs = new byte[ rgb.length ];
        byte[] bs = new byte[ rgb.length ];

        for (int i = 0; i < rgb.length; i++) {
            int p = rgb[i];
            rs[i] = (byte) (p >> 16);
            gs[i] = (byte) (p >>  8);
            bs[i] = (byte) (p      );
        }

        return new byte[][] { rs, gs, bs };
    }

    public static BufferedImage convertImage(BufferedImage source, int type) {
        BufferedImage target = new BufferedImage(source.getWidth(), source.getHeight(), type);
        Graphics2D g = target.createGraphics();
        g.drawImage(source, null, null);
        g.dispose();
        return target;
    }

    public static JFrame showImage(String title, BufferedImage image) {
        final JFrame f = new JFrame(title);
        Container pane = f.getContentPane();
        JButton display = new JButton(new ImageIcon(image));
        display.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                f.dispose();
            }
        });
        display.setMargin(new Insets(50, 50, 50, 50));
        pane.add(display);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setVisible(true);
        return f;
    }

    public static BufferedImage duplicateImage(BufferedImage image) {
        BufferedImage dup = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
        image.copyData(dup.getWritableTile(0, 0));
        dup.releaseWritableTile(0, 0);
        return dup;
    }

    public static BufferedImage scaleImage(BufferedImage image, int width, int height) {
        return scaleImage(image, image.getType(), width, height);
    }

    public static BufferedImage scaleImage(BufferedImage image, int type, int width, int height) {
        BufferedImage scaled = new BufferedImage(width, height, type);
        Graphics2D g = scaled.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(image, 0, 0, width, height, null);
        g.dispose();
        return scaled;
    }

    public static BufferedImage fromIntIntensity(int width, int height, int[] data, int shift) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        byte[] bytes = new byte[data.length];
        for (int i = 0; i < bytes.length; i++) {
            int v = Math.abs(data[i]);
            v = v >> shift;
            if (v > 255) v = 255;
            bytes[i] = (byte) v;    
        }
        image.getWritableTile(0, 0).setDataElements(0, 0, width, height, bytes);
        image.releaseWritableTile(0, 0);
        return image;
    }

    public static BufferedImage fromIntIntensityScaled(int width, int height, int[] data) {
        int max = 0;
        for (int d : data)
            if (d > max) max = d;
            else if (-d > max) max = -d;

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        byte[] bytes = new byte[data.length];
        if (max > 0) {
            for (int i = 0; i < bytes.length; i++) {
                bytes[i] = (byte) (Math.abs(data[i]) * 255 / max);  
            }
        }
        image.getWritableTile(0, 0).setDataElements(0, 0, width, height, bytes);
        image.releaseWritableTile(0, 0);
        return image;
    }

    public static BufferedImage fromBooleans(int width, int height, boolean[] bools) {
        byte[] data = new byte[width * height];
        for (int i = 0; i < data.length; i++) {
            data[i] = bools[i] ? (byte)255 : 0;
        }
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        image.getWritableTile(0, 0).setDataElements(0, 0, width, height, data);
        image.releaseWritableTile(0, 0);
        return image;
    }

    public static int[][] bytesToInts(byte[][] bytes) {
        int[][] ints = new int[bytes.length][];
        for (int a = 0; a < bytes.length; a++) {
            byte[] bs = bytes[a];
            int[] is = new int[bs.length];
            ints[a] = is;
            for (int i = 0; i < bs.length; i++) {
                is[i] = bs[i] & 0xff;
            }
        }
        return ints;
    }

    public static byte[][] intsToBytes(int[][] ints) {
        byte[][] bytes = new byte[ints.length][];
        for (int a = 0; a < ints.length; a++) {
            int[] is = ints[a];
            byte[] bs = new byte[is.length];
            bytes[a] = bs;
            for (int i = 0; i < is.length; i++) {
                int v = is[i];
                if (v < 0) v = 0;
                else if (v > 255) v = 255;
                bs[i] = (byte) v;
            }
        }
        return bytes;
    }

    public static void unscaledYUVToRGB(int[][] yuv) {
        int length = yuv[0].length;
        final int[] ys = yuv[0];
        final int[] us = yuv[1];
        final int[] vs = yuv[2];

        for (int i = 0; i < length; i++) {
            int y = ys[i];
            int u = us[i];
            int v = vs[i];
            int g = (y-u-v) >> 2;
            us[i] = g;
            ys[i] = v + g;
            vs[i] = u + g;
        }
    }

    public static void rgbToUnscaledYUV(int[][] rgb) {
        int length = rgb[0].length;
        final int[] rs = rgb[0];
        final int[] gs = rgb[1];
        final int[] bs = rgb[2];

        for (int i = 0; i < length; i++) {
            int r = rs[i];
            int g = gs[i];
            int b = bs[i];
            rs[i] = r + 2*g + b;
            gs[i] = b-g;
            bs[i] = r-g;
        }
    }

    public static void rgbToScaledYUV(byte[][] rgb) {
        int length = rgb[0].length;
        final byte[] rs = rgb[0];
        final byte[] gs = rgb[1];
        final byte[] bs = rgb[2];

        for (int i = 0; i < length; i++) {
            int r = rs[i] & 0xff;
            int g = gs[i] & 0xff;
            int b = bs[i] & 0xff;
            rs[i] = (byte) ((r + (g << 1) + b) >> 2);
            gs[i] = (byte) ((b-g)/2);
            bs[i] = (byte) ((r-g)/2);
        }
    }

    public static void scaledYUVToRGB(byte[][] yuv) {
        int length = yuv[0].length;
        final byte[] ys = yuv[0];
        final byte[] us = yuv[1];
        final byte[] vs = yuv[2];

        for (int i = 0; i < length; i++) {
            int y = ys[i] & 0xff;
            int u = us[i];
            int v = vs[i];
            int g = y - ((u+v)/2);
            int r = g + (v*2);
            int b = g + (u*2);
            //TODO verify why these overflows occur
            if (b < 0) b = 0;
            if (r < 0) r = 0;
            us[i] = (byte) g;
            ys[i] = (byte) r;
            vs[i] = (byte) b;
        }
    }

}
blog comments powered by Disqus