Resmi JavaScript Canvas ile yeniden boyutlandırın (sorunsuz)


91

Bazı resimleri tuval ile yeniden boyutlandırmaya çalışıyorum ama onları nasıl düzelteceğim konusunda bilgim yok. Photoshop'ta, tarayıcılarda vb .. kullandıkları birkaç algoritma var (örn. Çift kübik, çift doğrusal) ama bunların tuvalde yapılıp yapılmadığını bilmiyorum.

İşte benim kemanım: 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);

Birincisi normal boyutlandırılmış bir resim etiketi ve ikincisi kanvas. Tuvalin ne kadar pürüzsüz olmadığına dikkat edin. 'Pürüzsüzlüğü' nasıl elde edebilirim?

Yanıtlar:


136

Daha iyi sonuçlar elde etmek için aşağı inmeyi kullanabilirsiniz. Çoğu tarayıcı, görüntüleri yeniden boyutlandırırken çift ​​kübik yerine doğrusal enterpolasyon kullanıyor gibi görünüyor .

( Güncelleme Spesifikasyonlara imageSmoothingQualityşu anda yalnızca Chrome'da kullanılabilen bir kalite özelliği eklendi .)

Düzgünleştirme veya en yakın komşu seçilmedikçe, tarayıcı, örtüşmeyi önlemek için düşük geçişli bir filtre olarak bu işlev olarak, küçültüldükten sonra her zaman görüntüyü enterpolasyona tabi tutacaktır.

Bi-linear, interpolasyon yapmak için 2x2 piksel kullanırken, bi-cubic 4x4 kullanır, böylece bunu adımlarla yaparak, sonuçta ortaya çıkan görüntülerde görüldüğü gibi bi-line interpolasyonu kullanırken bi-cubic sonuca yaklaşabilirsiniz.

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>

Yeniden boyutlandırmanızın ne kadar büyük olduğuna bağlı olarak, fark daha azsa 2. adımı atlayabilirsiniz.

Demoda, yeni sonucun artık resim öğesine çok benzediğini görebilirsiniz.


1
@steve heh, bazen böyle şeyler olur :) Resimler için bunu genellikle bir css BTW ayarlayarak geçersiz kılabilirsiniz.

Ken, ilk sonuç harika çalıştı ama resimleri değiştirdiğimde çok bulanık olduğunu görüyorsun jsfiddle.net/kcHLG Bu durumda ve diğerleri için ne yapılabilir?
steve

@steve adım sayısını yalnızca 1'e veya sıfıra düşürebilirsiniz (bazı görüntülerde bu iyi çalışır) Buna benzer olan bu yanıta da bakın , ancak burada ona keskin bir evrişim ekledim, böylece elde edilen görüntüyü küçüldükten sonra daha keskin hale getirebilirsiniz.

1
@steve, Bill ile birlikte yalnızca bir ekstra adım kullanan değiştirilmiş bir keman: jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic kodu, OPs kodunun bir devamıdır. Keman açılırsa, ctx'in tanımlandığını görürsünüz. Yanlış anlaşılmaları önlemek için buraya yazdım.

29

Yana Trung Le Nguyen Nhat en keman hiç doğru değil (sadece son adımda orijinal görüntüsünü kullanır)
Ben performans karşılaştırma ile kendi genel keman yazdı:

VAKTİNİ BOŞA HARCAMAK

Temelde şu:

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

Şimdiye kadar görülen en küçümsenen cevap.
Amsakanna

17

İlgilenen herkes için resimlerin / tuvallerin yüksek kalitede yeniden boyutlandırılması için yeniden kullanılabilir bir Angular hizmeti oluşturdum: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

Hizmet iki çözüm içerir çünkü her ikisinin de kendi artıları / eksileri vardır. Lanczos evrişim yaklaşımı, daha yavaş olma pahasına daha yüksek kalitede iken, adım adım ölçek küçültme yaklaşımı makul ölçüde antialiased sonuçlar üretir ve önemli ölçüde daha hızlıdır.

Örnek kullanım:

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

Bunun için üzgünüm - github kullanıcı adımı değiştirdim. Bağlantı gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2

3
Köşeli kelimesini gördüm, o tuhaf hisse
kapıldım

8

Bu kod parçacıklarından bazıları kısa ve çalışıyor olsa da, takip etmek ve anlamak önemsiz değil.

Stack-overflow'dan "kopyala-yapıştır" ın hayranı olmadığım için, geliştiricilerin yazılımlarına aktardıkları kodu anlamalarını istiyorum, umarım aşağıdakileri faydalı bulursunuz.

DEMO : JS ve HTML Canvas Demo fiddler ile görüntüleri yeniden boyutlandırma.

Bu yeniden boyutlandırmayı yapmak için kodun nasıl çalıştığını ve nedenini anlamanıza yardımcı olacak 3 farklı yöntem bulabilirsiniz.

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

Kodunuzda kullanmak isteyebileceğiniz hem demo hem de TypeScript yönteminin tam kodu GitHub projesinde bulunabilir.

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

Bu son kod:

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

Tüm renk verilerini korurken herhangi bir yüzdeyi azaltmanıza izin veren bir kitaplık oluşturdum.

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

Tarayıcıya ekleyebileceğiniz bu dosya. Sonuçlar, yakınlardaki verileri alıp diğerlerini düşürmek yerine tüm renk verilerini koruyarak, piksellerin ortalamasını alarak photoshop veya görsel büyülü gibi görünecek. Ortalamaları tahmin etmek için bir formül kullanmaz, tam ortalamayı alır.


1
Şimdi yeniden boyutlandırmak için muhtemelen webgl kullanırdım
Funkodebat

4

K3N cevabına göre, genel olarak herkes için kodu yeniden yazıyorum

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'YU GÜNCELLE

İşte ONLINE DEMOMUZ


2
Bu işe yaramaz: tuvali her yeniden boyutlandırdığınızda bağlamını temizler. 2 tuvale ihtiyacınız var. Burada, son boyutlarla doğrudan drawImage'ı çağırmakla aynı şey.
Kaiido

4

Neden kimsenin önermediğini anlamıyorum createImageBitmap.

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

güzel çalışır (resim ve tuval için kimlik belirlediğiniz varsayılarak).


1
Yaygın olarak desteklenmediği için caniuse.com/#search=createImageBitmap
Matt

2
createImageBitmap, tüm kullanıcıların% 73'ü için desteklenmektedir. Kullanım durumunuza bağlı olarak yeterince iyi olabilir. Destek eklemeyi reddeden sadece Safari. Olası bir çözüm olarak bahsetmeye değer olduğunu düşünüyorum.
cagdas_ucar

Güzel çözüm, ancak maalesef firefox'ta çalışmıyor
vcarel

1

Ön uçta görüntüyü kırpmak ve yeniden boyutlandırmak için küçük js-yardımcı programı yazdım. İşte GitHub projesindeki bağlantı . Ayrıca göndermek için son görüntüden blob alabilirsiniz.

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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.