Section wise division on canvas loop - javascript

I'm using Canvas animation for image sequences on the scroll.
And this animation is working fine but I need to reduce the loading time because I'm using almost 884 images for this animation.
so I need to add section-wise division in this loop. for example, the first 100 images will upload on start and after 5 seconds next 100 images will upload. so every after 5 seconds next 100 images will upload til 884 images.
please check my code and give me suggestions or help me please. thank you in advance.
const html = document.documentElement;
const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");
const frameCount = 884;
const currentFrame = index => (
`compressed/${index.toString().padStart(9, 'web_0000')}.webp`
)
const preloadImages = () => {
for (let i = 1; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
}
};
const img = new Image()
img.src = currentFrame(1);
canvas.width= window.innerWidth;
canvas.height=window.innerHeight;
img.onload=function(){
scaleToFill(this);
}
function scaleToFill(img){
var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
var x = (canvas.width / 2) - (img.width / 2) * scale;
var y = (canvas.height / 2) - (img.height / 2) * scale;
context.drawImage(img, x, y, img.width * scale, img.height * scale);
}
const updateImage = index => {
img.src = currentFrame(index);
}
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
preloadImages()
HTML
<canvas id="hero-lightpass"></canvas>

Related

Adding a section-wise division on loop

I'm using Canvas animation for image sequences on the scroll.
And this animation is working fine but I need to reduce the loading time because I'm using almost 884 images for this animation.
So, how can I add section-wise division in this loop? For example, the first 100 images will upload on start and after 5 seconds next 100 images will upload. So every after 5 seconds next 100 images will upload til 884 images.
const html = document.documentElement;
const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");
const frameCount = 884;
const currentFrame = index => (
`compressed/${index.toString().padStart(9, 'web_0000')}.webp`
)
const preloadImages = () => {
for (let i = 1; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
}
};
const img = new Image()
img.src = currentFrame(1);
canvas.width= window.innerWidth;
canvas.height=window.innerHeight;
img.onload=function(){
scaleToFill(this);
}
function scaleToFill(img){
var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
var x = (canvas.width / 2) - (img.width / 2) * scale;
var y = (canvas.height / 2) - (img.height / 2) * scale;
context.drawImage(img, x, y, img.width * scale, img.height * scale);
}
const updateImage = index => {
img.src = currentFrame(index);
}
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
preloadImages()
HTML
<canvas id="hero-lightpass"></canvas>
You can do that with an interval ...
We add a new variable loadedImages and change the preloadImages
let loadedImages = 0
let loadedImages = 0
const frameCount = 884;
const currentFrame = index => (
`compressed/${index.toString().padStart(9, 'web_0000')}.webp`
)
const preloadImages = () => {
for (let i = loadedImages + 1; i < Math.min(loadedImages + 100, frameCount); i++) {
const img = new Image();
img.src = currentFrame(i);
loadedImages++
}
};
setInterval(preloadImages, 5000)
...
preloadImages()

Nodejs warp image

