/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.core.utils.images;
import java.awt.Color;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.MemoryImageSource;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;
/**
* Class holding an image which is compressed either as png or using LZ4. When using LZ4 currently only image type TYPE_INT_ARGB is supported. Image
* is returned uncompressed.
*
* @author Phil
*
*/
public class CompressedImage {
private final int width;
private final int height;
private final CompressedType type;
private final byte[] data;
private final int uncompressedLength;
/**
* Returns an estimate of the total amount of memory consumed by this class.
*
* @return
*/
public int getSizeBytes() {
return 8 + 4 + 4 + 4 + data.length + 4;
}
public enum CompressedType {
/**
* Gives quickest compression / decompression but larger compressed size than png.
*/
LZ4,
/**
* Slowest to compress / decompress but best compression
*/
PNG
}
public CompressedImage(BufferedImage img, CompressedType type) {
this.width = img.getWidth();
this.height = img.getHeight();
this.type = type;
switch (type) {
case PNG:
this.data = ImageUtils.toPNG(img);
this.uncompressedLength = -1;
break;
case LZ4:
byte[] uncompressed = getUncompressedBytes(img);
this.uncompressedLength = uncompressed.length;
this.data = LZ4Factory.unsafeInstance().fastCompressor().compress(uncompressed);
break;
default:
throw new IllegalArgumentException();
}
}
public Image get() {
switch (type) {
case PNG:
return ImageUtils.fromPNG(data);
case LZ4:
LZ4FastDecompressor lz4Decompressor = LZ4Factory.unsafeInstance().fastDecompressor();
byte[] uncompressed = new byte[uncompressedLength];
lz4Decompressor.decompress(data, uncompressed);
return uncompressImage(width, height, uncompressed);
// byte[]decompressed = lz4Decompressor.d
// case LZ4:
// return LZ4Factory.unsafeInstance().fastCompressor().compress(getUncompressedBytes(img));
//
default:
throw new IllegalArgumentException();
}
}
/**
* Get compressed image as a buffered image. This can have more overhead than just getting as an Image.
*
* @return
*/
public BufferedImage getBufferedImage() {
return ImageUtils.toBufferedImage(get());
}
private static byte[] getUncompressedBytes(BufferedImage img) {
if (img.getType() != BufferedImage.TYPE_INT_ARGB) {
throw new RuntimeException("Image must be type TYPE_INT_ARGB to be compressed.");
}
DataBuffer uncastbuffer = img.getRaster().getDataBuffer();
byte[] uncompressed = null;
if (DataBufferInt.class.isInstance(uncastbuffer)) {
int[] ints = ((DataBufferInt) uncastbuffer).getData();
uncompressed = integersToBytes(ints);
} else {
uncompressed = ((DataBufferByte) uncastbuffer).getData();
}
return uncompressed;
}
private static byte[] integersToBytes(int[] values) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
for (int i = 0; i < values.length; ++i) {
try {
int v = values[i];
dos.writeInt(v);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return baos.toByteArray();
}
// private static String printUnsignedByte(byte b) {
// StringBuilder sb = new StringBuilder();
// while (b > 0) {
// sb.insert(0, b % 2 == 0 ? 0 : 1);
// b >>= 1;
// }
// for (int i = 8 - sb.length(); i > 0; i--) {
// sb.insert(0, 0);
// }
// return sb.toString();
// }
//
// private static String byteArrayToHex(byte[] a) {
// StringBuilder sb = new StringBuilder();
// for (byte b : a)
// sb.append(String.format("%02x", b & 0xff));
// return sb.toString();
// }
private static Image uncompressImage(final int width, final int height, byte[] bytes) {
// ColorModel cm = new ColorModel(32) {
//
// @Override
// public int getRed(int pixel) {
// return (pixel & 0x00FF0000) >>> 16;
// }
//
// @Override
// public int getGreen(int pixel) {
// return (pixel & 0x0000FF00) >>> 8;
// }
//
// @Override
// public int getBlue(int pixel) {
// return pixel & 0x000000FF;
// }
//
// @Override
// public int getAlpha(int pixel) {
// return (pixel & 0xFF000000) >>> 24;
// }
// };
int[] data = new int[bytes.length / 4];
for (int i = 0; i < data.length; i++) {
// for (int j = 0; j < 4; j++) {
// data[i] |= (bytes[i * 4 + j]) << ((3 - j) * 8);
// }
int j = i * 4;
data[i] = ((bytes[j++] & 0xFF) << 24) | ((bytes[j++] & 0xFF) << 16) | ((bytes[j++] & 0xFF) << 8) | ((bytes[j++] & 0xFF) << 0);
// int a = cm.getAlpha(data[i]);
// int r = cm.getRed(data[i]);
// int g = cm.getGreen(data[i]);
// int b = cm.getBlue(data[i]);
// System.out.print(" data=" + data[i] + " hex=" + Integer.toHexString(data[i]) + " ");
// System.out.print(" - a=" + a + " r=" + r + " g=" + g + " b=" + b);
// System.out.println();
}
MemoryImageSource mis = new MemoryImageSource(width, height, data, 0, width);
// MemoryImageSource mis2=new MemoryImageSource(width,height, ColorModel.getRGBdefault(),
// bytes,0, width*4);
return Toolkit.getDefaultToolkit().createImage(mis);
}
public static void main(String[] args) {
BufferedImage img = ImageUtils.createBlankImage(256, 256, BufferedImage.TYPE_INT_ARGB, Color.BLUE);
CompressedImage compressed = new CompressedImage(img, CompressedType.LZ4);
ImageUtils.showImage(compressed.get());
// byte[] test = new byte[]{20,40,60,80};
//
// int[] data = new int[test.length / 4];
// for (int i = 0; i < data.length; i++) {
// for (int j = 0; j < 4; j++) {
// byte original = test[i * 4 + j];
// int bitshift = (3 - j) * 8;
// int shifted = original << bitshift;
//
// data[i] |= shifted;
// int mask = 0xFF <<bitshift;
// int retrieved = data[i] & mask;
// retrieved = retrieved >> bitshift;
// System.out.println("original="+original
// + " bitshift=" + bitshift
// + " shifted="+shifted + " data=" + data[i]
// + " mask=" + Integer.toHexString(mask)
// + " retrieved=" + retrieved);
// }
// }
}
}