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 {
31 Approximator approximator;
37 // ColorStats colorStats = new ColorStats();
38 OperatingContext context = new OperatingContext();
39 OperatingContext context2 = new OperatingContext();
41 BitOutputStream bitOutputStream;
43 public ImageEncoder(final Image image) {
44 approximator = new Approximator();
46 // bitOutputStream = outputStream;
52 public void encode(final BitOutputStream bitOutputStream)
54 this.bitOutputStream = bitOutputStream;
56 approximator.initialize();
58 approximator.save(bitOutputStream);
60 width = image.metaData.width;
61 height = image.metaData.height;
63 final WritableRaster raster = image.bufferedImage.getRaster();
64 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
65 final byte[] pixels = dbi.getData();
67 if (yChannel == null) {
68 yChannel = new Channel(width, height);
73 if (uChannel == null) {
74 uChannel = new Channel(width, height);
79 if (vChannel == null) {
80 vChannel = new Channel(width, height);
85 // create YUV map out of RGB raster data
86 final Color color = new Color();
88 for (int y = 0; y < height; y++) {
89 for (int x = 0; x < width; x++) {
91 final int index = (y * width) + x;
92 final int colorBufferIndex = index * 3;
94 int blue = pixels[colorBufferIndex];
99 int green = pixels[colorBufferIndex + 1];
104 int red = pixels[colorBufferIndex + 2];
115 yChannel.map[index] = (byte) color.y;
116 uChannel.map[index] = (byte) color.u;
117 vChannel.map[index] = (byte) color.v;
121 yChannel.decodedMap[0] = yChannel.map[0];
122 uChannel.decodedMap[0] = uChannel.map[0];
123 vChannel.decodedMap[0] = vChannel.map[0];
125 bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
126 bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
127 bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
129 // detect initial step
130 int largestDimension;
132 if (width > height) {
133 largestDimension = width;
135 largestDimension = height;
138 while (initialStep < largestDimension) {
139 initialStep = initialStep * 2;
142 rangeGrid(initialStep);
144 saveGrid(initialStep);
147 private void encodeChannel(final Table table, final Channel channel,
148 final int averageDecodedValue, final int index, final int value,
149 final int range, final int parentIndex) throws IOException {
151 final byte[] decodedRangeMap = channel.decodedRangeMap;
152 final byte[] decodedMap = channel.decodedMap;
154 final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
156 final int inheritedBitCount = table
157 .proposeBitcountForRange(inheritedRange);
159 if (inheritedBitCount > 0) {
161 computedRange = table.proposeRangeForRange(range, inheritedRange);
162 decodedRangeMap[index] = (byte) computedRange;
165 if (computedRange != inheritedRange) {
166 // brightness range shrinked
167 bitOutputStream.storeBits(1, 1);
169 // brightness range stayed the same
170 bitOutputStream.storeBits(0, 1);
173 // encode brightness into available amount of bits
174 final int computedBitCount = table
175 .proposeBitcountForRange(computedRange);
177 if (computedBitCount > 0) {
179 final int differenceToEncode = -(value - averageDecodedValue);
180 final int bitEncodedDifference = encodeValueIntoGivenBits(
181 differenceToEncode, computedRange, computedBitCount);
183 channel.bitCount = channel.bitCount + computedBitCount;
184 bitOutputStream.storeBits(bitEncodedDifference,
187 final int decodedDifference = decodeValueFromGivenBits(
188 bitEncodedDifference, computedRange, computedBitCount);
189 int decodedValue = averageDecodedValue - decodedDifference;
190 if (decodedValue > 255) {
193 if (decodedValue < 0) {
197 decodedMap[index] = (byte) decodedValue;
199 decodedMap[index] = (byte) averageDecodedValue;
203 decodedRangeMap[index] = (byte) inheritedRange;
204 decodedMap[index] = (byte) averageDecodedValue;
208 public void printStatistics() {
209 System.out.println("Y channel:");
210 yChannel.printStatistics();
212 System.out.println("U channel:");
213 uChannel.printStatistics();
215 System.out.println("V channel:");
216 vChannel.printStatistics();
219 public void rangeGrid(final int step) {
221 // gridSquare(step / 2, step / 2, step, pixels);
223 rangeGridDiagonal(step / 2, step / 2, step);
224 rangeGridSquare(step / 2, 0, step);
225 rangeGridSquare(0, step / 2, step);
232 public void rangeGridDiagonal(final int offsetX, final int offsetY,
234 for (int y = offsetY; y < height; y = y + step) {
235 for (int x = offsetX; x < width; x = x + step) {
237 final int index = (y * width) + x;
238 final int halfStep = step / 2;
240 context.initialize(image, yChannel.map, uChannel.map,
243 context.measureNeighborEncode(x - halfStep, y - halfStep);
244 context.measureNeighborEncode(x + halfStep, y - halfStep);
245 context.measureNeighborEncode(x - halfStep, y + halfStep);
246 context.measureNeighborEncode(x + halfStep, y + halfStep);
248 yChannel.rangeMap[index] = (byte) context.getYRange(index);
249 uChannel.rangeMap[index] = (byte) context.getURange(index);
250 vChannel.rangeMap[index] = (byte) context.getVRange(index);
255 public void rangeGridSquare(final int offsetX, final int offsetY,
257 for (int y = offsetY; y < height; y = y + step) {
258 for (int x = offsetX; x < width; x = x + step) {
260 final int index = (y * width) + x;
261 final int halfStep = step / 2;
263 context.initialize(image, yChannel.map, uChannel.map,
266 context.measureNeighborEncode(x - halfStep, y);
267 context.measureNeighborEncode(x + halfStep, y);
268 context.measureNeighborEncode(x, y - halfStep);
269 context.measureNeighborEncode(x, y + halfStep);
271 yChannel.rangeMap[index] = (byte) context.getYRange(index);
272 uChannel.rangeMap[index] = (byte) context.getURange(index);
273 vChannel.rangeMap[index] = (byte) context.getVRange(index);
278 public void rangeRoundGrid(final int step) {
280 rangeRoundGridDiagonal(step / 2, step / 2, step);
281 rangeRoundGridSquare(step / 2, 0, step);
282 rangeRoundGridSquare(0, step / 2, step);
285 rangeRoundGrid(step * 2);
289 public void rangeRoundGridDiagonal(final int offsetX, final int offsetY,
291 for (int y = offsetY; y < height; y = y + step) {
292 for (int x = offsetX; x < width; x = x + step) {
294 final int index = (y * width) + x;
296 final int yRange = byteToInt(yChannel.rangeMap[index]);
297 final int uRange = byteToInt(uChannel.rangeMap[index]);
298 final int vRange = byteToInt(vChannel.rangeMap[index]);
300 final int halfStep = step / 2;
302 final int parentIndex = ((y - halfStep) * width)
305 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
307 if (parentYRange < yRange) {
308 parentYRange = yRange;
309 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
312 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
314 if (parentURange < uRange) {
315 parentURange = uRange;
316 uChannel.rangeMap[parentIndex] = (byte) parentURange;
319 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
321 if (parentVRange < vRange) {
322 parentVRange = vRange;
323 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
329 public void rangeRoundGridSquare(final int offsetX, final int offsetY,
331 for (int y = offsetY; y < height; y = y + step) {
332 for (int x = offsetX; x < width; x = x + step) {
334 final int index = (y * width) + x;
336 final int yRange = byteToInt(yChannel.rangeMap[index]);
337 final int uRange = byteToInt(uChannel.rangeMap[index]);
338 final int vRange = byteToInt(vChannel.rangeMap[index]);
340 final int halfStep = step / 2;
344 parentIndex = (y * width) + (x - halfStep);
346 parentIndex = ((y - halfStep) * width) + x;
349 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
351 if (parentYRange < yRange) {
352 parentYRange = yRange;
353 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
356 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
358 if (parentURange < uRange) {
359 parentURange = uRange;
360 uChannel.rangeMap[parentIndex] = (byte) parentURange;
363 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
365 if (parentVRange < vRange) {
366 parentVRange = vRange;
367 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
374 public void saveGrid(final int step) throws IOException {
376 saveGridDiagonal(step / 2, step / 2, step);
377 saveGridSquare(step / 2, 0, step);
378 saveGridSquare(0, step / 2, step);
385 public void saveGridDiagonal(final int offsetX, final int offsetY,
386 final int step) throws IOException {
387 for (int y = offsetY; y < height; y = y + step) {
388 for (int x = offsetX; x < width; x = x + step) {
390 final int halfStep = step / 2;
392 context2.initialize(image, yChannel.decodedMap,
393 uChannel.decodedMap, vChannel.decodedMap);
394 context2.measureNeighborEncode(x - halfStep, y - halfStep);
395 context2.measureNeighborEncode(x + halfStep, y - halfStep);
396 context2.measureNeighborEncode(x - halfStep, y + halfStep);
397 context2.measureNeighborEncode(x + halfStep, y + halfStep);
399 savePixel(step, offsetX, offsetY, x, y,
400 context2.colorStats.getAverageY(),
401 context2.colorStats.getAverageU(),
402 context2.colorStats.getAverageV());
408 public void saveGridSquare(final int offsetX, final int offsetY,
409 final int step) throws IOException {
410 for (int y = offsetY; y < height; y = y + step) {
411 for (int x = offsetX; x < width; x = x + step) {
413 final int halfStep = step / 2;
415 context2.initialize(image, yChannel.decodedMap,
416 uChannel.decodedMap, vChannel.decodedMap);
417 context2.measureNeighborEncode(x - halfStep, y);
418 context2.measureNeighborEncode(x + halfStep, y);
419 context2.measureNeighborEncode(x, y - halfStep);
420 context2.measureNeighborEncode(x, y + halfStep);
422 savePixel(step, offsetX, offsetY, x, y,
423 context2.colorStats.getAverageY(),
424 context2.colorStats.getAverageU(),
425 context2.colorStats.getAverageV());
431 public void savePixel(final int step, final int offsetX, final int offsetY,
432 final int x, final int y, final int averageDecodedY,
433 final int averageDecodedU, final int averageDecodedV)
436 final int index = (y * width) + x;
438 final int py = byteToInt(yChannel.map[index]);
439 final int pu = byteToInt(uChannel.map[index]);
440 final int pv = byteToInt(vChannel.map[index]);
442 final int yRange = byteToInt(yChannel.rangeMap[index]);
443 final int uRange = byteToInt(uChannel.rangeMap[index]);
444 final int vRange = byteToInt(vChannel.rangeMap[index]);
446 final int halfStep = step / 2;
452 parentIndex = ((y - halfStep) * width) + (x - halfStep);
455 parentIndex = (y * width) + (x - halfStep);
459 parentIndex = ((y - halfStep) * width) + x;
462 encodeChannel(approximator.yTable, yChannel, averageDecodedY, index,
463 py, yRange, parentIndex);
465 encodeChannel(approximator.uTable, uChannel, averageDecodedU, index,
466 pu, uRange, parentIndex);
468 encodeChannel(approximator.vTable, vChannel, averageDecodedV, index,
469 pv, vRange, parentIndex);
473 public static int byteToInt(final byte input) {
476 result = result + 256;
481 public static int decodeValueFromGivenBits(final int encodedBits,
482 final int range, final int bitCount) {
483 final int negativeBit = encodedBits & 1;
485 final int remainingBitCount = bitCount - 1;
487 if (remainingBitCount == 0) {
488 // no more bits remaining to encode actual value
490 if (negativeBit == 0) {
497 // still one or more bits left, encode value as precisely as
500 final int encodedValue = (encodedBits >>> 1) + 1;
502 final int realvalueForThisBitcount = 1 << remainingBitCount;
504 // int valueMultiplier = range / realvalueForThisBitcount;
505 int decodedValue = (range * encodedValue)
506 / realvalueForThisBitcount;
508 if (decodedValue > range) {
509 decodedValue = range;
512 if (negativeBit == 0) {
515 return -decodedValue;
521 public static int encodeValueIntoGivenBits(int value, final int range,
522 final int bitCount) {
531 final int remainingBitCount = bitCount - 1;
533 if (remainingBitCount == 0) {
534 // no more bits remaining to encode actual value
539 // still one or more bits left, encode value as precisely as
546 final int realvalueForThisBitcount = 1 << remainingBitCount;
547 // int valueMultiplier = range / realvalueForThisBitcount;
548 int encodedValue = (value * realvalueForThisBitcount) / range;
550 if (encodedValue >= realvalueForThisBitcount) {
551 encodedValue = realvalueForThisBitcount - 1;
554 encodedValue = (encodedValue << 1) + negativeBit;