I have two images locally. First image is a cup and another is a banner.
How do I warp and banner with the cup and then output a new file?
I've found the below function but how do I convert it to use in nodejs.
Wrap an image around a cylindrical object in HTML5 / JavaScript
function canvas3() {
var canvas = document.getElementById("canvas3");
var ctx = canvas.getContext("2d");
var productImg = new Image();
productImg.onload = function() {
var iw = productImg.width;
var ih = productImg.height;
canvas.width = iw;
canvas.height = ih;
ctx.drawImage(productImg, 0, 0, productImg.width, productImg.height,
0, 0, iw, ih);
loadUpperIMage()
};
productImg.src = "http://res.cloudinary.com/pussyhunter/image/upload/h_350/right_handle_cup_dsdhr7.jpg"
function loadUpperIMage() {
var img = new Image();
img.src = "http://res.cloudinary.com/pussyhunter/image/upload/v1488184107/500_F_97150423_M13q2FeAUZxxIx6CaPixHupprmyiVVli_skh6fe.jpg"
img.onload = function() {
var iw = img.width;
var ih = img.height;
//alert(iw)
var xOffset = 102, //left padding
yOffset = 110; //top padding
var a = 75.0; //image width
var b = 10; //round ness
var scaleFactor = iw / (3 * a);
// draw vertical slices
for (var X = 0; X < iw; X += 1) {
var y = b / a * Math.sqrt(a * a - (X - a) * (X - a)); // ellipsis equation
ctx.drawImage(img, X * scaleFactor, 0, iw / 1.5, ih, X + xOffset, y + yOffset, 1, 174);
}
};
}
};
You can use canvas package in node.js too.
https://www.npmjs.com/package/canvas
import {loadImage, createCanvas } from "canvas";
...
router.get("/test", async (req: Request, res: Response) => {
const myImg = await loadImage('https://res.cloudinary.com/pussyhunter/image/upload/h_350/right_handle_cup_dsdhr7.jpg');
const canvas = createCanvas(400, 400);
const ctx = canvas.getContext("2d")
ctx.clearRect(0, 0, 500, 500)
const myImg2 = await loadImage('https://i.stack.imgur.com/NkKnV.png');
canvas.getContext("2d").drawImage(myImg,0,0,myImg.width, myImg.height);
// flat
// canvas.getContext("2d").drawImage(myImg2,myImg.width/2-30,myImg.height/2-30,60,60);
//rounded
draw(ctx, myImg2)
const buffer = canvas.toBuffer('image/jpeg');
// write a copy to the disk
//fs.writeFileSync("c:\\dev\\image.png", buffer);
res.write(buffer);
res.status(200);
res.end();
});
function draw(ctx, image) {
const iw = image.width;
const ih = image.height;
const xOffset = 102; //left padding
const yOffset = 110; //top padding
const a = 75.0; //image width
const b = 10; //round ness
const scaleFactor = iw / (4 * a);
// draw vertical slices
for (let X = 0; X < iw; X += 1) {
const y = b / a * Math.sqrt(a * a - (X - a) * (X - a)); // ellipsis equation
ctx.drawImage(image, X * scaleFactor, 0, iw / 9, ih, X + xOffset, y + yOffset, 1, 174);
}
}
And the result;

