Changed license to CC0
[imagesqueeze.git] / src / main / java / eu / svjatoslav / imagesqueeze / codec / ImageEncoder.java
index 46c2135..0bf2fa8 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 encoder.
  */
 
+import eu.svjatoslav.commons.data.BitOutputStream;
+
 import java.awt.image.DataBufferByte;
 import java.awt.image.WritableRaster;
 import java.io.IOException;
 
+class ImageEncoder {
+
+    private final Image image;
+    private final Approximator approximator;
+    // ColorStats colorStats = new ColorStats();
+    private final OperatingContext context = new OperatingContext();
+    private final OperatingContext context2 = new OperatingContext();
+    int bitsForY;
+    int bitsForU;
+    int bitsForV;
+    private int width;
+    private int height;
+    private Channel yChannel;
+    private Channel uChannel;
+    private Channel vChannel;
+    private BitOutputStream bitOutputStream;
+
+    public ImageEncoder(final Image image) {
+        approximator = new Approximator();
+
+        // bitOutputStream = outputStream;
+
+        this.image = image;
+
+    }
+
+    public static int byteToInt(final byte input) {
+        int result = input;
+        if (result < 0)
+            result = result + 256;
+        return result;
+    }
+
+    public static int decodeValueFromGivenBits(final int encodedBits,
+                                               final int range, final int bitCount) {
+        final int negativeBit = encodedBits & 1;
+
+        final int remainingBitCount = bitCount - 1;
+
+        if (remainingBitCount == 0) {
+            // no more bits remaining to encode actual value
+
+            if (negativeBit == 0)
+                return range;
+            else
+                return -range;
+
+        } else {
+            // still one or more bits left, encode value as precisely as
+            // possible
+
+            final int encodedValue = (encodedBits >>> 1) + 1;
+
+            final int realvalueForThisBitcount = 1 << remainingBitCount;
+
+            // int valueMultiplier = range / realvalueForThisBitcount;
+            int decodedValue = (range * encodedValue)
+                    / realvalueForThisBitcount;
+
+            if (decodedValue > range)
+                decodedValue = range;
+
+            if (negativeBit == 0)
+                return decodedValue;
+            else
+                return -decodedValue;
+
+        }
+    }
+
+    private static int encodeValueIntoGivenBits(int value, final int range,
+                                                final int bitCount) {
+
+        int negativeBit = 0;
+
+        if (value < 0) {
+            negativeBit = 1;
+            value = -value;
+        }
+
+        final int remainingBitCount = bitCount - 1;
+
+        if (remainingBitCount == 0)
+            return negativeBit;
+        else {
+            // still one or more bits left, encode value as precisely as
+            // possible
+
+            if (value > range)
+                value = range;
+
+            final int realvalueForThisBitcount = 1 << remainingBitCount;
+            // int valueMultiplier = range / realvalueForThisBitcount;
+            int encodedValue = (value * realvalueForThisBitcount) / range;
+
+            if (encodedValue >= realvalueForThisBitcount)
+                encodedValue = realvalueForThisBitcount - 1;
+
+            encodedValue = (encodedValue << 1) + negativeBit;
+
+            return encodedValue;
+        }
+    }
+
+    public static void storeIntegerCompressed8(
+            final BitOutputStream outputStream, final int data)
+            throws IOException {
+
+        if (data < 256) {
+            outputStream.storeBits(0, 1);
+            outputStream.storeBits(data, 8);
+        } else {
+            outputStream.storeBits(1, 1);
+            outputStream.storeBits(data, 32);
+        }
+    }
+
+    public void encode(final BitOutputStream bitOutputStream)
+            throws IOException {
+        this.bitOutputStream = bitOutputStream;
+
+        approximator.initialize();
+
+        approximator.save(bitOutputStream);
+
+        width = image.metaData.width;
+        height = image.metaData.height;
+
+        final WritableRaster raster = image.bufferedImage.getRaster();
+        final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
+        final byte[] pixels = dbi.getData();
+
+        if (yChannel == null)
+            yChannel = new Channel(width, height);
+        else
+            yChannel.reset();
+
+        if (uChannel == null)
+            uChannel = new Channel(width, height);
+        else
+            uChannel.reset();
+
+        if (vChannel == null)
+            vChannel = new Channel(width, height);
+        else
+            vChannel.reset();
+
+        // create YUV map out of RGB raster data
+        final Color color = new Color();
+
+        for (int y = 0; y < height; y++)
+            for (int x = 0; x < width; x++) {
+
+                final int index = (y * width) + x;
+                final int colorBufferIndex = index * 3;
+
+                int blue = pixels[colorBufferIndex];
+                if (blue < 0)
+                    blue = blue + 256;
+
+                int green = pixels[colorBufferIndex + 1];
+                if (green < 0)
+                    green = green + 256;
+
+                int red = pixels[colorBufferIndex + 2];
+                if (red < 0)
+                    red = red + 256;
+
+                color.r = red;
+                color.g = green;
+                color.b = blue;
+
+                color.RGB2YUV();
+
+                yChannel.map[index] = (byte) color.y;
+                uChannel.map[index] = (byte) color.u;
+                vChannel.map[index] = (byte) color.v;
+            }
+
+        yChannel.decodedMap[0] = yChannel.map[0];
+        uChannel.decodedMap[0] = uChannel.map[0];
+        vChannel.decodedMap[0] = vChannel.map[0];
+
+        bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
+        bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
+        bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
+
+        // detect initial step
+        int largestDimension;
+        int initialStep = 2;
+        if (width > height)
+            largestDimension = width;
+        else
+            largestDimension = height;
+
+        while (initialStep < largestDimension)
+            initialStep = initialStep * 2;
+
+        rangeGrid(initialStep);
+        rangeRoundGrid(2);
+        saveGrid(initialStep);
+    }
+
+    private void encodeChannel(final Table table, final Channel channel,
+                               final int averageDecodedValue, final int index, final int value,
+                               final int range, final int parentIndex) throws IOException {
+
+        final byte[] decodedRangeMap = channel.decodedRangeMap;
+        final byte[] decodedMap = channel.decodedMap;
+
+        final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
+
+        final int inheritedBitCount = table
+                .proposeBitcountForRange(inheritedRange);
+
+        if (inheritedBitCount > 0) {
+            int computedRange;
+            computedRange = table.proposeRangeForRange(range, inheritedRange);
+            decodedRangeMap[index] = (byte) computedRange;
+
+            channel.bitCount++;
+            if (computedRange != inheritedRange)
+                // brightness range shrinked
+                bitOutputStream.storeBits(1, 1);
+            else
+                // brightness range stayed the same
+                bitOutputStream.storeBits(0, 1);
+
+            // encode brightness into available amount of bits
+            final int computedBitCount = table
+                    .proposeBitcountForRange(computedRange);
+
+            if (computedBitCount > 0) {
+
+                final int differenceToEncode = -(value - averageDecodedValue);
+                final int bitEncodedDifference = encodeValueIntoGivenBits(
+                        differenceToEncode, computedRange, computedBitCount);
+
+                channel.bitCount = channel.bitCount + computedBitCount;
+                bitOutputStream.storeBits(bitEncodedDifference,
+                        computedBitCount);
+
+                final int decodedDifference = decodeValueFromGivenBits(
+                        bitEncodedDifference, computedRange, computedBitCount);
+                int decodedValue = averageDecodedValue - decodedDifference;
+                if (decodedValue > 255)
+                    decodedValue = 255;
+                if (decodedValue < 0)
+                    decodedValue = 0;
+
+                decodedMap[index] = (byte) decodedValue;
+            } else
+                decodedMap[index] = (byte) averageDecodedValue;
+
+        } else {
+            decodedRangeMap[index] = (byte) inheritedRange;
+            decodedMap[index] = (byte) averageDecodedValue;
+        }
+    }
+
+    public void printStatistics() {
+        System.out.println("Y channel:");
+        yChannel.printStatistics();
+
+        System.out.println("U channel:");
+        uChannel.printStatistics();
+
+        System.out.println("V channel:");
+        vChannel.printStatistics();
+    }
+
+    private void rangeGrid(final int step) {
+
+        // gridSquare(step / 2, step / 2, step, pixels);
+
+        rangeGridDiagonal(step / 2, step / 2, step);
+        rangeGridSquare(step / 2, 0, step);
+        rangeGridSquare(0, step / 2, step);
+
+        if (step > 2)
+            rangeGrid(step / 2);
+    }
+
+    private void rangeGridDiagonal(final int offsetX, final int offsetY,
+                                   final int step) {
+        for (int y = offsetY; y < height; y = y + step)
+            for (int x = offsetX; x < width; x = x + step) {
+
+                final int index = (y * width) + x;
+                final int halfStep = step / 2;
+
+                context.initialize(image, yChannel.map, uChannel.map,
+                        vChannel.map);
+
+                context.measureNeighborEncode(x - halfStep, y - halfStep);
+                context.measureNeighborEncode(x + halfStep, y - halfStep);
+                context.measureNeighborEncode(x - halfStep, y + halfStep);
+                context.measureNeighborEncode(x + halfStep, y + halfStep);
+
+                yChannel.rangeMap[index] = (byte) context.getYRange(index);
+                uChannel.rangeMap[index] = (byte) context.getURange(index);
+                vChannel.rangeMap[index] = (byte) context.getVRange(index);
+            }
+    }
+
+    private void rangeGridSquare(final int offsetX, final int offsetY,
+                                 final int step) {
+        for (int y = offsetY; y < height; y = y + step)
+            for (int x = offsetX; x < width; x = x + step) {
+
+                final int index = (y * width) + x;
+                final int halfStep = step / 2;
+
+                context.initialize(image, yChannel.map, uChannel.map,
+                        vChannel.map);
+
+                context.measureNeighborEncode(x - halfStep, y);
+                context.measureNeighborEncode(x + halfStep, y);
+                context.measureNeighborEncode(x, y - halfStep);
+                context.measureNeighborEncode(x, y + halfStep);
+
+                yChannel.rangeMap[index] = (byte) context.getYRange(index);
+                uChannel.rangeMap[index] = (byte) context.getURange(index);
+                vChannel.rangeMap[index] = (byte) context.getVRange(index);
+            }
+    }
+
+    private void rangeRoundGrid(final int step) {
+
+        rangeRoundGridDiagonal(step / 2, step / 2, step);
+        rangeRoundGridSquare(step / 2, 0, step);
+        rangeRoundGridSquare(0, step / 2, step);
+
+        if (step < 1024)
+            rangeRoundGrid(step * 2);
+    }
+
+    private void rangeRoundGridDiagonal(final int offsetX, final int offsetY,
+                                        final int step) {
+        for (int y = offsetY; y < height; y = y + step)
+            for (int x = offsetX; x < width; x = x + step) {
+
+                final int index = (y * width) + x;
+
+                final int yRange = byteToInt(yChannel.rangeMap[index]);
+                final int uRange = byteToInt(uChannel.rangeMap[index]);
+                final int vRange = byteToInt(vChannel.rangeMap[index]);
+
+                final int halfStep = step / 2;
+
+                final int parentIndex = ((y - halfStep) * width)
+                        + (x - halfStep);
+
+                int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
+
+                if (parentYRange < yRange) {
+                    parentYRange = yRange;
+                    yChannel.rangeMap[parentIndex] = (byte) parentYRange;
+                }
+
+                int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
+
+                if (parentURange < uRange) {
+                    parentURange = uRange;
+                    uChannel.rangeMap[parentIndex] = (byte) parentURange;
+                }
 
-public class ImageEncoder {
-
-       Image image;
-       int width, height;
-
-       Channel yChannel;
-       Channel uChannel;
-       Channel vChannel;
-       
-       Approximator approximator;
-
-       int bitsForY;
-       int bitsForU;
-       int bitsForV;
-       
-       //ColorStats colorStats = new ColorStats();
-       OperatingContext context = new OperatingContext();
-       OperatingContext context2 = new OperatingContext();
-
-       BitOutputStream bitOutputStream;
-
-       public ImageEncoder(Image image){
-               approximator = new Approximator();
-
-               //bitOutputStream = outputStream;
-
-               this.image = image;
-
-       }
-
-
-       public void encode(BitOutputStream bitOutputStream) throws IOException {
-               this.bitOutputStream = bitOutputStream;
-               
-               approximator.initialize();
-               
-               approximator.save(bitOutputStream);
-               
-               width = image.metaData.width;
-               height = image.metaData.height;
-
-               WritableRaster raster = image.bufferedImage.getRaster();
-               DataBufferByte dbi = (DataBufferByte)raster.getDataBuffer();
-               byte [] pixels = dbi.getData();
-
-               if (yChannel == null){
-                       yChannel = new Channel(width, height);                  
-               } else {
-                       yChannel.reset();
-               }
-               
-               if (uChannel == null){
-                       uChannel = new Channel(width, height);                  
-               } else {
-                       uChannel.reset();
-               }
-               
-               if (vChannel == null){
-                       vChannel = new Channel(width, height);                  
-               } else {
-                       vChannel.reset();
-               }
-               
-               // create YUV map out of RGB raster data
-               Color color = new Color();
-               
-               for (int y=0; y < height; y++){
-                       for (int x=0; x < width; x++){
-
-                               int index = (y * width) + x;
-                               int colorBufferIndex = index * 3;
-
-                               int blue = pixels[colorBufferIndex];                            
-                               if (blue < 0) blue = blue + 256;
-
-                               int green = pixels[colorBufferIndex+1];
-                               if (green < 0) green = green + 256;
-
-                               int red = pixels[colorBufferIndex+2];
-                               if (red < 0) red = red + 256;
-
-                               color.r = red;
-                               color.g = green;
-                               color.b = blue;
-
-                               color.RGB2YUV();
-
-                               yChannel.map[index] = (byte)color.y;
-                               uChannel.map[index] = (byte)color.u;
-                               vChannel.map[index] = (byte)color.v;
-                       }
-               }
-
-               yChannel.decodedMap[0] = yChannel.map[0];
-               uChannel.decodedMap[0] = uChannel.map[0];
-               vChannel.decodedMap[0] = vChannel.map[0];
-               
-               bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);                                                               
-               bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);                                                               
-               bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);                                                               
-
-               // detect initial step
-               int largestDimension;
-               int initialStep = 2;
-               if (width > height) {
-                       largestDimension = width;
-               } else {
-                       largestDimension = height;
-               }
-               
-               while (initialStep < largestDimension){
-                       initialStep = initialStep * 2;
-               }
-                               
-               rangeGrid(initialStep);
-               rangeRoundGrid(2);
-               saveGrid(initialStep);
-       }
-
-       public void printStatistics(){
-               System.out.println("Y channel:");
-               yChannel.printStatistics();
-
-               System.out.println("U channel:");
-               uChannel.printStatistics();
-               
-               System.out.println("V channel:");
-               vChannel.printStatistics();             
-       }
-       
-       public void rangeGrid(int step){
-
-               //gridSquare(step / 2, step / 2, step, pixels);
-
-               rangeGridDiagonal(step / 2, step / 2, step);
-               rangeGridSquare(step / 2, 0, step);
-               rangeGridSquare(0, step / 2, step);
-
-               if (step > 2) rangeGrid(step / 2);
-       }
-
-
-       public void rangeRoundGrid(int step){
-
-               rangeRoundGridDiagonal(step / 2, step / 2, step);
-               rangeRoundGridSquare(step / 2, 0, step);
-               rangeRoundGridSquare(0, step / 2, step);
-
-               if (step < 1024) rangeRoundGrid(step * 2);
-       }
-
-       public void saveGrid(int step) throws IOException {
-
-               saveGridDiagonal(step / 2, step / 2, step);
-               saveGridSquare(step / 2, 0, step);
-               saveGridSquare(0, step / 2, step);
-
-               if (step > 2) saveGrid(step / 2);
-       }
-
-
-       public void rangeGridSquare(int offsetX, int offsetY, int step){
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-
-                               int index = (y * width) + x;
-                               int halfStep = step / 2;
-
-                               context.initialize(image, yChannel.map, uChannel.map, vChannel.map);
-                       
-                               context.measureNeighborEncode(x - halfStep, y);
-                               context.measureNeighborEncode(x + halfStep, y);
-                               context.measureNeighborEncode(x, y - halfStep);
-                               context.measureNeighborEncode(x, y + halfStep);
-
-                               yChannel.rangeMap[index] = (byte)context.getYRange(index);
-                               uChannel.rangeMap[index] = (byte)context.getURange(index);
-                               vChannel.rangeMap[index] = (byte)context.getVRange(index);
-                       }                       
-               }               
-       }
-
-       public void rangeGridDiagonal(int offsetX, int offsetY, int step){
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-
-                               int index = (y * width) + x;
-                               int halfStep = step / 2;
-
-                               context.initialize(image, yChannel.map, uChannel.map, vChannel.map);
-                               
-                               context.measureNeighborEncode(x - halfStep, y - halfStep);
-                               context.measureNeighborEncode(x + halfStep, y - halfStep);
-                               context.measureNeighborEncode(x - halfStep, y + halfStep);
-                               context.measureNeighborEncode(x + halfStep, y + halfStep);
-
-                               yChannel.rangeMap[index] = (byte)context.getYRange(index);
-                               uChannel.rangeMap[index] = (byte)context.getURange(index);
-                               vChannel.rangeMap[index] = (byte)context.getVRange(index);
-                       }                       
-               }               
-       }
-
-       public void rangeRoundGridDiagonal(int offsetX, int offsetY, int step){
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-
-                               int index = (y * width) + x;
-
-                               int yRange = byteToInt(yChannel.rangeMap[index]);
-                               int uRange = byteToInt(uChannel.rangeMap[index]);
-                               int vRange = byteToInt(vChannel.rangeMap[index]);
-
-                               int halfStep = step / 2;
-
-                               int parentIndex = ((y - halfStep) * width) + (x - halfStep);
-
-                               int parentYRange =  byteToInt(yChannel.rangeMap[parentIndex]);
-
-                               if (parentYRange < yRange){
-                                       parentYRange = yRange;
-                                       yChannel.rangeMap[parentIndex] = (byte)parentYRange;
-                               }
-
-                               int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
-
-                               if (parentURange < uRange){
-                                       parentURange = uRange;
-                                       uChannel.rangeMap[parentIndex] = (byte)parentURange;
-                               }
-
-                               int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
-
-                               if (parentVRange < vRange){
-                                       parentVRange = vRange;
-                                       vChannel.rangeMap[parentIndex] = (byte)parentVRange;
-                               }
-                       }                       
-               }               
-       }
-
-       public void rangeRoundGridSquare(int offsetX, int offsetY, int step){
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-
-                               int index = (y * width) + x;
-                               
-                               int yRange = byteToInt(yChannel.rangeMap[index]);
-                               int uRange = byteToInt(uChannel.rangeMap[index]);
-                               int vRange = byteToInt(vChannel.rangeMap[index]);
-
-                               int halfStep = step / 2;
-
-                               int parentIndex;
-                               if (offsetX > 0){
-                                       parentIndex = (y * width) + (x - halfStep);                                     
-                               } else {
-                                       parentIndex = ((y - halfStep) * width) + x;                                                                             
-                               }
-
-                               int parentYRange =  byteToInt(yChannel.rangeMap[parentIndex]);
-
-                               if (parentYRange < yRange){
-                                       parentYRange = yRange;
-                                       yChannel.rangeMap[parentIndex] = (byte)parentYRange;
-                               }
-
-                               int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
-
-                               if (parentURange < uRange){
-                                       parentURange = uRange;
-                                       uChannel.rangeMap[parentIndex] = (byte)parentURange;
-                               }
-
-                               int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
-
-                               if (parentVRange < vRange){
-                                       parentVRange = vRange;
-                                       vChannel.rangeMap[parentIndex] = (byte)parentVRange;
-                               }
-
-                       }                       
-               }               
-       }
-
-       public void saveGridSquare(int offsetX, int offsetY, int step) throws IOException{
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-
-                               int halfStep = step / 2;
-
-                               context2.initialize(image, yChannel.decodedMap, uChannel.decodedMap,  vChannel.decodedMap);
-                               context2.measureNeighborEncode(x - halfStep, y);
-                               context2.measureNeighborEncode(x + halfStep, y);
-                               context2.measureNeighborEncode(x, y - halfStep);
-                               context2.measureNeighborEncode(x, y + halfStep);
-                       
-                               
-                               savePixel(step, offsetX, offsetY, x, y,
-                                               context2.colorStats.getAverageY(),
-                                               context2.colorStats.getAverageU(),
-                                               context2.colorStats.getAverageV());                             
-
-                       }                       
-               }               
-       }
-
-       public void saveGridDiagonal(int offsetX, int offsetY, int step) throws IOException {
-               for (int y = offsetY; y < height; y = y + step){
-                       for (int x = offsetX; x < width; x = x + step){
-                               
-                               int halfStep = step / 2;
-
-                               context2.initialize(image, yChannel.decodedMap, uChannel.decodedMap,  vChannel.decodedMap);
-                               context2.measureNeighborEncode(x - halfStep, y - halfStep);
-                               context2.measureNeighborEncode(x + halfStep, y - halfStep);
-                               context2.measureNeighborEncode(x - halfStep, y + halfStep);
-                               context2.measureNeighborEncode(x + halfStep, y + halfStep);
-                       
-                               
-                               savePixel(step, offsetX, offsetY, x, y,
-                                               context2.colorStats.getAverageY(),
-                                               context2.colorStats.getAverageU(),
-                                               context2.colorStats.getAverageV());                             
-                               
-                       }                       
-               }               
-       }
-
-       public void savePixel(int step, int offsetX, int offsetY, int x, int y, int averageDecodedY, int averageDecodedU, int averageDecodedV) throws IOException {
-
-               int index = (y * width) + x;
-
-               int py = byteToInt(yChannel.map[index]);                
-               int pu = byteToInt(uChannel.map[index]);        
-               int pv = byteToInt(vChannel.map[index]);                
-               
-               int yRange = byteToInt(yChannel.rangeMap[index]);
-               int uRange = byteToInt(uChannel.rangeMap[index]);
-               int vRange = byteToInt(vChannel.rangeMap[index]);
-
-               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;                                                                             
-               }
-
-               encodeChannel(
-                               approximator.yTable,
-                               yChannel,
-                               averageDecodedY,
-                               index,
-                               py,
-                               yRange,
-                               parentIndex);
-
-               encodeChannel(
-                               approximator.uTable,
-                               uChannel,
-                               averageDecodedU,
-                               index,
-                               pu,
-                               uRange,
-                               parentIndex);
-
-               encodeChannel(
-                               approximator.vTable,
-                               vChannel,
-                               averageDecodedV,
-                               index,
-                               pv,
-                               vRange,
-                               parentIndex);
-
-       }
-
-
-       private void encodeChannel(Table table, Channel channel, int averageDecodedValue, int index,
-                       int value, int range, int parentIndex)
-                       throws IOException {
-               
-               byte[] decodedRangeMap = channel.decodedRangeMap;
-               byte[] decodedMap = channel.decodedMap;
-               
-               int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
-
-               int inheritedBitCount = table.proposeBitcountForRange(inheritedRange);
-
-               if (inheritedBitCount > 0){
-                       int computedRange;
-                       computedRange = table.proposeRangeForRange(range, inheritedRange);                                                                                                                              
-                       decodedRangeMap[index] = (byte)computedRange;
+                int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
 
-                       channel.bitCount++;
-                       if (computedRange != inheritedRange){
-                               // brightness range shrinked
-                               bitOutputStream.storeBits(1, 1);                        
-                       } else {
-                               // brightness range stayed the same
-                               bitOutputStream.storeBits(0, 1);                                                
-                       }
+                if (parentVRange < vRange) {
+                    parentVRange = vRange;
+                    vChannel.rangeMap[parentIndex] = (byte) parentVRange;
+                }
+            }
+    }
 
