threejs canvas todataurl is blank - html

Okay, I know that canvas.toDataUrl() will produce an image in png format. However, when I try to get the image from http://threejs.org/examples/#webgl_lines_sphere. All I see is a black image on chrome. To replicate the steps -
1) Open dev console and select the canvas element.
2) canvas = $0
3) context = canvas.getContext('webgl', {preserveDrawingBuffer: true})
4) img = canvas.toDataUrl()
5) document.write('<img src="'+img+'"/>')
The image is blank.
However, I tried with a different canvas at link http://threejs.org/examples/#canvas_geometry_cube. Please do the following steps to replicate.
1) Open dev console and select the canvas element.
2) canvas = $0
3) context = canvas.getContext('2d', {preserveDrawingBuffer: true})
4) img = canvas.toDataUrl()
5) document.write('<img src="'+img+'"/>')
This gave the expected result. Why is there a difference and how can this be avoided to retrieve first image too?

I was also getting a solid black image.
My code previously was:
this.renderer = new THREE.WebGLRenderer({premultipliedAlpha: false});
I have changed the parameter in the THREE.WebGLRenderer to:
this.renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true});
I am getting an image on taking a snapshot.
Hope it helps.

This is because the first example (see sources line 103) does use a THREE.WebGLRenderer creator, while the second one (see sources line 92) uses a THREE.CanvasRenderer.
Some notes :
There is no preserveDrawingBuffer contextAttribute in the context2d API, only in the WebGL one.
You can create only one context per canvas element.
You can't set the preserveDrawingBuffer flag after context's creation
With three.js you can simply call renderer.domElement.toDataURL() (you'll need to go to the iframe target to be able to call from the dev tools).
Another solution, (and better than preserveDrawingBuffer flag) is to call canvas.toDataURL() in the rendering loop itself, before the browser takes controls again.

Related

Unexpected CORS issue for normal images in Chrome and iOS Safari

I'm facing a CORS issue that is driving me insane. Allow me to share an example URL:
http://www.jungledragon.com/image/19905/mature_female_eastern_forktail.html/zoom
As the issue can only be reproduced once per page, here is a list of other images:
http://www.jungledragon.com/all/recent
From that overview, you can open any photo page. Next, from that photo page click the image once more to launch it fullscreen, as that is where the issue lies.
Now allow me to explain the setup, and the problem. The site itself is hosted on a Linux server within my control. The site is at www.jungledragon.com. The images, however, are stored at Amazon S3, where the image bucket has an alias of media.jungledragon.com.
The basic situation is simple:
<div id="slideshow-image-container">
<div class="slideshow-image-wrapper">
<img src="http://media.jungledragon.com/images/1755/19907_large.jpg?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&Expires=1409788810&Signature=QH26XDrVuhyr1Qimd7IOBsnui5s%3D" id="19907" class="img-slideshow img-sec wide" data-constrained="true" data-maxheight="2056" crossorigin="anonymous">
</div>
</div>
As you can see, I'm just using the normal 'html' way of loading an image. The image URL is signed and can time out, but that shouldn't be relevant. It is my understanding that CORS does not apply to this situation, since loading images from an external domain this way has been supported for decades. The image is not loaded using javascript, after all.
Just to be sure though, the crossorigin attribute is set in HTML. Furthermore, as a way of testing, I have set a very liberal CORS policy on the image bucket:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Content-Type</AllowedHeader>
<AllowedHeader>x-amz-acl</AllowedHeader>
<AllowedHeader>origin</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Now, the situation gets a bit more complicated. The fullscreen image viewer is supposed to get a background color that is the dominant/average color of the actual image on screen. That color is calculated using canvas, yet it is only calculated once. The first time it is calculated for that image, the result is communicated to the back-end using an ajax call and then stored forever. Subsequent visits to the image will not run the calculation logic again, it will simply set the background color of the body element and all is good.
Here is the logic that does the calculation:
<script>
$( document ).ready(function() {
<?php if (!$bigimage['dominantcolor']) { ?>
$('#<?= $bigimage['image_id'] ?>').load(function(){
var rgb = getAverageRGB(document.getElementById('<?= $bigimage['image_id'] ?>'));
document.body.style.backgroundColor = 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')';
if (rgb!==false) {
$.get(basepath + "image/<?= $bigimage['image_id'] ?>/setcolor/" + rgb.r + "-" + rgb.g + "-" + rgb.b);
}
});
<?php } ?>
});
Yes, I'm mixing in back-end code with front-end code. The above code says that if we do not yet know the dominant color in the scene, calculate it. The load function is used because at document ready, the actual image from the normal html may not have been loaded completely. Next, if the dominant color is not known yet, and the image is loaded, we trigger the function that calculates the dominant color. Here it is:
function getAverageRGB(imgEl) {
var blockSize = 5, // only visit every 5 pixels
defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {r:0,g:0,b:0},
count = 0;
if (!context) {
return defaultRGB;
}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
imgEl.crossOrigin = "anonymous";
context.drawImage(imgEl, 0, 0);
try {
data = context.getImageData(0, 0, width, height);
} catch(e) {
/* security error, img on diff domain */
return false;
}
length = data.data.length;
while ( (i += blockSize * 4) < length ) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i+1];
rgb.b += data.data[i+2];
}
// ~~ used to floor values
rgb.r = ~~(rgb.r/count);
rgb.g = ~~(rgb.g/count);
rgb.b = ~~(rgb.b/count);
return rgb;
}
The following line is CORS-relevant:
data = context.getImageData(0, 0, width, height);
Although I believe I have set up CORS correctly, I can live with this code failing in some browsers. It seems to work fine in Firefox and IE11, for example. If it fails, I would expect it to fail calculating the dominant color. However, something far worse is happening in highly specific cases: the image is not shown alltogether.
My thinking is that my 'classic' loading of the image via img src tags should have nothing to do with this script working or failing, in all cases at least the image should just load, irrespective of the canvas trick.
Here are the situations I discovered where the image does not load alltogether, which I consider a major issue:
On iOS7 on iPhone 5, the first load works fine. The calculation may fail but the image loads. Refreshing the page often breaks the image. 3rd and 4th tries then continue to succeed, and so on.
Worse, at work in Chrome 36 the image does not load alltogether. I say at work, since at home it is not an issue. Possibly a proxy makes the difference. I can refresh all I want, for images that do not have the calculation ran yet, it keeps failing.
The natural thing to do then is to debug it using Chrome's inspector. Guess what? With the inspector open, it always succeeds. The image will always load and the CORS request headers and responses look perfectly fine. This leaves me with virtually no way to debug this. I can tell though that when opening the inspector when the image does not load does give me the "CORS error" in the console, from the previous request I made. Refreshing with the inspector open will then make that go away.
From reading other questions I've learned that cache may be an influence, yet more likely the issue lies in the origin header not sent by the browser. I believe the issue may be in that direction, yet I fail to understand this:
How it influences my "normal" loading of the image using img tags
How it is only an issue behind a proxy (supposedly) in Chrome, and only when the inspector windows is closed
How it works so unreliably and inconsistently in Safari on iOS
As said, I can live with only some browsers succeeding with the canvas part, but I can't live with the image not being normally loaded in any case. That part should just work.
I realize the situation is incredibly hard for you to debug, but I hope my explanation triggers some much-needed help.
Update: I've discovered that when I remove crossorigin="anonymous" from the img tag, the image will load correctly in the specific scenarios I mentioned. However, the consequence of that move is that the color calculation will no longer work in Chrome, not at home and not at work. It continues to work in Firefox though. I'm investigating what to do next.
I managed to solve the issue myself. I still cannot fully explain cause and effect here, but this is what I did:
I removed crossorigin="anonymous" from the html's img element. This will at least make sure that the image is always loaded.
The color calculation part I solved by basically rewriting its logic:
var imgSrc = $('#<?= $bigimage['image_id'] ?>').attr('src');
var cacheBurstPrefix = imgSrc.indexOf("?") > -1 ? '&' : '?';
imgSrc += cacheBurstPrefix + 'ts=' + new Date().getTime();
var imagePreloader = new Image();
imagePreloader.crossOrigin = "Anonymous";
imagePreloader.src = imgSrc;
$(imagePreloader).imagesLoaded(function() {
var rgb = getAverageRGB(imagePreloader);
document.body.style.backgroundColor = 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')';
if (rgb!==false) {
$.get(basepath + "image/<?= $bigimage['image_id'] ?>/setcolor/" + rgb.r + "-" + rgb.g + "-" + rgb.b);
}
});
Instead of reusing the img element from the html, I'm creating a new in-memory image element. Using a cache bursting technique I'm making sure it is freshly loaded. Next, I'm using imagesLoaded (a 3rd party plugin) to detect the event of this in-memory image being loaded, which is far more reliable than jQuery's load() event.
I've tested extensively and can confirm that in no case does normal image loading ever break again. It works in every browser and proxy situation. As an added bonus, the color calculation part now seems to work in far more browsers, including several mobile browsers.
Although I am still not confident on the root cause, after much frustration I'm very happy with the new situation.

