pirika logo

ホームページ Pirikaで化学 ブログ 業務案内 お問い合わせ
情報化学+教育トップ 情報化学 MAGICIAN MOOC プログラミング
高校生レベルのプログラミングはMAGICIAN-Jrへどうぞ。
フィードバックはお問い合わせフォームからお願いします。

プログラミング・トップ > 化学,薬学系の親子で楽しみながらプログラミング >第2時限:美術

2022.2.27

2-5-1: ゼロからお絵描きが大変なら

著作権の問題があるので、人の作った絵や写真を勝手に使うのは問題があります。
その時には参考にしたい絵や写真を読み込んで、それをベースに、オリジナルとは言えなくても、2次著作を作りましょう。
イラストのプロだって、最初はそうやって練習していくものです。

楽しく創作しましょう。

実際にやってみましょう

下絵ファイル:
透明度

PNG画像

イメージを読み込むプログラムはNo2-6を参照してください。

ここでのお絵かきは320*320のキャンバス相手になります。
そこで、読み込んだ画像のサイズによっては拡大・縮小をしなければなりません。
読み込む先は320*320なので、縦横ともにそれより小さい場合、横だけが320より大きい場合、縦だけが大きい場合、両方とも大きい場合には、横が縦より大きい場合とその逆の場合の5種類について変換する式を作ります。
慣れないうちは、この場合分けがプログラムの一番難しい部分になります。

下絵ファイル:<input type="file" accept="image/jpeg,image/png,image/gif" onchange="fileup(this)" /><br>

const fileup = (e) => {
    const img = document.getElementById('img');
    const reader = new FileReader();
    const imgReader = new Image();
   reader.onloadend = () => {
      imgReader.onload = () => {
      
        const imgType = imgReader.src.substring(5, imgReader.src.indexOf(';'));
        
        var imgWidth;//イメージの横幅
        var imgHeight;//イメージの縦幅
        var Lx;//キャンバス中の左上のx位置
        var Ly;//キャンバス中の左上のy位置
        
        if(imgReader.height<=320 && imgReader.width<=320){
            Lx=(320-imgReader.width)/2;
            Ly=(320-imgReader.height)/2;
            imgWidth=imgReader.width+Lx;
            imgHeight=imgReader.height+Ly;
        }
        if(imgReader.height>320 && imgReader.width<320){
            Lx=(320-imgReader.width*320/imgReader.height)/2;
            Ly=0;
            imgWidth=imgReader.width*320/imgReader.height+Lx;
            imgHeight=320;
        }
        if(imgReader.height<320 && imgReader.width>320){
            Lx=0;
            Ly=(320-imgReader.height*320/imgReader.width)/2;
            imgWidth=320;
            imgHeight=imgReader.height*320/imgReader.width+Ly;
        }
        if(imgReader.height>320 && imgReader.width>320 && imgReader.height>imgReader.width){
            Lx=(320-imgReader.width*320/imgReader.height)/2;
            Ly=0
            imgWidth=imgReader.width*320/imgReader.height+Lx;
            imgHeight=320
        }
        if(imgReader.height>320 && imgReader.width>320 && imgReader.height<=imgReader.width){
            Lx=0;
            Ly=(320-imgReader.height*320/imgReader.width)/2;
            imgWidth=320;
            imgHeight=imgReader.height*320/imgReader.width+Ly;
        }
        clearCanvas();
        ctx.globalAlpha = Toumei;
        ctx.drawImage(imgReader,Lx,Ly,imgWidth,imgHeight);
        img.src = canvas.toDataURL(imgType);
        

      }
      imgReader.src = reader.result;
    }
    reader.readAsDataURL(e.files[0]);
  }

そして、一旦clearCanvasでキャンバスを消去してから、読み込んだ画像をctx.drawImageで描画します。
その前に、ctx.globalAlphaでアルファ・チャンネルを指定します。これは、どのくらいの透明度でキャンバスに描くかを指定していることになります。

この透明度はスライダーで(0.1〜1.0)設定します。<body>タグにスライダーを描く部分と、<script>タグの中にスライダーから値を取り出す部分のプログラムを書きます。

<body>
//スライダーとスライダーの値を描く
透明度<input id="slider1" type="range"  min="0.1" max="1.0" step="0.1" oninput="this.nextSibling.value = this.value" onchange="this.nextSibling.value = this.value" /><input id="Xval" type="text" size="3" value="0.5" />
<br>

<script>

var Toumei=0.5;//下絵の透明度初期値

var S1Slider = document.getElementById('slider1');//slider1のオブジェクトを取り出す。
S1Slider.addEventListener('input', inputChange);//変化(イベント)が起きたら、inputChange関数を呼ぶ。

