com.oneis.utils.ImageColouring.java Source code

Java tutorial

Introduction

Here is the source code for com.oneis.utils.ImageColouring.java

Source

/* Haplo Platform                                     http://haplo.org
 * (c) ONEIS Ltd 2006 - 2015                    http://www.oneis.co.uk
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.         */

package com.oneis.utils;

import java.io.File;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;

import java.awt.*;
import java.awt.image.*;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.IIOImage;
import javax.imageio.ImageWriteParam;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;

import org.apache.commons.io.FileUtils;

/**
 * Recolour images for user configurable colours in the application UI.
 */
public class ImageColouring {
    static public final int MAX_COLOURS = 3;

    static public final int METHOD_MAX = 0;
    static public final int METHOD_AVG = 1;
    static public final int METHOD_BLEND = 2;

    /**
     * Transform the colours in an image.
     *
     * @param Filename Full pathname to the source file
     * @param Method Which colouring method to use, use METHOD_* constants
     * @param Colours Array of 0xRRGGBB colours for recolouring
     * @param JPEGQuality If the image is a JPEG, the output quality for
     * reencoding
     * @param Output An OutputStream to write the file. Will be closed.
     */
    static public void colourImage(String Filename, int Method, int[] Colours, int JPEGQuality, OutputStream Output)
            throws IOException {
        ColouringInfo colouringInfo = prepareForColouring(Method, Colours);

        String extension = Filename.substring(Filename.lastIndexOf('.') + 1, Filename.length());
        String outputFormat = extension;

        if (outputFormat.equals("gif")) {
            if (!colourImageGIF(Filename, colouringInfo, Output)) {
                throw new RuntimeException("Failed to directly colour GIF file");
            }
            return;
        }

        BufferedImage image = ImageIO.read(new File(Filename));
        int width = image.getWidth();
        int height = image.getHeight();

        // Rewrite the pixels
        int[] pixelBuffer = new int[width];
        for (int y = 0; y < height; ++y) {
            image.getRGB(0, y, width, 1, pixelBuffer, 0, width);
            doColouring(pixelBuffer, colouringInfo);
            image.setRGB(0, y, width, 1, pixelBuffer, 0, width);
        }

        // Writing is done the hard way to enable the JPEG quality to be set.
        // Get a writer
        Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(outputFormat);
        if (!iter.hasNext()) {
            throw new RuntimeException("Couldn't write image of type " + outputFormat);
        }

        ImageWriter writer = iter.next();

        ImageWriteParam iwparam = null;

        // Is it JPEG? If so, might want to set the quality
        if (outputFormat.equals("jpeg") || outputFormat.equals("jpg")) {
            // Clamp value
            int q = JPEGQuality;
            if (q < 10) {
                q = 10;
            }
            if (q > 100) {
                q = 100;
            }

            JPEGImageWriteParam jparam = new JPEGImageWriteParam(null);
            jparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            jparam.setCompressionQuality((float) (((float) q) / 100.0));
            iwparam = jparam;
        }

        ImageOutputStream output = ImageIO.createImageOutputStream(Output);
        writer.setOutput(output);
        writer.write(null, new IIOImage(image, null, null), iwparam);

        output.close();
    }

