从画布保存/转换后文件质量下降的问题

时间:2014-06-17 03:32:35

标签: javascript canvas firefox-addon ico

这是我正在使用的代码。 (代码在这篇文章的底部,但这里是GitHubGist :: Noitidart / _ff-addon-snippet-browseForBadgeThenCreateSaveAnApply.js的链接)它是复制pastatble到scratchpad(我试过小提琴,但它需要privelage范围)。运行时会要求您选择16x16图像。然后它将获取firefox图标并将其放在画布上然后将您浏览的图标放到右下角。然后它会将其转换为.ico并以profilist16.icoprofilist32.ico保存到您的桌面。然后它将更改所有firefox窗口的图标。

执行上述操作后,请打开一个新的firefox窗口,然后在alt + tab中,您会看到徽章图标的firefox徽标更脏。

在底部,您会看到原始画布图(它看起来很模糊,但我认为这是我在Firefox上的缩放级别)。图标很清晰但是如果你注意到边缘上的徽章图标(右边)(特别是顶部),你会看到污垢,就像通常的图标(左侧)中看不到的黑色锯齿状物体一样

comparing images

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();

2 个答案:

答案 0 :(得分:2)

好的。这实际上非常可重复,但仅限于使用BMP图标而非PNG图标时。

似乎Firefox发布的图标编码器非常糟糕/错误(对于RGBA的东西)。嗯,实际上它是ICO编码器使用的BMP编码器......

因为比利时/阿尔及利亚(游戏,足球,而不是美国)刚才很无聊,我写了自己的图标编码器,实际上并不太难。

所以这是我的完整示例代码incl。图标编码器(仅设置32x32图标),但缺少图标的处置。但作为奖励,它显示了如何通过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:这是XP上的Task Switcher的截图:

Composed icons as displayed in XP Task Switcher

Composed icons as displayed in Win7 Task Switcher

答案 1 :(得分:0)

在Win7上测试了代码,一旦应用的图标真的很糟糕。在下图中,我们看到画布中的图标是完美的。在alt + tab菜单中,第一个图标是SUPER crappy,第二个图标是unbadged如此完美,第三个和第五个图标是正常的蹩脚。

图片在这里:http://i.stack.imgur.com/47dIr.png

编辑: 通过改变win7的这一行来修复SUPER的疯狂,它是32,32,我做了256,256,不知道为什么它修复了SUPER垃圾:

var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 256, 256, LR_LOADFROMFILE);

之前:var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 32, 32, LR_LOADFROMFILE); 然而,通常的废话,黑色粗糙的边缘仍然存在。