mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-20 00:41:12 +00:00
1c711da8fc
Janitorial removed function call which had a side effect. Thus the actor image load code crashed.
634 lines
17 KiB
C++
634 lines
17 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 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.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This code is based on Broken Sword 2.5 engine
|
|
*
|
|
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
|
*
|
|
* Licensed under GNU GPL v2
|
|
*
|
|
*/
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Includes
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#include "sword25/gfx/image/art.h"
|
|
#include "sword25/gfx/image/vectorimage.h"
|
|
#include "sword25/gfx/image/renderedimage.h"
|
|
|
|
#include "graphics/colormasks.h"
|
|
|
|
namespace Sword25 {
|
|
|
|
#define BEZSMOOTHNESS 0.5
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// SWF datatype
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Bitstream helper class
|
|
// -----------------------------------------------------------------------------
|
|
// The parsing of SWF files requires both bitwise readout and on Byte boundaries
|
|
// oriented reading.
|
|
// This class is specially equipped for this.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
class VectorImage::SWFBitStream {
|
|
public:
|
|
SWFBitStream(const byte *pData, uint dataSize) :
|
|
m_Pos(pData), m_End(pData + dataSize), m_WordMask(0)
|
|
{}
|
|
|
|
inline uint32 getBits(uint bitCount) {
|
|
if (bitCount == 0 || bitCount > 32) {
|
|
error("SWFBitStream::GetBits() must read at least 1 and at most 32 bits at a time");
|
|
}
|
|
|
|
uint32 value = 0;
|
|
while (bitCount) {
|
|
if (m_WordMask == 0)
|
|
flushByte();
|
|
|
|
value <<= 1;
|
|
value |= ((m_Word & m_WordMask) != 0) ? 1 : 0;
|
|
m_WordMask >>= 1;
|
|
|
|
--bitCount;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
inline int32 getSignedBits(uint bitCount) {
|
|
// readout bits
|
|
uint32 temp = getBits(bitCount);
|
|
|
|
// If the sign-bit is set, fill the rest of the return value with 1-bit (sign extension)
|
|
if (temp & 1 << (bitCount - 1))
|
|
return (0xffffffff << bitCount) | temp;
|
|
else
|
|
return temp;
|
|
}
|
|
|
|
inline uint32 getUInt32() {
|
|
uint32 byte1 = getByte();
|
|
uint32 byte2 = getByte();
|
|
uint32 byte3 = getByte();
|
|
uint32 byte4 = getByte();
|
|
|
|
return byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24);
|
|
}
|
|
|
|
inline uint16 getUInt16() {
|
|
uint32 byte1 = getByte();
|
|
uint32 byte2 = getByte();
|
|
|
|
return byte1 | (byte2 << 8);
|
|
}
|
|
|
|
inline byte getByte() {
|
|
flushByte();
|
|
byte value = m_Word;
|
|
m_WordMask = 0;
|
|
flushByte();
|
|
|
|
return value;
|
|
}
|
|
|
|
inline void flushByte() {
|
|
if (m_WordMask != 128) {
|
|
if (m_Pos >= m_End) {
|
|
error("Attempted to read past end of file");
|
|
} else {
|
|
m_Word = *m_Pos++;
|
|
m_WordMask = 128;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void skipBytes(uint skipLength) {
|
|
flushByte();
|
|
if (m_Pos + skipLength >= m_End) {
|
|
error("Attempted to read past end of file");
|
|
} else {
|
|
m_Pos += skipLength;
|
|
m_Word = *(m_Pos - 1);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const byte *m_Pos;
|
|
const byte *m_End;
|
|
|
|
byte m_Word;
|
|
uint m_WordMask;
|
|
};
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Constants and utility functions
|
|
// -----------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
// -----------------------------------------------------------------------------
|
|
// Constants
|
|
// -----------------------------------------------------------------------------
|
|
|
|
const uint32 MAX_ACCEPTED_FLASH_VERSION = 3; // The maximum flash file version that is accepted by the loader
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Converts SWF rectangle data in a bit stream in Common::Rect objects
|
|
// -----------------------------------------------------------------------------
|
|
|
|
Common::Rect flashRectToBSRect(VectorImage::SWFBitStream &bs) {
|
|
bs.flushByte();
|
|
|
|
// Determines how many bits of the single components are encoded
|
|
uint32 bitsPerValue = bs.getBits(5);
|
|
|
|
// Readout the single components
|
|
int32 xMin = bs.getSignedBits(bitsPerValue);
|
|
int32 xMax = bs.getSignedBits(bitsPerValue);
|
|
int32 yMin = bs.getSignedBits(bitsPerValue);
|
|
int32 yMax = bs.getSignedBits(bitsPerValue);
|
|
|
|
return Common::Rect(xMin, yMin, xMax + 1, yMax + 1);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Calculate the bounding box of a BS_VectorImageElement
|
|
// -----------------------------------------------------------------------------
|
|
|
|
Common::Rect CalculateBoundingBox(const VectorImageElement &vectorImageElement) {
|
|
double x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0;
|
|
|
|
for (int j = vectorImageElement.getPathCount() - 1; j >= 0; j--) {
|
|
ArtBpath *bez = vectorImageElement.getPathInfo(j).getVec();
|
|
ArtVpath *vec = art_bez_path_to_vec(bez, 0.5);
|
|
|
|
if (vec[0].code == ART_END) {
|
|
continue;
|
|
} else {
|
|
x0 = x1 = vec[0].x;
|
|
y0 = y1 = vec[0].y;
|
|
for (int i = 1; vec[i].code != ART_END; i++) {
|
|
if (vec[i].x < x0) x0 = vec[i].x;
|
|
if (vec[i].x > x1) x1 = vec[i].x;
|
|
if (vec[i].y < y0) y0 = vec[i].y;
|
|
if (vec[i].y > y1) y1 = vec[i].y;
|
|
}
|
|
}
|
|
free(vec);
|
|
}
|
|
|
|
return Common::Rect(static_cast<int>(x0), static_cast<int>(y0), static_cast<int>(x1) + 1, static_cast<int>(y1) + 1);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Construction
|
|
// -----------------------------------------------------------------------------
|
|
|
|
VectorImage::VectorImage(const byte *pFileData, uint fileSize, bool &success, const Common::String &fname) : _pixelData(0), _fname(fname) {
|
|
success = false;
|
|
|
|
// Create bitstream object
|
|
// In the following the file data will be readout of the bitstream object.
|
|
SWFBitStream bs(pFileData, fileSize);
|
|
|
|
// Check SWF signature
|
|
uint32 signature[3];
|
|
signature[0] = bs.getByte();
|
|
signature[1] = bs.getByte();
|
|
signature[2] = bs.getByte();
|
|
if (signature[0] != 'F' ||
|
|
signature[1] != 'W' ||
|
|
signature[2] != 'S') {
|
|
error("File is not a valid SWF-file");
|
|
return;
|
|
}
|
|
|
|
// Check the version
|
|
uint32 version = bs.getByte();
|
|
if (version > MAX_ACCEPTED_FLASH_VERSION) {
|
|
error("File is of version %d. Highest accepted version is %d.", version, MAX_ACCEPTED_FLASH_VERSION);
|
|
return;
|
|
}
|
|
|
|
// Readout filesize and compare with the actual size
|
|
uint32 storedFileSize = bs.getUInt32();
|
|
if (storedFileSize != fileSize) {
|
|
error("File is not a valid SWF-file");
|
|
return;
|
|
}
|
|
|
|
// readout SWF size
|
|
flashRectToBSRect(bs);
|
|
|
|
// Get frame rate and frame count
|
|
/* uint32 frameRate = */
|
|
bs.getUInt16();
|
|
/* uint32 frameCount = */
|
|
bs.getUInt16();
|
|
|
|
// Parse tags
|
|
// Because we are only interested in the first DifneShape-Tag...
|
|
bool keepParsing = true;
|
|
while (keepParsing) {
|
|
// Tags always begin on byte boundaries
|
|
bs.flushByte();
|
|
|
|
// Readout tag type and length
|
|
uint16 tagTypeAndLength = bs.getUInt16();
|
|
uint32 tagType = tagTypeAndLength >> 6;
|
|
uint32 tagLength = tagTypeAndLength & 0x3f;
|
|
if (tagLength == 0x3f)
|
|
tagLength = bs.getUInt32();
|
|
|
|
switch (tagType) {
|
|
case 2:
|
|
// DefineShape
|
|
success = parseDefineShape(2, bs);
|
|
return;
|
|
case 22:
|
|
// DefineShape2
|
|
success = parseDefineShape(2, bs);
|
|
return;
|
|
case 32:
|
|
success = parseDefineShape(3, bs);
|
|
return;
|
|
default:
|
|
// Ignore unknown tags
|
|
bs.skipBytes(tagLength);
|
|
}
|
|
}
|
|
|
|
// The execution must not arrive at this point: Either a shape is found, then the function will be leaved before, or it is found none, then
|
|
// an exception occurs as soon as it is read beyond of the end of file.
|
|
assert(false);
|
|
}
|
|
|
|
VectorImage::~VectorImage() {
|
|
for (int j = _elements.size() - 1; j >= 0; j--)
|
|
for (int i = _elements[j].getPathCount() - 1; i >= 0; i--)
|
|
if (_elements[j].getPathInfo(i).getVec())
|
|
free(_elements[j].getPathInfo(i).getVec());
|
|
|
|
if (_pixelData)
|
|
free(_pixelData);
|
|
}
|
|
|
|
|
|
ArtBpath *ensureBezStorage(ArtBpath *bez, int nodes, int *allocated) {
|
|
if (*allocated <= nodes) {
|
|
(*allocated) += 20;
|
|
|
|
return art_renew(bez, ArtBpath, *allocated);
|
|
}
|
|
|
|
return bez;
|
|
}
|
|
|
|
ArtBpath *VectorImage::storeBez(ArtBpath *bez, int lineStyle, int fillStyle0, int fillStyle1, int *bezNodes, int *bezAllocated) {
|
|
(*bezNodes)++;
|
|
|
|
bez = ensureBezStorage(bez, *bezNodes, bezAllocated);
|
|
bez[*bezNodes].code = ART_END;
|
|
|
|
ArtBpath *bez1 = art_new(ArtBpath, *bezNodes + 1);
|
|
if (!bez1)
|
|
error("[VectorImage::storeBez] Cannot allocate memory");
|
|
|
|
for (int i = 0; i <= *bezNodes; i++)
|
|
bez1[i] = bez[i];
|
|
|
|
_elements.back()._pathInfos.push_back(VectorPathInfo(bez1, *bezNodes, lineStyle, fillStyle0, fillStyle1));
|
|
|
|
return bez;
|
|
}
|
|
|
|
bool VectorImage::parseDefineShape(uint shapeType, SWFBitStream &bs) {
|
|
/*uint32 shapeID = */bs.getUInt16();
|
|
|
|
// readout bounding box
|
|
_boundingBox = flashRectToBSRect(bs);
|
|
|
|
// create first image element
|
|
_elements.resize(1);
|
|
|
|
// read styles
|
|
uint numFillBits;
|
|
uint numLineBits;
|
|
if (!parseStyles(shapeType, bs, numFillBits, numLineBits))
|
|
return false;
|
|
|
|
uint lineStyle = 0;
|
|
uint fillStyle0 = 0;
|
|
uint fillStyle1 = 0;
|
|
|
|
// parse shaperecord
|
|
// ------------------
|
|
|
|
double curX = 0;
|
|
double curY = 0;
|
|
int bezNodes = 0;
|
|
int bezAllocated = 10;
|
|
ArtBpath *bez = art_new(ArtBpath, bezAllocated);
|
|
|
|
bool endOfShapeDiscovered = false;
|
|
while (!endOfShapeDiscovered) {
|
|
uint32 typeFlag = bs.getBits(1);
|
|
|
|
// Non-Edge Record
|
|
if (typeFlag == 0) {
|
|
// Determines which parameters are set
|
|
uint32 stateNewStyles = bs.getBits(1);
|
|
uint32 stateLineStyle = bs.getBits(1);
|
|
uint32 stateFillStyle1 = bs.getBits(1);
|
|
uint32 stateFillStyle0 = bs.getBits(1);
|
|
uint32 stateMoveTo = bs.getBits(1);
|
|
|
|
uint prevLineStyle = lineStyle;
|
|
uint prevFillStyle0 = fillStyle0;
|
|
uint prevFillStyle1 = fillStyle1;
|
|
|
|
// End of the shape definition is reached?
|
|
if (!stateNewStyles && !stateLineStyle && !stateFillStyle0 && !stateFillStyle1 && !stateMoveTo) {
|
|
endOfShapeDiscovered = true;
|
|
// Decode parameters
|
|
} else {
|
|
if (stateMoveTo) {
|
|
uint32 moveToBits = bs.getBits(5);
|
|
curX = bs.getSignedBits(moveToBits);
|
|
curY = bs.getSignedBits(moveToBits);
|
|
}
|
|
|
|
if (stateFillStyle0) {
|
|
if (numFillBits > 0)
|
|
fillStyle0 = bs.getBits(numFillBits);
|
|
else
|
|
fillStyle0 = 0;
|
|
}
|
|
|
|
if (stateFillStyle1) {
|
|
if (numFillBits > 0)
|
|
fillStyle1 = bs.getBits(numFillBits);
|
|
else
|
|
fillStyle1 = 0;
|
|
}
|
|
|
|
if (stateLineStyle) {
|
|
if (numLineBits)
|
|
lineStyle = bs.getBits(numLineBits);
|
|
else
|
|
numLineBits = 0;
|
|
}
|
|
|
|
// Create a new path, unless there were only defined new styles
|
|
if (stateLineStyle || stateFillStyle0 || stateFillStyle1 || stateMoveTo) {
|
|
// Store previous curve if any
|
|
if (bezNodes) {
|
|
bez = storeBez(bez, prevLineStyle, prevFillStyle0, prevFillStyle1, &bezNodes, &bezAllocated);
|
|
}
|
|
|
|
// Start new curve
|
|
bez = ensureBezStorage(bez, 1, &bezAllocated);
|
|
bez[0].code = ART_MOVETO_OPEN;
|
|
bez[0].x3 = curX;
|
|
bez[0].y3 = curY;
|
|
bezNodes = 0;
|
|
}
|
|
|
|
if (stateNewStyles) {
|
|
// The old style definitions will be discarded and overwritten with the new at this point in Flash.
|
|
// A new element will be started with a new element.
|
|
_elements.resize(_elements.size() + 1);
|
|
if (!parseStyles(shapeType, bs, numFillBits, numLineBits))
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
// Edge record
|
|
uint32 edgeFlag = bs.getBits(1);
|
|
uint32 numBits = bs.getBits(4) + 2;
|
|
|
|
// Curved edge
|
|
if (edgeFlag == 0) {
|
|
double controlDeltaX = bs.getSignedBits(numBits);
|
|
double controlDeltaY = bs.getSignedBits(numBits);
|
|
double anchorDeltaX = bs.getSignedBits(numBits);
|
|
double anchorDeltaY = bs.getSignedBits(numBits);
|
|
|
|
double controlX = curX + controlDeltaX;
|
|
double controlY = curY + controlDeltaY;
|
|
double newX = controlX + anchorDeltaX;
|
|
double newY = controlY + anchorDeltaY;
|
|
|
|
#define WEIGHT (2.0/3.0)
|
|
|
|
bezNodes++;
|
|
bez = ensureBezStorage(bez, bezNodes, &bezAllocated);
|
|
bez[bezNodes].code = ART_CURVETO;
|
|
bez[bezNodes].x1 = WEIGHT * controlX + (1 - WEIGHT) * curX;
|
|
bez[bezNodes].y1 = WEIGHT * controlY + (1 - WEIGHT) * curY;
|
|
bez[bezNodes].x2 = WEIGHT * controlX + (1 - WEIGHT) * newX;
|
|
bez[bezNodes].y2 = WEIGHT * controlY + (1 - WEIGHT) * newY;
|
|
bez[bezNodes].x3 = newX;
|
|
bez[bezNodes].y3 = newY;
|
|
|
|
curX = newX;
|
|
curY = newY;
|
|
} else {
|
|
// Staight edge
|
|
int32 deltaX = 0;
|
|
int32 deltaY = 0;
|
|
|
|
uint32 generalLineFlag = bs.getBits(1);
|
|
if (generalLineFlag) {
|
|
deltaX = bs.getSignedBits(numBits);
|
|
deltaY = bs.getSignedBits(numBits);
|
|
} else {
|
|
uint32 vertLineFlag = bs.getBits(1);
|
|
if (vertLineFlag)
|
|
deltaY = bs.getSignedBits(numBits);
|
|
else
|
|
deltaX = bs.getSignedBits(numBits);
|
|
}
|
|
|
|
curX += deltaX;
|
|
curY += deltaY;
|
|
|
|
bezNodes++;
|
|
bez = ensureBezStorage(bez, bezNodes, &bezAllocated);
|
|
bez[bezNodes].code = ART_LINETO;
|
|
bez[bezNodes].x3 = curX;
|
|
bez[bezNodes].y3 = curY;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store last curve
|
|
if (bezNodes)
|
|
bez = storeBez(bez, lineStyle, fillStyle0, fillStyle1, &bezNodes, &bezAllocated);
|
|
|
|
free(bez);
|
|
|
|
// Calculate the bounding boxes of each element
|
|
Common::Array<VectorImageElement>::iterator it = _elements.begin();
|
|
for (; it != _elements.end(); ++it)
|
|
it->_boundingBox = CalculateBoundingBox(*it);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
bool VectorImage::parseStyles(uint shapeType, SWFBitStream &bs, uint &numFillBits, uint &numLineBits) {
|
|
bs.flushByte();
|
|
|
|
// Parse fill styles
|
|
// -----------------
|
|
|
|
// Determine number of fill styles
|
|
uint fillStyleCount = bs.getByte();
|
|
if (fillStyleCount == 0xff)
|
|
fillStyleCount = bs.getUInt16();
|
|
|
|
// Readout all fill styles. If a fill style with Typ != 0 is found, the parsing is aborted.
|
|
// Only "solid fill" (Typ 0) is supported.
|
|
_elements.back()._fillStyles.reserve(fillStyleCount);
|
|
for (uint i = 0; i < fillStyleCount; ++i) {
|
|
byte type = bs.getByte();
|
|
uint32 color;
|
|
byte r = bs.getByte();
|
|
byte g = bs.getByte();
|
|
byte b = bs.getByte();
|
|
byte a = 0xff;
|
|
|
|
if (shapeType == 3)
|
|
a = bs.getByte();
|
|
|
|
color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b);
|
|
|
|
if (type != 0)
|
|
return false;
|
|
|
|
_elements.back()._fillStyles.push_back(color);
|
|
}
|
|
|
|
// Line styles parsen
|
|
// -----------------
|
|
|
|
// Determine number of line styles
|
|
uint lineStyleCount = bs.getByte();
|
|
if (lineStyleCount == 0xff)
|
|
lineStyleCount = bs.getUInt16();
|
|
|
|
// Readout all line styles
|
|
_elements.back()._lineStyles.reserve(lineStyleCount);
|
|
for (uint i = 0; i < lineStyleCount; ++i) {
|
|
double width = bs.getUInt16();
|
|
uint32 color;
|
|
byte r = bs.getByte();
|
|
byte g = bs.getByte();
|
|
byte b = bs.getByte();
|
|
byte a = 0xff;
|
|
|
|
if (shapeType == 3)
|
|
a = bs.getByte();
|
|
|
|
color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b);
|
|
|
|
_elements.back()._lineStyles.push_back(VectorImageElement::LineStyleType(width, color));
|
|
}
|
|
|
|
// Readout the bit width for the following style indices
|
|
numFillBits = bs.getBits(4);
|
|
numLineBits = bs.getBits(4);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
bool VectorImage::fill(const Common::Rect *pFillRect, uint color) {
|
|
error("Fill() is not supported.");
|
|
return false;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
uint VectorImage::getPixel(int x, int y) {
|
|
error("GetPixel() is not supported. Returning black.");
|
|
return 0;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
bool VectorImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) {
|
|
error("SetContent() is not supported.");
|
|
return 0;
|
|
}
|
|
|
|
bool VectorImage::blit(int posX, int posY,
|
|
int flipping,
|
|
Common::Rect *pPartRect,
|
|
uint color,
|
|
int width, int height) {
|
|
static VectorImage *oldThis = 0;
|
|
static int oldWidth = -2;
|
|
static int oldHeight = -2;
|
|
|
|
// If width or height to 0, nothing needs to be shown.
|
|
if (width == 0 || height == 0)
|
|
return true;
|
|
|
|
// Determine if the old image in the cache can not be reused and must be recalculated
|
|
if (!(oldThis == this && oldWidth == width && oldHeight == height)) {
|
|
render(width, height);
|
|
|
|
oldThis = this;
|
|
oldHeight = height;
|
|
oldWidth = width;
|
|
}
|
|
|
|
RenderedImage *rend = new RenderedImage();
|
|
|
|
rend->replaceContent(_pixelData, width, height);
|
|
rend->blit(posX, posY, flipping, pPartRect, color, width, height);
|
|
|
|
delete rend;
|
|
|
|
return true;
|
|
}
|
|
|
|
} // End of namespace Sword25
|