HOME > 絵を描く

06.絵を描く


06.01 絵を描くプログラムを作成する

Windowsのプログラムを作ったことがないGGEにとっての最大の問題が最初にやってきました。それは、

  「windows内にどうやって絵を描くのか?

これをクリアしない限りwindows内に動くものなど夢物語となってしまいます。 

さて!そもそもコンピュータというのはどのようにディスプレイに表示するかを説明しておきましょう。

そもそもコンピュータディスプレイは複数の点々(以降ドット)の集まりでできています。そのドットと1対1の関係で内部にVRAM(ビデオRAM)というメモリに対応しています。
このVRAMには赤・青・緑(略してRGB)の色の情報を3バイト持っている三層構造をしています。つまり3つの色情報が集まって1つのドットを構成しているわけです。従ってフルHDの場合1920(横)×1080(縦)≒207万個、4Kの場合は3840×2160≒829万個のドット情報に加え、RGBが256段階に分けるため256×256×256=1670万色を表示できます。したがって1ドットに対し3バイトの色情報なので、フルHDの場合6メガバイト、4Kで24メガバイトの情報をリアルタイムに高速で処理します。しかし いくら速いCPUでも常時数メガのメモリー転送を行うと常時負荷がある程度かかるようになります。このため最近ではGPUというラフィック専用プロセッサを搭載するものがあります。少々前だとグラフィックボードとして拡張しましたが、最近ではさらに高速化を図る為CPU構造の内部に併設してGPUを持っているものもあります。

このように仕組みが分かってくると大昔であれば画面に高速で描画する場合は、OSを介さずに直接VRAMへ出力したものですが、現在はどうするかというと.NetFrameworkに描画モジュールが用意されていて、仮想のキャンバスに描画し最後にキャンバスイメージを出力します。
但し多分描画方法は、この方法以外にも多数あると予想されますが、描画速度を速くするのも勉強の1つであるため順次勉強して進化していきたいと考えていきたいと思います。

では前回作成したHellowと同じ手順で”Prototype”という名前のソリューションを作成してください。
そしてボタンコントロールを追加しダブルクリックしてイベントハンドラーを作成した後に、次にように内容を書き換えてください。


#pragma once
namespace ProtoType {

  using namespace System;
  using namespace System::ComponentModel;
  using namespace System::Collections;
  using namespace System::Windows::Forms;
  using namespace System::Data;
  using namespace System::Drawing;

  /// 
  /// MyForm の概要
  /// 
  public ref class MyForm : public System::Windows::Forms::Form
  {
  public:
    Graphics^      gf;
    Bitmap^       canvas;

    MyForm(void)
    {

      InitializeComponent();
      //
      //TODO: ここにコンストラクター コードを追加します
      //
      //pictureBoxを対象とするビットマップImageオブジェクトを作成する
      canvas = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height);
      //ImageオブジェクトのGraphicsオブジェクトを作成する
      gf = Graphics::FromImage(canvas);
    }

  protected:
    /// 
    /// 使用中のリソースをすべてクリーンアップします。
    /// 
    ~MyForm()
    {
      if (components)
      {
        delete components;
      }
    }
  private: System::Windows::Forms::Button^ button1;
  private: System::Windows::Forms::PictureBox^ pictureBox1;
  private: System::ComponentModel::IContainer^ components;
  protected:
  private:
    /// 
    /// 必要なデザイナー変数です。
    /// 


#pragma region Windows Form Designer generated code
    /// 
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// 
    void InitializeComponent(void)
    {
      this->button1 = (gcnew System::Windows::Forms::Button());
      this->pictureBox1 = (gcnew System::Windows::Forms::PictureBox());
      (cli::safe_cast(this->pictureBox1))->BeginInit();
      this->SuspendLayout();
      //
      // button1
      //
      this->button1->Location = System::Drawing::Point(12, 12);
      this->button1->Name = L"button1";
      this->button1->Size = System::Drawing::Size(41, 35);
      this->button1->TabIndex = 0;
      this->button1->Text = L"start";
      this->button1->UseVisualStyleBackColor = true;
      this->button1->Click += gcnew System::EventHandler(this, &MyForm::button1_Click);
      //
      // pictureBox1
      //
      this->pictureBox1->Location = System::Drawing::Point(68, 11);
      this->pictureBox1->Name = L"pictureBox1";
      this->pictureBox1->Size = System::Drawing::Size(1200, 700);
      this->pictureBox1->TabIndex = 1;
      this->pictureBox1->TabStop = false;
      //
      // MyForm
      //
      this->AutoScaleDimensions = System::Drawing::SizeF(6, 12);
      this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
      this->ClientSize = System::Drawing::Size(1350, 729);
      this->Controls->Add(this->pictureBox1);
      this->Controls->Add(this->button1);
      this->Name = L"MyForm";
      this->Text = L"1.3.0 BitmapImage";
      (cli::safe_cast(this->pictureBox1))->EndInit();
      this->ResumeLayout(false);

    }
#pragma endregion
/************************************************************************
*  リンク機構間に線を描画する                     *
************************************************************************/
  private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e)
  {
    Pen^      pen;

    pen = gcnew Pen(Color::Red, 3);

//座標(10,20)に100x80の長方形を描く
    gf->DrawRectangle(pen, 10, 20, 100, 80);
//四角の内側に楕円を描く
    gf->DrawEllipse(Pens::Blue, 110, 120, 200, 280);

    pictureBox1->Image = canvas;
  }

};
}



