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