Javascript, load an image only if it exists - javascript

I've a series of folders containing images numbered sequentially (1.jpg, 2.jpg, 3.jpg… etc).
I'm trying to load the images with a for loop until the first non existing image is found.
I read some other article managing similar problem (check if an image exists) with onload and onerror callback functions, but I'm stuck.
Here there is some code I wrote to store the images in an array and display them in the HTML page along with their src:
var arrImages = new Array();
function loadImgSeq() {
for (var i = 0; i < 11; i++) {
arrImages[i] = new Image();
arrImages[i].src = "slides/" + i + ".jpg"
arrImages[i].width = 400;
document.body.appendChild(arrImages[i]);
document.write("<p>"+arrImages[i].src+"</p>");
}
}
PS:I've no experience in computer programming, I just do it as hobbyist.

Here's one:
var found = true;
while(found)
{
var img = new Image();
img.src = 'path';
found = img.naturalWidth? true : false;
}

do not document.write after load
do not mix append with document.write
Here is a way
var i=0,img;
function loadImgSeq() {
i++;
var img = new Image();
img.onload=function() {
document.body.appendChild(img);
loadImgSeg(); // if success, do it again - will not be called if error
}
img.src = "slides/" + i + ".jpg";// IMPORTANT, must be AFTER onload/onerror handler
}
UPDATE: If you get IE issues from cache, try
img.onload=img.complete=function() {

Related

Memory leak when loading images

I'm using the following code to load and resize images.
$(imagesToProcess.files).each(function (idx, file) {
var img = new Image();
img.onload = function (e) {
//var resized = _resizeImage(e.target);
URL.revokeObjectURL(e.target.src);
};
img.src = URL.createObjectURL(file);
});
This code results in a gigantic memory spike in Chrome, even though I commented out the actual resizing. What am I doing wrong?
This code, which is based on this answer, solved it for me
var fileQueue = [];
var lock = false;
var img = new Image();
img.onload = function (e) {
URL.revokeObjectURL(e.target.src);
lock = false;
};
$(imagesToProcess.files).each(function (idx, file) {
fileQueue.push(file);
});
var processQueue = setInterval(processFile, 250);
function processFile() {
if (fileQueue.length == 0) {
console.log('nothing in queue');
clearInterval(processQueue);
return;
}
if (!lock) {
img.src = URL.createObjectURL(fileQueue.shift());
lock = true;
}
}
Don't use much of anonymous functions instead use reference of the functions.
$(imagesToProcess.files).each(createImage);
function createImage(idx, file){
var img = new Image();
img.onload = imageOnload;
img.src = URL.createObjectURL(file);
}
function imageOnload(e) {
URL.revokeObjectURL(e.target.src);
}
In you code for every image creation one instance of function object is created. That is what causing memory leak in your code.
I ended up solving this memory leak in my program by using an alternate method of jpeg-decoding (using library rather than browser's Image object): https://stackoverflow.com/a/62584068/2441655

Using PDFkit in browser, inserting an image from a link

Is there a simple way to get an image from a url to put in a PDFKit pdf?
I have a PDF being automatically generated in-browser. There's an image I want included, to which I have a URL. The catch is that I'm generating the PDF in-browser. Since I have the URL available from the internet, it seems like there should be an easy way to turn that image into something readable by PDFKit.
Is there a way for Javascript to turn an image URL into a buffer readable by PDFKit?
What I want is what you'd like the following command to do:
doc.image('http://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg')
Thanks in advance. The solutions I found online have your server take in the link, and respond with a buffer. Is this the only way? Or is there a way all in-browser with no http posting?
This is a pretty old question but I'll add my notes since it's the first suggestion when looking for "pdfkit browser image" on Google.
I based my solution on the data uri option supported by PDFKit:
Just pass an image path, buffer, or data uri with base64 encoded data
to the image method along with some optional arguments.
So after a quick look around I found the general approach to get a data uri from an image URL was using canvas, like in this post. Putting it together in PDFKit's interactive browser demo:
function getDataUri(url, callback) {
var image = new Image();
image.crossOrigin = 'anonymous'
image.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
canvas.getContext('2d').drawImage(this, 0, 0);
// // Get raw image data
// callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));
// ... or get as Data URI
callback(canvas.toDataURL('image/png'));
};
image.src = url;
}
// Usage
getDataUri('http://pdfkit.org/docs/img/14.png', function(dataUri) {
// create a document and pipe to a blob
var doc = new PDFDocument();
var stream = doc.pipe(blobStream());
doc.image(dataUri, 150, 200, {
width: 300
});
// end and display the document in the iframe to the right
doc.end();
stream.on('finish', function() {
iframe.src = stream.toBlobURL('application/pdf');
});
});
I retrieve the image via AJAX as a base64-encoded string, then use the following code to convert the base64-encoded string into a usable buffer:
var data = atob(base64);
var buffer = [];
for (var i = 0; i < data.length; ++i)
buffer.push(data.charCodeAt(i));
buffer._isBuffer = true;
buffer.readUInt16BE = function(offset, noAssert) {
var len = this.length;
if (offset >= len) return;
var val = this[offset] << 8;
if (offset + 1 < len)
val |= this[offset + 1];
return val;
};
pdf.image(buffer);
See also https://github.com/devongovett/pdfkit/issues/354#issuecomment-68666894, where the same issue is discussed as applied to fonts.
I'll weigh my 2 cents on the issue as I just spent a good deal of time getting it to work. It's a medley of answers I've found googling the issue.
var doc = new PDFDocument();
var stream = doc.pipe(blobStream());
var files = {
img1: {
url: 'http://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg',
}
};
Use the above object at a place to store all of the images and other files needed in the pdf.
var filesLoaded = 0;
//helper function to get 'files' object with base64 data
function loadedFile(xhr) {
for (var file in files) {
if (files[file].url === xhr.responseURL) {
var unit8 = new Uint8Array(xhr.response);
var raw = String.fromCharCode.apply(null,unit8);
var b64=btoa(raw);
var dataURI="data:image/jpeg;base64,"+b64;
files[file].data = dataURI;
}
}
filesLoaded += 1;
//Only create pdf after all files have been loaded
if (filesLoaded == Object.keys(files).length) {
showPDF();
}
}
//Initiate xhr requests
for (var file in files) {
files[file].xhr = new XMLHttpRequest();
files[file].xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
loadedFile(this);
}
};
files[file].xhr.responseType = 'arraybuffer';
files[file].xhr.open('GET', files[file].url);
files[file].xhr.send(null);
}
function showPDF() {
doc.image(files.img1.data, 100, 200, {fit: [80, 80]});
doc.end()
}
//IFFE that will download pdf on load
var saveData = (function () {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
return function (blob, fileName) {
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
};
}());
stream.on('finish', function() {
var blob = stream.toBlob('application/pdf');
saveData(blob, 'aa.pdf');
});
The biggest issue I came across was getting the info from the arraybuffer type to a string with base64 data. I hope this helps!
Here is the js fiddle where most of the xhr code came from.
I did it using NPM package axios to get a base64 encoded buffer:
on the project folder:
npm i axios
code:
var axios = require('axios');
let image = await axios.get("url", {responseType: 'arraybuffer'});
doc.image(image.data, 12, h, {
width: 570,
align: 'center',
valign: 'center'
});

