うしブログ

うしブログ

趣味で運営する、GeoGebraの専門ブログ。

(作業メモ)StartPoint要検証(2行の場合;テキスト変更時未定義問題)

マウスイベント、タッチイベントの発生位置(座標)を取得するJavaScript

 

how to refer mouse/tap coordinates – GeoGebra

 

function ggbOnInit() {
  //グラフィックスビュー1を表示しているcanvasを取得
  var canvas = document.getElementsByTagName('canvas')[0];

  //リッスンしたくないイベントはコメントアウト
  canvas.addEventListener('click', getEventData);  //クリック
  canvas.addEventListener('mousemove', getEventData);  //マウスポインタ移動
  canvas.addEventListener('touchstart', getEventData);  //タップ
  canvas.addEventListener('touchmove', getEventData);  //スワイプ
}

//イベント発生位置を取得
function getEventData(evt){
  evt.preventDefault();

  //発生したイベントのタイプをログに吐き出す
  console.log('eventType = '+evt.type);
  //text1に書き出す
  ggbApplet.setTextValue('text1', 'eventType = '+evt.type);

  var result;  //結果格納用

  var canvas = document.getElementsByTagName('canvas')[0];  //グラフィックスビュー1を表示しているcanvasを取得
  var deviceType = window.ontouchstart===null?'touchDevice':'mouseDevice';  //タッチデバイスか、マウスデバイスかを判定

  //スケール調整用
  var canvasWidth = canvas.width;  //e.g. 1000
  var styleWidth = canvas.style.width;  //e.g. '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  //e.g. 1234

  var styleHeight = canvas.style.height;  //e.g. '1234px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  //e.g. 1234

  var ratio = canvasWidth / styleWidth;

  //タッチデバイス用演算(マウスデバイスと違い、offsetXYプロパティが取れないため、やや計算が複雑)
  if(deviceType == 'touchDevice'){
    //pageXY
    var touchX = evt.changedTouches[0].pageX;
    var touchY = evt.changedTouches[0].pageY;

    //canvasの位置
    var clientRect = canvas.getBoundingClientRect();
    var positionX = clientRect.left + window.pageXOffset;
    var positionY = clientRect.top + window.pageYOffset;

    //加減
    var calcX = touchX - positionX;
    var calcY = touchY - positionY;

    //スケール調整(オフセット値にそろえる)
    calcX = calcX * styleWidth / clientRect.width;
    calcY = calcY * styleHeight / clientRect.height;

    result = [calcX, calcY];
  }

  //マウスデバイス用演算
  if(deviceType == 'mouseDevice'){
    result = [evt.offsetX, evt.offsetY];
  }

  //スケール調整前(オフセット値)、かつ、丸め前の値をログに吐き出す
  console.log('offsetData = '+result);
  //text2に書き出す
  ggbApplet.setTextValue('text2', 'offsetData = '+result);

  //スケール調整(現在のcanvasの大きさに合わせる)
  result[0] = ratio * result[0];
  result[1] = ratio * result[1];

  //丸め
  result[0] = Math.round( result[0] );
  result[1] = Math.round( result[1] );

  //スケール調整後、かつ、丸め後の値をログに吐き出す
  console.log('shapedData = '+result);
  //text3に書き出す
  ggbApplet.setTextValue('text3', 'shapedData = '+result);

  return result;
}

マウス/指位置の真下の描画をワイプ表示する

 

wipe – GeoGebra

タッチデバイスで、指の真下の描画を確認できるよう、ワイプ表示する実験です。

 

グローバルJavaScript

function ggbOnInit() {
  insertCanvasOnlyCanvasTag();

  //usicanvasのイベントリスナを設定
  var usicanvas = document.getElementsByClassName('usicanvas')[0];
  var evt = window.ontouchstart===null?'touchmove':'mousemove';
  usicanvas.addEventListener(evt, scanImage);

//  document.getElementsByClassName('usicanvas')[0].hidden = true;  //usicanvasを非表示(GeoGebraオブジェクトをクリックできない問題を回避。

}

