Skip to content

Commit d6cb254

Browse files
committed
Add image drawing capabilities to PixelCanvas
1 parent 63ec88b commit d6cb254

File tree

8 files changed

+328
-1
lines changed

8 files changed

+328
-1
lines changed

src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
requires javafx.base;
55
requires javafx.graphics;
66
requires javafx.controls;
7+
requires java.desktop;
78

89
// Just open/export everything. Do whatever you want.
910
exports software.coley.bentofx;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package software.coley.bentofx.control.canvas;
2+
3+
import jakarta.annotation.Nonnull;
4+
5+
import java.awt.image.BufferedImage;
6+
7+
/**
8+
* ARGB source wrapping a {@link BufferedImage}.
9+
*
10+
* @author Matt Coley
11+
*/
12+
public class ArgbBufferedImageSource implements ArgbSource {
13+
private final BufferedImage image;
14+
private int[] fullArgbCache;
15+
16+
/**
17+
* @param image
18+
* Wrapped image.
19+
*/
20+
public ArgbBufferedImageSource(@Nonnull BufferedImage image) {
21+
this.image = image;
22+
}
23+
24+
@Override
25+
public int getWidth() {
26+
return image.getWidth();
27+
}
28+
29+
@Override
30+
public int getHeight() {
31+
return image.getHeight();
32+
}
33+
34+
@Override
35+
public int getArgb(int x, int y) {
36+
try {
37+
return image.getRGB(x, y);
38+
} catch (Throwable t) {
39+
// Thrown when coordinates are out of bounds.
40+
// Default to transparent black.
41+
return 0;
42+
}
43+
}
44+
45+
@Override
46+
public int[] getArgb(int x, int y, int width, int height) {
47+
try {
48+
return image.getRGB(x, y, width, height, null, 0, width);
49+
} catch (Throwable t) {
50+
// Thrown when coordinates are out of bounds.
51+
return null;
52+
}
53+
}
54+
55+
@Nonnull
56+
@Override
57+
public int[] getArgb() {
58+
// We will likely be using this a bit, so it makes sense to cache the result.
59+
if (fullArgbCache == null)
60+
fullArgbCache = ArgbSource.super.getArgb();
61+
return fullArgbCache;
62+
}
63+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package software.coley.bentofx.control.canvas;
2+
3+
import jakarta.annotation.Nonnull;
4+
import javafx.scene.image.Image;
5+
import javafx.scene.image.PixelFormat;
6+
7+
import java.nio.IntBuffer;
8+
9+
/**
10+
* ARGB source wrapping an {@link Image}.
11+
*
12+
* @author Matt Coley
13+
*/
14+
public class ArgbImageSource implements ArgbSource {
15+
private final Image image;
16+
private int[] fullArgbCache;
17+
18+
/**
19+
* @param image
20+
* Wrapped image.
21+
*/
22+
public ArgbImageSource(@Nonnull Image image) {
23+
this.image = image;
24+
}
25+
26+
@Override
27+
public int getWidth() {
28+
return (int) image.getWidth();
29+
}
30+
31+
@Override
32+
public int getHeight() {
33+
return (int) image.getHeight();
34+
}
35+
36+
@Override
37+
public int getArgb(int x, int y) {
38+
try {
39+
return image.getPixelReader().getArgb(x, y);
40+
} catch (Throwable t) {
41+
// Thrown when coordinates are out of bounds.
42+
// Default to transparent black.
43+
return 0;
44+
}
45+
}
46+
47+
@Override
48+
public int[] getArgb(int x, int y, int width, int height) {
49+
try {
50+
IntBuffer buffer = IntBuffer.allocate(width * height);
51+
image.getPixelReader().getPixels(x, y, width, height, PixelFormat.getIntArgbInstance(), buffer, width);
52+
return buffer.array();
53+
} catch (Throwable t) {
54+
// Thrown when coordinates are out of bounds.
55+
return null;
56+
}
57+
}
58+
59+
@Nonnull
60+
@Override
61+
public int[] getArgb() {
62+
// We will likely be using this a bit, so it makes sense to cache the result.
63+
if (fullArgbCache == null)
64+
fullArgbCache = ArgbSource.super.getArgb();
65+
return fullArgbCache;
66+
}
67+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package software.coley.bentofx.control.canvas;
2+
3+
import jakarta.annotation.Nonnull;
4+
import jakarta.annotation.Nullable;
5+
6+
import java.util.Objects;
7+
8+
/**
9+
* ARGB source to wrap arbitrary inputs representing image data.
10+
*
11+
* @author Matt Coley.
12+
*/
13+
public interface ArgbSource {
14+
/**
15+
* @return Image width.
16+
*/
17+
int getWidth();
18+
19+
/**
20+
* @return Image height.
21+
*/
22+
int getHeight();
23+
24+
/**
25+
* @param x
26+
* Image X coordinate.
27+
* @param y
28+
* Image X coordinate.
29+
*
30+
* @return ARGB {@code int} at coordinate.
31+
* Defaults to {@code 0} for any coordinate out of the image bounds.
32+
*/
33+
int getArgb(int x, int y);
34+
35+
/**
36+
* @param x
37+
* Image X coordinate.
38+
* @param y
39+
* Image X coordinate.
40+
* @param width
41+
* Width of image section to grab.
42+
* @param height
43+
* Height of image section to grab.
44+
*
45+
* @return ARGB {@code int[]} at coordinates for the given width/height.
46+
* {@code null} when coordinates are out of the image bounds.
47+
*/
48+
@Nullable
49+
int[] getArgb(int x, int y, int width, int height);
50+
51+
/**
52+
* @return ARGB {@code int[]} for the full image.
53+
*/
54+
@Nonnull
55+
default int[] getArgb() {
56+
return Objects.requireNonNull(getArgb(0, 0, getWidth(), getHeight()),
57+
"Failed computing ARGB for full image dimensions");
58+
}
59+
}

src/main/java/software/coley/bentofx/control/canvas/PixelCanvas.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,42 @@ public void drawVerticalLine(int x, int y, int lineLength, int lineWidth, int co
275275
pixelPainter.drawVerticalLine(x, y, lineLength, lineWidth, color);
276276
}
277277

278+
/**
279+
* Draws an image at the given coordinates.
280+
*
281+
* @param x
282+
* X coordinate.
283+
* @param y
284+
* Y coordinate.
285+
* @param image
286+
* Image to draw.
287+
*/
288+
public void drawImage(int x, int y, @Nonnull ArgbSource image) {
289+
pixelPainter.drawImage(x, y, image);
290+
}
291+
292+
/**
293+
* Draws an image at the given coordinates.
294+
*
295+
* @param x
296+
* X coordinate to draw image at.
297+
* @param y
298+
* Y coordinate to draw image at.
299+
* @param sx
300+
* X coordinate offset into the image.
301+
* @param sy
302+
* Y coordinate offset into the image.
303+
* @param sw
304+
* Width of the image to draw.
305+
* @param sh
306+
* Height of the image to draw.
307+
* @param image
308+
* Image to draw.
309+
*/
310+
public void drawImage(int x, int y, int sx, int sy, int sw, int sh, @Nonnull ArgbSource image) {
311+
pixelPainter.drawImage(x, y, sx, sy, sw, sh, image);
312+
}
313+
278314
/**
279315
* Set a given pixel to the given color.
280316
*

src/main/java/software/coley/bentofx/control/canvas/PixelPainter.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,38 @@ default void drawVerticalLine(int x, int y, int lineLength, int lineWidth, int c
218218
fillRect(x - Math.max(1, lineWidth / 2), y, lineWidth, lineLength, color);
219219
}
220220

221+
/**
222+
* Draws an image at the given coordinates.
223+
*
224+
* @param x
225+
* X coordinate to draw image at.
226+
* @param y
227+
* Y coordinate to draw image at.
228+
* @param image
229+
* Image to draw.
230+
*/
231+
void drawImage(int x, int y, @Nonnull ArgbSource image);
232+
233+
/**
234+
* Draws an image at the given coordinates.
235+
*
236+
* @param x
237+
* X coordinate to draw image at.
238+
* @param y
239+
* Y coordinate to draw image at.
240+
* @param sx
241+
* X coordinate offset into the image.
242+
* @param sy
243+
* Y coordinate offset into the image.
244+
* @param sw
245+
* Width of the image to draw.
246+
* @param sh
247+
* Height of the image to draw.
248+
* @param image
249+
* Image to draw.
250+
*/
251+
void drawImage(int x, int y, int sx, int sy, int sw, int sh, @Nonnull ArgbSource image);
252+
221253
/**
222254
* Set a given pixel to the given color.
223255
*

src/main/java/software/coley/bentofx/control/canvas/PixelPainterByteBgra.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import javafx.scene.image.PixelFormat;
55
import javafx.scene.image.PixelWriter;
66

7-
import java.nio.Buffer;
87
import java.nio.ByteBuffer;
98
import java.util.Arrays;
109

@@ -81,6 +80,42 @@ public void fillRect(int x, int y, int width, int height, int color) {
8180
}
8281
}
8382

83+
@Override
84+
public void drawImage(int x, int y, @Nonnull ArgbSource source) {
85+
int sourceWidth = source.getWidth();
86+
int sourceHeight = source.getHeight();
87+
int[] argb = source.getArgb(0, 0, sourceWidth, sourceHeight);
88+
if (argb == null)
89+
return;
90+
int yBound = Math.min(y + sourceHeight, imageHeight);
91+
int xBound = Math.min(x + sourceWidth, imageWidth);
92+
for (int ly = y; ly < yBound; ly++) {
93+
int yOffsetSource = (ly - y) * sourceWidth;
94+
for (int lx = x; lx < xBound; lx++) {
95+
int sourceIndex = yOffsetSource + (lx - x);
96+
if (sourceIndex < argb.length)
97+
setColor(lx, ly, argb[sourceIndex]);
98+
}
99+
}
100+
}
101+
102+
@Override
103+
public void drawImage(int x, int y, int sx, int sy, int sw, int sh, @Nonnull ArgbSource source) {
104+
int[] argb = source.getArgb(sx, sy, sw, sh);
105+
if (argb == null)
106+
return;
107+
int yBound = Math.min(y + sh, imageHeight);
108+
int xBound = Math.min(x + sw, imageWidth);
109+
for (int ly = y; ly < yBound; ly++) {
110+
int yOffsetSource = (ly - y) * sw;
111+
for (int lx = x; lx < xBound; lx++) {
112+
int sourceIndex = yOffsetSource + (lx - x);
113+
if (sourceIndex < argb.length)
114+
setColor(lx, ly, argb[sourceIndex]);
115+
}
116+
}
117+
}
118+
84119
@Override
85120
public void setColor(int x, int y, int color) {
86121
int i = ((y * imageWidth) + x) * DATA_SIZE;

src/main/java/software/coley/bentofx/control/canvas/PixelPainterIntArgb.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,40 @@ public void fillRect(int x, int y, int width, int height, int color) {
7171
}
7272
}
7373

74+
@Override
75+
public void drawImage(int x, int y, @Nonnull ArgbSource source) {
76+
int sourceWidth = source.getWidth();
77+
int sourceHeight = source.getHeight();
78+
int[] argb = source.getArgb();
79+
int yBound = Math.min(y + sourceHeight, imageHeight);
80+
int xBound = Math.min(x + sourceWidth, imageWidth);
81+
for (int ly = y; ly < yBound; ly++) {
82+
int yOffsetSource = (ly - y) * sourceWidth;
83+
for (int lx = x; lx < xBound; lx++) {
84+
int sourceIndex = yOffsetSource + (lx - x);
85+
if (sourceIndex < argb.length)
86+
setColor(lx, ly, argb[sourceIndex]);
87+
}
88+
}
89+
}
90+
91+
@Override
92+
public void drawImage(int x, int y, int sx, int sy, int sw, int sh, @Nonnull ArgbSource source) {
93+
int[] argb = source.getArgb(sx, sy, sw, sh);
94+
if (argb == null)
95+
return;
96+
int yBound = Math.min(y + sh, imageHeight);
97+
int xBound = Math.min(x + sw, imageWidth);
98+
for (int ly = y; ly < yBound; ly++) {
99+
int yOffsetSource = (ly - y) * sw;
100+
for (int lx = x; lx < xBound; lx++) {
101+
int sourceIndex = yOffsetSource + (lx - x);
102+
if (sourceIndex < argb.length)
103+
setColor(lx, ly, argb[sourceIndex]);
104+
}
105+
}
106+
}
107+
74108
@Override
75109
public void setColor(int x, int y, int color) {
76110
int i = adapt(x, y);

0 commit comments

Comments
 (0)