LoginSignup
2
0

More than 1 year has passed since last update.

【OPOS・POS for .NET】PrintMemoryBitmapで、Graphicsで編集したBitmapを渡せない件

Last updated at Posted at 2020-07-31

これは「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開発関連の記事を投稿しています。もし良ければこちらからどうぞ。

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0