การปรับขนาดรูปภาพในผ้าใบ HTML5


314

ฉันพยายามสร้างภาพขนาดย่อทางฝั่งไคลเอ็นต์โดยใช้จาวาสคริปต์และองค์ประกอบผ้าใบ แต่เมื่อฉันลดขนาดรูปภาพลงมันดูแย่มาก ดูเหมือนว่ามันจะลดขนาดใน Photoshop ด้วยชุด resampling เป็น 'เพื่อนบ้านที่ใกล้ที่สุด' แทน Bicubic ฉันรู้ว่ามันเป็นไปได้ที่จะทำให้สิ่งนี้ดูถูกต้องเพราะไซต์นี้สามารถทำได้โดยใช้ Canvas เช่นกัน ฉันพยายามใช้รหัสเดียวกันกับที่พวกเขาทำตามที่แสดงในลิงก์ "[แหล่งที่มา]" แต่มันก็ดูแย่มาก มีบางอย่างที่ฉันขาดหายไปการตั้งค่าบางอย่างที่จำเป็นต้องตั้งค่าหรือบางอย่าง

แก้ไข:

ฉันพยายามปรับขนาด jpg ฉันลองปรับขนาด jpg เดียวกันบนเว็บไซต์ที่เชื่อมโยงและใน photoshop และมันก็ดูดีเมื่อลดขนาด

นี่คือรหัสที่เกี่ยวข้อง:

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:

ดูเหมือนว่าฉันเข้าใจผิดเว็บไซต์ที่เชื่อมโยงไม่ได้ทำงานเพื่อลดขนาดรูปภาพให้ดีขึ้น ฉันลองวิธีอื่น ๆ ที่แนะนำและไม่มีวิธีใดที่ดูดีกว่านี้ นี่คือวิธีการที่แตกต่างกันทำให้:

Photoshop:

ข้อความแสดงแทน

ผ้าใบ:

ข้อความแสดงแทน

ภาพที่มีการแสดงภาพ: ปรับคุณภาพชุดภาพและปรับขนาดด้วยความกว้าง / ความสูง:

ข้อความแสดงแทน

รูปภาพที่มีการเรนเดอร์ภาพ: ปรับคุณภาพชุดและปรับสัดส่วนด้วย -moz-transform:

ข้อความแสดงแทน

Canvas ปรับขนาดบน pixastic:

ข้อความแสดงแทน

ฉันเดาว่านี่หมายความว่า Firefox ไม่ได้ใช้การสุ่มตัวอย่างแบบ bicubic อย่างที่ควรจะเป็น ฉันจะต้องรอจนกว่าพวกเขาจะเพิ่มมัน

edit3:

รูปภาพต้นฉบับ


คุณสามารถโพสต์รหัสที่คุณใช้เพื่อปรับขนาดภาพได้หรือไม่?
Xavi

คุณพยายามที่จะปรับขนาดภาพ GIF หรือภาพที่คล้ายกันด้วยจานสี จำกัด ? แม้แต่ใน photoshop รูปภาพเหล่านี้ก็ไม่ได้ลดขนาดลงจนกว่าคุณจะแปลงเป็น RGB
leepowers

คุณสามารถโพสต์สำเนาของภาพต้นฉบับได้หรือไม่?
Xavi

การปรับขนาดรูปภาพโดยใช้ javascript เป็นเพียงเล็กน้อย kludge - ไม่เพียง แต่คุณใช้พลังการประมวลผลของไคลเอนต์เพื่อปรับขนาดภาพ ทำไมไม่เพียงแค่บันทึกเวอร์ชันที่ลดขนาดลงจาก photoshop และให้บริการแทน / ควบคู่กับภาพต้นฉบับ?
กำหนด

29
เนื่องจากฉันสร้างเครื่องมืออัปโหลดรูปภาพที่มีความสามารถในการปรับขนาดและครอบตัดรูปภาพก่อนที่จะอัปโหลด
Telanor

