I want to render a 3D volume with vtk.js. My issue is that all the tutorials/examples in the vtk.js documentation load and render vti files. In my case, my data is just a Float32Array which contains the values of a 3D image. How can I volume-render that using vtk.js? I need a solution that creates no additional files on the file system.
I managed (with a coworker's help) to render a random cube volume like this:
// pseudo imports (avoids having to use fully qualified names)
var vtkFullScreenRenderWindow = vtk.Rendering.Misc.vtkFullScreenRenderWindow;
var vtkImageData = vtk.Common.DataModel.vtkImageData;
var vtkDataArray = vtk.Common.Core.vtkDataArray;
var vtkVolume = vtk.Rendering.Core.vtkVolume;
var vtkVolumeMapper = vtk.Rendering.Core.vtkVolumeMapper;
var vtkColorTransferFunction = vtk.Rendering.Core.vtkColorTransferFunction;
var vtkPiecewiseFunction = vtk.Common.DataModel.vtkPiecewiseFunction;
var VtkDataTypes = vtkDataArray.VtkDataTypes;
function initRandomCubeVolume() {
var width = 50, height = 50, depth = 50;
var size = width * height * depth;
var values = [];
for (var i = 0; i < size; i++) {
values[i] = Math.random();
}
var scalars = vtkDataArray.newInstance({
values: values,
numberOfComponents: 1, // number of channels (grayscale)
dataType: VtkDataTypes.FLOAT, // values encoding
name: 'scalars'
});
var imageData = vtkImageData.newInstance();
imageData.setOrigin(0, 0, 0);
imageData.setSpacing(1, 1, 1);
imageData.setExtent(0, width - 1, 0, height - 1, 0, depth - 1);
imageData.getPointData().setScalars(scalars);
var volumeMapper = vtkVolumeMapper.newInstance();
volumeMapper.setInputData(imageData);
volumeMapper.setSampleDistance(0.7);
var volumeActor = vtkVolume.newInstance();
volumeActor.setMapper(volumeMapper);
initProps(volumeActor.getProperty());
var view3d = document.getElementById("view3d");
var fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
rootContainer: view3d,
containerStyle: {
height: '100%',
overflow: 'hidden'
},
background: [0, 0, 0]
});
var renderer = fullScreenRenderer.getRenderer();
renderer.addVolume(volumeActor);
renderer.resetCamera();
renderer.getActiveCamera().elevation(-70);
renderer.updateLightsGeometryToFollowCamera();
var renderWindow = fullScreenRenderer.getRenderWindow();
renderWindow.render();
}
function initProps(property) {
property.setRGBTransferFunction(0, newColorFunction());
property.setScalarOpacity(0, newOpacityFunction());
property.setScalarOpacityUnitDistance(0, 4.5);
property.setInterpolationTypeToLinear();
property.setUseGradientOpacity(0, true);
property.setGradientOpacityMinimumValue(0, 0, 5);
property.setGradientOpacityMinimumOpacity(0, 0.0);
property.setGradientOpacityMaximumValue(0, 1);
property.setGradientOpacityMaximumOpacity(0, 1.0);
property.setShade(true);
property.setAmbient(0.2);
property.setDiffuse(0.7);
property.setSpecular(0.3);
property.setSpecularPower(8.0);
}
function newColorFunction() {
var fun = vtkColorTransferFunction.newInstance();
fun.addRGBPoint(0, 0.4, 0.2, 0.0);
fun.addRGBPoint(1, 1.0, 1.0, 1.0);
return fun;
}
function newOpacityFunction() {
var fun = vtkPiecewiseFunction.newInstance();
fun.addPoint(0, 0);
fun.addPoint(0.5, 0);
fun.addPoint(0.5, 1);
fun.addPoint(1, 1);
return fun;
}
initRandomCubeVolume();
#view3d { height: calc(100vh - 20px); /* avoid scrollbar because of margins */ }
<script src="https://unpkg.com/vtk.js"></script>
<div id="view3d"></div>
Resistance is futile!
Related
I can scan the marker as I want to, however now I need my code to be able to scan the markers location and treat it as it is my mouseX and mouseY.
// https://kylemcdonald.github.io/cv-examples/
// more here:
// http://fhtr.org/JSARToolKit/demos/tests/test2.html
var capture;
var w = 640,
h = 480;
var raster, param, pmat, resultMat, detector;
function setup() {
pixelDensity(1); // this makes the internal p5 canvas smaller
capture = createCapture({
audio: false,
video: {
width: w,
height: h
}
}, function() {
console.log('capture ready.')
});
capture.elt.setAttribute('playsinline', '');
createCanvas(w, h);
capture.size(w, h);
capture.hide();
raster = new NyARRgbRaster_Canvas2D(canvas);
param = new FLARParam(canvas.width, canvas.height);
pmat = mat4.identity();
param.copyCameraMatrix(pmat, 100, 10000);
resultMat = new NyARTransMatResult();
detector = new FLARMultiIdMarkerDetector(param, 2);
detector.setContinueMode(true);
}
function draw() {
image(capture, 0, 0, w, h);
canvas.changed = true;
var thresholdAmount = 128; //select('#thresholdAmount').value() * 255 / 100;
detected = detector.detectMarkerLite(raster, thresholdAmount);
select('#markersDetected').elt.innerText = detected;
for (var i = 0; i < detected; i++) {
// read data from the marker
// var id = detector.getIdMarkerData(i);
// get the transformation for this marker
detector.getTransformMatrix(i, resultMat);
// convert the transformation to account for our camera
var mat = resultMat;
var cm = mat4.create();
cm[0] = mat.m00, cm[1] = -mat.m10, cm[2] = mat.m20, cm[3] = 0;
cm[4] = mat.m01, cm[5] = -mat.m11, cm[6] = mat.m21, cm[7] = 0;
cm[8] = -mat.m02, cm[9] = mat.m12, cm[10] = -mat.m22, cm[11] = 0;
cm[12] = mat.m03, cm[13] = -mat.m13, cm[14] = mat.m23, cm[15] = 1;
mat4.multiply(pmat, cm, cm);
// define a set of 3d vertices
var q = 1;
var verts = [
vec4.create(-q, -q, 0, 1),
vec4.create(q, -q, 0, 1),
vec4.create(q, q, 0, 1),
vec4.create(-q, q, 0, 1),
// vec4.create(0, 0, -2*q, 1) // poke up
];
// convert that set of vertices from object space to screen space
var w2 = width / 2,
h2 = height / 2;
verts.forEach(function (v) {
mat4.multiplyVec4(cm, v);
v[0] = v[0] * w2 / v[3] + w2;
v[1] = -v[1] * h2 / v[3] + h2;
});
noStroke();
fill(0, millis() % 255);
beginShape();
verts.forEach(function (v) {
vertex(v[0], v[1]);
});
endShape();
}
}
* {
font-family: sans-serif;
}
<html>
<head>
<meta charset="UTF-8">
<title>MarkerTracking</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js"></script>
<script src="//kig.github.io/JSARToolKit/demos/magi.js"></script>
<script src="//kig.github.io/JSARToolKit/JSARToolKit.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h2>cv-examples Marker Tracking source</h2>
<p>Track an AR marker. Take a picture of one of these markers. Make sure it has a white border.</p>
<!-- <p>Threshold: <input type="range" id="thresholdAmount" value="50"></p>-->
<p>Markers detected: <span id="markersDetected"></span></p>
<script src="sketch.js"></script>
</body>
</html>
The snippet might give an error since it is trying to access the camera to scan the marker.
I have tried creating new variables and starting a brand new detection process which didn't work. I am open to any ideas.
See my example: https://jsfiddle.net/ksumarine/x7f9yLb7/4/
I have a .Stage that has an offset which puts 0, 0 at the bottom center of the canvas. This has a .Layer which draws the .Shape of an objects path from an array of x,y coordinates.
let routes = [{x: #, y: #}, {x: #, y: #}, ......] // example routes
let testStage = new Konva.Stage({
container: con,
x: 0,
y: 0,
width: 300,
height: 300,
offsetX: ((300 / 2) * -1),
offsetY: (300 * -1)
});
let testLayer = new Konva.Layer();
let testShape = new Konva.Shape({
sceneFunc: function(context) {
context.beginPath();
for (var i = 0; i < routes.length - 1; i++) {
context.moveTo(routes[i].x, routes[i].y);
context.lineTo(routes[i + 1].x, routes[i + 1].y);
}
context.strokeStyle = '#000';
context.lineWidth = 1.5;
context.stroke();
}
});
testLayer.add(testShape);
testStage.add(testLayer);
//////////// here's the button click event
animateButton.addEventListener('click', e => {
let i = 0;
let _ctx = testLayer.getContext()._context;
let draw = () => {
setTimeout(() => {
if (routes.length) {
_ctx.clearRect(0, 0, 300, 300);
_ctx.beginPath();
_ctx.moveTo(routes[0].x, (routes[0].y * -1));
for (let p = 1, plen = i; p < plen; p++) {
_ctx.lineTo(routes[p].x, (routes[p].y * -1));
}
_ctx.strokeStyle = '#000';
_ctx.lineWidth = 1.5;
_ctx.stroke();
(i === routes.length - 1) ? i = 0: i++;
}
requestAnimationFrame(draw);
}, 1000 / 59.940);
}
draw();
});
Everything at this point works well and the path is drawn as expected. I would like to use Konva.Animation to show how the object moved, but I cannot figure it out or find any examples to help.
I've added normal Canvas JS in the button eventListener as an example of the desired effect, but using the testLayer context to draw with puts 0,0 back at the top left...not what I want.
Could someone point me in the right direction to accomplish this using Konva.Animation?
For those coming across this question it was asked again in Nov 18, and this time there are two answers. See them here
I want to make and dynamic crop area and found this snippet. It works perfect in normal usage, but when you scaled the original object before making the crop area, the crop zone seems not in the right position. Can you look into this pen for some help ?
var canvas = new fabric.CanvasEx('canvas');
var el;
var object, lastActive, object1, object2;
var cntObj = 0;
var selection_object_left = 0;
var selection_object_top = 0;
var src = "http://fabricjs.com/lib/pug.jpg";
fabric.Image.fromURL('https://omicron.aeon.co/images/08e7f2bb-f2ce-4058-a955-1c8d594468a2/card_SIZED-Aleksandr-Zykov-4975950437_b84f9f9ef8_o.jpg', function (oImg) {
oImg.top = canvas.getHeight()/2 - oImg.getHeight()/2;
oImg.left = canvas.getWidth()/2 - oImg.getWidth()/2;
canvas.add(oImg);
bindCropEvent(oImg);
});
canvas.renderAll();
function bindCropEvent(obj){
obj.on('object:dblclick', function(){
CropMode();
});
};
function CropMode() {
canvas.remove(el);
if (canvas.getActiveObject()) {
object = canvas.getActiveObject();
if (lastActive !== object) {
console.log('different object');
} else {
console.log('same object');
}
if (lastActive && lastActive !== object) {
//lastActive.clipTo = null; results in clip loss
}
el = new fabric.Rect({
fill: 'rgba(0,0,0,0.6)',
originX: 'left',
originY: 'top',
stroke: '#ccc',
strokeDashArray: [2, 2],
opacity: 1,
width: 1,
height: 1,
borderColor: 'red',
cornerColor: 'red',
hasRotatingPoint: false
});
el.left = canvas.getActiveObject().left;
selection_object_left = canvas.getActiveObject().left;
selection_object_top = canvas.getActiveObject().top;
el.top = canvas.getActiveObject().top;
el.width = canvas.getActiveObject().width * canvas.getActiveObject().scaleX;
el.height = canvas.getActiveObject().height * canvas.getActiveObject().scaleY;
//插入
canvas.add(el);
canvas.setActiveObject(el);
el.on('deselected', function(){
console.log('des');
doCrop();
});
} else {
alert("Please select an object or layer");
}
}
function doCrop() {
var eLeft = el.get('left');
var eTop = el.get('top');
var left = eLeft - object.left;
var top = eTop - object.top;
console.log(left, top);
left *= 1;
top *= 1;
console.log(left, top);
var eWidth = el.get('width');
var eHeight = el.get('height');
var eScaleX = el.get('scaleX');
var eScaleY = el.get('scaleY');
var width = eWidth * 1;
var height = eHeight * 1;
object.clipTo = function (ctx) {
ctx.rect(-(eWidth / 2) + left, -(eHeight / 2) + top, parseInt(width * eScaleX), parseInt( height * eScaleY));
}
canvas.remove(el);
lastActive = object;
canvas.renderAll();
}
Thanks !
when you create a rect, you can create new image with toDataURL(). What will be cropped image.
cropOptions = {
left: Math.floor(rect.left),
top: Math.floor(rect.top),
width: Math.floor(rect.width),
height: Math.floor(rect.height)
},
cropDataUrl ;
cropDataUrl = image.toDataURL(cropOptions);
new fabric.Image.fromURL(cropDataUrl, function(img) {
canvas.remove(image,rect).add(img); //this is your cropped image
})
I gone through documentation of cropper by fengyuanchen. I want the image to be fit by default into canvas if rotated. But I couldnt find a way to achieve this. Any idea how to achieve this functionality?
I want it to be like this to be default: link
Check issue demo here: link
I fixed this behavior but for my special needs. I just needed one rotate button which rotates an image in 90° steps. For other purposes you might extend/change my fix.
It works in "strict" mode by dynamically change the cropbox dimensions.
Here my function which is called, when I want to rotate an image. Ah and additionally the misplacement bug has also been fixed.
var $image;
function initCropper() {
$image = $('.imageUploadPreviewWrap > img').cropper({
autoCrop : true,
strict: true,
background: true,
autoCropArea: 1,
crop: function(e) {
}
});
}
function rotateImage() {
//get data
var data = $image.cropper('getCropBoxData');
var contData = $image.cropper('getContainerData');
var imageData = $image.cropper('getImageData');
//set data of cropbox to avoid unwanted behavior due to strict mode
data.width = 2;
data.height = 2;
data.top = 0;
var leftNew = (contData.width / 2) - 1;
data.left = leftNew;
$image.cropper('setCropBoxData',data);
//rotate
$image.cropper('rotate', 90);
//get canvas data
var canvData = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = canvData.height;
var heightNew = contData.height;
var koef = heightNew / heightOld;
var widthNew = canvData.width * koef;
canvData.height = heightNew;
canvData.width = widthNew;
canvData.top = 0;
if (canvData.width >= contData.width) {
canvData.left = 0;
}
else {
canvData.left = (contData.width - canvData.width) / 2;
}
$image.cropper('setCanvasData', canvData);
//and now set cropper "back" to full crop
data.left = 0;
data.top = 0;
data.width = canvData.width;
data.height = canvData.height;
$image.cropper('setCropBoxData',data);
}
This is my extended code provided by AlexanderZ to avoid cuttong wider images than container :)
var contData = $image.cropper('getContainerData');
$image.cropper('setCropBoxData',{
width: 2, height: 2, top: (contData.height/ 2) - 1, left: (contData.width / 2) - 1
});
$image.cropper('rotate', 90);
var canvData = $image.cropper('getCanvasData');
var newWidth = canvData.width * (contData.height / canvData.height);
if (newWidth >= contData.width) {
var newHeight = canvData.height * (contData.width / canvData.width);
var newCanvData = {
height: newHeight,
width: contData.width,
top: (contData.height - newHeight) / 2,
left: 0
};
} else {
var newCanvData = {
height: contData.height,
width: newWidth,
top: 0,
left: (contData.width - newWidth) / 2
};
}
$image.cropper('setCanvasData', newCanvData);
$image.cropper('setCropBoxData', newCanvData);
Not a direct answer to the question ... but i'm betting many people that use this plugin will find this helpfull..
Made this after picking up #AlexanderZ code to rotate the image.
So ... If you guys want to ROTATE or FLIP a image that has already a crop box defined and if you want that cropbox to rotate or flip with the image ... use these functions:
function flipImage(r, data) {
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var canv = $image.cropper('getCanvasData');
if (data.method == "scaleX") {
if (old_cbox.left == canv.left) {
new_cbox.left = canv.left + canv.width - old_cbox.width;
} else {
new_cbox.left = 2 * canv.left + canv.width - old_cbox.left - old_cbox.width;
}
} else {
new_cbox.top = canv.height - old_cbox.top - old_cbox.height;
}
$image.cropper('setCropBoxData', new_cbox);
/* BUG: When rotated to a perpendicular position of the original position , the user perceived axis are now inverted.
Try it yourself: GO to the demo page, rotate 90 degrees then try to flip X axis, you'll notice the image flippped vertically ... but still ... it fliped in relation to its original axis*/
if ( r == 90 || r == 270 || r == -90 || r == -270 ) {
if ( data.method == "scaleX") {
$image.cropper("scaleY", data.option);
} else {
$image.cropper("scaleX", data.option);
}
} else {
$image.cropper(data.method, data.option);
}
$image.cropper(data.method, data.option);
}
function rotateImage(rotate) {
/* var img = $image.cropper('getImageData'); */
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var old_canv = $image.cropper('getCanvasData');
var old_cont = $image.cropper('getContainerData');
$image.cropper('rotate', rotate);
var new_canv = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = new_canv.height;
var widthOld = new_canv.width;
var heightNew = old_cont.height;
var racio = heightNew / heightOld;
var widthNew = new_canv.width * racio;
new_canv.height = Math.round(heightNew);
new_canv.width = Math.round(widthNew);
new_canv.top = 0;
if (new_canv.width >= old_cont.width) {
new_canv.left = 0;
} else {
new_canv.left = Math.round((old_cont.width - new_canv.width) / 2);
}
$image.cropper('setCanvasData', new_canv);
if (rotate == 90) {
new_cbox.height = racio * old_cbox.width;
new_cbox.width = racio * old_cbox.height;
new_cbox.top = new_canv.top + racio * (old_cbox.left - old_canv.left);
new_cbox.left = new_canv.left + racio * (old_canv.height - old_cbox.height - old_cbox.top);
}
new_cbox.width = Math.round(new_cbox.width);
new_cbox.height = Math.round(new_cbox.height);
new_cbox.top = Math.round(new_cbox.top);
new_cbox.left = Math.round(new_cbox.left);
$image.cropper('setCropBoxData', new_cbox);
}
var photoToEdit = $('.photo_container img');
$( photoToEdit ).cropper({
autoCrop : true,
crop: function(e) {}
});
$("#rotate_left_btn").click( function () {
$( photoToEdit ).cropper('rotate', -90);
var containerHeightFactor = $(".photo_container").height() / $( photoToEdit).cropper('getCanvasData').height;
if ( containerHeightFactor < 1 ) { // if canvas height is greater than the photo container height, then scale (on both x and y
// axes to maintain aspect ratio) to make canvas height fit container height
$( photoToEdit).cropper('scale', containerHeightFactor, containerHeightFactor);
} else if ( $( photoToEdit).cropper('getData').scaleX != 1 || $( photoToEdit).cropper('getData').scaleY != 1 ) { // if canvas height
// is NOT greater than container height but image is already scaled, then revert the scaling cuz the current rotation will bring
// the image back to its original orientation (landscape/portrait)
$( photoToEdit).cropper('scale', 1, 1);
}
}
I Fixed this issue hope fully. i have added or changed the option to 0 (viewMode: 0,). Now its working well.
cropper = new Cropper(image, {
dragMode: 'none',
viewMode: 0,
width: 400,
height: 500,
zoomable: true,
rotatable: true,
crop: function(e) {
}
});
document.getElementById('rotateImg').addEventListener('click', function () {
cropper.rotate(90);
});
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.
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();
};
})();
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.