function inputChange(event){//スライダーに変化があったら呼ばれる。
  Toumei=parseFloat(S1Slider.value);//Slider.valueは文字列なので、paraseFloat関数で実数に変換する。
}

これで、下絵を参考にしながら絵を描くことができるようになりました。
ただ、1つのキャンバスに描いているので、消すのが面倒かもしれません。
この面倒という感覚が大事です。
面倒だから、改良したくなるのです。そこでプログラムを覚えるのです。

差し当たっては面倒なまま使って見ましょう。
どうしても、先を考えたい人へのアドバイスは、下絵を描くキャンバスと自分で描くキャンバスを2枚重ねにするのです。これはレイヤー(層)を作るという言い方になります。

次回はその辺りの作り方を説明していきましょう。

まずは次のプログラムをHogeHoge.htmlとセーブして遊んでください。

完成版プログラム(▶︎をクリックして開く)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Draw</title>
  </head>
  <body>
  
下絵ファイル:<input type="file" accept="image/jpeg,image/png,image/gif" onchange="fileup(this)" /><br>
透明度<input id="slider1" type="range"  min="0.1" max="1.0" step="0.1" oninput="this.nextSibling.value = this.value" onchange="this.nextSibling.value = this.value" /><input id="Xval" type="text" size="3" value="0.5" />
<br>


<div style="text-align:center;">
<canvas id="testCanvas" width="320" height="320" style="border:1px solid #CCC;"></canvas>
</div>
<div style="text-align:center;font-size:12px;">
    <input name="mode" type="radio" value="0" id="rb0" onclick="changeLineL(1)"><label for="rb0">極細</label>
        <input name="mode" type="radio" value="1" id="rb1" onclick="changeLineL(2)"><label for="rb1">細</label>
        <input name="mode" type="radio" value="2" id="rb2" onclick="changeLineL(4)"><label for="rb2">中</label>
        <input name="mode" type="radio" value="3" id="rb3" onclick="changeLineL(6)"><label for="rb3">太</label>
</div>
<div style="text-align:center;font-size:12px;">
<label><input type="color" id="colorBox" value="#CC0000">色の選択</label>
</div>
<div style="text-align:center;font-size:12px;">
  <input type="checkbox" id="mycheckbox" name="scales" ><label for="scales">消しゴム</label>
</div>


<p style="text-align:center;">
        <input type="button" onclick="addImageData();" value="PNG画像作成">
        <input type="button" onclick="clearCanvas();" value="画面をクリア">
    </p>
<div style="text-align:center;">
        <img src="" alt="PNG画像" width="320" height="320" id="image_png" style="border:1px solid #CCC;">
</div>

<script>
//キャンバスの初期処理
var canvas = document.getElementById('testCanvas');
//2Dコンテキスト
var ctx = canvas.getContext('2d');

init();
addImageData();
    //
var rb = document.getElementById('rb0');
rb.checked = true;
var lineL = 1;  //線の太さ初期値


var cW = 320;   //キャンバス横サイズ
var cH = 320;   //キャンバス縦サイズ
var touchM=0;//PC=0
var mouseX = new Array();//finger touch
var mouseY = new Array();
var mouseDownFlag = false;  //マウスダウンフラグ

var EraserMode=0;

var MyColor;
var colorB = document.getElementById('colorBox');

colorB.addEventListener('change', function(){
  MyColor = this.value;
});




function init() {

    canvas.addEventListener('mousedown', function(e) {

        mDown(pos(e));
    });
    canvas.addEventListener('mouseup', function(e) {
        mUp(pos(e));
    });

    canvas.addEventListener('mousemove', function(e) {

        mMove(pos(e));
    });
    canvas.addEventListener('touchstart', function(e) {
        if (e.changedTouches.length == 1) {
            mDown(pos(e.changedTouches[0]));
        }
    });

    canvas.addEventListener('touchend', function(e) {
        if (e.changedTouches.length == 1) {
            mUp(pos(e.changedTouches[0]));
        }
    });
    canvas.addEventListener('touchmove', function(e) {
        e.preventDefault();
        if (e.changedTouches.length == 1) {
            mMove(pos(e.changedTouches[0]));
        }
    });
    canvas.addEventListener('touchcancel', function(e) {
        mOut();
    });
}




function mOut(e) {
    mouseDownFlag=false;
}


//座標調整
function pos(e) {
    var x, y;

        x = e.clientX - canvas.getBoundingClientRect().left;
        y = e.clientY - canvas.getBoundingClientRect().top;
    
    mouseX[0] =x;
    mouseY[0] =y;
    return [x, y];
}


