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.
 
 
 
 
 
 

632 lines
14 KiB

#pragma once
/**
* @file clx_sprite.hpp
*
* @brief CLX format sprites.
*
* CLX is a format used for DevilutionX graphics at runtime.
*
* It is identical to CL2, except we use the frame header to store the frame's width and height.
*
* CLX frame header (10 bytes, same as CL2):
*
* Bytes | Type | Value
* :-----:|:--------:|------------------------------------
* 0..2 | uint16_t | offset to data start (same as CL2)
* 2..4 | uint16_t | width
* 4..6 | uint16_t | height
* 6..10 | - | unused
*
* The CLX format is otherwise identical to CL2.
*
* Since the header is identical to CL2, CL2 can be converted to CLX without reallocation.
*
* CL2 reference: https://github.com/savagesteel/d1-file-formats/blob/master/PC-Mac/CL2.md#2-file-structure
*/
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include "appfat.h"
#include "utils/endian.hpp"
#include "utils/intrusive_optional.hpp"
namespace devilution {
class OptionalClxSprite;
/**
* @brief A single CLX sprite.
*/
class ClxSprite {
static constexpr uint32_t HeaderSize = 10;
public:
explicit constexpr ClxSprite(const uint8_t *data, uint32_t dataSize)
: data_(data)
, pixel_data_size_(dataSize - HeaderSize)
{
assert(data != nullptr);
}
[[nodiscard]] constexpr uint16_t width() const
{
return LoadLE16(&data_[2]);
}
[[nodiscard]] constexpr uint16_t height() const
{
return LoadLE16(&data_[4]);
}
/**
* @brief The raw pixel data (CL2 frame data).
*
* Format: https://github.com/savagesteel/d1-file-formats/blob/master/PC-Mac/CL2.md#42-cl2-frame-data
*/
[[nodiscard]] constexpr const uint8_t *pixelData() const
{
return &data_[HeaderSize];
}
[[nodiscard]] constexpr uint32_t pixelDataSize() const
{
return pixel_data_size_;
}
constexpr bool operator==(const ClxSprite &other) const
{
return data_ == other.data_;
}
constexpr bool operator!=(const ClxSprite &other) const
{
return !(*this == other);
}
private:
// For OptionalClxSprite.
constexpr ClxSprite()
: data_(nullptr)
, pixel_data_size_(0)
{
}
const uint8_t *data_;
uint32_t pixel_data_size_;
friend class OptionalClxSprite;
};
class OwnedClxSpriteList;
class OptionalClxSpriteList;
class ClxSpriteListIterator;
/**
* @brief A list of `ClxSprite`s.
*/
class ClxSpriteList {
public:
explicit constexpr ClxSpriteList(const uint8_t *data)
: data_(data)
{
assert(data != nullptr);
}
ClxSpriteList(const OwnedClxSpriteList &owned);
[[nodiscard]] OwnedClxSpriteList clone() const;
[[nodiscard]] constexpr uint32_t numSprites() const
{
return LoadLE32(data_);
}
[[nodiscard]] constexpr ClxSprite operator[](size_t spriteIndex) const
{
assert(spriteIndex < numSprites());
const uint32_t begin = spriteOffset(spriteIndex);
const uint32_t end = spriteOffset(spriteIndex + 1);
return ClxSprite { &data_[begin], end - begin };
}
[[nodiscard]] constexpr uint32_t spriteOffset(size_t spriteIndex) const
{
return LoadLE32(&data_[4 + spriteIndex * 4]);
}
/** @brief The offset to the next sprite sheet, or file size if this is the last sprite sheet. */
[[nodiscard]] constexpr uint32_t nextSpriteSheetOffsetOrFileSize() const
{
return LoadLE32(&data_[4 + numSprites() * 4]);
}
[[nodiscard]] constexpr const uint8_t *data() const
{
return data_;
}
[[nodiscard]] constexpr ClxSpriteListIterator begin() const;
[[nodiscard]] constexpr ClxSpriteListIterator end() const;
private:
// For OptionalClxSpriteList.
constexpr ClxSpriteList()
: data_(nullptr)
{
}
const uint8_t *data_;
friend class OptionalClxSpriteList;
};
class ClxSpriteListIterator {
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = int;
using value_type = ClxSprite;
using pointer = void;
using reference = value_type &;
constexpr ClxSpriteListIterator(ClxSpriteList list, size_t index)
: list_(list)
, index_(index)
{
}
constexpr ClxSprite operator*()
{
return list_[index_];
}
constexpr ClxSpriteListIterator &operator++()
{
++index_;
return *this;
}
constexpr ClxSpriteListIterator operator++(int)
{
auto copy = *this;
++(*this);
return copy;
}
constexpr bool operator==(const ClxSpriteListIterator &other) const
{
return index_ == other.index_;
}
constexpr bool operator!=(const ClxSpriteListIterator &other) const
{
return !(*this == other);
}
private:
ClxSpriteList list_;
size_t index_;
};
inline constexpr ClxSpriteListIterator ClxSpriteList::begin() const
{
return { *this, 0 };
}
inline constexpr ClxSpriteListIterator ClxSpriteList::end() const
{
return { *this, numSprites() };
}
class OwnedClxSpriteSheet;
class OptionalClxSpriteSheet;
class ClxSpriteSheetIterator;
/**
* @brief A sprite sheet is a list of `ClxSpriteList`s.
*/
class ClxSpriteSheet {
public:
explicit constexpr ClxSpriteSheet(const uint8_t *data, uint16_t numLists)
: data_(data)
, num_lists_(numLists)
{
assert(data != nullptr);
assert(num_lists_ > 0);
}
ClxSpriteSheet(const OwnedClxSpriteSheet &owned);
[[nodiscard]] constexpr uint16_t numLists() const
{
return num_lists_;
}
[[nodiscard]] constexpr ClxSpriteList operator[](size_t sheetIndex) const
{
return ClxSpriteList { &data_[sheetOffset(sheetIndex)] };
}
[[nodiscard]] constexpr uint32_t sheetOffset(size_t sheetIndex) const
{
assert(sheetIndex < num_lists_);
return LoadLE32(&data_[4 * sheetIndex]);
}
[[nodiscard]] constexpr const uint8_t *data() const
{
return data_;
}
[[nodiscard]] constexpr ClxSpriteSheetIterator begin() const;
[[nodiscard]] constexpr ClxSpriteSheetIterator end() const;
private:
// For OptionalClxSpriteSheet.
constexpr ClxSpriteSheet()
: data_(nullptr)
, num_lists_(0)
{
}
const uint8_t *data_;
uint16_t num_lists_;
friend class OptionalClxSpriteSheet;
};
class ClxSpriteSheetIterator {
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = int;
using value_type = ClxSpriteList;
using pointer = void;
using reference = value_type &;
constexpr ClxSpriteSheetIterator(ClxSpriteSheet sheet, size_t index)
: sheet_(sheet)
, index_(index)
{
}
constexpr ClxSpriteList operator*()
{
return sheet_[index_];
}
constexpr ClxSpriteSheetIterator &operator++()
{
++index_;
return *this;
}
constexpr ClxSpriteSheetIterator operator++(int)
{
auto copy = *this;
++(*this);
return copy;
}
constexpr bool operator==(const ClxSpriteSheetIterator &other) const
{
return index_ == other.index_;
}
constexpr bool operator!=(const ClxSpriteSheetIterator &other) const
{
return !(*this == other);
}
private:
ClxSpriteSheet sheet_;
size_t index_;
};
inline constexpr ClxSpriteSheetIterator ClxSpriteSheet::begin() const
{
return { *this, 0 };
}
inline constexpr ClxSpriteSheetIterator ClxSpriteSheet::end() const
{
return { *this, num_lists_ };
}
class OptionalOwnedClxSpriteList;
class OwnedClxSpriteListOrSheet;
/**
* @brief Implicitly convertible to `ClxSpriteList` and owns its data.
*/
class OwnedClxSpriteList {
public:
explicit OwnedClxSpriteList(std::unique_ptr<uint8_t[]> &&data)
: data_(std::move(data))
{
assert(data_ != nullptr);
}
OwnedClxSpriteList(OwnedClxSpriteList &&) noexcept = default;
OwnedClxSpriteList &operator=(OwnedClxSpriteList &&) noexcept = default;
[[nodiscard]] OwnedClxSpriteList clone() const
{
return ClxSpriteList { *this }.clone();
}
[[nodiscard]] ClxSprite operator[](size_t spriteIndex) const
{
return ClxSpriteList { *this }[spriteIndex];
}
private:
// For OptionalOwnedClxSpriteList.
OwnedClxSpriteList() = default;
std::unique_ptr<uint8_t[]> data_;
friend class ClxSpriteList; // for implicit conversion
friend class OptionalOwnedClxSpriteList;
friend class OwnedClxSpriteListOrSheet;
};
inline ClxSpriteList::ClxSpriteList(const OwnedClxSpriteList &owned)
: data_(owned.data_.get())
{
}
inline OwnedClxSpriteList ClxSpriteList::clone() const
{
const size_t dataSize = nextSpriteSheetOffsetOrFileSize();
std::unique_ptr<uint8_t[]> data { new uint8_t[dataSize] };
memcpy(data.get(), data_, dataSize);
return OwnedClxSpriteList { std::move(data) };
}
/**
* @brief Implicitly convertible to `ClxSpriteSheet` and owns its data.
*/
class OwnedClxSpriteSheet {
public:
OwnedClxSpriteSheet(std::unique_ptr<uint8_t[]> &&data, uint16_t numLists)
: data_(std::move(data))
, num_lists_(numLists)
{
assert(data_ != nullptr);
assert(numLists > 0);
}
OwnedClxSpriteSheet(OwnedClxSpriteSheet &&) noexcept = default;
OwnedClxSpriteSheet &operator=(OwnedClxSpriteSheet &&) noexcept = default;
[[nodiscard]] ClxSpriteList operator[](size_t sheetIndex) const
{
return ClxSpriteSheet { *this }[sheetIndex];
}
[[nodiscard]] ClxSpriteSheetIterator begin() const
{
return ClxSpriteSheet { *this }.begin();
}
[[nodiscard]] ClxSpriteSheetIterator end() const
{
return ClxSpriteSheet { *this }.end();
}
private:
// For OptionalOwnedClxSpriteList.
OwnedClxSpriteSheet()
: data_(nullptr)
, num_lists_(0)
{
}
std::unique_ptr<uint8_t[]> data_;
uint16_t num_lists_;
friend class ClxSpriteSheet; // for implicit conversion.
friend class OptionalOwnedClxSpriteSheet;
friend class OwnedClxSpriteListOrSheet;
};
inline ClxSpriteSheet::ClxSpriteSheet(const OwnedClxSpriteSheet &owned)
: data_(owned.data_.get())
, num_lists_(owned.num_lists_)
{
}
class OwnedClxSpriteListOrSheet;
class OptionalClxSpriteListOrSheet;
inline uint16_t GetNumListsFromClxListOrSheetBuffer(const uint8_t *data, size_t size)
{
const uint32_t maybeNumFrames = LoadLE32(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)
return maybeNumFrames / 4;
// Not a sprite sheet.
return 0;
}
/**
* @brief A CLX sprite list or a sprite sheet (list of lists).
*/
class ClxSpriteListOrSheet {
public:
constexpr ClxSpriteListOrSheet(const uint8_t *data, uint16_t numLists)
: data_(data)
, num_lists_(numLists)
{
}
ClxSpriteListOrSheet(const OwnedClxSpriteListOrSheet &listOrSheet);
[[nodiscard]] constexpr ClxSpriteList list() const
{
assert(num_lists_ == 0);
return ClxSpriteList { data_ };
}
[[nodiscard]] constexpr ClxSpriteSheet sheet() const
{
assert(num_lists_ != 0);
return ClxSpriteSheet { data_, num_lists_ };
}
[[nodiscard]] constexpr bool isSheet() const
{
return num_lists_ != 0;
}
private:
const uint8_t *data_;
uint16_t num_lists_;
// For OptionalClxSpriteListOrSheet.
constexpr ClxSpriteListOrSheet()
: data_(nullptr)
, num_lists_(0)
{
}
friend class OptionalClxSpriteListOrSheet;
};
class OptionalOwnedClxSpriteListOrSheet;
/**
* @brief A CLX sprite list or a sprite sheet (list of lists).
*/
class OwnedClxSpriteListOrSheet {
public:
static OwnedClxSpriteListOrSheet FromBuffer(std::unique_ptr<uint8_t[]> &&data, size_t size)
{
const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(data.get(), size);
return OwnedClxSpriteListOrSheet { std::move(data), numLists };
}
explicit OwnedClxSpriteListOrSheet(std::unique_ptr<uint8_t[]> &&data, uint16_t numLists)
: data_(std::move(data))
, num_lists_(numLists)
{
}
explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteSheet &&sheet)
: data_(std::move(sheet.data_))
, num_lists_(sheet.num_lists_)
{
}
explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteList &&list)
: data_(std::move(list.data_))
, num_lists_(0)
{
}
[[nodiscard]] ClxSpriteList list() const &
{
assert(num_lists_ == 0);
return ClxSpriteList { data_.get() };
}
[[nodiscard]] OwnedClxSpriteList list() &&
{
assert(num_lists_ == 0);
return OwnedClxSpriteList { std::move(data_) };
}
[[nodiscard]] ClxSpriteSheet sheet() const &
{
assert(num_lists_ != 0);
return ClxSpriteSheet { data_.get(), num_lists_ };
}
[[nodiscard]] OwnedClxSpriteSheet sheet() &&
{
assert(num_lists_ != 0);
return OwnedClxSpriteSheet { std::move(data_), num_lists_ };
}
[[nodiscard]] bool isSheet() const
{
return num_lists_ != 0;
}
private:
std::unique_ptr<uint8_t[]> data_;
uint16_t num_lists_;
// For OptionalOwnedClxSpriteListOrSheet.
OwnedClxSpriteListOrSheet()
: data_(nullptr)
, num_lists_(0)
{
}
friend class ClxSpriteListOrSheet;
friend class OptionalOwnedClxSpriteListOrSheet;
};
inline ClxSpriteListOrSheet::ClxSpriteListOrSheet(const OwnedClxSpriteListOrSheet &listOrSheet)
: data_(listOrSheet.data_.get())
, num_lists_(listOrSheet.num_lists_)
{
}
/**
* @brief Equivalent to `std::optional<ClxSprite>` but smaller.
*/
class OptionalClxSprite {
DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSprite, ClxSprite, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<ClxSpriteList>` but smaller.
*/
class OptionalClxSpriteList {
DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSpriteList, ClxSpriteList, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<ClxSpriteSheet>` but smaller.
*/
class OptionalClxSpriteSheet {
DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSpriteSheet, ClxSpriteSheet, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<ClxSpriteListOrSheet>` but smaller.
*/
class OptionalClxSpriteListOrSheet {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalClxSpriteListOrSheet, ClxSpriteListOrSheet, data_, nullptr);
};
/**
* @brief Equivalent to `std::optional<OwnedClxSpriteList>` but smaller.
*/
class OptionalOwnedClxSpriteList {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteList, OwnedClxSpriteList, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<OwnedClxSpriteSheet>` but smaller.
*/
class OptionalOwnedClxSpriteSheet {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteSheet, OwnedClxSpriteSheet, data_, nullptr)
};
class OptionalOwnedClxSpriteListOrSheet {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteListOrSheet, OwnedClxSpriteListOrSheet, data_, nullptr);
};
} // namespace devilution