คำตอบ:


393

ดังนั้นคุณจะทำอย่างไรถ้าเบราว์เซอร์ทั้งหมด (ที่จริงแล้ว Chrome 5 ให้ฉันดีทีเดียว) จะไม่ให้คุณภาพที่ดีพอสำหรับการสุ่มตัวอย่างใหม่ คุณใช้มันด้วยตัวคุณเองแล้ว! โอ้มาแล้วเรากำลังเข้าสู่ยุคใหม่ของ Web 3.0, HTML5 เบราว์เซอร์ที่เข้ากันได้ดีที่สุดคอมไพเลอร์ JIT จาวาสคริปต์ JIT สุดยอดเครื่องมัลติคอร์ (†) พร้อมหน่วยความจำมากมายคุณกลัวอะไร? สวัสดีมีคำว่าจาวาสคริปต์ในจาวาสคริปต์ดังนั้นควรรับประกันประสิทธิภาพใช่มั้ย ดูเถิดรหัสสร้างภาพย่อ:

// 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 หรือไม่ฉันไม่ได้อ่าน)

อันที่จริงแล้วมีข้อ จำกัด ของจาวาสคริปต์จึงไม่รองรับมัลติคอร์


2
ที่จริงแล้วฉันได้ลองใช้งานด้วยตัวเองโดยทำตามที่คุณต้องการคัดลอกรหัสจากโปรแกรมแก้ไขรูปภาพโอเพนซอร์ซ เนื่องจากฉันไม่สามารถหาเอกสารที่เป็นของแข็งใด ๆ เกี่ยวกับอัลกอริทึมฉันจึงยากที่จะปรับให้เหมาะสม ในที่สุดเหมืองของฉันค่อนข้างช้า (ใช้เวลาสองสามวินาทีเพื่อปรับขนาดภาพ) เมื่อฉันได้รับโอกาสฉันจะลองของคุณและดูว่ามันเร็วกว่านี้ไหม และฉันคิดว่าเว็บเวิร์คทำให้ javascript แบบ multi-core เป็นไปได้ตอนนี้ ฉันจะลองใช้มันเพื่อเร่งความเร็ว แต่ฉันมีปัญหาในการหาวิธีทำให้เป็นอัลกอริธึมแบบมัลติเธรด
Telanor

3
ขออภัยลืม! ฉันแก้ไขคำตอบแล้ว มันจะไม่เร็วไปเลย bicubic น่าจะเร็วกว่านี้ ไม่ต้องพูดถึงอัลกอริทึมที่ฉันใช้ไม่ใช่การปรับขนาดแบบสองทางปกติ (ซึ่งคือแบบทีละบรรทัดแนวนอนและแนวตั้ง) ดังนั้นจึงเป็นการช้าลง
syockit

5
คุณยอดเยี่ยมและสมควรได้รับความยอดเยี่ยมมากมาย
Rocklan

5
สิ่งนี้ให้ผลลัพธ์ที่ยอดเยี่ยม แต่ใช้เวลา 7.4 วินาทีสำหรับภาพ 1.8 MP ใน Chrome เวอร์ชันล่าสุด ...
mpen

2
วิธีการเช่นนี้จัดการกับคะแนนสูงเช่นนี้? วิธีแก้ปัญหาที่แสดงนั้นล้มเหลวอย่างสิ้นเชิงในการคำนวณสเกลลอการิทึมที่ใช้ในการจัดเก็บข้อมูลสี RGB ของ 127,127,127 เป็นหนึ่งในสี่ของความสว่าง 255, 255, 255 ไม่ใช่ครึ่งหนึ่ง การสุ่มตัวอย่างลงในการแก้ปัญหาส่งผลให้ภาพมืด ความอัปยศนี้ถูกปิดเนื่องจากมีวิธีการที่ง่ายและรวดเร็วในการลดขนาดที่ให้ผลลัพธ์ที่ดียิ่งกว่า Photoshop (OP ต้องมีการตั้งค่าที่ผิด) ตัวอย่างที่มอบให้
Blindman67

