Latest Tweets

 

Java image processing code

This code is now available from my code repository

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;
        }
    }

}