调整HTML5画布中图像的大小


314

我正在尝试使用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;
}

编辑2:

似乎我弄错了,链接的网站在缩小图像尺寸方面做得更好。我尝试了建议的其他方法,但没有一个看起来更好。这就是不同方法导致的结果:

Photoshop:

替代文字

帆布:

替代文字

具有图像渲染的图像:optimizeQuality设置并随宽度/高度缩放:

替代文字

具有图像渲染的图像:optimizeQuality设置并使用-moz-transform缩放:

替代文字

画布在像素上调整大小:

替代文字

我猜这意味着Firefox并未像预期的那样使用双三次采样。我只需要等待,直到他们实际添加它。

编辑3:

原始图片


您可以发布用于调整图像大小的代码吗?
Xavi

您是否要使用有限的调色板调整GIF图像或类似图像的大小?即使在photoshop中,除非将它们转换为RGB,否则它们的缩放比例也不会很好。
leepowers

您可以张贴原始图像的副本吗?
哈维

使用javascript调整图像大小可能有点麻烦-不仅您要使用客户端处理能力来调整图像大小,而且还要在每次加载单个页面时执行此操作。为什么不只从Photoshop中保存缩小版本并与原始图像配合使用呢?
定义2010年

29
因为我要使图像上传器具有在上传之前调整图像大小和裁剪图像的功能。
Telanor 2010年

Answers:


393

那么,如果所有浏览器(实际上,Chrome 5给我提供了一个不错的浏览器)都无法为您提供足够好的重采样质量,您该怎么办?然后,您自己实现它们!哦,来吧,我们正在进入Web 3.0的新时代,符合HTML5的浏览器,超级优化的JIT javascript编译器,具有大量内存的多核(†)机器,您担心什么?嘿,javascript中有java一词,因此应该可以保证性能,对吗?看一下,缩略图生成代码:

// 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;
    };
}

// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
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(this.process1, 0, this, 0);
}

thumbnailer.prototype.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(self.process1, 0, self, u);
    else
        setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
    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";
};

...您可以用它产生这样的结果!

img717.imageshack.us/img717/8910/lanczos358.png

因此,无论如何,这是示例的“固定”版本:

img.onload = function() {
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
    // but feel free to raise it up to 8. Your client will appreciate
    // that the program makes full use of his machine.
    document.body.appendChild(canvas);
};

现在是时候让您最好的浏览器进站了,看看哪种浏览器最有可能增加您的客户的血压!

嗯,我的讽刺标签在哪里?

(由于代码的许多部分都基于Anrieff Gallery GeneratorGPL2还涵盖了它吗?我不知道)

实际上由于javascript的限制,不支持多核。


2
我实际上已经尝试过自己实现它,就像您所做的那样,从开源图像编辑器复制代码。由于找不到关于该算法的任何可靠文档,因此我很难优化它。最后,我的速度有点慢(花了几秒钟来调整图像的大小)。如果有机会,我会尝试一下,看看是否更快。而且我认为网络工作者现在使多核javascript成为可能。我打算尝试使用它们来加快速度,但是在弄清楚如何使其成为多线程算法方面遇到了麻烦
Telanor 2010年

3
对不起,忘记了!我已经编辑了回复。无论如何它不会很快,三次立方应该更快。更不用说我使用的算法不是通常的2向调整大小(逐行,水平然后垂直),因此速度慢了一些。
syockit'7

5
您真棒,应该得到大量的真棒。
罗克兰

5
这会产生不错的结果,但是在最新版本的Chrome中,要获得1.8 MP图像的时间需要7.4秒...
mpen

2
这样的方法如何取得如此高的分数?显示的解决方案完全无法说明用于存储颜色信息的对数刻度。127,127,127的RGB是255、255、255亮度的四分之一而不是一半。解决方案中的向下采样会导致图像变暗。羞耻,这是封闭,有一个非常简单和快速的向下大小的方法,产生比Photoshop中更好的结果(OP必须有偏好设置错误)采样给出
Blindman67

37

使用带有JavaScript的Hermite过滤器的快速图像调整大小/重采样算法。支持透明,提供良好的质量。预习:

在此处输入图片说明

更新:在GitHub上添加了2.0版(更快,网络工作者+可转移对象)。终于我得到了它的工作!

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

也许您可以包括指向miniPaint演示和Github存储库的链接?
syockit 2013年

