Changed license to CC0
[imagesqueeze.git] / src / main / java / eu / svjatoslav / imagesqueeze / codec / ImageDecoder.java
1 /*
2  * Image codec. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3  * This project is released under Creative Commons Zero (CC0) license.
4  */
5 package eu.svjatoslav.imagesqueeze.codec;
6
7 /**
8  * Compressed image pixels decoder.
9  */
10
11 import eu.svjatoslav.commons.data.BitInputStream;
12
13 import java.awt.image.DataBufferByte;
14 import java.awt.image.WritableRaster;
15 import java.io.IOException;
16
17 class ImageDecoder {
18
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();
31
32     public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
33         approximator = new Approximator();
34
35         this.image = image;
36         this.bitInputStream = bitInputStream;
37
38         width = image.metaData.width;
39         height = image.metaData.height;
40
41         decodedYRangeMap = new byte[width * height];
42         decodedYRangeMap[0] = (byte) (255);
43         decodedYMap = new byte[width * height];
44
45         decodedURangeMap = new byte[width * height];
46         decodedURangeMap[0] = (byte) (255);
47         decodedUMap = new byte[width * height];
48
49         decodedVRangeMap = new byte[width * height];
50         decodedVRangeMap[0] = (byte) (255);
51         decodedVMap = new byte[width * height];
52
53     }
54
55     public static int readIntegerCompressed8(final BitInputStream inputStream)
56             throws IOException {
57
58         if (inputStream.readBits(1) == 0)
59             return inputStream.readBits(8);
60         else
61             return inputStream.readBits(32);
62     }
63
64     public void decode() throws IOException {
65         approximator.load(bitInputStream);
66         approximator.computeLookupTables();
67
68         final WritableRaster raster = image.bufferedImage.getRaster();
69         final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
70         final byte[] pixels = dbi.getData();
71
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);
76
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]);
81
82         color.YUV2RGB();
83
84         pixels[0] = (byte) color.r;
85         pixels[0 + 1] = (byte) color.g;
86         pixels[0 + 2] = (byte) color.b;
87
88         // detect initial step
89         int largestDimension;
90         int initialStep = 2;
91         if (width > height)
92             largestDimension = width;
93         else
94             largestDimension = height;
95
96         while (initialStep < largestDimension)
97             initialStep = initialStep * 2;
98
99         grid(initialStep, pixels);
100     }
101
102     private void grid(final int step, final byte[] pixels) throws IOException {
103
104         gridDiagonal(step / 2, step / 2, step, pixels);
105         gridSquare(step / 2, 0, step, pixels);
106         gridSquare(0, step / 2, step, pixels);
107
108         if (step > 2)
109             grid(step / 2, pixels);
110     }
111
112     private void gridDiagonal(final int offsetX, final int offsetY,
113                               final int step, final byte[] pixels) throws IOException {
114
115         for (int y = offsetY; y < height; y = y + step)
116             for (int x = offsetX; x < width; x = x + step) {
117
118                 final int halfStep = step / 2;
119
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);
125
126                 loadPixel(step, offsetX, offsetY, x, y, pixels,
127                         context.colorStats.getAverageY(),
128                         context.colorStats.getAverageU(),
129                         context.colorStats.getAverageV());
130
131             }
132     }
133
134     private void gridSquare(final int offsetX, final int offsetY,
135                             final int step, final byte[] pixels) throws IOException {
136
137         for (int y = offsetY; y < height; y = y + step)
138             for (int x = offsetX; x < width; x = x + step) {
139
140                 final int halfStep = step / 2;
141
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);
147
148                 loadPixel(step, offsetX, offsetY, x, y, pixels,
149                         context.colorStats.getAverageY(),
150                         context.colorStats.getAverageU(),
151                         context.colorStats.getAverageV());
152
153             }
154     }
155
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;
161
162         final int inheritedRange = ImageEncoder
163                 .byteToInt(decodedRangeMap[parentIndex]);
164         int computedRange = inheritedRange;
165
166         final int bitCount = table.proposeBitcountForRange(inheritedRange);
167         int computedRangeBitCount;
168         if (bitCount > 0) {
169
170             final int rangeDecreases = bitInputStream.readBits(1);
171             if (rangeDecreases != 0)
172                 computedRange = table.proposeDecreasedRange(inheritedRange);
173
174             decodedRangeMap[index] = (byte) computedRange;
175             computedRangeBitCount = table
176                     .proposeBitcountForRange(computedRange);
177
178             if (computedRangeBitCount > 0) {
179
180                 final int encodedDifference = bitInputStream
181                         .readBits(computedRangeBitCount);
182
183                 final int decodedDifference = ImageEncoder
184                         .decodeValueFromGivenBits(encodedDifference,
185                                 computedRange, computedRangeBitCount);
186
187                 decodedValue = averageDecodedValue - decodedDifference;
188                 if (decodedValue > 255)
189                     decodedValue = 255;
190                 if (decodedValue < 0)
191                     decodedValue = 0;
192             }
193         } else
194             decodedRangeMap[index] = (byte) inheritedRange;
195         decodedMap[index] = (byte) decodedValue;
196         return decodedValue;
197     }
198
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 {
203
204         final int index = (y * width) + x;
205
206         final int halfStep = step / 2;
207
208         int parentIndex;
209         if (offsetX > 0) {
210             if (offsetY > 0)
211                 // diagonal approach
212                 parentIndex = ((y - halfStep) * width) + (x - halfStep);
213             else
214                 // take left pixel
215                 parentIndex = (y * width) + (x - halfStep);
216         } else
217             // take upper pixel
218             parentIndex = ((y - halfStep) * width) + x;
219
220         final int colorBufferIndex = index * 3;
221
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);
229
230         color.YUV2RGB();
231
232         pixels[colorBufferIndex] = (byte) color.r;
233         pixels[colorBufferIndex + 1] = (byte) color.g;
234         pixels[colorBufferIndex + 2] = (byte) color.b;
235
236     }
237
238 }