You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

188 lines
6.1 KiB

#include "engine/render/pcx_render.hpp"
#include <algorithm>
#include <cstring>
#include "engine/render/common_impl.h"
#include "utils/log.hpp"
namespace devilution {
namespace {
constexpr uint8_t PcxMaxSinglePixel = 0xBF;
constexpr uint8_t PcxRunLengthMask = 0x3F;
const uint8_t *SkipRestOfPcxLine(const uint8_t *src, unsigned remainingWidth)
{
while (remainingWidth > 0) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
--remainingWidth;
} else {
remainingWidth -= value & PcxRunLengthMask;
++src;
}
}
return src;
}
template <bool UseColorMap, bool HasTransparency>
void BlitPcxClipY(const Surface &out, Point position, const uint8_t *src, unsigned srcWidth, unsigned srcHeight, const uint8_t *colorMap, uint8_t transparentColor)
{
if (position.y >= out.h())
return;
while (position.y < 0 && srcHeight != 0) {
src = SkipRestOfPcxLine(src, srcWidth);
++position.y;
--srcHeight;
}
srcHeight = static_cast<unsigned>(std::min<int>(out.h() - position.y, srcHeight));
const auto dstSkip = static_cast<unsigned>(out.pitch() - srcWidth);
const unsigned srcSkip = srcWidth % 2;
uint8_t *dst = &out[position];
for (unsigned y = 0; y < srcHeight; y++) {
for (unsigned x = 0; x < srcWidth;) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
if (!(HasTransparency && value == transparentColor)) {
*dst = UseColorMap ? colorMap[value] : value;
}
++dst;
++x;
} else {
const uint8_t runLength = value & PcxRunLengthMask;
const uint8_t color = *src++;
if (!(HasTransparency && color == transparentColor)) {
std::memset(dst, UseColorMap ? colorMap[color] : color, runLength);
}
dst += runLength;
x += runLength;
}
}
dst += dstSkip;
src += srcSkip;
}
}
template <bool UseColorMap, bool HasTransparency>
void BlitPcxClipXY(const Surface &out, Point position, const uint8_t *src, unsigned srcWidth, unsigned srcHeight, ClipX clipX, const uint8_t *colorMap, uint8_t transparentColor)
{
if (position.y >= out.h() || position.x >= out.w())
return;
while (position.y < 0 && srcHeight != 0) {
src = SkipRestOfPcxLine(src, srcWidth);
++position.y;
--srcHeight;
}
srcHeight = static_cast<unsigned>(std::min<int>(out.h() - position.y, srcHeight));
position.x += static_cast<int>(clipX.left);
const auto dstSkip = static_cast<unsigned>(out.pitch() - clipX.width);
const unsigned srcSkip = srcWidth % 2;
uint8_t *dst = &out[position];
for (unsigned y = 0; y < srcHeight; y++) {
// Skip initial src if clipping on the left.
// Handles overshoot, i.e. when the RLE segment goes into the unclipped area.
auto remainingWidth = clipX.width;
auto remainingLeftClip = clipX.left;
while (remainingLeftClip > 0) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
--remainingLeftClip;
} else {
const uint8_t runLength = value & PcxRunLengthMask;
if (runLength > remainingLeftClip) {
const uint8_t overshoot = runLength - remainingLeftClip;
const uint8_t originalColor = *src++;
const uint8_t color = UseColorMap ? colorMap[originalColor] : originalColor;
if (overshoot > remainingWidth) {
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, remainingWidth);
}
dst += remainingWidth;
remainingWidth = 0;
} else {
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, overshoot);
}
dst += overshoot;
remainingWidth -= overshoot;
}
remainingLeftClip = 0;
break;
}
++src;
remainingLeftClip -= runLength;
}
}
while (remainingWidth > 0) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
if (!(HasTransparency && value == transparentColor)) {
*dst = UseColorMap ? colorMap[value] : value;
}
++dst;
--remainingWidth;
continue;
}
const uint8_t runLength = value & PcxRunLengthMask;
const uint8_t originalColor = *src++;
const uint8_t color = UseColorMap ? colorMap[originalColor] : originalColor;
if (runLength > remainingWidth) {
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, remainingWidth);
}
dst += remainingWidth;
remainingWidth -= runLength;
break;
}
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, runLength);
}
dst += runLength;
remainingWidth -= runLength;
}
src = SkipRestOfPcxLine(src, clipX.right + remainingWidth);
dst += dstSkip;
src += srcSkip;
}
}
template <bool UseColorMap>
void BlitPcxSprite(const Surface &out, Point position, PcxSprite sprite, const uint8_t *colorMap)
{
const ClipX clipX = CalculateClipX(position.x, sprite.width(), out);
if (clipX.width <= 0)
return;
if (static_cast<unsigned>(clipX.width) == sprite.width()) {
if (sprite.transparentColor()) {
BlitPcxClipY<UseColorMap, /*HasTransparency=*/true>(out, position, sprite.data(), sprite.width(), sprite.height(), colorMap, *sprite.transparentColor());
} else {
BlitPcxClipY<UseColorMap, /*HasTransparency=*/false>(out, position, sprite.data(), sprite.width(), sprite.height(), colorMap, 0);
}
} else {
if (sprite.transparentColor()) {
BlitPcxClipXY<UseColorMap, /*HasTransparency=*/true>(out, position, sprite.data(), sprite.width(), sprite.height(), clipX, colorMap, *sprite.transparentColor());
} else {
BlitPcxClipXY<UseColorMap, /*HasTransparency=*/false>(out, position, sprite.data(), sprite.width(), sprite.height(), clipX, colorMap, 0);
}
}
}
} // namespace
void RenderPcxSprite(const Surface &out, PcxSprite sprite, Point position)
{
BlitPcxSprite</*UseColorMap=*/false>(out, position, sprite, /*colorMap=*/nullptr);
}
void RenderPcxSpriteWithColorMap(const Surface &out, PcxSprite sprite, Point position, const std::array<uint8_t, 256> &colorMap)
{
BlitPcxSprite</*UseColorMap=*/true>(out, position, sprite, colorMap.data());
}
} // namespace devilution