Files
LuaVox/Src/Common/TexturePipelineProgram.hpp

2146 lines
73 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <string_view>
#include <optional>
#include <functional>
#include <unordered_map>
#include <algorithm>
#include <cmath>
#include <cstring>
// ========================
// External texture view
// ========================
struct Texture {
uint32_t Width, Height;
const uint32_t* Pixels; // assumed 0xAARRGGBB
};
// ========================
// Bytecode words are uint8_t (1 byte machine word)
// TexId is u24 (3 bytes, little-endian)
// Subprogram refs use off24/len24 in BYTES (<=65535)
// ========================
class TexturePipelineProgram {
public:
using Word = uint8_t;
enum AnimFlags : Word {
AnimSmooth = 1u << 0,
AnimHorizontal = 1u << 1,
AnimGrid = 1u << 2
};
static constexpr uint16_t DefaultAnimFpsQ = uint16_t(8u * 256u);
static constexpr size_t MaxCodeBytes = (1u << 16) + 1u; // 65537
struct OwnedTexture {
uint32_t Width = 0, Height = 0;
std::vector<uint32_t> Pixels;
Texture view() const { return Texture{Width, Height, Pixels.data()}; }
};
using IdResolverFunc = std::function<std::optional<uint32_t>(std::string_view)>;
using TextureProviderFunc = std::function<std::optional<Texture>(uint32_t)>;
// Patch point to 3 consecutive bytes where u24 texId lives (b0,b1,b2)
struct Patch {
size_t ByteIndex0 = 0; // Code_[i], Code_[i+1], Code_[i+2]
std::string Name;
};
bool compile(std::string_view src, std::string* err = nullptr) {
Source_ = src;
Code_.clear();
Patches_.clear();
PendingSub_.clear();
return _parseProgram(err);
}
bool link(const IdResolverFunc& resolver, std::string* err = nullptr) {
for (const auto& p : Patches_) {
auto idOpt = resolver(p.Name);
if(!idOpt) {
if(err) *err = "Не удалось разрешить имя текстуры: " + p.Name;
return false;
}
uint32_t id = *idOpt;
if(id >= (1u << 24)) {
if(err) *err = "TexId выходит за 24 бита (u24): " + p.Name + " => " + std::to_string(id);
return false;
}
if(p.ByteIndex0 + 2 >= Code_.size()) {
if(err) *err = "Внутренняя ошибка: применение идентификатора выходит за рамки кода";
return false;
}
Code_[p.ByteIndex0 + 0] = uint8_t(id & 0xFFu);
Code_[p.ByteIndex0 + 1] = uint8_t((id >> 8) & 0xFFu);
Code_[p.ByteIndex0 + 2] = uint8_t((id >> 16) & 0xFFu);
}
return true;
}
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, std::string* err = nullptr) const {
return bake(provider, out, 0.0, err);
}
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, double timeSeconds, std::string* err = nullptr) const {
VM vm(provider);
return vm.run(Code_, out, timeSeconds, err);
}
const std::vector<Word>& words() const { return Code_; }
const std::vector<Patch>& patches() const { return Patches_; }
std::vector<uint8_t> toBytes() const { return Code_; }
struct AnimSpec {
uint32_t TexId = 0;
bool HasTexId = false;
uint16_t FrameW = 0;
uint16_t FrameH = 0;
uint16_t FrameCount = 0;
uint16_t FpsQ = 0;
uint16_t Flags = 0;
};
static std::vector<AnimSpec> extractAnimationSpecs(const Word* code, size_t size) {
std::vector<AnimSpec> specs;
if(!code || size == 0) {
return specs;
}
struct Range {
size_t Start = 0;
size_t End = 0;
};
std::vector<Range> visited;
auto read8 = [&](size_t& ip, uint8_t& out)->bool{
if(ip >= size) return false;
out = code[ip++];
return true;
};
auto read16 = [&](size_t& ip, uint16_t& out)->bool{
if(ip + 1 >= size) return false;
out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8);
ip += 2;
return true;
};
auto read24 = [&](size_t& ip, uint32_t& out)->bool{
if(ip + 2 >= size) return false;
out = uint32_t(code[ip]) |
(uint32_t(code[ip + 1]) << 8) |
(uint32_t(code[ip + 2]) << 16);
ip += 3;
return true;
};
auto read32 = [&](size_t& ip, uint32_t& out)->bool{
if(ip + 3 >= size) return false;
out = uint32_t(code[ip]) |
(uint32_t(code[ip + 1]) << 8) |
(uint32_t(code[ip + 2]) << 16) |
(uint32_t(code[ip + 3]) << 24);
ip += 4;
return true;
};
struct SrcMeta {
SrcKind Kind = SrcKind::TexId;
uint32_t TexId = 0;
uint32_t Off = 0;
uint32_t Len = 0;
};
auto readSrc = [&](size_t& ip, SrcMeta& out)->bool{
uint8_t kind = 0;
if(!read8(ip, kind)) return false;
out.Kind = static_cast<SrcKind>(kind);
if(out.Kind == SrcKind::TexId) {
return read24(ip, out.TexId);
}
if(out.Kind == SrcKind::Sub) {
return read24(ip, out.Off) && read24(ip, out.Len);
}
return false;
};
auto scan = [&](auto&& self, size_t start, size_t end) -> void {
if(start >= end || end > size) {
return;
}
for(const auto& r : visited) {
if(r.Start == start && r.End == end) {
return;
}
}
visited.push_back(Range{start, end});
size_t ip = start;
while(ip < end) {
uint8_t opByte = 0;
if(!read8(ip, opByte)) return;
Op op = static_cast<Op>(opByte);
switch(op) {
case Op::End:
return;
case Op::Base_Tex: {
SrcMeta src{};
if(!readSrc(ip, src)) return;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(subStart < subEnd && subEnd <= size) {
self(self, subStart, subEnd);
}
}
} break;
case Op::Base_Fill: {
uint16_t tmp16 = 0;
uint32_t tmp32 = 0;
if(!read16(ip, tmp16)) return;
if(!read16(ip, tmp16)) return;
if(!read32(ip, tmp32)) return;
} break;
case Op::Base_Anim: {
SrcMeta src{};
if(!readSrc(ip, src)) return;
uint16_t frameW = 0;
uint16_t frameH = 0;
uint16_t frameCount = 0;
uint16_t fpsQ = 0;
uint8_t flags = 0;
if(!read16(ip, frameW)) return;
if(!read16(ip, frameH)) return;
if(!read16(ip, frameCount)) return;
if(!read16(ip, fpsQ)) return;
if(!read8(ip, flags)) return;
if(src.Kind == SrcKind::TexId) {
AnimSpec spec{};
spec.TexId = src.TexId;
spec.HasTexId = true;
spec.FrameW = frameW;
spec.FrameH = frameH;
spec.FrameCount = frameCount;
spec.FpsQ = fpsQ;
spec.Flags = flags;
specs.push_back(spec);
} else if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(subStart < subEnd && subEnd <= size) {
self(self, subStart, subEnd);
}
}
} break;
case Op::Resize: {
uint16_t tmp16 = 0;
if(!read16(ip, tmp16)) return;
if(!read16(ip, tmp16)) return;
} break;
case Op::Transform:
case Op::Opacity:
case Op::Invert:
if(!read8(ip, opByte)) return;
break;
case Op::NoAlpha:
case Op::Brighten:
break;
case Op::MakeAlpha:
if(ip + 2 >= size) return;
ip += 3;
break;
case Op::Contrast:
if(ip + 1 >= size) return;
ip += 2;
break;
case Op::Multiply:
case Op::Screen: {
uint32_t tmp32 = 0;
if(!read32(ip, tmp32)) return;
} break;
case Op::Colorize: {
uint32_t tmp32 = 0;
if(!read32(ip, tmp32)) return;
if(!read8(ip, opByte)) return;
} break;
case Op::Anim: {
uint16_t frameW = 0;
uint16_t frameH = 0;
uint16_t frameCount = 0;
uint16_t fpsQ = 0;
uint8_t flags = 0;
if(!read16(ip, frameW)) return;
if(!read16(ip, frameH)) return;
if(!read16(ip, frameCount)) return;
if(!read16(ip, fpsQ)) return;
if(!read8(ip, flags)) return;
AnimSpec spec{};
spec.HasTexId = false;
spec.FrameW = frameW;
spec.FrameH = frameH;
spec.FrameCount = frameCount;
spec.FpsQ = fpsQ;
spec.Flags = flags;
specs.push_back(spec);
} break;
case Op::Overlay:
case Op::Mask: {
SrcMeta src{};
if(!readSrc(ip, src)) return;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(subStart < subEnd && subEnd <= size) {
self(self, subStart, subEnd);
}
}
} break;
case Op::LowPart: {
if(!read8(ip, opByte)) return;
SrcMeta src{};
if(!readSrc(ip, src)) return;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(subStart < subEnd && subEnd <= size) {
self(self, subStart, subEnd);
}
}
} break;
case Op::Combine: {
uint16_t w = 0, h = 0, n = 0;
if(!read16(ip, w)) return;
if(!read16(ip, h)) return;
if(!read16(ip, n)) return;
for(uint16_t i = 0; i < n; ++i) {
uint16_t tmp16 = 0;
if(!read16(ip, tmp16)) return;
if(!read16(ip, tmp16)) return;
SrcMeta src{};
if(!readSrc(ip, src)) return;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(subStart < subEnd && subEnd <= size) {
self(self, subStart, subEnd);
}
}
}
(void)w; (void)h;
} break;
default:
return;
}
}
};
scan(scan, 0, size);
return specs;
}
static bool remapTexIds(std::vector<uint8_t>& code, const std::vector<uint32_t>& remap, std::string* err = nullptr) {
struct Range {
size_t Start = 0;
size_t End = 0;
};
struct SrcMeta {
SrcKind Kind = SrcKind::TexId;
uint32_t TexId = 0;
uint32_t Off = 0;
uint32_t Len = 0;
size_t TexIdOffset = 0;
};
const size_t size = code.size();
std::vector<Range> visited;
auto read8 = [&](size_t& ip, uint8_t& out)->bool{
if(ip >= size) return false;
out = code[ip++];
return true;
};
auto read16 = [&](size_t& ip, uint16_t& out)->bool{
if(ip + 1 >= size) return false;
out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8);
ip += 2;
return true;
};
auto read24 = [&](size_t& ip, uint32_t& out)->bool{
if(ip + 2 >= size) return false;
out = uint32_t(code[ip]) |
(uint32_t(code[ip + 1]) << 8) |
(uint32_t(code[ip + 2]) << 16);
ip += 3;
return true;
};
auto read32 = [&](size_t& ip, uint32_t& out)->bool{
if(ip + 3 >= size) return false;
out = uint32_t(code[ip]) |
(uint32_t(code[ip + 1]) << 8) |
(uint32_t(code[ip + 2]) << 16) |
(uint32_t(code[ip + 3]) << 24);
ip += 4;
return true;
};
auto readSrc = [&](size_t& ip, SrcMeta& out)->bool{
uint8_t kind = 0;
if(!read8(ip, kind)) return false;
out.Kind = static_cast<SrcKind>(kind);
if(out.Kind == SrcKind::TexId) {
out.TexIdOffset = ip;
return read24(ip, out.TexId);
}
if(out.Kind == SrcKind::Sub) {
return read24(ip, out.Off) && read24(ip, out.Len);
}
return false;
};
auto patchTexId = [&](const SrcMeta& src)->bool{
if(src.Kind != SrcKind::TexId)
return true;
if(src.TexId >= remap.size()) {
if(err) *err = "Идентификатор текстуры вне диапазона переназначения";
return false;
}
uint32_t newId = remap[src.TexId];
if(newId >= (1u << 24)) {
if(err) *err = "TexId выходит за 24 бита (u24)";
return false;
}
if(src.TexIdOffset + 2 >= code.size()) {
if(err) *err = "Применение идентификатора выходит за рамки кода";
return false;
}
code[src.TexIdOffset + 0] = uint8_t(newId & 0xFFu);
code[src.TexIdOffset + 1] = uint8_t((newId >> 8) & 0xFFu);
code[src.TexIdOffset + 2] = uint8_t((newId >> 16) & 0xFFu);
return true;
};
std::move_only_function<bool(size_t, size_t)> scan;
scan = [&](size_t start, size_t end) -> bool {
if(start >= end || end > size) {
return true;
}
for(const auto& r : visited) {
if(r.Start == start && r.End == end) {
return true;
}
}
visited.push_back(Range{start, end});
size_t ip = start;
while(ip < end) {
uint8_t opByte = 0;
if(!read8(ip, opByte)) return false;
Op op = static_cast<Op>(opByte);
switch(op) {
case Op::End:
return true;
case Op::Base_Tex: {
SrcMeta src{};
if(!readSrc(ip, src)) return false;
if(!patchTexId(src)) return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
} break;
case Op::Base_Fill: {
uint16_t tmp16 = 0;
uint32_t tmp32 = 0;
if(!read16(ip, tmp16)) return false;
if(!read16(ip, tmp16)) return false;
if(!read32(ip, tmp32)) return false;
} break;
case Op::Base_Anim: {
SrcMeta src{};
if(!readSrc(ip, src)) return false;
if(!patchTexId(src)) return false;
uint16_t frameW = 0;
uint16_t frameH = 0;
uint16_t frameCount = 0;
uint16_t fpsQ = 0;
uint8_t flags = 0;
if(!read16(ip, frameW)) return false;
if(!read16(ip, frameH)) return false;
if(!read16(ip, frameCount)) return false;
if(!read16(ip, fpsQ)) return false;
if(!read8(ip, flags)) return false;
(void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
} break;
case Op::Resize: {
uint16_t tmp16 = 0;
if(!read16(ip, tmp16)) return false;
if(!read16(ip, tmp16)) return false;
} break;
case Op::Transform:
case Op::Opacity:
case Op::Invert:
if(!read8(ip, opByte)) return false;
break;
case Op::NoAlpha:
case Op::Brighten:
break;
case Op::MakeAlpha:
if(ip + 2 >= size) return false;
ip += 3;
break;
case Op::Contrast:
if(ip + 1 >= size) return false;
ip += 2;
break;
case Op::Multiply:
case Op::Screen: {
uint32_t tmp32 = 0;
if(!read32(ip, tmp32)) return false;
} break;
case Op::Colorize: {
uint32_t tmp32 = 0;
if(!read32(ip, tmp32)) return false;
if(!read8(ip, opByte)) return false;
} break;
case Op::Anim: {
uint16_t frameW = 0;
uint16_t frameH = 0;
uint16_t frameCount = 0;
uint16_t fpsQ = 0;
uint8_t flags = 0;
if(!read16(ip, frameW)) return false;
if(!read16(ip, frameH)) return false;
if(!read16(ip, frameCount)) return false;
if(!read16(ip, fpsQ)) return false;
if(!read8(ip, flags)) return false;
(void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags;
} break;
case Op::Overlay:
case Op::Mask: {
SrcMeta src{};
if(!readSrc(ip, src)) return false;
if(!patchTexId(src)) return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
} break;
case Op::LowPart: {
if(!read8(ip, opByte)) return false;
SrcMeta src{};
if(!readSrc(ip, src)) return false;
if(!patchTexId(src)) return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
} break;
case Op::Combine: {
uint16_t w = 0, h = 0, n = 0;
if(!read16(ip, w)) return false;
if(!read16(ip, h)) return false;
if(!read16(ip, n)) return false;
for(uint16_t i = 0; i < n; ++i) {
uint16_t tmp16 = 0;
if(!read16(ip, tmp16)) return false;
if(!read16(ip, tmp16)) return false;
SrcMeta src{};
if(!readSrc(ip, src)) return false;
if(!patchTexId(src)) return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
}
(void)w; (void)h;
} break;
default:
return false;
}
}
return true;
};
return scan(0, size);
}
static std::vector<AnimSpec> extractAnimationSpecs(const std::vector<Word>& code) {
return extractAnimationSpecs(code.data(), code.size());
}
void fromBytes(std::vector<uint8_t> bytes) {
Code_ = std::move(bytes);
Patches_.clear();
Source_.clear();
PendingSub_.clear();
}
private:
// ========================
// Byte helpers (little-endian)
// ========================
static inline uint16_t _rd16(const std::vector<uint8_t>& c, size_t& ip) {
uint16_t v = uint16_t(c[ip]) | (uint16_t(c[ip+1]) << 8);
ip += 2;
return v;
}
static inline uint32_t _rd24(const std::vector<uint8_t>& c, size_t& ip) {
uint32_t v = uint32_t(c[ip]) | (uint32_t(c[ip+1]) << 8) | (uint32_t(c[ip+2]) << 16);
ip += 3;
return v;
}
static inline uint32_t _rd32(const std::vector<uint8_t>& c, size_t& ip) {
uint32_t v = uint32_t(c[ip]) |
(uint32_t(c[ip+1]) << 8) |
(uint32_t(c[ip+2]) << 16) |
(uint32_t(c[ip+3]) << 24);
ip += 4;
return v;
}
static inline void _wr8 (std::vector<uint8_t>& o, uint32_t v){ o.push_back(uint8_t(v & 0xFFu)); }
static inline void _wr16(std::vector<uint8_t>& o, uint32_t v){
o.push_back(uint8_t(v & 0xFFu));
o.push_back(uint8_t((v >> 8) & 0xFFu));
}
static inline void _wr24(std::vector<uint8_t>& o, uint32_t v){
o.push_back(uint8_t(v & 0xFFu));
o.push_back(uint8_t((v >> 8) & 0xFFu));
o.push_back(uint8_t((v >> 16) & 0xFFu));
}
static inline void _wr32(std::vector<uint8_t>& o, uint32_t v){
o.push_back(uint8_t(v & 0xFFu));
o.push_back(uint8_t((v >> 8) & 0xFFu));
o.push_back(uint8_t((v >> 16) & 0xFFu));
o.push_back(uint8_t((v >> 24) & 0xFFu));
}
// ========================
// SrcRef encoding in bytes (variable length)
// kind(1) + payload
// TexId: id24(3) => total 4
// Sub : off16(3) + len16(3) => total 7
// ========================
enum class SrcKind : uint8_t { TexId = 0, Sub = 1 };
struct SrcRef {
SrcKind Kind{};
uint32_t TexId24 = 0; // for TexId
uint16_t Off24 = 0; // for Sub
uint16_t Len24 = 0; // for Sub
};
// ========================
// Opcodes (1 byte)
// ========================
enum class Op : uint8_t {
End = 0,
Base_Tex = 1, // SrcRef(TexId)
Base_Fill = 2, // w16, h16, color32
Base_Anim = 3, // SrcRef(TexId), frameW16, frameH16, frames16, fpsQ16, flags8
Resize = 10, // w16, h16
Transform = 11, // t8
Opacity = 12, // a8
NoAlpha = 13, // -
MakeAlpha = 14, // rgb24 (3 bytes) RR,GG,BB
Invert = 15, // mask8
Brighten = 16, // -
Contrast = 17, // cBias8, bBias8 (bias-127)
Multiply = 18, // color32
Screen = 19, // color32
Colorize = 20, // color32, ratio8
Anim = 21, // frameW16, frameH16, frames16, fpsQ16, flags8
Overlay = 30, // SrcRef (var)
Mask = 31, // SrcRef (var)
LowPart = 32, // percent8, SrcRef (var)
Combine = 40 // w16,h16,n16 then n*(x16,y16,SrcRef) (если понадобится — допишем DSL)
};
// ========================
// Pixel helpers (assume 0xAARRGGBB)
// ========================
static inline uint8_t _a(uint32_t c){ return uint8_t((c >> 24) & 0xFF); }
static inline uint8_t _r(uint32_t c){ return uint8_t((c >> 16) & 0xFF); }
static inline uint8_t _g(uint32_t c){ return uint8_t((c >> 8) & 0xFF); }
static inline uint8_t _b(uint32_t c){ return uint8_t((c >> 0) & 0xFF); }
static inline uint32_t _pack(uint8_t a,uint8_t r,uint8_t g,uint8_t b){
return (uint32_t(a)<<24)|(uint32_t(r)<<16)|(uint32_t(g)<<8)|(uint32_t(b));
}
static inline uint8_t _clampu8(int v){ return uint8_t(std::min(255, std::max(0, v))); }
// ========================
// VM (executes bytes)
// ========================
struct Image {
uint32_t W=0,H=0;
std::vector<uint32_t> Px;
};
class VM {
public:
using TextureProvider = TexturePipelineProgram::TextureProviderFunc;
explicit VM(TextureProvider provider) : Provider_(std::move(provider)) {}
bool run(const std::vector<uint8_t>& code, OwnedTexture& out, double timeSeconds, std::string* err) {
if(code.empty()) { if(err) *err="Пустой байткод"; return false; }
Image cur;
std::unordered_map<uint32_t, Image> texCache;
std::unordered_map<uint64_t, Image> subCache; // key = (off<<24) | len
size_t ip = 0;
auto need = [&](size_t n)->bool{
if(ip + n > code.size()) { if(err) *err="Байткод усечен"; return false; }
return true;
};
while(true) {
if(!need(1)) return false;
Op op = static_cast<Op>(code[ip++]);
if(op == Op::End) break;
switch(op) {
case Op::Base_Tex: {
SrcRef src;
if(!_readSrc(code, ip, src, err)) return false;
if(src.Kind != SrcKind::TexId) return _bad(err, "Base_Tex must be TexId");
cur = _loadTex(src.TexId24, texCache, err);
if(cur.W == 0) return false;
} break;
case Op::Base_Fill: {
if(!need(2+2+4)) return false;
uint32_t w = _rd16(code, ip);
uint32_t h = _rd16(code, ip);
uint32_t color = _rd32(code, ip);
cur = _makeSolid(w, h, color);
} break;
case Op::Base_Anim: {
SrcRef src;
if(!_readSrc(code, ip, src, err)) return false;
if(src.Kind != SrcKind::TexId) return _bad(err, "Base_Anim must be TexId");
if(!need(2+2+2+2+1)) return false;
uint32_t frameW = _rd16(code, ip);
uint32_t frameH = _rd16(code, ip);
uint32_t frameCount = _rd16(code, ip);
uint32_t fpsQ = _rd16(code, ip);
uint32_t flags = code[ip++];
Image sheet = _loadTex(src.TexId24, texCache, err);
if(sheet.W == 0) return false;
uint32_t fw = frameW ? frameW : sheet.W;
uint32_t fh = frameH ? frameH : sheet.H;
if(fw == 0 || fh == 0) return _bad(err, "Base_Anim invalid frame size");
bool useGrid = (flags & AnimGrid) != 0;
bool horizontal = (flags & AnimHorizontal) != 0;
if(frameCount == 0) {
uint32_t avail = 0;
if(useGrid) {
uint32_t cols = sheet.W / fw;
uint32_t rows = sheet.H / fh;
avail = cols * rows;
} else {
avail = horizontal ? (sheet.W / fw) : (sheet.H / fh);
}
frameCount = std::max<uint32_t>(1u, avail);
}
uint32_t fpsQv = fpsQ ? fpsQ : DefaultAnimFpsQ;
double fps = double(fpsQv) / 256.0;
double frameTime = timeSeconds * fps;
if(frameTime < 0.0) frameTime = 0.0;
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
double frac = frameTime - std::floor(frameTime);
cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh)
: _cropFrame(sheet, frameIndex, fw, fh, horizontal);
if(flags & AnimSmooth) {
uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u;
Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh)
: _cropFrame(sheet, nextIndex, fw, fh, horizontal);
_lerp(cur, next, frac);
}
} break;
case Op::Anim: {
if(!cur.W || !cur.H) return _bad(err, "Anim requires base image");
if(!need(2+2+2+2+1)) return false;
uint32_t frameW = _rd16(code, ip);
uint32_t frameH = _rd16(code, ip);
uint32_t frameCount = _rd16(code, ip);
uint32_t fpsQ = _rd16(code, ip);
uint32_t flags = code[ip++];
const Image& sheet = cur;
uint32_t fw = frameW ? frameW : sheet.W;
uint32_t fh = frameH ? frameH : sheet.H;
if(fw == 0 || fh == 0) return _bad(err, "Anim invalid frame size");
bool useGrid = (flags & AnimGrid) != 0;
bool horizontal = (flags & AnimHorizontal) != 0;
if(frameCount == 0) {
uint32_t avail = 0;
if(useGrid) {
uint32_t cols = sheet.W / fw;
uint32_t rows = sheet.H / fh;
avail = cols * rows;
} else {
avail = horizontal ? (sheet.W / fw) : (sheet.H / fh);
}
frameCount = std::max<uint32_t>(1u, avail);
}
uint32_t fpsQv = fpsQ ? fpsQ : DefaultAnimFpsQ;
double fps = double(fpsQv) / 256.0;
double frameTime = timeSeconds * fps;
if(frameTime < 0.0) frameTime = 0.0;
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
double frac = frameTime - std::floor(frameTime);
cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh)
: _cropFrame(sheet, frameIndex, fw, fh, horizontal);
if(flags & AnimSmooth) {
uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u;
Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh)
: _cropFrame(sheet, nextIndex, fw, fh, horizontal);
_lerp(cur, next, frac);
}
} break;
case Op::Overlay: {
SrcRef src;
if(!_readSrc(code, ip, src, err)) return false;
Image over = _loadSrc(code, src, texCache, subCache, timeSeconds, err);
if(over.W == 0) return false;
if(!cur.W) { cur = std::move(over); break; }
over = _resizeNN_ifNeeded(over, cur.W, cur.H);
_alphaOver(cur, over);
} break;
case Op::Mask: {
SrcRef src;
if(!_readSrc(code, ip, src, err)) return false;
Image m = _loadSrc(code, src, texCache, subCache, timeSeconds, err);
if(m.W == 0) return false;
if(!cur.W) return _bad(err, "Mask requires base image");
m = _resizeNN_ifNeeded(m, cur.W, cur.H);
_applyMask(cur, m);
} break;
case Op::LowPart: {
if(!need(1)) return false;
uint32_t pct = std::min<uint32_t>(100u, uint32_t(code[ip++]));
SrcRef src;
if(!_readSrc(code, ip, src, err)) return false;
Image over = _loadSrc(code, src, texCache, subCache, timeSeconds, err);
if(over.W == 0) return false;
if(!cur.W) return _bad(err, "LowPart requires base image");
over = _resizeNN_ifNeeded(over, cur.W, cur.H);
_lowpart(cur, over, pct);
} break;
case Op::Resize: {
if(!cur.W) return _bad(err, "Resize requires base image");
if(!need(2+2)) return false;
uint32_t w = _rd16(code, ip);
uint32_t h = _rd16(code, ip);
cur = _resizeNN(cur, w, h);
} break;
case Op::Transform: {
if(!cur.W) return _bad(err, "Transform requires base image");
if(!need(1)) return false;
uint32_t t = code[ip++] & 7u;
cur = _transform(cur, t);
} break;
case Op::Opacity: {
if(!cur.W) return _bad(err, "Opacity requires base image");
if(!need(1)) return false;
uint32_t a = code[ip++] & 0xFFu;
_opacity(cur, uint8_t(a));
} break;
case Op::NoAlpha: {
if(!cur.W) return _bad(err, "NoAlpha requires base image");
_noAlpha(cur);
} break;
case Op::MakeAlpha: {
if(!cur.W) return _bad(err, "MakeAlpha requires base image");
if(!need(3)) return false;
uint32_t rr = code[ip++], gg = code[ip++], bb = code[ip++];
uint32_t rgb24 = (rr << 16) | (gg << 8) | bb;
_makeAlpha(cur, rgb24);
} break;
case Op::Invert: {
if(!cur.W) return _bad(err, "Invert requires base image");
if(!need(1)) return false;
uint32_t mask = code[ip++] & 0xFu;
_invert(cur, mask);
} break;
case Op::Brighten: {
if(!cur.W) return _bad(err, "Brighten requires base image");
_brighten(cur);
} break;
case Op::Contrast: {
if(!cur.W) return _bad(err, "Contrast requires base image");
if(!need(2)) return false;
int c = int(code[ip++]) - 127;
int b = int(code[ip++]) - 127;
_contrast(cur, c, b);
} break;
case Op::Multiply: {
if(!cur.W) return _bad(err, "Multiply requires base image");
if(!need(4)) return false;
uint32_t color = _rd32(code, ip);
_multiply(cur, color);
} break;
case Op::Screen: {
if(!cur.W) return _bad(err, "Screen requires base image");
if(!need(4)) return false;
uint32_t color = _rd32(code, ip);
_screen(cur, color);
} break;
case Op::Colorize: {
if(!cur.W) return _bad(err, "Colorize requires base image");
if(!need(4+1)) return false;
uint32_t color = _rd32(code, ip);
uint32_t ratio = code[ip++] & 0xFFu;
_colorize(cur, color, uint8_t(ratio));
} break;
default:
return _bad(err, "Unknown opcode (no skip table in this minimal VM)");
}
}
out.Width = cur.W;
out.Height = cur.H;
out.Pixels = std::move(cur.Px);
return true;
}
private:
TextureProvider Provider_;
static bool _bad(std::string* err, const char* msg){ if(err) *err = msg; return false; }
static bool _readSrc(const std::vector<uint8_t>& code, size_t& ip, SrcRef& out, std::string* err) {
if(ip >= code.size()) return _bad(err, "Bytecode truncated (SrcRef.kind)");
out.Kind = static_cast<SrcKind>(code[ip++]);
if(out.Kind == SrcKind::TexId) {
if(ip + 3 > code.size()) return _bad(err, "Bytecode truncated (TexId24)");
out.TexId24 = _rd24(code, ip);
out.Off24 = 0; out.Len24 = 0;
return true;
}
if(out.Kind == SrcKind::Sub) {
if(ip + 6 > code.size()) return _bad(err, "Bytecode truncated (Sub off/len)");
out.Off24 = _rd24(code, ip);
out.Len24 = _rd24(code, ip);
out.TexId24 = 0;
return true;
}
return _bad(err, "Unknown SrcKind");
}
Image _loadTex(uint32_t id, std::unordered_map<uint32_t, Image>& cache, std::string* err) {
auto it = cache.find(id);
if(it != cache.end()) return it->second;
auto t = Provider_(id);
if(!t || !t->Pixels || !t->Width || !t->Height) {
if(err) *err = "Идентификатор текстуры не найден: " + std::to_string(id);
return {};
}
Image img;
img.W = t->Width; img.H = t->Height;
img.Px.assign(t->Pixels, t->Pixels + size_t(img.W)*size_t(img.H));
cache.emplace(id, img);
return img;
}
Image _loadSub(const std::vector<uint8_t>& code,
uint32_t off, uint32_t len,
std::unordered_map<uint32_t, Image>& /*texCache*/,
std::unordered_map<uint64_t, Image>& subCache,
double timeSeconds,
std::string* err) {
uint64_t key = (uint32_t(off) << 24) | uint32_t(len);
auto it = subCache.find(key);
if(it != subCache.end()) return it->second;
size_t start = size_t(off);
size_t end = start + size_t(len);
if(end > code.size()) { if(err) *err="Подпрограмма выходит за пределы"; return {}; }
std::vector<uint8_t> slice(code.begin()+start, code.begin()+end);
OwnedTexture tmp;
VM nested(Provider_);
if(!nested.run(slice, tmp, timeSeconds, err)) return {};
Image img;
img.W = tmp.Width; img.H = tmp.Height; img.Px = std::move(tmp.Pixels);
subCache.emplace(key, img);
return img;
}
Image _loadSrc(const std::vector<uint8_t>& code,
const SrcRef& src,
std::unordered_map<uint32_t, Image>& texCache,
std::unordered_map<uint64_t, Image>& subCache,
double timeSeconds,
std::string* err) {
if(src.Kind == SrcKind::TexId) return _loadTex(src.TexId24, texCache, err);
if(src.Kind == SrcKind::Sub) return _loadSub(code, src.Off24, src.Len24, texCache, subCache, timeSeconds, err);
if(err) *err = "Неизвестный SrcKind";
return {};
}
// ---- image ops (как в исходнике) ----
static Image _makeSolid(uint32_t w, uint32_t h, uint32_t color) {
Image img; img.W=w; img.H=h;
img.Px.assign(size_t(w)*size_t(h), color);
return img;
}
static Image _resizeNN(const Image& src, uint32_t nw, uint32_t nh) {
Image dst; dst.W=nw; dst.H=nh;
dst.Px.resize(size_t(nw)*size_t(nh));
for(uint32_t y=0;y<nh;y++){
uint32_t sy = (uint64_t(y) * src.H) / nh;
for(uint32_t x=0;x<nw;x++){
uint32_t sx = (uint64_t(x) * src.W) / nw;
dst.Px[size_t(y)*nw + x] = src.Px[size_t(sy)*src.W + sx];
}
}
return dst;
}
static Image _resizeNN_ifNeeded(Image img, uint32_t w, uint32_t h) {
if(img.W == w && img.H == h) return img;
return _resizeNN(img, w, h);
}
static Image _cropFrame(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh, bool horizontal) {
Image out;
out.W = fw;
out.H = fh;
out.Px.assign(size_t(fw) * size_t(fh), 0u);
uint32_t baseX = horizontal ? (index * fw) : 0u;
uint32_t baseY = horizontal ? 0u : (index * fh);
for(uint32_t y = 0; y < fh; ++y) {
uint32_t sy = baseY + y;
if(sy >= sheet.H) continue;
for(uint32_t x = 0; x < fw; ++x) {
uint32_t sx = baseX + x;
if(sx >= sheet.W) continue;
out.Px[size_t(y) * fw + x] = sheet.Px[size_t(sy) * sheet.W + sx];
}
}
return out;
}
static Image _cropFrameGrid(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh) {
Image out;
out.W = fw;
out.H = fh;
out.Px.assign(size_t(fw) * size_t(fh), 0u);
if(fw == 0 || fh == 0) return out;
uint32_t cols = sheet.W / fw;
uint32_t rows = sheet.H / fh;
if(cols == 0 || rows == 0) return out;
uint32_t col = index % cols;
uint32_t row = index / cols;
if(row >= rows) return out;
uint32_t baseX = col * fw;
uint32_t baseY = row * fh;
for(uint32_t y = 0; y < fh; ++y) {
uint32_t sy = baseY + y;
if(sy >= sheet.H) continue;
for(uint32_t x = 0; x < fw; ++x) {
uint32_t sx = baseX + x;
if(sx >= sheet.W) continue;
out.Px[size_t(y) * fw + x] = sheet.Px[size_t(sy) * sheet.W + sx];
}
}
return out;
}
static void _lerp(Image& base, const Image& over, double t) {
if(t <= 0.0) return;
if(t >= 1.0) { base = over; return; }
if(base.W != over.W || base.H != over.H) return;
const size_t n = base.Px.size();
for(size_t i = 0; i < n; ++i) {
uint32_t a = base.Px[i];
uint32_t b = over.Px[i];
int ar = _r(a), ag = _g(a), ab = _b(a), aa = _a(a);
int br = _r(b), bg = _g(b), bb = _b(b), ba = _a(b);
uint8_t rr = _clampu8(int(ar + (br - ar) * t));
uint8_t rg = _clampu8(int(ag + (bg - ag) * t));
uint8_t rb = _clampu8(int(ab + (bb - ab) * t));
uint8_t ra = _clampu8(int(aa + (ba - aa) * t));
base.Px[i] = _pack(ra, rr, rg, rb);
}
}
static void _alphaOver(Image& base, const Image& over) {
const size_t n = base.Px.size();
for(size_t i=0;i<n;i++){
uint32_t b = base.Px[i], o = over.Px[i];
uint8_t ba=_a(b), br=_r(b), bg=_g(b), bb=_b(b);
uint8_t oa=_a(o), or_=_r(o), og=_g(o), ob=_b(o);
uint32_t brp = (uint32_t(br) * ba) / 255;
uint32_t bgp = (uint32_t(bg) * ba) / 255;
uint32_t bbp = (uint32_t(bb) * ba) / 255;
uint32_t orp = (uint32_t(or_) * oa) / 255;
uint32_t ogp = (uint32_t(og) * oa) / 255;
uint32_t obp = (uint32_t(ob) * oa) / 255;
uint32_t inv = 255 - oa;
uint32_t outA = oa + (uint32_t(ba) * inv) / 255;
uint32_t outRp = orp + (brp * inv) / 255;
uint32_t outGp = ogp + (bgp * inv) / 255;
uint32_t outBp = obp + (bbp * inv) / 255;
uint8_t outR=0,outG=0,outB=0;
if(outA) {
outR = uint8_t(std::min<uint32_t>(255, (outRp * 255) / outA));
outG = uint8_t(std::min<uint32_t>(255, (outGp * 255) / outA));
outB = uint8_t(std::min<uint32_t>(255, (outBp * 255) / outA));
}
base.Px[i] = _pack(uint8_t(outA), outR, outG, outB);
}
}
static void _applyMask(Image& base, const Image& mask) {
const size_t n = base.Px.size();
for(size_t i=0;i<n;i++){
uint32_t b = base.Px[i], m = mask.Px[i];
uint8_t outA = uint8_t((uint32_t(_a(b)) * uint32_t(_a(m))) / 255);
base.Px[i] = _pack(outA, _r(b), _g(b), _b(b));
}
}
static void _opacity(Image& img, uint8_t mul) {
for(auto& p : img.Px) {
uint8_t na = uint8_t((uint32_t(_a(p)) * mul) / 255);
p = _pack(na, _r(p), _g(p), _b(p));
}
}
static void _noAlpha(Image& img) {
for(auto& p : img.Px) p = _pack(255, _r(p), _g(p), _b(p));
}
static void _makeAlpha(Image& img, uint32_t rgb24) {
uint8_t rr = uint8_t((rgb24 >> 16) & 0xFF);
uint8_t gg = uint8_t((rgb24 >> 8) & 0xFF);
uint8_t bb = uint8_t((rgb24 >> 0) & 0xFF);
for(auto& p : img.Px) {
if(_r(p)==rr && _g(p)==gg && _b(p)==bb) p = _pack(0, _r(p), _g(p), _b(p));
}
}
static void _invert(Image& img, uint32_t maskBits) {
for(auto& p : img.Px) {
uint8_t a=_a(p), r=_r(p), g=_g(p), b=_b(p);
if(maskBits & 1u) r = 255 - r;
if(maskBits & 2u) g = 255 - g;
if(maskBits & 4u) b = 255 - b;
if(maskBits & 8u) a = 255 - a;
p = _pack(a,r,g,b);
}
}
static void _brighten(Image& img) {
for(auto& p : img.Px) {
int r = _r(p), g = _g(p), b = _b(p);
r = r + (255 - r) / 3;
g = g + (255 - g) / 3;
b = b + (255 - b) / 3;
p = _pack(_a(p), _clampu8(r), _clampu8(g), _clampu8(b));
}
}
static void _contrast(Image& img, int c, int br) {
double C = double(std::max(-127, std::min(127, c)));
double factor = (259.0 * (C + 255.0)) / (255.0 * (259.0 - C));
for(auto& p : img.Px) {
int r = int(factor * (int(_r(p)) - 128) + 128) + br;
int g = int(factor * (int(_g(p)) - 128) + 128) + br;
int b = int(factor * (int(_b(p)) - 128) + 128) + br;
p = _pack(_a(p), _clampu8(r), _clampu8(g), _clampu8(b));
}
}
static void _multiply(Image& img, uint32_t color) {
uint8_t cr=_r(color), cg=_g(color), cb=_b(color);
for(auto& p : img.Px) {
uint8_t r = uint8_t((uint32_t(_r(p)) * cr) / 255);
uint8_t g = uint8_t((uint32_t(_g(p)) * cg) / 255);
uint8_t b = uint8_t((uint32_t(_b(p)) * cb) / 255);
p = _pack(_a(p), r,g,b);
}
}
static void _screen(Image& img, uint32_t color) {
uint8_t cr=_r(color), cg=_g(color), cb=_b(color);
for(auto& p : img.Px) {
uint8_t r = uint8_t(255 - ((255 - _r(p)) * (255 - cr)) / 255);
uint8_t g = uint8_t(255 - ((255 - _g(p)) * (255 - cg)) / 255);
uint8_t b = uint8_t(255 - ((255 - _b(p)) * (255 - cb)) / 255);
p = _pack(_a(p), r,g,b);
}
}
static void _colorize(Image& img, uint32_t color, uint8_t ratio) {
uint8_t cr=_r(color), cg=_g(color), cb=_b(color);
for(auto& p : img.Px) {
int r = (int(_r(p)) * (255 - ratio) + int(cr) * ratio) / 255;
int g = (int(_g(p)) * (255 - ratio) + int(cg) * ratio) / 255;
int b = (int(_b(p)) * (255 - ratio) + int(cb) * ratio) / 255;
p = _pack(_a(p), uint8_t(r), uint8_t(g), uint8_t(b));
}
}
static void _lowpart(Image& base, const Image& over, uint32_t percent) {
uint32_t startY = base.H - (base.H * percent) / 100;
for(uint32_t y=startY; y<base.H; y++){
for(uint32_t x=0; x<base.W; x++){
size_t i = size_t(y)*base.W + x;
uint32_t b = base.Px[i], o = over.Px[i];
uint8_t ba=_a(b), br=_r(b), bg=_g(b), bb=_b(b);
uint8_t oa=_a(o), or_=_r(o), og=_g(o), ob=_b(o);
uint32_t brp = (uint32_t(br) * ba) / 255;
uint32_t bgp = (uint32_t(bg) * ba) / 255;
uint32_t bbp = (uint32_t(bb) * ba) / 255;
uint32_t orp = (uint32_t(or_) * oa) / 255;
uint32_t ogp = (uint32_t(og) * oa) / 255;
uint32_t obp = (uint32_t(ob) * oa) / 255;
uint32_t inv = 255 - oa;
uint32_t outA = oa + (uint32_t(ba) * inv) / 255;
uint32_t outRp = orp + (brp * inv) / 255;
uint32_t outGp = ogp + (bgp * inv) / 255;
uint32_t outBp = obp + (bbp * inv) / 255;
uint8_t outR=0,outG=0,outB=0;
if(outA) {
outR = uint8_t(std::min<uint32_t>(255, (outRp * 255) / outA));
outG = uint8_t(std::min<uint32_t>(255, (outGp * 255) / outA));
outB = uint8_t(std::min<uint32_t>(255, (outBp * 255) / outA));
}
base.Px[i] = _pack(uint8_t(outA), outR, outG, outB);
}
}
}
static Image _transform(const Image& src, uint32_t t) {
Image dst;
auto at = [&](uint32_t x, uint32_t y)->uint32_t { return src.Px[size_t(y)*src.W + x]; };
auto make = [&](uint32_t w, uint32_t h){
Image d; d.W=w; d.H=h; d.Px.resize(size_t(w)*size_t(h));
return d;
};
auto set = [&](Image& im, uint32_t x, uint32_t y, uint32_t v){
im.Px[size_t(y)*im.W + x] = v;
};
switch (t & 7u) {
case 0: return src;
case 1: { dst = make(src.H, src.W);
for(uint32_t y=0;y<dst.H;y++) for(uint32_t x=0;x<dst.W;x++)
set(dst, x,y, at(y, src.H-1-x));
} break;
case 2: { dst = make(src.W, src.H);
for(uint32_t y=0;y<dst.H;y++) for(uint32_t x=0;x<dst.W;x++)
set(dst, x,y, at(src.W-1-x, src.H-1-y));
} break;
case 3: { dst = make(src.H, src.W);
for(uint32_t y=0;y<dst.H;y++) for(uint32_t x=0;x<dst.W;x++)
set(dst, x,y, at(src.W-1-y, x));
} break;
case 4: { dst = make(src.W, src.H);
for(uint32_t y=0;y<dst.H;y++) for(uint32_t x=0;x<dst.W;x++)
set(dst, x,y, at(src.W-1-x, y));
} break;
case 5: { dst = make(src.H, src.W);
for(uint32_t y=0;y<dst.H;y++) for(uint32_t x=0;x<dst.W;x++)
set(dst, x,y, at(src.H-1-y, src.H-1-x));
} break;
case 6: { dst = make(src.W, src.H);
for(uint32_t y=0;y<dst.H;y++) for(uint32_t x=0;x<dst.W;x++)
set(dst, x,y, at(x, src.H-1-y));
} break;
case 7: { dst = make(src.H, src.W);
for(uint32_t y=0;y<dst.H;y++) for(uint32_t x=0;x<dst.W;x++)
set(dst, x,y, at(y, x));
} break;
}
return dst;
}
};
// ========================
// Minimal DSL Lexer/Parser
// now supports:
// name |> op(...)
// 32x32 "#RRGGBBAA"
// optional prefix:
// tex name |> op(...)
// nested only where op expects a texture arg:
// overlay( tex other |> ... )
// Also supports overlay(other) / mask(other) / lowpart(50, other)
// ========================
enum class TokKind { End, Ident, Number, String, Pipe, Comma, LParen, RParen, Eq, X };
struct Tok {
TokKind Kind = TokKind::End;
std::string Text;
uint32_t U32 = 0;
};
struct Lexer {
std::string_view S;
size_t I=0;
bool HasBuf = false;
Tok Buf;
static bool isAlpha(char c){ return (c>='a'&&c<='z')||(c>='A'&&c<='Z')||c=='_'; }
static bool isNum(char c){ return (c>='0'&&c<='9'); }
static bool isAlnum(char c){ return isAlpha(c)||isNum(c); }
void unread(const Tok& t) {
// allow only 1-level unread
HasBuf = true;
Buf = t;
}
Tok peek() {
Tok t = next();
unread(t);
return t;
}
void skipWs() {
while (I < S.size()) {
char c = S[I];
if(c==' '||c=='\t'||c=='\r'||c=='\n'){ I++; continue; }
if(c=='#'){ while (I<S.size() && S[I]!='\n') I++; continue; }
if(c=='/' && I+1<S.size() && S[I+1]=='/'){ I+=2; while (I<S.size() && S[I]!='\n') I++; continue; }
break;
}
}
Tok next() {
if(HasBuf) { HasBuf = false; return Buf; }
skipWs();
if(I >= S.size()) return {TokKind::End, {}, 0};
if(S[I]=='|' && I+1<S.size() && S[I+1]=='>') { I+=2; return {TokKind::Pipe, "|>",0}; }
char c = S[I];
if(c==','){ I++; return {TokKind::Comma,",",0}; }
if(c=='('){ I++; return {TokKind::LParen,"(",0}; }
if(c==')'){ I++; return {TokKind::RParen,")",0}; }
if(c=='='){ I++; return {TokKind::Eq,"=",0}; }
if(c=='x' || c=='X'){ I++; return {TokKind::X,"x",0}; }
if(c=='"') {
I++;
std::string out;
while (I < S.size()) {
char ch = S[I++];
if(ch=='"') break;
if(ch=='\\' && I<S.size()) {
char esc = S[I++];
switch (esc) {
case 'n': out.push_back('\n'); break;
case 't': out.push_back('\t'); break;
case '"': out.push_back('"'); break;
case '\\':out.push_back('\\'); break;
default: out.push_back(esc); break;
}
} else out.push_back(ch);
}
return {TokKind::String, std::move(out), 0};
}
if(isNum(c)) {
uint64_t v=0;
size_t start=I;
while (I<S.size() && isNum(S[I])) { v = v*10 + uint64_t(S[I]-'0'); I++; }
return {TokKind::Number, std::string(S.substr(start, I-start)), uint32_t(v)};
}
if(isAlpha(c) || c=='#') {
size_t start=I;
I++;
while (I<S.size()) {
char ch = S[I];
if(isAlnum(ch) || ch=='_' || ch=='.' || ch=='-' || ch=='#') { I++; continue; }
break;
}
return {TokKind::Ident, std::string(S.substr(start, I-start)), 0};
}
I = S.size();
return {TokKind::End, {}, 0};
}
};
struct ArgVal {
enum class ValueKind { U32, Str, Ident };
ValueKind Kind = ValueKind::U32;
uint32_t U32 = 0;
std::string S;
};
struct ParsedOp {
std::string Name;
std::vector<ArgVal> Pos;
std::unordered_map<std::string, ArgVal> Named;
};
// ========================
// Compiler state
// ========================
std::string Source_;
std::vector<uint8_t> Code_;
std::vector<Patch> Patches_;
// ---- emit helpers (target = arbitrary out vector) ----
static inline void _emitOp(std::vector<uint8_t>& out, Op op) { _wr8(out, uint8_t(op)); }
static inline void _emitU8(std::vector<uint8_t>& out, uint32_t v){ _wr8(out, v); }
static inline void _emitU16(std::vector<uint8_t>& out, uint32_t v){ _wr16(out, v); }
static inline void _emitU24(std::vector<uint8_t>& out, uint32_t v){ _wr24(out, v); }
static inline void _emitU32(std::vector<uint8_t>& out, uint32_t v){ _wr32(out, v); }
// reserve 3 bytes for u24 texId and register patch (absolute or relative)
struct RelPatch { size_t Rel0; std::string Name; };
static void _emitTexPatchU24(std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
const std::string& name) {
const size_t idx = out.size();
out.push_back(0); out.push_back(0); out.push_back(0);
if(absPatches) absPatches->push_back(Patch{idx, name});
if(relPatches) relPatches->push_back(RelPatch{idx, name});
}
static void _emitSrcTexName(std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
const std::string& name) {
_emitU8(out, uint8_t(SrcKind::TexId));
_emitTexPatchU24(out, absPatches, relPatches, name);
}
static void _emitSrcSub(std::vector<uint8_t>& out, uint32_t off24, uint32_t len24) {
_emitU8(out, uint8_t(SrcKind::Sub));
_emitU24(out, off24);
_emitU24(out, len24);
}
// ========================
// Color parsing: #RRGGBB or #RRGGBBAA -> 0xAARRGGBB
// ========================
static bool _parseHexColor(std::string_view s, uint32_t& outARGB) {
if(s.size()!=7 && s.size()!=9) return false;
if(s[0] != '#') return false;
auto hex = [](char c)->int{
if(c>='0'&&c<='9') return c-'0';
if(c>='a'&&c<='f') return 10+(c-'a');
if(c>='A'&&c<='F') return 10+(c-'A');
return -1;
};
auto byteAt = [&](size_t idx)->std::optional<uint8_t>{
int hi=hex(s[idx]), lo=hex(s[idx+1]);
if(hi<0||lo<0) return std::nullopt;
return uint8_t((hi<<4)|lo);
};
auto r = byteAt(1), g = byteAt(3), b = byteAt(5);
if(!r||!g||!b) return false;
uint8_t a = 255;
if(s.size()==9) {
auto aa = byteAt(7);
if(!aa) return false;
a = *aa;
}
outARGB = (uint32_t(a)<<24) | (uint32_t(*r)<<16) | (uint32_t(*g)<<8) | (uint32_t(*b));
return true;
}
// ========================
// Parsing entry: full program
// ========================
bool _parseProgram(std::string* err) {
Lexer lx{Source_};
Tok t = lx.next();
bool hasTexPrefix = (t.Kind == TokKind::Ident && t.Text == "tex");
// compile base into main Code_
if(hasTexPrefix) {
if(!_compileBaseAfterTex(lx, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false;
} else {
if(!_compileBaseFromToken(lx, t, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false;
}
// pipeline: |> op ...
Tok nt = lx.next();
while (nt.Kind == TokKind::Pipe) {
Tok opName = lx.next();
if(opName.Kind != TokKind::Ident) { if(err) *err="Ожидалось имя операции после |>"; return false; }
ParsedOp op; op.Name = opName.Text;
Tok peek = lx.next();
if(peek.Kind == TokKind::LParen) {
if(!_parseArgListOrTextureExpr(lx, op, err)) return false;
nt = lx.next();
} else {
nt = peek; // no-arg op
}
if(!_compileOpInto(lx, op, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false;
}
_emitOp(Code_, Op::End);
if (Code_.size() > MaxCodeBytes) {
if (err)
*err = "Байткод пайплайна слишком большой: " + std::to_string(Code_.size()) +
" > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")";
return false;
}
return true;
}
// ========================
// Base compilation (optionally after 'tex')
// supports:
// 1) name
// 2) "name(.png/.jpg/.jpeg)" (allowed but normalized)
// 3) anim(...)
// 4) 32x32 "#RRGGBBAA"
// optional: all of the above may be prefixed with 'tex'
// ========================
bool _compileBaseAfterTex(Lexer& lx,
std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
std::string* err) {
Tok a = lx.next();
return _compileBaseFromToken(lx, a, out, absPatches, relPatches, err);
}
bool _compileBaseFromToken(Lexer& lx,
const Tok& a,
std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
std::string* err) {
if(a.Kind == TokKind::End) {
if(err) *err="Ожидалось текстурное выражение";
return false;
}
if(a.Kind == TokKind::Ident && a.Text == "anim") {
Tok lp = lx.next();
if(lp.Kind != TokKind::LParen) { if(err) *err="Ожидалась '(' после anim"; return false; }
ParsedOp op; op.Name="anim";
if(!_parseArgList(lx, op, err)) return false;
auto posU = [&](size_t i)->std::optional<uint32_t>{
if(i >= op.Pos.size()) return std::nullopt;
if(op.Pos[i].Kind != ArgVal::ValueKind::U32) return std::nullopt;
return op.Pos[i].U32;
};
auto posS = [&](size_t i)->std::optional<std::string>{
if(i >= op.Pos.size()) return std::nullopt;
return op.Pos[i].S;
};
auto namedU = [&](std::string_view k)->std::optional<uint32_t>{
auto it = op.Named.find(std::string(k));
if(it==op.Named.end() || it->second.Kind!=ArgVal::ValueKind::U32) return std::nullopt;
return it->second.U32;
};
auto namedS = [&](std::string_view k)->std::optional<std::string>{
auto it = op.Named.find(std::string(k));
if(it==op.Named.end()) return std::nullopt;
return it->second.S;
};
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
if(tex.empty()) { if(err) *err="anim требует имя текстуры"; return false; }
uint32_t frameW = namedU("frame_w").value_or(namedU("w").value_or(posU(1).value_or(0)));
uint32_t frameH = namedU("frame_h").value_or(namedU("h").value_or(posU(2).value_or(0)));
uint32_t frames = namedU("frames").value_or(namedU("count").value_or(posU(3).value_or(0)));
uint32_t fps = namedU("fps").value_or(posU(4).value_or(0));
uint32_t smooth = namedU("smooth").value_or(posU(5).value_or(0));
std::string axis = namedS("axis").value_or("");
bool axisHorizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h'));
bool axisVertical = (!axis.empty() && (axis[0] == 'y' || axis[0] == 'v'));
bool useGrid = axis.empty() || axis[0] == 'g';
if(frameW > 65535u || frameH > 65535u || frames > 65535u) {
if(err) *err="параметры anim должны помещаться в uint16";
return false;
}
uint32_t fpsQ = fps ? std::min<uint32_t>(0xFFFFu, fps * 256u) : DefaultAnimFpsQ;
uint32_t flags = (smooth ? AnimSmooth : 0);
if(useGrid) flags |= AnimGrid;
else if(axisHorizontal) flags |= AnimHorizontal;
else if(!axisVertical) flags |= AnimGrid;
_emitOp(out, Op::Base_Anim);
_emitSrcTexName(out, absPatches, relPatches, tex);
_emitU16(out, frameW);
_emitU16(out, frameH);
_emitU16(out, frames);
_emitU16(out, fpsQ);
_emitU8(out, flags);
return true;
}
if(a.Kind == TokKind::Ident || a.Kind == TokKind::String) {
// name (or "name.png" => normalized)
_emitOp(out, Op::Base_Tex);
_emitSrcTexName(out, absPatches, relPatches, a.Text);
return true;
}
if(a.Kind == TokKind::Number) {
// 32x32 "#RRGGBBAA"
Tok xTok = lx.next();
Tok b = lx.next();
Tok colTok = lx.next();
if(xTok.Kind != TokKind::X || b.Kind != TokKind::Number || (colTok.Kind!=TokKind::Ident && colTok.Kind!=TokKind::String)) {
if(err) *err="Ожидалось: <w>x<h> <#color>";
return false;
}
uint32_t w = a.U32, h = b.U32;
uint32_t color = 0;
if(!_parseHexColor(colTok.Text, color)) {
if(err) *err="Неверный литерал цвета (используйте #RRGGBB или #RRGGBBAA)";
return false;
}
if(w>65535u || h>65535u) { if(err) *err="w/h должны помещаться в uint16"; return false; }
_emitOp(out, Op::Base_Fill);
_emitU16(out, w);
_emitU16(out, h);
_emitU32(out, color);
return true;
}
if(err) *err="Некорректное базовое текстурное выражение";
return false;
}
// ========================
// Args parsing:
// - normal args: (a,b,key=v)
// - OR if first token inside '(' is 'tex' => parse nested program until ')'
// ========================
bool _parseArgListOrTextureExpr(Lexer& lx, ParsedOp& op, std::string* err) {
Tok first = lx.next();
if(first.Kind==TokKind::Ident && first.Text=="tex") {
// marker
ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = "__SUBTEX__";
op.Pos.push_back(av);
PendingSubData sub;
if(!_compileSubProgramFromAlreadySawTex(lx, sub, err)) return false;
Tok end = lx.next();
if(end.Kind != TokKind::RParen) { if(err) *err="Ожидалась ')' после подвыражения текстуры"; return false; }
PendingSub_[&op] = std::move(sub);
return true;
}
// otherwise parse as normal arg list, where `first` is first token inside '('
Tok t = first;
if(t.Kind == TokKind::RParen) return true;
while (true) {
if(t.Kind == TokKind::Ident) {
Tok maybeEq = lx.next();
if(maybeEq.Kind == TokKind::Eq) {
Tok v = lx.next();
ArgVal av;
if(!_tokToVal(v, av, err)) return false;
op.Named[t.Text] = std::move(av);
t = lx.next();
} else {
ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = t.Text;
op.Pos.push_back(std::move(av));
t = maybeEq;
}
} else {
ArgVal av;
if(!_tokToVal(t, av, err)) return false;
op.Pos.push_back(std::move(av));
t = lx.next();
}
if(t.Kind == TokKind::Comma) { t = lx.next(); continue; }
if(t.Kind == TokKind::RParen) return true;
if(err) *err = "Ожидалась ',' или ')' в списке аргументов";
return false;
}
}
bool _parseArgList(Lexer& lx, ParsedOp& op, std::string* err) {
Tok t = lx.next();
if(t.Kind == TokKind::RParen) return true;
while (true) {
if(t.Kind == TokKind::Ident) {
Tok maybeEq = lx.next();
if(maybeEq.Kind == TokKind::Eq) {
Tok v = lx.next();
ArgVal av;
if(!_tokToVal(v, av, err)) return false;
op.Named[t.Text] = std::move(av);
t = lx.next();
} else {
ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = t.Text;
op.Pos.push_back(std::move(av));
t = maybeEq;
}
} else {
ArgVal av;
if(!_tokToVal(t, av, err)) return false;
op.Pos.push_back(std::move(av));
t = lx.next();
}
if(t.Kind == TokKind::Comma) { t = lx.next(); continue; }
if(t.Kind == TokKind::RParen) return true;
if(err) *err = "Ожидалась ',' или ')' в списке аргументов";
return false;
}
}
bool _tokToVal(const Tok& t, ArgVal& out, std::string* err) {
if(t.Kind == TokKind::Number) { out.Kind=ArgVal::ValueKind::U32; out.U32=t.U32; return true; }
if(t.Kind == TokKind::String) { out.Kind=ArgVal::ValueKind::Str; out.S=t.Text; return true; }
if(t.Kind == TokKind::Ident) { out.Kind=ArgVal::ValueKind::Ident; out.S=t.Text; return true; }
if(err) *err = "Ожидалось значение";
return false;
}
// ========================
// Subprogram compilation:
// we already consumed 'tex'. Parse base + pipeline until next token is ')'
// DO NOT consume ')'
// ========================
struct PendingSubData {
std::vector<uint8_t> Bytes;
std::vector<RelPatch> RelPatches;
};
bool _compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err) {
outSub.Bytes.clear();
outSub.RelPatches.clear();
// base
if(!_compileBaseAfterTex(lx, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err))
return false;
// pipeline until ')'
while(true) {
// peek
Tok nt = lx.peek();
if(nt.Kind == TokKind::RParen) break;
if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; }
// consume pipe
lx.next();
Tok opName = lx.next();
if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; }
ParsedOp op; op.Name = opName.Text;
Tok lp = lx.next();
if(lp.Kind == TokKind::LParen) {
if(!_parseArgListOrTextureExpr(lx, op, err)) return false;
} else {
// no-arg op, lp already is next token (pipe or ')'), so we need to "unread" — can't.
// simplest: treat it as next token for outer loop by rewinding lexer state.
// We'll do it by storing the token back via a small hack: rebuild peek? Too heavy.
// Instead: enforce parentheses for ops in subprogram except no-arg ops (brighten/noalpha) which can be without.
// To keep behavior identical to main, we handle no-arg by rewinding I one token is not possible,
// so we accept that in subprogram, no-arg ops must be written as brighten() etc.
if(err) *err="Подтекстура: операции без аргументов должны использовать скобки, напр. brighten()";
return false;
}
if(!_compileOpInto(lx, op, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err))
return false;
}
// Pipeline until we see ')'
while (true) {
Tok nt = lx.peek();
if(nt.Kind == TokKind::RParen) break;
if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; }
// consume pipe
lx.next();
Tok opName = lx.next();
if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; }
ParsedOp op; op.Name = opName.Text;
// allow both op and op(...)
Tok maybe = lx.peek();
if(maybe.Kind == TokKind::LParen) {
lx.next(); // consume '('
if(!_parseArgListOrTextureExpr(lx, op, err)) return false;
} else {
// no-arg op; nothing to parse
}
if(!_compileOpInto(lx, op, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err))
return false;
}
_emitOp(outSub.Bytes, Op::End);
return true;
}
// pending subprogram associated with ParsedOp pointer (created during parsing)
mutable std::unordered_map<const ParsedOp*, PendingSubData> PendingSub_;
// Append subprogram to `out` and emit SrcRef(Sub, off16, len16), migrating patches properly.
static bool _appendSubprogram(std::vector<uint8_t>& out,
PendingSubData&& sub,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
uint32_t& outOff,
uint32_t& outLen,
std::string* err) {
const size_t offset = out.size();
const size_t len = sub.Bytes.size();
if(offset > 0xFFFFFFu || len > 0xFFFFFFu || (offset + len) > 0xFFFFFFu) {
if(err) *err = "Подпрограмма слишком большая (off/len должны влезать в u24 байт)";
return false;
}
if(offset + len > MaxCodeBytes) {
if(err) *err = "Байткод пайплайна слишком большой после вставки подпрограммы: " +
std::to_string(offset + len) + " > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")";
return false;
}
// migrate patches
if(absPatches) {
for(const auto& rp : sub.RelPatches) {
absPatches->push_back(Patch{offset + rp.Rel0, rp.Name});
}
}
if(relPatches) {
for(const auto& rp : sub.RelPatches) {
relPatches->push_back(RelPatch{offset + rp.Rel0, rp.Name});
}
}
out.insert(out.end(), sub.Bytes.begin(), sub.Bytes.end());
outOff = uint32_t(offset);
outLen = uint32_t(len);
return true;
}
// ========================
// Compile operations into arbitrary `out`
// absPatches != nullptr => patches recorded as absolute for this buffer
// relPatches != nullptr => patches recorded as relative for this buffer
// ========================
bool _compileOpInto(Lexer& /*lx*/,
const ParsedOp& op,
std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
std::string* err) {
auto posU = [&](size_t i)->std::optional<uint32_t>{
if(i >= op.Pos.size()) return std::nullopt;
if(op.Pos[i].Kind != ArgVal::ValueKind::U32) return std::nullopt;
return op.Pos[i].U32;
};
auto posS = [&](size_t i)->std::optional<std::string>{
if(i >= op.Pos.size()) return std::nullopt;
return op.Pos[i].S;
};
auto namedU = [&](std::string_view k)->std::optional<uint32_t>{
auto it = op.Named.find(std::string(k));
if(it==op.Named.end() || it->second.Kind!=ArgVal::ValueKind::U32) return std::nullopt;
return it->second.U32;
};
auto namedS = [&](std::string_view k)->std::optional<std::string>{
auto it = op.Named.find(std::string(k));
if(it==op.Named.end()) return std::nullopt;
return it->second.S;
};
auto emitSrcFromName = [&](const std::string& n){
_emitSrcTexName(out, absPatches, relPatches, n);
};
auto emitSrcFromPendingSub = [&]()->bool{
auto it = PendingSub_.find(&op);
if(it == PendingSub_.end()) { if(err) *err="Внутренняя ошибка: отсутствует подпрограмма"; return false; }
uint32_t off=0, len=0;
if(!_appendSubprogram(out, std::move(it->second), absPatches, relPatches, off, len, err)) return false;
PendingSub_.erase(it);
_emitSrcSub(out, off, len);
return true;
};
// --- Ops that accept a "texture" argument: overlay/mask/lowpart ---
if(op.Name == "overlay") {
_emitOp(out, Op::Overlay);
if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub();
// allow overlay(name) or overlay(tex=name)
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
if(tex.empty()) { if(err) *err="overlay требует аргумент-текстуру"; return false; }
emitSrcFromName(tex);
return true;
}
if(op.Name == "mask") {
_emitOp(out, Op::Mask);
if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub();
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
if(tex.empty()) { if(err) *err="mask требует аргумент-текстуру"; return false; }
emitSrcFromName(tex);
return true;
}
if(op.Name == "lowpart") {
uint32_t pct = namedU("percent").value_or(posU(0).value_or(0));
if(!pct) { if(err) *err="lowpart требует процент"; return false; }
_emitOp(out, Op::LowPart);
_emitU8(out, std::min<uint32_t>(100u, pct));
// 2nd arg can be nested subtex or name
if(op.Pos.size() >= 2 && op.Pos[1].S == "__SUBTEX__") return emitSrcFromPendingSub();
std::string tex = namedS("tex").value_or(posS(1).value_or(""));
if(tex.empty()) { if(err) *err="lowpart требует аргумент-текстуру"; return false; }
emitSrcFromName(tex);
return true;
}
// --- Unary ops ---
if(op.Name == "resize") {
uint32_t w = namedU("w").value_or(posU(0).value_or(0));
uint32_t h = namedU("h").value_or(posU(1).value_or(0));
if(!w || !h || w>65535u || h>65535u) { if(err) *err="resize(w,h) должны помещаться в uint16"; return false; }
_emitOp(out, Op::Resize); _emitU16(out, w); _emitU16(out, h);
return true;
}
if(op.Name == "transform") {
uint32_t t = namedU("t").value_or(posU(0).value_or(0));
_emitOp(out, Op::Transform); _emitU8(out, t & 7u);
return true;
}
if(op.Name == "opacity") {
uint32_t a = namedU("a").value_or(posU(0).value_or(255));
_emitOp(out, Op::Opacity); _emitU8(out, a & 0xFFu);
return true;
}
if(op.Name == "remove_alpha" || op.Name == "noalpha") {
_emitOp(out, Op::NoAlpha);
return true;
}
if(op.Name == "make_alpha") {
std::string col = namedS("color").value_or(posS(0).value_or(""));
uint32_t argb=0;
if(!_parseHexColor(col, argb)) { if(err) *err="make_alpha требует цвет #RRGGBB"; return false; }
uint32_t rgb24 = argb & 0x00FFFFFFu;
_emitOp(out, Op::MakeAlpha);
_emitU8(out, (rgb24 >> 16) & 0xFFu);
_emitU8(out, (rgb24 >> 8) & 0xFFu);
_emitU8(out, (rgb24 >> 0) & 0xFFu);
return true;
}
if(op.Name == "invert") {
std::string ch = namedS("channels").value_or(posS(0).value_or("rgb"));
uint32_t mask=0;
for(char c : ch) {
if(c=='r') mask |= 1;
if(c=='g') mask |= 2;
if(c=='b') mask |= 4;
if(c=='a') mask |= 8;
}
_emitOp(out, Op::Invert); _emitU8(out, mask & 0xFu);
return true;
}
if(op.Name == "brighten") {
_emitOp(out, Op::Brighten);
return true;
}
if(op.Name == "contrast") {
int c = int(namedU("value").value_or(posU(0).value_or(0)));
int b = int(namedU("brightness").value_or(posU(1).value_or(0)));
c = std::max(-127, std::min(127, c));
b = std::max(-127, std::min(127, b));
_emitOp(out, Op::Contrast);
_emitU8(out, uint32_t(c + 127));
_emitU8(out, uint32_t(b + 127));
return true;
}
auto compileColorOp = [&](Op opcode, bool needsRatio)->bool{
std::string col = namedS("color").value_or(posS(0).value_or(""));
uint32_t argb=0;
if(!_parseHexColor(col, argb)) { if(err) *err="Неверный литерал цвета"; return false; }
_emitOp(out, opcode);
_emitU32(out, argb);
if(needsRatio) {
uint32_t ratio = namedU("ratio").value_or(posU(1).value_or(255));
_emitU8(out, ratio & 0xFFu);
}
return true;
};
if(op.Name == "multiply") return compileColorOp(Op::Multiply, false);
if(op.Name == "screen") return compileColorOp(Op::Screen, false);
if(op.Name == "colorize") return compileColorOp(Op::Colorize, true);
if(op.Name == "anim") {
uint32_t frameW = namedU("frame_w").value_or(namedU("w").value_or(posU(0).value_or(0)));
uint32_t frameH = namedU("frame_h").value_or(namedU("h").value_or(posU(1).value_or(0)));
uint32_t frames = namedU("frames").value_or(namedU("count").value_or(posU(2).value_or(0)));
uint32_t fps = namedU("fps").value_or(posU(3).value_or(0));
uint32_t smooth = namedU("smooth").value_or(posU(4).value_or(0));
std::string axis = namedS("axis").value_or("");
bool axisHorizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h'));
bool axisVertical = (!axis.empty() && (axis[0] == 'y' || axis[0] == 'v'));
bool useGrid = axis.empty() || axis[0] == 'g';
if(frameW > 65535u || frameH > 65535u || frames > 65535u) {
if(err) *err="параметры anim должны помещаться в uint16";
return false;
}
uint32_t fpsQ = fps ? std::min<uint32_t>(0xFFFFu, fps * 256u) : DefaultAnimFpsQ;
uint32_t flags = (smooth ? AnimSmooth : 0);
if(useGrid) flags |= AnimGrid;
else if(axisHorizontal) flags |= AnimHorizontal;
else if(!axisVertical) flags |= AnimGrid;
_emitOp(out, Op::Anim);
_emitU16(out, frameW);
_emitU16(out, frameH);
_emitU16(out, frames);
_emitU16(out, fpsQ);
_emitU8(out, flags);
return true;
}
if(err) *err = "Неизвестная операция: " + op.Name;
return false;
}
};