非実用的アドインをつくる

きょうは、非実用的アドインを作成いたします。
Excel のセルを塗りつぶして・・・お絵かきをしましょう(笑)

  1. ファイル選択ダイアログを表示し(by DLL)
  2. 選択された画像ファイルを読み込んでピクセルデータを取り込んで(by DLL)
  3. 画像の高さと同じ行数・ビットマップの幅と同じ列数の範囲のセル高さ・幅を縮小し(by VBA)
  4. 各セルを、該当する ピクセルの色に塗りつぶします(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 &lt; width; x++)
{
for (int y = 0; y &lt; 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, .NetFramework タグ: , , , パーマリンク

非実用的アドインをつくる への2件のフィードバック

  1. y sakuda のコメント:

    単にExcelに写真貼りつけた方が早いw
    確かに非実用的www

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です