/*******************************************************************************
* Copyright (c) 2015
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.graphics.reader;
import java.io.IOException;
import jsettlers.graphics.image.Image;
import jsettlers.graphics.reader.bytereader.ByteReader;
import jsettlers.graphics.reader.translator.DatBitmapTranslator;
import jsettlers.graphics.reader.translator.HeaderType;
/**
* This class is capable of reading an image from the given stram.
*
* @param <T>
* The image type.
* @author michael
*/
public final class DatBitmapReader<T extends Image> {
// private final short[] data;
/**
* Creates a new reader that starts to read fom the given bytereader at its current position and uses the translator to convert the image.
*
* @param translator
* The translator that translates the image data.
* @param reader
* The reader to read from.
* @throws IOException
* If an io error occurred.
*/
// private DatBitmapReader(DatBitmapTranslator<T> translator, ByteReader
// reader)
// throws IOException {
// this.data = uncompressImage(reader, translator);
// }
/**
* Assumes that there is an iamge starting at the beginning of reader, and reads its contents and creates the image from the stream of compressed
* data.
* <p>
* The data format is as follows:
* <p>
* The first bytes are 0x0c, 0x0, 0x0, 0x0
* <p>
* The next short (little endian) is the width of the image. <br>
* The next short the height. <br>
* The next (signed) short the x offset. <br>
* The next (signed) short the y offset. <br>
* <p>
* If the alignment of the next short is uneven, then a 0-byte for padding is inserted.
* <p>
* Then the image data starts with the first meta short. <br>
* A meta short: <br>
* The first bit states that after drawing the line that follows this short, a linebreak should be inserted. <br>
* The next 7 bit state the number of pixels that should be skipped before drawing the line. <br>
* The next 8 bit state the line length (l)
* <p>
* Then a sequence of l short follows, each of them in a 5-5-5-color format.
* <p>
* Then a new meta short comes, until the end of the image is reached (a linebreak so that we get out of the image space)
* <p>
* This method initializes data, width, height and offset.
*
* @param reader
* @param translator
* @param
* @return The short array given, or null if the short array was not big enough.
* @throws IOException
*/
public static <T extends Image> void uncompressImage(ByteReader reader,
DatBitmapTranslator<T> translator, ImageMetadata metadata,
ImageArrayProvider array) throws IOException {
long currentPos = reader.getReadBytes();
HeaderType headerType = translator.getHeaderType();
if (headerType == HeaderType.DISPLACED) {
reader.assumeToRead(new byte[] {
0x0c, 0, 0, 0
});
}
metadata.width = reader.read16();
metadata.height = reader.read16();
if (headerType == HeaderType.DISPLACED) {
metadata.offsetX = reader.read16signed();
metadata.offsetY = reader.read16signed();
} else if (headerType == HeaderType.GUI) {
// mysterious bytes
reader.read16();
reader.read16();
} else {
// mysterious bytes?
reader.read16();
}
if (reader.getReadBytes() % 2 == 1) {
// uneven position => padding.
reader.read8();
}
array.startImage(metadata.width, metadata.height);
try {
readCompressedData(reader, translator, metadata.width,
metadata.height, array);
} catch (Throwable e) {
System.err.println("Error while loading image starting at "
+ currentPos
+ ". There is an error/overflow somewhere around "
+ reader.getReadBytes()
+ ". Error was: "
+ e.getMessage());
throw new IOException("Error uncompressing image", e);
}
}
/**
* Reads the compressed data.
*
* @param reader
* @param translator
* @return
* @throws IOException
*/
private static <T extends Image> void readCompressedData(
ByteReader reader, DatBitmapTranslator<T> translator, int width,
int lines, ImageArrayProvider array) throws IOException {
short transparent = translator.getTransparentColor();
// TODO: buffer the buffer but be thread safe!
short[] lineBuffer = new short[width];
for (int i = 0; i < lines; i++) {
boolean newLine = false;
int x = 0;
while (!newLine) {
int currentMeta = reader.read16();
int sequenceLength = currentMeta & 0xff;
int skip = (currentMeta & 0x7f00) >> 8;
newLine = (currentMeta & 0x8000) != 0;
int skipend = x + skip;
while (x < skipend) {
lineBuffer[x] = transparent;
x++;
}
int readPartEnd = x + sequenceLength;
while (x < readPartEnd) {
lineBuffer[x] = translator.readUntransparentColor(reader);
x++;
}
}
array.writeLine(lineBuffer, x);
}
}
// private void fillRestOfLine(DatBitmapTranslator<T> translator,
// short[] pixels, int x, int y) {
// int currentx = x;
// while (currentx < this.width) {
// pixels[y * this.width + currentx] =
// translator.getTransparentColor();
// currentx++;
// }
// }
//
// private int readPixels(ByteReader reader,
// DatBitmapTranslator<T> translator, short[] pixels, int x, int y,
// int sequenceLength) throws IOException {
// for (int i = 0; i < sequenceLength; i++) {
// int currentx = i + x;
// if (currentx >= this.width) {
// throw new IllegalArgumentException("The image line " + y
// + " exceeded width!");
// }
// pixels[y * this.width + currentx] =
// translator.readUntransparentColor(reader);
// }
// return x;
// }
// private int skipGivenBytes(DatBitmapTranslator<T> translator, int x, int
// skip) {
// for (int i = 0; i < skip; i++) {
// int currentx = i + x;
// // if (currentx >= this.width) {
// // throw new IllegalArgumentException(
// // "Skipped out of image at line " + y + "!");
// // }
// lineBuffer[currentx] =
// translator.getTransparentColor();
// }
// return x;
// }
// @Override
// public ShortBuffer getData() {
// return ShortBuffer.wrap(this.data);
// }
//
// @Override
// public int getWidth() {
// return this.width;
// }
//
// @Override
// public int getHeight() {
// return this.height;
// }
//
// @Override
// public int getOffsetX() {
// return this.offsetX;
// }
//
// @Override
// public int getOffsetY() {
// return this.offsetY;
// }
/**
* Gets an image form the reader.
*
* @param <T>
* The image type.
* @param translator
* A translator for the given type.
* @param reader
* The reader to read from.
* @return The read image.
* @throws IOException
* If an read error occurred.
*/
public static <T extends Image> T getImage(
DatBitmapTranslator<T> translator, ByteReader reader)
throws IOException {
ImageMetadata metadata = new ImageMetadata();
ShortArrayWriter array = new ShortArrayWriter();
uncompressImage(reader, translator, metadata, array);
return translator.createImage(metadata, array.getArray());
}
}