You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
138 lines
4.3 KiB
138 lines
4.3 KiB
#include "utils/cl2_to_clx.hpp" |
|
|
|
#include <cstdint> |
|
#include <cstring> |
|
|
|
#include <vector> |
|
|
|
#include "utils/clx_decode.hpp" |
|
#include "utils/clx_encode.hpp" |
|
#include "utils/endian.hpp" |
|
|
|
namespace devilution { |
|
|
|
namespace { |
|
|
|
constexpr size_t FrameHeaderSize = 10; |
|
|
|
struct SkipSize { |
|
int_fast16_t wholeLines; |
|
int_fast16_t xOffset; |
|
}; |
|
SkipSize GetSkipSize(int_fast16_t overrun, int_fast16_t srcWidth) |
|
{ |
|
SkipSize result; |
|
result.wholeLines = overrun / srcWidth; |
|
result.xOffset = overrun - srcWidth * result.wholeLines; |
|
return result; |
|
} |
|
|
|
} // namespace |
|
|
|
uint16_t Cl2ToClx(const uint8_t *data, size_t size, |
|
PointerOrValue<uint16_t> widthOrWidths, std::vector<uint8_t> &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<uint8_t> 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], 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<size_t>(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<uint32_t>(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() + FrameHeaderSize); |
|
WriteLE16(&clxData[frameHeaderPos], FrameHeaderSize); |
|
WriteLE16(&clxData[frameHeaderPos + 2], frameWidth); |
|
|
|
unsigned transparentRunWidth = 0; |
|
int_fast16_t xOffset = 0; |
|
size_t frameHeight = 0; |
|
const uint8_t *src = frameBegin + FrameHeaderSize; |
|
while (src != frameEnd) { |
|
auto remainingWidth = static_cast<int_fast16_t>(frameWidth) - xOffset; |
|
while (remainingWidth > 0) { |
|
const BlitCommand cmd = ClxGetBlitCommand(src); |
|
switch (cmd.type) { |
|
case BlitType::Transparent: |
|
if (!pixels.empty()) { |
|
AppendClxPixelsOrFillRun(pixels.data(), pixels.size(), clxData); |
|
pixels.clear(); |
|
} |
|
|
|
transparentRunWidth += cmd.length; |
|
break; |
|
case BlitType::Fill: |
|
case BlitType::Pixels: |
|
AppendClxTransparentRun(transparentRunWidth, clxData); |
|
transparentRunWidth = 0; |
|
|
|
if (cmd.type == BlitType::Fill) { |
|
pixels.insert(pixels.end(), cmd.length, cmd.color); |
|
} else { // BlitType::Pixels |
|
pixels.insert(pixels.end(), src + 1, cmd.srcEnd); |
|
} |
|
break; |
|
} |
|
src = cmd.srcEnd; |
|
remainingWidth -= cmd.length; |
|
} |
|
|
|
++frameHeight; |
|
if (remainingWidth < 0) { |
|
const auto skipSize = GetSkipSize(-remainingWidth, static_cast<int_fast16_t>(frameWidth)); |
|
xOffset = skipSize.xOffset; |
|
frameHeight += skipSize.wholeLines; |
|
} else { |
|
xOffset = 0; |
|
} |
|
} |
|
if (!pixels.empty()) { |
|
AppendClxPixelsOrFillRun(pixels.data(), pixels.size(), clxData); |
|
pixels.clear(); |
|
} |
|
AppendClxTransparentRun(transparentRunWidth, clxData); |
|
|
|
WriteLE16(&clxData[frameHeaderPos + 4], frameHeight); |
|
memset(&clxData[frameHeaderPos + 6], 0, 4); |
|
} |
|
|
|
WriteLE32(&clxData[clxDataOffset + 4 * (1 + static_cast<size_t>(numFrames))], static_cast<uint32_t>(clxData.size() - clxDataOffset)); |
|
} |
|
return numGroups == 1 ? 0 : numGroups; |
|
} |
|
|
|
} // namespace devilution
|
|
|