Downloading a dynamically generated SVG file from the browser

If I create an image using HTML SVG element, can I then offer this as an SVG file download to the user. For example I may want to load an SVG image, apply some basic transformations to it, add some text, then let the user download the result as a vector image.
Is that possible? I have been doing something similar with Canvas but have been struggling creating a vector image. I wasn't aware that SVG elements were so versatile when I cam across them this morning but if I can do the above it would be great.
Simple solution using a data URI:
var svg_root = document.getElementById('your_svg_root_element_here');
var svg_source = svg_root.outerHTML;
var svg_data_uri = 'data:image/svg+xml;base64,' + btoa(svg_source);
var link = document.getElementById('anchor_element');
link.setAttribute('href', svg_data_uri);
Although it worked, when clicking on the link, the browser stalled for a few seconds.
This seems to be the simplest solution and should be compatible with all modern browsers. However, it has some noticeable overhead. If someone else knows a different solution (maybe using blobs or something similar), please add here as another answer!

Chrome canvas rendering issue

I've created a small javascript game and I tested on my local computer in all major browsers and it works fine . After that I uploaded the game on my hosting server and the game won't display in Chrome , the canvas area is grey but it works fine in firefox , anyone knows why ? Here is the link for the demo
http://djordjepetrovic.rs/igrica/
In your catcher_game.js file I found at least on of this:
draw: function(){
basket_catcherImg = new Image();
basket_catcherImg.src = 'images/basket.png';
ctx.drawImage(basket_catcherImg, this.x, this.y, this.w, this.h);
// ...
This won't work very well. It works locally on your computer as the image is cached from disk.
Loading images is an asynchronous operation so your drawImage needs to wait until the image is loaded - the proper way is:
draw: function(){
var me = this;
basket_catcherImg = document.createElement('img');
basket_catcherImg.onload = function() {
ctx.drawImage(basket_catcherImg, me.x, me.y, me.w, me.h);
}
basket_catcherImg.src = 'images/basket.png';
//...
You need to do this with other such instances of img as well.
The reason you need me here is because this is changed to the image element when called on the onload callback. So you need to keep a reference to the original this context.
Also replace new Image() to createElement('img') as there is currently an issue in Chrome that doesn't handle this correctly.
Nice graphics by the way!

How do you change the background color of a live tile at runtime?

I'm trying to change the background color of a live tiles on my Windows 8 application at runtime. Is this possible? Does anybody have a code snippet?
Background color is specified in app manifest. Thus it does not seems to be possible.
The same can be emulated by sending custom time at runtime with appropriate background image (for a color). This is quirky way, but my little mind can think of this approach only.
Suppose you have already fixed the problem but I will post this info here, might be useful if someone come accross this thread.
For Primary Tile the answer from Bruno Lemos is correct.
For Seconday Tiles I would use TileUpdateManager.CreateTileUpdaterForSecondaryTile(Tile2_ID).
Using TileNotifications and TileUpdaterManager works instantly to me.
Using the Tile templates you will have to modify some XML code from the template, but you could also download NotificationsExtensions NuGet package and do it this way:
var tile2 = TileContentFactory.CreateTileSquare150x150PeekImageAndText01();
tile2.Branding = TileBranding.Name;
tile2.Image.Src = "ms-appx:///assets/Logo-transparent.png"; //Useful to have the same logo image with transparent background and other colors
tile2.TextHeading.Text = "Heading";
tile2.TextBody1.Text = "String1";
tile2.TextBody2.Text = "String2";
tile2.TextBody3.Text = "String3";
var doc = new XmlDocument();
doc.LoadXml(tile2.ToString());
var updater = TileUpdateManager.CreateTileUpdaterForSecondaryTile(tile2_id);
updater.EnableNotificationQueueForSquare150x150(true); //enables up to 5 different tile *animations* for 150 square icon, can be enabled for other sizes too
updater.Update(new TileNotification(doc) { Tag = "1" });
The code above takes into account the SecondaryTile has been created previously.
You can see the different templates for icons here: https://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.ui.notifications.tiletemplatetype.aspx
You only have to change the line TileContentFactory.CreateNAMEOFTEMPLATE and fill the different string/image fields.
Info about EnableNotificationQueue to have more than 1 animation in the tile can be found here: https://msdn.microsoft.com/en-us/library/windows/apps/hh781199.aspx
For Primary / Default Tile, you can do what #Tilak said: create tile images notifications and use TileUpdateManager.CreateTileUpdaterForApplication().Update to update it.
For Secondary Tiles, do the following:
In the app manifest, set the background color to "transparent"
In the app manifest, use images with transparent background
Now you can do that at runtime:
var tile = new SecondaryTile("YOUR_TILE_ID");
tile.VisualElements.BackgroundColor = Colors.Red;
await tile.UpdateAsync();
The problem is: The background is not updating immediately and I don't
know why. But you can see the effect after sign out / sign in, for
example. (tested using windows 10)
So this answer is incomplete, but hopefully can give insights to people coming here from google, like me.
#brunolemos

How to Copy Contents of One Canvas to Another Canvas Locally

I'd like to copy ALL contents of one canvas and transfer them to another all on the client-side. I would think that I would use the canvas.toDataURL() and context.drawImage() method to implement this but I am running into a few issues.
My solution would be to get Canvas.toDataURL() and store this in an Image object in Javascript, and then use the context.drawImage() method to place it back.
However, I believe the toDataURL method returns a 64 bit encoded tag with "data:image/png;base64," prepended to it. This does not seem to be a valid tag, (I could always use some RegEx to remove this), but is that 64 bit encoded string AFTER the "data:image/png;base64," substring a valid image? Can I say image.src=iVBORw...ASASDAS, and draw this back on the canvas?
I've looked at some related issues:
Display canvas image from one canvas to another canvas using base64
But the solutions don't appear to be correct.
Actually you don't have to create an image at all. drawImage() will accept a Canvas as well as an Image object.
//grab the context from your destination canvas
var destCtx = destinationCanvas.getContext('2d');
//call its drawImage() function passing it the source canvas directly
destCtx.drawImage(sourceCanvas, 0, 0);
Way faster than using an ImageData object or Image element.
Note that sourceCanvas can be a HTMLImageElement, HTMLVideoElement, or a HTMLCanvasElement. As mentioned by Dave in a comment below this answer, you cannot use a canvas drawing context as your source. If you have a canvas drawing context instead of the canvas element it was created from, there is a reference to the original canvas element on the context under context.canvas.
Here is a jsPerf to demonstrate why this is the only right way to clone a canvas: http://jsperf.com/copying-a-canvas-element
#robert-hurst has a cleaner approach.
However, this solution may also be used, in places when you actually want to have a copy of Data Url after copying. For example, when you are building a website that uses lots of image/canvas operations.
// select canvas elements
var sourceCanvas = document.getElementById("some-unique-id");
var destCanvas = document.getElementsByClassName("some-class-selector")[0];
//copy canvas by DataUrl
var sourceImageData = sourceCanvas.toDataURL("image/png");
var destCanvasContext = destCanvas.getContext('2d');
var destinationImage = new Image;
destinationImage.onload = function(){
destCanvasContext.drawImage(destinationImage,0,0);
};
destinationImage.src = sourceImageData;