Changed license to CC0
[imagesqueeze.git] / src / main / java / eu / svjatoslav / imagesqueeze / codec / ImageDecoder.java
index 37f3c0e..a131059 100755 (executable)
+/*
+ * 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;
 
-public class ImageDecoder {
-
-       int width, height;
-       Image image;
+class ImageDecoder {
+
+    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();
+
+    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];
 
-       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 (Image image, 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();
-               
-               WritableRaster raster = image.bufferedImage.getRaster();
-               DataBufferByte dbi = (DataBufferByte)raster.getDataBuffer();
-               byte [] pixels = dbi.getData();
+    }
 
-               // load top-, left-most pixel. 
-               decodedYMap[0] = (byte)bitInputStream.readBits(8);
-               decodedUMap[0] = (byte)bitInputStream.readBits(8);
-               decodedVMap[0] = (byte)bitInputStream.readBits(8);
-               
-               Color color = new Color();
-               color.y = ImageEncoder.byteToInt(decodedYMap[0]);
-               color.u = ImageEncoder.byteToInt(decodedUMap[0]);
-               color.v = ImageEncoder.byteToInt(decodedVMap[0]);
-               
-               color.YUV2RGB();
-               
-               pixels[0] = (byte)color.r;
-               pixels[0+1] = (byte)color.g;
-               pixels[0+2] = (byte)color.b;                            
-
-                               
-               // detect initial step
-               int largestDimension;
-               int initialStep = 2;
-               if (width > height) {
-                       largestDimension = width;
-               } else {
-                       largestDimension = height;
-               }
-               
-               while (initialStep < largestDimension){
-                       initialStep = initialStep * 2;
-               }
-
-               grid(initialStep, pixels);
-       }
-
-
-       public void grid(int step, byte [] pixels) throws IOException {
-
-               gridDiagonal(step / 2, step / 2, step, pixels);
-               gridSquare(step / 2, 0, step, pixels);
-               gridSquare(0, step / 2, step, pixels);
-
-               if (step > 2) grid(step / 2, pixels);
-       }
-
-
-       public void gridSquare(int offsetX, int offsetY, int step, byte [] pixels) throws IOException{
-
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-
-
-                               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());
-
-                       }                       
-               }               
-       }
-
-
-       public void gridDiagonal(int offsetX, int offsetY, int step, byte [] pixels) throws IOException{
-
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-
-                               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());
-
-                       }                       
-               }               
-       }
-
-
-       public void loadPixel(int step, int offsetX, int offsetY, int x, int y, byte[] pixels,
-                       int averageDecodedY, int averageDecodedU, int averageDecodedV)
-       throws IOException{
-
-               int index = (y * width) + x;
-
-               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;                                                                             
-               }
+    public static int readIntegerCompressed8(final BitInputStream inputStream)
+            throws IOException {
 
+        if (inputStream.readBits(1) == 0)
+            return inputStream.readBits(8);
+        else
+            return inputStream.readBits(32);
+    }
 
+    public void decode() throws IOException {
+        approximator.load(bitInputStream);
+        approximator.computeLookupTables();
 
-               int colorBufferIndex = index * 3;
+        final WritableRaster raster = image.bufferedImage.getRaster();
+        final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
+        final byte[] pixels = dbi.getData();
 
-               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;                             
+        // load top-, left-most pixel.
+        decodedYMap[0] = (byte) bitInputStream.readBits(8);
+        decodedUMap[0] = (byte) bitInputStream.readBits(8);
+        decodedVMap[0] = (byte) bitInputStream.readBits(8);
 
-       }
+        final Color color = new Color();
+        color.y = ImageEncoder.byteToInt(decodedYMap[0]);
+        color.u = ImageEncoder.byteToInt(decodedUMap[0]);
+        color.v = ImageEncoder.byteToInt(decodedVMap[0]);
 
+        color.YUV2RGB();
 
-       private int loadChannel(byte [] decodedRangeMap, byte [] decodedMap, Table table, int averageDecodedValue, int index,
-                       int parentIndex) throws IOException {
-               int decodedValue = averageDecodedValue;
+        pixels[0] = (byte) color.r;
+        pixels[0 + 1] = (byte) color.g;
+        pixels[0 + 2] = (byte) color.b;
 
-               int inheritedRange = ImageEncoder.byteToInt(decodedRangeMap[parentIndex]);
-               int computedRange = inheritedRange;
+        // detect initial step
+        int largestDimension;
+        int initialStep = 2;
+        if (width > height)
+            largestDimension = width;
+        else
+            largestDimension = height;
+
+        while (initialStep < largestDimension)
+            initialStep = initialStep * 2;
 
-               int bitCount = table.proposeBitcountForRange(inheritedRange);
-               int computedRangeBitCount = 0;
-               if ( bitCount > 0){
+        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);
 
-                       int rangeDecreases = bitInputStream.readBits(1);
-                       if (rangeDecreases != 0){
-                               computedRange = table.proposeDecreasedRange(inheritedRange);
-                       }                               
-
-                       decodedRangeMap[index] = (byte)computedRange;
-                       computedRangeBitCount = table.proposeBitcountForRange(computedRange);
-
-                       if (computedRangeBitCount > 0){
-
-                               int encodedDifference = bitInputStream.readBits(computedRangeBitCount);
-
-                               int decodedDifference = ImageEncoder.decodeValueFromGivenBits(encodedDifference, computedRange, computedRangeBitCount);
+        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;
+    }
 
-                               decodedValue = averageDecodedValue - decodedDifference;
-                               if (decodedValue > 255) decodedValue = 255;
-                               if (decodedValue < 0) decodedValue = 0;
-                       }
-               } else {
-                       decodedRangeMap[index] = (byte)inheritedRange;
-               }
-               decodedMap[index] = (byte)decodedValue;
-               return decodedValue;
-       }
+    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;
+
+    }
 
 }