//マウス・指位置周辺のGV1ピクセルデータをusicanvasにコピー
function scanImage(evt){
  evt.preventDefault();

  //取得
  var canvas = document.getElementsByTagName('canvas')[0];
  var context = canvas.getContext('2d');
  var centerX = getEventData(evt)[0];
  var centerY = getEventData(evt)[1];
  var gv1ImageData = context.getImageData(centerX-40, centerY-40, 80, 80);

  //クリア
  var usicanvas = document.getElementsByClassName('usicanvas')[0];
  clearCanvas(usicanvas);

  //描画
  var usiContext = usicanvas.getContext('2d');
  usiContext.putImageData(gv1ImageData, 0, 0);

  //枠線
  usiContext.strokeStyle="blue";  //線の色を青に指定
  usiContext.strokeRect(0, 0, 80, 80);

}

//canvasを指定して、描画をクリア
function clearCanvas(canvas){
  if (canvas.getContext) {
    var context = canvas.getContext('2d');
    //描画をクリア
    context.clearRect(0, 0, canvas.width, canvas.height);
  }
}

function insertCanvasOnlyCanvasTag(){
  // 既存のcanvas要素を取得
  var preCanvas = document.getElementsByTagName('canvas')[0];

  // 既存のcanvasの(縮小前の)サイズを取得
  var canvasWidth = preCanvas.style.width;  //スタイルから取得。最初はテキスト+pxの形になっている。
  canvasWidth = eval( canvasWidth.slice( 0, canvasWidth.indexOf('px') ) );  //数字のみに加工
  var canvasHeight = preCanvas.style.height;
  canvasHeight = eval( canvasHeight.slice( 0, canvasHeight.indexOf('px') ) );

  // objListの要素をHTMLリスト化
  preCanvas.insertAdjacentHTML('afterend','<canvas height=\"'+canvasHeight+'\" width=\"'+canvasWidth+'\" dir=\"ltr\" tabindex=\"10000\" class=\"usicanvas\" style=\"position: absolute; right: 0px; bottom: 0px;\">UsiCanvas</canvas>');

}

//[現在のwidth, 本来のwidth, 現在のheight, 本来のheight]。GV1のcanvasに使用することを想定している。
function getCanvasSize(){
  var canvas = document.getElementsByTagName('canvas')[0];
  var canvasWidth = canvas.width;
  var styleWidth = canvas.style.width;  // '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  // 1234
  var canvasHeight = canvas.height;
  var styleHeight = canvas.style.height;  // '5678px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  // 5678
  return [canvasWidth, styleWidth, canvasHeight, styleHeight];
}

//イベント発生位置を取得
function getEventData(evt){
  evt.preventDefault();

  //発生したイベントのタイプをログに吐き出す
  console.log('eventType = '+evt.type);

  var result;  //結果格納用

  var canvas = document.getElementsByTagName('canvas')[0];  //グラフィックスビュー1を表示しているcanvasを取得
  var deviceType = window.ontouchstart===null?'touchDevice':'mouseDevice';  //タッチデバイスか、マウスデバイスかを判定

  //スケール調整用
  var canvasWidth = canvas.width;  //e.g. 1000
  var styleWidth = canvas.style.width;  //e.g. '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  //e.g. 1234
  var styleHeight = canvas.style.height;  //e.g. '1234px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  //e.g. 1234

  var ratio = canvasWidth / styleWidth;

  //タッチデバイス用演算(マウスデバイスと違い、offsetXYプロパティが取れないため、やや計算が複雑)
  if(deviceType == 'touchDevice'){
    //pageXY
    var touchX = evt.changedTouches[0].pageX;
    var touchY = evt.changedTouches[0].pageY;

    //canvasの位置
    var clientRect = canvas.getBoundingClientRect();
    var positionX = clientRect.left + window.pageXOffset;
    var positionY = clientRect.top + window.pageYOffset;

    //加減
    var calcX = touchX - positionX;
    var calcY = touchY - positionY;

    //スケール調整(オフセット値にそろえる)
    calcX = calcX * styleWidth / clientRect.width;
    calcY = calcY * styleHeight / clientRect.height;

    result = [calcX, calcY];
  }

  //マウスデバイス用演算
  if(deviceType == 'mouseDevice'){
    result = [evt.offsetX, evt.offsetY];
  }

  //スケール調整前、かつ、丸め前の値をログに吐き出す
  console.log('roughData = '+result);

  //スケール調整(現在のcanvasの大きさに合わせる)
  result[0] = ratio * result[0];
  result[1] = ratio * result[1];

  //丸め
  result[0] = Math.round( result[0] );
  result[1] = Math.round( result[1] );

  //スケール調整後、かつ、丸め後の値をログに吐き出す
  console.log('shapedData = '+result);

  return result;
}

