/
BufferedRgbaImage.java
150 lines (118 loc) · 4.44 KB
/
BufferedRgbaImage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
* PNG library (Java)
*
* Copyright (c) Project Nayuki
* MIT License. See readme file.
* https://www.nayuki.io/page/png-library
*/
package io.nayuki.png.image;
import java.util.Objects;
/**
* A mutable RGBA image where all pixels are stored in memory.
*/
public final class BufferedRgbaImage implements RgbaImage, Cloneable {
/*---- Fields ----*/
private final int width;
private final int height;
private int[] bitDepths;
private final long illegalOnes;
private long[] pixels;
/*---- Constructors ----*/
/**
* Constructs an all-zero image with the specified dimensions and
* channel bit depths. {@code bitDepths} is a length-4 array:
* <ul>
* <li>Index 0: Red channel bit depth, in the range [1, 16]</li>
* <li>Index 1: Green channel bit depth, in the range [1, 16]</li>
* <li>Index 2: Blue channel bit depth, in the range [1, 16]</li>
* <li>Index 3: Alpha channel bit depth, in the range [0, 16], where 0 means all pixels are opaque</li>
* </ul>
* <p>The dimensions and bit depths are immutable after
* construction; only the pixel values can be modified.</p>
* @param width the width of the image, a positive number
* @param height the height of the image, a positive number
* @param bitDepths the bit depths of the channels of the image (not {@code null})
* @throws NullPointerException if {@code bitDepths} is {@code null}
* @throws IllegalArgumentException if the width, height, or bit depths are out of range
* @throws ArithmeticException if {@code width * height > Integer.MAX_VALUE}
*/
public BufferedRgbaImage(int width, int height, int[] bitDepths) {
if (width <= 0 || height <= 0)
throw new IllegalArgumentException("Non-positive dimensions");
this.width = width;
this.height = height;
Objects.requireNonNull(bitDepths);
bitDepths = bitDepths.clone();
if (bitDepths.length != 4)
throw new IllegalArgumentException("Invalid bit depth array length");
for (int i = 0; i < bitDepths.length; i++) {
if (!((i == bitDepths.length - 1 ? 0 : 1) <= bitDepths[i] && bitDepths[i] <= 16))
throw new IllegalArgumentException("Invalid bit depths");
}
this.bitDepths = bitDepths;
long temp = 0;
for (int numBits : bitDepths) {
temp <<= 16;
temp |= 0x10000 - (1 << numBits);
}
illegalOnes = temp;
pixels = new long[Math.multiplyExact(width, height)];
}
/**
* Constructs an image by copying pixel values and bit depths from the specified image.
* @param img the image to copy from (not {@code null})
* @throws NullPointerException if {@code img} is {@code null}
* @throws ArithmeticException if {@code width * height > Integer.MAX_VALUE}
*/
public BufferedRgbaImage(RgbaImage img) {
this(img.getWidth(), img.getHeight(), img.getBitDepths());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++)
pixels[y * width + x] = img.getPixel(x, y);
}
}
/*---- Methods ----*/
@Override public int[] getBitDepths() {
return bitDepths.clone();
}
@Override public int getWidth() {
return width;
}
@Override public int getHeight() {
return height;
}
@Override public long getPixel(int x, int y) {
return pixels[getIndex(x, y)];
}
/**
* Sets the pixel at the specified coordinates to the specified value.
* @param x x the <var>x</var> coordinate of the pixel to set, in the range [0, {@code getWidth()})
* @param y y the <var>y</var> coordinate of the pixel to set, in the range [0, {@code getHeight()})
* @param val the new channel sample values of the pixel
* @throws IndexOutOfBoundsException if the (<var>x</var>, <var>y</var>) coordinates are out of bounds
* @throws IllegalArgumentException if any of the channel sample values are outside of their bit depth
*/
public void setPixel(int x, int y, long val) {
if ((val & illegalOnes) != 0)
throw new IllegalArgumentException("Invalid sample value");
pixels[getIndex(x, y)] = val;
}
private int getIndex(int x, int y) {
if (0 <= x && x < width && 0 <= y && y < height)
return y * width + x;
else {
throw new IndexOutOfBoundsException(String.format(
"(x,y) = (%d,%d); (width,height) = (%d,%d)", x, y, width, height));
}
}
@Override public BufferedRgbaImage clone() {
try {
var result = (BufferedRgbaImage)super.clone();
result.bitDepths = result.bitDepths.clone();
result.pixels = result.pixels.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError("Unreachable exception", e);
}
}
}