#include "engine/render/pcx_render.hpp" #include #include #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 void BlitPcxClipY(const Surface &out, Point position, const uint8_t *src, unsigned srcWidth, unsigned srcHeight, const uint8_t *colorMap, uint8_t transparentColor) { const unsigned srcSkip = srcWidth % 2; while (position.y < 0 && srcHeight != 0) { src = SkipRestOfPcxLine(src, srcWidth) + srcSkip; ++position.y; --srcHeight; } srcHeight = static_cast(std::min(out.h() - position.y, srcHeight)); const auto dstSkip = static_cast(out.pitch() - srcWidth); 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 void BlitPcxClipXY(const Surface &out, Point position, const uint8_t *src, unsigned srcWidth, unsigned srcHeight, ClipX clipX, const uint8_t *colorMap, uint8_t transparentColor) { const unsigned srcSkip = srcWidth % 2; while (position.y < 0 && srcHeight != 0) { src = SkipRestOfPcxLine(src, srcWidth) + srcSkip; ++position.y; --srcHeight; } srcHeight = static_cast(std::min(out.h() - position.y, srcHeight)); position.x += static_cast(clipX.left); const auto dstSkip = static_cast(out.pitch() - clipX.width); 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 void BlitPcxSprite(const Surface &out, Point position, PcxSprite sprite, const uint8_t *colorMap) { if (position.y >= out.h() || position.y + sprite.height() <= 0) return; const ClipX clipX = CalculateClipX(position.x, sprite.width(), out); if (clipX.width <= 0) return; if (static_cast(clipX.width) == sprite.width()) { if (sprite.transparentColor()) { BlitPcxClipY(out, position, sprite.data(), sprite.width(), sprite.height(), colorMap, *sprite.transparentColor()); } else { BlitPcxClipY(out, position, sprite.data(), sprite.width(), sprite.height(), colorMap, 0); } } else { if (sprite.transparentColor()) { BlitPcxClipXY(out, position, sprite.data(), sprite.width(), sprite.height(), clipX, colorMap, *sprite.transparentColor()); } else { BlitPcxClipXY(out, position, sprite.data(), sprite.width(), sprite.height(), clipX, colorMap, 0); } } } } // namespace void RenderPcxSprite(const Surface &out, PcxSprite sprite, Point position) { BlitPcxSprite(out, position, sprite, /*colorMap=*/nullptr); } void RenderPcxSpriteWithColorMap(const Surface &out, PcxSprite sprite, Point position, const std::array &colorMap) { BlitPcxSprite(out, position, sprite, colorMap.data()); } } // namespace devilution