2 * Image codec. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3 * This project is released under Creative Commons Zero (CC0) license.
5 package eu.svjatoslav.imagesqueeze.codec;
8 * Compressed image pixels decoder.
11 import eu.svjatoslav.commons.data.BitInputStream;
13 import java.awt.image.DataBufferByte;
14 import java.awt.image.WritableRaster;
15 import java.io.IOException;
19 private final int width;
20 private final int height;
21 private final Image image;
22 private final byte[] decodedYRangeMap;
23 private final byte[] decodedYMap;
24 private final byte[] decodedURangeMap;
25 private final byte[] decodedUMap;
26 private final byte[] decodedVRangeMap;
27 private final byte[] decodedVMap;
28 private final Approximator approximator;
29 private final BitInputStream bitInputStream;
30 private final OperatingContext context = new OperatingContext();
32 public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
33 approximator = new Approximator();
36 this.bitInputStream = bitInputStream;
38 width = image.metaData.width;
39 height = image.metaData.height;
41 decodedYRangeMap = new byte[width * height];
42 decodedYRangeMap[0] = (byte) (255);
43 decodedYMap = new byte[width * height];
45 decodedURangeMap = new byte[width * height];
46 decodedURangeMap[0] = (byte) (255);
47 decodedUMap = new byte[width * height];
49 decodedVRangeMap = new byte[width * height];
50 decodedVRangeMap[0] = (byte) (255);
51 decodedVMap = new byte[width * height];
55 public static int readIntegerCompressed8(final BitInputStream inputStream)
58 if (inputStream.readBits(1) == 0)
59 return inputStream.readBits(8);
61 return inputStream.readBits(32);
64 public void decode() throws IOException {
65 approximator.load(bitInputStream);
66 approximator.computeLookupTables();
68 final WritableRaster raster = image.bufferedImage.getRaster();
69 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
70 final byte[] pixels = dbi.getData();
72 // load top-, left-most pixel.
73 decodedYMap[0] = (byte) bitInputStream.readBits(8);
74 decodedUMap[0] = (byte) bitInputStream.readBits(8);
75 decodedVMap[0] = (byte) bitInputStream.readBits(8);
77 final Color color = new Color();
78 color.y = ImageEncoder.byteToInt(decodedYMap[0]);
79 color.u = ImageEncoder.byteToInt(decodedUMap[0]);
80 color.v = ImageEncoder.byteToInt(decodedVMap[0]);
84 pixels[0] = (byte) color.r;
85 pixels[0 + 1] = (byte) color.g;
86 pixels[0 + 2] = (byte) color.b;
88 // detect initial step
92 largestDimension = width;
94 largestDimension = height;
96 while (initialStep < largestDimension)
97 initialStep = initialStep * 2;
99 grid(initialStep, pixels);
102 private void grid(final int step, final byte[] pixels) throws IOException {
104 gridDiagonal(step / 2, step / 2, step, pixels);
105 gridSquare(step / 2, 0, step, pixels);
106 gridSquare(0, step / 2, step, pixels);
109 grid(step / 2, pixels);
112 private void gridDiagonal(final int offsetX, final int offsetY,
113 final int step, final byte[] pixels) throws IOException {
115 for (int y = offsetY; y < height; y = y + step)
116 for (int x = offsetX; x < width; x = x + step) {
118 final int halfStep = step / 2;
120 context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
121 context.measureNeighborEncode(x - halfStep, y - halfStep);
122 context.measureNeighborEncode(x + halfStep, y - halfStep);
123 context.measureNeighborEncode(x - halfStep, y + halfStep);
124 context.measureNeighborEncode(x + halfStep, y + halfStep);
126 loadPixel(step, offsetX, offsetY, x, y, pixels,
127 context.colorStats.getAverageY(),
128 context.colorStats.getAverageU(),
129 context.colorStats.getAverageV());
134 private void gridSquare(final int offsetX, final int offsetY,
135 final int step, final byte[] pixels) throws IOException {
137 for (int y = offsetY; y < height; y = y + step)
138 for (int x = offsetX; x < width; x = x + step) {
140 final int halfStep = step / 2;
142 context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
143 context.measureNeighborEncode(x - halfStep, y);
144 context.measureNeighborEncode(x + halfStep, y);
145 context.measureNeighborEncode(x, y - halfStep);
146 context.measureNeighborEncode(x, y + halfStep);
148 loadPixel(step, offsetX, offsetY, x, y, pixels,
149 context.colorStats.getAverageY(),
150 context.colorStats.getAverageU(),
151 context.colorStats.getAverageV());
156 private int loadChannel(final byte[] decodedRangeMap,
157 final byte[] decodedMap, final Table table,
158 final int averageDecodedValue, final int index,
159 final int parentIndex) throws IOException {
160 int decodedValue = averageDecodedValue;
162 final int inheritedRange = ImageEncoder
163 .byteToInt(decodedRangeMap[parentIndex]);
164 int computedRange = inheritedRange;
166 final int bitCount = table.proposeBitcountForRange(inheritedRange);
167 int computedRangeBitCount;
170 final int rangeDecreases = bitInputStream.readBits(1);
171 if (rangeDecreases != 0)
172 computedRange = table.proposeDecreasedRange(inheritedRange);
174 decodedRangeMap[index] = (byte) computedRange;
175 computedRangeBitCount = table
176 .proposeBitcountForRange(computedRange);
178 if (computedRangeBitCount > 0) {
180 final int encodedDifference = bitInputStream
181 .readBits(computedRangeBitCount);
183 final int decodedDifference = ImageEncoder
184 .decodeValueFromGivenBits(encodedDifference,
185 computedRange, computedRangeBitCount);
187 decodedValue = averageDecodedValue - decodedDifference;
188 if (decodedValue > 255)
190 if (decodedValue < 0)
194 decodedRangeMap[index] = (byte) inheritedRange;
195 decodedMap[index] = (byte) decodedValue;
199 private void loadPixel(final int step, final int offsetX, final int offsetY,
200 final int x, final int y, final byte[] pixels,
201 final int averageDecodedY, final int averageDecodedU,
202 final int averageDecodedV) throws IOException {
204 final int index = (y * width) + x;
206 final int halfStep = step / 2;
212 parentIndex = ((y - halfStep) * width) + (x - halfStep);
215 parentIndex = (y * width) + (x - halfStep);
218 parentIndex = ((y - halfStep) * width) + x;
220 final int colorBufferIndex = index * 3;
222 final Color color = new Color();
223 color.y = loadChannel(decodedYRangeMap, decodedYMap,
224 approximator.yTable, averageDecodedY, index, parentIndex);
225 color.u = loadChannel(decodedURangeMap, decodedUMap,
226 approximator.uTable, averageDecodedU, index, parentIndex);
227 color.v = loadChannel(decodedVRangeMap, decodedVMap,
228 approximator.vTable, averageDecodedV, index, parentIndex);
232 pixels[colorBufferIndex] = (byte) color.r;
233 pixels[colorBufferIndex + 1] = (byte) color.g;
234 pixels[colorBufferIndex + 2] = (byte) color.b;