(覚書)グラフィックスビュー1をcanvasとして分析する(PCデバイス前提)

(随時更新します)

 

バージョン

6.0.541.0-w

 

実験アプレット

https://www.geogebra.org/m/zg3be8kn

 

各種モードの区別

アプレットJavaScriptで、location.hrefの戻り値を取得する。

作成画面:geogebra.org/classic/ [ id ]

ワークシート画面:geogebra.org/m/ [ id ]

編集画面:geogebra.org/material/edit/id/ [ id ]

埋め込み時:geogebra.org/material/iframe/id/ [ id ] /width/...

//アプレットが、どのモード画面で開かれているかを、テキストで取得
function getAppletType(){
  var url = location.href;
  var e1 = url.split('/')[3];
  if(e1 == 'material'){e1 = url.split('/')[4];}
  return e1;  //作成画面→'classic'、ワークシート画面→'m'、編集画面→'edit'、埋め込み時→'iframe'
}

 

 

グラフィックスビュー1を表すcanvasエレメントを取得する

document.getElementsByTagName('canvas')[0];

で取得可能。

 

※以下、グラフィックスビュー1のことを、「GV1」と略記する場合がある。

 

※ただし、GV1以外のビューを、GV1と同時に表示している場合には、上記ではうまく取得できないかもしれない。以下では、GV1以外のビューが表示されていないことを前提とする。

 

※webページ埋め込み時に、コンソールで、当該webページを対象フレームとして、上記getElementsByTagNameを実行しても、undefinedが返される。

対象フレームは、www.geogebra.orgの「false」を選択すべし。

f:id:usiblog:20190603060322p:plain

 

埋め込み時の主な階層

iframe

#document

div class =  'wf-mathsans-n4-active wf-active'

body

div class = 'applet_container'

div class = 'applet_scaler ggbTransform'

div class = 'notranslate'

div class = 'GeoGebraFrame applet-unfocused jsloaded'

div class = 'gwt-SplitLayoutPanel splitterFixed'(1個目)

div class = 'gwt-SplitLayoutPanel splitterFixed'(2個目)

div class = 'ggbdockpanelhack'

div class = 'EuclidianPanel'

canvas

 

(参考)アプレットが格納されているiframeを取得する

function getGeoFrame(){
  var iframeArr = document.getElementsByTagName('iframe');
  for(var k=0; k<iframeArr.length; k++){
    if(iframeArr[k].outerHTML.indexOf('geogebra.org/material/iframe/') != -1){
      return iframeArr[k];
    }
  }
}

 

ワークシート画面の主な階層

 

編集画面の主な階層

 

ウインドウサイズとcanvas.width / height

アプレット作成画面:Corner[5]と、canvas.width, heightは一致

ワークシート画面:一致せず(ウインドウサイズが小さい場合、canvas.width, heightが可変)

編集画面:一致

埋め込み時:一致せず(ウインドウサイズが小さい場合、canvas.width, heightが可変)

 

ただし、タッチデバイスの場合、(ウインドウサイズが小さくなく、縮小の必要がない状態で)canvas.width, heightは、Corner[5]の各座標の2倍の値をもつ。

f:id:usiblog:20190606003124p:plain

 

現在のcanvasのサイズ、本来のcanvasのサイズを配列で取得

//[現在のwidth, 本来のwidth, 現在のheight, 本来のheight]。GV1のcanvasに使用することを想定している。
function getCanvasSize(){
  var canvas = document.getElementsByTagName('canvas')[0];
  var canvasWidth = canvas.width;
  var styleWidth = canvas.style.width;  // '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  // 1234
  var canvasHeight = canvas.height;
  var styleHeight = canvas.style.height;  // '5678px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  // 5678
  return [canvasWidth, styleWidth, canvasHeight, styleHeight];
}

 

座標→ピクセル座標

