diff options
author | Johannes Schickel | 2010-10-13 03:57:44 +0000 |
---|---|---|
committer | Johannes Schickel | 2010-10-13 03:57:44 +0000 |
commit | 75e8452b6e6a2bf4fb2f588aa00b428a60d873b5 (patch) | |
tree | f29541d55309487a94bd1d38e8b53bb3dde9aec6 /engines/sword25/gfx/image/vectorimage.cpp | |
parent | 48ee83b88957dab86bc763e9ef21a70179fa8679 (diff) | |
parent | e9f50882ea5b6beeefa994040be9d3bab6a1f107 (diff) | |
download | scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.tar.gz scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.tar.bz2 scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.zip |
OPENGL: Merged from trunk, from rev 52105 to 53396.
This includes an rather hacky attempt to merge all the recent gp2x backend
changes into the branch. I suppose the gp2x backend and probably all new
backends, i.e. gph, dingux etc., might not compile anymore.
Since I have no way of testing those it would be nice if porters could look
into getting those up to speed in this branch.
svn-id: r53399
Diffstat (limited to 'engines/sword25/gfx/image/vectorimage.cpp')
-rw-r--r-- | engines/sword25/gfx/image/vectorimage.cpp | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/engines/sword25/gfx/image/vectorimage.cpp b/engines/sword25/gfx/image/vectorimage.cpp new file mode 100644 index 0000000000..c2a80cb5f2 --- /dev/null +++ b/engines/sword25/gfx/image/vectorimage.cpp @@ -0,0 +1,637 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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/kernel/bs_stdint.h" +#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 BS_LOG_PREFIX "VECTORIMAGE" + +#define BEZSMOOTHNESS 0.5 + +// ----------------------------------------------------------------------------- +// SWF Datentypen +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Bitstream Hilfsklasse +// ----------------------------------------------------------------------------- +// Das Parsen von SWF-Dateien erfordert sowohl bitweises Auslesen als auch an +// Bytegrenzen ausgerichtetes Lesen. +// Diese Klasse ist speziell dafür ausgestattet. +// ----------------------------------------------------------------------------- + +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) { + // Bits einlesen + uint32 temp = getBits(bitCount); + + // Falls das Sign-Bit gesetzt ist, den Rest des Rückgabewertes mit 1-Bits auffüllen (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; +}; + + +// ----------------------------------------------------------------------------- +// Konstanten und Hilfsfunktionen +// ----------------------------------------------------------------------------- + +namespace { +// ----------------------------------------------------------------------------- +// Konstanten +// ----------------------------------------------------------------------------- + +const uint32 MAX_ACCEPTED_FLASH_VERSION = 3; // Die höchste Flash-Dateiversion, die vom Lader akzeptiert wird + + +// ----------------------------------------------------------------------------- +// Konvertiert SWF-Rechteckdaten in einem Bitstrom in Common::Rect-Objekte +// ----------------------------------------------------------------------------- + +Common::Rect flashRectToBSRect(VectorImage::SWFBitStream &bs) { + bs.flushByte(); + + // Feststellen mit wie vielen Bits die einzelnen Komponenten kodiert sind + uint32 bitsPerValue = bs.getBits(5); + + // Die einzelnen Komponenten einlesen + 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); +} + +// ----------------------------------------------------------------------------- +// Berechnet die Bounding-Box eines 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); +} + +} + + +// ----------------------------------------------------------------------------- +// Konstruktion +// ----------------------------------------------------------------------------- + +VectorImage::VectorImage(const byte *pFileData, uint fileSize, bool &success, const Common::String &fname) : _pixelData(0), _fname(fname) { + success = false; + + // Bitstream-Objekt erzeugen + // Im Folgenden werden die Dateidaten aus diesem ausgelesen. + SWFBitStream bs(pFileData, fileSize); + + // SWF-Signatur überprüfen + 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') { + BS_LOG_ERRORLN("File is not a valid SWF-file"); + return; + } + + // Versionsangabe überprüfen + uint32 version = bs.getByte(); + if (version > MAX_ACCEPTED_FLASH_VERSION) { + BS_LOG_ERRORLN("File is of version %d. Highest accepted version is %d.", version, MAX_ACCEPTED_FLASH_VERSION); + return; + } + + // Dateigröße auslesen und mit der tatsächlichen Größe vergleichen + uint32 storedFileSize = bs.getUInt32(); + if (storedFileSize != fileSize) { + BS_LOG_ERRORLN("File is not a valid SWF-file"); + return; + } + + // SWF-Maße auslesen + Common::Rect movieRect = flashRectToBSRect(bs); + + // Framerate und Frameanzahl auslesen + /* uint32 frameRate = */ + bs.getUInt16(); + /* uint32 frameCount = */ + bs.getUInt16(); + + // Tags parsen + // Da wir uns nur für das erste DefineShape-Tag interessieren + bool keepParsing = true; + while (keepParsing) { + // Tags beginnen immer an Bytegrenzen + bs.flushByte(); + + // Tagtyp und Länge auslesen + 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: + // Unbekannte Tags ignorieren + bs.skipBytes(tagLength); + } + } + + // Die Ausführung darf nicht an dieser Stelle ankommen: Entweder es wird ein Shape gefunden, dann wird die Funktion mit vorher verlassen, oder + // es wird keines gefunden, dann tritt eine Exception auf sobald über das Ende der Datei hinaus gelesen wird. + BS_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); + + 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(); + + // Bounding Box auslesen + _boundingBox = flashRectToBSRect(bs); + + // Erstes Image-Element erzeugen + _elements.resize(1); + + // Styles einlesen + uint numFillBits; + uint numLineBits; + if (!parseStyles(shapeType, bs, numFillBits, numLineBits)) + return false; + + uint lineStyle = 0; + uint fillStyle0 = 0; + uint fillStyle1 = 0; + + // Shaperecord parsen + // ------------------ + + 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) { + // Feststellen welche Parameter gesetzt werden + 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 der Shape-Definition erreicht? + if (!stateNewStyles && !stateLineStyle && !stateFillStyle0 && !stateFillStyle1 && !stateMoveTo) { + endOfShapeDiscovered = true; + // Parameter dekodieren + } 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; + } + + // Ein neuen Pfad erzeugen, es sei denn, es wurden nur neue Styles definiert + 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) { + // An dieser Stelle werden in Flash die alten Style-Definitionen verworfen und mit den neuen überschrieben. + // Es wird ein neues Element begonnen. + _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); + + // Bounding-Boxes der einzelnen Elemente berechnen + 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(); + + // Fillstyles parsen + // ----------------- + + // Anzahl an Fillstyles bestimmen + uint fillStyleCount = bs.getByte(); + if (fillStyleCount == 0xff) + fillStyleCount = bs.getUInt16(); + + // Alle Fillstyles einlesen, falls ein Fillstyle mit Typ != 0 gefunden wird, wird das Parsen abgebrochen. + // Es wird nur "solid fill" (Typ 0) unterstützt. + _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); + } + + // Linestyles parsen + // ----------------- + + // Anzahl an Linestyles bestimmen + uint lineStyleCount = bs.getByte(); + if (lineStyleCount == 0xff) + lineStyleCount = bs.getUInt16(); + + // Alle Linestyles einlesen + _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)); + } + + // Bitbreite für die folgenden Styleindizes auslesen + numFillBits = bs.getBits(4); + numLineBits = bs.getBits(4); + + return true; +} + + +// ----------------------------------------------------------------------------- + +bool VectorImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} + + +// ----------------------------------------------------------------------------- + +uint VectorImage::getPixel(int x, int y) { + BS_LOG_ERRORLN("GetPixel() is not supported. Returning black."); + return 0; +} + +// ----------------------------------------------------------------------------- + +bool VectorImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + BS_LOG_ERRORLN("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; + + // Falls Breite oder Höhe 0 sind, muss nichts dargestellt werden. + if (width == 0 || height == 0) + return true; + + // Feststellen, ob das alte Bild im Cache nicht wiederbenutzt werden kann und neu Berechnet werden muss + 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 |