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

net.freeutils.scrollphat.Canvas Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright © 2016 Amichai Rothman
 *
 *  This file is part of JScrollPhat - the Java Scroll pHAT package.
 *
 *  JScrollPhat is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JScrollPhat is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with JScrollPhat.  If not, see .
 *
 *  For additional info see http://www.freeutils.net/source/jscrollphat/
 */

package net.freeutils.scrollphat;

import java.io.IOException;
import java.util.Arrays;

/**
 * A convenience class for manipulating a LED matrix
 * buffer and preparing the display content.
 */
public class Canvas {

    byte[] matrix;
    byte[] buffer;
    int width;
    int height;
    int offset;
    boolean flip;
    boolean reverse;

    /**
     * Converts the pixel matrix represented by a given byte array to a string
     * containing the ASCII-art representation of the pixels.
     *
     * @param matrix the pixel matrix
     * @param offset the offset within the matrix where the data to convert starts
     * @param len the length in bytes of the data to convert
     * @param height the height of the pixel matrix in pixels (max 8)
     * @return an ASCII-art string representing the pixel matrix
     */
    public static String toAsciiArt(byte[] matrix, int offset, int len, int height) {
        if (len == 0)
            return "";
        StringBuilder sb = new StringBuilder((len + 2) * height);
        for (int row = 0; row < height ; row++) { // from top to bottom
            for (int col = offset, end = offset + len; col < end; col++) {
                boolean bit = ((matrix[col] >> row) & 1) == 1;
                sb.append(bit ? '\u2593' : '\u2591');
            }
            sb.append('\n');
        }
        return sb.toString();
    }

    /**
     * Converts the pixel matrix represented by a given byte array to a string
     * containing the ASCII-art representation of the pixels.
     *
     * @param matrix the pixel matrix
     * @param height the height of the pixel matrix in pixels (max 8)
     * @return an ASCII-art string representing the pixel matrix
     */
    public static String toAsciiArt(byte[] matrix, int height) {
        return toAsciiArt(matrix, 0, matrix.length, height);
    }

    /**
     * Flips the order of bits in a byte.
     *
     * @param b a byte
     * @return the flipped byte
     */
    public static byte flip(byte b) {
        b = (byte)((b & 0xf0) >>> 4 | (b & 0x0f) << 4);
        b = (byte)((b & 0xcc) >>> 2 | (b & 0x33) << 2);
        b = (byte)((b & 0xaa) >>> 1 | (b & 0x55) << 1);
        return b;
    }

    /**
     * Flips the order of the least-significant bits in a byte.
     *
     * @param b a byte
     * @param height the number of least-significant bits to flip
     *        (all higher bits are set to zero)
     * @return the flipped byte
     */
    public static byte flip(byte b, int height) {
        return (byte)((flip(b) & 0xff) >>> (8 - height));
    }

    /**
     * Flips the order of the least-significant bits in an array of bytes.
     *
     * @param matrix the bytes to flip
     * @param height the number of least-significant bits to flip
     *        (all higher bits are set to zero)
     * @param out an array into which the flipped bits are written
     *        (can be identical to the given source array)
     * @return the output array
     */
    public static byte[] flip(byte[] matrix, int height, byte[] out) {
        int len = matrix.length;
        if (len != out.length)
            throw new IllegalArgumentException("array length mismatch");
        for (int i = 0; i < len; i++)
            out[i] = flip(matrix[i], height);
        return out;
    }

    /**
     * Reverses the order of bytes in an array.
     *
     * @param matrix the bytes to flip
     * @param out an array into which the reversed bytes are written
     *        (can be identical to the given source array)
     * @return the output array
     */
    public static byte[] reverse(byte[] matrix, byte[] out) {
        int len = matrix.length;
        if (len != out.length)
            throw new IllegalArgumentException("array length mismatch");
        if (matrix != out) {
            for (int i = 0; i < len; i++)
                out[i] = matrix[len - 1 - i];
        } else {
            System.arraycopy(matrix, 0, out, 0, len);
            len >>= 1;
            for (int i = 0, j = out.length - 1; i < len; i++, j--) {
                byte temp = out[i];
                out[i] = out[j];
                out[j] = temp;
            }
        }
        return out;
    }

    /**
     * Crops the given pixel matrix horizontally.
     *
     * @param matrix the pixel matrix to crop
     * @param x the X position to start cropping at (left side of cropped rectangle)
     * @param width the number of pixels to crop horizontally
     * @param out an array into which the cropped bytes are written
     *        (can be identical to the given source array)
     * @return the output array
     */
    public static byte[] cropX(byte[] matrix, int x, int width, byte[] out) {
        int dest = 0;
        if (x < 0) {
            dest = -x;
            width += x;
            x = 0;
        }
        width = Math.min(width, Math.max(0, matrix.length - x));
        int len = out.length;
        if (dest < len && width > 0)
            System.arraycopy(matrix, x, out, dest, width);
        for (int i = 0; i < dest && i < len; i++)
            out[i] = 0;
        for (int i = dest + width; i < len; i++)
            out[i] = 0;
        return out;
    }