37

อัลกอริธึมการปรับขนาดภาพที่รวดเร็ว / resample โดยใช้ตัวกรองHermiteพร้อม JavaScript สนับสนุนความโปร่งใสให้คุณภาพดี ตัวอย่าง:

ป้อนคำอธิบายรูปภาพที่นี่

ปรับปรุง : เพิ่มรุ่น 2.0 ใน GitHub (เร็วกว่าเว็บเวิร์ค + ออบเจ็กต์ที่ถ่ายโอนได้) ในที่สุดฉันก็ทำงานได้!

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 repo
syockit

1
คุณจะแชร์เวอร์ชันของเว็บเวิร์คเกอร์ด้วยหรือไม่ อาจเป็นเพราะการตั้งค่าโสหุ้ยมันช้ากว่าสำหรับรูปภาพขนาดเล็ก แต่อาจมีประโยชน์สำหรับรูปภาพต้นฉบับที่มีขนาดใหญ่กว่า
syockit

เพิ่มการสาธิตลิงก์คอมไพล์และเวอร์ชั่นมัลติคอร์ Btw ฉันไม่ได้ใช้เวลามากเกินไปในการเพิ่มประสิทธิภาพรุ่นมัลติคอร์ ... รุ่นเดียวที่ฉันเชื่อว่าเหมาะสมที่สุด
ViliusL

ความแตกต่างอย่างมากและประสิทธิภาพที่ดี ขอบคุณมาก! ก่อนและ หลัง
KevBurnsJr

2
@ViliusL อ่าฉันจำได้ว่าทำไมคนทำงานเว็บถึงทำงานได้ไม่ดี พวกเขาไม่ได้แชร์หน่วยความจำมาก่อนและยังไม่มีในตอนนี้! บางทีสักวันหนึ่งเมื่อพวกเขาจัดการเพื่อเรียงลำดับรหัสของคุณจะถูกนำไปใช้ (หรืออาจจะเป็นคนที่ใช้ PNaCl แทน)
syockit

26

ลองใช้pica - ซึ่งเป็น resizer ที่ได้รับการปรับปรุงให้ดีที่สุดพร้อม algorythms ที่เลือกได้ ดูตัวอย่างการสาธิต

ตัวอย่างเช่นภาพต้นฉบับจากโพสต์แรกจะถูกปรับขนาดใน 120ms ด้วยตัวกรอง Lanczos และหน้าต่าง 3px หรือ 60ms พร้อมตัวกรองกล่องและหน้าต่าง 0.5px สำหรับภาพขนาดใหญ่ 17mb ขนาด 5000x3000px จะใช้เวลาประมาณ 1 วินาทีบนเดสก์ท็อปและบนมือถือ 3 วินาที

หลักการปรับขนาดทั้งหมดถูกอธิบายไว้อย่างดีในหัวข้อนี้และ pica ไม่ได้เพิ่มวิทยาศาสตร์จรวด แต่มันได้รับการปรับปรุงให้ดีที่สุดสำหรับ JIT-s ที่ทันสมัยและพร้อมที่จะใช้งานนอกกรอบ (ผ่าน npm หรือ bower) นอกจากนี้ยังใช้เว็บเวิร์คเมื่อพร้อมใช้งานเพื่อหลีกเลี่ยงการค้างอินเตอร์เฟส

ฉันวางแผนที่จะเพิ่มการรองรับรูปแบบที่ไม่คมชัดในไม่ช้าเพราะมันมีประโยชน์มากหลังจากที่ลดระดับลง


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 ของฉันถูกเรียกหนึ่งครั้งเมื่อโหลดเอกสารและหลังจากนั้นจะถูกเรียกทุกครั้งที่หน้าต่างได้รับเหตุการณ์การปรับขนาด