function mUp(pos) {
    mouseDownFlag =false;
            
}

function mDown(pos) {
    event.preventDefault();
    mouseDownFlag = true;
    
    MyColor=colorBox.value;
            var element = document.getElementById('mycheckbox');
                if(element.checked){
                    EraserMode=1;
                }
                else{
                    EraserMode=0;
             }
  
    
}

function mMove(pos) {
    event.preventDefault();
    if (mouseDownFlag) {

            //座標調整
            //adjustXY(e);
            //円を描く
            ctx.globalAlpha =1.0;
            ctx.beginPath();
            
            if(EraserMode==0){
                ctx.fillStyle = MyColor;
                ctx.arc(pos[0],pos[1], lineL , 0, Math.PI * 2, false);
            }
            else{
                ctx.clearRect(pos[0],pos[1], lineL ,lineL);
            }
            ctx.fill();
        }

}

//透明度スライダー処理
var S1Slider = document.getElementById('slider1');//slider1のオブジェクトを取り出す。
S1Slider.addEventListener('input', inputChange);//変化(イベント)が起きたら、inputChange関数を呼ぶ。

var Toumei=0.5;//下絵の透明度
function inputChange(event){//スライダーに変化があったら呼ばれる。
  Toumei=parseFloat(S1Slider.value);//Slider.valueは文字列なので、paraseFloat関数で実数に変換する。
}

//下絵を読み込むと実行される。
const fileup = (e) => {
    const img = document.getElementById('img');
    const reader = new FileReader();
    const imgReader = new Image();
    //const imgWidth320 = 320;
    reader.onloadend = () => {
      imgReader.onload = () => {
      
        const imgType = imgReader.src.substring(5, imgReader.src.indexOf(';'));
        
        var imgWidth;
        var imgHeight;
        var Lx;
        var Ly;
        
        if(imgReader.height<=320 && imgReader.width<=320){
            Lx=(320-imgReader.width)/2;
            Ly=(320-imgReader.height)/2;
            imgWidth=imgReader.width+Lx;
            imgHeight=imgReader.height+Ly;
        }
        if(imgReader.height>320 && imgReader.width<320){
            Lx=(320-imgReader.width*320/imgReader.height)/2;
            Ly=0;
            imgWidth=imgReader.width*320/imgReader.height+Lx;
            imgHeight=320;
        }
        if(imgReader.height<320 && imgReader.width>320){
            Lx=0;
            Ly=(320-imgReader.height*320/imgReader.width)/2;
            imgWidth=320;
            imgHeight=imgReader.height*320/imgReader.width+Ly;
        }
        if(imgReader.height>320 && imgReader.width>320 && imgReader.height>imgReader.width){
            Lx=(320-imgReader.width*320/imgReader.height)/2;
            Ly=0
            imgWidth=imgReader.width*320/imgReader.height+Lx;
            imgHeight=320
        }
        if(imgReader.height>320 && imgReader.width>320 && imgReader.height<=imgReader.width){
            Lx=0;
            Ly=(320-imgReader.height*320/imgReader.width)/2;
            imgWidth=320;
            imgHeight=imgReader.height*320/imgReader.width+Ly;
        }
        
        clearCanvas();
        ctx.globalAlpha = Toumei;
        ctx.drawImage(imgReader,Lx,Ly,imgWidth,imgHeight);
        //img.src = canvas.toDataURL(imgType);

      }
      imgReader.src = reader.result;
    }
    reader.readAsDataURL(e.files[0]);
  }
  


//画像変換
function addImageData() {
    //キャンバスの初期処理
    var canvas = document.getElementById('testCanvas');
    if ( ! canvas || ! canvas.getContext ) return false;
    try {
        var img_png_src = canvas.toDataURL();
        document.getElementById("image_png").src = img_png_src;
        
    } catch(e) {
        document.getElementById("image_png").alt = "未対応";
    }
}
//線の太さ
function changeLineL(n) {
    lineL = n;
}

//クリア
function clearCanvas() {
    //キャンバスの初期処理
    var canvas = document.getElementById('testCanvas');
    if ( ! canvas || ! canvas.getContext ) return false;
    //2Dコンテキスト
    var ctx = canvas.getContext('2d');
    
    ctx.clearRect(0, 0, cW, cH);
    //画像描画
    addImageData();
}

</script>
  </body>
</html>

プログラミング・トップ > 化学,薬学系の親子で楽しみながらプログラミング


Copyright pirika.com since 1999-
Mail: yamahiroXpirika.com (Xを@に置き換えてください) メールの件名は[pirika]で始めてください。