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

前端 未结 2 2151
余生分开走
余生分开走 2021-01-26 09:53

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 c

2条回答
  •  梦毁少年i
    2021-01-26 10:11

    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:

    Composed icons as displayed in XP Task Switcher

    Composed icons as displayed in Win7 Task Switcher

提交回复
热议问题