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

笑着哭i 提交于 2019-12-02 19:56:07

问题


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

回答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');

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:




回答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.



来源:https://stackoverflow.com/questions/24255298/issue-with-reduced-quality-on-file-after-saving-converting-from-canvas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!