Pepper: strange image from camera - javascript

I want to show an image in the tablet of Pepper from his camera. I have done the Javascript code to process and show the image in a web view. The problem is that the image showed is strange:
I have written this code based on this example:
var CAMERA_ID = 'fger';
QiSession(function (session) {
session.service('ALMemory').then(function (ALMemory) {
// Listener to Choregraphe APP
ALMemory.subscriber('PepperQiMessaging/totablet').then(function(subscriber) {
subscriber.signal.connect(toTabletHandler);
});
// Says to Choregraphe that I'm listening with the id QR
ALMemory.raiseEvent('PepperQiMessaging/fromTabletResponse', 'QR');
// Video receiver function
session.service('ALVideoDevice').then(playVideo);
});
});
// This is the important part
function playVideo(video) {
video.subscribeCamera(
CAMERA_ID,
0, // cameraId kTop kBottom kInfred(color:17)
1, // Image of 640*480px
11, //RGB
10 // framerate 1~30
).then(function(result){
video.getImageRemote(result).then(function(image) {
if (image) {
var canvas = $('canvas')[0].getContext('2d');
var width = image[0];
var height = image[1];
var nb = image[2];
var imageData = image[6];
var read = 0;
var img = canvas.createImageData(width, height);
var pixs = img.data;
var binary = window.btoa(imageData);
var len = imageData.length;
var m = len/nb;
// Transformations to get the image in RGB
for (var i = 0; i < m; i++) {
pixs[i*4] = binary.charCodeAt(i*nb);
pixs[i*4+1] = binary.charCodeAt(i*nb+1);
pixs[i*4+2] = binary.charCodeAt(i*nb+2);
pixs[i*4+3] = 255;
}
canvas.putImageData(img, 0, 0);
}
video.unsubscribe(CAMERA_ID);
});
});
}
What I have to do to solve this problem?

At the end I found the solution. I found another example that it worked for me (the image is greyscale).
This is the algorithm to transform the bytes received form the camera to an image:
function getImage(ALVideoDevice, subscriberId) {
ALVideoDevice.getImageRemote(subscriberId).then(function (image) {
if(image) {
var imageWidth = image[0];
var imageHeight = image[1];
var imageBuf = image[6];
if (!context) {
context = document.getElementById('canvas').getContext('2d');
}
if (!imgData || imageWidth != imgData.width || imageHeight != imgData.height) {
imgData = context.createImageData(imageWidth, imageHeight);
}
var data = imgData.data;
for (var i = 0, len = imageHeight * imageWidth; i < len; i++) {
var v = imageBuf[i];
data[i * 4 + 0] = v;
data[i * 4 + 1] = v;
data[i * 4 + 2] = v;
data[i * 4 + 3] = 255;
}
context.putImageData(imgData, 0, 0);
}
if(previewRunning) {
setTimeout(function() { getImage(ALVideoDevice, subscriberId) }, 100);
}
});
}
Here is the complete Choregraphe project and the complete code.

Related

SVG save options in Illustrator script

I have made the following script to batch resize and center JPG photos in an SVG file:
function container()
{
var sourceFolder = Folder.selectDialog();
var TypeList = prompt("Enter file types to act on","ai,eps,pdf,jpg,png","File Types");
var reg = new RegExp("\.(" + TypeList.replace(/,/g, '|') + ")$", 'i');
var filesToProcess = sourceFolder.getFiles(reg);
var x = 400;
var y = 300;
var w = 800;
var h = 600;
var userDesktop = Folder.desktop;
var f = new File(userDesktop+'/Satz 2.aia'); // make a file object first, using a string
function fitObjectToArtboardBounds()
{
var idoc = app.activeDocument;
var layers = idoc.layers;
var scriptID = "Random Select v0.2";
var selec = idoc.selection;
for(var x=0,len=layers.length;x<len;x++)
{
layers[x].hasSelectedArtwork = true;
}
selec = idoc.selection;
if (selec.length == 1)
{
var docw = idoc.width;
var doch = idoc.height;
var activeAB = idoc.artboards[idoc.artboards.getActiveArtboardIndex()]; // get active AB
docLeft = activeAB.artboardRect[0];
docTop = activeAB.artboardRect[1];
var sel = idoc.selection[0];
var selVB = sel.visibleBounds;
var selVw = selVB[2] - selVB[0];
var selVh = selVB[1] - selVB[3];
var selGB = sel.geometricBounds;
var selGw = selGB[2] - selGB[0];
var selGh = selGB[1] - selGB[3];
var deltaX = selVw - selGw;
var deltaY = selVh - selGh;
if (sel.width > sel.height)
{
var newHeight = doch - deltaY;
var ratio = sel.height / newHeight;
sel.height = newHeight;
sel.width = sel.width / ratio;
}
else
{
var newHeight = doch - deltaY;
var ratio = sel.height / newHeight;
sel.height = newHeight;
sel.width = sel.width / ratio;
}
sel.top = (doch / 2) - (sel.height / 2);
sel.left = (docw / 2) - (sel.width / 2);
app.doScript('Aktion1', 'Satz 3');
app.executeMenuCommand('fitall');
}
else
{
alert("select ONE object before running");
}
}
var userDesktop = Folder.desktop;
var g = new File(userDesktop+'/Satz 4.aia'); // make a file object first, using a string
app.loadAction (g); // the argument here is a file object
g = new File(userDesktop+'/Satz 3.aia'); // make a file object first, using a string
app.loadAction (g); // the argument here is a file object
// Create progress bar
var win = new Window("palette", "ProgressBar", [300, 150, 750, 280]);
win.pnl = win.add("panel", [10, 10, 440, 120], "Script Progress");
win.pnl.progBar = win.pnl.add("progressbar", [20, 35, 410, 60], 0, 100);
win.pnl.progBarLabel = win.pnl.add("statictext", [20, 20, 320, 35], "0%");
win.pnl.fileLabel = win.pnl.add("statictext", [20, 65, 410, 80], "File processed: none");
win.show();
// Increment through the files
for (var i=0; i < filesToProcess.length; i++) {
app.open(filesToProcess[i]);
app.activeDocument.artboards[0].artboardRect = [x, -y, (x + w), -(y + h)];
fitObjectToArtboardBounds();
win.pnl.fileLabel.text = "File: " + app.activeDocument.name;
app.doScript ('Aktion3', 'Satz 4');
app.activeDocument.close();
win.pnl.progBar.value = parseInt( (i/filesToProcess.length)*100 );
win.pnl.progBarLabel.text = win.pnl.progBar.value+"%";
win.update();
}
win.pnl.progBar.value = 100;
win.pnl.progBarLabel.text = win.pnl.progBar.value+"%";
win.update();
alert('Done!');
win.close();
win.pnl.progBar.value = parseInt( (i/filesToProcess.length)*100 );
win.pnl.progBarLabel.text = win.pnl.progBar.value+"%";
win.update();
win.pnl.progBar.value = 100;
win.pnl.progBarLabel.text = win.pnl.progBar.value+"%";
win.update();
alert('Done!');
win.close();
}
container();
The output file size is as big as the original JPG file (around 5 MB) while it should be around max 200 kB.
How could I reduce size ?
I thought about SVG options, but I don't know how to make it since the Saving part in my script is just an Illustrator action app.doScript ('Aktion3', 'Satz 4'); and is not modifiable.
For information, JPEG photo inside the SVG file is linked I think, and while its dimensions is reduced to 800x600 px, its DPI is still very high (> 400).
Thanks in advance for your help !

