WINTERMUTE: Integrate SDL_rotozoom code for scale/rotoscale

This commit is contained in:
Willem Jan Palenstijn 2013-10-06 14:15:51 +02:00
parent 14cb1f09c0
commit 8c8b1b7da7
2 changed files with 354 additions and 227 deletions

View File

@ -17,8 +17,16 @@
* 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.
*
*
* The bottom part of this is file is adapted from SDL_rotozoom.c. The
* relevant copyright notice for those specific functions can be found at the
* top of that section.
*
*/
#include "common/algorithm.h"
#include "common/endian.h"
#include "common/util.h"
@ -29,6 +37,8 @@
#include "engines/wintermute/graphics/transparent_surface.h"
#include "engines/wintermute/graphics/transform_tools.h"
//#define ENABLE_BILINEAR
namespace Wintermute {
void doBlitOpaqueFast(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep);
@ -269,119 +279,6 @@ void BlenderAdditive::blendPixel(byte ina, byte inr, byte ing, byte inb, byte *o
}
}
#if ENABLE_BILINEAR
void TransparentSurface::copyPixelBilinear(float projX, float projY, int dstX, int dstY, const Common::Rect &srcRect, const Common::Rect &dstRect, const TransparentSurface *src, TransparentSurface *dst) {
// TODO: Do some optimization on this. This is completely naive.
int srcW = srcRect.width();
int srcH = srcRect.height();
int dstW = dstRect.width();
int dstH = dstRect.height();
assert(dstX >= 0 && dstX < dstW);
assert(dstY >= 0 && dstY < dstH);
float x1 = floor(projX);
float x2 = ceil(projX);
float y1 = floor(projY);
float y2 = ceil(projY);
uint32 Q11, Q12, Q21, Q22;
if (x1 >= srcW || x1 < 0 || y1 >= srcH || y1 < 0) {
Q11 = 0;
} else {
Q11 = READ_UINT32((const byte *)src->getBasePtr((int)(x1 + srcRect.left), (int)(y1 + srcRect.top)));
}
if (x1 >= srcW || x1 < 0 || y2 >= srcH || y2 < 0) {
Q12 = 0;
} else {
Q12 = READ_UINT32((const byte *)src->getBasePtr((int)(x1 + srcRect.left), (int)(y2 + srcRect.top)));
}
if (x2 >= srcW || x2 < 0 || y1 >= srcH || y1 < 0) {
Q21 = 0;
} else {
Q21 = READ_UINT32((const byte *)src->getBasePtr((int)(x2 + srcRect.left), (int)(y1 + srcRect.top)));
}
if (x2 >= srcW || x2 < 0 || y2 >= srcH || y2 < 0) {
Q22 = 0;
} else {
Q22 = READ_UINT32((const byte *)src->getBasePtr((int)(x2 + srcRect.left), (int)(y2 + srcRect.top)));
}
byte *Q11s = (byte *)&Q11;
byte *Q12s = (byte *)&Q12;
byte *Q21s = (byte *)&Q21;
byte *Q22s = (byte *)&Q22;
uint32 color;
byte *dest = (byte *)&color;
float q11x = (x2 - projX);
float q11y = (y2 - projY);
float q21x = (projX - x1);
float q21y = (y2 - projY);
float q12x = (x2 - projX);
float q12y = (projY - y1);
if (x1 == x2 && y1 == y2) {
for (int c = 0; c < 4; c++) {
dest[c] = ((float)Q11s[c]);
}
} else {
if (x1 == x2) {
q11x = 0.5;
q12x = 0.5;
q21x = 0.5;
} else if (y1 == y2) {
q11y = 0.5;
q12y = 0.5;
q21y = 0.5;
}
for (int c = 0; c < 4; c++) {
dest[c] = (byte)(
((float)Q11s[c]) * q11x * q11y +
((float)Q21s[c]) * q21x * q21y +
((float)Q12s[c]) * q12x * q12y +
((float)Q22s[c]) * (1.0 -
q11x * q11y -
q21x * q21y -
q12x * q12y)
);
}
}
WRITE_UINT32((byte *)dst->getBasePtr(dstX + dstRect.left, dstY + dstRect.top), color);
}
#else
void TransparentSurface::copyPixelNearestNeighbor(float projX, float projY, int dstX, int dstY, const Common::Rect &srcRect, const Common::Rect &dstRect, const TransparentSurface *src, TransparentSurface *dst) {
// TODO: Have the Rect arguments become completely useless at this point?
int srcW = srcRect.width();
int srcH = srcRect.height();
int dstW = dstRect.width();
int dstH = dstRect.height();
assert(dstX >= 0 && dstX < dstW);
assert(dstY >= 0 && dstY < dstH);
uint32 color;
if (projX >= srcW || projX < 0 || projY >= srcH || projY < 0) {
color = 0;
} else {
color = READ_UINT32((const byte *)src->getBasePtr((int)projX, (int)projY));
}
WRITE_UINT32((byte *)dst->getBasePtr(dstX, dstY), color);
}
#endif
TransparentSurface::TransparentSurface() : Surface(), _alphaMode(ALPHA_FULL) {}
@ -655,92 +552,6 @@ Common::Rect TransparentSurface::blit(Graphics::Surface &target, int posX, int p
return retSize;
}
TransparentSurface *TransparentSurface::rotoscale(const TransformStruct &transform) const {
assert(transform._angle != 0); // This would not be ideal; rotoscale() should never be called in conditional branches where angle = 0 anyway.
Point32 newHotspot;
Common::Rect srcRect(0, 0, (int16)w, (int16)h);
Rect32 rect = TransformTools::newRect(Rect32(srcRect), transform, &newHotspot);
Common::Rect dstRect(0, 0, (int16)(rect.right - rect.left), (int16)(rect.bottom - rect.top));
TransparentSurface *target = new TransparentSurface();
assert(format.bytesPerPixel == 4);
int dstW = dstRect.width();
int dstH = dstRect.height();
target->create((uint16)dstW, (uint16)dstH, this->format);
uint32 invAngle = 360 - (transform._angle % 360);
float invCos = cos(invAngle * M_PI / 180.0);
float invSin = sin(invAngle * M_PI / 180.0);
float targX;
float targY;
for (int y = 0; y < dstH; y++) {
for (int x = 0; x < dstW; x++) {
int x1 = x - newHotspot.x;
int y1 = y - newHotspot.y;
targX = ((x1 * invCos - y1 * invSin)) * kDefaultZoomX / transform._zoom.x + srcRect.left;
targY = ((x1 * invSin + y1 * invCos)) * kDefaultZoomY / transform._zoom.y + srcRect.top;
targX += transform._hotspot.x;
targY += transform._hotspot.y;
#if ENABLE_BILINEAR
copyPixelBilinear(targX, targY, x, y, srcRect, dstRect, this, target);
#else
copyPixelNearestNeighbor(targX, targY, x, y, srcRect, dstRect, this, target);
#endif
}
}
return target;
}
TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight) const {
Common::Rect srcRect(0, 0, (int16)w, (int16)h);
Common::Rect dstRect(0, 0, (int16)newWidth, (int16)newHeight);
TransparentSurface *target = new TransparentSurface();
assert(format.bytesPerPixel == 4);
int srcW = srcRect.width();
int srcH = srcRect.height();
int dstW = dstRect.width();
int dstH = dstRect.height();
target->create((uint16)dstW, (uint16)dstH, this->format);
#if ENABLE_BILINEAR
for (int y = 0; y < dstH; y++) {
float projY = y / (float)dstH * srcH;
for (int x = 0; x < dstW; x++) {
float projX = x / (float)dstW * srcW;
copyPixelBilinear(projX, projY, x, y, srcRect, dstRect, this, target);
}
}
#else
int *scaleCacheX = new int[dstW];
for (int x = 0; x < dstW; x++)
scaleCacheX[x] = (x * srcW) / dstW;
for (int y = 0; y < dstH; y++) {
uint32 *destP = (uint32 *)target->getBasePtr(0, y);
const uint32 *srcP = (const uint32 *)getBasePtr(0, (y * srcH) / dstH);
for (int x = 0; x < dstW; x++)
*destP++ = srcP[scaleCacheX[x]];
}
delete[] scaleCacheX;
#endif
return target;
}
/**
* Writes a color key to the alpha channel of the surface
* @param rKey the red component of the color key
@ -773,4 +584,348 @@ TransparentSurface::AlphaType TransparentSurface::getAlphaMode() const {
void TransparentSurface::setAlphaMode(TransparentSurface::AlphaType mode) {
_alphaMode = mode;
}
/*
The below two functions are adapted from SDL_rotozoom.c,
taken from SDL_gfx-2.0.18.
Its copyright notice:
=============================================================================
SDL_rotozoom.c: rotozoomer, zoomer and shrinker for 32bit or 8bit surfaces
Copyright (C) 2001-2012 Andreas Schiffler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
Andreas Schiffler -- aschiffler at ferzkopp dot net
=============================================================================
The functions have been adapted for different structures and coordinate
systems.
*/
TransparentSurface *TransparentSurface::rotoscale(const TransformStruct &transform) const {
assert(transform._angle != 0); // This would not be ideal; rotoscale() should never be called in conditional branches where angle = 0 anyway.
Point32 newHotspot;
Common::Rect srcRect(0, 0, (int16)w, (int16)h);
Rect32 rect = TransformTools::newRect(Rect32(srcRect), transform, &newHotspot);
Common::Rect dstRect(0, 0, (int16)(rect.right - rect.left), (int16)(rect.bottom - rect.top));
TransparentSurface *target = new TransparentSurface();
assert(format.bytesPerPixel == 4);
int srcW = w;
int srcH = h;
int dstW = dstRect.width();
int dstH = dstRect.height();
target->create((uint16)dstW, (uint16)dstH, this->format);
if (transform._zoom.x == 0 || transform._zoom.y == 0)
return target;
uint32 invAngle = 360 - (transform._angle % 360);
float invCos = cos(invAngle * M_PI / 180.0);
float invSin = sin(invAngle * M_PI / 180.0);
struct tColorRGBA { byte r; byte g; byte b; byte a; };
int icosx = (int)(invCos * (65536.0f * kDefaultZoomX / transform._zoom.x));
int isinx = (int)(invSin * (65536.0f * kDefaultZoomX / transform._zoom.x));
int icosy = (int)(invCos * (65536.0f * kDefaultZoomY / transform._zoom.y));
int isiny = (int)(invSin * (65536.0f * kDefaultZoomY / transform._zoom.y));
bool flipx = false, flipy = false; // TODO: See mirroring comment in RenderTicket ctor
int xd = (srcRect.left + transform._hotspot.x) << 16;
int yd = (srcRect.top + transform._hotspot.y) << 16;
int cx = newHotspot.x;
int cy = newHotspot.y;
int ax = -icosx * cx;
int ay = -isiny * cx;
int sw = srcW - 1;
int sh = srcH - 1;
tColorRGBA *pc = (tColorRGBA*)target->getBasePtr(0, 0);
for (int y = 0; y < dstH; y++) {
int t = cy - y;
int sdx = ax + (isinx * t) + xd;
int sdy = ay - (icosy * t) + yd;
for (int x = 0; x < dstW; x++) {
int dx = (sdx >> 16);
int dy = (sdy >> 16);
if (flipx) dx = sw - dx;
if (flipy) dy = sh - dy;
#ifdef ENABLE_BILINEAR
if ((dx > -1) && (dy > -1) && (dx < sw) && (dy < sh)) {
const tColorRGBA *sp = (const tColorRGBA *)getBasePtr(dx, dy);
tColorRGBA c00, c01, c10, c11, cswap;
c00 = *sp;
sp += 1;
c01 = *sp;
sp += (this->pitch/4);
c11 = *sp;
sp -= 1;
c10 = *sp;
if (flipx) {
cswap = c00; c00=c01; c01=cswap;
cswap = c10; c10=c11; c11=cswap;
}
if (flipy) {
cswap = c00; c00=c10; c10=cswap;
cswap = c01; c01=c11; c11=cswap;
}
/*
* Interpolate colors
*/
int ex = (sdx & 0xffff);
int ey = (sdy & 0xffff);
int t1, t2;
t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff;
t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff;
pc->r = (((t2 - t1) * ey) >> 16) + t1;
t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff;
t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff;
pc->g = (((t2 - t1) * ey) >> 16) + t1;
t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff;
t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff;
pc->b = (((t2 - t1) * ey) >> 16) + t1;
t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff;
t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff;
pc->a = (((t2 - t1) * ey) >> 16) + t1;
}
#else
if ((dx >= 0) && (dy >= 0) && (dx < srcW) && (dy < srcH)) {
const tColorRGBA *sp = (const tColorRGBA *)getBasePtr(dx, dy);
*pc = *sp;
}
#endif
sdx += icosx;
sdy += isiny;
pc++;
}
}
return target;
}
TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight) const {
Common::Rect srcRect(0, 0, (int16)w, (int16)h);
Common::Rect dstRect(0, 0, (int16)newWidth, (int16)newHeight);
TransparentSurface *target = new TransparentSurface();
assert(format.bytesPerPixel == 4);
int srcW = srcRect.width();
int srcH = srcRect.height();
int dstW = dstRect.width();
int dstH = dstRect.height();
target->create((uint16)dstW, (uint16)dstH, this->format);
#ifdef ENABLE_BILINEAR
// NB: The actual order of these bytes may not be correct, but
// since all values are treated equal, that does not matter.
struct tColorRGBA { byte r; byte g; byte b; byte a; };
bool flipx = false, flipy = false; // TODO: See mirroring comment in RenderTicket ctor
int *sax = new int[dstW+1];
int *say = new int[dstH+1];
assert(sax && say);
/*
* Precalculate row increments
*/
int spixelw = (srcW - 1);
int spixelh = (srcH - 1);
int sx = (int) (65536.0f * (float) spixelw / (float) (dstW - 1));
int sy = (int) (65536.0f * (float) spixelh / (float) (dstH - 1));
/* Maximum scaled source size */
int ssx = (srcW << 16) - 1;
int ssy = (srcH << 16) - 1;
/* Precalculate horizontal row increments */
int csx = 0;
int *csax = sax;
for (int x = 0; x <= dstW; x++) {
*csax = csx;
csax++;
csx += sx;
/* Guard from overflows */
if (csx > ssx) {
csx = ssx;
}
}
/* Precalculate vertical row increments */
int csy = 0;
int *csay = say;
for (int y = 0; y <= dstH; y++) {
*csay = csy;
csay++;
csy += sy;
/* Guard from overflows */
if (csy > ssy) {
csy = ssy;
}
}
const tColorRGBA *sp = (const tColorRGBA *) getBasePtr(0,0);
tColorRGBA *dp = (tColorRGBA *) target->getBasePtr(0,0);
int spixelgap = srcW;
if (flipx)
sp += spixelw;
if (flipy)
sp += spixelgap * spixelh;
csay = say;
for (int y = 0; y < dstH; y++) {
const tColorRGBA *csp = sp;
csax = sax;
for (int x = 0; x < dstW; x++) {
/*
* Setup color source pointers
*/
int ex = (*csax & 0xffff);
int ey = (*csay & 0xffff);
int cx = (*csax >> 16);
int cy = (*csay >> 16);
const tColorRGBA *c00, *c01, *c10, *c11;
c00 = sp;
c01 = sp;
c10 = sp;
if (cy < spixelh) {
if (flipy)
c10 -= spixelgap;
else
c10 += spixelgap;
}
c11 = c10;
if (cx < spixelw) {
if (flipx) {
c01--;
c11--;
} else {
c01++;
c11++;
}
}
/*
* Draw and interpolate colors
*/
int t1, t2;
t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff;
t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff;
dp->r = (((t2 - t1) * ey) >> 16) + t1;
t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff;
t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff;
dp->g = (((t2 - t1) * ey) >> 16) + t1;
t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff;
t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff;
dp->b = (((t2 - t1) * ey) >> 16) + t1;
t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff;
t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff;
dp->a = (((t2 - t1) * ey) >> 16) + t1;
/*
* Advance source pointer x
*/
int *salastx = csax;
csax++;
int sstepx = (*csax >> 16) - (*salastx >> 16);
if (flipx)
sp -= sstepx;
else
sp += sstepx;
/*
* Advance destination pointer x
*/
dp++;
}
/*
* Advance source pointer y
*/
int *salasty = csay;
csay++;
int sstepy = (*csay >> 16) - (*salasty >> 16);
sstepy *= spixelgap;
if (flipy)
sp = csp - sstepy;
else
sp = csp + sstepy;
}
delete[] sax;
delete[] say;
#else
int *scaleCacheX = new int[dstW];
for (int x = 0; x < dstW; x++)
scaleCacheX[x] = (x * srcW) / dstW;
for (int y = 0; y < dstH; y++) {
uint32 *destP = (uint32 *)target->getBasePtr(0, y);
const uint32 *srcP = (const uint32 *)getBasePtr(0, (y * srcH) / dstH);
for (int x = 0; x < dstW; x++)
*destP++ = srcP[scaleCacheX[x]];
}
delete[] scaleCacheX;
#endif
return target;
}
} // End of namespace Wintermute