    /**
     * Crops the given pixel matrix vertically.
     *
     * @param matrix the pixel matrix to crop
     * @param y the Y position to start cropping at (top side of cropped rectangle)
     * @param height the number of pixels to crop vertically
     * @param out an array into which the cropped bytes are written
     *        (can be identical to the given source array)
     * @return the output array
     */
    public static byte[] cropY(byte[] matrix, int y, int height, byte[] out) {
        int mask = (1 << height) - 1;
        int len = matrix.length;
        if (y >= 0) {
            for (int i = 0; i < len; i++)
                out[i] = (byte)((matrix[i] & 0xff) >>> y & mask);
        } else {
            y = -y;
            for (int i = 0; i < len; i++)
                out[i] = (byte)(matrix[i] << y & mask);
        }
        return out;
    }

    /**
     * Crops the given pixel matrix.
     *
     * @param matrix the pixel matrix to crop
     * @param x the X position to start cropping at (left side of cropped rectangle)
     * @param y the Y position to start cropping at (top side of cropped rectangle)
     * @param width the number of pixels to crop horizontally
     * @param height the number of pixels to crop vertically
     * @param out an array into which the cropped bytes are written
     *        (can be identical to the given source array)
     * @return the output array
     */
    public static byte[] crop(byte[] matrix, int x, int y, int width, int height, byte[] out) {
        return cropY(cropX(matrix, x, width, out), y, height, out);
    }

    /**
     * Empties a byte array by filling it with zeros.
     *
     * @param matrix a byte array
     * @return the byte array
     */
    public static byte[] empty(byte[] matrix) {
        return fill(matrix, 0);
    }

    /**
     * Fills a byte array with a single value.
     *
     * @param matrix a byte array
     * @param value the value to fill with
     * @return the byte array
     */
    public static byte[] fill(byte[] matrix, int value) {
        Arrays.fill(matrix, (byte)value);
        return matrix;
    }

    /**
     * Fills a byte array with 0xFF bytes (i.e. all bits set to 1).
     *
     * @param matrix a byte array
     * @return the byte array
     */
    public static byte[] fill(byte[] matrix) {
        return fill(matrix, 0xff);
    }

    /**
     * Sets the value of a single bit within a matrix
     * of bits represented by a byte array.
     *
     * @param matrix a byte array (matrix of bits)
     * @param x the x coordinate of the bit to set (left to right)
     * @param y the y coordinate of the bit to set (top to bottom)
     * @param value the value to set (true means 1, false means 0)
     */
    public static void setBit(byte[] matrix, int x, int y, boolean value) {
        if (value)
            matrix[x] |= (1 << y);
        else
            matrix[x] &= ~(1 << y);
    }

    /**
     * Draws a graph with the given width and height in pixels
     * from the given data values and boundaries.
     *
     * @param matrix the pixel matrix to draw on
     * @param width the graph width in pixels
     * @param height the graph height in pixels
     * @param values the data values
     * @param low the value represented at the bottom of the graph,
     *        or null if the minimum data value should be used
     * @param high the value represented at the top of the graph,
     *        or null if the maximum data value should be used
     * @param bars specifies whether the graph is a bar graph (with
     *        filled area under the value), or a scatter plot
     *        (single point per value)
     */
    public static void graph(byte[] matrix, int width, int height,
            float[] values, Float low, Float high, boolean bars) {
        // validate
        int len = values.length;
        if (len > width)
            throw new IllegalArgumentException("too many values (max " + width + ")");
        if (len == 0)
            return;
        // find min/max/range
        float min = Float.MAX_VALUE;
        float max = Float.MIN_VALUE;
        for (float value : values) {
            if (value < min)
                min = value;
            if (value > max)
                max = value;
        }
        if (low != null)
            min = low;
        if (high != null)
            max = high;
        float range = max - min;
        // write graph data
        for (int i = 0; i < width; i++) {
            byte val = i < len ? (byte)Math.ceil((values[i] - min) / range * height) : 0;
            if (val <= 0)
                val = 0;
            else if (bars)
                val = (byte)(((1 << height) - 1) & ~((1 << (height - (val > height ? height : val))) - 1));
            else
                val = (byte)(1 << (height - val));
            matrix[i] = val;
        }
    }

    /**
     * Draws a graph with the given width and height in pixels
     * from the given data values and boundaries.
     *
     * @param matrix the pixel matrix to draw on
     * @param width the graph width in pixels
     * @param height the graph height in pixels
     * @param values the data values
     * @param low the value represented at the bottom of the graph,
     *        or null if the minimum data value should be used
     * @param high the value represented at the top of the graph,
     *        or null if the maximum data value should be used
     * @param bars specifies whether the graph is a bar graph (with
     *        filled area under the value), or a scatter plot
     *        (single point per value)
     */
    public static void graph(byte[] matrix, int width, int height,
            int[] values, Float low, Float high, boolean bars) {
        float[] floats = new float[values.length];
        for (int i = 0; i < values.length; i++)
            floats[i] = values[i];
        graph(matrix, width, height, floats, low, high, bars);
    }