How to upload image directly from file upload button in javascript

Below is my script which was working fine.
But right now my image url was static at bottom of the script,
But i want to make it dynamic using file upload button.
Means if i select a file from upload button, then it will preview both images.
Below is the line
img.src = "https://image.ibb.co/bGtv0z/Product_Two.jpg";
where my image was fixed. i want to get this URL from file upload button.
Please help me to make it dynamic.
var img = new Image(),
$canvas = $("<canvas>"),
canvas = $canvas[0],
context;
var removeBlanks = function(imgWidth, imgHeight) {
var imageData = context.getImageData(0, 0, imgWidth, imgHeight),
data = imageData.data,
getRBG = function(x, y) {
var offset = imgWidth * y + x;
return {
red: data[offset * 4],
green: data[offset * 4 + 1],
blue: data[offset * 4 + 2],
opacity: data[offset * 4 + 3]
};
},
isWhite = function(rgb) {
// many images contain noise, as the white is not a pure #fff white
return rgb.red > 242 && rgb.green > 240 && rgb.blue > 240;
},
scanY = function(fromTop) {
var offset = fromTop ? 1 : -1;
// loop through each row
for (var y = fromTop ? 0 : imgHeight - 1; fromTop ? (y < imgHeight) : (y > -1); y += offset) {
// loop through each column
for (var x = 0; x < imgWidth; x++) {
var rgb = getRBG(x, y);
if (!isWhite(rgb)) {
return y;
}
}
}
return null; // all image is white
},
scanX = function(fromLeft) {
var offset = fromLeft ? 1 : -1;
// loop through each column
for (var x = fromLeft ? 0 : imgWidth - 1; fromLeft ? (x < imgWidth) : (x > -1); x += offset) {
// loop through each row
for (var y = 0; y < imgHeight; y++) {
var rgb = getRBG(x, y);
if (!isWhite(rgb)) {
return x;
}
}
}
return null; // all image is white
};
var cropTop = scanY(true),
cropBottom = scanY(false),
cropLeft = scanX(true),
cropRight = scanX(false),
cropWidth = cropRight - cropLeft,
cropHeight = cropBottom - cropTop;
var $croppedCanvas = $("<canvas>").attr({
width: cropWidth,
height: cropHeight
});
// finally crop the guy
$croppedCanvas[0].getContext("2d").drawImage(canvas,
cropLeft, cropTop, cropWidth, cropHeight,
0, 0, cropWidth, cropHeight);
$("#oldimg").
append("<p>same image with white spaces cropped:</p>").
append($croppedCanvas);
console.log(cropTop, cropBottom, cropLeft, cropRight);
};
img.crossOrigin = "anonymous";
img.onload = function() {
$canvas.attr({
width: this.width,
height: this.height
});
context = canvas.getContext("2d");
if (context) {
context.drawImage(this, 0, 0);
$("#newimg").append("<p>original image:</p>").append($canvas);
removeBlanks(this.width, this.height);
} else {
alert('Get a real browser!');
}
};
// define here an image from your domain
img.src = "https://image.ibb.co/bGtv0z/Product_Two.jpg";
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<body style="background-color:#ddd">
<input type="file" src="">
<div id="oldimg"></div>
<div id="newimg"></div>
</body>
When you choose a file in file input it is only in temporary memory and not on a server. Thus you will be unable to display it.
In order to do to what you want, you can either add a form that uploads the file and after file upload you can display it or use a FileReader. But this question was already answered, so please before asking a question try to find the solution by your own first.
Here is an answer with FileReader

audio element not valid

