Related
I have the following code
public draw() : void
{
const x : number = this.ctx.canvas.width * this.x_percentage;
const y : number = this.ctx.canvas.height * this.y_percentage;
const width : number = this.ctx.canvas.width * this.width_percentage;
const height : number = this.ctx.canvas.height * this.height_percentage;
const text_margin : number = 10;
this.ctx.font = "7vh " + this.font_name;
this.ctx.textAlign="center";
this.ctx.textBaseline = "middle";
this.ctx.fillStyle = this.fill_color;
this.ctx.fillRect(x, y, width, height);
this.ctx.fillStyle = this.text_color;
this.ctx.fillText(this.text, x + width / 2, y + height / 2, width - text_margin);
}
This code is being called every frame, however, the text isn't appearing until I manually reload the page. What could be causing this behavior?
Using drawImage, I am trying to do the following with an image that is 1280x720...
Upscale it to 1920x1080
Crop it so that only 600x1080 remains from the centre
I have this so far...
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
img=new Image();
img.onload=function(){
canvas.width=1920;
canvas.height=1080;
ctx.drawImage(img,0,0,img.width,img.height,0,0,1920,1080);
}
img.src="https://dummyimage.com/1280x720/000/fff";
//img.src="https://dummyimage.com/1920x1080/000/fff";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<canvas id="canvas" width=100 height=100></canvas>
The upscaling part I have got working but now I am looking at the crop, anyone have an example I can see?
Is there any benefit from cropping first before rescaling?
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 1920, 1080);
Something like that:
x = (img.width - 600) / 2;
y = (img.height - 1080) / 2;
ctx.drawImage(img, x, y, 600, 1080, 0, 0, 1920, 1080);
but check for the destination area parameters depending on what exactly you want to get.
Clip image to fit canvas
The canvas will clip the image for you.
By default all rendering has a clip region set to the canvas size. Because the clip is performed regardless of the size of the content (all content must be checked against the clip region and is done in hardware (GPU)) rendering the full image is slightly quicker than rendering a portion of the image.
ctx.drawImage(image,x,y); // is the quicker function
ctx.drawImage(image,ix,iy,iw,ih,x,y,w,h); // the slower function
Note; This is not true when the rendered visible destination content is significantly smaller than the image source
Thus to render the image cropped to a smaller canvas you only need to find the center and then render the image at half its size away from that center.
ctx.drawImage(
image, // image to render
(ctx.canvas.width - image.width) / 2, // center sub half image width
(ctx.canvas.height - image.height) / 2 // center sub half image height
);
If you need to up scale first the following will render any size image to fit 1080 height.
const imgW = 1920;
const imgH = 1080;
ctx.drawImage(
image, // image to render
(ctx.canvas.width - imgW) / 2, // center sub half image display width
(ctx.canvas.height - imgH) / 2, // center sub half image display height
imgW, imgH
);
Crop image
If you wish to save memory and crop the image you use a canvas to hold the cropped image.
function cropImageCenter(image,w,h){
const c = document.createElement("canvas");
c.width = w;
c.height = h;
const ctx = c.getContext("2d");
ctx.drawImage(image,(w - image.width) / 2, (h - image.height) / 2);
return c;
}
var img = new Image;
img.src = "imageURL1280by720.jpg";
img.onload = () => {
img = cropImageCenter(img, 600, 1080);
ctx.drawImage(img,0,0); /// render cropped image on to canvas
};
Or to upscale and crop
function scaleCropToHeight(image,w,h){
const c = document.createElement("canvas");
c.width = w;
c.height = h;
const scale = h / image.height;
const ctx = c.getContext("2d");
ctx.drawImage(
image,
(w - image.width * scale) / 2,
(h - image.height * scale) / 2,
image.width * scale,
image.height * scale
);
return c;
}
var img = new Image;
img.src = "imageURL1920by1080.jpg";
img.onload = () => {
img = scaleCropToHeight(img, 600, 1080);
ctx.drawImage(img,0,0); /// render cropped image on to canvas
};
i was trying to move the physics body by changing its coordinate in scheduler and i used this code. if i run this code in browser it will work but after js binding it doesn't work on mac or ios. Physics body doesn't move at all on these devices
init: function{
var mass = 1;
var width = 1, height = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
this.playerBody.getPos().x += 2 * dt;
this.playerBody.getPos().y += 2 * dt;
}
Try removing that getPos() from those lines, leave them at: this.playerBody.p.x += 2 * dt;. I think that's most likely the cause of your problem.
Additionally, avoid manipulating the coordinates yourself and let the physics engine handle everything.
For example, you could assign the velocity by hand like this:
init: function{
var mass = 1;
var width = 1, height = 1;
var vx = 1, vy = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.playerBody.vx = vx;
this.playerBody.vy = vy;
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
}
Or, if you want to give a "bump" to the object in a certain direction, you could use applyImpulse like this:
init: function{
var mass = 1;
var width = 1, height = 1;
var fx = 1, fy = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.playerBody.applyImpuse(cp.v(fx, fy), cp.v(0,0));
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
}
Or, if you want to apply a constant force to the object, change applyImpulse to applyForce in that last example.
Note: the cp.v(0,0) parameter is telling the engine to apply the force to the center of the object, so it should not rotate.
PS: if (and only if) you happen to see some strange behaviour with the physics simulation, look at this answer.
after adding few lines my player started to move in mac
init: function{
var mass = 1;
var width = 1, height = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
var a = this.playerBody.local2World(this.playerBody.p);
a.y += 2 * dt;
a.x += 2 * dt ;
a = this.playerBody.world2Local(a);
this.playerBody.p = a;
}
but i don't have explanation of this code
I have a <div style="border:1px solid border;" /> and canvas, which is drawn using:
context.lineWidth = 1;
context.strokeStyle = "gray";
The drawing looks quite blurry (lineWidth less than one creates even worse picture), and nothing near to the div's border. Is it possible to get the same quality of drawing as HTML using canvas?
var ctx = document.getElementById("canvas").getContext("2d");
ctx.lineWidth = 1;
ctx.moveTo(2, 2);
ctx.lineTo(98, 2);
ctx.lineTo(98, 98);
ctx.lineTo(2, 98);
ctx.lineTo(2, 2);
ctx.stroke();
div {
border: 1px solid black;
width: 100px;
height: 100px;
}
canvas, div {background-color: #F5F5F5;}
canvas {border: 1px solid white;display: block;}
<table>
<tr><td>Line on canvas:</td><td>1px border:</td></tr>
<tr><td><canvas id="canvas" width="100" height="100"/></td><td><div> </div></td></tr>
</table>
I found that setting the canvas size in CSS caused my images to be displayed in a blurry manner.
Try this:
<canvas id="preview" width="640" height="260"></canvas>
as per my post: HTML Blurry Canvas Images
When drawing lines in canvas, you actually need to straddle the pixels. It was a bizarre choice in the API in my opinion, but easy to work with:
Instead of this:
context.moveTo(10, 0);
context.lineTo(10, 30);
Do this:
context.moveTo(10.5, 0);
context.lineTo(10.5, 30);
Dive into HTML5's canvas chapter talks about this nicely
Even easier fix is to just use this:
context = canvas.context2d;
context.translate(0.5, 0.5);
From here on out your coordinates should be adjusted by that 0.5 pixel.
I use a retina display and I found a solution that worked for me here.
Small recap :
First you need to set the size of your canvas twice as large as you want it, for example :
canvas = document.getElementById('myCanvas');
canvas.width = 200;
canvas.height = 200;
Then using CSS you set it to the desired size :
canvas.style.width = "100px";
canvas.style.height = "100px";
And finally you scale the drawing context by 2 :
const dpi = window.devicePixelRatio;
canvas.getContext('2d').scale(dpi, dpi);
The Mozilla website has example code for how to apply the correct resolution in a canvas:
https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Set display size (css pixels).
var size = 200;
canvas.style.width = size + "px";
canvas.style.height = size + "px";
// Set actual size in memory (scaled to account for extra pixel density).
var scale = window.devicePixelRatio; // Change to 1 on retina screens to see blurry canvas.
canvas.width = size * scale;
canvas.height = size * scale;
// Normalize coordinate system to use css pixels.
ctx.scale(scale, scale);
ctx.fillStyle = "#bada55";
ctx.fillRect(10, 10, 300, 300);
ctx.fillStyle = "#ffffff";
ctx.font = '18px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var x = size / 2;
var y = size / 2;
var textString = "I love MDN";
ctx.fillText(textString, x, y);
<canvas id="canvas"></canvas>
Lines are blurred because the canvas virtual size is zoomed to its HTML element actual size. To overcome this issue you need to adjust canvas virtual size before drawing:
function Draw () {
var e, surface;
e = document.getElementById ("surface");
/* Begin size adjusting. */
e.width = e.offsetWidth;
e.height = e.offsetHeight;
/* End size adjusting. */
surface = e.getContext ("2d");
surface.strokeRect (10, 10, 20, 20);
}
window.onload = Draw ()
<!DOCTYPE html>
<html>
<head>
<title>Canvas size adjusting demo</title>
</head>
<body>
<canvas id="surface"></canvas>
</body>
</html>
HTML:
Ok, I've figured this out once and for all. You need to do two things:
place any lines on 0.5 px. Refer to this, which provides a great explanation:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#A_lineWidth_example
There are essentially two heights and two widths associated with the canvas. There is the canvas height and width and then there is the css style height and width of the element. These need to be in sync.
To do this, you need to calculate the css height and width as:
var myCanvasEl = document.getElementById('myCanvas');
var ctx = myCanvasEl.getContext('2d');
myCanvasEl.style.height = myCanvasEl.height / window.devicePixelRatio + "px";
myCanvasEl.style.width = myCanvasEl.width / window.devicePixelRatio + "px";
where myCanvasEl.style.height and myCanvasEl.style.widthis the css styling height and width of the element, while myCanvasEl.height and myCanvasEl.width is the height and width of the canvas.
OLD ANSWER (superseded by above):
This is the best solution I've found in 2020. Notice I've multiplied the devicePixelRatio by 2:
var size = 100;
var scale = window.devicePixelRatio*2;
context.width = size * scale;
cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
context.height = size * scale;
context.scale(scale, scale);
Something else that nobody talked about here when images are scaled (which was my issue) is imageSmoothingEnabled.
The imageSmoothingEnabled property of the CanvasRenderingContext2D interface, part of the Canvas API, determines whether scaled images are smoothed (true, default) or not (false). On getting the imageSmoothingEnabled property, the last value it was set to is returned.
This property is useful for games and other apps that use pixel art. When enlarging images, the default resizing algorithm will blur the pixels. Set this property to false to retain the pixels' sharpness.
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled
To disable it, simply set the properity to false:
ctx.imageSmoothingEnabled = false;
canvas.width=canvas.clientWidth
canvas.height=canvas.clientHeight
To avoid this issue in animation I would like to share a small demo.
Basically I am checking increment values each time & jumping in a set of 1px by removing float values.
HTML:
<canvas id="canvas" width="600" height="600"></canvas>
CSS:
html, body{
height: 100%;
}
body{
font-family: monaco, Consolas,"Lucida Console", monospace;
background: #000;
}
canvas{
position: fixed;
top: 0;
left: 0;
transform: translateZ(0);
}
JS:
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
ctx.translate(0.5, 0.5);
var i = 0;
var iInc = 0.005;
var range = 0.5;
raf = window.requestAnimationFrame(draw);
function draw() {
var animInc = EasingFunctions.easeInQuad(i) * 250;
ctx.clearRect(0, 0, 600, 600);
ctx.save();
ctx.beginPath();
ctx.strokeStyle = '#fff';
var rectInc = 10 + animInc;
// Avoid Half Pixel
rectIncFloat = rectInc % 1; // Getting decimal value.
rectInc = rectInc - rectIncFloat; // Removing decimal.
// console.log(rectInc);
ctx.rect(rectInc, rectInc, 130, 60);
ctx.stroke();
ctx.closePath();
ctx.font = "14px arial";
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.fillText("MAIN BUTTON", 65.5 + rectInc, 35.5 + rectInc);
i += iInc;
if (i >= 1) {
iInc = -iInc;
}
if (i <= 0) {
iInc = Math.abs(iInc);
}
raf = window.requestAnimationFrame(draw);
}
// Easing
EasingFunctions = {
// no easing, no acceleration
linear: function(t) {
return t
},
// accelerating from zero velocity
easeInQuad: function(t) {
return t * t
},
// decelerating to zero velocity
easeOutQuad: function(t) {
return t * (2 - t)
},
// acceleration until halfway, then deceleration
easeInOutQuad: function(t) {
return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
},
// accelerating from zero velocity
easeInCubic: function(t) {
return t * t * t
},
// decelerating to zero velocity
easeOutCubic: function(t) {
return (--t) * t * t + 1
},
// acceleration until halfway, then deceleration
easeInOutCubic: function(t) {
return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
},
// accelerating from zero velocity
easeInQuart: function(t) {
return t * t * t * t
},
// decelerating to zero velocity
easeOutQuart: function(t) {
return 1 - (--t) * t * t * t
},
// acceleration until halfway, then deceleration
easeInOutQuart: function(t) {
return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
},
// accelerating from zero velocity
easeInQuint: function(t) {
return t * t * t * t * t
},
// decelerating to zero velocity
easeOutQuint: function(t) {
return 1 + (--t) * t * t * t * t
},
// acceleration until halfway, then deceleration
easeInOutQuint: function(t) {
return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
}
}
A related issue could be that you're setting the <canvas>'s height and width from CSS or other sources. I'm guessing it scales the canvas and associated drawings. Setting the <canvas> size using the height and width property (either from the HTML tag or a JS script) resolved the error for me.
Here is my solution: set width and height for canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
Also set in css, so it will not overflow from its parent
canvas {
width: 100%
height: 100%
}
Although LittleJoe's solution worked perfect on desktop it didn't work on mobile because on iphone 11 pro for example the dpi is 3 so I had to set width/height based on dpi. At the end it worked:
let width = 100, height = 100;
const dpi = window.devicePixelRatio;
canvas = document.getElementById('myCanvas');
canvas.width = width * dpi;
canvas.height = height * dpi;
canvas.style.width = width + "px";
canvas.style.height = width + "px";
canvas.getContext('2d').scale(dpi, dpi);
in order to get rid of the blurryness you need to set the size of the canvas in two manners:
first withcanvas.width = yourwidthhere;
and canvas.height = yourheighthere;
second by setting the css attribute either by js or a stylesheet
HTML:
<canvas class="canvas_hangman"></canvas>
JS:
function setUpCanvas() {
canvas = document.getElementsByClassName("canvas_hangman")[0];
ctx = canvas.getContext('2d');
ctx.translate(0.5, 0.5);
// Set display size (vw/vh).
var sizeWidth = 80 * window.innerWidth / 100,
sizeHeight = 100 * window.innerHeight / 100 || 766;
// console.log(sizeWidth, sizeHeight);
// Setting the canvas height and width to be responsive
canvas.width = sizeWidth;
canvas.height = sizeHeight;
canvas.style.width = sizeWidth;
canvas.style.height = sizeHeight;
}
window.onload = setUpCanvas();
This perfectly sets up your HTML canvas to draw on, and in a responsive manner too :)
I'm having a little bit of a problem scaling my images to a properly predefined size. I was wondering - since it is purely mathematics, if there's some sort of common logical algorithm that works in every language (PHP, ActionScript, Javascript etc.) to scale images proportionally.
I'm using this at the moment:
var maxHeight = 300;
var maxWidth = 300;
var ratio:Number = height / width;
if (height > maxHeight) {
height = maxHeight;
width = Math.round(height / ratio);
}
else if(width > maxWidth) {
width = maxWidth;
height = Math.round(width * ratio);
}
But it doesn't work properly. The images scales proportionately, sure enough, but the size isn't set at 300 (either in width or in height). It kind of makes sense, but I was wondering if there's a fool-proof, easy way to scale images proportionally.
ratio = MIN( maxWidth / width, maxHeight/ height );
width = ratio * width;
height = ratio * height;
Make sure all divides are floating-point.
Dark Shikari has it. Your solution as stated in the question fails because you aren't first establishing which dimenson's size-to-maxsize ratio is greater and then reducing both dimensions by that greater ratio.
Your current solution's use of a serial, conditional analysis of one potential dimensional violation and then the other won't work.
Note also that if you want to upscale images, your current solution won't fly, and Dark Shikari's again will.
I'd recommend not writing this code yourself; there are myriads of pixel-level details that take a serious while to get right. Use ImageMagick, it's the best graphics library out there.
Here is how I do it:
+ (NSSize) scaleHeight:(NSSize)origSize
newHeight:(CGFloat)height {
NSSize newSize = NSZeroSize;
if ( origSize.height == 0 ) return newSize;
newSize.height = height;
CGFloat factor = ( height / origSize.height );
newSize.width = (origSize.width * factor );
return newSize;
}
+ (NSSize) scaleWidth:(NSSize)origSize
newWidth:(CGFloat)width {
NSSize newSize = NSZeroSize;
if ( origSize.width == 0 ) return newSize;
newSize.width = width;
CGFloat factor = ( width / origSize.width );
newSize.height = (origSize.height * factor );
return newSize;
}
Here's a function I've developed for my site, you might want to use. It's based on your answer above.
It does other things not only the image processing - please remove everything which is unnecessary.
<?php
$thumb_width = 500;
$thumb_height = 500;
if ($handle = opendir('to-do')) {
echo "Directory handle: $handle<br />";
echo "Files:<br /><br />";
/* This is the correct way to loop over the directory. */
while (false !== ($file = readdir($handle))) {
if ( ($file != ".") && ($file != "..") ){
echo "$file";
$original_path = "to-do/" . $file;
$source_image = ImageCreateFromJPEG( $original_path );
$thumb_width = $thumb_width;
$thumb_height = $thumb_height;
// Create the image, of the required size
$thumbnail = imagecreatetruecolor($thumb_width, $thumb_height);
if($thumbnail === false) {
//creation failed -- probably not enough memory
return null;
}
// Fill the image with a white color (this will be visible in the padding around the image,
// if the aspect ratios of the image and the thumbnail do not match)
// Replace this with any color you want, or comment it out for black.
// I used grey for testing =)
$fill = imagecolorallocate($thumbnail, 255, 255, 255);
imagefill($thumbnail, 0, 0, $fill);
// Compute resize ratio
$hratio = $thumb_height / imagesy($source_image);
$wratio = $thumb_width / imagesx($source_image);
$ratio = min($hratio, $wratio);
// If the source is smaller than the thumbnail size,
// Don't resize -- add a margin instead
// (that is, dont magnify images)
if ($ratio > 1.0)
$ratio = 1.0;
// Compute sizes
$sy = floor(imagesy($source_image) * $ratio);
$sx = floor(imagesx($source_image) * $ratio);
// Compute margins
// Using these margins centers the image in the thumbnail.
// If you always want the image to the top left, set both of these to 0
$m_y = floor(($thumb_height - $sy) / 2);
$m_x = floor(($thumb_width - $sx) / 2);
// Copy the image data, and resample
// If you want a fast and ugly thumbnail, replace imagecopyresampled with imagecopyresized
if (!imagecopyresampled($thumbnail, $source_image,
$m_x, $m_y, //dest x, y (margins)
0, 0, //src x, y (0,0 means top left)
$sx, $sy,//dest w, h (resample to this size (computed above)
imagesx($source_image), imagesy($source_image)) //src w, h (the full size of the original)
) {
//copy failed
imagedestroy($thumbnail);
return null;
}
/* Set the new file name */
$thumbnail_file_name = $file;
/* Apply changes on the original image and write the result on the disk */
ImageJPEG( $thumbnail, $complete_path . "done/" . $thumbnail_file_name );
unset($source_image);
unset($thumbnail);
unset($original_path);
unset($targeted_image_size);
echo " done<br />";
}
}
closedir($handle);
}
?>
well I made this function to scale proportional, it uses a given width, height, and optionally the max width/height u want (depends on the given width and height)
function scaleProportional($img_w,$img_h,$max=50)
{
$w = 0;
$h = 0;
$img_w > $img_h ? $w = $img_w / $img_h : $w = 1;
$img_h > $img_w ? $h = $img_h / $img_w : $h = 1;
$ws = $w > $h ? $ws = ($w / $w) * $max : $ws = (1 / $h) * $max;
$hs = $h > $w ? $hs = ($h / $h) * $max : $hs = (1 / $w) * $max;
return array(
'width'=>$ws,
'height'=>$hs
);
}
usage:
$getScale = scaleProportional(600,200,500);
$targ_w = $getScale['width']; //returns 500
$targ_h = $getScale['height']; //returns 16,6666667