I'm currently working on a Google Chrome app that will allow users to open image files for viewing.
What I found was that it was far too hurtful to performance to read many files concurrently, so I decided to make a queue, and a limit as to how many files can be read concurrently.
However, the issue I am facing is with my file reading function, readFile(file).
At one point, files stop being read, and I am left with 3 readers, and all the remaining files still in the queue. This is the result:
I have rewritten and omitted some parts of the JavaScript and CSS to make it web browser ready, and here is the JSFiddle.
Here is the function in its entirety, along with some of the variables associated within the function:
var data = [], readers = [];
function readFile(file){
if(readers.length >= 4){
//if there are already 4 files being read concurrently
queue.push(file);
}else{
//Note: remove last reader, first from queue
var item = document.createElement('div');
item.innerHTML = '<img/>Loading...';
fileList.appendChild(item);
readers.push(new FileReader());
var currentReader = readers[readers.length-1]; //get the last reader
currentReader.addEventListener('error',function(){
});
currentReader.addEventListener('load',function(event){
var result = this.result||event.target.result;
var img = item.getElementsByTagName('img')[0];
var tmp = new Image();
tmp.src = result;
data.push({
"file":file,
"name":file.name,
"ext":file.name.substring(file.name.lastIndexOf(".")),
"item":item,
"img":result,
"thumb":img
});
tmp.addEventListener('load',function(){
canvas2.width = canvas2.height = thumbnailDimension;
ctx2.drawImage(this,0,0,canvas2.width,canvas2.height);
img.src = canvas2.toDataURL('image/webp','.1');
setTimeout(function(){
if(queue.length){
readFile(queue[0]);
queue.splice(0,1);
}
readers.splice(readers.length-1,1);
},1000);
});
});
currentReader.readAsDataURL(file);
}
}
For my app, I want to read the files, resize a canvas, and draw the images onto it to make thumbnails for a side panel. For that reason, the temporary image, img, must be loaded before the next file can be read (to prevent things from being added to the queue twice). As you can see, I set a one second delay, but I am still having an issue.
Thank you all for your helpful answers in advance. If you would like to reproduce the problem I am having for yourself, just select a LOT of image files. I chose 168 JPG files (each around 500-600k).
Update: 1/7/2015 11:39am EST: I did some inspecting and found that the tmp would stop firing it's onload event. Maybe it's bad to have one event listener inside of another? If so, how could I achieve getting the thumbnails to load at a proper time?
#RickyAYoder, seems there is an error on the page. Run debugger on your browser (Chrome is the best!) to see if there are any other errors. I see launchData is not defined in your code:
function onLaunch(){
if(!(launchData && launchData.items && launchData.items[0])) return;
for(var x = 0; x < launchData.items.length; x++){
//iterate through files passed to app
launchData.items[x].entry.file(function(file){
});
}
Draw();
}
onLaunch();
can you check to see if it is defined somewhere??
Related
I found out that you can have a fallback URL for an <img> using onerror if the first loading fails.
But it is possible to provide a list of sources (urls) to try and keep trying until one of them loads?
I was messing around with this for about an hour and was working towards this solution with JavaScript. I think its very clunky and I wonder if there is more more pretty way of doing this.
My ugly approach
Each image has several data attributes such as fallback1, fallback2 with the alternatives sources to load from if loading fails. Every image also has this.onerror=null;this.src='error.jpg
So every image that fails to load will show the picture error.jpg instead.
So an image may look like this
<img src="https://i.imgur.com/iYNdJeW.jpg" onerror="this.onerror=null;this.src='error.jpg'" data-fallback1="https://i.imgur.com/iYNdJeW2.jpg" data-fallback2="https://i.imgur.com/iYNdJeW3.jpg" />
Call a script in window.onload that iterates over each image and check if the source ends with error.jpg.
If it does it takes the first fallback url in that image's data properties (fallback1) and changes the source to that. Then removes that data-property from the image (not sure if that is possible or meant to be done), because he has already been tried. Then the script recursively runs again.
The recursion stops if if no images' source ends in error.jpg OR If all images whose source ends with error.jpg do not have any data attributes (no more alternative sources to try).
I think it would work, but it seem very very hackish.
I found out about <picture> and <source> and was very optimistic, but <picture> just accepts a 404 if the image does not load.
Has anyone come up with a better approach for doing this? Ideally I wish you could give img a list of urls and it would just keep trying until it got a non-error.
You're setting onerror to null when your first error is fired. Onerror will automatically fire if you change the source and it fails to load again. You could just store the list of fallbacks and increment an index each time onerror is fired.
Here's an example, and you could easily convert this to store all of the different variables directly on the element. I've provided 4 fake URLs and a final placeholder image as a real URL. You'll see it does load the placeholder image.
var fallbacks = ["https://example.com/badurl1.jpg", "https://example.com/badurl2.jpg", "https://example.com/badurl3.jpg", "https://via.placeholder.com/350x150"];
var index = 0;
document.querySelector("img").onerror = function(){
if(index >= fallbacks.length) return;
let next = fallbacks[index];
this.src = next;
index++;
};
<img src="https://example.com/badurl.jpg"/>
This will cycle through a list until your image stops getting an error
to edit it, change the 'fallbacksrc' array
HTML
<img src="*" id="img" onerror="ImgOnError()"></img>
JS
let fallbacksrc=[]; //All All Fallbacks Here, when error will start at the second one because 1st one is already tried
let fallbackcount = 1;
let Img = document.getElementById('img')
function ImgOnError(){
if(fallbackcount >= fallbacksrc.length){
fallbackcount = 0
Img.src=fallbacksrc[fallbackcount]
}else{
Img.src=fallbacksrc[fallbackcount]
}
fallbackcount++;
}
I've come up with a vanilla-JavaScript way where you don't need to specify the image in javascript.
Define this function:
function incrementFallbackSrc(img, srcs) {
if (typeof img.fallbackSrcIndex === 'undefined') img.fallbackSrcIndex = 0;
img.src = srcs[img.fallbackSrcIndex++];
}
and for each image, pass all the fallback URLs in the function call.
<img
src="https://i.imgur.com/iYNdJeW.jpg"
onerror="javascript: incrementFallbackSrc(this, ['https://i.imgur.com/iYNdJeW2.jpg',
'https://i.imgur.com/iYNdJeW3.jpg',
'error.jpg'])">
>
to set it programmatically, you may have to put the function inside the definitiaion like so:
img.setAttribute('onerror', "function incrementFallbackSrc(img, srcs) {if (typeof img.fallbackSrcIndex === 'undefined') img.fallbackSrcIndex = 0;img.src = srcs[img.fallbackSrcIndex++];}; incrementFallbackSrc(this, ['" + fallbackUrl1 + "," + fallbackUrl2 + "'])");
I am making my first steps coding with JavaScript and also playing with a webgl library called Three.js.
After see some tutorials and make some experiments I finally made this: https://dl.dropboxusercontent.com/u/15814455/Monogram.html.
As you can see in my code, the object reflects randomly a group of 6 images that I have in a folder of 13 images.
var numberOfImages = 13, images = [];
for (var i = 1; i <= numberOfImages; i++) {
images.push('sources/instagram/image' + i + ".jpg");
}
var urls = images.sort(function(){return .6 - Math.random()}).slice(0,6);
var reflectionCube = THREE.ImageUtils.loadTextureCube( urls );
reflectionCube.format = THREE.RGBFormat;
The thing is that each time I upload an Instagram picture, it will be saved in that folder called instagram.
Now my problem is that, if I upload for example, 10 images to the folder I have to change this line of code: var numberOfImages = 13 to this var numberOfImages = 23.
So I am looking for a way to modify my code and not to set a limit of number of images. So I could upload images in the folder and then automatically see them in my 3d object.
I've been reading on internet and I found that I can use something called regular expressions in my code to solve this problem.
I would like to know if using regular expressions is a real solution. Is it worth to learn regular expressions to solve this problem?
Do you have some suggestion? There is another solution? Maybe its something simple and I should write a different line of code, but if it's something more complicated and I should learn some language I would like to learn the right language.
First off, if you are going to be programming at length in pretty much any language, it will be worth knowing regular expressions and how/when to use them so it's will be useful to learn them.
If this was a client/server problem where you controlled the server, the usual way to solve this problem is that the server would scan the file system on the server and it would tell the client how many images to prepare for.
If you have to solve this entirely on the client, then you can't directly scan the file system from the client, but you can request increasing file numbers and you can see (asynchronously) when you stop getting successful loading of images. This is not particularly easy to code because the response will be asynchronous, but it could be done.
Here's a scheme for preloading images and finding out where they stopped preloading successfully:
function preloadImages(srcs, callback) {
var img, imgs = [];
var remaining = srcs.length;
var failed = [];
function checkDone() {
--remaining;
if (remaining <= 0) {
callback(failed);
}
}
for (var i = 0; i < srcs.length; i++) {
img = new Image();
img.onload = checkDone;
img.onerror = img.onabort = function() {
failed.push(this.src);
checkDone();
}
img.src = srcs[i];
imgs.push(img);
}
}
var maxNumberOfImages = 30, images = [];
for (var i = 1; i <= maxNumberOfImages; i++) {
images.push('sources/instagram/image' + i + ".jpg");
}
preloadImges(images, function(failed) {
var regex = /(\d+)\.jpg$/;
var nums = failed.map(function(item) {
var matches = item.match(regex);
return parseInt(matches[1], 10);
}).sort();
var numImages = nums[0];
// now you know how many good images there are
// put your code here to use that knowledge
});
Note: This does use a regular expression to parse the image number out of a URL.
It would be possible to code this without a preset limit, but it would be more work. You'd probably request images 10 at a time and if you didn't get any errors, then request the next block of 10 until you found the first failure.
In an effort to reduce clientside load, we are attempting to do the work of flattening Paper.js layers on a Node Express server. We have many layers to flatten with lots of image data. And rather than overwriting our data structure, we want to end up with new objects containing the rasterized (flattened) layers.
So we have an Express route that looks like this:
app.post('/flatten', function (request, response) {
var pdfs = JSON.parse(request.body.pdfs);
// Attempt to set up canvas on the server side to work with
var canvas = new paper.Canvas(1000, 1000);
paper.setup(canvas);
paper.view.draw();
for (var i = 0; i < pdfs.length; i++) {
var pdf = pdfs[i];
if (pdf !== null) {
for (var j = 0; j < pdf.pages.length; j++) {
if (pdf.pages[j].layer !== undefined) {
paper.project.layers.push(pdf.pages[j].layer); // Attempt to add to current project; necessary?
pdf.pages[j].layer.activate(); // Blows up
pdf.pages[j].layer.visible = true;
var layerAsRaster = pdf.pages[j].layer.rasterize(); // Blows up
layerAsRaster.visible = false;
var dataString = layerAsRaster.toDataURL();
pdfs[i].pages[j].pageImageData = dataString.split(',')[1];
pdf.pages[j].layer.visible = false;
}
}
}
}
response.send(pdfs);
});
The .layer is a native Paper.js layer that was made on the clientside.
We receive this error when hitting this route:
TypeError: pdf.pages[j].layer.activate is not a function
Thinking that perhaps we don't need to worry about activating layers on the serverside, I commented that out, but got the same error for the .rasterize line. (See the two lines commented "Blows up".)
Do I need to somehow import the layers we're receiving from the client into the project? I attempt to do that with the line:
paper.project.layers.push(pdf.pages[j].layer);
but to no avail.
How can I modify this method to successfully work with layers on the serverside?
The problem is that you are directly adding the layer to the project with the line paper.project.layers.push(pdf.pages[j].layer);
You're not allowed to directly manipulate paper's data structures. If you want to add a layer to a project use the following (note that this is not documented and will change with the next release of paper, but I don't think you'll need to do this):
(paperscript)
project.addChild(layer);
(javascript)
paper.project.addChild(layer);
It's not clear how pdf.pages[i].layer was created on the server side, whether it was imported via JSON (in which case it could already be inserted into the project), or whether it was removed from another project, so there may be other complications.
I think there is another problem. It doesn't appear that pdf.pages[i].layer has been turned into a server-side layer. So the key question is how was it transferred from the client to the server?
Here's a stab at the whole process:
(client side)
jsonLayer = paper.project.activeLayer.exportJSON();
// send jsonLayer to server using some method
(server side)
// get jsonLayer from client
layer = new paper.Layer();
layer.importJSON(jsonLayer);
layer should already be inserted into the project and should contain all the items that were in jsonLayer which was the layer on the client.
Here's a link to a discussion on how importJSON and exportJSON map to one another:
paperjs group discussion
Recently I have started working on a simple top down tile based program where you can move a player character around the map and zoom in and out of the map.
It's been going well, I have a background drawn and now I want to start drawing some tiles.
Currently I have something that looks kind of like this:
var tileset1 = new Image();
tileset1.src = "Images/Tileset1.gif";
var tx = [];
var ty = [];
var txo = [];
var tyo = []; //Background tile x and y on the map and the x and y offset in the image for drawing
var tilesize = 32; //constant for each tiles width and height in pixels.
function map1data() {
"use strict";
tx[0] = 0;
ty[0] = 0;
txo[0] = 0;
tyo[0] = 0;
tx[1] = 32;
ty[1] = 0;
txo[1] = 32;
tyo[1] = 0;
}
map1data();
ctx.drawImage(tileset1, txo[i], tyo[i], tilesize, tilesize, tx[i], ty[i], tilesize, tilesize);
This works fine, but my main issue is with using arrays to draw the tiles and having to give the properties of each tile by hard coding it into the script.
If my map had 100 tiles in it, i would have to manually write 400 lines of code, not ideal.
So what I'm wondering is there a way to source a plain text file on the actual server the web page is hosted on (As in, in the same root file system as the index page?) and set a variable to the contents of that, like I did with the images? Rather than having to use DOM to request it from a specific servers url?
Is there a reason you are not wanting to use ajax? You can use PHP as follows (assume the following file is called myscript.php):
//place any opening javascript code here if necessary.
<?php
// open a file using the fopen function
// read each line of the file using the fgets function
// convert each line into your appropriate javascript code such as an array etc.
// echo the javascript code
//place any closing javascript code here if necessary.
Then you can include it just like including any other javascript file:
<script src="myscript.php"></script>
Also, since it is in a PHP script, you can reference files whether they are in the web root or outside of the web root.
You can use the XMLHttpRequest API. What this does is tell the browser to request the specified file from the server without loading a new page and presenting it to the user. You can then assign the returned data to a variable in your code and work with it from there.
If you don't mind using jQuery, you could use the .get() method, which has simpler syntax:
$.get( "tiles.txt", function( data ) {
// assign 'data' to an existing variable or pass into a function
});
You might want to look into storing the data as JSON or another similar format rather than plaintext (it'll be easier to manipulate).
I am trying to capture a still frame from an (any) external swf file, by using my own flash movie as a proxy to load it and hand information regarding the Stage onto javascript. I want to keep it as wide compatible as possible, so I went with AS2 / Flash 8 for now.
The script works fine in the Flash debugger, i.e. the
trace(flash2canvasScreenshot.getPixel(w, h).toString(16));
returns the correct pixel color, where as:
ExternalInterface.call("sendToJS",flash2canvasScreenshot.getPixel(w, h).toString(16));
in the published movie doesn't.
This method can obviously be quite slow for large flash (dimension wise) movies, as it iterates every single pixel. If someone has any better methods in mind, feel free to share, but as said, the problem I am facing is that I am getting differentiating results in debugging and publishing, with the pixel information not getting fetched when published.
import flash.display.BitmapData;
import flash.external.*;
var myLoader:MovieClipLoader = new MovieClipLoader();
var mclListener:Object = new Object();
mclListener.onLoadInit = function(target_mc:MovieClip)
{
var stageW = Stage.width;
var flash2canvasScreenshot:BitmapData = new BitmapData(stageW, Stage.height, false, 0x00000000);
var pixels:Array = new Array();
flash2canvasScreenshot.draw(element);
for (w = 0; w <= stageW; w++)
{
trace(flash2canvasScreenshot.getPixel(w, h).toString(16)); // this gives correct color value for the pixels in the debugger
ExternalInterface.call("sendToJS",flash2canvasScreenshot.getPixel(w, h).toString(16)); // this just returns the bitmap default color, 0 in this case.
/*
for (h = 0; h <= Stage.height; h++)
{
var pixel = flash2canvasScreenshot.getPixel(w, h).toString(16);
pixels.push(pixel);
}
*/
}
//ExternalInterface.call("sendToJS",pixels.toString());*/
};
myLoader.addListener(mclListener);
myLoader.loadClip("http://i.cdn.turner.com/cnn/cnnintl_adspaces/2.0/creatives/2010/6/9/21017300x250-03.swf", 0);
//myLoader.loadClip("https://s.ytimg.com/yt/swfbin/watch_as3-vflJjAza6.swf", 0);
//myLoader.loadClip(_level0.flash2canvasurl, _root.mc);
There are few problems with the snippet you posted:
like the one Joey mentioned, but the one that stands out from my
point of view is the element variable which isn't defined
anywhere, so that either is a type o, or you're trying to draw an
undefined object.
You're drawing as soon as the load is finished, but the animation you're loading might start slightly later. Maybe take the snapshot a bit after the load is complete.
Haven't touched as2 for some time and don't remember how security issue are handled, but if you're swf is loading another swf from a different domain, then the domain hosting the swf you're loading should also have a crossdomain.xml policy file allowing you to access the content of the loaded swf. If you simply load and display a swf from another domain, that's fine. However, if you're trying to draw the swf using BitmapData, you're actually attempting to access pixel data from the content of that swf, therefore you would need permissions. If you have no control over the crossdomain policy file, you might need to use a server side script to copy/proxy the file over to a domain that can grant your loaded swf access.
Here's a simplified version of your snippet that works (sans the external interface/pixel values part):
var myLoader:MovieClipLoader = new MovieClipLoader();
var mclListener:Object = new Object();
mclListener.onLoadInit = function(target_mc:MovieClip)
{
var pixels:Array = new Array();
setTimeout(takeSnapshot,2000,target_mc);
}
myLoader.addListener(mclListener);
myLoader.loadClip("http://www.bbc.co.uk/science/humanbody/sleep/sheep/reaction_version5.swf",1);
//myLoader.loadClip("http://i.cdn.turner.com/cnn/cnnintl_adspaces/2.0/creatives/2010/6/9/21017300x250-03.swf", 1);
//myLoader.loadClip("https://s.ytimg.com/yt/swfbin/watch_as3-vflJjAza6.swf", 0);
function takeSnapshot(target:MovieClip):Void {
var flash2canvasScreenshot:BitmapData = new BitmapData(150, 150, false, 0x00000000);//tiny sample
flash2canvasScreenshot.draw(target);
_level1._alpha = 20;//fade the loaded content
_level0.attachBitmap(flash2canvasScreenshot,0);//show the snapshop. sorry about using _root
}
Here's a quick zoomed preview of the 150x150 snap:
Here's an as3 snippet to illustrate the security sandbox handling issue:
var swf:Loader = new Loader();
swf.contentLoaderInfo.addEventListener(Event.COMPLETE,loaderComplete);
swf.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR,loaderSecurityError);
swf.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,loaderIOError);
swf.load(new URLRequest("http://i.cdn.turner.com/cnn/cnnintl_adspaces/2.0/creatives/2010/6/9/21017300x250-03.swf"),new LoaderContext(true));
function loaderComplete(event:Event):void{
setTimeout(takeSWFSnapshot,2000);
}
function loaderSecurityError(event:SecurityErrorEvent):void {
trace('caught security error',event.errorID,event.text);
}
function loaderIOError(event:IOErrorEvent):void{
trace('caught I/O error',event.errorID,event.text,'\tattempting to load\t',swf.contentLoaderInfo.url);
}
function takeSWFSnapshot():void{
var clone:BitmapData = new BitmapData(swf.content.width,swf.content.height,false,0);
try{
clone.draw(swf.content);
}catch(e:SecurityError){
trace(e.name,e.message,e.getStackTrace());
}
addChild(new Bitmap(clone));
}
HTH
My approach to this would be:
-Use AS3 for the reason lukevanin commented:
Just remember that AS3 can load an AS2 SWF, but an AS2 SWF cannot load
an AS3 SWF, so you actually achieve greater compatibility (with your
content) if you publish AS3
-Use a proxy file to fetch the swf file to get around sandbox violation issues (although if the swf loads external resources and uses relative paths it might get a bit more complex)
-Take a snapshot of the frame ( see George Profenza's solution )
-Encode the image using base64 and send that** to a JS method, and then decode to get the image.
** I'm pretty sure there are no size limitations...