/*
- * Imagesqueeze - Image codec optimized for photos.
- * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of version 2 of the GNU General Public License
- * as published by the Free Software Foundation.
+ * Image codec. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
*/
-
package eu.svjatoslav.imagesqueeze.codec;
/**
* Compressed image pixels decoder.
*/
+import eu.svjatoslav.commons.data.BitInputStream;
+
import java.awt.image.DataBufferByte;
import java.awt.image.WritableRaster;
import java.io.IOException;
-import eu.svjatoslav.commons.data.BitInputStream;
-
-public class ImageDecoder {
-
- public static int readIntegerCompressed8(final BitInputStream inputStream)
- throws IOException {
-
- if (inputStream.readBits(1) == 0)
- return inputStream.readBits(8);
- else
- return inputStream.readBits(32);
- }
-
- int width, height;
-
- Image image;
- byte[] decodedYRangeMap;
-
- byte[] decodedYMap;
- byte[] decodedURangeMap;
-
- byte[] decodedUMap;
- byte[] decodedVRangeMap;
-
- byte[] decodedVMap;
-
- Color tmpColor = new Color();
- Approximator approximator;
-
- BitInputStream bitInputStream;
- ColorStats colorStats = new ColorStats();
-
- OperatingContext context = new OperatingContext();
-
- public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
- approximator = new Approximator();
-
- this.image = image;
- this.bitInputStream = bitInputStream;
-
- width = image.metaData.width;
- height = image.metaData.height;
-
- decodedYRangeMap = new byte[width * height];
- decodedYRangeMap[0] = (byte) (255);
- decodedYMap = new byte[width * height];
-
- decodedURangeMap = new byte[width * height];
- decodedURangeMap[0] = (byte) (255);
- decodedUMap = new byte[width * height];
-
- decodedVRangeMap = new byte[width * height];
- decodedVRangeMap[0] = (byte) (255);
- decodedVMap = new byte[width * height];
-
- }
-
- public void decode() throws IOException {
- approximator.load(bitInputStream);
- approximator.computeLookupTables();
-
- final WritableRaster raster = image.bufferedImage.getRaster();
- final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
- final byte[] pixels = dbi.getData();
+class ImageDecoder {
- // load top-, left-most pixel.
- decodedYMap[0] = (byte) bitInputStream.readBits(8);
- decodedUMap[0] = (byte) bitInputStream.readBits(8);
- decodedVMap[0] = (byte) bitInputStream.readBits(8);
+ private final int width;
+ private final int height;
+ private final Image image;
+ private final byte[] decodedYRangeMap;
+ private final byte[] decodedYMap;
+ private final byte[] decodedURangeMap;
+ private final byte[] decodedUMap;
+ private final byte[] decodedVRangeMap;
+ private final byte[] decodedVMap;
+ private final Approximator approximator;
+ private final BitInputStream bitInputStream;
+ private final OperatingContext context = new OperatingContext();
- final Color color = new Color();
- color.y = ImageEncoder.byteToInt(decodedYMap[0]);
- color.u = ImageEncoder.byteToInt(decodedUMap[0]);
- color.v = ImageEncoder.byteToInt(decodedVMap[0]);
+ public ImageDecoder(final Image image, final BitInputStream bitInputStream) {
+ approximator = new Approximator();
- color.YUV2RGB();
+ this.image = image;
+ this.bitInputStream = bitInputStream;
- pixels[0] = (byte) color.r;
- pixels[0 + 1] = (byte) color.g;
- pixels[0 + 2] = (byte) color.b;
+ width = image.metaData.width;
+ height = image.metaData.height;
- // detect initial step
- int largestDimension;
- int initialStep = 2;
- if (width > height)
- largestDimension = width;
- else
- largestDimension = height;
+ decodedYRangeMap = new byte[width * height];
+ decodedYRangeMap[0] = (byte) (255);
+ decodedYMap = new byte[width * height];
- while (initialStep < largestDimension)
- initialStep = initialStep * 2;
+ decodedURangeMap = new byte[width * height];
+ decodedURangeMap[0] = (byte) (255);
+ decodedUMap = new byte[width * height];
- grid(initialStep, pixels);
- }
+ decodedVRangeMap = new byte[width * height];
+ decodedVRangeMap[0] = (byte) (255);
+ decodedVMap = new byte[width * height];
- public void grid(final int step, final byte[] pixels) throws IOException {
+ }
- gridDiagonal(step / 2, step / 2, step, pixels);
- gridSquare(step / 2, 0, step, pixels);
- gridSquare(0, step / 2, step, pixels);
+ public static int readIntegerCompressed8(final BitInputStream inputStream)
+ throws IOException {
- if (step > 2)
- grid(step / 2, pixels);
- }
+ if (inputStream.readBits(1) == 0)
+ return inputStream.readBits(8);
+ else
+ return inputStream.readBits(32);
+ }
- public void gridDiagonal(final int offsetX, final int offsetY,
- final int step, final byte[] pixels) throws IOException {
+ public void decode() throws IOException {
+ approximator.load(bitInputStream);
+ approximator.computeLookupTables();
- for (int y = offsetY; y < height; y = y + step)
- for (int x = offsetX; x < width; x = x + step) {
+ final WritableRaster raster = image.bufferedImage.getRaster();
+ final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
+ final byte[] pixels = dbi.getData();
- final int halfStep = step / 2;
+ // load top-, left-most pixel.
+ decodedYMap[0] = (byte) bitInputStream.readBits(8);
+ decodedUMap[0] = (byte) bitInputStream.readBits(8);
+ decodedVMap[0] = (byte) bitInputStream.readBits(8);
- context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
- context.measureNeighborEncode(x - halfStep, y - halfStep);
- context.measureNeighborEncode(x + halfStep, y - halfStep);
- context.measureNeighborEncode(x - halfStep, y + halfStep);
- context.measureNeighborEncode(x + halfStep, y + halfStep);
+ final Color color = new Color();
+ color.y = ImageEncoder.byteToInt(decodedYMap[0]);
+ color.u = ImageEncoder.byteToInt(decodedUMap[0]);
+ color.v = ImageEncoder.byteToInt(decodedVMap[0]);
- loadPixel(step, offsetX, offsetY, x, y, pixels,
- context.colorStats.getAverageY(),
- context.colorStats.getAverageU(),
- context.colorStats.getAverageV());
+ color.YUV2RGB();
- }
- }
+ pixels[0] = (byte) color.r;
+ pixels[0 + 1] = (byte) color.g;
+ pixels[0 + 2] = (byte) color.b;
- public void gridSquare(final int offsetX, final int offsetY,
- final int step, final byte[] pixels) throws IOException {
+ // detect initial step
+ int largestDimension;
+ int initialStep = 2;
+ if (width > height)
+ largestDimension = width;
+ else
+ largestDimension = height;
+
+ while (initialStep < largestDimension)
+ initialStep = initialStep * 2;
- for (int y = offsetY; y < height; y = y + step)
- for (int x = offsetX; x < width; x = x + step) {
+ grid(initialStep, pixels);
+ }
+
+ private void grid(final int step, final byte[] pixels) throws IOException {
+
+ gridDiagonal(step / 2, step / 2, step, pixels);
+ gridSquare(step / 2, 0, step, pixels);
+ gridSquare(0, step / 2, step, pixels);
- final int halfStep = step / 2;
-
- context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
- context.measureNeighborEncode(x - halfStep, y);
- context.measureNeighborEncode(x + halfStep, y);
- context.measureNeighborEncode(x, y - halfStep);
- context.measureNeighborEncode(x, y + halfStep);
-
- loadPixel(step, offsetX, offsetY, x, y, pixels,
- context.colorStats.getAverageY(),
- context.colorStats.getAverageU(),
- context.colorStats.getAverageV());
-
- }
- }
-
- private int loadChannel(final byte[] decodedRangeMap,
- final byte[] decodedMap, final Table table,
- final int averageDecodedValue, final int index,
- final int parentIndex) throws IOException {
- int decodedValue = averageDecodedValue;
-
- final int inheritedRange = ImageEncoder
- .byteToInt(decodedRangeMap[parentIndex]);
- int computedRange = inheritedRange;
-
- final int bitCount = table.proposeBitcountForRange(inheritedRange);
- int computedRangeBitCount = 0;
- if (bitCount > 0) {
-
- final int rangeDecreases = bitInputStream.readBits(1);
- if (rangeDecreases != 0)
- computedRange = table.proposeDecreasedRange(inheritedRange);
-
- decodedRangeMap[index] = (byte) computedRange;
- computedRangeBitCount = table
- .proposeBitcountForRange(computedRange);
-
- if (computedRangeBitCount > 0) {
-
- final int encodedDifference = bitInputStream
- .readBits(computedRangeBitCount);
-
- final int decodedDifference = ImageEncoder
- .decodeValueFromGivenBits(encodedDifference,
- computedRange, computedRangeBitCount);
-
- decodedValue = averageDecodedValue - decodedDifference;
- if (decodedValue > 255)
- decodedValue = 255;
- if (decodedValue < 0)
- decodedValue = 0;
- }
- } else
- decodedRangeMap[index] = (byte) inheritedRange;
- decodedMap[index] = (byte) decodedValue;
- return decodedValue;
- }
+ if (step > 2)
+ grid(step / 2, pixels);
+ }
+
+ private void gridDiagonal(final int offsetX, final int offsetY,
+ final int step, final byte[] pixels) throws IOException {
+
+ for (int y = offsetY; y < height; y = y + step)
+ for (int x = offsetX; x < width; x = x + step) {
+
+ final int halfStep = step / 2;
+
+ context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
+ context.measureNeighborEncode(x - halfStep, y - halfStep);
+ context.measureNeighborEncode(x + halfStep, y - halfStep);
+ context.measureNeighborEncode(x - halfStep, y + halfStep);
+ context.measureNeighborEncode(x + halfStep, y + halfStep);
+
+ loadPixel(step, offsetX, offsetY, x, y, pixels,
+ context.colorStats.getAverageY(),
+ context.colorStats.getAverageU(),
+ context.colorStats.getAverageV());
+
+ }
+ }
+
+ private void gridSquare(final int offsetX, final int offsetY,
+ final int step, final byte[] pixels) throws IOException {
+
+ for (int y = offsetY; y < height; y = y + step)
+ for (int x = offsetX; x < width; x = x + step) {
+
+ final int halfStep = step / 2;
+
+ context.initialize(image, decodedYMap, decodedUMap, decodedVMap);
+ context.measureNeighborEncode(x - halfStep, y);
+ context.measureNeighborEncode(x + halfStep, y);
+ context.measureNeighborEncode(x, y - halfStep);
+ context.measureNeighborEncode(x, y + halfStep);
+
+ loadPixel(step, offsetX, offsetY, x, y, pixels,
+ context.colorStats.getAverageY(),
+ context.colorStats.getAverageU(),
+ context.colorStats.getAverageV());
+
+ }
+ }
+
+ private int loadChannel(final byte[] decodedRangeMap,
+ final byte[] decodedMap, final Table table,
+ final int averageDecodedValue, final int index,
+ final int parentIndex) throws IOException {
+ int decodedValue = averageDecodedValue;
+
+ final int inheritedRange = ImageEncoder
+ .byteToInt(decodedRangeMap[parentIndex]);
+ int computedRange = inheritedRange;
+
+ final int bitCount = table.proposeBitcountForRange(inheritedRange);
+ int computedRangeBitCount;
+ if (bitCount > 0) {
+
+ final int rangeDecreases = bitInputStream.readBits(1);
+ if (rangeDecreases != 0)
+ computedRange = table.proposeDecreasedRange(inheritedRange);
+
+ decodedRangeMap[index] = (byte) computedRange;
+ computedRangeBitCount = table
+ .proposeBitcountForRange(computedRange);
+
+ if (computedRangeBitCount > 0) {
+
+ final int encodedDifference = bitInputStream
+ .readBits(computedRangeBitCount);
+
+ final int decodedDifference = ImageEncoder
+ .decodeValueFromGivenBits(encodedDifference,
+ computedRange, computedRangeBitCount);
+
+ decodedValue = averageDecodedValue - decodedDifference;
+ if (decodedValue > 255)
+ decodedValue = 255;
+ if (decodedValue < 0)
+ decodedValue = 0;
+ }
+ } else
+ decodedRangeMap[index] = (byte) inheritedRange;
+ decodedMap[index] = (byte) decodedValue;
+ return decodedValue;
+ }
- public void loadPixel(final int step, final int offsetX, final int offsetY,
- final int x, final int y, final byte[] pixels,
- final int averageDecodedY, final int averageDecodedU,
- final int averageDecodedV) throws IOException {
-
- final int index = (y * width) + x;
-
- final int halfStep = step / 2;
-
- int parentIndex;
- if (offsetX > 0) {
- if (offsetY > 0)
- // diagonal approach
- parentIndex = ((y - halfStep) * width) + (x - halfStep);
- else
- // take left pixel
- parentIndex = (y * width) + (x - halfStep);
- } else
- // take upper pixel
- parentIndex = ((y - halfStep) * width) + x;
-
- final int colorBufferIndex = index * 3;
-
- final Color color = new Color();
- color.y = loadChannel(decodedYRangeMap, decodedYMap,
- approximator.yTable, averageDecodedY, index, parentIndex);
- color.u = loadChannel(decodedURangeMap, decodedUMap,
- approximator.uTable, averageDecodedU, index, parentIndex);
- color.v = loadChannel(decodedVRangeMap, decodedVMap,
- approximator.vTable, averageDecodedV, index, parentIndex);
-
- color.YUV2RGB();
-
- pixels[colorBufferIndex] = (byte) color.r;
- pixels[colorBufferIndex + 1] = (byte) color.g;
- pixels[colorBufferIndex + 2] = (byte) color.b;
-
- }
+ private void loadPixel(final int step, final int offsetX, final int offsetY,
+ final int x, final int y, final byte[] pixels,
+ final int averageDecodedY, final int averageDecodedU,
+ final int averageDecodedV) throws IOException {
+
+ final int index = (y * width) + x;
+
+ final int halfStep = step / 2;
+
+ int parentIndex;
+ if (offsetX > 0) {
+ if (offsetY > 0)
+ // diagonal approach
+ parentIndex = ((y - halfStep) * width) + (x - halfStep);
+ else
+ // take left pixel
+ parentIndex = (y * width) + (x - halfStep);
+ } else
+ // take upper pixel
+ parentIndex = ((y - halfStep) * width) + x;
+
+ final int colorBufferIndex = index * 3;
+
+ final Color color = new Color();
+ color.y = loadChannel(decodedYRangeMap, decodedYMap,
+ approximator.yTable, averageDecodedY, index, parentIndex);
+ color.u = loadChannel(decodedURangeMap, decodedUMap,
+ approximator.uTable, averageDecodedU, index, parentIndex);
+ color.v = loadChannel(decodedVRangeMap, decodedVMap,
+ approximator.vTable, averageDecodedV, index, parentIndex);
+
+ color.YUV2RGB();
+
+ pixels[colorBufferIndex] = (byte) color.r;
+ pixels[colorBufferIndex + 1] = (byte) color.g;
+ pixels[colorBufferIndex + 2] = (byte) color.b;
+
+ }
}