|
|
|
@@ -30,7 +30,8 @@ public:
|
|
|
|
|
|
|
|
|
|
|
|
enum AnimFlags : Word {
|
|
|
|
enum AnimFlags : Word {
|
|
|
|
AnimSmooth = 1u << 0,
|
|
|
|
AnimSmooth = 1u << 0,
|
|
|
|
AnimHorizontal = 1u << 1
|
|
|
|
AnimHorizontal = 1u << 1,
|
|
|
|
|
|
|
|
AnimGrid = 1u << 2
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static constexpr uint16_t DefaultAnimFpsQ = uint16_t(8u * 256u);
|
|
|
|
static constexpr uint16_t DefaultAnimFpsQ = uint16_t(8u * 256u);
|
|
|
|
@@ -51,8 +52,8 @@ public:
|
|
|
|
std::string Name;
|
|
|
|
std::string Name;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
bool compile(std::string src, std::string* err = nullptr) {
|
|
|
|
bool compile(std::string_view src, std::string* err = nullptr) {
|
|
|
|
Source_ = std::move(src);
|
|
|
|
Source_ = src;
|
|
|
|
Code_.clear();
|
|
|
|
Code_.clear();
|
|
|
|
Patches_.clear();
|
|
|
|
Patches_.clear();
|
|
|
|
PendingSub_.clear();
|
|
|
|
PendingSub_.clear();
|
|
|
|
@@ -423,7 +424,7 @@ public:
|
|
|
|
if(src.Kind != SrcKind::TexId)
|
|
|
|
if(src.Kind != SrcKind::TexId)
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
if(src.TexId >= remap.size()) {
|
|
|
|
if(src.TexId >= remap.size()) {
|
|
|
|
if(err) *err = "Texture id out of remap range";
|
|
|
|
if(err) *err = "Идентификатор текстуры вне диапазона переназначения";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uint32_t newId = remap[src.TexId];
|
|
|
|
uint32_t newId = remap[src.TexId];
|
|
|
|
@@ -733,7 +734,7 @@ private:
|
|
|
|
explicit VM(TextureProvider provider) : Provider_(std::move(provider)) {}
|
|
|
|
explicit VM(TextureProvider provider) : Provider_(std::move(provider)) {}
|
|
|
|
|
|
|
|
|
|
|
|
bool run(const std::vector<uint8_t>& code, OwnedTexture& out, double timeSeconds, std::string* err) {
|
|
|
|
bool run(const std::vector<uint8_t>& code, OwnedTexture& out, double timeSeconds, std::string* err) {
|
|
|
|
if(code.empty()) { if(err) *err="Empty bytecode"; return false; }
|
|
|
|
if(code.empty()) { if(err) *err="Пустой байткод"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
Image cur;
|
|
|
|
Image cur;
|
|
|
|
std::unordered_map<uint32_t, Image> texCache;
|
|
|
|
std::unordered_map<uint32_t, Image> texCache;
|
|
|
|
@@ -742,7 +743,7 @@ private:
|
|
|
|
size_t ip = 0;
|
|
|
|
size_t ip = 0;
|
|
|
|
|
|
|
|
|
|
|
|
auto need = [&](size_t n)->bool{
|
|
|
|
auto need = [&](size_t n)->bool{
|
|
|
|
if(ip + n > code.size()) { if(err) *err="Bytecode truncated"; return false; }
|
|
|
|
if(ip + n > code.size()) { if(err) *err="Байткод усечен"; return false; }
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@@ -787,9 +788,17 @@ private:
|
|
|
|
uint32_t fh = frameH ? frameH : sheet.H;
|
|
|
|
uint32_t fh = frameH ? frameH : sheet.H;
|
|
|
|
if(fw == 0 || fh == 0) return _bad(err, "Base_Anim invalid frame size");
|
|
|
|
if(fw == 0 || fh == 0) return _bad(err, "Base_Anim invalid frame size");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool useGrid = (flags & AnimGrid) != 0;
|
|
|
|
bool horizontal = (flags & AnimHorizontal) != 0;
|
|
|
|
bool horizontal = (flags & AnimHorizontal) != 0;
|
|
|
|
if(frameCount == 0) {
|
|
|
|
if(frameCount == 0) {
|
|
|
|
uint32_t avail = horizontal ? (sheet.W / fw) : (sheet.H / fh);
|
|
|
|
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);
|
|
|
|
frameCount = std::max<uint32_t>(1u, avail);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -801,11 +810,13 @@ private:
|
|
|
|
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
|
|
|
|
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
|
|
|
|
double frac = frameTime - std::floor(frameTime);
|
|
|
|
double frac = frameTime - std::floor(frameTime);
|
|
|
|
|
|
|
|
|
|
|
|
cur = _cropFrame(sheet, frameIndex, fw, fh, horizontal);
|
|
|
|
cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh)
|
|
|
|
|
|
|
|
: _cropFrame(sheet, frameIndex, fw, fh, horizontal);
|
|
|
|
|
|
|
|
|
|
|
|
if(flags & AnimSmooth) {
|
|
|
|
if(flags & AnimSmooth) {
|
|
|
|
uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u;
|
|
|
|
uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u;
|
|
|
|
Image next = _cropFrame(sheet, nextIndex, fw, fh, horizontal);
|
|
|
|
Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh)
|
|
|
|
|
|
|
|
: _cropFrame(sheet, nextIndex, fw, fh, horizontal);
|
|
|
|
_lerp(cur, next, frac);
|
|
|
|
_lerp(cur, next, frac);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
} break;
|
|
|
|
@@ -825,9 +836,17 @@ private:
|
|
|
|
uint32_t fh = frameH ? frameH : sheet.H;
|
|
|
|
uint32_t fh = frameH ? frameH : sheet.H;
|
|
|
|
if(fw == 0 || fh == 0) return _bad(err, "Anim invalid frame size");
|
|
|
|
if(fw == 0 || fh == 0) return _bad(err, "Anim invalid frame size");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool useGrid = (flags & AnimGrid) != 0;
|
|
|
|
bool horizontal = (flags & AnimHorizontal) != 0;
|
|
|
|
bool horizontal = (flags & AnimHorizontal) != 0;
|
|
|
|
if(frameCount == 0) {
|
|
|
|
if(frameCount == 0) {
|
|
|
|
uint32_t avail = horizontal ? (sheet.W / fw) : (sheet.H / fh);
|
|
|
|
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);
|
|
|
|
frameCount = std::max<uint32_t>(1u, avail);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -839,10 +858,12 @@ private:
|
|
|
|
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
|
|
|
|
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
|
|
|
|
double frac = frameTime - std::floor(frameTime);
|
|
|
|
double frac = frameTime - std::floor(frameTime);
|
|
|
|
|
|
|
|
|
|
|
|
cur = _cropFrame(sheet, frameIndex, fw, fh, horizontal);
|
|
|
|
cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh)
|
|
|
|
|
|
|
|
: _cropFrame(sheet, frameIndex, fw, fh, horizontal);
|
|
|
|
if(flags & AnimSmooth) {
|
|
|
|
if(flags & AnimSmooth) {
|
|
|
|
uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u;
|
|
|
|
uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u;
|
|
|
|
Image next = _cropFrame(sheet, nextIndex, fw, fh, horizontal);
|
|
|
|
Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh)
|
|
|
|
|
|
|
|
: _cropFrame(sheet, nextIndex, fw, fh, horizontal);
|
|
|
|
_lerp(cur, next, frac);
|
|
|
|
_lerp(cur, next, frac);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
} break;
|
|
|
|
@@ -997,7 +1018,7 @@ private:
|
|
|
|
|
|
|
|
|
|
|
|
auto t = Provider_(id);
|
|
|
|
auto t = Provider_(id);
|
|
|
|
if(!t || !t->Pixels || !t->Width || !t->Height) {
|
|
|
|
if(!t || !t->Pixels || !t->Width || !t->Height) {
|
|
|
|
if(err) *err = "Texture id not found: " + std::to_string(id);
|
|
|
|
if(err) *err = "Идентификатор текстуры не найден: " + std::to_string(id);
|
|
|
|
return {};
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Image img;
|
|
|
|
Image img;
|
|
|
|
@@ -1019,7 +1040,7 @@ private:
|
|
|
|
|
|
|
|
|
|
|
|
size_t start = size_t(off);
|
|
|
|
size_t start = size_t(off);
|
|
|
|
size_t end = start + size_t(len);
|
|
|
|
size_t end = start + size_t(len);
|
|
|
|
if(end > code.size()) { if(err) *err="Subprogram out of range"; return {}; }
|
|
|
|
if(end > code.size()) { if(err) *err="Подпрограмма выходит за пределы"; return {}; }
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> slice(code.begin()+start, code.begin()+end);
|
|
|
|
std::vector<uint8_t> slice(code.begin()+start, code.begin()+end);
|
|
|
|
OwnedTexture tmp;
|
|
|
|
OwnedTexture tmp;
|
|
|
|
@@ -1040,7 +1061,7 @@ private:
|
|
|
|
std::string* err) {
|
|
|
|
std::string* err) {
|
|
|
|
if(src.Kind == SrcKind::TexId) return _loadTex(src.TexId24, texCache, 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(src.Kind == SrcKind::Sub) return _loadSub(code, src.Off24, src.Len24, texCache, subCache, timeSeconds, err);
|
|
|
|
if(err) *err = "Unknown SrcKind";
|
|
|
|
if(err) *err = "Неизвестный SrcKind";
|
|
|
|
return {};
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1090,6 +1111,36 @@ private:
|
|
|
|
return out;
|
|
|
|
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) {
|
|
|
|
static void _lerp(Image& base, const Image& over, double t) {
|
|
|
|
if(t <= 0.0) return;
|
|
|
|
if(t <= 0.0) return;
|
|
|
|
if(t >= 1.0) { base = over; return; }
|
|
|
|
if(t >= 1.0) { base = over; return; }
|
|
|
|
@@ -1307,8 +1358,10 @@ private:
|
|
|
|
// ========================
|
|
|
|
// ========================
|
|
|
|
// Minimal DSL Lexer/Parser
|
|
|
|
// Minimal DSL Lexer/Parser
|
|
|
|
// now supports:
|
|
|
|
// now supports:
|
|
|
|
|
|
|
|
// name |> op(...)
|
|
|
|
|
|
|
|
// 32x32 "#RRGGBBAA"
|
|
|
|
|
|
|
|
// optional prefix:
|
|
|
|
// tex name |> op(...)
|
|
|
|
// tex name |> op(...)
|
|
|
|
// tex 32x32 "#RRGGBBAA"
|
|
|
|
|
|
|
|
// nested only where op expects a texture arg:
|
|
|
|
// nested only where op expects a texture arg:
|
|
|
|
// overlay( tex other |> ... )
|
|
|
|
// overlay( tex other |> ... )
|
|
|
|
// Also supports overlay(other) / mask(other) / lowpart(50, other)
|
|
|
|
// Also supports overlay(other) / mask(other) / lowpart(50, other)
|
|
|
|
@@ -1501,19 +1554,20 @@ private:
|
|
|
|
bool _parseProgram(std::string* err) {
|
|
|
|
bool _parseProgram(std::string* err) {
|
|
|
|
Lexer lx{Source_};
|
|
|
|
Lexer lx{Source_};
|
|
|
|
Tok t = lx.next();
|
|
|
|
Tok t = lx.next();
|
|
|
|
if(!(t.Kind==TokKind::Ident && t.Text=="tex")) {
|
|
|
|
bool hasTexPrefix = (t.Kind == TokKind::Ident && t.Text == "tex");
|
|
|
|
if(err) *err="Expected 'tex' at start";
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// compile base into main Code_
|
|
|
|
// compile base into main Code_
|
|
|
|
|
|
|
|
if(hasTexPrefix) {
|
|
|
|
if(!_compileBaseAfterTex(lx, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false;
|
|
|
|
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 ...
|
|
|
|
// pipeline: |> op ...
|
|
|
|
Tok nt = lx.next();
|
|
|
|
Tok nt = lx.next();
|
|
|
|
while (nt.Kind == TokKind::Pipe) {
|
|
|
|
while (nt.Kind == TokKind::Pipe) {
|
|
|
|
Tok opName = lx.next();
|
|
|
|
Tok opName = lx.next();
|
|
|
|
if(opName.Kind != TokKind::Ident) { if(err) *err="Expected op name after |>"; return false; }
|
|
|
|
if(opName.Kind != TokKind::Ident) { if(err) *err="Ожидалось имя операции после |>"; return false; }
|
|
|
|
ParsedOp op; op.Name = opName.Text;
|
|
|
|
ParsedOp op; op.Name = opName.Text;
|
|
|
|
|
|
|
|
|
|
|
|
Tok peek = lx.next();
|
|
|
|
Tok peek = lx.next();
|
|
|
|
@@ -1530,7 +1584,7 @@ private:
|
|
|
|
_emitOp(Code_, Op::End);
|
|
|
|
_emitOp(Code_, Op::End);
|
|
|
|
if (Code_.size() > MaxCodeBytes) {
|
|
|
|
if (Code_.size() > MaxCodeBytes) {
|
|
|
|
if (err)
|
|
|
|
if (err)
|
|
|
|
*err = "Pipeline bytecode too large: " + std::to_string(Code_.size()) +
|
|
|
|
*err = "Байткод пайплайна слишком большой: " + std::to_string(Code_.size()) +
|
|
|
|
" > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")";
|
|
|
|
" > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1539,12 +1593,13 @@ private:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================
|
|
|
|
// ========================
|
|
|
|
// Base compilation after 'tex'
|
|
|
|
// Base compilation (optionally after 'tex')
|
|
|
|
// supports:
|
|
|
|
// supports:
|
|
|
|
// 1) tex name
|
|
|
|
// 1) name
|
|
|
|
// 2) tex "name(.png/.jpg/.jpeg)" (allowed but normalized)
|
|
|
|
// 2) "name(.png/.jpg/.jpeg)" (allowed but normalized)
|
|
|
|
// 3) tex anim(...)
|
|
|
|
// 3) anim(...)
|
|
|
|
// 4) tex 32x32 "#RRGGBBAA"
|
|
|
|
// 4) 32x32 "#RRGGBBAA"
|
|
|
|
|
|
|
|
// optional: all of the above may be prefixed with 'tex'
|
|
|
|
// ========================
|
|
|
|
// ========================
|
|
|
|
bool _compileBaseAfterTex(Lexer& lx,
|
|
|
|
bool _compileBaseAfterTex(Lexer& lx,
|
|
|
|
std::vector<uint8_t>& out,
|
|
|
|
std::vector<uint8_t>& out,
|
|
|
|
@@ -1552,10 +1607,23 @@ private:
|
|
|
|
std::vector<RelPatch>* relPatches,
|
|
|
|
std::vector<RelPatch>* relPatches,
|
|
|
|
std::string* err) {
|
|
|
|
std::string* err) {
|
|
|
|
Tok a = lx.next();
|
|
|
|
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") {
|
|
|
|
if(a.Kind == TokKind::Ident && a.Text == "anim") {
|
|
|
|
Tok lp = lx.next();
|
|
|
|
Tok lp = lx.next();
|
|
|
|
if(lp.Kind != TokKind::LParen) { if(err) *err="Expected '(' after anim"; return false; }
|
|
|
|
if(lp.Kind != TokKind::LParen) { if(err) *err="Ожидалась '(' после anim"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
ParsedOp op; op.Name="anim";
|
|
|
|
ParsedOp op; op.Name="anim";
|
|
|
|
if(!_parseArgList(lx, op, err)) return false;
|
|
|
|
if(!_parseArgList(lx, op, err)) return false;
|
|
|
|
@@ -1581,7 +1649,7 @@ private:
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
|
|
|
|
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
|
|
|
|
if(tex.empty()) { if(err) *err="anim requires texture name"; return false; }
|
|
|
|
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 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 frameH = namedU("frame_h").value_or(namedU("h").value_or(posU(2).value_or(0)));
|
|
|
|
@@ -1590,15 +1658,20 @@ private:
|
|
|
|
uint32_t smooth = namedU("smooth").value_or(posU(5).value_or(0));
|
|
|
|
uint32_t smooth = namedU("smooth").value_or(posU(5).value_or(0));
|
|
|
|
|
|
|
|
|
|
|
|
std::string axis = namedS("axis").value_or("");
|
|
|
|
std::string axis = namedS("axis").value_or("");
|
|
|
|
bool horizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h'));
|
|
|
|
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(frameW > 65535u || frameH > 65535u || frames > 65535u) {
|
|
|
|
if(err) *err="anim params must fit uint16";
|
|
|
|
if(err) *err="параметры anim должны помещаться в uint16";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t fpsQ = fps ? std::min<uint32_t>(0xFFFFu, fps * 256u) : DefaultAnimFpsQ;
|
|
|
|
uint32_t fpsQ = fps ? std::min<uint32_t>(0xFFFFu, fps * 256u) : DefaultAnimFpsQ;
|
|
|
|
uint32_t flags = (smooth ? AnimSmooth : 0) | (horizontal ? AnimHorizontal : 0);
|
|
|
|
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);
|
|
|
|
_emitOp(out, Op::Base_Anim);
|
|
|
|
_emitSrcTexName(out, absPatches, relPatches, tex);
|
|
|
|
_emitSrcTexName(out, absPatches, relPatches, tex);
|
|
|
|
@@ -1611,28 +1684,28 @@ private:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(a.Kind == TokKind::Ident || a.Kind == TokKind::String) {
|
|
|
|
if(a.Kind == TokKind::Ident || a.Kind == TokKind::String) {
|
|
|
|
// tex name (or tex "name.png" => normalized)
|
|
|
|
// name (or "name.png" => normalized)
|
|
|
|
_emitOp(out, Op::Base_Tex);
|
|
|
|
_emitOp(out, Op::Base_Tex);
|
|
|
|
_emitSrcTexName(out, absPatches, relPatches, a.Text);
|
|
|
|
_emitSrcTexName(out, absPatches, relPatches, a.Text);
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(a.Kind == TokKind::Number) {
|
|
|
|
if(a.Kind == TokKind::Number) {
|
|
|
|
// tex 32x32 "#RRGGBBAA"
|
|
|
|
// 32x32 "#RRGGBBAA"
|
|
|
|
Tok xTok = lx.next();
|
|
|
|
Tok xTok = lx.next();
|
|
|
|
Tok b = lx.next();
|
|
|
|
Tok b = lx.next();
|
|
|
|
Tok colTok = lx.next();
|
|
|
|
Tok colTok = lx.next();
|
|
|
|
if(xTok.Kind != TokKind::X || b.Kind != TokKind::Number || (colTok.Kind!=TokKind::Ident && colTok.Kind!=TokKind::String)) {
|
|
|
|
if(xTok.Kind != TokKind::X || b.Kind != TokKind::Number || (colTok.Kind!=TokKind::Ident && colTok.Kind!=TokKind::String)) {
|
|
|
|
if(err) *err="Expected: tex <w>x<h> <#color>";
|
|
|
|
if(err) *err="Ожидалось: <w>x<h> <#color>";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uint32_t w = a.U32, h = b.U32;
|
|
|
|
uint32_t w = a.U32, h = b.U32;
|
|
|
|
uint32_t color = 0;
|
|
|
|
uint32_t color = 0;
|
|
|
|
if(!_parseHexColor(colTok.Text, color)) {
|
|
|
|
if(!_parseHexColor(colTok.Text, color)) {
|
|
|
|
if(err) *err="Bad color literal (use #RRGGBB or #RRGGBBAA)";
|
|
|
|
if(err) *err="Неверный литерал цвета (используйте #RRGGBB или #RRGGBBAA)";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(w>65535u || h>65535u) { if(err) *err="w/h must fit in uint16"; return false; }
|
|
|
|
if(w>65535u || h>65535u) { if(err) *err="w/h должны помещаться в uint16"; return false; }
|
|
|
|
_emitOp(out, Op::Base_Fill);
|
|
|
|
_emitOp(out, Op::Base_Fill);
|
|
|
|
_emitU16(out, w);
|
|
|
|
_emitU16(out, w);
|
|
|
|
_emitU16(out, h);
|
|
|
|
_emitU16(out, h);
|
|
|
|
@@ -1640,7 +1713,7 @@ private:
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(err) *err="Bad 'tex' base expression";
|
|
|
|
if(err) *err="Некорректное базовое текстурное выражение";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1661,7 +1734,7 @@ private:
|
|
|
|
if(!_compileSubProgramFromAlreadySawTex(lx, sub, err)) return false;
|
|
|
|
if(!_compileSubProgramFromAlreadySawTex(lx, sub, err)) return false;
|
|
|
|
|
|
|
|
|
|
|
|
Tok end = lx.next();
|
|
|
|
Tok end = lx.next();
|
|
|
|
if(end.Kind != TokKind::RParen) { if(err) *err="Expected ')' after sub texture expr"; return false; }
|
|
|
|
if(end.Kind != TokKind::RParen) { if(err) *err="Ожидалась ')' после подвыражения текстуры"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
PendingSub_[&op] = std::move(sub);
|
|
|
|
PendingSub_[&op] = std::move(sub);
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
@@ -1695,7 +1768,7 @@ private:
|
|
|
|
if(t.Kind == TokKind::Comma) { t = lx.next(); continue; }
|
|
|
|
if(t.Kind == TokKind::Comma) { t = lx.next(); continue; }
|
|
|
|
if(t.Kind == TokKind::RParen) return true;
|
|
|
|
if(t.Kind == TokKind::RParen) return true;
|
|
|
|
|
|
|
|
|
|
|
|
if(err) *err = "Expected ',' or ')' in argument list";
|
|
|
|
if(err) *err = "Ожидалась ',' или ')' в списке аргументов";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1728,7 +1801,7 @@ private:
|
|
|
|
if(t.Kind == TokKind::Comma) { t = lx.next(); continue; }
|
|
|
|
if(t.Kind == TokKind::Comma) { t = lx.next(); continue; }
|
|
|
|
if(t.Kind == TokKind::RParen) return true;
|
|
|
|
if(t.Kind == TokKind::RParen) return true;
|
|
|
|
|
|
|
|
|
|
|
|
if(err) *err = "Expected ',' or ')' in argument list";
|
|
|
|
if(err) *err = "Ожидалась ',' или ')' в списке аргументов";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1737,7 +1810,7 @@ private:
|
|
|
|
if(t.Kind == TokKind::Number) { out.Kind=ArgVal::ValueKind::U32; out.U32=t.U32; return true; }
|
|
|
|
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::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(t.Kind == TokKind::Ident) { out.Kind=ArgVal::ValueKind::Ident; out.S=t.Text; return true; }
|
|
|
|
if(err) *err = "Expected value token";
|
|
|
|
if(err) *err = "Ожидалось значение";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1764,12 +1837,12 @@ private:
|
|
|
|
// peek
|
|
|
|
// peek
|
|
|
|
Tok nt = lx.peek();
|
|
|
|
Tok nt = lx.peek();
|
|
|
|
if(nt.Kind == TokKind::RParen) break;
|
|
|
|
if(nt.Kind == TokKind::RParen) break;
|
|
|
|
if(nt.Kind != TokKind::Pipe) { if(err) *err="Sub tex: expected '|>' or ')'"; return false; }
|
|
|
|
if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
// consume pipe
|
|
|
|
// consume pipe
|
|
|
|
lx.next();
|
|
|
|
lx.next();
|
|
|
|
Tok opName = lx.next();
|
|
|
|
Tok opName = lx.next();
|
|
|
|
if(opName.Kind != TokKind::Ident) { if(err) *err="Sub tex: expected op name"; return false; }
|
|
|
|
if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
ParsedOp op; op.Name = opName.Text;
|
|
|
|
ParsedOp op; op.Name = opName.Text;
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1783,7 +1856,7 @@ private:
|
|
|
|
// Instead: enforce parentheses for ops in subprogram except no-arg ops (brighten/noalpha) which can be without.
|
|
|
|
// 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,
|
|
|
|
// 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.
|
|
|
|
// so we accept that in subprogram, no-arg ops must be written as brighten() etc.
|
|
|
|
if(err) *err="Sub tex: no-arg ops must use parentheses, e.g. brighten()";
|
|
|
|
if(err) *err="Подтекстура: операции без аргументов должны использовать скобки, напр. brighten()";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1795,13 +1868,13 @@ private:
|
|
|
|
while (true) {
|
|
|
|
while (true) {
|
|
|
|
Tok nt = lx.peek();
|
|
|
|
Tok nt = lx.peek();
|
|
|
|
if(nt.Kind == TokKind::RParen) break;
|
|
|
|
if(nt.Kind == TokKind::RParen) break;
|
|
|
|
if(nt.Kind != TokKind::Pipe) { if(err) *err="Sub tex: expected '|>' or ')'"; return false; }
|
|
|
|
if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
// consume pipe
|
|
|
|
// consume pipe
|
|
|
|
lx.next();
|
|
|
|
lx.next();
|
|
|
|
|
|
|
|
|
|
|
|
Tok opName = lx.next();
|
|
|
|
Tok opName = lx.next();
|
|
|
|
if(opName.Kind != TokKind::Ident) { if(err) *err="Sub tex: expected op name"; return false; }
|
|
|
|
if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
ParsedOp op; op.Name = opName.Text;
|
|
|
|
ParsedOp op; op.Name = opName.Text;
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1838,12 +1911,12 @@ private:
|
|
|
|
const size_t len = sub.Bytes.size();
|
|
|
|
const size_t len = sub.Bytes.size();
|
|
|
|
|
|
|
|
|
|
|
|
if(offset > 0xFFFFFFu || len > 0xFFFFFFu || (offset + len) > 0xFFFFFFu) {
|
|
|
|
if(offset > 0xFFFFFFu || len > 0xFFFFFFu || (offset + len) > 0xFFFFFFu) {
|
|
|
|
if(err) *err = "Subprogram слишком большой (off/len должны влезать в u24 байт)";
|
|
|
|
if(err) *err = "Подпрограмма слишком большая (off/len должны влезать в u24 байт)";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(offset + len > MaxCodeBytes) {
|
|
|
|
if(offset + len > MaxCodeBytes) {
|
|
|
|
if(err) *err = "Pipeline bytecode too large after sub append: " +
|
|
|
|
if(err) *err = "Байткод пайплайна слишком большой после вставки подпрограммы: " +
|
|
|
|
std::to_string(offset + len) + " > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")";
|
|
|
|
std::to_string(offset + len) + " > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1904,7 +1977,7 @@ private:
|
|
|
|
|
|
|
|
|
|
|
|
auto emitSrcFromPendingSub = [&]()->bool{
|
|
|
|
auto emitSrcFromPendingSub = [&]()->bool{
|
|
|
|
auto it = PendingSub_.find(&op);
|
|
|
|
auto it = PendingSub_.find(&op);
|
|
|
|
if(it == PendingSub_.end()) { if(err) *err="Internal: missing subprogram"; return false; }
|
|
|
|
if(it == PendingSub_.end()) { if(err) *err="Внутренняя ошибка: отсутствует подпрограмма"; return false; }
|
|
|
|
uint32_t off=0, len=0;
|
|
|
|
uint32_t off=0, len=0;
|
|
|
|
if(!_appendSubprogram(out, std::move(it->second), absPatches, relPatches, off, len, err)) return false;
|
|
|
|
if(!_appendSubprogram(out, std::move(it->second), absPatches, relPatches, off, len, err)) return false;
|
|
|
|
PendingSub_.erase(it);
|
|
|
|
PendingSub_.erase(it);
|
|
|
|
@@ -1919,7 +1992,7 @@ private:
|
|
|
|
|
|
|
|
|
|
|
|
// allow overlay(name) or overlay(tex=name)
|
|
|
|
// allow overlay(name) or overlay(tex=name)
|
|
|
|
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
|
|
|
|
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
|
|
|
|
if(tex.empty()) { if(err) *err="overlay requires texture arg"; return false; }
|
|
|
|
if(tex.empty()) { if(err) *err="overlay требует аргумент-текстуру"; return false; }
|
|
|
|
emitSrcFromName(tex);
|
|
|
|
emitSrcFromName(tex);
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1929,14 +2002,14 @@ private:
|
|
|
|
if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub();
|
|
|
|
if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub();
|
|
|
|
|
|
|
|
|
|
|
|
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
|
|
|
|
std::string tex = namedS("tex").value_or(posS(0).value_or(""));
|
|
|
|
if(tex.empty()) { if(err) *err="mask requires texture arg"; return false; }
|
|
|
|
if(tex.empty()) { if(err) *err="mask требует аргумент-текстуру"; return false; }
|
|
|
|
emitSrcFromName(tex);
|
|
|
|
emitSrcFromName(tex);
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(op.Name == "lowpart") {
|
|
|
|
if(op.Name == "lowpart") {
|
|
|
|
uint32_t pct = namedU("percent").value_or(posU(0).value_or(0));
|
|
|
|
uint32_t pct = namedU("percent").value_or(posU(0).value_or(0));
|
|
|
|
if(!pct) { if(err) *err="lowpart requires percent"; return false; }
|
|
|
|
if(!pct) { if(err) *err="lowpart требует процент"; return false; }
|
|
|
|
|
|
|
|
|
|
|
|
_emitOp(out, Op::LowPart);
|
|
|
|
_emitOp(out, Op::LowPart);
|
|
|
|
_emitU8(out, std::min<uint32_t>(100u, pct));
|
|
|
|
_emitU8(out, std::min<uint32_t>(100u, pct));
|
|
|
|
@@ -1945,7 +2018,7 @@ private:
|
|
|
|
if(op.Pos.size() >= 2 && op.Pos[1].S == "__SUBTEX__") return emitSrcFromPendingSub();
|
|
|
|
if(op.Pos.size() >= 2 && op.Pos[1].S == "__SUBTEX__") return emitSrcFromPendingSub();
|
|
|
|
|
|
|
|
|
|
|
|
std::string tex = namedS("tex").value_or(posS(1).value_or(""));
|
|
|
|
std::string tex = namedS("tex").value_or(posS(1).value_or(""));
|
|
|
|
if(tex.empty()) { if(err) *err="lowpart requires texture arg"; return false; }
|
|
|
|
if(tex.empty()) { if(err) *err="lowpart требует аргумент-текстуру"; return false; }
|
|
|
|
emitSrcFromName(tex);
|
|
|
|
emitSrcFromName(tex);
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1954,7 +2027,7 @@ private:
|
|
|
|
if(op.Name == "resize") {
|
|
|
|
if(op.Name == "resize") {
|
|
|
|
uint32_t w = namedU("w").value_or(posU(0).value_or(0));
|
|
|
|
uint32_t w = namedU("w").value_or(posU(0).value_or(0));
|
|
|
|
uint32_t h = namedU("h").value_or(posU(1).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) must fit uint16"; return false; }
|
|
|
|
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);
|
|
|
|
_emitOp(out, Op::Resize); _emitU16(out, w); _emitU16(out, h);
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1979,7 +2052,7 @@ private:
|
|
|
|
if(op.Name == "make_alpha") {
|
|
|
|
if(op.Name == "make_alpha") {
|
|
|
|
std::string col = namedS("color").value_or(posS(0).value_or(""));
|
|
|
|
std::string col = namedS("color").value_or(posS(0).value_or(""));
|
|
|
|
uint32_t argb=0;
|
|
|
|
uint32_t argb=0;
|
|
|
|
if(!_parseHexColor(col, argb)) { if(err) *err="make_alpha requires color #RRGGBB"; return false; }
|
|
|
|
if(!_parseHexColor(col, argb)) { if(err) *err="make_alpha требует цвет #RRGGBB"; return false; }
|
|
|
|
uint32_t rgb24 = argb & 0x00FFFFFFu;
|
|
|
|
uint32_t rgb24 = argb & 0x00FFFFFFu;
|
|
|
|
_emitOp(out, Op::MakeAlpha);
|
|
|
|
_emitOp(out, Op::MakeAlpha);
|
|
|
|
_emitU8(out, (rgb24 >> 16) & 0xFFu);
|
|
|
|
_emitU8(out, (rgb24 >> 16) & 0xFFu);
|
|
|
|
@@ -2020,7 +2093,7 @@ private:
|
|
|
|
auto compileColorOp = [&](Op opcode, bool needsRatio)->bool{
|
|
|
|
auto compileColorOp = [&](Op opcode, bool needsRatio)->bool{
|
|
|
|
std::string col = namedS("color").value_or(posS(0).value_or(""));
|
|
|
|
std::string col = namedS("color").value_or(posS(0).value_or(""));
|
|
|
|
uint32_t argb=0;
|
|
|
|
uint32_t argb=0;
|
|
|
|
if(!_parseHexColor(col, argb)) { if(err) *err="Bad color literal"; return false; }
|
|
|
|
if(!_parseHexColor(col, argb)) { if(err) *err="Неверный литерал цвета"; return false; }
|
|
|
|
_emitOp(out, opcode);
|
|
|
|
_emitOp(out, opcode);
|
|
|
|
_emitU32(out, argb);
|
|
|
|
_emitU32(out, argb);
|
|
|
|
if(needsRatio) {
|
|
|
|
if(needsRatio) {
|
|
|
|
@@ -2042,15 +2115,20 @@ private:
|
|
|
|
uint32_t smooth = namedU("smooth").value_or(posU(4).value_or(0));
|
|
|
|
uint32_t smooth = namedU("smooth").value_or(posU(4).value_or(0));
|
|
|
|
|
|
|
|
|
|
|
|
std::string axis = namedS("axis").value_or("");
|
|
|
|
std::string axis = namedS("axis").value_or("");
|
|
|
|
bool horizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h'));
|
|
|
|
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(frameW > 65535u || frameH > 65535u || frames > 65535u) {
|
|
|
|
if(err) *err="anim params must fit uint16";
|
|
|
|
if(err) *err="параметры anim должны помещаться в uint16";
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t fpsQ = fps ? std::min<uint32_t>(0xFFFFu, fps * 256u) : DefaultAnimFpsQ;
|
|
|
|
uint32_t fpsQ = fps ? std::min<uint32_t>(0xFFFFu, fps * 256u) : DefaultAnimFpsQ;
|
|
|
|
uint32_t flags = (smooth ? AnimSmooth : 0) | (horizontal ? AnimHorizontal : 0);
|
|
|
|
uint32_t flags = (smooth ? AnimSmooth : 0);
|
|
|
|
|
|
|
|
if(useGrid) flags |= AnimGrid;
|
|
|
|
|
|
|
|
else if(axisHorizontal) flags |= AnimHorizontal;
|
|
|
|
|
|
|
|
else if(!axisVertical) flags |= AnimGrid;
|
|
|
|
|
|
|
|
|
|
|
|
_emitOp(out, Op::Anim);
|
|
|
|
_emitOp(out, Op::Anim);
|
|
|
|
_emitU16(out, frameW);
|
|
|
|
_emitU16(out, frameW);
|
|
|
|
@@ -2061,7 +2139,7 @@ private:
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(err) *err = "Unknown op: " + op.Name;
|
|
|
|
if(err) *err = "Неизвестная операция: " + op.Name;
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|