きょうは、非実用的アドインを作成いたします。
Excel のセルを塗りつぶして・・・お絵かきをしましょう(笑)
- ファイル選択ダイアログを表示し(by DLL)
- 選択された画像ファイルを読み込んでピクセルデータを取り込んで(by DLL)
- 画像の高さと同じ行数・ビットマップの幅と同じ列数の範囲のセル高さ・幅を縮小し(by VBA)
- 各セルを、該当する ピクセルの色に塗りつぶします(by VBA)
見た目は、画像が貼り付けられたような状態となります(笑)
(こんな感じ↓)
このシートでは、選択したビットマップファイルのサイズと同じ 640行 x 480列 の範囲の307,200個のセルを、高さ・幅を縮小して正方形にし、各セルを 画像データと同じ色に塗りつぶしてあります。非実用的です。
では、さっそく作ってみましょう(笑)
まず、.NetFramework を使ってBitmapオブジェクトを取り扱うDLL( FastBitmap.dll )を作ります。
(VBA からこの DLL を利用して Bitmap オブジェクトのピクセル単位の色情報を取得し、セルを塗りつぶします。)
画像データの取り扱いは、System.Drawing 名前空間の Bitmapクラス を使います。
(Bitmapクラスが扱えるオブジェクトの詳細は こちら を参照してください。)
ファイルを選択するためのダイアログボックスを表示しますので、System.Windows.Forms 名前空間の
FileDialog クラスも使います。
それと・・・
今回は取り扱うデータが ビットマップ(巨大な配列データ)なので処理を効率良く行うために、unsafe なコードを記述しますので、プロジェクトのプロパティーに、「アンセーフコードの許可」を設定しておきましょう。
(こんな感じ↓)
DLLは、「Excel から使うマネージDLL を作る」 にあるとおりに記述・登録していきます。今回作成する DLL はVBA から利用しますので、オートメーションとしての登録はしません。コードを見た方が早いですね^^
(こんな感じ↓)
using System; using System.Drawing; // Bitmapクラスを利用(参照設定が必要) using System.Drawing.Imaging; // BitmapDataクラスを利用 using System.Runtime.InteropServices; // COM 属性定義に必要 using System.Windows.Forms; // OpenfileDialogクラスを利用(参照設定が必要)</code> namespace FastBitmap { // インターフェース定義 [ComVisible(true)] public interface IGetPixcel { bool GetFromFile(); // ファイルから Bitmapオブジェクトを生成し、ピクセルデータを取得するメソッド byte[,] R(); // ピクセルの赤色データバイト配列を返すメソッド byte[,] G(); // ピクセルの緑色データバイト配列を返すメソッド byte[,] B(); // ピクセルの青色データバイト配列を返すメソッド int Width(); // 読み込んだ画像の幅を返すメソッド int Height(); // 読み込んだ画像の高さを返すメソッド } // クラス定義 [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] public class GetPixcel : IGetPixcel { private byte[,] r; // ピクセルの赤色データバイト配列 private byte[,] g; // ピクセルの緑色データバイト配列 private byte[,] b; // ピクセルの青色データバイト配列 private int width; // 読み込んだ画像の幅 private int height; // 読み込んだ画像の高さ public byte[,] R() { return this.r; } public byte[,] G() { return this.g; } public byte[,] B() { return this.b; } public int Width() { return this.width; } public int Height() { return this.height; } public bool GetFromFile() { try { // ファイルダイアログでファイルを選択 OpenFileDialog fdlg = new OpenFileDialog(); fdlg.ShowDialog(); string file_name = fdlg.FileName; // ファイルからbitmapデータを生成 Bitmap bmp = new Bitmap(file_name); // bitmap データをロック BitmapData bmpd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); // サイズ取得 this.width = bmpd.Width; this.height = bmpd.Height; // RGBデータ保持配列変数を初期化 this.r = new byte[width, height]; this.g = new byte[width, height]; this.b = new byte[width, height]; // Pixcelデータ取得 for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { this.r[x, y] = GetPixcelColorBytes(x, y, bmpd)[0]; this.g[x, y] = GetPixcelColorBytes(x, y, bmpd)[1]; this.b[x, y] = GetPixcelColorBytes(x, y, bmpd)[2]; } } // bitamp データのロックを解除など後始末 bmp.UnlockBits(bmpd); bmpd = null; bmp = null; return true; } catch (Exception e) { MessageBox.Show(e.ToString()); return false; } } private byte[] GetPixcelColorBytes(int x, int y, BitmapData bmpd) { byte[] bytes = new byte[3]; // ロックされたBitmapDataメモリへポインタアクセス unsafe { byte* adr = (byte*)bmpd.Scan0; // 先頭アドレスを取得 int pos = x * 3 + bmpd.Stride * y; // ポインタ計算 byte _b = adr[pos + 0]; byte _g = adr[pos + 1]; byte _r = adr[pos + 2]; bytes[0] = _r; bytes[1] = _g; bytes[2] = _b; } return bytes; } } }
さて、ビルドしたら regasm FastBitmap.dll /tlb /codebase として、作成した DLL を登録します。
つづいて、Excel を開き、VBA のコードを記述しましょう。
まず、作成・登録した FastBitmap.dll を参照設定しておいてください。
(こんな感じ↓)
参照設定したら、コードを記述していきます。
Sub BmpToBackColor() Dim gpx As GetPixcel 'GetPixcelオブジェクトの変数定義 Set gpx = New GetPixcel 'GetPixcelオブジェクトのインスタンス化 Dim x As Integer, y As Integer 'ピクセル位置を示す変数 x,y を定義 Dim arrR, arrG, arrB '各ピクセルの色データを受け取る変数を定義</code> If gpx.GetFromFile = True Then '画像データを読込み、ピクセルの取得に成功したら arrR = gpx.R: arrG = gpx.G: arrB = gpx.B '赤・緑・青色の配列データを受け取る With ActiveSheet .Range(Rows(1), Rows(gpx.Height)).RowHeight = 1 '画像高さ分の行高さを1にする .Range(Columns(1), Columns(gpx.Width)).ColumnWidth = 0.1 '画像幅分の列幅を0.1にする For x = 1 To gpx.Width For y = 1 To gpx.Height Cells(y, x).Interior.color = _ RGB(arrR(x - 1, y - 1), arrG(x - 1, y - 1), arrB(x - 1, y - 1)) ' ピクセルデータを、RGB関数を使って背景色に設定 Next DoEvents '無応答にならないように Next End With End If End Sub
[/code]
実行すると、選択した画像サイズと同じ範囲のセルサイズが変更され、背景色が設定されます。
※注意:大きな画像を選択すると・・・時間がかかります。面積は大きさの2乗に比例しますからね(笑)
色データを変更することも出来ます。RGB データを 0xFF との排他的論理和(XOR)をとり、ネガ にしてみます。
(こんな感じ↓)
Cells(y, x).Interior.color = _ RGB(arrR(x - 1, y - 1), arrG(x - 1, y - 1), arrB(x - 1, y - 1))
の部分を
Cells(y, x).Interior.color = RGB(arrR(x - 1, y - 1) Xor 255, _ arrG(x - 1, y - 1) Xor 255, _ arrB(x - 1, y - 1) Xor 255)
に変更するとネガになります。
(こんな感じ↓)
ところで、今回作成したプロジェクトの本当の目的は・・・
マネージDLLで、配列にアンセーフなポインタアクセスを行う実験でした^^
ポインタを使うまでもなく、素直にBitmapクラスの GetPixcelメソッドを使って
this.r[x,y] = bmp.GetPixcel(x,y).R;
と記述しても、座標 x,y の赤色のバイトデータを取得できますが、遅いので、
LockBitsメソッドでメモリ上に固定したBitmapData オブジェクトを作ってポインタアクセスしてみました。
マネージコードで大きな配列を扱う場合に、処理を高速化するお手軽な方法です♪
それにしても・・・VBA でセルを塗りつぶす処理が遅すぎる。これでは、処理時間の面でも非実用的ですなぁ~(笑)
Excel-DNA で XLL から処理できないものか?(意味ないか・・・^^)
では、また(笑)
今回作成したプロジェクトのソースは こちら
単にExcelに写真貼りつけた方が早いw
確かに非実用的www
>確かに非実用的www
お褒めにあずかり、光栄です (笑)