Merge 2 images with node.js? - image

I want to merge 2 images using node.js. Or rather, i want to place one smaller image on cordinates x,y on a larger image.
Even more precise: I have an image of glasses, and an image of a face and i want to put the glasses on the face.
I did some googling, and found some image manipulating libraries, but none seem to be able to merge images.

You might need this: https://github.com/zhangyuanwei/node-images
Cross-platform image decoder(png/jpeg/gif) and encoder(png/jpeg) for Nodejs
images("big.jpg").draw(images("small.jpg"), 10, 10).save("output.jpg");

I do no have enough reputation to add a comment, or else I would to Schmidko's answer.
Sharp works well, however, overlayWith is deprecated and you instead need to use composite. See below:
sharp(path + 'example.jpg')
.composite([{input: path + 'logo.png', gravity: 'southeast' }])
.toFile(path + 'output.png');
If you would like to center the image being overlayed:
gravity: 'centre'

I've used:
https://github.com/learnboost/node-canvas
to do something similar (build a composite image from components on the fly).
It works great.
Here's some example code:
var Canvas = require('canvas'),
fs = require('fs'),
Image = Canvas.Image;
var _components = [{prefix:'f', count:12},
{prefix:'h', count:12},
{prefix:'i', count:12},
{prefix:'m', count:12}];
var _total = 1;
for (var i=_components.length - 1; i>=0; i--){
_components[i].mult = _total;
_total *= _components[i].count;
}
module.exports.ensureImageExists = function(img, cb){
fs.stat(__dirname + '/../public/images/rb/' + img, function(err, stats){
if (err){
if (err.code == 'ENOENT')
generateImage(img, cb);
else
cb(err);
}
else{
cb();
}
});
}
function generateImage(name, cb){
var re = /rb([0-9]*)\.png/
var num = parseInt(re.exec(name)[1]) % _total;
var parts = [];
for (var i=0; i<_components.length; i++){
var n = Math.floor(num / _components[i].mult);
parts.push(_components[i].prefix + (n + 1));
num -= n * _components[i].mult;
}
var canvas = new Canvas(45, 45),
ctx = canvas.getContext('2d');
drawParts();
function drawParts(){
var part = parts.shift();
if (!part)
saveCanvas();
else {
var img = new Image;
img.onload = function(){
ctx.drawImage(img, 0, 0, 45, 45);
drawParts();
};
img.src = __dirname + '/components/' + part + '.png';
}
}
function saveCanvas(){
canvas.toBuffer(function(err, buf){
if (err)
cb(err);
else
fs.writeFile(__dirname + '/../public/images/rb/' + name, buf, function(){
cb();
});
});
}
}
In this case, the components are selected based upon the name of the image, but you clearly could do otherwise. Also, I imagine you could just stream the image out if you wanted -- I write it to a file so it's available the next time it's requested.
I put a route like this in to handle the generation:
app.get('/images/rb/:img', requireLogin, function(req, res, next){
//first make sure image exists, then pass along so it is handled
//by the static router
rbgen.ensureImageExists(req.params.img, function(err){
next();
})
});

Tested some librarys for a similar task and implemented finally this one.
https://github.com/lovell/sharp.
Clean API and all you need to merge two images together.
Example:
sharp(path + 'example.jpg')
.overlayWith(path + 'label.png', { gravity: sharp.gravity.southeast } )
.toFile(path + 'output.png')

Related

Jcrop Image Intervention Laravel 5

