I am trying to figure out a way I can fill a shape in PIXI.js using a texture created from a canvas.
The reason for this is I wanna be able to create a gradient on a normal html canvas, and they make a texture out of it and add it to the pixi stage. Now I can do that, that was the first thing I tested, it works. But the end goal is to create shapes in PIXI.js using the Graphics class and then fill them with my gradient. I do not know how to accomplish this, as the .beginFill() method only accepts a color. How do I fill a shape with a texture?
Here is my code. I know the auxillary canvas creation is a little verbose, but that is a problem for later.
$(document).ready(function() {
var stage = new PIXI.Container();
var renderer = PIXI.autoDetectRenderer(800, 600);
document.body.appendChild(renderer.view);
//Aliases
var Sprite = PIXI.Sprite;
var TextureCache = PIXI.utils.TextureCache;
var resources = PIXI. loader.resources;
function AuxCanvas(id, w, h, color1, color2) {
this.id = id;
this.w = w;
this.h = h;
this.color1 = color1;
this.color2 = color2;
}
// create and append the canvas to body
AuxCanvas.prototype.create = function() {
$('body').append('<canvas id="'+
this.id+'" width="'+
this.w+'" height="'+
this.h+'"></canvas>');
}
// draw gradient
AuxCanvas.prototype.drawGradient = function() {
var canvas = document.getElementById(this.id);
var ctx = canvas.getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, 800, 0);
gradient.addColorStop(0, this.color1);
gradient.addColorStop(1, this.color2);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, this.w, this.h);
}
function setup() {
var graphics = new PIXI.Graphics();
graphics.beginFill(PIXI.Texture.fromCanvas(can1)); //This doesn't work obviously
graphics.drawCircle(60, 185, 40);
graphics.endFill();
stage.addChild(graphics);
renderer.render(stage);
}
var can1 = new AuxCanvas("can1", 800, 600, "green", "yellow");
can1.create();
can1.drawGradient();
var can2 = new AuxCanvas("can2", 800, 600, "blue", "red");
can2.create();
can2.drawGradient();
setup();
})
Allright I figured out a way, actually it was easy.
Just make the Graphics object a mask for the sprite created from the html canvas.
function setup() {
var can2 = document.getElementById('can2');
var sprite = new Sprite(PIXI.Texture.fromCanvas(can2))
var graphics = new PIXI.Graphics();
graphics.beginFill();
graphics.drawCircle(300, 300, 200);
graphics.endFill();
sprite.mask = graphics;
stage.addChild(sprite);
renderer.render(stage);
}
Moreover, appending the graphic as a child of the sprite is the best way to go, just need to make sure that they are the same dimentions. With this done, I can move the sprite freely, and it's gradient texture doesn't change, or more precisely, it moves with the sprite. Of course everything has to be equal in dimentions.
var graphics = new PIXI.Graphics();
graphics.beginFill();
graphics.drawCircle(100, 100, 100);
graphics.endFill();
sprite.addChild(graphics);
sprite.mask = graphics;
Related
I created a small plug-in for my map application. This plug-in adds text labels to geometric features. It looks like so:
On the screen above you can see a map, a horizontal linestring and a text label. I created this label by using canvas, canvas.getContext("2d") and a bunch of standard functions like ctx.strokeText, ctx.fillText etc. The problem I face now is that the linestring on the screen is interactive or moveable and I want my label to move as well. I'm not asking about the exact solution to my problem. What I'm interested in is just how to get backround pixels (right below my text label), so that I could restore them before I "move" or redraw the label at a new place. If you can provide a teeny-weeny example where you have some background and then draw some object and then "remove" it, it will be great.
You probably want to use context.getImageData and context.putImageData
Assuming your canvas has the id "myCanvas", calling doDraw() will cause a black rectangle to blink on a complex background.
First, the background is drawn in doDraw(). Then, the background that is to be covered by the rectangle is captured in drawRectangle() and saved in the variable "imageData". Then the rectangle is drawn over the background. Then, 1 second later, eraseRectangle() is called, and the background is replaced by a call to putImageData().
In this fiddle:
https://jsfiddle.net/f3Luxcoc/
Here's the javascript:
//coordinates of rectangle
var xp = 20;
var yp = 20;
var wp = 80;
var hp = 80;
//saved background image
var imageData = null;
function doDraw() {
var can = document.getElementById("myCanvas");
can.width = 500;
can.height = 500;
var context = can.getContext("2d");
//draw background contents
var image = getImage();
context.drawImage(image, 0, 0);
context.drawImage(image, 100, 0);
context.drawImage(image, 0, 100);
context.drawImage(image, 100, 100);
drawRectangle();
}
function drawRectangle() {
var can = document.getElementById("myCanvas");
var context = can.getContext("2d");
//capture background
imageData = context.getImageData(xp, yp, wp, hp);
//draw Rectangle
context.rect(xp, yp, wp, hp);
context.fill();
setTimeout(function() {
eraseRectangle();
}, 1000);
}
function eraseRectangle() {
var can = document.getElementById("myCanvas");
var context = can.getContext("2d");
context.putImageData(imageData, xp, yp);
setTimeout(function() {
drawRectangle();
}, 1000);
}
doDraw();
function getImage() {
var image1 = new Image(237, 110);
image1.src = "data:image/png;base64,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"
return image1;
}
I have followed the answer in this post; fill image with texture pattern, and it is working perfectly.
Is there a way to do the same with KonvaJS?
AFAIK, KonvaJS does not yet support the compositing required to create your texture overlay. But a Konva.Image can take a native html5 canvas element as its image source, so just do your overlay on an html5 canvas element and then feed it to Konva: var textureImage = new Konva.Image({ image:myCanvasElement })
Example annotated code and a Demo:
About Microsoft: Requires Edge -- IE doesn't allow compositing
var stage;
// Attributions of code that applies textures using compositing:
// Indirectly from your SO Q&A: http://stackoverflow.com/questions/36097859/add-texture-to-image-object-in-konvajs
// Directly from this SO Q&A: http://stackoverflow.com/questions/28545747/fill-image-with-texture-pattern/28552076#28552076
// image loading for demo (ignore)
var img1 = new Image;
var img2 = new Image;
var cnt = 2;
img1.onload = img2.onload = function() {
if (!--cnt) go()
};
img1.src = "http://i.imgur.com/8WqH9v4.png"; // sofa
img2.src = "http://i.stack.imgur.com/sQlu8.png"; // pattern
//
function createCompositedCanvas(img1, img2) {
// create canvas
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d");
canvas.width = img1.width;
canvas.height = img1.height;
// create a pattern
ctx.fillStyle = ctx.createPattern(img2, "repeat");
// fill canvas with pattern
ctx.fillRect(0, 0, canvas.width, canvas.height);
// use blending mode multiply
ctx.globalCompositeOperation = "multiply";
// draw sofa on top
ctx.drawImage(img1, 0, 0, img1.width * .5, img1.height * .5);
// change composition mode (blending mode is automatically set to normal)
ctx.globalCompositeOperation = "destination-in";
// draw to cut-out sofa
ctx.drawImage(img1, 0, 0, img1.width * .5, img1.height * .5);
//
document.body.appendChild(canvas);
return (canvas);
}
// end attibuted code
function go() {
// create stage
stage = new Konva.Stage({
container: 'container',
width: img1.width,
height: img1.height
});
var layer = new Konva.Layer();
stage.add(layer);
// create composited canvas
var canvas = createCompositedCanvas(img1, img2);
// use the in-memory canvas as an image source for Konva.Image
var img = new Konva.Image({
x: -200,
y: -50,
image: canvas,
draggable: true
});
layer.add(img);
layer.draw();
}
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
}
canvas{border:solid 1px red;}
<script src="https://cdn.rawgit.com/konvajs/konva/0.9.0/konva.min.js"></script>
<div id="container"></div>
<h4>Native canvas element used to do compositing</h4>
I'm developing web app using canvas and I made three. canvas, canvas_panorama and canvas_image.
First one is something like main canvas, conteiner for the others. canvas_panorama is a background for canvas_image.
After canvas is right clicked, I'm computing angle to rotate canvas_image:
function getAngle( e, pw /*canvas*/ ){
var offset = pw.offset();
var center_x = (offset.left) + ($(pw).width() / 2);
var center_y = (offset.top) + ($(pw).height() / 2);
var mouse_x = e.pageX;
var mouse_y = e.pageY;
var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
angle = radians;
}
After I have an angle I'm trying to rotate canvas_image like this:
function redraw(){
var p1 = ctx.transformedPoint(0,0);
var p2 = ctx.transformedPoint(canvas.width,canvas.height);
ctx.clearRect( p1.x, p1.y, p2.x-p1.x, p2.y-p1.y );
canvas_image_ctx.drawImage(image_img, 0, 0, 150, 150);
canvas_panorama_ctx.drawImage(panorama_img, 0, 0, 600, 300);
canvas_panorama_ctx.drawImage(canvas_image, 20, 20);
// rotate panorama_img around its center
// x = x + 0.5 * width
// y = y + 0.5 * height
canvas_panorama_ctx.translate(95, 95);
canvas_panorama_ctx.rotate(angle);
// translate to back
canvas_panorama_ctx.translate(-95, -95);
ctx.drawImage(canvas_panorama, 0, 0);
}
But this rotates both canvas_image and canvas_panorama. It should only rotate canvas_image
JSFiddle to show you my problem
I think you are confusing yourself with this idea of multiple canvases.
Once in the drawImage() method, every of your canvases are just images, and could be just one or even just plain shapes.
Transformation methods do apply to the canvas' context's matrix, and will have effect only if you do some drawing operations when they are set.
Note : To reset your context matrix, you can either use save(); and restore() methods which will also save all other properties of your context, so if you only need to reset the transform, then it's preferred to simply reset the transformation matrix to its default : ctx.setTransform(1,0,0,1,0,0).
Here is a simplified example to make things clearer :
var ctx = canvas.getContext('2d');
// a single shape, with the border of the context matrix
var drawRect = function(){
ctx.beginPath();
ctx.rect(10, 10, 50, 20);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.stroke();
};
// set the color of our shapes
var gradient = ctx.createLinearGradient(0,0,70,0);
gradient.addColorStop(0,"green");
gradient.addColorStop(1,"yellow");
ctx.fillStyle = gradient;
// here comes the actual drawings
//we don't have modified the transform matrix yet
ctx.strokeStyle = "green";
drawRect();
// here we translate of 100px then we do rotate the context of 45deg
ctx.translate(100, 0)
ctx.rotate(Math.PI/4)
ctx.strokeStyle = "red";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
// here we move of 150px to the right and 25px to the bottom
ctx.translate(150, 25)
ctx.strokeStyle = "blue";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
<canvas id="canvas" width="500" height="200"></canvas>
In your code, you are setting the transformations on the canvas that does represent your image, and you do draw every of your canvases at each call.
What you want instead, is to set the transformation on the main canvas only, and draw the non-transformed image :
var main_ctx = canvas.getContext('2d');
var img_canvas = canvas.cloneNode();
var bg_canvas = canvas.cloneNode();
var angle = 0;
// draw on the main canvas, and only on the main canvas
var drawToMain = function(){
// first clear the canvas
main_ctx.clearRect(0,0,canvas.width, canvas.height);
// draw the background image
main_ctx.drawImage(bg_canvas, 0,0);
// do the transforms
main_ctx.translate(img_canvas.width/2, img_canvas.height/2);
main_ctx.rotate(angle);
main_ctx.translate(-img_canvas.width/2, -img_canvas.height/2);
// draw the img with the transforms applied
main_ctx.drawImage(img_canvas, 0,0);
// reset the transforms
main_ctx.setTransform(1,0,0,1,0,0);
};
// I changed the event to a simple onclick
canvas.onclick = function(e){
e.preventDefault();
angle+=Math.PI/8;
drawToMain();
}
// a dirty image loader
var init = function(){
var img = (this.src.indexOf('lena')>0);
var this_canvas = img ? img_canvas : bg_canvas;
this_canvas.width = this.width;
this_canvas.height = this.height;
this_canvas.getContext('2d').drawImage(this, 0,0);
if(!--toLoad){
drawToMain();
}
};
var toLoad = 2;
var img = new Image();
img.onload = init;
img.src = "http://pgmagick.readthedocs.org/en/latest/_images/lena_scale.jpg";
var bg = new Image();
bg.onload = init;
bg.src = 'http://www.fnordware.com/superpng/pnggradHDrgba.png';
<canvas id="canvas" width="500" height="300"></canvas>
So I'm drawing a world map to a canvas. I want this world map to plot the coordinates of things dynamically by drawing circles with the arc method. I draw the world map to the canvas, get a URL for the canvas, and there are no errors in drawing the circles... The only problem is the circles are being drawn underneath the image background. Here is my code.
// The containing function is a method of an object.
createCanvas: function(done) {
var imageObj = new Image();
imageObj.onload = _.bind(function(){
var canvas = document.createElement("canvas"),
context = canvas.getContext('2d'),
w = imageObj.width,
h = imageObj.height;
canvas.width = w;
canvas.height = h;
context.drawImage(imageObj, 0, 0, w, h);
for(var i = 0; i < arrayOfCoords.length; i++){
var mapPoint = arrayOfCoords[i];
//Invoke a function that gets an object {long: x, lat: y}
var coordinates = returnCoordinatesObject(mapPoint.long, mapPoint.lat)
context.beginPath();
context.arc(coordinates, long, coordinates.lat, 10, 0, Math.PI*2, true);
context.closePath();
context.fill();
}
var canvasImage = canvas.toDataURL('image/png');
console.log(canvasImage);
done.call(this, canvasImage);
}, this);
imageObj.src="img/le-world-map.png";
},
The code isn't erroring so I presume the circles are being drawn, but drawn behind the map. There are other threads on stackoverflow that have asked similar questions but I found them unclear and was unable to implement the solutions suggested...
Thx
--Gaweyne
I'm designing a simple app for which I need to use multiple globalCompositeOperation, and therefore I need to use multiple hidden items, then merge them to get the final result.
one of the canvas items is used to drawImage() and then use it as an alpha mask.
I assumed that on 2nd canvas if I use to draw 1st canvas, it will copy it exactly so I can add 2nd thing on top of it. It does copy only the fillRect() and ignores the drawImage() function...
Any idea how can I forward entire content of the first canvas to 2nd? I need the masked part to move to the 2nd canvas.
Been stuck on it for hours and need your help. Tried to use toDataUrl("image/png") and then output that into 2nd canvas, but getting same results :(
simplified version under:
http://jsfiddle.net/EbVmm/17/
Thanks
var c1 = document.getElementById("canvas1");
var c2 = document.getElementById("canvas2");
function drawScene(mainColour) {
var ctx = c1.getContext("2d");
var alphaPath = "http://eskarian.com/images/alpha-test.png";
var alphaObj = new Image();
alphaObj.src = alphaPath;
ctx.fillStyle = mainColour;
ctx.fillRect(0, 0, 200, 300);
ctx.globalCompositeOperation = 'xor';
alphaObj.onload = function () {
ctx.drawImage(alphaObj, 0, 0);
};
};
function addScene(colour) {
var ctx2 = c2.getContext("2d");
ctx2.drawImage(c1, 0, 0);
ctx2.globalCompositeOperation = "source-over";
ctx2.fillStyle = colour;
ctx2.fillRect(50, 50, 100, 100);
};
You are trying to use alphaObj before it is fully loaded.
Try something like this:
var c1 = document.getElementById("canvas1");
var c2 = document.getElementById("canvas2");
var alphaPath = "http://eskarian.com/images/alpha-test.png";
var alphaObj = new Image();
alphaObj.onload = function () {
drawScene(mainColour);
addScene(colour)
};
alphaObj.src = alphaPath;
function drawScene(mainColour) {
var ctx = c1.getContext("2d");
ctx.drawImage(alphaObj, 0, 0);
ctx.fillStyle = mainColour;
ctx.fillRect(0, 0, 200, 300);
ctx.globalCompositeOperation = 'xor';
};
function addScene(colour) {
var ctx2 = c2.getContext("2d");
ctx2.drawImage(c1, 0, 0);
ctx2.globalCompositeOperation = "source-over";
ctx2.fillStyle = colour;
ctx2.fillRect(50, 50, 100, 100);
};