A lightweight C++ loader for REXPaint .xp map files,
with an optional palette system for mapping colors to tile types.
Drop two files into your project and go from a .xp file to a populated tile map in under
ten lines of code.
- Loads any
.xpfile (gzip-compressed, all REXPaint versions) - Full multi-layer support — access any layer, not just layer 0
- Two-tier API: raw struct access or callback-based cell iteration
XPPalette<T>— maps(charCode, RGB)pairs to any user-defined type with configurable color tolerance- Neighbor lookup in callbacks — infer door orientation, auto-tile, etc.
- No dependencies beyond miniz.h (single-header, drop-in)
- C++17, header + one
.cppfile
Tier 1 — Raw load
XPImage img;
if (loadXP("level.xp", img)) {
std::cout << img.layers[0].width << "x" << img.layers[0].height << "\n";
const XPCell* cell = img.layers[0].at(5, 3);
if (cell)
std::cout << "code=" << cell->code << "\n";
}Tier 2 — Callback with palette
enum class Tile { Floor, WallBrick, WallStone, Door };
XPPalette<Tile> palette;
palette.add(219, {102, 178, 255}, Tile::Floor);
palette.add(219, {153, 102, 0}, Tile::WallBrick);
palette.add(219, { 0, 0, 0}, Tile::WallStone);
loadAndParseXP("level.xp", [&](int col, int row,
const XPCell& cell,
const XPLayer&) {
myMap[col][row] = palette.matchOrDefault(
cell.code, cell.fg_r, cell.fg_g, cell.fg_b, Tile::WallStone);
});Neighbor-aware callback (e.g. door orientation)
loadAndParseXP("level.xp", [&](int col, int row,
const XPCell& cell,
const XPLayer& layer) {
auto t = palette.match(cell.code, cell.fg_r, cell.fg_g, cell.fg_b);
if (t == Tile::Door) {
bool wallE = isWall(layer, col + 1, row);
bool wallW = isWall(layer, col - 1, row);
myMap[col][row] = (wallE || wallW) ? Tile::DoorH : Tile::DoorV;
} else {
myMap[col][row] = t.value_or(Tile::WallStone);
}
});- Copy
XPLoader.h,XPLoader.cpp, andXPPalette.hinto your project - Drop
miniz.hinto the same folder - Add
XPLoader.cppto your build #include "XPLoader.h"and optionally#include "XPPalette.h"
No CMake changes required beyond adding the .cpp file.
// Load a .xp file into an XPImage struct.
bool loadXP(const std::string& path, XPImage& out);
// Walk every cell of one layer and invoke callback for each.
bool parseXPLayer(const XPImage& image, XPCellCallback callback, int layerIndex = 0);
// Convenience: load and parse in a single call.
bool loadAndParseXP(const std::string& path, XPCellCallback callback, int layerIndex = 0);Data types:
struct XPCell {
int32_t code; // CP437 character code
uint8_t fg_r, fg_g, fg_b; // Foreground color
uint8_t bg_r, bg_g, bg_b; // Background color
};
struct XPLayer {
int32_t width, height;
std::vector<XPCell> cells; // Column-major: cell(col, row) = cells[col * height + row]
const XPCell* at(int col, int row) const; // Bounds-checked accessor
};
struct XPImage {
std::vector<XPLayer> layers;
int layerCount() const;
};Note: Cells are stored column-major to match REXPaint's native format. Use
layer.at(col, row)rather than indexing directly.
XPPalette<T> palette;
// Add a color → value mapping (default tolerance: 18 per channel, Chebyshev)
palette.add(int charCode, RGB color, T value, int tolerance = 18);
// Match any foreground color for a given character code
palette.addAnyColor(int charCode, T value);
// Try to match — returns std::nullopt if no entry matches
std::optional<T> palette.match(int32_t charCode, uint8_t r, uint8_t g, uint8_t b);
// Match with a fallback default
T palette.matchOrDefault(int32_t charCode, uint8_t r, uint8_t g, uint8_t b, T defaultValue);Constants:
XPPalette<T>::ANY_CODE // Wildcard: match any character code
XPPalette<T>::ANY_RGB // Wildcard: match any foreground color
XPPalette<T>::DEFAULT_TOLERANCE // 18 (Chebyshev per-channel)Entries are matched in insertion order — first match wins. Color distance uses Chebyshev (max-channel) distance, which handles REXPaint's compressed color values well.
Requires CMake and vcpkg with miniz installed.
git clone https://github.com/AEspinosa-FoxglowGames/xp-loader
cd xp-loader
cmake --preset x64-debug
cmake --build out/build/x64-debugPlace your .xp files in a maps/ folder next to the executable. The build
copies the maps/ directory automatically post-build.
MIT — see LICENSE.
Built by Austin Espinosa — indie developer and Information Science student at ICU Tokyo.
Originally developed for use in raycaster-3d.
REXPaint の .xp マップファイル用の軽量C++ローダーです。色情報をタイルタイプにマッピングするオプションのパレットシステムも付属しています。
ファイルを2つプロジェクトにコピーするだけで、10行以内のコードで .xp ファイルからタイルマップを構築できます。
- あらゆる
.xpファイルをロード(gzip圧縮、全REXPaintバージョン対応) - マルチレイヤー完全対応 — レイヤー0だけでなく任意のレイヤーにアクセス可
- 二段階API:生のストラクトアクセス、またはコールバックベースのセル反復
XPPalette<T>—(charCode, RGB)のペアをユーザー定義型にマッピング、カラー許容値設定可- コールバック内で隣接セルを参照可能 — ドアの向き推定やオートタイルなどに対応
- 依存関係は miniz.h のみ(シングルヘッダー、ドロップイン)
- C++17、ヘッダー +
.cpp1ファイル
Tier 1 — 生のロード
XPImage img;
if (loadXP("level.xp", img)) {
std::cout << img.layers[0].width << "x" << img.layers[0].height << "\n";
const XPCell* cell = img.layers[0].at(5, 3);
if (cell)
std::cout << "code=" << cell->code << "\n";
}Tier 2 — コールバックとパレットの組み合わせ
enum class Tile { Floor, WallBrick, WallStone, Door };
XPPalette<Tile> palette;
palette.add(219, {102, 178, 255}, Tile::Floor);
palette.add(219, {153, 102, 0}, Tile::WallBrick);
palette.add(219, { 0, 0, 0}, Tile::WallStone);
loadAndParseXP("level.xp", [&](int col, int row,
const XPCell& cell,
const XPLayer&) {
myMap[col][row] = palette.matchOrDefault(
cell.code, cell.fg_r, cell.fg_g, cell.fg_b, Tile::WallStone);
});隣接セル参照コールバック(例:ドアの向き推定)
loadAndParseXP("level.xp", [&](int col, int row,
const XPCell& cell,
const XPLayer& layer) {
auto t = palette.match(cell.code, cell.fg_r, cell.fg_g, cell.fg_b);
if (t == Tile::Door) {
bool wallE = isWall(layer, col + 1, row);
bool wallW = isWall(layer, col - 1, row);
myMap[col][row] = (wallE || wallW) ? Tile::DoorH : Tile::DoorV;
} else {
myMap[col][row] = t.value_or(Tile::WallStone);
}
});XPLoader.h・XPLoader.cpp・XPPalette.hをプロジェクトにコピーminiz.hを同じフォルダに配置XPLoader.cppをビルドに追加#include "XPLoader.h"(必要に応じて#include "XPPalette.h"も)
CMakeの変更は .cpp ファイルの追加のみで完了します。
// .xp ファイルを XPImage 構造体にロード
bool loadXP(const std::string& path, XPImage& out);
// レイヤーの全セルを反復してコールバックを呼び出す
bool parseXPLayer(const XPImage& image, XPCellCallback callback, int layerIndex = 0);
// ロードとパースを1回の呼び出しで実行
bool loadAndParseXP(const std::string& path, XPCellCallback callback, int layerIndex = 0);データ型:
struct XPCell {
int32_t code; // CP437文字コード
uint8_t fg_r, fg_g, fg_b; // 前景色
uint8_t bg_r, bg_g, bg_b; // 背景色
};
struct XPLayer {
int32_t width, height;
std::vector<XPCell> cells; // 列優先: cell(col, row) = cells[col * height + row]
const XPCell* at(int col, int row) const; // 境界チェック付きアクセサ
};
struct XPImage {
std::vector<XPLayer> layers;
int layerCount() const;
};注意: REXPaintのネイティブフォーマットに合わせ、セルは列優先で格納されています。 直接インデックス指定ではなく
layer.at(col, row)を使用してください。
XPPalette<T> palette;
// 色 → 値のマッピングを追加(デフォルト許容値:チェビシェフ距離18)
palette.add(int charCode, RGB color, T value, int tolerance = 18);
// 前景色を問わず指定文字コードにマッチするエントリを追加
palette.addAnyColor(int charCode, T value);
// マッチを試みる — マッチなしの場合は std::nullopt を返す
std::optional<T> palette.match(int32_t charCode, uint8_t r, uint8_t g, uint8_t b);
// マッチ結果またはデフォルト値を返す
T palette.matchOrDefault(int32_t charCode, uint8_t r, uint8_t g, uint8_t b, T defaultValue);定数:
XPPalette<T>::ANY_CODE // ワイルドカード:任意の文字コードにマッチ
XPPalette<T>::ANY_RGB // ワイルドカード:任意の前景色にマッチ
XPPalette<T>::DEFAULT_TOLERANCE // 18(チェビシェフ距離、チャンネルごと)エントリは登録順に照合され、最初にマッチしたものが採用されます。色距離にはチェビシェフ距離(最大チャンネル差)を使用しており、REXPaintの圧縮カラー値に適しています。
CMakeと、miniz をインストール済みのvcpkgが必要です。
git clone https://github.com/AEspinosa-FoxglowGames/xp-loader
cd xp-loader
cmake --preset x64-debug
cmake --build out/build/x64-debug.xp ファイルは実行ファイルの隣の maps/ フォルダに配置してください。ビルド後に maps/ ディレクトリは自動的にコピーされます。
MIT — LICENSE を参照。
Austin Espinosa 制作 — インディーゲーム開発者・ICU東京在籍の情報科学専攻学生。
raycaster-3d プロジェクトでの使用を目的として開発。