1
您还将分享Webworkers版本吗?可能是由于设置开销所致,对于较小的图像,它的速度较慢,但​​对于较大的源图像,它可能很有用。
syockit 2013年

添加了演示,git链接以及多核版本。顺便说一句,我没有花太多的时间在优化多核版本上。
ViliusL 2013年

巨大的差异和良好的性能。非常感谢你!之前之后
KevBurnsJr

2
@ViliusL啊,现在我想起了为什么网络工作者无法很好地工作。他们以前没有共享内存,现在仍然没有共享内存!也许有一天,当他们设法解决它时,您的代码将开始使用(或者人们可能使用
PNaCl

26

尝试异食癖 -这是一个高度优化的图像缩放,可选择algorythms。参见演示

例如,使用Lanczos滤镜和3px窗口将第一篇文章的原始图像调整为120毫秒,使用Box滤镜和0.5px窗口将其调整为60毫秒。对于17mb的巨大图片,5000x3000px的调整大小在台式机上约为1秒,在移动设备上约为3秒。

在本主题中,所有调整大小的原理都得到了很好的描述,而pica并没有增加火箭技术。但是它对于现代JIT-s进行了很好的优化,并且可以直接使用(通过npm或bower)使用。此外,它在可用时使用Webworkers以避免界面冻结。

我还计划很快添加不清晰的遮罩支持,因为在缩小后它非常有用。


14

我知道这是一个旧线程,但是对于像我这样的某些人来说,几个月后首次遇到此问题可能很有用。

这是一些代码,每次重新加载图像时都会调整图像的大小。我知道这根本不是最佳选择,但我将其作为概念证明。

另外,很抱歉将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事件时,都会调用我的createImage函数。

我在Mac上的Chrome 6和Firefox 3.6中进行了测试。这种“技术”就像夏天在吃冰淇淋一样,会吃掉加工机,但是却成功了。


9

我提出了一些算法来对html canvas像素数组进行图像插值,这可能在这里有用:

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

可以复制/粘贴这些文件,并可以在网络工作者中使用它们来调整图像大小(或其他任何需要插值的操作-我现在正在使用它们来去除图像)。

我没有在上面添加lanczos的内容,因此,如果您愿意,可以随意添加它作为比较。


6

这是从@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();

}

你的做法是非常快的,但它产生的模糊图像,你可以在这里看到:stackoverflow.com/questions/18922880/...
confile

6

我强烈建议您检查此链接,并确保将其设置为true。

控制图像缩放行为

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

Gecko 1.9.2在画布元素中引入了mozImageSmoothingEnabled属性。如果此布尔值为false,则缩放后图像不会平滑。默认情况下,此属性为true。查看原图?

  1. cx.mozImageSmoothingEnabled = false;

5

如果您只是想调整图像的大小,建议您使用CSS 设置widthheight图像。这是一个简单的例子:

.small-image {
    width: 100px;
    height: 100px;
}

请注意,heightwidth也可以使用JavaScript进行设置。这是快速的代码示例:

var img = document.getElement("my-image");
img.style.width = 100 + "px";  // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE

另外,为确保调整后的图像看起来不错,请在图像选择器中添加以下css规则:

据我所知,默认情况下,除IE以外的所有浏览器都使用双三次算法来调整图像大小,因此,在Firefox和Chrome浏览器中,调整大小后的图像应该看起来不错。

如果设置的CSS widthheight不工作,你可能想用CSS来打transform

如果出于任何原因需要使用画布,请注意可以通过两种方式调整图像的大小:通过使用CSS调整画布的大小或以较小的尺寸绘制图像。

有关更多详细信息,请参见此问题


5
可悲的是,调整画布的大小或以较小的尺寸绘制图像都不能解决问题(在Chrome中)。
Nestor

1
Chrome 27可以生成调整大小的图像,但是您无法将结果复制到画布上;尝试这样做将改为复制原始图像。
syockit

4

为了将图像调整为宽度小于原始图像,我使用:

    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, .... )

它有效=)。


4

我通过右键单击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';

我尝试做您所做的事情,但效果却不如您。除非我错过了什么,否则您所做的唯一更改就是使用缩放方法签名而不是切片,对吗?由于某种原因,它对我不起作用。
特拉诺

3

这些解决方案中的一些解决方案的问题在于,它们直接访问像素数据并遍历像素数据以执行下采样。根据图像的大小,这可能会占用大量资源,因此最好使用浏览器的内部算法。

所述的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] );

此帖子的积分


2