+    private void rangeRoundGridSquare(final int offsetX, final int offsetY,
+                                      final int step) {
+        for (int y = offsetY; y < height; y = y + step)
+            for (int x = offsetX; x < width; x = x + step) {
 
-                       // encode brightness into available amount of bits
-                       int computedBitCount = table.proposeBitcountForRange(computedRange);
-
-                       if (computedBitCount > 0){
+                final int index = (y * width) + x;
 
-                               int differenceToEncode = -(value - averageDecodedValue);
-                               int bitEncodedDifference = encodeValueIntoGivenBits(differenceToEncode, computedRange, computedBitCount);
+                final int yRange = byteToInt(yChannel.rangeMap[index]);
+                final int uRange = byteToInt(uChannel.rangeMap[index]);
+                final int vRange = byteToInt(vChannel.rangeMap[index]);
 
-                               channel.bitCount = channel.bitCount + computedBitCount;
-                               bitOutputStream.storeBits(bitEncodedDifference, computedBitCount);                                                              
+                final int halfStep = step / 2;
 
-                               int decodedDifference = decodeValueFromGivenBits(bitEncodedDifference, computedRange, computedBitCount);
-                               int decodedValue = averageDecodedValue - decodedDifference;
-                               if (decodedValue > 255) decodedValue = 255;
-                               if (decodedValue < 0) decodedValue = 0;
+                int parentIndex;
+                if (offsetX > 0)
+                    parentIndex = (y * width) + (x - halfStep);
+                else
+                    parentIndex = ((y - halfStep) * width) + x;
 
-                               decodedMap[index] = (byte)decodedValue;                 
-                       } else {                                
-                               decodedMap[index] = (byte)averageDecodedValue;                  
-                       }                       
+                int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
 
-               } else {
-                       decodedRangeMap[index] = (byte)inheritedRange;                  
-                       decodedMap[index] = (byte)averageDecodedValue;                  
-               }
-       }
+                if (parentYRange < yRange) {
+                    parentYRange = yRange;
+                    yChannel.rangeMap[parentIndex] = (byte) parentYRange;
+                }
 
