从画布保存/转换后文件质量降低的问题 [英] Issue with reduced quality on file after saving/converting from canvas

查看:143
本文介绍了从画布保存/转换后文件质量降低的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我使用的代码。 (代码位于此帖的底部,但此处链接到 GitHubGist :: Noitidart / _ff-addon-snippet -browseForBadgeThenCreateSaveAnApply.js )它是复制pastatble到scratchpad(我试图fiddle但它需要privelage范围)。当您运行它将要求您选择一个16x16图像。然后它会采取firefox图标,并把它放在画布上,然后拿你浏览的图标,并覆盖在右下角。然后它将它转换为 .ico 并保存到您的桌面 profilist16.ico profilist32.ico

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.

完成上述操作后,请打开一个新的firefox窗口,然后在alt +标签页中,您将看到firefox徽标的徽章图标更脏。

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.

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

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


推荐答案

这实际上是相当可重现的,但仅在使用 BMP 图标,而不是 PNG 图标时。

Alright. This is actually quite reproducible, but only when using BMP icons, but not PNG icons.

看起来Firefox的图标编码器相当糟糕/ buggy(对于RGBA东西)。那么,实际上是ICO编码器使用的BMP编码器...

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.

这里是我完整的示例代码incl。图标编码器(只是设置32x32图标),但缺乏图标的停用。但是作为奖励,它显示如何通过WNDCLASS设置图标。

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:以下是XP上任务切换器的屏幕截图:

PS: Here is a screenshot from the Task Switcher on XP:

这篇关于从画布保存/转换后文件质量降低的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