( (x(A) - x(Corner[4] + ( (x(Corner[2]) - x(Corner[1]) ) / (x(Corner[5]) + 2), (-(y(Corner[4]) - y(Corner[1]) ) ) / (y(Corner[5]) + 2) ) ) ) (x(Corner[5]) + 2) / (x(Corner[2]) - x(Corner[1] ) ), (y(Corner[4] + ( (x(Corner[2]) - x(Corner[1]) ) / (x(Corner[5]) + 2), (-(y(Corner[4]) - y(Corner[1]) ) ) / (y(Corner[5]) + 2) ) ) - y(A)) (y(Corner[5]) + 2) / (y(Corner[4]) - y(Corner[1]) ) )

 

ピクセル座標→座標

Corner[4] + ( (w + 1) (x(Corner[2]) - x(Corner[1]) ) / (x(Corner[5]) + 2), -(h + 1) (y(Corner[4]) - y(Corner[1]) ) / (y(Corner[5]) + 2) )

 

右下隅の点

Corner[2] + ( (-(x(Corner[2] ) - x(Corner[1] ) ) ) / (x(Corner[5] ) + 2), (y(Corner[4]) - y(Corner[1]) ) / (y(Corner[5]) + 2) ) 

この点のピクセル座標は、Corner[5]と一致する。

 

ウインドウサイズ拡大/縮小時の値の変化

Corner[5]:作成画面のみ可変

右下隅の点のピクセル座標:作成画面のみ可変

canvas.width, canvas.height:すべてのモードで可変

canvas.style.width, canvas.style.height:作成画面のみ可変

 

canvasのattributes変更を監視する

function startObserve(){
  var canvas = document.getElementsByTagName('canvas')[0];

  var mo = new MutationObserver(function(records){
    //GV1のattributesのうち、width, またはheight, またはstlyeが更新されたタイミングで、関数getCanvasSizeを実行し、キャンバスサイズを測定する。そして、usicanvasのwidth, heightを、GV1のstyle.width, style.heightの値にそろえ、描画をやり直す。
    if(records){
      var usiArr = [];
      for(var k=0; k < records.length; k++){
        usiArr[k] = records[k].attributeName;
      }
      if( usiArr.indexOf('width') != -1 || usiArr.indexOf('height') != -1 || usiArr.indexOf('style') != -1){
        var currentCanvasSize = getCanvasSize();
        console.log('now the sizeData of GraphicsView1 is ' + currentCanvasSize); 
        var usicanvas = document.getElementsByClassName('usicanvas')[0];
        if(usicanvas){
          usicanvas.width = currentCanvasSize[1];
          usicanvas.height = currentCanvasSize[3];
          clearCanvas(usicanvas);
          drawCanvas(usicanvas);
        }
      }
    }
  });

  var config = {attributes: true};
  mo.observe(canvas, config);
}

 

 

canvasに描画、描画をクリア

function drawCanvas(canvas){
  if (canvas.getContext) {
    var context = canvas.getContext('2d');
    //左からa1, 上からa2の位置に、幅a3, 高さa4の四角形を描く
    context.fillStyle = 'rgba(' + [0, 0, 255, 0.3] + ')';
    context.fillRect(0,0,250,200); 
  } 
}

function clearCanvas(canvas){
  if (canvas.getContext) {
    var context = canvas.getContext('2d');
    //描画をクリア
    context.clearRect(0, 0, canvas.width, canvas.height);
  }
}

GV1にdrawcanvasを実行しても、一瞬しか描画されない。

ウインドウサイズ拡大・縮小で、canvasのスケールが変わっても、描画結果自体もそれに合わせて拡大・縮小される。タッチデバイスでも同じ。

 

 

新たなcanvasの作成(方法A : 埋め込み時、編集画面にのみ有効)

一瞬しか描画されないのは困る。requestAnimationFrameで、フレームごとに描画命令を出しても良いが、処理が多く負荷がかかるので、避ける。

GV1と同じ大きさ、同じ位置に、新しいcanvas(以下ではusicanvasと呼ぶことにする)を設置して、そこに目的の描画を行う方向でいく。