ฉันทดสอบใน Chrome 6 และ Firefox 3.6 ทั้งบน Mac "เทคนิค" นี้กินโปรเซสเซอร์ราวกับว่าเป็นไอศครีมในช่วงฤดูร้อน แต่มันก็เป็นกลอุบาย


9

ฉันได้วางอัลกอริทึมเพื่อทำการแก้ไขภาพในอาร์เรย์พิกเซลผ้าใบ html ที่อาจมีประโยชน์ที่นี่:

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

สิ่งเหล่านี้สามารถคัดลอก / วางและสามารถใช้ภายในเว็บเวิร์คเพื่อปรับขนาดภาพ (หรือการดำเนินการอื่น ๆ ที่ต้องมีการแก้ไข - ฉันใช้พวกเขาเพื่อป้องกันภาพในขณะนี้)

ฉันยังไม่ได้เพิ่มสิ่งที่ lanczos ข้างต้นดังนั้นอย่าลังเลที่จะเพิ่มสิ่งนั้นเพื่อเป็นการเปรียบเทียบหากคุณต้องการ


6

นี่คือฟังก์ชั่นจาวาสคริปต์ที่ดัดแปลงมาจากรหัสของ @ 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();

}

วิธีการของคุณเร็วมาก แต่ให้ภาพที่ชัดเจนเช่นเดียวกับคุณ: stackoverflow.com/questions/18922880/…
confile

6

ฉันขอแนะนำให้คุณตรวจสอบลิงก์นี้และตรวจสอบให้แน่ใจว่าได้ตั้งค่าเป็นจริง

การควบคุมพฤติกรรมการปรับสเกลของภาพ

แนะนำใน Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 แนะนำคุณสมบัติ mozImageSmoothingEnabled ให้กับองค์ประกอบผ้าใบ หากค่าบูลีนนี้เป็นเท็จรูปภาพจะไม่ถูกปรับให้เรียบเมื่อปรับขนาด คุณสมบัตินี้เป็นจริงโดยค่าเริ่มต้น ดูพิมพ์ธรรมดา?

  1. cx.mozImageSmoothingEnabled = false;

5

หากคุณเพียงแค่พยายามปรับขนาดภาพฉันขอแนะนำให้ตั้งค่าwidthและheightภาพด้วย CSS นี่คือตัวอย่างรวดเร็ว:

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

โปรดทราบว่าheightและwidthยังสามารถตั้งค่าโดยใช้ 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 โดยใช้อัลกอริทึม bicubic เพื่อปรับขนาดรูปภาพตามค่าเริ่มต้นดังนั้นรูปภาพที่ปรับขนาดแล้วของคุณควรดูดีใน Firefox และ Chrome

หากตั้งค่า css widthและheightไม่ทำงานคุณอาจต้องการเล่นกับ 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 และบันทึกเป็น

ข้อความแสดงแทน

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

ฉันพยายามทำสิ่งที่คุณทำและมันก็ไม่ออกมาดีเหมือนของคุณ การเปลี่ยนแปลงเดียวที่คุณทำคือใช้ลายเซ็นวิธีการปรับขนาดแทนการแบ่งส่วนใช่ไหม? ด้วยเหตุผลบางอย่างมันไม่ทำงานสำหรับฉัน
Telanor

3

ปัญหาของการแก้ปัญหานี้คือพวกเขาเข้าถึงข้อมูลพิกเซลโดยตรงและวนซ้ำผ่านมันเพื่อทำการสุ่มตัวอย่าง ขึ้นอยู่กับขนาดของรูปภาพสิ่งนี้อาจใช้ทรัพยากรอย่างมากและจะเป็นการดีกว่าถ้าใช้อัลกอริธึมภายในของเบราว์เซอร์

