javascript - canvas寬高 - canvas自適應




在HTML5畫布中調整圖片大小 (12)

使用帶JavaScript的Hermite濾鏡快速調整圖像大小/重採樣算法。 支持透明度,提供良好的質量。 預習:


更新 :在GitHub上添加了2.0版(更快,web工作者+可轉移對象)。 最後我得到它的工作!

Git: https://github.com/viliusle/Hermite-resizehttps://github.com/viliusle/Hermite-resize
演示: http://viliusle.github.io/miniPaint/ : 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);
}

我試圖在客戶端使用javascript和canvas元素創建縮略圖,但是當我縮小圖像時,它看起來很糟糕。 看起來好像它在photoshop中縮小了,重新採樣設置為“最近鄰居”而不是Bicubic。 我知道它有可能讓這看起來很正確,因為這個網站可以很好地使用畫布。 我試過使用與“[Source]”鏈接中顯示的相同的代碼,但它看起來仍然很糟糕。 有什麼我失踪,需要設置一些設置或什麼?

編輯:

我正在嘗試調整JPG格式。 我曾嘗試在鏈接網站和photoshop上調整相同的JPG大小,並在縮小尺寸時看起來很好。

這裡是相關的代碼:

reader.onloadend = function(e)
{
    var img = new Image();
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");

    img.onload = function()
    {
        var ratio = 1;

        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;
        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    };

    img.src = reader.result;
}

EDIT2:

似乎我錯了,鏈接的網站在縮小圖像方面沒有做得更好。 我嘗試了其他建議的方法,沒有一個看起來更好。 這就是不同方法的結果:

Photoshop中:

帆布:

具有圖像渲染的圖像:optimizeQuality集合,並按照寬度/高度縮放:

帶圖像渲染的圖像:optimizeQuality集合併使用-moz-transform進行縮放:

帆佈在pixastic上調整大小:

我想這意味著Firefox並沒有像它應該的那樣使用雙三次採樣。 我只需要等到他們實際添加它。

EDIT3:

原始圖像


嘗試pica - 這是一個高度優化的調整器與可選algoryory。 看demo

例如,第一篇文章的原始圖像在Lanczos濾鏡和3px窗口的120ms內調整大小,在Box濾鏡和0.5px窗口中調整大小為60ms。 對於龐大的17MB圖像5000x3000px調整大小在桌面上需要1秒,在手機上需要3秒。

所有調整原則在這個主題中都有很好的描述,異食癖並沒有增加火箭科學。 但是它對現代JIT-s的優化非常好,並且可以使用開箱即用(通過npm或bower)。 此外,它可以使用webworkers,以避免界面凍結。

我還計劃很快添加不敏銳的蒙版支持,因為它在縮減後非常有用。


尋找另一個非常簡單的解決方案

var img=document.createElement('img');
img.src=canvas.toDataURL();
$(img).css("background", backgroundColor);
$(img).width(settings.width);
$(img).height(settings.height);

這個解決方案將使用瀏覽器的resize算法! :)


對於寬度小於原始大小的圖像,我使用:

    function resize2(i) {
      var cc = document.createElement("canvas");
      cc.width = i.width / 2;
      cc.height = i.height / 2;
      var ctx = cc.getContext("2d");
      ctx.drawImage(i, 0, 0, cc.width, cc.height);
      return cc;
    }
    var cc = img;
    while (cc.width > 64 * 2) {
      cc = resize2(cc);
    }
    // .. than drawImage(cc, .... )

它工作=)。


感謝@syockit提供了一個非常棒的答案。 然而,我不得不重新格式化一下,以使其工作。 也許是由於DOM掃描問題:

$(document).ready(function () {

$('img').on("load", clickA);
function clickA() {
    var img = this;
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 50, 3);
    document.body.appendChild(canvas);
}

function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width: sx,
        height: Math.round(img.height * sx / img.width)
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(process1, 0, this, 0);
}

//returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function (x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    }
}

process1 = function (self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(process1, 0, self, u);
    else
        setTimeout(process2, 0, self);
};

process2 = function (self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
}
});

我只是跑了一頁並排比較,除非最近有什麼變化,我可以看到沒有更好的縮小(縮放)使用畫布與簡單的CSS。 我在FF6 Mac OSX 10.7中測試過。 仍比原來稍微軟。

