diff options
author | Eugene Sandulenko | 2010-09-18 10:03:54 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2010-10-12 23:58:51 +0000 |
commit | 1e07d9561fc43e1b1c0763d54c3eea4154fff070 (patch) | |
tree | c101b3f437b2afca186627455a9278670f241906 | |
parent | b2003364ff109e1f0170ea87fe379496631f6040 (diff) | |
download | scummvm-rg350-1e07d9561fc43e1b1c0763d54c3eea4154fff070.tar.gz scummvm-rg350-1e07d9561fc43e1b1c0763d54c3eea4154fff070.tar.bz2 scummvm-rg350-1e07d9561fc43e1b1c0763d54c3eea4154fff070.zip |
SWORD25: Add standalone SWF renderer
svn-id: r53372
22 files changed, 7354 insertions, 0 deletions
diff --git a/engines/sword25/tools/swfdisplay/Makefile b/engines/sword25/tools/swfdisplay/Makefile new file mode 100644 index 0000000000..7e2005134e --- /dev/null +++ b/engines/sword25/tools/swfdisplay/Makefile @@ -0,0 +1,33 @@ +CC = g++ +DEBFLAGS = -g -Wall -O0 +LDFLAGS = -lgdk_pixbuf-2.0 -lpng +CXXFLAGS = -I/usr/include/gtk-2.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include/ -I/usr/include/gdk-pixbuf-2.0/ +BCFLAGS = -I/usr/include/libart-2.0 -I/usr/include/gtk-2.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include/ -I/usr/include/gdk-pixbuf-2.0/ +BIN_TARGET = swfrender +OBJ_TARGETS = \ + swfrender.o \ + art.o \ + art_svp_intersect.o \ + art_svp_render_aa.o \ + art_svp_vpath.o \ + art_svp_vpath_stroke.o \ + art_vpath_bpath.o \ + vectorimage.o \ + vectorimagerenderer.o + +all: ${BIN_TARGET} + +${BIN_TARGET}: ${OBJ_TARGETS} + ${CC} ${DEBFLAGS} $^ -o $@ ${LDFLAGS} + +%.o: %.cpp Makefile + ${CC} ${CXXFLAGS} ${DEBFLAGS} -c $< -o $@ + +bezierrender: bezierrender.o + ${CC} ${DEBFLAGS} $^ -o $@ ${LDFLAGS} -lart_lgpl_2 + +bezierrender.o: bezierrender.cpp + ${CC} ${BCFLAGS} ${DEBFLAGS} -c $< -o $@ + +clean: ${BIN_TARGET} Makefile + rm -f *~ *.o ${BIN_TARGET} 2>/dev/null diff --git a/engines/sword25/tools/swfdisplay/algorithm.h b/engines/sword25/tools/swfdisplay/algorithm.h new file mode 100644 index 0000000000..8d6743a911 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/algorithm.h @@ -0,0 +1,255 @@ +/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/common/algorithm.h $ + * $Id: algorithm.h 51524 2010-07-30 23:42:50Z lordhoto $ + */ + +#ifndef COMMON_ALGORITHM_H +#define COMMON_ALGORITHM_H + +#include "func.h" + +namespace Common { + +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + */ +template<class In, class Out> +Out copy(In first, In last, Out dst) { + while (first != last) + *dst++ = *first++; + return dst; +} + +/** + * Copies data from the range [first, last) to [dst - (last - first), dst). + * It requires the range [dst - (last - first), dst) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy copy_backward copies the data from the end to the beginning. + */ +template<class In, class Out> +Out copy_backward(In first, In last, Out dst) { + while (first != last) + *--dst = *--last; + return dst; +} + +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy or copy_backward it does not copy all data. It only copies + * a data element when operator() of the op parameter returns true for the + * passed data element. + */ +template<class In, class Out, class Op> +Out copy_if(In first, In last, Out dst, Op op) { + while (first != last) { + if (op(*first)) + *dst++ = *first; + ++first; + } + return dst; +} + +// Our 'specialized' 'set_to' template for char, signed char and unsigned char arrays. +// Since C++ doesn't support partial specialized template functions (currently) we +// are going this way... +// With this we assure the usage of memset for those, which should be +// faster than a simple loop like for the generic 'set_to'. +template<class Value> +signed char *set_to(signed char *first, signed char *last, Value val) { + memset(first, (val & 0xFF), last - first); + return last; +} + +template<class Value> +unsigned char *set_to(unsigned char *first, unsigned char *last, Value val) { + memset(first, (val & 0xFF), last - first); + return last; +} + +template<class Value> +char *set_to(char *first, char *last, Value val) { + memset(first, (val & 0xFF), last - first); + return last; +} + +/** + * Sets all elements in the range [first, last) to val. + */ +template<class In, class Value> +In set_to(In first, In last, Value val) { + while (first != last) + *first++ = val; + return first; +} + +/** + * Finds the first data value in the range [first, last) matching v. + * For data comperance it uses operator == of the data elements. + */ +template<class In, class T> +In find(In first, In last, const T &v) { + while (first != last) { + if (*first == v) + return first; + ++first; + } + return last; +} + +/** + * Finds the first data value in the range [first, last) for which + * the specified predicate p returns true. + */ +template<class In, class Pred> +In find_if(In first, In last, Pred p) { + while (first != last) { + if (p(*first)) + return first; + ++first; + } + return last; +} + +/** + * Applies the function f on all elements of the range [first, last). + * The processing order is from beginning to end. + */ +template<class In, class Op> +Op for_each(In first, In last, Op f) { + while (first != last) f(*first++); + return f; +} + +template<typename T> +unsigned int distance(T *first, T *last) { + return last - first; +} + +template<typename T> +unsigned int distance(T first, T last) { + unsigned int n = 0; + while (first != last) { + ++n; + ++first; + } + return n; +} + +template<typename T> +T *sortChoosePivot(T *first, T *last) { + return first + distance(first, last) / 2; +} + +template<typename T> +T sortChoosePivot(T first, T last) { + unsigned int n = distance(first, last); + n /= 2; + while (n--) + ++first; + return first; +} + +template<typename T, class StrictWeakOrdering> +T sortPartition(T first, T last, T pivot, StrictWeakOrdering &comp) { + --last; + SWAP(*pivot, *last); + + T sorted; + for (sorted = first; first != last; ++first) { + if (!comp(*last, *first)) { + if (first != sorted) + SWAP(*first, *sorted); + ++sorted; + } + } + + SWAP(*last, *sorted); + return sorted; +} + +/** + * Simple sort function, modeled after std::sort. + * It compares data with the given comparator object comp. + * + * Like std::sort this is not guaranteed to be stable. + * + * Two small quotes from wikipedia about stability: + * + * Stable sorting algorithms maintain the relative order of records with + * equal keys. + * + * Unstable sorting algorithms may change the relative order of records with + * equal keys, but stable sorting algorithms never do so. + * + * For more information on that topic check out: + * http://en.wikipedia.org/wiki/Sorting_algorithm#Stability + * + * NOTE: Actually as the time of writing our implementation is unstable. + */ +template<typename T, class StrictWeakOrdering> +void sort(T first, T last, StrictWeakOrdering comp) { + if (first == last) + return; + + T pivot = sortChoosePivot(first, last); + pivot = sortPartition(first, last, pivot, comp); + sort<T, StrictWeakOrdering>(first, pivot, comp); + sort<T, StrictWeakOrdering>(++pivot, last, comp); +} + +/** + * Simple sort function, modeled after std::sort. + */ +template<typename T> +void sort(T *first, T *last) { + sort(first, last, Common::Less<T>()); +} + +template<class T> +void sort(T first, T last) { + sort(first, last, Common::Less<typename T::ValueType>()); +} + +/** + * Euclid's algorithm to compute the greatest common divisor. + */ +template<class T> +T gcd(T a, T b) { + if (a <= 0) a = -a; + if (b <= 0) b = -b; + while (a > 0) { + T tmp = a; + a = b % a; + b = tmp; + } + return b; +} + +} // End of namespace Common +#endif + diff --git a/engines/sword25/tools/swfdisplay/array.h b/engines/sword25/tools/swfdisplay/array.h new file mode 100644 index 0000000000..f09db9c63c --- /dev/null +++ b/engines/sword25/tools/swfdisplay/array.h @@ -0,0 +1,318 @@ +/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/common/array.h $ + * $Id: array.h 45247 2009-10-19 17:46:50Z fingolfin $ + */ + +#ifndef COMMON_ARRAY_H +#define COMMON_ARRAY_H + +#include "algorithm.h" + +namespace Common { + +/** + * This class implements a dynamically sized container, which + * can be accessed similar to a regular C++ array. Accessing + * elements is performed in constant time (like with plain arrays). + * In addition, one can append, insert and remove entries (this + * is the 'dynamic' part). Doing that in general takes time + * proportional to the number of elements in the array. + * + * The container class closest to this in the C++ standard library is + * std::vector. However, there are some differences. The most important one is + * that std::vector has a far more sophisticated (and complicated) memory + * management scheme. There, only elements that 'live' are actually constructed + * (i.e., have their constructor called), and objects that are removed are + * immediately destructed (have their destructor called). + * With Common::Array, this is not the case; instead, it simply uses new[] and + * delete[] to allocate whole blocks of objects, possibly more than are + * currently 'alive'. This simplifies memory management, but may have + * undesirable side effects when one wants to use an Array of complex + * data types. + * + * @todo Improve the storage management of this class. + * In particular, don't use new[] and delete[], but rather + * construct/destruct objects manually. This way, we can + * ensure that storage which is not currently used does not + * correspond to a live active object. + * (This is only of interest for array of non-POD objects). + */ +template<class T> +class Array { +protected: + uint _capacity; + uint _size; + T *_storage; + +public: + typedef T *iterator; + typedef const T *const_iterator; + + typedef T value_type; + +public: + Array() : _capacity(0), _size(0), _storage(0) {} + + Array(const Array<T> &array) : _capacity(array._size), _size(array._size), _storage(0) { + if (array._storage) { + _storage = new T[_capacity]; + assert(_storage); + copy(array._storage, array._storage + _size, _storage); + } + } + + /** + * Construct an array by copying data from a regular array. + */ + template<class T2> + Array(const T2 *data, int n) { + _capacity = _size = n; + _storage = new T[_capacity]; + assert(_storage); + copy(data, data + _size, _storage); + } + + ~Array() { + delete[] _storage; + _storage = 0; + _capacity = _size = 0; + } + + /** Appends element to the end of the array. */ + void push_back(const T &element) { + if (_size + 1 <= _capacity) + _storage[_size++] = element; + else + insert_aux(end(), &element, &element + 1); + } + + void push_back(const Array<T> &array) { + if (_size + array.size() <= _capacity) { + copy(array.begin(), array.end(), end()); + _size += array.size(); + } else + insert_aux(end(), array.begin(), array.end()); + } + + /** Removes the last element of the array. */ + void pop_back() { + assert(_size > 0); + _size--; + } + + /** Returns a reference to the first element of the array. */ + T &front() { + assert(_size > 0); + return _storage[0]; + } + + /** Returns a reference to the first element of the array. */ + const T &front() const { + assert(_size > 0); + return _storage[0]; + } + + /** Returns a reference to the last element of the array. */ + T &back() { + assert(_size > 0); + return _storage[_size-1]; + } + + /** Returns a reference to the last element of the array. */ + const T &back() const { + assert(_size > 0); + return _storage[_size-1]; + } + + + void insert_at(int idx, const T &element) { + assert(idx >= 0 && (uint)idx <= _size); + insert_aux(_storage + idx, &element, &element + 1); + } + + T remove_at(int idx) { + assert(idx >= 0 && (uint)idx < _size); + T tmp = _storage[idx]; + copy(_storage + idx + 1, _storage + _size, _storage + idx); + _size--; + return tmp; + } + + // TODO: insert, remove, ... + + T& operator[](int idx) { + assert(idx >= 0 && (uint)idx < _size); + return _storage[idx]; + } + + const T& operator[](int idx) const { + assert(idx >= 0 && (uint)idx < _size); + return _storage[idx]; + } + + Array<T>& operator=(const Array<T> &array) { + if (this == &array) + return *this; + + delete[] _storage; + _size = array._size; + _capacity = _size + 32; + _storage = new T[_capacity]; + assert(_storage); + copy(array._storage, array._storage + _size, _storage); + + return *this; + } + + uint size() const { + return _size; + } + + void clear() { + delete[] _storage; + _storage = 0; + _size = 0; + _capacity = 0; + } + + bool empty() const { + return (_size == 0); + } + + bool operator==(const Array<T> &other) const { + if (this == &other) + return true; + if (_size != other._size) + return false; + for (uint i = 0; i < _size; ++i) { + if (_storage[i] != other._storage[i]) + return false; + } + return true; + } + bool operator!=(const Array<T> &other) const { + return !(*this == other); + } + + + iterator begin() { + return _storage; + } + + iterator end() { + return _storage + _size; + } + + const_iterator begin() const { + return _storage; + } + + const_iterator end() const { + return _storage + _size; + } + + void reserve(uint newCapacity) { + if (newCapacity <= _capacity) + return; + + T *old_storage = _storage; + _capacity = newCapacity; + _storage = new T[newCapacity]; + assert(_storage); + + if (old_storage) { + // Copy old data + copy(old_storage, old_storage + _size, _storage); + delete[] old_storage; + } + } + + void resize(uint newSize) { + reserve(newSize); + for (uint i = _size; i < newSize; ++i) + _storage[i] = T(); + _size = newSize; + } + +protected: + static uint roundUpCapacity(uint capacity) { + // Round up capacity to the next power of 2; + // we use a minimal capacity of 8. + uint capa = 8; + while (capa < capacity) + capa <<= 1; + return capa; + } + + /** + * Insert a range of elements coming from this or another array. + * Unlike std::vector::insert, this method does not accept + * arbitrary iterators, mainly because our iterator system is + * seriously limited and does not distinguish between input iterators, + * output iterators, forward iterators or random access iterators. + * + * So, we simply restrict to Array iterators. Extending this to arbitrary + * random access iterators would be trivial. + * + * Moreover, this method does not handle all cases of inserting a subrange + * of an array into itself; this is why it is private for now. + */ + iterator insert_aux(iterator pos, const_iterator first, const_iterator last) { + assert(_storage <= pos && pos <= _storage + _size); + assert(first <= last); + const uint n = last - first; + if (n) { + const uint idx = pos - _storage; + T *newStorage = _storage; + if (_size + n > _capacity) { + // If there is not enough space, allocate more and + // copy old elements over. + uint newCapacity = roundUpCapacity(_size + n); + newStorage = new T[newCapacity]; + assert(newStorage); + copy(_storage, _storage + idx, newStorage); + pos = newStorage + idx; + } + + // Make room for the new elements by shifting back + // existing ones. + copy_backward(_storage + idx, _storage + _size, newStorage + _size + n); + + // Insert the new elements. + copy(first, last, pos); + + // Finally, update the internal state + if (newStorage != _storage) { + delete[] _storage; + _capacity = roundUpCapacity(_size + n); + _storage = newStorage; + } + _size += n; + } + return pos; + } + +}; + +} // End of namespace Common + +#endif diff --git a/engines/sword25/tools/swfdisplay/art-non-display1.cpp b/engines/sword25/tools/swfdisplay/art-non-display1.cpp new file mode 100644 index 0000000000..b3d4457e71 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art-non-display1.cpp @@ -0,0 +1,488 @@ +#include <libart_lgpl/art_vpath_bpath.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_svp_vpath_stroke.h> +#include <libart_lgpl/art_svp_render_aa.h> +#include <libart_lgpl/art_rgb_svp.h> +#include <libart_lgpl/art_rgb.h> + +#define SAVING 1 + +#define WIDTH 3000 +#define HEIGHT 3000 +#define BYTES_PER_PIXEL 3 /* 24 packed rgb bits */ +#define ROWSTRIDE (WIDTH*BYTES_PER_PIXEL) + + +static unsigned char *render_path(void); + +#if SAVING +static void save_buffer(unsigned char *buffer, const char *filename); + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <png.h> +#include <stdio.h> + +static gboolean +pixbuf_save_to_file(const GdkPixbuf *pixbuf, const char *file_name) { + FILE *handle; + unsigned char *buffer; + gboolean has_alpha; + int width, height, depth, rowstride; + guchar *pixels; + png_structp png_ptr; + png_infop info_ptr; + png_text text[2]; + int i; + + g_return_val_if_fail(pixbuf != NULL, FALSE); + g_return_val_if_fail(file_name != NULL, FALSE); + g_return_val_if_fail(file_name[0] != '\0', FALSE); + + handle = fopen(file_name, "wb"); + if (handle == NULL) { + return FALSE; + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + fclose(handle); + return FALSE; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + fclose(handle); + return FALSE; + } + + if (setjmp(png_ptr->jmpbuf)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(handle); + return FALSE; + } + + png_init_io(png_ptr, handle); + + has_alpha = gdk_pixbuf_get_has_alpha(pixbuf); + width = gdk_pixbuf_get_width(pixbuf); + height = gdk_pixbuf_get_height(pixbuf); + depth = gdk_pixbuf_get_bits_per_sample(pixbuf); + pixels = gdk_pixbuf_get_pixels(pixbuf); + rowstride = gdk_pixbuf_get_rowstride(pixbuf); + + png_set_IHDR(png_ptr, info_ptr, width, height, + depth, PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + /* Some text to go with the png image */ + text[0].key = "Title"; + text[0].text = (char *) file_name; + text[0].compression = PNG_TEXT_COMPRESSION_NONE; + text[1].key = "Software"; + text[1].text = "Nautilus Thumbnail"; + text[1].compression = PNG_TEXT_COMPRESSION_NONE; + png_set_text(png_ptr, info_ptr, text, 2); + + /* Write header data */ + png_write_info(png_ptr, info_ptr); + + /* if there is no alpha in the data, allocate buffer to expand into */ + if (has_alpha) { + buffer = NULL; + } else { + buffer = g_malloc(4 * width); + } + + /* pump the raster data into libpng, one scan line at a time */ + for (i = 0; i < height; i++) { + if (has_alpha) { + png_bytep row_pointer = pixels; + png_write_row(png_ptr, row_pointer); + } else { + /* expand RGB to RGBA using an opaque alpha value */ + int x; + unsigned char *buffer_ptr = buffer; + unsigned char *source_ptr = pixels; + for (x = 0; x < width; x++) { + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = 255; + } + png_write_row(png_ptr, (png_bytep) buffer); + } + pixels += rowstride; + } + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + g_free(buffer); + + fclose(handle); + return TRUE; +} + +static void +save_buffer(unsigned char *buffer, const char *filename) { + GdkPixbuf *pixbuf; + + g_type_init(); + + pixbuf = gdk_pixbuf_new_from_data(buffer, + GDK_COLORSPACE_RGB, +#if BYTES_PER_PIXEL == 4 + TRUE, +#else + FALSE, +#endif + 8, + WIDTH, HEIGHT, + ROWSTRIDE, + NULL, NULL); + + pixbuf_save_to_file(pixbuf, filename); + + gdk_pixbuf_unref(pixbuf); +} + +#endif + +void +art_rgb_fill_run1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, gint n) { + int i; + + if (r == g && g == b && r == 255) { + memset(buf, g, n + n + n + n); + } else { + art_u32 *alt = (art_u32 *)buf; + //art_u32 color = (r << 24) | (g << 16) | (b << 8) | 0xff; + art_u32 color = (r << 0) | (g << 8) | (b << 16) | (0xff << 24); + for (i = 0; i < n; i++) + *alt++ = color; + } +} + +void +art_rgb_run_alpha1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) { + int i; + int v; + + for (i = 0; i < n; i++) { + v = *buf; + *buf++ = v + (((r - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((g - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((b - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((alpha - v) * alpha + 0x80) >> 8); + } +} + +typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData; + +struct _ArtRgbSVPAlphaData { + int alphatab[256]; + art_u8 r, g, b, alpha; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void +art_rgb_svp_alpha_callback1(void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } else { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + + data->buf += data->rowstride; +} + +static void +art_rgb_svp_alpha_opaque_callback1(void *callback_data, int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, run_x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x0 - x0) * 4, r, g, b, run_x1 - run_x0); + else + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x1 - x0) * 4, r, g, b, x1 - run_x1); + else + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } + } else { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + } + + data->buf += data->rowstride; +} + +void +art_rgb_svp_alpha1(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 rgba, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma) { + ArtRgbSVPAlphaData data; + int r, g, b, alpha; + int i; + int a, da; + + alpha = rgba >> 24; + r = (rgba >> 16) & 0xff; + g = (rgba >> 8) & 0xff; + b = rgba & 0xff; + + data.r = r; + data.g = g; + data.b = b; + data.alpha = alpha; + + a = 0x8000; + da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */ + + for (i = 0; i < 256; i++) { + data.alphatab[i] = a >> 16; + a += da; + } + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + if (alpha == 255) + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback1, &data); + else + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback1, &data); +} + +static int art_vpath_len (ArtVpath * a) { + int i; + + for (i = 0; a[i].code != ART_END; i++); + return i; +} + +ArtVpath *art_vpath_reverse(ArtVpath * a) { + ArtVpath *dest; + ArtVpath it; + int len; + int state = 0; + int i; + + len = art_vpath_len(a); + dest = g_malloc((len + 1) * sizeof(ArtVpath)); + + for (i = 0; i < len; i++) { + it = a[len - i - 1]; + if (state) { + it.code = ART_LINETO; + } else { + it.code = ART_MOVETO_OPEN; + state = 1; + } + if (a[len - i - 1].code == ART_MOVETO || + a[len - i - 1].code == ART_MOVETO_OPEN) { + state = 0; + } + dest[i] = it; + } + dest[len] = a[len]; + + return dest; +} + +ArtVpath *art_vpath_reverse_free(ArtVpath *a) { + ArtVpath *dest; + + dest = art_vpath_reverse(a); + art_free(a); + + return dest; +} + +void art_svp_make_convex (ArtSVP *svp) { + int i; + + if (svp->n_segs > 0 && svp->segs[0].dir == 0) { + for (i = 0; i < svp->n_segs; i++) { + svp->segs[i].dir = !svp->segs[i].dir; + } + } +} + + +void drawBez(ArtBpath *bez, art_u8 *buffer, double scaleX, double scaleY, double penWidth, unsigned int color) { + ArtVpath *vec = NULL; + ArtSVP *svp = NULL; + + vec = art_bez_path_to_vec(bez, 0.5); + + if (scaleX != 1.0 || scaleY != 1.0) { + ArtVpath *vec1; + double matrix[6]; + + matrix[0] = scaleX; + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = scaleY; + matrix[4] = matrix[5] = 0; + + vec1 = art_vpath_affine_transform(vec, matrix); + art_free(vec); + + vec = vec1; + } + + if (penWidth != -1) { + svp = art_svp_vpath_stroke(vec, ART_PATH_STROKE_JOIN_ROUND, ART_PATH_STROKE_CAP_ROUND, penWidth, 1.0, 0.5); + } else { + svp = art_svp_from_vpath(vec); + } + +#if BYTES_PER_PIXEL == 4 + art_rgb_svp_alpha1(svp, 0, 0, WIDTH, HEIGHT, color, buffer, ROWSTRIDE, NULL); +#else + color <<= 8; + color |= 0xff; + art_rgb_svp_alpha(svp, 0, 0, WIDTH, HEIGHT, color, buffer, ROWSTRIDE, NULL); +#endif + art_free(svp); + + art_free(vec); +} + +static unsigned char *render_path() { + art_u8 *buffer = NULL; + + ArtBpath *bez = NULL; + + buffer = art_new(art_u8, WIDTH * HEIGHT * BYTES_PER_PIXEL); +#if BYTES_PER_PIXEL == 4 + memset(buffer, 0, WIDTH*HEIGHT*BYTES_PER_PIXEL); +#else + memset(buffer, 0xff, WIDTH*HEIGHT*BYTES_PER_PIXEL); +#endif + bez = art_new(ArtBpath, 100); + +#include "image.c" + + art_free(bez); + + return (unsigned char *) buffer; +} + +int main(int argc, char *argv[]) { + unsigned char *buffer; + + buffer = render_path(); + +#if SAVING + save_buffer(buffer, "foo.png"); +#endif + + return 0; +} diff --git a/engines/sword25/tools/swfdisplay/art.cpp b/engines/sword25/tools/swfdisplay/art.cpp new file mode 100644 index 0000000000..e88d6e55c4 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art.cpp @@ -0,0 +1,134 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Various utility functions RLL finds useful. */ + +#include "art.h" + +#ifdef HAVE_UINSTD_H +#include <unistd.h> +#endif +#include <stdio.h> +#include <stdarg.h> + +/** + * art_die: Print the error message to stderr and exit with a return code of 1. + * @fmt: The printf-style format for the error message. + * + * Used for dealing with severe errors. + **/ +void +art_die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(1); +} + +/** + * art_warn: Print the warning message to stderr. + * @fmt: The printf-style format for the warning message. + * + * Used for generating warnings. + **/ +void +art_warn(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +/** + * art_svp_free: Free an #ArtSVP structure. + * @svp: #ArtSVP to free. + * + * Frees an #ArtSVP structure and all the segments in it. + **/ +void +art_svp_free(ArtSVP *svp) { + int n_segs = svp->n_segs; + int i; + + for (i = 0; i < n_segs; i++) + free(svp->segs[i].points); + free(svp); +} + +#ifdef ART_USE_NEW_INTERSECTOR +#define EPSILON 0 +#else +#define EPSILON 1e-6 +#endif + +/** + * art_svp_seg_compare: Compare two segments of an svp. + * @seg1: First segment to compare. + * @seg2: Second segment to compare. + * + * Compares two segments of an svp. Return 1 if @seg2 is below or to the + * right of @seg1, -1 otherwise. + **/ +int +art_svp_seg_compare(const void *s1, const void *s2) { + const ArtSVPSeg *seg1 = (const ArtSVPSeg *)s1; + const ArtSVPSeg *seg2 = (const ArtSVPSeg *)s2; + + if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1; + else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1; + else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1; + else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1; + else if ((seg1->points[1].x - seg1->points[0].x) * + (seg2->points[1].y - seg2->points[0].y) - + (seg1->points[1].y - seg1->points[0].y) * + (seg2->points[1].x - seg2->points[0].x) > 0) return 1; + else return -1; +} + +/** + * art_vpath_add_point: Add point to vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @code: The pathcode for the new point. + * @x: The X coordinate of the new point. + * @y: The Y coordinate of the new point. + * + * Adds a new point to *@p_vpath, reallocating and updating *@p_vpath + * and *@pn_points_max as necessary. *@pn_points is incremented. + * + * This routine always adds the point after all points already in the + * vpath. Thus, it should be called in the order the points are + * desired. + **/ +void +art_vpath_add_point(ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y) { + int i; + + i = (*pn_points)++; + if (i == *pn_points_max) + art_expand(*p_vpath, ArtVpath, *pn_points_max); + (*p_vpath)[i].code = code; + (*p_vpath)[i].x = x; + (*p_vpath)[i].y = y; +} diff --git a/engines/sword25/tools/swfdisplay/art.h b/engines/sword25/tools/swfdisplay/art.h new file mode 100644 index 0000000000..aa66036287 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art.h @@ -0,0 +1,182 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Simple macros to set up storage allocation and basic types for libart + functions. */ + +#ifndef __ART_MISC_H__ +#define __ART_MISC_H__ + +#define DEBUG 1 + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <math.h> + +typedef unsigned char byte; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned int uint; +typedef short int16; +typedef int int32; + +typedef byte art_u8; +typedef uint16 art_u16; +typedef uint32 art_u32; + +#define FLIP_NONE 0 + +#define BS_ASSERT(x) assert(x) +#define error(x) assert(0) +#define BS_LOG_ERRORLN(x) assert(0) + +#define BS_ARGB(A,R,G,B) (((A) << 24) | ((R) << 16) | ((G) << 8) | (B)) + +/* These aren't, strictly speaking, configuration macros, but they're + damn handy to have around, and may be worth playing with for + debugging. */ +#define art_new(type, n) ((type *)malloc ((n) * sizeof(type))) + +#define art_renew(p, type, n) ((type *)realloc (p, (n) * sizeof(type))) + +/* This one must be used carefully - in particular, p and max should + be variables. They can also be pstruct->el lvalues. */ +#define art_expand(p, type, max) do { if(max) { p = art_renew (p, type, max <<= 1); } else { max = 1; p = art_new(type, 1); } } while (0) + +typedef int art_boolean; +#define ART_FALSE 0 +#define ART_TRUE 1 + +/* define pi */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif /* M_PI */ + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif /* M_SQRT2 */ + +/* Provide macros to feature the GCC function attribute. + */ +#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)) +#define ART_GNUC_PRINTF( format_idx, arg_idx ) \ + __attribute__((__format__ (__printf__, format_idx, arg_idx))) +#define ART_GNUC_NORETURN \ + __attribute__((__noreturn__)) +#else /* !__GNUC__ */ +#define ART_GNUC_PRINTF( format_idx, arg_idx ) +#define ART_GNUC_NORETURN +#endif /* !__GNUC__ */ + +void ART_GNUC_NORETURN +art_die(const char *fmt, ...) ART_GNUC_PRINTF(1, 2); + +void +art_warn(const char *fmt, ...) ART_GNUC_PRINTF(1, 2); + +#define ART_USE_NEW_INTERSECTOR + +typedef struct _ArtDRect ArtDRect; +typedef struct _ArtIRect ArtIRect; + +struct _ArtDRect { + /*< public >*/ + double x0, y0, x1, y1; +}; + +struct _ArtIRect { + /*< public >*/ + int x0, y0, x1, y1; +}; + +typedef struct _ArtPoint ArtPoint; + +struct _ArtPoint { + /*< public >*/ + double x, y; +}; + +/* Basic data structures and constructors for sorted vector paths */ + +typedef struct _ArtSVP ArtSVP; +typedef struct _ArtSVPSeg ArtSVPSeg; + +struct _ArtSVPSeg { + int n_points; + int dir; /* == 0 for "up", 1 for "down" */ + ArtDRect bbox; + ArtPoint *points; +}; + +struct _ArtSVP { + int n_segs; + ArtSVPSeg segs[1]; +}; + +void +art_svp_free(ArtSVP *svp); + +int +art_svp_seg_compare(const void *s1, const void *s2); + +/* Basic data structures and constructors for bezier paths */ + +typedef enum { + ART_MOVETO, + ART_MOVETO_OPEN, + ART_CURVETO, + ART_LINETO, + ART_END +} ArtPathcode; + +typedef struct _ArtBpath ArtBpath; + +struct _ArtBpath { + /*< public >*/ + ArtPathcode code; + double x1; + double y1; + double x2; + double y2; + double x3; + double y3; +}; + +/* Basic data structures and constructors for simple vector paths */ + +typedef struct _ArtVpath ArtVpath; + +/* CURVETO is not allowed! */ +struct _ArtVpath { + ArtPathcode code; + double x; + double y; +}; + +/* Some of the functions need to go into their own modules */ + +void +art_vpath_add_point(ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y); + +ArtVpath *art_bez_path_to_vec(const ArtBpath *bez, double flatness); + +#endif /* __ART_MISC_H__ */ diff --git a/engines/sword25/tools/swfdisplay/art_svp_intersect.cpp b/engines/sword25/tools/swfdisplay/art_svp_intersect.cpp new file mode 100644 index 0000000000..56164996b6 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_intersect.cpp @@ -0,0 +1,1329 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2001 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* This file contains a testbed implementation of the new intersection + code. +*/ + +#include "art.h" +#include "art_svp_intersect.h" + +#include <math.h> /* for sqrt */ + +/* This can be used in production, to prevent hangs. Eventually, it + should not be necessary. */ +#define CHEAP_SANITYCHECK + +/* A priority queue - perhaps move to a separate file if it becomes + needed somewhere else */ + +#define ART_PRIQ_USE_HEAP + +typedef struct _ArtPriQ ArtPriQ; +typedef struct _ArtPriPoint ArtPriPoint; + +struct _ArtPriQ { + int n_items; + int n_items_max; + ArtPriPoint **items; +}; + +struct _ArtPriPoint { + double x; + double y; + void *user_data; +}; + +static ArtPriQ * +art_pri_new(void) { + ArtPriQ *result = art_new(ArtPriQ, 1); + + result->n_items = 0; + result->n_items_max = 16; + result->items = art_new(ArtPriPoint *, result->n_items_max); + return result; +} + +static void +art_pri_free(ArtPriQ *pq) { + free(pq->items); + free(pq); +} + +static art_boolean +art_pri_empty(ArtPriQ *pq) { + return pq->n_items == 0; +} + +#ifdef ART_PRIQ_USE_HEAP + +/* This heap implementation is based on Vasek Chvatal's course notes: + http://www.cs.rutgers.edu/~chvatal/notes/pq.html#heap */ + +static void +art_pri_bubble_up(ArtPriQ *pq, int vacant, ArtPriPoint *missing) { + ArtPriPoint **items = pq->items; + int parent; + + parent = (vacant - 1) >> 1; + while (vacant > 0 && (missing->y < items[parent]->y || + (missing->y == items[parent]->y && + missing->x < items[parent]->x))) { + items[vacant] = items[parent]; + vacant = parent; + parent = (vacant - 1) >> 1; + } + + items[vacant] = missing; +} + +static void +art_pri_insert(ArtPriQ *pq, ArtPriPoint *point) { + if (pq->n_items == pq->n_items_max) + art_expand(pq->items, ArtPriPoint *, pq->n_items_max); + + art_pri_bubble_up(pq, pq->n_items++, point); +} + +static void +art_pri_sift_down_from_root(ArtPriQ *pq, ArtPriPoint *missing) { + ArtPriPoint **items = pq->items; + int vacant = 0, child = 2; + int n = pq->n_items; + + while (child < n) { + if (items[child - 1]->y < items[child]->y || + (items[child - 1]->y == items[child]->y && + items[child - 1]->x < items[child]->x)) + child--; + items[vacant] = items[child]; + vacant = child; + child = (vacant + 1) << 1; + } + if (child == n) { + items[vacant] = items[n - 1]; + vacant = n - 1; + } + + art_pri_bubble_up(pq, vacant, missing); +} + +static ArtPriPoint * +art_pri_choose(ArtPriQ *pq) { + ArtPriPoint *result = pq->items[0]; + + art_pri_sift_down_from_root(pq, pq->items[--pq->n_items]); + return result; +} + +#else + +/* Choose least point in queue */ +static ArtPriPoint * +art_pri_choose(ArtPriQ *pq) { + int i; + int best = 0; + double best_x, best_y; + double y; + ArtPriPoint *result; + + if (pq->n_items == 0) + return NULL; + + best_x = pq->items[best]->x; + best_y = pq->items[best]->y; + + for (i = 1; i < pq->n_items; i++) { + y = pq->items[i]->y; + if (y < best_y || (y == best_y && pq->items[i]->x < best_x)) { + best = i; + best_x = pq->items[best]->x; + best_y = y; + } + } + result = pq->items[best]; + pq->items[best] = pq->items[--pq->n_items]; + return result; +} + +static void +art_pri_insert(ArtPriQ *pq, ArtPriPoint *point) { + if (pq->n_items == pq->n_items_max) + art_expand(pq->items, ArtPriPoint *, pq->n_items_max); + + pq->items[pq->n_items++] = point; +} + +#endif + +/* A virtual class for an "svp writer". A client of this object creates an + SVP by repeatedly calling "add segment" and "add point" methods on it. +*/ + +typedef struct _ArtSvpWriterRewind ArtSvpWriterRewind; + +/* An implementation of the svp writer virtual class that applies the + winding rule. */ + +struct _ArtSvpWriterRewind { + ArtSvpWriter super; + ArtWindRule rule; + ArtSVP *svp; + int n_segs_max; + int *n_points_max; +}; + +static int +art_svp_writer_rewind_add_segment(ArtSvpWriter *self, int wind_left, + int delta_wind, double x, double y) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *svp; + ArtSVPSeg *seg; + art_boolean left_filled, right_filled; + int wind_right = wind_left + delta_wind; + int seg_num; + const int init_n_points_max = 4; + + switch (swr->rule) { + case ART_WIND_RULE_NONZERO: + left_filled = (wind_left != 0); + right_filled = (wind_right != 0); + break; + case ART_WIND_RULE_INTERSECT: + left_filled = (wind_left > 1); + right_filled = (wind_right > 1); + break; + case ART_WIND_RULE_ODDEVEN: + left_filled = (wind_left & 1); + right_filled = (wind_right & 1); + break; + case ART_WIND_RULE_POSITIVE: + left_filled = (wind_left > 0); + right_filled = (wind_right > 0); + break; + default: + art_die("Unknown wind rule %d\n", swr->rule); + } + if (left_filled == right_filled) { + /* discard segment now */ + return -1; + } + + svp = swr->svp; + seg_num = svp->n_segs++; + if (swr->n_segs_max == seg_num) { + swr->n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (swr->n_segs_max - 1) * + sizeof(ArtSVPSeg)); + swr->svp = svp; + swr->n_points_max = art_renew(swr->n_points_max, int, + swr->n_segs_max); + } + seg = &svp->segs[seg_num]; + seg->n_points = 1; + seg->dir = right_filled; + swr->n_points_max[seg_num] = init_n_points_max; + seg->bbox.x0 = x; + seg->bbox.y0 = y; + seg->bbox.x1 = x; + seg->bbox.y1 = y; + seg->points = art_new(ArtPoint, init_n_points_max); + seg->points[0].x = x; + seg->points[0].y = y; + return seg_num; +} + +static void +art_svp_writer_rewind_add_point(ArtSvpWriter *self, int seg_id, + double x, double y) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVPSeg *seg; + int n_points; + + if (seg_id < 0) + /* omitted segment */ + return; + + seg = &swr->svp->segs[seg_id]; + n_points = seg->n_points++; + if (swr->n_points_max[seg_id] == n_points) + art_expand(seg->points, ArtPoint, swr->n_points_max[seg_id]); + seg->points[n_points].x = x; + seg->points[n_points].y = y; + if (x < seg->bbox.x0) + seg->bbox.x0 = x; + if (x > seg->bbox.x1) + seg->bbox.x1 = x; + seg->bbox.y1 = y; +} + +static void +art_svp_writer_rewind_close_segment(ArtSvpWriter *self, int seg_id) { + /* Not needed for this simple implementation. A potential future + optimization is to merge segments that can be merged safely. */ +} + +ArtSVP * +art_svp_writer_rewind_reap(ArtSvpWriter *self) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *result = swr->svp; + + free(swr->n_points_max); + free(swr); + return result; +} + +ArtSvpWriter * +art_svp_writer_rewind_new(ArtWindRule rule) { + ArtSvpWriterRewind *result = art_new(ArtSvpWriterRewind, 1); + + result->super.add_segment = art_svp_writer_rewind_add_segment; + result->super.add_point = art_svp_writer_rewind_add_point; + result->super.close_segment = art_svp_writer_rewind_close_segment; + + result->rule = rule; + result->n_segs_max = 16; + result->svp = (ArtSVP *)malloc(sizeof(ArtSVP) + + (result->n_segs_max - 1) * sizeof(ArtSVPSeg)); + result->svp->n_segs = 0; + result->n_points_max = art_new(int, result->n_segs_max); + + return &result->super; +} + +/* Now, data structures for the active list */ + +typedef struct _ArtActiveSeg ArtActiveSeg; + +/* Note: BNEG is 1 for \ lines, and 0 for /. Thus, + x[(flags & BNEG) ^ 1] <= x[flags & BNEG] */ +#define ART_ACTIVE_FLAGS_BNEG 1 + +/* This flag is set if the segment has been inserted into the active + list. */ +#define ART_ACTIVE_FLAGS_IN_ACTIVE 2 + +/* This flag is set when the segment is to be deleted in the + horiz commit process. */ +#define ART_ACTIVE_FLAGS_DEL 4 + +/* This flag is set if the seg_id is a valid output segment. */ +#define ART_ACTIVE_FLAGS_OUT 8 + +/* This flag is set if the segment is in the horiz list. */ +#define ART_ACTIVE_FLAGS_IN_HORIZ 16 + +struct _ArtActiveSeg { + int flags; + int wind_left, delta_wind; + ArtActiveSeg *left, *right; /* doubly linked list structure */ + + const ArtSVPSeg *in_seg; + int in_curs; + + double x[2]; + double y0, y1; + double a, b, c; /* line equation; ax+by+c = 0 for the line, a^2 + b^2 = 1, + and a>0 */ + + /* bottom point and intersection point stack */ + int n_stack; + int n_stack_max; + ArtPoint *stack; + + /* horiz commit list */ + ArtActiveSeg *horiz_left, *horiz_right; + double horiz_x; + int horiz_delta_wind; + int seg_id; +}; + +typedef struct _ArtIntersectCtx ArtIntersectCtx; + +struct _ArtIntersectCtx { + const ArtSVP *in; + ArtSvpWriter *out; + + ArtPriQ *pq; + + ArtActiveSeg *active_head; + + double y; + ArtActiveSeg *horiz_first; + ArtActiveSeg *horiz_last; + + /* segment index of next input segment to be added to pri q */ + int in_curs; +}; + +#define EPSILON_A 1e-5 /* Threshold for breaking lines at point insertions */ + +/** + * art_svp_intersect_setup_seg: Set up an active segment from input segment. + * @seg: Active segment. + * @pri_pt: Priority queue point to initialize. + * + * Sets the x[], a, b, c, flags, and stack fields according to the + * line from the current cursor value. Sets the priority queue point + * to the bottom point of this line. Also advances the input segment + * cursor. + **/ +static void +art_svp_intersect_setup_seg(ArtActiveSeg *seg, ArtPriPoint *pri_pt) { + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs++; + double x0, y0, x1, y1; + double dx, dy, s; + double a, b, r2; + + x0 = in_seg->points[in_curs].x; + y0 = in_seg->points[in_curs].y; + x1 = in_seg->points[in_curs + 1].x; + y1 = in_seg->points[in_curs + 1].y; + pri_pt->x = x1; + pri_pt->y = y1; + dx = x1 - x0; + dy = y1 - y0; + r2 = dx * dx + dy * dy; + s = r2 == 0 ? 1 : 1 / sqrt(r2); + seg->a = a = dy * s; + seg->b = b = -dx * s; + seg->c = -(a * x0 + b * y0); + seg->flags = (seg->flags & ~ART_ACTIVE_FLAGS_BNEG) | (dx > 0); + seg->x[0] = x0; + seg->x[1] = x1; + seg->y0 = y0; + seg->y1 = y1; + seg->n_stack = 1; + seg->stack[0].x = x1; + seg->stack[0].y = y1; +} + +/** + * art_svp_intersect_add_horiz: Add point to horizontal list. + * @ctx: Intersector context. + * @seg: Segment with point to insert into horizontal list. + * + * Inserts @seg into horizontal list, keeping it in ascending horiz_x + * order. + * + * Note: the horiz_commit routine processes "clusters" of segs in the + * horiz list, all sharing the same horiz_x value. The cluster is + * processed in active list order, rather than horiz list order. Thus, + * the order of segs in the horiz list sharing the same horiz_x + * _should_ be irrelevant. Even so, we use b as a secondary sorting key, + * as a "belt and suspenders" defensive coding tactic. + **/ +static void +art_svp_intersect_add_horiz(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + ArtActiveSeg **pp = &ctx->horiz_last; + ArtActiveSeg *place; + ArtActiveSeg *place_right = NULL; + + +#ifdef CHEAP_SANITYCHECK + if (seg->flags & ART_ACTIVE_FLAGS_IN_HORIZ) { + art_warn("*** attempt to put segment in horiz list twice\n"); + return; + } + seg->flags |= ART_ACTIVE_FLAGS_IN_HORIZ; +#endif + + for (place = *pp; place != NULL && (place->horiz_x > seg->horiz_x || + (place->horiz_x == seg->horiz_x && + place->b < seg->b)); + place = *pp) { + place_right = place; + pp = &place->horiz_left; + } + *pp = seg; + seg->horiz_left = place; + seg->horiz_right = place_right; + if (place == NULL) + ctx->horiz_first = seg; + else + place->horiz_right = seg; +} + +static void +art_svp_intersect_push_pt(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x, double y) { + ArtPriPoint *pri_pt; + int n_stack = seg->n_stack; + + if (n_stack == seg->n_stack_max) + art_expand(seg->stack, ArtPoint, seg->n_stack_max); + seg->stack[n_stack].x = x; + seg->stack[n_stack].y = y; + seg->n_stack++; + + seg->x[1] = x; + seg->y1 = y; + + pri_pt = art_new(ArtPriPoint, 1); + pri_pt->x = x; + pri_pt->y = y; + pri_pt->user_data = seg; + art_pri_insert(ctx->pq, pri_pt); +} + +typedef enum { + ART_BREAK_LEFT = 1, + ART_BREAK_RIGHT = 2 +} ArtBreakFlags; + +/** + * art_svp_intersect_break: Break an active segment. + * + * Note: y must be greater than the top point's y, and less than + * the bottom's. + * + * Return value: x coordinate of break point. + */ +static double +art_svp_intersect_break(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x_ref, double y, ArtBreakFlags break_flags) { + double x0, y0, x1, y1; + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + double x; + + x0 = in_seg->points[in_curs - 1].x; + y0 = in_seg->points[in_curs - 1].y; + x1 = in_seg->points[in_curs].x; + y1 = in_seg->points[in_curs].y; + x = x0 + (x1 - x0) * ((y - y0) / (y1 - y0)); + if ((break_flags == ART_BREAK_LEFT && x > x_ref) || + (break_flags == ART_BREAK_RIGHT && x < x_ref)) { + } + + /* I think we can count on min(x0, x1) <= x <= max(x0, x1) with sane + arithmetic, but it might be worthwhile to check just in case. */ + + if (y > ctx->y) + art_svp_intersect_push_pt(ctx, seg, x, y); + else { + seg->x[0] = x; + seg->y0 = y; + seg->horiz_x = x; + art_svp_intersect_add_horiz(ctx, seg); + } + + return x; +} + +/** + * art_svp_intersect_add_point: Add a point, breaking nearby neighbors. + * @ctx: Intersector context. + * @x: X coordinate of point to add. + * @y: Y coordinate of point to add. + * @seg: "nearby" segment, or NULL if leftmost. + * + * Return value: Segment immediately to the left of the new point, or + * NULL if the new point is leftmost. + **/ +static ArtActiveSeg * +art_svp_intersect_add_point(ArtIntersectCtx *ctx, double x, double y, + ArtActiveSeg *seg, ArtBreakFlags break_flags) { + ArtActiveSeg *left, *right; + double x_min = x, x_max = x; + art_boolean left_live, right_live; + double d; + double new_x; + ArtActiveSeg *test, *result = NULL; + double x_test; + + left = seg; + if (left == NULL) + right = ctx->active_head; + else + right = left->right; + left_live = (break_flags & ART_BREAK_LEFT) && (left != NULL); + right_live = (break_flags & ART_BREAK_RIGHT) && (right != NULL); + while (left_live || right_live) { + if (left_live) { + if (x <= left->x[left->flags & ART_ACTIVE_FLAGS_BNEG] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != left->y0 && y < left->y1) { + d = x_min * left->a + y * left->b + left->c; + if (d < EPSILON_A) { + new_x = art_svp_intersect_break(ctx, left, x_min, y, + ART_BREAK_LEFT); + if (new_x > x_max) { + x_max = new_x; + right_live = (right != NULL); + } else if (new_x < x_min) + x_min = new_x; + left = left->left; + left_live = (left != NULL); + } else + left_live = ART_FALSE; + } else + left_live = ART_FALSE; + } else if (right_live) { + if (x >= right->x[(right->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != right->y0 && y < right->y1) { + d = x_max * right->a + y * right->b + right->c; + if (d > -EPSILON_A) { + new_x = art_svp_intersect_break(ctx, right, x_max, y, + ART_BREAK_RIGHT); + if (new_x < x_min) { + x_min = new_x; + left_live = (left != NULL); + } else if (new_x >= x_max) + x_max = new_x; + right = right->right; + right_live = (right != NULL); + } else + right_live = ART_FALSE; + } else + right_live = ART_FALSE; + } + } + + /* Ascending order is guaranteed by break_flags. Thus, we don't need + to actually fix up non-ascending pairs. */ + + /* Now, (left, right) defines an interval of segments broken. Sort + into ascending x order. */ + test = left == NULL ? ctx->active_head : left->right; + result = left; + if (test != NULL && test != right) { + if (y == test->y0) + x_test = test->x[0]; + else /* assert y == test->y1, I think */ + x_test = test->x[1]; + for (;;) { + if (x_test <= x) + result = test; + test = test->right; + if (test == right) + break; + new_x = x_test; + if (new_x < x_test) { + art_warn("art_svp_intersect_add_point: non-ascending x\n"); + } + x_test = new_x; + } + } + return result; +} + +static void +art_svp_intersect_swap_active(ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg) { + right_seg->left = left_seg->left; + if (right_seg->left != NULL) + right_seg->left->right = right_seg; + else + ctx->active_head = right_seg; + left_seg->right = right_seg->right; + if (left_seg->right != NULL) + left_seg->right->left = left_seg; + left_seg->left = right_seg; + right_seg->right = left_seg; +} + +/** + * art_svp_intersect_test_cross: Test crossing of a pair of active segments. + * @ctx: Intersector context. + * @left_seg: Left segment of the pair. + * @right_seg: Right segment of the pair. + * @break_flags: Flags indicating whether to break neighbors. + * + * Tests crossing of @left_seg and @right_seg. If there is a crossing, + * inserts the intersection point into both segments. + * + * Return value: True if the intersection took place at the current + * scan line, indicating further iteration is needed. + **/ +static art_boolean +art_svp_intersect_test_cross(ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg, + ArtBreakFlags break_flags) { + double left_x0, left_y0, left_x1; + double left_y1 = left_seg->y1; + double right_y1 = right_seg->y1; + double d; + + const ArtSVPSeg *in_seg; + int in_curs; + double d0, d1, t; + double x, y; /* intersection point */ + + if (left_seg->y0 == right_seg->y0 && left_seg->x[0] == right_seg->x[0]) { + /* Top points of left and right segments coincide. This case + feels like a bit of duplication - we may want to merge it + with the cases below. However, this way, we're sure that this + logic makes only localized changes. */ + + if (left_y1 < right_y1) { + /* Test left (x1, y1) against right segment */ + left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) { + /* I'm unsure about the break flags here. */ + double right_x1 = art_svp_intersect_break(ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else if (left_y1 > right_y1) { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) { + /* See above regarding break flags. */ + left_x1 = art_svp_intersect_break(ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else { /* left_y1 == right_y1 */ + left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + art_svp_intersect_swap_active(ctx, left_seg, right_seg); + return ART_TRUE; + } + + if (left_y1 < right_y1) { + /* Test left (x1, y1) against right segment */ + left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) { + double right_x1 = art_svp_intersect_break(ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else if (left_y1 > right_y1) { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) { + left_x1 = art_svp_intersect_break(ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else { /* left_y1 == right_y1 */ + left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + + /* The segments cross. Find the intersection point. */ + + in_seg = left_seg->in_seg; + in_curs = left_seg->in_curs; + left_x0 = in_seg->points[in_curs - 1].x; + left_y0 = in_seg->points[in_curs - 1].y; + left_x1 = in_seg->points[in_curs].x; + left_y1 = in_seg->points[in_curs].y; + d0 = left_x0 * right_seg->a + left_y0 * right_seg->b + right_seg->c; + d1 = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d0 == d1) { + x = left_x0; + y = left_y0; + } else { + /* Is this division always safe? It could possibly overflow. */ + t = d0 / (d0 - d1); + if (t <= 0) { + x = left_x0; + y = left_y0; + } else if (t >= 1) { + x = left_x1; + y = left_y1; + } else { + x = left_x0 + t * (left_x1 - left_x0); + y = left_y0 + t * (left_y1 - left_y0); + } + } + + /* Make sure intersection point is within bounds of right seg. */ + if (y < right_seg->y0) { + x = right_seg->x[0]; + y = right_seg->y0; + } else if (y > right_seg->y1) { + x = right_seg->x[1]; + y = right_seg->y1; + } else if (x < right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) + x = right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]; + else if (x > right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]) + x = right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]; + + if (y == left_seg->y0) { + if (y != right_seg->y0) { + art_svp_intersect_push_pt(ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point(ctx, x, y, right_seg->right, + break_flags); + } else { + /* Intersection takes place at current scan line; process + immediately rather than queueing intersection point into + priq. */ + ArtActiveSeg *winner, *loser; + + /* Choose "most vertical" segement */ + if (left_seg->a > right_seg->a) { + winner = left_seg; + loser = right_seg; + } else { + winner = right_seg; + loser = left_seg; + } + + loser->x[0] = winner->x[0]; + loser->horiz_x = loser->x[0]; + loser->horiz_delta_wind += loser->delta_wind; + winner->horiz_delta_wind -= loser->delta_wind; + + art_svp_intersect_swap_active(ctx, left_seg, right_seg); + return ART_TRUE; + } + } else if (y == right_seg->y0) { + art_svp_intersect_push_pt(ctx, left_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point(ctx, x, y, left_seg->left, + break_flags); + } else { + /* Insert the intersection point into both segments. */ + art_svp_intersect_push_pt(ctx, left_seg, x, y); + art_svp_intersect_push_pt(ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point(ctx, x, y, left_seg->left, break_flags); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point(ctx, x, y, right_seg->right, break_flags); + } + return ART_FALSE; +} + +/** + * art_svp_intersect_active_delete: Delete segment from active list. + * @ctx: Intersection context. + * @seg: Segment to delete. + * + * Deletes @seg from the active list. + **/ +static /* todo inline */ void +art_svp_intersect_active_delete(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + ArtActiveSeg *left = seg->left, *right = seg->right; + + if (left != NULL) + left->right = right; + else + ctx->active_head = right; + if (right != NULL) + right->left = left; +} + +/** + * art_svp_intersect_active_free: Free an active segment. + * @seg: Segment to delete. + * + * Frees @seg. + **/ +static /* todo inline */ void +art_svp_intersect_active_free(ArtActiveSeg *seg) { + free(seg->stack); + free(seg); +} + +/** + * art_svp_intersect_insert_cross: Test crossings of newly inserted line. + * + * Tests @seg against its left and right neighbors for intersections. + * Precondition: the line in @seg is not purely horizontal. + **/ +static void +art_svp_intersect_insert_cross(ArtIntersectCtx *ctx, + ArtActiveSeg *seg) { + ArtActiveSeg *left = seg, *right = seg; + + for (;;) { + if (left != NULL) { + ArtActiveSeg *leftc; + + for (leftc = left->left; leftc != NULL; leftc = leftc->left) + if (!(leftc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (leftc != NULL && + art_svp_intersect_test_cross(ctx, leftc, left, + ART_BREAK_LEFT)) { + if (left == right || right == NULL) + right = left->right; + } else { + left = NULL; + } + } else if (right != NULL && right->right != NULL) { + ArtActiveSeg *rightc; + + for (rightc = right->right; rightc != NULL; rightc = rightc->right) + if (!(rightc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (rightc != NULL && + art_svp_intersect_test_cross(ctx, right, rightc, + ART_BREAK_RIGHT)) { + if (left == right || left == NULL) + left = right->left; + } else { + right = NULL; + } + } else + break; + } +} + +/** + * art_svp_intersect_horiz: Add horizontal line segment. + * @ctx: Intersector context. + * @seg: Segment on which to add horizontal line. + * @x0: Old x position. + * @x1: New x position. + * + * Adds a horizontal line from @x0 to @x1, and updates the current + * location of @seg to @x1. + **/ +static void +art_svp_intersect_horiz(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x0, double x1) { + ArtActiveSeg *hs; + + if (x0 == x1) + return; + + hs = art_new(ArtActiveSeg, 1); + + hs->flags = ART_ACTIVE_FLAGS_DEL | (seg->flags & ART_ACTIVE_FLAGS_OUT); + if (seg->flags & ART_ACTIVE_FLAGS_OUT) { + ArtSvpWriter *swr = ctx->out; + + swr->add_point(swr, seg->seg_id, x0, ctx->y); + } + hs->seg_id = seg->seg_id; + hs->horiz_x = x0; + hs->horiz_delta_wind = seg->delta_wind; + hs->stack = NULL; + + /* Ideally, the (a, b, c) values will never be read. However, there + are probably some tests remaining that don't check for _DEL + before evaluating the line equation. For those, these + initializations will at least prevent a UMR of the values, which + can crash on some platforms. */ + hs->a = 0.0; + hs->b = 0.0; + hs->c = 0.0; + + seg->horiz_delta_wind -= seg->delta_wind; + + art_svp_intersect_add_horiz(ctx, hs); + + if (x0 > x1) { + ArtActiveSeg *left; + art_boolean first = ART_TRUE; + + for (left = seg->left; left != NULL; left = seg->left) { + int left_bneg = left->flags & ART_ACTIVE_FLAGS_BNEG; + + if (left->x[left_bneg] <= x1) + break; + if (left->x[left_bneg ^ 1] <= x1 && + x1 *left->a + ctx->y *left->b + left->c >= 0) + break; + if (left->y0 != ctx->y && left->y1 != ctx->y) { + art_svp_intersect_break(ctx, left, x1, ctx->y, ART_BREAK_LEFT); + } + art_svp_intersect_swap_active(ctx, left, seg); + if (first && left->right != NULL) { + art_svp_intersect_test_cross(ctx, left, left->right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } else { + ArtActiveSeg *right; + art_boolean first = ART_TRUE; + + for (right = seg->right; right != NULL; right = seg->right) { + int right_bneg = right->flags & ART_ACTIVE_FLAGS_BNEG; + + if (right->x[right_bneg ^ 1] >= x1) + break; + if (right->x[right_bneg] >= x1 && + x1 *right->a + ctx->y *right->b + right->c <= 0) + break; + if (right->y0 != ctx->y && right->y1 != ctx->y) { + art_svp_intersect_break(ctx, right, x1, ctx->y, + ART_BREAK_LEFT); + } + art_svp_intersect_swap_active(ctx, seg, right); + if (first && right->left != NULL) { + art_svp_intersect_test_cross(ctx, right->left, right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } + + seg->x[0] = x1; + seg->x[1] = x1; + seg->horiz_x = x1; + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +} + +/** + * art_svp_intersect_insert_line: Insert a line into the active list. + * @ctx: Intersector context. + * @seg: Segment containing line to insert. + * + * Inserts the line into the intersector context, taking care of any + * intersections, and adding the appropriate horizontal points to the + * active list. + **/ +static void +art_svp_intersect_insert_line(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + if (seg->y1 == seg->y0) { + art_svp_intersect_horiz(ctx, seg, seg->x[0], seg->x[1]); + } else { + art_svp_intersect_insert_cross(ctx, seg); + art_svp_intersect_add_horiz(ctx, seg); + } +} + +static void +art_svp_intersect_process_intersection(ArtIntersectCtx *ctx, + ArtActiveSeg *seg) { + int n_stack = --seg->n_stack; + seg->x[1] = seg->stack[n_stack - 1].x; + seg->y1 = seg->stack[n_stack - 1].y; + seg->x[0] = seg->stack[n_stack].x; + seg->y0 = seg->stack[n_stack].y; + seg->horiz_x = seg->x[0]; + art_svp_intersect_insert_line(ctx, seg); +} + +static void +art_svp_intersect_advance_cursor(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + ArtPriPoint *pri_pt) { + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + ArtSvpWriter *swr = seg->flags & ART_ACTIVE_FLAGS_OUT ? ctx->out : NULL; + + if (swr != NULL) + swr->add_point(swr, seg->seg_id, seg->x[1], seg->y1); + if (in_curs + 1 == in_seg->n_points) { + ArtActiveSeg *left = seg->left, *right = seg->right; + +#if 0 + if (swr != NULL) + swr->close_segment(swr, seg->seg_id); + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +#endif + seg->flags |= ART_ACTIVE_FLAGS_DEL; + art_svp_intersect_add_horiz(ctx, seg); + art_svp_intersect_active_delete(ctx, seg); + if (left != NULL && right != NULL) + art_svp_intersect_test_cross(ctx, left, right, + (ArtBreakFlags)(ART_BREAK_LEFT | ART_BREAK_RIGHT)); + free(pri_pt); + } else { + seg->horiz_x = seg->x[1]; + + art_svp_intersect_setup_seg(seg, pri_pt); + art_pri_insert(ctx->pq, pri_pt); + art_svp_intersect_insert_line(ctx, seg); + } +} + +static void +art_svp_intersect_add_seg(ArtIntersectCtx *ctx, const ArtSVPSeg *in_seg) { + ArtActiveSeg *seg = art_new(ArtActiveSeg, 1); + ArtActiveSeg *test; + double x0, y0; + ArtActiveSeg *beg_range; + ArtActiveSeg *last = NULL; + ArtActiveSeg *left, *right; + ArtPriPoint *pri_pt = art_new(ArtPriPoint, 1); + + seg->flags = 0; + seg->in_seg = in_seg; + seg->in_curs = 0; + + seg->n_stack_max = 4; + seg->stack = art_new(ArtPoint, seg->n_stack_max); + + seg->horiz_delta_wind = 0; + + seg->wind_left = 0; + + pri_pt->user_data = seg; + art_svp_intersect_setup_seg(seg, pri_pt); + art_pri_insert(ctx->pq, pri_pt); + + /* Find insertion place for new segment */ + /* This is currently a left-to-right scan, but should be replaced + with a binary search as soon as it's validated. */ + + x0 = in_seg->points[0].x; + y0 = in_seg->points[0].y; + beg_range = NULL; + for (test = ctx->active_head; test != NULL; test = test->right) { + double d; + int test_bneg = test->flags & ART_ACTIVE_FLAGS_BNEG; + + if (x0 < test->x[test_bneg]) { + if (x0 < test->x[test_bneg ^ 1]) + break; + d = x0 * test->a + y0 * test->b + test->c; + if (d < 0) + break; + } + last = test; + } + + left = art_svp_intersect_add_point(ctx, x0, y0, last, (ArtBreakFlags)(ART_BREAK_LEFT | ART_BREAK_RIGHT)); + seg->left = left; + if (left == NULL) { + right = ctx->active_head; + ctx->active_head = seg; + } else { + right = left->right; + left->right = seg; + } + seg->right = right; + if (right != NULL) + right->left = seg; + + seg->delta_wind = in_seg->dir ? 1 : -1; + seg->horiz_x = x0; + + art_svp_intersect_insert_line(ctx, seg); +} + +/** + * art_svp_intersect_horiz_commit: Commit points in horiz list to output. + * @ctx: Intersection context. + * + * The main function of the horizontal commit is to output new + * points to the output writer. + * + * This "commit" pass is also where winding numbers are assigned, + * because doing it here provides much greater tolerance for inputs + * which are not in strict SVP order. + * + * Each cluster in the horiz_list contains both segments that are in + * the active list (ART_ACTIVE_FLAGS_DEL is false) and that are not, + * and are scheduled to be deleted (ART_ACTIVE_FLAGS_DEL is true). We + * need to deal with both. + **/ +static void +art_svp_intersect_horiz_commit(ArtIntersectCtx *ctx) { + ArtActiveSeg *seg; + int winding_number = 0; /* initialization just to avoid warning */ + int horiz_wind = 0; + double last_x = 0; /* initialization just to avoid warning */ + + /* Output points to svp writer. */ + for (seg = ctx->horiz_first; seg != NULL;) { + /* Find a cluster with common horiz_x, */ + ArtActiveSeg *curs; + double x = seg->horiz_x; + + /* Generate any horizontal segments. */ + if (horiz_wind != 0) { + ArtSvpWriter *swr = ctx->out; + int seg_id; + + seg_id = swr->add_segment(swr, winding_number, horiz_wind, + last_x, ctx->y); + swr->add_point(swr, seg_id, x, ctx->y); + swr->close_segment(swr, seg_id); + } + + /* Find first active segment in cluster. */ + + for (curs = seg; curs != NULL && curs->horiz_x == x; + curs = curs->horiz_right) + if (!(curs->flags & ART_ACTIVE_FLAGS_DEL)) + break; + + if (curs != NULL && curs->horiz_x == x) { + /* There exists at least one active segment in this cluster. */ + + /* Find beginning of cluster. */ + for (; curs->left != NULL; curs = curs->left) + if (curs->left->horiz_x != x) + break; + + if (curs->left != NULL) + winding_number = curs->left->wind_left + curs->left->delta_wind; + else + winding_number = 0; + + do { + if (!(curs->flags & ART_ACTIVE_FLAGS_OUT) || + curs->wind_left != winding_number) { + ArtSvpWriter *swr = ctx->out; + + if (curs->flags & ART_ACTIVE_FLAGS_OUT) { + swr->add_point(swr, curs->seg_id, + curs->horiz_x, ctx->y); + swr->close_segment(swr, curs->seg_id); + } + + curs->seg_id = swr->add_segment(swr, winding_number, + curs->delta_wind, + x, ctx->y); + curs->flags |= ART_ACTIVE_FLAGS_OUT; + } + curs->wind_left = winding_number; + winding_number += curs->delta_wind; + curs = curs->right; + } while (curs != NULL && curs->horiz_x == x); + } + + /* Skip past cluster. */ + do { + ArtActiveSeg *next = seg->horiz_right; + + seg->flags &= ~ART_ACTIVE_FLAGS_IN_HORIZ; + horiz_wind += seg->horiz_delta_wind; + seg->horiz_delta_wind = 0; + if (seg->flags & ART_ACTIVE_FLAGS_DEL) { + if (seg->flags & ART_ACTIVE_FLAGS_OUT) { + ArtSvpWriter *swr = ctx->out; + swr->close_segment(swr, seg->seg_id); + } + art_svp_intersect_active_free(seg); + } + seg = next; + } while (seg != NULL && seg->horiz_x == x); + + last_x = x; + } + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; +} + +void +art_svp_intersector(const ArtSVP *in, ArtSvpWriter *out) { + ArtIntersectCtx *ctx; + ArtPriQ *pq; + ArtPriPoint *first_point; + + if (in->n_segs == 0) + return; + + ctx = art_new(ArtIntersectCtx, 1); + ctx->in = in; + ctx->out = out; + pq = art_pri_new(); + ctx->pq = pq; + + ctx->active_head = NULL; + + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; + + ctx->in_curs = 0; + first_point = art_new(ArtPriPoint, 1); + first_point->x = in->segs[0].points[0].x; + first_point->y = in->segs[0].points[0].y; + first_point->user_data = NULL; + ctx->y = first_point->y; + art_pri_insert(pq, first_point); + + while (!art_pri_empty(pq)) { + ArtPriPoint *pri_point = art_pri_choose(pq); + ArtActiveSeg *seg = (ArtActiveSeg *)pri_point->user_data; + + if (ctx->y != pri_point->y) { + art_svp_intersect_horiz_commit(ctx); + ctx->y = pri_point->y; + } + + if (seg == NULL) { + /* Insert new segment from input */ + const ArtSVPSeg *in_seg = &in->segs[ctx->in_curs++]; + art_svp_intersect_add_seg(ctx, in_seg); + if (ctx->in_curs < in->n_segs) { + const ArtSVPSeg *next_seg = &in->segs[ctx->in_curs]; + pri_point->x = next_seg->points[0].x; + pri_point->y = next_seg->points[0].y; + /* user_data is already NULL */ + art_pri_insert(pq, pri_point); + } else + free(pri_point); + } else { + int n_stack = seg->n_stack; + + if (n_stack > 1) { + art_svp_intersect_process_intersection(ctx, seg); + free(pri_point); + } else { + art_svp_intersect_advance_cursor(ctx, seg, pri_point); + } + } + } + + art_svp_intersect_horiz_commit(ctx); + + art_pri_free(pq); + free(ctx); +} diff --git a/engines/sword25/tools/swfdisplay/art_svp_intersect.h b/engines/sword25/tools/swfdisplay/art_svp_intersect.h new file mode 100644 index 0000000000..5e70ac6d79 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_intersect.h @@ -0,0 +1,58 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2001 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_INTERSECT_H__ +#define __ART_SVP_INTERSECT_H__ + +/* The funky new SVP intersector. */ + +#include "art.h" + +#ifndef ART_WIND_RULE_DEFINED +#define ART_WIND_RULE_DEFINED +typedef enum { + ART_WIND_RULE_NONZERO, + ART_WIND_RULE_INTERSECT, + ART_WIND_RULE_ODDEVEN, + ART_WIND_RULE_POSITIVE +} ArtWindRule; +#endif + +typedef struct _ArtSvpWriter ArtSvpWriter; + +struct _ArtSvpWriter { + int (*add_segment)(ArtSvpWriter *self, int wind_left, int delta_wind, + double x, double y); + void (*add_point)(ArtSvpWriter *self, int seg_id, double x, double y); + void (*close_segment)(ArtSvpWriter *self, int seg_id); +}; + +ArtSvpWriter * +art_svp_writer_rewind_new(ArtWindRule rule); + +ArtSVP * +art_svp_writer_rewind_reap(ArtSvpWriter *self); + +int +art_svp_seg_compare(const void *s1, const void *s2); + +void +art_svp_intersector(const ArtSVP *in, ArtSvpWriter *out); + +#endif /* __ART_SVP_INTERSECT_H__ */ diff --git a/engines/sword25/tools/swfdisplay/art_svp_render_aa.cpp b/engines/sword25/tools/swfdisplay/art_svp_render_aa.cpp new file mode 100644 index 0000000000..616977fb5e --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_render_aa.cpp @@ -0,0 +1,428 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* The spiffy antialiased renderer for sorted vector paths. */ + +#include "art.h" +#include "art_svp_render_aa.h" + +#include <math.h> +#include <string.h> /* for memmove */ + +#include <stdio.h> + +typedef double artfloat; + +struct _ArtSVPRenderAAIter { + const ArtSVP *svp; + int x0, x1; + int y; + int seg_ix; + + int *active_segs; + int n_active_segs; + int *cursor; + artfloat *seg_x; + artfloat *seg_dx; + + ArtSVPRenderAAStep *steps; +}; + +static void +art_svp_render_insert_active(int i, int *active_segs, int n_active_segs, + artfloat *seg_x, artfloat *seg_dx) { + int j; + artfloat x; + int tmp1, tmp2; + + /* this is a cheap hack to get ^'s sorted correctly */ + x = seg_x[i] + 0.001 * seg_dx[i]; + for (j = 0; j < n_active_segs && seg_x[active_segs[j]] < x; j++); + + tmp1 = i; + while (j < n_active_segs) { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + j++; + } + active_segs[j] = tmp1; +} + +static void +art_svp_render_delete_active(int *active_segs, int j, int n_active_segs) { + int k; + + for (k = j; k < n_active_segs; k++) + active_segs[k] = active_segs[k + 1]; +} + +#define EPSILON 1e-6 + +/* Render the sorted vector path in the given rectangle, antialiased. + + This interface uses a callback for the actual pixel rendering. The + callback is called y1 - y0 times (once for each scan line). The y + coordinate is given as an argument for convenience (it could be + stored in the callback's private data and incremented on each + call). + + The rendered polygon is represented in a semi-runlength format: a + start value and a sequence of "steps". Each step has an x + coordinate and a value delta. The resulting value at position x is + equal to the sum of the start value and all step delta values for + which the step x coordinate is less than or equal to x. An + efficient algorithm will traverse the steps left to right, keeping + a running sum. + + All x coordinates in the steps are guaranteed to be x0 <= x < x1. + (This guarantee is a change from the gfonted vpaar renderer, and is + designed to simplify the callback). + + There is now a further guarantee that no two steps will have the + same x value. This may allow for further speedup and simplification + of renderers. + + The value 0x8000 represents 0% coverage by the polygon, while + 0xff8000 represents 100% coverage. This format is designed so that + >> 16 results in a standard 0x00..0xff value range, with nice + rounding. + + Status of this routine: + + Basic correctness: OK + + Numerical stability: pretty good, although probably not + bulletproof. + + Speed: Needs more aggressive culling of bounding boxes. Can + probably speed up the [x0,x1) clipping of step values. Can do more + of the step calculation in fixed point. + + Precision: No known problems, although it should be tested + thoroughly, especially for symmetry. + +*/ + +ArtSVPRenderAAIter * +art_svp_render_aa_iter(const ArtSVP *svp, + int x0, int y0, int x1, int y1) { + ArtSVPRenderAAIter *iter = art_new(ArtSVPRenderAAIter, 1); + + iter->svp = svp; + iter->y = y0; + iter->x0 = x0; + iter->x1 = x1; + iter->seg_ix = 0; + + iter->active_segs = art_new(int, svp->n_segs); + iter->cursor = art_new(int, svp->n_segs); + iter->seg_x = art_new(artfloat, svp->n_segs); + iter->seg_dx = art_new(artfloat, svp->n_segs); + iter->steps = art_new(ArtSVPRenderAAStep, x1 - x0); + iter->n_active_segs = 0; + + return iter; +} + +#define ADD_STEP(xpos, xdelta) \ + /* stereotype code fragment for adding a step */ \ + if (n_steps == 0 || steps[n_steps - 1].x < xpos) \ + { \ + sx = n_steps; \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + else \ + { \ + for (sx = n_steps; sx > 0; sx--) \ + { \ + if (steps[sx - 1].x == xpos) \ + { \ + steps[sx - 1].delta += xdelta; \ + sx = n_steps; \ + break; \ + } \ + else if (steps[sx - 1].x < xpos) \ + { \ + break; \ + } \ + } \ + if (sx < n_steps) \ + { \ + memmove (&steps[sx + 1], &steps[sx], \ + (n_steps - sx) * sizeof(steps[0])); \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + } + +void +art_svp_render_aa_iter_step(ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps) { + const ArtSVP *svp = iter->svp; + int *active_segs = iter->active_segs; + int n_active_segs = iter->n_active_segs; + int *cursor = iter->cursor; + artfloat *seg_x = iter->seg_x; + artfloat *seg_dx = iter->seg_dx; + int i = iter->seg_ix; + int j; + int x0 = iter->x0; + int x1 = iter->x1; + int y = iter->y; + int seg_index; + + int x; + ArtSVPRenderAAStep *steps = iter->steps; + int n_steps; + artfloat y_top, y_bot; + artfloat x_top, x_bot; + artfloat x_min, x_max; + int ix_min, ix_max; + artfloat delta; /* delta should be int too? */ + int last, this_; + int xdelta; + artfloat rslope, drslope; + int start; + const ArtSVPSeg *seg; + int curs; + artfloat dy; + + int sx; + + /* insert new active segments */ + for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++) { + if (svp->segs[i].bbox.y1 > y && + svp->segs[i].bbox.x0 < x1) { + seg = &svp->segs[i]; + /* move cursor to topmost vector which overlaps [y,y+1) */ + for (curs = 0; seg->points[curs + 1].y < y; curs++); + cursor[i] = curs; + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs(dy) >= EPSILON) + seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) / + dy; + else + seg_dx[i] = 1e12; + seg_x[i] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[i]; + art_svp_render_insert_active(i, active_segs, n_active_segs++, + seg_x, seg_dx); + } + } + + n_steps = 0; + + /* render the runlengths, advancing and deleting as we go */ + start = 0x8000; + + for (j = 0; j < n_active_segs; j++) { + seg_index = active_segs[j]; + seg = &svp->segs[seg_index]; + curs = cursor[seg_index]; + while (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) { + y_top = y; + if (y_top < seg->points[curs].y) + y_top = seg->points[curs].y; + y_bot = y + 1; + if (y_bot > seg->points[curs + 1].y) + y_bot = seg->points[curs + 1].y; + if (y_top != y_bot) { + delta = (seg->dir ? 16711680.0 : -16711680.0) * + (y_bot - y_top); + x_top = seg_x[seg_index] + (y_top - y) * seg_dx[seg_index]; + x_bot = seg_x[seg_index] + (y_bot - y) * seg_dx[seg_index]; + if (x_top < x_bot) { + x_min = x_top; + x_max = x_bot; + } else { + x_min = x_bot; + x_max = x_top; + } + ix_min = (int)floor(x_min); + ix_max = (int)floor(x_max); + if (ix_min >= x1) { + /* skip; it starts to the right of the render region */ + } else if (ix_max < x0) + /* it ends to the left of the render region */ + start += (int)delta; + else if (ix_min == ix_max) { + /* case 1, antialias a single pixel */ + xdelta = (ix_min + 1 - (x_min + x_max) * 0.5) * delta; + + ADD_STEP(ix_min, xdelta) + + if (ix_min + 1 < x1) { + xdelta = delta - xdelta; + + ADD_STEP(ix_min + 1, xdelta) + } + } else { + /* case 2, antialias a run */ + rslope = 1.0 / fabs(seg_dx[seg_index]); + drslope = delta * rslope; + last = + drslope * 0.5 * + (ix_min + 1 - x_min) * (ix_min + 1 - x_min); + xdelta = last; + if (ix_min >= x0) { + ADD_STEP(ix_min, xdelta) + + x = ix_min + 1; + } else { + start += last; + x = x0; + } + if (ix_max > x1) + ix_max = x1; + for (; x < ix_max; x++) { + this_ = (seg->dir ? 16711680.0 : -16711680.0) * rslope * + (x + 0.5 - x_min); + xdelta = this_ - last; + last = this_; + + ADD_STEP(x, xdelta) + } + if (x < x1) { + this_ = + delta * (1 - 0.5 * + (x_max - ix_max) * (x_max - ix_max) * + rslope); + xdelta = this_ - last; + last = this_; + + ADD_STEP(x, xdelta) + + if (x + 1 < x1) { + xdelta = delta - last; + + ADD_STEP(x + 1, xdelta) + } + } + } + } + curs++; + if (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) { + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs(dy) >= EPSILON) + seg_dx[seg_index] = (seg->points[curs + 1].x - + seg->points[curs].x) / dy; + else + seg_dx[seg_index] = 1e12; + seg_x[seg_index] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[seg_index]; + } + /* break here, instead of duplicating predicate in while? */ + } + if (seg->points[curs].y >= y + 1) { + curs--; + cursor[seg_index] = curs; + seg_x[seg_index] += seg_dx[seg_index]; + } else { + art_svp_render_delete_active(active_segs, j--, + --n_active_segs); + } + } + + *p_start = start; + *p_steps = steps; + *p_n_steps = n_steps; + + iter->seg_ix = i; + iter->n_active_segs = n_active_segs; + iter->y++; +} + +void +art_svp_render_aa_iter_done(ArtSVPRenderAAIter *iter) { + free(iter->steps); + + free(iter->seg_dx); + free(iter->seg_x); + free(iter->cursor); + free(iter->active_segs); + free(iter); +} + +/** + * art_svp_render_aa: Render SVP antialiased. + * @svp: The #ArtSVP to render. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @callback: The callback which actually paints the pixels. + * @callback_data: Private data for @callback. + * + * Renders the sorted vector path in the given rectangle, antialiased. + * + * This interface uses a callback for the actual pixel rendering. The + * callback is called @y1 - @y0 times (once for each scan line). The y + * coordinate is given as an argument for convenience (it could be + * stored in the callback's private data and incremented on each + * call). + * + * The rendered polygon is represented in a semi-runlength format: a + * start value and a sequence of "steps". Each step has an x + * coordinate and a value delta. The resulting value at position x is + * equal to the sum of the start value and all step delta values for + * which the step x coordinate is less than or equal to x. An + * efficient algorithm will traverse the steps left to right, keeping + * a running sum. + * + * All x coordinates in the steps are guaranteed to be @x0 <= x < @x1. + * (This guarantee is a change from the gfonted vpaar renderer from + * which this routine is derived, and is designed to simplify the + * callback). + * + * The value 0x8000 represents 0% coverage by the polygon, while + * 0xff8000 represents 100% coverage. This format is designed so that + * >> 16 results in a standard 0x00..0xff value range, with nice + * rounding. + * + **/ +void +art_svp_render_aa(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback)(void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data) { + ArtSVPRenderAAIter *iter; + int y; + int start; + ArtSVPRenderAAStep *steps; + int n_steps; + + iter = art_svp_render_aa_iter(svp, x0, y0, x1, y1); + + + for (y = y0; y < y1; y++) { + art_svp_render_aa_iter_step(iter, &start, &steps, &n_steps); + (*callback)(callback_data, y, start, steps, n_steps); + } + + art_svp_render_aa_iter_done(iter); +} diff --git a/engines/sword25/tools/swfdisplay/art_svp_render_aa.h b/engines/sword25/tools/swfdisplay/art_svp_render_aa.h new file mode 100644 index 0000000000..ac4f538805 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_render_aa.h @@ -0,0 +1,55 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_RENDER_AA_H__ +#define __ART_SVP_RENDER_AA_H__ + +/* The spiffy antialiased renderer for sorted vector paths. */ + +#include "art.h" + +typedef struct _ArtSVPRenderAAStep ArtSVPRenderAAStep; +typedef struct _ArtSVPRenderAAIter ArtSVPRenderAAIter; + +struct _ArtSVPRenderAAStep { + int x; + int delta; /* stored with 16 fractional bits */ +}; + +ArtSVPRenderAAIter * +art_svp_render_aa_iter(const ArtSVP *svp, + int x0, int y0, int x1, int y1); + +void +art_svp_render_aa_iter_step(ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps); + +void +art_svp_render_aa_iter_done(ArtSVPRenderAAIter *iter); + +void +art_svp_render_aa(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback)(void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data); + +#endif /* __ART_SVP_RENDER_AA_H__ */ diff --git a/engines/sword25/tools/swfdisplay/art_svp_vpath.cpp b/engines/sword25/tools/swfdisplay/art_svp_vpath.cpp new file mode 100644 index 0000000000..e689e21e84 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_vpath.cpp @@ -0,0 +1,192 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Sort vector paths into sorted vector paths */ + +#include "art.h" + +#include <stdlib.h> +#include <math.h> + +/* reverse a list of points in place */ +static void +reverse_points(ArtPoint *points, int n_points) { + int i; + ArtPoint tmp_p; + + for (i = 0; i < (n_points >> 1); i++) { + tmp_p = points[i]; + points[i] = points[n_points - (i + 1)]; + points[n_points - (i + 1)] = tmp_p; + } +} + +/** + * art_svp_from_vpath: Convert a vpath to a sorted vector path. + * @vpath: #ArtVPath to convert. + * + * Converts a vector path into sorted vector path form. The svp form is + * more efficient for rendering and other vector operations. + * + * Basically, the implementation is to traverse the vector path, + * generating a new segment for each "run" of points in the vector + * path with monotonically increasing Y values. All the resulting + * values are then sorted. + * + * Note: I'm not sure that the sorting rule is correct with respect + * to numerical stability issues. + * + * Return value: Resulting sorted vector path. + **/ +ArtSVP * +art_svp_from_vpath(ArtVpath *vpath) { + int n_segs, n_segs_max; + ArtSVP *svp; + int dir; + int new_dir; + int i; + ArtPoint *points; + int n_points, n_points_max; + double x, y; + double x_min, x_max; + + n_segs = 0; + n_segs_max = 16; + svp = (ArtSVP *)malloc(sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + + dir = 0; + n_points = 0; + n_points_max = 0; + points = NULL; + i = 0; + + x = y = 0; /* unnecessary, given "first code must not be LINETO" invariant, + but it makes gcc -Wall -ansi -pedantic happier */ + x_min = x_max = 0; /* same */ + + while (vpath[i].code != ART_END) { + if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN) { + if (points != NULL && n_points >= 2) { + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + points = NULL; + } + + if (points == NULL) { + n_points_max = 4; + points = art_new(ArtPoint, n_points_max); + } + + n_points = 1; + points[0].x = x = vpath[i].x; + points[0].y = y = vpath[i].y; + x_min = x; + x_max = x; + dir = 0; + } else { /* must be LINETO */ + new_dir = (vpath[i].y > y || + (vpath[i].y == y && vpath[i].x > x)) ? 1 : -1; + if (dir && dir != new_dir) { + /* new segment */ + x = points[n_points - 1].x; + y = points[n_points - 1].y; + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + + n_points = 1; + n_points_max = 4; + points = art_new(ArtPoint, n_points_max); + points[0].x = x; + points[0].y = y; + x_min = x; + x_max = x; + } + + if (points != NULL) { + if (n_points == n_points_max) + art_expand(points, ArtPoint, n_points_max); + points[n_points].x = x = vpath[i].x; + points[n_points].y = y = vpath[i].y; + if (x < x_min) x_min = x; + else if (x > x_max) x_max = x; + n_points++; + } + dir = new_dir; + } + i++; + } + + if (points != NULL) { + if (n_points >= 2) { + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + } else + free(points); + } + + svp->n_segs = n_segs; + + qsort(&svp->segs, n_segs, sizeof(ArtSVPSeg), art_svp_seg_compare); + + return svp; +} + diff --git a/engines/sword25/tools/swfdisplay/art_svp_vpath.h b/engines/sword25/tools/swfdisplay/art_svp_vpath.h new file mode 100644 index 0000000000..d0f5f51b28 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_vpath.h @@ -0,0 +1,30 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_VPATH_H__ +#define __ART_SVP_VPATH_H__ + +#include "art.h" + +/* Sort vector paths into sorted vector paths. */ + +ArtSVP * +art_svp_from_vpath(ArtVpath *vpath); + +#endif /* __ART_SVP_VPATH_H__ */ diff --git a/engines/sword25/tools/swfdisplay/art_svp_vpath_stroke.cpp b/engines/sword25/tools/swfdisplay/art_svp_vpath_stroke.cpp new file mode 100644 index 0000000000..66cf3b783c --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_vpath_stroke.cpp @@ -0,0 +1,643 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include "art.h" +#include "art_svp_vpath_stroke.h" + +#include <stdlib.h> +#include <math.h> + +#include "art_svp_intersect.h" +#include "art_svp_vpath.h" + +#define EPSILON 1e-6 +#define EPSILON_2 1e-12 + +#define yes_OPTIMIZE_INNER + +/* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1, + yc + y1), centered at (xc, yc), and with given radius. Both x0^2 + + y0^2 and x1^2 + y1^2 should be equal to radius^2. + + A positive value of radius means curve to the left, negative means + curve to the right. +*/ +static void +art_svp_vpath_stroke_arc(ArtVpath **p_vpath, int *pn, int *pn_max, + double xc, double yc, + double x0, double y0, + double x1, double y1, + double radius, + double flatness) { + double theta; + double th_0, th_1; + int n_pts; + int i; + double aradius; + + aradius = fabs(radius); + theta = 2 * M_SQRT2 * sqrt(flatness / aradius); + th_0 = atan2(y0, x0); + th_1 = atan2(y1, x1); + if (radius > 0) { + /* curve to the left */ + if (th_0 < th_1) th_0 += M_PI * 2; + n_pts = ceil((th_0 - th_1) / theta); + } else { + /* curve to the right */ + if (th_1 < th_0) th_1 += M_PI * 2; + n_pts = ceil((th_1 - th_0) / theta); + } +#ifdef VERBOSE + printf("start %f %f; th_0 = %f, th_1 = %f, r = %f, theta = %f\n", x0, y0, th_0, th_1, radius, theta); +#endif + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + x0, yc + y0); + for (i = 1; i < n_pts; i++) { + theta = th_0 + (th_1 - th_0) * i / n_pts; + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + cos(theta) * aradius, + yc + sin(theta) * aradius); +#ifdef VERBOSE + printf("mid %f %f\n", cos(theta) * radius, sin(theta) * radius); +#endif + } + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + x1, yc + y1); +#ifdef VERBOSE + printf("end %f %f\n", x1, y1); +#endif +} + +/* Assume that forw and rev are at point i0. Bring them to i1, + joining with the vector i1 - i2. + + This used to be true, but isn't now that the stroke_raw code is + filtering out (near)zero length vectors: {It so happens that all + invocations of this function maintain the precondition i1 = i0 + 1, + so we could decrease the number of arguments by one. We haven't + done that here, though.} + + forw is to the line's right and rev is to its left. + + Precondition: no zero-length vectors, otherwise a divide by + zero will happen. */ +static void +render_seg(ArtVpath **p_forw, int *pn_forw, int *pn_forw_max, + ArtVpath **p_rev, int *pn_rev, int *pn_rev_max, + ArtVpath *vpath, int i0, int i1, int i2, + ArtPathStrokeJoinType join, + double line_width, double miter_limit, double flatness) { + double dx0, dy0; + double dx1, dy1; + double dlx0, dly0; + double dlx1, dly1; + double dmx, dmy; + double dmr2; + double scale; + double cross; + +#ifdef VERBOSE + printf("join style = %d\n", join); +#endif + + /* The vectors of the lines from i0 to i1 and i1 to i2. */ + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + dx1 = vpath[i2].x - vpath[i1].x; + dy1 = vpath[i2].y - vpath[i1].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx1 * dx1 + dy1 * dy1); + dlx1 = dy1 * scale; + dly1 = -dx1 * scale; + +#ifdef VERBOSE + printf("%% render_seg: (%g, %g) - (%g, %g) - (%g, %g)\n", + vpath[i0].x, vpath[i0].y, + vpath[i1].x, vpath[i1].y, + vpath[i2].x, vpath[i2].y); + + printf("%% render_seg: d[xy]0 = (%g, %g), dl[xy]0 = (%g, %g)\n", + dx0, dy0, dlx0, dly0); + + printf("%% render_seg: d[xy]1 = (%g, %g), dl[xy]1 = (%g, %g)\n", + dx1, dy1, dlx1, dly1); +#endif + + /* now, forw's last point is expected to be colinear along d[xy]0 + to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */ + + /* positive for positive area (i.e. left turn) */ + cross = dx1 * dy0 - dx0 * dy1; + + dmx = (dlx0 + dlx1) * 0.5; + dmy = (dly0 + dly1) * 0.5; + dmr2 = dmx * dmx + dmy * dmy; + + if (join == ART_PATH_STROKE_JOIN_MITER && + dmr2 * miter_limit * miter_limit < line_width * line_width) + join = ART_PATH_STROKE_JOIN_BEVEL; + + /* the case when dmr2 is zero or very small bothers me + (i.e. near a 180 degree angle) + ALEX: So, we avoid the optimization when dmr2 is very small. This should + be safe since dmx/y is only used in optimization and in MITER case, and MITER + should be converted to BEVEL when dmr2 is very small. */ + if (dmr2 > EPSILON_2) { + scale = line_width * line_width / dmr2; + dmx *= scale; + dmy *= scale; + } + + if (cross *cross < EPSILON_2 && dx0 *dx1 + dy0 *dy1 >= 0) { + /* going straight */ +#ifdef VERBOSE + printf("%% render_seg: straight\n"); +#endif + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + } else if (cross > 0) { + /* left turn, forw is outside and rev is inside */ + +#ifdef VERBOSE + printf("%% render_seg: left\n"); +#endif + if ( +#ifdef NO_OPTIMIZE_INNER + 0 && +#endif + (dmr2 > EPSILON_2) && + /* check that i1 + dm[xy] is inside i0-i1 rectangle */ + (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 && + /* and that i1 + dm[xy] is inside i1-i2 rectangle */ + ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */ + (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 && + /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0) +#endif + ) { + /* can safely add single intersection point */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } else { + /* need to loop-de-loop the inside */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) { + /* bevel */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } else if (join == ART_PATH_STROKE_JOIN_MITER) { + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc(p_forw, pn_forw, pn_forw_max, + vpath[i1].x, vpath[i1].y, + -dlx0, -dly0, + -dlx1, -dly1, + line_width, + flatness); + } else { + /* right turn, rev is outside and forw is inside */ +#ifdef VERBOSE + printf("%% render_seg: right\n"); +#endif + + if ( +#ifdef NO_OPTIMIZE_INNER + 0 && +#endif + (dmr2 > EPSILON_2) && + /* check that i1 - dm[xy] is inside i0-i1 rectangle */ + (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 && + /* and that i1 - dm[xy] is inside i1-i2 rectangle */ + ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */ + (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 && + /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0) +#endif + ) { + /* can safely add single intersection point */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } else { + /* need to loop-de-loop the inside */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) { + /* bevel */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } else if (join == ART_PATH_STROKE_JOIN_MITER) { + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc(p_rev, pn_rev, pn_rev_max, + vpath[i1].x, vpath[i1].y, + dlx0, dly0, + dlx1, dly1, + -line_width, + flatness); + + } +} + +/* caps i1, under the assumption of a vector from i0 */ +static void +render_cap(ArtVpath **p_result, int *pn_result, int *pn_result_max, + ArtVpath *vpath, int i0, int i1, + ArtPathStrokeCapType cap, double line_width, double flatness) { + double dx0, dy0; + double dlx0, dly0; + double scale; + int n_pts; + int i; + + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + +#ifdef VERBOSE + printf("cap style = %d\n", cap); +#endif + + switch (cap) { + case ART_PATH_STROKE_CAP_BUTT: + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_ROUND: + n_pts = ceil(M_PI / (2.0 * M_SQRT2 * sqrt(flatness / line_width))); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + for (i = 1; i < n_pts; i++) { + double theta, c_th, s_th; + + theta = M_PI * i / n_pts; + c_th = cos(theta); + s_th = sin(theta); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 * c_th - dly0 * s_th, + vpath[i1].y - dly0 * c_th + dlx0 * s_th); + } + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_SQUARE: + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 - dly0, + vpath[i1].y - dly0 + dlx0); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x + dlx0 - dly0, + vpath[i1].y + dly0 + dlx0); + break; + } +} + +/** + * art_svp_from_vpath_raw: Stroke a vector path, raw version + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Exactly the same as art_svp_vpath_stroke(), except that the resulting + * stroke outline may self-intersect and have regions of winding number + * greater than 1. + * + * Return value: Resulting raw stroked outline in svp format. + **/ +ArtVpath * +art_svp_vpath_stroke_raw(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) { + int begin_idx, end_idx; + int i; + ArtVpath *forw, *rev; + int n_forw, n_rev; + int n_forw_max, n_rev_max; + ArtVpath *result; + int n_result, n_result_max; + double half_lw = 0.5 * line_width; + int closed; + int last, this_, next, second; + double dx, dy; + + n_forw_max = 16; + forw = art_new(ArtVpath, n_forw_max); + + n_rev_max = 16; + rev = art_new(ArtVpath, n_rev_max); + + n_result = 0; + n_result_max = 16; + result = art_new(ArtVpath, n_result_max); + + for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx) { + n_forw = 0; + n_rev = 0; + + closed = (vpath[begin_idx].code == ART_MOVETO); + + /* we don't know what the first point joins with until we get to the + last point and see if it's closed. So we start with the second + line in the path. + + Note: this is not strictly true (we now know it's closed from + the opening pathcode), but why fix code that isn't broken? + */ + + this_ = begin_idx; + /* skip over identical points at the beginning of the subpath */ + for (i = this_ + 1; vpath[i].code == ART_LINETO; i++) { + dx = vpath[i].x - vpath[this_].x; + dy = vpath[i].y - vpath[this_].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + second = next; + + /* invariant: this doesn't coincide with next */ + while (vpath[next].code == ART_LINETO) { + last = this_; + this_ = next; + /* skip over identical points after the beginning of the subpath */ + for (i = this_ + 1; vpath[i].code == ART_LINETO; i++) { + dx = vpath[i].x - vpath[this_].x; + dy = vpath[i].y - vpath[this_].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + if (vpath[next].code != ART_LINETO) { + /* reached end of path */ + /* make "closed" detection conform to PostScript + semantics (i.e. explicit closepath code rather than + just the fact that end of the path is the beginning) */ + if (closed && + vpath[this_].x == vpath[begin_idx].x && + vpath[this_].y == vpath[begin_idx].y) { + int j; + + /* path is closed, render join to beginning */ + render_seg(&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this_, second, + join, half_lw, miter_limit, flatness); + +#ifdef VERBOSE + printf("%% forw %d, rev %d\n", n_forw, n_rev); +#endif + /* do forward path */ + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, forw[n_forw - 1].x, + forw[n_forw - 1].y); + for (j = 0; j < n_forw; j++) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + + /* do reverse path, reversed */ + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, rev[0].x, + rev[0].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + } else { + /* path is open */ + int j; + + /* add to forw rather than result to ensure that + forw has at least one point. */ + render_cap(&forw, &n_forw, &n_forw_max, + vpath, last, this_, + cap, half_lw, flatness); + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, forw[0].x, + forw[0].y); + for (j = 1; j < n_forw; j++) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + render_cap(&result, &n_result, &n_result_max, + vpath, second, begin_idx, + cap, half_lw, flatness); + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[0].x, + forw[0].y); + } + } else + render_seg(&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this_, next, + join, half_lw, miter_limit, flatness); + } + end_idx = next; + } + + free(forw); + free(rev); +#ifdef VERBOSE + printf("%% n_result = %d\n", n_result); +#endif + art_vpath_add_point(&result, &n_result, &n_result_max, ART_END, 0, 0); + return result; +} + +#define noVERBOSE + +#ifdef VERBOSE + +#define XOFF 50 +#define YOFF 700 + +static void +print_ps_vpath(ArtVpath *vpath) { + int i; + + for (i = 0; vpath[i].code != ART_END; i++) { + switch (vpath[i].code) { + case ART_MOVETO: + printf("%g %g moveto\n", XOFF + vpath[i].x, YOFF - vpath[i].y); + break; + case ART_LINETO: + printf("%g %g lineto\n", XOFF + vpath[i].x, YOFF - vpath[i].y); + break; + default: + break; + } + } + printf("stroke showpage\n"); +} + +static void +print_ps_svp(ArtSVP *vpath) { + int i, j; + + printf("%% begin\n"); + for (i = 0; i < vpath->n_segs; i++) { + printf("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0); + for (j = 0; j < vpath->segs[i].n_points; j++) { + printf("%g %g %s\n", + XOFF + vpath->segs[i].points[j].x, + YOFF - vpath->segs[i].points[j].y, + j ? "lineto" : "moveto"); + } + printf("stroke\n"); + } + + printf("showpage\n"); +} +#endif + +/* Render a vector path into a stroked outline. + + Status of this routine: + + Basic correctness: Only miter and bevel line joins are implemented, + and only butt line caps. Otherwise, seems to be fine. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Should be pretty good. + + Precision: The perturbation fuzzes the coordinates slightly, + but not enough to be visible. */ +/** + * art_svp_vpath_stroke: Stroke a vector path. + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Computes an svp representing the stroked outline of @vpath. The + * width of the stroked line is @line_width. + * + * Lines are joined according to the @join rule. Possible values are + * ART_PATH_STROKE_JOIN_MITER (for mitered joins), + * ART_PATH_STROKE_JOIN_ROUND (for round joins), and + * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join + * is converted to a bevelled join if the miter would extend to a + * distance of more than @miter_limit * @line_width from the actual + * join point. + * + * If there are open subpaths, the ends of these subpaths are capped + * according to the @cap rule. Possible values are + * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end + * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at + * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap, + * extending half @line_width past the end point). + * + * The @flatness parameter controls the accuracy of the rendering. It + * is most important for determining the number of points to use to + * approximate circular arcs for round lines and joins. In general, the + * resulting vector path will be within @flatness pixels of the "ideal" + * path containing actual circular arcs. I reserve the right to use + * the @flatness parameter to convert bevelled joins to miters for very + * small turn angles, as this would reduce the number of points in the + * resulting outline path. + * + * The resulting path is "clean" with respect to self-intersections, i.e. + * the winding number is 0 or 1 at each point. + * + * Return value: Resulting stroked outline in svp format. + **/ +ArtSVP * +art_svp_vpath_stroke(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) { + ArtVpath *vpath_stroke; + ArtSVP *svp, *svp2; + ArtSvpWriter *swr; + + vpath_stroke = art_svp_vpath_stroke_raw(vpath, join, cap, + line_width, miter_limit, flatness); + svp = art_svp_from_vpath(vpath_stroke); + free(vpath_stroke); + + swr = art_svp_writer_rewind_new(ART_WIND_RULE_NONZERO); + art_svp_intersector(svp, swr); + + svp2 = art_svp_writer_rewind_reap(swr); + art_svp_free(svp); + return svp2; +} diff --git a/engines/sword25/tools/swfdisplay/art_svp_vpath_stroke.h b/engines/sword25/tools/swfdisplay/art_svp_vpath_stroke.h new file mode 100644 index 0000000000..d16e324a3a --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_svp_vpath_stroke.h @@ -0,0 +1,56 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_VPATH_STROKE_H__ +#define __ART_SVP_VPATH_STROKE_H__ + +/* Sort vector paths into sorted vector paths. */ + +#include "art.h" + +typedef enum { + ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_JOIN_ROUND, + ART_PATH_STROKE_JOIN_BEVEL +} ArtPathStrokeJoinType; + +typedef enum { + ART_PATH_STROKE_CAP_BUTT, + ART_PATH_STROKE_CAP_ROUND, + ART_PATH_STROKE_CAP_SQUARE +} ArtPathStrokeCapType; + +ArtSVP * +art_svp_vpath_stroke(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + +/* This version may have winding numbers exceeding 1. */ +ArtVpath * +art_svp_vpath_stroke_raw(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + +#endif /* __ART_SVP_VPATH_STROKE_H__ */ diff --git a/engines/sword25/tools/swfdisplay/art_vpath_bpath.cpp b/engines/sword25/tools/swfdisplay/art_vpath_bpath.cpp new file mode 100644 index 0000000000..82f1668ff7 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/art_vpath_bpath.cpp @@ -0,0 +1,261 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for bezier paths */ + +#include "art.h" + +#include <math.h> + +#define RENDER_LEVEL 4 +#define RENDER_SIZE (1 << (RENDER_LEVEL)) + +/** + * art_vpath_render_bez: Render a bezier segment into the vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @x0: X coordinate of starting bezier point. + * @y0: Y coordinate of starting bezier point. + * @x1: X coordinate of first bezier control point. + * @y1: Y coordinate of first bezier control point. + * @x2: X coordinate of second bezier control point. + * @y2: Y coordinate of second bezier control point. + * @x3: X coordinate of ending bezier point. + * @y3: Y coordinate of ending bezier point. + * @flatness: Flatness control. + * + * Renders a bezier segment into the vector path, reallocating and + * updating *@p_vpath and *@pn_vpath_max as necessary. *@pn_vpath is + * incremented by the number of vector points added. + * + * This step includes (@x0, @y0) but not (@x3, @y3). + * + * The @flatness argument guides the amount of subdivision. The Adobe + * PostScript reference manual defines flatness as the maximum + * deviation between the any point on the vpath approximation and the + * corresponding point on the "true" curve, and we follow this + * definition here. A value of 0.25 should ensure high quality for aa + * rendering. +**/ +static void +art_vpath_render_bez(ArtVpath **p_vpath, int *pn, int *pn_max, + double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + double flatness) { + double x3_0, y3_0; + double z3_0_dot; + double z1_dot, z2_dot; + double z1_perp, z2_perp; + double max_perp_sq; + + double x_m, y_m; + double xa1, ya1; + double xa2, ya2; + double xb1, yb1; + double xb2, yb2; + + /* It's possible to optimize this routine a fair amount. + + First, once the _dot conditions are met, they will also be met in + all further subdivisions. So we might recurse to a different + routine that only checks the _perp conditions. + + Second, the distance _should_ decrease according to fairly + predictable rules (a factor of 4 with each subdivision). So it might + be possible to note that the distance is within a factor of 4 of + acceptable, and subdivide once. But proving this might be hard. + + Third, at the last subdivision, x_m and y_m can be computed more + expeditiously (as in the routine above). + + Finally, if we were able to subdivide by, say 2 or 3, this would + allow considerably finer-grain control, i.e. fewer points for the + same flatness tolerance. This would speed things up downstream. + + In any case, this routine is unlikely to be the bottleneck. It's + just that I have this undying quest for more speed... + + */ + + x3_0 = x3 - x0; + y3_0 = y3 - y0; + + /* z3_0_dot is dist z0-z3 squared */ + z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0; + + if (z3_0_dot < 0.001) { + /* if start and end point are almost identical, the flatness tests + * don't work properly, so fall back on testing whether both of + * the other two control points are the same as the start point, + * too. + */ + if (hypot(x1 - x0, y1 - y0) < 0.001 + && hypot(x2 - x0, y2 - y0) < 0.001) + goto nosubdivide; + else + goto subdivide; + } + + /* we can avoid subdivision if: + + z1 has distance no more than flatness from the z0-z3 line + + z1 is no more z0'ward than flatness past z0-z3 + + z1 is more z0'ward than z3'ward on the line traversing z0-z3 + + and correspondingly for z2 */ + + /* perp is distance from line, multiplied by dist z0-z3 */ + max_perp_sq = flatness * flatness * z3_0_dot; + + z1_perp = (y1 - y0) * x3_0 - (x1 - x0) * y3_0; + if (z1_perp * z1_perp > max_perp_sq) + goto subdivide; + + z2_perp = (y3 - y2) * x3_0 - (x3 - x2) * y3_0; + if (z2_perp * z2_perp > max_perp_sq) + goto subdivide; + + z1_dot = (x1 - x0) * x3_0 + (y1 - y0) * y3_0; + if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq) + goto subdivide; + + z2_dot = (x3 - x2) * x3_0 + (y3 - y2) * y3_0; + if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq) + goto subdivide; + + if (z1_dot + z1_dot > z3_0_dot) + goto subdivide; + + if (z2_dot + z2_dot > z3_0_dot) + goto subdivide; + + +nosubdivide: + /* don't subdivide */ + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, x3, y3); + return; + +subdivide: + + xa1 = (x0 + x1) * 0.5; + ya1 = (y0 + y1) * 0.5; + xa2 = (x0 + 2 * x1 + x2) * 0.25; + ya2 = (y0 + 2 * y1 + y2) * 0.25; + xb1 = (x1 + 2 * x2 + x3) * 0.25; + yb1 = (y1 + 2 * y2 + y3) * 0.25; + xb2 = (x2 + x3) * 0.5; + yb2 = (y2 + y3) * 0.5; + x_m = (xa2 + xb1) * 0.5; + y_m = (ya2 + yb1) * 0.5; +#ifdef VERBOSE + printf("%g,%g %g,%g %g,%g %g,%g\n", xa1, ya1, xa2, ya2, + xb1, yb1, xb2, yb2); +#endif + art_vpath_render_bez(p_vpath, pn, pn_max, + x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, flatness); + art_vpath_render_bez(p_vpath, pn, pn_max, + x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, flatness); +} + +/** + * art_bez_path_to_vec: Create vpath from bezier path. + * @bez: Bezier path. + * @flatness: Flatness control. + * + * Creates a vector path closely approximating the bezier path defined by + * @bez. The @flatness argument controls the amount of subdivision. In + * general, the resulting vpath deviates by at most @flatness pixels + * from the "ideal" path described by @bez. + * + * Return value: Newly allocated vpath. + **/ +ArtVpath * +art_bez_path_to_vec(const ArtBpath *bez, double flatness) { + ArtVpath *vec; + int vec_n, vec_n_max; + int bez_index; + double x, y; + + vec_n = 0; + vec_n_max = RENDER_SIZE; + vec = art_new(ArtVpath, vec_n_max); + + /* Initialization is unnecessary because of the precondition that the + bezier path does not begin with LINETO or CURVETO, but is here + to make the code warning-free. */ + x = 0; + y = 0; + + bez_index = 0; + do { +#ifdef VERBOSE + printf("%s %g %g\n", + bez[bez_index].code == ART_CURVETO ? "curveto" : + bez[bez_index].code == ART_LINETO ? "lineto" : + bez[bez_index].code == ART_MOVETO ? "moveto" : + bez[bez_index].code == ART_MOVETO_OPEN ? "moveto-open" : + "end", bez[bez_index].x3, bez[bez_index].y3); +#endif + /* make sure space for at least one more code */ + if (vec_n >= vec_n_max) + art_expand(vec, ArtVpath, vec_n_max); + switch (bez[bez_index].code) { + case ART_MOVETO_OPEN: + case ART_MOVETO: + case ART_LINETO: + x = bez[bez_index].x3; + y = bez[bez_index].y3; + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = x; + vec[vec_n].y = y; + vec_n++; + break; + case ART_END: + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = 0; + vec[vec_n].y = 0; + vec_n++; + break; + case ART_CURVETO: +#ifdef VERBOSE + printf("%g,%g %g,%g %g,%g %g,%g\n", x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3); +#endif + art_vpath_render_bez(&vec, &vec_n, &vec_n_max, + x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3, + flatness); + x = bez[bez_index].x3; + y = bez[bez_index].y3; + break; + } + } while (bez[bez_index++].code != ART_END); + return vec; +} + diff --git a/engines/sword25/tools/swfdisplay/bezierrender.cpp b/engines/sword25/tools/swfdisplay/bezierrender.cpp new file mode 100644 index 0000000000..ca0051fbe3 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/bezierrender.cpp @@ -0,0 +1,584 @@ +#include <libart_lgpl/art_misc.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_bpath.h> +#include <libart_lgpl/art_vpath_bpath.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_svp_vpath_stroke.h> +#include <libart_lgpl/art_svp_render_aa.h> +#include <libart_lgpl/art_rgb_svp.h> +#include <libart_lgpl/art_rgb.h> + +#define SAVING 1 + +#define WIDTH 1500 +#define HEIGHT 1500 +#define BYTES_PER_PIXEL 4 /* 24 packed rgb bits */ +#define ROWSTRIDE (WIDTH*BYTES_PER_PIXEL) + + +static unsigned char *render_path (void); + +#if SAVING +static void save_buffer (unsigned char *buffer, const char *filename); + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <png.h> +#include <stdio.h> + +/** + * pixbuf_save_to_file: + * @pixbuf: pixel buffer to save. + * @filename: file name to save the pixel buffer into. + * + * Saves the pixel buffer in the given filename under + * the .png format. Returns TRUE is succesful and FALSE + * otherwise. + * + * Copyright is to Iain Holmes and Eazel, inc for this function. + * It was stolen from nautilus-gdk-pixbuf-extensions.c but + * was originally coming from gnome-iconedit by Iain Holmes + * It was hacked by Ramiro Estrugo for Eazel, inc. + */ +static gboolean +pixbuf_save_to_file (const GdkPixbuf *pixbuf, const char *file_name) +{ + FILE *handle; + unsigned char *buffer; + gboolean has_alpha; + int width, height, depth, rowstride; + guchar *pixels; + png_structp png_ptr; + png_infop info_ptr; + png_text text[2]; + int i; + + g_return_val_if_fail (pixbuf != NULL, FALSE); + g_return_val_if_fail (file_name != NULL, FALSE); + g_return_val_if_fail (file_name[0] != '\0', FALSE); + + handle = fopen (file_name, "wb"); + if (handle == NULL) { + return FALSE; + } + + png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + fclose (handle); + return FALSE; + } + + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL) { + png_destroy_write_struct (&png_ptr, (png_infopp)NULL); + fclose (handle); + return FALSE; + } + + if (setjmp (png_ptr->jmpbuf)) { + png_destroy_write_struct (&png_ptr, &info_ptr); + fclose (handle); + return FALSE; + } + + png_init_io (png_ptr, handle); + + has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + depth = gdk_pixbuf_get_bits_per_sample (pixbuf); + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + png_set_IHDR (png_ptr, info_ptr, width, height, + depth, PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + /* Some text to go with the png image */ + text[0].key = "Title"; + text[0].text = (char *) file_name; + text[0].compression = PNG_TEXT_COMPRESSION_NONE; + text[1].key = "Software"; + text[1].text = "Nautilus Thumbnail"; + text[1].compression = PNG_TEXT_COMPRESSION_NONE; + png_set_text (png_ptr, info_ptr, text, 2); + + /* Write header data */ + png_write_info (png_ptr, info_ptr); + + /* if there is no alpha in the data, allocate buffer to expand into */ + if (has_alpha) { + buffer = NULL; + } else { + buffer = (unsigned char *)g_malloc(4 * width); + } + + /* pump the raster data into libpng, one scan line at a time */ + for (i = 0; i < height; i++) { + if (has_alpha) { + png_bytep row_pointer = pixels; + png_write_row (png_ptr, row_pointer); + } else { + /* expand RGB to RGBA using an opaque alpha value */ + int x; + unsigned char *buffer_ptr = buffer; + unsigned char *source_ptr = pixels; + for (x = 0; x < width; x++) { + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = 255; + } + png_write_row (png_ptr, (png_bytep) buffer); + } + pixels += rowstride; + } + + png_write_end (png_ptr, info_ptr); + png_destroy_write_struct (&png_ptr, &info_ptr); + + g_free (buffer); + + fclose (handle); + return TRUE; +} + +static void +save_buffer (unsigned char *buffer, const char *filename) +{ + GdkPixbuf *pixbuf; + + g_type_init(); + + pixbuf = gdk_pixbuf_new_from_data (buffer, + GDK_COLORSPACE_RGB, + TRUE, 8, + WIDTH, HEIGHT, + ROWSTRIDE, + NULL, NULL); + + pixbuf_save_to_file (pixbuf, filename); + + gdk_pixbuf_unref (pixbuf); +} + +#endif + +void +art_rgb_fill_run1 (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, gint n) +{ + int i; + + if (r == g && g == b && r == 255) + { + memset (buf, g, n + n + n + n); + } + else + { + art_u32 *alt = (art_u32 *)buf; + art_u32 color = (r << 24) | (g << 16) | (b << 8) | 0xff; + for (i = 0; i < n; i++) + *alt++ = color; + } +} + +/** + * art_rgb_run_alpha: Render semitransparent color over RGB buffer. + * @buf: Buffer for rendering. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @alpha: Alpha, range 0..256. + * @n: Number of RGB triples to render. + * + * Renders a sequential run of solid (@r, @g, @b) color over @buf with + * opacity @alpha. + **/ +void +art_rgb_run_alpha1 (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) +{ + int i; + int v; + + for (i = 0; i < n; i++) + { + v = *buf; + *buf++ = v + (((r - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((g - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((b - v) * alpha + 0x80) >> 8); + v = *buf + alpha; + if (v > 255) + v = 255; + *buf++ = v; + } +} + +typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData; + +struct _ArtRgbSVPAlphaData { + int alphatab[256]; + art_u8 r, g, b, alpha; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void +art_rgb_svp_alpha_callback1 (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1 (linebuf, + r, g, b, alphatab[alpha], + run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1 (linebuf + (run_x0 - x0) * 4, + r, g, b, alphatab[alpha], + run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1 (linebuf + (run_x1 - x0) * 4, + r, g, b, alphatab[alpha], + x1 - run_x1); + } + } + else + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1 (linebuf, + r, g, b, alphatab[alpha], + x1 - x0); + } + + data->buf += data->rowstride; +} + +static void +art_rgb_svp_alpha_opaque_callback1 (void *callback_data, int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run1 (linebuf, + r, g, b, + run_x1 - x0); + else + art_rgb_run_alpha1 (linebuf, + r, g, b, alphatab[alpha], + run_x1 - x0); + } + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run1 (linebuf + (run_x0 - x0) * 4, + r, g, b, + run_x1 - run_x0); + else + art_rgb_run_alpha1 (linebuf + (run_x0 - x0) * 4, + r, g, b, alphatab[alpha], + run_x1 - run_x0); + } + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run1 (linebuf + (run_x1 - x0) * 4, + r, g, b, + x1 - run_x1); + else + art_rgb_run_alpha1 (linebuf + (run_x1 - x0) * 4, + r, g, b, alphatab[alpha], + x1 - run_x1); + } + } + } + else + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run1 (linebuf, + r, g, b, + x1 - x0); + else + art_rgb_run_alpha1 (linebuf, + r, g, b, alphatab[alpha], + x1 - x0); + } + } + + data->buf += data->rowstride; +} + +/** + * art_rgb_svp_alpha: Alpha-composite sorted vector path over RGB buffer. + * @svp: The source sorted vector path. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @rgba: Color in 0xRRGGBBAA format. + * @buf: Destination RGB buffer. + * @rowstride: Rowstride of @buf buffer. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Renders the shape specified with @svp over the @buf RGB buffer. + * @x1 - @x0 specifies the width, and @y1 - @y0 specifies the height, + * of the rectangle rendered. The new pixels are stored starting at + * the first byte of @buf. Thus, the @x0 and @y0 parameters specify + * an offset within @svp, and may be tweaked as a way of doing + * integer-pixel translations without fiddling with @svp itself. + * + * The @rgba argument specifies the color for the rendering. Pixels of + * entirely 0 winding number are left untouched. Pixels of entirely + * 1 winding number have the color @rgba composited over them (ie, + * are replaced by the red, green, blue components of @rgba if the alpha + * component is 0xff). Pixels of intermediate coverage are interpolated + * according to the rule in @alphagamma, or default to linear if + * @alphagamma is NULL. + **/ +void +art_rgb_svp_alpha1 (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 rgba, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma) +{ + ArtRgbSVPAlphaData data; + int r, g, b, alpha; + int i; + int a, da; + + r = rgba >> 24; + g = (rgba >> 16) & 0xff; + b = (rgba >> 8) & 0xff; + alpha = rgba & 0xff; + + data.r = r; + data.g = g; + data.b = b; + data.alpha = alpha; + + a = 0x8000; + da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */ + + for (i = 0; i < 256; i++) + { + data.alphatab[i] = a >> 16; + a += da; + } + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + if (alpha == 255) + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback1, + &data); + else + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback1, &data); +} + + + +static unsigned char * +render_path () +{ + art_u8 *buffer = NULL; + art_u32 color1 = (0xFF << 24) | (0x00 <<16) | (0x00<<8) | (0xFF) ; /* RRGGBBAA */ + art_u32 color2 = (0x00 << 24) | (0x00 <<16) | (0xFF<<8) | (0xFF) ; /* RRGGBBAA */ + + ArtBpath *bez1 = NULL; + ArtVpath *vec = NULL; + ArtSVP *svp1 = NULL; + ArtSVP *svp2 = NULL; + + bez1 = art_new (ArtBpath, 30); + bez1[0].code = ART_MOVETO; + bez1[0].x3 = 352.000000; bez1[0].y3 = 59.000000; + bez1[1].code = ART_CURVETO; + bez1[1].x1 = 396.000000; bez1[1].y1 = 41.000000; + bez1[1].x2 = 434.333333; bez1[1].y2 = 57.666667; + bez1[1].x3 = 467.000000; bez1[1].y3 = 109.000000; + bez1[2].code = ART_CURVETO; + bez1[2].x1 = 481.666667; bez1[2].y1 = 137.666667; + bez1[2].x2 = 487.666667; bez1[2].y2 = 172.000000; + bez1[2].x3 = 485.000000; bez1[2].y3 = 212.000000; + bez1[3].code = ART_LINETO; + bez1[3].x3 = 485.000000; bez1[3].y3 = 216.000000; + bez1[4].code = ART_LINETO; + bez1[4].x3 = 483.000000; bez1[4].y3 = 231.000000; + bez1[5].code = ART_LINETO; + bez1[5].x3 = 468.000000; bez1[5].y3 = 291.000000; + bez1[6].code = ART_LINETO; + bez1[6].x3 = 466.000000; bez1[6].y3 = 299.000000; + bez1[7].code = ART_LINETO; + bez1[7].x3 = 446.000000; bez1[7].y3 = 349.000000; + bez1[8].code = ART_LINETO; + bez1[8].x3 = 445.000000; bez1[8].y3 = 351.000000; + bez1[9].code = ART_CURVETO; + bez1[9].x1 = 442.333333; bez1[9].y1 = 376.333333; + bez1[9].x2 = 450.000000; bez1[9].y2 = 394.000000; + bez1[9].x3 = 468.000000; bez1[9].y3 = 404.000000; + bez1[10].code = ART_CURVETO; + bez1[10].x1 = 373.333333; bez1[10].y1 = 428.666667; + bez1[10].x2 = 294.666667; bez1[10].y2 = 426.666667; + bez1[10].x3 = 232.000000; bez1[10].y3 = 398.000000; + bez1[11].code = ART_CURVETO; + bez1[11].x1 = 249.333333; bez1[11].y1 = 380.666667; + bez1[11].x2 = 257.666667; bez1[11].y2 = 365.000000; + bez1[11].x3 = 257.000000; bez1[11].y3 = 351.000000; + bez1[12].code = ART_LINETO; + bez1[12].x3 = 255.000000; bez1[12].y3 = 345.000000; + bez1[13].code = ART_CURVETO; + bez1[13].x1 = 244.333333; bez1[13].y1 = 326.333333; + bez1[13].x2 = 236.000000; bez1[13].y2 = 307.666667; + bez1[13].x3 = 230.000000; bez1[13].y3 = 289.000000; + bez1[14].code = ART_CURVETO; + bez1[14].x1 = 223.333333; bez1[14].y1 = 270.333333; + bez1[14].x2 = 219.000000; bez1[14].y2 = 252.000000; + bez1[14].x3 = 217.000000; bez1[14].y3 = 234.000000; + bez1[15].code = ART_LINETO; + bez1[15].x3 = 216.000000; bez1[15].y3 = 233.000000; + bez1[16].code = ART_LINETO; + bez1[16].x3 = 215.000000; bez1[16].y3 = 229.000000; + bez1[17].code = ART_CURVETO; + bez1[17].x1 = 201.666667; bez1[17].y1 = 187.666667; + bez1[17].x2 = 210.666667; bez1[17].y2 = 144.000000; + bez1[17].x3 = 242.000000; bez1[17].y3 = 98.000000; + bez1[18].code = ART_CURVETO; + bez1[18].x1 = 274.000000; bez1[18].y1 = 56.000000; + bez1[18].x2 = 310.333333; bez1[18].y2 = 43.333333; + bez1[18].x3 = 351.000000; bez1[18].y3 = 60.000000; + bez1[19].code = ART_LINETO; + bez1[19].x3 = 352.000000; bez1[19].y3 = 59.000000; + bez1[20].code = ART_END; + + vec = art_bez_path_to_vec (bez1, 0.5); + svp1 = art_svp_from_vpath (vec); + + int penWidth = 5; + svp2 = art_svp_vpath_stroke(vec, ART_PATH_STROKE_JOIN_ROUND, ART_PATH_STROKE_CAP_ROUND, penWidth, 1.0, 0.5); + + + buffer = art_new (art_u8, WIDTH*HEIGHT*BYTES_PER_PIXEL); + memset(buffer, 0, WIDTH*HEIGHT*BYTES_PER_PIXEL); + art_rgb_run_alpha (buffer, 0, 0, 0, 0, WIDTH*HEIGHT); + art_rgb_svp_alpha1 (svp1, 0, 0, WIDTH, HEIGHT, color1, buffer, ROWSTRIDE, NULL); + art_rgb_svp_alpha1 (svp2, 0, 0, WIDTH, HEIGHT, color2, buffer, ROWSTRIDE, NULL); + + art_free(svp2); + art_free(svp1); + art_free(vec); + art_free(bez1); + + return (unsigned char *) buffer; +} + +int main (int argc, char *argv[]) +{ + unsigned char *buffer; + + buffer = render_path (); + +#if SAVING + save_buffer (buffer, "foo.png"); +#endif + + return 0; +} + + + + + + + diff --git a/engines/sword25/tools/swfdisplay/func.h b/engines/sword25/tools/swfdisplay/func.h new file mode 100644 index 0000000000..251c1b4e7c --- /dev/null +++ b/engines/sword25/tools/swfdisplay/func.h @@ -0,0 +1,542 @@ +/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/common/func.h $ + * $Id: func.h 46129 2009-11-24 22:10:34Z fingolfin $ + */ + +#ifndef COMMON_FUNC_H +#define COMMON_FUNC_H + +namespace Common { + +/** + * Generic unary function. + */ +template<class Arg, class Result> +struct UnaryFunction { + typedef Arg ArgumenType; + typedef Result ResultType; +}; + +/** + * Generic binary function. + */ +template<class Arg1, class Arg2, class Result> +struct BinaryFunction { + typedef Arg1 FirstArgumentType; + typedef Arg2 SecondArgumentType; + typedef Result ResultType; +}; + +/** + * Predicate to check for equallity of two data elements. + */ +template<class T> +struct EqualTo : public BinaryFunction<T, T, bool> { + bool operator()(const T &x, const T &y) const { return x == y; } +}; + +/** + * Predicate to check for x being less than y. + */ +template<class T> +struct Less : public BinaryFunction<T, T, bool> { + bool operator()(const T &x, const T &y) const { return x < y; } +}; + +/** + * Predicate to check for x being greater than y. + */ +template<class T> +struct Greater : public BinaryFunction<T, T, bool> { + bool operator()(const T &x, const T &y) const { return x > y; } +}; + +template<class Op> +class Binder1st : public UnaryFunction<typename Op::SecondArgumentType, typename Op::ResultType> { +private: + Op _op; + typename Op::FirstArgumentType _arg1; +public: + Binder1st(const Op &op, typename Op::FirstArgumentType arg1) : _op(op), _arg1(arg1) {} + + typename Op::ResultType operator()(typename Op::SecondArgumentType v) const { + return _op(_arg1, v); + } +}; + +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template<class Op> +inline Binder1st<Op> bind1st(const Op &op, typename Op::FirstArgumentType t) { + return Binder1st<Op>(op, t); +} + +template<class Op> +class Binder2nd : public UnaryFunction<typename Op::FirstArgumentType, typename Op::ResultType> { +private: + Op _op; + typename Op::SecondArgumentType _arg2; +public: + Binder2nd(const Op &op, typename Op::SecondArgumentType arg2) : _op(op), _arg2(arg2) {} + + typename Op::ResultType operator()(typename Op::FirstArgumentType v) const { + return _op(v, _arg2); + } +}; + +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template<class Op> +inline Binder2nd<Op> bind2nd(const Op &op, typename Op::SecondArgumentType t) { + return Binder2nd<Op>(op, t); +} + +template<class Arg, class Result> +class PointerToUnaryFunc : public UnaryFunction<Arg, Result> { +private: + Result (*_func)(Arg); +public: + typedef Result (*FuncType)(Arg); + + PointerToUnaryFunc(const FuncType &func) : _func(func) {} + Result operator()(Arg v) const { + return _func(v); + } +}; + +template<class Arg1, class Arg2, class Result> +class PointerToBinaryFunc : public BinaryFunction<Arg1, Arg2, Result> { +private: + Result (*_func)(Arg1, Arg2); +public: + typedef Result (*FuncType)(Arg1, Arg2); + + PointerToBinaryFunc(const FuncType &func) : _func(func) {} + Result operator()(Arg1 v1, Arg2 v2) const { + return _func(v1, v2); + } +}; + +/** + * Creates an unary function object from a function pointer. + */ +template<class Arg, class Result> +inline PointerToUnaryFunc<Arg, Result> ptr_fun(Result (*func)(Arg)) { + return PointerToUnaryFunc<Arg, Result>(func); +} + +/** + * Creates an binary function object from a function pointer. + */ +template<class Arg1, class Arg2, class Result> +inline PointerToBinaryFunc<Arg1, Arg2, Result> ptr_fun(Result (*func)(Arg1, Arg2)) { + return PointerToBinaryFunc<Arg1, Arg2, Result>(func); +} + +template<class Result, class T> +class MemFunc0 : public UnaryFunction<T *, Result> { +private: + Result (T::*_func)(); +public: + typedef Result (T::*FuncType)(); + + MemFunc0(const FuncType &func) : _func(func) {} + Result operator()(T *v) const { + return (v->*_func)(); + } +}; + +template<class Result, class T> +class ConstMemFunc0 : public UnaryFunction<T *, Result> { +private: + Result (T::*_func)() const; +public: + typedef Result (T::*FuncType)() const; + + ConstMemFunc0(const FuncType &func) : _func(func) {} + Result operator()(const T *v) const { + return (v->*_func)(); + } +}; + +template<class Result, class Arg, class T> +class MemFunc1 : public BinaryFunction<T *, Arg, Result> { +private: + Result (T::*_func)(Arg); +public: + typedef Result (T::*FuncType)(Arg); + + MemFunc1(const FuncType &func) : _func(func) {} + Result operator()(T *v1, Arg v2) const { + return (v1->*_func)(v2); + } +}; + +template<class Result, class Arg, class T> +class ConstMemFunc1 : public BinaryFunction<T *, Arg, Result> { +private: + Result (T::*_func)(Arg) const; +public: + typedef Result (T::*FuncType)(Arg) const; + + ConstMemFunc1(const FuncType &func) : _func(func) {} + Result operator()(const T *v1, Arg v2) const { + return (v1->*_func)(v2); + } +}; + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ +template<class Result, class T> +inline MemFunc0<Result, T> mem_fun(Result (T::*f)()) { + return MemFunc0<Result, T>(f); +} + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ +template<class Result, class T> +inline ConstMemFunc0<Result, T> mem_fun(Result (T::*f)() const) { + return ConstMemFunc0<Result, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline MemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg)) { + return MemFunc1<Result, Arg, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline ConstMemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg) const) { + return ConstMemFunc1<Result, Arg, T>(f); +} + +template<class Result, class T> +class MemFuncRef0 : public UnaryFunction<T &, Result> { +private: + Result (T::*_func)(); +public: + typedef Result (T::*FuncType)(); + + MemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(T &v) const { + return (v.*_func)(); + } +}; + +template<class Result, class T> +class ConstMemFuncRef0 : public UnaryFunction<T &, Result> { +private: + Result (T::*_func)() const; +public: + typedef Result (T::*FuncType)() const; + + ConstMemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(const T &v) const { + return (v.*_func)(); + } +}; + +template<class Result, class Arg, class T> +class MemFuncRef1 : public BinaryFunction<T &, Arg, Result> { +private: + Result (T::*_func)(Arg); +public: + typedef Result (T::*FuncType)(Arg); + + MemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +template<class Result, class Arg, class T> +class ConstMemFuncRef1 : public BinaryFunction<T &, Arg, Result> { +private: + Result (T::*_func)(Arg) const; +public: + typedef Result (T::*FuncType)(Arg) const; + + ConstMemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(const T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template<class Result, class T> +inline MemFuncRef0<Result, T> mem_fun_ref(Result (T::*f)()) { + return MemFuncRef0<Result, T>(f); +} + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template<class Result, class T> +inline ConstMemFuncRef0<Result, T> mem_fun_Ref(Result (T::*f)() const) { + return ConstMemFuncRef0<Result, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline MemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg)) { + return MemFuncRef1<Result, Arg, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline ConstMemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg) const) { + return ConstMemFuncRef1<Result, Arg, T>(f); +} + +// functor code + +/** + * Generic functor object for function objects without parameters. + * + * @see Functor1 + */ +template<class Res> +struct Functor0 { + virtual ~Functor0() {} + + virtual bool isValid() const = 0; + virtual Res operator()() const = 0; +}; + +/** + * Functor object for a class member function without parameter. + * + * Example creation: + * + * Foo bar; + * Functor0Mem<void, Foo> myFunctor(&bar, &Foo::myFunc); + * + * Example usage: + * + * myFunctor(); + */ +template<class Res, class T> +class Functor0Mem : public Functor0<Res> { +public: + typedef Res (T::*FuncType)(); + + Functor0Mem(T *t, const FuncType &func) : _t(t), _func(func) {} + + bool isValid() const { return _func != 0 && _t != 0; } + Res operator()() const { + return (_t->*_func)(); + } +private: + mutable T *_t; + const FuncType _func; +}; + +/** + * Generic functor object for unary function objects. + * + * A typical usage for an unary function object is for executing opcodes + * in a script interpreter. To achieve that one can create an Common::Array + * object with 'Functor1<Arg, Res> *' as type. Now after the right engine version + * has been determined and the opcode table to use is found one could easily + * add the opcode implementations like this: + * + * Common::Array<Functor1<ScriptState, void> *> opcodeTable; + * opcodeTable[0] = new Functor1Mem<ScriptState, void, MyEngine_v1>(&myEngine, &MyEngine_v1::o1_foo); + * opcodeTable[1] = new Functor1Mem<ScriptState, void, MyEngine_v2>(&myEngine, &MyEngine_v2::o2_foo); + * // unimplemented/unused opcode + * opcodeTable[2] = 0; + * etc. + * + * This makes it easy to add member functions of different classes as + * opcode functions to the function table. Since with the generic + * Functor1<ScriptState, void> object the only requirement for an + * function to be used is 'ScriptState' as argument and 'void' as return + * value. + * + * Now for calling the opcodes one has simple to do: + * if (opcodeTable[opcodeNum] && opcodeTable[opcodeNum]->isValid()) + * (*opcodeTable[opcodeNum])(scriptState); + * else + * warning("Unimplemented opcode %d", opcodeNum); + * + * If you want to see an real world example check the kyra engine. + * Files: engines/kyra/script.cpp and .h and engines/kyra/script_*.cpp + * are interesting for that matter. + */ +template<class Arg, class Res> +struct Functor1 : public Common::UnaryFunction<Arg, Res> { + virtual ~Functor1() {} + + virtual bool isValid() const = 0; + virtual Res operator()(Arg) const = 0; +}; + +/** + * Functor object for an unary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take one parameter though. + * + * @see Functor0Mem + */ +template<class Arg, class Res, class T> +class Functor1Mem : public Functor1<Arg, Res> { +public: + typedef Res (T::*FuncType)(Arg); + + Functor1Mem(T *t, const FuncType &func) : _t(t), _func(func) {} + + bool isValid() const { return _func != 0 && _t != 0; } + Res operator()(Arg v1) const { + return (_t->*_func)(v1); + } +private: + mutable T *_t; + const FuncType _func; +}; + +/** + * Generic functor object for binary function objects. + * + * @see Functor1 + */ +template<class Arg1, class Arg2, class Res> +struct Functor2 : public Common::BinaryFunction<Arg1, Arg2, Res> { + virtual ~Functor2() {} + + virtual bool isValid() const = 0; + virtual Res operator()(Arg1, Arg2) const = 0; +}; + +/** + * Functor object for a binary function. + * + * @see Functor2Mem + */ +template<class Arg1, class Arg2, class Res> +class Functor2Fun : public Functor2<Arg1, Arg2, Res> { +public: + typedef Res (*FuncType)(Arg1, Arg2); + + Functor2Fun(const FuncType func) : _func(func) {} + + bool isValid() const { return _func != 0; } + Res operator()(Arg1 v1, Arg2 v2) const { + return (*_func)(v1, v2); + } +private: + const FuncType _func; +}; + +/** + * Functor object for a binary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take two parameter though. + * + * @see Functor0Mem + */ +template<class Arg1, class Arg2, class Res, class T> +class Functor2Mem : public Functor2<Arg1, Arg2, Res> { +public: + typedef Res (T::*FuncType)(Arg1, Arg2); + + Functor2Mem(T *t, const FuncType &func) : _t(t), _func(func) {} + + bool isValid() const { return _func != 0 && _t != 0; } + Res operator()(Arg1 v1, Arg2 v2) const { + return (_t->*_func)(v1, v2); + } +private: + mutable T *_t; + const FuncType _func; +}; + +/** + * Base template for hash functor objects, used by HashMap. + * This needs to be specialized for every type that you need to hash. + */ +template<typename T> struct Hash; + + +#define GENERATE_TRIVIAL_HASH_FUNCTOR(T) \ + template<> struct Hash<T> : public UnaryFunction<T, uint> { \ + uint operator()(T val) const { return (uint)val; } \ + } + +GENERATE_TRIVIAL_HASH_FUNCTOR(bool); +GENERATE_TRIVIAL_HASH_FUNCTOR(char); +GENERATE_TRIVIAL_HASH_FUNCTOR(signed char); +GENERATE_TRIVIAL_HASH_FUNCTOR(unsigned char); +GENERATE_TRIVIAL_HASH_FUNCTOR(short); +GENERATE_TRIVIAL_HASH_FUNCTOR(int); +GENERATE_TRIVIAL_HASH_FUNCTOR(long); +GENERATE_TRIVIAL_HASH_FUNCTOR(unsigned short); +GENERATE_TRIVIAL_HASH_FUNCTOR(unsigned int); +GENERATE_TRIVIAL_HASH_FUNCTOR(unsigned long); + +#undef GENERATE_TRIVIAL_HASH_FUNCTOR + +} // End of namespace Common + +#endif + diff --git a/engines/sword25/tools/swfdisplay/rect.h b/engines/sword25/tools/swfdisplay/rect.h new file mode 100644 index 0000000000..365dff09be --- /dev/null +++ b/engines/sword25/tools/swfdisplay/rect.h @@ -0,0 +1,223 @@ +/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/common/rect.h $ + * $Id: rect.h 52249 2010-08-21 08:17:06Z dreammaster $ + * + */ + +#ifndef COMMON_RECT_H +#define COMMON_RECT_H + +namespace Common { + +/** + * Simple class for handling both 2D position and size. + */ +struct Point { + int16 x; ///< The horizontal part of the point + int16 y; ///< The vertical part of the point + + Point() : x(0), y(0) {} + Point(int16 x1, int16 y1) : x(x1), y(y1) {} + bool operator==(const Point &p) const { return x == p.x && y == p.y; } + bool operator!=(const Point &p) const { return x != p.x || y != p.y; } + + /** + * Return the square of the distance between this point and the point p. + * + * @param p the other point + * @return the distance between this and p + */ +}; + +/** + * Simple class for handling a rectangular zone. + * + * Note: This implementation is built around the assumption that (top,left) is + * part of the rectangle, but (bottom,right) is not. This is reflected in + * various methods, including contains(), intersects() and others. + * + * Another very wide spread approach to rectangle classes treats (bottom,right) + * also as a part of the rectangle. + * + * Conceptually, both are sound, but the approach we use saves many intermediate + * computations (like computing the height in our case is done by doing this: + * height = bottom - top; + * while in the alternate system, it would be + * height = bottom - top + 1; + * + * When writing code using our Rect class, always keep this principle in mind! +*/ +struct Rect { + int16 top, left; ///< The point at the top left of the rectangle (part of the rect). + int16 bottom, right; ///< The point at the bottom right of the rectangle (not part of the rect). + + Rect() : top(0), left(0), bottom(0), right(0) {} + Rect(int16 w, int16 h) : top(0), left(0), bottom(h), right(w) {} + Rect(int16 x1, int16 y1, int16 x2, int16 y2) : top(y1), left(x1), bottom(y2), right(x2) { + assert(isValidRect()); + } + bool operator==(const Rect &rhs) const { return equals(rhs); } + bool operator!=(const Rect &rhs) const { return !equals(rhs); } + + int16 width() const { return right - left; } + int16 height() const { return bottom - top; } + + void setWidth(int16 aWidth) { + right = left + aWidth; + } + + void setHeight(int16 aHeight) { + bottom = top + aHeight; + } + + /** + * Check if given position is inside this rectangle. + * + * @param x the horizontal position to check + * @param y the vertical position to check + * + * @return true if the given position is inside this rectangle, false otherwise + */ + bool contains(int16 x, int16 y) const { + return (left <= x) && (x < right) && (top <= y) && (y < bottom); + } + + /** + * Check if given point is inside this rectangle. + * + * @param p the point to check + * + * @return true if the given point is inside this rectangle, false otherwise + */ + bool contains(const Point &p) const { + return contains(p.x, p.y); + } + + /** + * Check if the given rect is contained inside this rectangle. + * + * @param r The rectangle to check + * + * @return true if the given rect is inside, false otherwise + */ + bool contains(const Rect &r) const { + return (left <= r.left) && (r.right <= right) && (top <= r.top) && (r.bottom <= bottom); + } + + /** + * Check if the given rect is equal to this one. + * + * @param r The rectangle to check + * + * @return true if the given rect is equal, false otherwise + */ + bool equals(const Rect &r) const { + return (left == r.left) && (right == r.right) && (top == r.top) && (bottom == r.bottom); + } + + /** + * Check if given rectangle intersects with this rectangle + * + * @param r the rectangle to check + * + * @return true if the given rectangle is inside the rectangle, false otherwise + */ + bool intersects(const Rect &r) const { + return (left < r.right) && (r.left < right) && (top < r.bottom) && (r.top < bottom); + } + + /** + * Extend this rectangle so that it contains r + * + * @param r the rectangle to extend by + */ + + /** + * Extend this rectangle in all four directions by the given number of pixels + * + * @param offset the size to grow by + */ + void grow(int16 offset) { + top -= offset; + left -= offset; + bottom += offset; + right += offset; + } + + void clip(const Rect &r) { + assert(isValidRect()); + assert(r.isValidRect()); + + if (top < r.top) top = r.top; + else if (top > r.bottom) top = r.bottom; + + if (left < r.left) left = r.left; + else if (left > r.right) left = r.right; + + if (bottom > r.bottom) bottom = r.bottom; + else if (bottom < r.top) bottom = r.top; + + if (right > r.right) right = r.right; + else if (right < r.left) right = r.left; + } + + void clip(int16 maxw, int16 maxh) { + clip(Rect(0, 0, maxw, maxh)); + } + + bool isEmpty() const { + return (left >= right || top >= bottom); + } + + bool isValidRect() const { + return (left <= right && top <= bottom); + } + + void moveTo(int16 x, int16 y) { + bottom += y - top; + right += x - left; + top = y; + left = x; + } + + void translate(int16 dx, int16 dy) { + left += dx; right += dx; + top += dy; bottom += dy; + } + + void moveTo(const Point &p) { + moveTo(p.x, p.y); + } + + /** + * Create a rectangle around the given center. + */ + static Rect center(int16 cx, int16 cy, int16 w, int16 h) { + w /= 2; + h /= 2; + return Rect(cx - w, cy - h, cx + w, cy + h); + } +}; + +} // End of namespace Common + +#endif diff --git a/engines/sword25/tools/swfdisplay/swfrender.cpp b/engines/sword25/tools/swfdisplay/swfrender.cpp new file mode 100644 index 0000000000..af536978c0 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/swfrender.cpp @@ -0,0 +1,172 @@ +#include "art.h" +#include "vectorimage.h" + +#define SAVING 1 + +#if SAVING +static void save_buffer(unsigned char *buffer, uint width, uint height, const char *filename); + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <png.h> +#include <stdio.h> + +static gboolean +pixbuf_save_to_file(const GdkPixbuf *pixbuf, const char *file_name) { + FILE *handle; + unsigned char *buffer; + gboolean has_alpha; + int width, height, depth, rowstride; + guchar *pixels; + png_structp png_ptr; + png_infop info_ptr; + png_text text[2]; + int i; + + g_return_val_if_fail(pixbuf != NULL, FALSE); + g_return_val_if_fail(file_name != NULL, FALSE); + g_return_val_if_fail(file_name[0] != '\0', FALSE); + + handle = fopen(file_name, "wb"); + if (handle == NULL) { + return FALSE; + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + fclose(handle); + return FALSE; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + fclose(handle); + return FALSE; + } + + if (setjmp(png_ptr->jmpbuf)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(handle); + return FALSE; + } + + png_init_io(png_ptr, handle); + + has_alpha = gdk_pixbuf_get_has_alpha(pixbuf); + width = gdk_pixbuf_get_width(pixbuf); + height = gdk_pixbuf_get_height(pixbuf); + depth = gdk_pixbuf_get_bits_per_sample(pixbuf); + pixels = gdk_pixbuf_get_pixels(pixbuf); + rowstride = gdk_pixbuf_get_rowstride(pixbuf); + + png_set_IHDR(png_ptr, info_ptr, width, height, + depth, PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + /* Some text to go with the png image */ + text[0].key = "Title"; + text[0].text = (char *) file_name; + text[0].compression = PNG_TEXT_COMPRESSION_NONE; + text[1].key = "Software"; + text[1].text = "Nautilus Thumbnail"; + text[1].compression = PNG_TEXT_COMPRESSION_NONE; + png_set_text(png_ptr, info_ptr, text, 2); + + /* Write header data */ + png_write_info(png_ptr, info_ptr); + + /* if there is no alpha in the data, allocate buffer to expand into */ + if (has_alpha) { + buffer = NULL; + } else { + buffer = (unsigned char *)g_malloc(4 * width); + } + + /* pump the raster data into libpng, one scan line at a time */ + for (i = 0; i < height; i++) { + if (has_alpha) { + png_bytep row_pointer = pixels; + png_write_row(png_ptr, row_pointer); + } else { + /* expand RGB to RGBA using an opaque alpha value */ + int x; + unsigned char *buffer_ptr = buffer; + unsigned char *source_ptr = pixels; + for (x = 0; x < width; x++) { + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = *source_ptr++; + *buffer_ptr++ = 255; + } + png_write_row(png_ptr, (png_bytep) buffer); + } + pixels += rowstride; + } + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + g_free(buffer); + + fclose(handle); + return TRUE; +} + +static void +save_buffer(unsigned char *buffer, uint width, uint height, const char *filename) { + GdkPixbuf *pixbuf; + + g_type_init(); + + pixbuf = gdk_pixbuf_new_from_data(buffer, + GDK_COLORSPACE_RGB, + TRUE, + 8, + width, height, + width * 4, + NULL, NULL); + + pixbuf_save_to_file(pixbuf, filename); + + gdk_pixbuf_unref(pixbuf); +} + +#endif + + +int main(int argc, char *argv[]) { + if (argc < 2) { + printf("Usage: %s <file>\n", argv[0]); + exit(0); + } + + int fnamelen = strlen(argv[1]); + char *fname = (char *)malloc(fnamelen + 1); + strcpy(fname, argv[1]); + fname[fnamelen-3] = 'p'; + fname[fnamelen-2] = 'n'; + fname[fnamelen-1] = 'g'; + + FILE *in = fopen(argv[1], "r"); + fseek(in, 0, SEEK_END); + uint32 len = ftell(in); + + byte *buf = (byte *)malloc(len); + fseek(in, 0, SEEK_SET); + + fread(buf, len, 1, in); + fclose(in); + + bool success; + Sword25::VectorImage img(buf, len, success); + + img.render(img.getWidth(), img.getHeight()); + +#if SAVING + save_buffer(img.getPixelData(), img.getWidth(), img.getHeight(), fname); +#endif + + return 0; +} diff --git a/engines/sword25/tools/swfdisplay/vectorimage.cpp b/engines/sword25/tools/swfdisplay/vectorimage.cpp new file mode 100644 index 0000000000..7b266d8780 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/vectorimage.cpp @@ -0,0 +1,658 @@ +/* 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 "art.h" +#include "vectorimage.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 = 4; // 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) : _pixelData(0) { + 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]; + +#if DEBUG + printf("storeBez(bez, %d, %d, %d, %d, %d)\n", lineStyle, fillStyle0, fillStyle1, *bezNodes, *bezAllocated); +#endif + + _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 DEBUG + printf("M --> %lf, %lf\n", curX, curY); +#endif + } + + if (stateFillStyle0) { + if (numFillBits > 0) + fillStyle0 = bs.getBits(numFillBits); + else + fillStyle0 = 0; +#if DEBUG + printf("0 --> %d\n", fillStyle0); +#endif + } + + if (stateFillStyle1) { + if (numFillBits > 0) + fillStyle1 = bs.getBits(numFillBits); + else + fillStyle1 = 0; +#if DEBUG + printf("1 --> %d\n", fillStyle1); +#endif + } + + if (stateLineStyle) { + if (numLineBits) + lineStyle = bs.getBits(numLineBits); + else + lineStyle = 0; +#if DEBUG + printf("L --> %d\n", lineStyle); +#endif + } + + if (stateNewStyles) { +#if DEBUG + printf("New Style\n"); +#endif + // 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; + } + + // 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; + } + } + } 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); + color = BS_ARGB(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); + color = BS_ARGB(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; +} + + +// ----------------------------------------------------------------------------- + +#if 0 +bool VectorImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} +#endif + +// ----------------------------------------------------------------------------- + +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; +} + +#if 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; + } + + GLImage *rend = new GLImage(); + + rend->replaceContent(_pixelData, width, height); + rend->blit(posX, posY, flipping, pPartRect, color, width, height); + + delete rend; + + return true; +} +#endif + +} // End of namespace Sword25 diff --git a/engines/sword25/tools/swfdisplay/vectorimage.h b/engines/sword25/tools/swfdisplay/vectorimage.h new file mode 100644 index 0000000000..70a46b62d7 --- /dev/null +++ b/engines/sword25/tools/swfdisplay/vectorimage.h @@ -0,0 +1,233 @@ +/* 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 + * + */ + +#ifndef SWORD25_VECTORIMAGE_H +#define SWORD25_VECTORIMAGE_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "rect.h" +#include "array.h" + +#include "art.h" + +namespace Sword25 { + +class VectorImage; + +/** + @brief Pfadinformationen zu BS_VectorImageElement Objekten + + Jedes BS_VectorImageElement besteht aus Kantenzügen, oder auch Pfaden. Jeder dieser Pfad hat Eigenschaften, die in Objekten diesen Typs + gespeichert werden. +*/ + +class VectorPathInfo { +public: + VectorPathInfo(ArtBpath *vec, int len, uint lineStyle, uint fillStyle0, uint fillStyle1) : + _vec(vec), _lineStyle(lineStyle), _fillStyle0(fillStyle0), _fillStyle1(fillStyle1), _len(len) {} + + VectorPathInfo() { + _lineStyle = _fillStyle0 = _fillStyle1 = _len = 0; + _vec = 0; + } + + ArtBpath *getVec() const { + return _vec; + } + int getVecLen() const { + return _len; + } + uint getLineStyle() const { + return _lineStyle; + } + uint getFillStyle0() const { + return _fillStyle0; + } + uint getFillStyle1() const { + return _fillStyle1; + } + +private: + ArtBpath *_vec; + uint _lineStyle; + uint _fillStyle0; + uint _fillStyle1; + uint _len; +}; + +/** + @brief Ein Element eines Vektorbild. Ein BS_VectorImage besteht aus diesen Elementen, die jeweils einen Teil der Graphik definieren. + Werden alle Elemente eines Vektorbildes übereinandergelegt, ergibt sich das komplette Bild. +*/ +class VectorImageElement { + friend class VectorImage; +public: + uint getPathCount() const { + return _pathInfos.size(); + } + const VectorPathInfo &getPathInfo(uint pathNr) const { + BS_ASSERT(pathNr < getPathCount()); + return _pathInfos[pathNr]; + } + + double getLineStyleWidth(uint lineStyle) const { + BS_ASSERT(lineStyle < _lineStyles.size()); + return _lineStyles[lineStyle].width; + } + + uint getLineStyleCount() const { + return _lineStyles.size(); + } + + uint32 getLineStyleColor(uint lineStyle) const { + BS_ASSERT(lineStyle < _lineStyles.size()); + return _lineStyles[lineStyle].color; + } + + uint getFillStyleCount() const { + return _fillStyles.size(); + } + + uint32 getFillStyleColor(uint fillStyle) const { + BS_ASSERT(fillStyle < _fillStyles.size()); + return _fillStyles[fillStyle]; + } + + const Common::Rect &getBoundingBox() const { + return _boundingBox; + } + +private: + struct LineStyleType { + LineStyleType(double width_, uint32 color_) : width(width_), color(color_) {} + LineStyleType() { + width = 0; + color = 0; + } + double width; + uint32 color; + }; + + Common::Array<VectorPathInfo> _pathInfos; + Common::Array<LineStyleType> _lineStyles; + Common::Array<uint32> _fillStyles; + Common::Rect _boundingBox; +}; + + +/** + @brief Eine Vektorgraphik + + Objekte dieser Klasse enthalten die Informationen eines SWF-Shapes. +*/ + +class VectorImage { +public: + VectorImage(const byte *pFileData, uint fileSize, bool &success); + ~VectorImage(); + + uint getElementCount() const { + return _elements.size(); + } + const VectorImageElement &getElement(uint elementNr) const { + BS_ASSERT(elementNr < _elements.size()); + return _elements[elementNr]; + } + const Common::Rect &getBoundingBox() const { + return _boundingBox; + } + + // + // Die abstrakten Methoden von BS_Image + // + virtual int getWidth() const { + return _boundingBox.width(); + } + virtual int getHeight() const { + return _boundingBox.height(); + } + + void render(int width, int height); + + virtual uint getPixel(int x, int y); + virtual bool isBlitSource() const { + return true; + } + virtual bool isBlitTarget() const { + return false; + } + virtual bool isScalingAllowed() const { + return true; + } + virtual bool isFillingAllowed() const { + return false; + } + virtual bool isAlphaAllowed() const { + return true; + } + virtual bool isColorModulationAllowed() const { + return true; + } + virtual bool isSetContentAllowed() const { + return false; + } + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + + byte *getPixelData() { return _pixelData; } +#if 0 + virtual bool blit(int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1); +#endif + class SWFBitStream; + +private: + bool parseDefineShape(uint shapeType, SWFBitStream &bs); + bool parseStyles(uint shapeType, SWFBitStream &bs, uint &numFillBits, uint &numLineBits); + + ArtBpath *storeBez(ArtBpath *bez, int lineStyle, int fillStyle0, int fillStyle1, int *bezNodes, int *bezAllocated); + Common::Array<VectorImageElement> _elements; + Common::Rect _boundingBox; + + byte *_pixelData; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/tools/swfdisplay/vectorimagerenderer.cpp b/engines/sword25/tools/swfdisplay/vectorimagerenderer.cpp new file mode 100644 index 0000000000..ba964b6a9d --- /dev/null +++ b/engines/sword25/tools/swfdisplay/vectorimagerenderer.cpp @@ -0,0 +1,480 @@ +/* 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 + * + */ + +#include "art_svp_vpath.h" +#include "art_svp_vpath_stroke.h" +#include "art_svp_render_aa.h" + +#include "vectorimage.h" + +namespace Sword25 { + +void art_rgb_fill_run1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n) { + int i; + + if (r == g && g == b && r == 255) { + memset(buf, g, n + n + n + n); + } else { + uint32 *alt = (uint32 *)buf; + //uint32 color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(0xff, r, g, b); + uint32 color = BS_ARGB(0xff, r, g, b); + + for (i = 0; i < n; i++) + *alt++ = color; + } +} + +void art_rgb_run_alpha1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) { + int i; + int v; + + for (i = 0; i < n; i++) { + v = *buf; + *buf++ = v + (((b - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((g - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((r - v) * alpha + 0x80) >> 8); + v = *buf; + if (v + alpha > 255) + *buf++ = 255; + else + *buf++ = v + alpha; //(((alpha - v) * alpha + 0x80) >> 8); + } +} + +typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData; + +struct _ArtRgbSVPAlphaData { + int alphatab[256]; + art_u8 r, g, b, alpha; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void art_rgb_svp_alpha_callback1(void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } else { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + + data->buf += data->rowstride; +} + +static void art_rgb_svp_alpha_opaque_callback1(void *callback_data, int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, run_x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x0 - x0) * 4, r, g, b, run_x1 - run_x0); + else + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x1 - x0) * 4, r, g, b, x1 - run_x1); + else + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } + } else { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + } + + data->buf += data->rowstride; +} + +void art_rgb_svp_alpha1(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + uint32 color, + art_u8 *buf, int rowstride) { + ArtRgbSVPAlphaData data; + byte r, g, b, alpha; + int i; + int a, da; + + //Graphics::colorToARGB<Graphics::ColorMasks<8888> >(color, alpha, r, g, b); + alpha = (color >> 24) & 0xff; + b = (color >> 16) & 0xff; + g = (color >> 8) & 0xff; + r = (color >> 0) & 0xff; + + data.r = r; + data.g = g; + data.b = b; + data.alpha = alpha; + + a = 0x8000; + da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */ + + for (i = 0; i < 256; i++) { + data.alphatab[i] = a >> 16; + a += da; + } + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + if (alpha == 255) + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback1, &data); + else + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback1, &data); +} + +static int art_vpath_len(ArtVpath *a) { + int i; + + for (i = 0; a[i].code != ART_END; i++); + return i; +} + +ArtVpath *art_vpath_cat(ArtVpath *a, ArtVpath *b) { + ArtVpath *dest; + ArtVpath *p; + int len_a, len_b; + + len_a = art_vpath_len(a); + len_b = art_vpath_len(b); + dest = art_new(ArtVpath, len_a + len_b + 1); + p = dest; + + for (int i = 0; i < len_a; i++) + *p++ = *a++; + for (int i = 0; i <= len_b; i++) + *p++ = *b++; + + return dest; +} + +void art_svp_make_convex(ArtSVP *svp) { + int i; + + if (svp->n_segs > 0 && svp->segs[0].dir == 0) { + for (i = 0; i < svp->n_segs; i++) { + svp->segs[i].dir = !svp->segs[i].dir; + } + } +} + +ArtVpath *art_vpath_reverse(ArtVpath *a) { + ArtVpath *dest; + ArtVpath it; + int len; + int state = 0; + int i; + + len = art_vpath_len(a); + dest = art_new(ArtVpath, len + 1); + + for (i = 0; i < len; i++) { + it = a[len - i - 1]; + if (state) { + it.code = ART_LINETO; + } else { + it.code = ART_MOVETO_OPEN; + state = 1; + } + if (a[len - i - 1].code == ART_MOVETO || + a[len - i - 1].code == ART_MOVETO_OPEN) { + state = 0; + } + dest[i] = it; + } + dest[len] = a[len]; + + return dest; +} + +ArtVpath *art_vpath_reverse_free(ArtVpath *a) { + ArtVpath *dest; + + dest = art_vpath_reverse(a); + free(a); + + return dest; +} + +void drawBez(ArtBpath *bez1, ArtBpath *bez2, art_u8 *buffer, int width, int height, int deltaX, int deltaY, double scaleX, double scaleY, double penWidth, unsigned int color) { + ArtVpath *vec = NULL; + ArtVpath *vec1 = NULL; + ArtVpath *vec2 = NULL; + ArtSVP *svp = NULL; + +#if DEBUG + const char *codes[] = {"ART_MOVETO", "ART_MOVETO_OPEN", "ART_CURVETO", "ART_LINETO", "ART_END"}; + for (int i = 0;; i++) { + printf(" bez1[%d].code = %s;\n", i, codes[bez1[i].code]); + if (bez1[i].code == ART_END) + break; + if (bez1[i].code == ART_CURVETO) { + printf(" bez1[%d].x1 = %f; bez1[%d].y1 = %f;\n", i, bez1[i].x1, i, bez1[i].y1); + printf(" bez1[%d].x2 = %f; bez1[%d].y2 = %f;\n", i, bez1[i].x2, i, bez1[i].y2); + } + printf(" bez1[%d].x3 = %f; bez1[%d].y3 = %f;\n", i, bez1[i].x3, i, bez1[i].y3); + } + if (bez2 != 0) { + for (int i = 0;; i++) { + printf(" bez2[%d].code = %s;\n", i, codes[bez2[i].code]); + if (bez2[i].code == ART_END) + break; + if (bez2[i].code == ART_CURVETO) { + printf(" bez2[%d].x1 = %f; bez2[%d].y1 = %f;\n", i, bez2[i].x1, i, bez2[i].y1); + printf(" bez2[%d].x2 = %f; bez2[%d].y2 = %f;\n", i, bez2[i].x2, i, bez2[i].y2); + } + printf(" bez2[%d].x3 = %f; bez2[%d].y3 = %f;\n", i, bez2[i].x3, i, bez2[i].y3); + } + printf(" drawBez(bez1, bez2, buffer, 1.0, 1.0, %f, 0x%08x);\n", penWidth, color); + } else { + printf(" drawBez(bez1, 0, buffer, 1.0, 1.0, %f, 0x%08x);\n", penWidth, color); + } + +#endif + + vec1 = art_bez_path_to_vec(bez1, 0.5); + if (bez2 != 0) { + vec2 = art_bez_path_to_vec(bez2, 0.5); + vec2 = art_vpath_reverse_free(vec2); + vec = art_vpath_cat(vec1, vec2); + + free(vec1); + free(vec2); + } else { + vec = vec1; + } + + if (scaleX != 1.0 || scaleY != 1.0) { + ArtVpath *vect; + int size = art_vpath_len(vec); + + vect = art_new(ArtVpath, size + 1); + + int k; + for (k = 0; k < size; k++) { + vect[k].code = vec[k].code; + vect[k].x = vec[k].x * scaleX; + vect[k].y = vec[k].y * scaleY; + } + vect[k].code = ART_END; + free(vec); + + vec = vect; + } + + if (bez2 == 0) { // Line drawing + svp = art_svp_vpath_stroke(vec, ART_PATH_STROKE_JOIN_ROUND, ART_PATH_STROKE_CAP_ROUND, penWidth, 1.0, 0.5); + } else { + svp = art_svp_from_vpath(vec); + art_svp_make_convex(svp); + } + + art_rgb_svp_alpha1(svp, 0 + deltaX, 0 + deltaY, width + deltaX, height + deltaY, color, buffer, width * 4); + + free(svp); + free(vec); +} + +void VectorImage::render(int width, int height) { + double scaleX = (width == - 1) ? 1 : static_cast<double>(width) / static_cast<double>(getWidth()); + double scaleY = (height == - 1) ? 1 : static_cast<double>(height) / static_cast<double>(getHeight()); + + //debug(0, "VectorImage::render(%d, %d) %s", width, height, _fname.c_str()); + + printf("%d x %d\n", width, height); + printf("%d x %d - %d x %d\n", _boundingBox.left, _boundingBox.top, _boundingBox.right, _boundingBox.bottom); + + if (_pixelData) + free(_pixelData); + + _pixelData = (byte *)malloc(width * height * 4); + memset(_pixelData, 0, width * height * 4); + + for (uint e = 0; e < _elements.size(); e++) { +#if DEBUG + printf("----------------> Element\n"); +#endif + + //// Draw shapes + for (uint s = 0; s < _elements[e].getFillStyleCount(); s++) { + int fill0len = 0; + int fill1len = 0; + + // Count vector sizes in order to minimize memory + // fragmentation + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getFillStyle0() == s + 1) + fill0len += _elements[e].getPathInfo(p).getVecLen(); + + if (_elements[e].getPathInfo(p).getFillStyle1() == s + 1) + fill1len += _elements[e].getPathInfo(p).getVecLen(); + } + + // Now lump together vectors + ArtBpath *fill1 = art_new(ArtBpath, fill1len + 1); + ArtBpath *fill0 = art_new(ArtBpath, fill0len + 1); + ArtBpath *fill1pos = fill1; + ArtBpath *fill0pos = fill0; + + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getFillStyle0() == s + 1) { + for (int i = 0; i < _elements[e].getPathInfo(p).getVecLen(); i++) + *fill0pos++ = _elements[e].getPathInfo(p).getVec()[i]; + } + + if (_elements[e].getPathInfo(p).getFillStyle1() == s + 1) { + for (int i = 0; i < _elements[e].getPathInfo(p).getVecLen(); i++) + *fill1pos++ = _elements[e].getPathInfo(p).getVec()[i]; + } + } + + // Close vectors + (*fill0pos).code = ART_END; + (*fill1pos).code = ART_END; + + drawBez(fill1, fill0, _pixelData, width, height, _boundingBox.left, _boundingBox.top, scaleX, scaleY, -1, _elements[e].getFillStyleColor(s)); + + free(fill0); + free(fill1); + } + + //// Draw strokes + for (uint s = 0; s < _elements[e].getLineStyleCount(); s++) { + double penWidth = _elements[e].getLineStyleWidth(s); + penWidth *= sqrt(fabs(scaleX * scaleY)); + + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getLineStyle() == s + 1) { + drawBez(_elements[e].getPathInfo(p).getVec(), 0, _pixelData, width, height, _boundingBox.left, _boundingBox.top, scaleX, scaleY, penWidth, _elements[e].getLineStyleColor(s)); + } + } + } + } +} + + +} // End of namespace Sword25 |