OgzhnOzlci
OgzhnOzlci

Reputation: 56

Worst quality perspective image on canvas

I have a problem on my project.

I am developing a perspective mockup creating module for designers. Users upload images and i get them for placing in mockups with making some perspective calculations. Then users can download this image. I made all of this on clientside with js.

But there is a problem for images which are drawn on canvas with perspective calculations like this;

Sample img: http://oi62.tinypic.com/2h49dec.jpg

orginal image size: 6500 x 3592 and you can see spread edges on image...

I tried a few technics like ctx.imageSmoothingEnabled true etc.. But result was always same.

What can i do for solve this problem? What do you think about this?

edit

For more detail;

I get an image (Resolution free) from user then crop it for mockup ratio. For example in my sample image, user image was cropped for imac ratio 16:9 then making calculation with four dot of screen. By the way, my mockup image size is 6500 x 3592. so i made scale, transform etc this cropped image and put it in mockup on canvas. And then use blob to download this image to client...

Thanks.

Upvotes: 1

Views: 491

Answers (2)

OgzhnOzlci
OgzhnOzlci

Reputation: 56

Solved.

I use perspective.js for calculation on canvas. so I made some revisions on this js source.

If you wanna use or check source;

// Copyright 2010 futomi  http://www.html5.jp/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0

// perspective.js v0.0.2
// 2010-08-28
/* -------------------------------------------------------------------
 * define objects (name space) for this library.
 * ----------------------------------------------------------------- */
if (typeof html5jp == 'undefined') {
    html5jp = new Object();
}

