scummvm/saga/image.cpp

442 lines
9.4 KiB
C++
Raw Normal View History

/* ScummVM - Scumm Interpreter
* Copyright (C) 2004 The ScummVM project
*
* The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
*
* This program 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 2
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*
*/
2004-05-01 09:19:35 +00:00
// SAGA Image resource management routines
2004-08-02 16:20:35 +00:00
#include "saga/saga.h"
2004-08-02 16:20:35 +00:00
#include "saga/game_mod.h"
2004-08-02 16:20:35 +00:00
#include "saga/image.h"
namespace Saga {
2004-05-01 09:19:35 +00:00
static int granulate(int value, int granularity) {
2004-04-27 11:44:17 +00:00
int remainder;
if (value == 0)
return 0;
if (granularity == 0)
return 0;
remainder = value % granularity;
if (remainder == 0) {
return value;
} else {
return (granularity - remainder + value);
}
}
2004-07-31 13:49:26 +00:00
int SagaEngine::decodeBGImage(const byte *image_data, size_t image_size,
byte **output_buf, size_t *output_buf_len, int *w, int *h) {
IMAGE_HEADER hdr;
int modex_height;
const byte *RLE_data_ptr;
size_t RLE_data_len;
byte *decode_buf;
size_t decode_buf_len;
byte *out_buf;
size_t out_buf_len;
if (image_size <= SAGA_IMAGE_DATA_OFFSET) {
/* Image size is way too small */
return FAILURE;
}
MemoryReadStream readS(image_data, image_size);
hdr.width = readS.readUint16LE();
hdr.height = readS.readUint16LE();
// The next four bytes of the image header aren't used.
readS.readUint16LE();
readS.readUint16LE();
RLE_data_ptr = image_data + SAGA_IMAGE_DATA_OFFSET;
RLE_data_len = image_size - SAGA_IMAGE_DATA_OFFSET;
2004-04-27 11:44:17 +00:00
modex_height = granulate(hdr.height, 4);
decode_buf_len = hdr.width * modex_height;
decode_buf = (byte *)malloc(decode_buf_len);
out_buf_len = hdr.width * hdr.height;
out_buf = (byte *)malloc(out_buf_len);
if (decodeBGImageRLE(RLE_data_ptr,
RLE_data_len, decode_buf, decode_buf_len) != SUCCESS) {
free(decode_buf);
free(out_buf);
return FAILURE;
}
unbankBGImage(out_buf, decode_buf, hdr.width, hdr.height);
2004-05-01 09:19:35 +00:00
// For some reason bg images in IHNM are upside down
if (GAME_GetGameType() == GID_IHNM) {
flipImage(out_buf, hdr.width, hdr.height);
}
free(decode_buf);
*output_buf_len = out_buf_len;
*output_buf = out_buf;
*w = hdr.width;
*h = hdr.height;
return SUCCESS;
}
int SagaEngine::decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, byte *outbuf, size_t outbuf_len) {
const byte *inbuf_ptr;
byte *outbuf_ptr;
2004-05-01 15:08:20 +00:00
uint32 inbuf_remain;
const byte *inbuf_end;
byte *outbuf_end;
2004-05-01 15:08:20 +00:00
uint32 outbuf_remain;
byte mark_byte;
int test_byte;
2004-05-01 16:24:39 +00:00
uint32 runcount;
byte bitfield;
byte bitfield_byte1;
byte bitfield_byte2;
byte *backtrack_ptr;
int backtrack_amount;
uint16 c, b;
int decode_err = 0;
inbuf_ptr = inbuf;
inbuf_remain = inbuf_len;
outbuf_ptr = outbuf;
outbuf_remain = outbuf_len;
inbuf_end = (inbuf + inbuf_len) - 1;
outbuf_end = (outbuf + outbuf_len) - 1;
memset(outbuf, 0, outbuf_len);
while ((inbuf_remain > 1) && (outbuf_remain > 0) && !decode_err) {
if ((inbuf_ptr > inbuf_end) || (outbuf_ptr > outbuf_end)) {
return FAILURE;
}
mark_byte = *inbuf_ptr++;
inbuf_remain--;
2004-05-01 09:19:35 +00:00
test_byte = mark_byte & 0xC0; // Mask all but two high order bits
switch (test_byte) {
2004-05-01 09:19:35 +00:00
case 0xC0: // 1100 0000
// Uncompressed run follows: Max runlength 63
runcount = mark_byte & 0x3f;
2004-05-01 09:19:35 +00:00
if ((inbuf_remain < runcount) || (outbuf_remain < runcount)) {
return FAILURE;
}
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *inbuf_ptr++;
}
inbuf_remain -= runcount;
outbuf_remain -= runcount;
continue;
break;
2004-05-01 09:19:35 +00:00
case 0x80: // 1000 0000
// Compressed run follows: Max runlength 63
runcount = (mark_byte & 0x3f) + 3;
if (!inbuf_remain || (outbuf_remain < runcount)) {
return FAILURE;
}
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *inbuf_ptr;
}
inbuf_ptr++;
inbuf_remain--;
outbuf_remain -= runcount;
continue;
break;
2004-05-01 09:19:35 +00:00
case 0x40: // 0100 0000
// Repeat decoded sequence from output stream:
// Max runlength 10
runcount = ((mark_byte >> 3) & 0x07U) + 3;
backtrack_amount = *inbuf_ptr;
2004-05-01 09:19:35 +00:00
if (!inbuf_remain || (backtrack_amount > (outbuf_ptr - outbuf)) || (runcount > outbuf_remain)) {
return FAILURE;
}
inbuf_ptr++;
inbuf_remain--;
backtrack_ptr = outbuf_ptr - backtrack_amount;
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *backtrack_ptr++;
}
outbuf_remain -= runcount;
continue;
break;
2004-05-01 09:19:35 +00:00
default: // 0000 0000
break;
}
2004-05-01 09:19:35 +00:00
// Mask all but the third and fourth highest order bits
test_byte = mark_byte & 0x30;
switch (test_byte) {
2004-05-01 09:19:35 +00:00
case 0x30: // 0011 0000
// Bitfield compression
runcount = (mark_byte & 0x0F) + 1;
2004-05-01 09:19:35 +00:00
if ((inbuf_remain < (runcount + 2)) || (outbuf_remain < (runcount * 8))) {
return FAILURE;
}
bitfield_byte1 = *inbuf_ptr++;
bitfield_byte2 = *inbuf_ptr++;
for (c = 0; c < runcount; c++) {
bitfield = *inbuf_ptr;
for (b = 0; b < 8; b++) {
if (bitfield & 0x80) {
*outbuf_ptr = bitfield_byte2;
} else {
*outbuf_ptr = bitfield_byte1;
}
bitfield <<= 1;
outbuf_ptr++;
}
inbuf_ptr++;
}
inbuf_remain -= (runcount + 2);
outbuf_remain -= (runcount * 8);
continue;
break;
2004-05-01 09:19:35 +00:00
case 0x20: // 0010 0000
// Uncompressed run follows
runcount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr;
2004-05-01 09:19:35 +00:00
if ((inbuf_remain < (runcount + 1)) || (outbuf_remain < runcount)) {
return FAILURE;
}
inbuf_ptr++;
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *inbuf_ptr++;
}
inbuf_remain -= (runcount + 1);
outbuf_remain -= runcount;
continue;
break;
2004-05-01 09:19:35 +00:00
case 0x10: // 0001 0000
// Repeat decoded sequence from output stream
backtrack_amount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr;
if (inbuf_remain < 2) {
return FAILURE;
}
inbuf_ptr++;
runcount = *inbuf_ptr++;
2004-05-01 09:19:35 +00:00
if ((backtrack_amount > (outbuf_ptr - outbuf)) || (outbuf_remain < runcount)) {
return FAILURE;
}
backtrack_ptr = outbuf_ptr - backtrack_amount;
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *backtrack_ptr++;
}
inbuf_remain -= 2;
outbuf_remain -= runcount;
continue;
break;
default:
return FAILURE;
break;
}
2004-05-01 09:19:35 +00:00
}
return SUCCESS;
}
int SagaEngine::flipImage(byte *img_buf, int columns, int scanlines) {
int line;
byte *tmp_scan;
byte *flip_p1;
byte *flip_p2;
int flipcount = scanlines / 2;
tmp_scan = (byte *)malloc(columns);
if (tmp_scan == NULL) {
return FAILURE;
}
flip_p1 = img_buf;
flip_p2 = img_buf + (columns * (scanlines - 1));
for (line = 0; line < flipcount; line++) {
memcpy(tmp_scan, flip_p1, columns);
memcpy(flip_p1, flip_p2, columns);
memcpy(flip_p2, tmp_scan, columns);
flip_p1 += columns;
flip_p2 -= columns;
}
free(tmp_scan);
return SUCCESS;
}
int SagaEngine::unbankBGImage(byte *dst_buf, const byte *src_buf, int columns, int scanlines) {
int x, y;
int temp;
int quadruple_rows;
int remain_rows;
int rowjump_src;
int rowjump_dest;
const byte *src_p;
byte *dst_p;
const byte *srcptr1, *srcptr2, *srcptr3, *srcptr4;
byte *dstptr1, *dstptr2, *dstptr3, *dstptr4;
quadruple_rows = scanlines - (scanlines % 4);
remain_rows = scanlines - quadruple_rows;
assert(scanlines > 0);
src_p = src_buf;
dst_p = dst_buf + columns;
srcptr1 = src_p;
srcptr2 = src_p + 1;
srcptr3 = src_p + 2;
srcptr4 = src_p + 3;
dstptr1 = dst_buf;
dstptr2 = dst_buf + columns;
dstptr3 = dst_buf + columns * 2;
dstptr4 = dst_buf + columns * 3;
rowjump_src = columns * 4;
rowjump_dest = columns * 4;
2004-05-01 09:19:35 +00:00
// Unbank groups of 4 first
for (y = 0; y < quadruple_rows; y += 4) {
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
dstptr2[x] = srcptr2[temp];
dstptr3[x] = srcptr3[temp];
dstptr4[x] = srcptr4[temp];
}
2004-05-01 09:19:35 +00:00
// This is to avoid generating invalid pointers -
// usually innocuous, but undefined
if (y < quadruple_rows - 4) {
dstptr1 += rowjump_dest;
dstptr2 += rowjump_dest;
dstptr3 += rowjump_dest;
dstptr4 += rowjump_dest;
srcptr1 += rowjump_src;
srcptr2 += rowjump_src;
srcptr3 += rowjump_src;
srcptr4 += rowjump_src;
}
}
2004-05-01 09:19:35 +00:00
// Unbank rows remaining
switch (remain_rows) {
case 1:
dstptr1 += rowjump_dest;
srcptr1 += rowjump_src;
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
}
break;
case 2:
dstptr1 += rowjump_dest;
dstptr2 += rowjump_dest;
srcptr1 += rowjump_src;
srcptr2 += rowjump_src;
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
dstptr2[x] = srcptr2[temp];
}
break;
case 3:
dstptr1 += rowjump_dest;
dstptr2 += rowjump_dest;
dstptr3 += rowjump_dest;
srcptr1 += rowjump_src;
srcptr2 += rowjump_src;
srcptr3 += rowjump_src;
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
dstptr2[x] = srcptr2[temp];
dstptr3[x] = srcptr3[temp];
}
break;
default:
break;
}
return SUCCESS;
}
2004-07-31 13:49:26 +00:00
const byte *SagaEngine::getImagePal(const byte *image_data, size_t image_size) {
if (image_size <= SAGA_IMAGE_HEADER_LEN) {
return NULL;
}
return image_data + SAGA_IMAGE_HEADER_LEN;
}
} // End of namespace Saga