I'm creating a audio player with visualizer.
But currently when I press the input to start the audio player my debug console returns:
Uncaught (in promise) DOMException: Failed to load because no
supported source was found.
What I'm currently doing is setting the whole audio element up in JS / jQuery:
var bins = 512;
var backgroundColour = "#2C2E3B";
var barColour = "#EC1A55";
var floorLevel = 32;
var audioContext;
var audioBuffer;
var audioAnalyserNode;
var initialized = false;
var songText = "";
var textSize;
var freqLookup = [];
var canvasContext;
var isStream = true;
var canvasWidth;
var canvasHeight;
var src;
var audioElement;
var isPlaying = false;
var volume = 1;
function play() {
audioElement = document.createElement('audio');
// Opus support check stuff
var streamEndpoint = 'http://**.**.**.**:8003/stream';
var canPlayOpus = (typeof audioElement.canPlayType === "function" && audioElement.canPlayType('audio/ogg; codecs="opus"') !== "");
if(volume > 1) {
volume = volume / 100;
}
audioElement.src = streamEndpoint;
audioElement.crossOrigin = 'anonymous';
audioElement.volume = volume;
audioElement.play();
isPlaying = true;
setUpCanvas(audioElement);
}
function pause() {
audioElement.pause();
audioElement.currentTime = 0;
audioElement.src = '';
isPlaying = false;
}
function setUpCanvas(audioElement){
try {
initCanvas(document.getElementById("canvas"));
if(typeof audioContext === 'undefined') {
audioContext = new AudioContext();
}
if (audioElement) {
isStream = true;
setupAudioApi(true, audioElement);
}
} catch(e) {
console.log(e);
}
}
function setupAudioApi(isStream, audioElement) {
//var src;
if (isStream){
if(typeof src === 'undefined'){
src = audioContext.createMediaElementSource(audioElement);
audioContext.crossOrigin = "anonymous";
audioAnalyserNode = audioContext.createAnalyser();
audioAnalyserNode.fftSize = bins * 4;
src.connect(audioAnalyserNode);
audioAnalyserNode.connect(audioContext.destination);
}
}
if (!isStream) {
src.start();
}
initialized = true;
initFreqLookupTable();
}
function initCanvas(canvasElement) {
canvasContext = canvasElement.getContext('2d');
canvasElement.width = canvasElement.clientWidth;
canvasElement.height = canvasElement.clientHeight;
canvasWidth = canvasElement.width;
canvasHeight = canvasElement.height;
requestAnimationFrame(paint);
}
function getFreqPoint(start, stop, n, binCount) {
return start * Math.pow(stop / start, n / (binCount - 1));
}
function initFreqLookupTable() {
var lastPoint = 0;
var bins = audioAnalyserNode.frequencyBinCount;
for(var i = 0; i < bins / 2; i++) {
//Scale to perceived frequency distribution
var newFreq = getFreqPoint(20, 20000, i * 2, bins);
var point = Math.floor(bins * newFreq / 20000);
while (point <= lastPoint) {
point++;
}
lastPoint = point;
freqLookup.push(point);
}
}
//Render some fancy bars
function paint() {
requestAnimationFrame(paint);
if(!initialized) {
alert('Er is iets fout gegaan');
return false;
}
canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
canvasContext.fillStyle = backgroundColour;
canvasContext.fillRect(0, 0, canvasWidth, canvasHeight);
var bins = audioAnalyserNode.frequencyBinCount;
var data = new Uint8Array(bins);
audioAnalyserNode.getByteFrequencyData(data);
canvasContext.fillStyle = barColour;
for(var i = 0; i < bins; i++) {
var point = freqLookup[i];
//Pretty much any volume will push it over 128 so we set that as the bottom threshold
//I suspect I should be doing a logarithmic space for the volume as well
var height = Math.max(0, (data[point] - floorLevel));
//Scale to the height of the bar
//Since we change the base level in the previous operations, 256 should be changed to 160 (i think) if we want it to go all the way to the top
height = (height / (256 - floorLevel)) * canvasHeight * 0.8;
var width = Math.ceil(canvasWidth / ((bins / 2) - 1));
canvasContext.fillRect(i * width, canvasHeight - height, width, height);
}
}
The stream is in audio/mpeg format, it does load when I simply create an audio element in HTML with a src.
Can someone help me clarify and find the solution to the DOMException I'm getting. I have been searching other cases of this error but the fixes there didn't resolve the problem.
Try creating the audio tag like this:
var audio = new Audio('audio_file.mp3');
And try setting the type:
audio.type = "audio/mpeg";
I think that will fix your problem.
This creates an element, identical to the one you use in your code.
I suggest you put an extension on your stream.
I know this way works, and I don't know why the other way doesn't.

Issue with reduced quality on file after saving/converting from canvas

