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.
638 lines
15 KiB
638 lines
15 KiB
#pragma once |
|
/** |
|
* @file clx_sprite.hpp |
|
* |
|
* @brief CLX format sprites. |
|
* |
|
* CLX is a format used for DevilutionX graphics at runtime. |
|
* CLX encodes pixel in the same way CL2 but encodes metadata differently. |
|
* |
|
* Unlike CL2: |
|
* |
|
* 1. CLX frame header stores frame width and height. |
|
* 2. CLX frame header does not store 32-pixel block offsets. |
|
* |
|
* CLX frame header is 6 bytes: |
|
* |
|
* Bytes | Type | Value |
|
* :-----:|:--------:|------------- |
|
* 0..2 | uint16_t | header size |
|
* 2..4 | uint16_t | width |
|
* 4..6 | uint16_t | height |
|
* |
|
* 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 { |
|
public: |
|
explicit constexpr ClxSprite(const uint8_t *data, uint32_t dataSize) |
|
: data_(data) |
|
, pixel_data_size_(dataSize - LoadLE16(data)) |
|
{ |
|
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_[LoadLE16(data_)]; |
|
} |
|
|
|
[[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() = default; |
|
|
|
const uint8_t *data_ = nullptr; |
|
uint32_t pixel_data_size_ = 0; |
|
|
|
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 dataSize() 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() = default; |
|
|
|
const uint8_t *data_ = nullptr; |
|
|
|
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; |
|
|
|
[[nodiscard]] size_t dataSize() const |
|
{ |
|
return static_cast<size_t>(&data_[sheetOffset(num_lists_ - 1)] + (*this)[num_lists_ - 1].dataSize() - &data_[0]); |
|
} |
|
|
|
private: |
|
// For OptionalClxSpriteSheet. |
|
constexpr ClxSpriteSheet() = default; |
|
|
|
const uint8_t *data_ = nullptr; |
|
uint16_t num_lists_ = 0; |
|
|
|
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]; |
|
} |
|
|
|
[[nodiscard]] uint32_t numSprites() const |
|
{ |
|
return ClxSpriteList { *this }.numSprites(); |
|
} |
|
|
|
[[nodiscard]] size_t dataSize() const |
|
{ |
|
return ClxSpriteList { *this }.dataSize(); |
|
} |
|
|
|
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 size = dataSize(); |
|
std::unique_ptr<uint8_t[]> data { new uint8_t[size] }; |
|
memcpy(data.get(), data_, size); |
|
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(); |
|
} |
|
|
|
[[nodiscard]] size_t dataSize() const |
|
{ |
|
return ClxSpriteSheet { *this }.dataSize(); |
|
} |
|
|
|
private: |
|
// For OptionalOwnedClxSpriteList. |
|
OwnedClxSpriteSheet() = default; |
|
|
|
std::unique_ptr<uint8_t[]> data_; |
|
uint16_t num_lists_ = 0; |
|
|
|
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; |
|
} |
|
|
|
[[nodiscard]] size_t dataSize() const |
|
{ |
|
return isSheet() ? sheet().dataSize() : list().dataSize(); |
|
} |
|
|
|
private: |
|
// For OptionalClxSpriteListOrSheet. |
|
constexpr ClxSpriteListOrSheet() = default; |
|
|
|
const uint8_t *data_ = nullptr; |
|
uint16_t 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; |
|
} |
|
|
|
[[nodiscard]] uint16_t numLists() const { return num_lists_; } |
|
|
|
[[nodiscard]] size_t dataSize() const |
|
{ |
|
return ClxSpriteListOrSheet { *this }.dataSize(); |
|
} |
|
|
|
private: |
|
// For OptionalOwnedClxSpriteListOrSheet. |
|
OwnedClxSpriteListOrSheet() = default; |
|
|
|
std::unique_ptr<uint8_t[]> data_; |
|
uint16_t 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
|
|
|