I am trying to follow this tutorial here https://www.smashingmagazine.com/2012/10/design-your-own-mobile-game/ and I am stuck on the second part. (2. A Blank Canvas)
I am not sure where to put the POP.Draw object. Does it go inside of the var POP{} brackets where the other objects are created? I've tried keeping it inside, outside, and in the init function which I don't think makes sense. The purpose is to create methods within the new Draw object so they can be called later to create pictures in the canvas.
Here is my current code. It is the same as the one in the link:
var POP = {
//setting up initial values
WIDTH: 320,
HEIGHT: 480,
// we'll set the rest of these
//in the init function
RATIO: null,
currentWidth: null,
currentHeight: null,
canvas: null,
ctx: null,
init: function() {
//the proportion of width to height
POP.RATIO = POP.WIDTH / POP.HEIGHT;
//these will change when the screen is resized
POP.currentWidth = POP.WIDTH;
POP.currentHeight = POP.HEIGHT;
//this is our canvas element
POP.canvas = document.getElementsByTagName('canvas')[0];
//setting this is important
//otherwise the browser will
//default to 320x200
POP.canvas.width = POP.WIDTH;
POP.canvas.width = POP.HEIGHT;
//the canvas context enables us to
//interact with the canvas api
POP.ctx = POP.canvas.getContext('2d');
//we need to sniff out Android and iOS
// so that we can hide the address bar in
// our resize function
POP.ua = navigator.userAgent.toLowerCase();
POP.android = POP.ua.indexOf('android') > -1 ? true : false;
POP.ios = (POP.ua.indexOf('iphone') > -1 || POP.ua.indexOf('ipad') > -1) ? true : false;
//we're ready to resize
POP.resize();
POP.Draw.clear();
POP.Draw.rect(120, 120, 150, 150, 'green');
POP.Draw.circle(100, 100, 50, 'rgba(225,0,0,0.5)');
POP.Draw.text('Hello WOrld', 100, 100, 10, "#000");
},
resize: function() {
POP.currentHeight = window.innerHeight;
//resize the width in proportion to the new height
POP.currentWidth = POP.currentHeight * POP.RATIO;
//this will create some extra space on the page
//allowing us to scroll past the address bar thus hiding it
if (POP.android || POP.ios) {
document.body.style.height = (window.innerHeight + 50) + 'px';
}
//set the new canvas style width and height note:
//our canvas is still 320 x 400 but we're essentially scaling it with css
POP.canvas.style.width = POP.currentWidth + 'px';
POP.canvas.style.height = POP.currentHeight + 'px';
//we use a timeout here because some mobile browsers
//don't fire if there is not a short delay
window.selfTimeout(function() {
window.scrollTo(0, 1);
})
//this will create some extra space on the page
//enabling us to scroll past the address bar
//thus hiding it
if (POP.android || POP.ios) {
document.body.style.height = (window.innerHeight + 50) + 'px';
}
}
};
window.addEventListener('load', POP.init, false);
window.addEventListener('resize', POP.resize, false);
//abstracts various canvas operations into standalone functions
POP.Draw = {
clear: function() {
POP.ctx.clearRect(0, 0, POP.WIDTH, POP.HEIGHT);
},
rect: function(x, y, w, h, col) {
POP.ctx.fillStyle = col;
POP.ctx.fillRect(x, y, w, h);
},
circle: function(x, y, r, col) {
POP.ctx.fillStyle = col;
POP.ctx.beginPath();
POP.ctx.arc(x + 5, y + 5, r, 0, Math.PI * 2, true);
POP.ctx.closePath();
POP.ctx.fill();
},
text: function(string, x, y, size, col) {
POP.ctx.font = 'bold' + size + 'px Monospace';
POP.ctx.fillStyle = col;
POP.ctx.fillText(string, x, y);
}
};
SOLVED
I didn't realize but the completed code is on the webpage. I downloaded it and looked at the example for answers.
I solved the issue by placing the POP.Draw.clear, POP.Draw.rect methods before calling the POP.resize() method. I'm not really sure why the order matters, but it does.
Related
I'm using MatterJs for a physics based game and have not found a solution for the problem of preventing bodies being force-dragged by the mouse through other bodies. If you drag a body into another body, the body being dragged can force itself into and through the other body. I'm looking for a reliable way to prevent them from intersecting. You can observe this effect in any MatterJS demo by selecting a body with the mouse, and trying to force it through another body. Here is a typical example:
https://brm.io/matter-js/demo/#staticFriction
Unfortunately this breaks any games or simulations depending on drag-and-drop.
I have attempted numerous solutions, such as breaking the mouse constraint when a collision occurs, or reducing constraint stiffness, but nothing which works reliably.
Any suggestions welcome!
I think that the best answer here would be a significant overhaul to the Matter.Resolver module to implement predictive avoidance of physical conflicts between any bodies. Anything short of that is guaranteed to fail under certain circumstances. That being said here are two "solutions" which, in reality, are just partial solutions. They are outlined below.
Solution 1 (Update)
This solution has several advantages:
It is more concise than Solution 2
It creates a smaller computational footprint than Solution 2
The drag behavior is not interrupted the way it is in Solution 2
It can be non-destructively combined with Solution 2
The idea behind this approach is to resolve the paradox of what happens "when an unstoppable force meets an immovable object" by rendering the force stoppable. This is enabled by the Matter.Event beforeUpdate, which allows the absolute velocity and impulse (or rather positionImpulse, which isn't really physical impulse) in each direction to be constrained to within user-defined bounds.
window.addEventListener('load', function() {
var canvas = document.getElementById('world')
var mouseNull = document.getElementById('mouseNull')
var engine = Matter.Engine.create();
var world = engine.world;
var render = Matter.Render.create({ element: document.body, canvas: canvas,
engine: engine, options: { width: 800, height: 800,
background: 'transparent',showVelocity: true }});
var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}),
size = 50, counter = -1;
var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6,
0, 0, function(x, y) {
return Matter.Bodies.rectangle(x, y, size * 2, size, {
slop: 0, friction: 1, frictionStatic: Infinity });
});
Matter.World.add(world, [ body, stack,
Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
]);
Matter.Events.on(engine, 'beforeUpdate', function(event) {
counter += 0.014;
if (counter < 0) { return; }
var px = 400 + 100 * Math.sin(counter);
Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
Matter.Body.setPosition(body, { x: px, y: body.position.y });
if (dragBody != null) {
if (dragBody.velocity.x > 25.0) {
Matter.Body.setVelocity(dragBody, {x: 25, y: dragBody.velocity.y });
}
if (dragBody.velocity.y > 25.0) {
Matter.Body.setVelocity(dragBody, {x: dragBody.velocity.x, y: 25 });
}
if (dragBody.positionImpulse.x > 25.0) {
dragBody.positionImpulse.x = 25.0;
}
if (dragBody.positionImpulse.y > 25.0) {
dragBody.positionImpulse.y = 25.0;
}
}
});
var mouse = Matter.Mouse.create(render.canvas),
mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
constraint: { stiffness: 0.1, render: { visible: false }}});
var dragBody = null
Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
dragBody = event.body;
});
Matter.World.add(world, mouseConstraint);
render.mouse = mouse;
Matter.Engine.run(engine);
Matter.Render.run(render);
});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>
In the example I am restricting the velocity and positionImpulse in x and y to a maximum magnitude of 25.0. The result is shown below
As you can see, it is possible to be quite violent in dragging the bodies and they will not pass through one another. This is what sets this approach apart from others: most other potential solutions fail when the user is sufficiently violent with their dragging.
The only shortcoming I have encountered with this method is that it is possible to use a non-static body to hit another non-static body hard enough to give it sufficient velocity to the point where the Resolver module will fail to detect the collision and allow the second body to pass through other bodies. (In the static friction example the required velocity is around 50.0, I've only managed to do this successfully one time, and consequently I do not have an animation depicting it).
Solution 2
This is an additional solution, fair warning though: it is not straightforward.
In broad terms the way this works is to check if the body being dragged, dragBody, has collided with a static body and if the mouse has since moved too far without dragBody following. If it detects that the separation between the mouse and dragBody has become too large it removes the Matter.js mouse.mousemove event listener from mouse.element and replaces it with a different mousemove function, mousemove(). This function checks if the mouse has returned to within a given proximity of the center of the body. Unfortunately I couldn't get the built-in Matter.Mouse._getRelativeMousePosition() method to work properly so I had to include it directly (someone more knowledgeable than me in Javascript will have to figure that one out). Finally, if a mouseup event is detected it switches back to the normal mousemove listener.
window.addEventListener('load', function() {
var canvas = document.getElementById('world')
var mouseNull = document.getElementById('mouseNull')
var engine = Matter.Engine.create();
var world = engine.world;
var render = Matter.Render.create({ element: document.body, canvas: canvas,
engine: engine, options: { width: 800, height: 800,
background: 'transparent',showVelocity: true }});
var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}),
size = 50, counter = -1;
var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6,
0, 0, function(x, y) {
return Matter.Bodies.rectangle(x, y, size * 2, size, {
slop: 0.5, friction: 1, frictionStatic: Infinity });
});
Matter.World.add(world, [ body, stack,
Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
]);
Matter.Events.on(engine, 'beforeUpdate', function(event) {
counter += 0.014;
if (counter < 0) { return; }
var px = 400 + 100 * Math.sin(counter);
Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
Matter.Body.setPosition(body, { x: px, y: body.position.y });
});
var mouse = Matter.Mouse.create(render.canvas),
mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
constraint: { stiffness: 0.2, render: { visible: false }}});
var dragBody, overshoot = 0.0, threshold = 50.0, loc, dloc, offset,
bodies = Matter.Composite.allBodies(world), moveOn = true;
getMousePosition = function(event) {
var element = mouse.element, pixelRatio = mouse.pixelRatio,
elementBounds = element.getBoundingClientRect(),
rootNode = (document.documentElement || document.body.parentNode ||
document.body),
scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset :
rootNode.scrollLeft,
scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset :
rootNode.scrollTop,
touches = event.changedTouches, x, y;
if (touches) {
x = touches[0].pageX - elementBounds.left - scrollX;
y = touches[0].pageY - elementBounds.top - scrollY;
} else {
x = event.pageX - elementBounds.left - scrollX;
y = event.pageY - elementBounds.top - scrollY;
}
return {
x: x / (element.clientWidth / (element.width || element.clientWidth) *
pixelRatio) * mouse.scale.x + mouse.offset.x,
y: y / (element.clientHeight / (element.height || element.clientHeight) *
pixelRatio) * mouse.scale.y + mouse.offset.y
};
};
mousemove = function() {
loc = getMousePosition(event);
dloc = dragBody.position;
overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
if (overshoot < threshold) {
mouse.element.removeEventListener("mousemove", mousemove);
mouse.element.addEventListener("mousemove", mouse.mousemove);
moveOn = true;
}
}
Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
dragBody = event.body;
loc = mouse.position;
dloc = dragBody.position;
offset = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5;
Matter.Events.on(mouseConstraint, 'mousemove', function(event) {
loc = mouse.position;
dloc = dragBody.position;
for (var i = 0; i < bodies.length; i++) {
overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
if (bodies[i] != dragBody &&
Matter.SAT.collides(bodies[i], dragBody).collided == true) {
if (overshoot > threshold) {
if (moveOn == true) {
mouse.element.removeEventListener("mousemove", mouse.mousemove);
mouse.element.addEventListener("mousemove", mousemove);
moveOn = false;
}
}
}
}
});
});
Matter.Events.on(mouseConstraint, 'mouseup', function(event) {
if (moveOn == false){
mouse.element.removeEventListener("mousemove", mousemove);
mouse.element.addEventListener("mousemove", mouse.mousemove);
moveOn = true;
}
});
Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
overshoot = 0.0;
Matter.Events.off(mouseConstraint, 'mousemove');
});
Matter.World.add(world, mouseConstraint);
render.mouse = mouse;
Matter.Engine.run(engine);
Matter.Render.run(render);
});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>
After applying the event listener switching scheme the bodies now behave more like this
I have tested this fairly thoroughly, but I can't guarantee it will work in every case. It also bears noting that the mouseup event is not detected unless the mouse is within the canvas when it occurs - but this is true for any Matter.js mouseup detection so I didn't try to fix that.
If the velocity is sufficiently large, Resolver will fail to detect any collision, and since it lacks predictive prevention of this flavor of physical conflict, will allow the body to pass through, as shown here.
This can be resolved by combining with Solution 1.
One last note here, it is possible to apply this to only certain interactions (e.g. those between a static and a non-static body). Doing so is accomplished by changing
if (bodies[i] != dragBody && Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
to (for e.g. static bodies)
if (bodies[i].isStatic == true && bodies[i] != dragBody &&
Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
Failed solutions
In case any future users come across this question and find both solutions insufficient for their use case, here are some of the solutions I attempted which did not work. A guide of sorts for what not to do.
Calling mouse.mouseup directly: object deleted immediately.
Calling mouse.mouseup via Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse}): overridden by Engine.update, behavior unchanged.
Making the dragged object temporarily static: object deleted on returning to non-static (whether via Matter.Body.setStatic(body, false) or body.isStatic = false).
Setting the force to (0,0) via setForce when approaching conflict: object can still pass through, would need to be implemented in Resolver to actually work.
Changing mouse.element to a different canvas via setElement() or by mutating mouse.element directly: object deleted immediately.
Reverting object to last 'valid' position: still allows pass through,
Change behavior via collisionStart: inconsistent collision detection still permits pass through with this method
I would have managed the feature in another way:
No "drag" (so no continuos align of dragpoint with offset Vs dragged object)
On mouseDown the mouse pointer position give an oriented velocity vector for object to follow
On mouseUp reset your velocity vector
Let the matter simulation do the rest
To control collision when dragged you need to utilize collision filter and events.
Create bodies with default collision filter mask 0x0001. Add catch startdrag and enddrag events and set different body collision filter category to temporarily avoid collisions.
Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
});
Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
});
window.addEventListener('load', function () {
//Fetch our canvas
var canvas = document.getElementById('world');
//Setup Matter JS
var engine = Matter.Engine.create();
var world = engine.world;
var render = Matter.Render.create({
canvas: canvas,
engine: engine,
options: {
width: 800,
height: 800,
background: 'transparent',
wireframes: false,
showAngleIndicator: false
}
});
//Add a ball
const size = 50;
const stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 0, 0, (x, y) => {
return Matter.Bodies.rectangle(x, y, size * 2, size, {
collisionFilter: {
mask: 0x0001,
},
slop: 0.5,
friction: 1,
frictionStatic: Infinity,
});
});
Matter.World.add(engine.world, stack);
//Add a floor
var floor = Matter.Bodies.rectangle(250, 520, 500, 40, {
isStatic: true, //An immovable object
render: {
visible: false
}
});
Matter.World.add(world, floor);
//Make interactive
var mouseConstraint = Matter.MouseConstraint.create(engine, { //Create Constraint
element: canvas,
constraint: {
render: {
visible: false
},
stiffness: 0.8
}
});
Matter.World.add(world, mouseConstraint);
// add events to listen drag
Matter.Events.on(mouseConstraint, 'startdrag', function (event) {
event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
});
Matter.Events.on(mouseConstraint, 'enddrag', function (event) {
event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
});
//Start the engine
Matter.Engine.run(engine);
Matter.Render.run(render);
});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.min.js"></script>
This seems to be related to issue 672 on their GitHub page which seems to suggest that this occurs due to a lack of Continuous Collision Detection (CCD).
An attempt to remedy this has been made and the code for it can be found here but the issue is still open so it looks like you might need to edit the engine to build CCD into it yourself.
I am using PDF.js to show PDF in browser. PDF.js uses canvas to render PDF. I have js scripts that draws the lines on the canvas when user double clicks on the canvas. It also adds X check mark to remove the already drawn line.
based on my research i cannot simply just remove the line from the canvas because underneath pixels are gone when you draw something on it. To get it working i have to store lines and then clear canvas and re-load canvas and re-draw lines
Issue
I am not able to store canvas and restore canvas. When i click on X i was able to get lines re-drawn but canvas does not get restored. Canvas remains blank
Run the demo in full page
$(function () {
var $canvas = $("#myCanvas");
var canvasEl = $canvas.get(0);
var ctx = canvasEl.getContext("2d");
var lines = [];
var backupCanvas = document.createElement("canvas");
var loadingTask = pdfjsLib.getDocument('https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf');
loadingTask.promise.then(function (doc) {
console.log("This file has " + doc._pdfInfo.numPages + " pages");
doc.getPage(1).then(page => {
var scale = 1;
var viewPort = page.getViewport(scale);
canvasEl.width = viewPort.width;
canvasEl.height = viewPort.height;
canvasEl.style.width = "100%";
canvasEl.style.height = "100%";
var wrapper = document.getElementById("wrapperDiv");
wrapper.style.width = Math.floor(viewPort.width / scale) + 'px';
wrapper.style.height = Math.floor(viewPort.height / scale) + 'px';
page.render({
canvasContext: ctx,
viewport: viewPort
});
storeCanvas();
});
});
function storeCanvas() {
backupCanvas.width = canvasEl.width;
backupCanvas.height = canvasEl.height;
backupCanvas.ctx = backupCanvas.getContext("2d");
backupCanvas.ctx.drawImage(canvasEl, 0, 0);
}
function restoreCanvas() {
ctx.drawImage(backupCanvas, 0, 0);
}
$canvas.dblclick(function (e) {
var mousePos = getMousePos(canvasEl, e);
var line = { startX: 0, startY: mousePos.Y, endX: canvasEl.width, endY: mousePos.Y, pageY: e.pageY };
lines.push(line);
drawLine(line, lines.length - 1);
});
function drawLine(line, index) {
// draw line
ctx.beginPath();
ctx.strokeStyle = '#df4b26';
ctx.moveTo(line.startX, line.startY);
ctx.lineTo(line.endX, line.endY);
ctx.closePath();
ctx.stroke();
// add remove mark
var top = line.pageY;
var left = canvasEl.width + 20;
var $a = $("<a href='#' class='w-remove-line'>")
.data("line-index", index)
.attr("style", "line-height:0")
.css({ top: top, left: left, position: 'absolute' })
.html("x")
.click(function () {
var index = $(this).data("line-index");
$(".w-remove-line").remove();
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
// restore canvas
restoreCanvas();
lines.splice(index, 1);
for (var i = 0; i < lines.length; i++) {
drawLine(lines[i], i);
}
});
$("body").append($a);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
X: Math.floor(evt.clientX - rect.left),
Y: Math.floor(evt.clientY - rect.top),
};
}
});
canvas {
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<b> Double Click on PDF to draw line and then click on X to remove lines</b>
<div id="wrapperDiv">
<canvas id="myCanvas"></canvas>
</div>
The PDF.js render() function is async so you need to store the canvas after the render has finished. Your code is firing storeCanvas() too early and storing a blank canvas. Easy fix, render() returns a promise so ...
page.render({
canvasContext: ctx,
viewport: viewPort
}).then( () => {
storeCanvas();
});
https://jsfiddle.net/fyLant01/1/
Reference: from https://github.com/mozilla/pdf.js/blob/master/src/display/api.js#L998
/**
* Begins the process of rendering a page to the desired context.
* #param {RenderParameters} params Page render parameters.
* #return {RenderTask} An object that contains the promise, which
* is resolved when the page finishes rendering.
*/
I am working with Processing.js (version 1.4.8).
I have 5 white points, which coordinates I chose specifically. The black dot marks the center of the sketch! I want to be able to translate and scale my sketch. ALSO, I want it to occupy the whole window.
var mapWidth, mapHeight, canvas, pjs, centerX, centerY;
var points = [[100, 100], [300, 100], [100, 300], [300, 300], [200, 200]];
var setSize = function() {
mapWidth = $(window).outerWidth();
mapHeight = $(window).outerHeight();
if (pjs) {
pjs.size(mapWidth, mapHeight);
}
};
var clear = function() {
pjs.background(200);
};
var drawPoint = function(coordinates) {
var radius = 30;
pjs.ellipse(coordinates[0], coordinates[1], radius, radius);
};
var drawPoints = function() {
pjs.fill(255);
points.map(function(point) {
drawPoint(point);
});
};
var calculateCenter = function() {
centerX = Math.floor(mapWidth / 2);
centerY = Math.floor(mapHeight / 2);
};
var drawCenter = function() {
calculateCenter();
var radius = 10;
pjs.fill(0);
pjs.ellipse(centerX, centerY, radius, radius);
console.log("center", centerX, centerY);
};
var move = function() {
pjs.translate(200, 300);
redraw();
};
var zoomIn = function() {
pjs.scale(2, 2);
redraw();
};
var draw = function() {
clear();
drawPoints();
drawCenter();
};
var redraw = function() {
clear();
draw();
};
var addEvent = function(object, type, callback) {
if (object == null || typeof object == "undefined") return;
if (object.addEventListener) {
object.addEventListener(type, callback, false);
} else if (object.attachEvent) {
object.attachEvent("on" + type, callback);
} else {
object["on" + type] = callback;
}
};
$(function() {
canvas = document.getElementById("map");
setSize();
var pjsRun = function(processingjs) {
pjs = processingjs;
pjs.setup = function() {
pjs.size(mapWidth, mapHeight);
draw();
};
};
var p = new Processing(canvas, pjsRun);
addEvent(window, "resize", function(event) {
setSize();
redraw();
});
});
Until here, everything is fine, as you can see in this CodePen.
I want to be able to resize the window AND keep the transformations (translations, scales, ...) that I had already performed.
Please, open the CodePen and try to reproduce this weird behaviour:
1) Perform one (or two) transformation(s) using the top-right buttons
The map is translated by 200 to the right and 300 downwards.
Everything OK by now...
But the problem arises now.
2) Resize the window
The five points are again where they were before the "translate" operation.
So... Again... Is there a way to resize without losing all the transformations that had been performed?
Thanks
Like you've discovered, it appears as though calling the size() function resets the transformation matrix. The short answer to your question is that you need to keep track of the transformations, and then apply them whenever you draw something.
The longer answer to your question is that you're using Processing.js a little bit differently than people typically use it. You've left out the draw() function (note that your draw() function is not the draw() function that's automatically called 60 times per second) and are trying to code event handlers yourself. This disconnect is why you're having issues.
If I were you, I'd start with a more basic sketch that starts out using Processing's built-in draw() function. Write code that draws the scene every frame. Make sure you set the translation and scale every frame. Here's an example:
var draw = function() {
scale(scaleX, scaleY);
translate(translateX, translateY);
background(200);
fill(255);
points.map(function(point) {
ellipse(coordinates[0], coordinates[1], 30, 30);
});
fill(0);
ellipse(width/2, height/2, 10, 10);
};
Then setup event listeners that change the values of scaleX and scaleY as well as translateX and translateY. Let Processing handle the rest.
I am using fillRect to draw rectangular in canvas. I want to add text I want it to be editable like edit box. Is there a way to achieve this in JavaScript?
I don't want fillText because I want the text to be editable on canvas.
For sure! (Although it's tricky)
Thankfully someone has already done it and posted it in a Github repository: https://github.com/goldfire/CanvasInput
It is under the MIT Licence, so make sure to abide by its conditions!
It can be done, but don't!!!
This is the most basic of examples. I do not recommend that anyone do this unless it is for their own use, and that they have strict control over the browser and the browser version, localisation, and more. This will break for more reasons then there are lines of code.
canvasTextBox defines all that is needed. It creates a HTMLInputElement and then listens to the keyup and input events.
An animation loop checks for any change in state and renders the text if needed, in sync with all other DOM rendering.
I have added a basic blinking cursor, but no selection of text, no insert, overwrite modes, no writing direction (left to right only), no spell checking, no no, and no.
I have added no focus checking, no way to turn it off, there is no mouse interaction, and no context menu.
To implement just a basic usable public version using this method would need well over 1000 lines of code, and months of testing. Even then it will not always work as there will always be some special case that is unknowable from within the JavaScript context.
Canvas textbox
Example works on Chrome beta on a Win10 machine, may work on other browsers/OS but I have not bothered to check.
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 200;
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime; //
var canvasTextBox = {
x : 10,
y : 10,
h : 50,
w : 300,
ready : false,
font : "40px arial",
fontCol : "blue",
selectColour : "blue",
background : "#CCC",
border : "black 2px", // this is not a CSS style and will break if you dont have colour and width
create : function(){
var text = document.createElement("input")
text.type = "text";
text.style.position = "absolute";
var bounds = canvas.getBoundingClientRect();
text.style.top = (bounds.top + this.x + 2) + "px";
text.style.left = (bounds.left + this.y +2 )+ "px";
text.style.zIndex = -100;
document.body.appendChild(text);
this.textState.element = text;
// get some events
this.textState.events = (function(event){
this.changed = true;
this.text = this.element.value;
}).bind(this.textState);
// add a blink thing
this.textState.blink = (function(){
this.cursorOn = !this.cursorOn;
this.changed = true;
setTimeout(this.blink,this.cursorRate);
}).bind(this.textState);
// listen for changes
text.addEventListener("input",this.textState.events);
text.addEventListener("keyup",this.textState.events);
this.ready = true;
},
render : function(){
var start,end;
ctx.font = this.font;
ctx.fillStyle = "#AAA";
ctx.strokeStyle = this.border.split(" ")[0];
ctx.lineWidth = this.border.split(" ")[1].replace("px","");
ctx.fillRect(this.x,this.y,this.w,this.h);
ctx.strokeRect(this.x,this.y,this.w,this.h);
ctx.fillStyle = this.fontCol;
start = 0;
end = 0;
if(this.textState.element.selectionStart !== undefined){
start = this.textState.element.selectionStart;
}
var text = this.textState.text;
var textStart = text.substr(0,start);
var w = ctx.measureText(text).width;
var wStart = ctx.measureText(textStart).width;
var cursor = this.x + wStart;
ctx.save();
ctx.beginPath();
ctx.rect(this.x,this.y,this.w,this.h);
ctx.clip();
if(w > this.w){
cursor = this.x + this.w - w + wStart;
if(cursor < this.x){
ctx.fillText(this.textState.text,this.x+this.w-w + (this.x - cursor)+3,this.y + 40);
cursor = this.x;
}else{
ctx.fillText(this.textState.text,this.x+this.w-w,this.y + 40);
}
}else{
ctx.fillText(this.textState.text,this.x,this.y + 40);
}
if(this.textState.cursorOn){
ctx.fillStyle = "red";
ctx.fillRect(cursor,this.y,3,this.h);
}
ctx.restore();
},
textState : {
text : "",
cursor : 0,
cursorOn : false,
cursorRate : 250,
changed : true,
events : null,
element : null,
},
update : function(){
if(this.textState.changed){
this.textState.changed = false;
this.render();
}
},
focus : function(){
this.textState.element.focus();
this.textState.blink();
},
}
canvasTextBox.create();
canvasTextBox.focus();
// main update function
function update(timer){
globalTime = timer;
if(canvasTextBox.ready){
canvasTextBox.update();
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
So in Firefox I'm getting this error in the console when using drawImage on a canvas element.
"IndexSizeError: Index or size is negative or greater than the allowed amount"
Everything is working fine in Chrome. Common causes for this I found were returning negative values or trying to draw the image to the canvas before the image is actually loaded. Neither of which seem to be the case. What am I missing here?
Any ideas would be greatly appreciated. http://jsfiddle.net/Ra9KQ/3/
var NameSpace = NameSpace || {};
NameSpace.Pixelator = (function() {
var _cache = {
'wrapper' : null,
'canvas' : null,
'ctx' : null,
'img' : new Image()
},
_config = {
'canvasWidth' : 250,
'canvasHeight' : 250,
'isPlaying' : false,
'distortMin' : 3,
'distortMax' : 100,
'distortValue' : 100, // matches distortMax by default
'initDistortValue' : 3,
'speed' : 2.5,
'delta' : 2.5, // delta (+/- step), matches speed by default
'animation' : null,
'origImgWidth' : null,
'origImgHeight' : null,
'imgHeightRatio' : null,
'imgWidthRatio' : null,
'newImgWidth' : null,
'newImgHeight' : null
},
_init = function _init() {
_setupCache();
_setupCanvas();
_setupImage();
},
_setupCache = function _setupCache() {
_cache.wrapper = $('#dummy-wrapper');
_cache.canvas = document.getElementById('dummy-canvas');
_cache.ctx = _cache.canvas.getContext('2d');
},
_setupCanvas = function _setupCanvas() {
_cache.ctx.mozImageSmoothingEnabled = false;
_cache.ctx.webkitImageSmoothingEnabled = false;
_cache.ctx.imageSmoothingEnabled = false;
},
_setupImage = function _setupImage() {
_cache.img.onload = function() {
_adjustImageScale();
_pixelate();
_assignEvents();
};
_cache.img.src = _cache.canvas.getAttribute('data-src');
},
_adjustImageScale = function _adjustImageScale() {
var scaledHeight,
scaledWidth;
_config.origImgWidth = _cache.img.width;
_config.origImgHeight = _cache.img.height;
_config.imgHeightRatio = _config.origImgHeight / _config.origImgWidth;
_config.imgWidthRatio = _config.origImgWidth / _config.origImgHeight;
scaledHeight = Math.round(250 * _config.imgHeightRatio);
scaledWidth = Math.round(250 * _config.imgWidthRatio);
if (scaledHeight < 250) {
_config.newImgHeight = 250;
_config.newImgWidth = Math.round(_config.newImgHeight * _config.imgWidthRatio);
} else if (scaledWidth < 250) {
_config.newImgWidth = 250;
_config.newImgHeight = Math.round(_config.newImgWidth * _config.imgHeightRatio);
}
},
_assignEvents = function _assignEvents() {
_cache.wrapper.on('mouseenter', _mouseEnterHandler);
_cache.wrapper.on('mouseleave', _mouseLeaveHandler);
},
_mouseEnterHandler = function _mouseEnterHandler(e) {
_config.delta = -_config.speed;
if (_config.isPlaying === false) {
_config.isPlaying = true;
_animate();
}
},
_mouseLeaveHandler = function _mouseLeaveHandler(e) {
_config.delta = _config.speed;
if (_config.isPlaying === false) {
_config.isPlaying = true;
_animate();
}
},
_pixelate = function _pixelate(val) {
var size = val ? val * 0.01 : 1,
w = Math.ceil(_config.newImgWidth * size),
h = Math.ceil(_config.newImgHeight * size);
console.log('w: ' + w,'h: ' + h,'_config.newImgWidth: ' + _config.newImgWidth,'_config.newImgHeight: ' + _config.newImgHeight);
_cache.ctx.drawImage(_cache.img, 0, 0, w, h);
_cache.ctx.drawImage(_cache.canvas, 0, 0, w, h, 0, 0, _config.canvasWidth, _config.canvasHeight);
},
_animate = function _animate() {
// increase/decrese with delta set by mouse over/out
_config.distortValue += _config.delta;
if (_config.distortValue >= _config.distortMax || _config.distortValue <= _config.distortMin) {
_config.isPlaying = false;
cancelAnimationFrame(_config.animation);
return;
} else {
// pixelate
_pixelate(_config.distortValue);
_config.animation = requestAnimationFrame(_animate);
}
};
return {
init: _init
};
})();
NameSpace.Pixelator.init();
When you are using clipping you need to make sure that the source region is within the image (not necessary for target as this will be clipped by canvas).
So if you add restriction control it should work. Here is one way of doing this (before using the clipping functionality of drawImage):
if (w > _config.canvasWidth) w = _config.canvasWidth;
if (h > _config.canvasHeight) h = _config.canvasHeight;
_cache.ctx.drawImage(_cache.canvas, 0, 0, w, h,
0, 0, _config.canvasWidth, _config.canvasHeight);
Modified fiddle here.
Tip 1:
Normally this also applies to x and y but as these are 0 here there is no need to check.
Tip 2:
If you need the image to be drawn narrower than you need to instead of changing source region, change the target region.
Width and Height of image you draw when you specify size values should be larger or equal to 1. As well for performance advantages floor all values you pass to it.
If width and/or height is 0, it will result in:
IndexSizeError: Index or size is negative or greater than the allowed amount
In Firefox:
width = Math.max(1, Math.floor(width));
height = Math.max(1, Math.floor(height));
ctx.drawImage(image, x, y, width, height);
To help others, I had the same problem in canvas and I solve taking account on image load, for example :
var image = new Image();
image.crossOrigin = "use-credentials";
image.onload = function(){
// here canvas behavior
};
image.src = imgSrc;
I had the same problem but in IE and Safari. There were three things in particular that I had to fix:
1) I had to set the width and height of the image manually.
var image = new Image();
...
image.width = 34;
image.height = 34;
2) I had to avoid using negative offset when creating ol.style.Icon. In this case I had to change my SVG icon, but that pretty much depends to your icon. So this would cause exception because of the negative offset:
var icon = new ol.style.Style({
"image": new ol.style.Icon({
...
"offset": [0, -50]
})
});
3) I had to round the width and height values. As some of them were calculated dynamically, I had values like 34.378 which were causing problems. So I had to round them and pass the Integer value.