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