文章目录
- 前言
- 一、条码打印功能模块
- 二、图片打印功能
- 1.图片转斑马协议字段
- 2.Canvas 生成图片
- 三、斑马打印模拟器
- 四、斑马打印机偏移调节
- 总结
前言
最近客户要求新增斑马打印机打印出货牌,按照斑马的文档规则来画可真麻烦,所以打算直接界面画好展示直接图片打印好了。
一、条码打印功能模块
于是找到了一个标准的打印模块代码
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);
// SendBytesToPrinter()
// When the function is given a printer name and an unmanaged array
// of bytes, the function sends those bytes to the print queue.
// Returns true on success, false on failure.
public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
{
Int32 dwError = 0, dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false; // Assume failure unless you specifically succeed.
di.pDocName = "My C#.NET RAW Document";
di.pDataType = "RAW";
// Open the printer.
if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
// Start a document.
if (StartDocPrinter(hPrinter, 1, di))
{
// Start a page.
if (StartPagePrinter(hPrinter))
{
// Write your bytes.
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if (bSuccess == false)
{
dwError = Marshal.GetLastWin32Error();
}
return bSuccess;
}
public static bool SendFileToPrinter(string szPrinterName, string szFileName)
{
// Open the file.
FileStream fs = new FileStream(szFileName, FileMode.Open);
// Create a BinaryReader on the file.
BinaryReader br = new BinaryReader(fs);
// Dim an array of bytes big enough to hold the file's contents.
Byte[] bytes = new Byte[fs.Length];
bool bSuccess = false;
// Your unmanaged pointer.
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength;
nLength = Convert.ToInt32(fs.Length);
// Read the contents of the file into the array.
bytes = br.ReadBytes(nLength);
// Allocate some unmanaged memory for those bytes.
pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
// Copy the managed byte array into the unmanaged array.
Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
// Send the unmanaged bytes to the printer.
bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
// Free the unmanaged memory that you allocated earlier.
Marshal.FreeCoTaskMem(pUnmanagedBytes);
return bSuccess;
}
public static bool SendStringToPrinter(string szPrinterName, string szString)
{
IntPtr pBytes;
Int32 dwCount;
// How many characters are in the string?
dwCount = szString.Length;
// Assume that the printer is expecting ANSI text, and then convert
// the string to ANSI text.
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
// Send the converted ANSI string to the printer.
SendBytesToPrinter(szPrinterName, pBytes, dwCount);
Marshal.FreeCoTaskMem(pBytes);
return true;
}
/// <summary>
/// 打印条码
/// </summary>
/// <param name="str"></param>
/// <param name="colorDepth"></param>
/// <param name="marginX"></param>
/// <param name="marginY"></param>
/// <param name="marginX1"></param>
/// <param name="marginY1"></param>
/// <param name="fontSizeHeight"></param>
/// <param name="fontSizeWidth"></param>
/// <param name="barCodeScale1"></param>
/// <param name="barCodeScale2"></param>
/// <param name="barCodeHeight"></param>
/// <returns></returns>
public static string GetZd420String(string str,
int colorDepth = 30,
int marginX = 380,
int marginY = 40,
int marginX1 = 0,
int marginY1 = 0,
int fontSizeHeight = 30,
int fontSizeWidth = 24,
int barCodeScale1 = 1,
int barCodeScale2 = 5,
int barCodeHeight = 100)
{
StringBuilder builder = new StringBuilder();
builder.AppendLine($"^XA");
builder.AppendLine($"^MD{colorDepth}");
builder.AppendLine($"^LH{marginX},{marginY}");
builder.AppendLine($"^FO{marginX1},{marginY1}^GB40");
builder.AppendLine($"^ACN,{fontSizeHeight},{fontSizeWidth}");
builder.AppendLine($"^A0N,40,47");
builder.AppendLine($"^BY{barCodeScale1}.{barCodeScale2},2,{barCodeHeight}");
builder.AppendLine($"^BCN,,Y,N");
builder.AppendLine($"^FD{str}^FS");
builder.AppendLine($"^XZ");
return builder.ToString();
}
这么一来,直接遍历打印机,选一个打印就好了,对于简易的条码打印已经完成功能了
List<string> strlist = new List<string>();
foreach (string sPrint in PrinterSettings.InstalledPrinters)
{
strlist .Add(sPrint);
}
SendStringToPrinter("打印机名称", GetZd420String(str))
二、图片打印功能
1.图片转斑马协议字段
public string SendBitmap(Bitmap bitmap)
{
int totalbytes = 64800;
int rowbytes = 90;
string hex = BitmapToHex(bitmap, out totalbytes, out rowbytes);
string mubanstring =
"^XA" +
"~DGR:ZLOGO.GRF," + totalbytes.ToString() + "," + rowbytes.ToString() + "," + hex +
"^XA^FO0,0^XGR:ZLOGO.GRF,1,1^FS" +
"^XZ";
return mubanstring;
}
public string SendBitmapPath(string bitmapPath, double doubleNum = 1)
{
Bitmap bitmap = new Bitmap(bitmapPath);
int newWidth = (int)(bitmap.Width * doubleNum);
int newHeight = (int)(bitmap.Height * doubleNum);
bitmap = (Bitmap)bitmap.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
int totalbytes = 64800;
int rowbytes = 90;
string hex = BitmapToHex(bitmap, out totalbytes, out rowbytes);
string mubanstring =
"^XA" +
"~DGR:ZLOGO.GRF," + totalbytes.ToString() + "," + rowbytes.ToString() + "," + hex +
$"^XA^FO0,0^XGR:ZLOGO.GRF,1,1^FS" +
"^XZ";
return mubanstring;
}
/// <summary>
/// 根据图片生成图片的ASCII 十六进制
/// </summary>
/// <param name="sourceBmp">原始图片</param>
/// <param name="totalBytes">总共字节数</param>
/// <param name="rowBytes">每行的字节数</param>
/// <returns>ASCII 十六进制</returns>
public string BitmapToHex(Image sourceBmp, out int totalBytes, out int rowBytes)
{
// 转成单色图
Bitmap grayBmp = ConvertToGrayscale(sourceBmp as Bitmap);
// 锁定位图数据
Rectangle rect = new Rectangle(0, 0, grayBmp.Width, grayBmp.Height);
System.Drawing.Imaging.BitmapData bmpData = grayBmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, grayBmp.PixelFormat);
// 获取位图数据第一行的起始地址
IntPtr ptr = bmpData.Scan0;
// 定义数组以存放位图的字节流数据
// 处理像素宽对应的字节数,如不为8的倍数,则对最后一个字节补0
int width = (int)Math.Ceiling(grayBmp.Width / 8.0);
// 获取位图实际的字节宽,这个值因为要考虑4的倍数关系,可能大于width
int stride = Math.Abs(bmpData.Stride);
// 计算位图数据实际所占的字节数,并定义数组
int bitmapDataLength = stride * grayBmp.Height;
byte[] ImgData = new byte[bitmapDataLength];
// 从位图文件复制图像数据到数组,从实际图像数据的第一行开始;因ptr指针而无需再考虑行倒序存储的处理
System.Runtime.InteropServices.Marshal.Copy(ptr, ImgData, 0, bitmapDataLength);
// 计算异或操作数,以处理包含图像数据但又有补0操作的那个字节
byte mask = 0xFF;
// 计算这个字节补0的个数
//int offset = 8 * width - grayBmp.Width;
int offset = 8 - (grayBmp.Width % 8);
//offset %= 8;
offset = offset % 8;
// 按补0个数对0xFF做相应位数的左移位操作
mask <<= (byte)offset;
// 图像反色处理
for (int j = 0; j < grayBmp.Height; j++)
{
for (int i = 0; i < stride; i++)
{
if (i < width - 1) //无补0的图像数据
{
ImgData[j * stride + i] ^= 0xFF;
}
else if (i == width - 1) //有像素的最后一个字节,可能有补0
{
ImgData[j * stride + i] ^= mask;
}
else //为满足行字节宽为4的倍数而最后补的字节
{
//ImgData[j * stride + i] = 0x00;
ImgData[j * stride + i] ^= 0x00;
}
}
}
// 将位图数据转换为16进制的ASCII字符
string zplString = BitConverter.ToString(ImgData).Replace("-", string.Empty);
zplString = CompressLZ77(zplString);
totalBytes = bitmapDataLength;
rowBytes = stride;
return zplString;
}
#region 获取单色位图数据
/// <summary>
/// 获取单色位图数据
/// </summary>
/// <param name="pimage"></param>
/// <returns></returns>
private Bitmap ConvertToGrayscale(Bitmap pimage)
{
Bitmap source = null;
// If original bitmap is not already in 32 BPP, ARGB format, then convert
if (pimage.PixelFormat != PixelFormat.Format32bppArgb)
{
source = new Bitmap(pimage.Width, pimage.Height, PixelFormat.Format32bppArgb);
source.SetResolution(pimage.HorizontalResolution, pimage.VerticalResolution);
using (Graphics g = Graphics.FromImage(source))
{
g.DrawImageUnscaled(pimage, 0, 0);
}
}
else
{
source = pimage;
}
// Lock source bitmap in memory
BitmapData sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// Copy image data to binary array
int imageSize = sourceData.Stride * sourceData.Height;
byte[] sourceBuffer = new byte[imageSize];
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize);
// Unlock source bitmap
source.UnlockBits(sourceData);
// Create destination bitmap
Bitmap destination = new Bitmap(source.Width, source.Height, PixelFormat.Format1bppIndexed);
// Lock destination bitmap in memory
BitmapData destinationData = destination.LockBits(new Rectangle(0, 0, destination.Width, destination.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
// Create destination buffer
imageSize = destinationData.Stride * destinationData.Height;
byte[] destinationBuffer = new byte[imageSize];
int sourceIndex = 0;
int destinationIndex = 0;
int pixelTotal = 0;
byte destinationValue = 0;
int pixelValue = 128;
int height = source.Height;
int width = source.Width;
int threshold = 500;
// Iterate lines
for (int y = 0; y < height; y++)
{
sourceIndex = y * sourceData.Stride;
destinationIndex = y * destinationData.Stride;
destinationValue = 0;
pixelValue = 128;
// Iterate pixels
for (int x = 0; x < width; x++)
{
// Compute pixel brightness (i.e. total of Red, Green, and Blue values)
pixelTotal = sourceBuffer[sourceIndex + 1] + sourceBuffer[sourceIndex + 2] + sourceBuffer[sourceIndex + 3];
if (pixelTotal > threshold)
{
destinationValue += (byte)pixelValue;
}
if (pixelValue == 1)
{
destinationBuffer[destinationIndex] = destinationValue;
destinationIndex++;
destinationValue = 0;
pixelValue = 128;
}
else
{
pixelValue >>= 1;
}
sourceIndex += 4;
}
if (pixelValue != 128)
{
destinationBuffer[destinationIndex] = destinationValue;
}
}
// Copy binary image data to destination bitmap
Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize);
// Unlock destination bitmap
destination.UnlockBits(destinationData);
// Dispose of source if not originally supplied bitmap
if (source != pimage)
{
source.Dispose();
}
// Return
return destination;
}
/// <summary>
/// 获取单色位图数据(1bpp),不含文件头、信息头、调色板三类数据。
/// </summary>
/// <returns></returns>
private byte[] getBitmapData()
{
MemoryStream srcStream = new MemoryStream();
MemoryStream dstStream = new MemoryStream();
Bitmap srcBmp = null;
Bitmap dstBmp = null;
byte[] srcBuffer = null;
byte[] dstBuffer = null;
byte[] result = null;
try
{
srcStream = new MemoryStream(GraphBuffer);
srcBmp = Bitmap.FromStream(srcStream) as Bitmap;
srcBuffer = srcStream.ToArray();
GraphWidth = srcBmp.Width;
GraphHeight = srcBmp.Height;
//dstBmp = srcBmp.Clone(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), PixelFormat.Format1bppIndexed);
dstBmp = ConvertToGrayscale(srcBmp);
dstBmp.Save(dstStream, ImageFormat.Bmp);
dstBuffer = dstStream.ToArray();
result = dstBuffer;
int bfOffBits = BitConverter.ToInt32(dstBuffer, 10);
result = new byte[GraphHeight * RowRealBytesCount];
读取时需要反向读取每行字节实现上下翻转的效果,打印机打印顺序需要这样读取。
for (int i = 0; i < GraphHeight; i++)
{
int sindex = bfOffBits + (GraphHeight - 1 - i) * RowSize;
int dindex = i * RowRealBytesCount;
Array.Copy(dstBuffer, sindex, result, dindex, RowRealBytesCount);
}
for (int i = 0; i < result.Length; i++)
{
result[i] ^= 0xFF;
}
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
finally
{
if (srcStream != null)
{
srcStream.Dispose();
srcStream = null;
}
if (dstStream != null)
{
dstStream.Dispose();
dstStream = null;
}
if (srcBmp != null)
{
srcBmp.Dispose();
srcBmp = null;
}
if (dstBmp != null)
{
dstBmp.Dispose();
dstBmp = null;
}
}
return result;
}
#endregion
#region 定义属性
/// <summary>
/// 图像的二进制数据
/// </summary>
public static byte[] GraphBuffer { get; set; }
/// <summary>
/// 图像的宽度
/// </summary>
private static int GraphWidth { get; set; }
/// <summary>
/// 图像的高度
/// </summary>
private static int GraphHeight { get; set; }
private static int RowSize
{
get
{
return (((GraphWidth) + 31) >> 5) << 2;
}
}
/// <summary>
/// 每行的字节数
/// </summary>
private static int RowRealBytesCount
{
get
{
if ((GraphWidth % 8) > 0)
{
return GraphWidth / 8 + 1;
}
else
{
return GraphWidth / 8;
}
}
}
#endregion
#region 定义私有字段
/// <summary>
/// 线程锁,防止多线程调用。
/// </summary>
private static object SyncRoot = new object();
/// <summary>
/// ZPL压缩字典
/// </summary>
private List<KeyValuePair<char, int>> compressDictionary;
public List<KeyValuePair<char, int>> CompressDictionary
{
get
{
if (compressDictionary is null)
{
InitCompressCode();
}
return compressDictionary;
}
}
#endregion
#region LZ77图像字节流压缩方法
private string CompressLZ77(string text)
{
//将转成16进制的文本进行压缩
string result = string.Empty;
char[] arrChar = text.ToCharArray();
int count = 1;
for (int i = 1; i < text.Length; i++)
{
if (arrChar[i - 1] == arrChar[i])
{
count++;
}
else
{
result += convertNumber(count) + arrChar[i - 1];
count = 1;
}
if (i == text.Length - 1)
{
result += convertNumber(count) + arrChar[i];
}
}
return result;
}
private string DecompressLZ77(string text)
{
string result = string.Empty;
char[] arrChar = text.ToCharArray();
int count = 0;
for (int i = 0; i < arrChar.Length; i++)
{
if (isHexChar(arrChar[i]))
{
//十六进制值
result += new string(arrChar[i], count == 0 ? 1 : count);
count = 0;
}
else
{
//压缩码
int value = GetCompressValue(arrChar[i]);
count += value;
}
}
return result;
}
private int GetCompressValue(char c)
{
int result = 0;
for (int i = 0; i < CompressDictionary.Count; i++)
{
if (c == CompressDictionary[i].Key)
{
result = CompressDictionary[i].Value;
}
}
return result;
}
private bool isHexChar(char c)
{
return c > 47 && c < 58 || c > 64 && c < 71 || c > 96 && c < 103;
}
private string convertNumber(int count)
{
//将连续的数字转换成LZ77压缩代码,如000可用I0表示。
string result = string.Empty;
if (count > 1)
{
while (count > 0)
{
for (int i = CompressDictionary.Count - 1; i >= 0; i--)
{
if (count >= CompressDictionary[i].Value)
{
result += CompressDictionary[i].Key;
count -= CompressDictionary[i].Value;
break;
}
}
}
}
return result;
}
private void InitCompressCode()
{
compressDictionary = new List<KeyValuePair<char, int>>();
//G H I J K L M N O P Q R S T U V W X Y 对应1,2,3,4……18,19。
//g h i j k l m n o p q r s t u v w x y z 对应20,40,60,80……340,360,380,400。
for (int i = 0; i < 19; i++)
{
compressDictionary.Add(new KeyValuePair<char, int>(Convert.ToChar(71 + i), i + 1));
}
for (int i = 0; i < 20; i++)
{
compressDictionary.Add(new KeyValuePair<char, int>(Convert.ToChar(103 + i), (i + 1) * 20));
}
}
#endregion
这下图片的打印代码也搞定了
//可以直接传路径
SendStringToPrinter("打印机名称", SendBitmapPath("图片路径","放大倍数"));
//不需要保留图片的要求可以直接Bitmap打印
SendStringToPrinter("打印机名称", SendBitmap(Bitmap)));
2.Canvas 生成图片
客户样板就不展示了,写一个模板CanvasXAML代码:
<Window x:Class="SaveCanvasAsJpg.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Save Canvas as JPG" Height="300" Width="400">
<Grid>
<Canvas x:Name="canvas" Background="White" Width="200" Height="150" Margin="50">
<Ellipse Fill="Red" Width="100" Height="100"/>
<TextBlock Text="Hello, World!" Margin="30, 60, 0, 0" FontWeight="Bold"/>
</Canvas>
<Button Content="Save as JPG" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0, 0, 0, 20" Click="Button_Click"/>
</Grid>
</Window>
C#代码:
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace SaveCanvasAsJpg
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// 获取Canvas控件的尺寸
double width = canvas.ActualWidth;
double height = canvas.ActualHeight;
// 创建RenderTargetBitmap对象,并以Canvas为参数进行实例化
RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)width, (int)height, 96d, 96d, System.Windows.Media.PixelFormats.Pbgra32);
renderBitmap.Render(canvas);
// 创建JpegBitmapEncoder对象,并以RenderTargetBitmap为参数进行实例化
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// 将jpg图片保存到D盘的示例路径中
string path = @"D:\canvas.jpg";
using (FileStream fs = File.Open(path, FileMode.Create))
{
encoder.Save(fs);
}
MessageBox.Show("Image saved to " + path);
}
}
}
三、斑马打印模拟器
远程开发头疼的就是写完不知道打印效果,后来找了个模拟器网站测试,强力推荐,这下十拿九稳了
http://labelary.com/
四、斑马打印机偏移调节
最后打印效果还需要居中一点点,直接电脑上微调了
总结
功能是实现了,就是个人觉得代码越少越好,看上去代码还是太多了,有更好的实现方法还请各位大佬指点指点。
斑马打印机的规则为没有太深的去研究,大家可以去看看一下链接大佬写的,会了斑马打印机协议也是一种不错的选择,就是感觉可视化比较麻烦,而且客户要求要保留条码图片。