i am using Image Intervention and jcrop to crop and resize image in laravel, but having problems. The issue which i think is that , when i save the file width and height is correct according to the selection, but the x & y is not correct, I am completely lost here, dont know what to do , Please help.
I have made but cropping area is wrong.
here is the code example.
// convert bytes into friendly format
function bytesToSize(bytes) {
var sizes = ['Bytes', 'KB', 'MB'];
if (bytes == 0) return 'n/a';
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
}
// check for selected crop region
function checkForm() {
if (parseInt($('#w').val())) return true;
$('.setting-image-error').html('Select area').show();
return false;
}
// update info by cropping (onChange and onSelect events handler)
function updateInfo(e) {
$('#x1').val(e.x);
$('#y1').val(e.y);
$('#x2').val(e.x2);
$('#y2').val(e.y2);
$('#w').val(e.w);
$('#h').val(e.h);
}
// clear info by cropping (onRelease event handler)
function clearInfo() {
$('#w').val('');
$('#h').val('');
}
// Create variables (in this scope) to hold the Jcrop API and image size
var jcrop_api, boundx, boundy;
function fileSelectHandler() {
// get selected file
var oFile = $('#picture')[0].files[0];
// hide all errors
$('.setting-image-error').hide();
// check for image type (jpg and png are allowed)
var rFilter = /^(image\/jpeg|image\/png)$/i;
if (!rFilter.test(oFile.type)) {
$('.setting-image-error').html('Select only jpg, png').show();
return;
}
// check for file size
if (oFile.size > 10000000) {
$('.setting-image-error').html('Too Big file ').show();
return;
}
// preview element
var oImage = document.getElementById('preview');
// prepare HTML5 FileReader
var oReader = new FileReader();
oReader.onload = function (e) {
// e.target.result contains the DataURL which we can use as a source of the image
oImage.src = e.target.result;
oImage.onload = function () { // onload event handler
// display step 2
$('.setting-image-cropping-stage').fadeIn(500);
// display some basic image info
var sResultFileSize = bytesToSize(oFile.size);
$('#filesize').val(sResultFileSize);
$('#filetype').val(oFile.type);
$('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
// destroy Jcrop api if already initialized
if (typeof jcrop_api !== 'undefined') {
jcrop_api.destroy();
jcrop_api = null;
$('#preview').width(oImage.naturalWidth);
$('#preview').height(oImage.naturalHeight);
}
//Scroll the page to the cropping image div
$("html, body").animate({scrollTop: $(document).height()}, "slow");
// initialize Jcrop
$('#preview').Jcrop({
minSize: [32, 32], // min crop size
aspectRatio: 1, // keep aspect ratio 1:1
bgFade: true, // use fade effect
bgOpacity: .3, // fade opacity
onChange: updateInfo,
onSelect: updateInfo,
onRelease: clearInfo
}, function () {
// use the Jcrop API to get the real image size
var bounds = this.getBounds();
boundx = bounds[0];
boundy = bounds[1];
// Store the Jcrop API in the jcrop_api variable
jcrop_api = this;
});
}
}
// read selected file as DataURL
oReader.readAsDataURL(oFile);
}
and Controller code is below.
public function image_crop_resize_and_upload($file, $user_id,$width,$height,$x1,$y1)
{
$filename = $user_id . '.jpg';// image file name
$target_path = User::PICTURE_PATH . $filename;//path where to create picture with new dimensions
$img = \Image::make($file->getRealPath());// create the instance of image with the real path of the image
$filetype = $img->mime();//get file mime type
$filetypes = ['image/jpg', 'image/jpeg', 'image/png']; //allowed files types
//if file exists in the target folder, system will delete the file and next step will create new one.
if (File::exists($target_path)) {
File::delete($target_path);
}
if (in_array($filetype, $filetypes, true)) {
$img->crop($width, $height,$x1,$y1);
$img->encode('jpg', 85);
$img->resize($width,$height);
$img->save('uploads/' . $user_id . '.jpg');
return true;
} else {
return false;
}
}
When i have the file the file width and height is correct, but the selection area, x & y is not correct.
Yes , I have got the answer. The problem is very simple the x and y position of image are wrong because it is inside a bootstrap responsive class. the proper solution is just to remove the class . So the image actually dimension will be shown. and than select the are. Thats it.
<img id="preview" name="preview" class="img-responsive"/>
this should be
<img id="preview" name="preview"/>

postman - how to check picture size

I am trying to write some tests in Postman (I am running the Postman Jetpacks packaged app, if it does matter) and I am facing some inconsistencies.
The scope of my test is to verify the size (width and height) of a set of images against some predefined values.
The scenario is like this: I make a call to a method that returns some urls, then I set the URLs as environment variables and then I verifying the size of each picture. Below is the code I am using in Tests tab in Postman.
tests["Status code is 200"] = responseCode.code === 200;
var data = JSON.parse(responseBody);
//test that response contains the expected attributes
tests["splash_image_url present"] = data.hasOwnProperty("splash_image_url");
tests["home_image_url present"] = data.hasOwnProperty("home_image_url");
tests["login_image_url present"] = data.hasOwnProperty("login_image_url");
tests["register_image_url present"] = data.hasOwnProperty("register_image_url");
tests["splash_logo_url present"] = data.hasOwnProperty("splash_logo_url");
tests["bar_logo_url present"] = data.hasOwnProperty("bar_logo_url");
//set each image URL as environment variable
postman.setEnvironmentVariable("splash_image_url", data.splash_image_url);
postman.setEnvironmentVariable("home_image_url", data.home_image_url);
postman.setEnvironmentVariable("login_image_url", data.login_image_url);
postman.setEnvironmentVariable("register_image_url", data.register_image_url);
postman.setEnvironmentVariable("splash_logo_url", data.splash_logo_url);
postman.setEnvironmentVariable("bar_logo_url", data.bar_logo_url);
//extract images from each URL
var splash_image_url = document.createElement("img");
splash_image_url.src = environment.splash_image_url;
var home_image_url = document.createElement("img");
home_image_url.src = environment.home_image_url;
var login_image_url = document.createElement("img");
login_image_url.src = environment.login_image_url;
var register_image_url = document.createElement("img");
register_image_url.src = environment.register_image_url;
var splash_logo_url = document.createElement("img");
splash_logo_url.src = environment.splash_logo_url;
var bar_logo_url = document.createElement("img");
bar_logo_url.src = environment.bar_logo_url;
//test the size for each picture
tests["splash_image_url width"] = splash_image_url.width === 640;
tests["splash_image_url height"] = splash_image_url.height === 960;
tests["home_image_url width"] = home_image_url.width === 640;
tests["home_image_url height"] = home_image_url.height === 960;
tests["login_image_url width"] = login_image_url.width === 640;
tests["login_image_url height"] = login_image_url.height === 960;
tests["register_image_url width"] = register_image_url.width === 640;
tests["register_image_url height"] = register_image_url.height === 960;
tests["splash_logo_url width"] = splash_logo_url.width === 310;
tests["splash_logo_url height"] = splash_logo_url.height === 80;
tests["bar_logo_url width"] = bar_logo_url.width === 155;
tests["bar_logo_url height"] = bar_logo_url.height === 40;
The problem is that sometimes when running the request all or some of the picture size verification fail. If I continue to manually run the same request again and again it will eventually show all the tests passed. This inconsistency makes the test unreliable.
I am missing something or doing something wrong? Is there a batter way to verify the picture size?
Thanks
Very nice question, was a fun challenge. Thanks !
1. Your problem
The tests actually work after you manually run them a couple of times, because the images are cached at that point.
The main issue here, is that you are not waiting for images to actually load, before checking the properties associated with them.
2. Postman Test Results
I tried a proof of concept on this by waiting for the images to actually load and found out... that my tests were not actually being displayed either as passing or failing.
This was mainly due to the way tests are run. Postman uses eval in the context of the request (more or less).
in Evaluator.js # 111
if (command === "runcode") {
try {
var result = eval(code);
event.source.postMessage({'type': 'test_result', 'result': result, 'scriptType': scriptType}, event.origin);
}
catch(e) {
console.log(e);
event.source.postMessage({'type': 'test_error', 'errorMessage': e.message, 'scriptType': scriptType}, event.origin);
}
}
Unfortunately for us, any sort of the callback logic will not get retroactively pushed back into the results chain.
2.1 Postman Secondary Test Results
It seems that posting a new set of results to event.source will not trigger a new set of results, but get completely discarded.
I have managed to find a workaround for this. Simply end the script with:
function postmanJetpacksSupportsOnlyOneResultPerTest()
{
event.source.postMessage({'type': 'test_result', 'result': tests, 'scriptType': 'test'}, event.origin);
}
throw 'ignore this. Enforcing failure on this test case, real values will come by manually calling *postmanJetpacksSupportsOnlyOneResultPerTest* when you are done with the test case.';
Then just call postmanJetpacksSupportsOnlyOneResultPerTest when all of your callbacks are done.
3. Postman Developers
I really hope you can somehow include the concept of promises. Example:
In the test runner:
var defered = new jQuery.Deferred();
tests['before timeout'] = true;
setTimeout(function() {
tests['on timeout'] = true;
defered.resolve(tests);
}, 500);
tests['after timeout'] = true;
return defered.promise();
In Evaluator.js # 113:
var result = eval(code);
if (result.promise instanceof Function) {
result.then(function(result) {
event.source.postMessage({'type': 'test_result', 'result': result, 'scriptType': scriptType}, event.origin);
});
} else {
event.source.postMessage({'type': 'test_result', 'result': result, 'scriptType': scriptType}, event.origin);
}
4. An example
// handlers
var waiting = 0;
function errorHandler() {
waiting--;
tests[this.image.name + ' load'] = false;
}
function successHandler() {
waiting--;
tests[this.image.name + ' load'] = true;
tests[this.image.name + ' width'] = this.image.width == this.width;
tests[this.image.name + ' height'] = this.image.height == this.height;
}
// test case kind of
function createImageTest(name, url, width, height)
{
// create image tag
var image = document.createElement('img');
// set the name
image.name = name;
// set error handlers
image.onerror = errorHandler.bind({
image: image
});
image.onload = successHandler.bind({
image: image,
width: width,
height: height
});
// finally attach the src
image.src = url;
// waiting on either a fail or a load
waiting++;
}
// the actual test cases
createImageTest('stackexchange logo', 'http://cdn.sstatic.net/stackexchange/img/se-logo.png', 223, 52);
createImageTest('stackoverflow logo', 'http://cdn.sstatic.net/stackoverflow/img/sprites.png', 240, 500);
// wait for all callbacks finished
(function checkFinished(){
// still images to process
if (waiting) {
// check back in another 0.1 seconds
return setTimeout(checkFinished, 100);
}
// ready to send result
postmanJetpacksSupportsOnlyOneResultPerTest();
})();
// the hack from #2.1
function postmanJetpacksSupportsOnlyOneResultPerTest()
{
event.source.postMessage({'type': 'test_result', 'result': tests, 'scriptType': 'test'}, event.origin);
}
throw 'ignore this. Enforcing failure on this test case, real values will come from init...';

Clicking or hovering over multiple images in canvas?

I am working on a project for my programming class. I'd like to essentially have a canvas with a background element (say a room.jpg) and then maybe three interactive objects in the room (lamp.jpg, couch.jpg, desk.jpg). I'd like for it to be that if you hover over the lamp a small box or text pops out, giving you some information. Or maybe have it so if you click an image, the same concept happens. You know, something interactive with the objects in the canvas. Again, I'm new to canvas but we have to use it in our assignment. My current code is:
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var sources = {
room: 'room.jpg',
title: 'title.jpg'
};
loadImages(sources, function(images) {
context.drawImage(images.room, 0,0);
context.drawImage(images.title, 0,0);
});
}
But from what I understand, it makes the two jpegs a "permanent" canvas element (unable to be messed with). I had been trying to get it so that when I clicked I'd go from the title.jpg to the room.jpg but I've since given up. Essentially, all I want now is just to have the room.jpg appear when the page is first loaded, and have three other png objects on top (as objects in the room). Are these able to be interacted with, or do I have to put the images into the canvas in another way? Thanks for all your help and your patience!
// --- Image Loader ----
var images = {};
var loadedImages = 0;
var pictures = {
room: 'room.jpg',
title: 'title.jpg'
lamp1: 'lampoff.jpg'
lamp2: 'lampon.jpg'
};
function loadImages(sources, callback) {
var numImages = 0;
for(var src in sources)numImages++;
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
// --- Mouse Down Functionality ----
$('#canvas').addEventListener('mouseDown', function(e){
if(e.clientX){
var rect = this.getBoundingClientRect();
if(rect) clickCanvas(e.clientX - rect.left, e.clientY - rect.top)
else clickCanvas(e.clientX - this.offsetLeft, e.clientY - this.offsetTop);
}else if(e.offsetX) clickCanvas(e.offsetX, e.offsetY);
else if(e.layerX) clickCanvas(e.layerX, e.layerY);
else console.warn("Couldn't Determine Mouse Coordinates");
})
var lampOn;
function drawCanvas(showLamp){
lampOn = showLamp;
canvas.width = canvas.width //clears canvas
context.drawImage(images.room, 0,0);
context.drawImage(images.title, 0,0);
if(lampOn){
context.drawImage(images.lamp2, 100,100);
}else{
context.drawImage(images.lamp1, 100,100);
}
}
function clickCanvas(x,y){
console.log('clicked canvas at:',x,y)
if(clickedLamp){
drawCanvas(!lampOn)
}
}
loadImages(pictures, function(images) {
drawCanvas(false)
});
Make sure to replace "clickedLamp" and "#canvas"! The idea here is that you redraw the same canvas, using the same function. Everytime you modify any of it, you rerender ALL of it. See if you can get this example working, it will help clarify alot. If you don't understand something comment

JavaScript for loop and print like animation (Canvas)

I have question about my code.
It is really working weird.... I have no clue why this happens but let me explain. (MY English is bad but I will try my best to explain and give you guys good picture what is going on)
var spaceshipIncoming = document.createElement('audio');
var canvasMS12Main = document.getElementById('canvasMS12Main');
var imgMainMs12 = canvasMS12Main.getContext('2d');
var mainMs12 = new Image();
var number2 = 5;
var formatImage = ".png";
var stopeIncreaseMS12 = false;
function introMenu(){ //509,734,306,345
imgMain.drawImage(mainImg, 6, 8, gameWidth, gameHeight, 0, 0, gameWidth, gameHeight);
audioElement.setAttribute('src', 'soma.mp3');
audioElement.play();
}
function ms12AniAndSound(){
setTimeout("introMS12()",5000);
spaceshipIncoming.setAttribute('src', 'spaceshipIncoming.mp3');
spaceshipIncoming.play();
}
function introMS12(){//78
if(number2 < 78){
number2 = number2 + 1;
}else if (number2 == 78){
stopeIncreaseMS12 = true;
}
var num = new Number(number2);
mainMs12.src = 'ms12IntroAnimation/introMS12-' + num + formatImage;
clearMainMS12();
imgMainMs12.drawImage(mainMs12, 0, 0, gameWidth, gameHeight, 0, 0, gameWidth, gameHeight);
delayMS12();
}
function delayMS12(){
if(stopeIncreaseMS12 == false){
setTimeout("introMS12()",25);
}else if(stopeIncreaseMS12 == true){
clearMainMS12();
}
}
function clearMainMS12(){
imgMainMs12.clearRect(0, 0, 800, 500);
}
I have folder that has animation images, and the each PNG files are named like: introMS12-#. and each # starts from 6 to 78 (so basically it looks like introMS12-6.png, introMS12-7.png, introMS12-8.png.....introMS12-78.png)
When I first load the webpage, it doesn't show animation, but plays only mp3 sound on canvas. However, if I refresh it or press F5, it plays animation..... sometimes it doesn't play animation even if I refresh it. However, if I close the Chrome web browser and restart it and try refresh button, then it starts playing animation..... (it works randomly....)
I have been trying to make this work over 10 hours... but I do not know what is wrong with my code...... pleas help me ! thanks.

detecting "we have no imagery" of google maps street view static images

I'm generating street view static images like so:
https://maps.googleapis.com/maps/api/streetview?size=1080x400&location=%s&fov=90&heading=235&pitch=0&key=%s
If you visit that link you see an image that says, "Sorry, we have no imagery for this..."
Is there any way to detect this "sorry" state so that I can fall back to another image?
One quick solution would be to load the image file using xmlrpc and check that its md5sum is 30234b543d5438e0a0614bf07f1ebd25, or that its size is 1717 bytes (it's unlikely that another image can have exactly the same size), but that's not very robust since I have seen Google change the position of the text in the image. Though it's a very good start for a prototype.
You could go for image processing instead. Note that it's still not perfectly robust since Google could decide to change the looks of the image anytime. You'll have to decide whether it's worth it.
Anyway, here is how I would do it using jQuery:
load the image and open a 2D context for direct pxiel access (see this question for how to do it)
analyse the image:
sample groups of 2×2 pixels at random locations; I recommend at least 30 groups
a group of 2×2 pixels is good if all the pixels have the same value and their R/G/B values do not differ by more than 10% (ie. they're grey)
count the ratio of good pixel groups in the image
if there are more than 70% good pixel groups, then we are pretty sure this is the “no imagery” version: replace it with another image of your choice.
The reason I do not recommend testing directly for an RGB value is because JPEG decompression may have slightly different behaviours on different browsers.
this situation is already build in in the 3.0 version due
the boolean test status === streetviewStatus.Ok, here is a snippet from my situation solving
if (status === google.maps.StreetViewStatus.OK) {
var img = document.createElement("IMG");
img.src = 'http://maps.googleapis.com/maps/api/streetview?size=160x205&location='+ lat +','+ lng +'&sensor=false&key=AIzaSyC_OXsfB8-03ZXcslwOiN9EXSLZgwRy94s';
var oldImg = document.getElementById('streetViewImage');
document.getElementById('streetViewContainerShow').replaceChild(img, streetViewImage);
} else {
var img = document.createElement("IMG");
img.src = '../../images/ProfilnoProfilPicture.jpg';
img.height = 205;
img.width = 160;
var oldImg = document.getElementById('streetViewImage');
document.getElementById('streetViewContainerShow').replaceChild(img, streetViewImage);
}
As of 2016, you can use the new Street View Image Metadata API.
Now you just need the status field to know if a panorama is found.
Example requests:
https://maps.googleapis.com/maps/api/streetview/metadata?size=600x300&location=78.648401,14.194336&fov=90&heading=235&pitch=10&key=YOUR_API_KEY
{
"status" : "ZERO_RESULTS"
}
https://maps.googleapis.com/maps/api/streetview/metadata?size=600x300&location=eiffel%20tower,%20paris,%20france&heading=-45&pitch=42&fov=110&key=YOUR_API_KEY
{
...
"status" : "OK"
}
You can use the getPanoramaByLocation function (see http://code.google.com/apis/maps/documentation/javascript/services.html#StreetViewService).
try something like this:
function handleMapClick()
{
var ll= new google.maps.LatLng(latitude,longitude);
sv.getPanoramaByLocation(ll, 50, processSVData);
}
function processSVData(data, status) {
if (status==google.maps.StreetViewStatus.ZERO_RESULTS)
{
<DO SOMETHING>
}
}
Request a google street view image and if it has a specific file size it is a 'Not street view avaible'. I did the follwing:
var url = 'google street view url';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function (e) {
if (this.status == 200) {
try {
var image = new Blob([this.response], {type: 'image/jpeg'});
if (image.size) {
if (url.indexOf('640x640') > -1 && image.size === 8410) {
// Not street view
}
if (url.indexOf('400x300') > -1 && image.size === 3946) {
// Not street view
}
}
} catch (err) {
// IE 9 doesn't support blob
}
}
};
xhr.send();
Another way is to load the image and then compare some pixels colors. The "no streetview" image from google is always the same. Here is how you would compare 2 pixels:
var url = STREETVIEWURL
var img = new Image();
// Add some info to prevent cross origin tainting
img.src = url + '?' + new Date().getTime();
img.setAttribute('crossOrigin', '');
img.crossOrigin = "Anonymous";
img.onload = function() {
var context = document.createElement('CANVAS').getContext('2d');
context.drawImage(img, 0, 0);
//load 2 pixels. I chose the first one and the 5th row
var data1 = context.getImageData(0, 0, 1, 1).data;
var data2 = context.getImageData(0, 5, 1, 1).data;
console.log(data1);
// google unknown image is this pixel color [228,227,223,255]
if(data1[0]==228 && data1[1]==227 && data1[2]==223 && data1[3]==255 &&
data2[0]==228 && data2[1]==227 && data2[2]==223 && data2[3]==255){
console.log("NO StreetView Available");
}else{
console.log("StreetView is Available");
}
};
Some potential issues:
I've seen some errors with CrossOrigin tainting. Also, if google changes the image returned this code will break.

Resources