(function() {


html5jp.perspective = function(ctxd, image) {
    // check the arguments
    if (!ctxd || !ctxd.strokeStyle) {
        return;
    }
    if (!image || !image.width || !image.height) {
        return;
    }
    // prepare a <canvas> for the image
    var cvso = document.createElement('canvas');
    cvso.width = parseInt(image.width) * 2;
    cvso.height = parseInt(image.height) * 2;
    var ctxo = cvso.getContext('2d');
    ctxo.drawImage(image, 0, 0, cvso.width, cvso.height);
    // prepare a <canvas> for the transformed image
    var cvst = document.createElement('canvas');
    cvst.width = ctxd.canvas.width;
    cvst.height = ctxd.canvas.height;
    var ctxt = cvst.getContext('2d');

    ctxt.imageSmoothingEnabled = true;
    ctxt.mozImageSmoothingEnabled = true;
    ctxt.webkitImageSmoothingEnabled = true;
    ctxt.msImageSmoothingEnabled = true;


    // parameters
    this.p = {
        ctxd: ctxd,
        cvso: cvso,
        ctxo: ctxo,
        ctxt: ctxt
    }
};

var proto = html5jp.perspective.prototype;

proto.draw = function(points) {
    var d0x = points[0][0];
    var d0y = points[0][1];
    var d1x = points[1][0];
    var d1y = points[1][1];
    var d2x = points[2][0];
    var d2y = points[2][1];
    var d3x = points[3][0];
    var d3y = points[3][1];
    // compute the dimension of each side
    var dims = [
        Math.sqrt(Math.pow(d0x - d1x, 2) + Math.pow(d0y - d1y, 2)), // top side
        Math.sqrt(Math.pow(d1x - d2x, 2) + Math.pow(d1y - d2y, 2)), // right side
        Math.sqrt(Math.pow(d2x - d3x, 2) + Math.pow(d2y - d3y, 2)), // bottom side
        Math.sqrt(Math.pow(d3x - d0x, 2) + Math.pow(d3y - d0y, 2)) // left side
    ];
    //
    var ow = this.p.cvso.width;
    var oh = this.p.cvso.height;
    // specify the index of which dimension is longest
    var base_index = 0;
    var max_scale_rate = 0;
    var zero_num = 0;
    for (var i = 0; i < 4; i++) {
        var rate = 0;
        if (i % 2) {
            rate = dims[i] / ow;
        } else {
            rate = dims[i] / oh;
        }
        if (rate > max_scale_rate) {
            base_index = i;
            max_scale_rate = rate;
        }
        if (dims[i] == 0) {
            zero_num++;
        }
    }
    if (zero_num > 1) {
        return;
    }
    //
    var step = 0.10;
    var cover_step = step * 250;
    //
    var ctxo = this.p.ctxo;
    var ctxt = this.p.ctxt;
    //*** ctxt.clearRect(0, 0, ctxt.canvas.width, ctxt.canvas.height);
    if (base_index % 2 == 0) { // top or bottom side
        var ctxl = this.create_canvas_context(ow, cover_step);
        var cvsl = ctxl.canvas;
        for (var y = 0; y < oh; y += step) {
            var r = y / oh;
            var sx = d0x + (d3x - d0x) * r;
            var sy = d0y + (d3y - d0y) * r;
            var ex = d1x + (d2x - d1x) * r;
            var ey = d1y + (d2y - d1y) * r;
            var ag = Math.atan((ey - sy) / (ex - sx));
            var sc = Math.sqrt(Math.pow(ex - sx, 2) + Math.pow(ey - sy, 2)) / ow;
            ctxl.setTransform(1, 0, 0, 1, 0, -y);
            ctxl.drawImage(ctxo.canvas, 0, 0);
            //
            ctxt.translate(sx, sy);
            ctxt.rotate(ag);
            ctxt.scale(sc, sc);
            ctxt.drawImage(cvsl, 0, 0);
            //
            ctxt.setTransform(1, 0, 0, 1, 0, 0);
        }
    } else if (base_index % 2 == 1) { // right or left side
        var ctxl = this.create_canvas_context(cover_step, oh);
        var cvsl = ctxl.canvas;
        for (var x = 0; x < ow; x += step) {
            var r = x / ow;
            var sx = d0x + (d1x - d0x) * r;
            var sy = d0y + (d1y - d0y) * r;
            var ex = d3x + (d2x - d3x) * r;
            var ey = d3y + (d2y - d3y) * r;
            var ag = Math.atan((sx - ex) / (ey - sy));
            var sc = Math.sqrt(Math.pow(ex - sx, 2) + Math.pow(ey - sy, 2)) / oh;
            ctxl.setTransform(1, 0, 0, 1, -x, 0);
            ctxl.drawImage(ctxo.canvas, 0, 0);
            //
            ctxt.translate(sx, sy);
            ctxt.rotate(ag);
            ctxt.scale(sc, sc);
            ctxt.drawImage(cvsl, 0, 0);
            //
            ctxt.setTransform(1, 0, 0, 1, 0, 0);
        }
    }
    // set a clipping path and draw the transformed image on the destination canvas.
    this.p.ctxd.save();
    this.set_clipping_path(this.p.ctxd, [
        [d0x, d0y],
        [d1x, d1y],
        [d2x, d2y],
        [d3x, d3y]
    ]);
    this.p.ctxd.drawImage(ctxt.canvas, 0, 0);
    this.p.ctxd.restore();
}



proto.create_canvas_context = function(w, h) {
    var canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    var ctx = canvas.getContext('2d');

    ctx.imageSmoothingEnabled = true;
    ctx.mozImageSmoothingEnabled = true;
    ctx.webkitImageSmoothingEnabled = true;
    ctx.msImageSmoothingEnabled = true;


    return ctx;
};

proto.set_clipping_path = function(ctx, points) {
    ctx.beginPath();
    ctx.moveTo(points[0][0], points[0][1]);
    for (var i = 1; i < points.length; i++) {
        ctx.lineTo(points[i][0], points[i][1]);
    }
    ctx.closePath();
    ctx.clip();
};

})();

Upvotes: 1

user1693593
user1693593

Reputation:

The problem is (most likely, but no code shows so..) that the image is actually too big.

The canvas typically uses bi-linear interpolation (2x2 samples) rather than bi-cubic (4x4 samples). That means if you scale it down a large percentage in one chunk the algorithm will skip some pixels that otherwise should have been sampled, resulting in a more pixelated look.

The solution do is to resize the image in steps, ie. 50% of itself repeatably until a suitable size is achieved. Then use perspective calculations on it. The exact destination size is something you need to find by trial and error, but a good starting point is to use the largest side of the resulting perspective image.

Here is one way to step-down rescale an image in steps.

Upvotes: 0

Related Questions