/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ // SAGA Image resource management routines #include "saga/saga.h" namespace Saga { static int granulate(int value, int granularity) { 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); } } bool SagaEngine::decodeBGImage(const ByteArray &imageData, ByteArray &outputBuffer, int *w, int *h, bool flip) { ImageHeader hdr; int modex_height; const byte *RLE_data_ptr; size_t RLE_data_len; ByteArray decodeBuffer; if (imageData.size() <= SAGA_IMAGE_DATA_OFFSET) { error("decodeBGImage() Image size is way too small (%d)", (int)imageData.size()); } ByteArrayReadStreamEndian readS(imageData, isBigEndian()); hdr.width = readS.readUint16(); hdr.height = readS.readUint16(); // The next four bytes of the image header aren't used. readS.readUint16(); readS.readUint16(); RLE_data_ptr = &imageData.front() + SAGA_IMAGE_DATA_OFFSET; RLE_data_len = imageData.size() - SAGA_IMAGE_DATA_OFFSET; modex_height = granulate(hdr.height, 4); decodeBuffer.resize(hdr.width * modex_height); outputBuffer.resize(hdr.width * hdr.height); if (!decodeBGImageRLE(RLE_data_ptr, RLE_data_len, decodeBuffer)) { return false; } unbankBGImage(outputBuffer.getBuffer(), decodeBuffer.getBuffer(), hdr.width, hdr.height); // For some reason bg images in IHNM are upside down if (getGameId() == GID_IHNM && !flip) { flipImage(outputBuffer.getBuffer(), hdr.width, hdr.height); } *w = hdr.width; *h = hdr.height; return true; } bool SagaEngine::decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, ByteArray &outbuf) { const byte *inbuf_ptr; byte *outbuf_ptr; byte *outbuf_start; uint32 inbuf_remain; const byte *inbuf_end; byte *outbuf_end; uint32 outbuf_remain; byte mark_byte; int test_byte; 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_start = outbuf_ptr = outbuf.getBuffer(); outbuf_remain = outbuf.size(); outbuf_end = (outbuf_start + outbuf_remain) - 1; memset(outbuf_start, 0, outbuf_remain); inbuf_end = (inbuf + inbuf_len) - 1; while ((inbuf_remain > 1) && (outbuf_remain > 0) && !decode_err) { if ((inbuf_ptr > inbuf_end) || (outbuf_ptr > outbuf_end)) { return false; } mark_byte = *inbuf_ptr++; inbuf_remain--; test_byte = mark_byte & 0xC0; // Mask all but two high order bits switch (test_byte) { case 0xC0: // 1100 0000 // Uncompressed run follows: Max runlength 63 runcount = mark_byte & 0x3f; if ((inbuf_remain < runcount) || (outbuf_remain < runcount)) { return false; } for (c = 0; c < runcount; c++) { *outbuf_ptr++ = *inbuf_ptr++; } inbuf_remain -= runcount; outbuf_remain -= runcount; continue; break; case 0x80: // 1000 0000 // Compressed run follows: Max runlength 63 runcount = (mark_byte & 0x3f) + 3; if (!inbuf_remain || (outbuf_remain < runcount)) { return false; } for (c = 0; c < runcount; c++) { *outbuf_ptr++ = *inbuf_ptr; } inbuf_ptr++; inbuf_remain--; outbuf_remain -= runcount; continue; break; case 0x40: // 0100 0000 // Repeat decoded sequence from output stream: // Max runlength 10 runcount = ((mark_byte >> 3) & 0x07U) + 3; backtrack_amount = *inbuf_ptr; if (!inbuf_remain || (backtrack_amount > (outbuf_ptr - outbuf_start)) || (runcount > outbuf_remain)) { return false; } 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; default: // 0000 0000 break; } // Mask all but the third and fourth highest order bits test_byte = mark_byte & 0x30; switch (test_byte) { case 0x30: // 0011 0000 // Bitfield compression runcount = (mark_byte & 0x0F) + 1; if ((inbuf_remain < (runcount + 2)) || (outbuf_remain < (runcount * 8))) { return false; } 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; case 0x20: // 0010 0000 // Uncompressed run follows runcount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr; if ((inbuf_remain < (runcount + 1)) || (outbuf_remain < runcount)) { return false; } inbuf_ptr++; for (c = 0; c < runcount; c++) { *outbuf_ptr++ = *inbuf_ptr++; } inbuf_remain -= (runcount + 1); outbuf_remain -= runcount; continue; break; case 0x10: // 0001 0000 // Repeat decoded sequence from output stream backtrack_amount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr; if (inbuf_remain < 2) { return false; } inbuf_ptr++; runcount = *inbuf_ptr++; if ((backtrack_amount > (outbuf_ptr - outbuf_start)) || (outbuf_remain < runcount)) { return false; } 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 false; } } return true; } void SagaEngine::flipImage(byte *imageBuffer, int columns, int scanlines) { int line; ByteArray tmp_scan; byte *flip_p1; byte *flip_p2; byte *flip_tmp; int flipcount = scanlines / 2; tmp_scan.resize(columns); flip_tmp = tmp_scan.getBuffer(); if (flip_tmp == NULL) { return; } flip_p1 = imageBuffer; flip_p2 = imageBuffer + (columns * (scanlines - 1)); for (line = 0; line < flipcount; line++) { memcpy(flip_tmp, flip_p1, columns); memcpy(flip_p1, flip_p2, columns); memcpy(flip_p2, flip_tmp, columns); flip_p1 += columns; flip_p2 -= columns; } } void 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; 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; 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; // 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]; } // 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; } } // 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; } } } // End of namespace Saga