Issue With Reduced Quality On File After Saving/converting From Canvas
Solution 1:
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');
letIMAGE_BITMAP=0;
letIMAGE_ICON=1;
letWM_SETICON=128;
letGCLP_HICON= -14;
letuser32= ctypes.open('user32.dll');
letSendMessage= 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
);
letCreateIconFromResourceEx= 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
);
letSetClassLongPtr= 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
);
letgdi32= ctypes.open('gdi32.dll');
letDeleteObject= gdi32.declare(
'DeleteObject',
ctypes.winapi_abi,
ctypes.int,
ctypes.voidptr_t // Object
);
letsetPerWindow=false;
letbadges= [
'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 {
letp= Promise.defer();
letimg=newImage();
img.onload = () => p.resolve();
img.src = 'chrome://branding/content/icon32.png';
yield p.promise;
p = Promise.defer();
letbadge=newImage();
badge.onload = () => p.resolve();
badge.src = badges[getRandomInt(0, badges.length - 1)];
console.log(badge.src);
yield p.promise;
letcanvas= document.createElementNS(
'http://www.w3.org/1999/xhtml',
'canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
letctx= canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
letonethird= 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.letdata= ctx.getImageData(0, 0, canvas.width, canvas.height).data;
letXOR= data.length;
letAND= canvas.width * canvas.height / 8;
letsize=22/* ICONDIR + ICONDIRENTRY */ + 40/* BITMAPHEADER */ + XOR + AND;
letbuffer=newArrayBuffer(size);
// ICONDIRletview=newDataView(buffer);
view.setUint16(2, 1, true); // type 1
view.setUint16(4, 1, true); // count;// ICONDIRENTRY
view = newDataView(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 = newDataView(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 -> BGRAfor (leti=0; i < XOR; i += 4) {
lettemp= data[i];
data[i] = data[i + 2];
data[i + 2] = temp;
}
letico=newUint8Array(buffer, 22 + 40);
letstride= canvas.width * 4;
// Write bottom to topfor (leti=0; i < canvas.height; ++i) {
letsu= 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)letwritePath= OS.Path.join(OS.Constants.Path.desktopDir, 'icon32.ico');
yield OS.File.writeAtomic(writePath, newUint8Array(buffer), {
tmpPath: writePath + '.tmp'
});
// Cut off ICONDIR/ICONDIRENTRY for CreateIconFromResourceEx
buffer = buffer.slice(22);
lethicon= CreateIconFromResourceEx(
ctypes.uint8_t.ptr(buffer),
buffer.byteLength,
IMAGE_ICON,
0x30000,
0,
0,
0);
if (hicon.isNull()) {
thrownewError("Failed to load icon");
}
if (setPerWindow) {
letDOMWindows= Services.wm.getEnumerator(null);
while (DOMWindows.hasMoreElements()) {
letwin= DOMWindows.getNext().QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
lethandle= ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
console.error("Failed to get window handle");
continue;
}
varlparam= ctypes.cast(hicon, ctypes.intptr_t);
varoldIcon= 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 {
letwin= Services.wm.getMostRecentWindow(null).
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
lethandle= ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
thrownewError("Failed to get window handle");
}
letoldIcon= 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:
Solution 2:
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.
Post a Comment for "Issue With Reduced Quality On File After Saving/converting From Canvas"