2 * Imagesqueeze - Image codec optimized for photos.
3 * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of version 2 of the GNU General Public License
7 * as published by the Free Software Foundation.
10 package eu.svjatoslav.imagesqueeze.codec;
13 * Compressed image pixels encoder.
16 import eu.svjatoslav.commons.data.BitOutputStream;
18 import java.awt.image.DataBufferByte;
19 import java.awt.image.WritableRaster;
20 import java.io.IOException;
24 private final Image image;
25 private final Approximator approximator;
26 // ColorStats colorStats = new ColorStats();
27 private final OperatingContext context = new OperatingContext();
28 private final OperatingContext context2 = new OperatingContext();
34 private Channel yChannel;
35 private Channel uChannel;
36 private Channel vChannel;
37 private BitOutputStream bitOutputStream;
39 public ImageEncoder(final Image image) {
40 approximator = new Approximator();
42 // bitOutputStream = outputStream;
48 public static int byteToInt(final byte input) {
51 result = result + 256;
55 public static int decodeValueFromGivenBits(final int encodedBits,
56 final int range, final int bitCount) {
57 final int negativeBit = encodedBits & 1;
59 final int remainingBitCount = bitCount - 1;
61 if (remainingBitCount == 0) {
62 // no more bits remaining to encode actual value
70 // still one or more bits left, encode value as precisely as
73 final int encodedValue = (encodedBits >>> 1) + 1;
75 final int realvalueForThisBitcount = 1 << remainingBitCount;
77 // int valueMultiplier = range / realvalueForThisBitcount;
78 int decodedValue = (range * encodedValue)
79 / realvalueForThisBitcount;
81 if (decodedValue > range)
92 private static int encodeValueIntoGivenBits(int value, final int range,
102 final int remainingBitCount = bitCount - 1;
104 if (remainingBitCount == 0)
107 // still one or more bits left, encode value as precisely as
113 final int realvalueForThisBitcount = 1 << remainingBitCount;
114 // int valueMultiplier = range / realvalueForThisBitcount;
115 int encodedValue = (value * realvalueForThisBitcount) / range;
117 if (encodedValue >= realvalueForThisBitcount)
118 encodedValue = realvalueForThisBitcount - 1;
120 encodedValue = (encodedValue << 1) + negativeBit;
126 public static void storeIntegerCompressed8(
127 final BitOutputStream outputStream, final int data)
131 outputStream.storeBits(0, 1);
132 outputStream.storeBits(data, 8);
134 outputStream.storeBits(1, 1);
135 outputStream.storeBits(data, 32);
139 public void encode(final BitOutputStream bitOutputStream)
141 this.bitOutputStream = bitOutputStream;
143 approximator.initialize();
145 approximator.save(bitOutputStream);
147 width = image.metaData.width;
148 height = image.metaData.height;
150 final WritableRaster raster = image.bufferedImage.getRaster();
151 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
152 final byte[] pixels = dbi.getData();
154 if (yChannel == null)
155 yChannel = new Channel(width, height);
159 if (uChannel == null)
160 uChannel = new Channel(width, height);
164 if (vChannel == null)
165 vChannel = new Channel(width, height);
169 // create YUV map out of RGB raster data
170 final Color color = new Color();
172 for (int y = 0; y < height; y++)
173 for (int x = 0; x < width; x++) {
175 final int index = (y * width) + x;
176 final int colorBufferIndex = index * 3;
178 int blue = pixels[colorBufferIndex];
182 int green = pixels[colorBufferIndex + 1];
186 int red = pixels[colorBufferIndex + 2];
196 yChannel.map[index] = (byte) color.y;
197 uChannel.map[index] = (byte) color.u;
198 vChannel.map[index] = (byte) color.v;
201 yChannel.decodedMap[0] = yChannel.map[0];
202 uChannel.decodedMap[0] = uChannel.map[0];
203 vChannel.decodedMap[0] = vChannel.map[0];
205 bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
206 bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
207 bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
209 // detect initial step
210 int largestDimension;
213 largestDimension = width;
215 largestDimension = height;
217 while (initialStep < largestDimension)
218 initialStep = initialStep * 2;
220 rangeGrid(initialStep);
222 saveGrid(initialStep);
225 private void encodeChannel(final Table table, final Channel channel,
226 final int averageDecodedValue, final int index, final int value,
227 final int range, final int parentIndex) throws IOException {
229 final byte[] decodedRangeMap = channel.decodedRangeMap;
230 final byte[] decodedMap = channel.decodedMap;
232 final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
234 final int inheritedBitCount = table
235 .proposeBitcountForRange(inheritedRange);
237 if (inheritedBitCount > 0) {
239 computedRange = table.proposeRangeForRange(range, inheritedRange);
240 decodedRangeMap[index] = (byte) computedRange;
243 if (computedRange != inheritedRange)
244 // brightness range shrinked
245 bitOutputStream.storeBits(1, 1);
247 // brightness range stayed the same
248 bitOutputStream.storeBits(0, 1);
250 // encode brightness into available amount of bits
251 final int computedBitCount = table
252 .proposeBitcountForRange(computedRange);
254 if (computedBitCount > 0) {
256 final int differenceToEncode = -(value - averageDecodedValue);
257 final int bitEncodedDifference = encodeValueIntoGivenBits(
258 differenceToEncode, computedRange, computedBitCount);
260 channel.bitCount = channel.bitCount + computedBitCount;
261 bitOutputStream.storeBits(bitEncodedDifference,
264 final int decodedDifference = decodeValueFromGivenBits(
265 bitEncodedDifference, computedRange, computedBitCount);
266 int decodedValue = averageDecodedValue - decodedDifference;
267 if (decodedValue > 255)
269 if (decodedValue < 0)
272 decodedMap[index] = (byte) decodedValue;
274 decodedMap[index] = (byte) averageDecodedValue;
277 decodedRangeMap[index] = (byte) inheritedRange;
278 decodedMap[index] = (byte) averageDecodedValue;
282 public void printStatistics() {
283 System.out.println("Y channel:");
284 yChannel.printStatistics();
286 System.out.println("U channel:");
287 uChannel.printStatistics();
289 System.out.println("V channel:");
290 vChannel.printStatistics();
293 private void rangeGrid(final int step) {
295 // gridSquare(step / 2, step / 2, step, pixels);
297 rangeGridDiagonal(step / 2, step / 2, step);
298 rangeGridSquare(step / 2, 0, step);
299 rangeGridSquare(0, step / 2, step);
305 private void rangeGridDiagonal(final int offsetX, final int offsetY,
307 for (int y = offsetY; y < height; y = y + step)
308 for (int x = offsetX; x < width; x = x + step) {
310 final int index = (y * width) + x;
311 final int halfStep = step / 2;
313 context.initialize(image, yChannel.map, uChannel.map,
316 context.measureNeighborEncode(x - halfStep, y - halfStep);
317 context.measureNeighborEncode(x + halfStep, y - halfStep);
318 context.measureNeighborEncode(x - halfStep, y + halfStep);
319 context.measureNeighborEncode(x + halfStep, y + halfStep);
321 yChannel.rangeMap[index] = (byte) context.getYRange(index);
322 uChannel.rangeMap[index] = (byte) context.getURange(index);
323 vChannel.rangeMap[index] = (byte) context.getVRange(index);
327 private void rangeGridSquare(final int offsetX, final int offsetY,
329 for (int y = offsetY; y < height; y = y + step)
330 for (int x = offsetX; x < width; x = x + step) {
332 final int index = (y * width) + x;
333 final int halfStep = step / 2;
335 context.initialize(image, yChannel.map, uChannel.map,
338 context.measureNeighborEncode(x - halfStep, y);
339 context.measureNeighborEncode(x + halfStep, y);
340 context.measureNeighborEncode(x, y - halfStep);
341 context.measureNeighborEncode(x, y + halfStep);
343 yChannel.rangeMap[index] = (byte) context.getYRange(index);
344 uChannel.rangeMap[index] = (byte) context.getURange(index);
345 vChannel.rangeMap[index] = (byte) context.getVRange(index);
349 private void rangeRoundGrid(final int step) {
351 rangeRoundGridDiagonal(step / 2, step / 2, step);
352 rangeRoundGridSquare(step / 2, 0, step);
353 rangeRoundGridSquare(0, step / 2, step);
356 rangeRoundGrid(step * 2);
359 private void rangeRoundGridDiagonal(final int offsetX, final int offsetY,
361 for (int y = offsetY; y < height; y = y + step)
362 for (int x = offsetX; x < width; x = x + step) {
364 final int index = (y * width) + x;
366 final int yRange = byteToInt(yChannel.rangeMap[index]);
367 final int uRange = byteToInt(uChannel.rangeMap[index]);
368 final int vRange = byteToInt(vChannel.rangeMap[index]);
370 final int halfStep = step / 2;
372 final int parentIndex = ((y - halfStep) * width)
375 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
377 if (parentYRange < yRange) {
378 parentYRange = yRange;
379 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
382 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
384 if (parentURange < uRange) {
385 parentURange = uRange;
386 uChannel.rangeMap[parentIndex] = (byte) parentURange;
389 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
391 if (parentVRange < vRange) {
392 parentVRange = vRange;
393 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
398 private void rangeRoundGridSquare(final int offsetX, final int offsetY,
400 for (int y = offsetY; y < height; y = y + step)
401 for (int x = offsetX; x < width; x = x + step) {
403 final int index = (y * width) + x;
405 final int yRange = byteToInt(yChannel.rangeMap[index]);
406 final int uRange = byteToInt(uChannel.rangeMap[index]);
407 final int vRange = byteToInt(vChannel.rangeMap[index]);
409 final int halfStep = step / 2;
413 parentIndex = (y * width) + (x - halfStep);
415 parentIndex = ((y - halfStep) * width) + x;
417 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
419 if (parentYRange < yRange) {
420 parentYRange = yRange;
421 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
424 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
426 if (parentURange < uRange) {
427 parentURange = uRange;
428 uChannel.rangeMap[parentIndex] = (byte) parentURange;
431 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
433 if (parentVRange < vRange) {
434 parentVRange = vRange;
435 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
441 private void saveGrid(final int step) throws IOException {
443 saveGridDiagonal(step / 2, step / 2, step);
444 saveGridSquare(step / 2, 0, step);
445 saveGridSquare(0, step / 2, step);
451 private void saveGridDiagonal(final int offsetX, final int offsetY,
452 final int step) throws IOException {
453 for (int y = offsetY; y < height; y = y + step)
454 for (int x = offsetX; x < width; x = x + step) {
456 final int halfStep = step / 2;
458 context2.initialize(image, yChannel.decodedMap,
459 uChannel.decodedMap, vChannel.decodedMap);
460 context2.measureNeighborEncode(x - halfStep, y - halfStep);
461 context2.measureNeighborEncode(x + halfStep, y - halfStep);
462 context2.measureNeighborEncode(x - halfStep, y + halfStep);
463 context2.measureNeighborEncode(x + halfStep, y + halfStep);
465 savePixel(step, offsetX, offsetY, x, y,
466 context2.colorStats.getAverageY(),
467 context2.colorStats.getAverageU(),
468 context2.colorStats.getAverageV());
473 private void saveGridSquare(final int offsetX, final int offsetY,
474 final int step) throws IOException {
475 for (int y = offsetY; y < height; y = y + step)
476 for (int x = offsetX; x < width; x = x + step) {
478 final int halfStep = step / 2;
480 context2.initialize(image, yChannel.decodedMap,
481 uChannel.decodedMap, vChannel.decodedMap);
482 context2.measureNeighborEncode(x - halfStep, y);
483 context2.measureNeighborEncode(x + halfStep, y);
484 context2.measureNeighborEncode(x, y - halfStep);
485 context2.measureNeighborEncode(x, y + halfStep);
487 savePixel(step, offsetX, offsetY, x, y,
488 context2.colorStats.getAverageY(),
489 context2.colorStats.getAverageU(),
490 context2.colorStats.getAverageV());
495 private void savePixel(final int step, final int offsetX, final int offsetY,
496 final int x, final int y, final int averageDecodedY,
497 final int averageDecodedU, final int averageDecodedV)
500 final int index = (y * width) + x;
502 final int py = byteToInt(yChannel.map[index]);
503 final int pu = byteToInt(uChannel.map[index]);
504 final int pv = byteToInt(vChannel.map[index]);
506 final int yRange = byteToInt(yChannel.rangeMap[index]);
507 final int uRange = byteToInt(uChannel.rangeMap[index]);
508 final int vRange = byteToInt(vChannel.rangeMap[index]);
510 final int halfStep = step / 2;
516 parentIndex = ((y - halfStep) * width) + (x - halfStep);
519 parentIndex = (y * width) + (x - halfStep);
522 parentIndex = ((y - halfStep) * width) + x;
524 encodeChannel(approximator.yTable, yChannel, averageDecodedY, index,
525 py, yRange, parentIndex);
527 encodeChannel(approximator.uTable, uChannel, averageDecodedU, index,
528 pu, uRange, parentIndex);
530 encodeChannel(approximator.vTable, vChannel, averageDecodedV, index,
531 pv, vRange, parentIndex);