function insertCanvas(){
  // 既存のcanvas要素を取得
  var preCanvas = document.getElementsByTagName('canvas')[0];

  // 既存のcanvasのサイズとcssスタイルを取得
  var canvasWidth = preCanvas.width;
  var canvasHeight = preCanvas.height;
  var canvasCss = preCanvas.style.cssText;

  // objListの要素をHTMLリスト化
  preCanvas.parentNode.insertAdjacentHTML('afterend','<div style="position: absolute; right: 0px; bottom: -4px;"><canvas height=\"'+canvasHeight+'\" width=\"'+canvasWidth+'\" dir=\"ltr\" tabindex=\"10000\" class=\"usicanvas\">UsiCanvas</canvas></div>');

  // usicanvasのスタイルを設定
  document.getElementsByClassName('usicanvas')[0].style.cssText = canvasCss;
}

埋め込み時には、ウインドウサイズ縮小に合わせて、GV1のcanvasのスケールが変わる。この場合には、それに合わせてusicanvasのスケールも変わるから、描画にズレは生じない。

ただし、usicanvas.width / height は、手動で値を更新しないと変わらない。

現在のwidth / height を取得したいときは、GV1のcanvasの方で測定すべし(attributes変更を監視)。

編集画面では、そもそもウインドウサイズにかかわらず、GV1のcanvasのスケールは変わらないから、描画のズレも、もちろん生じない。

※上記方法は、作成画面、ワークシート画面では、ズレが生じてしまう。試行錯誤の結果、すべてのモードにおいてズレが生じない、「方法B」を開発した(下記)。そこで、方法Aの開発は、いったんここで打ち切る。

 

新たなcanvasの作成(方法B : 全モードに完全対応)

function insertCanvasOnlyCanvasTag(){
  // 既存のcanvas要素を取得
  var preCanvas = document.getElementsByTagName('canvas')[0];

  // 既存のcanvasの(縮小前の)サイズを取得
  var canvasWidth = preCanvas.style.width;  //スタイルから取得。最初はテキスト+pxの形になっている。
  canvasWidth = eval( canvasWidth.slice( 0, canvasWidth.indexOf('px') ) );  //数字のみに加工
  var canvasHeight = preCanvas.style.height;
  canvasHeight = eval( canvasHeight.slice( 0, canvasHeight.indexOf('px') ) );

  // objListの要素をHTMLリスト化
  preCanvas.insertAdjacentHTML('afterend','<canvas height=\"'+canvasHeight+'\" width=\"'+canvasWidth+'\" dir=\"ltr\" tabindex=\"10000\" class=\"usicanvas\" style=\"position: absolute; right: 0px; bottom: 0px;\">UsiCanvas</canvas>');

}

関数名どおり、canvasタグのみを挿入する。divタグは作らず、GV1のcanvasを包んでいるdivの子要素として、canvasを作成する。

style.position はabsoluteで、right, bottomともに0pxである。

この設定では、作成画面以外のモードで、描画のズレは起こらない。

作成画面では、いったん描画したのち、ウインドウサイズを変更すると、ズレが起こる。これは、他のモードと違い、作成画面では、GV1のcanvasにおけるcanvas.style.width および canvas.style.height が、ウインドウサイズの変更にあわせて変化するからだと思われる。

f:id:usiblog:20190608025539g:plain

そこで、GV1のcanvasのattributesを監視して、canvas.style.width またはcanvas.style.height 変更のタイミングで、usicanvas.width, usicanvas.heightを、変更後の canvas.style.width, canvas.style.heightの値に揃えることにした。

なお、usicanvas.width / height をいじると、描画がリセットされるので(canvasの仕様のようだ)、いじるたびに描画をやり直す必要がある。

上記操作については、上記startObserve 関数内のコールバック関数の記述を参照。

 

usicanvasの表示・非表示

document.getElementsByClassName('usicanvas')[0].hidden = true;  //非表示

document.getElementsByClassName('usicanvas')[0].hidden = false;  //表示

 ggbOnInit内で、usicanvasを作成して、そのままにしておくと、GV1上のGeoGebraオブジェクトに対するマウス操作が効かなかった。

そこで、usicanvas作成後は、ただちにusicanvasを非表示にして、マウス操作を可能にした(下記 ggbOnInit 関数参照)。

ボタン等でdrawCanvas 関数を実行したタイミングで、usicanvasを表示にすれば、以降のマウス操作は可能のようである。