drawImage ()ฟังก์ชั่นใช้เส้นการแก้ไขที่ใกล้ที่สุดเพื่อนบ้านวิธี resampling ใช้งานได้ดีเมื่อคุณไม่ลดขนาดลงมากกว่าครึ่งหนึ่งของขนาดดั้งเดิมทำงานได้ดีเมื่อคุณไม่ได้ปรับขนาดลดลงกว่าครึ่งหนึ่งของขนาดเดิม

หากคุณวนลูปเพื่อปรับขนาดสูงสุดทีละครึ่งเท่านั้นผลลัพธ์จะค่อนข้างดีและเร็วกว่าการเข้าถึงข้อมูลพิกเซล

ฟังก์ชั่นนี้ลดขนาดลงเหลือเพียงครึ่งละครั้งจนได้ขนาดที่ต้องการ

  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 เพื่อปรับขนาดผืนผ้าใบจริง ๆ แล้วมันจะยืด (เช่น: ปรับขนาด) เนื้อหาของผืนผ้าใบเพื่อให้พอดีกับผืนผ้าใบแบบเต็ม (แทนที่จะเพิ่มหรือลดพื้นที่ผ้าใบเท่านั้น

มันน่าจะคุ้มค่าสำหรับการลองวาดภาพเป็นตัวควบคุมผืนผ้าใบด้วยแอตทริบิวต์ความสูงและความกว้างที่กำหนดเป็นขนาดของรูปภาพแล้วใช้ CSS เพื่อปรับขนาดผืนผ้าใบให้เป็นขนาดที่คุณต้องการ บางทีนี่อาจจะใช้อัลกอริทึมการปรับขนาดที่แตกต่างกัน

ควรสังเกตว่า Canvas มีเอฟเฟกต์ต่างกันในเบราว์เซอร์ที่ต่างกัน (และแม้แต่เบราว์เซอร์ที่แตกต่างกันเวอร์ชันต่าง ๆ ) อัลกอริทึมและเทคนิคที่ใช้ในเบราว์เซอร์มีแนวโน้มที่จะเปลี่ยนแปลงตลอดเวลา (โดยเฉพาะกับ Firefox 4 และ Chrome 6 ที่กำลังจะออกมาในไม่ช้าซึ่งจะเน้นหนักไปที่ประสิทธิภาพการเรนเดอร์ผ้าใบ)

นอกจากนี้คุณอาจต้องการให้ SVG ถ่ายภาพด้วยเช่นกันเนื่องจากอาจใช้อัลกอริทึมที่แตกต่างเช่นกัน

ขอให้โชคดี!


1
การตั้งค่าความกว้างหรือความสูงของผืนผ้าใบผ่านทางแอตทริบิวต์ HTML ทำให้ผ้าใบถูกล้างดังนั้นจึงไม่มีการปรับขนาดใด ๆ ด้วยวิธีการนั้น นอกจากนี้ SVG ยังมีไว้สำหรับจัดการกับภาพทางคณิตศาสตร์ ฉันต้องสามารถวาด PNG และสิ่งอื่น ๆ ได้ดังนั้นมันจะไม่ช่วยฉัน
Telanor

การตั้งค่าความสูงและความกว้างของผืนผ้าใบและการปรับขนาดด้วย CSS ไม่ได้ช่วยฉันได้พบ (ใน Chrome) แม้แต่การปรับขนาดโดยใช้ -webkit-transform มากกว่าความกว้าง / ความสูง CSS ก็ไม่ทำให้การแก้ไขมีค่า
Nestor

2

ฉันมีความรู้สึกว่าโมดูลที่ฉันเขียนจะให้ผลลัพธ์คล้ายกับ photoshop เพราะมันรักษาข้อมูลสีโดยเฉลี่ยโดยไม่ใช้อัลกอริทึม มันค่อนข้างช้า แต่สำหรับฉันมันดีที่สุดเพราะมันเก็บข้อมูลสีทั้งหมด

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

ไม่ใช้เพื่อนบ้านที่ใกล้ที่สุดและวางพิกเซลอื่น ๆ หรือสุ่มกลุ่มและใช้ค่าเฉลี่ยแบบสุ่ม ใช้สัดส่วนที่แน่นอนแต่ละพิกเซลต้นทางควรส่งออกเป็นพิกเซลปลายทาง สีพิกเซลเฉลี่ยในแหล่งที่มาจะเป็นสีพิกเซลเฉลี่ยในปลายทางซึ่งสูตรอื่น ๆ เหล่านี้ฉันคิดว่าพวกเขาจะไม่เป็น

ตัวอย่างของวิธีใช้อยู่ที่ด้านล่างของ https://github.com/danschumann/limby-resize

ปรับปรุงตุลาคม 2018 : วันนี้ตัวอย่างของฉันเป็นวิชาการมากกว่าสิ่งอื่นใด 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.
})

