#include "utils/cl2_to_clx.hpp" #include #include #include #include "utils/clx_decode.hpp" #include "utils/clx_encode.hpp" #include "utils/endian.hpp" namespace devilution { uint16_t Cl2ToClx(const uint8_t *data, size_t size, PointerOrValue widthOrWidths, std::vector &clxData) { uint32_t numGroups = 1; const uint32_t maybeNumFrames = LoadLE32(data); const uint8_t *groupBegin = data; // 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; clxData.resize(maybeNumFrames); } // Transient buffer for a contiguous run of non-transparent pixels. std::vector pixels; pixels.reserve(4096); for (size_t group = 0; group < numGroups; ++group) { uint32_t numFrames; if (numGroups == 1) { numFrames = maybeNumFrames; } else { groupBegin = &data[LoadLE32(&data[group * 4])]; numFrames = LoadLE32(groupBegin); WriteLE32(&clxData[4 * group], static_cast(clxData.size())); } // CLX header: frame count, frame offset for each frame, file size const size_t clxDataOffset = clxData.size(); clxData.resize(clxData.size() + 4 * (2 + static_cast(numFrames))); WriteLE32(&clxData[clxDataOffset], numFrames); const uint8_t *frameEnd = &groupBegin[LoadLE32(&groupBegin[4])]; for (size_t frame = 1; frame <= numFrames; ++frame) { WriteLE32(&clxData[clxDataOffset + 4 * frame], static_cast(clxData.size() - clxDataOffset)); const uint8_t *frameBegin = frameEnd; frameEnd = &groupBegin[LoadLE32(&groupBegin[4 * (frame + 1)])]; const uint16_t frameWidth = widthOrWidths.HoldsPointer() ? widthOrWidths.AsPointer()[frame - 1] : widthOrWidths.AsValue(); const size_t frameHeaderPos = clxData.size(); clxData.resize(clxData.size() + ClxFrameHeaderSize); WriteLE16(&clxData[frameHeaderPos], ClxFrameHeaderSize); WriteLE16(&clxData[frameHeaderPos + 2], frameWidth); unsigned transparentRunWidth = 0; int_fast16_t xOffset = 0; size_t frameHeight = 0; const uint8_t *src = frameBegin + LoadLE16(frameBegin); while (src != frameEnd) { auto remainingWidth = static_cast(frameWidth) - xOffset; while (remainingWidth > 0) { const uint8_t control = *src++; if (!IsClxOpaque(control)) { if (!pixels.empty()) { AppendClxPixelsOrFillRun(pixels.data(), pixels.size(), clxData); pixels.clear(); } transparentRunWidth += control; remainingWidth -= control; } else if (IsClxOpaqueFill(control)) { AppendClxTransparentRun(transparentRunWidth, clxData); transparentRunWidth = 0; const uint8_t width = GetClxOpaqueFillWidth(control); const uint8_t color = *src++; pixels.insert(pixels.end(), width, color); remainingWidth -= width; } else { AppendClxTransparentRun(transparentRunWidth, clxData); transparentRunWidth = 0; const uint8_t width = GetClxOpaquePixelsWidth(control); pixels.insert(pixels.end(), src, src + width); src += width; remainingWidth -= width; } } const auto skipSize = GetSkipSize(remainingWidth, static_cast(frameWidth)); xOffset = skipSize.xOffset; frameHeight += skipSize.wholeLines; } if (!pixels.empty()) { AppendClxPixelsOrFillRun(pixels.data(), pixels.size(), clxData); pixels.clear(); } AppendClxTransparentRun(transparentRunWidth, clxData); WriteLE16(&clxData[frameHeaderPos + 4], static_cast(frameHeight)); } WriteLE32(&clxData[clxDataOffset + 4 * (1 + static_cast(numFrames))], static_cast(clxData.size() - clxDataOffset)); } return numGroups == 1 ? 0 : numGroups; } } // namespace devilution