[javascript] HTML5 캔버스에서 이미지 크기 조정

자바 스크립트와 캔버스 요소를 사용하여 클라이언트 측에서 축소판 이미지를 만들려고하는데 이미지를 축소하면 끔찍한 것처럼 보입니다. Bicubic 대신 ‘Nearest Neighbor’로 리샘플링을 설정 한 상태에서 포토샵에서 축소 된 것처럼 보입니다. 이 사이트 가 캔버스를 사용하여 잘 수행 할 수 있기 때문에 이것이 올바르게 보이도록하는 것이 가능하다는 것을 알고 있습니다. “[Source]”링크에 표시된 것과 동일한 코드를 사용해 보았지만 여전히 끔찍합니다. 내가 놓친 것이 있습니까, 설정해야 할 설정이 있습니까?

편집하다:

jpg의 크기를 조정하려고합니다. 링크 된 사이트와 포토샵에서 동일한 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 :

내가 잘못 생각한 것 같습니다. 링크 된 웹 사이트가 이미지 크기를 줄이는 작업을 더 잘 수행하지 못했습니다. 나는 제안 된 다른 방법을 시도했지만 그중 어느 것도 더 좋아 보이지 않습니다. 이것은 다른 방법으로 얻은 것입니다.

포토샵 :

대체 텍스트

캔버스:

대체 텍스트

이미지 렌더링이 포함 된 이미지 : optimizeQuality가 너비 / 높이로 설정 및 조정됩니다.

대체 텍스트

이미지 렌더링이 포함 된 이미지 : -moz-transform을 사용하여 품질 설정 및 스케일 조정 :

대체 텍스트

pixastic에서 캔버스 크기 조정 :

대체 텍스트

나는 이것이 파이어 폭스가 예상대로 바이 큐빅 샘플링을 사용하지 않는다는 것을 의미한다고 생각합니다. 실제로 추가 할 때까지 기다려야합니다.

EDIT3 :

원본 이미지



답변

모든 브라우저 (실제로 Chrome 5에서 나에게 아주 좋은 것을 주 었음)가 충분한 리샘플링 품질을 제공하지 못하면 어떻게해야합니까? 그런 다음 직접 구현하십시오! 오, 우리는 웹 3.0, HTML5 호환 브라우저, 슈퍼 최적화 된 JIT 자바 스크립트 컴파일러, 멀티 코어 (†) 머신, 수많은 메모리를 가진 새로운 시대에 접어 들고 있습니다. 이봐, 자바 스크립트에 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 Generator를 기반으로 하기 때문에 GPL2에도 적용됩니까?

실제로 자바 스크립트 제한으로 인해 멀티 코어는 지원되지 않습니다.


답변

JavaScript로 Hermite 필터를 사용하여 빠른 이미지 크기 조정 / 재 샘플링 알고리즘 . 투명성을 지원하고 좋은 품질을 제공합니다. 시사:

여기에 이미지 설명을 입력하십시오

업데이트 : GitHub에 버전 2.0이 추가되었습니다 (빠른 웹 워커 + 전송 가능한 개체). 마침내 나는 그것을 작동시켰다!

힘내 : 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);
}


답변

선택 가능한 알고리즘을 가진 고도로 최적화 된 크기 조정기 인 pica를 사용해보십시오 . 데모를 참조하십시오 .

예를 들어, 첫 번째 게시물의 원본 이미지는 Lanczos 필터 및 3px 창에서 120ms 또는 상자 필터 및 0.5px 창에서 60ms로 크기가 조정됩니다. 대형 17MB 이미지의 경우 5000x3000px 크기 조정은 데스크톱에서 1 초, 모바일에서 3 초가 걸립니다.

이 스레드에서는 모든 크기 조정 원칙을 잘 설명했으며 pica는 로켓 과학을 추가하지 않습니다. 그러나 최신 JIT에 최적화되어 있으며 npm 또는 bower를 통해 즉시 사용할 수 있습니다. 또한 인터페이스 정지를 피하기 위해 웹 워커를 사용합니다.

언 스케일 마스크 지원도 곧 추가 할 계획입니다. 축소 후 매우 유용하기 때문입니다.


답변

나는 이것이 오래된 스레드라는 것을 알고 있지만 몇 달 후 처음 으로이 문제를 겪은 나 자신과 같은 일부 사람들에게는 유용 할 수 있습니다.

다음은 이미지를 다시로드 할 때마다 이미지 크기를 조정하는 코드입니다. 나는 이것이 최적이 아니라는 것을 알고 있지만 개념 증명으로 제공합니다.

또한 간단한 선택기에 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 함수는 문서가로드 될 때 한 번 호출 된 후 창에서 크기 조정 이벤트를받을 때마다 호출됩니다.

Mac에서 Chrome 6 및 Firefox 3.6에서 테스트했습니다. 이 “기술”은 여름에 아이스크림 인 것처럼 프로세서를 먹지만 속임수를 씁니다.


답변

여기에 유용 할 수있는 HTML 캔버스 픽셀 배열에서 이미지 보간을 수행하는 알고리즘을 작성했습니다.

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

이것들은 복사 / 붙여 넣기를 할 수 있으며 웹 워커 내부에서 이미지의 크기를 조정하는 데 사용할 수 있습니다 (또는 보간이 필요한 다른 작업 – 현재 이미지를 제거하는 데 사용하고 있습니다).

위의 lanczos 항목을 추가하지 않았으므로 원하는 경우 비교로 추가하십시오.


답변

이것은 @Telanor의 코드에서 채택 된 자바 스크립트 함수입니다. 이미지 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();

}


답변

이 링크 를 확인하여 해당 링크 가 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 = 거짓;