使用javascript画布调整图像大小(顺利)

时间:2013-10-09 02:41:25

标签: javascript html5 image canvas html5-canvas

我正在尝试用画布调整一些图像的大小,但我对如何平滑它们毫无头绪。 在Photoshop,浏览器等..他们使用了一些算法(例如双三次,双线性),但我不知道它们是否内置于画布中。

这是我的小提琴:http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

第一个是普通的已调整大小的图像标记,第二个是画布。请注意画布如何不那么平滑。我怎样才能实现“顺畅”?

10 个答案:

答案 0 :(得分:106)

您可以使用向下步进来获得更好的效果。调整图像大小时,大多数浏览器似乎use linear interpolation rather than bi-cubic

更新已为规格imageSmoothingQuality添加了质量属性,目前仅在Chrome中提供。)

除非选择平滑或最近邻居,否则浏览器将在缩小图像后始终对图像进行插值,因为此功能可用作低通滤波器。

双线性使用2x2像素进行插值,而双立方使用4x4,因此通过逐步进行,您可以接近双三次结果,同时使用双线性插值,如结果图像所示。

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

根据您调整大小的程度,如果差异较小,您可以跳过第2步。

在演示中,您可以看到新结果现在与图像元素非常相似。

答案 1 :(得分:17)

我创建了一个可重复使用的Angular服务,可以为感兴趣的人处理高质量的图像/画布大小调整:https://gist.github.com/transitive-bullshit/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
    })
})

答案 2 :(得分:10)

由于Trung Le Nguyen Nhat's fiddle根本不正确 (它只是在最后一步使用原始图像)
我用性能比较编写了自己的一般小提琴:

FIDDLE

基本上是:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}

答案 3 :(得分:4)

我创建了一个库,允许您在保留所有颜色数据的同时降低任何百分比。

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

您可以在浏览器中添加该文件。结果将看起来像Photoshop或图像magick,保留所有颜色数据,平均像素,而不是采取附近的和丢弃其他人。它不使用公式来猜测平均值,而是采用精确的平均值。

答案 4 :(得分:3)

我不明白为什么没人建议createImageBitmap

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

工作精美(假设您为图片和画布设置了ID)。

答案 5 :(得分:2)

基于K3N答案,我通常会为任何想要的人重写代码

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

更新JSFIDDLE DEMO

这是我的ONLINE DEMO

答案 6 :(得分:1)

我写了一个小js-utility来裁剪和调整前端的图片大小。这是GitHub项目的link。你也可以从最终图像中获取blob来发送它。

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;

答案 7 :(得分:1)

尽管其中一些代码段简短且有效,但遵循和理解它们并非易事。

由于我不喜欢堆栈溢出的“复制粘贴”功能,因此我希望开发人员了解他们将其推送到软件中的代码,希望您会发现以下有用的内容。

DEMO :使用JS和HTML Canvas演示小提琴手调整图像大小。

您可能会发现3种不同的方法来进行此大小调整,这将帮助您了解代码的工作方式以及原因。

https://jsfiddle.net/1b68eLdr/93089/

可以在GitHub项目中找到完整的演示代码以及您可能希望在代码中使用的TypeScript方法。

https://github.com/eyalc4/ts-image-resizer

这是最终代码:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the dize by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}

答案 8 :(得分:0)

这是我的代码,我希望它对 SO 社区中的某些人有用:

您可以将目标图像尺寸作为参数包含在脚本调用中。这将是图像宽度或高度的结果值,以较大者为准。调整较小的尺寸,保持图像纵横比不变。您还可以在脚本中对默认目标大小进行硬编码。

您可以轻松更改脚本以满足您的特定需求,例如您想要的图像类型(默认为“图像/png”)用于输出,并决定您要按百分比调整图像大小以获得更精细的大小结果(参见代码中的 const percentStep)。

   const ResizeImage = ( _ => {

const MAX_LENGTH = 260;     // default target size of largest dimension, either witdth or height
const percentStep = .3;     // resizing steps until reaching target size in percents (30% default)
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d");
const image = new Image();

const doResize = (callback, maxLength) => {

    // abort with error if image has a dimension equal to zero
    if(image.width == 0 || image.height == 0) {
        return {blob: null, error: "either image width or height was zero "};
    }

    // use caller dimension or default length if none provided
    const length = maxLength == null  ? MAX_LENGTH : maxLength;

    canvas.width = image.width;
    canvas.height = image.height;
    canvasContext.drawImage(image, 0, 0, image.width, image.height);
    // if image size already within target size, just copy and return blob
    if(image.width <= length && image.height <= length) {
        canvas.toBlob( blob => {
            callback({ blob: blob, error: null });
        }, "image/png", 1);
        return;
    }

    var startDim = Math.max(image.width, image.height);
    var startSmallerDim = Math.min(image.width, image.height);

    // gap to decrease in size until we reach the target size,
    // be it by decreasing the image width or height,
    // whichever is largest
    const gap = startDim - length;
    // step length of each resizing iteration
    const step = parseInt(percentStep*gap);
    //  no. of iterations
    var nSteps = 0;
    if(step == 0) {
        step = 1;
    } else {
        nSteps = parseInt(gap/step);
    }
    // length of last additional resizing step, if needed
    const lastStep = gap % step;
    // aspect ratio = value by which we'll  multiply the smaller dimension
    // in order to keep the aspect ratio unchanged in each iteration
    const ratio = startSmallerDim/startDim;

    var newDim;          // calculated new length for the bigger dimension of the image, be it image width or height
    var smallerDim;     // length along the smaller dimension of the image, width or height
    for(var i = 0; i < nSteps; i++) {
        // decrease longest dimension one step in pixels
        newDim = startDim - step;
        // decrease shortest dimension proportionally, so as to keep aspect ratio
        smallerDim = parseInt(ratio*newDim);
        // assign calculated vars to their corresponding canvas dimension, width or height
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [newDim, smallerDim];
        } else {
            [canvas.width, canvas.height] = [smallerDim, newDim];
        }
        // draw image one step smaller
        canvasContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
        // cycle var startDim for new loop
        startDim = newDim;
    }

    // do last missing resizing step to finally reach target image size
    if(lastStep > 0) {
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [startDim - lastStep, parseInt(ratio*(startDim - lastStep))];
        } else {
            [canvas.width, canvas.height] = [parseInt(ratio*(startDim -lastStep)), startDim - lastStep];
        }
        canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
    }

    // send blob to caller
    canvas.toBlob( blob => {
        callback({blob: blob, error: null});
    }, "image/png", 1);

};

const resize = async (imgSrc, callback, maxLength) => {
    image.src = imgSrc;
    image.onload = _ => {
       doResize(callback, maxLength);
    };
};

return { resize: resize }

})();

用法:

ResizeImage.resize("./path/to/image/or/blob/bytes/to/resize", imageObject => {
    if(imageObject.error != null) {
      // handle errors here
      console.log(imageObject.error);
      return;
    }
    // do whatever you want with the blob, like assinging it to
    // an img element, or uploading it to a database
    // ...
    document.querySelector("#my-image").src = imageObject.blob;
    // ...
}, 300);

答案 9 :(得分:0)

我通过对画布使用缩放解决了这个问题,我的情况下图像质量变得非常好。

所以首先我缩放画布内的内容:

ctx.scale(2, 2)

然后用css扩展canvas标签:

#myCanvas { transform: scale(0.5); }