자바 스크립트 캔버스로 이미지 크기 조정 (부드럽게)


90

캔버스로 일부 이미지의 크기를 조정하려고하지만 부드럽게하는 방법에 대해 잘 모르겠습니다. 포토샵, 브라우저 등에서 그들이 사용하는 몇 가지 알고리즘 (예 : bicubic, bilinear)이 있지만 이것이 캔버스에 내장되어 있는지 여부는 모르겠습니다.

여기 내 바이올린이 있습니다. 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);

첫 번째는 일반적인 크기 조정 이미지 태그이고 두 번째는 캔버스입니다. 캔버스가 부드럽 지 않은지 확인하십시오. '부드러움'을 얻으려면 어떻게해야합니까?

답변:


136

더 나은 결과를 얻기 위해 아래 단계를 사용할 수 있습니다. 대부분의 브라우저는 이미지 크기를 조정할 때 이중 입방체아닌 선형 보간법사용하는 것 같습니다 .

( 업데이트 사양에 품질 속성이 추가되었으며 imageSmoothingQuality현재 Chrome에서만 사용할 수 있습니다.)

평활화 또는 가장 가까운 이웃을 선택하지 않는 한 브라우저는 앨리어싱을 피하기 위해이 기능을 저역 통과 필터로 축소 한 후 항상 이미지를 보간합니다.

Bi-linear는 2x2 픽셀을 사용하여 보간을 수행하는 반면, bi-cubic은 4x4를 ​​사용하므로 단계별로 수행하면 결과 이미지에서 볼 수 있듯이 이중 선형 보간을 사용하는 동안 bi-cubic 결과에 근접 할 수 있습니다.

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
@steve heh, 때때로 이러한 일이 발생합니다. :) 이미지의 경우 일반적으로 CSS BTW를 설정하여이를 재정의 할 수 있습니다.

Ken, 첫 번째 결과는 훌륭했지만 이미지를 변경하면 너무 흐릿한 것을 볼 수 있습니다. jsfiddle.net/kcHLG 이 경우 및 다른 경우 어떻게 할 수 있습니까?
steve

@steve 당신은 단계 수를 1 또는 없음으로 줄일 수 있습니다 (일부 이미지의 경우 잘 작동합니다). 이것 과 비슷한 이 답변 을 참조하십시오. 하지만 여기에 선명하게 회선을 추가하여 축소 된 후 결과 이미지를 더 선명하게 만들 수 있습니다.

1
여기 @ 스티브 수정 하나의 추가 단계를 사용하여 빌과 바이올린입니다 : jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic 코드는 OP 코드의 연속입니다. 바이올린을 열면 ctx가 정의되는 것을 볼 수 있습니다. 오해를 피하기 위해 여기에 삽입했습니다.

27

Trung Le Nguyen Nhat의 바이올린 이 전혀 정확하지 않기 때문에 (마지막 단계에서 원본 이미지 만 사용함)
성능 비교와 함께 내 자신의 일반 바이올린을 작성했습니다.

깡깡이

기본적으로 다음과 같습니다.

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

지금까지 본 가장 과소 평가 된 답변입니다.
Amsakanna

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
    })
})

죄송합니다. github 사용자 이름을 변경했습니다. gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2

3
나는 단어 각도를보고, 그 재미 느낌이 있어요
SuperUberDuper

8

이러한 코드 스 니펫 중 일부는 짧고 작동하지만 따르고 이해하는 것이 사소한 것은 아닙니다.

저는 스택 오버플로의 "복사-붙여 넣기"를 좋아하지 않기 때문에 개발자가 소프트웨어에 푸시하는 코드를 이해하기를 바랍니다. 아래 내용이 유용하기를 바랍니다.

데모 : JS 및 HTML Canvas 데모 피들러로 이미지 크기 조정.

이 크기 조정을 수행하는 3 가지 방법을 찾을 수 있으며, 코드가 작동하는 방식과 이유를 이해하는 데 도움이됩니다.

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

코드에서 사용할 수있는 데모 및 TypeScript 메서드의 전체 코드는 GitHub 프로젝트에서 찾을 수 있습니다.

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

4

모든 색상 데이터를 유지하면서 백분율을 낮출 수있는 라이브러리를 만들었습니다.

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

브라우저에 포함 할 수있는 파일입니다. 결과는 포토샵 또는 이미지 마술처럼 보이며, 모든 색상 데이터를 보존하고, 주변의 픽셀을 가져 와서 다른 픽셀을 떨어 뜨리는 대신 픽셀을 평균화합니다. 평균을 추측하기 위해 공식을 사용하지 않고 정확한 평균을 취합니다.


1
나는 아마
webgl

4

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 데모 업데이트

온라인 데모입니다


2
작동하지 않습니다. 캔버스 크기를 조정할 때마다 컨텍스트가 지워집니다. 캔버스 2 개가 필요합니다. 여기서는 최종 치수로 drawImage를 직접 호출하는 것과 동일합니다.
Kaiido 2016-06-07

2

아무도 제안하지 않는 이유를 이해하지 못합니다 createImageBitmap.

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

아름답게 작동합니다 (이미지 및 캔버스에 대한 ID를 설정했다고 가정).


널리 지원되지 않기 때문에 caniuse.com/#search=createImageBitmap
Matt

모든 사용자의 73 %가 createImageBitmap을 지원합니다. 사용 사례에 따라 충분할 수 있습니다. 지원 추가를 거부하는 것은 사파리뿐입니다. 가능한 해결책으로 언급 할 가치가 있다고 생각합니다.
cagdas_ucar

좋은 솔루션이지만 불행히도 파이어 폭스에서 작동하지 않습니다
vcarel

1

프런트 엔드에서 이미지를 자르고 크기를 조정하기 위해 작은 js 유틸리티를 작성했습니다. 다음은 GitHub 프로젝트에 대한 링크 입니다. 또한 최종 이미지에서 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;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.