/*
* Copyright (C) 2014 James Lawrence.
*
* This file is part of LibLab.
*
* LibLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sqrt.liblab.codec;
import com.sqrt.liblab.entry.graphics.GrimBitmap;
import com.sqrt.liblab.io.DataSource;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.nio.BufferOverflowException;
public class BitmapCodec extends EntryCodec<GrimBitmap> {
public GrimBitmap _read(DataSource source) throws IOException {
int tag = source.getInt();
switch (tag) {
case ('B' << 24) | ('M' << 16) | (' ' << 8) | ' ':
return readBm(source);
default:
throw new IOException("Unknown bitmap format");
}
}
private GrimBitmap readBm(DataSource source) throws IOException {
int tag = source.getInt();
if (tag != ('F' << 24))
throw new IllegalArgumentException("Unknown BM tag");
GrimBitmap result = new GrimBitmap(source.container, source.getName());
int codec = source.getIntLE();
int paletteIncluded = source.getIntLE();
int numImages = source.getIntLE();
int x = source.getIntLE();
int y = source.getIntLE();
int transparentColor = source.getIntLE();
int format = source.getIntLE();
int bpp = source.getIntLE();
int redBits = source.getIntLE();
int greenBits = source.getIntLE();
int blueBits = source.getIntLE();
int redShift = source.getIntLE();
int greenShift = source.getIntLE();
int blueShift = source.getIntLE();
source.position(128);
int width = source.getIntLE();
int height = source.getIntLE();
source.position(128);
byte[][] data = new byte[numImages][];
int expectedDataLength = width * height * (bpp / 8);
for (int i = 0; i < numImages; i++) {
source.skip(8);
data[i] = new byte[expectedDataLength];
if (codec == 0)
source.get(data[i]);
else if (codec == 3) {
int compressedLen = source.getIntLE();
byte[] compressed = new byte[compressedLen];
source.get(compressed);
data[i] = decompress_codec3(compressed, expectedDataLength);
} else throw new UnsupportedOperationException("Invalid image codec: " + codec);
if (data[i].length != expectedDataLength)
throw new UnsupportedOperationException("Invalid data length, got: " + data[i].length + ", expected " + (width * height * (bpp / 8)) + " with " + bpp + "bpp");
if (format == 1) {
for (int j = 0; j < expectedDataLength; j += 2) {
byte t = data[i][j];
data[i][j] = data[i][j + 1];
data[i][j + 1] = t;
}
}
short[] rgb565 = new short[width * height];
for (int k = 0; k < expectedDataLength; k += 2)
rgb565[k / 2] = (short) (((data[i][k] & 0xff) << 8) | (data[i][k + 1] & 0xff));
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_565_RGB);
WritableRaster raster = bi.getRaster();
raster.setDataElements(0, 0, width, height, rgb565);
result.images.add(bi);
}
return result;
}
public DataSource write(GrimBitmap source) throws IOException {
throw new UnsupportedOperationException(); // Todo
}
public String[] getFileExtensions() {
return new String[]{"bm", "zbm"};
}
public Class<GrimBitmap> getEntryClass() {
return GrimBitmap.class;
}
private static byte[] decompress_codec3(byte[] compressed, int max) throws EOFException {
AccessibleByteArrayOutputStream result = new AccessibleByteArrayOutputStream();
DecompressorStream str = new DecompressorStream(compressed);
while (true) {
int copyOne = str.readBit();
if (copyOne == 1) { // Copy a single byte...
result.write(str.read());
if (result.size() > max)
throw new BufferOverflowException();
} else { // Copy multiple bytes...
int bit = str.readBit();
int copy_len, copy_offset;
if (bit == 0) { // Copy multiple bytes...
copy_len = 2 * str.readBit() + str.readBit() + 3;
copy_offset = str.read() - 256;
} else {
int a = str.read();
int b = str.read();
copy_offset = (a | ((b & 0xf0) << 4)) - 4096;
copy_len = (b & 0xf) + 3;
if (copy_len == 3) {
copy_len = str.read() + 1;
if (copy_len == 1)
break; // the end!
}
}
// Do the copy...
for (int i = 0; i < copy_len; i++)
result.write(result.get(result.size() + copy_offset));
}
if (result.size() > max)
throw new BufferOverflowException();
}
return result.toByteArray();
}
}
class DecompressorStream extends ByteArrayInputStream {
private int bitstr_len, bitstr_value;
public DecompressorStream(byte[] buf) throws EOFException {
super(buf);
nextBitString();
}
private void nextBitString() throws EOFException {
int c1 = read(), c2 = read();
if ((c1 | c2) < 0)
throw new EOFException();
bitstr_value = ((c2 << 8) | c1);
bitstr_len = 16;
}
public int readBit() throws EOFException {
int res = bitstr_value & 1;
bitstr_len--;
bitstr_value >>>= 1;
if (bitstr_len == 0)
nextBitString();
return res;
}
}
class AccessibleByteArrayOutputStream extends ByteArrayOutputStream {
public byte get(int index) {
return buf[index];
}
}