#pragma once #include #include #include #include #include #include #include #include #include #include // ======================== // 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 Pixels; Texture view() const { return Texture{Width, Height, Pixels.data()}; } }; using IdResolverFunc = std::function(std::string_view)>; using TextureProviderFunc = std::function(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); bool link(const IdResolverFunc& resolver, std::string* err = nullptr); bool bake(const TextureProviderFunc& provider, OwnedTexture& out, std::string* err = nullptr) const; bool bake(const TextureProviderFunc& provider, OwnedTexture& out, double timeSeconds, std::string* err = nullptr) const; const std::vector& words() const { return Code_; } const std::vector& patches() const { return Patches_; } std::vector 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 extractAnimationSpecs(const Word* code, size_t size); static bool remapTexIds(std::vector& code, const std::vector& remap, std::string* err = nullptr); static std::vector extractAnimationSpecs(const std::vector& code) { return extractAnimationSpecs(code.data(), code.size()); } void fromBytes(std::vector bytes); private: // ======================== // Byte helpers (little-endian) // ======================== static inline uint16_t _rd16(const std::vector& 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& 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& 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& o, uint32_t v){ o.push_back(uint8_t(v & 0xFFu)); } static inline void _wr16(std::vector& 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& 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& 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 Px; }; class VM { public: using TextureProvider = TexturePipelineProgram::TextureProviderFunc; explicit VM(TextureProvider provider); bool run(const std::vector& code, OwnedTexture& out, double timeSeconds, std::string* err); private: TextureProvider Provider_; static bool _bad(std::string* err, const char* msg); static bool _readSrc(const std::vector& code, size_t& ip, SrcRef& out, std::string* err); Image _loadTex(uint32_t id, std::unordered_map& cache, std::string* err); Image _loadSub(const std::vector& code, uint32_t off, uint32_t len, std::unordered_map& texCache, std::unordered_map& subCache, double timeSeconds, std::string* err); Image _loadSrc(const std::vector& code, const SrcRef& src, std::unordered_map& texCache, std::unordered_map& subCache, double timeSeconds, std::string* err); // ---- image ops (как в исходнике) ---- static Image _makeSolid(uint32_t w, uint32_t h, uint32_t color); static Image _resizeNN(const Image& src, uint32_t nw, uint32_t nh); static Image _resizeNN_ifNeeded(Image img, uint32_t w, uint32_t h); static Image _cropFrame(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh, bool horizontal); static Image _cropFrameGrid(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh); static void _lerp(Image& base, const Image& over, double t); static void _alphaOver(Image& base, const Image& over); static void _applyMask(Image& base, const Image& mask); static void _opacity(Image& img, uint8_t mul); static void _noAlpha(Image& img); static void _makeAlpha(Image& img, uint32_t rgb24); static void _invert(Image& img, uint32_t maskBits); static void _brighten(Image& img); static void _contrast(Image& img, int c, int br); static void _multiply(Image& img, uint32_t color); static void _screen(Image& img, uint32_t color); static void _colorize(Image& img, uint32_t color, uint8_t ratio); static void _lowpart(Image& base, const Image& over, uint32_t percent); static Image _transform(const Image& src, uint32_t t); }; // ======================== // 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); Tok peek(); void skipWs(); Tok next(); }; 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 Pos; std::unordered_map Named; }; // ======================== // Compiler state // ======================== std::string Source_; std::vector Code_; std::vector Patches_; // ---- emit helpers (target = arbitrary out vector) ---- static inline void _emitOp(std::vector& out, Op op) { _wr8(out, uint8_t(op)); } static inline void _emitU8(std::vector& out, uint32_t v){ _wr8(out, v); } static inline void _emitU16(std::vector& out, uint32_t v){ _wr16(out, v); } static inline void _emitU24(std::vector& out, uint32_t v){ _wr24(out, v); } static inline void _emitU32(std::vector& 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& out, std::vector* absPatches, std::vector* 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& out, std::vector* absPatches, std::vector* relPatches, const std::string& name) { _emitU8(out, uint8_t(SrcKind::TexId)); _emitTexPatchU24(out, absPatches, relPatches, name); } static void _emitSrcSub(std::vector& 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); // ======================== // Parsing entry: full program // ======================== bool _parseProgram(std::string* err); // ======================== // 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& out, std::vector* absPatches, std::vector* relPatches, std::string* err); bool _compileBaseFromToken(Lexer& lx, const Tok& a, std::vector& out, std::vector* absPatches, std::vector* relPatches, std::string* err); // ======================== // 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); bool _parseArgList(Lexer& lx, ParsedOp& op, std::string* err); bool _tokToVal(const Tok& t, ArgVal& out, std::string* err); // ======================== // Subprogram compilation: // we already consumed 'tex'. Parse base + pipeline until next token is ')' // DO NOT consume ')' // ======================== struct PendingSubData { std::vector Bytes; std::vector RelPatches; }; bool _compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err); // pending subprogram associated with ParsedOp pointer (created during parsing) mutable std::unordered_map PendingSub_; // Append subprogram to `out` and emit SrcRef(Sub, off16, len16), migrating patches properly. static bool _appendSubprogram(std::vector& out, PendingSubData&& sub, std::vector* absPatches, std::vector* relPatches, uint32_t& outOff, uint32_t& outLen, std::string* err); // ======================== // 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& out, std::vector* absPatches, std::vector* relPatches, std::string* err); };