2 * Image codec. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3 * This project is released under Creative Commons Zero (CC0) license.
5 package eu.svjatoslav.imagesqueeze.codec;
8 * Compressed image pixels encoder.
11 import eu.svjatoslav.commons.data.BitOutputStream;
13 import java.awt.image.DataBufferByte;
14 import java.awt.image.WritableRaster;
15 import java.io.IOException;
19 private final Image image;
20 private final Approximator approximator;
21 // ColorStats colorStats = new ColorStats();
22 private final OperatingContext context = new OperatingContext();
23 private final OperatingContext context2 = new OperatingContext();
29 private Channel yChannel;
30 private Channel uChannel;
31 private Channel vChannel;
32 private BitOutputStream bitOutputStream;
34 public ImageEncoder(final Image image) {
35 approximator = new Approximator();
37 // bitOutputStream = outputStream;
43 public static int byteToInt(final byte input) {
46 result = result + 256;
50 public static int decodeValueFromGivenBits(final int encodedBits,
51 final int range, final int bitCount) {
52 final int negativeBit = encodedBits & 1;
54 final int remainingBitCount = bitCount - 1;
56 if (remainingBitCount == 0) {
57 // no more bits remaining to encode actual value
65 // still one or more bits left, encode value as precisely as
68 final int encodedValue = (encodedBits >>> 1) + 1;
70 final int realvalueForThisBitcount = 1 << remainingBitCount;
72 // int valueMultiplier = range / realvalueForThisBitcount;
73 int decodedValue = (range * encodedValue)
74 / realvalueForThisBitcount;
76 if (decodedValue > range)
87 private static int encodeValueIntoGivenBits(int value, final int range,
97 final int remainingBitCount = bitCount - 1;
99 if (remainingBitCount == 0)
102 // still one or more bits left, encode value as precisely as
108 final int realvalueForThisBitcount = 1 << remainingBitCount;
109 // int valueMultiplier = range / realvalueForThisBitcount;
110 int encodedValue = (value * realvalueForThisBitcount) / range;
112 if (encodedValue >= realvalueForThisBitcount)
113 encodedValue = realvalueForThisBitcount - 1;
115 encodedValue = (encodedValue << 1) + negativeBit;
121 public static void storeIntegerCompressed8(
122 final BitOutputStream outputStream, final int data)
126 outputStream.storeBits(0, 1);
127 outputStream.storeBits(data, 8);
129 outputStream.storeBits(1, 1);
130 outputStream.storeBits(data, 32);
134 public void encode(final BitOutputStream bitOutputStream)
136 this.bitOutputStream = bitOutputStream;
138 approximator.initialize();
140 approximator.save(bitOutputStream);
142 width = image.metaData.width;
143 height = image.metaData.height;
145 final WritableRaster raster = image.bufferedImage.getRaster();
146 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
147 final byte[] pixels = dbi.getData();
149 if (yChannel == null)
150 yChannel = new Channel(width, height);
154 if (uChannel == null)
155 uChannel = new Channel(width, height);
159 if (vChannel == null)
160 vChannel = new Channel(width, height);
164 // create YUV map out of RGB raster data
165 final Color color = new Color();
167 for (int y = 0; y < height; y++)
168 for (int x = 0; x < width; x++) {
170 final int index = (y * width) + x;
171 final int colorBufferIndex = index * 3;
173 int blue = pixels[colorBufferIndex];
177 int green = pixels[colorBufferIndex + 1];
181 int red = pixels[colorBufferIndex + 2];
191 yChannel.map[index] = (byte) color.y;
192 uChannel.map[index] = (byte) color.u;
193 vChannel.map[index] = (byte) color.v;
196 yChannel.decodedMap[0] = yChannel.map[0];
197 uChannel.decodedMap[0] = uChannel.map[0];
198 vChannel.decodedMap[0] = vChannel.map[0];
200 bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
201 bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
202 bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
204 // detect initial step
205 int largestDimension;
208 largestDimension = width;
210 largestDimension = height;
212 while (initialStep < largestDimension)
213 initialStep = initialStep * 2;
215 rangeGrid(initialStep);
217 saveGrid(initialStep);
220 private void encodeChannel(final Table table, final Channel channel,
221 final int averageDecodedValue, final int index, final int value,
222 final int range, final int parentIndex) throws IOException {
224 final byte[] decodedRangeMap = channel.decodedRangeMap;
225 final byte[] decodedMap = channel.decodedMap;
227 final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
229 final int inheritedBitCount = table
230 .proposeBitcountForRange(inheritedRange);
232 if (inheritedBitCount > 0) {
234 computedRange = table.proposeRangeForRange(range, inheritedRange);
235 decodedRangeMap[index] = (byte) computedRange;
238 if (computedRange != inheritedRange)
239 // brightness range shrinked
240 bitOutputStream.storeBits(1, 1);
242 // brightness range stayed the same
243 bitOutputStream.storeBits(0, 1);
245 // encode brightness into available amount of bits
246 final int computedBitCount = table
247 .proposeBitcountForRange(computedRange);
249 if (computedBitCount > 0) {
251 final int differenceToEncode = -(value - averageDecodedValue);
252 final int bitEncodedDifference = encodeValueIntoGivenBits(
253 differenceToEncode, computedRange, computedBitCount);
255 channel.bitCount = channel.bitCount + computedBitCount;
256 bitOutputStream.storeBits(bitEncodedDifference,
259 final int decodedDifference = decodeValueFromGivenBits(
260 bitEncodedDifference, computedRange, computedBitCount);
261 int decodedValue = averageDecodedValue - decodedDifference;
262 if (decodedValue > 255)
264 if (decodedValue < 0)
267 decodedMap[index] = (byte) decodedValue;
269 decodedMap[index] = (byte) averageDecodedValue;
272 decodedRangeMap[index] = (byte) inheritedRange;
273 decodedMap[index] = (byte) averageDecodedValue;
277 public void printStatistics() {
278 System.out.println("Y channel:");
279 yChannel.printStatistics();
281 System.out.println("U channel:");
282 uChannel.printStatistics();
284 System.out.println("V channel:");
285 vChannel.printStatistics();
288 private void rangeGrid(final int step) {
290 // gridSquare(step / 2, step / 2, step, pixels);
292 rangeGridDiagonal(step / 2, step / 2, step);
293 rangeGridSquare(step / 2, 0, step);
294 rangeGridSquare(0, step / 2, step);
300 private void rangeGridDiagonal(final int offsetX, final int offsetY,
302 for (int y = offsetY; y < height; y = y + step)
303 for (int x = offsetX; x < width; x = x + step) {
305 final int index = (y * width) + x;
306 final int halfStep = step / 2;
308 context.initialize(image, yChannel.map, uChannel.map,
311 context.measureNeighborEncode(x - halfStep, y - halfStep);
312 context.measureNeighborEncode(x + halfStep, y - halfStep);
313 context.measureNeighborEncode(x - halfStep, y + halfStep);
314 context.measureNeighborEncode(x + halfStep, y + halfStep);
316 yChannel.rangeMap[index] = (byte) context.getYRange(index);
317 uChannel.rangeMap[index] = (byte) context.getURange(index);
318 vChannel.rangeMap[index] = (byte) context.getVRange(index);
322 private void rangeGridSquare(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;
328 final int halfStep = step / 2;
330 context.initialize(image, yChannel.map, uChannel.map,
333 context.measureNeighborEncode(x - halfStep, y);
334 context.measureNeighborEncode(x + halfStep, y);
335 context.measureNeighborEncode(x, y - halfStep);
336 context.measureNeighborEncode(x, y + halfStep);
338 yChannel.rangeMap[index] = (byte) context.getYRange(index);
339 uChannel.rangeMap[index] = (byte) context.getURange(index);
340 vChannel.rangeMap[index] = (byte) context.getVRange(index);
344 private void rangeRoundGrid(final int step) {
346 rangeRoundGridDiagonal(step / 2, step / 2, step);
347 rangeRoundGridSquare(step / 2, 0, step);
348 rangeRoundGridSquare(0, step / 2, step);
351 rangeRoundGrid(step * 2);
354 private void rangeRoundGridDiagonal(final int offsetX, final int offsetY,
356 for (int y = offsetY; y < height; y = y + step)
357 for (int x = offsetX; x < width; x = x + step) {
359 final int index = (y * width) + x;
361 final int yRange = byteToInt(yChannel.rangeMap[index]);
362 final int uRange = byteToInt(uChannel.rangeMap[index]);
363 final int vRange = byteToInt(vChannel.rangeMap[index]);
365 final int halfStep = step / 2;
367 final int parentIndex = ((y - halfStep) * width)
370 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
372 if (parentYRange < yRange) {
373 parentYRange = yRange;
374 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
377 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
379 if (parentURange < uRange) {
380 parentURange = uRange;
381 uChannel.rangeMap[parentIndex] = (byte) parentURange;
384 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
386 if (parentVRange < vRange) {
387 parentVRange = vRange;
388 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
393 private void rangeRoundGridSquare(final int offsetX, final int offsetY,
395 for (int y = offsetY; y < height; y = y + step)
396 for (int x = offsetX; x < width; x = x + step) {
398 final int index = (y * width) + x;
400 final int yRange = byteToInt(yChannel.rangeMap[index]);
401 final int uRange = byteToInt(uChannel.rangeMap[index]);
402 final int vRange = byteToInt(vChannel.rangeMap[index]);
404 final int halfStep = step / 2;
408 parentIndex = (y * width) + (x - halfStep);
410 parentIndex = ((y - halfStep) * width) + x;
412 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
414 if (parentYRange < yRange) {
415 parentYRange = yRange;
416 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
419 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
421 if (parentURange < uRange) {
422 parentURange = uRange;
423 uChannel.rangeMap[parentIndex] = (byte) parentURange;
426 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
428 if (parentVRange < vRange) {
429 parentVRange = vRange;
430 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
436 private void saveGrid(final int step) throws IOException {
438 saveGridDiagonal(step / 2, step / 2, step);
439 saveGridSquare(step / 2, 0, step);
440 saveGridSquare(0, step / 2, step);
446 private void saveGridDiagonal(final int offsetX, final int offsetY,
447 final int step) throws IOException {
448 for (int y = offsetY; y < height; y = y + step)
449 for (int x = offsetX; x < width; x = x + step) {
451 final int halfStep = step / 2;
453 context2.initialize(image, yChannel.decodedMap,
454 uChannel.decodedMap, vChannel.decodedMap);
455 context2.measureNeighborEncode(x - halfStep, y - halfStep);
456 context2.measureNeighborEncode(x + halfStep, y - halfStep);
457 context2.measureNeighborEncode(x - halfStep, y + halfStep);
458 context2.measureNeighborEncode(x + halfStep, y + halfStep);
460 savePixel(step, offsetX, offsetY, x, y,
461 context2.colorStats.getAverageY(),
462 context2.colorStats.getAverageU(),
463 context2.colorStats.getAverageV());
468 private void saveGridSquare(final int offsetX, final int offsetY,
469 final int step) throws IOException {
470 for (int y = offsetY; y < height; y = y + step)
471 for (int x = offsetX; x < width; x = x + step) {
473 final int halfStep = step / 2;
475 context2.initialize(image, yChannel.decodedMap,
476 uChannel.decodedMap, vChannel.decodedMap);
477 context2.measureNeighborEncode(x - halfStep, y);
478 context2.measureNeighborEncode(x + halfStep, y);
479 context2.measureNeighborEncode(x, y - halfStep);
480 context2.measureNeighborEncode(x, y + halfStep);
482 savePixel(step, offsetX, offsetY, x, y,
483 context2.colorStats.getAverageY(),
484 context2.colorStats.getAverageU(),
485 context2.colorStats.getAverageV());
490 private void savePixel(final int step, final int offsetX, final int offsetY,
491 final int x, final int y, final int averageDecodedY,
492 final int averageDecodedU, final int averageDecodedV)
495 final int index = (y * width) + x;
497 final int py = byteToInt(yChannel.map[index]);
498 final int pu = byteToInt(uChannel.map[index]);
499 final int pv = byteToInt(vChannel.map[index]);
501 final int yRange = byteToInt(yChannel.rangeMap[index]);
502 final int uRange = byteToInt(uChannel.rangeMap[index]);
503 final int vRange = byteToInt(vChannel.rangeMap[index]);
505 final int halfStep = step / 2;
511 parentIndex = ((y - halfStep) * width) + (x - halfStep);
514 parentIndex = (y * width) + (x - halfStep);
517 parentIndex = ((y - halfStep) * width) + x;
519 encodeChannel(approximator.yTable, yChannel, averageDecodedY, index,
520 py, yRange, parentIndex);
522 encodeChannel(approximator.uTable, uChannel, averageDecodedU, index,
523 pu, uRange, parentIndex);
525 encodeChannel(approximator.vTable, vChannel, averageDecodedV, index,
526 pv, vRange, parentIndex);