ประวัติศาสตร์

นี่เป็นเรื่องจริงหลังจากการค้นคว้าหลายรอบอ่านและพยายาม

อัลกอริธึมตัวปรับขนาดใช้สคริปต์ Hermite ของ @ ViliusL (ตัวเร่งปฏิกิริยา Hermite เป็นวิธีที่เร็วที่สุดและให้ผลลัพธ์ที่ดีพอสมควร) ขยายด้วยคุณสมบัติที่คุณต้องการ

แยกคนงาน 1 คนทำการปรับขนาดเพื่อไม่ให้เบราว์เซอร์ค้างเมื่อทำการปรับขนาดซึ่งแตกต่างจากตัวปรับแต่ง 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

ฉันเพิ่งทำการเปรียบเทียบหน้าเว็บแบบเคียงข้างกันและถ้าไม่มีอะไรเปลี่ยนแปลงไปเมื่อเร็ว ๆ นี้ฉันไม่เห็นการลดขนาด (การปรับขนาด) ที่ดีขึ้นโดยใช้ Canvas เทียบกับ CSS แบบง่าย ฉันทดสอบใน FF6 Mac OSX 10.7 ยังคงนุ่มเล็กน้อยเมื่อเทียบกับต้นฉบับ

อย่างไรก็ตามฉันทำบางสิ่งที่ทำให้เกิดความแตกต่างอย่างมากและนั่นก็คือการใช้ฟิลเตอร์ภาพในเบราว์เซอร์ที่รองรับ Canvas คุณสามารถปรับแต่งภาพได้ตามที่คุณต้องการใน Photoshop ด้วยความเบลอ, ความคมชัด, ความอิ่มตัวของสี, ระลอกคลื่น, โทนสีเทา ฯลฯ

ฉันพบปลั๊กอิน jQuery ที่ยอดเยี่ยมซึ่งทำให้แอปพลิเคชันตัวกรองเหล่านี้ใช้งานได้อย่างรวดเร็ว: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

ฉันใช้ฟิลเตอร์แปลงความคมชัดทันทีหลังจากปรับขนาดรูปภาพซึ่งควรให้เอฟเฟกต์ที่ต้องการแก่คุณ ฉันไม่จำเป็นต้องใช้องค์ประกอบผ้าใบ


-1

กำลังมองหาวิธีแก้ปัญหาที่ง่าย ๆ

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

วิธีนี้จะใช้อัลกอริธึมการปรับขนาดของเบราว์เซอร์! :)


คำถามเกี่ยวกับการสุ่มตัวอย่างภาพไม่ใช่แค่ปรับขนาด
Jesús Carrera

[... ] ฉันพยายามปรับขนาด jpg ฉันลองปรับขนาด jpg เดียวกันบนเว็บไซต์ที่เชื่อมโยงและใน photoshop และดูดีเมื่อลดขนาด [... ] ทำไมคุณไม่สามารถใช้ <img> Jesus Carrera ได้?
ale500
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.