-       public static int encodeValueIntoGivenBits(int value, int range, int bitCount){
+                int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
 
-               int negativeBit = 0;
+                if (parentURange < uRange) {
+                    parentURange = uRange;
+                    uChannel.rangeMap[parentIndex] = (byte) parentURange;
+                }
 
-               if (value <0){
-                       negativeBit = 1;
-                       value = -value;
-               }
+                int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
 
-               int remainingBitCount = bitCount - 1;
+                if (parentVRange < vRange) {
+                    parentVRange = vRange;
+                    vChannel.rangeMap[parentIndex] = (byte) parentVRange;
+                }
 
-               if (remainingBitCount == 0){                    
-                       // no more bits remaining to encode actual value
+            }
+    }
 
-                       return negativeBit;
+    private void saveGrid(final int step) throws IOException {
 
-               } else {
-                       // still one or more bits left, encode value as precisely as possible
+        saveGridDiagonal(step / 2, step / 2, step);
+        saveGridSquare(step / 2, 0, step);
+        saveGridSquare(0, step / 2, step);
 
-                       if (value > range) value = range;
+        if (step > 2)
+            saveGrid(step / 2);
+    }
 
+    private void saveGridDiagonal(final int offsetX, final int offsetY,
+                                  final int step) throws IOException {
+        for (int y = offsetY; y < height; y = y + step)
+            for (int x = offsetX; x < width; x = x + step) {
 
-                       int realvalueForThisBitcount = 1 << remainingBitCount;
-                       // int valueMultiplier = range / realvalueForThisBitcount;
-                       int encodedValue = value * realvalueForThisBitcount / range;
+                final int halfStep = step / 2;
 
-                       if (encodedValue >= realvalueForThisBitcount) encodedValue = realvalueForThisBitcount - 1;
+                context2.initialize(image, yChannel.decodedMap,
+                        uChannel.decodedMap, vChannel.decodedMap);
+                context2.measureNeighborEncode(x - halfStep, y - halfStep);
+                context2.measureNeighborEncode(x + halfStep, y - halfStep);
+                context2.measureNeighborEncode(x - halfStep, y + halfStep);
+                context2.measureNeighborEncode(x + halfStep, y + halfStep);
 
-                       encodedValue = (encodedValue << 1) + negativeBit;
+                savePixel(step, offsetX, offsetY, x, y,
+                        context2.colorStats.getAverageY(),
+                        context2.colorStats.getAverageU(),
+                        context2.colorStats.getAverageV());
 
-                       return encodedValue;
-               }
-       }
+            }
+    }
 
