2 * Imagesqueeze - Image codec. Copyright ©2012-2019, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public License
6 * or later as published by the Free Software Foundation.
9 package eu.svjatoslav.imagesqueeze.codec;
12 * Compressed image pixels decoder.
15 import eu.svjatoslav.commons.data.BitInputStream;
17 import java.awt.image.DataBufferByte;
18 import java.awt.image.WritableRaster;
19 import java.io.IOException;
23 private final int width;
24 private final int height;
25 private final Image image;
26 private final byte[] decodedYRangeMap;
27 private final byte[] decodedYMap;
28 private final byte[] decodedURangeMap;
29 private final byte[] decodedUMap;
30 private final byte[] decodedVRangeMap;
31 private final byte[] decodedVMap;
32 private final Approximator approximator;
33 private final BitInputStream bitInputStream;
34 private final OperatingContext context = new OperatingContext();
36 public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
37 approximator = new Approximator();
40 this.bitInputStream = bitInputStream;
42 width = image.metaData.width;
43 height = image.metaData.height;
45 decodedYRangeMap = new byte[width * height];
46 decodedYRangeMap[0] = (byte) (255);
47 decodedYMap = new byte[width * height];
49 decodedURangeMap = new byte[width * height];
50 decodedURangeMap[0] = (byte) (255);
51 decodedUMap = new byte[width * height];
53 decodedVRangeMap = new byte[width * height];
54 decodedVRangeMap[0] = (byte) (255);
55 decodedVMap = new byte[width * height];
59 public static int readIntegerCompressed8(final BitInputStream inputStream)
62 if (inputStream.readBits(1) == 0)
63 return inputStream.readBits(8);
65 return inputStream.readBits(32);
68 public void decode() throws IOException {
69 approximator.load(bitInputStream);
70 approximator.computeLookupTables();
72 final WritableRaster raster = image.bufferedImage.getRaster();
73 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
74 final byte[] pixels = dbi.getData();
76 // load top-, left-most pixel.
77 decodedYMap[0] = (byte) bitInputStream.readBits(8);
78 decodedUMap[0] = (byte) bitInputStream.readBits(8);
79 decodedVMap[0] = (byte) bitInputStream.readBits(8);
81 final Color color = new Color();
82 color.y = ImageEncoder.byteToInt(decodedYMap[0]);
83 color.u = ImageEncoder.byteToInt(decodedUMap[0]);
84 color.v = ImageEncoder.byteToInt(decodedVMap[0]);
88 pixels[0] = (byte) color.r;
89 pixels[0 + 1] = (byte) color.g;
90 pixels[0 + 2] = (byte) color.b;
92 // detect initial step
96 largestDimension = width;
98 largestDimension = height;
100 while (initialStep < largestDimension)
101 initialStep = initialStep * 2;
103 grid(initialStep, pixels);
106 private void grid(final int step, final byte[] pixels) throws IOException {
108 gridDiagonal(step / 2, step / 2, step, pixels);
109 gridSquare(step / 2, 0, step, pixels);
110 gridSquare(0, step / 2, step, pixels);
113 grid(step / 2, pixels);
116 private void gridDiagonal(final int offsetX, final int offsetY,
117 final int step, final byte[] pixels) throws IOException {
119 for (int y = offsetY; y < height; y = y + step)
120 for (int x = offsetX; x < width; x = x + step) {
122 final int halfStep = step / 2;
124 context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
125 context.measureNeighborEncode(x - halfStep, y - halfStep);
126 context.measureNeighborEncode(x + halfStep, y - halfStep);
127 context.measureNeighborEncode(x - halfStep, y + halfStep);
128 context.measureNeighborEncode(x + halfStep, y + halfStep);
130 loadPixel(step, offsetX, offsetY, x, y, pixels,
131 context.colorStats.getAverageY(),
132 context.colorStats.getAverageU(),
133 context.colorStats.getAverageV());
138 private void gridSquare(final int offsetX, final int offsetY,
139 final int step, final byte[] pixels) throws IOException {
141 for (int y = offsetY; y < height; y = y + step)
142 for (int x = offsetX; x < width; x = x + step) {
144 final int halfStep = step / 2;
146 context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
147 context.measureNeighborEncode(x - halfStep, y);
148 context.measureNeighborEncode(x + halfStep, y);
149 context.measureNeighborEncode(x, y - halfStep);
150 context.measureNeighborEncode(x, y + halfStep);
152 loadPixel(step, offsetX, offsetY, x, y, pixels,
153 context.colorStats.getAverageY(),
154 context.colorStats.getAverageU(),
155 context.colorStats.getAverageV());
160 private int loadChannel(final byte[] decodedRangeMap,
161 final byte[] decodedMap, final Table table,
162 final int averageDecodedValue, final int index,
163 final int parentIndex) throws IOException {
164 int decodedValue = averageDecodedValue;
166 final int inheritedRange = ImageEncoder
167 .byteToInt(decodedRangeMap[parentIndex]);
168 int computedRange = inheritedRange;
170 final int bitCount = table.proposeBitcountForRange(inheritedRange);
171 int computedRangeBitCount;
174 final int rangeDecreases = bitInputStream.readBits(1);
175 if (rangeDecreases != 0)
176 computedRange = table.proposeDecreasedRange(inheritedRange);
178 decodedRangeMap[index] = (byte) computedRange;
179 computedRangeBitCount = table
180 .proposeBitcountForRange(computedRange);
182 if (computedRangeBitCount > 0) {
184 final int encodedDifference = bitInputStream
185 .readBits(computedRangeBitCount);
187 final int decodedDifference = ImageEncoder
188 .decodeValueFromGivenBits(encodedDifference,
189 computedRange, computedRangeBitCount);
191 decodedValue = averageDecodedValue - decodedDifference;
192 if (decodedValue > 255)
194 if (decodedValue < 0)
198 decodedRangeMap[index] = (byte) inheritedRange;
199 decodedMap[index] = (byte) decodedValue;
203 private void loadPixel(final int step, final int offsetX, final int offsetY,
204 final int x, final int y, final byte[] pixels,
205 final int averageDecodedY, final int averageDecodedU,
206 final int averageDecodedV) throws IOException {
208 final int index = (y * width) + x;
210 final int halfStep = step / 2;
216 parentIndex = ((y - halfStep) * width) + (x - halfStep);
219 parentIndex = (y * width) + (x - halfStep);
222 parentIndex = ((y - halfStep) * width) + x;
224 final int colorBufferIndex = index * 3;
226 final Color color = new Color();
227 color.y = loadChannel(decodedYRangeMap, decodedYMap,
228 approximator.yTable, averageDecodedY, index, parentIndex);
229 color.u = loadChannel(decodedURangeMap, decodedUMap,
230 approximator.uTable, averageDecodedU, index, parentIndex);
231 color.v = loadChannel(decodedVRangeMap, decodedVMap,
232 approximator.vTable, averageDecodedV, index, parentIndex);
236 pixels[colorBufferIndex] = (byte) color.r;
237 pixels[colorBufferIndex + 1] = (byte) color.g;
238 pixels[colorBufferIndex + 2] = (byte) color.b;