I've been trying to write a script in Pixi that uses the canvas from a p5.js program as the entire "view" to apply a displacement filter on. I've already achieved this with a single image added as a sprite (see below), but I can't figure out how to interface with the output of p5.js and use it as a view with Pixi's autoDetectRenderer(). I've used p5's .parent() function to attach the canvas to a specific element but that doesn't seem to help. Ideally this would all end up existing in my #main-container div.
The next task would be to make sure this feed is coming in live, so animating elements from the p5.js program are constantly fed into Pixi and filtered.
Any help/pointers would be greatly appreciated!
HTML:
<!DOCTYPE html>
<html>
<head>
<title>pixi.js + p5.js displacement filter</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="https://cdn.rawgit.com/GoodBoyDigital/pixi.js/v1.6.1/bin/pixi.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.15/p5.min.js"></script>
<style>
#main-container {
position: relative;
width: 300px;
height: 300px;
border: 1px solid red;
}
</style>
</head>
<body>
<div id="main-container"></div>
<script type="text/javascript" src="js/program.js"></script>
</body>
</html>
program.js:
// p5.js program
var theCanvas, width, height;
function setup() {
width = document.getElementById('main-container').offsetWidth;
height = document.getElementById('main-container').offsetHeight;
theCanvas = createCanvas(width, height);
rectMode(CENTER);
}
function draw() {
background(0, 0, 255);
translate(width/2, height/2);
rotate(frameCount*0.01);
fill(0, 255, 0);
rect(0, 0, 100, 100);
}
// -_-_-_-_-_-_-_-_-_-_-_-_
// pixi.js
// Renderer
var renderer = PIXI.autoDetectRenderer(width, height);
document.body.appendChild(renderer.view);
// Stage
var stage = new PIXI.Stage(0xd92256);
// Container
var container = new PIXI.DisplayObjectContainer();
stage.addChild(container);
// Background
var bg = PIXI.Sprite.fromImage("https://i.imgur.com/3q3kNGh.png?1");
container.addChild(bg);
// Filter
var displacementTexture = PIXI.Texture.fromImage("http://i.imgur.com/2yYayZk.png");
var displacementFilter = new PIXI.DisplacementFilter(displacementTexture);
// Apply it
container.filters = [displacementFilter];
// Animate
requestAnimFrame(animate);
function animate() {
var offset = 1;
displacementFilter.offset.x += offset;
displacementFilter.offset.y += offset;
renderer.render(stage);
requestAnimFrame(animate);
}
Thank you!
I think the best best thing to do would be to take different approach to the problem, trying to connect P5 and Pixi is a lot work. I have tried using both libraries before and it went off the rails fast. What you are trying to do can be done with P5 or Pixi alone. The P5 only approach is what I know best so I will walk you though it.
The way that Pixi makes it filters is with webGL shaders, they are small programs the run on the GPU to manipulate images. They are written in a C like language called glsl. P5 has support for webGL shaders (filters) and so, we write our own displacement shader. I am not going to get into the glsl part here but I have made a demo with lots of comments here.
The first part of a shader is loading in the glsl code. Always do this in preload. As an alternative you can use with createShader and grave strings.
let displacementShader;
function preload() {
displacementShader = loadShader("displacement.vert", "displacement.frag");
}
Next you create a WEBGL mode canvas, this is not like a normal canvas and is for 3d graphics and shaders. You still need somewhere for your 2d graphics so make a buffer to draw 2d graphics too.
let buffer;
function setup(){
createCanvas(windowWidth, windowHeight, WEBGL);
buffer = createGraphics(windowWidth, windowHeight);
}
Now that everything is set up, all you need to do is run the shader.
function draw(){
buffer.circle(100, 100, 50, 50) // draw stuff to the buffer
shader(displacementShader);
// pass variables into the shader, it will need to buffer to distort it
displacementShader.setUniform("buffer", buffer);
rect(0, 0, width, height); // some geometry for the shader to draw on too
}
If you want to look at some examples of shader other that my demo there is a lovely Github repo for that. In my demo I also
Related
For an assignment I need to animate something simple using CSS and JavaScript. I've been able to figure out the CSS but everything I read to make an object fade in using JavaScript just doesn't seem to work with the object I drew in JavaScript. I just wanted to draw a circle in JavaScript and then animate it to fade in in 5 seconds.
Here is the basic Code I have so far:
HTML:
<body onload="draw();">
<canvas id="circle" width="450" height="450"></canvas>
</body>
JavaScript:
<script>
function draw()
{
var canvas = document.getElementById('circle');
if (canvas.getContext)
{
var ctx = canvas.getContext('2d');
var X = canvas.width / 2;
var Y = canvas.height / 2;
var R = 45;
ctx.beginPath();
ctx.arc(X, Y, R, 0, 2 * Math.PI, false);
ctx.lineWidth = 3;
ctx.strokeStyle = '#645862';
ctx.stroke();
}
}
</script>
As you can see I only have the circle part of the code. I have tried multiple versions of different fade in animations but I just can't quite get them to work. I'm not very good at JavaScript. It's the one language I have trouble understanding for some reason. I'm also really sick right now otherwise I would be troubleshooting more reasons as to why it isn't working.
To understand how a canvas works, you need to know that it's just a place to display something, and initially it doesn't do anything on its own. You've drawn the circle once, which is enough to display the circle, but not to animate it in any way.
If we want to move the circle in any direction, we must clear the canvas of the already drawn circle and draw the circle in a different place, changing its coordinates by N pixels. The same goes for transparency. We must change the transparency of the color of the circle in each frame, and draw the circle again and again.
This is how 2D and 3D canvas works, as well as all video games - they draw scenes 60 times per second, changing some values along the way, such as coordinates, values, color, transparency, height and width.
In order for this to work, we need two additional variables, opacity and the direction (fading) in which the opacity changes, to know whether the circle appears or disappears.
Also important is the recursive call to our draw() function. We will call it constantly, and we will constantly redraw our image on the canvas.
I also want to point out some conceptual mistakes in your code.
Dont use "var", it is deprecated. Use "let","const". Also don`t repeat "var","var","var" in every line. Use commas.
Dont use onload,onclick and others HTML on-attributes. They are only suitable for educational purposes, not for real work. Use script tag and document event listeners.
Dont name canvas id like "circle","box" etc. It is not a circle and a box, it is a canvas.
Use document.querySelector instead of document.getElementById. It is more modern
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas opacity animation</title>
</head>
<body>
<canvas id="canvas" width="450" height="450"></canvas>
<script>
document.addEventListener("DOMContentLoaded",()=>{
const OPACITY_SPEED = .005
let canvas = document.querySelector('canvas'),
context = canvas.getContext('2d'),
opacity = 1,
fading = true
draw()
function draw(){
// clear canvas for redrawing (important!)
context.clearRect(0, 0, canvas.width, canvas.height);
let circleX = canvas.width/2,
circleY = canvas.height/2,
radius = 45
// changing circle opacity
if(fading) opacity -= OPACITY_SPEED
else opacity += OPACITY_SPEED
// check if we need to fade in or to fade out
if(opacity >= 1) fading = true
if(opacity <= 0) fading = false
// draw circle
context.beginPath();
context.arc(circleX, circleY, radius, 0, 2 * Math.PI, false);
context.lineWidth = 3;
context.strokeStyle = `rgba(0, 0, 0, ${opacity})`;
context.stroke();
// call draw() again and again
requestAnimationFrame(draw)
}
})
</script>
</body>
</html>
I'm making a game that uses pixi and it renders on a canvas that's 640x480 pixels. As you can imagine, this is very small when viewed on a PC. I'd like to accomplish this:
I want to increase the size of the canvas so it fills up the whole screen
I want to zoom in on the content so that it fills up as much as possible without changing its aspect ratio
I'd like to center the canvas if there's left over space from the previous step
When I google for how to do this in pixi, I can find each of these individually. But I'd like to have the information on how to do this all in one place and on stackoverflow, because you usually want to do all of these things together.
I modified the source code in this example made by the creator: http://www.goodboydigital.com/pixi-js-tutorial-getting-started/ (source download)
Here's what I came up with:
<!DOCTYPE HTML>
<html>
<head>
<title>pixi.js example 1</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #000000;
}
</style>
<script src="pixi.js"></script>
</head>
<body>
<script>
// create an new instance of a pixi stage
var stage = new PIXI.Stage(0x66FF99);
// create a renderer instance
var renderer = PIXI.autoDetectRenderer(400, 300);
renderer.resize(800, 600);
// add the renderer view element to the DOM
document.body.appendChild(renderer.view);
requestAnimFrame( animate );
// create a texture from an image path
var texture = PIXI.Texture.fromImage("bunny.png");
// create a new Sprite using the texture
var bunny = new PIXI.Sprite(texture);
// center the sprites anchor point
bunny.anchor.x = 0.5;
bunny.anchor.y = 0.5;
// move the sprite t the center of the screen
bunny.position.x = 200;
bunny.position.y = 150;
var container = new PIXI.DisplayObjectContainer();
container.scale.x = 2;
container.scale.y = 2;
container.addChild(bunny);
stage.addChild(container);
function animate() {
requestAnimFrame( animate );
// just for fun, lets rotate mr rabbit a little
bunny.rotation += 0.1;
// render the stage
renderer.render(stage);
}
</script>
</body>
</html>
Now the one thing I didn't do is center it. I see two potential ways to do this. I could use CSS to center the canvas (what I'll probably use), or I could do this in code by adding another outer display object to the stage that centers container.
<!DOCTYPE HTML>
<html>
<head>
<title>Test</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #000000;
overflow: hidden;
}
</style>
<script src="http://www.goodboydigital.com/pixijs/examples/1/pixi.js"></script>
</head>
<body>
<script>
// create an new instance of a pixi stage
var stage = new PIXI.Stage(0x66FF99);
// create a renderer instance
var renderer = PIXI.autoDetectRenderer(window.innerWidth, window.innerHeight);
// add the renderer view element to the DOM
document.body.appendChild(renderer.view);
requestAnimFrame( animate );
// create a texture from an image path
var texture = PIXI.Texture.fromImage("https://dl.dropboxusercontent.com/s/en13743nxusaozy/player.PNG?dl=1&token_hash=AAFVxLm8fEjk3xxPad-kAZ98LJqLoZpdFy9fQtGrIfXL-A");
// create a new Sprite using the texture
var player = new PIXI.Sprite(texture);
// center the sprites anchor point
player.anchor.x = 0.5;
player.anchor.y = 0.5;
// move the sprite t the center of the screen
player.position.x = 200;
player.position.y = 150;
stage.addChild(player);
function animate() {
requestAnimFrame( animate );
//rotate player
player.rotation += 0.1;
// render the stage
renderer.render(stage);
}
</script>
</body>
</html>
This is my code (from the pixijs example, Loaiding the bunny), for some reason I can't seem to get the sprite to load... Can someone take a look at the code and help?
When I put in the right link (the stage rendering turns black). When I put in the wrong link to the sprite, then the stage renders fine but there is no sprite.
var texture = PIXI.Texture.fromImage("https://dl.dropboxusercontent.com/s....");
With the above code, a cross domain request is created for the Sprite texture to load. This is usually not allowed (as in Dropbox case).
In order to see the sprite you will have to copy the file to the local web server or allow Cross domain requests on the other server (https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS)
//local image instead of cross domain
var texture = PIXI.Texture.fromImage("img/player.PNG");
Based on Creating an HTML 5 canvas painting application I created a HTML5 canvas painting application. It works fine, but after creating each object I just need to drag the objects.
Working demo
How to implement drag and drop of the figures?
When the user clicks on the canvas, you have to check the coordinates (compare it to the coordinates for the objects), and see if it's on an object. E.g. You can test if a point (e.g. the coordinates for the mousedown even) is within a circle with this method:
function (pt) {
return Math.pow(pt.x - point.x,2) + Math.pow(pt.y - point.y,2) <
Math.pow(radius,2);
};
If the mousedown is on the object, you have to change the objects coordinates according to how the mouse is moved.
Here is an example, where you can drag a circle:
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = function() {
drawCircle(circle);
element = document.getElementById('canvas');
element.addEventListener('mousedown', startDragging, false);
element.addEventListener('mousemove', drag, false);
element.addEventListener('mouseup', stopDragging, false);
element.addEventListener('mouseout', stopDragging, false);
}
function mouseX(e) {
return e.clientX - element.offsetLeft;
}
function mouseY(e) {
return e.clientY - element.offsetTop;
}
var Point = function (x, y) {
this.x = x;
this.y = y;
return this;
}
var Circle = function (point, radius) {
this.point = point;
this.radius = radius;
this.isInside = function (pt) {
return Math.pow(pt.x - point.x, 2) + Math.pow(pt.y - point.y, 2) <
Math.pow(radius, 2);
};
return this;
}
function startDragging(e) {
var p = new Point(e.offsetX, e.offsetY);
if(circle.isInside(p)) {
deltaCenter = new Point(p.x - circle.point.x, p.y - circle.point.y);
}
}
function drag(e) {
if(deltaCenter != null) {
circle.point.x = (mouseX(e) - deltaCenter.x);
circle.point.y = (mouseY(e) - deltaCenter.y);
drawCircle(circle);
}
}
function stopDragging(e) {
deltaCenter = null;
}
function drawCircle(circle) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(circle.point.x, circle.point.y, circle.radius, 0, Math.PI*2, true);
ctx.fill();
}
var circle = new Circle(new Point(30, 40), 25);
var deltaCenter = null;
var element;
</script>
</head>
<body>
<canvas id="canvas" width="400" height="300"></canvas>
</body>
</html>
Try it on jsFiddle
The same effect can be accomplished using Raphael.js (http://raphaeljs.com/) with Joint.jS (http://www.jointjs.com/).
Shapes created with Raphael can be accessed like any DOM element and can be manipulated via attributes. It is an awesome framework.
Joint.js helps in connecting the shapes. They also have a diagramming library and can help create ERD, Statemachine and several common diagrams. The best part is that you can extend their diagram element and create your own custom elements. Its jaw-dropingly cool.
Checkout their demos with source code at http://www.jointjs.com/demos
If you are using the raphael as "raw" lib you must handle the undo/redo by yourself.
The graphiti lib did have Undo/Redo Stack inside and supports the export for SVG, PNG, JSON,...
Additional you have some kind of Viso like connectors and ports.
http://www.draw2d.org/graphiti/jsdoc/#!/example
Greetings
I don't think there's an easy way to do this.
If you're just dealing with lines, my approach would be to keep track of all lines created, with starting coordinates, ending coordinates and some kind of z-index. When the user starts a dragging action (onmousedown), you have to check if the point is near the line, and then update the object and redraw the canvas when the mouse is moved.
How can I tell if a point belongs to a certain line?
This gets a lot more complicated if you're dealing with complex objects though. You'll probably have to find a solution to check if a point is inside a path.
Objects drawn into HTML5 Canvas are turned into pixels and then forgotten. You can't adjust properties on them and have the canvas update to see the effects. You can remember them yourself, but the canvas will still have those pixels set, so you'd have to basically redraw the whole canvas (or at least some of it) when you adjust a property.
You might want to consider SVG for this application instead, SVG elements are remembered in the DOM and when their properties are updated the browser will update the graphic to reflect the changes.
If you must use canvas, then you're going to need to write quite a bit of code to handle mouse-hits, object properties, and repaints.
I am making a loading spinner with html5 canvas. I have my graphic on the canvas but when i rotate it the image rotates off the canvas. How do I tell it to spin the graphic on its center point?
<!DOCTYPE html>
<html>
<head>
<title>Canvas test</title>
<script type="text/javascript">
window.onload = function() {
var drawingCanvas = document.getElementById('myDrawing');
// Check the element is in the DOM and the browser supports canvas
if(drawingCanvas && drawingCanvas.getContext) {
// Initaliase a 2-dimensional drawing context
var context = drawingCanvas.getContext('2d');
//Load the image object in JS, then apply to canvas onload
var myImage = new Image();
myImage.onload = function() {
context.drawImage(myImage, 0, 0, 27, 27);
}
myImage.src = "img/loading.png";
context.rotate(45);
}
}
</script>
</head>
<body>
<canvas id="myDrawing" width="27" height="27">
</canvas>
</body>
</html>
Here is the complete working example:)
<!DOCTYPE html>
<html>
<head>
<title>Canvas Cog</title>
<script type="text/javascript">
var cog = new Image();
function init() {
cog.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAYAAACN1PRVAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABK1JREFUeNqMVt1ZGzkUvVfS4IW1l8GO82w6IBXE7mCpAFMB+Pt4Z6iApALcAe4AU0HoAJfg7BPYHinnXmmciX+y0YdmJHnQ0bk/R5cvh5cUyFPwRD4EChgEvGWMB36R3+JaiTkmD5gOs8yNb25uLlerFf1pM2yIGA82TEY7xow1oj4GBU6S6yywPNG4JwDH+XGv0Whs7ndN8n97mmPsLCSYgy7ImPQE/pFDyAF+7L0fgTNFUDBcLal90taD1doQ/T6NT9DnW8zkT+jJuQVYukG3hifCVk/L3JOxMBa8VVlSp9MhHKLaB+zpNo1fdgEpmByuMqUAV5viOQLwXNax9KBAFNEEpN1pUwnQmvl6aTza6zNjrCKaymeyOdYAMgfg18iG4T/qw+AC94zvpzDjcwqOXo3VGH26H0xMZ7jPxgT0R2zUi4BYt6bAfEbJvJFZKA4ODgZ5nhcJLE9mk35X21vWC/TXKmiwr2xszoQd/PQv3t/QCzY2twpqBpb5FKOp+hCgzWaTWq0W1Xx0ij5An9WC5VtiLMwvNBrVaSGMvQk5jHQVPN7sb0HzAtE+QJrNgrcUNEARieWCut0ugR0tl8sKcJ5Ahc3jRviPK8ZGTaaBwGKyT+gTiwM4a3Jrba6MbeVXo5F4kp9shn29ndUYC9vLirGDXzRhrYhD8DME5Hkg22df5rDYS/RXmVIsaP/Q/SXs600YnifTjbeSWliEdTYb3QyTqYfdDKTL4B1KS6tVqf6SgGq3P9BvZGpvNIrPCgVKZlGlCDQDxJiCjVppCab05DJHzb+b1Gm36X80cVjLuzozexs0f6IgRkA5XRhzIixRL1+IzhwdHVHrn1Y9oXe1i10aKT6bGGhg1CKK+cT0zCGCs0oXTIogybJMw/779//o48duMvnO9rzLn+Kz8wgS5Shqo4njpCoOQA5Ajb8adHh4SMvVghaLhYb/HsBip88krNVISSEigOlhjmi0LziNhr6wOsgO9C1339vbGznnNAU2AM9Svk235cqKieKGkldAf7DGvTrjnjJnzyQoMu0ZTuZgUqvmlYR+f39XIE4uqCX1E/rDZpCYmKwOOmivAfYK9KF1AM7EdG4uAMLAOjmQideQXOJQkyUisqYiFRhtSFbxCxj8do0T30dmTvLhC+an0MZZVBHX09tBTG4qFigZEJEChjTIEwtRik81Qa7uOQU0IrYAe7FRjqYw6SlYjgAyN1GmHsFIGPfVnxzFuFITKEkfYK+oWZ5qKlIkcZ7UE92oXBmeIgIxtAO5UtSHqo9uiLW+sme5ejSIRASeAFR4LYy8MMzL1aq3EYWzJF28BgMEzGYpBkrMKelgl+P6uTcVY8NjLYyYPwMTCcufSaouH6al9xNJcjC82vDb9uVZKbrWIumNO+waVsu1TCC+Wxcg6xaSpsZSYM2wLO9/U8qZWH+wztQnsfAxV/E3MIKZVf1FsmJVV8mamhEmxZ0X7sSsABsGv1tZJGejmptU7FBUDYzPAXQBwFEEl+9+stFEroJEci2ELwIMmZuWoSTE9DYYcWVCjlJrZWMpeBhlAEqBiulPE84S3ixU5gSTwGGOdyEVNJXxA8nPevshwABHktBS1YoQ+QAAAABJRU5ErkJggg=='; // Set source path
setInterval(draw,10);
}
var rotation = 0;
function draw(){
var ctx = document.getElementById('myCanvas').getContext('2d');
ctx.globalCompositeOperation = 'destination-over';
ctx.save();
ctx.clearRect(0,0,27,27);
ctx.translate(13.5,13.5); // to get it in the origin
rotation +=1;
ctx.rotate(rotation*Math.PI/64); //rotate in origin
ctx.translate(-13.5,-13.5); //put it back
ctx.drawImage(cog,0,0);
ctx.restore();
}
init();
</script>
</head>
<body>
<canvas width="27" height="27" id="myCanvas"></canvas>
</body>
</html>
rotate turns the canvas(?) around your current position, which is 0, 0 to start. you need to "move" to your desired center point, which you can accomplish with
context.translate(x,y);
after you move your reference point, you want to center your image over that point. you can do this by calling
context.drawImage(myImage, -(27/2), -(27/2), 27, 27);
this tells the browser to start drawing the image from above and to the left of your current reference point, by have the size of the image, whereas before you were starting at your reference point and drawing entirely below and to the right (all directions relative to the rotation of the canvas).
since your canvas is the size of your image, your call to translate will use the same measurement, (27/2), for x and y coordinates.
so, to put it all together
// initialization:
context.translate(27/2, 27/2);
// onload:
context.rotate(Math.PI * 45 / 180);
context.drawImage(myImage, -(27/2), -(27/2), 27, 27);
edit: also, rotation units are radians, so you'll need to translate degrees to radians in your code.
edits for rearranging stuff.
For anyone else looking into something like this, you might want to look at this script which does exactly what was originally being requested:
http://projects.nickstakenburg.com/spinners/
You can find the github source here:
https://github.com/staaky/spinners
He uses rotate, while keeping a cache of rectangles which slowly fade out, the older they are.
I find another way to do html loading spinner. You can use sprite sheet animation. This approach can work both by html5 canvas or normal html/javascript/css. Here is a simple way implemented by html/javascript/css.
It uses sprite sheet image as background. It create a Javascript timer to change the background image position to control the sprite sheet animation. The example code is below. You can also check the result here: http://jmsliu.com/1769/html-ajax-loading-spinner.html
<html>
<head><title></title></head>
<body>
<div class="spinner-bg">
<div id="spinner"></div>
</div>
<style>
.spinner-bg
{
width:44px;
height:41px;
background: #000000;
}
#spinner
{
width: 44px;
height: 41px;
background:url(./preloadericon.png) no-repeat;
}
</style>
<script>
var currentbgx = 0;
var circle = document.getElementById("spinner");
var circleTimer = setInterval(playAnimation, 100);
function playAnimation() {
if (circle != null) {
circle.style.backgroundPosition = currentbgx + "px 0";
}
currentbgx -= 44; //one frame width, there are 5 frame
//start from 0, end at 176, it depends on the png frame length
if (currentbgx < -176) {
currentbgx = 0;
}
}
</script>
</body>