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 java.awt.image.DataBufferByte;
17 import java.awt.image.WritableRaster;
18 import java.io.IOException;
20 import eu.svjatoslav.commons.data.BitOutputStream;
22 public class ImageEncoder {
24 public static int byteToInt(final byte input) {
27 result = result + 256;
31 public static int decodeValueFromGivenBits(final int encodedBits,
32 final int range, final int bitCount) {
33 final int negativeBit = encodedBits & 1;
35 final int remainingBitCount = bitCount - 1;
37 if (remainingBitCount == 0) {
38 // no more bits remaining to encode actual value
46 // still one or more bits left, encode value as precisely as
49 final int encodedValue = (encodedBits >>> 1) + 1;
51 final int realvalueForThisBitcount = 1 << remainingBitCount;
53 // int valueMultiplier = range / realvalueForThisBitcount;
54 int decodedValue = (range * encodedValue)
55 / realvalueForThisBitcount;
57 if (decodedValue > range)
68 public static int encodeValueIntoGivenBits(int value, final int range,
78 final int remainingBitCount = bitCount - 1;
80 if (remainingBitCount == 0)
83 // still one or more bits left, encode value as precisely as
89 final int realvalueForThisBitcount = 1 << remainingBitCount;
90 // int valueMultiplier = range / realvalueForThisBitcount;
91 int encodedValue = (value * realvalueForThisBitcount) / range;
93 if (encodedValue >= realvalueForThisBitcount)
94 encodedValue = realvalueForThisBitcount - 1;
96 encodedValue = (encodedValue << 1) + negativeBit;
102 public static void storeIntegerCompressed8(
103 final BitOutputStream outputStream, final int data)
107 outputStream.storeBits(0, 1);
108 outputStream.storeBits(data, 8);
110 outputStream.storeBits(1, 1);
111 outputStream.storeBits(data, 32);
122 Approximator approximator;
129 // ColorStats colorStats = new ColorStats();
130 OperatingContext context = new OperatingContext();
132 OperatingContext context2 = new OperatingContext();
134 BitOutputStream bitOutputStream;
136 public ImageEncoder(final Image image) {
137 approximator = new Approximator();
139 // bitOutputStream = outputStream;
145 public void encode(final BitOutputStream bitOutputStream)
147 this.bitOutputStream = bitOutputStream;
149 approximator.initialize();
151 approximator.save(bitOutputStream);
153 width = image.metaData.width;
154 height = image.metaData.height;
156 final WritableRaster raster = image.bufferedImage.getRaster();
157 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
158 final byte[] pixels = dbi.getData();
160 if (yChannel == null)
161 yChannel = new Channel(width, height);
165 if (uChannel == null)
166 uChannel = new Channel(width, height);
170 if (vChannel == null)
171 vChannel = new Channel(width, height);
175 // create YUV map out of RGB raster data
176 final Color color = new Color();
178 for (int y = 0; y < height; y++)
179 for (int x = 0; x < width; x++) {
181 final int index = (y * width) + x;
182 final int colorBufferIndex = index * 3;
184 int blue = pixels[colorBufferIndex];
188 int green = pixels[colorBufferIndex + 1];
192 int red = pixels[colorBufferIndex + 2];
202 yChannel.map[index] = (byte) color.y;
203 uChannel.map[index] = (byte) color.u;
204 vChannel.map[index] = (byte) color.v;
207 yChannel.decodedMap[0] = yChannel.map[0];
208 uChannel.decodedMap[0] = uChannel.map[0];
209 vChannel.decodedMap[0] = vChannel.map[0];
211 bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
212 bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
213 bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
215 // detect initial step
216 int largestDimension;
219 largestDimension = width;
221 largestDimension = height;
223 while (initialStep < largestDimension)
224 initialStep = initialStep * 2;
226 rangeGrid(initialStep);
228 saveGrid(initialStep);
231 private void encodeChannel(final Table table, final Channel channel,
232 final int averageDecodedValue, final int index, final int value,
233 final int range, final int parentIndex) throws IOException {
235 final byte[] decodedRangeMap = channel.decodedRangeMap;
236 final byte[] decodedMap = channel.decodedMap;
238 final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
240 final int inheritedBitCount = table
241 .proposeBitcountForRange(inheritedRange);
243 if (inheritedBitCount > 0) {
245 computedRange = table.proposeRangeForRange(range, inheritedRange);
246 decodedRangeMap[index] = (byte) computedRange;
249 if (computedRange != inheritedRange)
250 // brightness range shrinked
251 bitOutputStream.storeBits(1, 1);
253 // brightness range stayed the same
254 bitOutputStream.storeBits(0, 1);
256 // encode brightness into available amount of bits
257 final int computedBitCount = table
258 .proposeBitcountForRange(computedRange);
260 if (computedBitCount > 0) {
262 final int differenceToEncode = -(value - averageDecodedValue);
263 final int bitEncodedDifference = encodeValueIntoGivenBits(
264 differenceToEncode, computedRange, computedBitCount);
266 channel.bitCount = channel.bitCount + computedBitCount;
267 bitOutputStream.storeBits(bitEncodedDifference,
270 final int decodedDifference = decodeValueFromGivenBits(
271 bitEncodedDifference, computedRange, computedBitCount);
272 int decodedValue = averageDecodedValue - decodedDifference;
273 if (decodedValue > 255)
275 if (decodedValue < 0)
278 decodedMap[index] = (byte) decodedValue;
280 decodedMap[index] = (byte) averageDecodedValue;
283 decodedRangeMap[index] = (byte) inheritedRange;
284 decodedMap[index] = (byte) averageDecodedValue;
288 public void printStatistics() {
289 System.out.println("Y channel:");
290 yChannel.printStatistics();
292 System.out.println("U channel:");
293 uChannel.printStatistics();
295 System.out.println("V channel:");
296 vChannel.printStatistics();
299 public void rangeGrid(final int step) {
301 // gridSquare(step / 2, step / 2, step, pixels);
303 rangeGridDiagonal(step / 2, step / 2, step);
304 rangeGridSquare(step / 2, 0, step);
305 rangeGridSquare(0, step / 2, step);
311 public void rangeGridDiagonal(final int offsetX, final int offsetY,
313 for (int y = offsetY; y < height; y = y + step)
314 for (int x = offsetX; x < width; x = x + step) {
316 final int index = (y * width) + x;
317 final int halfStep = step / 2;
319 context.initialize(image, yChannel.map, uChannel.map,
322 context.measureNeighborEncode(x - halfStep, y - halfStep);
323 context.measureNeighborEncode(x + halfStep, y - halfStep);
324 context.measureNeighborEncode(x - halfStep, y + halfStep);
325 context.measureNeighborEncode(x + halfStep, y + halfStep);
327 yChannel.rangeMap[index] = (byte) context.getYRange(index);
328 uChannel.rangeMap[index] = (byte) context.getURange(index);
329 vChannel.rangeMap[index] = (byte) context.getVRange(index);
333 public void rangeGridSquare(final int offsetX, final int offsetY,
335 for (int y = offsetY; y < height; y = y + step)
336 for (int x = offsetX; x < width; x = x + step) {
338 final int index = (y * width) + x;
339 final int halfStep = step / 2;
341 context.initialize(image, yChannel.map, uChannel.map,
344 context.measureNeighborEncode(x - halfStep, y);
345 context.measureNeighborEncode(x + halfStep, y);
346 context.measureNeighborEncode(x, y - halfStep);
347 context.measureNeighborEncode(x, y + halfStep);
349 yChannel.rangeMap[index] = (byte) context.getYRange(index);
350 uChannel.rangeMap[index] = (byte) context.getURange(index);
351 vChannel.rangeMap[index] = (byte) context.getVRange(index);
355 public void rangeRoundGrid(final int step) {
357 rangeRoundGridDiagonal(step / 2, step / 2, step);
358 rangeRoundGridSquare(step / 2, 0, step);
359 rangeRoundGridSquare(0, step / 2, step);
362 rangeRoundGrid(step * 2);
365 public void rangeRoundGridDiagonal(final int offsetX, final int offsetY,
367 for (int y = offsetY; y < height; y = y + step)
368 for (int x = offsetX; x < width; x = x + step) {
370 final int index = (y * width) + x;
372 final int yRange = byteToInt(yChannel.rangeMap[index]);
373 final int uRange = byteToInt(uChannel.rangeMap[index]);
374 final int vRange = byteToInt(vChannel.rangeMap[index]);
376 final int halfStep = step / 2;
378 final int parentIndex = ((y - halfStep) * width)
381 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
383 if (parentYRange < yRange) {
384 parentYRange = yRange;
385 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
388 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
390 if (parentURange < uRange) {
391 parentURange = uRange;
392 uChannel.rangeMap[parentIndex] = (byte) parentURange;
395 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
397 if (parentVRange < vRange) {
398 parentVRange = vRange;
399 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
404 public void rangeRoundGridSquare(final int offsetX, final int offsetY,
406 for (int y = offsetY; y < height; y = y + step)
407 for (int x = offsetX; x < width; x = x + step) {
409 final int index = (y * width) + x;
411 final int yRange = byteToInt(yChannel.rangeMap[index]);
412 final int uRange = byteToInt(uChannel.rangeMap[index]);
413 final int vRange = byteToInt(vChannel.rangeMap[index]);
415 final int halfStep = step / 2;
419 parentIndex = (y * width) + (x - halfStep);
421 parentIndex = ((y - halfStep) * width) + x;
423 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
425 if (parentYRange < yRange) {
426 parentYRange = yRange;
427 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
430 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
432 if (parentURange < uRange) {
433 parentURange = uRange;
434 uChannel.rangeMap[parentIndex] = (byte) parentURange;
437 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
439 if (parentVRange < vRange) {
440 parentVRange = vRange;
441 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
447 public void saveGrid(final int step) throws IOException {
449 saveGridDiagonal(step / 2, step / 2, step);
450 saveGridSquare(step / 2, 0, step);
451 saveGridSquare(0, step / 2, step);
457 public void saveGridDiagonal(final int offsetX, final int offsetY,
458 final int step) throws IOException {
459 for (int y = offsetY; y < height; y = y + step)
460 for (int x = offsetX; x < width; x = x + step) {
462 final int halfStep = step / 2;
464 context2.initialize(image, yChannel.decodedMap,
465 uChannel.decodedMap, vChannel.decodedMap);
466 context2.measureNeighborEncode(x - halfStep, y - halfStep);
467 context2.measureNeighborEncode(x + halfStep, y - halfStep);
468 context2.measureNeighborEncode(x - halfStep, y + halfStep);
469 context2.measureNeighborEncode(x + halfStep, y + halfStep);
471 savePixel(step, offsetX, offsetY, x, y,
472 context2.colorStats.getAverageY(),
473 context2.colorStats.getAverageU(),
474 context2.colorStats.getAverageV());
479 public void saveGridSquare(final int offsetX, final int offsetY,
480 final int step) throws IOException {
481 for (int y = offsetY; y < height; y = y + step)
482 for (int x = offsetX; x < width; x = x + step) {
484 final int halfStep = step / 2;
486 context2.initialize(image, yChannel.decodedMap,
487 uChannel.decodedMap, vChannel.decodedMap);
488 context2.measureNeighborEncode(x - halfStep, y);
489 context2.measureNeighborEncode(x + halfStep, y);
490 context2.measureNeighborEncode(x, y - halfStep);
491 context2.measureNeighborEncode(x, y + halfStep);
493 savePixel(step, offsetX, offsetY, x, y,
494 context2.colorStats.getAverageY(),
495 context2.colorStats.getAverageU(),
496 context2.colorStats.getAverageV());
501 public void savePixel(final int step, final int offsetX, final int offsetY,
502 final int x, final int y, final int averageDecodedY,
503 final int averageDecodedU, final int averageDecodedV)
506 final int index = (y * width) + x;
508 final int py = byteToInt(yChannel.map[index]);
509 final int pu = byteToInt(uChannel.map[index]);
510 final int pv = byteToInt(vChannel.map[index]);
512 final int yRange = byteToInt(yChannel.rangeMap[index]);
513 final int uRange = byteToInt(uChannel.rangeMap[index]);
514 final int vRange = byteToInt(vChannel.rangeMap[index]);
516 final int halfStep = step / 2;
522 parentIndex = ((y - halfStep) * width) + (x - halfStep);
525 parentIndex = (y * width) + (x - halfStep);
528 parentIndex = ((y - halfStep) * width) + x;
530 encodeChannel(approximator.yTable, yChannel, averageDecodedY, index,
531 py, yRange, parentIndex);
533 encodeChannel(approximator.uTable, uChannel, averageDecodedU, index,
534 pu, uRange, parentIndex);
536 encodeChannel(approximator.vTable, vChannel, averageDecodedV, index,
537 pv, vRange, parentIndex);