2 * Imagesqueeze - Image codec optimized for photos.
3 * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
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.
10 package eu.svjatoslav.imagesqueeze.codec;
13 * Compressed image pixels decoder.
16 import java.awt.image.DataBufferByte;
17 import java.awt.image.WritableRaster;
18 import java.io.IOException;
20 import eu.svjatoslav.commons.data.BitInputStream;
22 public class ImageDecoder {
24 public static int readIntegerCompressed8(final BitInputStream inputStream)
27 if (inputStream.readBits(1) == 0)
28 return inputStream.readBits(8);
30 return inputStream.readBits(32);
36 byte[] decodedYRangeMap;
39 byte[] decodedURangeMap;
42 byte[] decodedVRangeMap;
46 Color tmpColor = new Color();
47 Approximator approximator;
49 BitInputStream bitInputStream;
50 ColorStats colorStats = new ColorStats();
52 OperatingContext context = new OperatingContext();
54 public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
55 approximator = new Approximator();
58 this.bitInputStream = bitInputStream;
60 width = image.metaData.width;
61 height = image.metaData.height;
63 decodedYRangeMap = new byte[width * height];
64 decodedYRangeMap[0] = (byte) (255);
65 decodedYMap = new byte[width * height];
67 decodedURangeMap = new byte[width * height];
68 decodedURangeMap[0] = (byte) (255);
69 decodedUMap = new byte[width * height];
71 decodedVRangeMap = new byte[width * height];
72 decodedVRangeMap[0] = (byte) (255);
73 decodedVMap = new byte[width * height];
77 public void decode() throws IOException {
78 approximator.load(bitInputStream);
79 approximator.computeLookupTables();
81 final WritableRaster raster = image.bufferedImage.getRaster();
82 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
83 final byte[] pixels = dbi.getData();
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);
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]);
97 pixels[0] = (byte) color.r;
98 pixels[0 + 1] = (byte) color.g;
99 pixels[0 + 2] = (byte) color.b;
101 // detect initial step
102 int largestDimension;
105 largestDimension = width;
107 largestDimension = height;
109 while (initialStep < largestDimension)
110 initialStep = initialStep * 2;
112 grid(initialStep, pixels);
115 public void grid(final int step, final byte[] pixels) throws IOException {
117 gridDiagonal(step / 2, step / 2, step, pixels);
118 gridSquare(step / 2, 0, step, pixels);
119 gridSquare(0, step / 2, step, pixels);
122 grid(step / 2, pixels);
125 public void gridDiagonal(final int offsetX, final int offsetY,
126 final int step, final byte[] pixels) throws IOException {
128 for (int y = offsetY; y < height; y = y + step)
129 for (int x = offsetX; x < width; x = x + step) {
131 final int halfStep = step / 2;
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);
139 loadPixel(step, offsetX, offsetY, x, y, pixels,
140 context.colorStats.getAverageY(),
141 context.colorStats.getAverageU(),
142 context.colorStats.getAverageV());
147 public void gridSquare(final int offsetX, final int offsetY,
148 final int step, final byte[] pixels) throws IOException {
150 for (int y = offsetY; y < height; y = y + step)
151 for (int x = offsetX; x < width; x = x + step) {
153 final int halfStep = step / 2;
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);
161 loadPixel(step, offsetX, offsetY, x, y, pixels,
162 context.colorStats.getAverageY(),
163 context.colorStats.getAverageU(),
164 context.colorStats.getAverageV());
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;
175 final int inheritedRange = ImageEncoder
176 .byteToInt(decodedRangeMap[parentIndex]);
177 int computedRange = inheritedRange;
179 final int bitCount = table.proposeBitcountForRange(inheritedRange);
180 int computedRangeBitCount = 0;
183 final int rangeDecreases = bitInputStream.readBits(1);
184 if (rangeDecreases != 0)
185 computedRange = table.proposeDecreasedRange(inheritedRange);
187 decodedRangeMap[index] = (byte) computedRange;
188 computedRangeBitCount = table
189 .proposeBitcountForRange(computedRange);
191 if (computedRangeBitCount > 0) {
193 final int encodedDifference = bitInputStream
194 .readBits(computedRangeBitCount);
196 final int decodedDifference = ImageEncoder
197 .decodeValueFromGivenBits(encodedDifference,
198 computedRange, computedRangeBitCount);
200 decodedValue = averageDecodedValue - decodedDifference;
201 if (decodedValue > 255)
203 if (decodedValue < 0)
207 decodedRangeMap[index] = (byte) inheritedRange;
208 decodedMap[index] = (byte) decodedValue;
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 {
217 final int index = (y * width) + x;
219 final int halfStep = step / 2;
225 parentIndex = ((y - halfStep) * width) + (x - halfStep);
228 parentIndex = (y * width) + (x - halfStep);
231 parentIndex = ((y - halfStep) * width) + x;
233 final int colorBufferIndex = index * 3;
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);
245 pixels[colorBufferIndex] = (byte) color.r;
246 pixels[colorBufferIndex + 1] = (byte) color.g;
247 pixels[colorBufferIndex + 2] = (byte) color.b;