Speed up canvas with webGL or webworkers? - javascript
I'm creating a blurred background from an image in canvas with this script: http://www.quasimondo.com/BoxBlurForCanvas/FastBlur2Demo.html
You can see a demo on my page here: http://www.cphrecmedia.dk/musikdk/stage/prelisten.php
I find the speed a little bit slow and I know I can make it faster by only make 1 iterations, but its not that pretty then. Can I somehow use WEbGL or any other kind of tech to speed up the canvas for this?
This is the stackbox-code:
var mul_table=[1,171,205,293,57,373,79,137,241,27,391,357,41,19,283,265,497,469,443,421,25,191,365,349,335,161,155,149,9,278,269,261,505,245,475,231,449,437,213,415,405,395,193,377,369,361,353,345,169,331,325,319,313,307,301,37,145,285,281,69,271,267,263,259,509,501,493,243,479,118,465,459,113,446,55,435,429,423,209,413,51,403,199,393,97,3,379,375,371,367,363,359,355,351,347,43,85,337,333,165,327,323,5,317,157,311,77,305,303,75,297,294,73,289,287,71,141,279,277,275,68,135,67,133,33,262,260,129,511,507,503,499,495,491,61,121,481,477,237,235,467,232,115,457,227,451,7,445,221,439,218,433,215,427,425,211,419,417,207,411,409,203,202,401,399,396,197,49,389,387,385,383,95,189,47,187,93,185,23,183,91,181,45,179,89,177,11,175,87,173,345,343,341,339,337,21,167,83,331,329,327,163,81,323,321,319,159,79,315,313,39,155,309,307,153,305,303,151,75,299,149,37,295,147,73,291,145,289,287,143,285,71,141,281,35,279,139,69,275,137,273,17,271,135,269,267,133,265,33,263,131,261,130,259,129,257,1];var shg_table=[0,9,10,11,9,12,10,11,12,9,13,13,10,9,13,13,14,14,14,14,10,13,14,14,14,13,13,13,9,14,14,14,15,14,15,14,15,15,14,15,15,15,14,15,15,15,15,15,14,15,15,15,15,15,15,12,14,15,15,13,15,15,15,15,16,16,16,15,16,14,16,16,14,16,13,16,16,16,15,16,13,16,15,16,14,9,16,16,16,16,16,16,16,16,16,13,14,16,16,15,16,16,10,16,15,16,14,16,16,14,16,16,14,16,16,14,15,16,16,16,14,15,14,15,13,16,16,15,17,17,17,17,17,17,14,15,17,17,16,16,17,16,15,17,16,17,11,17,16,17,16,17,16,17,17,16,17,17,16,17,17,16,16,17,17,17,16,14,17,17,17,17,15,16,14,16,15,16,13,16,15,16,14,16,15,16,12,16,15,16,17,17,17,17,17,13,16,15,17,17,17,16,15,17,17,17,16,15,17,17,14,16,17,17,16,17,17,16,15,17,16,14,17,16,15,17,16,17,17,16,17,15,16,17,14,17,16,15,17,16,17,13,17,16,17,17,16,17,14,17,16,17,16,17,16,17,9];function stackBoxBlurCanvasRGB(ctxt,top_x,top_y,width,height,radius,iterations){if(isNaN(radius)||radius<1)return;radius|=0;if(isNaN(iterations))iterations=1;iterations|=0;if(iterations>3)iterations=3;if(iterations<1)iterations=1;var imageData;try{try{imageData=ctxt.getImageData(top_x,top_y,width,height);}catch(e){try{netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");imageData=ctxt.getImageData(top_x,top_y,width,height);}catch(e){alert("Cannot access local image");throw new Error("unable to access local image data: "+e);return;}}}catch(e){alert("Cannot access image");throw new Error("unable to access image data: "+e);}
var pixels=imageData.data;var x,y,i,p,yp,yi,yw,r_sum,g_sum,b_sum,r_out_sum,g_out_sum,b_out_sum,r_in_sum,g_in_sum,b_in_sum,pr,pg,pb,rbs;var div=radius+radius+1;var w4=width<<2;var widthMinus1=width-1;var heightMinus1=height-1;var radiusPlus1=radius+1;var stackStart=new BlurStack();var stack=stackStart;for(i=1;i<div;i++){stack=stack.next=new BlurStack();if(i==radiusPlus1)var stackEnd=stack;}
stack.next=stackStart;var stackIn=null;var mul_sum=mul_table[radius];var shg_sum=shg_table[radius];while(iterations-->0){yw=yi=0;for(y=height;--y>-1;){r_sum=radiusPlus1*(pr=pixels[yi]);g_sum=radiusPlus1*(pg=pixels[yi+1]);b_sum=radiusPlus1*(pb=pixels[yi+2]);stack=stackStart;for(i=radiusPlus1;--i>-1;){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next;}
for(i=1;i<radiusPlus1;i++){p=yi+((widthMinus1<i?widthMinus1:i)<<2);r_sum+=(stack.r=pixels[p++]);g_sum+=(stack.g=pixels[p++]);b_sum+=(stack.b=pixels[p]);stack=stack.next;}
stackIn=stackStart;for(x=0;x<width;x++){pixels[yi++]=(r_sum*mul_sum)>>>shg_sum;pixels[yi++]=(g_sum*mul_sum)>>>shg_sum;pixels[yi++]=(b_sum*mul_sum)>>>shg_sum;yi++;p=(yw+((p=x+radius+1)<widthMinus1?p:widthMinus1))<<2;r_sum-=stackIn.r-(stackIn.r=pixels[p++]);g_sum-=stackIn.g-(stackIn.g=pixels[p++]);b_sum-=stackIn.b-(stackIn.b=pixels[p]);stackIn=stackIn.next;}
yw+=width;}
for(x=0;x<width;x++){yi=x<<2;r_sum=radiusPlus1*(pr=pixels[yi++]);g_sum=radiusPlus1*(pg=pixels[yi++]);b_sum=radiusPlus1*(pb=pixels[yi]);stack=stackStart;for(i=0;i<radiusPlus1;i++){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next;}
yp=width;for(i=1;i<=radius;i++){yi=(yp+x)<<2;r_sum+=(stack.r=pixels[yi++]);g_sum+=(stack.g=pixels[yi++]);b_sum+=(stack.b=pixels[yi]);stack=stack.next;if(i<heightMinus1)yp+=width;}
yi=x;stackIn=stackStart;for(y=0;y<height;y++){p=yi<<2;pixels[p]=(r_sum*mul_sum)>>>shg_sum;pixels[p+1]=(g_sum*mul_sum)>>>shg_sum;pixels[p+2]=(b_sum*mul_sum)>>>shg_sum;p=(x+(((p=y+radiusPlus1)<heightMinus1?p:heightMinus1)*width))<<2;r_sum-=stackIn.r-(stackIn.r=pixels[p]);g_sum-=stackIn.g-(stackIn.g=pixels[p+1]);b_sum-=stackIn.b-(stackIn.b=pixels[p+2]);stackIn=stackIn.next;yi+=width;}}}
ctxt.putImageData(imageData,top_x,top_y);}
function BlurStack(){this.r=0;this.g=0;this.b=0;this.a=0;this.next=null;}
And this is how its activated on each page:
<script type='text/javascript'>window.onload=function(){
//create image object
var imageObj = new Image();
//when image is finished loading
imageObj.onload = function() {
//get size
var w = imageObj.naturalWidth;
var h = imageObj.naturalHeight;
var canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
//create virtual canvas
var ctx = canvas.getContext('2d');
//draw the image on it
ctx.drawImage(imageObj, 0, 0);
//apply the blur
stackBoxBlurCanvasRGB(ctx, 0, 0, w, h, 11, 2);
//add grey filter
ctx.fillStyle='rgba(64,64,64,0.4)';
ctx.fillRect(0,0,w,h);
//and display it in 1 second using a fade
var $canvas = $(canvas)
$("#bgc").append(canvas);
$canvas.show();
var canvasRatio = $canvas.width()/$canvas.height();
var windowRatio = $(window).width()/$(window).height();
if (canvasRatio > windowRatio){
$canvas.css({
"height": "100%",
"width" : "auto"
});
} else {
$canvas.css({
"width": "100%",
"height": "auto"
});
}
$canvas.css({
"marginLeft" : -$canvas.width()/2+"px",
"marginTop" : -$canvas.height()/2+"px"
});
window.onresize = function(){
var canvasRatio = $canvas.width()/$canvas.height();
var windowRatio = $(window).width()/$(window).height();
if (canvasRatio > windowRatio){
$canvas.css({
"height": "100%",
"width" : "auto"
});
} else {
$canvas.css({
"width": "100%",
"height": "auto"
});
}
$canvas.css({
"marginLeft" : -$canvas.width()/2+"px",
"marginTop" : -$canvas.height()/2+"px"
});
}
};
//set the source of the image from the data source
imageObj.src = $('#bgc').data("src");
}
</script>
Maybe have a look at Pixi.js it is a library that wraps canvas and will use either the 2D context or the WebGL context based on browser capabilities. It has a built in Blur Filter that could possibly achieve the effect you are after http://www.goodboydigital.com/pixijs/docs/classes/BlurFilter.html
If you are working with WebKit based browsers there is a draft implementation of CSS filters that should be fast https://developer.mozilla.org/en-US/docs/Web/CSS/filter
Related
Why do I get "Cannot read property...of undefined with this object?
Could someone help me figure out what the problem is. I get the error "TypeError: Cannot read property 'draw_map' of undefined(…)" in browser when running the code below: $(document).ready(function() { Maps = function(id, img_src, width, height) { var self = { id: id, img: new Image(), width: width, height: height } self.img.src = img_src; self.draw_map = function() { ctx.drawImage(self.img, 0, 0, self.img.width, self.img.height, 100, 100, self.img.width * 2, self.img.height * 2); } } function update_canvas() { current_map.draw_map(); } /////////////////////////////////////////// // get context to draw on canvas var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); // Load Image for map var current_map = Maps("field", "img/dirt.jpeg", 120, 480); // Drawing setInterval(update_canvas, 500); }); // End of Document.ready
Probably because the function Maps = function(...) { ... } doesn't return self object. Probably you want to use it as a construction function so change this line using new operator: var current_map = new Maps("field", "img/dirt.jpeg", 120, 480);
Here is a working code (with the HTML elements commented out). The problem was that you are not using the this reference inside the function constructor and then not using the new operator to create an instance. $(document).ready(function() { Maps = function(id, img_src, width, height) { this.id = id; this.width = width; this.height = height; this.img = new Image() // replace with your Image this.img.src = img_src; var self = this; // Required for the draw_map function this.draw_map = function() { console.log("Draw image", self.img.width, self.img.height); // Uncomment in your code: ctx.drawImage(self.img, 0, 0, self.img.width, self.img.height, 100, 100, self.img.width * 2, self.img.height * 2); } } function update_canvas() { current_map.draw_map(); } /////////////////////////////////////////// // get context to draw on canvas. Uncomment //var canvas = document.getElementById("canvas"); //var ctx = canvas.getContext("2d"); // Load Image for map var current_map = new Maps("field", "img/dirt.jpeg", 120, 480); // Drawing setInterval(update_canvas, 500); }); // End of Document.ready <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
jsPDF Big table, headers overlap in second page
I'm using jsPDF and svg_to_pdf to export both an SVG and a table to a PDF. This is my code: function svg_to_pdf(svg, callback) { console.log("svgtopdf.js"); svgAsDataUri(svg, {}, function(svg_uri) { var image = document.createElement('img'); image.src = svg_uri; image.onload = function() { var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); var doc = new jsPDF('portrait', 'pt', 'a4', true); var dataUrl; source = $('#datos_alumnos')[0]; specialElementHandlers = { '#bypassme' : function(element, renderer){ return true; } }; margins = { top: 40, bottom: 60, left: 40, width: 270 }; canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0, image.width, image.height); dataUrl = canvas.toDataURL('image/jpeg'); doc.addImage(dataUrl, 'JPEG', 0, 0, 500, 263); doc.fromHTML( source, 40, 270, { 'width': 550, 'elementHandlers': specialElementHandlers }, function(dispose){ //doc.save('SVG.pdf'); } , margins); callback(doc); } }); } The result is a PDF with the image at the top and the table next. The problem comes when it enters the second page as the headers of the table overlap with the first row and it looks like this. http://i.imgur.com/vlfm4Fv.png Is it possible to remove the headers on the second page?
Ok so I've found a possible solution for this, if you are using this script for jsPDF https://raw.githubusercontent.com/MrRio/jsPDF/master/dist/jspdf.debug.js you can download it, and modify line 5201 to have a negative value, for example; this.margins.top = -50; With this there is no header on the second page of the PDF. I've tried playing around with the code without much success, if I find a better way I will update this answer (don't know if I'm allowed to do that).
When you draw table, just use: startY: doc.internal.getNumberOfPages() > 1 ? doc.autoTableEndPosY() + 20 : 200,
Try adding css classes like - thead { display: table-header-group } tfoot { display: table-row-group } tr { page-break-inside: avoid }
how reduce the memory usage of canvas in mobile browser
I'm trying to implement a feature in our project which will trigger the mobile camera when pressing a capture button from mobile browser, it will append the captured image as canvas to html page, but if we capture more than 2 image, the browser get's restart automatically and showing memory low warning. Please let me know is there any way to optimize the canvas size?. Note: I have tested only in mobile chrome. I'm attaching the sample code below html code: <input type="file" id="camera" accept="image/*" capture> <div id="MediaOutput"></div> javascript: var storedMediaList=[]; var mobile_picture = document.querySelector("#camera"); mobile_picture.onchange = function () { var file = mobile_picture.files[0]; storedMediaList.push({id:(Math.floor(Math.random()*90000)+10000),data:file}); drawImage(); }; function drawImage() { $(".rc").remove().empty(); $("#MediaOutput").html(""); $("#MediaOutput").html("<div class='mediaWrapper'></div>"); storedMediaList.forEach(function(row,index,array) { $(".mediaWrapper").append("<div style='float:left;' class='ir' id='c"+row.id+"' class='pic'><div media-id="+row.id+" class='close-image' id='close-image' style='width:24px;background:url(images/close.png) no-repeat;height:25px;cursor:pointer;'></div><canvas id='cv"+row.id+"'></canvas> </div>"); var reader = new FileReader(); reader.onload = function (e) { var dataURL = e.target.result, c = document.querySelector("#cv"+row.id), ctx = c.getContext('2d'), img = new Image(); img.onload = function() { c.width = img.width; c.height = img.height; ctx.drawImage(img, 0, 0); }; img.src = dataURL; }; reader.readAsDataURL(row.data); }); function resizeImage(img, percentage) { var coeff = percentage/100, width = $(img).width(), height = $(img).height(); return {"width": width*coeff, "height": height*coeff} } $('.ir canvas').each(function(){ var newDimensions = resizeImage( this, 70); this.style.width = newDimensions.width + "px"; this.style.height = newDimensions.height + "px"; }); var width = 0; $('#MediaOutput .mediaWrapper div').each(function() { width += $(this).outerWidth( true ); }); $('#MediaOutput .mediaWrapper').css('width', width + "px"); } Thanks in Advance!!
Resizing a hidden-frame
I am trying to load a webpage into a hidden-frame (sdk/frame/hidden-frame) and then render a screenshot of it. I can get the screenshot just fine, but the frame is not properly sized. The screenshot is rendered by using the following code var hiddenFrames = require("sdk/frame/hidden-frame"); let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({ onReady: function() { this.element.contentWindow.location = "http://example.com"; let self = this; this.element.addEventListener("DOMContentLoaded", function() { var cnvs = self.element.contentDocument.createElement("canvas"); var width = self.element.contentDocument.body.clientHeight; //for some reason clientWidth is 0 for www.example.com var height = self.element.contentDocument.body.clientHeight; cnvs.width = width; cnvs.height = height; var ctx = cnvs.getContext("2d"); console.log(width+" "+height); ctx.drawWindow(self.element.contentWindow, 0, 0, width, height, "rgb(255,255,255)"); console.log(cnvs.toDataURL()); }, true, true); } })); I have tried changing the width and height of the iframe with this.element.width = "1600"; this.element.height = "900"; changing the size of element.contentWindow with resizeTo(), and changing the size of the body. None of them seem to have an impact on the final screenshot, which looks like
Try adding this on the parent page after including the iframe (or script that includes the iframe). $('iframe').load(function(){ $(this).css({ width: 1600, height: 900 }); });
How do I erase an image on html5 canvas
I have a html5 canvas where you can erase the blur on top of an image. I would like to replace the blur with another image. I would like the end result to be once you erase the top image the bottom image will appear below it. I have been using an easeljs demo as a template. http://www.createjs.com/#!/EaselJS/demos/alphamask I feel like what I need to edit is in the html page script but it might be much deeper than that. (Full code can be viewed here) HTML <canvas id="testCanvas" width="960" height="400"></canvas> JavaScript var stage; var isDrawing; var drawingCanvas; var oldPt; var oldMidPt; var displayCanvas; var image; var bitmap; var maskFilter; var cursor; var text; var blur; function init() { if (window.top != window) { document.getElementById("header").style.display = "none"; } document.getElementById("loader").className = "loader"; image = new Image(); image.onload = handleComplete; image.src = "images/summer.jpg"; stage = new createjs.Stage("testCanvas"); text = new createjs.Text("Loading...", "20px Arial", "#999999"); text.set({x:stage.canvas.width/2, y:stage.canvas.height-80}); text.textAlign = "center"; } function handleComplete() { document.getElementById("loader").className = ""; createjs.Touch.enable(stage); stage.enableMouseOver(); stage.addEventListener("stagemousedown", handleMouseDown); stage.addEventListener("stagemouseup", handleMouseUp); stage.addEventListener("stagemousemove", handleMouseMove); drawingCanvas = new createjs.Shape(); bitmap = new createjs.Bitmap(image); blur = new createjs.Bitmap(image); blur.filters = [new createjs.BoxBlurFilter(15,15,2)]; blur.cache(0,0,960,400); blur.alpha = .5; text.text = "Click and Drag to Reveal the Image."; stage.addChild(blur, text, bitmap); updateCacheImage(false); cursor = new createjs.Shape(new createjs.Graphics().beginFill("#FFFFFF").drawCircle(0,0,20)); cursor.cursor = "pointer"; stage.addChild(cursor); } function handleMouseDown(event) { oldPt = new createjs.Point(stage.mouseX, stage.mouseY); oldMidPt = oldPt; isDrawing = true; } function handleMouseMove(event) { cursor.x = stage.mouseX; cursor.y = stage.mouseY; if (!isDrawing) { stage.update(); return; } var midPoint = new createjs.Point(oldPt.x + stage.mouseX>>1, oldPt.y+stage.mouseY>>1); drawingCanvas.graphics.setStrokeStyle(40, "round", "round") .beginStroke("rgba(0,0,0,0.15)") .moveTo(midPoint.x, midPoint.y) .curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y); oldPt.x = stage.mouseX; oldPt.y = stage.mouseY; oldMidPt.x = midPoint.x; oldMidPt.y = midPoint.y; updateCacheImage(true); } function handleMouseUp(event) { updateCacheImage(true); isDrawing = false; } function updateCacheImage(update) { if (update) { drawingCanvas.updateCache(0, 0, image.width, image.height); } else { drawingCanvas.cache(0, 0, image.width, image.height); } maskFilter = new createjs.AlphaMaskFilter(drawingCanvas.cacheCanvas); bitmap.filters = [maskFilter]; if (update) { bitmap.updateCache(0, 0, image.width, image.height); } else { bitmap.cache(0, 0, image.width, image.height); } stage.update(); }
If you simply want to replace the blur-image, you have to change the following parts: Add this after the loading of the first image: image.src = "images/summer.jpg"; // add new part image2 = new Image(); image2.onload = handleComplete; image2.src = "images/your_newImage.jpg"; In the handleComplete() you now wait for 2 images to be loaded, so you add this: function handleComplete() { loaded++; // and initialize this variable in the very top with: var loaded = 0; if ( window.loaded < 2 ) return; //... other stull and finally you change the following lines: // old blur = new createjs.Bitmap(image); blur.filters = [new createjs.BoxBlurFilter(15,15,2)]; blur.cache(0,0,960,400); blur.alpha = .5; // new blur = new createjs.Bitmap(image2); This should now replace the blurred image with your desired image, it's not the best way, but it should get you there. I recommend you to start with some tutorial if you are planing to do more "deeper" stuff ;)
a common, though kinda janky, way is to re-size your canvas real quick. so changing your <canvas> elements width and height attributes. This will erase everything in your canvas. Once that is done, reset it back to what you want it to be right away, and redraw the bottom image. As I said this method is kinda sketchy but it does get the job done.