Browse Source

Load all CEL as CL2

Convert CEL files to CL2 at load time. CL2 format is more efficient and is about as fast to render.

CEL vs CL2 sizes, on dLvl 5: https://gist.github.com/glebm/9bbdd76962abcd4fd2405ecd3379af97

Memory:

* Peak memory (while loading): -300 KiB
* Memory in-game (dLvl5): -700 KiB
* RG99 binary size: -15 KiB (1333096 -> 1317192)

Performance on rg99:

* On average, -1 FPS in town.
* Same FPS in dungeon (20 FPS on dLvl 1).
pull/4987/head
Gleb Mazovetskiy 4 years ago committed by Anders Jenbo
parent
commit
8ca71272b8
  1. 1
      Source/CMakeLists.txt
  2. 6
      Source/DiabloUI/diabloui.cpp
  3. 50
      Source/control.cpp
  4. 4
      Source/controls/touch/renderers.cpp
  5. 31
      Source/cursor.cpp
  6. 4
      Source/cursor.h
  7. 2
      Source/debug.cpp
  8. 14
      Source/diablo.cpp
  9. 6
      Source/doom.cpp
  10. 16
      Source/engine/cel_sprite.hpp
  11. 26
      Source/engine/load_cel.cpp
  12. 3
      Source/engine/load_cel.hpp
  13. 2
      Source/engine/render/cel_render.hpp
  14. 149
      Source/engine/render/cl2_render.cpp
  15. 42
      Source/engine/render/cl2_render.hpp
  16. 31
      Source/engine/render/scrollrt.cpp
  17. 8
      Source/engine/render/text_render.cpp
  18. 18
      Source/error.cpp
  19. 22
      Source/gmenu.cpp
  20. 2
      Source/hwcursor.cpp
  21. 6
      Source/interfac.cpp
  22. 30
      Source/inv.cpp
  23. 6
      Source/items.cpp
  24. 6
      Source/minitext.cpp
  25. 30
      Source/objects.cpp
  26. 10
      Source/panels/charpanel.cpp
  27. 4
      Source/panels/info_box.cpp
  28. 2
      Source/panels/mainpanel.cpp
  29. 14
      Source/panels/spell_book.cpp
  30. 8
      Source/panels/spell_icons.cpp
  31. 4
      Source/qol/itemlabels.cpp
  32. 8
      Source/qol/stash.cpp
  33. 8
      Source/quests.cpp
  34. 20
      Source/stores.cpp
  35. 6
      Source/towners.cpp
  36. 208
      Source/utils/cel_to_cl2.cpp
  37. 13
      Source/utils/cel_to_cl2.hpp

1
Source/CMakeLists.txt