因此,我在使用画布时发现的一些有趣的事情可能会有所帮助:

要自行调整画布控件的大小,您需要使用height=""width=""属性(或canvas.width/ canvas.height元素)。如果使用CSS调整画布的大小,它将实际上拉伸(即调整大小)画布的内容以适合整个画布(而不是简单地增加或减小画布的面积)。

尝试将图像绘制到canvas控件中,将height和width属性设置为图像的大小,然后使用CSS将画布的大小调整为所需的大小,这是值得尝试的。也许这将使用不同的调整大小算法。

还应注意,画布在不同的浏览器(甚至不同浏览器的不同版本)中具有不同的效果。浏览器中使用的算法和技术可能会随时间而变化(尤其是Firefox 4和Chrome 6很快问世,这将极大地强调画布渲染性能)。

另外,您可能也想尝试一下SVG,因为它也可能使用其他算法。

祝你好运!


1
通过HTML属性设置画布的宽度或高度会导致画布被清除,因此该方法无法进行任何大小调整。同样,SVG用于处理数学图像。我需要能够绘制PNG等,这样就不会对我有所帮助。
Telanor

我发现(在Chrome中)设置画布的高度和宽度并使用CSS调整大小无济于事。即使使用-webkit-transform而不是CSS width / height进行调整大小,插值也不会成功。
Nestor

2

我感觉我编写的模块将产生与photoshop相似的结果,因为它通过平均颜色数据而不使用算法来保留颜色数据。这有点慢,但对我来说是最好的,因为它保留了所有颜色数据。

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

它不会获取最近的邻居并丢弃其他像素,也不会采样一组并获取随机平均值。它采用每个源像素应输出到目标像素的确切比例。源中的平均像素颜色将是目标中的平均像素颜色,我认为其他公式不会。

https://github.com/danschumann/limby-resize的底部是一个使用方法的示例

2018年10月更新:这些天我的例子比其他任何东西都更具学术性。Webgl几乎是100%,因此最好调整它的大小以产生相似的结果,但速度更快。我相信PICA.js会这样做。–


1

我将@syockit的答案以及降压方法转换为对感兴趣的任何人可重复使用的Angular服务:https : //gist.github.com/fisch0920/37bac5e741eaec60e983

我提供了这两种解决方案,因为它们都有各自的优缺点。lanczos卷积方法的质量较高,但代价是速度较慢,而逐步缩小的方法则可以产生合理的抗锯齿结果,并且速度明显更快。

用法示例:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})

1

快速简单的Javascript图像大小调整器:

https://github.com/calvintwr/blitz-hermite-resize

const blitz = Blitz.create()

/* Promise */
blitz({
    source: DOM Image/DOM Canvas/jQuery/DataURL/File,
    width: 400,
    height: 600
}).then(output => {
    // handle output
})catch(error => {
    // handle error
})

/* Await */
let resized = await blizt({...})

/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
    // run your callback.
})

历史

这确实是经过多轮研究,阅读和尝试之后的结果。

调整大小算法使用@ViliusL的Hermite脚本(Hermite调整大小实际上是最快的,并提供了相当不错的输出)。扩展了您所需的功能。

分叉1个worker进行大小调整,以使其在调整大小时不会冻结浏览器,这与所有其他JS大小调整器不同。


0

感谢@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";
}
});

-1

我只是进行了并排比较的页面,除非最近有所变化,否则使用画布和简单的CSS不会出现更好的缩小(缩放)。我在FF6 Mac OSX 10.7中进行了测试。与原始版本相比仍然略微柔和。

但是,我确实偶然发现了一些确实起了很大作用的东西,那就是在支持画布的浏览器中使用图像过滤器。实际上,您可以像在Photoshop中一样使用模糊,锐化,饱和度,波纹,灰度等操作图像。

然后,我找到了一个很棒的jQuery插件,可以轻松应用这些过滤器:http : //codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

我只是在调整图像大小之后立即应用锐化滤镜,这将为您提供所需的效果。我什至不必使用canvas元素。


-1

寻找另一个很棒的简单解决方案?

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

该解决方案将使用浏览器的调整大小算法!:)


问题是关于对图像进行下采样,而不仅仅是调整大小。
耶苏斯·卡雷拉

[...]我正在尝试调整jpg的大小。我尝试在链接的网站和photoshop中调整相同jpg的大小,缩小尺寸后看起来还不错。[...]为什么您不能使用<img> Jesus Carrera?
ale500 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.