This is the code I am using. (code is at way bottom of this post but here is link to GitHubGist :: Noitidart / _ff-addon-snippet-browseForBadgeThenCreateSaveAnApply.js) It is copy pastatble to scratchpad (i tried fiddle but it needs privelage scope). When you run it will ask you to select a 16x16 image. Then it will take the firefox icon and put it on a canvas and then take the icon you browsed to and overlay it on the bottom right. Then it will convert it to .ico and save to your desktop as profilist16.ico and profilist32.ico. It will then change the icons of all your firefox windows.
After you do the above, please open a new firefox window and then in alt+tab you'll see the firefox logo of the badged icon is dirtier.
On the bottom you see the original canvas drawing (it looks blurry but i think thats my zoom level on firefox). The icon is crisp but if you notice the badged icon (on right) on the edges (especially top) you see dirt, like black jagged stuff which is not seen in the usual icon (at left)
var win = Services.wm.getMostRecentWindow(null);
var me = win;
//these should be global vars
var sizes = []; //os dependent
var img = {}; //holds Image for each size image
var osIconFileType = 'ico'; //os dependent
var cOS = 'Windows';
function badgeIt() {
var fp = Cc["#mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(win, "Select Badge Image", Ci.nsIFilePicker.modeOpen);
var fpCallback = function(rv) {
if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) {
if (sizes.length == 0) {
//figure out what os this is and populate sizes withthe sizes needed for this os
sizes = [32, 16]; //note: ask on SO how to determine what sizes the os uses for its icons?
}
loadBadgeImage();
} else {
//user did not select an file to badge with
}
}
var ranOnce0 = false;
var checkAllDefaultImagesLoaded = function() {
for (var i=0; i<sizes.length; i++) {
//console.log('img.sizes[i].loaded for i = ' + sizes[i] + ' is == ' + uneval(img[sizes[i]]));
if (!img[sizes[i]] || !img[sizes[i]].loaded) {
console.log('returning false as sizes[i]', sizes[i], 'is not loaded yet')
return false; //return as not yet all are done
}
//me.alert('all img sizes loaded');
}
//ok all sizes loaded
if (ranOnce0) {
alert('already ranOnce0 so return false');
return false;
}
ranOnce0 = true;
return true;
}
var loadDefaultImages = function() {
for (var i=0; i<sizes.length; i++) {
img[sizes[i]] = {};
img[sizes[i]].Image = new Image();
img[sizes[i]].Image.onload = function(iBinded) {
console.log('i', iBinded);
//console.log('img', img);
console.log('sizes[i]', sizes[iBinded]);
console.log('img[sizes[iBinded]].loaded=', uneval(img[sizes[iBinded]]), 'will now set it to true')
img[sizes[iBinded]].loaded = true;
console.log('just loaded size of (sizes[iBinded]) = ' + sizes[iBinded]);
var allLoaded = checkAllDefaultImagesLoaded();
if (allLoaded == true) {
console.log('allLoaded == true so createAndSave')
createAndSaveIcons();
} else {
console.warn('allLoaded is false so dont create')
}
}.bind(null, i)
img[sizes[i]].Image.src = 'chrome://branding/content/icon' + sizes[i] + '.png';
}
}
var loadBadgeImage = function() {
console.log('loadBadgeImage')
img.badge = {};
img.badge.Image = new Image();
img.badge.Image.onload = function() {
console.log('bagde image loaded')
img.badge.loaded = true;
if (checkAllDefaultImagesLoaded()) {
console.log('all dfault images PRELOADED so continue to createAndSaveIcons')
createAndSaveIcons();
} else {
console.log('all default images not loaded so start loading them')
loadDefaultImages();
}
}
img.badge.Image.src = Services.io.newFileURI(fp.file).spec;
}
var badgedIconMade = {};
var ranOnce = false;
var checkAllBadgedIconsMade = function() {
for (var i=0; i<sizes.length; i++) {
if (!badgedIconMade[sizes[i]]) {
return; //not yt done making
}
}
if (ranOnce) {
alert('already ranOnce so return');
return;
}
ranOnce = true;
// all badged icons made
applyIcons();
}
var blobCallback = function(size) {
return function (b) {
var r = new FileReader();
r.onloadend = function () {
// r.result contains the ArrayBuffer.
//alert(r.result)
img[size].ArrayBuffer = r.result;
badgedIconMade[size] = true;
//checkAllBadgedIconsMade();
Cu.import('resource://gre/modules/osfile.jsm');
var writePath = OS.Path.join(OS.Constants.Path.desktopDir, 'profilist' + size + '.' + osIconFileType);
console.log('writePath', writePath)
var promise = OS.File.writeAtomic(writePath, new Uint8Array(r.result), {tmpPath:writePath + '.tmp'});
promise.then(
function() {
//win.alert('success')
checkAllBadgedIconsMade();
},
function() {
//win.alert('failure')
}
);
};
//var url = window.URL.createObjectURL(b)
//img[size].blobUrl = url;
//prompt('', url)
r.readAsArrayBuffer(b);
}
}
var createAndSaveIcons = function() {
console.log('createAndSave')
var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
var ctx = canvas.getContext('2d');
gBrowser.contentDocument.documentElement.appendChild(canvas);
var badgeDim = { //holds key which is size of default icon, and the value is the dimension to draw the badge for that default icon size //this is set by me the dev, maybe make preference for this for user
'16': 10,
'32': 16
};
for (var i=0; i<sizes.length; i++) {
canvas.width = sizes[i];
canvas.height = sizes[i];
ctx.clearRect(0, 0, sizes[i], sizes[i]);
ctx.drawImage(img[sizes[i]].Image, 0, 0);
if (sizes[i] in badgeDim) {
if (badgeDim[sizes[i]] != sizes[i]) { //before i had `img.badge.Image.width` in place of `sizes[i]`, but can just use sizes[i] because thats the dim of the default icon duh
ctx.drawImage(img.badge.Image, sizes[i]-badgeDim[sizes[i]], sizes[i]-badgeDim[sizes[i]], badgeDim[sizes[i]], badgeDim[sizes[i]]);
} else {
//the redim size is same as icon size anyways so just draw it
ctx.drawImage(img.badge.Image, sizes[i]-badgeDim[sizes[i]], sizes[i]-badgeDim[sizes[i]]);
}
} else {
//sizes[i] is not in badgeDim meaning i dont care what size the badge is on this size of icon
ctx.drawImage(img.badge.Image, sizes[i]-badgeDim[sizes[i]], sizes[i]-badgeDim[sizes[i]]);
}
//canvas.mozFetchAsStream(mfasCallback(sizes[i]), 'image/vnd.microsoft.icon')
canvas.toBlob(blobCallback(sizes[i]), "image/vnd.microsoft.icon", "-moz-parse-options:format=bmp;bpp=32");
}
}
var applyIcons = function() {
if (cOS == 'Windows') {
Cu.import('resource://gre/modules/ctypes.jsm');
var user32 = ctypes.open('user32.dll');
/* http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950%28v=vs.85%29.aspx
* LRESULT WINAPI SendMessage(
* __in HWND hWnd,
* __in UINT Msg,
* __in WPARAM wParam,
* __in LPARAM lParam
* );
*/
var SendMessage = user32.declare('SendMessageW', ctypes.winapi_abi, ctypes.uintptr_t,
ctypes.voidptr_t,
ctypes.unsigned_int,
ctypes.int32_t,
ctypes.voidptr_t
);
/* http://msdn.microsoft.com/en-us/library/windows/desktop/ms648045%28v=vs.85%29.aspx
* HANDLE WINAPI LoadImage(
* __in_opt_ HINSTANCE hinst,
* __in_ LPCTSTR lpszName,
* __in_ UINT uType,
* __in_ int cxDesired,
* __in_ int cyDesired,
* __in_ UINT fuLoad
* );
*/
var LoadImage = user32.declare('LoadImageA', ctypes.winapi_abi, ctypes.voidptr_t,
ctypes.voidptr_t,
ctypes.char.ptr,
ctypes.unsigned_int,
ctypes.int,
ctypes.int,
ctypes.unsigned_int
);
var IMAGE_BITMAP = 0;
var IMAGE_ICON = 1;
var LR_LOADFROMFILE = 16;
var DOMWindows = Services.wm.getEnumerator(null);
while (DOMWindows.hasMoreElements()) {
var aDOMWindow = DOMWindows.getNext();
var baseWindow = aDOMWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.nsIBaseWindow;
var nativeHandle = baseWindow.nativeHandle;
var targetWindow_handle = ctypes.voidptr_t(ctypes.UInt64(nativeHandle));
console.log('aappplying now')
var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 32, 32, LR_LOADFROMFILE); //MUST BE A FILEPATH TO A ICO!!!
var hIconSmall = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist16.' + osIconFileType), IMAGE_ICON, 16, 16, LR_LOADFROMFILE); //MUST BE A FILEPATH TO A ICO!!!
var successSmall = SendMessage(targetWindow_handle, 0x0080 /** WM_SETICON **/ , 0 /** ICON_SMALL **/ , hIconSmall); //if it was success it will return 0? im not sure. on first time running it, and it was succesful it returns 0 for some reason
var successBig = SendMessage(targetWindow_handle, 0x0080 /** WM_SETICON **/ , 1 /** ICON_BIG **/ , hIconBig); //if it was success it will return 0? im not sure. on first time running it, and it was succesful it returns 0 for some reason
}
user32.close();
}
}
fp.open(fpCallback);
}
badgeIt();
Alright. This is actually quite reproducible, but only when using BMP icons, but not PNG icons.
Seems the icon encoder that Firefox ships is pretty bad/buggy indeed (for RGBA stuff). Well, actually it is the BMP encoder that the ICO encoder uses...
So since Belgium/Algeria (the game, football, not American) was mostly boring just now, I wrote my own icon encoder, which isn't too hard actually.
So here is my complete example code incl. icon encoder (just setting the 32x32 icon), but which lacks deposing of icons. But as a bonus, it shows how to set the icon via the WNDCLASS.
Cu.import('resource://gre/modules/ctypes.jsm');
Cu.import('resource://gre/modules/osfile.jsm');
let IMAGE_BITMAP = 0;
let IMAGE_ICON = 1;
let WM_SETICON = 128;
let GCLP_HICON = -14;
let user32 = ctypes.open('user32.dll');
let SendMessage = user32.declare(
'SendMessageW',
ctypes.winapi_abi,
ctypes.intptr_t,
ctypes.voidptr_t, // HWND
ctypes.uint32_t, // MSG
ctypes.uintptr_t, // WPARAM
ctypes.intptr_t // LPARAM
);
let CreateIconFromResourceEx = user32.declare(
'CreateIconFromResourceEx',
ctypes.winapi_abi,
ctypes.voidptr_t,
ctypes.uint8_t.ptr, // icon
ctypes.uint32_t, // size
ctypes.int32_t, // icon
ctypes.uint32_t, // dwVersion
ctypes.int, // dx
ctypes.int, // dy
ctypes.uint32_t // flags
);
let SetClassLongPtr = user32.declare(
ctypes.intptr_t.size == 8 ? 'SetClassLongPtrW' : 'SetClassLongW',
ctypes.winapi_abi,
ctypes.uintptr_t,
ctypes.voidptr_t, // HWND
ctypes.int, // index
ctypes.uintptr_t // value
);
let gdi32 = ctypes.open('gdi32.dll');
let DeleteObject = gdi32.declare(
'DeleteObject',
ctypes.winapi_abi,
ctypes.int,
ctypes.voidptr_t // Object
);
let setPerWindow = false;
let badges = [
'chrome://browser/skin/places/starred48.png',
'chrome://browser/skin/places/downloads.png',
'chrome://browser/skin/places/tag.png',
'chrome://browser/skin/places/livemark-item.png',
'chrome://browser/skin/places/query.png',
'chrome://browser/skin/pluginInstall-64.png',
'chrome://browser/skin/pluginInstall-16.png',
];
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Task.spawn(function* setIcon() {
"use strict";
try {
let p = Promise.defer();
let img = new Image();
img.onload = () => p.resolve();
img.src = 'chrome://branding/content/icon32.png';
yield p.promise;
p = Promise.defer();
let badge = new Image();
badge.onload = () => p.resolve();
badge.src = badges[getRandomInt(0, badges.length - 1)];
console.log(badge.src);
yield p.promise;
let canvas = document.createElementNS(
'http://www.w3.org/1999/xhtml',
'canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
let onethird = canvas.width / 3;
ctx.drawImage(
badge,
onethird,
onethird,
canvas.width - onethird,
canvas.height - onethird);
// Our own little ico encoder
// http://msdn.microsoft.com/en-us/library/ms997538.aspx
// Note: We would have been able to skip ICONDIR/ICONDIRENTRY,
// if we were to use CreateIconFromResourceEx only instead of also
// writing the icon to a file.
let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
let XOR = data.length;
let AND = canvas.width * canvas.height / 8;
let size = 22 /* ICONDIR + ICONDIRENTRY */ + 40 /* BITMAPHEADER */ + XOR + AND;
let buffer = new ArrayBuffer(size);
// ICONDIR
let view = new DataView(buffer);
view.setUint16(2, 1, true); // type 1
view.setUint16(4, 1, true); // count;
// ICONDIRENTRY
view = new DataView(buffer, 6);
view.setUint8(0, canvas.width % 256);
view.setUint8(1, canvas.height % 256);
view.setUint16(4, 1, true); // Planes
view.setUint16(6, 32, true); // BPP
view.setUint32(8, 40 + XOR + AND, true); // data size
view.setUint32(12, 22, true); // data start
// BITMAPHEADER
view = new DataView(buffer, 22);
view.setUint32(0, 40, true); // BITMAPHEADER size
view.setInt32(4, canvas.width, true);
view.setInt32(8, canvas.height * 2, true);
view.setUint16(12, 1, true); // Planes
view.setUint16(14, 32, true); // BPP
view.setUint32(20, XOR + AND, true); // size of data
// Reorder RGBA -> BGRA
for (let i = 0; i < XOR; i += 4) {
let temp = data[i];
data[i] = data[i + 2];
data[i + 2] = temp;
}
let ico = new Uint8Array(buffer, 22 + 40);
let stride = canvas.width * 4;
// Write bottom to top
for (let i = 0; i < canvas.height; ++i) {
let su = data.subarray(XOR - i * stride, XOR - i * stride + stride);
ico.set(su, i * stride);
}
// Write the icon to inspect later. (We don't really need to write it at all)
let writePath = OS.Path.join(OS.Constants.Path.desktopDir, 'icon32.ico');
yield OS.File.writeAtomic(writePath, new Uint8Array(buffer), {
tmpPath: writePath + '.tmp'
});
// Cut off ICONDIR/ICONDIRENTRY for CreateIconFromResourceEx
buffer = buffer.slice(22);
let hicon = CreateIconFromResourceEx(
ctypes.uint8_t.ptr(buffer),
buffer.byteLength,
IMAGE_ICON,
0x30000,
0,
0,
0);
if (hicon.isNull()) {
throw new Error("Failed to load icon");
}
if (setPerWindow) {
let DOMWindows = Services.wm.getEnumerator(null);
while (DOMWindows.hasMoreElements()) {
let win = DOMWindows.getNext().QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
let handle = ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
console.error("Failed to get window handle");
continue;
}
var lparam = ctypes.cast(hicon, ctypes.intptr_t);
var oldIcon = SendMessage(handle, WM_SETICON, 1, lparam);
if (ctypes.voidptr_t(oldIcon).isNull()) {
console.log("There was no old icon", oldIcon.toString());
}
else {
console.log("There was an old icon already", oldIcon.toString());
// In a perfect world, we should actually kill our old icons
// using DeleteObject...
}
}
}
else {
let win = Services.wm.getMostRecentWindow(null).
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
let handle = ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
throw new Error("Failed to get window handle");
}
let oldIcon = SetClassLongPtr(handle, GCLP_HICON, ctypes.cast(hicon, ctypes.uintptr_t));
if (ctypes.voidptr_t(oldIcon).isNull()) {
console.log("There was no old icon", oldIcon.toString());
}
else {
console.log("There was an old icon already", oldIcon.toString());
// In a perfect world, we should actually kill our old icons
// using DeleteObject...
}
}
console.log("done", badge.src);
}
catch (ex) {
console.error(ex);
}
});
PS: Here is a screenshot from the Task Switcher on XP:
Tested the code on Win7, the icon once applied is real real crappy. In image below, we see the icon in canvas is perfect. In the alt+tab menu the first icon is SUPER crappy, second icon is unbadged so perfect, and third and fifth icons are normal crappy.
Image is here: http://i.stack.imgur.com/47dIr.png
Edit:
Fixed the SUPER crappiness by changing this line for win7, it was 32, 32, i made it 256, 256, no clue why it fixed the SUPER crap:
var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 256, 256, LR_LOADFROMFILE);
before it was: var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
However the usual crap, black rough edge remains.

