/* 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 3 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, see .
*
*/
/*
* This code contains portions of Libart_LGPL - library of basic graphic primitives
*
* Copyright (c) 1998 Raph Levien
*
* Licensed under GNU LGPL v2
*
*/
/*
* This code contains portions of Swfdec
*
* Copyright (c) 2004-2006 David Schleef
*
* Licensed under GNU GPL v2
*
*/
#include "sword25/gfx/image/art.h"
#include "sword25/gfx/image/vectorimage.h"
namespace Sword25 {
void art_rgb_fill_run1(byte *buf, byte r, byte g, byte 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 = MS_RGB(r, g, b);
for (i = 0; i < n; i++)
*alt++ = color;
}
}
void art_rgb_run_alpha1(byte *buf, byte r, byte g, byte b, int alpha, int n) {
int i;
int v;
for (i = 0; i < n; i++) {
#if defined(SCUMM_LITTLE_ENDIAN)
v = *buf;
*buf++ = MIN(v + alpha, 0xff);
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);
#else
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++ = MIN(v + alpha, 0xff);
#endif
}
}
typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData;
struct _ArtRgbSVPAlphaData {
int alphatab[256];
byte r, g, b, alpha;
byte *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;
byte *linebuf;
int run_x0, run_x1;
uint32 running_sum = start;
int x0, x1;
int k;
byte 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;
byte *linebuf;
int run_x0, run_x1;
uint32 running_sum = start;
int x0, x1;
int k;
byte 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,
byte *buf, int rowstride) {
ArtRgbSVPAlphaData data;
int i;
int a, da;
data.r = (color >> 16) & 0xFF;
data.g = (color >> 8) & 0xFF;
data.b = (color >> 0) & 0xFF;
data.alpha = (color >> 24) & 0xFF;
a = 0x8000;
da = (data.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 (data.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 = 0;
while (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);
if (!dest)
error("[art_vpath_cat] Cannot allocate memory");
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);
if (!dest)
error("[art_vpath_reverse] Cannot allocate memory");
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, byte *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 0
const char *codes[] = {"ART_MOVETO", "ART_MOVETO_OPEN", "ART_CURVETO", "ART_LINETO", "ART_END"};
for (int i = 0;; i++) {
debugN(" bez[%d].code = %s;\n", i, codes[bez[i].code]);
if (bez[i].code == ART_END)
break;
if (bez[i].code == ART_CURVETO) {
debugN(" bez[%d].x1 = %f; bez[%d].y1 = %f;\n", i, bez[i].x1, i, bez[i].y1);
debugN(" bez[%d].x2 = %f; bez[%d].y2 = %f;\n", i, bez[i].x2, i, bez[i].y2);
}
debugN(" bez[%d].x3 = %f; bez[%d].y3 = %f;\n", i, bez[i].x3, i, bez[i].y3);
}
debugN(" drawBez(bez, buffer, 1.0, 1.0, %f, 0x%08x);\n", penWidth, color);
#endif
// HACK: Some frames have green bounding boxes drawn.
// Perhaps they were used by original game artist Umriss
// We skip them just like the original
if (bez2 == 0 && color == BS_RGB(0x00, 0xff, 0x00)) {
return;
}
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;
}
int size = art_vpath_len(vec);
ArtVpath *vect = art_new(ArtVpath, size + 1);
if (!vect)
error("[drawBez] Cannot allocate memory");
int k;
for (k = 0; k < size; k++) {
vect[k].code = vec[k].code;
vect[k].x = (vec[k].x - deltaX) * scaleX;
vect[k].y = (vec[k].y - deltaY) * scaleY;
}
vect[k].code = ART_END;
if (bez2 == 0) { // Line drawing
svp = art_svp_vpath_stroke(vect, ART_PATH_STROKE_JOIN_ROUND, ART_PATH_STROKE_CAP_ROUND, penWidth, 1.0, 0.5);
} else {
svp = art_svp_from_vpath(vect);
art_svp_make_convex(svp);
}
art_rgb_svp_alpha1(svp, 0, 0, width, height, color, buffer, width * 4);
free(vect);
art_svp_free(svp);
free(vec);
}
void VectorImage::render(int width, int height) {
double scaleX = (width == - 1) ? 1 : static_cast(width) / static_cast(getWidth());
double scaleY = (height == - 1) ? 1 : static_cast(height) / static_cast(getHeight());
debug(3, "VectorImage::render(%d, %d) %s", width, height, _fname.c_str());
if (_pixelData)
free(_pixelData);
_pixelData = (byte *)malloc(width * height * 4);
memset(_pixelData, 0, width * height * 4);
for (uint e = 0; e < _elements.size(); e++) {
//// 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