【C#】【Form】PictureBox [11] ~ スクロールバー付きの画像表示を考える ~

■ はじめに

PictureBox + HScrollBar / VScrollBar でのアプリ実装は大変なので、
別の方法を考える
HScrollBar / VScrollBar ~ 独自スクロールバーの実装 ~
https://blogs.yahoo.co.jp/dk521123/38010582.html

■ サンプル

例1:シンプルなサンプル

画面の構成
 + panel (Panelを画像の親コントロールとして配置する)
    + pictureBox (PictureBoxをPanelの子として配置する。親とドッキングしないこと)
プロパティの変更
 + panel
  - Ancher : Top, Bottom, Left, Right
  - AutoScroll : True
Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;

namespace SampleForm
{
  public partial class Form1 : Form
  {
    private Bitmap bitmap;

    public Form1()
    {
      this.bitmap = new Bitmap(@"C:\temp\20161215052204.gif");

      InitializeComponent();
    }

    // ロード
    private void Form1_Load(object sender, EventArgs e)
    {
      this.pictureBox1.Size = this.bitmap.Size;
      this.pictureBox1.Refresh();
    }

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

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

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
      // スクロールバー位置より元画像を切り抜いて表示
      Console.WriteLine(string.Format(
        "{0}, {1}, {2}, {3}",
        this.panel1.HorizontalScroll.Value,
        this.panel1.VerticalScroll.Value,
        this.panel1.ClientSize.Width,
        this.panel1.ClientSize.Height));

      e.Graphics.DrawImage(
        this.bitmap,
        new Rectangle(this.panel1.HorizontalScroll.Value,
        this.panel1.VerticalScroll.Value,
        this.panel1.ClientSize.Width,
        this.panel1.ClientSize.Height),
        this.panel1.HorizontalScroll.Value,
        this.panel1.VerticalScroll.Value,
        this.panel1.ClientSize.Width,
        this.panel1.ClientSize.Height,
        GraphicsUnit.Pixel);
    }
  }
}

例2:例1 + ドラッグ移動を追加

https://blogs.yahoo.co.jp/dk521123/37861699.html
の「例1:AutoScrollPositionを利用する」を追加する
Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;

namespace SampleForm
{
  public partial class Form1 : Form
  {
    private Bitmap bitmap;
    private Point startPoint = Point.Empty;
    private bool IsDragging
    {
      get
      {
        return !this.startPoint.IsEmpty;
      }
    }

    public Form1()
    {
      InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
      this.bitmap = new Bitmap(@"C:\temp\20161215052204.gif");
      this.pictureBox1.Size = this.bitmap.Size;

      this.pictureBox1.Refresh();
    }

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

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

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
      // スクロールバー位置より元画像を切り抜いて表示
      Console.WriteLine(string.Format(
        "{0}, {1}, {2}, {3}",
        this.panel1.HorizontalScroll.Value,
        this.panel1.VerticalScroll.Value,
        this.panel1.ClientSize.Width,
        this.panel1.ClientSize.Height));

      e.Graphics.DrawImage(
        this.bitmap,
        new Rectangle(this.panel1.HorizontalScroll.Value,
        this.panel1.VerticalScroll.Value,
        this.panel1.ClientSize.Width,
        this.panel1.ClientSize.Height),
        this.panel1.HorizontalScroll.Value,
        this.panel1.VerticalScroll.Value,
        this.panel1.ClientSize.Width,
        this.panel1.ClientSize.Height,
        GraphicsUnit.Pixel);
    }
    private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
    {
      this.pictureBox1.Focus();

      this.startPoint = e.Location;
    }

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

      var movementPoint = new Point(
        e.Location.X - this.startPoint.X, e.Location.Y - this.startPoint.Y);
      this.panel1.AutoScrollPosition = new Point(
          -panel1.AutoScrollPosition.X - movementPoint.X,
          -panel1.AutoScrollPosition.Y - movementPoint.Y);
    }

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

例3:アフィン変換でのドラッグ移動+拡大縮小機能

https://blogs.yahoo.co.jp/dk521123/37866101.html
https://blogs.yahoo.co.jp/dk521123/38061211.html
をベースに実装する
Form1.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
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 startPoint = Point.Empty;
    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);

      this.DrawImage();
    }

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

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

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

      this.startPoint = e.Location;
      this.oldPoint = this.startPoint;
    }

    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;

      var movementPoint = new Point(
        e.Location.X - this.startPoint.X, e.Location.Y - this.startPoint.Y);
      this.panel1.AutoScrollPosition = new Point(
          -panel1.AutoScrollPosition.X - movementPoint.X,
          -panel1.AutoScrollPosition.Y - movementPoint.Y);
    }

    private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
    {
      this.startPoint = Point.Empty;
      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);

      float minX = destinationPoints.Min(point => point.X);
      float maxX = destinationPoints.Max(point => point.X);
      float minY = destinationPoints.Min(point => point.Y);
      float maxY = destinationPoints.Max(point => point.Y);

      // 画像の幅と高さ
      int imageWidth = (int)Math.Ceiling(maxX - minX);
      int imageHeight = (int)Math.Ceiling(maxY - minY);
      this.pictureBox1.Width = imageWidth;
      this.pictureBox1.Height = imageHeight;

      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();
    }
  }
}

関連記事

Windows Form

Windows Form ~ 目次 ~
https://blogs.yahoo.co.jp/dk521123/8054245.html
Panel / ScrollableControl
https://blogs.yahoo.co.jp/dk521123/38013080.html
HScrollBar / VScrollBar ~ 独自スクロールバーの実装 ~
https://blogs.yahoo.co.jp/dk521123/38010582.html
スクロール に関するあれこれ
https://blogs.yahoo.co.jp/dk521123/37838702.html

Windows Form / PictureBox

PictureBox [3] ~ マウスホイール で画像の拡大・縮小する ~
https://blogs.yahoo.co.jp/dk521123/37866101.html

その他

画像処理 ~ アフィン変換・Matrixクラス ~
https://blogs.yahoo.co.jp/dk521123/38061211.html