|
|
|
|
/**
|
|
|
|
|
* @file cel_render.cpp
|
|
|
|
|
*
|
|
|
|
|
* CEL rendering.
|
|
|
|
|
*/
|
|
|
|
|
#include "engine/render/cel_render.hpp"
|
|
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
|
|
#include "engine/cel_header.hpp"
|
|
|
|
|
#include "engine/palette.h"
|
|
|
|
|
#include "engine/render/common_impl.h"
|
|
|
|
|
#include "engine/render/scrollrt.h"
|
|
|
|
|
#include "engine/trn.hpp"
|
|
|
|
|
#include "options.h"
|
|
|
|
|
#include "utils/attributes.h"
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
constexpr bool IsCelTransparent(uint8_t control)
|
|
|
|
|
{
|
|
|
|
|
constexpr uint8_t CelTransparentMin = 0x80;
|
|
|
|
|
return control >= CelTransparentMin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr uint8_t GetCelTransparentWidth(uint8_t control)
|
|
|
|
|
{
|
|
|
|
|
return -static_cast<std::int8_t>(control);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT BlitCommand CelGetBlitCommand(const uint8_t *src)
|
|
|
|
|
{
|
|
|
|
|
const uint8_t control = *src++;
|
|
|
|
|
if (IsCelTransparent(control))
|
|
|
|
|
return BlitCommand { BlitType::Transparent, src, GetCelTransparentWidth(control), 0 };
|
|
|
|
|
return BlitCommand { BlitType::Pixels, src + control, control, 0 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <bool North, bool West, bool South, bool East, bool SkipColorIndexZero>
|
|
|
|
|
uint8_t *RenderCelOutlinePixelsCheckFirstColumn(
|
|
|
|
|
uint8_t *dst, int dstPitch, int dstX,
|
|
|
|
|
const uint8_t *src, uint8_t width, uint8_t color)
|
|
|
|
|
{
|
|
|
|
|
if (dstX == -1) {
|
|
|
|
|
RenderOutlineForPixel</*North=*/false, /*West=*/false, /*South=*/false, East, SkipColorIndexZero>(
|
|
|
|
|
dst++, dstPitch, *src++, color);
|
|
|
|
|
--width;
|
|
|
|
|
}
|
|
|
|
|
if (width > 0) {
|
|
|
|
|
RenderOutlineForPixel<North, /*West=*/false, South, East, SkipColorIndexZero>(dst++, dstPitch, *src++, color);
|
|
|
|
|
--width;
|
|
|
|
|
}
|
|
|
|
|
if (width > 0) {
|
|
|
|
|
RenderOutlineForPixels<North, West, South, East, SkipColorIndexZero>(dst, dstPitch, width, src, color);
|
|
|
|
|
dst += width;
|
|
|
|
|
}
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <bool North, bool West, bool South, bool East, bool SkipColorIndexZero>
|
|
|
|
|
uint8_t *RenderCelOutlinePixelsCheckLastColumn(
|
|
|
|
|
uint8_t *dst, int dstPitch, int dstX, int dstW,
|
|
|
|
|
const uint8_t *src, uint8_t width, uint8_t color)
|
|
|
|
|
{
|
|
|
|
|
const bool lastPixel = dstX < dstW && width >= 1;
|
|
|
|
|
const bool oobPixel = dstX + width > dstW;
|
|
|
|
|
const int numSpecialPixels = (lastPixel ? 1 : 0) + (oobPixel ? 1 : 0);
|
|
|
|
|
if (width > numSpecialPixels) {
|
|
|
|
|
width -= numSpecialPixels;
|
|
|
|
|
RenderOutlineForPixels<North, West, South, East, SkipColorIndexZero>(dst, dstPitch, width, src, color);
|
|
|
|
|
src += width;
|
|
|
|
|
dst += width;
|
|
|
|
|
}
|
|
|
|
|
if (lastPixel)
|
|
|
|
|
RenderOutlineForPixel<North, West, South, /*East=*/false, SkipColorIndexZero>(dst++, dstPitch, *src++, color);
|
|
|
|
|
if (oobPixel)
|
|
|
|
|
RenderOutlineForPixel</*North=*/false, West, /*South=*/false, /*East=*/false, SkipColorIndexZero>(dst++, dstPitch, *src++, color);
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <bool SkipColorIndexZero, bool North, bool West, bool South, bool East, bool CheckFirstColumn, bool CheckLastColumn>
|
|
|
|
|
uint8_t *RenderCelOutlinePixels(
|
|
|
|
|
uint8_t *dst, int dstPitch, int dstX, int dstW,
|
|
|
|
|
const uint8_t *src, uint8_t width, uint8_t color)
|
|
|
|
|
{
|
|
|
|
|
if (CheckFirstColumn && dstX <= 0) {
|
|
|
|
|
return RenderCelOutlinePixelsCheckFirstColumn<North, West, South, East, SkipColorIndexZero>(
|
|
|
|
|
dst, dstPitch, dstX, src, width, color);
|
|
|
|
|
}
|
|
|
|
|
if (CheckLastColumn && dstX + width >= dstW) {
|
|
|
|
|
return RenderCelOutlinePixelsCheckLastColumn<North, West, South, East, SkipColorIndexZero>(
|
|
|
|
|
dst, dstPitch, dstX, dstW, src, width, color);
|
|
|
|
|
}
|
|
|
|
|
RenderOutlineForPixels<North, West, South, East, SkipColorIndexZero>(dst, dstPitch, width, src, color);
|
|
|
|
|
return dst + width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <bool SkipColorIndexZero, bool North, bool West, bool South, bool East,
|
|
|
|
|
bool ClipWidth = false, bool CheckFirstColumn = false, bool CheckLastColumn = false>
|
|
|
|
|
const uint8_t *RenderCelOutlineRowClipped( // NOLINT(readability-function-cognitive-complexity,misc-no-recursion)
|
|
|
|
|
const Surface &out, Point position, const uint8_t *src, ClipX clipX, uint8_t color)
|
|
|
|
|
{
|
|
|
|
|
int_fast16_t remainingWidth = clipX.width;
|
|
|
|
|
uint8_t v;
|
|
|
|
|
|
|
|
|
|
auto *dst = &out[position];
|
|
|
|
|
const auto dstPitch = out.pitch();
|
|
|
|
|
const auto dstW = out.w();
|
|
|
|
|
|
|
|
|
|
if (ClipWidth) {
|
|
|
|
|
auto remainingLeftClip = clipX.left;
|
|
|
|
|
while (remainingLeftClip > 0) {
|
|
|
|
|
v = static_cast<uint8_t>(*src++);
|
|
|
|
|
if (!IsCelTransparent(v)) {
|
|
|
|
|
if (v > remainingLeftClip) {
|
|
|
|
|
RenderCelOutlinePixels<SkipColorIndexZero, North, West, South, East, CheckFirstColumn, CheckLastColumn>(
|
|
|
|
|
dst, dstPitch, position.x, dstW, src, v - remainingLeftClip, color);
|
|
|
|
|
}
|
|
|
|
|
src += v;
|
|
|
|
|
} else {
|
|
|
|
|
v = GetCelTransparentWidth(v);
|
|
|
|
|
}
|
|
|
|
|
remainingLeftClip -= v;
|
|
|
|
|
}
|
|
|
|
|
dst -= static_cast<int>(remainingLeftClip);
|
|
|
|
|
position.x -= static_cast<int>(remainingLeftClip);
|
|
|
|
|
remainingWidth += remainingLeftClip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (remainingWidth > 0) {
|
|
|
|
|
v = static_cast<uint8_t>(*src++);
|
|
|
|
|
if (!IsCelTransparent(v)) {
|
|
|
|
|
dst = RenderCelOutlinePixels<SkipColorIndexZero, North, West, South, East, CheckFirstColumn, CheckLastColumn>(
|
|
|
|
|
dst, dstPitch, position.x, dstW, src,
|
|
|
|
|
std::min(remainingWidth, static_cast<int_fast16_t>(v)), color);
|
|
|
|
|
src += v;
|
|
|
|
|
} else {
|
|
|
|
|
v = GetCelTransparentWidth(v);
|
|
|
|
|
dst += v;
|
|
|
|
|
}
|
|
|
|
|
remainingWidth -= v;
|
|
|
|
|
position.x += v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
src = SkipRestOfLine<CelGetBlitCommand>(src, clipX.right + remainingWidth);
|
|
|
|
|
|
|
|
|
|
return src;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <bool SkipColorIndexZero>
|
|
|
|
|
void RenderCelOutlineClippedY(const Surface &out, Point position, RenderSrcBackwards src, uint8_t color) // NOLINT(readability-function-cognitive-complexity)
|
|
|
|
|
{
|
|
|
|
|
// Skip the bottom clipped lines.
|
|
|
|
|
const auto dstHeight = out.h();
|
|
|
|
|
SkipLinesForRenderBackwards<CelGetBlitCommand>(position, src, dstHeight);
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const ClipX clipX = { 0, 0, static_cast<decltype(ClipX {}.width)>(src.width) };
|
|
|
|
|
|
|
|
|
|
if (position.y == dstHeight) {
|
|
|
|
|
// After-bottom line - can only draw north.
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (position.y + 1 == dstHeight) {
|
|
|
|
|
// Bottom line - cannot draw south.
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (position.y > 0 && src.begin != src.end) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (position.y == 0) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (position.y == -1) {
|
|
|
|
|
// Special case: the top of the sprite is 1px below the last line, render just the outline above.
|
|
|
|
|
RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <bool SkipColorIndexZero>
|
|
|
|
|
void RenderCelOutlineClippedXY(const Surface &out, Point position, RenderSrcBackwards src, uint8_t color) // NOLINT(readability-function-cognitive-complexity)
|
|
|
|
|
{
|
|
|
|
|
// Skip the bottom clipped lines.
|
|
|
|
|
const auto dstHeight = out.h();
|
|
|
|
|
SkipLinesForRenderBackwards<CelGetBlitCommand>(position, src, dstHeight);
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ClipX clipX = CalculateClipX(position.x, src.width, out);
|
|
|
|
|
if (clipX.width < 0)
|
|
|
|
|
return;
|
|
|
|
|
if (clipX.left > 0) {
|
|
|
|
|
--clipX.left, ++clipX.width;
|
|
|
|
|
} else if (clipX.right > 0) {
|
|
|
|
|
--clipX.right, ++clipX.width;
|
|
|
|
|
}
|
|
|
|
|
position.x += static_cast<int>(clipX.left);
|
|
|
|
|
|
|
|
|
|
if (position.y == dstHeight) {
|
|
|
|
|
// After-bottom line - can only draw north.
|
|
|
|
|
if (position.x <= 0) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(out, position, src.begin, clipX, color);
|
|
|
|
|
} else if (position.x + clipX.width >= out.w()) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(out, position, src.begin, clipX, color);
|
|
|
|
|
} else {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false,
|
|
|
|
|
/*ClipWidth=*/true>(out, position, src.begin, clipX, color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (position.y + 1 == dstHeight) {
|
|
|
|
|
// Bottom line - cannot draw south.
|
|
|
|
|
if (position.x <= 0) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
} else if (position.x + clipX.width >= out.w()) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
} else {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
}
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (position.x <= 0) {
|
|
|
|
|
while (position.y > 0 && src.begin != src.end) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
} else if (position.x + clipX.width >= out.w()) {
|
|
|
|
|
while (position.y > 0 && src.begin != src.end) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
while (position.y > 0 && src.begin != src.end) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (position.y == 0) {
|
|
|
|
|
if (position.x <= 0) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
} else if (position.x + clipX.width >= out.w()) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
} else {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true,
|
|
|
|
|
/*ClipWidth=*/true>(
|
|
|
|
|
out, position, src.begin, clipX, color);
|
|
|
|
|
}
|
|
|
|
|
--position.y;
|
|
|
|
|
}
|
|
|
|
|
if (src.begin == src.end)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (position.y == -1) {
|
|
|
|
|
// After-bottom line - can only draw south.
|
|
|
|
|
if (position.x <= 0) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(out, position, src.begin, clipX, color);
|
|
|
|
|
} else if (position.x + clipX.width >= out.w()) {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false,
|
|
|
|
|
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(out, position, src.begin, clipX, color);
|
|
|
|
|
} else {
|
|
|
|
|
src.begin = RenderCelOutlineRowClipped<SkipColorIndexZero, /*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false,
|
|
|
|
|
/*ClipWidth=*/true>(out, position, src.begin, clipX, color);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <bool SkipColorIndexZero>
|
|
|
|
|
void RenderCelOutline(const Surface &out, Point position, const uint8_t *src, std::size_t srcSize,
|
|
|
|
|
std::size_t srcWidth, uint8_t color)
|
|
|
|
|
{
|
|
|
|
|
RenderSrcBackwards srcForBackwards { src, src + srcSize, static_cast<uint_fast16_t>(srcWidth) };
|
|
|
|
|
if (position.x > 0 && position.x + static_cast<int>(srcWidth) < static_cast<int>(out.w())) {
|
|
|
|
|
RenderCelOutlineClippedY<SkipColorIndexZero>(out, position, srcForBackwards, color);
|
|
|
|
|
} else {
|
|
|
|
|
RenderCelOutlineClippedXY<SkipColorIndexZero>(out, position, srcForBackwards, color);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Blit CEL sprite to the given buffer, checks for drawing outside the buffer.
|
|
|
|
|
* @param out Target buffer
|
|
|
|
|
* @param position Target buffer coordinate
|
|
|
|
|
* @param pRLEBytes CEL pixel stream (run-length encoded)
|
|
|
|
|
* @param nDataSize Size of CEL in bytes
|
|
|
|
|
* @param nWidth Width of sprite in pixels
|
|
|
|
|
*/
|
|
|
|
|
void CelBlitSafeTo(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth)
|
|
|
|
|
{
|
|
|
|
|
assert(pRLEBytes != nullptr);
|
|
|
|
|
DoRenderBackwards</*TransparentCommandCanCrossLines=*/false, CelGetBlitCommand>(
|
|
|
|
|
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitDirect {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Same as CelBlitLightSafe, with blended transparency applied
|
|
|
|
|
* @param out Target buffer
|
|
|
|
|
* @param position Target buffer coordinate
|
|
|
|
|
* @param pRLEBytes CEL pixel stream (run-length encoded)
|
|
|
|
|
* @param nDataSize Size of CEL in bytes
|
|
|
|
|
* @param nWidth Width of sprite in pixels
|
|
|
|
|
* @param tbl Palette translation table
|
|
|
|
|
*/
|
|
|
|
|
void CelBlitLightBlendedSafeTo(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, const uint8_t *tbl)
|
|
|
|
|
{
|
|
|
|
|
assert(pRLEBytes != nullptr);
|
|
|
|
|
if (tbl == nullptr)
|
|
|
|
|
tbl = &LightTables[LightTableIndex * 256];
|
|
|
|
|
|
|
|
|
|
DoRenderBackwards</*TransparentCommandCanCrossLines=*/false, CelGetBlitCommand>(
|
|
|
|
|
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitBlendedWithMap { tbl });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RenderCelWithLightTable(const Surface &out, Point position, const byte *src, std::size_t srcSize, std::size_t srcWidth, const uint8_t *tbl)
|
|
|
|
|
{
|
|
|
|
|
DoRenderBackwards</*TransparentCommandCanCrossLines=*/false, CelGetBlitCommand>(
|
|
|
|
|
out, position, reinterpret_cast<const uint8_t *>(src), srcSize, srcWidth, BlitWithMap { tbl });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Blit CEL sprite, and apply lighting, to the given buffer, checks for drawing outside the buffer
|
|
|
|
|
* @param out Target buffer
|
|
|
|
|
* @param position Target buffer coordinate
|
|
|
|
|
* @param pRLEBytes CEL pixel stream (run-length encoded)
|
|
|
|
|
* @param nDataSize Size of CEL in bytes
|
|
|
|
|
* @param nWidth Width of sprite in pixels
|
|
|
|
|
* @param tbl Palette translation table
|
|
|
|
|
*/
|
|
|
|
|
void CelBlitLightSafeTo(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *tbl)
|
|
|
|
|
{
|
|
|
|
|
assert(pRLEBytes != nullptr);
|
|
|
|
|
if (tbl == nullptr)
|
|
|
|
|
tbl = &LightTables[LightTableIndex * 256];
|
|
|
|
|
RenderCelWithLightTable(out, position, pRLEBytes, nDataSize, nWidth, tbl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void CelDrawTo(const Surface &out, Point position, CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const auto *pRLEBytes = CelGetFrame(cel.Data(), frame, &nDataSize);
|
|
|
|
|
CelBlitSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelClippedDrawTo(const Surface &out, Point position, CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const auto *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
|
|
|
|
|
|
|
|
|
|
CelBlitSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelDrawLightTo(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *tbl)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const auto *pRLEBytes = CelGetFrame(cel.Data(), frame, &nDataSize);
|
|
|
|
|
|
|
|
|
|
if (LightTableIndex != 0 || tbl != nullptr)
|
|
|
|
|
CelBlitLightSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame), tbl);
|
|
|
|
|
else
|
|
|
|
|
CelBlitSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelClippedDrawLightTo(const Surface &out, Point position, CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const auto *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
|
|
|
|
|
|
|
|
|
|
if (LightTableIndex != 0)
|
|
|
|
|
CelBlitLightSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame), nullptr);
|
|
|
|
|
else
|
|
|
|
|
CelBlitSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelDrawLightRedTo(const Surface &out, Point position, CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const auto *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
|
|
|
|
|
RenderCelWithLightTable(out, position, pRLEBytes, nDataSize, cel.Width(frame), GetInfravisionTRN());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelDrawItem(const Item &item, const Surface &out, Point position, CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
bool usable = item._iStatFlag;
|
|
|
|
|
if (!usable) {
|
|
|
|
|
CelDrawLightRedTo(out, position, cel, frame);
|
|
|
|
|
} else {
|
|
|
|
|
CelClippedDrawTo(out, position, cel, frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelClippedBlitLightTransTo(const Surface &out, Point position, CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
|
|
|
|
|
|
|
|
|
|
if (cel_transparency_active) {
|
|
|
|
|
CelBlitLightBlendedSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame), nullptr);
|
|
|
|
|
} else if (LightTableIndex != 0)
|
|
|
|
|
CelBlitLightSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame), nullptr);
|
|
|
|
|
else
|
|
|
|
|
CelBlitSafeTo(out, position, pRLEBytes, nDataSize, cel.Width(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelDrawUnsafeTo(const Surface &out, Point position, CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
int srcSize;
|
|
|
|
|
const auto *srcBegin = reinterpret_cast<const uint8_t *>(CelGetFrame(cel.Data(), frame, &srcSize));
|
|
|
|
|
RenderSrcBackwards src { srcBegin, srcBegin + srcSize, cel.Width(frame) };
|
|
|
|
|
DoRenderBackwardsClipY</*TransparentCommandCanCrossLines=*/false, &CelGetBlitCommand>(
|
|
|
|
|
out, position, src, BlitDirect {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelBlitOutlineTo(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame, bool skipColorIndexZero)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const uint8_t *src = reinterpret_cast<const uint8_t *>(CelGetFrameClipped(cel.Data(), frame, &nDataSize));
|
|
|
|
|
if (skipColorIndexZero)
|
|
|
|
|
RenderCelOutline<true>(out, position, src, nDataSize, cel.Width(frame), col);
|
|
|
|
|
else
|
|
|
|
|
RenderCelOutline<false>(out, position, src, nDataSize, cel.Width(frame), col);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::pair<int, int> MeasureSolidHorizontalBounds(CelSprite cel, int frame)
|
|
|
|
|
{
|
|
|
|
|
int nDataSize;
|
|
|
|
|
const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
|
|
|
|
|
const auto *end = &src[nDataSize];
|
|
|
|
|
const int celWidth = cel.Width(frame);
|
|
|
|
|
|
|
|
|
|
int xBegin = celWidth;
|
|
|
|
|
int xEnd = 0;
|
|
|
|
|
while (src < end) {
|
|
|
|
|
int xCur = 0;
|
|
|
|
|
while (xCur < celWidth) {
|
|
|
|
|
const auto val = static_cast<uint8_t>(*src++);
|
|
|
|
|
if (IsCelTransparent(val)) {
|
|
|
|
|
const int width = GetCelTransparentWidth(val);
|
|
|
|
|
xCur += width;
|
|
|
|
|
} else {
|
|
|
|
|
xBegin = std::min(xBegin, xCur);
|
|
|
|
|
xCur += val;
|
|
|
|
|
xEnd = std::max(xEnd, xCur);
|
|
|
|
|
src += val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (xBegin == 0 && xEnd == celWidth)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return { xBegin, xEnd };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CelApplyTrans(byte *p, const std::array<uint8_t, 256> &translation)
|
|
|
|
|
{
|
|
|
|
|
assert(p != nullptr);
|
|
|
|
|
const uint32_t numFrames = LoadLE32(p);
|
|
|
|
|
const byte *frameOffsets = p + 4;
|
|
|
|
|
p += 4 * (2 + static_cast<size_t>(numFrames));
|
|
|
|
|
|
|
|
|
|
uint32_t frameEnd = LoadLE32(&frameOffsets[0]);
|
|
|
|
|
for (uint32_t i = 0; i < numFrames; ++i) {
|
|
|
|
|
const uint32_t frameBegin = frameEnd;
|
|
|
|
|
frameEnd = LoadLE32(&frameOffsets[4 * (static_cast<size_t>(i) + 1)]);
|
|
|
|
|
|
|
|
|
|
const byte *end = p + (frameEnd - frameBegin);
|
|
|
|
|
const bool frameHasHeader = static_cast<uint8_t>(*p) == 0;
|
|
|
|
|
if (frameHasHeader) {
|
|
|
|
|
constexpr uint32_t FrameHeaderSize = 5 * 2;
|
|
|
|
|
p += FrameHeaderSize;
|
|
|
|
|
}
|
|
|
|
|
while (p != end) {
|
|
|
|
|
const auto val = static_cast<uint8_t>(*p++);
|
|
|
|
|
if (IsCelTransparent(val)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
for (unsigned i = 0; i < val; ++i) {
|
|
|
|
|
const auto color = static_cast<uint8_t>(*p);
|
|
|
|
|
*p++ = static_cast<byte>(translation[color]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace devilution
|