How to resize text on canvas - html5-canvas

I have found a way to be able to resize and squeeze my text on normal HTML5 Canvas with the following code:
$("input").on("input", function() {
ctx.clearRect(0, 0, 400, 400);
var width = ctx.measureText(text).width;
if(width <= 100) {
ctx.fillText(text, 0, 100);
} else {
ctx.save();
ctx.scale(100 / width, 1);
ctx.fillText(text, 0, 100);
ctx.restore();
}
});
I want to do the same thing but with using the FabricJS. Is there a way that I can do it?

Yeah sure, just check the width of the text element after you've created it and set the ScaleX property if needed before adding it to the canvas:
var canvas = new fabric.Canvas('c');
var t;
canvas.renderAll();
$("input").on("input", function () {
if (t) {
canvas.remove(t);
}
var textToDraw = $(this).val();
t = new fabric.Text(textToDraw, {
left: 0,
top: 0
});
if (t.width > c.width) {
console.log('scale -> ' + (c.width / t.width));
t.setScaleX(c.width / t.width);
}
canvas.add(t);
});
If you wish to do this on object resize, use the modified event handler like so:
function scaleText() {
console.log("scale=" + t.getScaleX() + " w=" + t.getWidth());
if ((t.width * t.getScaleX()) > c.width) {
t.setScaleX(c.width / t.width);
}
}
$("input").on("input", function () {
if (t) {
canvas.remove(t);
}
var textToDraw = $(this).val();
t = new fabric.Text(textToDraw, {
left: 0,
top: 0
});
scaleText();
t.on("modified", function (options) {
scaleText();
});
canvas.add(t);
});

Related

P5.JS - Filling a shape with a preloaded image, using an object, its properties and its functions