    /**
     * Transform the colours in an image.
     *
     * @param Filename Full pathname to the source file
     * @param Method Which colouring method to use, use METHOD_* constants
     * @param Colours Array of 0xRRGGBB colours for recolouring
     * @param JPEGQuality If the image is a JPEG, the output quality for
     * reencoding
     *
     * @return Recoloured image as a byte array
     */
    static public byte[] colourImage(String Filename, int Method, int[] Colours, int JPEGQuality)
            throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        colourImage(Filename, Method, Colours, JPEGQuality, output);
        return output.toByteArray();
    }

    static private final int GIF_FLAGS_OFFSET = 10;
    static private final int GIF_FLAGS_HAS_GLOBAL_COLOUR_TABLE = (1 << 7);
    static private final int GIF_FLAGS_GLOBAL_COLOUR_TABLE_SIZE_MASK = 7;
    static private final int GIF_GLOBAL_COLOUR_TABLE_OFFSET = 13;

    /**
     * Rewrite colours in GIF colour table without decoding and recoding the
     * image.
     */
    static private boolean colourImageGIF(String Filename, ColouringInfo colouringInfo, OutputStream Output)
            throws IOException {
        byte[] gif = FileUtils.readFileToByteArray(new File(Filename));

        if (gif.length < GIF_GLOBAL_COLOUR_TABLE_OFFSET) {
            return false;
        }
        if (gif[0] != (byte) 'G' || gif[1] != (byte) 'I' || gif[2] != (byte) 'F') {
            // Bad header
            return false;
        }
        if ((((int) gif[GIF_FLAGS_OFFSET]) & GIF_FLAGS_HAS_GLOBAL_COLOUR_TABLE) == 0) {
            // No global colour table to change
            return false;
        }
        int SizeOfGlobalColourTable = (((int) gif[GIF_FLAGS_OFFSET]) & GIF_FLAGS_GLOBAL_COLOUR_TABLE_SIZE_MASK);
        int entriesInColourTable = 1 << (SizeOfGlobalColourTable + 1);
        if (gif.length < (GIF_GLOBAL_COLOUR_TABLE_OFFSET + (entriesInColourTable * 3))) {
            // Not enough space for the advertised colour table
            return false;
        }
        if (entriesInColourTable > 1000) {
            // safety
            return false;
        }

        // Turn the entries into RGB ints
        int[] colourTable = new int[entriesInColourTable];
        for (int e = 0; e < entriesInColourTable; ++e) {
            // Where does the entry start?
            int s = GIF_GLOBAL_COLOUR_TABLE_OFFSET + (e * 3);
            colourTable[e] = ((((int) gif[s + RED]) << 16) & 0xff0000) | ((((int) gif[s + GREEN]) << 8) & 0xff00)
                    | (((int) gif[s + BLUE]) & 0xff);
        }

        doColouring(colourTable, colouringInfo);

        // Write the entiries back
        for (int e = 0; e < entriesInColourTable; ++e) {
            // Where does the entry start?
            int s = GIF_GLOBAL_COLOUR_TABLE_OFFSET + (e * 3);
            int r = (colourTable[e] >> 16) & 0xff;
            int g = (colourTable[e] >> 8) & 0xff;
            int b = colourTable[e] & 0xff;
            gif[s + RED] = (byte) r;
            gif[s + GREEN] = (byte) g;
            gif[s + BLUE] = (byte) b;
        }

        Output.write(gif);

        return true;
    }

    static private final int RED = 0;
    static private final int GREEN = 1;
    static private final int BLUE = 2;
    static private final int COLLEN = 4; // length of a colour in Replacements

    /**
     * Common preparation for recolouring
     */
    static private ColouringInfo prepareForColouring(int Method, int[] Colours) {
        if (Method < METHOD_MAX || Method > METHOD_BLEND || Colours.length <= 0 || Colours.length > MAX_COLOURS) {
            throw new RuntimeException("Bad arguments");
        }

        ColouringInfo info = new ColouringInfo();
        info.Method = Method;
        info.NumColours = Colours.length;
        info.Replacements = new int[16];

        for (int x = 0; x < Colours.length; ++x) {
            int i = x * COLLEN;
            info.Replacements[i + RED] = (Colours[x] >> 16) & 0xff;
            info.Replacements[i + GREEN] = (Colours[x] >> 8) & 0xff;
            info.Replacements[i + BLUE] = (Colours[x]) & 0xff;
        }

        return info;
    }

    /**
     * Recolour an array of pixel RRGGBB values.
     */
    static private void doColouring(int[] PixelBuffer, ColouringInfo info) {
        int[] replacements = info.Replacements;

        if (info.Method == METHOD_AVG) {
            for (int c = 0; c < PixelBuffer.length; c++) {
                // Modify this colour
                int r = 0, g = 0, b = 0;
                int t = 0;
                int pixel = PixelBuffer[c];
                for (int x = 0; x < info.NumColours; x++) {
                    int i = x * COLLEN;
                    int value = (pixel >> ((2 - x) * 8)) & 0xff;
                    r += (replacements[i + RED] * value);
                    g += (replacements[i + GREEN] * value);
                    b += (replacements[i + BLUE] * value);
                    t += value;
                }

                if (t == 0) {
                    PixelBuffer[c] = 0;
                } else {
                    r /= t;
                    if (r < 0) {
                        r = 0;
                    }
                    if (r > 0xff) {
                        r = 0xff;
                    }
                    g /= t;
                    if (g < 0) {
                        g = 0;
                    }
                    if (g > 0xff) {
                        g = 0xff;
                    }
                    b /= t;
                    if (b < 0) {
                        b = 0;
                    }
                    if (b > 0xff) {
                        b = 0xff;
                    }

                    PixelBuffer[c] = (r << 16) | (g << 8) | b;
                }
            }
        } else if (info.Method == METHOD_BLEND) {
            if (info.NumColours != 2) {
                // Must use two colours
                for (int c = 0; c < PixelBuffer.length; c++) {
                    PixelBuffer[c] = 0;
                }
                return;
            }

            // Do blending
            for (int c = 0; c < PixelBuffer.length; c++) {
                // Modify this colour
                int value = (PixelBuffer[c] >> 16) & 0xff;
                int r = (replacements[RED] * value) + (replacements[COLLEN + RED] * (255 - value));
                int g = (replacements[GREEN] * value) + (replacements[COLLEN + GREEN] * (255 - value));
                int b = (replacements[BLUE] * value) + (replacements[COLLEN + BLUE] * (255 - value));

                r /= 256;
                if (r < 0) {
                    r = 0;
                }
                if (r > 0xff) {
                    r = 0xff;
                }
                g /= 256;
                if (g < 0) {
                    g = 0;
                }
                if (g > 0xff) {
                    g = 0xff;
                }
                b /= 256;
                if (b < 0) {
                    b = 0;
                }
                if (b > 0xff) {
                    b = 0xff;
                }

                PixelBuffer[c] = (r << 16) | (g << 8) | b;
            }
        } else // METHOD_MAX
        {
            for (int c = 0; c < PixelBuffer.length; c++) {
                // Modify this colour
                int r = 0, g = 0, b = 0;
                int pixel = PixelBuffer[c];
                for (int x = 0; x < info.NumColours; x++) {
                    // In arrays
                    int value = (pixel >> ((2 - x) * 8)) & 0xff;
                    int i = x * COLLEN;
                    int ra = (replacements[i + RED] * value);
                    int ga = (replacements[i + GREEN] * value);
                    int ba = (replacements[i + BLUE] * value);
                    if (ra > r) {
                        r = ra;
                    }
                    if (ga > g) {
                        g = ga;
                    }
                    if (ba > b) {
                        b = ba;
                    }
                }

                r /= 256;
                if (r < 0) {
                    r = 0;
                }
                if (r > 0xff) {
                    r = 0xff;
                }
                g /= 256;
                if (g < 0) {
                    g = 0;
                }
                if (g > 0xff) {
                    g = 0xff;
                }
                b /= 256;
                if (b < 0) {
                    b = 0;
                }
                if (b > 0xff) {
                    b = 0xff;
                }

                PixelBuffer[c] = (r << 16) | (g << 8) | b;
            }
        }
    }

    /**
     * Prepared data for colouring.
     */
    static private class ColouringInfo {
        public int Method;
        public int NumColours;
        public int[] Replacements;
    }
}