Resizing an image in an HTML5 canvas

后端 未结 18 2733
半阙折子戏
半阙折子戏 2020-11-22 03:37

I\'m trying to create a thumbnail image on the client side using javascript and a canvas element, but when I shrink the image down, it looks terrible. It looks as if it was

相关标签:
18条回答
  • 2020-11-22 03:55

    Fast image resize/resample algorithm using Hermite filter with JavaScript. Support transparency, gives good quality. Preview:

    enter image description here

    Update: version 2.0 added on GitHub (faster, web workers + transferable objects). Finally i got it working!

    Git: https://github.com/viliusle/Hermite-resize
    Demo: http://viliusle.github.io/miniPaint/

    /**
     * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
     * 
     * @param {HtmlElement} canvas
     * @param {int} width
     * @param {int} height
     * @param {boolean} resize_canvas if true, canvas will be resized. Optional.
     */
    function resample_single(canvas, width, height, resize_canvas) {
        var width_source = canvas.width;
        var height_source = canvas.height;
        width = Math.round(width);
        height = Math.round(height);
    
        var ratio_w = width_source / width;
        var ratio_h = height_source / height;
        var ratio_w_half = Math.ceil(ratio_w / 2);
        var ratio_h_half = Math.ceil(ratio_h / 2);
    
        var ctx = canvas.getContext("2d");
        var img = ctx.getImageData(0, 0, width_source, height_source);
        var img2 = ctx.createImageData(width, height);
        var data = img.data;
        var data2 = img2.data;
    
        for (var j = 0; j < height; j++) {
            for (var i = 0; i < width; i++) {
                var x2 = (i + j * width) * 4;
                var weight = 0;
                var weights = 0;
                var weights_alpha = 0;
                var gx_r = 0;
                var gx_g = 0;
                var gx_b = 0;
                var gx_a = 0;
                var center_y = (j + 0.5) * ratio_h;
                var yy_start = Math.floor(j * ratio_h);
                var yy_stop = Math.ceil((j + 1) * ratio_h);
                for (var yy = yy_start; yy < yy_stop; yy++) {
                    var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
                    var center_x = (i + 0.5) * ratio_w;
                    var w0 = dy * dy; //pre-calc part of w
                    var xx_start = Math.floor(i * ratio_w);
                    var xx_stop = Math.ceil((i + 1) * ratio_w);
                    for (var xx = xx_start; xx < xx_stop; xx++) {
                        var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
                        var w = Math.sqrt(w0 + dx * dx);
                        if (w >= 1) {
                            //pixel too far
                            continue;
                        }
                        //hermite filter
                        weight = 2 * w * w * w - 3 * w * w + 1;
                        var pos_x = 4 * (xx + yy * width_source);
                        //alpha
                        gx_a += weight * data[pos_x + 3];
                        weights_alpha += weight;
                        //colors
                        if (data[pos_x + 3] < 255)
                            weight = weight * data[pos_x + 3] / 250;
                        gx_r += weight * data[pos_x];
                        gx_g += weight * data[pos_x + 1];
                        gx_b += weight * data[pos_x + 2];
                        weights += weight;
                    }
                }
                data2[x2] = gx_r / weights;
                data2[x2 + 1] = gx_g / weights;
                data2[x2 + 2] = gx_b / weights;
                data2[x2 + 3] = gx_a / weights_alpha;
            }
        }
        //clear and resize canvas
        if (resize_canvas === true) {
            canvas.width = width;
            canvas.height = height;
        } else {
            ctx.clearRect(0, 0, width_source, height_source);
        }
    
        //draw
        ctx.putImageData(img2, 0, 0);
    }
    
    0 讨论(0)
  • 2020-11-22 03:56

    So something interesting that I found a while ago while working with canvas that might be helpful:

    To resize the canvas control on its own, you need to use the height="" and width="" attributes (or canvas.width/canvas.height elements). If you use CSS to resize the canvas, it will actually stretch (i.e.: resize) the content of the canvas to fit the full canvas (rather than simply increasing or decreasing the area of the canvas.

    It'd be worth a shot to try drawing the image into a canvas control with the height and width attributes set to the size of the image and then using CSS to resize the canvas to the size you're looking for. Perhaps this would use a different resizing algorithm.

    It should also be noted that canvas has different effects in different browsers (and even different versions of different browsers). The algorithms and techniques used in the browsers is likely to change over time (especially with Firefox 4 and Chrome 6 coming out so soon, which will place heavy emphasis on canvas rendering performance).

    In addition, you may want to give SVG a shot, too, as it likely uses a different algorithm as well.

    Best of luck!

    0 讨论(0)
  • 2020-11-22 03:58

    I've put up some algorithms to do image interpolation on html canvas pixel arrays that might be useful here:

    https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2

    These can be copy/pasted and can be used inside of web workers to resize images (or any other operation that requires interpolation - I'm using them to defish images at the moment).

    I haven't added the lanczos stuff above, so feel free to add that as a comparison if you'd like.

    0 讨论(0)
  • 2020-11-22 03:59

    This is a javascript function adapted from @Telanor's code. When passing a image base64 as first argument to the function, it returns the base64 of the resized image. maxWidth and maxHeight are optional.

    function thumbnail(base64, maxWidth, maxHeight) {
    
      // Max size for thumbnail
      if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
      if(typeof(maxHeight) === 'undefined') var maxHeight = 500;
    
      // Create and initialize two canvas
      var canvas = document.createElement("canvas");
      var ctx = canvas.getContext("2d");
      var canvasCopy = document.createElement("canvas");
      var copyContext = canvasCopy.getContext("2d");
    
      // Create original image
      var img = new Image();
      img.src = base64;
    
      // Determine new ratio based on max size
      var ratio = 1;
      if(img.width > maxWidth)
        ratio = maxWidth / img.width;
      else if(img.height > maxHeight)
        ratio = maxHeight / img.height;
    
      // Draw original image in second canvas
      canvasCopy.width = img.width;
      canvasCopy.height = img.height;
      copyContext.drawImage(img, 0, 0);
    
      // Copy and resize second canvas to first canvas
      canvas.width = img.width * ratio;
      canvas.height = img.height * ratio;
      ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    
      return canvas.toDataURL();
    
    }
    
    0 讨论(0)
  • 2020-11-22 04:00

    i got this image by right clicking the canvas element in firefox and saving as.

    alt text

    var img = new Image();
    img.onload = function () {
        console.debug(this.width,this.height);
        var canvas = document.createElement('canvas'), ctx;
        canvas.width = 188;
        canvas.height = 150;
        document.body.appendChild(canvas);
        ctx = canvas.getContext('2d');
        ctx.drawImage(img,0,0,188,150);
    };
    img.src = 'original.jpg';
    

    so anyway, here is a 'fixed' version of your example:

    var img = new Image();
    // added cause it wasnt defined
    var canvas = document.createElement("canvas");
    document.body.appendChild(canvas);
    
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    // adding it to the body
    
    document.body.appendChild(canvasCopy);
    
    var copyContext = canvasCopy.getContext("2d");
    
    img.onload = function()
    {
            var ratio = 1;
    
            // defining cause it wasnt
            var maxWidth = 188,
                maxHeight = 150;
    
            if(img.width > maxWidth)
                    ratio = maxWidth / img.width;
            else if(img.height > maxHeight)
                    ratio = maxHeight / img.height;
    
            canvasCopy.width = img.width;
            canvasCopy.height = img.height;
            copyContext.drawImage(img, 0, 0);
    
            canvas.width = img.width * ratio;
            canvas.height = img.height * ratio;
            // the line to change
            // ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
            // the method signature you are using is for slicing
            ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
    };
    
    // changed for example
    img.src = 'original.jpg';
    
    0 讨论(0)
  • 2020-11-22 04:05

    Try pica - that's a highly optimized resizer with selectable algorythms. See demo.

    For example, original image from first post is resized in 120ms with Lanczos filter and 3px window or 60ms with Box filter and 0.5px window. For huge 17mb image 5000x3000px resize takes ~1s on desktop and 3s on mobile.

    All resize principles were described very well in this thread, and pica does not add rocket science. But it's optimized very well for modern JIT-s, and is ready to use out of box (via npm or bower). Also, it use webworkers when available to avoid interface freezes.

    I also plan to add unsharp mask support soon, because it's very useful after downscale.

    0 讨论(0)
提交回复
热议问题