| import * as evg from 'evg' | |
| export const description = `Draw video, shape, text, shape outline...`; | |
| export const help = `This scene can be used to setup a shape, its outline and specify the fill and strike modes. | |
| Supported shapes include: | |
| - a variety of rectangles, ellipse and other polygons | |
| - custom paths specified from JS | |
| - text | |
| The color modes for shapes and outlines include: | |
| - texturing using data from input media streams (shape fill only) | |
| - texturing using local JPEG and PNG files (shape fill only) | |
| - solid color | |
| - linear and radial gradients | |
| The default scene is optimized to fallback to fast blit when no transformations are used on a straight rectangle shape. | |
| All options can be updated at run time. | |
| The module accepts 0, 1 or 2 sequences as input. | |
| Color replacement operations can be specified for base scenes using source videos by specifying the \`replace\` option. The replacement source is: | |
| - the image data if \`img\` is set, potentially altered using \`*_rep\` options | |
| - otherwise a linear gradient if \`fill=linear\` or a radial gradient if \`fill=radial\` (NOT supported in GPU mode, use an offscreen group for this). | |
| Warning: Color replacement operations cannot be used with transition or mix effects. | |
| ## Text options | |
| Text can be loaded from file if \`text[0]\` is an existing local file. | |
| By default all lines are loaded. The number of loaded lines can be specified using \`text[1]\` as follows: | |
| - 0 or not present: all lines are loaded | |
| - N > 0: only keep the last N lines | |
| - N < 0: only keep the first N lines | |
| Text loaded from file will be refreshed whenever the file is modified. | |
| Predefined keywords can be used in input text, identified as \`$KEYWORD$\`. The following keywords (case insensitive) are defined: | |
| - time: replaced by UTC date | |
| - ltime: replaced by locale date | |
| - date: replaced by date (Y/M/D) | |
| - ldate: replaced by locale date (Y/M/D) | |
| - mtime: replaced by output media time | |
| - mtime_SRC: replaced by media time of input source \`SRC\` | |
| - cpu: replaced by current CPU usage of process | |
| - mem: replaced by current memory usage of process | |
| - version: replaced by GPAC version | |
| - fversion: replaced by GPAC full version | |
| - P4CC, PropName: replaced by corresponding PID property | |
| ## Custom paths | |
| Custom paths (shapes) can be created through JS code indicated in \'shape\', either inline or through a file. | |
| The following GPAC JS modules are imported: | |
| - \`Sys\` as \`sys\` | |
| - All EVG as \`evg\` | |
| - \`os\` form QuickJS | |
| See [https://doxygen.gpac.io]() for more information on EVG and Sys JS APIs. | |
| The code is exposed the scene as \`this\`. The variable \`this.path\` is created, representing an empty path. | |
| EX "shape": "this.path.add_rectangle(0, 0, this.width, this.height); let el = new evg.Path().ellipse(0, 0, this.width, this.height/3); this.path.add_path(el);" | |
| The default behaviour is to use the shape width and height as reference size for texture mapping. | |
| If your custom path is textured, with bounding rectangle size different from the indicated shape size, set the variable \`this.tx_adjust\` to true. | |
| In the previous example, the texture mapping will not be impacted by the custom path size. | |
| EX "shape": "this.path.add_rectangle(0, 0, this.width, this.height); let el = new evg.Path().ellipse(0, 0, this.width, this.height/3); this.path.add_path(el); this.tx_adjust = true;" | |
| In this example, the texture mapping will be adjusted to the desired size. | |
| The global variables and functions are available (c.f. \`gpac -h avmix:global\`): | |
| - get_media_time(): return media time in seconds (float) of output | |
| - get_media_time(SRC): get time of source with id \`SRC\`, return -4 if not found, -3 if not playing, -2 if in prefetch, -1 if timing not yet known, media time in seconds (float) otherwise | |
| - current_utc_clock: current UTC time in ms | |
| - video_time: output video time | |
| - video_timescale: output video timescale | |
| - video_width: output video width | |
| - video_height: output video height | |
| If your path needs to be reevaluated on regular basis, set the value \`this.reload\` to the timeout to next reload, in milliseconds. | |
| `; | |
| //first 6 flags are defined by avmix | |
| const UPDATE_LINE = 1<<6; | |
| const UPDATE_COLOR = 1<<7; | |
| export const options = [ | |
| {name:"rx", value: 0, desc: "horizontal radius for rounded rect in percent of object width if positive, in absolute value if negative, value `y` means use `ry`", dirty: UPDATE_SIZE|UPDATE_ALLOW_STRING}, | |
| {name:"ry", value: 0, desc: "vertical radius for rounded rect in percent of object height if positive, in absolute value if negative, value `x` means use `rx`", dirty: UPDATE_SIZE|UPDATE_ALLOW_STRING}, | |
| {name:"tl", value: 1.0, desc: "top-left corner scaler (positive, 0 disables corner)", dirty: UPDATE_SIZE}, | |
| {name:"bl", value: 1.0, desc: "bottom-left corner scaler (positive, 0 disables corner)", dirty: UPDATE_SIZE}, | |
| {name:"tr", value: 1.0, desc: "top-right corner scaler (positive, 0 disables corner)", dirty: UPDATE_SIZE}, | |
| {name:"br", value: 1.0, desc: "bottom-right corner scaler (positive, 0 disables corner)", dirty: UPDATE_SIZE}, | |
| {name:"rs", value: false, desc: "repeat texture horizontally", dirty: UPDATE_SIZE}, | |
| {name:"rt", value: false, desc: "repeat texture vertically", dirty: UPDATE_SIZE}, | |
| {name:"keep_ar", value: true, desc: "keep aspect ratio", dirty: UPDATE_POS}, | |
| {name:"pad_color", value: "0x00FFFFFF", desc: `color to use for texture padding if \`rs\` or \`rt\` are false. Use \`none\` to use texture edge, \`0x00FFFFFF\` for transparent (always enforced if source is transparent)`, dirty: UPDATE_POS}, | |
| {name:"txmx", value: [], desc: "texture matrix - all 6 coefficients must be set, i.e. [xx xy tx yx yy ty]", dirty: UPDATE_POS}, | |
| {name:"cmx", value: [], desc: "color transform - all 20 coefficients must be set in order, i.e. [Mrr, Mrg, Mrb, Mra, Tr, Mgr, Mgg ...]", dirty: UPDATE_POS}, | |
| {name:"line_width", value: 0, desc: "line width in percent of width if positive, or absolute value if negative", dirty: UPDATE_LINE}, | |
| {name:"line_color", value: "white", desc: "line color, `linear` for linear gradient and `radial` for radial gradient", dirty: UPDATE_LINE}, | |
| {name:"line_pos", value: 'center', desc: `line/shape positioning. Possible values are: | |
| - center: line is centered around shape | |
| - outside: line is outside the shape | |
| - inside: line is inside the shape`, dirty: UPDATE_LINE}, | |
| {name:"line_dash", value: 'plain', desc: `line dashing mode. Possible values are: | |
| - plain: no dash | |
| - dash: predefined dash pattern is used | |
| - dot: predefined dot pattern is used | |
| - dashdot: predefined dash-dot pattern is used | |
| - dashdashdot: predefined dash-dash-dot pattern is used | |
| - dashdotdot: predefined dash-dot-dot pattern is used`, dirty: UPDATE_LINE}, | |
| {name:"dashes", value:[], desc: "dash/dot pattern lengths for custom dashes (these will be multiplied by line size)", dirty: UPDATE_LINE}, | |
| {name:"cap", value: 'flat', desc: `line end style. Possible values are: | |
| - flat: flat end | |
| - round: round end | |
| - square: square end (extends limit compared to flat) | |
| - triangle: triangle end`, dirty: UPDATE_LINE}, | |
| {name:"join", value: 'miter', desc: `line joint style. Possible values are: | |
| - miter: miter join (straight lines) | |
| - round: round join | |
| - bevel: bevel join | |
| - bevelmiter: bevel+miter join`, dirty: UPDATE_LINE}, | |
| {name:"miter_limit", value: 2, desc: "miter limit for joint styles", dirty: UPDATE_LINE}, | |
| {name:"dash_length", value: -1.0, desc: "length of path to outline, negative values mean full path", dirty: UPDATE_LINE}, | |
| {name:"dash_offset", value: 0.0, desc: "offset in path at which the outline starts", dirty: UPDATE_LINE}, | |
| {name:"blit", value: true, desc: "use blit if possible, otherwise EVG texturing. If disabled, always use texturing", dirty: UPDATE_SIZE}, | |
| {name:"fill", value: "none", desc: "fill color if used without sources, `linear` for linear gradient and `radial` for radial gradient", dirty: UPDATE_COLOR}, | |
| {name:"img", value: "", desc: `image for scene without sources or when \`replace\` is set. Accepts either a path to a local image (JPG or PNG), the ID of an offscreen group or the ID of a sequence`, dirty: UPDATE_POS|UPDATE_FX}, | |
| {name:"alpha", value: 1, desc: "global texture transparency", dirty: UPDATE_COLOR}, | |
| {name:"replace", value: "", desc: `if \`img\` or \`fill\` is set and shape is using source, set multi texture option. Possible modes are: | |
| - a, r, g or b: replace alpha source component by indicated component from \`img\` . If prefix \`-\` is set, replace by one minus the indicated component | |
| - m: mix using \`mix_ratio\` the color components of source and \`img\` and set alpha to full opacity | |
| - M: mix using \`mix_ratio\` all components of source and \`img\`, including alpha | |
| - xC: mix source 1 and source 2 using \`img\` component \`C\` (\`a\`, \`r\`, \`g\` or \`b\`) and force alpha to full opacity | |
| - XC: mix source 1 and source 2 using \`img\` component \`C\` (\`a\`, \`r\`, \`g\` or \`b\`), including alpha | |
| `, dirty: UPDATE_SIZE}, | |
| {name:"shape", value: "rect", desc: `shape type. Possible values are: | |
| - rect: rounded rectangle | |
| - square: square using smaller width/height value | |
| - ellipse: ellipse | |
| - circle: circle using smaller width/height value | |
| - rhombus: axis-aligned rhombus | |
| - text: force text mode even if text field is empty | |
| - rects: same as rounded rectangle but use straight lines for corners | |
| - other value: JS code for custom path creation, either string or local file name (dynamic reload possible)`, dirty: UPDATE_SIZE}, | |
| {name:"grad_p", value: [], desc: "gradient positions between 0 and 1", dirty: UPDATE_COLOR}, | |
| {name:"grad_c", value: [], desc: "gradient colors for each position, as strings", dirty: UPDATE_COLOR}, | |
| {name:"grad_start", value: [0, 0], desc: "start point for linear gradient or center point for radial gradient", dirty: UPDATE_COLOR}, | |
| {name:"grad_end", value: [0, 0], desc: "end point for linear gradient or radius value for radial gradient", dirty: UPDATE_COLOR}, | |
| {name:"grad_focal", value: [0, 0], desc: "focal point for radial gradient", dirty: UPDATE_COLOR}, | |
| {name:"grad_mode", value: 'pad', desc: `gradient mode. Possible values are: | |
| - pad: color padding outside of gradient bounds | |
| - spread: mirror gradient outside of bounds | |
| - repeat: repeat gradient outside of bounds`, dirty: UPDATE_COLOR}, | |
| {name:"text", value: [], desc: "text lines (UTF-8 only). If not empty, force \`shape=text\`", dirty: UPDATE_SIZE}, | |
| {name:"font", value: ["SANS"], desc: "font name(s)", dirty: UPDATE_SIZE}, | |
| {name:"size", value: 20, desc: "font size in percent of height (horizontal text) or width (vertical text), or absolute value if negative", dirty: UPDATE_SIZE}, | |
| {name:"baseline", value: 'alphabetic', desc: `baseline position. Possible values are: | |
| - alphabetic: alphabetic position of baseline | |
| - top: baseline at top of EM Box | |
| - hanging: reserved, __not implemented__ | |
| - middle: baseline at middle of EM Box | |
| - ideograph: reserved, __not implemented__ | |
| - bottom: baseline at bottom of EM Box`, dirty: UPDATE_SIZE}, | |
| {name:"align", value: 'center', desc: `horizontal text alignment. Possible values are: | |
| - center: center of shape | |
| - start: start of shape (left or right depending on text direction) | |
| - end: end of shape (right or left depending on text direction) | |
| - left: left of shape | |
| - right: right of shape`, dirty: UPDATE_SIZE}, | |
| {name:"spacing", value: 0, desc: "line spacing in percent of height (horizontal text) or width (vertical text), or absolute value if negative", dirty: UPDATE_SIZE}, | |
| {name:"bold", value: false, desc: "use bold version of font", dirty: UPDATE_SIZE}, | |
| {name:"italic", value: false, desc: "use italic version of font", dirty: UPDATE_SIZE}, | |
| {name:"underline", value: false, desc: "underline text", dirty: UPDATE_SIZE}, | |
| {name:"vertical", value: false, desc: "draw text vertically", dirty: UPDATE_SIZE}, | |
| {name:"flip", value: false, desc: "flip text vertically", dirty: UPDATE_SIZE}, | |
| {name:"extend", value: 0, desc: "maximum text width in percent of width (for horizontal) or height (for vertical), or absolute value if negative", dirty: UPDATE_SIZE}, | |
| {name:"keep_ar_rep", value: true, desc: "same as `keep_ar` for local image in replace mode", dirty: UPDATE_POS}, | |
| {name:"txmx_rep", value: [], desc: "same as `txmx` for local image in replace mode", dirty: UPDATE_POS}, | |
| {name:"cmx_rep", value: [], desc: "same as `cmx` for local image in replace mode", dirty: UPDATE_POS}, | |
| {name:"pad_color_rep", value: "none", desc: "same as `pad_color` for local image in replace mode", dirty: UPDATE_POS}, | |
| {name:"rs_rep", value: false, desc: "same as `rs` for local image in replace mode", dirty: UPDATE_POS}, | |
| {name:"rt_rep", value: false, desc: "same as `rt` for local image in replace mode", dirty: UPDATE_POS}, | |
| {} | |
| ]; | |
| function make_rounded_rect(is_straight) | |
| { | |
| var hw, hh, rx_bl, ry_bl, rx_br, ry_br, rx_tl, ry_tl, rx_tr, ry_tr, rx, ry; | |
| hw = this.width / 2; | |
| hh = this.height / 2; | |
| /*compute default rx/ry*/ | |
| rx = ry = 0; | |
| if (typeof this.rx == 'number') { | |
| if (this.rx >=0 ) | |
| rx = this.rx * this.width / 100; | |
| else | |
| rx = this.rx; | |
| } | |
| if (typeof this.ry == 'number') { | |
| if (this.ry>=0) { | |
| ry = this.ry * this.height / 100; | |
| } else { | |
| ry = this.ry; | |
| } | |
| } | |
| if (this.rx == 'y') rx = ry; | |
| if (this.ry == 'x') ry = rx; | |
| if (rx >= hw) rx = hw; | |
| if (ry >= hh) ry = hh; | |
| rx_bl = rx_br = rx_tl = rx_tr = rx; | |
| ry_bl = ry_br = ry_tl = ry_tr = ry; | |
| rx_tl *= this.tl; | |
| ry_tl *= this.tl; | |
| rx_tr *= this.tr; | |
| ry_tr *= this.tr; | |
| rx_bl *= this.bl; | |
| ry_bl *= this.bl; | |
| rx_br *= this.br; | |
| ry_br *= this.br; | |
| this.path.move_to(hw - rx_tr, hh); | |
| if (is_straight) | |
| this.path.line_to(hw, hh - ry_tr); | |
| else | |
| this.path.quadratic_to(hw, hh, hw, hh - ry_tr); | |
| this.path.line_to(hw, -hh + ry_br); | |
| if (is_straight) | |
| this.path.line_to(hw - rx_br, -hh); | |
| else | |
| this.path.quadratic_to(hw, -hh, hw - rx_br, -hh); | |
| this.path.line_to(-hw + rx_bl, -hh); | |
| if (is_straight) | |
| this.path.line_to(-hw, -hh + ry_bl); | |
| else | |
| this.path.quadratic_to(-hw, -hh, -hw, -hh + ry_bl); | |
| this.path.line_to(-hw, hh - ry_tl); | |
| if (is_straight) | |
| this.path.line_to(-hw + rx_tl, hh); | |
| else | |
| this.path.quadratic_to(-hw, hh, -hw + rx_tl, hh); | |
| this.path.close(); | |
| } | |
| function create_brush(color) | |
| { | |
| let stencil = null; | |
| if (color=='none') return null; | |
| if ((color=='linear') || (color=='radial') ) { | |
| let s_x, s_y, e_x, e_y, f_x, f_y; | |
| s_x = this.grad_start[0]; | |
| s_y = this.grad_start[1]; | |
| e_x = this.grad_end[0]; | |
| e_y = this.grad_end[1]; | |
| f_x = this.grad_focal[0]; | |
| f_y = this.grad_focal[1]; | |
| if (color=='linear') { | |
| if ((s_x==e_x) && (s_y==e_y) && (s_x==0) && (s_y==0)) { | |
| e_x = 1; | |
| } | |
| stencil = new evg.LinearGradient(); | |
| stencil.set_points(s_x, s_y, e_x, e_y); | |
| } else { | |
| if ((s_x==e_x) && (s_y==e_y) && (s_x==0) && (s_y==0)) { | |
| s_x = f_x = 0.5; | |
| s_y = f_y = 0.5; | |
| e_x = 0.5; | |
| e_y = 0.5; | |
| } | |
| stencil = new evg.RadialGradient(); | |
| stencil.set_points(s_x, s_y, f_x, f_y, e_x, e_y); | |
| } | |
| let maxg = this.grad_p.length; | |
| if (this.grad_c.length < maxg) maxg = this.grad_c.length; | |
| for (let i=0; i<maxg; i++) { | |
| stencil.set_stop(this.grad_p[i], this.grad_c[i]); | |
| } | |
| stencil.set_alphaf(this.alpha); | |
| if (this.grad_mode == 'pad') stencil.pad = GF_GRADIENT_MODE_PAD; | |
| else if (this.grad_mode == 'spread') stencil.pad = GF_GRADIENT_MODE_SPREAD; | |
| else stencil.pad = GF_GRADIENT_MODE_REPEAT; | |
| let txmx = null; | |
| if (this.txmx && this.txmx.length==6) { | |
| txmx = new evg.Matrix2D(this.txmx[0], this.txmx[1], this.txmx[2], this.txmx[3], this.txmx[4], this.txmx[5]); | |
| } else { | |
| txmx = new evg.Matrix2D(); | |
| } | |
| stencil.mx = txmx; | |
| } else { | |
| stencil = new evg.SolidBrush(); | |
| stencil.set_color(color); | |
| stencil.set_alphaf(this.alpha); | |
| } | |
| return stencil; | |
| } | |
| function setup_texture(pid_link) | |
| { | |
| let texture = pid_link ? pid_link.texture : this.local_stencil; | |
| let pid = pid_link ? pid_link.pid : null; | |
| let txmx_pid = (pid_link || !this.replace_op) ? this.txmx : this.txmx_rep; | |
| let keep_ar_pid = (pid_link || !this.replace_op) ? this.keep_ar : this.keep_ar_rep; | |
| let cmx_pid = (pid_link || !this.replace_op) ? this.cmx : this.cmx_rep; | |
| let pad_color_pid = (pid_link || !this.replace_op) ? this.pad_color : this.pad_color_rep; | |
| let rs_pid = (pid_link || !this.replace_op) ? this.rs : this.rs_rep; | |
| let rt_pid = (pid_link || !this.replace_op) ? this.rt : this.rt_rep; | |
| if (!texture) { | |
| print(GF_LOG_ERROR, 'Scene ' + this.id + ' missing texture !'); | |
| return; | |
| } | |
| let txmx; | |
| if (txmx_pid && Array.isArray(txmx_pid) && (txmx_pid.length==6)) { | |
| txmx = new evg.Matrix2D(txmx_pid[0], txmx_pid[1], txmx_pid[2], txmx_pid[3], txmx_pid[4], txmx_pid[5]); | |
| } else { | |
| txmx = new evg.Matrix2D(); | |
| } | |
| let scale_tx_sw = 1; | |
| let scale_tx_sh = 1; | |
| let trans_tx_x = 0; | |
| let trans_tx_y = 0; | |
| let sw = this.sw; | |
| let sh = this.sh; | |
| let sw_o = this.sw; | |
| let sh_o = this.sh; | |
| let tx_w = texture.width; | |
| let tx_h = texture.height; | |
| let rotated = 0; | |
| let mirrored = 0; | |
| let tx_info = {}; | |
| if (pid_link) { | |
| pid_link.tx_info = tx_info; | |
| } else { | |
| this.replace_tx_info = tx_info; | |
| } | |
| if (!txmx.identity && !pid_link && this.replace_op) { | |
| tx_info.repeat_s = true; | |
| tx_info.repeat_t = true; | |
| keep_ar_pid = false; | |
| } else { | |
| tx_info.repeat_s = rs_pid; | |
| tx_info.repeat_t = rt_pid; | |
| } | |
| tx_info.pad_color = null; | |
| let mxflip = new evg.Matrix2D(); | |
| if (pid && pid.mirror) { | |
| mirrored = pid.mirror; | |
| if (pid.mirror==1) mxflip.scale(-1, 1); | |
| else if (pid.mirror==2) mxflip.scale(1, -1); | |
| else if (pid.mirror==3) { | |
| mxflip.scale(-1, 1); | |
| mxflip.scale(1, -1); | |
| } | |
| } | |
| if (pid && pid.rotate) { | |
| rotated = pid.rotate; | |
| if ((pid.rotate==1) || (pid.rotate==3)) { | |
| tx_w = texture.height; | |
| tx_h = texture.width; | |
| } | |
| if (pid.rotate==3) mxflip.rotate(0, 0, -Math.PI/2); | |
| else if (pid.rotate==2) mxflip.rotate(0, 0, Math.PI); | |
| else if (pid.rotate==1) mxflip.rotate(0, 0, Math.PI/2); | |
| } | |
| txmx.add(mxflip); | |
| if (keep_ar_pid) { | |
| if (sw * tx_h < sh * tx_w) { | |
| sh = sw * tx_h / tx_w; | |
| } else if (sw * tx_h > sh * tx_w) { | |
| sw = sh * tx_w / tx_h; | |
| } | |
| } | |
| this.blit_path = null; | |
| tx_info.pad_color = pad_color_pid; | |
| if ((sw != sw_o) || (sh != sh_o)) { | |
| this.can_reuse = false; | |
| scale_tx_sw = sw_o / sw; | |
| trans_tx_x = (sw - sw_o)/sw/2; | |
| scale_tx_sh = sh_o / sh; | |
| trans_tx_y = (sh - sh_o)/sh/2; | |
| if (!rs_pid || !rt_pid) { | |
| if (pid_link || !this.use_mix) { | |
| if (pad_color_pid == "none") { | |
| tx_info.pad_color = "none"; | |
| //no padding asked, no repeat s/t or transform and path is a rectangle, define a new path with the new coords to blit | |
| if ((this.pids.length<=1) && (this.path.is_rectangle) && txmx.identity) { | |
| this.blit_path = new evg.Path().rectangle(0, 0, sw, sh, true); | |
| } else { | |
| this.use_blit = false; | |
| } | |
| } else { | |
| this.use_blit = false; | |
| let a = sys.color_component(pad_color_pid, 0); | |
| if (a < 1.0) this.opaque = false; | |
| } | |
| } | |
| } else { | |
| this.use_blit = false; | |
| } | |
| } | |
| if (!txmx.identity) { | |
| this.use_blit = false; | |
| this.can_reuse = false; | |
| } | |
| //set texture matrix matrix | |
| tx_info.mx = new evg.Matrix2D(); | |
| tx_info.mx.scale(scale_tx_sw, scale_tx_sh); | |
| tx_info.mx.translate(trans_tx_x, trans_tx_y); | |
| tx_info.mx.add(txmx); | |
| if (rotated==0) { | |
| if (mirrored==2) tx_info.mx.translate(0, 1); | |
| else if (mirrored==3) tx_info.mx.translate(1, 1); | |
| else if (mirrored==1) tx_info.mx.translate(1, 0); | |
| else tx_info.mx.translate(0, 0); | |
| } | |
| else if (rotated==1) { | |
| if (mirrored==2) tx_info.mx.translate(0, 0); | |
| else if (mirrored==3) tx_info.mx.translate(0, 1); | |
| else if (mirrored==1) tx_info.mx.translate(1, 1); | |
| else tx_info.mx.translate(1, 0); | |
| } | |
| else if (rotated==2) { | |
| if (mirrored==2) tx_info.mx.translate(1, 0); | |
| else if (mirrored==3) tx_info.mx.translate(0, 0); | |
| else if (mirrored==1) tx_info.mx.translate(0, 1); | |
| else tx_info.mx.translate(1, 1); | |
| } | |
| else { // (rotated==3) | |
| if (mirrored==2) tx_info.mx.translate(1, 1); | |
| else if (mirrored==3) tx_info.mx.translate(1, 0); | |
| else if (mirrored==1) tx_info.mx.translate(0, 0); | |
| else tx_info.mx.translate(0, 1); | |
| } | |
| if (pid) { | |
| if ((tx_w != this.sw) || (tx_h != this.sh)) { | |
| this.can_reuse=false; | |
| } | |
| } | |
| if (this.alpha < 1) { | |
| this.use_blit = false; | |
| this.opaque = false; | |
| tx_info.alpha = this.alpha; | |
| } else { | |
| tx_info.alpha = 1; | |
| } | |
| tx_info.cmx = null; | |
| if (cmx_pid && (cmx_pid.length==20)) { | |
| let cmx = new evg.ColorMatrix(cmx_pid); | |
| if (!cmx.identity) { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| tx_info.cmx = cmx; | |
| } | |
| } | |
| } | |
| function set_text(txt) | |
| { | |
| let final_text = []; | |
| txt.forEach( line => { | |
| if (line.indexOf('$') < 0) { | |
| final_text.push(line); | |
| return; | |
| } | |
| let first_is_sep = (line.charAt(0)=='$') ? true : false; | |
| let last_is_sep = (line.charAt(line.length-1)=='$') ? true : false; | |
| let split_text = line.split('$'); | |
| let len = split_text.length; | |
| let new_line=''; | |
| split_text.forEach( (item, index) => { | |
| if (!index && !first_is_sep) { | |
| new_line += item; | |
| return; | |
| } | |
| if (!last_is_sep && (index==len-1)) { | |
| new_line += item; | |
| return; | |
| } | |
| let timeout=0; | |
| let kword = item.toLowerCase(); | |
| if (kword=='time') { | |
| item = new Date().toUTCString(); | |
| timeout = 1000; | |
| } | |
| else if (kword=='ltime') { | |
| item = new Date().toLocaleString(); | |
| timeout = 1000; | |
| } | |
| else if (kword=='date') { | |
| item = new Date().toDateString(); | |
| timeout = 1000 * 3600 * 24; | |
| } | |
| else if (kword=='ldate') { | |
| item = new Date().toLocaleDateString(); | |
| timeout = 1000 * 3600 * 24; | |
| } | |
| else if (kword.startsWith('mtime')) { | |
| let time; | |
| if (kword.startsWith('mtime_')) { | |
| let src = item.substring(6); | |
| time = get_media_time( src ); | |
| } else { | |
| time = get_media_time(); | |
| } | |
| if (time == -4) { | |
| item = 'not found'; | |
| } else if (time==-3) { | |
| item = 'not playing'; | |
| } else if (time==-2) { | |
| item = 'initializing'; | |
| } else if (time==-1) { | |
| item = 'prefetching'; | |
| } else { | |
| time *= 1000; | |
| let time_s = Math.floor(time/1000); | |
| let h = Math.floor(time_s/3600); | |
| let m = Math.floor(time_s/60 - h*60); | |
| let s = Math.floor((time_s - h*3600 - m*60)); | |
| item = ((h<10) ? '0' : '') + h + ((m<10) ? ':0' : ':') + m + ((s<10) ? ':0' : ':') + s + '.'; | |
| time = Math.floor(time - time_s*1000); | |
| if (time < 10) item += '00'; | |
| else if (time < 100) item += '0'; | |
| item += time; | |
| } | |
| timeout = 30; | |
| } | |
| else if (kword=='cpu') { | |
| item = '' + sys.process_cpu_usage; | |
| timeout = 500; | |
| } | |
| else if (kword=='mem') { | |
| item = '' + Math.floor(sys.process_memory/1000000) + ' MB'; | |
| timeout = 500; | |
| } | |
| else if (kword=='version') { | |
| item = sys.version; | |
| } | |
| else if (kword=='fversion') { | |
| item = sys.version_full; | |
| } else { | |
| try { | |
| let p = ipid.get_prop(item); | |
| if (!p) p = ipid.get_info(item); | |
| if (typeof p != 'undefined') { | |
| if (p==null) item = 'n/a'; | |
| else item = '' + p; | |
| } | |
| } catch (e) {} | |
| } | |
| new_line += item; | |
| if (timeout) { | |
| if (!this.reload) this.reload = timeout; | |
| else if (timeout < this.reload) this.reload = timeout; | |
| } | |
| }); | |
| final_text.push(new_line); | |
| }); | |
| this.path.set_text(final_text); | |
| } | |
| function set_texture_params(texture, tx_info) | |
| { | |
| texture.repeat_s = tx_info.repeat_s; | |
| texture.repeat_t = tx_info.repeat_t; | |
| texture.set_alphaf(tx_info.alpha); | |
| texture.set_pad_color(tx_info.pad_color); | |
| texture.mx = tx_info.mx; | |
| texture.cmx = tx_info.cmx; | |
| } | |
| export function load() { | |
| return { | |
| local_stencil: null, | |
| local_stencil_type: 0, | |
| path: null, | |
| mx: null, | |
| last_img: "", | |
| dyn_text: null, | |
| dyn_path: null, | |
| last_path_time: 0, | |
| last_text_time: 0, | |
| reload: 0, | |
| update: function() { | |
| if (this.update_flag & UPDATE_PID) { | |
| if (this.pids.length) { | |
| this.is_texture=true; | |
| } else { | |
| this.update_flag = UPDATE_SIZE; | |
| this.is_texture=false; | |
| } | |
| } | |
| if (this.reload && (this.reload < current_utc_clock)) { | |
| this.update_flag = UPDATE_SIZE; | |
| } | |
| else if (this.dyn_text && (this.last_text_time < sys.mod_time(this.dyn_text))) { | |
| this.update_flag = UPDATE_SIZE; | |
| } | |
| else if (this.last_path_time && (this.last_path_time < sys.mod_time(this.dyn_path))) { | |
| this.update_flag = UPDATE_SIZE; | |
| } | |
| if (!this.update_flag) return 1; | |
| this.replace_op = 0; | |
| this.replace_op_img = false; | |
| this.replace_par = 0; | |
| this.use_mix = false; | |
| this.replace_tx_info = null; | |
| let rep = this.replace; | |
| let op = GF_EVG_OPERAND_REPLACE_ALPHA; | |
| if (rep[0]=='-') { | |
| op = GF_EVG_OPERAND_REPLACE_ONE_MINUS_ALPHA; | |
| rep = rep.substring(1); | |
| } | |
| else if (rep[0]=='x') { | |
| op = GF_EVG_OPERAND_MIX_DYN; | |
| rep = rep.substring(1); | |
| } | |
| else if (rep[0]=='X') { | |
| op = GF_EVG_OPERAND_MIX_DYN_ALPHA; | |
| rep = rep.substring(1); | |
| } | |
| if (rep=="m") { this.replace_op = GF_EVG_OPERAND_MIX; this.use_mix = true; } | |
| else if (rep=="M") { this.replace_op = GF_EVG_OPERAND_MIX_ALPHA; this.use_mix = true; } | |
| else if (rep=="a") { this.replace_op = op; } | |
| else if (rep=="r") { this.replace_op = op; this.replace_par = 1; } | |
| else if (rep=="g") { this.replace_op = op; this.replace_par = 2; } | |
| else if (rep=="b") { this.replace_op = op; this.replace_par = 3; } | |
| //no associated sources, use image if set | |
| let reset_local_stencil = true; | |
| if ((!this.pids.length || this.replace_op) && this.img.length) { | |
| if (this.img != this.last_img) this.local_stencil = null; | |
| this.last_img = this.img; | |
| if (!this.local_stencil) { | |
| this.local_stencil_type = 0; | |
| try { | |
| this.local_stencil = get_group_texture(this.img); | |
| if (this.local_stencil) { | |
| this.local_stencil_type = 1; | |
| } else { | |
| this.local_stencil = get_sequence_texture(this.img); | |
| if (this.local_stencil) { | |
| this.local_stencil_type = 2; | |
| } else { | |
| let url = resolve_url(this.img); | |
| this.local_stencil = new evg.Texture(url, false); | |
| //when using mask from main scene, let the mask decide | |
| this.local_stencil.filtering = GF_TEXTURE_FILTER_HIGH_QUALITY; | |
| } | |
| } | |
| this.is_texture = true; | |
| if (this.replace_op) this.replace_op_img = true; | |
| reset_local_stencil = false; | |
| } catch (e) { | |
| if (this.replace_op) { | |
| print(GF_LOG_WARNING, 'Failed to load image ' + this.img + ': ' + e + ' - ignoring component replacement'); | |
| if (!this.pids.length) this.is_texture = false; | |
| this.replace_op = 0; | |
| this.replace_op_img = false; | |
| } else { | |
| print(GF_LOG_WARNING, 'Failed to load image ' + this.img + ': ' + e + ' - will use color fill'); | |
| if (!this.pids.length) this.is_texture = false; | |
| } | |
| } | |
| } else { | |
| reset_local_stencil = false; | |
| } | |
| } | |
| //no change in img, don't reset local stencil | |
| else if (this.img == this.last_img) { | |
| reset_local_stencil = false; | |
| } | |
| if (this.replace_op && !this.img.length) { | |
| if ((this.fill == 'linear') || (this.fill == 'radial')) {} | |
| else this.replace_op = 0; | |
| } | |
| //update color but local stencil is a gradient, rebuild gradient | |
| if ((this.update_flag & UPDATE_COLOR) && (!this.local_stencil || !this.local_stencil.solid_brush)) { | |
| this.update_flag |= UPDATE_POS; | |
| this.update_flag &= UPDATE_COLOR; | |
| } | |
| //update our objects | |
| this.opaque = false; | |
| this.use_blit = false; | |
| this.can_reuse = this.pids.length ? true : false; | |
| this.sw = this.width; | |
| this.sh = this.height; | |
| this.is_text = false; | |
| if (!this.path || (this.update_flag & UPDATE_SIZE)) { | |
| let path_loaded = false; | |
| this.path = new evg.Path(); | |
| this.reload = 0; | |
| if ((this.shape == 'text') || this.text.length) { | |
| this.is_text = true; | |
| if ((this.text.length == 1) || (this.text.length == 2)) { | |
| let url = resolve_url(this.text[0]); | |
| if (sys.file_exists(url)) { | |
| this.dyn_text = url; | |
| } | |
| } | |
| /*create a text*/ | |
| this.path = new evg.Text(); | |
| this.path.font = this.font; | |
| this._font_size = 0; | |
| if (this.size<0) | |
| this.path.fontsize = this._font_size = -this.size; | |
| else | |
| this.path.fontsize = this._font_size = this.size * (this.vertical ? this.width : this.height) / 100; | |
| if (this.spacing<0) | |
| this.path.lineSpacing = -this.spacing; | |
| else | |
| this.path.lineSpacing = this.spacing * (this.vertical ? this.width : this.height) / 100; | |
| if (this.baseline=='top') this.path.baseline = GF_TEXT_BASELINE_TOP; | |
| else if (this.baseline=='hanging') this.path.baseline = GF_TEXT_BASELINE_HANGING; | |
| else if (this.baseline=='middle') this.path.baseline = GF_TEXT_BASELINE_MIDDLE; | |
| else if (this.baseline=='ideographic') this.path.baseline = GF_TEXT_BASELINE_IDEOGRAPHIC; | |
| else if (this.baseline=='bottom') this.path.baseline = GF_TEXT_BASELINE_BOTTOM; | |
| else this.path.baseline = GF_TEXT_BASELINE_ALPHABETIC; | |
| if (this.align == 'start') this.path.align = GF_TEXT_ALIGN_START; | |
| else if (this.align == 'end') this.path.align = GF_TEXT_ALIGN_END; | |
| else if (this.align == 'left') this.path.align = GF_TEXT_ALIGN_LEFT; | |
| else if (this.align == 'right') this.path.align = GF_TEXT_ALIGN_RIGHT; | |
| else this.path.align = GF_TEXT_ALIGN_CENTER; | |
| this.path.bold = this.bold; | |
| this.path.italic = this.italic; | |
| this.path.underline = this.underline; | |
| this.path.horizontal = !this.vertical; | |
| this.path.flip = this.flip; | |
| this.path.maxWidth = 0; | |
| if (this.extend) { | |
| if (this.extend>0) | |
| this.path.maxWidth = this.extend * (this.vertical ? this.height : this.width) / 100; | |
| else | |
| this.path.maxWidth = this.extend; | |
| } | |
| if (this.dyn_text) { | |
| let txt = sys.load_file(this.dyn_text, true); | |
| if (this.text.length==2) { | |
| txt = txt.split(/\r?\n/); | |
| let nb_items = this.text[1]; | |
| if (nb_items>0) { | |
| var n = txt.length - nb_items; | |
| txt.splice(0, txt.length - nb_items); | |
| } else { | |
| var n = txt.length + nb_items; | |
| txt.splice(-nb_items, txt.length + nb_items); | |
| } | |
| } else { | |
| txt = [txt]; | |
| } | |
| set_text.apply(this, [txt]); | |
| this.last_text_time = sys.mod_time(this.dyn_text); | |
| } else { | |
| set_text.apply(this, [this.text]); | |
| } | |
| //apply alignment | |
| let tsize = this.path.measure(); | |
| let text_mx = new evg.Matrix2D(); | |
| if (this.vertical) { | |
| let align_type = 0; //0: top, 1: center, 2: bottom | |
| if ((this.align == 'end') || (this.align == 'right')) align_type = 2; | |
| else if (this.align == 'center') align_type = 1; | |
| if (align_type==0) { | |
| text_mx.translate(0, this.sh/2 - this._font_size); | |
| } | |
| else if (align_type==1) { | |
| text_mx.translate(0, tsize.height/2); | |
| } | |
| else if (align_type==2) { | |
| text_mx.translate(0, -this.sh/2 + tsize.height); | |
| } | |
| } else { | |
| let align_type = 0; //0: left, 1: center, 2: right | |
| if ((this.align == 'start') && tsize.right_to_left) align_type = 2; | |
| else if ((this.align == 'end') && !tsize.right_to_left) align_type = 2; | |
| else if (this.align == 'right') align_type = 2; | |
| else if (this.align == 'center') align_type = 1; | |
| if (align_type==0) { | |
| text_mx.translate(-this.sw/2, 0); | |
| } | |
| else if (align_type==1) { | |
| text_mx.translate(-tsize.width/2, 0); | |
| } | |
| else if (align_type==2) { | |
| text_mx.translate(this.sw/2 - tsize.width, 0); | |
| } | |
| } | |
| //trash text, keep path only | |
| this.path = this.path.get_path().transform(text_mx); | |
| path_loaded = true; | |
| } else if (this.shape == 'rect') { | |
| } else if (this.shape == 'square') { | |
| if (this.sh<this.sw) this.sw = this.sh; | |
| else this.sh = this.sw; | |
| this.path.rectangle(0, 0, this.sw, this.sh, true); | |
| path_loaded = true; | |
| } else if (this.shape == 'circle') { | |
| if (this.sh<this.sw) this.sw = this.sh; | |
| else this.sh = this.sw; | |
| this.path.ellipse(0, 0, this.sw, this.sh); | |
| path_loaded = true; | |
| } else if (this.shape == 'ellipse') { | |
| this.path.ellipse(0, 0, this.sw, this.sh); | |
| path_loaded = true; | |
| } else if (this.shape == 'rhombus') { | |
| let hsw = this.sw/2; | |
| let hsh = this.sh/2; | |
| this.path.move_to(this.sw, 0); | |
| this.path.line_to(0, this.sh); | |
| this.path.line_to(-this.sw, 0); | |
| this.path.line_to(0, -this.sh); | |
| path_loaded = true; | |
| } else if (this.shape == 'rects') { | |
| path_loaded = true; | |
| make_rounded_rect.apply(this, [true]); | |
| } else { | |
| try { | |
| let url = resolve_url(this.shape); | |
| this.tx_adjust=false; | |
| if (sys.file_exists(url)) { | |
| let f = new Function([], sys.load_file(url, true) ); | |
| let res = f.apply(this, []); | |
| if (typeof res == 'number') this.reload = res; | |
| path_loaded = true; | |
| this.dyn_path = url; | |
| this.last_path_time = sys.mod_time(url); | |
| } else { | |
| let f = new Function([], this.shape); | |
| let res = f.apply(this, []); | |
| if (typeof res == 'number') this.reload = res; | |
| path_loaded = true; | |
| this.dyn_path = null; | |
| } | |
| if (this.tx_adjust) { | |
| let rc = this.path.bounds; | |
| this.sw = rc.w; | |
| this.sh = rc.h; | |
| } | |
| } catch (e) { | |
| print(GF_LOG_ERROR, "Failed to load path: " + e + " - using default"); | |
| } | |
| } | |
| if (path_loaded) { | |
| } | |
| else if ((!this.rx && !this.ry) || (!this.tl && !this.tr && !this.bl && !this.br)) { | |
| this.path.rectangle(0, 0, this.sw, this.sh, true); | |
| } | |
| else if ((this.rx == 50) && (this.ry == 50) && this.tl && this.tr && this.bl && this.br) { | |
| this.path.ellipse(0, 0, this.sw, this.sh); | |
| } else { | |
| make_rounded_rect.apply(this, [false]); | |
| } | |
| if (this.reload) { | |
| this.reload += current_utc_clock; | |
| } | |
| //force setup of textures of outline | |
| this.update_flag = UPDATE_POS; | |
| //force loading of outline | |
| this.outline = null; | |
| } | |
| if (this.path.is_rectangle) { | |
| this.opaque = true; | |
| this.use_blit = blit_enabled ? this.blit : null; | |
| } | |
| if (reset_local_stencil) { | |
| this.local_stencil = null; | |
| } | |
| if (this.update_flag & UPDATE_POS) { | |
| //setup textures | |
| if (this.is_texture) { | |
| if (this.pids.length) { | |
| this.pids.forEach(p => { | |
| setup_texture.apply(this, [p]); | |
| }); | |
| if ((this.pids.length>1) || use_gpu) | |
| this.can_reuse = false; | |
| } | |
| //image case | |
| if (!this.pids.length || this.replace_op_img) { | |
| setup_texture.apply(this, [null]); | |
| if (this.replace_op) { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| } | |
| } | |
| if (!this.replace_op_img && this.replace_op) { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| this.local_stencil = create_brush.apply(this, [this.fill]); | |
| } | |
| } else { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| this.local_stencil = create_brush.apply(this, [this.fill]); | |
| //no fill specified, no input pids, consider this is no signal but we need to clear canvas | |
| if (!this.local_stencil) | |
| this.opaque = false; | |
| } | |
| } | |
| if (!this.outline || (this.update_flag & UPDATE_LINE)) { | |
| if (this.line_width) { | |
| let lwidth = (this.line_width<0) ? (-this.line_width) : (this.line_width*this.sw / 100); | |
| let pen_settings = { | |
| width: lwidth, | |
| dash: this.line_dash, | |
| miter: this.miter_limit, | |
| offset: this.dash_offset, | |
| length: this.dash_length, | |
| dashes: this.dashes | |
| }; | |
| if (this.line_pos=='outside') pen_settings.align = GF_PATH_LINE_OUTSIDE; | |
| else if (this.line_pos=='inside') pen_settings.align = GF_PATH_LINE_INSIDE; | |
| else pen_settings.align = GF_PATH_LINE_CENTER; | |
| if (this.line_dash=='dash') pen_settings.dash = GF_DASH_STYLE_DASH; | |
| else if (this.line_dash=='dot') pen_settings.dash = GF_DASH_STYLE_DOT; | |
| else if (this.line_dash=='dashdot') pen_settings.dash = GF_DASH_STYLE_DASH_DOT; | |
| else if (this.line_dash=='dashdashdot') pen_settings.dash = GF_DASH_STYLE_DASH_DASH_DOT; | |
| else if (this.line_dash=='dashdotdot') pen_settings.dash = GF_DASH_STYLE_DASH_DOT_DOT; | |
| else pen_settings.dash = GF_DASH_STYLE_PLAIN; | |
| if (this.cap=='round') pen_settings.cap = GF_LINE_CAP_ROUND; | |
| else if (this.cap=='square') pen_settings.cap = GF_LINE_CAP_SQUARE; | |
| else if (this.cap=='triangle') pen_settings.cap = GF_LINE_CAP_TRIANGLE; | |
| else pen_settings.cap = GF_LINE_CAP_FLAT; | |
| if (this.join=='round') pen_settings.join = GF_LINE_JOIN_ROUND; | |
| else if (this.join=='bevel') pen_settings.join = GF_LINE_JOIN_BEVEL; | |
| else if (this.join=='bevelmiter') pen_settings.join = GF_LINE_JOIN_MITER_SVG; | |
| else pen_settings.join = GF_LINE_JOIN_MITER; | |
| this.outline = this.path.outline(pen_settings); | |
| this.outline_brush = create_brush.apply(this, [this.line_color]); | |
| } else { | |
| this.outline=null; | |
| this.outline_brush = null; | |
| } | |
| } | |
| if (this.update_flag & UPDATE_COLOR) { | |
| if (this.outline_brush) this.outline_brush.set_color(this.line_color); | |
| if (!this.is_texture && this.local_stencil) { | |
| this.local_stencil.set_color(this.fill); | |
| this.local_stencil.set_alphaf(this.alpha); | |
| } | |
| } | |
| this.pids.forEach( pid_link => { | |
| pid_link.tx_info.alpha = this.alpha; | |
| if (this.alpha<1) { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| this.opaque = false; | |
| } | |
| }); | |
| this.update_flag = 0; | |
| if (this.pids.length>1) { | |
| //todo, get info from transition to check if we can blit / reuse source | |
| if (this.mix_ratio < 0) { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| } | |
| } | |
| if (this.replace_op) { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| this.opaque = false; | |
| } | |
| if (this.pids.length) { | |
| if (this.can_reuse) { | |
| print(GF_LOG_DEBUG, 'Shape ' + this.id + ' setup done: may reuse src as vout framebuffer'); | |
| } else { | |
| print(GF_LOG_DEBUG, 'Shape ' + this.id + ' setup done: draw using texture' + (this.use_blit ? ' or blit ' : '') + (this.opaque ? ', opaque' : ', transparent' ) ); | |
| } | |
| } else { | |
| this.can_reuse = false; | |
| this.use_blit = false; | |
| print(GF_LOG_DEBUG, 'Shape ' + this.id + ' setup done: ' + (this.is_texture ? 'image fill' : 'solid color fill') ); | |
| if (this.is_texture && sys.pixfmt_transparent(this.local_stencil.pixfmt)) { | |
| this.opaque = false; | |
| } | |
| } | |
| this.no_draw = false; | |
| return 1; | |
| }, | |
| fullscreen: function() | |
| { | |
| this.no_draw = false; | |
| if (this.opaque && this.can_reuse) { | |
| if (this.pids.length>1) { | |
| if (this.mix_ratio == 0) { | |
| this.no_draw = true; | |
| return 0; | |
| } | |
| else if (this.mix_ratio == 1) { | |
| this.no_draw = true; | |
| return 1; | |
| } | |
| return -1; | |
| } else { | |
| this.no_draw = true; | |
| return 0; | |
| } | |
| } | |
| return -1; | |
| }, | |
| identity: function() { | |
| if (this.no_draw && !this.outline) | |
| return true; | |
| return false; | |
| }, | |
| is_opaque: function() { | |
| return this.blit_path ? false : this.opaque; | |
| }, | |
| draw: function(canvas) | |
| { | |
| if (!this.no_draw || this.force_draw) { | |
| let blit_rect = this.screen_rect; | |
| let blit_tx = this.pids.length ? this.pids[0].texture : null; | |
| let do_blit = (blit_rect && this.use_blit && !use_gpu) ? true : false;; | |
| if (do_blit) { | |
| //if 2 inputs, only blit if one or the other | |
| if ((this.pids.length==2)) { | |
| if (this.mix_ratio == 0) { | |
| } else if (this.mix_ratio == 1) { | |
| blit_tx = this.pids[1].texture; | |
| } else { | |
| do_blit = false; | |
| } | |
| } | |
| //if more than 2 inputs, cannot blit | |
| else if (this.pids.length>2) { | |
| do_blit = false; | |
| } | |
| if (do_blit && sys.test_mode) { | |
| if (this.screen_rect.w != blit_tx.width) do_blit = false; | |
| if (this.screen_rect.h != blit_tx.height) do_blit = false; | |
| } | |
| } | |
| if (do_blit && this.blit_path) { | |
| blit_rect = get_screen_rect(this.blit_path); | |
| if (!blit_rect) do_blit = false; | |
| } | |
| if (do_blit) { | |
| try { | |
| canvas_blit(blit_tx, blit_rect); | |
| } catch (e) { | |
| this.use_blit = false; | |
| do_blit = false; | |
| } | |
| } | |
| if (!do_blit) { | |
| let tx = null; | |
| if (!this.pids.length || this.replace_op_img) { | |
| tx = this.local_stencil; | |
| //img points to a seq, update texture (since sequence might have multiple sources) | |
| if (this.local_stencil_type==2) { | |
| tx = get_sequence_texture(this.img); | |
| } | |
| if (canvas_yuv && this.is_texture && !use_gpu && !this.local_stencil_type) { | |
| if (this.yuv_cache_texture == null) { | |
| this.yuv_cache_texture = this.local_stencil.rgb2yuv(canvas); | |
| } | |
| tx = this.yuv_cache_texture; | |
| } | |
| } | |
| if (this.replace_op && !this.replace_op_img) { | |
| tx = this.local_stencil; | |
| } | |
| let skip_op = false; | |
| //update texture params | |
| this.pids.forEach( pid_link => { | |
| set_texture_params(pid_link.texture, pid_link.tx_info); | |
| if (pid_link.texture == tx) skip_op = true; | |
| }); | |
| if (tx && this.replace_tx_info && !skip_op) { | |
| set_texture_params(tx, this.replace_tx_info); | |
| } | |
| if (!this.pids.length) { | |
| if (tx) | |
| canvas_draw(this.path, tx); | |
| } else if (this.replace_op && tx) { | |
| let replace_par = this.replace_par; | |
| if (this.use_mix) | |
| replace_par = this.mix_ratio; | |
| canvas_draw_sources(this.path, this.replace_op, replace_par, tx); | |
| } else { | |
| canvas_draw_sources(this.path); | |
| } | |
| } | |
| } | |
| this.no_draw = false; | |
| if (this.outline) { | |
| canvas_draw(this.outline, this.outline_brush); | |
| } | |
| }, | |
| point_over: function(pt_x, pt_y) | |
| { | |
| if (this.blit_path) | |
| return this.blit_path.point_over(pt_x, pt_y); | |
| return this.path.point_over(pt_x, pt_y); | |
| }, | |
| }; } |