/*
- * 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;
+
+ 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();
-public class ImageEncoder {
+ approximator.save(bitOutputStream);
- Image image;
- int width, height;
+ width = image.metaData.width;
+ height = image.metaData.height;
- Channel yChannel;
- Channel uChannel;
- Channel vChannel;
+ final WritableRaster raster = image.bufferedImage.getRaster();
+ final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
+ final byte[] pixels = dbi.getData();
- Approximator approximator;
+ if (yChannel == null)
+ yChannel = new Channel(width, height);
+ else
+ yChannel.reset();
- int bitsForY;
- int bitsForU;
- int bitsForV;
+ if (uChannel == null)
+ uChannel = new Channel(width, height);
+ else
+ uChannel.reset();
- // ColorStats colorStats = new ColorStats();
- OperatingContext context = new OperatingContext();
- OperatingContext context2 = new OperatingContext();
+ if (vChannel == null)
+ vChannel = new Channel(width, height);
+ else
+ vChannel.reset();
- BitOutputStream bitOutputStream;
+ // create YUV map out of RGB raster data
+ final Color color = new Color();
- public ImageEncoder(final Image image) {
- approximator = new Approximator();
+ for (int y = 0; y < height; y++)
+ for (int x = 0; x < width; x++) {
- // bitOutputStream = outputStream;
+ final int index = (y * width) + x;
+ final int colorBufferIndex = index * 3;
- this.image = image;
+ int blue = pixels[colorBufferIndex];
+ if (blue < 0)
+ blue = blue + 256;
- }
+ int green = pixels[colorBufferIndex + 1];
+ if (green < 0)
+ green = green + 256;
- public void encode(final BitOutputStream bitOutputStream)
- throws IOException {
- this.bitOutputStream = bitOutputStream;
+ int red = pixels[colorBufferIndex + 2];
+ if (red < 0)
+ red = red + 256;
- approximator.initialize();
+ color.r = red;
+ color.g = green;
+ color.b = blue;
- approximator.save(bitOutputStream);
+ color.RGB2YUV();
- width = image.metaData.width;
- height = image.metaData.height;
+ yChannel.map[index] = (byte) color.y;
+ uChannel.map[index] = (byte) color.u;
+ vChannel.map[index] = (byte) color.v;
+ }
- final WritableRaster raster = image.bufferedImage.getRaster();
- final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
- final byte[] pixels = dbi.getData();
+ yChannel.decodedMap[0] = yChannel.map[0];
+ uChannel.decodedMap[0] = uChannel.map[0];
+ vChannel.decodedMap[0] = vChannel.map[0];
- if (yChannel == null) {
- yChannel = new Channel(width, height);
- } else {
- yChannel.reset();
- }
+ bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
+ bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
+ bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
- if (uChannel == null) {
- uChannel = new Channel(width, height);
- } else {
- uChannel.reset();
- }
+ // detect initial step
+ int largestDimension;
+ int initialStep = 2;
+ if (width > height)
+ largestDimension = width;
+ else
+ largestDimension = height;
- if (vChannel == null) {
- vChannel = new Channel(width, height);
- } else {
- vChannel.reset();
- }
+ while (initialStep < largestDimension)
+ initialStep = initialStep * 2;
- // create YUV map out of RGB raster data
- final Color color = new Color();
+ 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);
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
+ if (inheritedBitCount > 0) {
+ int computedRange;
+ computedRange = table.proposeRangeForRange(range, inheritedRange);
+ decodedRangeMap[index] = (byte) computedRange;
- final int index = (y * width) + x;
- final int colorBufferIndex = index * 3;
+ 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);
- int blue = pixels[colorBufferIndex];
- if (blue < 0) {
- blue = blue + 256;
- }
+ final int decodedDifference = decodeValueFromGivenBits(
+ bitEncodedDifference, computedRange, computedBitCount);
+ int decodedValue = averageDecodedValue - decodedDifference;
+ if (decodedValue > 255)
+ decodedValue = 255;
+ if (decodedValue < 0)
+ decodedValue = 0;
- int green = pixels[colorBufferIndex + 1];
- if (green < 0) {
- green = green + 256;
- }
+ decodedMap[index] = (byte) decodedValue;
+ } else
+ decodedMap[index] = (byte) averageDecodedValue;
- int red = pixels[colorBufferIndex + 2];
- if (red < 0) {
- red = red + 256;
- }
+ } else {
+ decodedRangeMap[index] = (byte) inheritedRange;
+ decodedMap[index] = (byte) averageDecodedValue;
+ }
+ }
- color.r = red;
- color.g = green;
- color.b = blue;
+ public void printStatistics() {
+ System.out.println("Y channel:");
+ yChannel.printStatistics();
- color.RGB2YUV();
+ System.out.println("U channel:");
+ uChannel.printStatistics();
- yChannel.map[index] = (byte) color.y;
- uChannel.map[index] = (byte) color.u;
- vChannel.map[index] = (byte) color.v;
- }
- }
+ System.out.println("V channel:");
+ vChannel.printStatistics();
+ }
- yChannel.decodedMap[0] = yChannel.map[0];
- uChannel.decodedMap[0] = uChannel.map[0];
- vChannel.decodedMap[0] = vChannel.map[0];
+ private void rangeGrid(final int step) {
- bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
- bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
- bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
+ // gridSquare(step / 2, step / 2, step, pixels);
+
+ rangeGridDiagonal(step / 2, step / 2, step);
+ rangeGridSquare(step / 2, 0, step);
+ rangeGridSquare(0, step / 2, step);
- // 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();
- }
-
- 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;
-
- 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 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);
+ if (step > 2)
+ rangeGrid(step / 2);
+ }
- yChannel.rangeMap[index] = (byte) context.getYRange(index);
- uChannel.rangeMap[index] = (byte) context.getURange(index);
- vChannel.rangeMap[index] = (byte) context.getVRange(index);
- }
- }
- }
+ 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) {
- public void rangeRoundGrid(final int step) {
+ final int index = (y * width) + x;
+ final int halfStep = step / 2;
- rangeRoundGridDiagonal(step / 2, step / 2, step);
- rangeRoundGridSquare(step / 2, 0, step);
- rangeRoundGridSquare(0, step / 2, step);
-
- if (step < 1024) {
- rangeRoundGrid(step * 2);
- }
- }
-
- 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) {
+ context.initialize(image, yChannel.map, uChannel.map,
+ vChannel.map);
- final int index = (y * width) + x;
+ context.measureNeighborEncode(x - halfStep, y - halfStep);
+ context.measureNeighborEncode(x + halfStep, y - halfStep);
+ context.measureNeighborEncode(x - halfStep, y + halfStep);
+ context.measureNeighborEncode(x + halfStep, y + halfStep);
- final int yRange = byteToInt(yChannel.rangeMap[index]);
- final int uRange = byteToInt(uChannel.rangeMap[index]);
- final int vRange = byteToInt(vChannel.rangeMap[index]);
+ yChannel.rangeMap[index] = (byte) context.getYRange(index);
+ uChannel.rangeMap[index] = (byte) context.getURange(index);
+ vChannel.rangeMap[index] = (byte) context.getVRange(index);
+ }
+ }
- final int halfStep = step / 2;
+ 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 parentIndex = ((y - halfStep) * width)
- + (x - halfStep);
+ final int index = (y * width) + x;
+ final int halfStep = step / 2;
- int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
+ context.initialize(image, yChannel.map, uChannel.map,
+ vChannel.map);
- if (parentYRange < yRange) {
- parentYRange = yRange;
- yChannel.rangeMap[parentIndex] = (byte) parentYRange;
- }
+ context.measureNeighborEncode(x - halfStep, y);
+ context.measureNeighborEncode(x + halfStep, y);
+ context.measureNeighborEncode(x, y - halfStep);
+ context.measureNeighborEncode(x, y + halfStep);
- int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
+ yChannel.rangeMap[index] = (byte) context.getYRange(index);
+ uChannel.rangeMap[index] = (byte) context.getURange(index);
+ vChannel.rangeMap[index] = (byte) context.getVRange(index);
+ }
+ }
- if (parentURange < uRange) {
- parentURange = uRange;
- uChannel.rangeMap[parentIndex] = (byte) parentURange;
- }
+ private void rangeRoundGrid(final int step) {
- int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
+ rangeRoundGridDiagonal(step / 2, step / 2, step);
+ rangeRoundGridSquare(step / 2, 0, step);
+ rangeRoundGridSquare(0, step / 2, step);
- if (parentVRange < vRange) {
- parentVRange = vRange;
- vChannel.rangeMap[parentIndex] = (byte) parentVRange;
- }
- }
- }
- }
-
- 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;
+ if (step < 1024)
+ rangeRoundGrid(step * 2);
+ }
- final int yRange = byteToInt(yChannel.rangeMap[index]);
- final int uRange = byteToInt(uChannel.rangeMap[index]);
- final int vRange = byteToInt(vChannel.rangeMap[index]);
+ 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 halfStep = step / 2;
+ final int index = (y * width) + x;
- int parentIndex;
- if (offsetX > 0) {
- parentIndex = (y * width) + (x - halfStep);
- } else {
- parentIndex = ((y - halfStep) * width) + x;
- }
+ final int yRange = byteToInt(yChannel.rangeMap[index]);
+ final int uRange = byteToInt(uChannel.rangeMap[index]);
+ final int vRange = byteToInt(vChannel.rangeMap[index]);
- int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
+ final int halfStep = step / 2;
- 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;
- }
-
- }
- }
- }
+ final int parentIndex = ((y - halfStep) * width)
+ + (x - halfStep);
- public void saveGrid(final int step) throws IOException {
+ int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
- saveGridDiagonal(step / 2, step / 2, step);
- saveGridSquare(step / 2, 0, step);
- saveGridSquare(0, step / 2, step);
-
- if (step > 2) {
- saveGrid(step / 2);
- }
- }
-
- 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) {
+ if (parentYRange < yRange) {
+ parentYRange = yRange;
+ yChannel.rangeMap[parentIndex] = (byte) parentYRange;
+ }
- final int halfStep = step / 2;
+ int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
- 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());
+ if (parentURange < uRange) {
+ parentURange = uRange;
+ uChannel.rangeMap[parentIndex] = (byte) parentURange;
+ }
- }
- }
- }
-
- 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) {
-
- 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);
-
- 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;
- }
- }
+ }
}