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];
98 int green = pixels[colorBufferIndex + 1];
102 int red = pixels[colorBufferIndex + 2];
112 yChannel.map[index] = (byte) color.y;
113 uChannel.map[index] = (byte) color.u;
114 vChannel.map[index] = (byte) color.v;
118 yChannel.decodedMap[0] = yChannel.map[0];
119 uChannel.decodedMap[0] = uChannel.map[0];
120 vChannel.decodedMap[0] = vChannel.map[0];
122 bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
123 bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
124 bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
126 // detect initial step
127 int largestDimension;
129 if (width > height) {
130 largestDimension = width;
132 largestDimension = height;
135 while (initialStep < largestDimension) {
136 initialStep = initialStep * 2;
139 rangeGrid(initialStep);
141 saveGrid(initialStep);
144 private void encodeChannel(final Table table, final Channel channel,
145 final int averageDecodedValue, final int index, final int value,
146 final int range, final int parentIndex) throws IOException {
148 final byte[] decodedRangeMap = channel.decodedRangeMap;
149 final byte[] decodedMap = channel.decodedMap;
151 final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
153 final int inheritedBitCount = table
154 .proposeBitcountForRange(inheritedRange);
156 if (inheritedBitCount > 0) {
158 computedRange = table.proposeRangeForRange(range, inheritedRange);
159 decodedRangeMap[index] = (byte) computedRange;
162 if (computedRange != inheritedRange) {
163 // brightness range shrinked
164 bitOutputStream.storeBits(1, 1);
166 // brightness range stayed the same
167 bitOutputStream.storeBits(0, 1);
170 // encode brightness into available amount of bits
171 final int computedBitCount = table
172 .proposeBitcountForRange(computedRange);
174 if (computedBitCount > 0) {
176 final int differenceToEncode = -(value - averageDecodedValue);
177 final int bitEncodedDifference = encodeValueIntoGivenBits(
178 differenceToEncode, computedRange, computedBitCount);
180 channel.bitCount = channel.bitCount + computedBitCount;
181 bitOutputStream.storeBits(bitEncodedDifference,
184 final int decodedDifference = decodeValueFromGivenBits(
185 bitEncodedDifference, computedRange, computedBitCount);
186 int decodedValue = averageDecodedValue - decodedDifference;
187 if (decodedValue > 255)
189 if (decodedValue < 0)
192 decodedMap[index] = (byte) decodedValue;
194 decodedMap[index] = (byte) averageDecodedValue;
198 decodedRangeMap[index] = (byte) inheritedRange;
199 decodedMap[index] = (byte) averageDecodedValue;
203 public void printStatistics() {
204 System.out.println("Y channel:");
205 yChannel.printStatistics();
207 System.out.println("U channel:");
208 uChannel.printStatistics();
210 System.out.println("V channel:");
211 vChannel.printStatistics();
214 public void rangeGrid(final int step) {
216 // gridSquare(step / 2, step / 2, step, pixels);
218 rangeGridDiagonal(step / 2, step / 2, step);
219 rangeGridSquare(step / 2, 0, step);
220 rangeGridSquare(0, step / 2, step);
226 public void rangeGridDiagonal(final int offsetX, final int offsetY,
228 for (int y = offsetY; y < height; y = y + step) {
229 for (int x = offsetX; x < width; x = x + step) {
231 final int index = (y * width) + x;
232 final int halfStep = step / 2;
234 context.initialize(image, yChannel.map, uChannel.map,
237 context.measureNeighborEncode(x - halfStep, y - halfStep);
238 context.measureNeighborEncode(x + halfStep, y - halfStep);
239 context.measureNeighborEncode(x - halfStep, y + halfStep);
240 context.measureNeighborEncode(x + halfStep, y + halfStep);
242 yChannel.rangeMap[index] = (byte) context.getYRange(index);
243 uChannel.rangeMap[index] = (byte) context.getURange(index);
244 vChannel.rangeMap[index] = (byte) context.getVRange(index);
249 public void rangeGridSquare(final int offsetX, final int offsetY,
251 for (int y = offsetY; y < height; y = y + step) {
252 for (int x = offsetX; x < width; x = x + step) {
254 final int index = (y * width) + x;
255 final int halfStep = step / 2;
257 context.initialize(image, yChannel.map, uChannel.map,
260 context.measureNeighborEncode(x - halfStep, y);
261 context.measureNeighborEncode(x + halfStep, y);
262 context.measureNeighborEncode(x, y - halfStep);
263 context.measureNeighborEncode(x, y + halfStep);
265 yChannel.rangeMap[index] = (byte) context.getYRange(index);
266 uChannel.rangeMap[index] = (byte) context.getURange(index);
267 vChannel.rangeMap[index] = (byte) context.getVRange(index);
272 public void rangeRoundGrid(final int step) {
274 rangeRoundGridDiagonal(step / 2, step / 2, step);
275 rangeRoundGridSquare(step / 2, 0, step);
276 rangeRoundGridSquare(0, step / 2, step);
279 rangeRoundGrid(step * 2);
282 public void rangeRoundGridDiagonal(final int offsetX, final int offsetY,
284 for (int y = offsetY; y < height; y = y + step) {
285 for (int x = offsetX; x < width; x = x + step) {
287 final int index = (y * width) + x;
289 final int yRange = byteToInt(yChannel.rangeMap[index]);
290 final int uRange = byteToInt(uChannel.rangeMap[index]);
291 final int vRange = byteToInt(vChannel.rangeMap[index]);
293 final int halfStep = step / 2;
295 final int parentIndex = ((y - halfStep) * width)
298 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
300 if (parentYRange < yRange) {
301 parentYRange = yRange;
302 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
305 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
307 if (parentURange < uRange) {
308 parentURange = uRange;
309 uChannel.rangeMap[parentIndex] = (byte) parentURange;
312 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
314 if (parentVRange < vRange) {
315 parentVRange = vRange;
316 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
322 public void rangeRoundGridSquare(final int offsetX, final int offsetY,
324 for (int y = offsetY; y < height; y = y + step) {
325 for (int x = offsetX; x < width; x = x + step) {
327 final int index = (y * width) + x;
329 final int yRange = byteToInt(yChannel.rangeMap[index]);
330 final int uRange = byteToInt(uChannel.rangeMap[index]);
331 final int vRange = byteToInt(vChannel.rangeMap[index]);
333 final int halfStep = step / 2;
337 parentIndex = (y * width) + (x - halfStep);
339 parentIndex = ((y - halfStep) * width) + x;
342 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
344 if (parentYRange < yRange) {
345 parentYRange = yRange;
346 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
349 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
351 if (parentURange < uRange) {
352 parentURange = uRange;
353 uChannel.rangeMap[parentIndex] = (byte) parentURange;
356 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
358 if (parentVRange < vRange) {
359 parentVRange = vRange;
360 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
367 public void saveGrid(final int step) throws IOException {
369 saveGridDiagonal(step / 2, step / 2, step);
370 saveGridSquare(step / 2, 0, step);
371 saveGridSquare(0, step / 2, step);
377 public void saveGridDiagonal(final int offsetX, final int offsetY,
378 final int step) throws IOException {
379 for (int y = offsetY; y < height; y = y + step) {
380 for (int x = offsetX; x < width; x = x + step) {
382 final int halfStep = step / 2;
384 context2.initialize(image, yChannel.decodedMap,
385 uChannel.decodedMap, vChannel.decodedMap);
386 context2.measureNeighborEncode(x - halfStep, y - halfStep);
387 context2.measureNeighborEncode(x + halfStep, y - halfStep);
388 context2.measureNeighborEncode(x - halfStep, y + halfStep);
389 context2.measureNeighborEncode(x + halfStep, y + halfStep);
391 savePixel(step, offsetX, offsetY, x, y,
392 context2.colorStats.getAverageY(),
393 context2.colorStats.getAverageU(),
394 context2.colorStats.getAverageV());
400 public void saveGridSquare(final int offsetX, final int offsetY,
401 final int step) throws IOException {
402 for (int y = offsetY; y < height; y = y + step) {
403 for (int x = offsetX; x < width; x = x + step) {
405 final int halfStep = step / 2;
407 context2.initialize(image, yChannel.decodedMap,
408 uChannel.decodedMap, vChannel.decodedMap);
409 context2.measureNeighborEncode(x - halfStep, y);
410 context2.measureNeighborEncode(x + halfStep, y);
411 context2.measureNeighborEncode(x, y - halfStep);
412 context2.measureNeighborEncode(x, y + halfStep);
414 savePixel(step, offsetX, offsetY, x, y,
415 context2.colorStats.getAverageY(),
416 context2.colorStats.getAverageU(),
417 context2.colorStats.getAverageV());
423 public void savePixel(final int step, final int offsetX, final int offsetY,
424 final int x, final int y, final int averageDecodedY,
425 final int averageDecodedU, final int averageDecodedV)
428 final int index = (y * width) + x;
430 final int py = byteToInt(yChannel.map[index]);
431 final int pu = byteToInt(uChannel.map[index]);
432 final int pv = byteToInt(vChannel.map[index]);
434 final int yRange = byteToInt(yChannel.rangeMap[index]);
435 final int uRange = byteToInt(uChannel.rangeMap[index]);
436 final int vRange = byteToInt(vChannel.rangeMap[index]);
438 final int halfStep = step / 2;
444 parentIndex = ((y - halfStep) * width) + (x - halfStep);
447 parentIndex = (y * width) + (x - halfStep);
451 parentIndex = ((y - halfStep) * width) + x;
454 encodeChannel(approximator.yTable, yChannel, averageDecodedY, index,
455 py, yRange, parentIndex);
457 encodeChannel(approximator.uTable, uChannel, averageDecodedU, index,
458 pu, uRange, parentIndex);
460 encodeChannel(approximator.vTable, vChannel, averageDecodedV, index,
461 pv, vRange, parentIndex);
465 public static int byteToInt(final byte input) {
468 result = result + 256;
472 public static int decodeValueFromGivenBits(final int encodedBits,
473 final int range, final int bitCount) {
474 final int negativeBit = encodedBits & 1;
476 final int remainingBitCount = bitCount - 1;
478 if (remainingBitCount == 0) {
479 // no more bits remaining to encode actual value
481 if (negativeBit == 0) {
488 // still one or more bits left, encode value as precisely as
491 final int encodedValue = (encodedBits >>> 1) + 1;
493 final int realvalueForThisBitcount = 1 << remainingBitCount;
495 // int valueMultiplier = range / realvalueForThisBitcount;
496 int decodedValue = (range * encodedValue)
497 / realvalueForThisBitcount;
499 if (decodedValue > range)
500 decodedValue = range;
502 if (negativeBit == 0) {
505 return -decodedValue;
511 public static int encodeValueIntoGivenBits(int value, final int range,
512 final int bitCount) {
521 final int remainingBitCount = bitCount - 1;
523 if (remainingBitCount == 0) {
524 // no more bits remaining to encode actual value
529 // still one or more bits left, encode value as precisely as
535 final int realvalueForThisBitcount = 1 << remainingBitCount;
536 // int valueMultiplier = range / realvalueForThisBitcount;
537 int encodedValue = (value * realvalueForThisBitcount) / range;
539 if (encodedValue >= realvalueForThisBitcount)
540 encodedValue = realvalueForThisBitcount - 1;
542 encodedValue = (encodedValue << 1) + negativeBit;