View File

@ -25,9 +25,6 @@
#include "graphics/surface.h"
#include "engines/wintermute/graphics/transform_struct.h"
#define ENABLE_BILINEAR 0
/*
* This code is based on Broken Sword 2.5 engine
*
@ -53,31 +50,6 @@ struct TransparentSurface : public Graphics::Surface {
void setColorKey(char r, char g, char b);
void disableColorKey();
#if ENABLE_BILINEAR
/*
* Pick color from a point in source and copy it to a pixel in target.
* The point in the source can be a float - we have subpixel accuracy in the arguments.
* We do bilinear interpolation to estimate the color of the point even if the
* point is specuified w/subpixel accuracy.
*
* @param projX, projY, point in the source to pick color from.
* @param dstX, dstY destionation pixel
* @param *src, *dst pointer to the source and dest surfaces
*/
static void copyPixelBilinear(float projX, float projY, int dstX, int dstY, const Common::Rect &srcRect, const Common::Rect &dstRect, const TransparentSurface *src, TransparentSurface *dst);
#else
/*
* Pick color from a point in source and copy it to a pixel in target.
* The point in the source can be a float - we have subpixel accuracy in the arguments.
* HOWEVER, this particular function just does nearest neighbor.
* Use copyPixelBilinear if you interpolation.
*
* @param projX, projY, point in the source to pick color from.
* @param dstX, dstY destionation pixel
* @param *src, *dst pointer to the source and dest surfaces
*/
static void copyPixelNearestNeighbor(float projX, float projY, int dstX, int dstY, const Common::Rect &srcRect, const Common::Rect &dstRect, const TransparentSurface *src, TransparentSurface *dst);
#endif
// Enums
/**
@brief The possible flipping parameters for the blit methode.