How to return value within onload function [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
I have base64SquareCrop function which resize images, the problem here that I can not return value from onload function and it was returned with undefined:
async function base64SquareCrop(imgbase64, size = 224) {
const img = document.createElement("img");
await img.setAttribute("src", imgbase64);
let width = 1240;
let height = 698;
const min = Math.min(width, height);
const scale = size / min;
const scaledW = Math.ceil(width * scale);
const scaledH = Math.ceil(height * scale);
const dx = scaledW - size;
const dy = scaledH - size;
canvasImage.width = canvasImage.height = size;
const ctx_img = canvasImage.getContext("2d");
img.onload = function() { ctx_img.drawImage(
img,
~~(dx / 2) * -1,
~~(dy / 2) * -1,
scaledW,
scaledH
);
return canvasImage.toDataURL("image/png");
}
}
You have to use promises to wait for the onload event.
function base64SquareCrop(imgbase64, size = 224) {
const img = document.createElement("img");
await img.setAttribute("src", imgbase64);
let width = 1240;
let height = 698;
const min = Math.min(width, height);
const scale = size / min;
const scaledW = Math.ceil(width * scale);
const scaledH = Math.ceil(height * scale);
const dx = scaledW - size;
const dy = scaledH - size;
canvasImage.width = canvasImage.height = size;
const ctx_img = canvasImage.getContext("2d");
var loadPromise = new Promise(function (resolve) {
img.onload = function() {
ctx_img.drawImage(
img,
~~(dx / 2) * -1,
~~(dy / 2) * -1,
scaledW,
scaledH
);
resolve(canvasImage.toDataURL("image/png"));
}
}, function (reject) {reject("ERROR!")});
return loadPromise;
}
From there, you'll have to await base64SquareCrop(...) inside of an asynchronous function. I don't know any better way to do it.

Does the animation run always with the same speed?

I have this portion of JavaScript code which draws a bar (for let's say a bar diagram):
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerHeight / 3 * 4;
canvas.height = window.innerHeight;
const xCharCnt = window.innerHeight / 3 * 4 / 48;
const yCharCnt = window.innerHeight / 34;
const foreground1 = "#AF5BEC";
let offset = 0;
const barHeight = 20;
const speed = 4;
const limit = yCharCnt * barHeight;
let requestId = window.requestAnimationFrame(render);
function render() {
requestId = window.requestAnimationFrame(render);
// Clear screen
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw bar
ctx.fillStyle = foreground1;
ctx.fillRect(xCharCnt * 7, yCharCnt * 28, xCharCnt, -offset);
offset = offset + speed;
// Cancel animation
if (offset >= limit) window.cancelAnimationFrame(requestId);
}
Questions:
a) Is it the right way to do an animation?
b) Does the animation run always with the same speed, regardless the resolution of the device it's running on?
It will be framerate dependent, so it will differ between devices.
The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation. requestAnimationFrame() calls are paused in most browsers when running in background tabs or hidden s in order to improve performance and battery life.
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
The above link also gives a suggestion on how you should code your animations so they do always run at the same speed. Translated to your code that would look like:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerHeight / 3 * 4;
canvas.height = window.innerHeight;
const xCharCnt = window.innerHeight / 3 * 4 / 48;
const yCharCnt = window.innerHeight / 34;
const foreground1 = "#AF5BEC";
let offset = 0;
const barHeight = 20;
const speed = 4;
const limit = yCharCnt * barHeight;
let requestId = window.requestAnimationFrame(render);
let previousTimestamp;
function render(timestamp) {
if(previousTimestamp === undefined) {
previousTimestmap = timestamp;
}
const delta = timestamp - previousTimestamp;
offset = Math.min(offset + (speed * delta), limit);
// Clear screen
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw bar
ctx.fillStyle = foreground1;
ctx.fillRect(xCharCnt * 7, yCharCnt * 28, xCharCnt, -offset);
// Cancel animation
requestId = window.requestAnimationFrame(render);
if (offset >= limit) window.cancelAnimationFrame(requestId);
previousTimestmap = timestamp;
}

How to draw an image on canvas as if it has the "background-size: cover" and "background-position: 100% 100%" property applied to it [duplicate]

