1

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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
OgzhnOzlci
  • 56
  • 3
  • If you enlarge an image, you will often get pixilation. Feed in an image with greater pixel density and enlarging will often not show as much pixilization. – markE Feb 23 '15 at 23:32

2 Answers2

1

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

})();
OgzhnOzlci
  • 56
  • 3
0

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.

  • Frankly, I'm confused :-z I looked at @OgzhnOzlci's image and saw what looked like an attempt to magnify part of the image (in the zoom box), and I assumed the question was about magnification. But if the problem is instead reducing the 6500 x 3592 image then you have a good answer and a good "reading between the lines" of the question. :-) – markE Feb 24 '15 at 04:10
  • @markE I assumed it was the input image that was the original image, but you could be right, it could be the sample image's original size and it doesn't state output size. OP should probably clarify. –  Feb 24 '15 at 07:31