Improved IntelliJ startup script.
[imagesqueeze.git] / src / main / java / eu / svjatoslav / imagesqueeze / codec / ImageDecoder.java
1 /*
2  * Imagesqueeze - Image codec. Copyright ©2012-2019, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3  *
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.
7  */
8
9 package eu.svjatoslav.imagesqueeze.codec;
10
11 /**
12  * Compressed image pixels decoder.
13  */
14
15 import eu.svjatoslav.commons.data.BitInputStream;
16
17 import java.awt.image.DataBufferByte;
18 import java.awt.image.WritableRaster;
19 import java.io.IOException;
20
21 class ImageDecoder {
22
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();
35
36     public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
37         approximator = new Approximator();
38
39         this.image = image;
40         this.bitInputStream = bitInputStream;
41
42         width = image.metaData.width;
43         height = image.metaData.height;
44
45         decodedYRangeMap = new byte[width * height];
46         decodedYRangeMap[0] = (byte) (255);
47         decodedYMap = new byte[width * height];
48
49         decodedURangeMap = new byte[width * height];
50         decodedURangeMap[0] = (byte) (255);
51         decodedUMap = new byte[width * height];
52
53         decodedVRangeMap = new byte[width * height];
54         decodedVRangeMap[0] = (byte) (255);
55         decodedVMap = new byte[width * height];
56
57     }
58
59     public static int readIntegerCompressed8(final BitInputStream inputStream)
60             throws IOException {
61
62         if (inputStream.readBits(1) == 0)
63             return inputStream.readBits(8);
64         else
65             return inputStream.readBits(32);
66     }
67
68     public void decode() throws IOException {
69         approximator.load(bitInputStream);
70         approximator.computeLookupTables();
71
72         final WritableRaster raster = image.bufferedImage.getRaster();
73         final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
74         final byte[] pixels = dbi.getData();
75
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);
80
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]);
85
86         color.YUV2RGB();
87
88         pixels[0] = (byte) color.r;
89         pixels[0 + 1] = (byte) color.g;
90         pixels[0 + 2] = (byte) color.b;
91
92         // detect initial step
93         int largestDimension;
94         int initialStep = 2;
95         if (width > height)
96             largestDimension = width;
97         else
98             largestDimension = height;
99
100         while (initialStep < largestDimension)
101             initialStep = initialStep * 2;
102
103         grid(initialStep, pixels);
104     }
105
106     private void grid(final int step, final byte[] pixels) throws IOException {
107
108         gridDiagonal(step / 2, step / 2, step, pixels);
109         gridSquare(step / 2, 0, step, pixels);
110         gridSquare(0, step / 2, step, pixels);
111
112         if (step > 2)
113             grid(step / 2, pixels);
114     }
115
116     private void gridDiagonal(final int offsetX, final int offsetY,
117                               final int step, final byte[] pixels) throws IOException {
118
119         for (int y = offsetY; y < height; y = y + step)
120             for (int x = offsetX; x < width; x = x + step) {
121
122                 final int halfStep = step / 2;
123
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);
129
130                 loadPixel(step, offsetX, offsetY, x, y, pixels,
131                         context.colorStats.getAverageY(),
132                         context.colorStats.getAverageU(),
133                         context.colorStats.getAverageV());
134
135             }
136     }
137
138     private void gridSquare(final int offsetX, final int offsetY,
139                             final int step, final byte[] pixels) throws IOException {
140
141         for (int y = offsetY; y < height; y = y + step)
142             for (int x = offsetX; x < width; x = x + step) {
143
144                 final int halfStep = step / 2;
145
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);
151
152                 loadPixel(step, offsetX, offsetY, x, y, pixels,
153                         context.colorStats.getAverageY(),
154                         context.colorStats.getAverageU(),
155                         context.colorStats.getAverageV());
156
157             }
158     }
159
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;
165
166         final int inheritedRange = ImageEncoder
167                 .byteToInt(decodedRangeMap[parentIndex]);
168         int computedRange = inheritedRange;
169
170         final int bitCount = table.proposeBitcountForRange(inheritedRange);
171         int computedRangeBitCount;
172         if (bitCount > 0) {
173
174             final int rangeDecreases = bitInputStream.readBits(1);
175             if (rangeDecreases != 0)
176                 computedRange = table.proposeDecreasedRange(inheritedRange);
177
178             decodedRangeMap[index] = (byte) computedRange;
179             computedRangeBitCount = table
180                     .proposeBitcountForRange(computedRange);
181
182             if (computedRangeBitCount > 0) {
183
184                 final int encodedDifference = bitInputStream
185                         .readBits(computedRangeBitCount);
186
187                 final int decodedDifference = ImageEncoder
188                         .decodeValueFromGivenBits(encodedDifference,
189                                 computedRange, computedRangeBitCount);
190
191                 decodedValue = averageDecodedValue - decodedDifference;
192                 if (decodedValue > 255)
193                     decodedValue = 255;
194                 if (decodedValue < 0)
195                     decodedValue = 0;
196             }
197         } else
198             decodedRangeMap[index] = (byte) inheritedRange;
199         decodedMap[index] = (byte) decodedValue;
200         return decodedValue;
201     }
202
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 {
207
208         final int index = (y * width) + x;
209
210         final int halfStep = step / 2;
211
212         int parentIndex;
213         if (offsetX > 0) {
214             if (offsetY > 0)
215                 // diagonal approach
216                 parentIndex = ((y - halfStep) * width) + (x - halfStep);
217             else
218                 // take left pixel
219                 parentIndex = (y * width) + (x - halfStep);
220         } else
221             // take upper pixel
222             parentIndex = ((y - halfStep) * width) + x;
223
224         final int colorBufferIndex = index * 3;
225
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);
233
234         color.YUV2RGB();
235
236         pixels[colorBufferIndex] = (byte) color.r;
237         pixels[colorBufferIndex + 1] = (byte) color.g;
238         pixels[colorBufferIndex + 2] = (byte) color.b;
239
240     }
241
242 }