X-Git-Url: http://www2.svjatoslav.eu/gitweb/?p=imagesqueeze.git;a=blobdiff_plain;f=src%2Fmain%2Fjava%2Feu%2Fsvjatoslav%2Fimagesqueeze%2Fcodec%2FImageEncoder.java;h=0bf2fa886308d47768f3a4a22a69e1f82414ad50;hp=46c2135b19f573dfad711835abf255f4ceb05bb1;hb=HEAD;hpb=c7d0b8e1723045c0df086d9214a35f54db47684c diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java index 46c2135..0bf2fa8 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java @@ -1,529 +1,530 @@ +/* + * 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; - } + } }