Package net.cakenet.jsaton.model

Source Code of net.cakenet.jsaton.model.SimpleImage

package net.cakenet.jsaton.model;

import net.cakenet.jsaton.util.CompressionUtil;
import net.cakenet.jsaton.util.DigestUtil;
import net.cakenet.jsaton.util.InvalidImageDataException;

import javax.xml.bind.DatatypeConverter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.lang.ref.WeakReference;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;

public class SimpleImage  {
    private static final Map<String, WeakReference<SimpleImage>> decoded = new HashMap<>();

    public final int width, height;
    public final int[] pixels;

    public SimpleImage(int[] pixels, int width, int height) {
        this.pixels = pixels;
        this.width = width;
        this.height = height;
        for(int i = 0; i < pixels.length; i++)
            pixels[i] &= 0xffffff; // Remove alpha...
    }

    // Helpful script functions...
    public Point findColor(int col) {
        for (int i = 0; i < pixels.length; i++) {
            if (pixels[i] == col) {
                int y = i / width;
                int x = i % width;
                return new Point(x, y);
            }
        }
        return null;
    }

    public boolean imageAt(SimpleImage image, int x, int y) {
        if(x > width - image.width || y > height - image.height)
            return false;
        final int[] needle = image.pixels;
        int off = y * width + x; // Offset into our pixel array to start at
        int srchOff = 0; // Offset into needle pixel array to start at...
        for(int y1 = 0; y1 < image.height; y1++) {
            for(int x1 = 0; x1 < image.width; x1++) {
                if(pixels[off + x1] != needle[srchOff + x1])
                    return false;
            }
            srchOff += image.width;
            off += width; // Todo: inline this (do the addition during the inner loop and add less here...)
        }
        return true;
    }

    public Point findImage(SimpleImage image) {
        final int endX = width - image.width;
        final int endY = height - image.height;
        for(int x = 0; x < endX; x++) {
            for(int y = 0; y < endY; y++) {
                if(imageAt(image, x, y))
                    return new Point(x, y);
            }
        }
        return null;
    }

    public Point findImage(String encodedImage) {
        try {
            SimpleImage needle = decode(encodedImage);
            return findImage(needle);
        } catch (InvalidImageDataException e) {
            e.printStackTrace();
        }
        return null;
    }

    // Todo: should we abstract this out and create an ArrayView class?
    // would drastically reduce array creation and copying during runtime... (but would add overhead in access...)
    public SimpleImage region(int x, int y, int width, int height) {
        int[] dest = new int[width * height];
        int srcOff = y * this.width + x;
        int destOff = 0;
        for(int i = y; i < height; i++) {
            System.arraycopy(pixels, srcOff, dest, destOff, width);
            srcOff += this.width;
            destOff += width;
        }
        return new SimpleImage(dest, width, height);
    }

    // Todo: more codecs (simba, the new SCAR format...)
    public byte[] toByteArray() {
        int pl = width * height;
        byte[] data = new byte[pl * 3];
        int off = 0;
        for (int p : pixels) {
            data[off++] = (byte) ((p >> 16) & 0xff);
            data[off++] = (byte) ((p >> 8) & 0xff);
            data[off++] = (byte) (p & 0xff);
        }
        return data;
    }

    public String encode() {
        byte[] raw = toByteArray();
        byte[] complete = new byte[raw.length + 4]; // 2 bytes per dimension
        complete[0] = (byte) ((width >> 8) & 0xff);
        complete[1] = (byte) (width & 0xff);
        complete[2] = (byte) ((height >> 8) & 0xff);
        complete[3] = (byte) (height & 0xff);
        System.arraycopy(raw, 0, complete, 4, raw.length);
        return "g" + DatatypeConverter.printBase64Binary(CompressionUtil.deflate(complete, true));
    }

    public String encodeScar() {
        return "c" + DatatypeConverter.printBase64Binary(CompressionUtil.deflate(toByteArray(), false));
    }

    public static SimpleImage decode(String str) throws InvalidImageDataException {
        if(decoded.containsKey(str)) {
            WeakReference<SimpleImage> ref = decoded.get(str);
            SimpleImage val = ref.get();
            if(val != null)
                return val;
            decoded.remove(str);
        }
        if (str.charAt(0) != 'g')
            throw new InvalidImageDataException("Not a gzip compressed image");
        byte[] data = CompressionUtil.inflate(DatatypeConverter.parseBase64Binary(str.substring(1)), true);
        int width = ((data[0] & 0xff) << 8) | (data[1] & 0xff);
        int height = ((data[2] & 0xff) << 8) | (data[3] & 0xff);
        byte[] raw = new byte[data.length - 4];
        System.arraycopy(data, 4, raw, 0, raw.length);
        SimpleImage img = toImage(raw, width, height);
        decoded.put(str, new WeakReference<>(img));
        return img;
    }

    public static SimpleImage decodeScar(String src, int width, int height) throws InvalidImageDataException {
        if(decoded.containsKey(src)) {
            WeakReference<SimpleImage> ref = decoded.get(src);
            SimpleImage val = ref.get();
            if(val != null)
                return val;
            decoded.remove(src);
        }
        if (src.charAt(0) != 'c')
            throw new InvalidImageDataException("Invalid SCAR string (doesn't start with 'c')");
        SimpleImage img = toImage(CompressionUtil.inflate(DatatypeConverter.parseBase64Binary(src.substring(1)), false), width, height);
        decoded.put(src, new WeakReference<>(img));
        return img;
    }

    private static SimpleImage toImage(byte[] source, int width, int height) throws InvalidImageDataException {
        int pl = width * height;
        if (source.length != pl*3)
            throw new InvalidImageDataException("Image data length doesn't match required length. act:" + source.length + " exp: " + (pl * 3));
        int[] pix = new int[pl];
        int off = 0;
        for (int i = 0; i < pix.length; i++) {
            int r = (source[off++] & 0xff), g = (source[off++] & 0xff), b = (source[off++] & 0xff);
            pix[i] = (r << 16) | (g << 8) | b;
        }
        return new SimpleImage(pix, width, height);
    }

    public static SimpleImage fromImage(Image i) {
        final BufferedImage bi;
        if(i instanceof BufferedImage)
            bi = (BufferedImage) i;
        else
            throw new RuntimeException("TODO");// Todo;
        final int width = bi.getWidth();
        final int height = bi.getHeight();
        int[] pix = new int[width * height];
        bi.getRGB(0, 0, width, height, pix, 0, width);
        return new SimpleImage(pix, width, height);
    }

    public BufferedImage toImage() {
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        bi.setRGB(0, 0, width, height, pixels, 0, width);
        return bi;
    }

    public boolean equals(Object o) {
        return o == this || (o != null && o instanceof SimpleImage && equals((SimpleImage) o));
    }

    public int hashCode() {
        // This may be slow (but it should be consistent).  Todo: benchmark
        MessageDigest md5 = DigestUtil.MD5;
        for(int p: pixels) {
            md5.update((byte) (p & 0xff));
            md5.update((byte) ((p >> 8) & 0xff));
            md5.update((byte) ((p >> 16) & 0xff));
        }
        byte[] hash = md5.digest();
        String hex = DigestUtil.toString(hash);
        return hex.hashCode();
    }

    public boolean equals(SimpleImage si) {
        if(si.width != width || si.height != height)
            return false;
        for(int i = 0; i < pixels.length; i++)
            if(pixels[i] != si.pixels[i])
                return false;
        return true;
    }

    public String toString() {
        return String.format("SimpleImage %dx%d", width, height);
    }
}
TOP

Related Classes of net.cakenet.jsaton.model.SimpleImage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.