【C#】【Form】PictureBox [3] ~ マウスホイール で画像の拡大・縮小する ~

■ ポイント

 * 現在表示している点を中心として画像を拡大縮小する
  => 中心の点を原点へ移動し、拡大縮小後、元の位置へ移動する
  => 以下のサイトの説明が分かりやすい
http://imagingsolution.blog.fc2.com/blog-entry-287.html

■ その他、学べる事

【1】マウスホイールイベントの追加方法
【2】アフィン変換

【1】マウスホイールイベントの追加方法

 * 以下の関連記事を参照のこと
https://blogs.yahoo.co.jp/dk521123/37867491.html

【2】アフィン変換 (Affine transformation)

 * 以下の関連記事を参照のこと
https://blogs.yahoo.co.jp/dk521123/38061211.html

■ サンプル

 アフィン変換を使うので、
https://blogs.yahoo.co.jp/dk521123/37861699.html
で行ったマウスでの移動も実装してみる

コントロール構成

 + PictureBox x 1

コード

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace SampleForm
{
  public partial class Form1 : Form
  {
    // アフィン変換 
    private Matrix affineTransformation = new Matrix();
    private Bitmap targetBitmap;
    // 描画元を指定する4点の座標(左上、右上、左下、右下の順)
    private PointF[] sourcePoints;
    private Point oldPoint = Point.Empty;
    private bool IsDragging
    {
      get
      {
        return !this.oldPoint.IsEmpty;
      }
    }

    public Form1()
    {
      InitializeComponent();

      // ホイールイベントの追加  
      this.pictureBox1.MouseWheel
          += new MouseEventHandler(this.pictureBox1_MouseWheel);
    }

    private void Form1_Load(object sender, EventArgs e)
    {
      this.OpenImageFile(@"C:\temp\20161215052204.gif");
    }

    // マウスホイールイベント  
    private void pictureBox1_MouseWheel(object sender, MouseEventArgs e)
    {
      this.affineTransformation.Translate(-e.X, -e.Y, MatrixOrder.Append);

      if (e.Delta > 0)
      {
        // 拡大  
        if (this.affineTransformation.Elements[0] < 100)
        {
          this.affineTransformation = this.GetAffineTransformationToScale(
            this.affineTransformation, 1.5f, e.Location);
        }
      }
      else
      {
        // 縮小  
        if (this.affineTransformation.Elements[0] > 0.01)
        {
          this.affineTransformation = this.GetAffineTransformationToScale(
            this.affineTransformation, 1.0f / 1.5f, e.Location);
        }
      }

      this.affineTransformation.Translate(e.X, e.Y, MatrixOrder.Append);
    }

    // リサイズ
    private void Form1_Resize(object sender, EventArgs e)
    {
      this.ResizePictureBox();
    }

    // スクロール
    private void panel1_Scroll(object sender, ScrollEventArgs e)
    {
      this.ResizePictureBox();
    }

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
    }

    private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
    {
      this.pictureBox1.Focus();

      this.oldPoint = e.Location;
    }

    private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
      if (!this.IsDragging)
      {
        return;
      }

      // 移動
      this.affineTransformation.Translate(
        e.X - this.oldPoint.X, e.Y - this.oldPoint.Y, MatrixOrder.Append);

      this.DrawImage();

      this.oldPoint = e.Location;
    }

    private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
    {
      this.oldPoint = Point.Empty;
    }

    private void OpenImageFile(string imageFilePath)
    {
      if (string.IsNullOrEmpty(imageFilePath))
      {
        return;
      }
      // 画像ファイルのImageオブジェクトを作成する
      this.targetBitmap = new Bitmap(imageFilePath);

      this.pictureBox1.Size = this.targetBitmap.Size;

      var sourceArea = new RectangleF(-0.5f, -0.5f, this.targetBitmap.Width, this.targetBitmap.Height);
      // 描画元を指定する4点の座標(左上、右上、左下、右下の順)
      this.sourcePoints = new PointF[4];
      this.sourcePoints[0] = new PointF(sourceArea.Left, sourceArea.Top);
      this.sourcePoints[1] = new PointF(sourceArea.Right, sourceArea.Top);
      this.sourcePoints[2] = new PointF(sourceArea.Left, sourceArea.Bottom);
      this.sourcePoints[3] = new PointF(sourceArea.Right, sourceArea.Bottom);

      this.DrawImage();
    }

    private void ResizePictureBox()
    {
      if (this.targetBitmap == null || (this.pictureBox1.Width == 0) || (this.pictureBox1.Height == 0))
      {
        return;
      }

      this.DrawImage();
    }

    private Matrix GetAffineTransformationToScale(Matrix matrix, float scale, Point point)
    {
      matrix.Translate(-point.X, -point.Y, MatrixOrder.Append);
      matrix.Scale(scale, scale, MatrixOrder.Append);
      matrix.Translate(point.X, point.Y, MatrixOrder.Append);

      return matrix;
    }

    // ビットマップの描画
    private void DrawImage()
    {
      if (this.pictureBox1.Width == 0 || this.pictureBox1.Height == 0)
      {
        return;
      }

      // 描画先の座標をアフィン変換で求める(左上、右上、左下の順)
      var destinationPoints = (PointF[])this.sourcePoints.Clone();
      // 描画先の座標をアフィン変換で求める(変換後の座標は上書きされる)
      this.affineTransformation.TransformPoints(destinationPoints);

      this.Text = string.Format(
        "({0:#.#}, {1:0.#}), ({2:#.#}, {3:0.#}), ({4:#.#}, {5:0.#}), ({6:#.#}, {7:0.#})",
        destinationPoints[0].X, destinationPoints[0].Y,
        destinationPoints[1].X, destinationPoints[1].Y,
        destinationPoints[2].X, destinationPoints[2].Y,
        destinationPoints[3].X, destinationPoints[3].Y);

      Bitmap clonedBitmap = new Bitmap(this.pictureBox1.Width, this.pictureBox1.Height);
      using (var graphics = Graphics.FromImage(clonedBitmap))
      {
        // まずは背景色を黒くする
        graphics.Clear(Color.Black);

        graphics.Transform = this.affineTransformation;

        // 高品質双三次補間を指定
        graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;

        // 描画(指定された位置に元の物理サイズで描画)
        graphics.DrawImageUnscaled(targetBitmap, 0, 0);
      }

      // 描画
      if (this.pictureBox1.Image != null)
      {
        this.pictureBox1.Image.Dispose();
      }

      this.pictureBox1.Image = clonedBitmap;
      this.pictureBox1.Refresh();
    }
  }
}