したがって、アプレット起動時のみ、usicanvasは非表示にしておき、その後、GeoGebraオブジェクトのイベントハンドラから、usicanvasを表示にすれば、この問題は回避できる。

 

実験アプレットにおけるグローバルJavaスクリプトの内容

以上のまとめ。

function ggbOnInit() {
  console.log( getAppletType() );

  startObserve();

  insertCanvasOnlyCanvasTag();

  document.getElementsByClassName('usicanvas')[0].hidden = true;  //usicanvasを非表示(GeoGebraオブジェクトをクリックできない問題を回避。drawCanvas実行時に表示にすればOK)

  console.log('ggbOnInit done.');
}

//アプレットが、どのモード画面で開かれているかを、テキストで取得
function getAppletType(){
  var url = location.href;
  var e1 = url.split('/')[3];
  if(e1 == 'material'){e1 = url.split('/')[4];}
  return e1;  //アプレット作成画面→'classic'、ワークシート画面→'m'、編集画面→'edit'、埋め込み時→'iframe'
}

//[現在のwidth, 本来のwidth, 現在のheight, 本来のheight]。グラフィックスビューのcanvasに使用することを想定している。
function getCanvasSize(){
  var canvas = document.getElementsByTagName('canvas')[0];
  var canvasWidth = canvas.width;
  var styleWidth = canvas.style.width;  // '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  // 1234
  var canvasHeight = canvas.height;
  var styleHeight = canvas.style.height;  // '5678px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  // 5678
  return [canvasWidth, styleWidth, canvasHeight, styleHeight];
}

//canvasを指定して描画
function drawCanvas(canvas){
  if (canvas.getContext) {
    var context = canvas.getContext('2d');
    //左からa1, 上からa2の位置に、幅a3, 高さa4の四角形を描く
    context.fillStyle = 'rgba(' + [0, 0, 255, 0.3] + ')';
    context.fillRect(0,0,250,200); 
  } 
}

//canvasを指定して、描画をクリア
function clearCanvas(canvas){
  if (canvas.getContext) {
    var context = canvas.getContext('2d');
    //描画をクリア
    context.clearRect(0, 0, canvas.width, canvas.height);
  }
}

//GV1のcanvasのattributes変更を監視
function startObserve(){
  var canvas = document.getElementsByTagName('canvas')[0];

  var mo = new MutationObserver(function(records){
    //GV1のattributesのうち、width, またはheight, またはstlyeが更新されたタイミングで、関数getCanvasSizeを実行し、キャンバスサイズを測定する。そして、usicanvasのwidth, heightを、GV1のstyle.width, style.heightの値にそろえ、描画をやり直す。
    if(records){
      var usiArr = [];
      for(var k=0; k < records.length; k++){
        usiArr[k] = records[k].attributeName;
      }
      if( usiArr.indexOf('width') != -1 || usiArr.indexOf('height') != -1 || usiArr.indexOf('style') != -1){
        var currentCanvasSize = getCanvasSize();
        console.log('now the sizeData of GraphicsView1 is ' + currentCanvasSize); 
        var usicanvas = document.getElementsByClassName('usicanvas')[0];
        if(usicanvas){
          usicanvas.width = currentCanvasSize[1];
          usicanvas.height = currentCanvasSize[3];
          clearCanvas(usicanvas);
          drawCanvas(usicanvas);
        }
      }
    }
  });

  var config = {attributes: true};
  mo.observe(canvas, config);
}

//GV1のcanvasにちょうど重なるようにusicanvasを生成
function insertCanvasOnlyCanvasTag(){
  // 既存のcanvas要素を取得
  var preCanvas = document.getElementsByTagName('canvas')[0];

  // 既存のcanvasの(縮小前の)サイズを取得
  var canvasWidth = preCanvas.style.width;  //スタイルから取得。最初はテキスト+pxの形になっている。
  canvasWidth = eval( canvasWidth.slice( 0, canvasWidth.indexOf('px') ) );  //数字のみに加工
  var canvasHeight = preCanvas.style.height;
  canvasHeight = eval( canvasHeight.slice( 0, canvasHeight.indexOf('px') ) );

  // objListの要素をHTMLリスト化
  preCanvas.insertAdjacentHTML('afterend','<canvas height=\"'+canvasHeight+'\" width=\"'+canvasWidth+'\" dir=\"ltr\" tabindex=\"10000\" class=\"usicanvas\" style=\"position: absolute; right: 0px; bottom: 0px;\">UsiCanvas</canvas>');

}

 

