Scheme - 図形言語(ペインタ演算を作り出す手続き、フレーム座標写像、ベクトル(構成子、選択子)、ペインタの定義)の最後の、
HTML5のcanvas要素、JavaScriptとかを使って実際に出力して合ってるか確かめてみたいけど、とりあえず先に進むことを優先することに。本書を最後まで終えてから、ブラウザ上に描けるように実装してみるかも。
をJavaScriptリファレンス 第6版を参考にしながらSchemeの学習の図形言語で使えるように実際に作ってみた。
(完成図形だけではなく、再帰関数で徐々に描かれていく様子を観察したい場合はこちら。)
フレーム: origin x: y: edge1 x: y: edge2 x: y:
基本的な線分のリスト:
(その他の線分のリストは下記参照。他にもコメント欄等で面白い線分のリストを教えて頂けると嬉しいです!)
用紙サイズ比較用:
ペインタの演算 (全ての演算の取り消し)
分割回数:
背景色: 線分の色:
描画:
追記: 2013/07/28
IE10(デスクトップ版)で描画できることを確認できた。
線分のリストについて、各線分1行に、(x1, y1)、(x2, y2)を結ぶ線分なら、x1, y1, x2, y2と入力。
painterの演算について。
- below: 上下に配置
- beside: 左右に配置
- flipVert: 上下逆さまに
- flipHoriz: 左右逆さまに
- rotate90: 半時計回りに90°回転
- rotate180: 半時計回りに180°回転
- rotate270: 半時計回りに270°回転
- shrinkToUpperRight: フレームの右上の四半分に収縮
- squashInwards: フレームの中央へ押し込む
その他は実行結果で。
Schemeの学習の寄り道としてScheme風にJavaScriptのコードを書いたけど、実際にcanvas要素で何かを描画したりするなら、JavaScriptに最初からある描画APIを使った方がもっといろんなことが出来て面白そう。(今回したようなことを車輪の再発明っていうのかな。。)
以下にHTMLの内容とJavaScriptのソースコード
2013/06/02 追記:
JavaScriptクックブックを読んで、Internet Explorer 9以前でも動くように、HTMLにErik Arvidssonが開発したJavaScript、Canvasエミュレーションライブラリ、explorercanvasを追加。
HTMLのソース(BBEdit)
<!--[if IE]> <script type="text/javascript" src="https://sites.google.com/site/sitekamimura/javascript/excanvas.compiled.js"></script> <![endif]-->
2013/06/04 追記:
用紙サイズ比較用を追加してみた。
2013/06/14 追記:
描画した画像のフォーマットをpngからjpegに変換できるようにしてみた。(pngは何もしてないところは黒の透明、jpegは黒となるので、それに対応するために線分を描く前にCanvasRenderingContext2DオブジェクトのfillStyleプロパティを設定してfillRectでcanvas全体を塗りつぶすことに。さらに背景色、線分の色(strokeStyleプロパティ)を設定できるように変更。)
2013/07/23 追記:
猫(?)の線分のリスト。
0.6,0,0.35,0.4 0.35,0.4,0,0.5 0,0.5,0.2,0.8 0.2,0.8,0.1,0.95 0.1,0.95,0.3,0.9 0.3,0.9,0.6,0.925 0.6,0.925,0.7,0.9 0.7,0.9,0.8,0.975 0.8,0.975,0.8,0.7 0.8,0.7,0.825,0.6 0.825,0.6,1,0.6 1,0.6,0.9,0.45 0.9,0.45,0.75,0.35 0.75,0.35,0.6,0 0.6,0,0.6,0.25 0.45,0.475,0.6,0.6 0.6,0.6,0.7,0.5 0.3,0.4,0.4,0.6 0.4,0.6,0.5,0.575 0.375,0.6,0.1,0.45 0.4,0.625,0.1,0.7 0.375,0.65,0.375,0.75 0.375,0.75,0.5,0.8 0.5,0.8,0.6,0.775 0.6,0.775,0.7,0.8 0.7,0.8,0.8,0.75 0.75,0.6,1,0.55 0.775,0.575,1,0.525 0.8,0.55,1,0.5 0.45,0.7,0.525,0.7 0.7,0.7,0.775,0.7 0.6,0.65,0.625,0.65
二等辺三角形の線分のリスト。
0,0,0.5,1 0.5,1,1,0 1,0,0,0
HTMLのソース(BBEdit)
<div style="width:650px; height:510px"> <div id="d0" style="float:right; width:500px; height:500px"> </div> <div style="float:left; width:140px"> <textarea id="segments" style="width:140px; height:250px"> 0.25,0,0.3,0.6 0.3,0.6,0.275,0.65 0.275,0.65,0.2,0.5 0.2,0.5,0,0.7 0,0.8,0.2,0.65 0.2,0.65,0.3,0.7 0.3,0.7,0.4,0.7 0.4,0.7,0.35,0.9 0.35,0.9,0.4,1 0.6,1,0.65,0.9 0.65,0.9,0.6,0.7 0.6,0.7,0.7,0.7 0.7,0.7,1,0.4 1,0.2,0.6,0.5 0.6,0.5,0.75,0 0.6,0,0.5,0.25 0.5,0.25,0.4,0 </textarea> <textarea id="painter_to_painters" style="width:140px; height:250px;"> squareLimit </textarea> </div> </div> <p> フレーム: origin x: <input id="x_origin" type="text" value="0" size="3" maxlength="3" /> y: <input id="y_origin" type="text" value="0" size="3" maxlength="3" /> edge1 x: <input id="x_edge1" type="text" value="500" size="3" maxlength="3" /> y: <input id="y_edge1" type="text" value="0" size="3" maxlength="3" /> edge2 x: <input id="x_edge2" type="text" value="0" size="3" maxlength="3" /> y: <input id="y_edge2" type="text" value="500" size="3" maxlength="3" /> </p> <p> 基本的な線分のリスト: <input id="frame_segments" type="button" value="frame" /> <input id="x_segments" type="button" value="X" /> <input id="diamond_segments" type="button" value="diamond" /> <input id="wave_segments" type="button" value="wave" /> <input class="clear_segments" type="button" value="clear" /> </p> <p>用紙サイズ比較用: <input id="size_a5" type="button" value="A5" /> <input id="size_b5" type="button" value="B5" /> <input id="size_a4" type="button" value="A4" /> <input id="size_b4" type="button" value="B4" /> <input id="size_a3" type="button" value="A3" /> <input id="size_b3" type="button" value="B3" /> <input id="size_a2" type="button" value="A2" /> <input id="size_b2" type="button" value="B2" /> <input id="size_a1" type="button" value="A1" /> <input id="size_b1" type="button" value="B1" /> <input id="size_a0" type="button" value="A0" /> <input id="size_b0" type="button" value="B0" /> <input class="clear_segments" type="button" value="clear" /> <p> ペインタの演算 <input id="reset" type="button" value="reset" /> (全ての演算の取り消し) </p> <p> <input class="operator" type="button" value="below" /> <input class="operator" type="button" value="beside" /> <input class="operator" type="button" value="flipVert" /> <input class="operator" type="button" value="flipHoriz" /> <input class="operator" type="button" value="flipedPairs" /> <input class="operator" type="button" value="rotate90" /> <input class="operator" type="button" value="rotate180" /> <input class="operator" type="button" value="rotate270" /> <input class="operator" type="button" value="shrinkToUpperRight" /> <input class="operator" type="button" value="squashInwards" /> </p> <p> 分割回数: <input id="split_n" type="text" value="4" size="3" maxlength="3" /> <input class="operator" type="button" value="rightSplit" /> <input class="operator" type="button" value="upSplit" /> <input class="operator" type="button" value="cornerSplit" /> <input class="operator" type="button" value="squareLimit" /> </p> <p>背景色: <input id="fill_style" value="white" size="10"/> 線分の色: <input id="stroke_style" value="black" size="10" /> <p> 描画: <input id="draw_button" type="button" value="draw" /> <input id="clear_button" type="button" value="clear" /> <input id="convert_to_jpeg" type="button" value="convert to jpeg" > </p>
JavaScriptのコード(BBEdit)
var makeVect = function ( x, y ) { return [x, y]; }, xcorVect = function ( v ) { return v[0]; }, ycorVect = function ( v ) { return v[1]; }, addVect = function ( v1, v2 ) { var x1 = xcorVect(v1), y1 = ycorVect(v1), x2 = xcorVect(v2), y2 = ycorVect(v2); return makeVect(x1 + x2, y1 + y2); }, subVect = function ( v1, v2 ) { var x1 = xcorVect(v1), y1 = ycorVect(v1), x2 = xcorVect(v2), y2 = ycorVect(v2); return makeVect(x1 - x2, y1 - y2); }, scaleVect = function( s, v ) { var x = xcorVect(v), y = ycorVect(v); return makeVect(s * x, s * y); }, makeFrame = function ( origin, edge1, edge2 ) { origin = makeVect(xcorVect(origin), ycorVect(origin)); edge1 = makeVect(xcorVect(edge1), ycorVect(edge1)); edge2 = makeVect(xcorVect(edge2), ycorVect(edge2)); return [origin, edge1, edge2]; }, originFrame = function( frame ) { return frame[0]; }, edge1Frame = function( frame ) { return frame[1]; }, edge2Frame = function( frame ) { return frame[2]; }, frameCoordMap = function( frame ) { return function ( v ) { return addVect( originFrame(frame), addVect(scaleVect(xcorVect(v), edge1Frame(frame)), scaleVect(ycorVect(v), edge2Frame(frame)))); }; }, makeSegment = function( v1, v2 ) { return [v1, v2]; }, startSegment = function( segment ) { return segment[0]; }, endSegment = function( segment ) { return segment[1]; }, drawLine = function( ctx, v1, v2 ) { var x1 = xcorVect(v1), y1 = ycorVect(v1), x2 = xcorVect(v2), y2 = ycorVect(v2); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); }, segmentsToPainter = function(ctx, segments ) { return function( frame ) { var i, max; for(i = 0, max = segments.length; i < max; i++) { drawLine( ctx, frameCoordMap(frame)(startSegment(segments[i])), frameCoordMap(frame)(endSegment(segments[i]))); } }; }, transformPainter = function ( painter, origin, corner1, corner2 ) { return function ( frame ) { var m = frameCoordMap(frame), newOrigin = m(origin), newEdge1 = subVect(m(corner1), newOrigin), newEdge2 = subVect(m(corner2), newOrigin), newFrame = makeFrame(newOrigin, newEdge1, newEdge2); painter( newFrame ); }; }, beside = function(painter1, painter2){ var splitPoint = makeVect(0.5, 0,0), paintLeft = transformPainter(painter1, makeVect(0, 0), splitPoint, makeVect(0, 1)), paintRight = transformPainter(painter2, splitPoint, makeVect(1, 0), makeVect(0.5, 1)); return function ( frame ) { paintLeft(frame); paintRight(frame); }; }, below = function(painter1, painter2) { var paintBelow = transformPainter(painter1, makeVect(0, 0), makeVect(1, 0), makeVect(0, 0.5)), paintUp = transformPainter(painter2, makeVect(0, 0.5), makeVect(1, 0.5), makeVect(0, 1)); return function ( frame ) { paintBelow(frame); paintUp(frame); }; }, flipVert = function ( painter ) { return transformPainter(painter, makeVect(0,1), makeVect(1,1), makeVect(0,0)); }, flipHoriz = function( painter ) { return transformPainter(painter, makeVect(1, 0), makeVect(0, 0), makeVect(1, 1)); }, rotate90 = function ( painter ) { return transformPainter(painter, makeVect(1, 0), makeVect(1, 1), makeVect(0, 0)); }, rotate180 = function( painter ) { return transformPainter(painter, makeVect(1, 1), makeVect(0, 1), makeVect(1, 0)); }, rotate270 = function ( painter ) { return transformPainter(painter, makeVect(0, 1), makeVect(0, 0), makeVect(1, 1)); }, shrinkToUpperRight = function ( painter ) { return transformPainter(painter, makeVect(0.5,0.5), makeVect(1, 0.5), makeVect(0.5, 1)); }, squashInwards = function( painter ) { return transformPainter( painter, makeVect(0,0), makeVect(0.65, 0.35), makeVect(0.35, 0.65)); }, squareOfFour = function( tl, tr, bl, br ) { return function( painter ) { var top = beside( tl(painter), tr(painter) ), bottom = beside( bl(painter), br(painter) ); return below(bottom, top); }; }, flipedPairs = function( painter ) { var combine4 = squareOfFour(identity, flipVert, identity, flipVert); return combine4(painter); }, cornerSplit = function( painter, n ) { if (n === 0) { return painter; } var up = upSplit(painter, n - 1), right = rightSplit(painter, n - 1), topLeft = beside(up, up), bottomRight = below(right, right), corner = cornerSplit(painter, n - 1); return beside(below(painter, topLeft), below(bottomRight, corner)); }, identity = function( painter ) { return painter; }, squareLimit = function( painter, n ) { var combine4 = squareOfFour(flipHoriz, identity, rotate180, flipVert); return combine4(cornerSplit(painter, n)); }, split = function( f, g ) { return function( painter, n ) { if (n === 0) { return painter; } var smaller = split(f, g)(painter, n - 1); return f(painter, g(smaller, smaller)); }; }, rightSplit = split( beside, below), upSplit = split(below, beside); painterToPainters = { "flipVert": flipVert, "flipHoriz": flipHoriz, "flipedPairs": flipedPairs, "rotate90": rotate90, "rotate180": rotate180, "rotate270": rotate270, "shrinkToUpperRight": shrinkToUpperRight, "squashInwards": squashInwards }, twoPainterToPainters = { "below": below, "beside": beside, }, painterWithNumToPainter = { "rightSplit": rightSplit, "upSplit": upSplit, "cornerSplit": cornerSplit, "squareLimit": squareLimit }, operator = function ( o ) { var $painter_to_painters = $('#painter_to_painters'), text = $painter_to_painters.val(); $painter_to_painters.val(text + o.value + "\n"); }, reset = function ( ) { $('#painter_to_painters').val(""); }, clear = function () { $('#d0').html('<canvas id="imgcanvas" width="500" height="500">' + '<p>paint</p>' + '</canvas>'); }, addSegments = function( segments ) { var $segments = $('#segments'), text = $segments.val(); $segments.val(text + segments); }, size_x_y_a = [[1189,841],[841,594],[594,420], [420,297],[297,210],[210,148]], size_x_y_b = [[1456,1030],[1030,728],[728,515], [515,364],[364,257],[257,182]], addSizeSegments = function ( x, y ) { addSegments("0,0," + x + ",0\n" + x + ",0," + x + "," + y + "\n" + x + "," + y + ",0," + y + "\n" + "0," + y + ",0,0"); }, clearSegments = function ( ) { $('#segments').val(""); }, convertToPng = function () { var $img = $('<img>'); imgcanvas = document.getElementById("imgcanvas"); if (!imgcanvas) { return; } $img.attr("src" , imgcanvas.toDataURL('image/jpeg', 0.5)); $('#d0').html($img); }, draw = function () { clear(); var imgcanvas = document.getElementById("imgcanvas"), ctx = imgcanvas.getContext('2d'), text_segments = $('#segments').val(), text_painter_to_painters = $('#painter_to_painters').val(), lines_segment = text_segments.split("\n"), lines_painter_to_painter = text_painter_to_painters.split("\n"), x_origin = parseFloat($('#x_origin').val()), y_origin = parseFloat($('#y_origin').val()), x_edge1 = parseFloat($('#x_edge1').val()), y_edge1 = parseFloat($('#y_edge1').val()), x_edge2 = parseFloat($('#x_edge2').val()), y_edge2 = parseFloat($('#y_edge2').val()), origin = makeVect(x_origin, y_origin), edge1 = makeVect(x_edge1, y_edge1), edge2 = makeVect(x_edge2, y_edge2), frame = makeFrame(origin, edge1, edge2), segments = [], painter_to_painters = [], $split_n = $('#split_n'), segment, x1, y1, x2, y2, v1, v2, painter, painter_to_painter, i, max, j, max_j, key; for (i = 0, max = lines_segment.length; i < max; i += 1) { segment = lines_segment[i].split(","); x1 = parseFloat(segment[0]) || 0; y1 = parseFloat(segment[1]) || 0; x2 = parseFloat(segment[2]) || 0; y2 = parseFloat(segment[3]) || 0; v1 = makeVect(x1, y1); v2 = makeVect(x2, y2); segments[i] = makeSegment(v1, v2); } ctx.translate(0, 500); ctx.scale(1, -1); ctx.fillStyle = $('#fill_style').val(); ctx.strokeStyle = $('#stroke_style').val(); ctx.fillRect(0, -150, 650, 650); painter = segmentsToPainter(ctx, segments); for (i = 0, max = lines_painter_to_painter.length; i < max; i += 1) { key = lines_painter_to_painter[i]; if (painterToPainters.hasOwnProperty(key)) { painter = painterToPainters[key](painter); } else if (twoPainterToPainters.hasOwnProperty(key)) { painter = twoPainterToPainters[key](painter, painter); } else if (painterWithNumToPainter.hasOwnProperty(key)) { n = parseInt($split_n.val(), 10); painter = painterWithNumToPainter[key](painter, n); } } painter(frame); ctx.stroke() }; $('#frame_segments').click(function(e) { addSegments("0,0,0,1\n" + "0,1,1,1\n" + "1,1,1,0\n" + "1,0,0,0\n"); }); $('#x_segments').click(function(e) { addSegments("0,0,1,1\n" + "1,0,0,1\n"); }); $('#diamond_segments').click(function(e) { addSegments("0.5,0,0,0.5\n" + "0,0.5,0.5,1\n" + "0.5,1,1,0.5\n" + "1,0.5,0.5,0\n"); }); $('#wave_segments').click(function(e) { addSegments("0.25,0,0.3,0.6\n" + "0.3,0.6,0.275,0.65\n" + "0.275,0.65,0.2,0.5\n" + "0.2,0.5,0,0.7\n" + "0,0.8,0.2,0.65\n" + "0.2,0.65,0.3,0.7\n" + "0.3,0.7,0.4,0.7\n" + "0.4,0.7,0.35,0.9\n" + "0.35,0.9,0.4,1\n" + "0.6,1,0.65,0.9\n" + "0.65,0.9,0.6,0.7\n" + "0.6,0.7,0.7,0.7\n" + "0.7,0.7,1,0.4\n" + "1,0.2,0.6,0.5\n" + "0.6,0.5,0.75,0\n" + "0.6,0,0.5,0.25\n" + "0.5,0.25,0.4,0\n"); }); $('.clear_segments').click(function(e) { clearSegments(); }); $.each(size_x_y_a, function( index, value ) { $('#size_a' + index).click(function ( ) { addSizeSegments(value[0] / 1456, value[1] / 1456); }); }); $.each(size_x_y_b, function(index, value) { $('#size_b' + index).click(function() { addSizeSegments(value[0] / 1456, value[1] / 1456); }); }); $('.operator').click(function(e) { operator(this); }); $('#reset').click(function() { reset(); }); $('#draw_button').click(function(e) { draw(); }); $('#clear_button').click(function(e) { clear(); }); $('#convert_to_jpeg').click(function (e) { convertToPng(); });
0 コメント:
コメントを投稿