鱼C论坛

 找回密码
 立即注册
查看: 37|回复: 5

如果打印图像

[复制链接]
发表于 5 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
用winspool方式可以打印文本到打印机,为什么不能打印图像,将图像转为字节打印到打印机就一直出纸不打印,我想将Opencv库的Mat灰色图像通过WritePrinter方式用EPSON喷墨打印机打印出来
bool PrintRawTextToPrinter(const std::wstring& printerName, const std::wstring& text) {
    HANDLE hPrinter = NULL;
    PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ACCESS_USE};

    // 1. 打开打印机
    if (!OpenPrinter(const_cast<LPWSTR>(printerName.c_str()), &hPrinter, &defaults)) {
        std::wcout << L"无法打开打印机,错误码: " << GetLastError() << std::endl;
        return false;
    }

    DOC_INFO_1 docInfo;
    docInfo.pDocName = L"Raw Text Print Job";
    docInfo.pOutputFile = NULL;
    docInfo.pDatatype = L"RAW";

    // 2. 开始打印作业
    DWORD jobId = StartDocPrinter(hPrinter, 1, (LPBYTE)&docInfo);
    if (jobId == 0) {
        std::wcout << L"StartDocPrinter 失败,错误码: " << GetLastError() << std::endl;
        ClosePrinter(hPrinter);
        return false;
    }

    // 3. 开始页面
    if (!StartPagePrinter(hPrinter)) {
        std::wcout << L"StartPagePrinter 失败,错误码: " << GetLastError() << std::endl;
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return false;
    }

    DWORD bytesWritten = 0;
    BOOL writeResult = FALSE;

    // 4. 写入文本数据
    // 首先写入打印机初始化命令(ESC @)
    std::string initCmd = "\x1B@"; // ESC @ 初始化打印机
    writeResult = WritePrinter(hPrinter, (LPVOID)initCmd.c_str(), initCmd.length(), &bytesWritten);
    if (!writeResult) {
        std::wcout << L"WritePrinter (初始化) 失败,错误码: " << GetLastError() << std::endl;
    }

    // 转换宽字符串到多字节字符串
    int requiredSize = WideCharToMultiByte(CP_ACP, 0, text.c_str(), -1, NULL, 0, NULL, NULL);
    std::string narrowText(requiredSize, 0);
    WideCharToMultiByte(CP_ACP, 0, text.c_str(), -1, &narrowText[0], requiredSize, NULL, NULL);

    // 写入实际文本内容
    writeResult = WritePrinter(hPrinter, (LPVOID)narrowText.c_str(), narrowText.length(), &bytesWritten);
    if (!writeResult) {
        std::wcout << L"WritePrinter (文本) 失败,错误码: " << GetLastError() << std::endl;
    }

    // 5. 结束页面
    if (!EndPagePrinter(hPrinter)) {
        std::wcout << L"EndPagePrinter 失败,错误码: " << GetLastError() << std::endl;
    }

    // 6. 结束打印作业
    if (!EndDocPrinter(hPrinter)) {
        std::wcout << L"EndDocPrinter 失败,错误码: " << GetLastError() << std::endl;
    }

    // 7. 关闭打印机句柄
    ClosePrinter(hPrinter);

    if (writeResult) {
        std::wcout << L"打印任务已成功发送到打印机" << std::endl;
        return true;
    } else {
        std::wcout << L"打印过程中出现错误" << std::endl;
        return false;
    }
}
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 小时前 | 显示全部楼层
用户的问题是:为什么使用Winspool方式可以打印文本,但不能打印图像?他们想将OpenCV的Mat灰色图像通过WritePrinter方式用EPSON喷墨打印机打印出来,但一直出纸不打印。


这是因为文本数据是纯字符,打印机可以直接解释并打印。而图像数据需要转换为打印机支持的特定格式(如位图或ESC/P命令),否则打印机会无法识别数据,导致只出纸不打印。

对于EPSON喷墨打印机,通常使用ESC/P命令集来打印图像。您需要将OpenCV的Mat图像转换为灰度位图,并嵌入到ESC/P命令中,再通过WritePrinter发送。