I am trying to create an object and for it to have a shape (an ellipse), an image, and a function that somehow fills the shape with a pre-loaded image.
I already found this post, but I can't get it to work if I try to make it neat and fit it all into an object.
To be clear, this is what it would look like:
let image;
let hypotheticalObject
function preload()
{
image = loadImage('Assets/randomimage.jpeg');
}
function setup()
{
hypotheticalObject = {
objectImage: image,
graphicsBuffer: createGraphics(100, 100),
xPos: width / 2,
yPos: height / 2,
size: 50,
colour: color(255, 255, 255),
shape: function()
{
fill(this.colour),
ellipse(this.xPos, this.yPos, this.size);
},
mask: function()
{
this.graphicsBuffer.shape();
},
render: function()
{
something something
}
}
function draw()
{
hypotheticalObject.render();
}
That's kind of how far I can get, as I can't figure out how to proceed.
There are a couple of issues with your code, namely this:
shape: function() {
// ...
},
mask: function() {
this.graphicsBuffer.shape();
},
You declare shape() on hypotheticalObject but then you try to call it on graphicsBuffer? This isn't how javascript works. You could do something like this:
hypotheticalObject.graphicsBuffer.shape = function() {
// When graphicsBuffer.shape() is called, the this keyword will be bound to graphicsBuffer
// Unfortunately we need a to access members from hypotheticalObject as well, so that is inconvenient:
this.fill(hypotheticalObject.colour),
this.ellipse(hypotheticalObject.xPos, hypotheticalObject.yPos, hypotheticalObject.size);
}
However I don't think that is the most idiomatic approach either. Here's a working solution that tries to stay close to your code:
let img;
let hypotheticalObject;
function preload() {
img = loadImage('https://www.paulwheeler.us/files/windows-95-desktop-background.jpg');
}
function setup() {
createCanvas(windowWidth, windowHeight);
hypotheticalObject = {
objectImage: img,
// The graphics you're using for the mask need to be the size of the image
graphicsBuffer: createGraphics(img.width, img.height),
xPos: width / 2,
yPos: height / 2,
size: 50,
init: function() {
this.maskedImage = createImage(this.objectImage.width, this.objectImage.height);
},
updateShape: function() {
this.graphicsBuffer.clear();
// The fill color is irrelevant so long as it is opaque
this.graphicsBuffer.fill(0);
// These drawing instructions need to happen on the p5.Graphics object
this.graphicsBuffer.ellipse(this.xPos, this.yPos, this.size);
},
render: function() {
this.updateShape();
// When you call mask on an image it changes the image so we need to make a copy
this.maskedImage.copy(
this.objectImage,
0, 0, this.objectImage.width, this.objectImage.height,
0, 0, this.objectImage.width, this.objectImage.height
);
this.maskedImage.mask(this.graphicsBuffer);
// Always draw the image at 0, 0 since the masked portion is already offset
image(this.maskedImage, 0, 0);
}
}
hypotheticalObject.init();
}
let xv = 2;
let yv = 2;
function draw() {
background(255);
hypotheticalObject.xPos += xv;
hypotheticalObject.yPos += yv;
if (hypotheticalObject.xPos > width) {
hypotheticalObject.xPos = width;
xv *= -1;
} else if (hypotheticalObject.xPos < 0) {
hypotheticalObject.xPos = 0;
xv *= -1;
}
if (hypotheticalObject.yPos > height) {
hypotheticalObject.yPos = height;
yv *= -1;
} else if (hypotheticalObject.yPos < 0) {
hypotheticalObject.yPos = 0;
yv *= -1;
}
hypotheticalObject.render();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Update: Animated Gifs
If you want this to work with animated gifs, you have to deal with an idiosyncrasy of p5.js: the fact that it updates gif images only when they are rendered with either the image() function or the texture() function. And that rendering code depends on the drawing context having timing information, and therefor does not work with off screen p5.Graphics buffers. Here's an example:
let img;
let hypotheticalObject;
function preload() {
img = loadImage('https://www.paulwheeler.us/files/Clouds.gif');
}
function setup() {
createCanvas(img.width, img.height);
hypotheticalObject = {
objectImage: img,
// The graphics you're using for the mask need to be the size of the image
graphicsBuffer: createGraphics(img.width, img.height),
xPos: width / 2,
yPos: height / 2,
size: 100,
init: function() {
this.maskedImage = createImage(this.objectImage.width, this.objectImage.height);
},
updateShape: function() {
this.graphicsBuffer.clear();
// The fill color is irrelevant so long as it is opaque
this.graphicsBuffer.fill(0);
// These drawing instructions need to happen on the p5.Graphics object
this.graphicsBuffer.ellipse(this.xPos, this.yPos, this.size);
},
render: function() {
this.updateShape();
// When you call mask on an image it changes the image so we need to make a copy
this.maskedImage.copy(
this.objectImage,
0, 0, this.objectImage.width, this.objectImage.height,
0, 0, this.objectImage.width, this.objectImage.height
);
this.maskedImage.mask(this.graphicsBuffer);
// Always draw the image at 0, 0 since the masked portion is already offset
image(this.maskedImage, 0, 0);
}
}
hypotheticalObject.init();
}
let xv = 2;
let yv = 2;
function draw() {
// In order to get a gif to animate it needs to be passed to either the image() function or the texture() function. And unfortunately it must be the global one, not the version on an off-screen p5.Graphics
// However we don't actually want to display this
image(img, width, height);
background(255);
hypotheticalObject.xPos += xv;
hypotheticalObject.yPos += yv;
if (hypotheticalObject.xPos > width) {
hypotheticalObject.xPos = width;
xv *= -1;
} else if (hypotheticalObject.xPos < 0) {
hypotheticalObject.xPos = 0;
xv *= -1;
}
if (hypotheticalObject.yPos > height) {
hypotheticalObject.yPos = height;
yv *= -1;
} else if (hypotheticalObject.yPos < 0) {
hypotheticalObject.yPos = 0;
yv *= -1;
}
hypotheticalObject.render();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Can't Detect Mouseover on This KineticJS Shape?

I have a KineticJS shape that draws a bezier curve that is wider on one end. It draws correctly, but I can't yet detect a 'mouseover' event on it. I have created a small JSFiddle demo of the anomaly, at:
http://jsfiddle.net/VikR0001/nZYxL/6/
How can I detect 'mouseover' events on this shape?
Thanks very much in advance to all for any info!
var mainLayer;
//bezier curve code:
//http://stackoverflow.com/questions/8325680/how-to-draw-a-bezier-curve-with-variable-thickness-on-an-html-canvas
//draw a bezier curve that gets larger as it flows
//adapted for use with KineticJS
function drawBezierCurve() {
var centerLeft = new Object();
centerLeft.x = 100;
centerLeft.y = 400;
var centerRight = new Object();
centerRight.x = 400;
centerRight.y = 100;
var thicknessLeft = 1;
var thicknessRight = 50;
var color = "#000";
var context = mainLayer.getContext();
var leftUpper = {
x: centerLeft.x,
y: centerLeft.y - thicknessLeft / 2
};
var leftLower = {
x: centerLeft.x,
y: leftUpper.y + thicknessLeft
};
var rightUpper = {
x: centerRight.x,
y: centerRight.y - thicknessRight / 2
};
var rightLower = {
x: centerRight.x,
y: rightUpper.y + thicknessRight
};
var center = (centerRight.x + centerLeft.x) / 2;
var cp1Upper = {
x: center,
y: leftUpper.y
};
var cp2Upper = {
x: center,
y: rightUpper.y
};
var cp1Lower = {
x: center,
y: rightLower.y
};
var cp2Lower = {
x: center,
y: leftLower.y
};
var bezierCurve = new Kinetic.Shape({
drawFunc: function (canvas) {
var context = mainLayer.getContext();
context.fillStyle = color;
context.beginPath();
context.moveTo(leftUpper.x, leftUpper.y);
context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
context.lineTo(rightLower.x, rightLower.y);
context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
context.lineTo(leftUpper.x, leftUpper.y);
context.fill();
canvas.stroke(this);
},
fill: color,
stroke: color,
strokeWidth: 1
});
bezierCurve.on('mouseover', function (evt) {
document.body.style.cursor = "pointer";
$("#debug").html("MOUSEOVER DETECTED."); //<==NEVER CALLED
});
bezierCurve.on('mouseout', function (evt) {
document.body.style.cursor = "default";
$("#debug").html("MOUSEOUT DETECTED."); //NEVER CALLED
});
bezierCurve.setAttrs({
'leftUpper': leftUpper,
'leftLower': leftLower,
'rightUpper': rightUpper,
'rightLower': rightLower,
'cp1Upper': cp1Upper,
'cp2Upper': cp2Upper,
'cp1Lower': cp1Lower,
'cp2Lower': cp2Lower
});
mainLayer.add(bezierCurve);
mainLayer.draw();
$("#debug").html("bezier curve has been drawn onscreen.");
}
$(document).ready(function () {
var stage = new Kinetic.Stage({
container: 'canvasContainer',
width: 500,
height: 500
});
mainLayer = new Kinetic.Layer('main');
stage.add(mainLayer);
mainLayer.draw();
drawBezierCurve();
});
Can you define it as an SVG element, and just give that an onmouseover?
Fixed it! Changes are shown at the jsFiddle link in the original post.
//FIXED!
//OLD VERSION: DOES NOT WORK
// var bezierCurve = new Kinetic.Shape({
// drawFunc: function (canvas) {
// var context = mainLayer.getContext();
// context.fillStyle = color;
// context.beginPath();
// context.moveTo(leftUpper.x, leftUpper.y);
// context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
// context.lineTo(rightLower.x, rightLower.y);
// context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
// context.lineTo(leftUpper.x, leftUpper.y);
// context.closePath();
// context.fill();
// canvas.stroke(this);
// },
// fill: color,
// stroke: color,
// strokeWidth: 1
// });
//NEW VERSION: WORKS!
var bezierCurve = new Kinetic.Shape({
drawFunc: function (canvas) {
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(leftUpper.x, leftUpper.y);
context.bezierCurveTo(cp1Upper.x,cp1Upper.y, cp2Upper.x,cp2Upper.y, rightUpper.x,rightUpper.y);
context.lineTo(rightLower.x, rightLower.y);
context.bezierCurveTo(cp1Lower.x,cp1Lower.y, cp2Lower.x,cp2Lower.y, leftLower.x,leftLower.y);
context.lineTo(leftUpper.x, leftUpper.y);
context.fill();
canvas.stroke(this);
},
fill: color,
stroke: color,
strokeWidth: 3
});

Mouseup event is intermittant from Scaled KineticJS layer

See here for example:
http://jsfiddle.net/tigz_uk/B8UDq/45/embedded/result/
Fiddle code:
http://jsfiddle.net/tigz_uk/B8UDq/45/
Most Relevant snippet:
function whenAreaSelected(stage, layer, image) {
var rect, down = false;
var eventObj = layer;
eventObj.off("mousedown");
eventObj.off("mousemove");
eventObj.off("mouseup");
eventObj.on("mousedown", function (e) {
console.log("Mousedown...");
if (rect) {
rect.remove();
}
var relativePos = getRelativePos ( stage, layer);
down = true;
var r = Math.round(Math.random() * 255),
g = Math.round(Math.random() * 255),
b = Math.round(Math.random() * 255);
rect = new Kinetic.Rect({
x: relativePos.x,
y: relativePos.y,
width: 11,
height: 1,
fill: 'rgb(' + r + ',' + g + ',' + b + ')',
stroke: 'black',
strokeWidth: 4,
opacity: 0.3
});
layer.add(rect);
});
eventObj.on("mousemove", function (e) {
if (!down) return;
var relativePos = getRelativePos ( stage, layer );
var p = rect.attrs;
rect.setWidth(relativePos.x - p.x);
rect.setHeight(relativePos.y - p.y);
layer.draw();
});
eventObj.on("mouseup", function (e) {
console.log("Mouse Up...");
down = false;
var p = rect.attrs;
var s = layer.getScale();
console.log("Rect x: " + p.x + " y: " + p.y + " width: " + p.width + " height: " + p.height + " sx: " + s.x + " sy: " + s.y);
});
}
var stageWidth = 1024;
var stageHeight = 700;
var imageWidth = 1299;
var imageHeight = 1064;
var initialScale = calcScale(imageWidth, imageHeight, stageWidth, stageHeight);
var stage = new Kinetic.Stage({
container: "canvas",
width: stageWidth,
height: stageHeight
});
var layer = new Kinetic.Layer();
var imageObj = new Image();
imageObj.onload = function () {
var diagram = new Kinetic.Image({
x: -500,
y: -500,
image: imageObj,
width: imageWidth,
height: imageHeight
});
layer.add(diagram);
layer.setScale(initialScale);
whenAreaSelected(stage, layer, diagram);
layer.draw();
}
var zoom = function (e) {
var zoomAmount = e.wheelDeltaY * 0.001;
layer.setScale(layer.getScale().x + zoomAmount)
layer.draw();
}
document.addEventListener("mousewheel", zoom, false);
stage.add(layer);
imageObj.src = 'https://dl.dropbox.com/u/746967/Serenity/MARAYA%20GA.png';
It seems to me as though the mouseup event is intermittent at best.
Any idea what's going on here? It also seems to be worse when the Image is offset rather than displayed at 0,0. And I think it relates to the scaling of the layer as it all works okay at scale 1.
Is this a kinetic bug?
Try using layer.drawScene() instead of layer.draw() in your mousemove handler
eventObj.on("mousemove", function (e) {
if (!down) return;
var relativePos = getRelativePos ( stage, layer );
var p = rect.attrs;
rect.setWidth(relativePos.x - p.x);
rect.setHeight(relativePos.y - p.y);
// try drawScene() instead of draw()
layer.drawScene();
});
[Edited based on info forwarded by from user814628 here: Binding MouseMove event causes inconsistency with mouse release event being fired

Why is my canvas drawing area so smal?

I am creating an enyo Control based on a canvas. It should capture mouse or finger events, and draw them onto it. However when I draw onto that canvas it draws only into a smaller part of it.
Look at that jsfiddle as it contains all relevant code.
enyo.kind({
name: "SignatureControl",
kind: "enyo.Canvas",
recording: false,
points: [],
handlers: {
ondown: "startRecord",
onmove: "record",
onup: "stopRecord"
},
startRecord: function(inSender, inEvent) {
this.recording = true;
if(node = this.hasNode()) {
this.points.push({
x: inEvent.clientX - node.offsetLeft,
y: inEvent.clientY - node.offsetTop,
d: false,
p: 1
});
}
this.update();
},
stopRecord: function() {
this.recording = false;
},
record: function(inSender, inEvent) {
if( this.recording ) {
if(node = this.hasNode()) {
this.points.push({
x: inEvent.clientX - node.offsetLeft,
y: inEvent.clientY - node.offsetTop,
d: true,
p: 1
});
}
this.update();
}
},
update: function() {
var canvas = this.hasNode();
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
this.log(ctx.canvas.width);
ctx.lineJoin = "round";
ctx.lineWidth = 1;
var i = this.points.length - 1;
ctx.strokeStyle = "rgba(0,0,0," + this.points[i].p + ")";
ctx.beginPath();
if(this.points[i].d && i){
ctx.moveTo(this.points[i-1].x, this.points[i-1].y);
}else{
ctx.moveTo(this.points[i].x-1, this.points[i].y);
}
ctx.lineTo(this.points[i].x, this.points[i].y);
ctx.closePath();
ctx.stroke();
}
}
});
You can only use the height/width attributes on the canvas, not size it via CSS. Check out this updated fiddle http://jsfiddle.net/AFqvD/4/
The relevant portion is:
{kind: "SignatureControl", attributes: {height: 150, width: 300}}

Capturing the mouse coordinates on every step of a jQuery animation

I'm trying to get the mouse coordinates during a jQuery animation, I'm doing this because I'm working on an interactive plug-in which moves the background image inside a div from covercss property to 100% of it's scale when the user go over the element.
I'm near to completing the plug-in but the animation is buggy because it work on the last position of the mouse fired by mousemove event of jQuery.
Does exists some way to avoid the problem?
This is my situation:
$(settings.selector).hover(function (e) {
$(this).bind('mousemove', setFollowMouse);
}, function () {
$(this).unbind('mousemove', setFollowMouse);
zoomOut();
});
var setFollowMouse = function (e) {
var o = {offsetLeft:this.offsetLeft, offsetTop:this.offsetTop};
if (!settings.bg.zooming_in && settings.bg.current_scale != 100) {
settings.bg.zooming_in = true;
zoomIn(e, o);
} else {
followMouse(e, o);
}
}
var zoomIn = function (e, o) {
$({scale:settings.bg.min_perc}).animate ({
scale:100
},{
easing:settings.zoom_in.easing,
duration:settings.zoom_in.duration,
step:function () {
settings.bg.current_scale = this.scale;
followMouse(e, o);
},
complete:function () {
settings.bg.current_scale = 100;
settings.bg.zooming_in = false;
followMouse(e, o);
}
});
}
var followMouse = function (e, o) {
var m_x = e.pageX - o.offsetLeft;
var m_y = e.pageY - o.offsetTop;
settings.bg.perc_pos_x = ((m_x * 100) / (a_w - bg_w)) + '%';
settings.bg.perc_pos_y = ((m_y * 100) / (a_h - bg_h)) + '%';
var bg_w = getScalePercent(settings.bg.width, settings.bg.current_scale);
var a_w = settings.container.width;
var bg_h = getScalePercent(settings.bg.height, settings.bg.current_scale);
var a_h = settings.container.height;
var bpx = - (bg_w - a_w) * m_x / a_w;
var bpy = - (bg_h - a_h) * m_y / a_h;
$(settings.selector).css({
backgroundPosition:bpx + 'px ' + bpy + 'px'
,backgroundSize:bg_w + 'px ' + bg_h + 'px'
});
}
As you see, I use animation to calculate the progressive scaling of the background-image, and trying to calculating it with the follow mouse method, but if I sto moving the mouse, the animation works with the last mousemove event.pageX and Y mouse position.
I've done this because I have problems with make animation method fluid if I trying to rewrite it continuously by with the mouse.
Should I follow some different way to avoid the bug?
forgive my dodgy math; but this should help!
<html>
<head>
<script type="text/javascript" charset="utf-8">
window.onload = function () {
var img = new Image();
img.src = 'http://wallpapers.free-review.net/wallpapers/23/Autumn_Theme_-_Windows_7_Backgrounds.jpg';
var canvas = document.getElementById("canvas1");
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousemove', onMouseMove, false);
var ctx = canvas.getContext("2d");
var scale = 0.9;
var scaledwidth = canvas.width * scale;
var scaledheight = canvas.height * scale;
var scaledcenterX = scaledwidth /2;
var scaledcenterY = scaledheight /2;
var animloop = setInterval(function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, scaledwidth, scaledheight, canvas.width - scaledcenterX, canvas.height - scaledcenterY, 0, 0, canvas.width, canvas.height);
}, .01);
function onMouseMove(e) {
mouseX = e.clientX - canvas.offsetLeft;
mouseY = e.clientY - canvas.offsetTop;
scale = mouseX/1000;
scaledwidth = canvas.width * scale;
scaledheight = canvas.height * scale;
}
};
</script>
<style>
body {
background: #001;
background-size: cover;
overflow: hidden;
}
#canvas1 {
position: absolute;
top: 0;
left: 0;
padding: 0;
margin: 0;
height: 100% auto;
}
</style>
</head>
<body>
<canvas id="canvas1"></canvas>
</body>
</html>
I've just solved the problem with this simple edit to my code:
var setFollowMouse = function (e) {
settings.mouse.x = e.pageX - this.offsetLeft;
settings.mouse.y = e.pageY - this.offsetTop;
if (!settings.bg.zooming_in && settings.bg.current_scale != 100) {
settings.bg.zooming_in = true;
zoomIn();
} else {
followMouse();
}
}
the old one:
var setFollowMouse = function (e) {
var o = {offsetLeft:this.offsetLeft, offsetTop:this.offsetTop};
if (!settings.bg.zooming_in && settings.bg.current_scale != 100) {
settings.bg.zooming_in = true;
zoomIn(e, o);
} else {
followMouse(e, o);
}
}
this has removed the buggy behavior.

Resources