2 * Imagesqueeze - Image codec. Copyright ©2012-2019, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public License
6 * or later as published by the Free Software Foundation.
9 package eu.svjatoslav.imagesqueeze.codec;
12 * Compressed image pixels encoder.
15 import eu.svjatoslav.commons.data.BitOutputStream;
17 import java.awt.image.DataBufferByte;
18 import java.awt.image.WritableRaster;
19 import java.io.IOException;
23 private final Image image;
24 private final Approximator approximator;
25 // ColorStats colorStats = new ColorStats();
26 private final OperatingContext context = new OperatingContext();
27 private final OperatingContext context2 = new OperatingContext();
33 private Channel yChannel;
34 private Channel uChannel;
35 private Channel vChannel;
36 private BitOutputStream bitOutputStream;
38 public ImageEncoder(final Image image) {
39 approximator = new Approximator();
41 // bitOutputStream = outputStream;
47 public static int byteToInt(final byte input) {
50 result = result + 256;
54 public static int decodeValueFromGivenBits(final int encodedBits,
55 final int range, final int bitCount) {
56 final int negativeBit = encodedBits & 1;
58 final int remainingBitCount = bitCount - 1;
60 if (remainingBitCount == 0) {
61 // no more bits remaining to encode actual value
69 // still one or more bits left, encode value as precisely as
72 final int encodedValue = (encodedBits >>> 1) + 1;
74 final int realvalueForThisBitcount = 1 << remainingBitCount;
76 // int valueMultiplier = range / realvalueForThisBitcount;
77 int decodedValue = (range * encodedValue)
78 / realvalueForThisBitcount;
80 if (decodedValue > range)
91 private static int encodeValueIntoGivenBits(int value, final int range,
101 final int remainingBitCount = bitCount - 1;
103 if (remainingBitCount == 0)
106 // still one or more bits left, encode value as precisely as
112 final int realvalueForThisBitcount = 1 << remainingBitCount;
113 // int valueMultiplier = range / realvalueForThisBitcount;
114 int encodedValue = (value * realvalueForThisBitcount) / range;
116 if (encodedValue >= realvalueForThisBitcount)
117 encodedValue = realvalueForThisBitcount - 1;
119 encodedValue = (encodedValue << 1) + negativeBit;
125 public static void storeIntegerCompressed8(
126 final BitOutputStream outputStream, final int data)
130 outputStream.storeBits(0, 1);
131 outputStream.storeBits(data, 8);
133 outputStream.storeBits(1, 1);
134 outputStream.storeBits(data, 32);
138 public void encode(final BitOutputStream bitOutputStream)
140 this.bitOutputStream = bitOutputStream;
142 approximator.initialize();
144 approximator.save(bitOutputStream);
146 width = image.metaData.width;
147 height = image.metaData.height;
149 final WritableRaster raster = image.bufferedImage.getRaster();
150 final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
151 final byte[] pixels = dbi.getData();
153 if (yChannel == null)
154 yChannel = new Channel(width, height);
158 if (uChannel == null)
159 uChannel = new Channel(width, height);
163 if (vChannel == null)
164 vChannel = new Channel(width, height);
168 // create YUV map out of RGB raster data
169 final Color color = new Color();
171 for (int y = 0; y < height; y++)
172 for (int x = 0; x < width; x++) {
174 final int index = (y * width) + x;
175 final int colorBufferIndex = index * 3;
177 int blue = pixels[colorBufferIndex];
181 int green = pixels[colorBufferIndex + 1];
185 int red = pixels[colorBufferIndex + 2];
195 yChannel.map[index] = (byte) color.y;
196 uChannel.map[index] = (byte) color.u;
197 vChannel.map[index] = (byte) color.v;
200 yChannel.decodedMap[0] = yChannel.map[0];
201 uChannel.decodedMap[0] = uChannel.map[0];
202 vChannel.decodedMap[0] = vChannel.map[0];
204 bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8);
205 bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8);
206 bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8);
208 // detect initial step
209 int largestDimension;
212 largestDimension = width;
214 largestDimension = height;
216 while (initialStep < largestDimension)
217 initialStep = initialStep * 2;
219 rangeGrid(initialStep);
221 saveGrid(initialStep);
224 private void encodeChannel(final Table table, final Channel channel,
225 final int averageDecodedValue, final int index, final int value,
226 final int range, final int parentIndex) throws IOException {
228 final byte[] decodedRangeMap = channel.decodedRangeMap;
229 final byte[] decodedMap = channel.decodedMap;
231 final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]);
233 final int inheritedBitCount = table
234 .proposeBitcountForRange(inheritedRange);
236 if (inheritedBitCount > 0) {
238 computedRange = table.proposeRangeForRange(range, inheritedRange);
239 decodedRangeMap[index] = (byte) computedRange;
242 if (computedRange != inheritedRange)
243 // brightness range shrinked
244 bitOutputStream.storeBits(1, 1);
246 // brightness range stayed the same
247 bitOutputStream.storeBits(0, 1);
249 // encode brightness into available amount of bits
250 final int computedBitCount = table
251 .proposeBitcountForRange(computedRange);
253 if (computedBitCount > 0) {
255 final int differenceToEncode = -(value - averageDecodedValue);
256 final int bitEncodedDifference = encodeValueIntoGivenBits(
257 differenceToEncode, computedRange, computedBitCount);
259 channel.bitCount = channel.bitCount + computedBitCount;
260 bitOutputStream.storeBits(bitEncodedDifference,
263 final int decodedDifference = decodeValueFromGivenBits(
264 bitEncodedDifference, computedRange, computedBitCount);
265 int decodedValue = averageDecodedValue - decodedDifference;
266 if (decodedValue > 255)
268 if (decodedValue < 0)
271 decodedMap[index] = (byte) decodedValue;
273 decodedMap[index] = (byte) averageDecodedValue;
276 decodedRangeMap[index] = (byte) inheritedRange;
277 decodedMap[index] = (byte) averageDecodedValue;
281 public void printStatistics() {
282 System.out.println("Y channel:");
283 yChannel.printStatistics();
285 System.out.println("U channel:");
286 uChannel.printStatistics();
288 System.out.println("V channel:");
289 vChannel.printStatistics();
292 private void rangeGrid(final int step) {
294 // gridSquare(step / 2, step / 2, step, pixels);
296 rangeGridDiagonal(step / 2, step / 2, step);
297 rangeGridSquare(step / 2, 0, step);
298 rangeGridSquare(0, step / 2, step);
304 private void rangeGridDiagonal(final int offsetX, final int offsetY,
306 for (int y = offsetY; y < height; y = y + step)
307 for (int x = offsetX; x < width; x = x + step) {
309 final int index = (y * width) + x;
310 final int halfStep = step / 2;
312 context.initialize(image, yChannel.map, uChannel.map,
315 context.measureNeighborEncode(x - halfStep, y - halfStep);
316 context.measureNeighborEncode(x + halfStep, y - halfStep);
317 context.measureNeighborEncode(x - halfStep, y + halfStep);
318 context.measureNeighborEncode(x + halfStep, y + halfStep);
320 yChannel.rangeMap[index] = (byte) context.getYRange(index);
321 uChannel.rangeMap[index] = (byte) context.getURange(index);
322 vChannel.rangeMap[index] = (byte) context.getVRange(index);
326 private void rangeGridSquare(final int offsetX, final int offsetY,
328 for (int y = offsetY; y < height; y = y + step)
329 for (int x = offsetX; x < width; x = x + step) {
331 final int index = (y * width) + x;
332 final int halfStep = step / 2;
334 context.initialize(image, yChannel.map, uChannel.map,
337 context.measureNeighborEncode(x - halfStep, y);
338 context.measureNeighborEncode(x + halfStep, y);
339 context.measureNeighborEncode(x, y - halfStep);
340 context.measureNeighborEncode(x, y + halfStep);
342 yChannel.rangeMap[index] = (byte) context.getYRange(index);
343 uChannel.rangeMap[index] = (byte) context.getURange(index);
344 vChannel.rangeMap[index] = (byte) context.getVRange(index);
348 private void rangeRoundGrid(final int step) {
350 rangeRoundGridDiagonal(step / 2, step / 2, step);
351 rangeRoundGridSquare(step / 2, 0, step);
352 rangeRoundGridSquare(0, step / 2, step);
355 rangeRoundGrid(step * 2);
358 private void rangeRoundGridDiagonal(final int offsetX, final int offsetY,
360 for (int y = offsetY; y < height; y = y + step)
361 for (int x = offsetX; x < width; x = x + step) {
363 final int index = (y * width) + x;
365 final int yRange = byteToInt(yChannel.rangeMap[index]);
366 final int uRange = byteToInt(uChannel.rangeMap[index]);
367 final int vRange = byteToInt(vChannel.rangeMap[index]);
369 final int halfStep = step / 2;
371 final int parentIndex = ((y - halfStep) * width)
374 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
376 if (parentYRange < yRange) {
377 parentYRange = yRange;
378 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
381 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
383 if (parentURange < uRange) {
384 parentURange = uRange;
385 uChannel.rangeMap[parentIndex] = (byte) parentURange;
388 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
390 if (parentVRange < vRange) {
391 parentVRange = vRange;
392 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
397 private void rangeRoundGridSquare(final int offsetX, final int offsetY,
399 for (int y = offsetY; y < height; y = y + step)
400 for (int x = offsetX; x < width; x = x + step) {
402 final int index = (y * width) + x;
404 final int yRange = byteToInt(yChannel.rangeMap[index]);
405 final int uRange = byteToInt(uChannel.rangeMap[index]);
406 final int vRange = byteToInt(vChannel.rangeMap[index]);
408 final int halfStep = step / 2;
412 parentIndex = (y * width) + (x - halfStep);
414 parentIndex = ((y - halfStep) * width) + x;
416 int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]);
418 if (parentYRange < yRange) {
419 parentYRange = yRange;
420 yChannel.rangeMap[parentIndex] = (byte) parentYRange;
423 int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
425 if (parentURange < uRange) {
426 parentURange = uRange;
427 uChannel.rangeMap[parentIndex] = (byte) parentURange;
430 int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
432 if (parentVRange < vRange) {
433 parentVRange = vRange;
434 vChannel.rangeMap[parentIndex] = (byte) parentVRange;
440 private void saveGrid(final int step) throws IOException {
442 saveGridDiagonal(step / 2, step / 2, step);
443 saveGridSquare(step / 2, 0, step);
444 saveGridSquare(0, step / 2, step);
450 private void saveGridDiagonal(final int offsetX, final int offsetY,
451 final int step) throws IOException {
452 for (int y = offsetY; y < height; y = y + step)
453 for (int x = offsetX; x < width; x = x + step) {
455 final int halfStep = step / 2;
457 context2.initialize(image, yChannel.decodedMap,
458 uChannel.decodedMap, vChannel.decodedMap);
459 context2.measureNeighborEncode(x - halfStep, y - halfStep);
460 context2.measureNeighborEncode(x + halfStep, y - halfStep);
461 context2.measureNeighborEncode(x - halfStep, y + halfStep);
462 context2.measureNeighborEncode(x + halfStep, y + halfStep);
464 savePixel(step, offsetX, offsetY, x, y,
465 context2.colorStats.getAverageY(),
466 context2.colorStats.getAverageU(),
467 context2.colorStats.getAverageV());
472 private void saveGridSquare(final int offsetX, final int offsetY,
473 final int step) throws IOException {
474 for (int y = offsetY; y < height; y = y + step)
475 for (int x = offsetX; x < width; x = x + step) {
477 final int halfStep = step / 2;
479 context2.initialize(image, yChannel.decodedMap,
480 uChannel.decodedMap, vChannel.decodedMap);
481 context2.measureNeighborEncode(x - halfStep, y);
482 context2.measureNeighborEncode(x + halfStep, y);
483 context2.measureNeighborEncode(x, y - halfStep);
484 context2.measureNeighborEncode(x, y + halfStep);
486 savePixel(step, offsetX, offsetY, x, y,
487 context2.colorStats.getAverageY(),
488 context2.colorStats.getAverageU(),
489 context2.colorStats.getAverageV());
494 private void savePixel(final int step, final int offsetX, final int offsetY,
495 final int x, final int y, final int averageDecodedY,
496 final int averageDecodedU, final int averageDecodedV)
499 final int index = (y * width) + x;
501 final int py = byteToInt(yChannel.map[index]);
502 final int pu = byteToInt(uChannel.map[index]);
503 final int pv = byteToInt(vChannel.map[index]);
505 final int yRange = byteToInt(yChannel.rangeMap[index]);
506 final int uRange = byteToInt(uChannel.rangeMap[index]);
507 final int vRange = byteToInt(vChannel.rangeMap[index]);
509 final int halfStep = step / 2;
515 parentIndex = ((y - halfStep) * width) + (x - halfStep);
518 parentIndex = (y * width) + (x - halfStep);
521 parentIndex = ((y - halfStep) * width) + x;
523 encodeChannel(approximator.yTable, yChannel, averageDecodedY, index,
524 py, yRange, parentIndex);
526 encodeChannel(approximator.uTable, uChannel, averageDecodedU, index,
527 pu, uRange, parentIndex);
529 encodeChannel(approximator.vTable, vChannel, averageDecodedV, index,
530 pv, vRange, parentIndex);