以下是解决方案步骤和示例代码:

  • 将OpenCV Mat图像转换为灰度位图格式。
  • 构建ESC/P命令序列,包括图像数据。
  • 使用WritePrinter发送命令和数据到打印机。


示例代码(基于您的函数修改):


  1. #include <windows.h>
  2. #include <iostream>
  3. #include <vector>
  4. #include <opencv2/opencv.hpp>

  5. // 将OpenCV Mat转换为灰度位图数据(单色位图格式)
  6. std::vector<BYTE> ConvertMatToBitmap(const cv::Mat& mat) {
  7.     // 确保图像是灰度图
  8.     cv::Mat grayMat;
  9.     if (mat.channels() > 1) {
  10.         cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY);
  11.     } else {
  12.         grayMat = mat.clone();
  13.     }

  14.     // 调整图像大小以适应打印机(示例:转换为单色位图)
  15.     cv::Mat binaryMat;
  16.     cv::threshold(grayMat, binaryMat, 128, 255, cv::THRESH_BINARY);

  17.     int width = binaryMat.cols;
  18.     int height = binaryMat.rows;

  19.     // 计算位图数据大小(每行字节数对齐到4字节)
  20.     int rowSize = ((width + 31) / 32) * 4;
  21.     std::vector<BYTE> bitmapData(rowSize * height, 0);

  22.     // 填充位图数据(单色:0为黑,1为白)
  23.     for (int y = 0; y < height; y++) {
  24.         for (int x = 0; x < width; x++) {
  25.             if (binaryMat.at<uchar>(y, x) > 0) {
  26.                 int byteIndex = y * rowSize + x / 8;
  27.                 int bitIndex = 7 - (x % 8);
  28.                 bitmapData[byteIndex] |= (1 << bitIndex);
  29.             }
  30.         }
  31.     }

  32.     return bitmapData;
  33. }

  34. // 构建ESC/P命令打印位图(适用于EPSON打印机)
  35. std::vector<BYTE> BuildESCPCommand(const std::vector<BYTE>& bitmapData, int width, int height) {
  36.     std::vector<BYTE> command;

  37.     // ESC/P命令示例:ESC * m n1 n2 data(单色位图模式)
  38.     // m=0 表示单密度,n1和n2是宽度和高度(以字节为单位)
  39.     int rowSize = ((width + 31) / 32) * 4;
  40.     int n1 = rowSize % 256;
  41.     int n2 = rowSize / 256;

  42.     // 添加命令头
  43.     command.push_back(0x1B); // ESC
  44.     command.push_back('*');  
  45.     command.push_back(0);    // m=0 单密度模式

  46.     // 添加宽度和高度
  47.     command.push_back(n1);
  48.     command.push_back(n2);

  49.     // 添加位图数据
  50.     command.insert(command.end(), bitmapData.begin(), bitmapData.end());

  51.     // 添加换行命令以确保打印完成
  52.     command.push_back(0x0A); // LF

  53.     return command;
  54. }

  55. bool PrintRawImageToPrinter(const std::wstring& printerName, const cv::Mat& image) {
  56.     HANDLE hPrinter = NULL;
  57.     PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ACCESS_USE};

  58.     // 1. 打开打印机
  59.     if (!OpenPrinter(const_cast<LPWSTR>(printerName.c_str()), &hPrinter, &defaults)) {
  60.         std::wcout << L"无法打开打印机" << std::endl;
  61.         return false;
  62.     }

  63.     // 2. 转换图像为位图并构建ESC/P命令
  64.     std::vector<BYTE> bitmapData = ConvertMatToBitmap(image);
  65.     std::vector<BYTE> escpCommand = BuildESCPCommand(bitmapData, image.cols, image.rows);

  66.     // 3. 发送数据到打印机
  67.     DWORD bytesWritten;
  68.     BOOL result = WritePrinter(hPrinter, escpCommand.data(), escpCommand.size(), &bytesWritten);

  69.     // 4. 关闭打印机
  70.     ClosePrinter(hPrinter);

  71.     if (!result || bytesWritten != escpCommand.size()) {
  72.         std::wcout << L"打印失败" << std::endl;
  73.         return false;
  74.     }

  75.     return true;
  76. }
