Files
tlib/oversampling/WDL/lice/lice_lvg.cpp
2024-05-24 13:28:31 +02:00

626 lines
17 KiB
C++

#ifndef WDL_NO_DEFINE_MINMAX
#define WDL_NO_DEFINE_MINMAX
#endif
#include "lice.h"
#include <stdio.h>
#include <math.h>
#include "../projectcontext.h"
#include "../lineparse.h"
#include "../ptrlist.h"
#include "../assocarray.h"
#define PI 3.1415926535897932384626433832795
static inline int chartohex(char c)
{
if (c >= '0' && c<='9') return c-'0';
else if (c>='A' && c<='F') return 10 + c - 'A';
else if (c>='a' && c<='f') return 10 + c - 'a';
return -1;
}
static int __boolval(const char *p, int defval)
{
if (!stricmp(p,"yes") ||
!stricmp(p,"true") ||
!stricmp(p,"on") ||
atoi(p)>0) return 1;
if (!stricmp(p,"no") ||
!stricmp(p,"false") ||
!stricmp(p,"off") ||
!stricmp(p,"0")) return 0;
return defval;
}
static LICE_pixel __colorval(const char *p, LICE_pixel def)
{
const size_t lp = strlen(p);
if (lp == 3)
{
int r = chartohex(p[0]);
int g = chartohex(p[1]);
int b = chartohex(p[2]);
if (r>=0&&g>=0&&b>=0)
def = LICE_RGBA(r+(r<<4),g+(g<<4),b+(b<<4),255);
}
else if (lp == 6||lp==8)
{
int r = chartohex(p[0]), r2 = chartohex(p[1]);
int g = chartohex(p[2]), g2 = chartohex(p[3]);
int b = chartohex(p[4]), b2 = chartohex(p[5]);
int a = 0xf, a2=0xf;
if (lp==8) { a=chartohex(p[6]); a2=chartohex(p[7]); }
if (r>=0&&g>=0&&b>=0&&r2>=0&&g2>=0&&b2>=0&&a>=0&&a2>=0)
def = LICE_RGBA((r<<4)+r2,(g<<4)+g2,(b<<4)+b2,(a<<4)+a2);
}
return def;
}
class lvgRenderState
{
public:
lvgRenderState()
{
m_color=LICE_RGBA(255,255,255,255);
m_alpha=1.0f;
m_blend = LICE_BLIT_MODE_COPY;
m_aa = false;
}
~lvgRenderState() { }
LICE_pixel m_color;
float m_alpha;
int m_blend;
bool m_aa;
WDL_TypedBuf<bool> m_aa_stack;
WDL_TypedBuf<float> m_alpha_stack;
WDL_TypedBuf<LICE_pixel> m_color_stack;
WDL_TypedBuf<int> m_blend_stack;
/////////
void parsealpha(const char *p)
{
int idx=0;
if (*p == '-') idx++;
if (p[idx] == '.' || (p[idx] >= '0' && p[idx] <= '9'))
m_alpha = (float)atof(p);
}
void parseaa(const char *p)
{
int a = __boolval(p,-1);
if (a>=0) m_aa = !!a;
}
void parseblend(const char *p)
{
if (!stricmp(p,"copy")) m_blend = LICE_BLIT_MODE_COPY;
else if (!stricmp(p,"add")) m_blend = LICE_BLIT_MODE_ADD;
else if (!stricmp(p,"dodge")) m_blend = LICE_BLIT_MODE_DODGE;
else if (!stricmp(p,"mul")||!stricmp(p,"multiply")) m_blend = LICE_BLIT_MODE_MUL;
else if (!stricmp(p,"overlay")) m_blend = LICE_BLIT_MODE_MUL;
else if (!stricmp(p,"hsvadj")) m_blend = LICE_BLIT_MODE_HSVADJ;
}
void parsecolor(const char *p)
{
m_color = __colorval(p,m_color);
}
#define DEF_PUSHPOP(name,defpopval) \
void push##name() { int sz=m_##name##_stack.GetSize(); m_##name##_stack.Resize(sz+1,false)[sz] = m_##name; } \
void pop##name() { int sz = m_##name##_stack.GetSize()-1; m_##name = sz>=0 ? m_##name##_stack.Get()[sz] : defpopval; m_##name##_stack.Resize(sz,false); }
DEF_PUSHPOP(color,LICE_RGBA(0,0,0,255))
DEF_PUSHPOP(alpha,1.0f)
DEF_PUSHPOP(aa,false)
DEF_PUSHPOP(blend,LICE_BLIT_MODE_COPY)
#undef DEF_PUSHPOP
bool processAttributeLine(LineParser *lp)
{
int i,numtok=lp->getnumtokens();
switch (lp->gettoken_enum(0,"color\0"
"alpha\0"
"aa\0"
"blend\0"
"\0"))
{
#define PROCTYPE(v,name) \
case v: for (i=1;i<numtok;i++) { \
const char *p = lp->gettoken_str(i); \
if (!stricmp(p,"push")) push##name(); \
else if (!stricmp(p,"pop")) pop##name(); \
else parse##name(p); \
} \
return true;
PROCTYPE(0,color)
PROCTYPE(1,alpha)
PROCTYPE(2,aa)
PROCTYPE(3,blend)
#undef PROCTYPE
}
return false;
}
};
class lvgImageCtx
{
public:
lvgImageCtx(lvgImageCtx *par) : m_images(true,deleteThis)
{
m_in_render=false;
m_par=par;
m_cachedImage=0;
m_base_w=0;
m_base_h=0;
}
~lvgImageCtx()
{
delete m_cachedImage;
m_lines.Empty(true,free);
}
WDL_PtrList<char> m_lines;
LICE_IBitmap *m_cachedImage;
lvgImageCtx *m_par;
WDL_StringKeyedArray<lvgImageCtx *> m_images;
int m_base_w,m_base_h;
bool m_in_render;
void render(lvgRenderState *rstate, int wantw, int wanth);
private:
static void deleteThis(lvgImageCtx *t) { delete t; }
double parsecoord(const char *p, double scale, bool round)
{
if (!*p) return 0;
if (p[0] == 'a' && p[1]) return atoi(p+1);
if (p[0] == 'w')
{
scale = m_cachedImage ? m_cachedImage->getWidth() : 0.0;
p++;
}
else if (p[0] == 'h')
{
scale = m_cachedImage ? m_cachedImage->getHeight() : 0.0;
p++;
}
return atof(p) * scale + (round ? 0.5 : 0.0);
}
void processLvgLine(LineParser *lp, lvgRenderState *state, LICE_IBitmap *bm, double xscale, double yscale);
};
#define DECL_OPT(type, cfunc) \
static type getoption_##type(LineParser *lp, int startidx, const char *name, type def) { \
const size_t namelen = strlen(name); \
for(;startidx<lp->getnumtokens();startidx++) { \
const char *p=lp->gettoken_str(startidx); \
if (!strnicmp(name,p,namelen) && p[namelen]=='=') return cfunc(p+namelen+1,def); \
} \
return def; \
}
static int __intval(const char *p, int def) { return atoi(p); }
static double __dblval(const char *p, double def) { return atof(p); }
DECL_OPT(bool,!!__boolval)
DECL_OPT(int,__intval)
DECL_OPT(double,__dblval)
DECL_OPT(LICE_pixel,__colorval)
#undef DECL_OPT
void lvgImageCtx::processLvgLine(LineParser *lp, lvgRenderState *state, LICE_IBitmap *bm, double xscale, double yscale)
{
if (state->processAttributeLine(lp)) return;
int numtok = lp->getnumtokens();
const char *tok = lp->gettoken_str(0);
if (!stricmp(tok,"line"))
{
int i;
float lx,ly;
for (i = 1; i < numtok-1; i+= 2)
{
const char *p=lp->gettoken_str(i);
if (strstr(p,"=")) break;
float x=(float)parsecoord(p,xscale,false);
p=lp->gettoken_str(i+1);
if (strstr(p,"=")) break;
float y=(float)parsecoord(p,yscale,false);
if (i!=1) LICE_FLine(bm,lx,ly,x,y,state->m_color,state->m_alpha,state->m_blend,state->m_aa);
lx=x;
ly=y;
}
}
else if (!stricmp(tok,"circle"))
{
if (numtok>=4)
{
float x=(float)parsecoord(lp->gettoken_str(1),xscale,false);
float y=(float)parsecoord(lp->gettoken_str(2),yscale,false);
float r=(float)(atof(lp->gettoken_str(3))*lice_min(xscale,yscale));
if (getoption_bool(lp,1,"fill",false))
{
LICE_FillCircle(bm,x,y,r,state->m_color,state->m_alpha,state->m_blend,state->m_aa);
}
else
LICE_Circle(bm,x,y,r,state->m_color,state->m_alpha,state->m_blend,state->m_aa);
}
}
else if (!stricmp(tok,"arc"))
{
if (numtok>=6)
{
float x=(float)parsecoord(lp->gettoken_str(1),xscale,false);
float y=(float)parsecoord(lp->gettoken_str(2),yscale,false);
float r=(float)(atof(lp->gettoken_str(3))*lice_min(xscale,yscale));
float a1=(float)(atof(lp->gettoken_str(4))*PI/180.0);
float a2=(float)(atof(lp->gettoken_str(5))*PI/180.0);
LICE_Arc(bm,x,y,r,a1,a2,state->m_color,state->m_alpha,state->m_blend,state->m_aa);
}
}
else if (!stricmp(tok,"fill"))
{
if (numtok>=3) // fill x y [cmask=xxxxxx kmask=xxxxxxx]
{
LICE_pixel cmask = getoption_LICE_pixel(lp,1,"cmask",LICE_RGBA(255,255,255,0));
LICE_pixel kmask = getoption_LICE_pixel(lp,1,"kmask",LICE_RGBA(0,0,0,0));
int x = (int)parsecoord(lp->gettoken_str(1),xscale,true);
int y = (int)parsecoord(lp->gettoken_str(2),yscale,true);
LICE_SimpleFill(bm,x,y,state->m_color,cmask,kmask);
}
}
else if (!stricmp(tok,"rect"))
{
if (numtok>=5) // rect x y w h [dcdx=xxxxxxxx dcdy=xxxxxxxxx dcdxscale=1.0 dcdyscale=1.0]
{
LICE_pixel dcdx = getoption_LICE_pixel(lp,1,"dcdx",LICE_RGBA(0x80,0x80,0x80,0x80));
LICE_pixel dcdy = getoption_LICE_pixel(lp,1,"dcdy",LICE_RGBA(0x80,0x80,0x80,0x80));
double dcdxsc = getoption_double(lp,1,"dcdxscale",1.0);
double dcdysc = getoption_double(lp,1,"dcdyscale",1.0);
// todo: any options?
int x = (int)parsecoord(lp->gettoken_str(1),xscale,true);
int y = (int)parsecoord(lp->gettoken_str(2),yscale,true);
int w = (int)parsecoord(lp->gettoken_str(3),xscale,true);
int h = (int)parsecoord(lp->gettoken_str(4),yscale,true);
if (w>0 && h>0)
{
if (dcdx!=LICE_RGBA(0x80,0x80,0x80,0x80) ||
dcdy!=LICE_RGBA(0x80,0x80,0x80,0x80))
{
LICE_pixel sc = state->m_color;
dcdxsc /= w*128.0;
dcdysc /= h*128.0;
LICE_GradRect(bm,x,y,w,h,
(float)(LICE_GETR(sc)/255.0),
(float)(LICE_GETG(sc)/255.0),
(float)(LICE_GETB(sc)/255.0),
(float)(LICE_GETA(sc)/255.0*state->m_alpha),
(float)(((int)LICE_GETR(dcdx)-0x80)*dcdxsc),
(float)(((int)LICE_GETG(dcdx)-0x80)*dcdxsc),
(float)(((int)LICE_GETB(dcdx)-0x80)*dcdxsc),
(float)(((int)LICE_GETA(dcdx)-0x80)*dcdxsc),
(float)(((int)LICE_GETR(dcdy)-0x80)*dcdysc),
(float)(((int)LICE_GETG(dcdy)-0x80)*dcdysc),
(float)(((int)LICE_GETB(dcdy)-0x80)*dcdysc),
(float)(((int)LICE_GETA(dcdy)-0x80)*dcdysc),
state->m_blend);
}
else
LICE_FillRect(bm,x,y,w,h,state->m_color,state->m_alpha,state->m_blend);
}
}
}
else if (!stricmp(tok,"rerender"))
{
if (numtok>=2)
{
int forcew=getoption_int(lp,1,"w",0),forceh=getoption_int(lp,1,"h",0);
bool useState=getoption_bool(lp,1,"usestate",false);
lvgImageCtx *scan = this;
while (scan)
{
lvgImageCtx *p = scan->m_images.Get(lp->gettoken_str(1));
if (p)
{
if (!p->m_in_render)
{
p->m_in_render=true;
p->render(useState ? state : NULL,forcew,forceh);
p->m_in_render=false;
}
break;
}
scan=scan->m_par;
}
}
}
else if (!stricmp(tok,"blit"))
{
if (numtok>=3) // blit image x y [options]
{
LICE_IBitmap *src=NULL;
lvgImageCtx *scan = this;
const char *rd = lp->gettoken_str(1);
while (!strnicmp(rd,"parent:",7)) { scan = scan ? scan->m_par : NULL; rd += 7; }
if (!stricmp(rd,"parent"))
{
if (scan) scan=scan->m_par;
if (scan) src=scan->m_cachedImage;
}
else if (!stricmp(rd,"self"))
{
if (scan) src=scan->m_cachedImage;
}
else while (scan&&!src)
{
lvgImageCtx *p = scan->m_images.Get(rd);
if (p)
{
if (!p->m_cachedImage && !p->m_in_render)
{
p->m_in_render=true;
p->render(NULL,0,0);
p->m_in_render=false;
}
src = p->m_cachedImage;
break;
}
scan=scan->m_par;
}
if (src)
{
int x = (int)parsecoord(lp->gettoken_str(2),xscale,true);
int y = (int)parsecoord(lp->gettoken_str(3),yscale,true);
// these will be options filter= srcalpha= w= h= scale=
bool filter=getoption_bool(lp,1,"filter",true);
bool usesrcalpha = getoption_bool(lp,1,"srcalpha",true);
int w = getoption_int(lp,1,"w",src->getWidth());
int h = getoption_int(lp,1,"h",src->getHeight());
double sc = getoption_double(lp,1,"scale",1.0f);
if (fabs(sc-1.0)>0.0000000001)
{
w = (int)(w*sc+0.5);
h = (int)(h*sc+0.5);
}
// double ang = getoption_double(lp,1,"rotate",0.0) * PI / 180.0;
float sx=(float)getoption_double(lp,1,"srcx",0.0);
float sy=(float)getoption_double(lp,1,"srcy",0.0);
float sw=(float)getoption_double(lp,1,"srcw",src->getWidth());
float sh=(float)getoption_double(lp,1,"srch",src->getHeight());
// if (fabs(ang)>0.0001) LICE_RotatedBlit(bm,src,x,y,w,h,sx,sy,sw,sh,ang,true,state->m_alpha,state->m_blend,0,0);
//else
LICE_ScaledBlit(bm,src,x,y,w,h,sx,sy,sw,sh,
state->m_alpha,state->m_blend|(filter ? LICE_BLIT_FILTER_BILINEAR : 0)|(usesrcalpha ? LICE_BLIT_USE_ALPHA : 0));
}
}
}
}
void lvgImageCtx::render(lvgRenderState *rstate, int wantw, int wanth)
{
if (wantw<1) wantw = m_base_w;
if (wanth<1) wanth = m_base_h;
if (wantw<1||wanth<1)
{
if (m_cachedImage) m_cachedImage->resize(0,0);
return;
}
if (!m_cachedImage) m_cachedImage = new LICE_MemBitmap(wantw,wanth);
else m_cachedImage->resize(wantw,wanth);
LICE_Clear(m_cachedImage,LICE_RGBA(0,0,0,0));
lvgRenderState rs;
if (rstate) rs = *rstate;
double xscale = wantw / lice_max(m_base_w,1);
double yscale = wanth / lice_max(m_base_h,1);
int x;
bool comment_state=false;
LineParser lp(comment_state);
for (x=0;x<m_lines.GetSize();x++)
{
if (!lp.parse(m_lines.Get(x)) && lp.getnumtokens()>0)
processLvgLine(&lp,&rs,m_cachedImage,xscale,yscale);
}
}
void *LICE_GetSubLVG(void *lvg, const char *subname)
{
if (!lvg||!subname||!*subname) return NULL;
lvgImageCtx *t = (lvgImageCtx *)lvg;
return t->m_images.Get(subname);
}
LICE_IBitmap *LICE_RenderLVG(void *lvg, int reqw, int reqh, LICE_IBitmap *bmOut)
{
lvgImageCtx *t = (lvgImageCtx *)lvg;
if (!t || !t->m_lines.GetSize() || t->m_in_render) return NULL;
if (bmOut)
{
delete t->m_cachedImage;
t->m_cachedImage = bmOut;
}
else if (!t->m_cachedImage) t->m_cachedImage = new LICE_MemBitmap;
t->m_in_render=true;
t->render(NULL,reqw,reqh);
t->m_in_render=false;
LICE_IBitmap *ret = t->m_cachedImage;
t->m_cachedImage=NULL;
return ret;
}
void LICE_DestroyLVG(void *lvg)
{
lvgImageCtx *t = (lvgImageCtx *)lvg;
if (t && !t->m_par) delete t;
}
class lvgRdContext : public ProjectStateContext
{
public:
lvgRdContext(FILE *fp) { m_fp=fp; }
virtual ~lvgRdContext() { }
virtual void AddLine(const char *fmt, ...) {};
virtual int GetLine(char *buf, int buflen) // returns -1 on eof
{
if (!m_fp) return -1;
for (;;)
{
buf[0]=0;
fgets(buf,buflen,m_fp);
if (!buf[0]) return -1;
char *p=buf;
while (*p) p++;
while (p>buf && (p[-1] == '\r' || p[-1]=='\n')) p--;
*p=0;
if (*buf) return 0;
}
}
virtual WDL_INT64 GetOutputSize() { return 0; }
virtual int GetTempFlag() { return 0; }
virtual void SetTempFlag(int flag) {}
FILE *m_fp;
};
void *LICE_LoadLVGFromContext(ProjectStateContext *ctx, const char *nameInfo, int defw, int defh)
{
if (!ctx) return NULL;
bool err=false;
int ignoreBlockCnt=0;
lvgImageCtx *retimg = new lvgImageCtx(NULL);
lvgImageCtx *curimg = NULL;
if (nameInfo)
{
curimg = retimg;
curimg->m_base_w = defw;
curimg->m_base_h = defh;
}
while (!err)
{
char line[4096];
line[0]=0;
if (ctx->GetLine(line,sizeof(line))) break;
char *p=line;
while (*p == ' ' || *p == '\t') p++;
if (!*p) continue;
if (ignoreBlockCnt>0)
{
if (*p == '<') ignoreBlockCnt++;
else if (*p == '>') ignoreBlockCnt--;
}
else
{
if (*p == '<')
{
bool comment_state=false;
LineParser lp(comment_state);
if (!lp.parse(p)&&lp.getnumtokens()>=2 && !strcmp(lp.gettoken_str(0),"<LVG"))
{
if (!curimg)
{
// lp.gettoken_str(1) = version info string?
curimg = retimg;
}
else
{
lvgImageCtx *img = new lvgImageCtx(curimg);
curimg->m_images.Insert(lp.gettoken_str(1),img);
curimg = img;
}
curimg->m_base_w = lp.gettoken_int(2);
curimg->m_base_h = lp.gettoken_int(3);
}
else ignoreBlockCnt++;
}
else if (curimg)
{
if (*p == '>')
{
curimg = curimg->m_par;
if (!curimg) break; // success!
}
else
{
curimg->m_lines.Add(strdup(p));
}
}
if (!curimg) err=true; // <LVG must be first non-whitespace line
}
}
if (err)
{
delete retimg;
return 0;
}
return retimg;
}
void *LICE_LoadLVG(const char *filename)
{
FILE *fp=NULL;
#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8)
#ifdef WDL_SUPPORT_WIN9X
if (GetVersion()<0x80000000)
#endif
{
WCHAR wf[2048];
if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wf,2048))
fp = _wfopen(wf,L"rb");
}
#endif
if (!fp) fp = WDL_fopenA(filename,"rb");
if (fp)
{
lvgRdContext ctx(fp);
void *p = LICE_LoadLVGFromContext(&ctx,NULL,0,0);
fclose(fp);
return p;
}
return 0;
}