canvasピクセルデータ(RGBA情報が格納された配列)を取得する

//(参考関数)canvasのピクセル配列を取得
function getPixelArray(canvas){
  var context = canvas.getContext('2d');

  var styleWidth = canvas.style.width;  // '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  // 1234
  if(!styleWidth){styleWidth = canvas.width;}  //style.widthがない場合(usicanvasに対して実行時)を想定
  var styleHeight = canvas.style.height;  // '5678px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  // 5678
  if(!styleHeight){styleHeight = canvas.height;}  //style.heightがない場合(usicanvasに対して実行時)を想定

  var imageData = context.getImageData(0, 0, styleWidth, styleHeight);
  var pixelArray = imageData.data;
  return pixelArray;
}

 

ピクセル通し番号→GV1における座標

ピクセル通し番号:GV1の左上を0として、横書きの要領で走査し、右下隅のwidth*height-1に至る。

//ピクセル通し番号(0〜width*height-1)に対応する、グラフィックスビュー1における座標[x,y]を取得
function getCoordsFromPixelIndex(pixelIndex){
  var canvas = document.getElementsByTagName('canvas')[0];

  var styleWidth = canvas.style.width;  // '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  // 1234
  var styleHeight = canvas.style.height;  // '5678px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  // 5678

  //ループ処理
  pixelIndex = pixelIndex % ( styleWidth * styleHeight );

  var pixelX = pixelIndex % styleWidth;
  var pixelY = Math.floor(pixelIndex / styleWidth);

  var gvXleftUp = ggbApplet.getValue('x(Corner[4]) + ('+pixelX+' + 1) (x(Corner[2]) - x(Corner[1])) / (x(Corner[5]) + 2)');
  var gvXrightDown = ggbApplet.getValue('x(Corner[4]) + ('+pixelX+' + 2) (x(Corner[2]) - x(Corner[1])) / (x(Corner[5]) + 2)');
  var gvX = (gvXleftUp + gvXrightDown) / 2;

  var gvYleftUp = ggbApplet.getValue('y(Corner[4]) - ('+pixelY+' + 1) (y(Corner[4]) - y(Corner[1])) / (y(Corner[5]) + 2)');
  var gvYrightDown = ggbApplet.getValue('y(Corner[4]) - ('+pixelY+' + 2) (y(Corner[4]) - y(Corner[1])) / (y(Corner[5]) + 2)');
  var gvY = (gvYleftUp + gvYrightDown) / 2

  return [gvX, gvY];

}

 

HSLA配列[H 0-1, S 0-1, L 0-1, A 0-1]を、RGBA配列[R 0-255, G 0-255, B 0-255, A 0-255]に変換

//hsla配列(0〜1)をrgba配列(0〜255)に変換
function getRGBAfromHSLA(hslaArray){
  var H = hslaArray[0] * 360;
  var S = hslaArray[1] * 100;
  var L = hslaArray[2] * 100;

  H = H % 360;
  var arr = [];
  if(L <= 49){
    var MAX = 2.55 * (L + L * (S / 100));
    var MIN = 2.55 * (L - L * (S / 100));
  }
  if(L >= 50){
    var MAX = 2.55 * (L + (100 - L) * (S / 100));
    var MIN = 2.55 * (L - (100 -L) * (S / 100));
  }
  if(H < 60){
    arr[0] = MAX;
    arr[1] = (H / 60) * (MAX - MIN) + MIN;
    arr[2] = MIN;
  }
  if(60 <= H && H < 120){
    arr[0] = ((120 - H) / 60) * (MAX - MIN) + MIN;
    arr[1] = MAX;
    arr[2] = MIN;
  }
  if(120 <= H && H < 180){
    arr[0] = MIN;
    arr[1] = MAX;
    arr[2] = ((H - 120) / 60) * (MAX - MIN) + MIN;
  }
  if(180 <= H && H < 240){
    arr[0] = MIN;
    arr[1] = ((240 - H) / 60) * (MAX - MIN) + MIN;
    arr[2] = MAX;
  }
  if(240 <= H && H < 300){
    arr[0] = ((H - 240) / 60) * (MAX - MIN) + MIN;
    arr[1] = MIN;
    arr[2] = MAX;
  }
  if(300 <= H && H < 360){
    arr[0] = MAX;
    arr[1] = MIN;
    arr[2] = ((360 - H) / 60) * (MAX - MIN) + MIN;
  }

  arr[0] = Math.round(arr[0]);
  arr[1] = Math.round(arr[1]);
  arr[2] = Math.round(arr[2]);
  arr[3] = hslaArray[3] * 255;

  return arr;
}

