/* 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(x0), static_cast(y0), static_cast(x1) + 1, static_cast(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 Common::Rect movieRect = 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::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 >(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 >(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