+    private void saveGridSquare(final int offsetX, final int offsetY,
+                                final int step) throws IOException {
+        for (int y = offsetY; y < height; y = y + step)
+            for (int x = offsetX; x < width; x = x + step) {
 
-       public static int decodeValueFromGivenBits(int encodedBits, int range, int bitCount){
-               int negativeBit = encodedBits & 1;
+                final int halfStep = step / 2;
 
-               int remainingBitCount = bitCount - 1;
+                context2.initialize(image, yChannel.decodedMap,
+                        uChannel.decodedMap, vChannel.decodedMap);
+                context2.measureNeighborEncode(x - halfStep, y);
+                context2.measureNeighborEncode(x + halfStep, y);
+                context2.measureNeighborEncode(x, y - halfStep);
+                context2.measureNeighborEncode(x, y + halfStep);
 
-               if (remainingBitCount == 0){                    
-                       // no more bits remaining to encode actual value
+                savePixel(step, offsetX, offsetY, x, y,
+                        context2.colorStats.getAverageY(),
+                        context2.colorStats.getAverageU(),
+                        context2.colorStats.getAverageV());
 
-                       if (negativeBit == 0){
-                               return range;                           
-                       } else {
-                               return -range;                                                          
-                       }
+            }
+    }
 
-               } else {
-                       // still one or more bits left, encode value as precisely as possible
+    private void savePixel(final int step, final int offsetX, final int offsetY,
+                           final int x, final int y, final int averageDecodedY,
+                           final int averageDecodedU, final int averageDecodedV)
+            throws IOException {
 
-                       int encodedValue = (encodedBits >>> 1) + 1;
+        final int index = (y * width) + x;
 
-                       int realvalueForThisBitcount = 1 << remainingBitCount;
+        final int py = byteToInt(yChannel.map[index]);
+        final int pu = byteToInt(uChannel.map[index]);
+        final int pv = byteToInt(vChannel.map[index]);
 
-                       // int valueMultiplier = range / realvalueForThisBitcount;
-                       int decodedValue = range * encodedValue / realvalueForThisBitcount;
+        final int yRange = byteToInt(yChannel.rangeMap[index]);
+        final int uRange = byteToInt(uChannel.rangeMap[index]);
+        final int vRange = byteToInt(vChannel.rangeMap[index]);
 
+        final int halfStep = step / 2;
 
-                       if (decodedValue > range) decodedValue = range;
+        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;
 
-                       if (negativeBit == 0){
-                               return decodedValue;                            
-                       } else {
-                               return -decodedValue;                                                           
-                       }
+        encodeChannel(approximator.yTable, yChannel, averageDecodedY, index,
+                py, yRange, parentIndex);
+
+        encodeChannel(approximator.uTable, uChannel, averageDecodedU, index,
+                pu, uRange, parentIndex);
 
-               }               
-       }
+        encodeChannel(approximator.vTable, vChannel, averageDecodedV, index,
+                pv, vRange, parentIndex);
 
-       public static int byteToInt(byte input){
-               int result = input;
-               if (result < 0) result = result + 256;
-               return result;
-       }
+    }
 
 }