Sharp NodeJS: How to add an overlay to an animated gif? - node.js

I'm trying to make a composition on an animated gif. The overlay appears only for a small instance. I suppose that's because it's being applied only on one frame/page.
So my question is, how do I apply the overlay to all the pages/frames in the gif with sharp? This is the code:
let image = await sharp(req.file.buffer, { animated: true, pages: -1 });
const metadata = await image.metadata();
await image
.composite([
{ input: logo, left: Math.floor(metadata.width * 0.1), top: Math.floor(metadata.height * 0.1) },
])
.toFile(filepath);
Thanks in advance!

Same issue here, fixed by using tile and gravity.
.composite([
{
input: logo,
tile: true, gravity: 'northwest'
}
])

Related

How to make one image from multiple images in node js

I want to make one image from multiples images in node js. I found the NPM package "spritesmith" for this. It combines the image and returns buffer representation of image, but I want to have more control over the final image. I want the input images to be display on specific position(pixel perfect) in final image, and also wants to downscale images. I have done some search, but found nothing about it. Can anyone please throw some possibillites for doing this thing. I want to combine about 10,000 images with 10 x 10 px for each image. What I'm planning is when a new image is uploaded by user, I want to add it to previously giant image.
Use the package Sharp.
images //your array of 10000 images
const emptyImage = await sharp({
create: {
width: 1000,
height: 1000,
channels: 3,
background: { r: 255, g: 255, b: 255 },
},
})
.composite(
images.map((image, index)=>({
input: image,
left: index%100,
top: parseInt(index/100),
width: 10,
height: 10,
}))
)
.toFormat('jpeg', { quality: 100 })
.toBuffer();

Watermark with sharp

I have a image and i put watermark there, but i want that watermark hs a opacity like 30%.
My code:
let sharp = require('sharp');
let buffer = null;
await sharp(image)
.composite([{ input: './logo.png', gravity: 'center' }])
.sharpen()
.withMetadata()
.toBuffer()
.then(function(outputBuffer) {
buffer = outputBuffer;
});
return buffer;
How can i say that logo has opacity like 30%?
Your need to change the opacity of "logo.png" use blur, then compose it like code snipp above.
Sharp cant do this yet.
Alternative method is use canvas to change image opacity before composite.
https://github.com/Automattic/node-canvas/blob/master/examples/globalAlpha.js

Overlaying png onto SVG using Sharp?

I've got a set of png files, that are 4500x5400.
What I am trying to do is the following:
Draw a circle that is 485x485 at 300dpi
Overlay the png inside the circle, so that its resized (scaled)
I've been messing around all night but I'm not getting very far:
I've got the code for my circle:
'<svg height="485" width="485"><circle cx="242.5" cy="242.5" r="242.5" fill="#3a4458"/></svg>'
and then some resize code that resizes my png, and masks it.
sharp(`${toConvert}/${file}`)
.trim()
.resize(485, 485)
.embed()
.overlayWith('overlay.png', { cutout: true } )
.toFile(`./converted/${file}-pop.png`)
.catch(err => {
console.log(err)
})
Does anyone know how I can combine the 2 so I can get a coloured circle with my png in it?
For reference, Sharp is an image processing library: https://github.com/lovell/sharp
This is from my project that has a circle cover pages,
First creating a circle SVG with the help of SVG.js you can use background property to get the colored circle.
const window = require('svgdom');
const SVG = require('svg.js')(window);
const document = window.document;
const ShapeUtil = {
drawCircle: function (width, height) {
return new Promise(function (resolve, reject) {
var circleDraw = SVG(document.documentElement).size(width, height);
circleDraw.clear();
circleDraw.viewbox(0, 0, width, height);
circleDraw.circle(width).cx(Math.abs(width / 2)).cy(Math.abs(width / 2));
resolve(circleDraw.svg());
});
}
};
Then overlay created SVG dom to cropped image with Sharp.io :
ShapeUtil.drawCircle( width, height)
.then(function (resultSVG) {
sharp(croppedImage)
.overlayWith(new Buffer(resultSVG), {cutout: true})
.png().toBuffer();
}
For anyone reading this in 2022, svg.js has changed a bit. I'm using Typescript and this is what I did to get it working (based off of #erhanasikoglu's answer):
const { createSVGWindow } = require('svgdom');
const window = createSVGWindow();
import { registerWindow, SVG } from '#svgdotjs/svg.js';
registerWindow(window, window.document);
export const ShapeUtil = {
drawCircle(width: number, height: number) {
var circleDraw = SVG().size(width, height);
circleDraw.clear();
circleDraw.viewbox(0, 0, width, height);
circleDraw
.circle(width)
.cx(Math.abs(width / 2))
.cy(Math.abs(width / 2));
return circleDraw.svg();
},
};
Note that #svgdotjs/svg.js is now the latest package for svgdotjs. It also has typings which is nice. :D

Phaser 3. Change dimensions of the game during runtime

Hi please help me to find out how trully responsive game can be created with Phaser3.
Respnsiveness is critical because game (representation layer of Blockly workspace) should be able to be exapanded to larger portion on screen and shrinked back many times during the session.
The question is How I can change dimentions of the game in runtime?
--- edited ---
It turns out there is pure css solution, canvas can be ajusted with css zoom property. In browser works well (no noticeable effect on performance), in cordova app (android) works too.
Here is Richard Davey's answer if css zoom can break things:
I've never actually tried it to be honest. Give it a go and see what
happens! It might break input, or it may carry on working. That (and
possibly the Scale Manager) are the only things it would break,
though, nothing else is likely to care much.
// is size of html element size that needed to fit
let props = { w: 1195, h: 612, elementId: 'myGame' };
// need to know game fixed size
let gameW = 1000, gameH = 750;
// detect zoom ratio from width or height
let isScaleWidth = gameW / gameH > props.w / props.h ? true : false;
// find zoom ratio
let zoom = isScaleWidth ? props.w / gameW : props.h / gameH;
// get DOM element, props.elementId is parent prop from Phaser game config
let el = document.getElementById(props.elementId);
// set css zoom of canvas element
el.style.zoom = zoom;
Resize the renderer as you're doing, but you also need to update the world bounds, as well as possibly the camera's bounds.
// the x,y, width and height of the games world
// the true, true, true, true, is setting which side of the world bounding box
// to check for collisions
this.physics.world.setBounds(x, y, width, height, true, true, true, true);
// setting the camera bound will set where the camera can scroll to
// I use the 'main' camera here fro simplicity, use whichever camera you use
this.cameras.main.setBounds(0, 0, width, height);
and that's how you can set the world boundary dynamically.
window.addEventListener('resize', () => {
game.resize(window.innerWidth, window.innerHeight);
});
There is some builtin support for resizing that can be configured in the Game Config. Check out the ScaleManager options. You have a number of options you can specify in the scale property, based on your needs.
I ended up using the following:
var config = {
type: Phaser.AUTO,
parent: 'game',
width: 1280, // initial width that determines the scaled size
height: 690,
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT ,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: {y: 0, x: 0},
debug: true
}
},
scene: {
key: 'main',
preload: preload,
create: this.create,
update: this.update
},
banner: false,
};
Just in case anybody else still has this problem, I found that just resizing the game didn't work for me and physics didn't do anything in my case.
To get it to work I needed to resize the game and also the scene's viewport (you can get the scene via the scenemanager which is a property of the game):
game.resize(width,height)
scene.cameras.main.setViewport(0,0,width,height)
For Phaser 3 the resize of the game now live inside the scale like this:
window.addEventListener('resize', () => {
game.scale.resize(window.innerWidth, window.innerHeight);
});
But if you need the entire game scale up and down only need this config:
const gameConfig: Phaser.Types.Core.GameConfig = {
...
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
},
...
};
export const game = new Phaser.Game(gameConfig);
Take a look at this article.
It explains how to dynamically resize the canvas while maintaining game ratio.
Note: All the code below is from the link above. I did not right any of this, it is sourced from the article linked above, but I am posting it here in case the link breaks in the future.
It uses CSS to center the canvas:
canvas{
display:block;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
It listens to the window 'resize' event and calls a function to resize the game.
Listening to the event:
window.onload = function(){
var gameConfig = {
//config here
};
var game = new Phaser.Game(gameConfig);
resize();
window.addEventListener("resize", resize, false);
}
Resizing the game:
function resize() {
var canvas = document.querySelector("canvas");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + "px";
canvas.style.height = (windowWidth / gameRatio) + "px";
}
else{
canvas.style.width = (windowHeight * gameRatio) + "px";
canvas.style.height = windowHeight + "px";
}
}
I have used and modified this code in my phaser 3 project and it works great.
If you want to see other ways to resize dynamically check here
Put this in your create function:
const resize = ()=>{
game.scale.resize(window.innerWidth, window.innerHeight)
}
window.addEventListener("resize", resize, false);
Important! Make sure you have phaser 3.16+
Or else this won't work!!!
Use the game.resize function:
// when the page is loaded, create our game instance
window.onload = () => {
var game = new Game(config);
game.resize(400,700);
};
Note this only changes the canvas size, nothing else.