ピクセル操作の際には、RGBA配列データ(全要素とも0〜255)が必要になる。個人的には、色指定はHSLAの方が考えやすいので、上記関数を作成した次第である。

 

HSLA配列を用いて、指定したピクセル通し番号に対応するピクセルを操作

//pixelIndexに対応するピクセルに、hslaでカラーセット
function setPixelColorByHSLA(canvas, pixelIndex, hslaArray){
  var context = canvas.getContext('2d');

  //canvasのwidth,heightを取得
  var styleWidth = canvas.style.width;  // '1234px'
  styleWidth = eval( styleWidth.slice( 0, styleWidth.indexOf('px') ) );  // 1234
  if(!styleWidth){styleWidth = canvas.width;}  //style.widthがない場合(usicanvasに対して実行時)を想定
  var styleHeight = canvas.style.height;  // '5678px'
  styleHeight = eval( styleHeight.slice( 0, styleHeight.indexOf('px') ) );  // 5678
  if(!styleHeight){styleHeight = canvas.height;}  //style.heightがない場合(usicanvasに対して実行時)を想定

  //ループ処理
  pixelIndex = pixelIndex % ( styleWidth * styleHeight );

  //pixelIndexに対応するピクセル座標を取得
  var pixelX = pixelIndex % styleWidth;
  var pixelY = Math.floor(pixelIndex / styleWidth);

  //ピクセルデータを取得
  var imageData = context.getImageData(0, 0, styleWidth, styleHeight);
  var pixelArray = imageData.data;

  //HSLAをRGBAに変換
  var rgbaArray = getRGBAfromHSLA(hslaArray);

  //ピクセルデータに書き込み
  var base = (pixelY * styleWidth + pixelX) * 4;
  pixelArray[base + 0] = rgbaArray[0];  // Red
  pixelArray[base + 1] = rgbaArray[1];  // Green
  pixelArray[base + 2] = rgbaArray[2];  // Blue
  pixelArray[base + 3] = rgbaArray[3];  // Alpha

  // キャンバスに反映
  context.putImageData(imageData, 0, 0);
}

 

ggbApplet APIを用いて、usicanvasのピクセルを操作

//グローバル変数
var currentIndex = 0;
//ggbAppletを用いながら、usicanvasのピクセルを操作
function drawUsiCanvasWithGgbApplet(){

  //usicanvasを取得
  var usicanvas = document.getElementsByClassName('usicanvas')[0];

  //pixelIndexに対応する、アプレット上の座標を取得
  var gvX = getCoordsFromPixelIndex(currentIndex)[0];
  var gvY = getCoordsFromPixelIndex(currentIndex)[1];

  //H
  var H = ggbApplet.getValue('Length[('+gvX+','+gvY+')]');

  //S
  var S = 1;

  //L
  var L = 0.5;

  //A
  var A = 0.6;

  var hslaArray = [H,S,L,A];

  //描画
  setPixelColorByHSLA(usicanvas, currentIndex, hslaArray);

  //グローバル変数currentIndexを更新
  currentIndex++;
}

すべてのピクセルについて、ggbApplet.getValueを実行すると、処理がとても重い。

そこで、ggbApplet.getValueを使うのは、GV1左上の座標情報と、1ピクセルの、GV1における大きさ情報を取得するときだけにしたい(グローバル変数扱い)。

その他のピクセルの、GV1における座標情報の取得や、それを用いた数式処理は、Math オブジェクトを活用するなどして、できるだけggbApplet APIを使わない方向で設計すべきであろう。