    /**
     * Constructs a Canvas which wraps the given matrix.
     *
     * @param matrix the matrix to wrap
     * @param height the height of the pixel matrix in pixels (max 8)
     * @throws IOException if an error occurs
     */
    public Canvas(byte[] matrix, int height) throws IOException {
        this.height = height;
        setMatrix(matrix);
    }

    /**
     * Constructs a Canvas with a new matrix of the given width and height.
     *
     * @param width the width of the pixel matrix in pixels
     * @param height the height of the pixel matrix in pixels (max 8)
     * @throws IOException if an error occurs
     */
    public Canvas(int width, int height) throws IOException {
        this(new byte[width], height);
    }

    /**
     * Returns the canvas width in pixels.
     *
     * @return the canvas width in pixels
     */
    public int getWidth() {
        return width;
    }

    /**
     * Returns the canvas height in pixels.
     *
     * @return the canvas height in pixels
     */
    public int getHeight() {
        return height;
    }

    /**
     * Sets whether the rendered matrix should be flipped vertically.
     *
     * @param flip true if the rendered matrix should be flipped vertically;
     *        false otherwise
     */
    public void setFlip(boolean flip) {
        this.flip = flip;
    }

    /**
     * Sets whether the rendered matrix should be reversed horizontally.
     *
     * @param reverse true if the rendered matrix should be reversed horizontally;
     *        false otherwise
     */
    public void setReverse(boolean reverse) {
        this.reverse = reverse;
    }

    /**
     * Returns the wrapped matrix bytes.
     *
     * @return the wrapped matrix bytes
     */
    public byte[] getMatrix() {
        return matrix;
    }

    /**
     * Sets the wrapped matrix bytes.
     *
     * @param matrix the wrapped matrix bytes
     */
    public void setMatrix(byte[] matrix) {
        this.matrix = matrix;
        this.width = matrix.length;
        this.buffer = new byte[matrix.length];
    }

    /**
     * Sets the value of a column.
     *
     * @param column the column to set
     * @param value the column value
     */
    public void setColumn(int column, int value) {
        matrix[column] = (byte)value;
    }

    /**
     * Sets the value of a sequence of columns.
     *
     * @param startColumn the first target column
     * @param columns the column values
     * @param offset the start offset of the column values
     * @param length the length of the column values
     */
    public void setColumns(int startColumn, byte[] columns, int offset, int length) {
        System.arraycopy(columns, offset, matrix, startColumn, length);
    }

    /**
     * Sets the value of a pixel.
     *
     * @param x the X coordinate of the pixel
     * @param y the Y coordinate of the pixel
     * @param value the pixel value (true for on, false for off)
     */
    public void setPixel(int x, int y, boolean value) {
        setBit(matrix, x, y, value);
    }

    /**
     * Fills the matrix with the given column value.
     *
     * @param value the column value to fill the matrix with
     */
    public void fill(int value) {
        fill(matrix, value);
    }

    /**
     * Empty the matrix (fill it with empty columns).
     *
     * @throws IOException if an error occurs
     */
    public void empty() throws IOException {
        fill(0);
        offset = 0;
    }

    /**
     * Render the matrix into a returned buffer,
     * with transformations applied (flip, reverse, crop, etc.)
     *
     * @return the rendered buffer
     * @throws IOException if an error occurs
     */
    public byte[] render() throws IOException {
        byte[] b = this.matrix;
        int offset = this.offset;
        if (flip)
            b = flip(b, height, buffer);
        if (reverse) {
            offset = b.length - offset;
            b = reverse(b, buffer);
        }
        buffer = cropX(b, offset, width, buffer);
        return buffer;
    }

    /**
     * Sets the offset of the column within the matrix at which
     * the rendering will start.
     *
     * @param offset the offset of the column within the matrix
     */
    public void setOffset(int offset) {
        this.offset = offset;
    }

    /**
     * Scrolls the rendered columns by the given number of columns
     * (positive or negative delta). This sets the offset relative
     * to the current offset.
     *
     * @param delta the number of columns to scroll by
     */
    public void scroll(int delta) {
        setOffset(offset + delta);
    }

    /**
     * Draws a graph with the given width and height in pixels
     * from the given data values and boundaries.
     *
     * @param values the data values
     * @param low the value represented at the bottom of the graph,
     *        or null if the minimum data value should be used
     * @param high the value represented at the top of the graph,
     *        or null if the maximum data value should be used
     * @param bars specifies whether the graph is a bar graph (with
     *        filled area under the value), or a scatter plot
     *        (single point per value)
     */
    public void graph(int[] values, Float low, Float high, boolean bars) {
        graph(matrix, getWidth(), getHeight(), values, low, high, bars);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy