Is it possible to create a tile 2x2 mosaic using Sharp Node? - node.js

Using Image Magick I was able to use montage to make this:
With this code:
montage -density 300 -tile 2x0 -geometry 150x150^ -gravity center -crop 150x150+0+0 -border 0 '.$img_array.' /var/www/html/uploads/output.jpg
I would like to know if this is possible too using Sharp, I saw the tile option but I don't understand how to use 4 different images together.

Thanks to the Author, I found this tricky solution.
Imagine you want a 2x2 mosaic with a fixed size of 400x400 pixels.
In the example below I have all my images into ./images/ folder. All 4 images are of fixed size 200x200 pixels.
You need also a background picture big enough to contain the mosaic. You can use for this any image and resize with Sharp as well.
const sharp = require('sharp');
sharp('./images/output4.jpg')
.resize(400, 400)
.toFile('bg.jpg', function(err) {
console.log("Something wrong",err)
});
Now that you have created the bg.jpg image you can run this JavaScript:
const sharp = require('sharp');
sharp('./images/bg.jpg') //here call the previous generated image
.composite([
{ input: './images/output1.jpg', gravity: 'northwest' },
{ input: './images/output2.jpg', gravity: 'northeast' },
{ input: './images/output3.jpg', gravity: 'southwest' },
{ input: './images/output4.jpg', gravity: 'southeast' },
])
.toFile('combined.jpg');
In the future will be easier with something like this (not yet implemented):
vips arrayjoin "./images/1.jpg ./images/2.jpg ./images/3.jpg ./images/4.jpg" out.jpg --across 2

Related

Sharp NodeJS: How to add an overlay to an animated gif?

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'
}
])

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

fabricjs set boundingrect to the new object after cropping object using clipto method

I have clipped an image using clipto method of fabricjs and this works fine. But the problem is boudingrect is still to the actual image area. Is there any way to reset boundingrect same as new cropped image? I tried setCoords but this does not work.
FabricJS has two methods available to "crop" an object, the one you are using clipTo(), and a toDataURL() method. Using toDataURL() creates a new image with a reset boundingrect same as new cropped image.
fabric.Image.fromURL('data:image/;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACa0lEQVRYhbVXMW7bQBAcMgRTWCJdC+qFOLAaudFDnEKdYL1AfoCRB8gvkJJUAQw/RI3cSIBg13ZShzxWBEK6sEjf7e3dkYC8Fckhduf2doZHryzLEpY4e/mp3M+jEW7TB/bdq+457rMnpGVeP9v3p7b08K0oE7NoiHk0Yotfxxdt07UnsEy3Gomq+F32qKy+SQRtCVTtn0VDAEBS5nXx7//WbdPpBOQ9/3YywDwaYRYNsUy3dfHksMquHwLF27uieF+53JFFssFK7Gps3Zsg9j+bCcjFb07H+PrnFwD3ik14NYQyCWsH5OKe5wHQ206Lu/BqODkSGgFanBbp+iG71y7cpBDvy/MPxQeozmUdL9Nt3WIuOFyeKc4ntA5UbaRmIw8htyIXXg3mffakPNd8gNM5Tb4SOyySTWPc5hNaB+hA0eRyEQCIvdCKy8W52fDn0Qj7/lRZsazzxOJsosideOyF9XUVV91z7PtT3JyOEbiczaRjqhYT7vKRAGivY1rchdvy1zPQVMeiyFmfcOGm/MoQJmVeezsXsRcCPrTkTXEuf02Afs8/ygdo/kAG77JHiCLXzIjTuVzEhdvyByadfrQPVPmDldhpEpJ94OV/piWvQhS59Uwlihz9T533dw9xmz6oWyAHZXxsH6Dd0ghw7TqmDyzFzv41NHn2sXyAhkf/CxbJxvo9Nw2dCafnicvOwH4mpBKSk9t8oqmPXHYGynP2TNhGx01wk5SNBGQSLh27Tsm24iyBdW+i3I///rb6BPdfIOt83ZsobY8O5wMjAXlAuHB1hK7Wla/1rxlX3KZzV7QmYPIJ2z7b4hVhK2M6UYICmwAAAABJRU5ErkJggg==', function(origImage) {
var origBoundingRect = origImage.getBoundingRect();
fabric.Image.fromURL(origImage.toDataURL({
width: origImage.width - 5,
height: origImage.height - 5
}), function(newImage) {
var newBoundingRect = newImage.getBoundingRect();
console.log('origBoundingRect:\r\n' + JSON.stringify(origBoundingRect) + '\r\n\r\n' + 'newBoundingRect:\r\n' + JSON.stringify(newBoundingRect));
});
});
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.7/fabric.min.js"></script>
If you run the code snip-it above it logs to the console a before and after shot of the boundingrect.
Hope this helps, matey. Let me know if you have any further questions.

Detect Graphical Regions in an Image on a Web Page

I've been beating around the bush a lot, so I'll explain my problem here and hope with the whole picture, somebody has some ideas. With the following image:
I need to detect a mouseover on the blobs over her eyes and mouth, and solve this problem in a general form. The model and blobs are on two different layers, so I can produce one image with only the blobs, and one with only the model, and somehow synchronise a virtual cursor over the blobs while it actually hovers over the model.
I can also make the blobs polygons, for hit testing, but I think a colour hit test would be much easier. If I hit blue, I am on her mouth and I show lipstick images; if I hit pink, I'm over her eyes, and display eye makeup images.
What are the suggestions and conversation of the learned ones here?
The simpler way to do it would be to load the layer image in a canvas, then get all its pixel data. When the mouse is hovering the model image, find out what color is currently selected and if it is different from a previous one, trigger an event to indicate that the selection has changed.
Here is an example, feel free to toy with it; but be aware that it doesn't handle all cases:
what if the layer and the model image are not the same size
what if the layer and the model image are not the same width/height ratio
what if you want to use some alpha channel (the example doesn't take it into account)
$(function() {
/* we load all the image data first */
var imageData = null;
var layerImage = new Image();
layerImage.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
context = canvas.getContext('2d');
context.drawImage(this, 0, 0, canvas.width, canvas.height);
imageData = context.getImageData(0, 0, canvas.width, canvas.height).data;
};
/* it's easier to set the image data for example as base64 data */
layerImage.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTFH80I3AAAA5klEQVR4Xu3WMQ7CMAwF0IxcgCtw/xsGdWApItWHKorLQ8qESe2HbWjNiwABAgQIECBAgAABAgQIECBAgMAUgd4e/ehMSeTMh4wK2j/nqPjt/TNzm3IXgEFb64CdgBG44hKcsmg8pKDAvbf+7SlY7nvK3xb/+lx5BAA/jMCGpwOqC/z9CFT/ApfP/9Z6T87yBaUJJsVvsen9y8cDMAJ2gCWY7IHll1qaYFL84a9AelnF+CFwxYLSnAGMBFLNivE6QAcMBCq2dJqzETACRuCzQDpPFePTv9riCRAgQIAAAQIECBC4nsATagY67TVyuhAAAAAASUVORK5CYII=";
var pColor = null;
/* on mouse over the model image */
$("#model").mousemove(function(event) {
/* we correct the offset */
var offset = $(this).offset();
var relX = event.pageX - offset.left;
var relY = event.pageY - Math.round(offset.top);
/* and get the pixel values at this place (note we are not keeping the alpha channel; it's your decision whether or not it is valuable */
var pixelIndex = relY * layerImage.width + relX;
var dataIndex = pixelIndex * 4;
var color = [imageData[dataIndex], imageData[dataIndex + 1], imageData[dataIndex + 2]];
if (pColor == null) {
/* we trigger when first entering the image */
$(this).trigger("newColor", {
message: "Initial layer color",
data: color
});
} else if (pColor[0] != color[0] || pColor[1] != color[1] || pColor[2] != color[2]) {
/* we trigger if the new position is a new color in the layer image */
$(this).trigger("newColor", {
message: "Changed layer color",
data: color
});
}
pColor = color;
});
/* some small help to convert rgb to css colors */
function rgb2hex(red, green, blue) {
var rgb = blue | (green << 8) | (red << 16);
return '#' + (0x1000000 + rgb).toString(16).slice(1)
}
/* there you have the new layer color event management; for the example sake we change the color of some text */
$("#model").on("newColor", function(event, eventData) {
$("#selector").css("color", rgb2hex(eventData.data[0], eventData.data[1], eventData.data[2]));
});
});
img {
border: 1px solid silver
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<body>
<h4>Model image</h4>
<img id="model" src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAtdJREFUaEPtmS2PwkAQhhGQEASEBIECFAJBgoCAxpDgsWBxSP4lPwGJRCLv5m4uk6Xt7s5He9dLFst2+z7vfG3bxsc//zX+uf6PBPDXEUwRSBEwOpBSyGig+fIUAbOFxg3KjMD1em3EfqvV6vl8GkW7l5cJsF6vY/p//i8RQwbwer22221U5W63K/T4fr93u1263LdMFB8ZAEc96BsOhwER5WIIAMB+NO92u4lMKlw8m80oFJaMEgCg/ZADdvW0A2GMRiPdtgKAEu13tUJG4c667sQFoBap8yl8FdiPDIqyZgFQ9oerU83mlrV0ExaAJfshdKfTiSMLg8BZKR5k4ewPS4TpNplMOLKqAqD88YngSwxjVAVgyR+O8bSmKoCKumeerVoAkZe6xQnA45vCGH7rfGuIFbVRBUDgvO2bCefzWXEj4I8PDtyXOYzQ0cVi4WMonAmWSR8HIDWbzUZ33grXNKifTqcA3Ov1FNUfB3AdhdOv4h6+S0D6fr/HWLVaLd1jBgsAFNChV5RLAVpKelDfbrd16lk1QCLczLZjoPEW6SiMG4FAdUphXO/tCSkD8GEwi9uVDvZLny6gZg6HQ8YvDYBr23g8DnR9319S6XjH5XIJGw4Gg7fxZwwiFDfsyGfQSQeRvnci1ghE+XXz1d3W7bb5WVF3ABpzvpZVdwDM+8CYqyOAmzPRx6l6ARyPx3w/CNd9jQDgNEHq+RO6LgCgvt/vA8Dlcol2tjLnQPRmhW00n+W4rNPpPB6P6J5cAHofqnhlSffIAIDT8/k8n+jNZhO8l6qPHOYKPxlJ3+Wj1swpqJRzKPc06n6JIOf4GBmz1U778kpWxJmvQ9EjEOQergEX1KegcEnIAHAvPgaIRgDpMwO/jjUA/N1hJT3Hia7iL64c4KtRfP/4mkQrq9r3rVUngEBMUgQYCZtqIGRSSqGUQgwHUgoZTSrrQ3KhjN8oYiN/+PJPqpb83Htu7qcAAAAASUVORK5CYII="
/>
<p>You are pointing at some <strong><span id="selector">color</span></strong>
</p>
<hr/>
<h4>Layer image (reference only, not displayed in page)</h4>
<img id="layer" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTFH80I3AAAA5klEQVR4Xu3WMQ7CMAwF0IxcgCtw/xsGdWApItWHKorLQ8qESe2HbWjNiwABAgQIECBAgAABAgQIECBAgMAUgd4e/ehMSeTMh4wK2j/nqPjt/TNzm3IXgEFb64CdgBG44hKcsmg8pKDAvbf+7SlY7nvK3xb/+lx5BAA/jMCGpwOqC/z9CFT/ApfP/9Z6T87yBaUJJsVvsen9y8cDMAJ2gCWY7IHll1qaYFL84a9AelnF+CFwxYLSnAGMBFLNivE6QAcMBCq2dJqzETACRuCzQDpPFePTv9riCRAgQIAAAQIECBC4nsATagY67TVyuhAAAAAASUVORK5CYII="
/>
</body>
If you can have both images (the one with the blob and the one without), I think you can do this using HTML5 canvas.
draw the image normally
draw the blob image beneath the master image so it is invisible
copy the blob to a Canvas
onMouseOver, retrieve pixel data (R,G,B and alpha) for the Canvas at the appropriate coordinates
profit
Twist: you might be able to do this with only one image and its alpha channel, if you don't need it for anything else - give the pixels a full opacity (A=255) everywhere except in blobs 1, 2 and 3, which will have opacity equal to 255-(1,2,3...). You can't have too many different blobs or the transparency will become noticeable. Haven't tried, but it should work. Given the likely compressibility of a "blob-only" image, a pair of images (one without transparency, one also without transparency and with only N+1 colours, PNG compressed) should yield better results.
More-or-less-pseudo code with two images, using jQuery (can be done without):
var image = document.getElementById('mainImage')
var blobs = document.getElementById('blobImage');
// Create a canvas
canvas = $('<canvas/>')[0];
canvas.width = image.width;
canvas.height = image.height;
// IMPORTANT: for this to work, this script and blobImage.src must be both
// in the same security domain, or you'll get "this operation is insecure"
canvas.getContext('2d').drawImage(blobs, 0, 0, image.width, image.height);
// Now wait for it.
$('#mainImage').mouseover(function(event) {
// TO DO: offset clientX, clientY by margin on mainImage
var ctx = canvas.getContext('2d');
// Get one pixel
var pix = ctx.getImageData(event.clientX, event.clientY, 1, 1);
// Retrieve the red component
var red = pix.data[0];
if (red > 128) {
// ... do something for red
}
});
You could use SVG graphics to layer over the image.
My example uses an ellipse but you could use a polygons just as easily.
You could use the colour like you stated in your question or add an extra property to the svg element. The example uses onclick but mouseover works as well.
example js:
function svg_clicked(objSVG)
{
alert(objSVG.style.fill);
alert(objSVG.getAttribute('data-category'));
}
example svg:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<ellipse cx="110" cy="80" rx="100" ry="50" style="fill:red;" onclick="svg_clicked(this);" data-category="lipstick" />
</svg>
Here's a fiddle (move the mouse over the O's in the picture)
It still works if you make the svg element transparent (using fill:transparent).
You can change the overlay to a colour or outline quickly for testing.
I highly recommend the time tested method.
The easiest way to both create blobs and to detect if the mouse is over them is to use svg graphics on top of the other image. SVG supports mouseover events and allows vector shapes which are going to give you far greater precision than using <map> or <area>.
I found this question that might also shed some light on where I am coming from: Hover only on non-transparent part of image. Read the second answer down because it will likely be prefered in your situation.
The svg elements on your image would be transparent (or whatever you would want), and you could easily detect the mouse over events.
The library from that question is called raphael. Hopefully this proves to be useful.

Resources