Why does chrome struggle to display lots of images on a canvas when the other browsers don't?

We're working with the HTML5 canvas, displaying lots of images at one time.
This is working pretty well but recently we've had a problem with chrome.
When drawing images on to a canvas you seem to reach a certain point where the performance degrades very quickly.
It's not a slow effect, it seems that you go right from 60fps to 2-4fps.
Here's some reproduction code:
// Helpers
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })();
// https://github.com/mrdoob/stats.js
var Stats = function () { var e = Date.now(), t = e; var n = 0, r = Infinity, i = 0; var s = 0, o = Infinity, u = 0; var a = 0, f = 0; var l = document.createElement("div"); l.id = "stats"; l.addEventListener("mousedown", function (e) { e.preventDefault(); y(++f % 2) }, false); l.style.cssText = "width:80px;opacity:0.9;cursor:pointer"; var c = document.createElement("div"); c.id = "fps"; c.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#002"; l.appendChild(c); var h = document.createElement("div"); h.id = "fpsText"; h.style.cssText = "color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; h.innerHTML = "FPS"; c.appendChild(h); var p = document.createElement("div"); p.id = "fpsGraph"; p.style.cssText = "position:relative;width:74px;height:30px;background-color:#0ff"; c.appendChild(p); while (p.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#113"; p.appendChild(d) } var v = document.createElement("div"); v.id = "ms"; v.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#020;display:none"; l.appendChild(v); var m = document.createElement("div"); m.id = "msText"; m.style.cssText = "color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; m.innerHTML = "MS"; v.appendChild(m); var g = document.createElement("div"); g.id = "msGraph"; g.style.cssText = "position:relative;width:74px;height:30px;background-color:#0f0"; v.appendChild(g); while (g.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#131"; g.appendChild(d) } var y = function (e) { f = e; switch (f) { case 0: c.style.display = "block"; v.style.display = "none"; break; case 1: c.style.display = "none"; v.style.display = "block"; break } }; var b = function (e, t) { var n = e.appendChild(e.firstChild); n.style.height = t + "px" }; return { REVISION: 11, domElement: l, setMode: y, begin: function () { e = Date.now() }, end: function () { var f = Date.now(); n = f - e; r = Math.min(r, n); i = Math.max(i, n); m.textContent = n + " MS (" + r + "-" + i + ")"; b(g, Math.min(30, 30 - n / 200 * 30)); a++; if (f > t + 1e3) { s = Math.round(a * 1e3 / (f - t)); o = Math.min(o, s); u = Math.max(u, s); h.textContent = s + " FPS (" + o + "-" + u + ")"; b(p, Math.min(30, 30 - s / 100 * 30)); t = f; a = 0 } return f }, update: function () { e = this.end() } } }
// Firefox events suck
function getOffsetXY(eventArgs) { return { X: eventArgs.offsetX == undefined ? eventArgs.layerX : eventArgs.offsetX, Y: eventArgs.offsetY == undefined ? eventArgs.layerY : eventArgs.offsetY }; }
function getWheelDelta(eventArgs) { if (!eventArgs) eventArgs = event; var w = eventArgs.wheelDelta; var d = eventArgs.detail; if (d) { if (w) { return w / d / 40 * d > 0 ? 1 : -1; } else { return -d / 3; } } else { return w / 120; } }
// Reproduction Code
var stats = new Stats();
document.body.appendChild(stats.domElement);
var masterCanvas = document.getElementById('canvas');
var masterContext = masterCanvas.getContext('2d');
var viewOffsetX = 0;
var viewOffsetY = 0;
var viewScaleFactor = 1;
var viewMinScaleFactor = 0.1;
var viewMaxScaleFactor = 10;
var mouseWheelSensitivity = 10; //Fudge Factor
var isMouseDown = false;
var lastMouseCoords = null;
var imageDimensionPixelCount = 25;
var paddingPixelCount = 2;
var canvasDimensionImageCount = 50;
var totalImageCount = Math.pow(canvasDimensionImageCount, 2);
var images = null;
function init() {
images = createLocalImages(totalImageCount, imageDimensionPixelCount);
initInteraction();
renderLoop();
}
function initInteraction() {
var handleMouseDown = function (eventArgs) {
isMouseDown = true;
var offsetXY = getOffsetXY(eventArgs);
lastMouseCoords = [
offsetXY.X,
offsetXY.Y
];
};
var handleMouseUp = function (eventArgs) {
isMouseDown = false;
lastMouseCoords = null;
}
var handleMouseMove = function (eventArgs) {
if (isMouseDown) {
var offsetXY = getOffsetXY(eventArgs);
var panX = offsetXY.X - lastMouseCoords[0];
var panY = offsetXY.Y - lastMouseCoords[1];
pan(panX, panY);
lastMouseCoords = [
offsetXY.X,
offsetXY.Y
];
}
};
var handleMouseWheel = function (eventArgs) {
var mouseX = eventArgs.pageX - masterCanvas.offsetLeft;
var mouseY = eventArgs.pageY - masterCanvas.offsetTop;
var zoom = 1 + (getWheelDelta(eventArgs) / mouseWheelSensitivity);
zoomAboutPoint(mouseX, mouseY, zoom);
if (eventArgs.preventDefault !== undefined) {
eventArgs.preventDefault();
} else {
return false;
}
}
masterCanvas.addEventListener("mousedown", handleMouseDown, false);
masterCanvas.addEventListener("mouseup", handleMouseUp, false);
masterCanvas.addEventListener("mousemove", handleMouseMove, false);
masterCanvas.addEventListener("mousewheel", handleMouseWheel, false);
masterCanvas.addEventListener("DOMMouseScroll", handleMouseWheel, false);
}
function pan(panX, panY) {
masterContext.translate(panX / viewScaleFactor, panY / viewScaleFactor);
viewOffsetX -= panX / viewScaleFactor;
viewOffsetY -= panY / viewScaleFactor;
}
function zoomAboutPoint(zoomX, zoomY, zoomFactor) {
var newCanvasScale = viewScaleFactor * zoomFactor;
if (newCanvasScale < viewMinScaleFactor) {
zoomFactor = viewMinScaleFactor / viewScaleFactor;
} else if (newCanvasScale > viewMaxScaleFactor) {
zoomFactor = viewMaxScaleFactor / viewScaleFactor;
}
masterContext.translate(viewOffsetX, viewOffsetY);
masterContext.scale(zoomFactor, zoomFactor);
viewOffsetX = ((zoomX / viewScaleFactor) + viewOffsetX) - (zoomX / (viewScaleFactor * zoomFactor));
viewOffsetY = ((zoomY / viewScaleFactor) + viewOffsetY) - (zoomY / (viewScaleFactor * zoomFactor));
viewScaleFactor *= zoomFactor;
masterContext.translate(-viewOffsetX, -viewOffsetY);
}
function renderLoop() {
clearCanvas();
renderCanvas();
stats.update();
requestAnimFrame(renderLoop);
}
function clearCanvas() {
masterContext.clearRect(viewOffsetX, viewOffsetY, masterCanvas.width / viewScaleFactor, masterCanvas.height / viewScaleFactor);
}
function renderCanvas() {
for (var imageY = 0; imageY < canvasDimensionImageCount; imageY++) {
for (var imageX = 0; imageX < canvasDimensionImageCount; imageX++) {
var x = imageX * (imageDimensionPixelCount + paddingPixelCount);
var y = imageY * (imageDimensionPixelCount + paddingPixelCount);
var imageIndex = (imageY * canvasDimensionImageCount) + imageX;
var image = images[imageIndex];
masterContext.drawImage(image, x, y, imageDimensionPixelCount, imageDimensionPixelCount);
}
}
}
function createLocalImages(imageCount, imageDimension) {
var tempCanvas = document.createElement('canvas');
tempCanvas.width = imageDimension;
tempCanvas.height = imageDimension;
var tempContext = tempCanvas.getContext('2d');
var images = new Array();
for (var imageIndex = 0; imageIndex < imageCount; imageIndex++) {
tempContext.clearRect(0, 0, imageDimension, imageDimension);
tempContext.fillStyle = "rgb(" + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ")";
tempContext.fillRect(0, 0, imageDimension, imageDimension);
var image = new Image();
image.src = tempCanvas.toDataURL('image/png');
images.push(image);
}
return images;
}
// Get this party started
init();
And a jsfiddle link for your interactive pleasure:
http://jsfiddle.net/BtyL6/14/
This is drawing 50px x 50px images in a 50 x 50 (2500) grid on the canvas. I've also quickly tried with 25px x 25px and 50 x 50 (2500) images.
We have other local examples that deal with bigger images and larger numbers of images and the other browser start to struggle with these at higher values.
As a quick test I jacked up the code in the js fiddle to 100px x 100px and 100 x 100 (10000) images and that was still running at 16fps when fully zoomed out. (Note: I had to lower the viewMinScaleFactor to 0.01 to fit it all in when zoomed out.)
Chrome on the other hand seems to hit some kind of limit and the FPS drops from 60 to 2-4.
Here's some info about what we've tried and the results:
We've tried using setinterval rather than requestAnimationFrame.
If you load 10 images and draw them 250 times each rather than 2500 images drawn once each then the problem goes away. This seems to indicate that chrome is hitting some kind of limit/trigger as to how much data it's storing about the rendering.
We have culling (not rendering images outside of the visual range) in our more complex examples and while this helps it's not a solution as we need to be able to show all the images at once.
We have the images only being rendered if there have been changes in our local code, against this helps (when nothing changes, obviously) but it isn't a full solution because the canvas should be interactive.
In the example code we're creating the images using a canvas, but the code can also be run hitting a web service to provide the images and the same behaviour (slowness) will be seen.
We've found it very hard to even search for this issue, most results are from a couple of years ago and woefully out of date.
If any more information would be useful then please ask!
EDIT: Changed js fiddle URL to reflect the same code as in the question. The code itself didn't actually change, just the formatting. But I want to be consistent.
EDIT: Updated jsfiddle and and code with css to prevent selection and call requestAnim after the render loop is done.
In Canary this code freezes it on my computer. As to why this happens in Chrome the simple answer is that it uses a different implementation than f.ex. FF. In-depth detail I don't know, but there is obviously room for optimizing the implementation in this area.
I can give some tip however on how you can optimize the given code to make it run in Chrome as well :-)
There are several things here:
You are storing each block of colors as images. This seem to have a huge performance impact on Canary / Chrome.
You are calling requestAnimationFrame at the beginning of the loop
You are clearing and rendering even if there are no changes
Try to (addressing the points):
If you only need solid blocks of colors, draw them directly using fillRect() instead and keep the color indexes in an array (instead of images). Even if you draw them to an off-screen canvas you will only have to do one draw to main canvas instead of multiple image draw operations.
Move requestAnimationFrame to the end of the code block to avoid stacking.
Use dirty flag to prevent unnecessary rendering:
I modified the code a bit - I modified it to use solid colors to demonstrate where the performance impact is in Chrome / Canary.
I set a dirty flag in global scope as true (to render the initial scene) which is set to true each time the mouse move occur:
//global
var isDirty = true;
//mouse move handler
var handleMouseMove = function (eventArgs) {
// other code
isDirty = true;
// other code
};
//render loop
function renderLoop() {
if (isDirty) {
clearCanvas();
renderCanvas();
}
stats.update();
requestAnimFrame(renderLoop);
}
//in renderCanvas at the end:
function renderCanvas() {
// other code
isDirty = false;
}
You will of course need to check for caveats for the isDirty flag elsewhere and also introduce more criteria if it's cleared at the wrong moment. I would store the old position of the mouse and only (in the mouse move) if it changed set the dirty flag - I didn't modify this part though.
As you can see you will be able to run this in Chrome and in FF at a higher FPS.
I also assume (I didn't test) that you can optimize the clearCanvas() function by only drawing the padding/gaps instead of clearing the whole canvas. But that need to be tested.
Added a CSS-rule to prevent the canvas to be selected when using the mouse:
For further optimizing in cases such as this, which is event driven, you don't actually need an animation loop at all. You can just call the redraw when the coords or mouse-wheel changes.
Modification:
http://jsfiddle.net/BtyL6/10/
This was a legitimate bug in chrome.
https://code.google.com/p/chromium/issues/detail?id=247912
It has now been fixed and should be in a chrome mainline release soon.

Categories