
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