mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-08 09:00:05 +00:00
WebGL support
Subgraph support FX nodes moved from gltexture to glfx Improves in gltexture
This commit is contained in:
363
src/litegraph.js
363
src/litegraph.js
@@ -19,6 +19,7 @@ var LiteGraph = {
|
||||
NODE_COLLAPSED_RADIUS: 10,
|
||||
NODE_COLLAPSED_WIDTH: 80,
|
||||
CANVAS_GRID_SIZE: 10,
|
||||
NODE_TITLE_COLOR: "#222",
|
||||
NODE_DEFAULT_COLOR: "#999",
|
||||
NODE_DEFAULT_BGCOLOR: "#444",
|
||||
NODE_DEFAULT_BOXCOLOR: "#AEF",
|
||||
@@ -27,6 +28,8 @@ var LiteGraph = {
|
||||
DEFAULT_POSITION: [100,100],//default node position
|
||||
node_images_path: "",
|
||||
|
||||
proxy: null, //used to redirect calls
|
||||
|
||||
debug: false,
|
||||
throw_errors: true,
|
||||
registered_node_types: {},
|
||||
@@ -88,6 +91,7 @@ var LiteGraph = {
|
||||
|
||||
node.type = type;
|
||||
if(!node.title) node.title = title;
|
||||
if(!node.properties) node.properties = {};
|
||||
if(!node.flags) node.flags = {};
|
||||
if(!node.size) node.size = node.computeSize();
|
||||
if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();
|
||||
@@ -793,8 +797,11 @@ LGraph.prototype.getGlobalInputData = function(name)
|
||||
}
|
||||
|
||||
//rename the global input
|
||||
LGraph.prototype.renameGlobalInput = function(old_name, name, data)
|
||||
LGraph.prototype.renameGlobalInput = function(old_name, name)
|
||||
{
|
||||
if(name == old_name)
|
||||
return;
|
||||
|
||||
if(!this.global_inputs[old_name])
|
||||
return false;
|
||||
|
||||
@@ -814,6 +821,19 @@ LGraph.prototype.renameGlobalInput = function(old_name, name, data)
|
||||
this.onGlobalsChange();
|
||||
}
|
||||
|
||||
LGraph.prototype.changeGlobalInputType = function(name, type)
|
||||
{
|
||||
if(!this.global_inputs[name])
|
||||
return false;
|
||||
|
||||
if(this.global_inputs[name].type == type)
|
||||
return;
|
||||
|
||||
this.global_inputs[name].type = type;
|
||||
if(this.onGlobalInputTypeChanged)
|
||||
this.onGlobalInputTypeChanged(name, type);
|
||||
}
|
||||
|
||||
LGraph.prototype.removeGlobalInput = function(name)
|
||||
{
|
||||
if(!this.global_inputs[name])
|
||||
@@ -861,7 +881,7 @@ LGraph.prototype.getGlobalOutputData = function(name)
|
||||
|
||||
|
||||
//rename the global output
|
||||
LGraph.prototype.renameGlobalOutput = function(old_name, name, data)
|
||||
LGraph.prototype.renameGlobalOutput = function(old_name, name)
|
||||
{
|
||||
if(!this.global_outputs[old_name])
|
||||
return false;
|
||||
@@ -882,6 +902,19 @@ LGraph.prototype.renameGlobalOutput = function(old_name, name, data)
|
||||
this.onGlobalsChange();
|
||||
}
|
||||
|
||||
LGraph.prototype.changeGlobalOutputType = function(name, type)
|
||||
{
|
||||
if(!this.global_outputs[name])
|
||||
return false;
|
||||
|
||||
if(this.global_outputs[name].type == type)
|
||||
return;
|
||||
|
||||
this.global_outputs[name].type = type;
|
||||
if(this.onGlobalOutputTypeChanged)
|
||||
this.onGlobalOutputTypeChanged(name, type);
|
||||
}
|
||||
|
||||
LGraph.prototype.removeGlobalOutput = function(name)
|
||||
{
|
||||
if(!this.global_outputs[name])
|
||||
@@ -1091,6 +1124,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color)
|
||||
+ onSerialize
|
||||
+ onSelected
|
||||
+ onDeselected
|
||||
+ onDropFile
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -1100,6 +1134,11 @@ LGraph.prototype.onNodeTrace = function(node, msg, color)
|
||||
*/
|
||||
|
||||
function LGraphNode(title)
|
||||
{
|
||||
this._ctor();
|
||||
}
|
||||
|
||||
LGraphNode.prototype._ctor = function( title )
|
||||
{
|
||||
this.title = title || "Unnamed";
|
||||
this.size = [LiteGraph.NODE_WIDTH,60];
|
||||
@@ -1115,6 +1154,7 @@ function LGraphNode(title)
|
||||
this.connections = [];
|
||||
|
||||
//local data
|
||||
this.properties = {};
|
||||
this.data = null; //persistent local data
|
||||
this.flags = {
|
||||
//skip_title_render: true,
|
||||
@@ -1132,6 +1172,14 @@ LGraphNode.prototype.configure = function(info)
|
||||
{
|
||||
if(j == "console") continue;
|
||||
|
||||
if(j == "properties")
|
||||
{
|
||||
//i dont want to clone properties, I want to reuse the old container
|
||||
for(var k in info.properties)
|
||||
this.properties[k] = info.properties[k];
|
||||
continue;
|
||||
}
|
||||
|
||||
if(info[j] == null)
|
||||
continue;
|
||||
else if (typeof(info[j]) == 'object') //object
|
||||
@@ -1223,46 +1271,9 @@ LGraphNode.prototype.clone = function()
|
||||
delete data["id"];
|
||||
node.configure(data);
|
||||
|
||||
/*
|
||||
node.size = this.size.concat();
|
||||
if(this.inputs)
|
||||
for(var i = 0, l = this.inputs.length; i < l; ++i)
|
||||
{
|
||||
if(node.findInputSlot( this.inputs[i].name ) == -1)
|
||||
node.addInput( this.inputs[i].name, this.inputs[i].type );
|
||||
}
|
||||
|
||||
if(this.outputs)
|
||||
for(var i = 0, l = this.outputs.length; i < l; ++i)
|
||||
{
|
||||
if(node.findOutputSlot( this.outputs[i].name ) == -1)
|
||||
node.addOutput( this.outputs[i].name, this.outputs[i].type );
|
||||
}
|
||||
*/
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
//reduced version of objectivize: NOT FINISHED
|
||||
/*
|
||||
LGraphNode.prototype.reducedObjectivize = function()
|
||||
{
|
||||
var o = this.objectivize();
|
||||
|
||||
var type = LiteGraph.getNodeType(o.type);
|
||||
|
||||
if(type.title == o.title)
|
||||
delete o["title"];
|
||||
|
||||
if(type.size && compareObjects(o.size,type.size))
|
||||
delete o["size"];
|
||||
|
||||
if(type.properties && compareObjects(o.properties, type.properties))
|
||||
delete o["properties"];
|
||||
|
||||
return o;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* serialize and stringify
|
||||
@@ -1470,7 +1481,7 @@ LGraphNode.prototype.removeOutput = function(slot)
|
||||
* @method addInput
|
||||
* @param {string} name
|
||||
* @param {string} type string defining the input type ("vec3","number",...)
|
||||
* @param {Object} extra_info this can be used to have special properties of an input (special color, position, etc)
|
||||
* @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)
|
||||
*/
|
||||
LGraphNode.prototype.addInput = function(name,type,extra_info)
|
||||
{
|
||||
@@ -1496,7 +1507,7 @@ LGraphNode.prototype.addInputs = function(array)
|
||||
for(var i in array)
|
||||
{
|
||||
var info = array[i];
|
||||
var o = {name:info[0],type:info[1],link:null};
|
||||
var o = {name:info[0], type:info[1], link:null};
|
||||
if(array[2])
|
||||
for(var j in info[2])
|
||||
o[j] = info[2][j];
|
||||
@@ -1547,7 +1558,7 @@ LGraphNode.prototype.addConnection = function(name,type,pos,direction)
|
||||
LGraphNode.prototype.computeSize = function(minHeight)
|
||||
{
|
||||
var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);
|
||||
var size = [0,0];
|
||||
var size = new Float32Array([0,0]);
|
||||
size[1] = rows * 14 + 6;
|
||||
if(!this.inputs || this.inputs.length == 0 || !this.outputs || this.outputs.length == 0)
|
||||
size[0] = LiteGraph.NODE_WIDTH * 0.5;
|
||||
@@ -1681,8 +1692,8 @@ LGraphNode.prototype.connect = function(slot, node, target_slot)
|
||||
output.links = [];
|
||||
output.links.push({id:node.id, slot: -1});
|
||||
}
|
||||
else if(output.type == 0 || //generic output
|
||||
node.inputs[target_slot].type == 0 || //generic input
|
||||
else if( !output.type || //generic output
|
||||
!node.inputs[target_slot].type || //generic input
|
||||
output.type == node.inputs[target_slot].type) //same type
|
||||
{
|
||||
//info: link structure => [ 0:link_id, 1:start_node_id, 2:start_slot, 3:end_node_id, 4:end_slot ]
|
||||
@@ -2005,7 +2016,7 @@ LGraphNode.prototype.localToScreen = function(x,y, graphcanvas)
|
||||
* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas itself)
|
||||
* @param {LGraph} graph [optional]
|
||||
*/
|
||||
function LGraphCanvas(canvas, graph)
|
||||
function LGraphCanvas(canvas, graph, skip_render)
|
||||
{
|
||||
//if(graph === undefined)
|
||||
// throw ("No graph assigned");
|
||||
@@ -2016,6 +2027,9 @@ function LGraphCanvas(canvas, graph)
|
||||
if(!canvas)
|
||||
throw("no canvas found");
|
||||
|
||||
this.max_zoom = 10;
|
||||
this.min_zoom = 0.1;
|
||||
|
||||
//link canvas and graph
|
||||
if(graph)
|
||||
graph.attachCanvas(this);
|
||||
@@ -2023,7 +2037,8 @@ function LGraphCanvas(canvas, graph)
|
||||
this.setCanvas(canvas);
|
||||
this.clear();
|
||||
|
||||
this.startRendering();
|
||||
if(!skip_render)
|
||||
this.startRendering();
|
||||
}
|
||||
|
||||
LGraphCanvas.link_type_colors = {'number':"#AAC",'node':"#DCA"};
|
||||
@@ -2173,8 +2188,8 @@ LGraphCanvas.prototype.setCanvas = function(canvas)
|
||||
|
||||
this.canvas = canvas;
|
||||
//this.canvas.tabindex = "1000";
|
||||
this.canvas.className += " lgraphcanvas";
|
||||
this.canvas.data = this;
|
||||
canvas.className += " lgraphcanvas";
|
||||
canvas.data = this;
|
||||
|
||||
//bg canvas: used for non changing stuff
|
||||
this.bgcanvas = null;
|
||||
@@ -2185,47 +2200,123 @@ LGraphCanvas.prototype.setCanvas = function(canvas)
|
||||
this.bgcanvas.height = this.canvas.height;
|
||||
}
|
||||
|
||||
if(this.canvas.getContext == null)
|
||||
if(canvas.getContext == null)
|
||||
{
|
||||
throw("This browser doesnt support Canvas");
|
||||
}
|
||||
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
this.bgctx = this.bgcanvas.getContext("2d");
|
||||
var ctx = this.ctx = canvas.getContext("2d");
|
||||
if(ctx == null)
|
||||
{
|
||||
console.warn("This canvas seems to be WebGL, enabling WebGL renderer");
|
||||
this.enableWebGL();
|
||||
}
|
||||
|
||||
//input: (move and up could be unbinded)
|
||||
this._mousemove_callback = this.processMouseMove.bind(this);
|
||||
this._mouseup_callback = this.processMouseUp.bind(this);
|
||||
|
||||
this.canvas.addEventListener("mousedown", this.processMouseDown.bind(this), true ); //down do not need to store the binded
|
||||
this.canvas.addEventListener("mousemove", this._mousemove_callback);
|
||||
canvas.addEventListener("mousedown", this.processMouseDown.bind(this), true ); //down do not need to store the binded
|
||||
canvas.addEventListener("mousemove", this._mousemove_callback);
|
||||
|
||||
this.canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; });
|
||||
canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; });
|
||||
|
||||
|
||||
this.canvas.addEventListener("mousewheel", this.processMouseWheel.bind(this), false);
|
||||
this.canvas.addEventListener("DOMMouseScroll", this.processMouseWheel.bind(this), false);
|
||||
canvas.addEventListener("mousewheel", this.processMouseWheel.bind(this), false);
|
||||
canvas.addEventListener("DOMMouseScroll", this.processMouseWheel.bind(this), false);
|
||||
|
||||
//touch events
|
||||
//if( 'touchstart' in document.documentElement )
|
||||
{
|
||||
//alert("doo");
|
||||
this.canvas.addEventListener("touchstart", this.touchHandler, true);
|
||||
this.canvas.addEventListener("touchmove", this.touchHandler, true);
|
||||
this.canvas.addEventListener("touchend", this.touchHandler, true);
|
||||
this.canvas.addEventListener("touchcancel", this.touchHandler, true);
|
||||
canvas.addEventListener("touchstart", this.touchHandler, true);
|
||||
canvas.addEventListener("touchmove", this.touchHandler, true);
|
||||
canvas.addEventListener("touchend", this.touchHandler, true);
|
||||
canvas.addEventListener("touchcancel", this.touchHandler, true);
|
||||
}
|
||||
|
||||
//this.canvas.onselectstart = function () { return false; };
|
||||
this.canvas.addEventListener("keydown", function(e) {
|
||||
canvas.addEventListener("keydown", function(e) {
|
||||
that.processKeyDown(e);
|
||||
});
|
||||
|
||||
this.canvas.addEventListener("keyup", function(e) {
|
||||
canvas.addEventListener("keyup", function(e) {
|
||||
that.processKeyUp(e);
|
||||
});
|
||||
|
||||
//droping files
|
||||
canvas.ondragover = function () { console.log('hover'); return false; };
|
||||
canvas.ondragend = function () { console.log('out'); return false; };
|
||||
canvas.ondrop = function (e) {
|
||||
e.preventDefault();
|
||||
that.adjustMouseEvent(e);
|
||||
|
||||
var pos = [e.canvasX,e.canvasY];
|
||||
var node = that.graph.getNodeOnPos(pos[0],pos[1]);
|
||||
if(!node)
|
||||
return;
|
||||
|
||||
if(!node.onDropFile)
|
||||
return;
|
||||
|
||||
var file = e.dataTransfer.files[0];
|
||||
var filename = file.name;
|
||||
var ext = LGraphCanvas.getFileExtension( filename );
|
||||
//console.log(file);
|
||||
|
||||
//prepare reader
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
//console.log(event.target);
|
||||
var data = event.target.result;
|
||||
node.onDropFile( data, filename, file );
|
||||
};
|
||||
|
||||
//read data
|
||||
var type = file.type.split("/")[0];
|
||||
if(type == "text")
|
||||
reader.readAsText(file);
|
||||
else if (type == "image")
|
||||
reader.readAsDataURL(file);
|
||||
else
|
||||
reader.readAsArrayBuffer(file);
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
LGraphCanvas.getFileExtension = function (url)
|
||||
{
|
||||
var question = url.indexOf("?");
|
||||
if(question != -1)
|
||||
url = url.substr(0,question);
|
||||
var point = url.lastIndexOf(".");
|
||||
if(point == -1)
|
||||
return "";
|
||||
return url.substr(point+1).toLowerCase();
|
||||
}
|
||||
|
||||
//this file allows to render the canvas using WebGL instead of Canvas2D
|
||||
//this is useful if you plant to render 3D objects inside your nodes
|
||||
LGraphCanvas.prototype.enableWebGL = function()
|
||||
{
|
||||
if(typeof(GL) === undefined)
|
||||
throw("litegl.js must be included to use a WebGL canvas");
|
||||
if(typeof(enableWebGLCanvas) === undefined)
|
||||
throw("webglCanvas.js must be included to use this feature");
|
||||
|
||||
this.gl = this.ctx = enableWebGLCanvas(this.canvas);
|
||||
this.ctx.webgl = true;
|
||||
this.bgcanvas = this.canvas;
|
||||
this.bgctx = this.gl;
|
||||
|
||||
/*
|
||||
GL.create({ canvas: this.bgcanvas });
|
||||
this.bgctx = enableWebGLCanvas( this.bgcanvas );
|
||||
window.gl = this.gl;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
LGraphCanvas.prototype.UIinit = function()
|
||||
{
|
||||
@@ -2324,14 +2415,6 @@ LGraphCanvas.prototype.startRendering = function()
|
||||
if(this.is_rendering)
|
||||
window.requestAnimationFrame( renderFrame.bind(this) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
this.rendering_timer_id = setInterval( function() {
|
||||
//trace("Frame: " + new Date().getTime() );
|
||||
that.draw();
|
||||
}, 1000/50);
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2439,6 +2522,13 @@ LGraphCanvas.prototype.processMouseDown = function(e)
|
||||
}
|
||||
}
|
||||
|
||||
//Search for corner
|
||||
if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, n.pos[0], n.pos[1] - LiteGraph.NODE_TITLE_HEIGHT ,LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ))
|
||||
{
|
||||
n.collapse();
|
||||
skip_action = true;
|
||||
}
|
||||
|
||||
//it wasnt clicked on the links boxes
|
||||
if(!skip_action)
|
||||
{
|
||||
@@ -2580,7 +2670,7 @@ LGraphCanvas.prototype.processMouseMove = function(e)
|
||||
if(slot != -1 && n.inputs[slot])
|
||||
{
|
||||
var slot_type = n.inputs[slot].type;
|
||||
if(slot_type == this.connecting_output.type || slot_type == "*" || this.connecting_output.type == "*")
|
||||
if(slot_type == this.connecting_output.type || !slot_type || !this.connecting_output.type )
|
||||
this._highlight_input = pos;
|
||||
}
|
||||
else
|
||||
@@ -2692,6 +2782,13 @@ LGraphCanvas.prototype.processMouseUp = function(e)
|
||||
{
|
||||
this.connecting_node.connect(this.connecting_slot, node, slot);
|
||||
}
|
||||
else
|
||||
{ //not on top of an input
|
||||
var input = node.getInputInfo(0);
|
||||
//simple connect
|
||||
if(input && !input.link && input.type == this.connecting_output.type)
|
||||
this.connecting_node.connect(this.connecting_slot, node, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2974,10 +3071,10 @@ LGraphCanvas.prototype.setZoom = function(value, zooming_center)
|
||||
|
||||
this.scale = value;
|
||||
|
||||
if(this.scale > 4)
|
||||
this.scale = 4;
|
||||
else if(this.scale < 0.1)
|
||||
this.scale = 0.1;
|
||||
if(this.scale > this.max_zoom)
|
||||
this.scale = this.max_zoom;
|
||||
else if(this.scale < this.min_zoom)
|
||||
this.scale = this.min_zoom;
|
||||
|
||||
var new_center = this.convertOffsetToCanvas( zooming_center );
|
||||
var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];
|
||||
@@ -3064,7 +3161,7 @@ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)
|
||||
}
|
||||
|
||||
if(this.dirty_bgcanvas || force_bgcanvas)
|
||||
this.drawBgcanvas();
|
||||
this.drawBackCanvas();
|
||||
|
||||
if(this.dirty_canvas || force_canvas)
|
||||
this.drawFrontCanvas();
|
||||
@@ -3075,7 +3172,15 @@ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)
|
||||
|
||||
LGraphCanvas.prototype.drawFrontCanvas = function()
|
||||
{
|
||||
if(!this.ctx)
|
||||
this.ctx = this.bgcanvas.getContext("2d");
|
||||
var ctx = this.ctx;
|
||||
if(!ctx) //maybe is using webgl...
|
||||
return;
|
||||
|
||||
if(ctx.start)
|
||||
ctx.start();
|
||||
|
||||
var canvas = this.canvas;
|
||||
|
||||
//reset in case of error
|
||||
@@ -3096,7 +3201,14 @@ LGraphCanvas.prototype.drawFrontCanvas = function()
|
||||
ctx.clearRect(0,0,canvas.width, canvas.height);
|
||||
|
||||
//draw bg canvas
|
||||
ctx.drawImage(this.bgcanvas,0,0);
|
||||
if(this.bgcanvas == this.canvas)
|
||||
this.drawBackCanvas();
|
||||
else
|
||||
ctx.drawImage(this.bgcanvas,0,0);
|
||||
|
||||
//rendering
|
||||
if(this.onRender)
|
||||
this.onRender(canvas, ctx);
|
||||
|
||||
//info widget
|
||||
if(this.show_info)
|
||||
@@ -3181,17 +3293,23 @@ LGraphCanvas.prototype.drawFrontCanvas = function()
|
||||
//this.dirty_area = null;
|
||||
}
|
||||
|
||||
if(ctx.finish) //this is a function I use in webgl renderer
|
||||
ctx.finish();
|
||||
|
||||
this.dirty_canvas = false;
|
||||
}
|
||||
|
||||
LGraphCanvas.prototype.drawBgcanvas = function()
|
||||
LGraphCanvas.prototype.drawBackCanvas = function()
|
||||
{
|
||||
var canvas = this.bgcanvas;
|
||||
if(!this.bgctx)
|
||||
this.bgctx = this.bgcanvas.getContext("2d");
|
||||
var ctx = this.bgctx;
|
||||
|
||||
if(ctx.start)
|
||||
ctx.start();
|
||||
|
||||
//clear
|
||||
canvas.width = canvas.width;
|
||||
ctx.clearRect(0,0,canvas.width, canvas.height);
|
||||
|
||||
//reset in case of error
|
||||
ctx.restore();
|
||||
@@ -3208,7 +3326,7 @@ LGraphCanvas.prototype.drawBgcanvas = function()
|
||||
if(this.background_image && this.scale > 0.5)
|
||||
{
|
||||
ctx.globalAlpha = (1.0 - 0.5 / this.scale) * this.editor_alpha;
|
||||
ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false
|
||||
ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false;
|
||||
if(!this._bg_img || this._bg_img.name != this.background_image)
|
||||
{
|
||||
this._bg_img = new Image();
|
||||
@@ -3237,8 +3355,12 @@ LGraphCanvas.prototype.drawBgcanvas = function()
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1.0;
|
||||
ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true;
|
||||
}
|
||||
|
||||
if(this.onBackgroundRender)
|
||||
this.onBackgroundRender(canvas, ctx);
|
||||
|
||||
//DEBUG: show clipping area
|
||||
//ctx.fillStyle = "red";
|
||||
//ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - this.visible_area[0] - 20, this.visible_area[3] - this.visible_area[1] - 20);
|
||||
@@ -3267,6 +3389,9 @@ LGraphCanvas.prototype.drawBgcanvas = function()
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if(ctx.finish)
|
||||
ctx.finish();
|
||||
|
||||
this.dirty_bgcanvas = false;
|
||||
this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas
|
||||
}
|
||||
@@ -3339,7 +3464,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx )
|
||||
var shape = node.shape || "box";
|
||||
var size = new Float32Array(node.size);
|
||||
if(node.flags.collapsed)
|
||||
size.set([LiteGraph.NODE_COLLAPSED_WIDTH, 0]);
|
||||
{
|
||||
size[0] = LiteGraph.NODE_COLLAPSED_WIDTH;
|
||||
size[1] = 0;
|
||||
}
|
||||
|
||||
//Start clipping
|
||||
if(node.flags.clip_area)
|
||||
@@ -3492,27 +3620,30 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo
|
||||
var shape = node.shape || "box";
|
||||
if(shape == "box")
|
||||
{
|
||||
ctx.beginPath();
|
||||
ctx.rect(0,no_title ? 0 : -title_height, size[0]+1, no_title ? size[1] : size[1] + title_height);
|
||||
ctx.fill();
|
||||
ctx.shadowColor = "transparent";
|
||||
|
||||
if(selected)
|
||||
{
|
||||
ctx.strokeStyle = "#CCC";
|
||||
ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5, size[0]+2, no_title ? (size[1]+2) : (size[1] + title_height+2) );
|
||||
ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5, size[0]+2, no_title ? (size[1]+2) : (size[1] + title_height+2) - 1);
|
||||
ctx.strokeStyle = fgcolor;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.rect(0,no_title ? 0.5 : -title_height + 0.5,size[0]+1, no_title ? size[1] : size[1] + title_height);
|
||||
}
|
||||
else if (node.shape == "round")
|
||||
{
|
||||
ctx.roundRect(0,no_title ? 0 : -title_height,size[0], no_title ? size[1] : size[1] + title_height, 10);
|
||||
ctx.fill();
|
||||
}
|
||||
else if (node.shape == "circle")
|
||||
{
|
||||
ctx.beginPath();
|
||||
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
ctx.shadowColor = "transparent";
|
||||
|
||||
//ctx.stroke();
|
||||
@@ -3536,15 +3667,16 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo
|
||||
if(shape == "box")
|
||||
{
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(0,-title_height,size[0]+1,title_height);
|
||||
ctx.stroke();
|
||||
ctx.rect(0, -title_height, size[0]+1, title_height);
|
||||
ctx.fill()
|
||||
//ctx.stroke();
|
||||
}
|
||||
else if (shape == "round")
|
||||
{
|
||||
ctx.roundRect(0,-title_height,size[0], title_height,10,0);
|
||||
//ctx.fillRect(0,8,size[0],NODE_TITLE_HEIGHT - 12);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
//ctx.stroke();
|
||||
}
|
||||
|
||||
//box
|
||||
@@ -3560,9 +3692,9 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo
|
||||
//title text
|
||||
ctx.font = this.title_text_font;
|
||||
var title = node.getTitle();
|
||||
if(title && this.scale > 0.8)
|
||||
if(title && this.scale > 0.5)
|
||||
{
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillStyle = LiteGraph.NODE_TITLE_COLOR;
|
||||
ctx.fillText( title, 16, 13 - title_height );
|
||||
}
|
||||
}
|
||||
@@ -4066,15 +4198,18 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function()
|
||||
//{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll }
|
||||
];
|
||||
|
||||
if(this._graph_stack)
|
||||
if(this._graph_stack && this._graph_stack.length > 0)
|
||||
options = [{content:"Close subgraph", callback: this.closeSubgraph.bind(this) },null].concat(options);
|
||||
}
|
||||
|
||||
if(this.getExtraMenuOptions)
|
||||
{
|
||||
var extra = this.getExtraMenuOptions(this);
|
||||
extra.push(null);
|
||||
options = extra.concat( options );
|
||||
if(extra)
|
||||
{
|
||||
extra.push(null);
|
||||
options = extra.concat( options );
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
@@ -4101,8 +4236,11 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node)
|
||||
if(node.getExtraMenuOptions)
|
||||
{
|
||||
var extra = node.getExtraMenuOptions(this);
|
||||
extra.push(null);
|
||||
options = extra.concat( options );
|
||||
if(extra)
|
||||
{
|
||||
extra.push(null);
|
||||
options = extra.concat( options );
|
||||
}
|
||||
}
|
||||
|
||||
if( node.clonable !== false )
|
||||
@@ -4426,14 +4564,35 @@ LiteGraph.closeAllContextualMenus = function()
|
||||
result[i].parentNode.removeChild( result[i] );
|
||||
}
|
||||
|
||||
LiteGraph.extendClass = function(origin, target)
|
||||
LiteGraph.extendClass = function ( target, origin )
|
||||
{
|
||||
for(var i in origin) //copy class properties
|
||||
{
|
||||
if(target.hasOwnProperty(i))
|
||||
continue;
|
||||
target[i] = origin[i];
|
||||
}
|
||||
|
||||
if(origin.prototype) //copy prototype properties
|
||||
for(var i in origin.prototype)
|
||||
target.prototype[i] = origin.prototype[i];
|
||||
}
|
||||
for(var i in origin.prototype) //only enumerables
|
||||
{
|
||||
if(!origin.prototype.hasOwnProperty(i))
|
||||
continue;
|
||||
|
||||
if(target.prototype.hasOwnProperty(i)) //avoid overwritting existing ones
|
||||
continue;
|
||||
|
||||
//copy getters
|
||||
if(origin.prototype.__lookupGetter__(i))
|
||||
target.prototype.__defineGetter__(i, origin.prototype.__lookupGetter__(i));
|
||||
else
|
||||
target.prototype[i] = origin.prototype[i];
|
||||
|
||||
//and setters
|
||||
if(origin.prototype.__lookupSetter__(i))
|
||||
target.prototype.__defineSetter__(i, origin.prototype.__lookupSetter__(i));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
LiteGraph.createNodetypeWrapper = function( class_object )
|
||||
|
||||
@@ -2,78 +2,27 @@
|
||||
(function(){
|
||||
|
||||
|
||||
//Input for a subgraph
|
||||
function GlobalInput()
|
||||
{
|
||||
this.title = "Input";
|
||||
|
||||
//random name to avoid problems with other outputs when added
|
||||
var genname = "input_" + (Math.random()*1000).toFixed();
|
||||
this.properties = { name: genname, type: "number" };
|
||||
this.addOutput("value",0);
|
||||
}
|
||||
|
||||
GlobalInput.title = "Input";
|
||||
GlobalInput.desc = "Input of the graph";
|
||||
|
||||
GlobalInput.prototype.onAdded = function()
|
||||
{
|
||||
this.graph.addGlobalInput( this.properties.name, this.properties.type );
|
||||
}
|
||||
|
||||
GlobalInput.prototype.onExecute = function()
|
||||
{
|
||||
var name = this.properties.name;
|
||||
|
||||
//read from global input
|
||||
var data = this.graph.global_inputs[name];
|
||||
if(!data) return;
|
||||
|
||||
//put through output
|
||||
this.setOutputData(0,data.value);
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("graph/input", GlobalInput);
|
||||
|
||||
|
||||
//Output for a subgraph
|
||||
function GlobalOutput()
|
||||
{
|
||||
this.title = "Output";
|
||||
|
||||
//random name to avoid problems with other outputs when added
|
||||
var genname = "output_" + (Math.random()*1000).toFixed();
|
||||
this.properties = { name: genname, type: "number" };
|
||||
this.addInput("value","number");
|
||||
}
|
||||
|
||||
GlobalOutput.title = "Ouput";
|
||||
GlobalOutput.desc = "Output of the graph";
|
||||
|
||||
GlobalOutput.prototype.onAdded = function()
|
||||
{
|
||||
var name = this.graph.addGlobalOutput( this.properties.name, this.properties.type );
|
||||
}
|
||||
|
||||
GlobalOutput.prototype.onExecute = function()
|
||||
{
|
||||
this.graph.setGlobalOutputData( this.properties.name, this.getInputData(0) );
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("graph/output", GlobalOutput);
|
||||
|
||||
|
||||
//Subgraph: a node that contains a graph
|
||||
function Subgraph()
|
||||
{
|
||||
var that = this;
|
||||
this.size = [120,60];
|
||||
|
||||
//create inner graph
|
||||
this.subgraph = new LGraph();
|
||||
this.subgraph._subgraph_node = this;
|
||||
this.subgraph._is_subgraph = true;
|
||||
this.subgraph.onGlobalInputAdded = this.onSubgraphNewGlobalInput.bind(this);
|
||||
this.subgraph.onGlobalOutputAdded = this.onSubgraphNewGlobalOutput.bind(this);
|
||||
|
||||
this.bgcolor = "#FA3";
|
||||
this.subgraph.onGlobalInputAdded = this.onSubgraphNewGlobalInput.bind(this);
|
||||
this.subgraph.onGlobalInputRenamed = this.onSubgraphRenamedGlobalInput.bind(this);
|
||||
this.subgraph.onGlobalInputTypeChanged = this.onSubgraphTypeChangeGlobalInput.bind(this);
|
||||
|
||||
this.subgraph.onGlobalOutputAdded = this.onSubgraphNewGlobalOutput.bind(this);
|
||||
this.subgraph.onGlobalOutputRenamed = this.onSubgraphRenamedGlobalOutput.bind(this);
|
||||
this.subgraph.onGlobalOutputTypeChanged = this.onSubgraphTypeChangeGlobalOutput.bind(this);
|
||||
|
||||
|
||||
this.bgcolor = "#940";
|
||||
}
|
||||
|
||||
Subgraph.title = "Subgraph";
|
||||
@@ -81,14 +30,55 @@ Subgraph.desc = "Graph inside a node";
|
||||
|
||||
Subgraph.prototype.onSubgraphNewGlobalInput = function(name, type)
|
||||
{
|
||||
//add input to the node
|
||||
this.addInput(name, type);
|
||||
}
|
||||
|
||||
Subgraph.prototype.onSubgraphRenamedGlobalInput = function(oldname, name)
|
||||
{
|
||||
var slot = this.findInputSlot( oldname );
|
||||
if(slot == -1)
|
||||
return;
|
||||
var info = this.getInputInfo(slot);
|
||||
info.name = name;
|
||||
}
|
||||
|
||||
Subgraph.prototype.onSubgraphTypeChangeGlobalInput = function(name, type)
|
||||
{
|
||||
var slot = this.findInputSlot( name );
|
||||
if(slot == -1)
|
||||
return;
|
||||
var info = this.getInputInfo(slot);
|
||||
info.type = type;
|
||||
}
|
||||
|
||||
|
||||
Subgraph.prototype.onSubgraphNewGlobalOutput = function(name, type)
|
||||
{
|
||||
//add output to the node
|
||||
this.addOutput(name, type);
|
||||
}
|
||||
|
||||
|
||||
Subgraph.prototype.onSubgraphRenamedGlobalOutput = function(oldname, name)
|
||||
{
|
||||
var slot = this.findOutputSlot( oldname );
|
||||
if(slot == -1)
|
||||
return;
|
||||
var info = this.getOutputInfo(slot);
|
||||
info.name = name;
|
||||
}
|
||||
|
||||
Subgraph.prototype.onSubgraphTypeChangeGlobalOutput = function(name, type)
|
||||
{
|
||||
var slot = this.findOutputSlot( name );
|
||||
if(slot == -1)
|
||||
return;
|
||||
var info = this.getOutputInfo(slot);
|
||||
info.type = type;
|
||||
}
|
||||
|
||||
|
||||
Subgraph.prototype.getExtraMenuOptions = function(graphcanvas)
|
||||
{
|
||||
var that = this;
|
||||
@@ -136,10 +126,147 @@ Subgraph.prototype.serialize = function()
|
||||
return data;
|
||||
}
|
||||
|
||||
Subgraph.prototype.clone = function()
|
||||
{
|
||||
var node = LiteGraph.createNode(this.type);
|
||||
var data = this.serialize();
|
||||
delete data["id"];
|
||||
delete data["inputs"];
|
||||
delete data["outputs"];
|
||||
node.configure(data);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
LiteGraph.registerNodeType("graph/subgraph", Subgraph);
|
||||
|
||||
|
||||
//Input for a subgraph
|
||||
function GlobalInput()
|
||||
{
|
||||
|
||||
//random name to avoid problems with other outputs when added
|
||||
var input_name = "input_" + (Math.random()*1000).toFixed();
|
||||
|
||||
this.addOutput(input_name, null );
|
||||
|
||||
this.properties = {name: input_name, type: null };
|
||||
|
||||
var that = this;
|
||||
|
||||
Object.defineProperty(this.properties, "name", {
|
||||
get: function() {
|
||||
return input_name;
|
||||
},
|
||||
set: function(v) {
|
||||
if(v == "")
|
||||
return;
|
||||
|
||||
var info = that.getOutputInfo(0);
|
||||
if(info.name == v)
|
||||
return;
|
||||
info.name = v;
|
||||
if(that.graph)
|
||||
that.graph.renameGlobalInput(input_name, v);
|
||||
input_name = v;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(this.properties, "type", {
|
||||
get: function() { return that.outputs[0].type; },
|
||||
set: function(v) {
|
||||
that.outputs[0].type = v;
|
||||
if(that.graph)
|
||||
that.graph.changeGlobalInputType(input_name, that.outputs[0].type);
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
|
||||
GlobalInput.title = "Input";
|
||||
GlobalInput.desc = "Input of the graph";
|
||||
|
||||
//When added to graph tell the graph this is a new global input
|
||||
GlobalInput.prototype.onAdded = function()
|
||||
{
|
||||
this.graph.addGlobalInput( this.properties.name, this.properties.type );
|
||||
}
|
||||
|
||||
GlobalInput.prototype.onExecute = function()
|
||||
{
|
||||
var name = this.properties.name;
|
||||
|
||||
//read from global input
|
||||
var data = this.graph.global_inputs[name];
|
||||
if(!data) return;
|
||||
|
||||
//put through output
|
||||
this.setOutputData(0,data.value);
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("graph/input", GlobalInput);
|
||||
|
||||
|
||||
|
||||
//Output for a subgraph
|
||||
function GlobalOutput()
|
||||
{
|
||||
//random name to avoid problems with other outputs when added
|
||||
var output_name = "output_" + (Math.random()*1000).toFixed();
|
||||
|
||||
this.addInput(output_name, null);
|
||||
|
||||
this.properties = {name: output_name, type: null };
|
||||
|
||||
var that = this;
|
||||
|
||||
Object.defineProperty(this.properties, "name", {
|
||||
get: function() {
|
||||
return output_name;
|
||||
},
|
||||
set: function(v) {
|
||||
if(v == "")
|
||||
return;
|
||||
|
||||
var info = that.getInputInfo(0);
|
||||
if(info.name == v)
|
||||
return;
|
||||
info.name = v;
|
||||
if(that.graph)
|
||||
that.graph.renameGlobalOutput(output_name, v);
|
||||
output_name = v;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(this.properties, "type", {
|
||||
get: function() { return that.inputs[0].type; },
|
||||
set: function(v) {
|
||||
that.inputs[0].type = v;
|
||||
if(that.graph)
|
||||
that.graph.changeGlobalInputType( output_name, that.inputs[0].type );
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
|
||||
GlobalOutput.title = "Ouput";
|
||||
GlobalOutput.desc = "Output of the graph";
|
||||
|
||||
GlobalOutput.prototype.onAdded = function()
|
||||
{
|
||||
var name = this.graph.addGlobalOutput( this.properties.name, this.properties.type );
|
||||
}
|
||||
|
||||
GlobalOutput.prototype.onExecute = function()
|
||||
{
|
||||
this.graph.setGlobalOutputData( this.properties.name, this.getInputData(0) );
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("graph/output", GlobalOutput);
|
||||
|
||||
|
||||
|
||||
//Constant
|
||||
function Constant()
|
||||
|
||||
538
src/nodes/glfx.js
Normal file
538
src/nodes/glfx.js
Normal file
@@ -0,0 +1,538 @@
|
||||
//Works with Litegl.js to create WebGL nodes
|
||||
if(typeof(LiteGraph) != "undefined")
|
||||
{
|
||||
|
||||
// Texture Lens *****************************************
|
||||
function LGraphFXLens()
|
||||
{
|
||||
this.addInput("Texture","Texture");
|
||||
this.addInput("Aberration","number");
|
||||
this.addInput("Distortion","number");
|
||||
this.addInput("Blur","number");
|
||||
this.addOutput("Texture","Texture");
|
||||
this.properties = { aberration:1.0, distortion: 1.0, blur: 1.0, precision: LGraphTexture.DEFAULT };
|
||||
|
||||
if(!LGraphFXLens._shader)
|
||||
LGraphFXLens._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphFXLens.pixel_shader );
|
||||
}
|
||||
|
||||
LGraphFXLens.title = "Lens";
|
||||
LGraphFXLens.desc = "Camera Lens distortion";
|
||||
LGraphFXLens.widgets_info = {
|
||||
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
|
||||
};
|
||||
|
||||
LGraphFXLens.prototype.onExecute = function()
|
||||
{
|
||||
var tex = this.getInputData(0);
|
||||
if(this.properties.precision === LGraphTexture.PASS_THROUGH )
|
||||
{
|
||||
this.setOutputData(0,tex);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tex) return;
|
||||
|
||||
this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
|
||||
|
||||
//iterations
|
||||
var aberration = this.properties.aberration;
|
||||
if( this.isInputConnected(1) )
|
||||
{
|
||||
aberration = this.getInputData(1);
|
||||
this.properties.aberration = aberration;
|
||||
}
|
||||
|
||||
var distortion = this.properties.distortion;
|
||||
if( this.isInputConnected(2) )
|
||||
{
|
||||
distortion = this.getInputData(2);
|
||||
this.properties.distortion = distortion;
|
||||
}
|
||||
|
||||
var blur = this.properties.blur;
|
||||
if( this.isInputConnected(3) )
|
||||
{
|
||||
blur = this.getInputData(3);
|
||||
this.properties.blur = blur;
|
||||
}
|
||||
|
||||
gl.disable( gl.BLEND );
|
||||
gl.disable( gl.DEPTH_TEST );
|
||||
var mesh = Mesh.getScreenQuad();
|
||||
var shader = LGraphFXLens._shader;
|
||||
var camera = Renderer._current_camera;
|
||||
|
||||
this._tex.drawTo( function() {
|
||||
tex.bind(0);
|
||||
shader.uniforms({u_texture:0, u_aberration: aberration, u_distortion: distortion, u_blur: blur })
|
||||
.draw(mesh);
|
||||
});
|
||||
|
||||
this.setOutputData(0, this._tex);
|
||||
}
|
||||
|
||||
LGraphFXLens.pixel_shader = "precision highp float;\n\
|
||||
precision highp float;\n\
|
||||
varying vec2 v_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform vec2 u_camera_planes;\n\
|
||||
uniform float u_aberration;\n\
|
||||
uniform float u_distortion;\n\
|
||||
uniform float u_blur;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
vec2 coord = v_coord;\n\
|
||||
float dist = distance(vec2(0.5), coord);\n\
|
||||
vec2 dist_coord = coord - vec2(0.5);\n\
|
||||
float percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\n\
|
||||
dist_coord *= percent;\n\
|
||||
coord = dist_coord + vec2(0.5);\n\
|
||||
vec4 color = texture2D(u_texture,coord, u_blur * dist);\n\
|
||||
color.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\n\
|
||||
color.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\n\
|
||||
gl_FragColor = color;\n\
|
||||
}\n\
|
||||
";
|
||||
/*
|
||||
float normalized_tunable_sigmoid(float xs, float k)\n\
|
||||
{\n\
|
||||
xs = xs * 2.0 - 1.0;\n\
|
||||
float signx = sign(xs);\n\
|
||||
float absx = abs(xs);\n\
|
||||
return signx * ((-k - 1.0)*absx)/(2.0*(-2.0*k*absx+k-1.0)) + 0.5;\n\
|
||||
}\n\
|
||||
*/
|
||||
|
||||
LiteGraph.registerNodeType("fx/lens", LGraphFXLens );
|
||||
window.LGraphFXLens = LGraphFXLens;
|
||||
|
||||
//*******************************************************
|
||||
|
||||
function LGraphFXBokeh()
|
||||
{
|
||||
this.addInput("Texture","Texture");
|
||||
this.addInput("Blurred","Texture");
|
||||
this.addInput("Mask","Texture");
|
||||
this.addInput("Threshold","number");
|
||||
this.addOutput("Texture","Texture");
|
||||
this.properties = { shape: "", size: 10, alpha: 1.0, threshold: 1.0, high_precision: false };
|
||||
}
|
||||
|
||||
LGraphFXBokeh.title = "Bokeh";
|
||||
LGraphFXBokeh.desc = "applies an Bokeh effect";
|
||||
|
||||
LGraphFXBokeh.widgets_info = {"shape": { widget:"texture" }};
|
||||
|
||||
LGraphFXBokeh.prototype.onExecute = function()
|
||||
{
|
||||
var tex = this.getInputData(0);
|
||||
var blurred_tex = this.getInputData(1);
|
||||
var mask_tex = this.getInputData(2);
|
||||
if(!tex || !mask_tex || !this.properties.shape)
|
||||
{
|
||||
this.setOutputData(0, tex);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!blurred_tex)
|
||||
blurred_tex = tex;
|
||||
|
||||
var shape_tex = LGraphTexture.getTexture( this.properties.shape );
|
||||
if(!shape_tex)
|
||||
return;
|
||||
|
||||
var threshold = this.properties.threshold;
|
||||
if( this.isInputConnected(3) )
|
||||
{
|
||||
threshold = this.getInputData(3);
|
||||
this.properties.threshold = threshold;
|
||||
}
|
||||
|
||||
|
||||
var precision = gl.UNSIGNED_BYTE;
|
||||
if(this.properties.high_precision)
|
||||
precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;
|
||||
if(!this._temp_texture || this._temp_texture.type != precision ||
|
||||
this._temp_texture.width != tex.width || this._temp_texture.height != tex.height)
|
||||
this._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR });
|
||||
|
||||
//iterations
|
||||
var size = this.properties.size;
|
||||
|
||||
var first_shader = LGraphFXBokeh._first_shader;
|
||||
if(!first_shader)
|
||||
first_shader = LGraphFXBokeh._first_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphFXBokeh._first_pixel_shader );
|
||||
|
||||
var second_shader = LGraphFXBokeh._second_shader;
|
||||
if(!second_shader)
|
||||
second_shader = LGraphFXBokeh._second_shader = new GL.Shader( LGraphFXBokeh._second_vertex_shader, LGraphFXBokeh._second_pixel_shader );
|
||||
|
||||
var points_mesh = this._points_mesh;
|
||||
if(!points_mesh || points_mesh._width != tex.width || points_mesh._height != tex.height || points_mesh._spacing != 2)
|
||||
points_mesh = this.createPointsMesh( tex.width, tex.height, 2 );
|
||||
|
||||
var screen_mesh = Mesh.getScreenQuad();
|
||||
|
||||
var point_size = this.properties.size;
|
||||
var min_light = this.properties.min_light;
|
||||
var alpha = this.properties.alpha;
|
||||
|
||||
gl.disable( gl.DEPTH_TEST );
|
||||
gl.disable( gl.BLEND );
|
||||
|
||||
this._temp_texture.drawTo( function() {
|
||||
tex.bind(0);
|
||||
blurred_tex.bind(1);
|
||||
mask_tex.bind(2);
|
||||
first_shader.uniforms({u_texture:0, u_texture_blur:1, u_mask: 2, u_texsize: [tex.width, tex.height] })
|
||||
.draw(screen_mesh);
|
||||
});
|
||||
|
||||
this._temp_texture.drawTo( function() {
|
||||
//clear because we use blending
|
||||
//gl.clearColor(0.0,0.0,0.0,1.0);
|
||||
//gl.clear( gl.COLOR_BUFFER_BIT );
|
||||
gl.enable( gl.BLEND );
|
||||
gl.blendFunc( gl.ONE, gl.ONE );
|
||||
|
||||
tex.bind(0);
|
||||
shape_tex.bind(3);
|
||||
second_shader.uniforms({u_texture:0, u_mask: 2, u_shape:3, u_alpha: alpha, u_threshold: threshold, u_pointSize: point_size, u_itexsize: [1.0/tex.width, 1.0/tex.height] })
|
||||
.draw(points_mesh, gl.POINTS);
|
||||
});
|
||||
|
||||
this.setOutputData(0, this._temp_texture);
|
||||
}
|
||||
|
||||
LGraphFXBokeh.prototype.createPointsMesh = function(width, height, spacing)
|
||||
{
|
||||
var nwidth = Math.round(width / spacing);
|
||||
var nheight = Math.round(height / spacing);
|
||||
|
||||
var vertices = new Float32Array(nwidth * nheight * 2);
|
||||
|
||||
var ny = -1;
|
||||
var dx = 2/width * spacing;
|
||||
var dy = 2/height * spacing;
|
||||
for(var y = 0; y < nheight; ++y )
|
||||
{
|
||||
var nx = -1;
|
||||
for(var x = 0; x < nwidth; ++x )
|
||||
{
|
||||
var pos = y*nwidth*2 + x*2;
|
||||
vertices[pos] = nx;
|
||||
vertices[pos+1] = ny;
|
||||
nx += dx;
|
||||
}
|
||||
ny += dy;
|
||||
}
|
||||
|
||||
this._points_mesh = GL.Mesh.load({vertices2D: vertices});
|
||||
this._points_mesh._width = width;
|
||||
this._points_mesh._height = height;
|
||||
this._points_mesh._spacing = spacing;
|
||||
|
||||
return this._points_mesh;
|
||||
}
|
||||
|
||||
/*
|
||||
LGraphTextureBokeh._pixel_shader = "precision highp float;\n\
|
||||
varying vec2 a_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform sampler2D u_shape;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
vec4 color = texture2D( u_texture, gl_PointCoord );\n\
|
||||
color *= v_color * u_alpha;\n\
|
||||
gl_FragColor = color;\n\
|
||||
}\n";
|
||||
*/
|
||||
|
||||
LGraphFXBokeh._first_pixel_shader = "precision highp float;\n\
|
||||
precision highp float;\n\
|
||||
varying vec2 v_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform sampler2D u_texture_blur;\n\
|
||||
uniform sampler2D u_mask;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
vec4 color = texture2D(u_texture, v_coord);\n\
|
||||
vec4 blurred_color = texture2D(u_texture_blur, v_coord);\n\
|
||||
float mask = texture2D(u_mask, v_coord).x;\n\
|
||||
gl_FragColor = mix(color, blurred_color, mask);\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
LGraphFXBokeh._second_vertex_shader = "precision highp float;\n\
|
||||
attribute vec2 a_vertex2D;\n\
|
||||
varying vec4 v_color;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform sampler2D u_mask;\n\
|
||||
uniform vec2 u_itexsize;\n\
|
||||
uniform float u_pointSize;\n\
|
||||
uniform float u_threshold;\n\
|
||||
void main() {\n\
|
||||
vec2 coord = a_vertex2D * 0.5 + 0.5;\n\
|
||||
v_color = texture2D( u_texture, coord );\n\
|
||||
v_color += texture2D( u_texture, coord + vec2(u_itexsize.x, 0.0) );\n\
|
||||
v_color += texture2D( u_texture, coord + vec2(0.0, u_itexsize.y));\n\
|
||||
v_color += texture2D( u_texture, coord + u_itexsize);\n\
|
||||
v_color *= 0.25;\n\
|
||||
float mask = texture2D(u_mask, coord).x;\n\
|
||||
float luminance = length(v_color) * mask;\n\
|
||||
/*luminance /= (u_pointSize*u_pointSize)*0.01 */;\n\
|
||||
luminance -= u_threshold;\n\
|
||||
if(luminance < 0.0)\n\
|
||||
{\n\
|
||||
gl_Position.x = -100.0;\n\
|
||||
return;\n\
|
||||
}\n\
|
||||
gl_PointSize = u_pointSize;\n\
|
||||
gl_Position = vec4(a_vertex2D,0.0,1.0);\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
LGraphFXBokeh._second_pixel_shader = "precision highp float;\n\
|
||||
varying vec4 v_color;\n\
|
||||
uniform sampler2D u_shape;\n\
|
||||
uniform float u_alpha;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
vec4 color = texture2D( u_shape, gl_PointCoord );\n\
|
||||
color *= v_color * u_alpha;\n\
|
||||
gl_FragColor = color;\n\
|
||||
}\n";
|
||||
|
||||
|
||||
LiteGraph.registerNodeType("fx/bokeh", LGraphFXBokeh );
|
||||
window.LGraphFXBokeh = LGraphFXBokeh;
|
||||
|
||||
//************************************************
|
||||
|
||||
function LGraphFXGeneric()
|
||||
{
|
||||
this.addInput("Texture","Texture");
|
||||
this.addInput("value1","number");
|
||||
this.addInput("value2","number");
|
||||
this.addOutput("Texture","Texture");
|
||||
this.properties = { fx: "halftone", value1: 1, value2: 1, precision: LGraphTexture.DEFAULT };
|
||||
}
|
||||
|
||||
LGraphFXGeneric.title = "FX";
|
||||
LGraphFXGeneric.desc = "applies an FX from a list";
|
||||
|
||||
LGraphFXGeneric.widgets_info = {
|
||||
"fx": { widget:"combo", values:["halftone","pixelate","lowpalette","noise"] },
|
||||
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
|
||||
};
|
||||
LGraphFXGeneric.shaders = {};
|
||||
|
||||
LGraphFXGeneric.prototype.onExecute = function()
|
||||
{
|
||||
var tex = this.getInputData(0);
|
||||
if(this.properties.precision === LGraphTexture.PASS_THROUGH )
|
||||
{
|
||||
this.setOutputData(0,tex);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tex) return;
|
||||
|
||||
this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
|
||||
|
||||
//iterations
|
||||
var value1 = this.properties.value1;
|
||||
if( this.isInputConnected(1) )
|
||||
{
|
||||
value1 = this.getInputData(1);
|
||||
this.properties.value1 = value1;
|
||||
}
|
||||
|
||||
var value2 = this.properties.value2;
|
||||
if( this.isInputConnected(2) )
|
||||
{
|
||||
value2 = this.getInputData(2);
|
||||
this.properties.value2 = value2;
|
||||
}
|
||||
|
||||
var fx = this.properties.fx;
|
||||
var shader = LGraphFXGeneric.shaders[ fx ];
|
||||
if(!shader)
|
||||
{
|
||||
var pixel_shader_code = LGraphFXGeneric["pixel_shader_" + fx ];
|
||||
if(!pixel_shader_code)
|
||||
return;
|
||||
|
||||
shader = LGraphFXGeneric.shaders[ fx ] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, pixel_shader_code );
|
||||
}
|
||||
|
||||
|
||||
gl.disable( gl.BLEND );
|
||||
gl.disable( gl.DEPTH_TEST );
|
||||
var mesh = Mesh.getScreenQuad();
|
||||
var camera = Renderer._current_camera;
|
||||
|
||||
var noise = null;
|
||||
if(fx == "noise")
|
||||
noise = LGraphTexture.getNoiseTexture();
|
||||
|
||||
this._tex.drawTo( function() {
|
||||
tex.bind(0);
|
||||
if(fx == "noise")
|
||||
noise.bind(1);
|
||||
|
||||
shader.uniforms({u_texture:0, u_noise:1, u_size: [tex.width, tex.height], u_rand:[ Math.random(), Math.random() ], u_value1: value1, u_value2: value2, u_camera_planes: [Renderer._current_camera.near,Renderer._current_camera.far] })
|
||||
.draw(mesh);
|
||||
});
|
||||
|
||||
this.setOutputData(0, this._tex);
|
||||
}
|
||||
|
||||
LGraphFXGeneric.pixel_shader_halftone = "precision highp float;\n\
|
||||
varying vec2 v_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform vec2 u_camera_planes;\n\
|
||||
uniform vec2 u_size;\n\
|
||||
uniform float u_value1;\n\
|
||||
uniform float u_value2;\n\
|
||||
\n\
|
||||
float pattern() {\n\
|
||||
float s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\n\
|
||||
vec2 tex = v_coord * u_size.xy;\n\
|
||||
vec2 point = vec2(\n\
|
||||
c * tex.x - s * tex.y ,\n\
|
||||
s * tex.x + c * tex.y \n\
|
||||
) * u_value2;\n\
|
||||
return (sin(point.x) * sin(point.y)) * 4.0;\n\
|
||||
}\n\
|
||||
void main() {\n\
|
||||
vec4 color = texture2D(u_texture, v_coord);\n\
|
||||
float average = (color.r + color.g + color.b) / 3.0;\n\
|
||||
gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n\
|
||||
}\n";
|
||||
|
||||
LGraphFXGeneric.pixel_shader_pixelate = "precision highp float;\n\
|
||||
varying vec2 v_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform vec2 u_camera_planes;\n\
|
||||
uniform vec2 u_size;\n\
|
||||
uniform float u_value1;\n\
|
||||
uniform float u_value2;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
vec2 coord = vec2( floor(v_coord.x * u_value1) / u_value1, floor(v_coord.y * u_value2) / u_value2 );\n\
|
||||
vec4 color = texture2D(u_texture, coord);\n\
|
||||
gl_FragColor = color;\n\
|
||||
}\n";
|
||||
|
||||
LGraphFXGeneric.pixel_shader_lowpalette = "precision highp float;\n\
|
||||
varying vec2 v_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform vec2 u_camera_planes;\n\
|
||||
uniform vec2 u_size;\n\
|
||||
uniform float u_value1;\n\
|
||||
uniform float u_value2;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
vec4 color = texture2D(u_texture, v_coord);\n\
|
||||
gl_FragColor = floor(color * u_value1) / u_value1;\n\
|
||||
}\n";
|
||||
|
||||
LGraphFXGeneric.pixel_shader_noise = "precision highp float;\n\
|
||||
varying vec2 v_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform sampler2D u_noise;\n\
|
||||
uniform vec2 u_size;\n\
|
||||
uniform float u_value1;\n\
|
||||
uniform float u_value2;\n\
|
||||
uniform vec2 u_rand;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
vec4 color = texture2D(u_texture, v_coord);\n\
|
||||
vec3 noise = texture2D(u_noise, v_coord * vec2(u_size.x / 512.0, u_size.y / 512.0) + u_rand).xyz - vec3(0.5);\n\
|
||||
gl_FragColor = vec4( color.xyz + noise * u_value1, color.a );\n\
|
||||
}\n";
|
||||
|
||||
|
||||
LiteGraph.registerNodeType("fx/generic", LGraphFXGeneric );
|
||||
window.LGraphFXGeneric = LGraphFXGeneric;
|
||||
|
||||
|
||||
// Vigneting ************************************
|
||||
|
||||
function LGraphFXVigneting()
|
||||
{
|
||||
this.addInput("Tex.","Texture");
|
||||
this.addInput("intensity","number");
|
||||
|
||||
this.addOutput("Texture","Texture");
|
||||
this.properties = { intensity: 1, invert: false, precision: LGraphTexture.DEFAULT };
|
||||
|
||||
if(!LGraphFXVigneting._shader)
|
||||
LGraphFXVigneting._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphFXVigneting.pixel_shader );
|
||||
}
|
||||
|
||||
LGraphFXVigneting.title = "Vigneting";
|
||||
LGraphFXVigneting.desc = "Vigneting";
|
||||
|
||||
LGraphFXVigneting.widgets_info = {
|
||||
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
|
||||
};
|
||||
|
||||
LGraphFXVigneting.prototype.onExecute = function()
|
||||
{
|
||||
var tex = this.getInputData(0);
|
||||
|
||||
if(this.properties.precision === LGraphTexture.PASS_THROUGH )
|
||||
{
|
||||
this.setOutputData(0,tex);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tex) return;
|
||||
|
||||
this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
|
||||
|
||||
var intensity = this.properties.intensity;
|
||||
if( this.isInputConnected(1) )
|
||||
{
|
||||
intensity = this.getInputData(1);
|
||||
this.properties.intensity = intensity;
|
||||
}
|
||||
|
||||
gl.disable( gl.BLEND );
|
||||
gl.disable( gl.DEPTH_TEST );
|
||||
|
||||
var mesh = Mesh.getScreenQuad();
|
||||
var shader = LGraphFXVigneting._shader;
|
||||
var invert = this.properties.invert;
|
||||
|
||||
this._tex.drawTo( function() {
|
||||
tex.bind(0);
|
||||
shader.uniforms({u_texture:0, u_intensity: intensity, u_isize:[1/tex.width,1/tex.height], u_invert: invert ? 1 : 0}).draw(mesh);
|
||||
});
|
||||
|
||||
this.setOutputData(0, this._tex);
|
||||
}
|
||||
|
||||
LGraphFXVigneting.pixel_shader = "precision highp float;\n\
|
||||
precision highp float;\n\
|
||||
varying vec2 v_coord;\n\
|
||||
uniform sampler2D u_texture;\n\
|
||||
uniform float u_intensity;\n\
|
||||
uniform int u_invert;\n\
|
||||
\n\
|
||||
void main() {\n\
|
||||
float luminance = 1.0 - length( v_coord - vec2(0.5) ) * 1.414;\n\
|
||||
vec4 color = texture2D(u_texture, v_coord);\n\
|
||||
if(u_invert == 1)\n\
|
||||
luminance = 1.0 - luminance;\n\
|
||||
luminance = mix(1.0, luminance, u_intensity);\n\
|
||||
gl_FragColor = vec4( luminance * color.xyz, color.a);\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
LiteGraph.registerNodeType("fx/vigneting", LGraphFXVigneting );
|
||||
window.LGraphFXVigneting = LGraphFXVigneting;
|
||||
}
|
||||
1506
src/nodes/gltextures.js
Normal file
1506
src/nodes/gltextures.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,105 @@
|
||||
(function(){
|
||||
|
||||
|
||||
|
||||
|
||||
function GraphicsImage()
|
||||
{
|
||||
this.inputs = [];
|
||||
this.addOutput("frame","image");
|
||||
this.properties = {"url":""};
|
||||
}
|
||||
|
||||
GraphicsImage.title = "Image";
|
||||
GraphicsImage.desc = "Image loader";
|
||||
GraphicsImage.widgets = [{name:"load",text:"Load",type:"button"}];
|
||||
|
||||
|
||||
GraphicsImage.prototype.onAdded = function()
|
||||
{
|
||||
if(this.properties["url"] != "" && this.img == null)
|
||||
{
|
||||
this.loadImage(this.properties["url"]);
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.onDrawBackground = function(ctx)
|
||||
{
|
||||
if(this.img && this.size[0] > 5 && this.size[1] > 5)
|
||||
ctx.drawImage(this.img, 0,0,this.size[0],this.size[1]);
|
||||
}
|
||||
|
||||
|
||||
GraphicsImage.prototype.onExecute = function()
|
||||
{
|
||||
if(!this.img)
|
||||
this.boxcolor = "#000";
|
||||
if(this.img && this.img.width)
|
||||
this.setOutputData(0,this.img);
|
||||
else
|
||||
this.setOutputData(0,null);
|
||||
if(this.img && this.img.dirty)
|
||||
this.img.dirty = false;
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.onPropertyChange = function(name,value)
|
||||
{
|
||||
this.properties[name] = value;
|
||||
if (name == "url" && value != "")
|
||||
this.loadImage(value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.onDropFile = function(file, filename)
|
||||
{
|
||||
var img = new Image();
|
||||
img.src = file;
|
||||
this.img = img;
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.loadImage = function(url)
|
||||
{
|
||||
if(url == "")
|
||||
{
|
||||
this.img = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.trace("loading image...");
|
||||
this.img = document.createElement("img");
|
||||
|
||||
var url = name;
|
||||
if(url.substr(0,7) == "http://")
|
||||
{
|
||||
if(LiteGraph.proxy) //proxy external files
|
||||
url = LiteGraph.proxy + url.substr(7);
|
||||
}
|
||||
|
||||
this.img.src = url;
|
||||
this.boxcolor = "#F95";
|
||||
var that = this;
|
||||
this.img.onload = function()
|
||||
{
|
||||
that.trace("Image loaded, size: " + that.img.width + "x" + that.img.height );
|
||||
this.dirty = true;
|
||||
that.boxcolor = "#9F9";
|
||||
that.setDirtyCanvas(true);
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.onWidget = function(e,widget)
|
||||
{
|
||||
if(widget.name == "load")
|
||||
{
|
||||
this.loadImage(this.properties["url"]);
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("graphics/image", GraphicsImage);
|
||||
|
||||
|
||||
|
||||
function ColorPalette()
|
||||
{
|
||||
this.addInput("f","number");
|
||||
@@ -263,81 +362,6 @@ ImageFade.prototype.onExecute = function()
|
||||
LiteGraph.registerNodeType("graphics/imagefade", ImageFade);
|
||||
|
||||
|
||||
function GraphicsImage()
|
||||
{
|
||||
this.inputs = [];
|
||||
this.addOutput("frame","image");
|
||||
this.properties = {"url":""};
|
||||
}
|
||||
|
||||
GraphicsImage.title = "Image";
|
||||
GraphicsImage.desc = "Image loader";
|
||||
GraphicsImage.widgets = [{name:"load",text:"Load",type:"button"}];
|
||||
|
||||
|
||||
GraphicsImage.prototype.onAdded = function()
|
||||
{
|
||||
if(this.properties["url"] != "" && this.img == null)
|
||||
{
|
||||
this.loadImage(this.properties["url"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GraphicsImage.prototype.onExecute = function()
|
||||
{
|
||||
if(!this.img)
|
||||
this.boxcolor = "#000";
|
||||
if(this.img && this.img.width)
|
||||
this.setOutputData(0,this.img);
|
||||
else
|
||||
this.setOutputData(0,null);
|
||||
if(this.img.dirty)
|
||||
this.img.dirty = false;
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.onPropertyChange = function(name,value)
|
||||
{
|
||||
this.properties[name] = value;
|
||||
if (name == "url" && value != "")
|
||||
this.loadImage(value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.loadImage = function(url)
|
||||
{
|
||||
if(url == "")
|
||||
{
|
||||
this.img = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.trace("loading image...");
|
||||
this.img = document.createElement("img");
|
||||
this.img.src = "miniproxy.php?url=" + url;
|
||||
this.boxcolor = "#F95";
|
||||
var that = this;
|
||||
this.img.onload = function()
|
||||
{
|
||||
that.trace("Image loaded, size: " + that.img.width + "x" + that.img.height );
|
||||
this.dirty = true;
|
||||
that.boxcolor = "#9F9";
|
||||
that.setDirtyCanvas(true);
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsImage.prototype.onWidget = function(e,widget)
|
||||
{
|
||||
if(widget.name == "load")
|
||||
{
|
||||
this.loadImage(this.properties["url"]);
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("graphics/image", GraphicsImage);
|
||||
|
||||
|
||||
|
||||
function ImageCrop()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user