1 package eu.svjatoslav.imagesqueeze.codec;
4 * Compressed image pixels encoder.
7 import java.awt.image.DataBufferByte;
8 import java.awt.image.WritableRaster;
9 import java.io.IOException;
12 public class ImageEncoder {
21 Approximator approximator;
27 //ColorStats colorStats = new ColorStats();
28 OperatingContext context = new OperatingContext();
29 OperatingContext context2 = new OperatingContext();
31 BitOutputStream bitOutputStream;
33 public ImageEncoder(Image image){
34 approximator = new Approximator();
36 //bitOutputStream = outputStream;
43 public void encode(BitOutputStream bitOutputStream) throws IOException {
44 this.bitOutputStream = bitOutputStream;
46 approximator.initialize();
48 approximator.save(bitOutputStream);
50 width = image.metaData.width;
51 height = image.metaData.height;
53 WritableRaster raster = image.bufferedImage.getRaster();
54 DataBufferByte dbi = (DataBufferByte)raster.getDataBuffer();
55 byte [] pixels = dbi.getData();
57 if (yChannel == null){
58 yChannel = new Channel(width, height);
63 if (uChannel == null){
64 uChannel = new Channel(width, height);
69 if (vChannel == null){
70 vChannel = new Channel(width, height);
75 // create YUV map out of RGB raster data
76 Color color = new Color();
78 for (int y=0; y < height; y++){
79 for (int x=0; x < width; x++){
81 int index = (y * width) + x;
82 int colorBufferIndex = index * 3;
84 int blue = pixels[colorBufferIndex];
85 if (blue < 0) blue = blue + 256;
87 int green = pixels[colorBufferIndex+1];
88 if (green < 0) green = green + 256;
90 int red = pixels[colorBufferIndex+2];
91 if (red < 0) red = red + 256;
99 yChannel.map[index] = (byte)color.y;
100 uChannel.map[index] = (byte)color.u;
101 vChannel.map[index] = (byte)color.v;
105 yChannel.decodedMap[0] = yChannel.map[0];
106 uChannel.decodedMap[0] = uChannel.map[0];
107 vChannel.decodedMap[0] = vChannel.map[0];
109 bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
110 bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
111 bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
113 // detect initial step
114 int largestDimension;
116 if (width > height) {
117 largestDimension = width;
119 largestDimension = height;
122 while (initialStep < largestDimension){
123 initialStep = initialStep * 2;
126 rangeGrid(initialStep);
128 saveGrid(initialStep);
131 public void printStatistics(){
132 System.out.println("Y channel:");
133 yChannel.printStatistics();
135 System.out.println("U channel:");
136 uChannel.printStatistics();
138 System.out.println("V channel:");
139 vChannel.printStatistics();
142 public void rangeGrid(int step){
144 //gridSquare(step / 2, step / 2, step, pixels);
146 rangeGridDiagonal(step / 2, step / 2, step);
147 rangeGridSquare(step / 2, 0, step);
148 rangeGridSquare(0, step / 2, step);
150 if (step > 2) rangeGrid(step / 2);
154 public void rangeRoundGrid(int step){
156 rangeRoundGridDiagonal(step / 2, step / 2, step);
157 rangeRoundGridSquare(step / 2, 0, step);
158 rangeRoundGridSquare(0, step / 2, step);
160 if (step < 1024) rangeRoundGrid(step * 2);
163 public void saveGrid(int step) throws IOException {
165 saveGridDiagonal(step / 2, step / 2, step);
166 saveGridSquare(step / 2, 0, step);
167 saveGridSquare(0, step / 2, step);
169 if (step > 2) saveGrid(step / 2);
173 public void rangeGridSquare(int offsetX, int offsetY, int step){
174 for (int y = offsetY; y < height; y = y + step){
175 for (int x = offsetX; x < width; x = x + step){
177 int index = (y * width) + x;
178 int halfStep = step / 2;
180 context.initialize(image, yChannel.map, uChannel.map, vChannel.map);
182 context.measureNeighborEncode(x - halfStep, y);
183 context.measureNeighborEncode(x + halfStep, y);
184 context.measureNeighborEncode(x, y - halfStep);
185 context.measureNeighborEncode(x, y + halfStep);
187 yChannel.rangeMap[index] = (byte)context.getYRange(index);
188 uChannel.rangeMap[index] = (byte)context.getURange(index);
189 vChannel.rangeMap[index] = (byte)context.getVRange(index);
194 public void rangeGridDiagonal(int offsetX, int offsetY, int step){
195 for (int y = offsetY; y < height; y = y + step){
196 for (int x = offsetX; x < width; x = x + step){
198 int index = (y * width) + x;
199 int halfStep = step / 2;
201 context.initialize(image, yChannel.map, uChannel.map, vChannel.map);
203 context.measureNeighborEncode(x - halfStep, y - halfStep);
204 context.measureNeighborEncode(x + halfStep, y - halfStep);
205 context.measureNeighborEncode(x - halfStep, y + halfStep);
206 context.measureNeighborEncode(x + halfStep, y + halfStep);
208 yChannel.rangeMap[index] = (byte)context.getYRange(index);
209 uChannel.rangeMap[index] = (byte)context.getURange(index);
210 vChannel.rangeMap[index] = (byte)context.getVRange(index);
215 public void rangeRoundGridDiagonal(int offsetX, int offsetY, int step){
216 for (int y = offsetY; y < height; y = y + step){
217 for (int x = offsetX; x < width; x = x + step){
219 int index = (y * width) + x;
221 int yRange = byteToInt(yChannel.rangeMap[index]);
222 int uRange = byteToInt(uChannel.rangeMap[index]);
223 int vRange = byteToInt(vChannel.rangeMap[index]);
225 int halfStep = step / 2;
227 int parentIndex = ((y - halfStep) * width) + (x - halfStep);
229 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
231 if (parentYRange < yRange){
232 parentYRange = yRange;
233 yChannel.rangeMap[parentIndex] = (byte)parentYRange;
236 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
238 if (parentURange < uRange){
239 parentURange = uRange;
240 uChannel.rangeMap[parentIndex] = (byte)parentURange;
243 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
245 if (parentVRange < vRange){
246 parentVRange = vRange;
247 vChannel.rangeMap[parentIndex] = (byte)parentVRange;
253 public void rangeRoundGridSquare(int offsetX, int offsetY, int step){
254 for (int y = offsetY; y < height; y = y + step){
255 for (int x = offsetX; x < width; x = x + step){
257 int index = (y * width) + x;
259 int yRange = byteToInt(yChannel.rangeMap[index]);
260 int uRange = byteToInt(uChannel.rangeMap[index]);
261 int vRange = byteToInt(vChannel.rangeMap[index]);
263 int halfStep = step / 2;
267 parentIndex = (y * width) + (x - halfStep);
269 parentIndex = ((y - halfStep) * width) + x;
272 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
274 if (parentYRange < yRange){
275 parentYRange = yRange;
276 yChannel.rangeMap[parentIndex] = (byte)parentYRange;
279 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
281 if (parentURange < uRange){
282 parentURange = uRange;
283 uChannel.rangeMap[parentIndex] = (byte)parentURange;
286 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
288 if (parentVRange < vRange){
289 parentVRange = vRange;
290 vChannel.rangeMap[parentIndex] = (byte)parentVRange;
297 public void saveGridSquare(int offsetX, int offsetY, int step) throws IOException{
298 for (int y = offsetY; y < height; y = y + step){
299 for (int x = offsetX; x < width; x = x + step){
301 int halfStep = step / 2;
303 context2.initialize(image, yChannel.decodedMap, uChannel.decodedMap, vChannel.decodedMap);
304 context2.measureNeighborEncode(x - halfStep, y);
305 context2.measureNeighborEncode(x + halfStep, y);
306 context2.measureNeighborEncode(x, y - halfStep);
307 context2.measureNeighborEncode(x, y + halfStep);
310 savePixel(step, offsetX, offsetY, x, y,
311 context2.colorStats.getAverageY(),
312 context2.colorStats.getAverageU(),
313 context2.colorStats.getAverageV());
319 public void saveGridDiagonal(int offsetX, int offsetY, int step) throws IOException {
320 for (int y = offsetY; y < height; y = y + step){
321 for (int x = offsetX; x < width; x = x + step){
323 int halfStep = step / 2;
325 context2.initialize(image, yChannel.decodedMap, uChannel.decodedMap, vChannel.decodedMap);
326 context2.measureNeighborEncode(x - halfStep, y - halfStep);
327 context2.measureNeighborEncode(x + halfStep, y - halfStep);
328 context2.measureNeighborEncode(x - halfStep, y + halfStep);
329 context2.measureNeighborEncode(x + halfStep, y + halfStep);
332 savePixel(step, offsetX, offsetY, x, y,
333 context2.colorStats.getAverageY(),
334 context2.colorStats.getAverageU(),
335 context2.colorStats.getAverageV());
341 public void savePixel(int step, int offsetX, int offsetY, int x, int y, int averageDecodedY, int averageDecodedU, int averageDecodedV) throws IOException {
343 int index = (y * width) + x;
345 int py = byteToInt(yChannel.map[index]);
346 int pu = byteToInt(uChannel.map[index]);
347 int pv = byteToInt(vChannel.map[index]);
349 int yRange = byteToInt(yChannel.rangeMap[index]);
350 int uRange = byteToInt(uChannel.rangeMap[index]);
351 int vRange = byteToInt(vChannel.rangeMap[index]);
353 int halfStep = step / 2;
359 parentIndex = ((y - halfStep) * width) + (x - halfStep);
362 parentIndex = (y * width) + (x - halfStep);
366 parentIndex = ((y - halfStep) * width) + x;
399 private void encodeChannel(Table table, Channel channel, int averageDecodedValue, int index,
400 int value, int range, int parentIndex)
403 byte[] decodedRangeMap = channel.decodedRangeMap;
404 byte[] decodedMap = channel.decodedMap;
406 int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
408 int inheritedBitCount = table.proposeBitcountForRange(inheritedRange);
410 if (inheritedBitCount > 0){
412 computedRange = table.proposeRangeForRange(range, inheritedRange);
413 decodedRangeMap[index] = (byte)computedRange;
416 if (computedRange != inheritedRange){
417 // brightness range shrinked
418 bitOutputStream.storeBits(1, 1);
420 // brightness range stayed the same
421 bitOutputStream.storeBits(0, 1);
425 // encode brightness into available amount of bits
426 int computedBitCount = table.proposeBitcountForRange(computedRange);
428 if (computedBitCount > 0){
430 int differenceToEncode = -(value - averageDecodedValue);
431 int bitEncodedDifference = encodeValueIntoGivenBits(differenceToEncode, computedRange, computedBitCount);
433 channel.bitCount = channel.bitCount + computedBitCount;
434 bitOutputStream.storeBits(bitEncodedDifference, computedBitCount);
436 int decodedDifference = decodeValueFromGivenBits(bitEncodedDifference, computedRange, computedBitCount);
437 int decodedValue = averageDecodedValue - decodedDifference;
438 if (decodedValue > 255) decodedValue = 255;
439 if (decodedValue < 0) decodedValue = 0;
441 decodedMap[index] = (byte)decodedValue;
443 decodedMap[index] = (byte)averageDecodedValue;
447 decodedRangeMap[index] = (byte)inheritedRange;
448 decodedMap[index] = (byte)averageDecodedValue;
452 public static int encodeValueIntoGivenBits(int value, int range, int bitCount){
461 int remainingBitCount = bitCount - 1;
463 if (remainingBitCount == 0){
464 // no more bits remaining to encode actual value
469 // still one or more bits left, encode value as precisely as possible
471 if (value > range) value = range;
474 int realvalueForThisBitcount = 1 << remainingBitCount;
475 // int valueMultiplier = range / realvalueForThisBitcount;
476 int encodedValue = value * realvalueForThisBitcount / range;
478 if (encodedValue >= realvalueForThisBitcount) encodedValue = realvalueForThisBitcount - 1;
480 encodedValue = (encodedValue << 1) + negativeBit;
487 public static int decodeValueFromGivenBits(int encodedBits, int range, int bitCount){
488 int negativeBit = encodedBits & 1;
490 int remainingBitCount = bitCount - 1;
492 if (remainingBitCount == 0){
493 // no more bits remaining to encode actual value
495 if (negativeBit == 0){
502 // still one or more bits left, encode value as precisely as possible
504 int encodedValue = (encodedBits >>> 1) + 1;
506 int realvalueForThisBitcount = 1 << remainingBitCount;
508 // int valueMultiplier = range / realvalueForThisBitcount;
509 int decodedValue = range * encodedValue / realvalueForThisBitcount;
512 if (decodedValue > range) decodedValue = range;
514 if (negativeBit == 0){
517 return -decodedValue;
523 public static int byteToInt(byte input){
525 if (result < 0) result = result + 256;