■ 補足1:回転処理について

 * 以下の関連記事を参照。
画像処理 ~ 回転 ~
https://blogs.yahoo.co.jp/dk521123/37853430.html
PictureBox [9] ~ 画像を任意の角度で回転させる ~
https://blogs.yahoo.co.jp/dk521123/38055503.html


関連記事

Windows Form

Windows Form ~ 目次 ~
https://blogs.yahoo.co.jp/dk521123/8054245.html
PictureBox [1] ~ 画像を表示する ~
https://blogs.yahoo.co.jp/dk521123/23504075.html
PictureBox [2] ~ PictureBox を マウスで移動する ~
https://blogs.yahoo.co.jp/dk521123/37861699.html
PictureBox [4] ~ PictureBox 内に文字列を描画する ~
https://blogs.yahoo.co.jp/dk521123/37890831.html
PictureBox [5] ~ PictureBox 内に画像を描画する ~
https://blogs.yahoo.co.jp/dk521123/37890873.html
PictureBox [9] ~ 画像を任意の角度で回転させる ~
https://blogs.yahoo.co.jp/dk521123/38055503.html
マウスホイールイベントの追加方法
https://blogs.yahoo.co.jp/dk521123/37867491.html
Graphics ~ さまざまな描画 ~
https://blogs.yahoo.co.jp/dk521123/32877749.html

画像処理

画像処理 ~ アフィン変換 ~
https://blogs.yahoo.co.jp/dk521123/38061211.html
画像処理 ~ 回転 ~
https://blogs.yahoo.co.jp/dk521123/37853430.html