『象印クイズ ヒントでピント』は、テレビ朝日系列局ほかで、1979年3月4日から1994年9月25日まで、毎週日曜 19:30 – 20:00に放送されていたクイズ番組です。
Wikipediaを見て知ったのですが、この番組は魔法瓶メーカーの象印が一社提供しているため、同業他社であり象印のライバル企業でもあるタイガー魔法瓶に配慮し、トラそのもの、およびそれに関する問題は出題されたことがなく、野球の阪神タイガースに関する問題さえ作れなかったそうです。
いまから作ろうとしているのは、最初に分割され順序がバラバラの画像が表示され、だんだん元の画像に近づいていくというものです。
ではさっそく作成してみましょう。
最初に画像を分割します。そして分割された画像を保存するためのクラスを作成します。Bitmapは分割された画像、TrueRowとTrueColumは全体のなかにおける画像の本当の位置、RowとColumはそのときに表示されている画像の位置です。XとYは実際に描画するときのXY座標です。
また全体が一斉に表示されるのではなく、最初は渦巻き状にひとつずつ表示されていきます。IsShowがfalseのときは描画されません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class BitmapPart { public int Row = 0; public int Colum = 0; public Bitmap Bitmap = null; public bool IsShow = false; public BitmapPart(int colum, int row, Bitmap bitmap) { Row = row; Colum = colum; TrueRow = Row; TrueColum = Colum; Bitmap = bitmap; } public int TrueRow { get; } public int TrueColum { get; } public int X { get { return (Bitmap.Width) * Colum; } } public int Y { get { return (Bitmap.Height) * Row; } } } |
Form1クラスにおける処理ですが、最初に分割されたBitmapのリストを作成します。
縦と横に何分割するかを決めたら画像を一部だけ切り取ったものからBitmapPartオブジェクトを作成してそのリストをつくります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public partial class Form1 : Form { List<BitmapPart> BitmapParts = new List<BitmapPart>(); int RowMax = 10; // 横に何分割するか? int ColumMax = 16; // 縦に何分割するか? // 引数は元になる画像 void CreateBitmapParts(Bitmap sourceBitmap) { int bitmapWidth = sourceBitmap.Width; int bitmapHeight = sourceBitmap.Height; for (int row = 0; row < RowMax; row++) { for (int colum = 0; colum < ColumMax; colum++) { // 画像を一部だけ切りとる Bitmap bitmap = new Bitmap(bitmapWidth / ColumMax, bitmapHeight / RowMax); Graphics graphics = Graphics.FromImage(bitmap); graphics.DrawImage( sourceBitmap, new Rectangle( 0, 0, sourceBitmap.Width / ColumMax, sourceBitmap.Height / RowMax), new Rectangle( colum * bitmapWidth / ColumMax, row * bitmapHeight / RowMax, sourceBitmap.Width / ColumMax, sourceBitmap.Height / RowMax), GraphicsUnit.Pixel ); graphics.Dispose(); BitmapParts.Add(new BitmapPart(colum, row, bitmap)); } } } } |
BitmapPartオブジェクトのリストが生成されたら順番をシャッフルします。そして縦横どの位置に描画するかを変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public partial class Form1 : Form { Random Random = new Random(); void ShuffleBitmapParts() { int index = 0; // リストのコピーを作成して自身はクリアする List<BitmapPart> bitmapParts = new List<BitmapPart>(BitmapParts); BitmapParts.Clear(); // 順番をシャッフルする // 乱数で抜き出したものをリストに空のリストに追加する while (bitmapParts.Count > 0) { index = Random.Next(bitmapParts.Count); BitmapParts.Add(bitmapParts[index]); bitmapParts.RemoveAt(index); } // シャッフルしたものをどの位置に描画するかを決める index = 0; for (int row = 0; row < RowMax; row++) { for (int colum = 0; colum < ColumMax; colum++) { BitmapParts[index].Colum = colum; BitmapParts[index].Row = row; index++; } } } } |
次に描画の処理ですが、最初は渦巻き状に1枚ずつ表示させます。
まだ表示されていないもののなかで、一番上の行で列番号が一番大きいもの、一番左の列で行番号が一番小さいもの、一番下の行で列番号が一番小さいもの、一番右の列で行番号が一番大きいものの順で探し、見つからなければ2周目に入ったということなので、「一番上の行」なら「2番目に上の行」と読み替えてすべての画像が表示されるまでこれを繰り返します。そして最後にInvalidateメソッドを呼び出して再描画させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public partial class Form1 : Form { void ShowBitmapParts() { if (!BitmapParts.Any(x => !x.IsShow)) return; int i = 0; while (true) { List<BitmapPart> bitmapParts = BitmapParts.Where(x => x.Row == 0 + i && !x.IsShow).ToList(); if (bitmapParts.Count > 0) { int max = bitmapParts.Max(x => x.Colum); BitmapPart first = bitmapParts.First(x => x.Colum == max); first.IsShow = true; Invalidate(); return; } bitmapParts = BitmapParts.Where(x => x.Colum == 0 + i && !x.IsShow).ToList(); if (bitmapParts.Count > 0) { int min = bitmapParts.Min(x => x.Row); BitmapPart first = bitmapParts.First(x => x.Row == min); first.IsShow = true; Invalidate(); return; } bitmapParts = BitmapParts.Where(x => x.Row == RowMax - 1 - i && !x.IsShow).ToList(); if (bitmapParts.Count > 0) { int min = bitmapParts.Min(x => x.Colum); BitmapPart first = bitmapParts.First(x => x.Colum == min); first.IsShow = true; Invalidate(); return; } bitmapParts = BitmapParts.Where(x => x.Colum == ColumMax - 1 - i && !x.IsShow).ToList(); if (bitmapParts.Count > 0) { int max = bitmapParts.Max(x => x.Row); BitmapPart first = bitmapParts.First(x => x.Row == max); first.IsShow = true; Invalidate(); return; } i++; } } } |
すべての画像が表示されたら今度は各画像を元の位置に戻します。RowとColumのどちらかが本当の値と違っているものをLINQのメソッドWhereをつかって集めて、そのなかからどれかひとつを乱数で選びます。そしてこれを本当の位置にあるものと位置を入れ替えます。これをすべての画像が本当の位置に移動されるまで繰り返します。すべて終わったらタイマーを止めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class Form1 : Form { void RestoreBitmapParts() { List<BitmapPart> bitmapParts = BitmapParts.Where(x => x.Row != x.TrueRow || x.Colum != x.TrueColum).ToList(); int index = Random.Next(bitmapParts.Count); int oldRow = bitmapParts[index].Row; int oldColum = bitmapParts[index].Colum; // 正しい位置に移動する bitmapParts[index].Row = bitmapParts[index].TrueRow; bitmapParts[index].Colum = bitmapParts[index].TrueColum; // 入れ替える BitmapPart bitmapPart = BitmapParts.First(x => x.Row == bitmapParts[index].Row && x.Colum == bitmapParts[index].Colum); bitmapPart.Row = oldRow; bitmapPart.Colum = oldColum; Invalidate(); } } |
次に描画の処理を示します。画像が完全に復元されるまで分割された画像の間に境界線を描画します。それ以外はBitmapPart.IsShowがtrueの画像はすべて描画します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public partial class Form1 : Form { Pen Pen = new Pen(Color.Black, 2); int MarginLeft = 10; int MarginTop = 10; protected override void OnPaint(PaintEventArgs e) { foreach (BitmapPart bitmapPart in BitmapParts) { if (!bitmapPart.IsShow) continue; e.Graphics.DrawImage(bitmapPart.Bitmap, new Point(bitmapPart.X + MarginLeft, bitmapPart.Y + MarginTop)); } DrawBorders(e.Graphics); base.OnPaint(e); } void DrawBorders(Graphics graphics) { int width = SourceBitmap.Width / ColumMax; int height = SourceBitmap.Height / RowMax; if (BitmapParts.Any(x => x.Row != x.TrueRow || x.Colum != x.TrueColum)) { for (int x = 1; x < ColumMax; x++) graphics.DrawLine(Pen, new Point(x * width + MarginLeft, 0 + MarginTop), new Point(x * width + MarginLeft, height * RowMax + MarginTop)); for (int y = 0; y < RowMax; y++) graphics.DrawLine(Pen, new Point(0 + MarginLeft, y * height + MarginTop), new Point(width * ColumMax + MarginLeft, y * height + MarginTop)); } } } |
プログラムが開始されたらタイマーをセットしてShowBitmapPartsメソッドとRestoreBitmapPartsメソッドが一定間隔おきに呼び出されるようにします。
最初はBitmapPartsメソッドを呼び出し、BitmapPart.IsShowがfalseのものがなくなったらRestoreBitmapPartsメソッドを呼び出します。 そして完全に復元されたらタイマーを止めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public partial class Form1 : Form { Timer Timer = new Timer(); // 再掲 int MarginLeft = 10; // 再掲 int MarginTop = 10; public Form1() { InitializeComponent(); CreateBitmapParts(SourceBitmap); ShuffleBitmapParts(); Timer.Interval = 500; Timer.Tick += Timer_Tick; Timer.Start(); this.DoubleBuffered = true; this.BackColor = Color.Black; // 画像サイズと合うようにFormを適切なサイズにする this.ClientSize = new Size(SourceBitmap.Width + MarginLeft * 2, SourceBitmap.Height + MarginTop * 2); } private void Timer_Tick(object sender, EventArgs e) { if (BitmapParts.Any(x => !x.IsShow)) { Timer.Interval = 20; ShowBitmapParts(); } else { Timer.Interval = 200; RestoreBitmapParts(); // 完全に復元されたらタイマーを止める if (!BitmapParts.Any(x => x.Row != x.TrueRow || x.Colum != x.TrueColum)) Timer.Stop(); } } } |