★ビットマップイメージ
windowsのアクセサリの中に最初から入っているアプリの中にペイントという描画ソフトがありますが、最終的に保存するときに、「.BMP」という拡張子をつけると思います。これカタカナ読みすると「ビットマップ拡張子」といいます。壁紙なんかも、この形式でないとNGというのもあってご存知の方も多いと思いますが、このビットマップとはなにかご存知?

このビットマップこそ、先に説明した3階層になったVRAMへ書き出すドットの集まりを指します。このため画像ファイルの容量を比べてみるとBMPは非常に大きくJPGやGIF等々は小容量です。これはJPG・GIFは「圧縮」という技術を使用しているためで、圧縮技術は複数もある為それによりJPG・GIFなど種類がいくつか存在しているわけです。
話を元に戻しますが、圧縮がされていない着色された生ドットデータ列を指すのはファイルだけではなく、パソコン内でもVRAMへ転送する前の状態は同様にビットマップを持っているわけで、一般的に「ビットマップイメージ」などといいます。


★クラスのオブジェクト指向の.Netプログラム
爺になってくると、例え元プログラマーであっても未知の世界の横文字は中々覚えられないもので困ります。
ここでちょっと横文字でも書いておかねばならない箇所が出ましたのでご説明します。

1)名前空間
プロジェクト名を決めるとソリューションエクスプローラに表示されますが、プログラムの先頭にも下記のように表示されます。これを「名前空間」という名前で呼びます。これは大昔であればすべてのシステムをすべて自分で作成しますので問題ありませんが、今回使用する.NetFrameworkも含め他人が作成したものを借りて作成していくことになります。この時例えば「name」「file」といったような名称は、いろいろな方が既に使用している可能性があるじゃないですかぁ。しかし全ての人が全く別々の名前を使用するというのも難しいために、「自分のテリトリー」を作って、その中だけしか使用できないことになっています。このような「自分のテリトリー」のことを「名前空間」と呼び下記のように表記します。

namespace prototype

この名前空間(namespace)でいうと例えばVS2017上のデバッグウィンドウへ表示するためのメソッド(方法)としてWriteLine()が用意されていますが、このメソッド本当のありかは「System::Diagnostics::Debug::WriteLine」で「System::Diagnostics」の名前空間上にあります。(ちなみに「::」はスコープ演算子といい名前空間の守備範囲を示していますが、多言語の場合ドット演算子「.」が用いられますので注意してください)しかし、頻度があるメソッドの場合長い名前をいちいち書くのは面倒なので、”using”という宣言により名前空間名を省略することができます。(下記を記入した場合だとDebug::writeLine()でよい)
using namespace System::Diagnostics;



2)クラスの宣言
最初に作成した時に下記のようなものが先頭につきますが、これを「クラス名」といいます。詳細は後日説明いたします。
public ref class MyForm : public System::Windows::Forms::Form

 

3)コンストラクターとディストラクター
プロジェクトを新規作成すると下記のような2つの関数が自動的に作成されます。一体何か?
これはウィンドウズ上でプログラムが起動すると最初にクラス名と同じ名前の関数①が必ず呼ばれます。このようなクラス生成初期化時処理のことを「コンストラクタ」といいます。逆に”X"をクリックされるか終了動作によりアプリのウィンドウを閉じる場合、使用してきたメモリ領域のすべてを自動的に開放する動作=「ガベージコレクション」を行うと同時に下記②の関数が起動します。この”~”が付いた関数を「ディストラクター」といいます。(内部にユーザプログラムを書くことができます)

MyForm(void)    ← ①
~MyForm()        ← ②

 

4)クラスのインスタンス化
ちょっと難しいのですが、これからBitmapクラスを使用して描画してきます。使用するクラスを通常変数に「宣言」したのが下記の記述となります。「」は「ハット記号」といいます。難しい言葉でいうと「マネージコードのクラスインスタンス領域を指し示す記号」となるわけですが、そんなことはどうでもよくVC++の場合は(他の言語は違うので注意してください)、「使用クラス+”^”」となると覚えておけばいいです。(したの例だと「変数canvasをBitmapクラスで宣言する際の記号」となるわけです)

Bitmap^ canvas;

よく間違うのですが、例えば
    int            cnt;

だと、「変数cntを整数(int)で使用する」となるのですが、上の場合はそうではありません 違うんです!
ハット記号がついていると
    「変数canvasをBitmapクラスで宣言する

つまり、これでは使用することはできないのです!! これは単なる「宣言」だけで実態がありません。
実体化することを「インスタンス化」といい、「gcnew」(ほかの言語は違うので注意)を使用し次のように表記します。

canvas = gcnew Bitmap();

一般的には面倒なので下記のように一気に書いてしまうのが普通のようです。

Bitmap^ canvas = gcnew Bitmap();

コーディングが完了しビルドも成功していると、起動後「start」をクリックすると下記のような図形が表示されます。






HOME > 絵を描く > Timerの設置