Skip to content

Commit

Permalink
Added Image float array ctor
Browse files Browse the repository at this point in the history
  • Loading branch information
pcantrell committed Nov 2, 2024
1 parent 7dc5e56 commit 4dafaca
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 3 deletions.
47 changes: 47 additions & 0 deletions src/edu/macalester/graphics/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ public Image(double x, double y, String path) {
setImagePath(path);
}

public Image(int width, int height, float[] pixels, PixelFormat format) {
this(format.makeBufferedImage(pixels, width, height));
}

/**
* Creates a bitmap image from the given BufferedImage, positioned at (0, 0).
* Note that changing the BufferedImage externally does not automatically
Expand Down Expand Up @@ -245,4 +249,47 @@ protected Object getEqualityAttributes() {
public String toString() {
return "Image at position " + getPosition() + " with file " + path;
}

public enum PixelFormat {
GRAYSCALE(BufferedImage.TYPE_BYTE_GRAY, 1, 3),
RGB(BufferedImage.TYPE_INT_RGB, 3, 3),
ARGB(BufferedImage.TYPE_INT_ARGB, 4, 4);

private final int bufferedImageType;
private final int inChannels, outChannels;

PixelFormat(int bufferedImageType, int inChannels, int outChannels) {
this.bufferedImageType = bufferedImageType;
this.inChannels = inChannels;
this.outChannels = outChannels;
}

public BufferedImage makeBufferedImage(float[] pixels, int width, int height) {
int expectedArrayLen = width * height * inChannels;
if (pixels.length != expectedArrayLen) {
throw new IllegalArgumentException(
"Invalid input array length for " + this.name() + ": expected "
+ width + " w * " + height + " h * " + inChannels + " channels = "
+ expectedArrayLen + ", but got " + pixels.length);
}

int[] rawData = new int[width * height];
for (int i = 0; i < rawData.length; i++) {
int pix = 0;
for(int c = 0; c < outChannels; c++) {
pix = pix << 8 | colorChannelToByte(
pixels[i * inChannels + c % inChannels]);
}
rawData[i] = pix;
}

BufferedImage buf = new BufferedImage(width, height, bufferedImageType);
buf.setRGB(0, 0, width, height, rawData, 0, width);
return buf;
}

private static int colorChannelToByte(float value) {
return (int) (Math.min(1, Math.max(0, value)) * 255);
}
}
}
56 changes: 53 additions & 3 deletions test/edu/macalester/graphics/ImageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@
import edu.macalester.graphics.testsupport.GraphicsObjectTestSuite;
import edu.macalester.graphics.testsupport.RenderingTest;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import javax.imageio.ImageIO;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

import static edu.macalester.graphics.testsupport.RenderingTestMode.PLAIN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ImageTest implements GraphicsObjectTestSuite {
public static final String FOXFLOWER_IMAGE = "res/foxflower.png";
public static final String FOXBOT_IMAGE = "res/foxbot.png";
Expand Down Expand Up @@ -66,7 +73,7 @@ void pixelAlignment() {

@RenderingTest
void loadedFromBufferedImage() {
try{
try {
InputStream resource = Image.class.getResourceAsStream("/" + FOXBOT_IMAGE);
if (resource == null) {
throw new IOException("No resource named /" + FOXBOT_IMAGE);
Expand All @@ -78,6 +85,49 @@ void loadedFromBufferedImage() {
}
}

@Test
void pixelsConstructorsCheckBounds() {
for (var ctorCall : List.of((Executable)
() -> new Image(2, 3, new float[18], Image.PixelFormat.GRAYSCALE), // too large
() -> new Image(3, 2, new float[6], Image.PixelFormat.RGB) // too small
)) {
assertThrows(IllegalArgumentException.class, ctorCall);
}
}

@RenderingTest(modes = { PLAIN })
void pixelsFloatGrayscale() {
image = new Image(70, 90, generateFloatData(70, 90, 1), Image.PixelFormat.GRAYSCALE);
}

@RenderingTest(modes = { PLAIN })
void pixelsFloatRGB() {
image = new Image(100, 80, generateFloatData(100, 80, 3), Image.PixelFormat.RGB);
}

@RenderingTest(modes = { PLAIN })
void pixelsFloatARGB() {
image = new Image(97, 93, generateFloatData(97, 93, 4), Image.PixelFormat.ARGB);
}

private float[] generateFloatData(int w, int h, int chans) {
float[] pixels = new float[w * h * chans];
int i = 0;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
for (int c = 0; c < chans; c++) {
// This is engineered to include a mix of 0, NaN, out of bounds, and values
// that are very different per channel
pixels[i++] = (float)
( Math.sin(x * 3.0 * (c * 2.7 + 1) / w)
/ Math.sin(y * 4.0 * (c * 1.9 + 1) / w));
}
}
}
assertTrue(Float.isNaN(pixels[0]));
return pixels;
}

@RenderingTest
void empty() {
image = new Image(1, 1);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4dafaca

Please sign in to comment.