@ -153,6 +153,7 @@ set(libdevilutionx_SRCS
storm/storm_net.cpp
storm/storm_svid.cpp
utils/cel_to_cl2.cpp
utils/console.cpp
utils/display.cpp
utils/file_util.cpp

6
Source/DiabloUI/diabloui.cpp

@ -17,7 +17,7 @@
#include "engine/dx.h"
#include "engine/load_pcx.hpp"
#include "engine/pcx_sprite.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/pcx_render.hpp"
#include "hwcursor.hpp"
#include "utils/display.h"
@ -748,14 +748,14 @@ void UiFadeIn()
void DrawCel(CelSpriteWithFrameHeight sprite, Point p)
{
const Surface &out = Surface(DiabloUiSurface());
CelDrawTo(out, { p.x, static_cast<int>(p.y + sprite.frameHeight) }, sprite.sprite, 0);
Cl2Draw(out, { p.x, static_cast<int>(p.y + sprite.frameHeight) }, sprite.sprite, 0);
}
void DrawAnimatedCel(CelSpriteWithFrameHeight sprite, Point p)
{
const Surface &out = Surface(DiabloUiSurface());
const int frame = GetAnimationFrame(LoadLE32(sprite.sprite.Data()));
CelDrawTo(out, { p.x, static_cast<int>(p.y + sprite.frameHeight) }, sprite.sprite, frame);
Cl2Draw(out, { p.x, static_cast<int>(p.y + sprite.frameHeight) }, sprite.sprite, frame);
}
void DrawSelector(const SDL_Rect &rect)

50
Source/control.cpp

@ -20,7 +20,7 @@
#include "cursor.h"
#include "engine/cel_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/trn.hpp"
#include "error.h"
@ -316,7 +316,7 @@ int DrawDurIcon4Item(const Surface &out, Item &pItem, int x, int c)
}
if (pItem._iDurability > 2)
c += 8;
CelDrawTo(out, { x, -17 + GetMainPanel().position.y }, CelSprite { *pDurIcons }, c);
Cl2Draw(out, { x, -17 + GetMainPanel().position.y }, CelSprite { *pDurIcons }, c);
return x - 32 - 8;
}
@ -552,25 +552,25 @@ void InitControlPan()
LoadCharPanel();
LoadSpellIcons();
{
const OwnedCelSprite sprite = LoadCel("CtrlPan\\Panel8.CEL", GetMainPanel().size.width);
CelDrawUnsafeTo(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) - 1 }, CelSprite { sprite }, 0);
const OwnedCelSprite sprite = LoadCelAsCl2("CtrlPan\\Panel8.CEL", GetMainPanel().size.width);
Cl2Draw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) - 1 }, CelSprite { sprite }, 0);
}
{
const Point bulbsPosition { 0, 87 };
const OwnedCelSprite statusPanel = LoadCel("CtrlPan\\P8Bulbs.CEL", 88);
CelDrawUnsafeTo(*pLifeBuff, bulbsPosition, CelSprite { statusPanel }, 0);
CelDrawUnsafeTo(*pManaBuff, bulbsPosition, CelSprite { statusPanel }, 1);
const OwnedCelSprite statusPanel = LoadCelAsCl2("CtrlPan\\P8Bulbs.CEL", 88);
Cl2Draw(*pLifeBuff, bulbsPosition, CelSprite { statusPanel }, 0);
Cl2Draw(*pManaBuff, bulbsPosition, CelSprite { statusPanel }, 1);
}
}
talkflag = false;
if (IsChatAvailable()) {
if (!HeadlessMode) {
{
const OwnedCelSprite sprite = LoadCel("CtrlPan\\TalkPanl.CEL", GetMainPanel().size.width);
CelDrawUnsafeTo(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) * 2 - 1 }, CelSprite { sprite }, 0);
const OwnedCelSprite sprite = LoadCelAsCl2("CtrlPan\\TalkPanl.CEL", GetMainPanel().size.width);
Cl2Draw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) * 2 - 1 }, CelSprite { sprite }, 0);
}
multiButtons = LoadCel("CtrlPan\\P8But2.CEL", 33);
talkButtons = LoadCel("CtrlPan\\TalkButt.CEL", 61);
multiButtons = LoadCelAsCl2("CtrlPan\\P8But2.CEL", 33);
talkButtons = LoadCelAsCl2("CtrlPan\\TalkButt.CEL", 61);
}
sgbPlrTalkTbl = 0;
TalkMessage[0] = '\0';
@ -583,8 +583,10 @@ void InitControlPan()
lvlbtndown = false;
if (!HeadlessMode) {
LoadMainPanel();
pPanelButtons = LoadCel("CtrlPan\\Panel8bu.CEL", 71);
pChrButtons = LoadCel("Data\\CharBut.CEL", 41);
pPanelButtons = LoadCelAsCl2("CtrlPan\\Panel8bu.CEL", 71);
static const uint16_t CharButtonsFrameWidths[9] { 95, 41, 41, 41, 41, 41, 41, 41, 41 };
pChrButtons = LoadCelAsCl2("Data\\CharBut.CEL", CharButtonsFrameWidths);
}
ClearPanBtn();
if (!IsChatAvailable())
@ -592,7 +594,7 @@ void InitControlPan()
else
PanelButtonIndex = 8;
if (!HeadlessMode)
pDurIcons = LoadCel("Items\\DurIcons.CEL", 32);
pDurIcons = LoadCelAsCl2("Items\\DurIcons.CEL", 32);
for (bool &buttonEnabled : chrbtn)
buttonEnabled = false;
chrbtnactive = false;
@ -607,8 +609,8 @@ void InitControlPan()
if (!HeadlessMode) {
InitSpellBook();
pQLogCel = LoadCel("Data\\Quest.CEL", static_cast<uint16_t>(SidePanelSize.width));
pGBoxBuff = LoadCel("CtrlPan\\Golddrop.cel", 261);
pQLogCel = LoadCelAsCl2("Data\\Quest.CEL", static_cast<uint16_t>(SidePanelSize.width));
pGBoxBuff = LoadCelAsCl2("CtrlPan\\Golddrop.cel", 261);
}
CloseGoldDrop();
dropGoldValue = 0;
@ -635,17 +637,17 @@ void DrawCtrlBtns(const Surface &out)
DrawPanelBox(out, MakeSdlRect(PanBtnPos[i].x, PanBtnPos[i].y + 16, 71, 20), mainPanelPosition + Displacement { PanBtnPos[i].x, PanBtnPos[i].y });
} else {
Point position = mainPanelPosition + Displacement { PanBtnPos[i].x, PanBtnPos[i].y + 18 };
CelDrawTo(out, position, CelSprite { *pPanelButtons }, i);
Cl2Draw(out, position, CelSprite { *pPanelButtons }, i);
DrawArt(out, position + Displacement { 4, -18 }, &PanelButtonDown, i);
}
}
if (PanelButtonIndex == 8) {
CelSprite sprite { *multiButtons };
CelDrawTo(out, mainPanelPosition + Displacement { 87, 122 }, sprite, PanelButtons[6] ? 1 : 0);
Cl2Draw(out, mainPanelPosition + Displacement { 87, 122 }, sprite, PanelButtons[6] ? 1 : 0);
if (MyPlayer->friendlyMode)
CelDrawTo(out, mainPanelPosition + Displacement { 527, 122 }, sprite, PanelButtons[7] ? 3 : 2);
Cl2Draw(out, mainPanelPosition + Displacement { 527, 122 }, sprite, PanelButtons[7] ? 3 : 2);
else
CelDrawTo(out, mainPanelPosition + Displacement { 527, 122 }, sprite, PanelButtons[7] ? 5 : 4);
Cl2Draw(out, mainPanelPosition + Displacement { 527, 122 }, sprite, PanelButtons[7] ? 5 : 4);
}
}
@ -970,7 +972,7 @@ void DrawLevelUpIcon(const Surface &out)
if (IsLevelUpButtonVisible()) {
int nCel = lvlbtndown ? 2 : 1;
DrawString(out, _("Level Up"), { GetMainPanel().position + Displacement { 0, -62 }, { 120, 0 } }, UiFlags::ColorWhite | UiFlags::AlignCenter);
CelDrawTo(out, GetMainPanel().position + Displacement { 40, -17 }, CelSprite { *pChrButtons }, nCel);
Cl2Draw(out, GetMainPanel().position + Displacement { 40, -17 }, CelSprite { *pChrButtons }, nCel);
}
}
@ -1072,7 +1074,7 @@ void DrawGoldSplit(const Surface &out, int amount)
{
const int dialogX = 30;
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), CelSprite { *pGBoxBuff }, 0);
Cl2Draw(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), CelSprite { *pGBoxBuff }, 0);
const std::string description = fmt::format(
fmt::runtime(ngettext(
@ -1161,14 +1163,14 @@ void DrawTalkPan(const Surface &out)
if (WhisperList[i]) {
if (TalkButtonsDown[talkBtn]) {
int nCel = talkBtn != 0 ? 3 : 2;
CelDrawTo(out, talkPanPosition, CelSprite { *talkButtons }, nCel);
Cl2Draw(out, talkPanPosition, CelSprite { *talkButtons }, nCel);
DrawArt(out, talkPanPosition + Displacement { 4, -15 }, &TalkButton, 2);
}
} else {
int nCel = talkBtn != 0 ? 1 : 0;
if (TalkButtonsDown[talkBtn])
nCel += 4;
CelDrawTo(out, talkPanPosition, CelSprite { *talkButtons }, nCel);
Cl2Draw(out, talkPanPosition, CelSprite { *talkButtons }, nCel);
DrawArt(out, talkPanPosition + Displacement { 4, -15 }, &TalkButton, TalkButtonsDown[talkBtn] ? 1 : 0);
}
if (player.plractive) {

4
Source/controls/touch/renderers.cpp

@ -5,7 +5,7 @@
#include "diablo.h"
#include "doom.h"
#include "engine.h"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "init.h"
#include "inv.h"
#include "levels/gendung.h"
@ -143,7 +143,7 @@ void LoadPotionArt(Art *potionArt, SDL_Renderer *renderer)
const int frame = GetInvItemFrame(cursorID);
const CelSprite potionSprite { GetInvItemSprite(cursorID) };
position.y += potionSize.height;
CelClippedDrawTo(Surface(surface.get()), position, potionSprite, frame);
Cl2Draw(Surface(surface.get()), position, potionSprite, frame);
}
potionArt->logical_width = potionSize.width;

31
Source/cursor.cpp

@ -15,7 +15,8 @@
#include "engine.h"
#include "engine/load_cel.hpp"
#include "engine/point.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/trn.hpp"
#include "hwcursor.hpp"
#include "inv.h"
#include "levels/trigs.h"
@ -65,7 +66,8 @@ const uint16_t InvItemWidth2[] = {
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
2 * 28, 2 * 28, 1 * 28, 1 * 28, 1 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28,
2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28,
2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28
2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28,
2 * 28
// clang-format on
};
constexpr uint16_t InvItems1Size = sizeof(InvItemWidth1) / sizeof(InvItemWidth1[0]);
@ -101,7 +103,8 @@ const uint16_t InvItemHeight2[InvItems2Size] = {
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
2 * 28, 2 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28,
3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28,
3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28
3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28,
3 * 28
// clang-format on
};
@ -130,9 +133,9 @@ int pcurs;
void InitCursor()
{
assert(!pCursCels);
pCursCels = LoadCel("Data\\Inv\\Objcurs.CEL", InvItemWidth1);
pCursCels = LoadCelAsCl2("Data\\Inv\\Objcurs.CEL", InvItemWidth1);
if (gbIsHellfire)
pCursCels2 = LoadCel("Data\\Inv\\Objcurs2.CEL", InvItemWidth2);
pCursCels2 = LoadCelAsCl2("Data\\Inv\\Objcurs2.CEL", InvItemWidth2);
ClearCursor();
}
@ -161,6 +164,16 @@ Size GetInvItemSize(int cursId)
return { InvItemWidth1[i], InvItemHeight1[i] };
}
void DrawItem(const Item &item, const Surface &out, Point position, CelSprite cel, int frame)
{
const bool usable = item._iStatFlag;
if (usable) {
Cl2Draw(out, position, cel, frame);
} else {
Cl2DrawTRN(out, position, cel, frame, GetInfravisionTRN());
}
}
void ResetCursor()
{
NewCursor(pcurs);
@ -194,16 +207,16 @@ void NewCursor(int cursId)
}
}
void CelDrawCursor(const Surface &out, Point position, int cursId)
void DrawSoftwareCursor(const Surface &out, Point position, int cursId)
{
const CelSprite sprite { GetInvItemSprite(cursId) };
const int frame = GetInvItemFrame(cursId);
if (!MyPlayer->HoldItem.isEmpty()) {
const auto &heldItem = MyPlayer->HoldItem;
CelBlitOutlineTo(out, GetOutlineColor(heldItem, true), position, sprite, frame, false);
CelDrawItem(heldItem, out, position, sprite, frame);
Cl2DrawOutline(out, GetOutlineColor(heldItem, true), position, sprite, frame);
DrawItem(heldItem, out, position, sprite, frame);
} else {
CelClippedDrawTo(out, position, sprite, frame);
Cl2Draw(out, position, sprite, frame);
}
}

4
Source/cursor.h

@ -57,7 +57,9 @@ void CheckRportal();
void CheckTown();
void CheckCursMove();
void CelDrawCursor(const Surface &out, Point position, int cursId);
void DrawSoftwareCursor(const Surface &out, Point position, int cursId);
void DrawItem(const Item &item, const Surface &out, Point position, CelSprite cel, int frame);
/** Returns the sprite for the given inventory index. */
const OwnedCelSprite &GetInvItemSprite(int i);

2
Source/debug.cpp

@ -990,7 +990,7 @@ std::vector<DebugCmdItem> DebugCmdList = {
void LoadDebugGFX()
{
pSquareCel = LoadCel("Data\\Square.CEL", 64);
pSquareCel = LoadCelAsCl2("Data\\Square.CEL", 64);
}
void FreeDebugGFX()

14
Source/diablo.cpp

@ -1090,37 +1090,37 @@ void LoadLvlGFX()
pDungeonCels = LoadFileInMem("Levels\\TownData\\Town.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\TownData\\Town.TIL");
}
pSpecialCels = LoadCel("Levels\\TownData\\TownS.CEL", SpecialCelWidth);
pSpecialCels = LoadCelAsCl2("Levels\\TownData\\TownS.CEL", SpecialCelWidth);
break;
case DTYPE_CATHEDRAL:
pDungeonCels = LoadFileInMem("Levels\\L1Data\\L1.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L1Data\\L1.TIL");
pSpecialCels = LoadCel("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
pSpecialCels = LoadCelAsCl2("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
break;
case DTYPE_CATACOMBS:
pDungeonCels = LoadFileInMem("Levels\\L2Data\\L2.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L2Data\\L2.TIL");
pSpecialCels = LoadCel("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
pSpecialCels = LoadCelAsCl2("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
break;
case DTYPE_CAVES:
pDungeonCels = LoadFileInMem("Levels\\L3Data\\L3.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L3Data\\L3.TIL");
pSpecialCels = LoadCel("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
pSpecialCels = LoadCelAsCl2("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
break;
case DTYPE_HELL:
pDungeonCels = LoadFileInMem("Levels\\L4Data\\L4.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L4Data\\L4.TIL");
pSpecialCels = LoadCel("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
pSpecialCels = LoadCelAsCl2("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
break;
case DTYPE_NEST:
pDungeonCels = LoadFileInMem("NLevels\\L6Data\\L6.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("NLevels\\L6Data\\L6.TIL");
pSpecialCels = LoadCel("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
pSpecialCels = LoadCelAsCl2("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
break;
case DTYPE_CRYPT:
pDungeonCels = LoadFileInMem("NLevels\\L5Data\\L5.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("NLevels\\L5Data\\L5.TIL");
pSpecialCels = LoadCel("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth);
pSpecialCels = LoadCelAsCl2("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth);
break;
default:
app_fatal("LoadLvlGFX");

6
Source/doom.cpp

@ -9,7 +9,7 @@
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "utils/stdcompat/optional.hpp"
namespace devilution {
@ -21,7 +21,7 @@ bool DoomFlag;
void doom_init()
{
DoomCel = LoadCel("Items\\Map\\MapZtown.CEL", 640);
DoomCel = LoadCelAsCl2("Items\\Map\\MapZtown.CEL", 640);
DoomFlag = true;
}
@ -37,7 +37,7 @@ void doom_draw(const Surface &out)
return;
}
CelDrawTo(out, GetUIRectangle().position + Displacement { 0, 352 }, CelSprite { *DoomCel }, 0);
Cl2Draw(out, GetUIRectangle().position + Displacement { 0, 352 }, CelSprite { *DoomCel }, 0);
}
} // namespace devilution

16
Source/engine/cel_sprite.hpp

@ -54,6 +54,11 @@ public:
return width_.HoldsPointer() ? width_.AsPointer()[frame] : width_.AsValue();
}
[[nodiscard]] PointerOrValue<uint16_t> widthOrWidths() const
{
return width_;
}
[[nodiscard]] bool operator==(CelSprite other) const
{
return data_ptr_ == other.data_ptr_;
@ -162,6 +167,12 @@ public:
{
}
OwnedCelSprite(std::unique_ptr<byte[]> data, PointerOrValue<uint16_t> widths)
: data_(std::move(data))
, width_(widths)
{
}
OwnedCelSprite(OwnedCelSprite &&) noexcept = default;
OwnedCelSprite &operator=(OwnedCelSprite &&) noexcept = default;
@ -170,6 +181,11 @@ public:
return data_.get();
}
std::unique_ptr<byte[]> data() &&
{
return std::move(data_);
}
private:
// for OptionalOwnedCelSprite.
OwnedCelSprite()

26
Source/engine/load_cel.cpp

@ -1,6 +1,12 @@
#include "engine/load_cel.hpp"
#ifdef DEBUG_CEL_TO_CL2_SIZE
#include <iostream>
#endif
#include "engine/load_file.hpp"
#include "utils/cel_to_cl2.hpp"
#include "utils/pointer_value_union.hpp"
namespace devilution {
@ -14,4 +20,24 @@ OwnedCelSprite LoadCel(const char *pszName, const uint16_t *widths)
return OwnedCelSprite(LoadFileInMem(pszName), widths);
}
OwnedCelSprite LoadCelAsCl2(const char *pszName, uint16_t width)
{
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(pszName, &size);
#ifdef DEBUG_CEL_TO_CL2_SIZE
std::cout << pszName;
#endif
return CelToCl2(data.get(), size, PointerOrValue<uint16_t> { width });
}
OwnedCelSprite LoadCelAsCl2(const char *pszName, const uint16_t *widths)
{
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(pszName, &size);
#ifdef DEBUG_CEL_TO_CL2_SIZE
std::cout << pszName;
#endif
return CelToCl2(data.get(), size, PointerOrValue<uint16_t> { widths });
}
} // namespace devilution

3
Source/engine/load_cel.hpp

@ -12,4 +12,7 @@ namespace devilution {
OwnedCelSprite LoadCel(const char *pszName, uint16_t width);
OwnedCelSprite LoadCel(const char *pszName, const uint16_t *widths);
OwnedCelSprite LoadCelAsCl2(const char *pszName, uint16_t width);
OwnedCelSprite LoadCelAsCl2(const char *pszName, const uint16_t *widths);
} // namespace devilution

2
Source/engine/render/cel_render.hpp

@ -111,6 +111,6 @@ void CelDrawItem(const Item &item, const Surface &out, Point position, CelSprite
* @param frame CEL frame number
* @param skipColorIndexZero If true, color in index 0 will be treated as transparent (these are typically used for shadows in sprites)
*/
void CelBlitOutlineTo(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame, bool skipColorIndexZero = true);
void Cl2DrawOutline(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame, bool skipColorIndexZero = true);
} // namespace devilution

149
Source/engine/render/cl2_render.cpp

@ -69,7 +69,7 @@ BlitCommand Cl2GetBlitCommand(const uint8_t *src)
* @param nDataSize Size of CL2 in bytes
* @param nWidth Width of sprite
*/
void Cl2BlitSafe(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth)
void Cl2Blit(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth)
{
DoRenderBackwards</*TransparentCommandCanCrossLines=*/true, Cl2GetBlitCommand>(
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitDirect {});
@ -85,13 +85,19 @@ void Cl2BlitSafe(const Surface &out, Point position, const byte *pRLEBytes, int
* @param nWidth With of CL2 sprite
* @param pTable Light color table
*/
void Cl2BlitLightSafe(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *pTable)
void Cl2BlitTRN(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *pTable)
{
DoRenderBackwards</*TransparentCommandCanCrossLines=*/true, Cl2GetBlitCommand>(
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitWithMap { pTable });
}
template <bool Fill, bool North, bool West, bool South, bool East>
void Cl2BlitBlendedTRN(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *pTable)
{
DoRenderBackwards</*TransparentCommandCanCrossLines=*/true, Cl2GetBlitCommand>(
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitBlendedWithMap { pTable });
}
template <bool Fill, bool North, bool West, bool South, bool East, bool SkipColorIndexZero>
uint8_t *RenderCl2OutlinePixelsCheckFirstColumn(
uint8_t *dst, int dstPitch, int dstX,
const uint8_t *src, uint8_t width, uint8_t color)
@ -101,7 +107,7 @@ uint8_t *RenderCl2OutlinePixelsCheckFirstColumn(
RenderOutlineForPixel</*North=*/false, /*West=*/false, /*South=*/false, East>(
dst++, dstPitch, color);
} else {
RenderOutlineForPixel</*North=*/false, /*West=*/false, /*South=*/false, East>(
RenderOutlineForPixel</*North=*/false, /*West=*/false, /*South=*/false, East, SkipColorIndexZero>(
dst++, dstPitch, *src++, color);
}
--width;
@ -110,7 +116,7 @@ uint8_t *RenderCl2OutlinePixelsCheckFirstColumn(
if (Fill) {
RenderOutlineForPixel<North, /*West=*/false, South, East>(dst++, dstPitch, color);
} else {
RenderOutlineForPixel<North, /*West=*/false, South, East>(dst++, dstPitch, *src++, color);
RenderOutlineForPixel<North, /*West=*/false, South, East, SkipColorIndexZero>(dst++, dstPitch, *src++, color);
}
--width;
}
@ -118,14 +124,14 @@ uint8_t *RenderCl2OutlinePixelsCheckFirstColumn(
if (Fill) {
RenderOutlineForPixels<North, West, South, East>(dst, dstPitch, width, color);
} else {
RenderOutlineForPixels<North, West, South, East>(dst, dstPitch, width, src, color);
RenderOutlineForPixels<North, West, South, East, SkipColorIndexZero>(dst, dstPitch, width, src, color);
}
dst += width;
}
return dst;
}
template <bool Fill, bool North, bool West, bool South, bool East>
template <bool Fill, bool North, bool West, bool South, bool East, bool SkipColorIndexZero>
uint8_t *RenderCl2OutlinePixelsCheckLastColumn(
uint8_t *dst, int dstPitch, int dstX, int dstW,
const uint8_t *src, uint8_t width, uint8_t color)
@ -138,7 +144,7 @@ uint8_t *RenderCl2OutlinePixelsCheckLastColumn(
if (Fill) {
RenderOutlineForPixels<North, West, South, East>(dst, dstPitch, width, color);
} else {
RenderOutlineForPixels<North, West, South, East>(dst, dstPitch, width, src, color);
RenderOutlineForPixels<North, West, South, East, SkipColorIndexZero>(dst, dstPitch, width, src, color);
src += width;
}
dst += width;
@ -147,44 +153,44 @@ uint8_t *RenderCl2OutlinePixelsCheckLastColumn(
if (Fill) {
RenderOutlineForPixel<North, West, South, /*East=*/false>(dst++, dstPitch, color);
} else {
RenderOutlineForPixel<North, West, South, /*East=*/false>(dst++, dstPitch, *src++, color);
RenderOutlineForPixel<North, West, South, /*East=*/false, SkipColorIndexZero>(dst++, dstPitch, *src++, color);
}
}
if (oobPixel) {
if (Fill) {
RenderOutlineForPixel</*North=*/false, West, /*South=*/false, /*East=*/false>(dst++, dstPitch, color);
} else {
RenderOutlineForPixel</*North=*/false, West, /*South=*/false, /*East=*/false>(dst++, dstPitch, *src++, color);
RenderOutlineForPixel</*North=*/false, West, /*South=*/false, /*East=*/false, SkipColorIndexZero>(dst++, dstPitch, *src++, color);
}
}
return dst;
}
template <bool Fill, bool North, bool West, bool South, bool East, bool CheckFirstColumn, bool CheckLastColumn>
template <bool Fill, bool North, bool West, bool South, bool East, bool SkipColorIndexZero, bool CheckFirstColumn, bool CheckLastColumn>
uint8_t *RenderCl2OutlinePixels(
uint8_t *dst, int dstPitch, int dstX, int dstW,
const uint8_t *src, uint8_t width, uint8_t color)
{
if (Fill && *src == 0)
if (SkipColorIndexZero && Fill && *src == 0)
return dst + width;
if (CheckFirstColumn && dstX <= 0) {
return RenderCl2OutlinePixelsCheckFirstColumn<Fill, North, West, South, East>(
return RenderCl2OutlinePixelsCheckFirstColumn<Fill, North, West, South, East, SkipColorIndexZero>(
dst, dstPitch, dstX, src, width, color);
}
if (CheckLastColumn && dstX + width >= dstW) {
return RenderCl2OutlinePixelsCheckLastColumn<Fill, North, West, South, East>(
return RenderCl2OutlinePixelsCheckLastColumn<Fill, North, West, South, East, SkipColorIndexZero>(
dst, dstPitch, dstX, dstW, src, width, color);
}
if (Fill) {
RenderOutlineForPixels<North, West, South, East>(dst, dstPitch, width, color);
} else {
RenderOutlineForPixels<North, West, South, East>(dst, dstPitch, width, src, color);
RenderOutlineForPixels<North, West, South, East, SkipColorIndexZero>(dst, dstPitch, width, src, color);
}
return dst + width;
}
template <bool North, bool West, bool South, bool East,
template <bool North, bool West, bool South, bool East, bool SkipColorIndexZero,
bool ClipWidth = false, bool CheckFirstColumn = false, bool CheckLastColumn = false>
const uint8_t *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognitive-complexity)
const Surface &out, Point position, const uint8_t *src, std::size_t srcWidth,
@ -198,11 +204,11 @@ const uint8_t *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognit
const auto renderPixels = [&](bool fill, uint8_t w) {
if (fill) {
dst = RenderCl2OutlinePixels</*Fill=*/true, North, West, South, East, CheckFirstColumn, CheckLastColumn>(
dst = RenderCl2OutlinePixels</*Fill=*/true, North, West, South, East, SkipColorIndexZero, CheckFirstColumn, CheckLastColumn>(
dst, dstPitch, position.x, out.w(), src, w, color);
++src;
} else {
dst = RenderCl2OutlinePixels</*Fill=*/false, North, West, South, East, CheckFirstColumn, CheckLastColumn>(
dst = RenderCl2OutlinePixels</*Fill=*/false, North, West, South, East, SkipColorIndexZero, CheckFirstColumn, CheckLastColumn>(
dst, dstPitch, position.x, out.w(), src, w, color);
src += v;
}
@ -276,6 +282,7 @@ const uint8_t *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognit
return src;
}
template <bool SkipColorIndexZero>
void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackwards src, // NOLINT(readability-function-cognitive-complexity)
uint8_t color)
{
@ -289,7 +296,7 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
if (position.y == dstHeight) {
// After-bottom line - can only draw north.
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false>(
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
@ -298,13 +305,13 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
if (position.y + 1 == dstHeight) {
// Bottom line - cannot draw south.
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true>(
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true>(
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
@ -312,7 +319,7 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
return;
if (position.y == 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true>(
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
@ -321,11 +328,12 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
if (position.y == -1) {
// Special case: the top of the sprite is 1px below the last line, render just the outline above.
RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false>(
RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
}
}
template <bool SkipColorIndexZero>
void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBackwards src, // NOLINT(readability-function-cognitive-complexity)
uint8_t color)
{
@ -348,13 +356,13 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y == dstHeight) {
// After-bottom line - can only draw north.
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
}
position.y -= static_cast<int>(skipSize.wholeLines);
@ -365,15 +373,15 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y + 1 == dstHeight) {
// Bottom line - cannot draw south.
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
}
@ -382,21 +390,21 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.x <= 0) {
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
} else if (position.x + clipX.width >= out.w()) {
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
} else {
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
@ -407,15 +415,15 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y == 0) {
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true,
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
}
@ -427,26 +435,27 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y == -1) {
// Before-top line - can only draw south.
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false,
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false,
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false,
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
}
}
}
template <bool SkipColorIndexZero>
void RenderCl2Outline(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())) {
RenderCl2OutlineClippedY(out, position, srcForBackwards, color);
RenderCl2OutlineClippedY<SkipColorIndexZero>(out, position, srcForBackwards, color);
} else {
RenderCl2OutlineClippedXY(out, position, srcForBackwards, color);
RenderCl2OutlineClippedXY<SkipColorIndexZero>(out, position, srcForBackwards, color);
}
}
@ -485,6 +494,42 @@ void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int numFrames)
}
}
std::pair<int, int> Cl2MeasureSolidHorizontalBounds(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;
int xCur = 0;
while (src < end) {
while (xCur < celWidth) {
auto val = static_cast<uint8_t>(*src++);
if (!IsCl2Opaque(val)) {
xCur += val;
continue;
}
if (IsCl2OpaqueFill(val)) {
val = GetCl2OpaqueFillWidth(val);
++src;
} else {
val = GetCl2OpaquePixelsWidth(val);
src += val;
}
xBegin = std::min(xBegin, xCur);
xCur += val;
xEnd = std::max(xEnd, xCur);
}
while (xCur >= celWidth)
xCur -= celWidth;
if (xBegin == 0 && xEnd == celWidth)
break;
}
return { xBegin, xEnd };
}
void Cl2Draw(const Surface &out, Point position, CelSprite cel, int frame)
{
assert(frame >= 0);
@ -492,7 +537,7 @@ void Cl2Draw(const Surface &out, Point position, CelSprite cel, int frame)
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
Cl2BlitSafe(out, position, pRLEBytes, nDataSize, cel.Width(frame));
Cl2Blit(out, position, pRLEBytes, nDataSize, cel.Width(frame));
}
void Cl2DrawOutline(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame)
@ -502,7 +547,17 @@ void Cl2DrawOutline(const Surface &out, uint8_t col, Point position, CelSprite c
int nDataSize;
const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
RenderCl2Outline(out, position, reinterpret_cast<const uint8_t *>(src), nDataSize, cel.Width(frame), col);
RenderCl2Outline</*SkipColorIndexZero=*/true>(out, position, reinterpret_cast<const uint8_t *>(src), nDataSize, cel.Width(frame), col);
}
void Cl2DrawOutlineSkipColorZero(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame)
{
assert(frame >= 0);
int nDataSize;
const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
RenderCl2Outline</*SkipColorIndexZero=*/true>(out, position, reinterpret_cast<const uint8_t *>(src), nDataSize, cel.Width(frame), col);
}
void Cl2DrawTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn)
@ -511,20 +566,16 @@ void Cl2DrawTRN(const Surface &out, Point position, CelSprite cel, int frame, ui
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
Cl2BlitLightSafe(out, position, pRLEBytes, nDataSize, cel.Width(frame), trn);
Cl2BlitTRN(out, position, pRLEBytes, nDataSize, cel.Width(frame), trn);
}
void Cl2DrawLight(const Surface &out, Point position, CelSprite cel, int frame)
void Cl2DrawBlendedTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn)
{
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
if (LightTableIndex != 0)
Cl2BlitLightSafe(out, position, pRLEBytes, nDataSize, cel.Width(frame), &LightTables[LightTableIndex * 256]);
else
Cl2BlitSafe(out, position, pRLEBytes, nDataSize, cel.Width(frame));
Cl2BlitBlendedTRN(out, position, pRLEBytes, nDataSize, cel.Width(frame), trn);
}
} // namespace devilution

42
Source/engine/render/cl2_render.hpp

@ -5,12 +5,15 @@
*/
#pragma once
#include <array>
#include <cstdint>
#include <array>
#include <utility>
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/point.hpp"
#include "lighting.h"
namespace devilution {
@ -32,7 +35,7 @@ void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int numFrames)
void Cl2Draw(const Surface &out, Point position, CelSprite cel, int frame);
/**
* @brief Blit a solid colder shape one pixel larger than the given sprite shape, to the given buffer at the given coordianates
* @brief Blit a solid colder shape one pixel larger than the given sprite shape, to the given buffer at the given coordinates
* @param col Color index from current palette
* @param out Output buffer
* @param position Target buffer coordinate
@ -41,6 +44,17 @@ void Cl2Draw(const Surface &out, Point position, CelSprite cel, int frame);
*/
void Cl2DrawOutline(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame);
/**
* @brief Same as `Cl2DrawOutline` but considers colors with index 0 (usually shadows) to be transparent.
*
* @param col Color index from current palette
* @param out Output buffer
* @param position Target buffer coordinate
* @param cel CL2 buffer
* @param frame CL2 frame number
*/
void Cl2DrawOutlineSkipColorZero(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame);
/**
* @brief Blit CL2 sprite, and apply given TRN to the given buffer at the given coordinates
* @param out Output buffer
@ -51,6 +65,11 @@ void Cl2DrawOutline(const Surface &out, uint8_t col, Point position, CelSprite c
*/
void Cl2DrawTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn);
void Cl2DrawBlendedTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn);
// defined in scrollrt.cpp
extern int LightTableIndex;
/**
* @brief Blit CL2 sprite, and apply lighting, to the given buffer at the given coordinates
* @param out Output buffer
@ -58,6 +77,23 @@ void Cl2DrawTRN(const Surface &out, Point position, CelSprite cel, int frame, ui
* @param cel CL2 buffer
* @param frame CL2 frame number
*/
void Cl2DrawLight(const Surface &out, Point position, CelSprite cel, int frame);
inline void Cl2DrawLight(const Surface &out, Point position, CelSprite cel, int frame)
{
if (LightTableIndex != 0)
Cl2DrawTRN(out, position, cel, frame, &LightTables[LightTableIndex * 256]);
else
Cl2Draw(out, position, cel, frame);
}
inline void Cl2DrawLightBlended(const Surface &out, Point position, CelSprite cel, int frame)
{
Cl2DrawBlendedTRN(out, position, cel, frame, &LightTables[LightTableIndex * 256]);
}
/**
* Returns a pair of X coordinates containing the start (inclusive) and end (exclusive)
* of fully transparent columns in the sprite.
*/
std::pair<int, int> Cl2MeasureSolidHorizontalBounds(CelSprite cel, int frame = 0);
} // namespace devilution

31
Source/engine/render/scrollrt.cpp

@ -13,7 +13,6 @@
#include "dead.h"
#include "doom.h"
#include "engine/dx.h"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/dun_render.hpp"
#include "engine/render/text_render.hpp"
@ -306,7 +305,7 @@ void DrawCursor(const Surface &out)
Clip(sgdwCursY, sgdwCursHgt, out.h());
BlitCursor(sgSaveBack, sgdwCursWdt, out.at(sgdwCursX, sgdwCursY), out.pitch());
CelDrawCursor(out, MousePosition + Displacement { 0, cursSize.height - 1 }, pcurs);
DrawSoftwareCursor(out, MousePosition + Displacement { 0, cursSize.height - 1 }, pcurs);
}
/**
@ -546,7 +545,7 @@ void DrawPlayer(const Surface &out, const Player &player, Point tilePosition, Po
}
if (pcursplr >= 0 && pcursplr < MAX_PLRS && &player == &Players[pcursplr])
Cl2DrawOutline(out, 165, spriteBufferPosition, *sprite, nCel);
Cl2DrawOutlineSkipColorZero(out, 165, spriteBufferPosition, *sprite, nCel);
if (&player == MyPlayer) {
Cl2Draw(out, spriteBufferPosition, *sprite, nCel);
@ -636,12 +635,12 @@ void DrawObject(const Surface &out, Point tilePosition, Point targetBufferPositi
CelSprite cel { objectToDraw._oAnimData, objectToDraw._oAnimWidth };
if (pcursobj != -1 && &objectToDraw == &Objects[pcursobj]) {
CelBlitOutlineTo(out, 194, screenPosition, cel, nCel);
Cl2DrawOutlineSkipColorZero(out, 194, screenPosition, cel, nCel);
}
if (objectToDraw._oLight) {
CelClippedDrawLightTo(out, screenPosition, cel, nCel);
Cl2DrawLight(out, screenPosition, cel, nCel);
} else {
CelClippedDrawTo(out, screenPosition, cel, nCel);
Cl2Draw(out, screenPosition, cel, nCel);
}
}
@ -733,9 +732,9 @@ void DrawItem(const Surface &out, Point tilePosition, Point targetBufferPosition
int px = targetBufferPosition.x - CalculateWidth2(cel->Width());
const Point position { px, targetBufferPosition.y };
if (bItem - 1 == pcursitem || AutoMapShowItems) {
CelBlitOutlineTo(out, GetOutlineColor(item, false), position, *cel, nCel);
Cl2DrawOutlineSkipColorZero(out, GetOutlineColor(item, false), position, *cel, nCel);
}
CelClippedDrawLightTo(out, position, *cel, nCel);
Cl2DrawLight(out, position, *cel, nCel);
if (item.AnimInfo.currentFrame == item.AnimInfo.numberOfFrames - 1 || item._iCurs == ICURS_MAGIC_ROCK)
AddItemToLabelQueue(bItem - 1, px, targetBufferPosition.y);
}
@ -756,10 +755,10 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe
const Point position { px, targetBufferPosition.y };
const CelSprite sprite = towner.Sprite();
if (mi == pcursmonst) {
CelBlitOutlineTo(out, 166, position, sprite, towner._tAnimFrame);
Cl2DrawOutlineSkipColorZero(out, 166, position, sprite, towner._tAnimFrame);
}
assert(towner._tAnimData);
CelClippedDrawTo(out, position, sprite, towner._tAnimFrame);
Cl2Draw(out, position, sprite, towner._tAnimFrame);
return;
}
@ -785,7 +784,7 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe
const Point monsterRenderPosition { targetBufferPosition + offset - Displacement { CalculateWidth2(cel.Width()), 0 } };
if (mi == pcursmonst) {
Cl2DrawOutline(out, 233, monsterRenderPosition, cel, monster.animInfo.getFrameToUseForRendering());
Cl2DrawOutlineSkipColorZero(out, 233, monsterRenderPosition, cel, monster.animInfo.getFrameToUseForRendering());
}
DrawMonster(out, tilePosition, monsterRenderPosition, monster);
}
@ -832,7 +831,7 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
#ifdef _DEBUG
if (DebugVision && IsTileLit(tilePosition)) {
CelClippedDrawTo(out, targetBufferPosition, CelSprite { *pSquareCel }, 1);
Cl2Draw(out, targetBufferPosition, CelSprite { *pSquareCel }, 1);
}
#endif
@ -886,7 +885,11 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
cel_transparency_active = false; // Turn transparency off here for debugging
}
#endif
CelClippedBlitLightTransTo(out, targetBufferPosition, CelSprite { *pSpecialCels }, bArch - 1);
if (cel_transparency_active) {
Cl2DrawLightBlended(out, targetBufferPosition, CelSprite { *pSpecialCels }, bArch - 1);
} else {
Cl2DrawLight(out, targetBufferPosition, CelSprite { *pSpecialCels }, bArch - 1);
}
#ifdef _DEBUG
if ((SDL_GetModState() & KMOD_ALT) != 0) {
cel_transparency_active = TransList[bMap]; // Turn transparency back to its normal state
@ -900,7 +903,7 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
if (tilePosition.x > 0 && tilePosition.y > 0 && targetBufferPosition.y > TILE_HEIGHT) {
char bArch = dSpecial[tilePosition.x - 1][tilePosition.y - 1];
if (bArch != 0) {
CelDrawTo(out, targetBufferPosition + Displacement { 0, -TILE_HEIGHT }, CelSprite { *pSpecialCels }, bArch - 1);
Cl2Draw(out, targetBufferPosition + Displacement { 0, -TILE_HEIGHT }, CelSprite { *pSpecialCels }, bArch - 1);
}
}
}

8
Source/engine/render/text_render.cpp

@ -15,13 +15,13 @@
#include "DiabloUI/art_draw.h"
#include "DiabloUI/diabloui.h"
#include "DiabloUI/ui_item.h"
#include "cel_render.hpp"
#include "engine.h"
#include "engine/load_cel.hpp"
#include "engine/load_file.hpp"
#include "engine/load_pcx.hpp"
#include "engine/palette.h"
#include "engine/point.hpp"
#include "engine/render/cl2_render.hpp"
#include "pcx_render.hpp"
#include "utils/display.h"
#include "utils/language.h"
@ -405,7 +405,7 @@ int DoDrawString(const Surface &out, string_view text, Rectangle rect, Point &ch
void LoadSmallSelectionSpinner()
{
pSPentSpn2Cels = LoadCel("Data\\PentSpn2.CEL", 12);
pSPentSpn2Cels = LoadCelAsCl2("Data\\PentSpn2.CEL", 12);
}
void UnloadFonts(GameFontTables size, text_color color)
@ -652,7 +652,7 @@ uint32_t DrawString(const Surface &out, string_view text, const Rectangle &rect,
const int bytesDrawn = DoDrawString(out, text, rect, characterPosition, spacing, lineHeight, lineWidth, rightMargin, bottomMargin, flags, size, color);
if (HasAnyOf(flags, UiFlags::PentaCursor)) {
CelDrawTo(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
Cl2Draw(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
} else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) {
DrawFont(out, characterPosition, LoadFont(size, color, 0), color, '|');
}
@ -760,7 +760,7 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA
}
if (HasAnyOf(flags, UiFlags::PentaCursor)) {
CelDrawTo(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
Cl2Draw(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
} else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) {
DrawFont(out, characterPosition, LoadFont(size, color, 0), color, '|');
}

18
Source/error.cpp

@ -9,7 +9,7 @@
#include "error.h"
#include "DiabloUI/ui_flags.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "panels/info_box.hpp"
#include "stores.h"
@ -145,21 +145,21 @@ void DrawDiabloMsg(const Surface &out)
int dialogStartY = ((gnScreenHeight - GetMainPanel().size.height) / 2) - (ErrorWindowHeight / 2) + 9;
CelSprite sprite { *pSTextSlidCels };
CelDrawTo(out, { uiRectanglePosition.x + 101, dialogStartY }, sprite, 0);
CelDrawTo(out, { uiRectanglePosition.x + 101, dialogStartY + ErrorWindowHeight - 6 }, sprite, 1);
CelDrawTo(out, { uiRectanglePosition.x + 527, dialogStartY + ErrorWindowHeight - 6 }, sprite, 2);
CelDrawTo(out, { uiRectanglePosition.x + 527, dialogStartY }, sprite, 3);
Cl2Draw(out, { uiRectanglePosition.x + 101, dialogStartY }, sprite, 0);
Cl2Draw(out, { uiRectanglePosition.x + 101, dialogStartY + ErrorWindowHeight - 6 }, sprite, 1);
Cl2Draw(out, { uiRectanglePosition.x + 527, dialogStartY + ErrorWindowHeight - 6 }, sprite, 2);
Cl2Draw(out, { uiRectanglePosition.x + 527, dialogStartY }, sprite, 3);
int sx = uiRectanglePosition.x + 109;
for (int i = 0; i < 35; i++) {
CelDrawTo(out, { sx, dialogStartY }, sprite, 4);
CelDrawTo(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, sprite, 6);
Cl2Draw(out, { sx, dialogStartY }, sprite, 4);
Cl2Draw(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, sprite, 6);
sx += 12;
}
int drawnYborder = 12;
while ((drawnYborder + 12) < ErrorWindowHeight) {
CelDrawTo(out, { uiRectanglePosition.x + 101, dialogStartY + drawnYborder }, sprite, 5);
CelDrawTo(out, { uiRectanglePosition.x + 527, dialogStartY + drawnYborder }, sprite, 7);
Cl2Draw(out, { uiRectanglePosition.x + 101, dialogStartY + drawnYborder }, sprite, 5);
Cl2Draw(out, { uiRectanglePosition.x + 527, dialogStartY + drawnYborder }, sprite, 7);
drawnYborder += 12;
}

22
Source/gmenu.cpp

@ -12,7 +12,7 @@
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "miniwin/misc_msg.h"
#include "options.h"
@ -108,12 +108,12 @@ void GmenuDrawMenuItem(const Surface &out, TMenuItem *pItem, int y)
if ((pItem->dwFlags & GMENU_SLIDER) != 0) {
int uiPositionX = GetUIRectangle().position.x;
int x = 16 + w / 2;
CelDrawTo(out, { x + uiPositionX, y + 40 }, CelSprite { *optbar_cel }, 0);
Cl2Draw(out, { x + uiPositionX, y + 40 }, CelSprite { *optbar_cel }, 0);
uint16_t step = pItem->dwFlags & 0xFFF;
uint16_t steps = std::max<uint16_t>((pItem->dwFlags & 0xFFF000) >> 12, 2);
uint16_t pos = step * 256 / steps;
GmenuClearBuffer(out, x + 2 + uiPositionX, y + 38, pos + 13, 28);
CelDrawTo(out, { x + 2 + pos + uiPositionX, y + 38 }, CelSprite { *option_cel }, 0);
Cl2Draw(out, { x + 2 + pos + uiPositionX, y + 38 }, CelSprite { *option_cel }, 0);
}
int x = (gnScreenWidth - w) / 2;
@ -121,8 +121,8 @@ void GmenuDrawMenuItem(const Surface &out, TMenuItem *pItem, int y)
DrawString(out, _(pItem->pszStr), Point { x, y }, style | UiFlags::FontSize46, 2);
if (pItem == sgpCurrItem) {
CelSprite sprite { *PentSpin_cel };
CelDrawTo(out, { x - 54, y + 51 }, sprite, PentSpn2Spin());
CelDrawTo(out, { x + 4 + w, y + 51 }, sprite, PentSpn2Spin());
Cl2Draw(out, { x - 54, y + 51 }, sprite, PentSpn2Spin());
Cl2Draw(out, { x + 4 + w, y + 51 }, sprite, PentSpn2Spin());
}
}
@ -195,12 +195,12 @@ void gmenu_init_menu()
return;
if (gbIsHellfire)
sgpLogo = LoadCel("Data\\hf_logo3.CEL", 430);
sgpLogo = LoadCelAsCl2("Data\\hf_logo3.CEL", 430);
else
sgpLogo = LoadCel("Data\\Diabsmal.CEL", 296);
PentSpin_cel = LoadCel("Data\\PentSpin.CEL", 48);
option_cel = LoadCel("Data\\option.CEL", 27);
optbar_cel = LoadCel("Data\\optbar.CEL", 287);
sgpLogo = LoadCelAsCl2("Data\\Diabsmal.CEL", 296);
PentSpin_cel = LoadCelAsCl2("Data\\PentSpin.CEL", 48);
option_cel = LoadCelAsCl2("Data\\option.CEL", 27);
optbar_cel = LoadCelAsCl2("Data\\optbar.CEL", 287);
}
bool gmenu_is_active()
@ -247,7 +247,7 @@ void gmenu_draw(const Surface &out)
}
int uiPositionY = GetUIRectangle().position.y;
CelSprite sprite { *sgpLogo };
CelDrawTo(out, { (gnScreenWidth - sprite.Width()) / 2, 102 + uiPositionY }, sprite, LogoAnim_frame);
Cl2Draw(out, { (gnScreenWidth - sprite.Width()) / 2, 102 + uiPositionY }, sprite, LogoAnim_frame);
int y = 110 + uiPositionY;
TMenuItem *i = sgpCurrentMenu;
if (sgpCurrentMenu->fnMenu != nullptr) {

2
Source/hwcursor.cpp

@ -119,7 +119,7 @@ bool SetHardwareCursorFromSprite(int pcurs)
constexpr std::uint8_t TransparentColor = 1;
SDL_FillRect(out.surface, nullptr, TransparentColor);
SDL_SetColorKey(out.surface, 1, TransparentColor);
CelDrawCursor(out, { outlineWidth, size.height - outlineWidth }, pcurs);
DrawSoftwareCursor(out, { outlineWidth, size.height - outlineWidth }, pcurs);
const bool result = SetHardwareCursor(out.surface, isItem ? HotpointPosition::Center : HotpointPosition::TopLeft);
return result;

6
Source/interfac.cpp

@ -16,7 +16,7 @@
#include "engine/load_pcx.hpp"
#include "engine/palette.h"
#include "engine/pcx_sprite.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/pcx_render.hpp"
#include "hwcursor.hpp"
#include "init.h"
@ -164,7 +164,7 @@ void LoadCutsceneBackground(interface_mode uMsg)
}
assert(!sgpBackCel);
sgpBackCel = LoadCel(celPath, 640);
sgpBackCel = LoadCelAsCl2(celPath, 640);
LoadPalette(palPath);
sgdwProgress = 0;
@ -184,7 +184,7 @@ void DrawCutsceneBackground()
const PcxSprite sprite { *ArtCutsceneWidescreen };
RenderPcxSprite(out, sprite, { uiRectangle.position.x - (sprite.width() - uiRectangle.size.width) / 2, uiRectangle.position.y });
}
CelDrawTo(out, { uiRectangle.position.x, 480 - 1 + uiRectangle.position.y }, CelSprite { *sgpBackCel }, 0);
Cl2Draw(out, { uiRectangle.position.x, 480 - 1 + uiRectangle.position.y }, CelSprite { *sgpBackCel }, 0);
}
void DrawCutsceneForeground()

30
Source/inv.cpp

@ -13,7 +13,7 @@
#include "cursor.h"
#include "engine/cel_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/size.hpp"
#include "hwcursor.hpp"
@ -1064,24 +1064,24 @@ void InitInv()
switch (MyPlayer->_pClass) {
case HeroClass::Warrior:
case HeroClass::Barbarian:
pInvCels = LoadCel("Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCelAsCl2("Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Rogue:
case HeroClass::Bard:
pInvCels = LoadCel("Data\\Inv\\Inv_rog.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_rog.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Sorcerer:
pInvCels = LoadCel("Data\\Inv\\Inv_Sor.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_Sor.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Monk:
pInvCels = LoadCel(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCelAsCl2(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
}
}
void DrawInv(const Surface &out)
{
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { 0, 351 }), CelSprite { *pInvCels }, 0);
Cl2Draw(out, GetPanelPosition(UiPanels::Inventory, { 0, 351 }), CelSprite { *pInvCels }, 0);
Size slotSize[] = {
{ 2, 2 }, // head
@ -1129,10 +1129,10 @@ void DrawInv(const Surface &out)
const Point position = GetPanelPosition(UiPanels::Inventory, { screenX, screenY });
if (pcursinvitem == slot) {
CelBlitOutlineTo(out, GetOutlineColor(myPlayer.InvBody[slot], true), position, cel, celFrame, false);
Cl2DrawOutline(out, GetOutlineColor(myPlayer.InvBody[slot], true), position, cel, celFrame);
}
CelDrawItem(myPlayer.InvBody[slot], out, position, cel, celFrame);
DrawItem(myPlayer.InvBody[slot], out, position, cel, celFrame);
if (slot == INVLOC_HAND_LEFT) {
if (myPlayer.GetItemLocation(myPlayer.InvBody[slot]) == ILOC_TWOHAND) {
@ -1142,7 +1142,7 @@ void DrawInv(const Surface &out)
const int dstX = GetRightPanel().position.x + slotPos[INVLOC_HAND_RIGHT].x + (frameSize.width == InventorySlotSizeInPixels.width ? INV_SLOT_HALF_SIZE_PX : 0) - 1;
const int dstY = GetRightPanel().position.y + slotPos[INVLOC_HAND_RIGHT].y;
CelClippedBlitLightTransTo(out, { dstX, dstY }, cel, celFrame);
Cl2DrawLightBlended(out, { dstX, dstY }, cel, celFrame);
cel_transparency_active = false;
}
@ -1168,14 +1168,10 @@ void DrawInv(const Surface &out)
const int celFrame = GetInvItemFrame(cursId);
const Point position = GetPanelPosition(UiPanels::Inventory, InvRect[j + SLOTXY_INV_FIRST]) + Displacement { 0, -1 };
if (pcursinvitem == ii + INVITEM_INV_FIRST) {
CelBlitOutlineTo(
out,
GetOutlineColor(myPlayer.InvList[ii], true),
position,
cel, celFrame, false);
Cl2DrawOutline(out, GetOutlineColor(myPlayer.InvList[ii], true), position, cel, celFrame);
}
CelDrawItem(
DrawItem(
myPlayer.InvList[ii],
out,
position,
@ -1210,11 +1206,11 @@ void DrawInvBelt(const Surface &out)
if (pcursinvitem == i + INVITEM_BELT_FIRST) {
if (ControlMode == ControlTypes::KeyboardAndMouse || invflag) {
CelBlitOutlineTo(out, GetOutlineColor(myPlayer.SpdList[i], true), position, cel, celFrame, false);
Cl2DrawOutline(out, GetOutlineColor(myPlayer.SpdList[i], true), position, cel, celFrame);
}
}
CelDrawItem(myPlayer.SpdList[i], out, position, cel, celFrame);
DrawItem(myPlayer.SpdList[i], out, position, cel, celFrame);
if (AllItemsList[myPlayer.SpdList[i].IDidx].iUsable
&& myPlayer.SpdList[i]._itype != ItemType::Gold) {

6
Source/items.cpp

@ -24,7 +24,7 @@
#include "engine/dx.h"
#include "engine/load_cel.hpp"
#include "engine/random.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "inv_iterators.hpp"
@ -1757,7 +1757,7 @@ void PrintItemOil(char iDidx)
void DrawUniqueInfoWindow(const Surface &out)
{
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { 24 - SidePanelSize.width, 327 }), CelSprite { *pSTextBoxCels }, 0);
Cl2Draw(out, GetPanelPosition(UiPanels::Inventory, { 24 - SidePanelSize.width, 327 }), CelSprite { *pSTextBoxCels }, 0);
DrawHalfTransparentRectTo(out, GetRightPanel().position.x - SidePanelSize.width + 27, GetRightPanel().position.y + 28, 265, 297);
}
@ -2282,7 +2282,7 @@ void InitItemGFX()
int itemTypes = gbIsHellfire ? ITEMTYPES : 35;
for (int i = 0; i < itemTypes; i++) {
*BufCopy(arglist, "Items\\", ItemDropNames[i], ".CEL") = '\0';
itemanims[i] = LoadCel(arglist, ItemAnimWidth);
itemanims[i] = LoadCelAsCl2(arglist, ItemAnimWidth);
}
memset(UniqueItemFlags, 0, sizeof(UniqueItemFlags));
}

6
Source/minitext.cpp

@ -12,7 +12,7 @@
#include "engine/cel_sprite.hpp"
#include "engine/dx.h"
#include "engine/load_cel.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "textdat.h"
#include "utils/language.h"
@ -126,7 +126,7 @@ void FreeQuestText()
void InitQuestText()
{
pTextBoxCels = LoadCel("Data\\TextBox.CEL", 591);
pTextBoxCels = LoadCelAsCl2("Data\\TextBox.CEL", 591);
}
void InitQTextMsg(_speech_id m)
@ -144,7 +144,7 @@ void InitQTextMsg(_speech_id m)
void DrawQTextBack(const Surface &out)
{
const Point uiPosition = GetUIRectangle().position;
CelDrawTo(out, uiPosition + Displacement { 24, 327 }, CelSprite { *pTextBoxCels }, 0);
Cl2Draw(out, uiPosition + Displacement { 24, 327 }, CelSprite { *pTextBoxCels }, 0);
DrawHalfTransparentRectTo(out, uiPosition.x + 27, uiPosition.y + 28, 585, 297);
}

30
Source/objects.cpp

@ -17,6 +17,7 @@
#ifdef _DEBUG
#include "debug.h"
#endif
#include "engine/load_cel.hpp"
#include "engine/load_file.hpp"
#include "engine/points_in_rectangle_range.hpp"
#include "engine/random.hpp"
@ -4062,37 +4063,37 @@ bool IsItemBlockingObjectAtPosition(Point position)
return false;
}
void LoadLevelObjects(bool filesLoaded[65])
void LoadLevelObjects(uint16_t filesWidths[65])
{
if (HeadlessMode)
return;
for (const ObjectData objectData : AllObjects) {
if (leveltype == objectData.olvltype) {
filesLoaded[objectData.ofindex] = true;
filesWidths[objectData.ofindex] = objectData.oAnimWidth;
}
}
for (int i = OFILE_L1BRAZ; i <= OFILE_L5BOOKS; i++) {
if (!filesLoaded[i]) {
if (filesWidths[i] == 0) {
continue;
}
ObjFileList[numobjfiles] = static_cast<object_graphic_id>(i);
char filestr[32];
*BufCopy(filestr, "Objects\\", ObjMasterLoadList[i], ".CEL") = '\0';
pObjCels[numobjfiles] = LoadFileInMem(filestr);
pObjCels[numobjfiles] = LoadCelAsCl2(filestr, filesWidths[i]).data();
numobjfiles++;
}
}
void InitObjectGFX()
{
bool filesLoaded[65] = {};
uint16_t filesWidths[65] = {};
if (IsAnyOf(currlevel, 4, 8, 12)) {
filesLoaded[OFILE_BKSLBRNT] = true;
filesLoaded[OFILE_CANDLE2] = true;
filesWidths[OFILE_BKSLBRNT] = AllObjects[OBJ_STORYBOOK].oAnimWidth;
filesWidths[OFILE_CANDLE2] = AllObjects[OBJ_STORYCANDLE].oAnimWidth;
}
for (const ObjectData objectData : AllObjects) {
@ -4101,22 +4102,22 @@ void InitObjectGFX()
continue;
}
filesLoaded[objectData.ofindex] = true;
filesWidths[objectData.ofindex] = objectData.oAnimWidth;
}
if (objectData.otheme != THEME_NONE) {
for (int j = 0; j < numthemes; j++) {
if (themes[j].ttype == objectData.otheme) {
filesLoaded[objectData.ofindex] = true;
filesWidths[objectData.ofindex] = objectData.oAnimWidth;
}
}
}
if (objectData.oquest != Q_INVALID && Quests[objectData.oquest].IsAvailable()) {
filesLoaded[objectData.ofindex] = true;
filesWidths[objectData.ofindex] = objectData.oAnimWidth;
}
}
LoadLevelObjects(filesLoaded);
LoadLevelObjects(filesWidths);
}
void FreeObjectGFX()
@ -4347,7 +4348,7 @@ void InitObjects()
void SetMapObjects(const uint16_t *dunData, int startx, int starty)
{
bool filesLoaded[65] = {};
uint16_t filesWidths[65] = {};
ClrAllObjects();
ApplyObjectLighting = true;
@ -4367,12 +4368,13 @@ void SetMapObjects(const uint16_t *dunData, int startx, int starty)
for (int i = 0; i < width; i++) {
auto objectId = static_cast<uint8_t>(SDL_SwapLE16(objectLayer[j * width + i]));
if (objectId != 0) {
filesLoaded[AllObjects[ObjTypeConv[objectId]].ofindex] = true;
const ObjectData &objectData = AllObjects[ObjTypeConv[objectId]];
filesWidths[objectData.ofindex] = objectData.oAnimWidth;
}
}
}
LoadLevelObjects(filesLoaded);
LoadLevelObjects(filesWidths);
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {

10
Source/panels/charpanel.cpp

@ -7,7 +7,7 @@
#include "DiabloUI/art.h"
#include "DiabloUI/art_draw.h"
#include "control.h"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "panels/ui_panels.hpp"
#include "player.h"
@ -255,13 +255,13 @@ void DrawStatButtons(const Surface &out)
if (MyPlayer->_pStatPts > 0) {
CelSprite sprite { *pChrButtons };
if (MyPlayer->_pBaseStr < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Strength))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Strength)] ? 2 : 1);
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Strength)] ? 2 : 1);
if (MyPlayer->_pBaseMag < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Magic))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Magic)] ? 4 : 3);
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Magic)] ? 4 : 3);
if (MyPlayer->_pBaseDex < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Dexterity))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Dexterity)] ? 6 : 5);
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Dexterity)] ? 6 : 5);
if (MyPlayer->_pBaseVit < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Vitality))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Vitality)] ? 8 : 7);
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Vitality)] ? 8 : 7);
}
}

4
Source/panels/info_box.cpp

@ -9,8 +9,8 @@ OptionalOwnedCelSprite pSTextSlidCels;
void InitInfoBoxGfx()
{
pSTextSlidCels = LoadCel("Data\\TextSlid.CEL", 12);
pSTextBoxCels = LoadCel("Data\\TextBox2.CEL", 271);
pSTextSlidCels = LoadCelAsCl2("Data\\TextSlid.CEL", 12);
pSTextBoxCels = LoadCelAsCl2("Data\\TextBox2.CEL", 271);
}
void FreeInfoBoxGfx()

2
Source/panels/mainpanel.cpp

@ -1,7 +1,7 @@
#include "panels/mainpanel.hpp"
#include "control.h"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "utils/display.h"
#include "utils/language.h"

14
Source/panels/spell_book.cpp

@ -6,7 +6,7 @@
#include "engine/cel_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "missiles.h"
@ -81,9 +81,9 @@ spell_type GetSBookTrans(spell_id ii, bool townok)
void InitSpellBook()
{
pSpellBkCel = LoadCel("Data\\SpellBk.CEL", static_cast<uint16_t>(SidePanelSize.width));
pSBkBtnCel = LoadCel("Data\\SpellBkB.CEL", gbIsHellfire ? 61 : 76);
pSBkIconCels = LoadCel("Data\\SpellI2.CEL", 37);
pSpellBkCel = LoadCelAsCl2("Data\\SpellBk.CEL", static_cast<uint16_t>(SidePanelSize.width));
pSBkBtnCel = LoadCelAsCl2("Data\\SpellBkB.CEL", gbIsHellfire ? 61 : 76);
pSBkIconCels = LoadCelAsCl2("Data\\SpellI2.CEL", 37);
Player &player = *MyPlayer;
if (player._pClass == HeroClass::Warrior) {
@ -110,16 +110,16 @@ void FreeSpellBook()
void DrawSpellBook(const Surface &out)
{
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), CelSprite { *pSpellBkCel }, 0);
Cl2Draw(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), CelSprite { *pSpellBkCel }, 0);
if (gbIsHellfire && sbooktab < 5) {
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), CelSprite { *pSBkBtnCel }, sbooktab);
Cl2Draw(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), CelSprite { *pSBkBtnCel }, sbooktab);
} else {
// BUGFIX: rendering of page 3 and page 4 buttons are both off-by-one pixel (fixed).
int sx = 76 * sbooktab + 7;
if (sbooktab == 2 || sbooktab == 3) {
sx++;
}
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), CelSprite { *pSBkBtnCel }, sbooktab);
Cl2Draw(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), CelSprite { *pSBkBtnCel }, sbooktab);
}
Player &player = *MyPlayer;
uint64_t spl = player._pMemSpells | player._pISpells | player._pAblSpells;

8
Source/panels/spell_icons.cpp

@ -2,7 +2,7 @@
#include "engine/load_cel.hpp"
#include "engine/palette.h"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "init.h"
#include "utils/stdcompat/optional.hpp"
@ -71,9 +71,9 @@ const char SpellITbl[] = {
void LoadSpellIcons()
{
if (!gbIsHellfire)
pSpellCels = LoadCel("CtrlPan\\SpelIcon.CEL", SPLICONLENGTH);
pSpellCels = LoadCelAsCl2("CtrlPan\\SpelIcon.CEL", SPLICONLENGTH);
else
pSpellCels = LoadCel("Data\\SpelIcon.CEL", SPLICONLENGTH);
pSpellCels = LoadCelAsCl2("Data\\SpelIcon.CEL", SPLICONLENGTH);
SetSpellTrans(RSPLTYPE_SKILL);
}
@ -89,7 +89,7 @@ void DrawSpellCel(const Surface &out, Point position, int nCel)
void DrawSpellCel(const Surface &out, Point position, const OwnedCelSprite &sprite, int nCel)
{
CelDrawLightTo(out, position, CelSprite { sprite }, nCel, SplTransTbl);
Cl2DrawTRN(out, position, CelSprite { sprite }, nCel, SplTransTbl);
}
void SetSpellTrans(spell_type t)

4
Source/qol/itemlabels.cpp

@ -9,7 +9,7 @@
#include "control.h"
#include "cursor.h"
#include "engine/point.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "gmenu.h"
#include "inv.h"
#include "itemlabels.h"
@ -80,7 +80,7 @@ void AddItemToLabelQueue(int id, int x, int y)
nameWidth += MarginX * 2;
int index = ItemCAnimTbl[item._iCurs];
if (!labelCenterOffsets[index]) {
std::pair<int, int> itemBounds = MeasureSolidHorizontalBounds(*item.AnimInfo.celSprite, item.AnimInfo.currentFrame);
std::pair<int, int> itemBounds = Cl2MeasureSolidHorizontalBounds(*item.AnimInfo.celSprite, item.AnimInfo.currentFrame);
labelCenterOffsets[index].emplace((itemBounds.first + itemBounds.second) / 2);
}

8
Source/qol/stash.cpp

@ -10,7 +10,7 @@
#include "cursor.h"
#include "engine/points_in_rectangle_range.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/size.hpp"
#include "hwcursor.hpp"
@ -375,10 +375,10 @@ void DrawStash(const Surface &out)
if (pcursstashitem == itemId) {
uint8_t color = GetOutlineColor(item, true);
CelBlitOutlineTo(out, color, position, cel, celFrame, false);
Cl2DrawOutline(out, color, position, cel, celFrame);
}
CelDrawItem(item, out, position, cel, celFrame);
DrawItem(item, out, position, cel, celFrame);
}
Point position = GetPanelPosition(UiPanels::Stash);
@ -621,7 +621,7 @@ void DrawGoldWithdraw(const Surface &out, int amount)
const int dialogX = 30;
CelDrawTo(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), CelSprite { *pGBoxBuff }, 0);
Cl2Draw(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), CelSprite { *pGBoxBuff }, 0);
// Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words
const std::string wrapped = WordWrapString(_("How many gold pieces do you want to withdraw?"), 200);

8
Source/quests.cpp

@ -12,7 +12,7 @@
#include "cursor.h"
#include "engine/load_file.hpp"
#include "engine/random.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "levels/gendung.h"
@ -215,11 +215,11 @@ void PrintQLString(const Surface &out, int x, int y, string_view str, bool marke
int width = GetLineWidth(str);
x += std::max((257 - width) / 2, 0);
if (marked) {
CelDrawTo(out, GetPanelPosition(UiPanels::Quest, { x - 20, y + 13 }), CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
Cl2Draw(out, GetPanelPosition(UiPanels::Quest, { x - 20, y + 13 }), CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
}
DrawString(out, str, { GetPanelPosition(UiPanels::Quest, { x, y }), { 257, 0 } }, disabled ? UiFlags::ColorWhitegold : UiFlags::ColorWhite);
if (marked) {
CelDrawTo(out, GetPanelPosition(UiPanels::Quest, { x + width + 7, y + 13 }), CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
Cl2Draw(out, GetPanelPosition(UiPanels::Quest, { x + width + 7, y + 13 }), CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
}
}
@ -676,7 +676,7 @@ void DrawQuestLog(const Surface &out)
SelectedQuest = l;
}
const auto x = InnerPanel.position.x;
CelDrawTo(out, GetPanelPosition(UiPanels::Quest, { 0, 351 }), CelSprite { *pQLogCel }, 0);
Cl2Draw(out, GetPanelPosition(UiPanels::Quest, { 0, 351 }), CelSprite { *pQLogCel }, 0);
int y = InnerPanel.position.y + ListYOffset;
for (int i = 0; i < EncounteredQuestCount; i++) {
if (i == FirstFinishedQuest) {

20
Source/stores.cpp

@ -13,7 +13,7 @@
#include "cursor.h"
#include "engine/load_cel.hpp"
#include "engine/random.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "minitext.h"
@ -196,7 +196,7 @@ void CalculateLineHeights()
void DrawSTextBack(const Surface &out)
{
const Point uiPosition = GetUIRectangle().position;
CelDrawTo(out, { uiPosition.x + 320 + 24, 327 + uiPosition.y }, CelSprite { *pSTextBoxCels }, 0);
Cl2Draw(out, { uiPosition.x + 320 + 24, 327 + uiPosition.y }, CelSprite { *pSTextBoxCels }, 0);
DrawHalfTransparentRectTo(out, uiPosition.x + 347, uiPosition.y + 28, 265, 297);
}
@ -207,17 +207,17 @@ void DrawSSlider(const Surface &out, int y1, int y2)
int yd2 = y2 * 12 + 44 + uiPosition.y;
CelSprite sprite { *pSTextSlidCels };
if (stextscrlubtn != -1)
CelDrawTo(out, { uiPosition.x + 601, yd1 }, sprite, 11);
Cl2Draw(out, { uiPosition.x + 601, yd1 }, sprite, 11);
else
CelDrawTo(out, { uiPosition.x + 601, yd1 }, sprite, 9);
Cl2Draw(out, { uiPosition.x + 601, yd1 }, sprite, 9);
if (stextscrldbtn != -1)
CelDrawTo(out, { uiPosition.x + 601, yd2 }, sprite, 10);
Cl2Draw(out, { uiPosition.x + 601, yd2 }, sprite, 10);
else
CelDrawTo(out, { uiPosition.x + 601, yd2 }, sprite, 8);
Cl2Draw(out, { uiPosition.x + 601, yd2 }, sprite, 8);
yd1 += 12;
int yd3 = yd1;
for (; yd3 < yd2; yd3 += 12) {
CelDrawTo(out, { uiPosition.x + 601, yd3 }, sprite, 13);
Cl2Draw(out, { uiPosition.x + 601, yd3 }, sprite, 13);
}
if (stextsel == BackButtonLine())
yd3 = stextlhold;
@ -227,7 +227,7 @@ void DrawSSlider(const Surface &out, int y1, int y2)
yd3 = 1000 * (stextsval + ((yd3 - stextup) / 4)) / (storenumh - 1) * (y2 * 12 - y1 * 12 - 24) / 1000;
else
yd3 = 0;
CelDrawTo(out, { uiPosition.x + 601, (y1 + 1) * 12 + 44 + uiPosition.y + yd3 }, sprite, 12);
Cl2Draw(out, { uiPosition.x + 601, (y1 + 1) * 12 + 44 + uiPosition.y + yd3 }, sprite, 12);
}
void AddSLine(size_t y)
@ -2174,13 +2174,13 @@ void DrawSelector(const Surface &out, const Rectangle &rect, string_view text, U
if (HasAnyOf(flags, UiFlags::AlignCenter))
x1 += (rect.size.width - lineWidth) / 2;
CelDrawTo(out, { x1, rect.position.y + 13 }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
Cl2Draw(out, { x1, rect.position.y + 13 }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
int x2 = rect.position.x + rect.size.width + 5;
if (HasAnyOf(flags, UiFlags::AlignCenter))
x2 = rect.position.x + (rect.size.width - lineWidth) / 2 + lineWidth + 5;
CelDrawTo(out, { x2, rect.position.y + 13 }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
Cl2Draw(out, { x2, rect.position.y + 13 }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
}
} // namespace

6
Source/towners.cpp

@ -2,11 +2,13 @@
#include "cursor.h"
#include "engine/cel_header.hpp"
#include "engine/load_cel.hpp"
#include "engine/load_file.hpp"
#include "engine/random.hpp"
#include "inv.h"
#include "minitext.h"
#include "stores.h"
#include "utils/cel_to_cl2.hpp"
#include "utils/language.h"
namespace devilution {
@ -52,7 +54,7 @@ void InitTownerInfo(int i, const TownerData &townerData)
void LoadTownerAnimations(Towner &towner, const char *path, int frames, int delay)
{
towner.data = LoadFileInMem(path);
towner.data = LoadCelAsCl2(path, towner._tAnimWidth).data();
NewTownerAnim(towner, towner.data.get(), frames, delay);
}
@ -818,7 +820,7 @@ void InitTowners()
{
assert(CowCels == nullptr);
CowCels = LoadFileInMem("Towners\\Animals\\Cow.CEL");
CowCels = LoadCelAsCl2("Towners\\Animals\\Cow.CEL", 128).data();
int i = 0;
for (const auto &townerData : TownersData) {

208
Source/utils/cel_to_cl2.cpp

@ -0,0 +1,208 @@
#include "utils/cel_to_cl2.hpp"
#include <cstring>
#include <vector>
#ifdef DEBUG_CEL_TO_CL2_SIZE
#include <iomanip>
#include <iostream>
#endif
#include "appfat.h"
#include "utils/endian.hpp"
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<int8_t>(control);
}
void AppendCl2TransparentRun(unsigned width, std::vector<uint8_t> &out)
{
while (width >= 0x7F) {
out.push_back(0x7F);
width -= 0x7F;
}
if (width == 0)
return;
out.push_back(width);
}
void AppendCl2FillRun(uint8_t color, unsigned width, std::vector<uint8_t> &out)
{
while (width >= 0x3F) {
out.push_back(0x80);
out.push_back(color);
width -= 0x3F;
}
if (width == 0)
return;
out.push_back(0xBF - width);
out.push_back(color);
}
void AppendCl2PixelsRun(const uint8_t *src, unsigned width, std::vector<uint8_t> &out)
{
while (width >= 0x41) {
out.push_back(0xBF);
for (size_t i = 0; i < 0x41; ++i)
out.push_back(src[i]);
width -= 0x41;
src += 0x41;
}
if (width == 0)
return;
out.push_back(256 - width);
for (size_t i = 0; i < width; ++i)
out.push_back(src[i]);
}
void AppendCl2PixelsOrFillRun(const uint8_t *src, unsigned length, std::vector<uint8_t> &out)
{
const uint8_t *begin = src;
const uint8_t *prevColorBegin = src;
unsigned prevColorRunLength = 1;
uint8_t prevColor = *src++;
while (--length > 0) {
const uint8_t color = *src;
if (prevColor == color) {
++prevColorRunLength;
} else {
// A tunable parameter that decides at which minimum length we encode a fill run.
// 3 appears to be optimal for most of our data (much better than 2, rarely very slightly worse than 4).
constexpr unsigned MinFillRunLength = 3;
if (prevColorRunLength >= MinFillRunLength) {
AppendCl2PixelsRun(begin, prevColorBegin - begin, out);
AppendCl2FillRun(prevColor, prevColorRunLength, out);
begin = src;
}
prevColorBegin = src;
prevColorRunLength = 1;
prevColor = color;
}
++src;
}
AppendCl2PixelsRun(begin, prevColorBegin - begin, out);
AppendCl2FillRun(prevColor, prevColorRunLength, out);
}
} // namespace
OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths)
{
// A CEL file either begins with:
// 1. A CEL header.
// 2. A list of offsets to frame groups (each group is a CEL file).
size_t groupsHeaderSize = 0;
uint32_t numGroups = 1;
const uint32_t maybeNumFrames = LoadLE32(data);
std::vector<uint8_t> cl2Data;
// Most files become smaller with CL2. Allocate exactly enough bytes to avoid reallocation.
// The only file that becomes larger is Data\hf_logo3.CEL, by exactly 4445 bytes.
cl2Data.reserve(size + 4445);
// If it is a number of frames, then the last frame offset will be equal to the size of the file.
if (LoadLE32(&data[maybeNumFrames * 4 + 4]) != size) {
// maybeNumFrames is the address of the first group, right after
// the list of group offsets.
numGroups = maybeNumFrames / 4;
groupsHeaderSize = maybeNumFrames;
data += groupsHeaderSize;
cl2Data.resize(groupsHeaderSize);
}
for (size_t group = 0; group < numGroups; ++group) {
uint32_t numFrames;
if (numGroups == 1) {
numFrames = maybeNumFrames;
} else {
numFrames = LoadLE32(data);
WriteLE32(&cl2Data[4 * group], cl2Data.size());
}
// CL2 header: frame count, frame offset for each frame, file size
const size_t cl2DataOffset = cl2Data.size();
cl2Data.resize(cl2Data.size() + 4 * (2 + static_cast<size_t>(numFrames)));
WriteLE32(cl2Data.data(), numFrames);
const uint8_t *srcEnd = &data[LoadLE32(&data[4])];
for (size_t frame = 1; frame <= numFrames; ++frame) {
const uint8_t *src = srcEnd;
srcEnd = &data[LoadLE32(&data[4 * (frame + 1)])];
WriteLE32(&cl2Data[cl2DataOffset + 4 * frame], static_cast<uint32_t>(cl2Data.size() - cl2DataOffset));
// Skip CEL frame header if there is one.
constexpr size_t CelFrameHeaderSize = 10;
const bool celFrameHasHeader = LoadLE16(src) == CelFrameHeaderSize;
if (celFrameHasHeader)
src += CelFrameHeaderSize;
const unsigned frameWidth = widthOrWidths.HoldsPointer() ? widthOrWidths.AsPointer()[frame - 1] : widthOrWidths.AsValue();
// Frame header: 5 16-bit offsets to 32-pixel height blocks.
const size_t frameHeaderPos = cl2Data.size();
constexpr size_t FrameHeaderSize = 10;
cl2Data.resize(cl2Data.size() + FrameHeaderSize);
// Frame header offset (first of five):
WriteLE16(&cl2Data[frameHeaderPos], FrameHeaderSize);
unsigned transparentRunWidth = 0;
size_t line = 0;
while (src != srcEnd) {
// Process line:
for (unsigned remainingCelWidth = frameWidth; remainingCelWidth != 0;) {
uint8_t val = *src++;
if (IsCelTransparent(val)) {
val = GetCelTransparentWidth(val);
transparentRunWidth += val;
} else {
AppendCl2TransparentRun(transparentRunWidth, cl2Data);
transparentRunWidth = 0;
AppendCl2PixelsOrFillRun(src, val, cl2Data);
src += val;
}
remainingCelWidth -= val;
}
// Frame header offset:
switch (++line) {
case 32:
case 64:
case 96:
case 128:
// Finish any active transparent run to not allow it to go over an offset line boundary.
AppendCl2TransparentRun(transparentRunWidth, cl2Data);
transparentRunWidth = 0;
WriteLE16(&cl2Data[frameHeaderPos + line / 16], static_cast<uint16_t>(cl2Data.size() - frameHeaderPos));
break;
}
}
AppendCl2TransparentRun(transparentRunWidth, cl2Data);
}
WriteLE32(&cl2Data[cl2DataOffset + 4 * (1 + static_cast<size_t>(numFrames))], static_cast<uint32_t>(cl2Data.size() - cl2DataOffset));
data = srcEnd;
}
auto out = std::unique_ptr<byte[]>(new byte[cl2Data.size()]);
memcpy(&out[0], cl2Data.data(), cl2Data.size());
#ifdef DEBUG_CEL_TO_CL2_SIZE
std::cout << "\t" << size << "\t" << cl2Data.size() << "\t" << std::setprecision(1) << std::fixed << (static_cast<int>(cl2Data.size()) - static_cast<int>(size)) / ((float)size) * 100 << "%" << std::endl;
#endif
return OwnedCelSprite { std::move(out), widthOrWidths };
}
} // namespace devilution

13
Source/utils/cel_to_cl2.hpp

@ -0,0 +1,13 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "engine/cel_sprite.hpp"
#include "utils/pointer_value_union.hpp"
namespace devilution {
OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths);
} // namespace devilution
Loading…
Cancel
Save