scummvm/engines/sword25/gfx/image/vectorimage.cpp
Eugene Sandulenko 1c711da8fc SWORD25: Fix regression introduced in 5dd8f2575b
Janitorial removed function call which had a side effect.
Thus the actor image load code crashed.
2011-07-10 18:10:58 +03:00

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