I've checked every question related to my problem in Stack Overflow and couldn't find a way to fix it.
What I am trying to do is; User tries to upload images, they get resized in the client-side and then they get uploaded. I've used Pica library. Everything works fine for one file. However, when I changed it to multiple files, I get duplicates of the last image.
What's happening: Loop 1 thru N times -> resizeImg N times
Ideal Solution: Loop 1 -> resizeImg(1) -> Loop 2 -> resizeImg(2)
Any help would be appreciated.
My code is below:
function resizeImg(source) {
img = new Image;
img.src = source;
img.onload = function() {
width = img.naturalWidth;
height = img.naturalHeight;
ratio = Math.min(targetWidth / width, targetHeight / height);
resizer = window.pica();
canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");
ctx.canvas.width = width * ratio;
ctx.canvas.height = height * ratio;
resizer.resize(img, canvas, {
quality: 3,
alpha: true,
unsharpAmount: 0
}).then(result => resizer.toBlob(result, 'image/jpeg', 0.90)).then(blob => imgBlobArray.push(blob)).then(function() {
console.log(i);
console.log(imgBlobArray);
});
};
}
document.getElementById('select').onchange = function(evt) {
for (i = 0; i < this.files.length; i++) {
resizeImg(window.URL.createObjectURL(this.files[i]));
}
}
The problem is that you don't have a separate binding of img for each call of resizeImg - you don't have var or const or let in front of the first use of img. You're implicitly creating a global variable. So, to the interpreter, it looks something like
var img;
function resizeImg(source) {
img = new Image;
img.src = source;
img.onload = function() {
img is getting continuously reassigned. So, after all iterations, img will end up being only the last img created with resizeImg - the references to the other Images have been lost.
So, always declare variables explicitly to ensure each call of resizeImg has a separate img binding. Do the same with all your other variables as well, or they'll be implicitly global.
function resizeImg(source) {
const img = new Image;
img.src = source;
img.onload = function() {
const width = img.naturalWidth;
const height = img.naturalHeight;
const ratio = Math.min(targetWidth / width, targetHeight / height);
const resizer = window.pica();
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.canvas.width = width * ratio;
ctx.canvas.height = height * ratio;
resizer.resize(img, canvas, {
quality: 3,
alpha: true,
unsharpAmount: 0
}).then(result => resizer.toBlob(result, 'image/jpeg', 0.90)).then(blob => imgBlobArray.push(blob)).then(function() {
console.log(i);
console.log(imgBlobArray);
});
};
}
document.getElementById('select').onchange = function(evt) {
for (let i = 0; i < this.files.length; i++) {
resizeImg(window.URL.createObjectURL(this.files[i]));
}
}
You could try defining a recursive function like so
function resizeImgRec(files, i) {
if (i >= files.length)
return;
...
img.onload = function() {
...
resizer.resize(img, canvas, {
...
}).then(result => resizer.toBlob(result, 'image/jpeg', 0.90)).then(blob => imgBlobArray.push(blob)).then(function() {
console.log(i);
console.log(imgBlobArray);
resizeImg(files, i + 1);
});
};
}
document.getElementById('select').onchange = function(evt) {
resizeImgRec(this.files, 0);
}
This way the next resizeImg will only execute after the last promise is resolved.
Related
I am currently building an application, in which a user can upload several images and work with them simultaneously. Since some processes performed poorly due to large image files, I want to resize them, before the user gets them.
In my resizer() function I try to resize using canvas. It works, but since the 'canvas.toDataURL()' is inside the img.onload function, I don't know how to return the value and parse it to the handleFiles() function.
Also...I tried some cases in which I got some code from the handleFiles() - like:
var preview = document.getElementById("img"+count);
var surface = document.getElementById("cubface"+count);
var count = counterImg();
preview.src = resizer(e.target.result, count);
surface.src = resizer(e.target.result, count);
And put them in the end of the img.onload function like
var preview = document.getElementById("img"+number);
var surface = document.getElementById("cubface"+number);
preview.src = canvas.toDataURL;
surface.src = canvas.toDataURL;
I got the resize, but I got only the last image from the loop to be processed.
So the questions are:
In the resizer() function, how to return the canvas.toDataURL value which is in img.onload?
Why does the loop cover only the last instance and not every image and how to solve that?
Full code:
JavaScript:
function resizer(base64, number){
// Max size for thumbnail
if(typeof(maxWidth) === 'undefined') maxWidth = 1200;
if(typeof(maxHeight) === 'undefined') maxHeight = 1200;
var img = new Image();
img.src = base64;
// Create and initialize two canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
img.onload = function() {
// Determine new ratio based on max size
var ratio = 1;
if (img.width > maxWidth)
ratio = maxWidth / img.width;
else if (img.height > maxHeight)
ratio = maxHeight / img.height;
// Draw original image in second canvas
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
// Copy and resize second canvas to first canvas
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL();
};
return img.onload;
}
function handleFiles(files) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
var count = counterImg();
var preview = document.getElementById("img"+count);
var surface = document.getElementById("cubface"+count);
var reader = new FileReader();
reader.onload = (function (preview, surface) {
return function (e) {
var newimage = resizer(e.target.result, count);
preview.src = newimage;
surface.src = newimage;
$('#image-cropper'+count).cropit('imageSrc', e.target.result);
}
})(preview, surface);
reader.readAsDataURL(file);
}
}
The variable count is not scoped properly.
There's a delay between the time you declare the function and the time you use it, therefore, each execution ends up with the same value. So the jquery selector will always return the same element. Which explains why only the last image is modified.
Here's a jsfiddle that demonstrate the execution.
https://jsfiddle.net/Lc6bngv5/1/
To fix this, simply pass count to the function that encapsulate your preview and surface :
reader.onload = (function (preview, surface, count) {
return function (e) {
var newimage = resizer(e.target.result, count);
preview.src = newimage;
surface.src = newimage;
$('#image-cropper'+count).cropit('imageSrc', e.target.result);
}
})(preview, surface, count);
For the second question :
The resize function returns a function. It does not return the result of that function. To get the url properly, I would use a callback function :
reader.onload = (function (preview, surface, count) {
return function (e) {
var newimage = resizer(e.target.result, count, function(url){
preview.src = url;
surface.src = url;
$('#image-cropper'+count).cropit('imageSrc', e.target.result);
});
}
})(preview, surface, count);
And you would have to do the following changes in resize :
function resizer(base64, number, cb){
...
img.onload = function() {
...
// return canvas.toDataURL();
cb(canvas.toDataURL());
};
}
canvas.toDataURL("image/jpg") is working when I'm using an image that is local.
Like this this.originalImage = 'assets/img/defaultImage-900.jpg';
When I change it to a picture from the camera its not working.
Like this this.originalImage = "data:image/jpeg;base64," + imageData;
The error I get in Xcode is
{"code":18,"name":"SecurityError","message":"The operation is insecure.","line":40830,"column":61,"sourceURL":"http://localhost:8080/var/containers/Bundle/Application/4FDE886B-8A64-46AD-8E0C-FDA23C5218CD/Oslo%20Origo.app/www/build/main.js"}
This worked before I updated to iOS 10.3.1...
How do i fix?
What I want do to is to merge two images (900x900) on top of each other, to one image.
save() {
//get filter index
this.selectedFilter = this.slides.getActiveIndex();
// run the canvas thing
this.applyFilter(this.filters[this.selectedFilter]).subscribe(() => {
//done
});
}
Apply the filter on this.originalImage(base64 image from the camera)
applyFilter(filterUrl: string) {
return Observable.create((observer: any) => {
let baseImg = new Image();
let filterImg = new Image();
let canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
baseImg.src = this.originalImage;
baseImg.onload = () => {
canvas.width = baseImg.width;
canvas.height = baseImg.height;
ctx.drawImage(baseImg, 0, 0);
filterImg.src = filterUrl;
filterImg.onload = () => {
let hRatio = canvas.width / filterImg.width;
let vRatio = canvas.height / filterImg.height;
let ratio = Math.max(hRatio, vRatio);
let centerShift_x = (canvas.width - filterImg.width * ratio) / 2;
let centerShift_y = (canvas.height - filterImg.height * ratio) / 2;
ctx.drawImage(filterImg, 0, 0, filterImg.width, filterImg.height, centerShift_x, centerShift_y, filterImg.width * ratio, filterImg.height * ratio);
try{
this.resultImage = canvas.toDataURL("image/jpg");
} catch (error){
console.log(error)
}
observer.next();
}
};
});
}
Camera settings
// set options
let options = {
quality : 30,
destinationType : Camera.DestinationType.DATA_URL,
sourceType : 1,
allowEdit : true,
encodingType: Camera.EncodingType.JPEG,
cameraDirection: Camera.Direction.FRONT,
correctOrientation: true,
saveToPhotoAlbum: false,
targetHeight: 900,
targetWidth: 900
};
Camera.getPicture(options).then((imageData) => {
this.zone.run(() => {
this.originalImage = "data:image/jpeg;base64," + imageData;
});
}, (err) => {
if(this.originalImage == null || undefined){
this.navCtrl.setRoot(SocialTabsPage, { tabIndex: 0 });
}
});
For future people with a similar issue. In my case, originalImage came from the HTML.
I was getting the same error when its HTML was like this:
<img src="https://example.com/img.jpg" crossorigin="anonymous" ... />
I simply changed the order of the attributes, and the error went away:
<img crossorigin="anonymous" src="https://example.com/img.jpg" ... />
I found a solution.
The error I got was The operation is insecure.. And for whatever reason the app did not liked that I was using the image from the camera.
So I added baseImg.crossOrigin = 'anonymous'; to the image from the camera and a filterImg.crossOrigin = 'anonymous'; to the filter image.
But this only worked for iOS 10.3 and greater, så I added another if{} function.
So now it looks like this...
...
let baseImg = new Image();
let filterImg = new Image();
if (this.platform.is('ios')){
if (this.platform.version().num > 10.2){
baseImg.crossOrigin = 'anonymous';
filterImg.crossOrigin = 'anonymous';
}
}
let canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
...
I'm trying to output the pixel values from an image. The image is loading correctly; another answer on here suggested that the browswer is trying to read the pixels before the image is finished loading, but I can see that it's loaded by the time the alert() fires.
function initContext(canvasID, contextType)
{
var canvas = document.getElementById(canvasID);
var context = canvas.getContext(contextType);
return context;
}
function loadImage(imageSource, context)
{
var imageObj = new Image();
imageObj.onload = function()
{
context.drawImage(imageObj, 0, 0);
};
imageObj.src = imageSource;
return imageObj;
}
function readImage(imageData)
{
console.log();
console.log(imageData.data[0]);
}
var context = initContext('canvas','2d');
var imageObj = loadImage('images/color-test.png',context);
var imageData = context.getImageData(0,0,10,10);
alert();
readImage(imageData);
Image.onload() is called asynchronously when the image has been loaded. That can happen after your current code calls context.getImageData().
The following code should work:
function initContext(canvasID, contextType)
{
var canvas = document.getElementById(canvasID);
var context = canvas.getContext(contextType);
return context;
}
function loadImage(imageSource, context)
{
var imageObj = new Image();
imageObj.onload = function()
{
context.drawImage(imageObj, 0, 0);
var imageData = context.getImageData(0,0,10,10);
readImage(imageData);
};
imageObj.src = imageSource;
return imageObj;
}
function readImage(imageData)
{
console.log();
console.log(imageData.data[0]);
}
var context = initContext('canvas','2d');
var imageObj = loadImage('images/color-test.png',context);
If you want to access the imageData value outside of loadImage(), you have 2 basic choices:
Store the imageData in a variable that is declared outside the function. In this case, the value will be unset until your onload() is called, so the outside code would have to test it for reasonableness before calling readImage(imageData).
Have the outside code register a callback function, and have your onload() call that function with the imageData value.
It is also possible you are evaluating a portion of an image that is transparent. I had this issue while scanning a PNG.
Share a situation I encountered when I used a loop to request multiple images at the same time and get their pixels respectively. Then I encountered this problem.
//my code looks like this
for(let i = 0; i < urls.length; ++i){
let img = new Image() // it will be destoryed every loop begin, even if use keyword 'var' insteads of 'let'.
img.onload = ()=>{
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
let imgData = ctx.getImageData(0, 0, 1024, 1024) // imgData will be all 0 or some other unexpected result.
}
img.src = urls[i]
}
// fixed bug
window.imgs = []
for(let i = 0; i < urls.length; ++i)
window.imgs[i] = new Image()
for(let i = 0; i < urls.length; ++i){
let img = window.imgs[i]
img.onload = ()=>{
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
let imgData = ctx.getImageData(0, 0, 1024, 1024)
}
img.src = urls[i]
}
The reason may be object reference, garbage collection and other problems. I don't know. Because I'm a beginner of js.
I tried rendering PDF document using pdf.js library. I know only basics in javascript and I am new to promises, so at first I followed advice on this page: Render .pdf to single Canvas using pdf.js and ImageData (2. answer). But as a result, I rendered my document with all pages blank. All pictures and colors are fine, but not even a line of text. I also tried some other tutorials, but either I get the same result, or the document is completely missing.
Right now, my code looks like this: (It's almost identical to the tutorial)
function loadPDFJS(pid, pageUrl){
PDFJS.disableWorker = true;
PDFJS.workerSrc = 'pdfjs/build/pdf.worker.js';
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var pages = [];
var currentPage = 1;
var url = '/search/nimg/IMG_FULL/' + pid + '#page=1';
PDFJS.getDocument(url).then(function (pdf) {
if(currentPage <= pdf.numPages) getPage();
function getPage() {
pdf.getPage(currentPage).then(function(page){
var scale = 1.5;
var viewport = page.getViewport(scale);
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: ctx,
viewport: viewport
};
page.render(renderContext).then(function() {
pages.push(canvas.toDataURL());
if(currentPage < pdf.numPages) {
currentPage++;
getPage();
} else {
done();
}
});
});
}
});
function done() {
for(var i = 0; i < pages.length; i++){
drawPage(i, addPage);
}
}
function addPage(img){
document.body.appendChild(img);
}
function drawPage(index, callback){
var img = new Image;
img.onload = function() {
ctx.drawImage(this, 0, 0, ctx.canvas.width, ctx.canvas.height);
callback(this);
}
img.src = pages[index];
}
}
K so I just looked at my code again and I started all over. I made it simpler and I finally got it to work. Now it looks like this:
var canvasContainer = document.getElementById('pdfImageImg');
function loadPDFJS(pid, pageUrl){
PDFJS.workerSrc = 'pdfjs/build/pdf.worker.js';
var currentPage = 1;
var pages = [];
var url = '/search/nimg/IMG_FULL/' + pid + '#page=1';
PDFJS.getDocument(url).then(function(pdf) {
pdf.getPage(currentPage).then(renderPage);
function renderPage(page) {
var height = 700;
var viewport = page.getViewport(1);
var scale = height / viewport.height;
var scaledViewport = page.getViewport(scale);
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
var renderContext = {
canvasContext: context,
viewport: scaledViewport
};
page.render(renderContext).then(function () {
if(currentPage < pdf.numPages) {
pages[currentPage] = canvas;
currentPage++;
pdf.getPage(currentPage).then(renderPage);
} else {
for (var i = 1; i < pages.length; i++) {
document.getElementById('pdfImageImg').appendChild(pages[i]);
}
}
});
}
});
}
Thank you #user3913960, your concept worked for me. I found some issues in your code which I fixed. Here is the code:
function loadPDFJS(pageUrl) {
PDFJS.workerSrc = 'resources/js/pdfjs/pdf.worker.js';
var currentPage = 1;
var pages = [];
var globalPdf = null;
var container = document.getElementById('pdf-container');
function renderPage(page) {
//
// Prepare canvas using PDF page dimensions
//
var canvas = document.createElement('canvas');
// Link: http://stackoverflow.com/a/13039183/1577396
// Canvas width should be set to the window's width for appropriate
// scaling factor of the document with respect to the canvas width
var viewport = page.getViewport(window.screen.width / page.getViewport(1.0).width);
// append the created canvas to the container
container.appendChild(canvas);
// Get context of the canvas
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
//
// Render PDF page into canvas context
//
var renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext).then(function () {
if (currentPage < globalPdf.numPages) {
pages[currentPage] = canvas;
currentPage++;
globalPdf.getPage(currentPage).then(renderPage);
} else {
// Callback function here, which will trigger when all pages are loaded
}
});
}
PDFJS.getDocument(pageUrl).then(function (pdf) {
if(!globalPdf){
globalPdf = pdf;
}
pdf.getPage(currentPage).then(renderPage);
});
}
loadPDFJS("somepdffilenamehere.pdf");
The pdfjs-dist library contains parts for building PDF viewer. You can use PDFPageView to render all pages. Based on https://github.com/mozilla/pdf.js/blob/master/examples/components/pageviewer.html :
var url = "https://cdn.mozilla.net/pdfjs/tracemonkey.pdf";
var container = document.getElementById('container');
// Load document
PDFJS.getDocument(url).then(function (doc) {
var promise = Promise.resolve();
for (var i = 0; i < doc.numPages; i++) {
// One-by-one load pages
promise = promise.then(function (id) {
return doc.getPage(id + 1).then(function (pdfPage) {
// Add div with page view.
var SCALE = 1.0;
var pdfPageView = new PDFJS.PDFPageView({
container: container,
id: id,
scale: SCALE,
defaultViewport: pdfPage.getViewport(SCALE),
// We can enable text/annotations layers, if needed
textLayerFactory: new PDFJS.DefaultTextLayerFactory(),
annotationLayerFactory: new PDFJS.DefaultAnnotationLayerFactory()
});
// Associates the actual page with the view, and drawing it
pdfPageView.setPdfPage(pdfPage);
return pdfPageView.draw();
});
}.bind(null, i));
}
return promise;
});
#container > *:not(:first-child) {
border-top: solid 1px black;
}
<link href="https://npmcdn.com/pdfjs-dist/web/pdf_viewer.css" rel="stylesheet"/>
<script src="https://npmcdn.com/pdfjs-dist/web/compatibility.js"></script>
<script src="https://npmcdn.com/pdfjs-dist/build/pdf.js"></script>
<script src="https://npmcdn.com/pdfjs-dist/web/pdf_viewer.js"></script>
<div id="container" class="pdfViewer singlePageView"></div>
See also How to display whole PDF (not only one page) with PDF.JS?
I have used below code to render a pdf with multiple pages.
PDFJS.disableWorker = true; // due to CORS
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
pages = [],
currentPage = 1,
url = 'your_pdf.pdf';
PDFJS.getDocument(url).then(function(pdf) {
if (currentPage <= pdf.numPages) getPage();
// main entry point/function for loop
function getPage() {
// when promise is returned do as usual
pdf.getPage(currentPage).then(function(page) {
var scale = 1;
var viewport = page.getViewport(scale);
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: ctx,
viewport: viewport
};
// now, tap into the returned promise from render:
page.render(renderContext).then(function() {
// store compressed image data in array
pages.push(canvas.toDataURL());
if (currentPage < pdf.numPages) {
currentPage++;
getPage(); // get next page
} else {
// after all the pages are parsed
for (var i = 0; i < pages.length; i++) {
drawPage(i);
}
}
});
});
}
});
function drawPage(index, callback) {
var img = new Image;
img.onload = function() {
ctx.drawImage(this, 0, 0, ctx.canvas.width, ctx.canvas.height);
if (index > 0) img.style.display = 'inline-block';
document.body.appendChild(img);
}
img.src = pages[index]; // start loading the data-uri as source
}
I am trying to read an entire .pdf Document using PDF.js and then render all the pages on a single canvas.
My idea: render each page onto a canvas and get the ImageData (context.getImageData()), clear the canvas do the next page. I store all the ImageDatas in an array and once all pages are in there I want to put all the ImageDatas from the array onto a single canvas.
var pdf = null;
PDFJS.disableWorker = true;
var pages = new Array();
//Prepare some things
var canvas = document.getElementById('cv');
var context = canvas.getContext('2d');
var scale = 1.5;
PDFJS.getDocument(url).then(function getPdfHelloWorld(_pdf) {
pdf = _pdf;
//Render all the pages on a single canvas
for(var i = 1; i <= pdf.numPages; i ++){
pdf.getPage(i).then(function getPage(page){
var viewport = page.getViewport(scale);
canvas.width = viewport.width;
canvas.height = viewport.height;
page.render({canvasContext: context, viewport: viewport});
pages[i-1] = context.getImageData(0, 0, canvas.width, canvas.height);
context.clearRect(0, 0, canvas.width, canvas.height);
p.Out("pre-rendered page " + i);
});
}
//Now we have all 'dem Pages in "pages" and need to render 'em out
canvas.height = 0;
var start = 0;
for(var i = 0; i < pages.length; i++){
if(canvas.width < pages[i].width) canvas.width = pages[i].width;
canvas.height = canvas.height + pages[i].height;
context.putImageData(pages[i], 0, start);
start += pages[i].height;
}
});
So from the way I understnad thing this should work, right?
When I run this I end up with the canvas that is big enought to contain all the pages of the pdf but doesn't show the pdf...
Thank you for helping.
The PDF operations are asynchronous at all stages. This means you also need to catch the promise at the last render as well. If you not catch it you will only get a blank canvas as the rendering isn't finished before the loop continues to the next page.
Tip: I would also recommend that you use something else than getImageData as this will store uncompressed bitmap, for example the data-uri instead which is compressed data.
Here is a slightly different approach eliminating the for-loop and uses the promises better for this purpose:
LIVE FIDDLE
var canvas = document.createElement('canvas'), // single off-screen canvas
ctx = canvas.getContext('2d'), // to render to
pages = [],
currentPage = 1,
url = 'path/to/document.pdf'; // specify a valid url
PDFJS.getDocument(url).then(iterate); // load PDF document
/* To avoid too many levels, which easily happen when using chained promises,
the function is separated and just referenced in the first promise callback
*/
function iterate(pdf) {
// init parsing of first page
if (currentPage <= pdf.numPages) getPage();
// main entry point/function for loop
function getPage() {
// when promise is returned do as usual
pdf.getPage(currentPage).then(function(page) {
var scale = 1.5;
var viewport = page.getViewport(scale);
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: ctx,
viewport: viewport
};
// now, tap into the returned promise from render:
page.render(renderContext).then(function() {
// store compressed image data in array
pages.push(canvas.toDataURL());
if (currentPage < pdf.numPages) {
currentPage++;
getPage(); // get next page
}
else {
done(); // call done() when all pages are parsed
}
});
});
}
}
When you then need to retrieve a page you simply create an image element and set the data-uri as source:
function drawPage(index, callback) {
var img = new Image;
img.onload = function() {
/* this will draw the image loaded onto canvas at position 0,0
at the optional width and height of the canvas.
'this' is current image loaded
*/
ctx.drawImage(this, 0, 0, ctx.canvas.width, ctx.canvas.height);
callback(); // invoke callback when we're done
}
img.src = pages[index]; // start loading the data-uri as source
}
Due to the image loading it will be asynchronous in nature as well which is why we need the callback. If you don't want the asynchronous nature then you could also do this step (creating and setting the image element) in the render promise above storing image elements instead of data-uris.
Hope this helps!
I can’t speak to the part of your code that renders the pdf into a canvas, but I do see some problems.
Every resetting canvas.width or canvas.height automatically clears the canvas contents. So in the top section, your clearRect is not needed because the canvas is cleared by canvas.width prior to your every page.render.
More importantly, in the bottom section, all your previous pdf drawings are cleared by every canvas resizing (oops!).
getImageData() gets an array where each pixel is represented by 4 consecutive elements of that array (red then green then blue then alpha). Since getImageData() is an array, so it doesn’t have a pages[i].width or pages[i].height—it only has a pages[i].length. That array length cannot be used to determine widths or heights.
So to get you started, I would start by changing your code to this (very, very untested!):
var pdf = null;
PDFJS.disableWorker = true;
var pages = new Array();
//Prepare some things
var canvas = document.getElementById('cv');
var context = canvas.getContext('2d');
var scale = 1.5;
var canvasWidth=0;
var canvasHeight=0;
var pageStarts=new Array();
pageStarts[0]=0;
PDFJS.getDocument(url).then(function getPdfHelloWorld(_pdf) {
pdf = _pdf;
//Render all the pages on a single canvas
for(var i = 1; i <= pdf.numPages; i ++){
pdf.getPage(i).then(function getPage(page){
var viewport = page.getViewport(scale);
// changing canvas.width and/or canvas.height auto-clears the canvas
canvas.width = viewport.width;
canvas.height = viewport.height;
page.render({canvasContext: context, viewport: viewport});
pages[i-1] = context.getImageData(0, 0, canvas.width, canvas.height);
// calculate the width of the final display canvas
if(canvas.width>maxCanvasWidth){
maxCanvasWidth=canvas.width;
}
// calculate the accumulated with of the final display canvas
canvasHeight+=canvas.height;
// save the "Y" starting position of this pages[i]
pageStarts[i]=pageStarts[i-1]+canvas.height;
p.Out("pre-rendered page " + i);
});
}
canvas.width=canvasWidth;
canvas.height = canvasHeight; // this auto-clears all canvas contents
for(var i = 0; i < pages.length; i++){
context.putImageData(pages[i], 0, pageStarts[i]);
}
});
Alternatively, here’s a more traditional way of accomplishing your task:
Use a single “display” canvas and allow the user to “page through” each desired page.
Since you already start by drawing each page into a canvas, why not keep a separate, hidden canvas for each page. Then when the user wants to see page#6, you just copy the hidden canvas#6 onto your display canvas.
The Mozilla devs use this approach in their pdfJS demo here: http://mozilla.github.com/pdf.js/web/viewer.html
You can check out the code for the viewer here: http://mozilla.github.com/pdf.js/web/viewer.js
You can pass the number page to the promises , get that page canvas data and render in the right order on canvas
var renderPageFactory = function (pdfDoc, num) {
return function () {
var localCanvas = document.createElement('canvas');
///return pdfDoc.getPage(num).then(renderPage);
return pdfDoc.getPage(num).then((page) => {
renderPage(page, localCanvas, num);
});
};
};
var renderPages = function (pdfDoc) {
var renderedPage = $q.resolve();
for (var num = 1; num <= pdfDoc.numPages; num++) {
// Wait for the last page t render, then render the next
renderedPage = renderedPage.then(renderPageFactory(pdfDoc, num));
}
};
renderPages(pdf);
Complete example
function renderPDF(url, canvas) {
var pdf = null;
PDFJS.disableWorker = true;
var pages = new Array();
var context = canvas.getContext('2d');
var scale = 1;
var canvasWidth = 256;
var canvasHeight = 0;
var pageStarts = new Array();
pageStarts[0] = 0;
var k = 0;
function finishPage(localCanvas, num) {
var ctx = localCanvas.getContext('2d');
pages[num] = ctx.getImageData(0, 0, localCanvas.width, localCanvas.height);
// calculate the accumulated with of the final display canvas
canvasHeight += localCanvas.height;
// save the "Y" starting position of this pages[i]
pageStarts[num] = pageStarts[num -1] + localCanvas.height;
if (k + 1 >= pdf.numPages) {
canvas.width = canvasWidth;
canvas.height = canvasHeight; // this auto-clears all canvas contents
for (var i = 0; i < pages.length; i++) {
context.putImageData(pages[i+1], 0, pageStarts[i]);
}
var img = canvas.toDataURL("image/png");
$scope.printPOS(img);
}
k++;
}
function renderPage(page, localCanvas, num) {
var ctx = localCanvas.getContext('2d');
var viewport = page.getViewport(scale);
// var viewport = page.getViewport(canvas.width / page.getViewport(1.0).width);
// changing canvas.width and/or canvas.height auto-clears the canvas
localCanvas.width = viewport.width;
/// viewport.width = canvas.width;
localCanvas.height = viewport.height;
var renderTask = page.render({canvasContext: ctx, viewport: viewport});
renderTask.then(() => {
finishPage(localCanvas, num);
});
}
PDFJS.getDocument(url).then(function getPdfHelloWorld(_pdf) {
pdf = _pdf;
var renderPageFactory = function (pdfDoc, num) {
return function () {
var localCanvas = document.createElement('canvas');
///return pdfDoc.getPage(num).then(renderPage);
return pdfDoc.getPage(num).then((page) => {
renderPage(page, localCanvas, num);
});
};
};
var renderPages = function (pdfDoc) {
var renderedPage = $q.resolve();
for (var num = 1; num <= pdfDoc.numPages; num++) {
// Wait for the last page t render, then render the next
renderedPage = renderedPage.then(renderPageFactory(pdfDoc, num));
}
};
renderPages(pdf);
});
}