/** * Projective texturing using Canvas. * * (c) Steven Wittens 2008 * http://www.acko.net/ */ function obj_dump(obj) { var txt = ''; for (var one in obj){ txt += one + "=" + obj[one] + "\n"; } alert(txt); } // 4点ハンドルの座標位置を画面から計算 var screenW = window.innerWidth; var screenH = screenW / 4 * 3; var centerX = Math.round( screenW / 2 ); var centerY = Math.round( screenH / 2 ); major = Math.round(screenW / 12) centerX = major * 4; centerY = major * 3; // ドアによっては縦横比変わるけどドンマイ! var points = [ [centerX - major, centerY - major*1], [centerX + major, centerY - major*1], [centerX - major, centerY + major*3], [centerX + major, centerY + major*3] ]; var options = { wireframe: false, image: 'http://kaku-gen.com/images/product/p12043_1.php', subdivisionLimit: 5, patchSize: 64 }; var refresh, update; // add var isTouch = ('ontouchend' in window); (function () { var $handles; var drag = null; var offset = null; var canvas = null, ctx = null, transform = null; var image = null, iw = 0, ih = 0; var log; $(document).ready( function(){ init(); }); /** * Refresh image. */ refresh = function () { image = new Image(); image.onload = update; image.src = options.image; } /** * Initialize the handles and canvas. */ function init() { // Position handles. $handles = $('div.handle').each( function(i){ this.index = i; $(this).css({ left: points[this.index][0], top : points[this.index][1] }); }); // SmartPhone ----------- // if ( isTouch ) { $handles.on('touchstart', function(e){ drag = this.index; offset = { x: e.originalEvent.changedTouches[0].pageX - parseInt($(this).css('left')), y: e.originalEvent.changedTouches[0].pageY - parseInt($(this).css('top')) }; return false; }); $(document).on('touchmove', function(e){ if (drag != null) { points[drag][0] = e.originalEvent.changedTouches[0].pageX - offset.x; points[drag][1] = e.originalEvent.changedTouches[0].pageY - offset.y; $handles[drag].style.left = points[drag][0] + 'px'; $handles[drag].style.top = points[drag][1] + 'px'; // $("#canvas").css("opacity","0.5"); log = $handles[drag].style.left + " / " + $handles[drag].style.top ; // dump領域に座標表示 // $("div#dump").html( log ); update(); } }).touchend( function(){ drag = null; // $("#canvas").css("opacity","1.0"); }); // PC ------------------- // } else { $handles.mousedown( function(event){ drag = this.index; offset = { x: event.pageX - parseInt($(this).css('left')), y: event.pageY - parseInt($(this).css('top')) }; return false; }); $(document).mousemove( function(event){ if (drag != null) { points[drag][0] = event.pageX - offset.x; points[drag][1] = event.pageY - offset.y; $handles[drag].style.left = points[drag][0] + 'px'; $handles[drag].style.top = points[drag][1] + 'px'; log = $handles[drag].style.left + " / " + $handles[drag].style.top ; $("div#dump").html( log ); update(); } }).mouseup( function(){ drag = null; }); // } // Create canvas and load image. canvas = createCanvas(0, 0, 1, 1); refresh(); // Render image. update(); } /** * Update the display to match a new point configuration. */ update = function () { // Get extents. var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; $.each(points, function () { minX = Math.min(minX, Math.floor(this[0])); maxX = Math.max(maxX, Math.ceil(this[0])); minY = Math.min(minY, Math.floor(this[1])); maxY = Math.max(maxY, Math.ceil(this[1])); }); minX--; minY--; maxX++; maxY++; var width = maxX - minX; var height = maxY - minY; // Reshape canvas. canvas.style.left = minX +'px'; canvas.style.top = minY +'px'; canvas.width = width; canvas.height = height; // Measure texture. iw = image.width; ih = image.height; // Set up basic drawing context. ctx = canvas.getContext("2d"); ctx.translate(-minX, -minY); ctx.clearRect(minX, minY, width, height); ctx.strokeStyle = "rgb(220,0,100)"; transform = getProjectiveTransform(points); // Begin subdivision process. var ptl = transform.transformProjectiveVector([0, 0, 1]); var ptr = transform.transformProjectiveVector([1, 0, 1]); var pbl = transform.transformProjectiveVector([0, 1, 1]); var pbr = transform.transformProjectiveVector([1, 1, 1]); ctx.beginPath(); ctx.moveTo(ptl[0], ptl[1]); ctx.lineTo(ptr[0], ptr[1]); ctx.lineTo(pbr[0], pbr[1]); ctx.lineTo(pbl[0], pbl[1]); ctx.closePath(); ctx.clip(); divide(0, 0, 1, 1, ptl, ptr, pbl, pbr, options.subdivisionLimit); if (options.wireframe) { ctx.beginPath(); ctx.moveTo(ptl[0], ptl[1]); ctx.lineTo(ptr[0], ptr[1]); ctx.lineTo(pbr[0], pbr[1]); ctx.lineTo(pbl[0], pbl[1]); ctx.closePath(); ctx.stroke(); } } /** * Render a projective patch. */ function divide(u1, v1, u4, v4, p1, p2, p3, p4, limit) { // See if we can still divide. if (limit) { // Measure patch non-affinity. var d1 = [p2[0] + p3[0] - 2 * p1[0], p2[1] + p3[1] - 2 * p1[1]]; var d2 = [p2[0] + p3[0] - 2 * p4[0], p2[1] + p3[1] - 2 * p4[1]]; var d3 = [d1[0] + d2[0], d1[1] + d2[1]]; var r = Math.abs((d3[0] * d3[0] + d3[1] * d3[1]) / (d1[0] * d2[0] + d1[1] * d2[1])); // Measure patch area. d1 = [p2[0] - p1[0] + p4[0] - p3[0], p2[1] - p1[1] + p4[1] - p3[1]]; d2 = [p3[0] - p1[0] + p4[0] - p2[0], p3[1] - p1[1] + p4[1] - p2[1]]; var area = Math.abs(d1[0] * d2[1] - d1[1] * d2[0]); // Check area > patchSize pixels (note factor 4 due to not averaging d1 and d2) // The non-affinity measure is used as a correction factor. if ((u1 == 0 && u4 == 1) || ((.25 + r * 5) * area > (options.patchSize * options.patchSize))) { // Calculate subdivision points (middle, top, bottom, left, right). var umid = (u1 + u4) / 2; var vmid = (v1 + v4) / 2; var pmid = transform.transformProjectiveVector([umid, vmid, 1]); var pt = transform.transformProjectiveVector([umid, v1, 1]); var pb = transform.transformProjectiveVector([umid, v4, 1]); var pl = transform.transformProjectiveVector([u1, vmid, 1]); var pr = transform.transformProjectiveVector([u4, vmid, 1]); // Subdivide. limit--; divide(u1, v1, umid, vmid, p1, pt, pl, pmid, limit); divide(umid, v1, u4, vmid, pt, p2, pmid, pr, limit); divide(u1, vmid, umid, v4, pl, pmid, p3, pb, limit); divide(umid, vmid, u4, v4, pmid, pr, pb, p4, limit); if (options.wireframe) { ctx.beginPath(); ctx.moveTo(pt[0], pt[1]); ctx.lineTo(pb[0], pb[1]); ctx.stroke(); ctx.beginPath(); ctx.moveTo(pl[0], pl[1]); ctx.lineTo(pr[0], pr[1]); ctx.stroke(); } return; } } // Render this patch. ctx.save(); // Set clipping path. ctx.beginPath(); ctx.moveTo(p1[0], p1[1]); ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p4[0], p4[1]); ctx.lineTo(p3[0], p3[1]); ctx.closePath(); //ctx.clip(); // Get patch edge vectors. var d12 = [p2[0] - p1[0], p2[1] - p1[1]]; var d24 = [p4[0] - p2[0], p4[1] - p2[1]]; var d43 = [p3[0] - p4[0], p3[1] - p4[1]]; var d31 = [p1[0] - p3[0], p1[1] - p3[1]]; // Find the corner that encloses the most area var a1 = Math.abs(d12[0] * d31[1] - d12[1] * d31[0]); var a2 = Math.abs(d24[0] * d12[1] - d24[1] * d12[0]); var a4 = Math.abs(d43[0] * d24[1] - d43[1] * d24[0]); var a3 = Math.abs(d31[0] * d43[1] - d31[1] * d43[0]); var amax = Math.max(Math.max(a1, a2), Math.max(a3, a4)); var dx = 0, dy = 0, padx = 0, pady = 0; // Align the transform along this corner. switch (amax) { case a1: ctx.transform(d12[0], d12[1], -d31[0], -d31[1], p1[0], p1[1]); // Calculate 1.05 pixel padding on vector basis. if (u4 != 1) padx = 1.05 / Math.sqrt(d12[0] * d12[0] + d12[1] * d12[1]); if (v4 != 1) pady = 1.05 / Math.sqrt(d31[0] * d31[0] + d31[1] * d31[1]); break; case a2: ctx.transform(d12[0], d12[1], d24[0], d24[1], p2[0], p2[1]); // Calculate 1.05 pixel padding on vector basis. if (u4 != 1) padx = 1.05 / Math.sqrt(d12[0] * d12[0] + d12[1] * d12[1]); if (v4 != 1) pady = 1.05 / Math.sqrt(d24[0] * d24[0] + d24[1] * d24[1]); dx = -1; break; case a4: ctx.transform(-d43[0], -d43[1], d24[0], d24[1], p4[0], p4[1]); // Calculate 1.05 pixel padding on vector basis. if (u4 != 1) padx = 1.05 / Math.sqrt(d43[0] * d43[0] + d43[1] * d43[1]); if (v4 != 1) pady = 1.05 / Math.sqrt(d24[0] * d24[0] + d24[1] * d24[1]); dx = -1; dy = -1; break; case a3: // Calculate 1.05 pixel padding on vector basis. ctx.transform(-d43[0], -d43[1], -d31[0], -d31[1], p3[0], p3[1]); if (u4 != 1) padx = 1.05 / Math.sqrt(d43[0] * d43[0] + d43[1] * d43[1]); if (v4 != 1) pady = 1.05 / Math.sqrt(d31[0] * d31[0] + d31[1] * d31[1]); dy = -1; break; } // Calculate image padding to match. var du = (u4 - u1); var dv = (v4 - v1); var padu = padx * du; var padv = pady * dv; ctx.drawImage( image, u1 * iw, v1 * ih, Math.min(u4 - u1 + padu, 1) * iw, Math.min(v4 - v1 + padv, 1) * ih, dx, dy, 1 + padx, 1 + pady ); ctx.restore(); } /** * Create a canvas at the specified coordinates. */ function createCanvas(x, y, width, height) { // Create var canvas; if (typeof G_vmlCanvasManager != 'undefined') { canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; $('#canvas').appendChild(canvas); canvas = G_vmlCanvasManager.initElement(canvas); } else { canvas = $(''); $('#canvas').append(canvas); canvas = canvas[0]; } return canvas; } /** * Calculate a projective transform that maps [0,1]x[0,1] onto the given set of points. */ function getProjectiveTransform(points) { var eqMatrix = new Matrix(9, 8, [ [ 1, 1, 1, 0, 0, 0, -points[3][0],-points[3][0],-points[3][0] ], [ 0, 1, 1, 0, 0, 0, 0,-points[2][0],-points[2][0] ], [ 1, 0, 1, 0, 0, 0, -points[1][0], 0,-points[1][0] ], [ 0, 0, 1, 0, 0, 0, 0, 0,-points[0][0] ], [ 0, 0, 0, -1,-1,-1, points[3][1], points[3][1], points[3][1] ], [ 0, 0, 0, 0,-1,-1, 0, points[2][1], points[2][1] ], [ 0, 0, 0, -1, 0,-1, points[1][1], 0, points[1][1] ], [ 0, 0, 0, 0, 0,-1, 0, 0, points[0][1] ] ]); var kernel = eqMatrix.rowEchelon().values; var transform = new Matrix(3, 3, [ [-kernel[0][8], -kernel[1][8], -kernel[2][8]], [-kernel[3][8], -kernel[4][8], -kernel[5][8]], [-kernel[6][8], -kernel[7][8], 1] ]); return transform; } })();