scummvm/engines/mediastation/bitmap.cpp
2025-01-23 20:23:39 -05:00

221 lines
8.7 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 "mediastation/datum.h"
#include "mediastation/bitmap.h"
#include "mediastation/debugchannels.h"
namespace MediaStation {
BitmapHeader::BitmapHeader(Chunk &chunk) {
uint headerSizeInBytes = Datum(chunk, kDatumTypeUint16_1).u.i;
debugC(5, kDebugLoading, "BitmapHeader::BitmapHeader(): headerSize = 0x%x", headerSizeInBytes);
_dimensions = Datum(chunk).u.point;
_compressionType = static_cast<BitmapCompressionType>(Datum(chunk, kDatumTypeUint16_1).u.i);
debugC(5, kDebugLoading, "BitmapHeader::BitmapHeader(): _compressionType = 0x%x", static_cast<uint>(_compressionType));
// TODO: Figure out what this is.
// This has something to do with the width of the bitmap but is always
// a few pixels off from the width. And in rare cases it seems to be
// the true width!
unk2 = Datum(chunk, kDatumTypeUint16_1).u.i;
}
BitmapHeader::~BitmapHeader() {
delete _dimensions;
_dimensions = nullptr;
}
bool BitmapHeader::isCompressed() {
return (_compressionType != kUncompressedBitmap1) && (_compressionType != kUncompressedBitmap2);
}
Bitmap::Bitmap(Chunk &chunk, BitmapHeader *bitmapHeader) :
_bitmapHeader(bitmapHeader) {
// The header must be constructed beforehand.
uint16 width = _bitmapHeader->_dimensions->x;
uint16 height = _bitmapHeader->_dimensions->y;
_surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
_surface.setTransparentColor(0);
uint8 *pixels = (uint8 *)_surface.getPixels();
if (_bitmapHeader->isCompressed()) {
// DECOMPRESS THE IMAGE.
debugC(5, kDebugLoading, "Bitmap::Bitmap(): Decompressing bitmap (@0x%llx)", static_cast<long long int>(chunk.pos()));
decompress(chunk);
debugC(5, kDebugLoading, "Bitmap::Bitmap(): Finished decompressing bitmap (@0x%llx) [%d remaining bytes]", static_cast<long long int>(chunk.pos()), chunk.bytesRemaining());
// TODO: Make sure there is nothing important in here. They are likely
// just zeroes.
chunk.skip(chunk.bytesRemaining());
} else {
// READ THE UNCOMPRESSED IMAGE DIRECTLY.
// TODO: Understand why we need to ignore these 2 bytes.
chunk.skip(2);
chunk.read(pixels, chunk.bytesRemaining());
}
}
Bitmap::~Bitmap() {
delete _bitmapHeader;
_bitmapHeader = nullptr;
}
uint16 Bitmap::width() {
return _bitmapHeader->_dimensions->x;
}
uint16 Bitmap::height() {
return _bitmapHeader->_dimensions->y;
}
void Bitmap::decompress(Chunk &chunk) {
// MAKE SURE WE READ PAST THE FIRST 2 BYTES.
uint unk1 = chunk.readByte();
uint unk2 = chunk.readByte();
if ((unk1 == 0) && (unk2 == 0)) {
if (chunk.bytesRemaining() == 0) {
// Sometimes there are compressed images that actually have no
// contents! If we've hit this case, exit the decompression now.
return;
}
} else {
chunk.seek(chunk.pos() - 2);
}
// GET THE DECOMPRESSED PIXELS BUFFER.
// Media Station has 8 bits per pixel, so the decompression buffer is
// simple.
char *decompressedImage = static_cast<char *>(_surface.getPixels());
// DECOMPRESS THE RLE-COMPRESSED BITMAP STREAM.
// TODO: Comemnted out becuase transparency runs not supported yet,
// and there were compiler warnings about these variables not being used.
// bool transparencyRunEverRead = false;
// size_t transparencyRunTopYCoordinate = 0;
// size_t transparencyRunLeftXCoordinate = 0;
bool imageFullyRead = false;
size_t currentYCoordinate = 0;
while (currentYCoordinate < height()) {
size_t currentXCoordinate = 0;
bool readingTransparencyRun = false;
while (true) {
byte operation = chunk.readByte();
if (operation == 0x00) {
// ENTER CONTROL MODE.
operation = chunk.readByte();
if (operation == 0x00) {
// MARK THE END OF THE LINE.
// Also check if the image is finished being read.
if (chunk.bytesRemaining() == 0) {
imageFullyRead = true;
}
break;
} else if (operation == 0x01) {
// MARK THE END OF THE IMAGE.
// TODO: When is this actually used?
imageFullyRead = true;
break;
} else if (operation == 0x02) {
// MARK THE START OF A KEYFRAME TRANSPARENCY REGION.
// Until a color index other than 0x00 (usually white) is read on this line,
// all pixels on this line will be marked transparent.
// If no transparency regions are present in this image, all 0x00 color indices are treated
// as transparent. Otherwise, only the 0x00 color indices within transparency regions
// are considered transparent. Only intraframes (frames that are not keyframes) have been
// observed to have transparency regions, and these intraframes have them so the keyframe
// can extend outside the boundary of the intraframe and
// still be removed.
//
// TODO: Comemnted out becuase transparency runs not
// supported yet, and there were compiler warnings about
// these variables being set but not used.
// readingTransparencyRun = true;
// transparencyRunTopYCoordinate = currentYCoordinate;
// transparencyRunLeftXCoordinate = currentXCoordinate;
// transparencyRunEverRead = true;
} else if (operation == 0x03) {
// ADJUST THE PIXEL POSITION.
// This permits jumping to a different part of the same row without
// needing a run of pixels in between. But the actual data consumed
// seems to actually be higher this way, as you need the control byte
// first.
// So to skip 10 pixels using this approach, you would encode 00 03 0a 00.
// But to "skip" 10 pixels by encoding them as blank (0xff), you would encode 0a ff.
// What gives? I'm not sure.
byte x_change = chunk.readByte();
currentXCoordinate += x_change;
byte y_change = chunk.readByte();
currentYCoordinate += y_change;
} else if (operation >= 0x04) {
// READ A RUN OF UNCOMPRESSED PIXELS.
size_t yOffset = currentYCoordinate * width();
size_t runStartingOffset = yOffset + currentXCoordinate;
char *runStartingPointer = decompressedImage + runStartingOffset;
byte runLength = operation;
// TODO: Is there a better way to do this than just copying?
char *uncompressedPixels = new char[runLength];
chunk.read(uncompressedPixels, runLength);
memcpy(runStartingPointer, uncompressedPixels, runLength);
delete[] uncompressedPixels;
currentXCoordinate += operation;
if (chunk.pos() % 2 == 1) {
chunk.readByte();
}
}
} else {
// READ A RUN OF LENGTH ENCODED PIXELS.
size_t yOffset = currentYCoordinate * width();
size_t runStartingOffset = yOffset + currentXCoordinate;
char *runStartingPointer = decompressedImage + runStartingOffset;
byte colorIndexToRepeat = chunk.readByte();
byte repetitionCount = operation;
memset(runStartingPointer, colorIndexToRepeat, repetitionCount);
currentXCoordinate += repetitionCount;
if (readingTransparencyRun) {
// TODO: This code is comemnted out becuase the engine
// doesn't support the keyframes/transparency regions on
// movies yet. However, only some movies have this to start with.
// GET THE TRANSPARENCY RUN STARTING OFFSET.
// size_t transparencyRunYOffset = transparencyRunTopYCoordinate * width();
// size_t transparencyRunStartOffset = transparencyRunYOffset + transparencyRunLeftXCoordinate;
// size_t transparencyRunEndingOffset = yOffset + currentXCoordinate;
// size_t transparency_run_length = transparencyRunEndingOffset - transparencyRunStartOffset;
// char *transparencyRunSrcPointer = keyframe_image + runStartingOffset;
// char *transparencyRunDestPointer = decompressedImage + runStartingOffset;
// COPY THE TRANSPARENT AREA FROM THE KEYFRAME.
// The "interior" of transparency regions is always encoded by a single run of
// pixels, usually 0x00 (white).
// memcpy(transparencyRunDestPointer, transparencyRunSrcPointer, transparency_run_length);
readingTransparencyRun = false;
}
}
}
currentYCoordinate++;
if (imageFullyRead) {
break;
}
}
}
}