复制代码


使用说明:
- 确保OpenCV库已正确链接。
- 调用PrintRawImageToPrinter函数,传入打印机名称和OpenCV Mat图像。
- 此代码将图像转换为单色位图,并使用ESC/P命令发送,适用于大多数EPSON打印机。

注意事项:
- 如果打印机不支持ESC/P,可能需要调整命令(参考打印机手册)。
- 图像大小可能需调整以避免内存问题。
- 测试前请确保打印机连接正常。

如果问题仍然存在,请检查打印机驱动或尝试其他图像格式(如PCL)。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 5 小时前 | 显示全部楼层
FishC 发表于 2025-11-19 09:52
这是因为文本数据是纯字符,打印机可以直接解释并打印。而图像数据需要转换为打印机支持的特定格式(如 ...

PrintRawImageToPrinter函数内缺少开始文档开始页面结束文档结束页面等命令,我补上之后可以打印数据到打印机,但打印出来不是图像,而是“三”字个符号,打了好几页都是这个,属于乱码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 5 小时前 | 显示全部楼层
cxnet 发表于 2025-11-19 10:05
FishC 发表于 2025-11-19 09:52
这是因为文本数据是 ...


您遇到的问题是发送到打印机的数据格式不正确。打印机将图像数据误解释为文本,所以打印出了乱码字符。需要正确构建包含图像数据的ESC/P命令序列。

以下是修正后的完整代码:

  1. bool PrintRawImageToPrinter(const std::wstring& printerName, const cv::Mat& image) {
  2.     HANDLE hPrinter = NULL;
  3.     PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ACCESS_USE};
  4.     DOC_INFO_1 docInfo;
  5.     BOOL bPrinting = FALSE;
  6.    
  7.     // 打开打印机
  8.     if (!OpenPrinter(const_cast<LPWSTR>(printerName.c_str()), &hPrinter, &defaults)) {
  9.         return false;
  10.     }
  11.    
  12.     // 设置文档信息
  13.     docInfo.pDocName = L"OpenCV Image Print";
  14.     docInfo.pOutputFile = NULL;
  15.     docInfo.pDatatype = L"RAW";
  16.    
  17.     // 开始打印作业
  18.     if (StartDocPrinter(hPrinter, 1, (LPBYTE)&docInfo) == 0) {
  19.         ClosePrinter(hPrinter);
  20.         return false;
  21.     }
  22.    
  23.     if (!StartPagePrinter(hPrinter)) {
  24.         EndDocPrinter(hPrinter);
  25.         ClosePrinter(hPrinter);
  26.         return false;
  27.     }
  28.    
  29.     bPrinting = TRUE;
  30.    
  31.     // 将图像转换为单色位图数据
  32.     std::vector<BYTE> bitmapData = ConvertMatToBitmap(image);
  33.     if (bitmapData.empty()) {
  34.         goto cleanup;
  35.     }
  36.    
  37.     // 构建ESC/P命令序列
  38.     std::vector<BYTE> escpCommands;
  39.    
  40.     // ESC/P初始化命令
  41.     escpCommands.push_back(0x1B); // ESC
  42.     escpCommands.push_back('@');  // 初始化打印机
  43.    
  44.     // 设置位图模式 (ESC * m nL nH)
  45.     escpCommands.push_back(0x1B); // ESC
  46.     escpCommands.push_back('*');  // 位图命令
  47.     escpCommands.push_back(0);    // 模式0: 单密度
  48.    
  49.     int width = image.cols;
  50.     int height = image.rows;
  51.     int bytesPerLine = (width + 7) / 8; // 每行字节数
  52.    
  53.     // 宽度参数 (低字节在前)
  54.     escpCommands.push_back(bytesPerLine % 256);   // nL
  55.     escpCommands.push_back(bytesPerLine / 256);   // nH
  56.    
  57.     // 发送位图数据
  58.     for (int y = 0; y < height; y++) {
  59.         std::vector<BYTE> lineData(bitmapData.begin() + y * bytesPerLine,
  60.                                   bitmapData.begin() + (y + 1) * bytesPerLine);
  61.         
  62.         // 写入位图数据
  63.         DWORD bytesWritten;
  64.         if (!WritePrinter(hPrinter, lineData.data(), lineData.size(), &bytesWritten)) {
  65.             goto cleanup;
  66.         }
  67.         
  68.         // 换行命令
  69.         BYTE lineFeed = 0x0A;
  70.         if (!WritePrinter(hPrinter, &lineFeed, 1, &bytesWritten)) {
  71.             goto cleanup;
  72.         }
  73.     }
  74.    
  75.     // 换页命令 (如果需要)
  76.     BYTE formFeed = 0x0C;
  77.     DWORD bytesWritten;
  78.     WritePrinter(hPrinter, &formFeed, 1, &bytesWritten);
  79.    
  80. cleanup:
  81.     if (bPrinting) {
  82.         EndPagePrinter(hPrinter);
  83.         EndDocPrinter(hPrinter);
  84.     }
  85.     ClosePrinter(hPrinter);
  86.     return bPrinting;
  87. }

  88. std::vector<BYTE> ConvertMatToBitmap(const cv::Mat& mat) {
  89.     cv::Mat grayMat;
  90.     if (mat.channels() > 1) {
  91.         cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY);
  92.     } else {
  93.         grayMat = mat.clone();
  94.     }
  95.    
  96.     // 转换为二值图像
  97.     cv::Mat binaryMat;
  98.     cv::threshold(grayMat, binaryMat, 128, 255, cv::THRESH_BINARY);
  99.    
  100.     int width = binaryMat.cols;
  101.     int height = binaryMat.rows;
  102.     int bytesPerLine = (width + 7) / 8; // 每行字节数 (8像素=1字节)
  103.    
  104.     std::vector<BYTE> bitmapData(bytesPerLine * height, 0);
  105.    
  106.     // 填充位图数据 (MSB顺序)
  107.     for (int y = 0; y < height; y++) {
  108.         for (int x = 0; x < width; x++) {
  109.             // 如果像素为黑色(0),设置对应的位
  110.             if (binaryMat.at<uchar>(y, x) == 0) {
  111.                 int byteIndex = y * bytesPerLine + x / 8;
  112.                 int bitIndex = 7 - (x % 8); // MSB顺序
  113.                 bitmapData[byteIndex] |= (1 << bitIndex);
  114.             }
  115.         }
  116.     }
  117.    
  118.     return bitmapData;
  119. }