Pdf.js and viewer.js. Pass a stream or blob to the viewer

I'm having troubles in finding a solution for this:
I retrieve a PDF blob from a SQL filestream field using Javascript in this way (it's a lightswitch project)
var blob = new Blob([screen.WebReportsPdfFilesStream.selectedItem.Pdf], { type: "application/pdf;base64" });
I have the blob and I can even convert it in a filestream or to base64("JVBERi0....." or "%PDF 1.6 ......", etc.)
No problem so far.
Now I need to display it in a viewer. I prefer the viewer to open in a new window but i'm open to embed it into my page somehow.
I'm wondering if I can directly pass the blob or the stream to the viewer and display the document. I've tried something like
PDFView.open(pdfAsArray, 0)
Nothing happens in the embedded viewer in this case.
The pdfAsArray is good since I can display it appending the stream to a canvas within the same page. I just want to display the viewer, not embed the PDF in a canvas, possibly in a new window.
Can anyone provide few lines of code on how to achieve that in Javascript?
I'm using PDFJS.version = '1.0.1040'; PDFJS.build = '997096f';
The code that worked for me to get base64 pdf data loaded was this:
function (base64Data) {
var pdfData = base64ToUint8Array(base64Data);
PDFJS.getDocument(pdfData).then(function (pdf) {
pdf.getPage(1).then(function (page) {
var scale = 1;
var viewport = page.getViewport(scale);
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({ canvasContext: context, viewport: viewport });
});
});
function base64ToUint8Array(base64) {
var raw = atob(base64);
var uint8Array = new Uint8Array(raw.length);
for (var i = 0; i < raw.length; i++) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
}
}
This function could be the success function of an api call promise. What I'm doing here is rendering the pdf onto a canvas element myCanvas.
<canvas id="myCanvas"></canvas>
This shows the first page of the pdf but has no functionality. I can see why the viewer is desirable. If I get this hooked up to the viewer (viewer.html / viewer.js) I will edit my answer.
EDIT: How to hook up the viewer
1 In bower.json, add "pdfjs-viewer": "1.0.1040"
2 Html:
<iframe id="pdfViewer" src="lib/pdfjs-viewer/web/viewer.html" style="width: 100%; height: 700px;" allowfullscreen="" webkitallowfullscreen=""></iframe>
3 Change the stupid default document in the viewer.js file:
var DEFAULT_URL = '';
4 Controller:
var pdfjsframe = document.getElementById('pdfViewer');
pdfjsframe.onload = function() {
LoadPdfDocument();
};
$scope.myApiCallThatReturnsBase64PdfData.then(
function(base64Data) {
$scope.base64Data = base64Data;
LoadPdfDocument();
},
function(failure) {
//NotificationService.error(failure.Message);
});
function LoadPdfDocument() {
if ($scope.PdfDocumentLoaded)
return;
if (!$scope.base64Data)
return;
var pdfData = base64ToUint8Array($scope.base64Data);
pdfjsframe.contentWindow.PDFViewerApplication.open(pdfData);
$scope.PdfDocumentLoaded = true;
}
function base64ToUint8Array(base64) {
var raw = atob(base64);
var uint8Array = new Uint8Array(raw.length);
for (var i = 0; i < raw.length; i++) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
}
If you've got an typed array (e.g. an Uint8Array), then the file can be opened using PDFView.open(typedarray, 0);.
If you've got a Blob or File object, then the data has to be converted to a typed array before you can view it:
var fr = new FileReader();
fr.onload = function() {
var arraybuffer = this.result;
var uint8array = new Uint8Array(arraybuffer);
PDFView.open(uint8array, 0);
};
fr.readAsArrayBuffer(blob);
Another method is to create a URL for the Blob/File object:
var url = URL.createObjectURL(blob);
PDFView.open(url, 0);
If the PDF Viewer is hosted at the same origin as your website that embeds the frame, then you can also view the PDF by passing the blob URL to the viewer:
var url = URL.createObjectURL(blob);
var viewerUrl = 'web/viewer.html?file=' + encodeURIComponent(url);
// TODO: Load the PDF.js viewer in a frame or new tab/window.
I finally made the PDFView.open method working. Now if I embed the viewer into my page and call the open function as Rob suggested in the first 2 examples it works.
For those who are looking for this kind of solution I provide some lines of code here:
This is the code in my Lightswitch mainPage.lsml.js. The js scripts (pdf.js, viewer and Others) are referenced in the main html page of the Lightswitch project (Default.html); I assume it should work with any other html page not Lightswitch based.
myapp.MainPage.ShowPdf_execute = function (screen) {
// Write code here.
// Getting the stream from sql
var blob = new Blob([screen.WebReportsPdfFilesStream.selectedItem.Pdf], { type: "application/pdf;base64" });
// Pass the stream to an aspx page that makes some manipulations and returns a response
var formData = new FormData();
formData.tagName = pdfName;
formData.append(pdfName, blob);
var xhr = new XMLHttpRequest();
var url = "../OpenPdf.aspx";
xhr.open('POST', url, false);
xhr.onload = function (e) {
var response = e.target.response;
var pdfAsArray = convertDataURIToBinary("data:application/pdf;base64, " + response);
var pdfDocument;
// Use PDFJS to render a pdfDocument from pdf array
PDFJS.getDocument(pdfAsArray).then(function (pdf) {
pdfDocument = pdf;
var url = URL.createObjectURL(blob);
PDFView.load(pdfDocument, 1.5)
})
};
xhr.send(formData); // multipart/form-data
};
This is the convertDataURIToBinary function
function convertDataURIToBinary(dataURI) {
var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
var base64 = dataURI.substring(base64Index);
var raw = window.atob(base64);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for (i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}
What is still missed is the possibility to pass the stream directly to the viewer.html page in order to open it in a new window and have a separate ui where make the rendering.
This code is still not working since I got an empty viewer with no document inside:
var blob = new Blob([screen.WebReportsPdfFilesStream.selectedItem.Pdf], { type: "application/pdf;base64" });
var url = URL.createObjectURL(blob);
var viewerUrl = 'Scripts/pdfViewer/web/viewer.html?file=' + encodeURIComponent(url);
window.open(viewerUrl);
Looks like the encodeURIComponent(url) is not passing to the viewer a good object to load into the viewer.
Any idea or suggestion?
var url = URL.createObjectURL(blob);
var viewerUrl = 'web/viewer.html?file=' + encodeURIComponent(url);
// TODO: Load the PDF.js viewer in a frame or new tab/window.
//-- Abobe code opens a new window but with errors : 'Missing PDF
// blob://http://server-addr:port/converted-blob'
I am using viewer in an iframe;
<iframe
id="pdfIframe"
src="pdfjs/web/viewer.html"
style="width: 100%; height: 100%;"
>
</iframe>
And fetch API used as follows;
fetch(pdfSourceUrl).then((response: Response) => {
response.blob().then((blob) => {
var url = URL.createObjectURL(blob);
pdfIframe.src = `pdfjs/web/viewer.html?file=${url}`;
});
});
Eventually iframe src created as follows;
http://localhost:9000/pdfjs/web/viewer.html?file=blob:http://localhost:9000/14f6a2ec-ad25-40ab-9db8-560c15e90f6e

How do I make an image load synchronously?

I want to create an object that has an image property, but I want the contstructor to finish running only once the image is loaded. Or to describe this with code:
GraphicObject = Class.extend({
//This is the constructor
init: function(){
this.graphic = new Image();
this.graphic.src = 'path/to/file.png';
while(true)
{
this.graphic.onload = function(){break;};
//I know this won't work since the 'break' is on a different context
//but you got what I try to do.
}
}
})
For those who are unfamiliar with the Class notation I'm using in my script, it's based on this
Any ideas?
It is possible, but only with the ancient art of Base64 and Data-URL.
GIF image converted to Base64.
rune.b64
R0lGODlhIwAjAIAAAP///wAAACwAAAAAIwAjAAACf4SPqcsb3R40ocpJK7YaA35FnPdZGxg647kyqId2SQzHqdlCdgdmqcvbHXKi4AthYiGPvp9KVuoNocWLMOpUtHaS5CS54mZntiWNRWymn14tU7c2t6ukOJlKR5OiNTzQ7wb41LdnJ1coeNg3pojGqFZniPU4lTi0d4mpucmpUAAAOw==
JavaScript which loads the converted image form the same server via blocking AJAX.
loader.js
var request = new XMLHttpRequest();
var image = document.createElement('img');
request.open('GET', 'rune.b64', false);
request.send(null);
if (request.status === 200) {
image.src= 'data:image/gif;base64,' + request.responseText.trim();
document.getElementsByTagName("body")[0].appendChild(image);
}
Problems
Some older browsers don't like (big) Data-URLs
Base64 encoding makes images about 37% bigger
The whole UI is blocked till the image is loaded
This is a very-evil way
There is a non-evil way to load images in Javascript synchronously.
loadImage = async img => {
return new Promise((resolve, reject) => {
img.onload = async () => {
console.log("Image Loaded");
resolve(true);
};
});
};
Call it with await anywhere. like this
for(let i=0;i<photos.length;i++){
await loadImage(photos[i]);
}
It will load all images one by one.
Note: Calling function must be async to use await
Put the dependent code in the callback. There is no other non-evil way.
GraphicObject = Class.extend({
//This is the constructor
init: function(){
this.graphic = new Image();
this.graphic.onload = function ()
{
// the rest of the ctor code here
};
this.graphic.src = 'path/to/file.png';
}
});
var timeOut = 5*1000; //ms - waiting for max 5s to laoad
var start = new Date().getTime();
while(1)
if(img.complete || img.naturalWidth || new Date().getTime()-start>timeOut)
break;
Based on this answer.
I wrapped it into function, and it worked!
for (var i = 0, i < asset.length; i++) {
var img = new Image();
img.src = "file:///" + folder + "/" + asset[i].name;
getWdrp(img);
function getWdrp (img) {
img.onload = function(){
// work with the image file
}
}
}
This is an example that worked for me, because before, when I was processing the image without wrapping in the function, it would work async, now it is async.

Function to preload images?

I'm successfully pre-loading an image on my website with this JavaScript:
loveHover = new Image();
loveHover.src = "http://mypage.com/images/love-hover.png";
Is there an easy an good way to pack this thing into a function? Something like:
function preloadImage(image) {
var image = new Image();
var path = "http://mypage.com/images/";
image.src = path + image;
}
["love-hover.jpg", "like-hover.jpg", "hate-hover.jpg"].forEach(function(img)
{
new Image().src = "http://mypage.com/" + img;
});
To get this to work in IE versions earlier than 9, see the Array.forEach Compatibility section for instructions.
Well the unique part of the function would be the src (link to image). So make that the argument.
function preloadImage(src) {
var image = new Image();
image.src = src;
}
Then if you have multiple urls store them in an array:
var imageSrcs = [
"http://mypage.com/images/love-hover.png#",
"http://mypage.com/images/love-hover2.png",
"http://mypage.com/images/love-hover3.png"
];
And preload the images with a loop:
for (var i = 0; i < imageSrcs.lengthl i++)
preloadImage(imageSrcs[i]);
Have you tried not using javascript at all?
http://perishablepress.com/press/2008/06/14/a-way-to-preload-images-without-javascript-that-is-so-much-better/

Categories

Resources