Files
2026-02-02 04:50:13 +01:00

442 lines
12 KiB
C++

/* 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 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/>.
*
*/
#include "ags/shared/util/compress.h"
#include "common/std/vector.h"
#include "ags/shared/ac/common.h" // quit, update_polled_stuff
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/util/file.h"
#include "ags/shared/util/lzw.h"
#include "ags/shared/util/memory_stream.h"
#include "ags/globals.h"
#if AGS_PLATFORM_ENDIAN_BIG
#include "ags/shared/util/bbop.h"
#endif
#include "common/compression/deflate.h"
namespace AGS3 {
using namespace AGS::Shared;
//-----------------------------------------------------------------------------
// RLE
//-----------------------------------------------------------------------------
static void cpackbitl(const uint8_t *line, size_t size, Stream *out) {
size_t cnt = 0; // bytes encoded
while (cnt < size) {
// IMPORTANT: the algorithm below requires signed operations
int i = static_cast<int32_t>(cnt);
int j = i + 1;
int jmax = i + 126;
if (static_cast<uint32_t>(jmax) >= size)
jmax = size - 1;
if (static_cast<uint32_t>(i) == size - 1) { //......last byte alone
out->WriteInt8(0);
out->WriteInt8(line[i]);
cnt++;
} else if (line[i] == line[j]) { //....run
while ((j < jmax) && (line[j] == line[j + 1]))
j++;
out->WriteInt8(i - j);
out->WriteInt8(line[i]);
cnt += j - i + 1;
} else { //.............................sequence
while ((j < jmax) && (line[j] != line[j + 1]))
j++;
out->WriteInt8(j - i);
out->Write(line + i, j - i + 1);
cnt += j - i + 1;
}
} // end while
}
static void cpackbitl16(const uint16_t *line, size_t size, Stream *out) {
size_t cnt = 0; // bytes encoded
while (cnt < size) {
// IMPORTANT: the algorithm below requires signed operations
int i = cnt;
int j = i + 1;
int jmax = i + 126;
if (static_cast<uint32_t>(jmax) >= size)
jmax = size - 1;
if (static_cast<uint32_t>(i) == size - 1) { //......last byte alone
out->WriteInt8(0);
out->WriteInt16(line[i]);
cnt++;
} else if (line[i] == line[j]) { //....run
while ((j < jmax) && (line[j] == line[j + 1]))
j++;
out->WriteInt8(i - j);
out->WriteInt16(line[i]);
cnt += j - i + 1;
} else { //.............................sequence
while ((j < jmax) && (line[j] != line[j + 1]))
j++;
out->WriteInt8(j - i);
out->WriteArray(line + i, j - i + 1, 2);
cnt += j - i + 1;
}
} // end while
}
static void cpackbitl32(const uint32_t *line, size_t size, Stream *out) {
size_t cnt = 0; // bytes encoded
while (cnt < size) {
// IMPORTANT: the algorithm below requires signed operations
int i = cnt;
int j = i + 1;
int jmax = i + 126;
if (static_cast<uint32_t>(jmax) >= size)
jmax = size - 1;
if (static_cast<uint32_t>(i) == size - 1) { //......last byte alone
out->WriteInt8(0);
out->WriteInt32(line[i]);
cnt++;
} else if (line[i] == line[j]) { //....run
while ((j < jmax) && (line[j] == line[j + 1]))
j++;
out->WriteInt8(i - j);
out->WriteInt32(line[i]);
cnt += j - i + 1;
} else { //.............................sequence
while ((j < jmax) && (line[j] != line[j + 1]))
j++;
out->WriteInt8(j - i);
out->WriteArray(line + i, j - i + 1, 4);
cnt += j - i + 1;
}
} // end while
}
static int cunpackbitl(uint8_t *line, size_t size, Stream *in) {
size_t n = 0; // number of bytes decoded
while (n < size) {
int ix = in->ReadByte(); // get index byte
int8 cx = ix;
if (cx == -128)
cx = 0;
if (cx < 0) { //.............run
int i = 1 - cx;
char ch = in->ReadInt8();
while (i--) {
// test for buffer overflow
if (n >= size)
return -1;
line[n++] = ch;
}
} else { //.....................seq
int i = cx + 1;
while (i--) {
// test for buffer overflow
if (n >= size)
return -1;
line[n++] = in->ReadByte();
}
}
}
return 0;
}
static int cunpackbitl16(uint16_t *line, size_t size, Stream *in) {
size_t n = 0; // number of bytes decoded
while (n < size) {
int ix = in->ReadByte(); // get index byte
int8 cx = ix;
if (cx == -128)
cx = 0;
if (cx < 0) { //.............run
int i = 1 - cx;
unsigned short ch = in->ReadInt16();
while (i--) {
// test for buffer overflow
if (n >= size)
return -1;
line[n++] = ch;
}
} else { //.....................seq
int i = cx + 1;
while (i--) {
// test for buffer overflow
if (n >= size)
return -1;
line[n++] = in->ReadInt16();
}
}
}
return 0;
}
static int cunpackbitl32(uint32_t *line, size_t size, Stream *in) {
size_t n = 0; // number of bytes decoded
while (n < size) {
int ix = in->ReadByte(); // get index byte
int8 cx = ix;
if (cx == -128)
cx = 0;
if (cx < 0) { //.............run
int i = 1 - cx;
unsigned int ch = in->ReadInt32();
while (i--) {
// test for buffer overflow
if (n >= size)
return -1;
line[n++] = ch;
}
} else { //.....................seq
int i = cx + 1;
while (i--) {
// test for buffer overflow
if (n >= size)
return -1;
line[n++] = (unsigned int)in->ReadInt32();
}
}
}
return 0;
}
bool rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Stream *out) {
switch (image_bpp) {
case 1: cpackbitl(data, data_sz, out); break;
case 2: cpackbitl16(reinterpret_cast<const uint16_t *>(data), data_sz / sizeof(uint16_t), out); break;
case 4: cpackbitl32(reinterpret_cast<const uint32_t *>(data), data_sz / sizeof(uint32_t), out); break;
default: assert(0); break;
}
return true;
}
bool rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Stream *in) {
switch (image_bpp) {
case 1: cunpackbitl(data, data_sz, in); break;
case 2: cunpackbitl16(reinterpret_cast<uint16_t *>(data), data_sz / sizeof(uint16_t), in); break;
case 4: cunpackbitl32(reinterpret_cast<uint32_t *>(data), data_sz / sizeof(uint32_t), in); break;
default: assert(0); break;
}
return true;
}
void save_rle_bitmap8(Stream *out, const Bitmap *bmp, const RGB(*pal)[256]) {
assert(bmp->GetBPP() == 1);
out->WriteInt16(static_cast<uint16_t>(bmp->GetWidth()));
out->WriteInt16(static_cast<uint16_t>(bmp->GetHeight()));
// Pack the pixels
cpackbitl(bmp->GetData(), bmp->GetWidth() * bmp->GetHeight(), out);
// Save palette
if (!pal) { // if no pal, write dummy palette, because we have to
out->WriteByteCount(0, 256 * 3);
return;
}
const RGB *ppal = *pal;
for (int i = 0; i < 256; ++i) {
out->WriteInt8(ppal[i].r);
out->WriteInt8(ppal[i].g);
out->WriteInt8(ppal[i].b);
}
}
Shared::Bitmap *load_rle_bitmap8(Stream *in, RGB(*pal)[256]) {
int w = in->ReadInt16();
int h = in->ReadInt16();
Bitmap *bmp = BitmapHelper::CreateBitmap(w, h, 8);
if (!bmp) return nullptr;
// Unpack the pixels
cunpackbitl(bmp->GetDataForWriting(), w * h, in);
// Load or skip the palette
if (!pal) {
in->Seek(3 * 256);
return bmp;
}
RGB *ppal = *pal;
for (int i = 0; i < 256; ++i) {
ppal[i].r = in->ReadInt8();
ppal[i].g = in->ReadInt8();
ppal[i].b = in->ReadInt8();
}
return bmp;
}
void skip_rle_bitmap8(Stream *in) {
int w = in->ReadInt16();
int h = in->ReadInt16();
// Unpack the pixels into temp buf
std::vector<uint8_t> buf;
buf.resize(w * h);
cunpackbitl(&buf[0], w * h, in);
// Skip RGB palette
in->Seek(3 * 256);
}
//-----------------------------------------------------------------------------
// LZW
//-----------------------------------------------------------------------------
bool lzw_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *out) {
// LZW algorithm that we use fails on sequence less than 16 bytes.
if (data_sz < 16) {
out->Write(data, data_sz);
return true;
}
MemoryStream mem_in(data, data_sz);
return lzwcompress(&mem_in, out);
}
bool lzw_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *in, size_t in_sz) {
// LZW algorithm that we use fails on sequence less than 16 bytes.
if (data_sz < 16) {
in->Read(data, data_sz);
return true;
}
std::vector<uint8_t> in_buf(in_sz);
in->Read(in_buf.data(), in_sz);
return lzwexpand(in_buf.data(), in_sz, data, data_sz);
}
void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
// First write original bitmap's info and data into the memory buffer
// NOTE: we must do this purely for backward compatibility with old room formats:
// because they also included bmp width and height into compressed data!
std::vector<uint8_t> membuf;
{
VectorStream memws(membuf, kStream_Write);
int w = bmpp->GetWidth(), h = bmpp->GetHeight(), bpp = bmpp->GetBPP();
memws.WriteInt32(w * bpp); // stride
memws.WriteInt32(h);
switch (bpp) {
case 1: memws.Write(bmpp->GetData(), w * h * bpp); break;
case 2: memws.WriteArrayOfInt16(reinterpret_cast<const int16_t *>(bmpp->GetData()), w *h); break;
case 4: memws.WriteArrayOfInt32(reinterpret_cast<const int32_t *>(bmpp->GetData()), w *h); break;
default: assert(0); break;
}
}
// Open same buffer for reading, and begin writing compressed data into the output
VectorStream mem_in(membuf);
// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
if (pal)
out->WriteArray(*pal, sizeof(RGB), 256);
else
out->WriteByteCount(0, sizeof(RGB) * 256);
out->WriteInt32((uint32_t)mem_in.GetLength());
// reserve space for compressed size
soff_t cmpsz_at = out->GetPosition();
out->WriteInt32(0);
lzwcompress(&mem_in, out);
soff_t toret = out->GetPosition();
out->Seek(cmpsz_at, kSeekBegin);
soff_t compressed_sz = (toret - cmpsz_at) - sizeof(uint32_t);
out->WriteInt32(compressed_sz); // write compressed size
// seek back to the end of the output stream
out->Seek(toret, kSeekBegin);
}
Bitmap *load_lzw(Stream *in, int dst_bpp, RGB(*pal)[256]) {
// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
if (pal)
in->Read(*pal, sizeof(RGB) * 256);
else
in->Seek(sizeof(RGB) * 256);
const size_t uncomp_sz = in->ReadInt32();
const size_t comp_sz = in->ReadInt32();
const soff_t end_pos = in->GetPosition() + comp_sz;
// First decompress data into the memory buffer
std::vector<uint8_t> inbuf(comp_sz);
std::vector<uint8_t> membuf(uncomp_sz);
in->Read(inbuf.data(), comp_sz);
lzwexpand(inbuf.data(), comp_sz, membuf.data(), uncomp_sz);
// Open same buffer for reading and get params and pixels
VectorStream mem_in(membuf);
int stride = mem_in.ReadInt32(); // width * bpp
int height = mem_in.ReadInt32();
Bitmap *bmm = BitmapHelper::CreateBitmap((stride / dst_bpp), height, dst_bpp * 8);
if (!bmm) return nullptr; // out of mem?
size_t num_pixels = stride * height / dst_bpp;
uint8_t *bmp_data = bmm->GetDataForWriting();
switch (dst_bpp) {
case 1: mem_in.Read(bmp_data, num_pixels); break;
case 2: mem_in.ReadArrayOfInt16(reinterpret_cast<int16_t *>(bmp_data), num_pixels); break;
case 4: mem_in.ReadArrayOfInt32(reinterpret_cast<int32_t *>(bmp_data), num_pixels); break;
default: assert(0); break;
}
if (in->GetPosition() != end_pos)
in->Seek(end_pos, kSeekBegin);
return bmm;
}
bool deflate_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *out) {
// TODO
return false;
}
bool inflate_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *in, size_t in_sz) {
std::vector<uint8_t> in_buf(in_sz);
in->Read(in_buf.data(), in_sz);
return Common::inflateZlib(data, (unsigned long)data_sz, in_buf.data(), in_sz);
}
} // namespace AGS3