然而,我確實遇到了一些影響巨大的事情,那就是在支持canvas的瀏覽器中使用圖像過濾器。 實際上,您可以在Photoshop中使用模糊,銳化,飽和度,波紋,灰度等操作圖像。

然後,我發現了一個令人敬畏的jQuery插件,它使這些過濾器的應用程序變得簡單: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234 : http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

調整圖像大小後,我只需應用銳化濾鏡即可獲得所需的效果。 我甚至不必使用畫布元素。


我已經提出了一些算法來對HTML canvas像素數組進行圖像插值,這在這裡可能很有用:

http://jsperf.com/pixel-interpolation/2

這些可以被複製/粘貼,並且可以在網絡工作者內部使用來調整圖像大小(或者任何其他需要插值的操作 - 我正在使用它們來減少圖像)。

我還沒有在上面添加lanczos的東西,所以隨意添加,作為比較,如果你願意。


我強烈建議你查看這個鏈接 ,並確保它設置為true。

控製圖像縮放行為

在Gecko 1.9.2中引入(Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2將mozImageSmoothingEnabled屬性引入了canvas元素; 如果此佈爾值為false,則縮放時圖像將不會平滑。 該屬性默認為true。 查看文本打印

  1. cx.mozImageSmoothingEnabled = false;

我知道這是一條古老的線索,但對於像幾個月後第一次觸及這個問題的幾個人來說,這可能是有用的。

下面是一些代碼,每次重新加載圖像時都會調整圖像的大小。 我知道這並不是最理想的,但我提供它作為概念的證明。

另外,對於簡單的選擇器使用jQuery感到抱歉,但我只是覺得語法太舒服了。

$(document).on('ready', createImage);
$(window).on('resize', createImage);

var createImage = function(){
  var canvas = document.getElementById('myCanvas');
  canvas.width = window.innerWidth || $(window).width();
  canvas.height = window.innerHeight || $(window).height();
  var ctx = canvas.getContext('2d');
  img = new Image();
  img.addEventListener('load', function () {
    ctx.drawImage(this, 0, 0, w, h);
  });
  img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};
html, body{
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #000;
}
canvas{
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Canvas Resize</title>
  </head>
  <body>
    <canvas id="myCanvas"></canvas>
  </body>
</html>

當文檔被加載時,我的createImage函數被調用一次,然後每次窗口收到一個resize事件時調用它。

我在Mac上對Chrome 6和Firefox 3.6進行了測試。 這種“技術”在夏天就像冰淇淋一樣吃處理器,但它卻有效。


我通過右鍵單擊Firefox中的canvas元素並保存為。

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';

無論如何,這裡是你的例子的一個“固定”版本:

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';

這些解決方案中的一個問題是他們直接訪問像素數據並通過它循環執行下採樣。 根據圖像的大小,這可能會佔用大量資源,並且最好使用瀏覽器的內部算法。

drawImage()函數使用線性內插最近鄰居重採樣方法。 如果您不調整原來尺寸的一半以上,則效果很好

如果您只循環調整最大一半大小,結果將會非常好,並且比訪問像素數據要快得多。

此功能每次減半採樣直到達到所需的大小:

  function resize_image( src, dst, type, quality ) {
     var tmp = new Image(),
         canvas, context, cW, cH;

     type = type || 'image/jpeg';
     quality = quality || 0.92;

     cW = src.naturalWidth;
     cH = src.naturalHeight;

     tmp.src = src.src;
     tmp.onload = function() {

        canvas = document.createElement( 'canvas' );

        cW /= 2;
        cH /= 2;

        if ( cW < src.width ) cW = src.width;
        if ( cH < src.height ) cH = src.height;

        canvas.width = cW;
        canvas.height = cH;
        context = canvas.getContext( '2d' );
        context.drawImage( tmp, 0, 0, cW, cH );

        dst.src = canvas.toDataURL( type, quality );

        if ( cW <= src.width || cH <= src.height )
           return;

        tmp.src = dst.src;
     }

  }
  // The images sent as parameters can be in the DOM or be image objects
  resize_image( $( '#original' )[0], $( '#smaller' )[0] );

感謝這篇文章


這是從@ Telanor的代碼改編的javascript函數。 將圖像base64作為第一個參數傳遞給函數時,它會返回調整大小的圖像的base64。 maxWidth和maxHeight是可選的。

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

}




image-resizing