From 8ca71272b8e06ad0eb531425bdf6d8a50b78db6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Mon, 18 Jul 2022 14:17:45 +0100 Subject: [PATCH] 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). --- Source/CMakeLists.txt | 1 + Source/DiabloUI/diabloui.cpp | 6 +- Source/control.cpp | 50 +++---- Source/controls/touch/renderers.cpp | 4 +- Source/cursor.cpp | 31 ++-- Source/cursor.h | 4 +- Source/debug.cpp | 2 +- Source/diablo.cpp | 14 +- Source/doom.cpp | 6 +- Source/engine/cel_sprite.hpp | 16 +++ Source/engine/load_cel.cpp | 26 ++++ Source/engine/load_cel.hpp | 3 + Source/engine/render/cel_render.hpp | 2 +- Source/engine/render/cl2_render.cpp | 149 ++++++++++++------- Source/engine/render/cl2_render.hpp | 42 +++++- Source/engine/render/scrollrt.cpp | 31 ++-- Source/engine/render/text_render.cpp | 8 +- Source/error.cpp | 18 +-- Source/gmenu.cpp | 22 +-- Source/hwcursor.cpp | 2 +- Source/interfac.cpp | 6 +- Source/inv.cpp | 30 ++-- Source/items.cpp | 6 +- Source/minitext.cpp | 6 +- Source/objects.cpp | 30 ++-- Source/panels/charpanel.cpp | 10 +- Source/panels/info_box.cpp | 4 +- Source/panels/mainpanel.cpp | 2 +- Source/panels/spell_book.cpp | 14 +- Source/panels/spell_icons.cpp | 8 +- Source/qol/itemlabels.cpp | 4 +- Source/qol/stash.cpp | 8 +- Source/quests.cpp | 8 +- Source/stores.cpp | 20 +-- Source/towners.cpp | 6 +- Source/utils/cel_to_cl2.cpp | 208 +++++++++++++++++++++++++++ Source/utils/cel_to_cl2.hpp | 13 ++ 37 files changed, 597 insertions(+), 223 deletions(-) create mode 100644 Source/utils/cel_to_cl2.cpp create mode 100644 Source/utils/cel_to_cl2.hpp diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index cef9d50cc..1583510f1 100644 --- a/Source/CMakeLists.txt +++ b/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 diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 2681e2550..8a4f6a840 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/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(p.y + sprite.frameHeight) }, sprite.sprite, 0); + Cl2Draw(out, { p.x, static_cast(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(p.y + sprite.frameHeight) }, sprite.sprite, frame); + Cl2Draw(out, { p.x, static_cast(p.y + sprite.frameHeight) }, sprite.sprite, frame); } void DrawSelector(const SDL_Rect &rect) diff --git a/Source/control.cpp b/Source/control.cpp index 6aa458a41..068b819b4 100644 --- a/Source/control.cpp +++ b/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(SidePanelSize.width)); - pGBoxBuff = LoadCel("CtrlPan\\Golddrop.cel", 261); + pQLogCel = LoadCelAsCl2("Data\\Quest.CEL", static_cast(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) { diff --git a/Source/controls/touch/renderers.cpp b/Source/controls/touch/renderers.cpp index cebe9e211..81ed2c15c 100644 --- a/Source/controls/touch/renderers.cpp +++ b/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; diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 853c3e5e3..9786b9008 100644 --- a/Source/cursor.cpp +++ b/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); } } diff --git a/Source/cursor.h b/Source/cursor.h index ced3b06b1..e70f5cbe8 100644 --- a/Source/cursor.h +++ b/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); diff --git a/Source/debug.cpp b/Source/debug.cpp index b5db16209..6bb6715e2 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -990,7 +990,7 @@ std::vector DebugCmdList = { void LoadDebugGFX() { - pSquareCel = LoadCel("Data\\Square.CEL", 64); + pSquareCel = LoadCelAsCl2("Data\\Square.CEL", 64); } void FreeDebugGFX() diff --git a/Source/diablo.cpp b/Source/diablo.cpp index b78d65cec..b2e8a4fed 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -1090,37 +1090,37 @@ void LoadLvlGFX() pDungeonCels = LoadFileInMem("Levels\\TownData\\Town.CEL"); pMegaTiles = LoadFileInMem("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("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("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("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("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("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("NLevels\\L5Data\\L5.TIL"); - pSpecialCels = LoadCel("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth); + pSpecialCels = LoadCelAsCl2("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth); break; default: app_fatal("LoadLvlGFX"); diff --git a/Source/doom.cpp b/Source/doom.cpp index ab35298ce..626e46160 100644 --- a/Source/doom.cpp +++ b/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 diff --git a/Source/engine/cel_sprite.hpp b/Source/engine/cel_sprite.hpp index 793cb8d92..11c74d36f 100644 --- a/Source/engine/cel_sprite.hpp +++ b/Source/engine/cel_sprite.hpp @@ -54,6 +54,11 @@ public: return width_.HoldsPointer() ? width_.AsPointer()[frame] : width_.AsValue(); } + [[nodiscard]] PointerOrValue 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 data, PointerOrValue 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 data() && + { + return std::move(data_); + } + private: // for OptionalOwnedCelSprite. OwnedCelSprite() diff --git a/Source/engine/load_cel.cpp b/Source/engine/load_cel.cpp index 14261f5e5..7a1de7cb6 100644 --- a/Source/engine/load_cel.cpp +++ b/Source/engine/load_cel.cpp @@ -1,6 +1,12 @@ #include "engine/load_cel.hpp" +#ifdef DEBUG_CEL_TO_CL2_SIZE +#include +#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 data = LoadFileInMem(pszName, &size); +#ifdef DEBUG_CEL_TO_CL2_SIZE + std::cout << pszName; +#endif + return CelToCl2(data.get(), size, PointerOrValue { width }); +} + +OwnedCelSprite LoadCelAsCl2(const char *pszName, const uint16_t *widths) +{ + size_t size; + std::unique_ptr data = LoadFileInMem(pszName, &size); +#ifdef DEBUG_CEL_TO_CL2_SIZE + std::cout << pszName; +#endif + return CelToCl2(data.get(), size, PointerOrValue { widths }); +} + } // namespace devilution diff --git a/Source/engine/load_cel.hpp b/Source/engine/load_cel.hpp index 14e031750..5e7f9e77a 100644 --- a/Source/engine/load_cel.hpp +++ b/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 diff --git a/Source/engine/render/cel_render.hpp b/Source/engine/render/cel_render.hpp index b2443d5ab..a90934021 100644 --- a/Source/engine/render/cel_render.hpp +++ b/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 diff --git a/Source/engine/render/cl2_render.cpp b/Source/engine/render/cl2_render.cpp index 351420213..e2d0b8715 100644 --- a/Source/engine/render/cl2_render.cpp +++ b/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( out, position, reinterpret_cast(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( out, position, reinterpret_cast(pRLEBytes), nDataSize, nWidth, BlitWithMap { pTable }); } -template +void Cl2BlitBlendedTRN(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *pTable) +{ + DoRenderBackwards( + out, position, reinterpret_cast(pRLEBytes), nDataSize, nWidth, BlitBlendedWithMap { pTable }); +} + +template 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( dst++, dstPitch, color); } else { - RenderOutlineForPixel( + RenderOutlineForPixel( dst++, dstPitch, *src++, color); } --width; @@ -110,7 +116,7 @@ uint8_t *RenderCl2OutlinePixelsCheckFirstColumn( if (Fill) { RenderOutlineForPixel(dst++, dstPitch, color); } else { - RenderOutlineForPixel(dst++, dstPitch, *src++, color); + RenderOutlineForPixel(dst++, dstPitch, *src++, color); } --width; } @@ -118,14 +124,14 @@ uint8_t *RenderCl2OutlinePixelsCheckFirstColumn( if (Fill) { RenderOutlineForPixels(dst, dstPitch, width, color); } else { - RenderOutlineForPixels(dst, dstPitch, width, src, color); + RenderOutlineForPixels(dst, dstPitch, width, src, color); } dst += width; } return dst; } -template +template 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(dst, dstPitch, width, color); } else { - RenderOutlineForPixels(dst, dstPitch, width, src, color); + RenderOutlineForPixels(dst, dstPitch, width, src, color); src += width; } dst += width; @@ -147,44 +153,44 @@ uint8_t *RenderCl2OutlinePixelsCheckLastColumn( if (Fill) { RenderOutlineForPixel(dst++, dstPitch, color); } else { - RenderOutlineForPixel(dst++, dstPitch, *src++, color); + RenderOutlineForPixel(dst++, dstPitch, *src++, color); } } if (oobPixel) { if (Fill) { RenderOutlineForPixel(dst++, dstPitch, color); } else { - RenderOutlineForPixel(dst++, dstPitch, *src++, color); + RenderOutlineForPixel(dst++, dstPitch, *src++, color); } } return dst; } -template +template 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( + return RenderCl2OutlinePixelsCheckFirstColumn( dst, dstPitch, dstX, src, width, color); } if (CheckLastColumn && dstX + width >= dstW) { - return RenderCl2OutlinePixelsCheckLastColumn( + return RenderCl2OutlinePixelsCheckLastColumn( dst, dstPitch, dstX, dstW, src, width, color); } if (Fill) { RenderOutlineForPixels(dst, dstPitch, width, color); } else { - RenderOutlineForPixels(dst, dstPitch, width, src, color); + RenderOutlineForPixels(dst, dstPitch, width, src, color); } return dst + width; } -template 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( + dst = RenderCl2OutlinePixels( dst, dstPitch, position.x, out.w(), src, w, color); ++src; } else { - dst = RenderCl2OutlinePixels( + dst = RenderCl2OutlinePixels( 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 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( + src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(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( + src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } while (position.y > 0 && src.begin != src.end) { - src.begin = RenderCl2OutlineRowClipped( + src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } @@ -312,7 +319,7 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw return; if (position.y == 0) { - src.begin = RenderCl2OutlineRowClipped( + src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(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( + RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); } } +template 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(out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } position.y -= static_cast(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( out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped( 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( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } } else if (position.x + clipX.width >= out.w()) { while (position.y > 0 && src.begin != src.end) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } } else { while (position.y > 0 && src.begin != src.end) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(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( out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped( 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(out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } } } +template 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(srcWidth) }; if (position.x > 0 && position.x + static_cast(srcWidth) < static_cast(out.w())) { - RenderCl2OutlineClippedY(out, position, srcForBackwards, color); + RenderCl2OutlineClippedY(out, position, srcForBackwards, color); } else { - RenderCl2OutlineClippedXY(out, position, srcForBackwards, color); + RenderCl2OutlineClippedXY(out, position, srcForBackwards, color); } } @@ -485,6 +494,42 @@ void Cl2ApplyTrans(byte *p, const std::array &ttbl, int numFrames) } } +std::pair 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(*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(src), nDataSize, cel.Width(frame), col); + RenderCl2Outline(out, position, reinterpret_cast(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(out, position, reinterpret_cast(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 diff --git a/Source/engine/render/cl2_render.hpp b/Source/engine/render/cl2_render.hpp index 2dadc0cc2..ffa473b50 100644 --- a/Source/engine/render/cl2_render.hpp +++ b/Source/engine/render/cl2_render.hpp @@ -5,12 +5,15 @@ */ #pragma once -#include #include +#include +#include + #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 &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 Cl2MeasureSolidHorizontalBounds(CelSprite cel, int frame = 0); } // namespace devilution diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 5b8c0a74f..5e38859f5 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/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); } } } diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index 129b4ba87..8ddc48445 100644 --- a/Source/engine/render/text_render.cpp +++ b/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, '|'); } diff --git a/Source/error.cpp b/Source/error.cpp index d88e24ae3..b0e6d7b19 100644 --- a/Source/error.cpp +++ b/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; } diff --git a/Source/gmenu.cpp b/Source/gmenu.cpp index 3ed34a59f..40be49c77 100644 --- a/Source/gmenu.cpp +++ b/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((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) { diff --git a/Source/hwcursor.cpp b/Source/hwcursor.cpp index 2b4883876..e7de6a19f 100644 --- a/Source/hwcursor.cpp +++ b/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; diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 40cbb22f9..5254fe92b 100644 --- a/Source/interfac.cpp +++ b/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() diff --git a/Source/inv.cpp b/Source/inv.cpp index c5edb58eb..e2cac611e 100644 --- a/Source/inv.cpp +++ b/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(SidePanelSize.width)); + pInvCels = LoadCelAsCl2("Data\\Inv\\Inv.CEL", static_cast(SidePanelSize.width)); break; case HeroClass::Rogue: case HeroClass::Bard: - pInvCels = LoadCel("Data\\Inv\\Inv_rog.CEL", static_cast(SidePanelSize.width)); + pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_rog.CEL", static_cast(SidePanelSize.width)); break; case HeroClass::Sorcerer: - pInvCels = LoadCel("Data\\Inv\\Inv_Sor.CEL", static_cast(SidePanelSize.width)); + pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_Sor.CEL", static_cast(SidePanelSize.width)); break; case HeroClass::Monk: - pInvCels = LoadCel(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast(SidePanelSize.width)); + pInvCels = LoadCelAsCl2(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast(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) { diff --git a/Source/items.cpp b/Source/items.cpp index 49545adb5..4ca165df6 100644 --- a/Source/items.cpp +++ b/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)); } diff --git a/Source/minitext.cpp b/Source/minitext.cpp index 04e32943c..005123f5a 100644 --- a/Source/minitext.cpp +++ b/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); } diff --git a/Source/objects.cpp b/Source/objects.cpp index cde27e3df..cc48424ea 100644 --- a/Source/objects.cpp +++ b/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(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(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++) { diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 85581debd..26e8cc6f8 100644 --- a/Source/panels/charpanel.cpp +++ b/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(CharacterAttribute::Strength)] ? 2 : 1); + Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), sprite, chrbtn[static_cast(CharacterAttribute::Strength)] ? 2 : 1); if (MyPlayer->_pBaseMag < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Magic)) - CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), sprite, chrbtn[static_cast(CharacterAttribute::Magic)] ? 4 : 3); + Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), sprite, chrbtn[static_cast(CharacterAttribute::Magic)] ? 4 : 3); if (MyPlayer->_pBaseDex < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Dexterity)) - CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), sprite, chrbtn[static_cast(CharacterAttribute::Dexterity)] ? 6 : 5); + Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), sprite, chrbtn[static_cast(CharacterAttribute::Dexterity)] ? 6 : 5); if (MyPlayer->_pBaseVit < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Vitality)) - CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), sprite, chrbtn[static_cast(CharacterAttribute::Vitality)] ? 8 : 7); + Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), sprite, chrbtn[static_cast(CharacterAttribute::Vitality)] ? 8 : 7); } } diff --git a/Source/panels/info_box.cpp b/Source/panels/info_box.cpp index 14a6c6929..9dc9ee5cf 100644 --- a/Source/panels/info_box.cpp +++ b/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() diff --git a/Source/panels/mainpanel.cpp b/Source/panels/mainpanel.cpp index 1ea6ed488..61e8c6fb8 100644 --- a/Source/panels/mainpanel.cpp +++ b/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" diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index 0db36e4fa..dbc317e3b 100644 --- a/Source/panels/spell_book.cpp +++ b/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(SidePanelSize.width)); - pSBkBtnCel = LoadCel("Data\\SpellBkB.CEL", gbIsHellfire ? 61 : 76); - pSBkIconCels = LoadCel("Data\\SpellI2.CEL", 37); + pSpellBkCel = LoadCelAsCl2("Data\\SpellBk.CEL", static_cast(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; diff --git a/Source/panels/spell_icons.cpp b/Source/panels/spell_icons.cpp index cbcaa26f0..d13f9a865 100644 --- a/Source/panels/spell_icons.cpp +++ b/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) diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp index 80ca24936..066fe365a 100644 --- a/Source/qol/itemlabels.cpp +++ b/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 itemBounds = MeasureSolidHorizontalBounds(*item.AnimInfo.celSprite, item.AnimInfo.currentFrame); + std::pair itemBounds = Cl2MeasureSolidHorizontalBounds(*item.AnimInfo.celSprite, item.AnimInfo.currentFrame); labelCenterOffsets[index].emplace((itemBounds.first + itemBounds.second) / 2); } diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 9459080ca..07d9d3628 100644 --- a/Source/qol/stash.cpp +++ b/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); diff --git a/Source/quests.cpp b/Source/quests.cpp index 75d39ec9f..feee92906 100644 --- a/Source/quests.cpp +++ b/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) { diff --git a/Source/stores.cpp b/Source/stores.cpp index bb9efe231..20e2bc171 100644 --- a/Source/stores.cpp +++ b/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 diff --git a/Source/towners.cpp b/Source/towners.cpp index 00ad41e4b..85efe5dbd 100644 --- a/Source/towners.cpp +++ b/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) { diff --git a/Source/utils/cel_to_cl2.cpp b/Source/utils/cel_to_cl2.cpp new file mode 100644 index 000000000..97dfd474f --- /dev/null +++ b/Source/utils/cel_to_cl2.cpp @@ -0,0 +1,208 @@ +#include "utils/cel_to_cl2.hpp" + +#include + +#include + +#ifdef DEBUG_CEL_TO_CL2_SIZE +#include +#include +#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(control); +} + +void AppendCl2TransparentRun(unsigned width, std::vector &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 &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 &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 &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 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 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(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(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(cl2Data.size() - frameHeaderPos)); + break; + } + } + AppendCl2TransparentRun(transparentRunWidth, cl2Data); + } + + WriteLE32(&cl2Data[cl2DataOffset + 4 * (1 + static_cast(numFrames))], static_cast(cl2Data.size() - cl2DataOffset)); + data = srcEnd; + } + + auto out = std::unique_ptr(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(cl2Data.size()) - static_cast(size)) / ((float)size) * 100 << "%" << std::endl; +#endif + return OwnedCelSprite { std::move(out), widthOrWidths }; +} + +} // namespace devilution diff --git a/Source/utils/cel_to_cl2.hpp b/Source/utils/cel_to_cl2.hpp new file mode 100644 index 000000000..8bfaaa3ed --- /dev/null +++ b/Source/utils/cel_to_cl2.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#include "engine/cel_sprite.hpp" +#include "utils/pointer_value_union.hpp" + +namespace devilution { + +OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue widthOrWidths); + +} // namespace devilution