Files
scummvm/graphics/managed_surface.cpp
T
Kaloyan Chehlarski be67cf827b GRAPHICS: Prevent segfault in ManagedSurface
The create() overload of ManagedSurface that creates a
new, owned surface, previously assumed that the
_innerSurface was owned before the call was made. Thus,
when switching between sub-surface and owning surface
modes, a segfault would occur when attempting to free
the pixel data of the inner surface, which is not actually
owned by the ManagedSurface. This commit makes sure
that when free() is called, the inner surface no longer
has an active pointer to its old pixel data.
2024-01-28 02:06:05 +01:00

855 lines
30 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "graphics/managed_surface.h"
#include "graphics/blit.h"
#include "common/algorithm.h"
#include "common/textconsole.h"
#include "common/endian.h"
namespace Graphics {
const int SCALE_THRESHOLD = 0x100;
ManagedSurface::ManagedSurface() :
w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format),
_disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr),
_transparentColor(0),_transparentColorSet(false), _paletteSet(false) {
memset(_palette, 0, sizeof(_palette));
}
ManagedSurface::ManagedSurface(const ManagedSurface &surf) :
w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format),
_disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr),
_transparentColor(0), _transparentColorSet(false), _paletteSet(false) {
memset(_palette, 0, sizeof(_palette));
*this = surf;
}
ManagedSurface::ManagedSurface(int width, int height) :
w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format),
_disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr),
_transparentColor(0), _transparentColorSet(false), _paletteSet(false) {
memset(_palette, 0, sizeof(_palette));
create(width, height);
}
ManagedSurface::ManagedSurface(int width, int height, const Graphics::PixelFormat &pixelFormat) :
w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format),
_disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr),
_transparentColor(0), _transparentColorSet(false), _paletteSet(false) {
memset(_palette, 0, sizeof(_palette));
create(width, height, pixelFormat);
}
ManagedSurface::ManagedSurface(ManagedSurface &surf, const Common::Rect &bounds) :
w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format),
_disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr),
_transparentColor(0), _transparentColorSet(false), _paletteSet(false) {
memset(_palette, 0, sizeof(_palette));
create(surf, bounds);
}
ManagedSurface::ManagedSurface(Surface *surf, DisposeAfterUse::Flag disposeAfterUse) :
w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format),
_owner(nullptr), _transparentColor(0), _transparentColorSet(false), _paletteSet(false) {
if (!surf) {
_disposeAfterUse = DisposeAfterUse::YES;
return;
}
_disposeAfterUse = disposeAfterUse;
if (disposeAfterUse == DisposeAfterUse::YES) {
_innerSurface.w = surf->w;
_innerSurface.h = surf->h;
_innerSurface.pitch = surf->pitch;
_innerSurface.format = surf->format;
_innerSurface.setPixels(surf->getPixels());
delete surf;
} else {
copyFrom(*surf);
}
}
ManagedSurface::ManagedSurface(const Surface *surf) :
w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format),
_owner(nullptr), _transparentColor(0), _transparentColorSet(false), _paletteSet(false) {
if (!surf) {
_disposeAfterUse = DisposeAfterUse::YES;
return;
}
_disposeAfterUse = DisposeAfterUse::NO;
copyFrom(*surf);
}
ManagedSurface::~ManagedSurface() {
free();
}
ManagedSurface &ManagedSurface::operator=(const ManagedSurface &surf) {
// Free any current surface
free();
if (surf._disposeAfterUse == DisposeAfterUse::YES) {
// Create a new surface and copy the pixels from the source surface
create(surf.w, surf.h, surf.format);
Common::copy((const byte *)surf.getPixels(), (const byte *)surf.getPixels() +
surf.w * surf.h * surf.format.bytesPerPixel, (byte *)this->getPixels());
} else {
// Source isn't managed, so simply copy its fields
_owner = surf._owner;
_offsetFromOwner = surf._offsetFromOwner;
void *srcPixels = const_cast<void *>(surf._innerSurface.getPixels());
_innerSurface.setPixels(srcPixels);
_innerSurface.w = surf.w;
_innerSurface.h = surf.h;
_innerSurface.pitch = surf.pitch;
this->format = surf.format;
// Copy miscellaneous properties
_transparentColorSet = surf._transparentColorSet;
_transparentColor = surf._transparentColor;
_paletteSet = surf._paletteSet;
memcpy(_palette, surf._palette, sizeof(_palette));
}
return *this;
}
void ManagedSurface::setPixels(void *newPixels) {
free();
_innerSurface.setPixels(newPixels);
}
void ManagedSurface::create(int16 width, int16 height) {
create(width, height, PixelFormat::createFormatCLUT8());
}
void ManagedSurface::create(int16 width, int16 height, const PixelFormat &pixelFormat) {
free();
_innerSurface.create(width, height, pixelFormat);
// For pixel formats with an alpha channel, we need to do a clear
// so that all the pixels will have full alpha (0xff)
if (pixelFormat.aBits() != 0)
clear(0);
_disposeAfterUse = DisposeAfterUse::YES;
markAllDirty();
}
void ManagedSurface::create(ManagedSurface &surf, const Common::Rect &bounds) {
free();
_offsetFromOwner = Common::Point(bounds.left, bounds.top);
_innerSurface.setPixels(surf.getBasePtr(bounds.left, bounds.top));
_innerSurface.pitch = surf.pitch;
_innerSurface.format = surf.format;
_innerSurface.w = bounds.width();
_innerSurface.h = bounds.height();
_owner = &surf;
_disposeAfterUse = DisposeAfterUse::NO;
// Copy miscellaneous properties
_transparentColorSet = surf._transparentColorSet;
_transparentColor = surf._transparentColor;
_paletteSet = surf._paletteSet;
memcpy(_palette, surf._palette, sizeof(_palette));
}
void ManagedSurface::free() {
if (_disposeAfterUse == DisposeAfterUse::YES) {
_innerSurface.free();
} else {
_innerSurface.setPixels(nullptr);
}
_disposeAfterUse = DisposeAfterUse::NO;
_owner = nullptr;
_offsetFromOwner = Common::Point(0, 0);
_transparentColorSet = false;
_paletteSet = false;
}
void ManagedSurface::copyFrom(const ManagedSurface &surf) {
// Surface::copyFrom frees pixel pointer so let's free up ManagedSurface to be coherent
free();
// Copy the surface
_innerSurface.copyFrom(surf._innerSurface);
markAllDirty();
// Pixels data is now owned by us
_disposeAfterUse = DisposeAfterUse::YES;
// Copy miscellaneous properties
_transparentColorSet = surf._transparentColorSet;
_transparentColor = surf._transparentColor;
_paletteSet = surf._paletteSet;
memcpy(_palette, surf._palette, sizeof(_palette));
}
void ManagedSurface::copyFrom(const Surface &surf) {
// Surface::copyFrom frees pixel pointer so let's free up ManagedSurface to be coherent
free();
// Copy the surface
_innerSurface.copyFrom(surf);
markAllDirty();
// Pixels data is now owned by us
_disposeAfterUse = DisposeAfterUse::YES;
// Set miscellaneous properties to sane values
_transparentColorSet = false;
_transparentColor = 0;
_paletteSet = false;
memset(_palette, 0, sizeof(_palette));
}
void ManagedSurface::blitFrom(const Surface &src, const byte *srcPalette) {
blitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Point(0, 0), srcPalette);
}
void ManagedSurface::blitFrom(const Surface &src, const Common::Point &destPos, const byte *srcPalette) {
blitFrom(src, Common::Rect(0, 0, src.w, src.h), destPos, srcPalette);
}
void ManagedSurface::blitFrom(const Surface &src, const Common::Rect &srcRect,
const Common::Point &destPos, const byte *srcPalette) {
blitFromInner(src, srcRect, Common::Rect(destPos.x, destPos.y, destPos.x + srcRect.width(),
destPos.y + srcRect.height()), srcPalette);
}
void ManagedSurface::blitFrom(const Surface &src, const Common::Rect &srcRect,
const Common::Rect &destRect, const byte *srcPalette) {
blitFromInner(src, srcRect, destRect, srcPalette);
}
void ManagedSurface::blitFrom(const ManagedSurface &src) {
blitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Point(0, 0));
}
void ManagedSurface::blitFrom(const ManagedSurface &src, const Common::Point &destPos) {
blitFrom(src, Common::Rect(0, 0, src.w, src.h), destPos);
}
void ManagedSurface::blitFrom(const ManagedSurface &src, const Common::Rect &srcRect,
const Common::Point &destPos) {
if (src._transparentColorSet)
transBlitFrom(src, srcRect, destPos);
else
blitFromInner(src._innerSurface, srcRect, Common::Rect(destPos.x, destPos.y, destPos.x + srcRect.width(),
destPos.y + srcRect.height()), src._paletteSet ? src._palette : nullptr);
}
void ManagedSurface::blitFrom(const ManagedSurface &src, const Common::Rect &srcRect,
const Common::Rect &destRect) {
if (src._transparentColorSet)
transBlitFrom(src, srcRect, destRect, (uint32)-1);
else
blitFromInner(src._innerSurface, srcRect, destRect, src._paletteSet ? src._palette : nullptr);
}
void ManagedSurface::blitFromInner(const Surface &src, const Common::Rect &srcRect,
const Common::Rect &destRect, const byte *srcPalette) {
if (destRect.isEmpty())
return;
const int scaleX = SCALE_THRESHOLD * srcRect.width() / destRect.width();
const int scaleY = SCALE_THRESHOLD * srcRect.height() / destRect.height();
if (!srcRect.isValidRect())
return;
// Copy format so compiler can optimize better.
// This should allow it to do some loop optimizations and condition hoisting as it can tell nothing
// inside of the loop will clobber the format.
Graphics::PixelFormat destFormat = format;
Graphics::PixelFormat srcFormat = src.format;
bool isSameFormat = (destFormat == srcFormat);
if (!isSameFormat) {
assert(destFormat.bytesPerPixel == 1 || destFormat.bytesPerPixel == 2 || destFormat.bytesPerPixel == 3 || destFormat.bytesPerPixel == 4);
assert(srcFormat.bytesPerPixel == 1 || srcFormat.bytesPerPixel == 2 || srcFormat.bytesPerPixel == 3 || srcFormat.bytesPerPixel == 4);
if (srcFormat.bytesPerPixel == 1) {
// When the pixel format differs, the destination must be non-paletted
assert(!destFormat.isCLUT8() && srcPalette);
}
}
uint32 alphaMask = 0;
if (srcFormat.aBits() > 0)
alphaMask = (((static_cast<uint32>(1) << (srcFormat.aBits() - 1)) - 1) * 2 + 1) << srcFormat.aShift;
const bool noScale = scaleX == SCALE_THRESHOLD && scaleY == SCALE_THRESHOLD;
for (int destY = destRect.top, scaleYCtr = 0; destY < destRect.bottom; ++destY, scaleYCtr += scaleY) {
if (destY < 0 || destY >= h)
continue;
const byte *srcP = (const byte *)src.getBasePtr(srcRect.left, scaleYCtr / SCALE_THRESHOLD + srcRect.top);
byte *destP = (byte *)getBasePtr(destRect.left, destY);
// For paletted format, assume the palette is the same and there is no transparency.
// We can thus do a straight copy of the pixels.
if (destFormat.isCLUT8() && noScale) {
int width = srcRect.width();
if (destRect.left + width > w)
width = w - destRect.left;
if (destRect.left < 0) {
srcP -= destRect.left;
destP -= destRect.left;
width += destRect.left;
}
if (width > 0)
Common::copy(srcP, srcP + width, destP);
continue;
}
// Loop through drawing the pixels of the row
for (int destX = destRect.left, xCtr = 0, scaleXCtr = 0; destX < destRect.right; ++destX, ++xCtr, scaleXCtr += scaleX) {
if (destX < 0 || destX >= w)
continue;
const byte *srcVal = &srcP[scaleXCtr / SCALE_THRESHOLD * srcFormat.bytesPerPixel];
byte *destVal = &destP[xCtr * destFormat.bytesPerPixel];
if (destFormat.isCLUT8()) {
*destVal = *srcVal;
continue;
}
uint32 col = 0;
// Use the src's pixel format to split up the source pixel
if (srcFormat.bytesPerPixel == 1)
col = *reinterpret_cast<const uint8 *>(srcVal);
else if (srcFormat.bytesPerPixel == 2)
col = *reinterpret_cast<const uint16 *>(srcVal);
else if (srcFormat.bytesPerPixel == 4)
col = *reinterpret_cast<const uint32 *>(srcVal);
else
col = READ_UINT24(srcVal);
const bool isOpaque = srcFormat.isCLUT8() ? true : ((col & alphaMask) == alphaMask);
const bool isTransparent = srcFormat.isCLUT8() ? false : ((col & alphaMask) == 0);
uint32 destPixel = 0;
// Need to check isOpaque in case alpha mask is 0
if (!isOpaque && isTransparent) {
// Completely transparent, so skip
continue;
} else if (isOpaque && isSameFormat) {
// Completely opaque, same format, copy the entire value
destPixel = col;
} else {
byte rSrc, gSrc, bSrc, aSrc;
byte aDest = 0, rDest = 0, gDest = 0, bDest = 0;
// Different format or partially transparent
if (srcFormat.isCLUT8()) {
rSrc = srcPalette[(col * 3) + 0];
gSrc = srcPalette[(col * 3) + 1];
bSrc = srcPalette[(col * 3) + 2];
aSrc = 0xff;
} else {
srcFormat.colorToARGB(col, aSrc, rSrc, gSrc, bSrc);
}
if (isOpaque) {
aDest = aSrc;
rDest = rSrc;
gDest = gSrc;
bDest = bSrc;
} else {
// Partially transparent, so calculate new pixel colors
uint32 destColor;
if (destFormat.bytesPerPixel == 1)
destColor = *reinterpret_cast<uint8 *>(destVal);
else if (destFormat.bytesPerPixel == 2)
destColor = *reinterpret_cast<uint16 *>(destVal);
else if (destFormat.bytesPerPixel == 4)
destColor = *reinterpret_cast<uint32 *>(destVal);
else
destColor = READ_UINT24(destVal);
destFormat.colorToARGB(destColor, aDest, rDest, gDest, bDest);
if (aDest == 0xff) {
// Opaque target
rDest = static_cast<uint8>((((rDest * (255U - aSrc) + rSrc * aSrc) * (257U * 257U)) >> 24) & 0xff);
gDest = static_cast<uint8>((((gDest * (255U - aSrc) + gSrc * aSrc) * (257U * 257U)) >> 24) & 0xff);
bDest = static_cast<uint8>((((bDest * (255U - aSrc) + bSrc * aSrc) * (257U * 257U)) >> 24) & 0xff);
} else {
// Translucent target
double sAlpha = (double)aSrc / 255.0;
double dAlpha = (double)aDest / 255.0;
dAlpha *= (1.0 - sAlpha);
rDest = static_cast<uint8>((rSrc * sAlpha + rDest * dAlpha) / (sAlpha + dAlpha));
gDest = static_cast<uint8>((gSrc * sAlpha + gDest * dAlpha) / (sAlpha + dAlpha));
bDest = static_cast<uint8>((bSrc * sAlpha + bDest * dAlpha) / (sAlpha + dAlpha));
aDest = static_cast<uint8>(255. * (sAlpha + dAlpha));
}
}
destPixel = destFormat.ARGBToColor(aDest, rDest, gDest, bDest);
}
if (destFormat.bytesPerPixel == 1)
*(uint8 *)destVal = destPixel;
else if (destFormat.bytesPerPixel == 2)
*(uint16 *)destVal = destPixel;
else if (destFormat.bytesPerPixel == 4)
*(uint32 *)destVal = destPixel;
else
WRITE_UINT24(destVal, destPixel);
}
}
addDirtyRect(Common::Rect(0, 0, this->w, this->h));
}
void ManagedSurface::transBlitFrom(const Surface &src, uint32 transColor, bool flipped,
uint32 overrideColor, uint32 srcAlpha, const byte *srcPalette) {
transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(0, 0, this->w, this->h),
transColor, flipped, overrideColor, srcAlpha, nullptr, false, srcPalette);
}
void ManagedSurface::transBlitFrom(const Surface &src, const Common::Point &destPos,
uint32 transColor, bool flipped, uint32 overrideColor, uint32 srcAlpha, const byte *srcPalette) {
transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(destPos.x, destPos.y,
destPos.x + src.w, destPos.y + src.h), transColor, flipped, overrideColor, srcAlpha, nullptr, false, srcPalette);
}
void ManagedSurface::transBlitFrom(const Surface &src, const Common::Point &destPos,
const ManagedSurface &mask, const byte *srcPalette) {
transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(destPos.x, destPos.y,
destPos.x + src.w, destPos.y + src.h), 0, false, 0, 0xff, &mask._innerSurface, true, srcPalette);
}
void ManagedSurface::transBlitFrom(const Surface &src, const Common::Point &destPos,
const Surface &mask, const byte *srcPalette) {
transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(destPos.x, destPos.y,
destPos.x + src.w, destPos.y + src.h), 0, false, 0, 0xff, &mask, true, srcPalette);
}
void ManagedSurface::transBlitFrom(const Surface &src, const Common::Rect &srcRect,
const Common::Point &destPos, uint32 transColor, bool flipped, uint32 overrideColor, uint32 srcAlpha, const byte *srcPalette) {
transBlitFrom(src, srcRect, Common::Rect(destPos.x, destPos.y,
destPos.x + srcRect.width(), destPos.y + srcRect.height()), transColor, flipped, overrideColor, srcAlpha, nullptr, false, srcPalette);
}
void ManagedSurface::transBlitFrom(const Surface &src, const Common::Rect &srcRect, const Common::Rect &destRect, const byte *srcPalette) {
transBlitFrom(src, srcRect, destRect, 0, false, 0, 0xff, nullptr, false, srcPalette);
}
void ManagedSurface::transBlitFrom(const Surface &src, const Common::Rect &srcRect,
const Common::Rect &destRect, uint32 transColor, bool flipped, uint32 overrideColor, uint32 srcAlpha,
const Surface *mask, bool maskOnly, const byte *srcPalette) {
transBlitFromInner(src, srcRect, destRect, transColor, flipped, overrideColor, srcAlpha,
srcPalette, nullptr, mask, maskOnly);
}
void ManagedSurface::transBlitFrom(const ManagedSurface &src, uint32 transColor, bool flipped,
uint32 overrideColor, uint32 srcAlpha) {
transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(0, 0, this->w, this->h),
transColor, flipped, overrideColor, srcAlpha);
}
void ManagedSurface::transBlitFrom(const ManagedSurface &src, const Common::Point &destPos,
uint32 transColor, bool flipped, uint32 overrideColor, uint32 srcAlpha) {
transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(destPos.x, destPos.y,
destPos.x + src.w, destPos.y + src.h), transColor, flipped, overrideColor, srcAlpha);
}
void ManagedSurface::transBlitFrom(const ManagedSurface &src, const Common::Point &destPos,
const ManagedSurface &mask) {
transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(destPos.x, destPos.y,
destPos.x + src.w, destPos.y + src.h), 0, false, 0, 0xff, &mask._innerSurface, true);
}
void ManagedSurface::transBlitFrom(const ManagedSurface &src, const Common::Rect &srcRect,
const Common::Point &destPos, uint32 transColor, bool flipped, uint32 overrideColor, uint32 srcAlpha) {
uint32 tColor = !transColor && src._transparentColorSet ? src._transparentColor : transColor;
transBlitFrom(src, srcRect, Common::Rect(destPos.x, destPos.y, destPos.x + srcRect.width(),
destPos.y + srcRect.height()), tColor, flipped, overrideColor, srcAlpha);
}
void ManagedSurface::transBlitFrom(const ManagedSurface &src, const Common::Rect &srcRect,
const Common::Rect &destRect, uint32 transColor, bool flipped, uint32 overrideColor, uint32 srcAlpha,
const Surface *mask, bool maskOnly) {
if (transColor == (uint32)-1 && src._transparentColorSet)
transColor = src._transparentColor;
const byte *srcPalette = src._paletteSet ? src._palette : nullptr;
const byte *dstPalette = _paletteSet ? _palette : nullptr;
transBlitFromInner(src._innerSurface, srcRect, destRect, transColor, flipped, overrideColor,
srcAlpha, srcPalette, dstPalette, mask, maskOnly);
}
static uint findBestColor(const byte *palette, byte cr, byte cg, byte cb) {
uint bestColor = 0;
double min = 0xFFFFFFFF;
for (uint i = 0; i < 256; ++i) {
int rmean = (*(palette + 3 * i + 0) + cr) / 2;
int r = *(palette + 3 * i + 0) - cr;
int g = *(palette + 3 * i + 1) - cg;
int b = *(palette + 3 * i + 2) - cb;
double dist = sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8));
if (min > dist) {
bestColor = i;
min = dist;
}
}
return bestColor;
}
static byte *createPaletteLookup(const byte *srcPalette, const byte *dstPalette) {
byte *lookup = new byte[256];
for (int i = 0; i < 256; i++) {
byte rSrc = srcPalette[(i * 3) + 0];
byte gSrc = srcPalette[(i * 3) + 1];
byte bSrc = srcPalette[(i * 3) + 2];
byte rDst = dstPalette[(i * 3) + 0];
byte gDst = dstPalette[(i * 3) + 1];
byte bDst = dstPalette[(i * 3) + 2];
if (rSrc == rDst && gSrc == gDst && bSrc == bDst) {
lookup[i] = i;
} else {
lookup[i] = findBestColor(dstPalette, rSrc, gSrc, bSrc);
}
}
return lookup;
}
template<typename TSRC, typename TDEST>
void transBlitPixel(TSRC srcVal, TDEST &destVal, const Graphics::PixelFormat &srcFormat, const Graphics::PixelFormat &destFormat,
uint32 overrideColor, uint32 srcAlpha, const byte *srcPalette, const byte *lookup) {
// Decode and re-encode each pixel
byte aSrc, rSrc, gSrc, bSrc;
if (srcFormat.isCLUT8()) {
assert(srcPalette != nullptr); // Catch the cases when palette is missing
// Get the palette color
rSrc = srcPalette[(srcVal * 3) + 0];
gSrc = srcPalette[(srcVal * 3) + 1];
bSrc = srcPalette[(srcVal * 3) + 2];
aSrc = 0xff;
} else {
srcFormat.colorToARGB(srcVal, aSrc, rSrc, gSrc, bSrc);
}
if (srcAlpha != 0xff) {
aSrc = aSrc * srcAlpha / 255;
}
byte aDest, rDest, gDest, bDest;
if (aSrc == 0) {
// Completely transparent, so skip
return;
} else if (aSrc == 0xff) {
// Completely opaque, so copy RGB values over
rDest = rSrc;
gDest = gSrc;
bDest = bSrc;
aDest = 0xff;
} else {
// Partially transparent, so calculate new pixel colors
destFormat.colorToARGB(destVal, aDest, rDest, gDest, bDest);
double sAlpha = (double)aSrc / 255.0;
double dAlpha = (double)aDest / 255.0;
dAlpha *= (1.0 - sAlpha);
rDest = static_cast<uint8>((rSrc * sAlpha + rDest * dAlpha) / (sAlpha + dAlpha));
gDest = static_cast<uint8>((gSrc * sAlpha + gDest * dAlpha) / (sAlpha + dAlpha));
bDest = static_cast<uint8>((bSrc * sAlpha + bDest * dAlpha) / (sAlpha + dAlpha));
aDest = static_cast<uint8>(255. * (sAlpha + dAlpha));
}
destVal = destFormat.ARGBToColor(aDest, rDest, gDest, bDest);
}
template<>
void transBlitPixel<byte, byte>(byte srcVal, byte &destVal, const Graphics::PixelFormat &srcFormat, const Graphics::PixelFormat &destFormat,
uint32 overrideColor, uint32 srcAlpha, const byte *srcPalette, const byte *lookup) {
if (srcAlpha == 0) {
// Completely transparent, so skip
return;
}
destVal = overrideColor ? overrideColor : srcVal;
if (lookup)
destVal = lookup[destVal];
}
template<typename TSRC, typename TDEST>
void transBlit(const Surface &src, const Common::Rect &srcRect, ManagedSurface &dest, const Common::Rect &destRect,
TSRC transColor, bool flipped, uint32 overrideColor, uint32 srcAlpha, const byte *srcPalette,
const byte *dstPalette, const Surface *mask, bool maskOnly) {
int scaleX = SCALE_THRESHOLD * srcRect.width() / destRect.width();
int scaleY = SCALE_THRESHOLD * srcRect.height() / destRect.height();
byte rst = 0, gst = 0, bst = 0, rdt = 0, gdt = 0, bdt = 0;
byte r = 0, g = 0, b = 0;
byte *lookup = nullptr;
if (srcPalette && dstPalette)
lookup = createPaletteLookup(srcPalette, dstPalette);
// If we're dealing with a 32-bit source surface, we need to split up the RGB,
// since we'll want to find matching RGB pixels irrespective of the alpha
bool isSrcTrans32 = src.format.aBits() != 0 && transColor != (uint32)-1 && transColor > 0;
if (isSrcTrans32) {
src.format.colorToRGB(transColor, rst, gst, bst);
}
bool isDestTrans32 = dest.format.aBits() != 0 && dest.hasTransparentColor();
if (isDestTrans32) {
dest.format.colorToRGB(dest.getTransparentColor(), rdt, gdt, bdt);
}
// Loop through drawing output lines
for (int destY = destRect.top, scaleYCtr = 0; destY < destRect.bottom; ++destY, scaleYCtr += scaleY) {
if (destY < 0 || destY >= dest.h)
continue;
const TSRC *srcLine = (const TSRC *)src.getBasePtr(srcRect.left, scaleYCtr / SCALE_THRESHOLD + srcRect.top);
const TSRC *mskLine = nullptr;
if (mask)
mskLine = (const TSRC *)mask->getBasePtr(srcRect.left, scaleYCtr / SCALE_THRESHOLD + srcRect.top);
TDEST *destLine = (TDEST *)dest.getBasePtr(destRect.left, destY);
// Loop through drawing the pixels of the row
for (int destX = destRect.left, xCtr = 0, scaleXCtr = 0; destX < destRect.right; ++destX, ++xCtr, scaleXCtr += scaleX) {
if (destX < 0 || destX >= dest.w)
continue;
TSRC srcVal = srcLine[flipped ? src.w - scaleXCtr / SCALE_THRESHOLD - 1 : scaleXCtr / SCALE_THRESHOLD];
TDEST &destVal = destLine[xCtr];
dest.format.colorToRGB(destVal, r, g, b);
// Check if dest pixel is transparent
bool isDestPixelTrans = false;
if (isDestTrans32) {
dest.format.colorToRGB(destVal, r, g, b);
if (rdt == r && gdt == g && bdt == b)
isDestPixelTrans = true;
} else if (dest.hasTransparentColor()) {
isDestPixelTrans = destVal == dest.getTransparentColor();
}
if (isSrcTrans32 && !maskOnly) {
src.format.colorToRGB(srcVal, r, g, b);
if (rst == r && gst == g && bst == b)
continue;
} else if (srcVal == transColor && !maskOnly)
continue;
if (mask) {
TSRC mskVal = mskLine[flipped ? src.w - scaleXCtr / SCALE_THRESHOLD - 1 : scaleXCtr / SCALE_THRESHOLD];
if (!mskVal)
continue;
if (isDestPixelTrans)
// Remove transparent color on dest so it isn't alpha blended
destVal = 0;
transBlitPixel<TSRC, TDEST>(srcVal, destVal, src.format, dest.format, overrideColor, mskVal, srcPalette, lookup);
} else {
if (isDestPixelTrans)
// Remove transparent color on dest so it isn't alpha blended
destVal = 0;
transBlitPixel<TSRC, TDEST>(srcVal, destVal, src.format, dest.format, overrideColor, srcAlpha, srcPalette, lookup);
}
}
}
delete[] lookup;
}
#define HANDLE_BLIT(SRC_BYTES, DEST_BYTES, SRC_TYPE, DEST_TYPE) \
if (src.format.bytesPerPixel == SRC_BYTES && format.bytesPerPixel == DEST_BYTES) \
transBlit<SRC_TYPE, DEST_TYPE>(src, srcRect, *this, destRect, transColor, flipped, overrideColor, srcAlpha, srcPalette, dstPalette, mask, maskOnly); \
else
void ManagedSurface::transBlitFromInner(const Surface &src, const Common::Rect &srcRect,
const Common::Rect &destRect, uint32 transColor, bool flipped, uint32 overrideColor,
uint32 srcAlpha, const byte *srcPalette, const byte *dstPalette, const Surface *mask, bool maskOnly) {
if (src.w == 0 || src.h == 0 || destRect.width() == 0 || destRect.height() == 0)
return;
if (mask) {
if (mask->w != src.w || mask->h != src.h)
error("Surface::transBlitFrom: mask dimensions do not match src");
}
HANDLE_BLIT(1, 1, uint8, uint8)
HANDLE_BLIT(1, 2, uint8, uint16)
HANDLE_BLIT(1, 4, uint8, uint32)
HANDLE_BLIT(2, 1, uint16, uint8)
HANDLE_BLIT(2, 2, uint16, uint16)
HANDLE_BLIT(2, 4, uint16, uint32)
HANDLE_BLIT(4, 1, uint32, uint8)
HANDLE_BLIT(4, 2, uint32, uint16)
HANDLE_BLIT(4, 4, uint32, uint32)
error("Surface::transBlitFrom: bytesPerPixel must be 1, 2, or 4");
// Mark the affected area
addDirtyRect(destRect);
}
#undef HANDLE_BLIT
Common::Rect ManagedSurface::blendBlitTo(ManagedSurface &target,
const int posX, const int posY,
const int flipping,
const Common::Rect *srcRect,
const uint colorMod,
const int width, const int height,
const TSpriteBlendMode blend,
const AlphaType alphaType) {
return blendBlitTo(*target.surfacePtr(), posX, posY, flipping, srcRect, colorMod, width, height, blend, alphaType);
}
Common::Rect ManagedSurface::blendBlitTo(Surface &target,
const int posX, const int posY,
const int flipping,
const Common::Rect *srcRect,
const uint colorMod,
const int width, const int height,
const TSpriteBlendMode blend,
const AlphaType alphaType) {
Common::Rect srcArea = srcRect ? *srcRect : Common::Rect(0, 0, w, h);
Common::Rect dstArea(posX, posY, posX + (width == -1 ? srcArea.width() : width), posY + (height == -1 ? srcArea.height() : height));
if (!isBlendBlitPixelFormatSupported(format, target.format)) {
warning("ManagedSurface::blendBlitTo only accepts RGBA32!");
return Common::Rect(0, 0, 0, 0);
}
// Alpha is zero
if ((colorMod & MS_ARGB(255, 0, 0, 0)) == 0) return Common::Rect(0, 0, 0, 0);
const int scaleX = BlendBlit::getScaleFactor(srcArea.width(), dstArea.width());
const int scaleY = BlendBlit::getScaleFactor(srcArea.height(), dstArea.height());
int scaleXoff = 0, scaleYoff = 0;
if (dstArea.left < 0) {
scaleXoff = (-dstArea.left * scaleX) % BlendBlit::SCALE_THRESHOLD;
srcArea.left += -dstArea.left * scaleX / BlendBlit::SCALE_THRESHOLD;
dstArea.left = 0;
}
if (dstArea.top < 0) {
scaleYoff = (-dstArea.top * scaleY) % BlendBlit::SCALE_THRESHOLD;
srcArea.top += -dstArea.top * scaleY / BlendBlit::SCALE_THRESHOLD;
dstArea.top = 0;
}
if (dstArea.right > target.w) {
srcArea.right -= (dstArea.right - target.w) * scaleX / BlendBlit::SCALE_THRESHOLD;
dstArea.right = target.w;
}
if (dstArea.bottom > target.h) {
srcArea.bottom -= (dstArea.bottom - target.h) * scaleY / BlendBlit::SCALE_THRESHOLD;
dstArea.bottom = target.h;
}
if (flipping & FLIP_H) {
int tmp_w = srcArea.width();
srcArea.left = w - srcArea.right;
srcArea.right = srcArea.left + tmp_w;
scaleXoff = (BlendBlit::SCALE_THRESHOLD - (scaleXoff + dstArea.width() * scaleX)) % BlendBlit::SCALE_THRESHOLD;
}
if (flipping & FLIP_V) {
int tmp_h = srcArea.height();
srcArea.top = h - srcArea.bottom;
srcArea.bottom = srcArea.top + tmp_h;
scaleYoff = (BlendBlit::SCALE_THRESHOLD - (scaleYoff + dstArea.height() * scaleY)) % BlendBlit::SCALE_THRESHOLD;
}
if (!dstArea.isEmpty() && !srcArea.isEmpty()) {
BlendBlit::blit(
(byte *)target.getBasePtr(0, 0),
(const byte *)getBasePtr(srcArea.left, srcArea.top),
target.pitch, pitch,
dstArea.left, dstArea.top,
dstArea.width(), dstArea.height(),
scaleX, scaleY,
scaleXoff, scaleYoff,
colorMod, flipping,
blend, alphaType);
}
if (dstArea.isEmpty()) return Common::Rect(0, 0, 0, 0);
else return Common::Rect(0, 0, dstArea.width(), dstArea.height());
}
void ManagedSurface::markAllDirty() {
addDirtyRect(Common::Rect(0, 0, this->w, this->h));
}
void ManagedSurface::addDirtyRect(const Common::Rect &r) {
if (_owner) {
Common::Rect bounds = r;
bounds.clip(Common::Rect(0, 0, this->w, this->h));
bounds.translate(_offsetFromOwner.x, _offsetFromOwner.y);
_owner->addDirtyRect(bounds);
}
}
void ManagedSurface::clear(uint32 color) {
if (!empty())
fillRect(getBounds(), color);
}
void ManagedSurface::grabPalette(byte *colors, uint start, uint num) const {
assert(start < 256 && (start + num) <= 256);
memcpy(colors, _palette + 3 * start, 3 * num);
}
void ManagedSurface::setPalette(const byte *colors, uint start, uint num) {
assert(start < 256 && (start + num) <= 256);
memcpy(_palette + 3 * start, colors, 3 * num);
_paletteSet = true;
if (_owner)
_owner->setPalette(colors, start, num);
}
} // End of namespace Graphics