Copy & paste custom FabricJS object - javascript

I'm trying to copy & paste objects in my FabricJS project.
It's FabricJS version 2.3.3
With native FabricJS objects it works fine. Exactly like in the demo (http://fabricjs.com/copypaste).
But after I created my custom Class (like here for example http://fabricjs.com/cross) I'm unable to paste objects based on my custom Class. Copy is OK, just Paste function throws an error.
All I get is an error in Console log: this._render is not a function pointing to some line number within FabricJS library.
Can anybody tell why? Thanks!
Here is my custom Class:
const C_Cross = fabric.util.createClass(fabric.Object, {
initialize: function(options) {
this.callSuper('initialize', options);
this.width = 100;
this.height = 100;
this.w1 = this.h2 = 100;
this.h1 = this.w2 = 30;
},
_render: function (ctx) {
ctx.fillRect(-this.w1 / 2, -this.h1 / 2, this.w1, this.h1);
ctx.fillRect(-this.w2 / 2, -this.h2 / 2, this.w2, this.h2);
}
});
This is HTML file: (BODY tag only)
<body>
<canvas id="c" width="1000" height="800" style="border:1px solid #ccc"></canvas>
<br>
<button onclick="testCopy()">COPY</button>
<button onclick="testPaste()">PASTE</button>
<script type="text/javascript">
var TheCanvas = new fabric.Canvas('c');
var myCross = new C_Cross({ top: 100, left: 100 });
TheCanvas.add(myCross);
</script>
</body>
And here functions for Copy & Paste:
function testCopy(){
TheCanvas.getActiveObject().clone(function(cloned) {
TheClipboard = cloned;
});
}
function testPaste(){
TheClipboard.clone(function(clonedObj) {
TheCanvas.discardActiveObject();
clonedObj.set({
left: clonedObj.left + 10,
top: clonedObj.top + 10,
evented: true,
});
if (clonedObj.type === 'activeSelection') {
// active selection needs a reference to the canvas
clonedObj.canvas = canvas;
clonedObj.forEachObject(function(obj) {
TheCanvas.add(obj);
});
// this should solve the unselectability
clonedObj.setCoords();
} else {
TheCanvas.add(clonedObj);
}
TheClipboard.top += 10;
TheClipboard.left += 10;
TheCanvas.setActiveObject(clonedObj);
TheCanvas.requestRenderAll();
});
}
Here is the piece of code from FabricJS library where it crashes:
On the last line of this function.
drawObject: function(ctx) {
this._renderBackground(ctx);
this._setStrokeStyles(ctx, this);
this._setFillStyles(ctx, this);
this._render(ctx); // <--- crashes here
},

You need to add fromObject for your custom class. and need to define type same as class name which require to read while finding specific class.
DEMO
fabric.Cross = fabric.util.createClass(fabric.Object, {
type: 'cross',
initialize: function(options) {
this.callSuper('initialize', options);
this.width = 100;
this.height = 100;
this.w1 = this.h2 = 100;
this.h1 = this.w2 = 30;
},
_render: function(ctx) {
ctx.fillRect(-this.w1 / 2, -this.h1 / 2, this.w1, this.h1);
ctx.fillRect(-this.w2 / 2, -this.h2 / 2, this.w2, this.h2);
}
});
fabric.Cross.fromObject = function(object, callback) {
var cross = new fabric.Cross(object);
callback && callback(cross);
return cross;
};
var TheCanvas = new fabric.Canvas('c');
var myCross = new fabric.Cross({
top: 100,
left: 100
});
TheCanvas.add(myCross);
function testCopy() {
TheCanvas.getActiveObject().clone(function(cloned) {
TheClipboard = cloned;
console.log(TheClipboard)
});
}
function testPaste() {
TheClipboard.clone(function(clonedObj) {
TheCanvas.discardActiveObject();
clonedObj.set({
left: clonedObj.left + 10,
top: clonedObj.top + 10,
evented: true,
});
if (clonedObj.type === 'activeSelection') {
// active selection needs a reference to the canvas
clonedObj.canvas = TheCanvas;
clonedObj.forEachObject(function(obj) {
TheCanvas.add(obj);
});
// this should solve the unselectability
clonedObj.setCoords();
} else {
TheCanvas.add(clonedObj);
}
TheClipboard.top += 10;
TheClipboard.left += 10;
TheCanvas.setActiveObject(clonedObj);
TheCanvas.requestRenderAll();
});
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="400" height="400" style="border:1px solid #ccc"></canvas>
<br>
<button onclick="testCopy()">COPY</button>
<button onclick="testPaste()">PASTE</button>

Related

Uncaught TypeError: Cannot read properties of undefined (reading 'fromURL') while upload gif to canvas

Good evening, dear colleagues!
My task is to upload a gif file to fabric. for this I use the code provided in the official documentation. The code of uploading file is below.
(function() {
var canvas = this.__canvas = new fabric.Canvas('myCanvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
fabric.Object.prototype.transparentCorners = false;
for (var i = 0, len = 5; i < len; i++) {
for (var j = 0, jlen = 5; j < jlen; j++) {
fabric.Sprite.fromURL('https://i.ibb.co/3TkHBVg/sprite.png', createSprite(i, j));
}
}
function createSprite(i, j) {
return function(sprite) {
sprite.set({
left: i * 100 + 50,
top: j * 100 + 50,
angle: fabric.util.getRandomInt(-30, 30)
});
canvas.add(sprite);
setTimeout(function() {
sprite.set('dirty', true);
sprite.play();
}, fabric.util.getRandomInt(1, 10) * 100);
};
}
(function render() {
canvas.renderAll();
fabric.util.requestAnimFrame(render);
})();
})();
<canvas id="myCanvas" width="100" height="100">
</canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js" integrity="sha512-BNaRQnYJYiPSqHHDb58B0yaPfCu+Wgds8Gp/gU33kqBtgNS4tSPHuGibyoeqMV/TJlSKda6FXzoEyYGjTe+vXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/fabric#4.6.0/dist/fabric.js"></script>
I want to upload gif file, but now I get the error show below
"message": "Uncaught TypeError: Cannot read properties of undefined (reading 'fromURL')"
How should I solve this problem?
I'm afraid this is happening simply because the Sprite extension isn't part of fabric.js' core library.
You need to download it from http://fabricjs.com/js/sprite.class.js and include it in your html document's <head> section
<script src="sprite.class.js"></script>
right after including fabric.js itself.
(function() {
var canvas = this.__canvas = new fabric.Canvas('myCanvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
fabric.Object.prototype.transparentCorners = false;
//console.log(fabric.Sprite,fabric)
// for (var i = 0, len = 5; i < len; i++) {
// for (var j = 0, jlen = 5; j < jlen; j++) {
fabric.Sprite.fromURL('https://hot_data_kuzovkin_info_private.hb.bizmrg.com/sprite.png', createSmth()); //createSprite(i, j)
// }
// }
function createSprite(i, j) {
return function(sprite) {
sprite.set({
left: i * 100 + 50,
top: j * 100 + 50,
angle: 0
});
canvas.add(sprite);
setTimeout(function() {
sprite.set('dirty', true);
sprite.play();
}, fabric.util.getRandomInt(1, 10) * 100);
};
}
function createSmth() {
return function(sprite) {
sprite.set({
left: 50,
top: 50,
angle: 0
});
canvas.add(sprite);
setTimeout(function() {
sprite.set('dirty', true);
sprite.play();
}, fabric.util.getRandomInt(1, 10) * 100);
};
}
(function render() {
canvas.renderAll();
fabric.util.requestAnimFrame(render);
})();
})();
<canvas id="myCanvas" width="300" height="300">
</canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js" integrity="sha512-BNaRQnYJYiPSqHHDb58B0yaPfCu+Wgds8Gp/gU33kqBtgNS4tSPHuGibyoeqMV/TJlSKda6FXzoEyYGjTe+vXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/fabric#4.6.0/dist/fabric.js"></script>
<script>
fabric.Sprite = fabric.util.createClass(fabric.Image, {
type: 'sprite',
spriteWidth: 50,
spriteHeight: 72,
spriteIndex: 0,
frameTime: 100,
initialize: function(element, options) {
options || (options = { });
options.width = this.spriteWidth;
options.height = this.spriteHeight;
this.callSuper('initialize', element, options);
this.createTmpCanvas();
this.createSpriteImages();
},
createTmpCanvas: function() {
this.tmpCanvasEl = fabric.util.createCanvasElement();
this.tmpCanvasEl.width = this.spriteWidth || this.width;
this.tmpCanvasEl.height = this.spriteHeight || this.height;
},
createSpriteImages: function() {
this.spriteImages = [ ];
var steps = this._element.width / this.spriteWidth;
for (var i = 0; i < steps; i++) {
this.createSpriteImage(i);
}
},
createSpriteImage: function(i) {
var tmpCtx = this.tmpCanvasEl.getContext('2d');
tmpCtx.clearRect(0, 0, this.tmpCanvasEl.width, this.tmpCanvasEl.height);
tmpCtx.drawImage(this._element, -i * this.spriteWidth, 0);
var dataURL = this.tmpCanvasEl.toDataURL('image/png');
var tmpImg = fabric.util.createImage();
tmpImg.src = dataURL;
this.spriteImages.push(tmpImg);
},
_render: function(ctx) {
ctx.drawImage(
this.spriteImages[this.spriteIndex],
-this.width / 2,
-this.height / 2
);
},
play: function() {
var _this = this;
this.animInterval = setInterval(function() {
_this.onPlay && _this.onPlay();
_this.dirty = true;
_this.spriteIndex++;
if (_this.spriteIndex === _this.spriteImages.length) {
_this.spriteIndex = 0;
}
}, this.frameTime);
},
stop: function() {
clearInterval(this.animInterval);
}
});
fabric.Sprite.fromURL = function(url, callback, imgOptions) {
fabric.util.loadImage(url, function(img) {
callback(new fabric.Sprite(img, imgOptions));
},null,{'crossOrigin':'Anonymous'});
};
fabric.Sprite.async = true;
</script>

Give id to a canvas

I have it so that when you (the PaintBrush) hit the finish everything clears and a button appears. When the button is clicked it starts level two, here it creates a new canvas. I've added some code so that when the button is clicked the old canvas is deleted, then the new one is made. However, this requires the canvas to have an id. how do i get this code:
canvas: document.createElement("canvas"),
To also include an id for it? I cannot have it say var canvas = blah blah and then have
canvas.id = "canvas";
because of the way i have it set up.
P.S the remove code is this if you need to know:
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = this.length - 1; i >= 0; i--) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
and in the button "click" function area i put this for the remove:
document.getElementById("canvas").remove();
Please help! Thanks in Advance!
EDIT: Here is some extra code to help, it is what occurs when the PaintBrush hits the finish:
else if (PaintBrush.crashWith(Finish)) {
PaintBrush.y = 50;
var button = document.createElement("button");
button.innerHTML = "Level 3";
button.id = "button-2"; // add the id to the button
document.body.appendChild(button); // append to body
GameArena2.clear();
GameArena2.stop();
button.addEventListener ("click", function() {
startGame2();
document.getElement("canvas").remove();
document.getElementById("button-2").remove();
});
EDIT 2: Game canvas code:
var GameArena2 = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 1280;
this.canvas.height = 480;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea2, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
},
drawGameOver: function() {
ctx2 = GameArena2.context;
ctx2.fillStyle = "rgba(0,0,0,"+fader +")";
ctx2.fillRect(0,0,GameArena2.width,GameArena2.height);
drawStatic(fader/2);
fader += .1 * 1/fps
ctx2.fillStyle = "rgba(255,255,255," + fader + ")";
ctx2.font = "72px sans-serif";
ctx2.fillText("GAME OVER",GameArena2.width/2 - 220,GameArena2.height/2);
}
}
function everyinterval(n) {
if ((GameArena.frameNo / n) % 1 == 0) {
return true;
}
else if ((GameArena.frameNo / n) % 1 == 0) {
return true;
}
return false;
}
I think the best way is to create a static function like this:
function createElementOnDom (type,id) {
var element=document.createElement(type);
element.id=id;
return element;
}
console.log(createElementOnDom('CANVAS','myId'));
So you can simply create the canvas adding it his specific id using a simple single row function call like you need.
I only know a way to do direcly this on jQuery, that you wouldn't like to use.
simply you need to do that:
function createElementOnDom (type,id) {
var element=document.createElement(type);
element.id=id;
return element;
}
var GameArena2 = {
canvas: createElementOnDom('CANVAS','myId'),
Simple! Just assign it immediately after the GameArena2 object is created.
var GameArena2 = {
canvas: document.createElement("canvas"),
start: function() {
...
...
}
// give id to a canvas
GameArena2.canvas.id = "GameArena2";
function everyinterval(n) {
if ((GameArena.frameNo / n) % 1 == 0) {
...
...
}

Fabric.js - Constrain Resize/Scale to Canvas/Object

How can an object be constrained to being resized/scaled only within the bounds of the canvas (or another object in my case) when using Fabric.js?
Currently, I'm handling the object:scaling event:
// Typescript being used;
onObjectScaling = (event): boolean => {
// Prevent scaling object outside of image
var object = <fabric.IObject>event.target;
var objectBounds: BoundsHelper;
var imageBounds: BoundsHelper;
object.setCoords();
objectBounds = new BoundsHelper(object);
imageBounds = new BoundsHelper(this.imageManipulatorContext.getImageObject());
if (objectBounds.left < imageBounds.left || objectBounds.right > imageBounds.right) {
console.log('horizontal bounds exceeded');
object.scaleX = this.selectedObjectLastScaleX;
object.lockScalingX = true;
object.setCoords();
} else {
this.selectedObjectLastScaleX = object.scaleX;
}
if (objectBounds.top < imageBounds.top || objectBounds.bottom > imageBounds.bottom) {
console.log('vertical bounds exceeded');
object.scaleY = this.selectedObjectLastScaleY;
object.lockScalingY = true;
object.setCoords();
} else {
this.selectedObjectLastScaleY = object.scaleY;
}
return true;
}
**edit The BoundsHelper class is just a helper for wrapping up the math of calculating the right/bottom sides of the bounding box for an object.
import fabric = require('fabric');
class BoundsHelper {
private assetRect: { left: number; top: number; width: number; height: number; };
get left(): number { return this.assetRect.left; }
get top(): number { return this.assetRect.top; }
get right(): number { return this.assetRect.left + this.assetRect.width; }
get bottom(): number { return this.assetRect.top + this.assetRect.height; }
get height(): number { return this.assetRect.height; }
get width(): number { return this.assetRect.width; }
constructor(asset: fabric.IObject) {
this.assetRect = asset.getBoundingRect();
}
}
export = BoundsHelper;
I also make use of the onBeforeScaleRotate callback to disable the scaling lock added by the above:
onObjectBeforeScaleRotate = (targetObject: fabric.IObject): boolean => {
targetObject.lockScalingX = targetObject.lockScalingY = false;
return true;
}
The issue I observe is that it seems like Fabric doesn't redraw the object fast enough for me to accurately detect that scale is passing the image's boundary. In other words, if I scale slowly, then life is good; if I scale quickly, then I can scale outside of the image's bounds.
It is not about speed.
Events are discrete sampling of something you are doing with the mouse.
Even if you move pixel by pixel virtually, the mouse has its own sample rate and the javascript event do not fire every pixel you move when you go fast.
So if you limit your application to stop scaling when you overcome a limit, when you overcome this limit you will stop your scaling, some pixel after the limit, simply because the last check you were 1 pixel before bound, the event after you are 10 pixel after it.
I changed the code, that is not perfect at all, but gives you idea for dealing with the issue.
When you overcome the limit, instead of lock scaling, calculate the correct scaling to be inside the image and apply that scale.
This logic has problems when you are completely outside the image, so i placed the rect already in, just to demonstrate the different approach.
var BoundsHelper = (function () {
function BoundsHelper(asset) {
this.assetRect = asset.getBoundingRect();
}
Object.defineProperty(BoundsHelper.prototype, "left", {
get: function () {
return this.assetRect.left;
},
enumerable: true,
configurable: true
});
Object.defineProperty(BoundsHelper.prototype, "top", {
get: function () {
return this.assetRect.top;
},
enumerable: true,
configurable: true
});
Object.defineProperty(BoundsHelper.prototype, "right", {
get: function () {
return this.assetRect.left + this.assetRect.width;
},
enumerable: true,
configurable: true
});
Object.defineProperty(BoundsHelper.prototype, "bottom", {
get: function () {
return this.assetRect.top + this.assetRect.height;
},
enumerable: true,
configurable: true
});
Object.defineProperty(BoundsHelper.prototype, "height", {
get: function () {
return this.assetRect.height;
},
enumerable: true,
configurable: true
});
Object.defineProperty(BoundsHelper.prototype, "width", {
get: function () {
return this.assetRect.width;
},
enumerable: true,
configurable: true
});
return BoundsHelper;
})();
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
var canvas = new fabric.Canvas('c');
var rectangle = new fabric.Rect({
fill: 'black',
originX: 'left',
originY: 'top',
stroke: 'false',
opacity: 1,
left: 180,
top: 180,
height: 50,
width: 50
});
var rectangleBounds = new BoundsHelper(rectangle);
var image = new fabric.Image(i, {
selectable: false,
borderColor: 'black',
width: 200,
height: 200
});
canvas.on("object:scaling", function (event) {
var object = event.target;
var objectBounds = null;
var imageBounds = null;
var desiredLength;
object.setCoords();
objectBounds = new BoundsHelper(object);
imageBounds = new BoundsHelper(image);
if (objectBounds.left < imageBounds.left) {
object.lockScalingX = true;
// now i have to calculate the right scaleX factor.
desiredLength =objectBounds.right - imageBounds.left;
object.scaleX = desiredLength / object.width;
}
if (objectBounds.right > imageBounds.right) {
object.lockScalingX = true;
desiredLength = imageBounds.right - objectBounds.left;
object.scaleX = desiredLength / object.width;
}
if (objectBounds.top < imageBounds.top) {
object.lockScalingY = true;
desiredLength = objectBounds.bottom - imageBounds.top;
object.scaleY = desiredLength / object.height;
}
if (objectBounds.bottom > imageBounds.bottom) {
object.lockScalingY = true;
desiredLength = imageBounds.bottom - objectBounds.top;
object.scaleY = desiredLength / object.height;
}
return true;
});
canvas.onBeforeScaleRotate = function (targetObject) {
targetObject.lockScalingX = targetObject.lockScalingY = false;
return true;
};
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
var bound = image.getBoundingRect();
canvas.contextContainer.strokeRect(
bound.left + 0.5,
bound.top + 0.5,
bound.width,
bound.height
);
});
canvas.add(image);
canvas.centerObject(image);
image.setCoords();
canvas.add(rectangle);
canvas.renderAll();
img {
display: none;
}
canvas {
border: solid black 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.12/fabric.min.js"></script>
<img id="i" src="http://fabricjs.com/assets/ladybug.png" />
<canvas id="c" width="500" height="500"></canvas>
https://jsfiddle.net/84zovnek/2/
if (objectBounds.left < imageBounds.left) {
//object.lockScalingX = true;
// now i have to calculate the right scaleX factor.
desiredLength = imageBounds.left - objectBounds.right;
object.scaleX = desiredLength / object.width;
}
Other that, you should update to latest version

Javascript jQuery plugin - multiple instances, external control

I am trying to wrap a canvas function in a jquery plugin, so that it can be invoked via multiple instances.
I want to be able to loop through found items and call the plugin like this
http://jsfiddle.net/M99EY/69/
HTML...
<div id="select1" class="foo" data-init="multi">A</div>
<div id="select2" class="foo" data-init="multi">B</div>
<div id="select3" class="foo" data-init="multi">C</div>
<div id="select4" class="foo" data-init="multi">D</div>
JS
...
var complicatedObj = {
init: function(element){
this.el = element;
console.log("init method", this.el);
//start a complicated process
//like rendering a canvas applicaation
this.bindEvent();
this.addRandom(this.el);
},
addRandom: function(el){
$(el).text(Math.random());
},
reInit: function(){
console.log("re-initialize method");
},
bindEvent: function(){
$(this.el).click(function() {
console.log("Letter.", $(this).text());
});
}
}
//An application with complicated functions -- initialize, re-initialize
$.multiInstance = {
id: 'multiInstance',
version: '1.0',
defaults: { // default settings
foo: 'bar'
}
};
(function ($) {
//Attach this new method to jQuery
$.fn.extend({
multiInstance: function (params) {
//Merge default and user parameters
var otherGeneralVars = 'example';
return this.each(function () {
var $this = $(this), opts = $.extend({},$.multiInstance.defaults, params);
switch (params) {
case "init":
complicatedObj.init($this);
break;
case "reInit":
complicatedObj.addRandom($this);
break;
}
//console.log("$this", $this);
console.log("params", params);
//$this.text(opts.foo);
});
}
})
})(jQuery);
/*
$("#select1").multiInstance();
$("#select2").multiInstance({foo:"foobar"})
$("#select3").multiInstance("init");*/
$('[data-init="multi"]').each(function( index ) {
//console.log( index + ": " + $( this ).text());
$(this).multiInstance("init");
});
setTimeout(function(){ $('#select3').multiInstance("reInit"); }, 2000);
but I need to be able to invoke different methods, pass arguments to these methods -- and then when a change has occurred - provide a callback to catch changes to the instance
Is this the correct way of building the plugin... I want to be able to create multiple instances of the app -- but also control it externally - and also pull values out of it for external results.
This is the application I am trying to wrap into its own jquery plugin.
https://jsfiddle.net/7a4738jo/10/
css..
body {
background-color: #CCCCCC;
margin: 0;
padding: 0;
overflow: hidden;
}
canvas{
background: grey;
}
html..
<script type="text/javascript" src="https://code.createjs.com/easeljs-0.6.0.min.js"></script>
<canvas id='canvas1' data-init="canvas360" width="465" height="465"></canvas>
<canvas id='canvas2' data-init="canvas360" width="465" height="465"></canvas>
js...
var stage;
function init(element) {
var canvas = $(element)[0];
if (!canvas || !canvas.getContext) return;
stage = new createjs.Stage(canvas);
stage.enableMouseOver(true);
stage.mouseMoveOutside = true;
createjs.Touch.enable(stage);
var imgList = ["http://jsrun.it/assets/N/b/D/X/NbDXj.jpg",
"http://jsrun.it/assets/f/K/7/y/fK7yE.jpg",
"http://jsrun.it/assets/j/U/q/d/jUqdG.jpg",
"http://jsrun.it/assets/q/o/4/j/qo4jP.jpg",
"http://jsrun.it/assets/i/Q/e/1/iQe1f.jpg",
"http://jsrun.it/assets/5/k/y/R/5kyRi.jpg",
"http://jsrun.it/assets/x/T/I/h/xTIhA.jpg",
"http://jsrun.it/assets/4/X/G/F/4XGFt.jpg",
"http://jsrun.it/assets/6/7/n/r/67nrO.jpg",
"http://jsrun.it/assets/k/i/r/8/kir8T.jpg",
"http://jsrun.it/assets/2/3/F/q/23Fqt.jpg",
"http://jsrun.it/assets/c/l/d/5/cld59.jpg",
"http://jsrun.it/assets/e/J/O/f/eJOf1.jpg",
"http://jsrun.it/assets/o/j/Z/x/ojZx4.jpg",
"http://jsrun.it/assets/w/K/2/m/wK2m3.jpg",
"http://jsrun.it/assets/w/K/2/m/wK2m3.jpg",
"http://jsrun.it/assets/4/b/g/V/4bgVf.jpg",
"http://jsrun.it/assets/4/m/1/8/4m18z.jpg",
"http://jsrun.it/assets/4/w/b/F/4wbFX.jpg",
"http://jsrun.it/assets/4/k/T/G/4kTGQ.jpg",
"http://jsrun.it/assets/s/n/C/r/snCrr.jpg",
"http://jsrun.it/assets/7/f/H/u/7fHuI.jpg",
"http://jsrun.it/assets/v/S/d/F/vSdFm.jpg",
"http://jsrun.it/assets/m/g/c/S/mgcSp.jpg",
"http://jsrun.it/assets/t/L/t/P/tLtPF.jpg",
"http://jsrun.it/assets/j/7/e/H/j7eHx.jpg",
"http://jsrun.it/assets/m/o/8/I/mo8Ij.jpg",
"http://jsrun.it/assets/n/P/7/h/nP7ht.jpg",
"http://jsrun.it/assets/z/f/K/S/zfKSP.jpg",
"http://jsrun.it/assets/2/3/4/U/234U6.jpg",
"http://jsrun.it/assets/d/Z/y/m/dZymk.jpg"];
var images = [], loaded = 0, currentFrame = 0, totalFrames = imgList.length;
var rotate360Interval, start_x;
var bg = new createjs.Shape();
stage.addChild(bg);
var bmp = new createjs.Bitmap();
stage.addChild(bmp);
var myTxt = new createjs.Text("HTC One", '24px Ubuntu', "#ffffff");
myTxt.x = myTxt.y =20;
myTxt.alpha = 0.08;
stage.addChild(myTxt);
function load360Image() {
var img = new Image();
img.src = imgList[loaded];
img.onload = img360Loaded;
images[loaded] = img;
}
function img360Loaded(event) {
loaded++;
bg.graphics.clear()
bg.graphics.beginFill("#222").drawRect(0,0,stage.canvas.width * loaded/totalFrames, stage.canvas.height);
bg.graphics.endFill();
if(loaded==totalFrames) start360();
else load360Image();
}
function start360() {
document.body.style.cursor='none';
// 360 icon
var iconImage = new Image();
iconImage.src = "http://jsrun.it/assets/y/n/D/c/ynDcT.png";
iconImage.onload = iconLoaded;
// update-draw
update360(0);
// first rotation
rotate360Interval = setInterval(function(){ if(currentFrame===totalFrames-1) { clearInterval(rotate360Interval); addNavigation(); } update360(1); }, 25);
}
function iconLoaded(event) {
var iconBmp = new createjs.Bitmap();
iconBmp.image = event.target;
iconBmp.x = 20;
iconBmp.y = canvas.height - iconBmp.image.height - 20;
stage.addChild(iconBmp);
}
function update360(dir) {
currentFrame+=dir;
if(currentFrame<0) currentFrame = totalFrames-1;
else if(currentFrame>totalFrames-1) currentFrame = 0;
bmp.image = images[currentFrame];
}
//-------------------------------
function addNavigation() {
stage.onMouseOver = mouseOver;
stage.onMouseDown = mousePressed;
document.body.style.cursor='auto';
}
function mouseOver(event) {
document.body.style.cursor='pointer';
}
function mousePressed(event) {
start_x = event.rawX;
stage.onMouseMove = mouseMoved;
stage.onMouseUp = mouseUp;
document.body.style.cursor='w-resize';
}
function mouseMoved(event) {
var dx = event.rawX - start_x;
var abs_dx = Math.abs(dx);
if(abs_dx>5) {
update360(dx/abs_dx);
start_x = event.rawX;
}
}
function mouseUp(event) {
stage.onMouseMove = null;
stage.onMouseUp = null;
document.body.style.cursor='pointer';
}
function handleTick() {
stage.update();
}
document.body.style.cursor='progress';
load360Image();
// TICKER
createjs.Ticker.addEventListener("tick", handleTick);
createjs.Ticker.setFPS(60);
createjs.Ticker.useRAF = true;
}
// Init
$(document).ready(function() {
//create multiple instances of canvas
$('[data-init="canvas360"]').each(function(index) {
init(this);
});
});

Maximum call stack size exceeded in custom Image filter

I am looking for a way to fill an Image with an image pattern in a FabricJs canvas, since there is not a built-in image filter to do this. Although my code is still in an early version, basically it should work(unfortunately it doesn't);
All the eavy work is done by applyTo method, where I load an image, then fill an hidden canvas with a pattern. I noticed that there is a function (source) which fall in an infinite recursion:
var pattern = new fabric.Pattern({
source: function () {
fhc.setDimensions({
width: smile.getWidth() + 5,
height: smile.getHeight() + 5,
});
return fhc.getElement();
},
repeat: 'repeat'
});
I have prepared a demo on JSFiddle
Could anyone help me to understand how to solve this issue?
(function (global) {
'use strict';
var fabric = global.fabric || (global.fabric = {}),
extend = fabric.util.object.extend;
fabric.Image.filters.ImagePatternEffect = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
type: 'ImagePatternEffect',
initialize: function (options) {
options = options || {};
this.img = options.img;
},
applyTo: function (canvasEl) {
var w = canvasEl.width;
var h = canvasEl.height;
var hc = document.createElement('canvas');
fabric.Image.fromURL('http://i.imgur.com/0Ks0mlY.png', function (smile) {
debugger;
hc.setAttribute('width', w);
hc.setAttribute('height', h);
var fhc = new fabric.StaticCanvas(hc);
fhc.add(smile);
var pattern = new fabric.Pattern({
source: function () {
fhc.setDimensions({
width: smile.getWidth() + 5,
height: smile.getHeight() + 5,
});
return fhc.getElement();
},
repeat: 'repeat'
});
var rect = new fabric.Rect({
fill: pattern,
width: w,
height: h
});
fhc.add(rect);
fhc.renderAll();
var ifhcContext = fhc.getContext('2d');
var fhcImageData = ifhcContext.getImageData(0, 0, fhc.width, fhc.height);
var fhcData = fhcImageData.data;
var context = canvasEl.getContext('2d'),
imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
data = imageData.data;
for (var i = 0, n = data.length; i < n; i += 4) {
data[i] += fhcData[i];
data[i + 1] += fhcData[i + 1];
data[i + 2] += fhcData[i + 2];
}
context.putImageData(imageData, 0, 0);
});
},
toObject: function () {
return extend(this.callSuper('toObject'), {
});
}
});
fabric.Image.filters.ImagePatternEffect.fromObject = function (object) {
return new fabric.Image.filters.ImagePatternEffect(object);
};
})(typeof exports !== 'undefined' ? exports : this);
var canvas = new fabric.Canvas('c');
fabric.Image.fromURL('http://i.imgur.com/qaQ8jir.png', function (img) {
var orImg = img;
img.filters.push(new fabric.Image.filters.ImagePatternEffect({
img: orImg,
}));
img.applyFilters(canvas.renderAll.bind(canvas));
canvas.add(img.set({
width: 300,
height: 300,
}));
}, {
crossOrigin: ''
});
i also use pattern image on my web application , in order to add it on objects, and it works.
All you need is this function function loadPattern(url, seatObj) where i pass the object which i want to add the pattern each time and the image(pattern) link.
here is the snippet :
function loadPattern(url, seatObj) {
//i use the loadImage function to load the image and pass it to the fabric.Pattern constructor
fabric.util.loadImage(url, function (img) {
//i change the fill property of the object to the
//image that i want to load on it as a pattern
seatObj.fill = new fabric.Pattern({
source: img,
repeat: 'no-repeat'
});
//refresh canvas and watch your image pattern on the object
canvas.renderAll();
});
}
hope this helps,
good luck

Categories

Resources