// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.resource.graphics;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.util.io.StreamUtils;
public class MosV2Decoder extends MosDecoder
{
private static final int HeaderSize = 16; // size of the MOS header
private static final int BlockSize = 28; // size of a single data block
private ByteBuffer mosBuffer;
private int width, height, blockCount, ofsData;
public MosV2Decoder(ResourceEntry mosEntry)
{
super(mosEntry);
init();
}
/**
* Forces the internal cache to be filled with all PVRZ resources required by this MOS resource.
* This will ensure that tiles are decoded at constant speed.
*/
public void preloadPvrz()
{
for (int i = 0; i < getBlockCount(); i++) {
int ofs = getBlockOffset(i);
if (ofs > 0) {
int page = mosBuffer.getInt(ofs);
if (page >= 0) {
getPVR(page);
}
}
}
}
@Override
public void close()
{
PvrDecoder.flushCache();
mosBuffer = null;
width = height = blockCount = 0;
ofsData = 0;
}
@Override
public void reload()
{
init();
}
@Override
public ByteBuffer getResourceBuffer()
{
return mosBuffer;
}
@Override
public int getWidth()
{
return width;
}
@Override
public int getHeight()
{
return height;
}
@Override
public Image getImage()
{
if (isInitialized()) {
BufferedImage image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
if (getImage(image)) {
return image;
} else {
image = null;
}
}
return null;
}
@Override
public boolean getImage(Image canvas)
{
if (isInitialized() && canvas != null) {
boolean bRet = false;
for (int i = 0; i < getBlockCount(); i++) {
int ofs = getBlockOffset(i);
if (ofs > 0) {
int dx = mosBuffer.getInt(ofs + 0x14);
int dy = mosBuffer.getInt(ofs + 0x18);
bRet |= renderBlock(i, canvas, dx, dy);
}
}
return bRet;
}
return false;
}
@Override
public int[] getImageData()
{
if (isInitialized()) {
int[] buffer = new int[getWidth()*getHeight()];
if (getImageData(buffer)) {
return buffer;
} else {
buffer = null;
}
}
return null;
}
@Override
public boolean getImageData(int[] buffer)
{
if (isInitialized() && buffer != null) {
boolean bRet = false;
for (int i = 0; i < getBlockCount(); i++) {
int ofs = getBlockOffset(i);
if (ofs > 0) {
int dx = mosBuffer.getInt(ofs + 0x14);
int dy = mosBuffer.getInt(ofs + 0x18);
bRet |= renderBlock(i, buffer, getWidth(), getHeight(), dx, dy);
}
}
return bRet;
}
return false;
}
@Override
public int getBlockCount()
{
return blockCount;
}
@Override
public int getBlockWidth(int blockIdx)
{
int ofs = getBlockOffset(blockIdx);
if (ofs > 0) {
return mosBuffer.getInt(ofs + 0x0c);
}
return 0;
}
@Override
public int getBlockHeight(int blockIdx)
{
int ofs = getBlockOffset(blockIdx);
if (ofs > 0) {
return mosBuffer.getInt(ofs + 0x10);
}
return 0;
}
@Override
public Image getBlock(int blockIdx)
{
if (isValidBlock(blockIdx)) {
Image image = ColorConvert.createCompatibleImage(getBlockWidth(blockIdx), getBlockHeight(blockIdx), true);
if (renderBlock(blockIdx, image, 0, 0)) {
return image;
} else {
image = null;
}
}
return null;
}
@Override
public boolean getBlock(int blockIdx, Image canvas)
{
if (isValidBlock(blockIdx) && canvas != null) {
return renderBlock(blockIdx, canvas, 0, 0);
}
return false;
}
@Override
public int[] getBlockData(int blockIdx)
{
if (isValidBlock(blockIdx)) {
int w = getBlockWidth(blockIdx);
int h = getBlockHeight(blockIdx);
int[] buffer = new int[w*h];
if (renderBlock(blockIdx, buffer, w, h, 0, 0)) {
return buffer;
} else {
buffer = null;
}
}
return null;
}
@Override
public boolean getBlockData(int blockIdx, int[] buffer)
{
if (isValidBlock(blockIdx) && buffer != null) {
int w = getBlockWidth(blockIdx);
int h = getBlockHeight(blockIdx);
if (buffer.length >= w*h) {
return renderBlock(blockIdx, buffer, w, h, 0, 0);
}
}
return false;
}
private void init()
{
close();
if (getResourceEntry() != null) {
try {
mosBuffer = getResourceEntry().getResourceBuffer();
String signature = StreamUtils.readString(mosBuffer, 0x00, 4);
String version = StreamUtils.readString(mosBuffer, 0x04, 4);
if ("MOS ".equals(signature) && "V2 ".equals(version)) {
setType(Type.MOSV2);
} else {
throw new Exception("Invalid MOS type");
}
// evaluating header data
width = mosBuffer.getInt(0x08);
if (width <= 0) {
throw new Exception("Invalid MOS width: " + width);
}
height = mosBuffer.getInt(0x0c);
if (height <= 0) {
throw new Exception("Invalid MOS height: " + height);
}
blockCount = mosBuffer.getInt(0x10);
if (blockCount <= 0) {
throw new Exception("Invalid number of data blocks: " + blockCount);
}
ofsData = mosBuffer.getInt(0x14);
if (width < HeaderSize) {
throw new Exception("Invalid data offset: " + ofsData);
}
} catch (Exception e) {
e.printStackTrace();
close();
}
}
}
// Returns and caches the PVRZ resource of the specified page
private PvrDecoder getPVR(int page)
{
try {
String name = String.format("MOS%1$04d.PVRZ", page);
ResourceEntry entry = ResourceFactory.getResourceEntry(name);
if (entry != null) {
return PvrDecoder.loadPvr(entry);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// Returns if a valid MOS has been initialized
private boolean isInitialized()
{
return (mosBuffer != null && blockCount > 0 && width > 0 && height > 0);
}
// Returns whether the specified block index is valid
private boolean isValidBlock(int blockIdx)
{
return (blockIdx >= 0 && blockIdx < blockCount);
}
// Returns the start offset of the specified data block
private int getBlockOffset(int blockIdx)
{
if (blockIdx >= 0 && blockIdx < blockCount) {
return ofsData + blockIdx*BlockSize;
}
return -1;
}
// Renders the specified block onto the canvas at position (left, top)
private boolean renderBlock(int blockIdx, Image canvas, int left, int top)
{
int ofsBlock = getBlockOffset(blockIdx);
if (ofsBlock > 0 && canvas != null && left >= 0 && top >= 0) {
int page = mosBuffer.getInt(ofsBlock);
int srcX = mosBuffer.getInt(ofsBlock + 0x04);
int srcY = mosBuffer.getInt(ofsBlock + 0x08);
int blockWidth = mosBuffer.getInt(ofsBlock + 0x0c);
int blockHeight = mosBuffer.getInt(ofsBlock + 0x10);
PvrDecoder decoder = getPVR(page);
if (decoder != null) {
try {
int w = (left + blockWidth < canvas.getWidth(null)) ? canvas.getWidth(null) - left : blockWidth;
int h = (top + blockHeight < canvas.getHeight(null)) ? canvas.getHeight(null) - top : blockHeight;
if (w > 0 && h > 0) {
BufferedImage imgBlock = decoder.decode(srcX, srcY, blockWidth, blockHeight);
Graphics2D g = (Graphics2D)canvas.getGraphics();
try {
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
g.drawImage(imgBlock, left, top, left + w, top + h, 0, 0, w, h, null);
} finally {
g.dispose();
g = null;
}
imgBlock = null;
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
}
}
return false;
}
// Writes the specified block into the buffer of specified dimensions at position (left, top)
private boolean renderBlock(int blockIdx, int[] buffer, int width, int height, int left, int top)
{
int ofsBlock = getBlockOffset(blockIdx);
if (ofsBlock > 0 && buffer != null && width > 0 && height > 0 && left >= 0 && top >= 0) {
int page = mosBuffer.getInt(ofsBlock);
int srcX = mosBuffer.getInt(ofsBlock + 0x04);
int srcY = mosBuffer.getInt(ofsBlock + 0x08);
int blockWidth = mosBuffer.getInt(ofsBlock + 0x0c);
int blockHeight = mosBuffer.getInt(ofsBlock + 0x10);
PvrDecoder decoder = getPVR(page);
if (decoder != null) {
try {
int w = (left + blockWidth < width) ? width - left : blockWidth;
int h = (top + blockHeight < height) ? height - top : blockHeight;
if (w > 0 && h > 0) {
BufferedImage imgBlock = decoder.decode(srcX, srcY, blockWidth, blockHeight);
int[] srcData = ((DataBufferInt)imgBlock.getRaster().getDataBuffer()).getData();
int srcOfs = 0;
int dstOfs = top*width + left;
for (int y = 0; y < h; y++) {
System.arraycopy(srcData, srcOfs, buffer, dstOfs, w);
srcOfs += blockWidth;
dstOfs += width;
}
srcData = null;
imgBlock = null;
decoder = null;
return true;
}
} catch (Exception e) {
e.printStackTrace();
decoder = null;
}
}
}
return false;
}
}