Applying clipTo path on image in fabric.js incorrectly repositions the image

Please take a look at this fiddle:
http://jsfiddle.net/2ktvyk4e/5/
var imgUrl, snapshotCanvas;
imgUrl = 'http://cdn-development.wecora.com/boards/backgrounds/000/001/388/cover/ocean-background.jpg';
snapshotCanvas = new fabric.StaticCanvas('snapshotCanvas', {
backgroundColor: '#e0e0e0',
width: 1000,
height: 1500
});
fabric.Image.fromURL(imgUrl, function(img) {
img.set({
width: 1000,
left: 0,
top: 0,
clipTo: function(ctx) {
return ctx.rect(0, 0, 1000, 400);
}
});
return snapshotCanvas.add(img).renderAll();
}, {
crossOrigin: 'Anonymous'
});
It's pretty simple. I'm loading an image and then trying to clip it so that the full width of the canvas but clipped so only the top 400 pixels are showing. For some reason, the clipTo causes the image to move and resize inexplicably:
As you can see, when the clipping path is applied the image is repositioned on the canvas inexplicably. If I remove the clipTo, then the image loads full canvas width no problem (of course its also full height, which we don't want).
I have no idea what is happening here or why this is occuring so any help is appreciated.
Make sure you have originX and originY left to top and left on both the canvas and your image. Also - you don't really need to clip anything to the canvas. The only real use I've found for clipTo was clipping collage images to their bounding shapes. If you want to restrict the image from being dragged above that 400px, I would recommend rendering a rectangle below it (evented = false, selectable = false) and then clipping to that rectangle.
I put this together without clipTo (and changed some numbers so I wasn't scrolling sideways). It renders the image half way down the canvas.
http://jsfiddle.net/2ktvyk4e/6/
Edit:
I dug through some source code to find the clipByName method and the two helper methods for finding stuff. I use this for keeping track of collage images and their bounding rectanlges ("images" and "clips"). I store them in an object:
imageObjects: {
'collage_0': http://some.tld/to/image.ext,
'collage_1': http://some.tld/to/image2.ext
}
Helper methods for finding either the clip or image:
findClipByClipName: function (clipName) {
var clip = _(canvas.getObjects()).where({ clipFor: clipName }).first();
return clip;
},
findImageByClipFor: function (clipFor) {
var image = _(canvas.getObjects()).where({ clipName: clipFor }).first();
return image;
},
Actual clipping method:
clipByName: function (ctx) {
this.setCoords();
var clipRect, scaleXTo1, scaleYTo1;
clipRect = collage.findClipByClipName(this.clipName);
scaleXTo1 = (1 / this.scaleX);
scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft, ctxTop;
ctxLeft = -(this.width / 2) + clipRect.strokeWidth;
ctxTop = -(this.height / 2) + clipRect.strokeWidth;
ctx.translate(ctxLeft, ctxTop);
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
},
And finally adding images to canvas where all of this comes together:
var clipName, clip, image;
clipName = helpers.findKeyByValue(url, this.imageObjects);
clip = this.findClipByClipName(clipName);
image = new Image();
image.onload = function () {
var collageImage = new fabric.Image(image, $.extend({}, collage.commonImageProps, {
left: clip.left,
top: clip.top,
clipName: clipName,
clipTo: function (ctx) {
return _.bind(collage.clipByName, collageImage)(ctx);
}
}));
collage.scaleImagesToClip(collageImage);
canvas.add(collageImage);
};

Resources