I'm drawing images on a canvas like this:
ctx.drawImage(data[i].image, data[i].pos.x, data[i].pos.y, data[i].pos.w, data[i].pos.h);
The picture is getting stretched and I don't want this. How can I simulate the CSS property?
background-size: cover
when drawing images on the canvas?
http://www.w3schools.com/cssref/playit.asp?filename=playcss_background-size&preval=cover
see the difference between 100% 100% (what I currently have) and cover (my goal).
It's a bit more complicated to get a cover functionality, though here is one solution for this:
Updated 2016-04-03 to address special cases. Also see #Yousef's comment below.
/**
* By Ken Fyrstenberg Nilsen
*
* drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
*
* If image and context are only arguments rectangle will equal canvas
*/
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// default offset is center
offsetX = typeof offsetX === "number" ? offsetX : 0.5;
offsetY = typeof offsetY === "number" ? offsetY : 0.5;
// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, // new prop. width
nh = ih * r, // new prop. height
cx, cy, cw, ch, ar = 1;
// decide which gap to fill
if (nw < w) ar = w / nw;
if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
Now you can call it like this:
drawImageProp(ctx, image, 0, 0, width, height);
and it will scale the image proportionally to fit inside in that container.
Use the two last parameters to offset the image:
var offsetX = 0.5; // center x
var offsetY = 0.5; // center y
drawImageProp(ctx, image, 0, 0, width, height, offsetX, offsetY);
If you're looking for a simpler solution that will work for most cases, and also includes css contain-like functionality, try this:
function fit(contains) {
return (parentWidth, parentHeight, childWidth, childHeight, scale = 1, offsetX = 0.5, offsetY = 0.5) => {
const childRatio = childWidth / childHeight
const parentRatio = parentWidth / parentHeight
let width = parentWidth * scale
let height = parentHeight * scale
if (contains ? (childRatio > parentRatio) : (childRatio < parentRatio)) {
height = width / childRatio
} else {
width = height * childRatio
}
return {
width,
height,
offsetX: (parentWidth - width) * offsetX,
offsetY: (parentHeight - height) * offsetY
}
}
}
export const contain = fit(true)
export const cover = fit(false)
slightly modified version of intrinsic-scale to include scale and offset
Usage:
import {cover, contain} from './intrinsic-scale'
const {
offsetX,
offsetY,
width,
height
} = cover(parentWidth, parentHeight, imageWidth, imageHeight)
// or...
const {
offsetX,
offsetY,
width,
height
} = contain(parentWidth, parentHeight, imageWidth, imageHeight)
ctx.drawImage(image, offsetX, offsetY, width, height)
Canvas image fitting canvas like background-size cover and contain
const coverImg = (img, type) => {
const imgRatio = img.height / img.width
const winRatio = window.innerHeight / window.innerWidth
if ((imgRatio < winRatio && type === 'contain') || (imgRatio > winRatio && type === 'cover')) {
const h = window.innerWidth * imgRatio
ctx.drawImage(img, 0, (window.innerHeight - h) / 2, window.innerWidth, h)
}
if ((imgRatio > winRatio && type === 'contain') || (imgRatio < winRatio && type === 'cover')) {
const w = window.innerWidth * winRatio / imgRatio
ctx.drawImage(img, (win.w - w) / 2, 0, w, window.innerHeight)
}
}
Codepen demo
Usage:
coverImg(myImage, 'cover');
coverImg(myImage, 'contain');
Try this (based on #daviestar's answer):
getCanvas = function(img, w, h) {
// Create canvas
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
// Set width and height
canvas.width = w;
canvas.height = h;
// Draw the image
let containerRatio = h / w;
let width = img.naturalWidth;
let height = img.naturalHeight;
let imgRatio = height / width;
if (imgRatio > containerRatio) { // image's height too big
height = width * containerRatio;
} else { // image's width too big
width = height / containerRatio;
}
let s = {
width: width,
height: height,
offsetX: (img.naturalWidth - width) * .5,
offsetY: (img.naturalHeight - height) * .5
};
ctx.drawImage(img, s.offsetX, s.offsetY, s.width, s.height, 0, 0, w, h);
return canvas;
}
The below script is a reference from #user1693593, and I do some modifications as below
use Destructuring assignment to make the options more flexible
The script provides a runnable example, so you don't worry the link is not found.
<script>
function clamp(num, a, b) {
return num > b ? b
: num < a ? a
: num
}
/**
* #param {CanvasRenderingContext2D} ctx
* #param {HTMLImageElement} img
* #param {Number} dx
* #param {Number} dy
* #param {Number} dw
* #param {Number} dh
* #param {Number} offsetX
* #param {Number} offsetY
* */
function drawImageProp(ctx, img, {dx = 0, dy = 0, dw = undefined, dh = undefined, offsetX = 0.5, offsetY = 0.5}) {
dw = dw ?? ctx.canvas.width
dh = dh ?? ctx.canvas.height
// keep bounds [0.0, 1.0]
offsetX = clamp(offsetX, 0, 1)
offsetY = clamp(offsetY, 0, 1)
let iw = img.width,
ih = img.height,
ratio = Math.min(dw / iw, dh / ih),
nw = iw * ratio,
nh = ih * ratio, // new prop. height
sx, sy, sw, sh, ar = 1;
// decide which gap to fill
if (nw < dw) ar = dw / nw
if (Math.abs(ar - 1) < 1e-14 && nh < dh) ar = dh / nh // updated
nw *= ar
nh *= ar
// source rectangle
sw = iw / (nw / dw)
sh = ih / (nh / dh)
sx = (iw - sw) * offsetX
sy = (ih - sh) * offsetY
// make sure source rectangle is valid
if (sx < 0) sx = 0
if (sy < 0) sy = 0
if (sw > iw) sw = iw
if (sh > ih) sh = ih
img.onload = (event) => {
// fill image in dest. rectangle
ctx.drawImage(event.target, sx, sy, sw, sh, dx, dy, dw, dh)
}
}
// Test Only 👇
window.onload = () => {
const testArray = [
["Default", (ctx, img)=>{drawImageProp(ctx, img, {})}],
["Full", (ctx, img)=>{drawImageProp(ctx, img, {offsetY:0})}], // If you don't want to it cutting from two sides, you can set "offsetY = 0" then it will cut a large part from the bottom
["1/2",(ctx, img)=>{drawImageProp(ctx, img, {dx:window.innerWidth/4, dy:window.innerHeight/4, dw: window.innerWidth/2, dh:window.innerHeight/2})}],
["3/4",(ctx, img)=>{drawImageProp(ctx, img, {dx:window.innerWidth/8, dy:window.innerHeight/8, dw: window.innerWidth*3/4, dh:window.innerHeight*3/4})}]
]
for (const [testName, drawFunc] of testArray) {
const btn = document.createElement("button")
btn.innerText = testName
btn.onclick = () => {
document.querySelectorAll(`canvas`).forEach(e=>e.remove()) // remove old data
const img = new Image(590, 470)
img.src = "https://upload.wikimedia.org/wikipedia/commons/b/bd/Test.svg"
const canvas = document.createElement("canvas")
canvas.width = window.innerWidth
canvas.height = window.innerHeight
const ctx = canvas.getContext("2d")
drawFunc(ctx, img)
document.querySelector(`body`).append(canvas)
}
document.querySelector(`body`).append(btn)
}
}
</script>
For whom it may concern, I was looking for this answer but had to develop my own solution written in typescript, with file type solution:
const resizeBase64Img = (
base64: string, // image base64
type: string, // image mime type
newWidth: number, // new image width
newHeight: number // new image height
) =>
new Promise<string>((resolve, reject) => {
// rejects promise if no document variable
if (!document) {
reject('document is not available');
}
// create a brand new canvas element
const canvasElement = document.createElement('canvas');
// set its width
canvasElement.width = newWidth;
// and height
canvasElement.height = newHeight;
// adjust style for width
canvasElement.style.width = newWidth.toString() + 'px';
// and height
canvasElement.style.height = newHeight.toString() + 'px';
// get canvas context
const context = canvasElement.getContext('2d') as CanvasRenderingContext2D;
// create nem image
const img = new Image();
// set it's source from base64 argument
img.src = base64;
// when it loads
img.onload = () => {
// get the imgRatioType: landscape or portrait
const imgRatioType =
img.width / img.height >= 1 ? 'landscape' : 'portrait'; // 1 > landscape ; 1 < portrait
// if image is a portrait, then what's limiting it's sWidth is the img width. Otherwise it'll be the img height
const sWidth = imgRatioType === 'portrait' ? img.width : img.height;
// if image is a landscape, then what's limiting it's sHeight is the img height. Otherwise it'll be the img width
const sHeight = imgRatioType === 'landscape' ? img.height : img.width;
// if landscape, the image width is equals to 2 equal sx plus the sWidth. Otherwise, it should be 0.
const sx = imgRatioType === 'landscape' ? (img.width - sWidth) / 2 : 0;
// if portrait, the image height is equals to 2 equal sy plus the sHeight. Otherwise, it should be 0.
const sy = imgRatioType === 'portrait' ? (img.height - sHeight) / 2 : 0;
// destination canvas should have no space on dx
const dx = 0;
// neither on dy
const dy = 0;
// it's dWidth should be equal to the canvas width
const dWidth = canvasElement.width;
// and the same applies to dHeight with height
const dHeight = canvasElement.height;
// use clearRect for setting pixels in a clear rectangle with defined width and height
context.clearRect(0, 0, canvasElement.width, canvasElement.height);
// then draws the image
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
// resolve the promise with canvas toDataUrl method using type argument for mime
resolve(canvasElement.toDataURL(type));
};
img.onerror = (err) => {
// if image fails to load, rejects promise
reject(err);
};
});

Categories