minor fixes
[imagesqueeze.git] / src / main / java / eu / svjatoslav / imagesqueeze / codec / ImageDecoder.java
1 /*
2  * Imagesqueeze - Image codec optimized for photos.
3  * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU General Public License
7  * as published by the Free Software Foundation.
8  */
9
10 package eu.svjatoslav.imagesqueeze.codec;
11
12 /**
13  * Compressed image pixels decoder.
14  */
15
16 import java.awt.image.DataBufferByte;
17 import java.awt.image.WritableRaster;
18 import java.io.IOException;
19
20 import eu.svjatoslav.commons.data.BitInputStream;
21
22 public class ImageDecoder {
23
24         int width, height;
25         Image image;
26
27         byte[] decodedYRangeMap;
28         byte[] decodedYMap;
29
30         byte[] decodedURangeMap;
31         byte[] decodedUMap;
32
33         byte[] decodedVRangeMap;
34         byte[] decodedVMap;
35
36         Color tmpColor = new Color();
37
38         Approximator approximator;
39         BitInputStream bitInputStream;
40
41         ColorStats colorStats = new ColorStats();
42         OperatingContext context = new OperatingContext();
43
44         public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
45                 approximator = new Approximator();
46
47                 this.image = image;
48                 this.bitInputStream = bitInputStream;
49
50                 width = image.metaData.width;
51                 height = image.metaData.height;
52
53                 decodedYRangeMap = new byte[width * height];
54                 decodedYRangeMap[0] = (byte) (255);
55                 decodedYMap = new byte[width * height];
56
57                 decodedURangeMap = new byte[width * height];
58                 decodedURangeMap[0] = (byte) (255);
59                 decodedUMap = new byte[width * height];
60
61                 decodedVRangeMap = new byte[width * height];
62                 decodedVRangeMap[0] = (byte) (255);
63                 decodedVMap = new byte[width * height];
64
65         }
66
67         public void decode() throws IOException {
68                 approximator.load(bitInputStream);
69                 approximator.computeLookupTables();
70
71                 final WritableRaster raster = image.bufferedImage.getRaster();
72                 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
73                 final byte[] pixels = dbi.getData();
74
75                 // load top-, left-most pixel.
76                 decodedYMap[0] = (byte) bitInputStream.readBits(8);
77                 decodedUMap[0] = (byte) bitInputStream.readBits(8);
78                 decodedVMap[0] = (byte) bitInputStream.readBits(8);
79
80                 final Color color = new Color();
81                 color.y = ImageEncoder.byteToInt(decodedYMap[0]);
82                 color.u = ImageEncoder.byteToInt(decodedUMap[0]);
83                 color.v = ImageEncoder.byteToInt(decodedVMap[0]);
84
85                 color.YUV2RGB();
86
87                 pixels[0] = (byte) color.r;
88                 pixels[0 + 1] = (byte) color.g;
89                 pixels[0 + 2] = (byte) color.b;
90
91                 // detect initial step
92                 int largestDimension;
93                 int initialStep = 2;
94                 if (width > height) {
95                         largestDimension = width;
96                 } else {
97                         largestDimension = height;
98                 }
99
100                 while (initialStep < largestDimension) {
101                         initialStep = initialStep * 2;
102                 }
103
104                 grid(initialStep, pixels);
105         }
106
107         public void grid(final int step, final byte[] pixels) throws IOException {
108
109                 gridDiagonal(step / 2, step / 2, step, pixels);
110                 gridSquare(step / 2, 0, step, pixels);
111                 gridSquare(0, step / 2, step, pixels);
112
113                 if (step > 2) {
114                         grid(step / 2, pixels);
115                 }
116         }
117
118         public void gridDiagonal(final int offsetX, final int offsetY,
119                         final int step, final byte[] pixels) throws IOException {
120
121                 for (int y = offsetY; y < height; y = y + step) {
122                         for (int x = offsetX; x < width; x = x + step) {
123
124                                 final int halfStep = step / 2;
125
126                                 context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
127                                 context.measureNeighborEncode(x - halfStep, y - halfStep);
128                                 context.measureNeighborEncode(x + halfStep, y - halfStep);
129                                 context.measureNeighborEncode(x - halfStep, y + halfStep);
130                                 context.measureNeighborEncode(x + halfStep, y + halfStep);
131
132                                 loadPixel(step, offsetX, offsetY, x, y, pixels,
133                                                 context.colorStats.getAverageY(),
134                                                 context.colorStats.getAverageU(),
135                                                 context.colorStats.getAverageV());
136
137                         }
138                 }
139         }
140
141         public void gridSquare(final int offsetX, final int offsetY,
142                         final int step, final byte[] pixels) throws IOException {
143
144                 for (int y = offsetY; y < height; y = y + step) {
145                         for (int x = offsetX; x < width; x = x + step) {
146
147                                 final int halfStep = step / 2;
148
149                                 context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
150                                 context.measureNeighborEncode(x - halfStep, y);
151                                 context.measureNeighborEncode(x + halfStep, y);
152                                 context.measureNeighborEncode(x, y - halfStep);
153                                 context.measureNeighborEncode(x, y + halfStep);
154
155                                 loadPixel(step, offsetX, offsetY, x, y, pixels,
156                                                 context.colorStats.getAverageY(),
157                                                 context.colorStats.getAverageU(),
158                                                 context.colorStats.getAverageV());
159
160                         }
161                 }
162         }
163
164         private int loadChannel(final byte[] decodedRangeMap,
165                         final byte[] decodedMap, final Table table,
166                         final int averageDecodedValue, final int index,
167                         final int parentIndex) throws IOException {
168                 int decodedValue = averageDecodedValue;
169
170                 final int inheritedRange = ImageEncoder
171                                 .byteToInt(decodedRangeMap[parentIndex]);
172                 int computedRange = inheritedRange;
173
174                 final int bitCount = table.proposeBitcountForRange(inheritedRange);
175                 int computedRangeBitCount = 0;
176                 if (bitCount > 0) {
177
178                         final int rangeDecreases = bitInputStream.readBits(1);
179                         if (rangeDecreases != 0) {
180                                 computedRange = table.proposeDecreasedRange(inheritedRange);
181                         }
182
183                         decodedRangeMap[index] = (byte) computedRange;
184                         computedRangeBitCount = table
185                                         .proposeBitcountForRange(computedRange);
186
187                         if (computedRangeBitCount > 0) {
188
189                                 final int encodedDifference = bitInputStream
190                                                 .readBits(computedRangeBitCount);
191
192                                 final int decodedDifference = ImageEncoder
193                                                 .decodeValueFromGivenBits(encodedDifference,
194                                                                 computedRange, computedRangeBitCount);
195
196                                 decodedValue = averageDecodedValue - decodedDifference;
197                                 if (decodedValue > 255) {
198                                         decodedValue = 255;
199                                 }
200                                 if (decodedValue < 0) {
201                                         decodedValue = 0;
202                                 }
203                         }
204                 } else {
205                         decodedRangeMap[index] = (byte) inheritedRange;
206                 }
207                 decodedMap[index] = (byte) decodedValue;
208                 return decodedValue;
209         }
210
211         public void loadPixel(final int step, final int offsetX, final int offsetY,
212                         final int x, final int y, final byte[] pixels,
213                         final int averageDecodedY, final int averageDecodedU,
214                         final int averageDecodedV) throws IOException {
215
216                 final int index = (y * width) + x;
217
218                 final int halfStep = step / 2;
219
220                 int parentIndex;
221                 if (offsetX > 0) {
222                         if (offsetY > 0) {
223                                 // diagonal approach
224                                 parentIndex = ((y - halfStep) * width) + (x - halfStep);
225                         } else {
226                                 // take left pixel
227                                 parentIndex = (y * width) + (x - halfStep);
228                         }
229                 } else {
230                         // take upper pixel
231                         parentIndex = ((y - halfStep) * width) + x;
232                 }
233
234                 final int colorBufferIndex = index * 3;
235
236                 final Color color = new Color();
237                 color.y = loadChannel(decodedYRangeMap, decodedYMap,
238                                 approximator.yTable, averageDecodedY, index, parentIndex);
239                 color.u = loadChannel(decodedURangeMap, decodedUMap,
240                                 approximator.uTable, averageDecodedU, index, parentIndex);
241                 color.v = loadChannel(decodedVRangeMap, decodedVMap,
242                                 approximator.vTable, averageDecodedV, index, parentIndex);
243
244                 color.YUV2RGB();
245
246                 pixels[colorBufferIndex] = (byte) color.r;
247                 pixels[colorBufferIndex + 1] = (byte) color.g;
248                 pixels[colorBufferIndex + 2] = (byte) color.b;
249
250         }
251
252 }