复制代码


关键改进点:

  • 添加了完整的打印作业管理(StartDocPrinter、StartPagePrinter等)
  • 使用正确的ESC/P命令序列:
  • ESC @ - 初始化打印机
  • ESC * - 设置位图模式
  • 正确的位图数据格式(MSB顺序)
  • 添加换行和换页命令
  • 修正了位图数据转换函数,确保数据格式正确


使用方法:

  1. cv::Mat image = cv::imread("image.jpg");
  2. PrintRawImageToPrinter(L"Your Printer Name", image);
复制代码


请确保:
  • 打印机名称正确
  • 图像尺寸适合打印机分辨率
  • 打印机支持ESC/P命令集(大多数EPSON打印机都支持)


以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 5 小时前 | 显示全部楼层
FishC 发表于 2025-11-19 10:07
您遇到的问题是发送到打印机的数据格式不正确。打印机将图像数据误解释为文本,所以打印出了乱码字符。 ...

“goto cleanup”跳过了“escpCommands”的初始化错误,屏蔽了goto cleanup命令行后打印出来也是乱码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 5 小时前 | 显示全部楼层
cxnet 发表于 2025-11-19 10:13
FishC 发表于 2025-11-19 10:07
您遇到的问题是发送 ...


您说得对,`goto cleanup`确实跳过了错误处理。乱码问题是因为图像数据没有正确转换为打印机可识别的格式。让我们重新整理一个完整的解决方案:

  1. bool PrintRawImageToPrinter(const std::wstring& printerName, const cv::Mat& image) {
  2.     HANDLE hPrinter = NULL;
  3.     PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ACCESS_USE};
  4.     DOC_INFO_1 docInfo;
  5.    
  6.     if (!OpenPrinter(const_cast<LPWSTR>(printerName.c_str()), &hPrinter, &defaults)) {
  7.         return false;
  8.     }
  9.    
  10.     docInfo.pDocName = L"Image Print";
  11.     docInfo.pOutputFile = NULL;
  12.     docInfo.pDatatype = L"RAW";
  13.    
  14.     DWORD jobID = StartDocPrinter(hPrinter, 1, (LPBYTE)&docInfo);
  15.     if (jobID == 0) {
  16.         ClosePrinter(hPrinter);
  17.         return false;
  18.     }
  19.    
  20.     if (!StartPagePrinter(hPrinter)) {
  21.         EndDocPrinter(hPrinter);
  22.         ClosePrinter(hPrinter);
  23.         return false;
  24.     }
  25.    
  26.     bool success = false;
  27.    
  28.     // 构建ESC/P命令
  29.     std::vector<BYTE> printData;
  30.    
  31.     // 1. 初始化打印机
  32.     printData.push_back(0x1B); // ESC
  33.     printData.push_back('@');  // 初始化
  34.    
  35.     // 2. 设置行间距为n/216英寸 (n=24)
  36.     printData.push_back(0x1B); // ESC
  37.     printData.push_back('3');  
  38.     printData.push_back(24);   // 24/216英寸
  39.    
  40.     // 3. 转换图像为位图数据
  41.     cv::Mat grayMat;
  42.     if (image.channels() > 1) {
  43.         cv::cvtColor(image, grayMat, cv::COLOR_BGR2GRAY);
  44.     } else {
  45.         grayMat = image.clone();
  46.     }
  47.    
  48.     cv::Mat binaryMat;
  49.     cv::threshold(grayMat, binaryMat, 128, 255, cv::THRESH_BINARY);
  50.    
  51.     int width = binaryMat.cols;
  52.     int height = binaryMat.rows;
  53.     int bytesPerLine = (width + 7) / 8;
  54.    
  55.     // 4. 逐行发送位图数据
  56.     for (int y = 0; y < height; y++) {
  57.         // ESC * m nL nH - 位图命令
  58.         printData.push_back(0x1B); // ESC
  59.         printData.push_back('*');  // 位图命令
  60.         printData.push_back(0);    // 单密度模式
  61.         printData.push_back(bytesPerLine % 256); // nL
  62.         printData.push_back(bytesPerLine / 256); // nH
  63.         
  64.         // 当前行的位图数据
  65.         for (int xByte = 0; xByte < bytesPerLine; xByte++) {
  66.             BYTE byteData = 0;
  67.             for (int bit = 0; bit < 8; bit++) {
  68.                 int x = xByte * 8 + bit;
  69.                 if (x < width && binaryMat.at<uchar>(y, x) == 0) {
  70.                     byteData |= (1 << (7 - bit)); // MSB first
  71.                 }
  72.             }
  73.             printData.push_back(byteData);
  74.         }
  75.         
  76.         // 换行
  77.         printData.push_back(0x0D); // CR
  78.         printData.push_back(0x0A); // LF
  79.     }
  80.    
  81.     // 5. 发送数据到打印机
  82.     DWORD bytesWritten;
  83.     if (WritePrinter(hPrinter, printData.data(), printData.size(), &bytesWritten)) {
  84.         success = (bytesWritten == printData.size());
  85.     }
  86.    
  87.     EndPagePrinter(hPrinter);
  88.     EndDocPrinter(hPrinter);
  89.     ClosePrinter(hPrinter);
  90.    
  91.     return success;
  92. }
复制代码


关键修改点:

1. 移除了`goto cleanup`,使用正常的错误处理流程
2. 添加了正确的ESC/P命令序列:
   - ESC @ - 初始化打印机
   - ESC 3 - 设置行间距
   - ESC * - 位图模式命令
3. 修正了位图数据生成逻辑,确保MSB顺序正确
4. 每行位图数据后添加CR+LF换行

如果仍然出现乱码,请检查:
- 打印机是否支持ESC/P命令集
- 图像尺寸是否适合打印机分辨率
- 尝试使用不同的位图模式(将`printData.push_back(0)`改为`printData.push_back(1)`或`printData.push_back(32)`)

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-11-19 15:44

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表