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 {
27 byte[] decodedYRangeMap;
30 byte[] decodedURangeMap;
33 byte[] decodedVRangeMap;
36 Color tmpColor = new Color();
38 Approximator approximator;
39 BitInputStream bitInputStream;
41 ColorStats colorStats = new ColorStats();
42 OperatingContext context = new OperatingContext();
44 public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
45 approximator = new Approximator();
48 this.bitInputStream = bitInputStream;
50 width = image.metaData.width;
51 height = image.metaData.height;
53 decodedYRangeMap = new byte[width * height];
54 decodedYRangeMap[0] = (byte) (255);
55 decodedYMap = new byte[width * height];
57 decodedURangeMap = new byte[width * height];
58 decodedURangeMap[0] = (byte) (255);
59 decodedUMap = new byte[width * height];
61 decodedVRangeMap = new byte[width * height];
62 decodedVRangeMap[0] = (byte) (255);
63 decodedVMap = new byte[width * height];
67 public void decode() throws IOException {
68 approximator.load(bitInputStream);
69 approximator.computeLookupTables();
71 final WritableRaster raster = image.bufferedImage.getRaster();
72 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
73 final byte[] pixels = dbi.getData();
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);
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]);
87 pixels[0] = (byte) color.r;
88 pixels[0 + 1] = (byte) color.g;
89 pixels[0 + 2] = (byte) color.b;
91 // detect initial step
95 largestDimension = width;
97 largestDimension = height;
100 while (initialStep < largestDimension) {
101 initialStep = initialStep * 2;
104 grid(initialStep, pixels);
107 public void grid(final int step, final byte[] pixels) throws IOException {
109 gridDiagonal(step / 2, step / 2, step, pixels);
110 gridSquare(step / 2, 0, step, pixels);
111 gridSquare(0, step / 2, step, pixels);
114 grid(step / 2, pixels);
118 public void gridDiagonal(final int offsetX, final int offsetY,
119 final int step, final byte[] pixels) throws IOException {
121 for (int y = offsetY; y < height; y = y + step) {
122 for (int x = offsetX; x < width; x = x + step) {
124 final int halfStep = step / 2;
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);
132 loadPixel(step, offsetX, offsetY, x, y, pixels,
133 context.colorStats.getAverageY(),
134 context.colorStats.getAverageU(),
135 context.colorStats.getAverageV());
141 public void gridSquare(final int offsetX, final int offsetY,
142 final int step, final byte[] pixels) throws IOException {
144 for (int y = offsetY; y < height; y = y + step) {
145 for (int x = offsetX; x < width; x = x + step) {
147 final int halfStep = step / 2;
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);
155 loadPixel(step, offsetX, offsetY, x, y, pixels,
156 context.colorStats.getAverageY(),
157 context.colorStats.getAverageU(),
158 context.colorStats.getAverageV());
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;
170 final int inheritedRange = ImageEncoder
171 .byteToInt(decodedRangeMap[parentIndex]);
172 int computedRange = inheritedRange;
174 final int bitCount = table.proposeBitcountForRange(inheritedRange);
175 int computedRangeBitCount = 0;
178 final int rangeDecreases = bitInputStream.readBits(1);
179 if (rangeDecreases != 0) {
180 computedRange = table.proposeDecreasedRange(inheritedRange);
183 decodedRangeMap[index] = (byte) computedRange;
184 computedRangeBitCount = table
185 .proposeBitcountForRange(computedRange);
187 if (computedRangeBitCount > 0) {
189 final int encodedDifference = bitInputStream
190 .readBits(computedRangeBitCount);
192 final int decodedDifference = ImageEncoder
193 .decodeValueFromGivenBits(encodedDifference,
194 computedRange, computedRangeBitCount);
196 decodedValue = averageDecodedValue - decodedDifference;
197 if (decodedValue > 255) {
200 if (decodedValue < 0) {
205 decodedRangeMap[index] = (byte) inheritedRange;
207 decodedMap[index] = (byte) decodedValue;
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 {
216 final int index = (y * width) + x;
218 final int halfStep = step / 2;
224 parentIndex = ((y - halfStep) * width) + (x - halfStep);
227 parentIndex = (y * width) + (x - halfStep);
231 parentIndex = ((y - halfStep) * width) + x;
234 final int colorBufferIndex = index * 3;
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);
246 pixels[colorBufferIndex] = (byte) color.r;
247 pixels[colorBufferIndex + 1] = (byte) color.g;
248 pixels[colorBufferIndex + 2] = (byte) color.b;