6.1 ビットマップとイメージ
6.1.1 対応フォーマット
ゲームは、ゲームのルールを明確に定義して流れを制御するアルゴリズムさえあれば、単純な描画処理だけで構築することも不可能ではありません。しかし、ユーザーは視覚的な情報でソフトウェアを感じるので、ゲームだけではなく近年の多くのアプリケーションが画像を使った視覚的な効果を多用しています。
最近では、画像やイメージという言葉を聞けば JPEG や GIF、または PNG といったインターネットで利用されている画像ファイルを想像するかもしれません。これらの画像ファイル形式はあらゆるプラットフォームでサポートされていますし、多くのアプリケーションが対応しています。本章では、こうした画像ファイルに保存されているイメージをプログラムでどのように扱うかを解説します。
アプリケーションでは、イメージの多くはアイコンのようにユーザーに対する視覚的な効果を狙ってイメージを使うことが多いと考えられますが、ゲームの場合は画面上のすべての要素が画像で作られていると考えてかまいません。マップを移動するキャラクター、マップ背景、ウィンドウなど、これらはプログラムが実行時に drawLine() や fillRectangle() などを使って構築しているものではなく(理屈では可能かもしれませんが、そのようなことをする意味がありません)ディスクから画像ファイルを読み込んで貼り付けているのです。
画像ファイルの作り方は改めて説明するまでもないでしょう。Windows であれば「スタート」→「ファイル名を指定して実行」で mspaint と入力すればペイントツールが起動します。これで好きな絵を描いて保存すれば画像ファイルの完成です。デジタルカメラで撮影した写真をメモリカードリーダーで読み込んでもかまいませんし、高度なイラストを描きたければ専用のアプリケーションを購入すればよいのです。
.NET Framework では標準的な画像形式の多くをサポートしているため、開発者が画像ファイルを読み込む専用のコードを記述する必要はありません。ファイルパスを指定するメソッドひとつで、イメージオブジェクトを生成することができます。標準で読み込むことができる画像形式は System.Drawing.Imaging.ImageFormat クラスの静的プロパティで定義されています。
public sealed class ImageFormat
ImageFormat クラスは画像フォーマットを表します。静的プロパティでは、ビットマップを表す Bmp プロパティ、JPEG を表す Jpeg プロパティ、GIF を表す Gif プロパティ、PNG を表す Png プロパティなどが用意されています。これらのプロパティから ImageFormat オブジェクトを取得できます。
public static ImageFormat Bmp { get; }
public static ImageFormat Jpeg { get; }
public static ImageFormat Gif { get; }
public static ImageFormat Png { get; }
ImageFormat オブジェクトは、内部で GUID と呼ばれる画像フォーマットを識別するためのグローバルな ID を保有しています。
ゲームのような精密なイメージを求めるソフトウェアの場合、圧縮形式の画像を使うことは推奨できません。Windows では、すべてのピクセルのカラー情報を保存する形式に .bmp という拡張子のビットマップファイルがあります。ビットマップイメージは、古くから使われている Windows 標準の画像形式です。標準的な JPEG ファイルに比べると、ビットマップファイルは圧縮されていないのでファイルサイズが大きくなりますが、ピクセルが破損することはありません。ゲームの場合は透過処理(キャラクターの背景を透明にする)などの処理が必要になるため、すべてのピクセルの値が正確に再現される必要があります。
ビットマップで保存した場合は、イメージエディタで作成した画像のピクセル情報が正しく保存されます。しかし、例えば JPEG で圧縮した画像は情報の一部が失われてしまうため、一部のピクセル色が変色してしまいます。どの程度情報が失われるかは、圧縮率によって変化します。JPEG の圧縮は人間の目で見ると情報の損失をそれほど感じませんが、圧縮前の画像とピクセルを比較すれば変色していることを確認できます。
6.1.2 ラスタグラフィックスとベクタグラフィックス
グラフィックスには、大きく分けてラスタグラフィックスとベクタグラフィックスの概念に分かれます。イメージを出力するデバイスも、画像ファイルも、まずはラスタグラフィックスなのか、ベクタグラフィックスなのかで性質が異なります。
ラスタグラフィックスとは、現在ごく一般的に使われている形式のグラフィックスで、ディスプレイやプリンタなどピクセルの集合で構成されるグラフィックスのことです。小さな点の集合で平面イメージを構成するため、写真のような複雑なイメージを表示したり、保存するのに適しています。しかし、その一方でピクセルの数だけいろ情報を保存する必要があるため、解像度が大きくなれば比例してイメージを表現するために必要なメモリサイズが大きくなります。また、グラフィックスを伸縮するとピクセルを失ったり、画質が低下してしまいます。よって、単純な図形を保存するには向いていません。
一方、ベクタグラフィックスは描画手順や形状を記録したレコードと呼ばれるデータを持ち、イメージを表示するときに保存されているレコードに基づいて描画します。ラスタグラフィックスが単純なピクセルの集合を保存し、描画時にはピクセルの集合をデバイスに転送することで表示していたのに対し、ベクタグラフィックスは保存されている描画手順に従って表示するときに再生するという仕組みを持っています。解像度にデータサイズが影響されず、イメージの伸縮を行っても再生時に調整されるため、画質が低下したりピクセルが欠けるといったラスタグラフィックス特有の問題は発生しません。ただし、図形の描画手順を保存するだけなので写真のような緻密な映像を保存することには向いていません。
ビットマップや JPEG など、現在使われている標準的なイメージ形式はすべてラスタグラフィックスです。ベクタグラフィックスのイメージ形式には古くから Windows で使われている Windows メタファイル(拡張子は .wmf 形式)と、これを拡張した 拡張 Windows メタファイル(拡張子は .emf) があります。
キャラクターの画像など、形状が固定ではないイラストにはビットマップを使って、ウィンドウやテキストなど形状が固定で伸縮して様々なサイズで利用されることが想定されるイメージにベクタグラフィックスを使うと効率的です。
6.1.3 イメージの読み込み
ディスク上の画像ファイルを読み込むことは難しくありません。.NET でイメージを操作するには System.Drawing.Image クラスを使います。
System.Object System.MarshalByRefObject System.Drawing.Image
[Serializable] [ComVisible(true)] public abstract class Image : MarshalByRefObject, ISerializable,ICloneable, IDisposable
Image クラスは、イメージの実装がビットマップまたはメタファイル両方で共通の基本操作を提供するための抽象クラスです。このクラスは抽象クラスなので、コンストラクタから直接インスタンスを生成することはできません。
ディスク上のファイルから画像ファイルを読み込んで Image オブジェクトを取得するには Image クラスの FromFile() メソッドを使います。
public static Image FromFile(string filename)
filename パラメータに、Image オブジェクトの元となる画像ファイルのパスを指定します。読み込んだ画像ファイルは Image オブジェクトとして返されるので、このオブジェクトからイメージを制御することができます。
Image オブジェクトのイメージ形式は RawFormat プロパティから取得することができます。
public ImageFormat RawFormat { get; }
RawFormat はイメージの形式を表す ImageFormat オブジェクトを返します。イメージの幅と高さは Size プロパティ、または幅を表す Width プロパティと高さを表す Height プロパティから取得できます。
public Size Size { get; }
public int Width { get; }
public int Height { get; }
どのような画像ファイルを読み込むか事前に想定できないようなプログラムでは、こうした情報がイメージを描画するときのヒントになるでしょう。
using System.Drawing; using System.Windows.Forms; public class Test : Form { private Image image; protected override void OnPaint(PaintEventArgs e) { base.OnPaint (e); if (image == null) image = Image.FromFile("test.bmp"); Font font = new Font(Font.Name, 20); Brush brush = new SolidBrush(Color.Black); e.Graphics.DrawString( image.ToString(), font, brush, 0, 0 ); e.Graphics.DrawString( "Width=" + image.Width + ",Height=" + image.Height, font, brush, 0, font.Height ); } static void Main() { Application.Run(new Test()); } }
コード1は、実行ファイルと同じディレクトリに配置されている test.bmp という名前のファイルを読み込みます。イメージを読み込むことができなかった場合は例外が発生します。ウィンドウには、生成された Image オブジェクトの文字列表現と、読み込んだイメージの幅と高さを描画しています。
実行結果を見ると、Image オブジェクトのインスタンスが System.Drawing.Bitmap クラスのものであることが分かります。ビットマップやビットマップを圧縮した JPEG ファイルなどはすべて Bitmap クラスが用いられ、メタファイルであれば System.Drawing.Imaging.Metafile クラスが用いられます。
System.Object System.MarshalByRefObject System.Drawing.Image System.Drawing.Bitmap
[Serializable] [ComVisible(true)] public sealed class Bitmap : Image
System.Object System.MarshalByRefObject System.Drawing.Image System.Drawing.Imaging.Metafile
[Serializable] [ComVisible(false)] public sealed class Metafile : Image
これらのクラスは Image クラスを継承しているため、イメージの実装がビットマップであれメタファイルであれ、イメージという抽象的なオブジェクトを操作するときには Image オブジェクトとして扱うことができます。イメージをファイルから読み込んで描画する場合は、イメージがビットマップであるかメタファイルであるかを Image オブジェクトの利用者が知る必要はありません。
あらかじめ、読み込むイメージがラスタグラフィックス系のファイル(BMP、JPEG など)であることが確定している場合は Bitmap クラスのコンストラクタから、ベクタグラフィックス系のファイル(WMF、EMFなど)であることが確定しているならば Metafile クラスのコンストラクタから、それぞれインスタンスを作ることも可能です。
メタファイルの詳細については後述するので、この場では Bitmap クラスのコンストラクタからイメージを読み込む方法を説明します。結果は Image クラスの FromFile() メソッドと同じですが、FromFile() メソッドの戻り値は Image 型です。確実に Bitmap 型のイメージを取得したい場合は Bitmap クラスの次のコンストラクタからインスタンスを生成してください。
public Bitmap(string filename);
public Bitmap(Stream stream);
filename パラメータには、文字列で読み込むファイルのパスを指定します。stream パラメータの場合は、ビットマップファイルへのデータストリームオブジェクトを指定します。メモリ上に展開されているビットマップファイルなど、ローカルディスク以外の特殊なデバイスに配置されているビットマップを読み込むときに使うことができます。