これは「C#によるPOS開発入門(的な)」と称して連載している記事の1つです。他はこちら
はじめに
System.Drawing.Bitmap
を渡すとサーマルプリンターで印刷してくれるPrintMemoryBitmap
メソッドというものがあります。Graphicsを使って生成した画像を渡すことが出来るのは便利ですね。
……と思いきや、なんとこのPrintMemoryBitmap
メソッド、Graphicsで汚染されたBitmapは受け付けてくれないのです。Graphicsが使えないとこのメソッドの魅力が大幅に減ってしまいます。
そこで、無理やりPrintMemoryBitmap
メソッドにGraphics由来の画像を突っ込む方法を調べました。
POS for .NETって?
Microsoftが定めた、POS機器を扱うための共通規格です。機器メーカーにPOS for .NETを通じて動作させるドライバーを作らせることで、使用する機器を他メーカー製のものに入れ替えても今までのコードをそのまま使えるのがポイントです。
POS for .NETによるPOS機器の制御の基礎は別の記事にまとめました。良ければそちらも参考にして下さい。
前提コード
以下の全てのサンプルコードの前には、以下のコードがあるものとします。
/*
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using Microsoft.PointOfService;
*/
Bitmap baseBmp = new Bitmap(200, 150, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(baseBmp)) // 透明なBitmapの全体に赤いバツ印を描く
{
g.DrawLine(Pens.Red, 0, 0, bitmap.Width, bitmap.Height);
g.DrawLine(Pens.Red, bitmap.Width, 0, 0, bitmap.Height);
}
まずは普通に渡してみる
posPrinter.PrintMemoryBitmap(PrinterStation.Receipt, baseBmp, PosPrinter.PrinterBitmapAsIs, PosPrinter.PrinterBitmapRight);
これだと例外を吐いて駄目なんですね。
Bitmapを複製してみる
Bitmapを複製し、新しいものを渡してみます。
Bitmap newBmp = new Bitmap(baseBmp);
posPrinter.PrintMemoryBitmap(PrinterStation.Receipt, newBmp, PosPrinter.PrinterBitmapAsIs, PosPrinter.PrinterBitmapRight);
これでも駄目です。
(一応OK)一旦ファイルに保存する
new Bitmap("fileName")
したままのものを渡すと正常に動作するので、一度生成したBitmapをファイルとして保存し、それを読み込み直せば動くのでは?と思いやってみました。
(コードはありません)
勿論これで動くことは動きます。が、一度ファイルにするのは……なんかやだ。パフォーマンスの問題もありますし、何よりスマートじゃない。
(解決)bmp形式のヘッダーバイナリを自作して、くっつけてみる
new Bitmap("fileName")
したままのものを渡すと正常に動作することから、元のBitmapからbmp形式のファイルのバイナリを生成し、それをnew Bitmap()
の引数に渡すと上手くいくんじゃないかと予想しました。
そこで、自前でbmp形式のファイルのヘッダーバイナリを作成し、そこに元のBitmapのビットマップ部分をLockBitsで取得したものをくっつけて渡してみます。
// 32bitアルファチャンネル付bmpを24bitに変換
// EPSON製のプリンターではアルファチャンネルが無視されて真っ黒になるため、アルファチャンネル無しに変換する必要がある
BitmapData bmpData = baseBmp.LockBits(new Rectangle(0, 0, baseBmp.Width, baseBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] basePixels = new byte[bmpData.Stride * bmpData.Height];
Marshal.Copy(bmpData.Scan0, basePixels, 0, basePixels.Length);
int stride = (int)Math.Ceiling(3.0 * bmpData.Width / 4) * 4;
byte[] pixels = new byte[14 + 40 + stride * bmpData.Height];
for (int y = 0; y < bmpData.Height; y++)
{
for (int x = 0; x < bmpData.Width; x++)
{
int basePos = 4 * x + bmpData.Stride * y;
int pos = 14 + 40 + 3 * x + stride * (bmpData.Height - y - 1);
pixels[pos] = (byte)(255 + (basePixels[basePos] - 255) * basePixels[basePos + 3] / 255);
pixels[pos + 1] = (byte)(255 + (basePixels[basePos + 1] - 255) * basePixels[basePos + 3] / 255);
pixels[pos + 2] = (byte)(255 + (basePixels[basePos + 2] - 255) * basePixels[basePos + 3] / 255);
}
}
// bmpファイルのヘッダーバイナリを自作
byte[] bfSize = BitConverter.GetBytes(pixels.GetLength(0));
byte[] biWidth = BitConverter.GetBytes(bmpData.Width);
byte[] biHeight = BitConverter.GetBytes(bmpData.Height);
byte[] biSizeImage = BitConverter.GetBytes(pixels.GetLength(0) - 14 - 40);
byte[] header = new byte[14 + 40]
{
0x42, 0x4d, // bfType
bfSize[0], bfSize[1], bfSize[2], bfSize[3], // bfSize
0x00, 0x00, // bfReserved1
0x00, 0x00, // bfReserved2
0x01, 0x00, 0x00, 0x00, // bfOffBits
0x28, 0x00, 0x00, 0x00, // biSize
biWidth[0], biWidth[1], biWidth[2], biWidth[3], // biWidth
biHeight[0], biHeight[1], biHeight[2], biHeight[3], // biHeight
0x01, 0x00, // biPlanes
0x18, 0x00, // biBitCount
0x00, 0x00, 0x00, 0x00, // biCompression
biSizeImage[0], biSizeImage[1], biSizeImage[2], biSizeImage[3], // biSizeImage
0xc4, 0x0e, 0x00, 0x00, // biXPixPerMeter
0xc4, 0x0e, 0x00, 0x00, // biYPixPerMeter
0x00, 0x00, 0x00, 0x00, // biCirUsed
0x00, 0x00, 0x00, 0x00, // biCirImportant
};
Array.Copy(header, 0, pixels, 0, 14 + 40); // ヘッダーとビットマップデータをくっつける
Bitmap newBmp = new Bitmap(new MemoryStream(pixels)); // Graphicsに汚染されていないBitmapの出来上がり
posPrinter.PrintMemoryBitmap(PrinterStation.Receipt, newBmp, PosPrinter.PrinterBitmapAsIs, PosPrinter.PrinterBitmapRight); // これでOK
これだと上手くいきました。かなり抜け道的な解法ですが……まあ動いたし、そこまで見た目も汚くないので良いでしょう(?)
終わりに
正直私の解決方法も綺麗とは言い難いです。もしもっと良い方法をご存知でしたら教えて頂けると嬉しいです。
元々C++等向けだったのを.NET向けに実装したものなので、この辺りの仕様がこうなっているのは理解出来ますが、やはり使いづらいです。
この記事以外にもPOS開発関連の記事を投稿しています。もし良ければこちらからどうぞ。