cxnet 发表于 7 天前

如果打印图像

用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, 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;
    }
}

FishC 发表于 7 天前

用户的问题是:为什么使用Winspool方式可以打印文本,但不能打印图像?他们想将OpenCV的Mat灰色图像通过WritePrinter方式用EPSON喷墨打印机打印出来,但一直出纸不打印。

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

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

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


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


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


#include <windows.h>
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>

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

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

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

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

    // 填充位图数据(单色:0为黑,1为白)
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
            if (binaryMat.at<uchar>(y, x) > 0) {
                int byteIndex = y * rowSize + x / 8;
                int bitIndex = 7 - (x % 8);
                bitmapData |= (1 << bitIndex);
            }
      }
    }

    return bitmapData;
}

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

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

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

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

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

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

    return command;
}

bool PrintRawImageToPrinter(const std::wstring& printerName, const cv::Mat& image) {
    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"无法打开打印机" << std::endl;
      return false;
    }

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

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

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

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

    return true;
}


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

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

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

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 7 天前

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

PrintRawImageToPrinter函数内缺少开始文档开始页面结束文档结束页面等命令,我补上之后可以打印数据到打印机,但打印出来不是图像,而是“三”字个符号,打了好几页都是这个,属于乱码

FishC 发表于 7 天前

cxnet 发表于 2025-11-19 10:05
FishC 发表于 2025-11-19 09:52
这是因为文本数据是 ...

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

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

bool PrintRawImageToPrinter(const std::wstring& printerName, const cv::Mat& image) {
    HANDLE hPrinter = NULL;
    PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ACCESS_USE};
    DOC_INFO_1 docInfo;
    BOOL bPrinting = FALSE;
   
    // 打开打印机
    if (!OpenPrinter(const_cast<LPWSTR>(printerName.c_str()), &hPrinter, &defaults)) {
      return false;
    }
   
    // 设置文档信息
    docInfo.pDocName = L"OpenCV Image Print";
    docInfo.pOutputFile = NULL;
    docInfo.pDatatype = L"RAW";
   
    // 开始打印作业
    if (StartDocPrinter(hPrinter, 1, (LPBYTE)&docInfo) == 0) {
      ClosePrinter(hPrinter);
      return false;
    }
   
    if (!StartPagePrinter(hPrinter)) {
      EndDocPrinter(hPrinter);
      ClosePrinter(hPrinter);
      return false;
    }
   
    bPrinting = TRUE;
   
    // 将图像转换为单色位图数据
    std::vector<BYTE> bitmapData = ConvertMatToBitmap(image);
    if (bitmapData.empty()) {
      goto cleanup;
    }
   
    // 构建ESC/P命令序列
    std::vector<BYTE> escpCommands;
   
    // ESC/P初始化命令
    escpCommands.push_back(0x1B); // ESC
    escpCommands.push_back('@');// 初始化打印机
   
    // 设置位图模式 (ESC * m nL nH)
    escpCommands.push_back(0x1B); // ESC
    escpCommands.push_back('*');// 位图命令
    escpCommands.push_back(0);    // 模式0: 单密度
   
    int width = image.cols;
    int height = image.rows;
    int bytesPerLine = (width + 7) / 8; // 每行字节数
   
    // 宽度参数 (低字节在前)
    escpCommands.push_back(bytesPerLine % 256);   // nL
    escpCommands.push_back(bytesPerLine / 256);   // nH
   
    // 发送位图数据
    for (int y = 0; y < height; y++) {
      std::vector<BYTE> lineData(bitmapData.begin() + y * bytesPerLine,
                                  bitmapData.begin() + (y + 1) * bytesPerLine);
      
      // 写入位图数据
      DWORD bytesWritten;
      if (!WritePrinter(hPrinter, lineData.data(), lineData.size(), &bytesWritten)) {
            goto cleanup;
      }
      
      // 换行命令
      BYTE lineFeed = 0x0A;
      if (!WritePrinter(hPrinter, &lineFeed, 1, &bytesWritten)) {
            goto cleanup;
      }
    }
   
    // 换页命令 (如果需要)
    BYTE formFeed = 0x0C;
    DWORD bytesWritten;
    WritePrinter(hPrinter, &formFeed, 1, &bytesWritten);
   
cleanup:
    if (bPrinting) {
      EndPagePrinter(hPrinter);
      EndDocPrinter(hPrinter);
    }
    ClosePrinter(hPrinter);
    return bPrinting;
}

std::vector<BYTE> ConvertMatToBitmap(const cv::Mat& mat) {
    cv::Mat grayMat;
    if (mat.channels() > 1) {
      cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY);
    } else {
      grayMat = mat.clone();
    }
   
    // 转换为二值图像
    cv::Mat binaryMat;
    cv::threshold(grayMat, binaryMat, 128, 255, cv::THRESH_BINARY);
   
    int width = binaryMat.cols;
    int height = binaryMat.rows;
    int bytesPerLine = (width + 7) / 8; // 每行字节数 (8像素=1字节)
   
    std::vector<BYTE> bitmapData(bytesPerLine * height, 0);
   
    // 填充位图数据 (MSB顺序)
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
            // 如果像素为黑色(0),设置对应的位
            if (binaryMat.at<uchar>(y, x) == 0) {
                int byteIndex = y * bytesPerLine + x / 8;
                int bitIndex = 7 - (x % 8); // MSB顺序
                bitmapData |= (1 << bitIndex);
            }
      }
    }
   
    return bitmapData;
}

关键改进点:


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


使用方法:

cv::Mat image = cv::imread("image.jpg");
PrintRawImageToPrinter(L"Your Printer Name", image);


请确保:

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


以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 7 天前

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

“goto cleanup”跳过了“escpCommands”的初始化错误,屏蔽了goto cleanup命令行后打印出来也是乱码

FishC 发表于 7 天前

cxnet 发表于 2025-11-19 10:13
FishC 发表于 2025-11-19 10:07
您遇到的问题是发送 ...

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

bool PrintRawImageToPrinter(const std::wstring& printerName, const cv::Mat& image) {
    HANDLE hPrinter = NULL;
    PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ACCESS_USE};
    DOC_INFO_1 docInfo;
   
    if (!OpenPrinter(const_cast<LPWSTR>(printerName.c_str()), &hPrinter, &defaults)) {
      return false;
    }
   
    docInfo.pDocName = L"Image Print";
    docInfo.pOutputFile = NULL;
    docInfo.pDatatype = L"RAW";
   
    DWORD jobID = StartDocPrinter(hPrinter, 1, (LPBYTE)&docInfo);
    if (jobID == 0) {
      ClosePrinter(hPrinter);
      return false;
    }
   
    if (!StartPagePrinter(hPrinter)) {
      EndDocPrinter(hPrinter);
      ClosePrinter(hPrinter);
      return false;
    }
   
    bool success = false;
   
    // 构建ESC/P命令
    std::vector<BYTE> printData;
   
    // 1. 初始化打印机
    printData.push_back(0x1B); // ESC
    printData.push_back('@');// 初始化
   
    // 2. 设置行间距为n/216英寸 (n=24)
    printData.push_back(0x1B); // ESC
    printData.push_back('3');
    printData.push_back(24);   // 24/216英寸
   
    // 3. 转换图像为位图数据
    cv::Mat grayMat;
    if (image.channels() > 1) {
      cv::cvtColor(image, grayMat, cv::COLOR_BGR2GRAY);
    } else {
      grayMat = image.clone();
    }
   
    cv::Mat binaryMat;
    cv::threshold(grayMat, binaryMat, 128, 255, cv::THRESH_BINARY);
   
    int width = binaryMat.cols;
    int height = binaryMat.rows;
    int bytesPerLine = (width + 7) / 8;
   
    // 4. 逐行发送位图数据
    for (int y = 0; y < height; y++) {
      // ESC * m nL nH - 位图命令
      printData.push_back(0x1B); // ESC
      printData.push_back('*');// 位图命令
      printData.push_back(0);    // 单密度模式
      printData.push_back(bytesPerLine % 256); // nL
      printData.push_back(bytesPerLine / 256); // nH
      
      // 当前行的位图数据
      for (int xByte = 0; xByte < bytesPerLine; xByte++) {
            BYTE byteData = 0;
            for (int bit = 0; bit < 8; bit++) {
                int x = xByte * 8 + bit;
                if (x < width && binaryMat.at<uchar>(y, x) == 0) {
                  byteData |= (1 << (7 - bit)); // MSB first
                }
            }
            printData.push_back(byteData);
      }
      
      // 换行
      printData.push_back(0x0D); // CR
      printData.push_back(0x0A); // LF
    }
   
    // 5. 发送数据到打印机
    DWORD bytesWritten;
    if (WritePrinter(hPrinter, printData.data(), printData.size(), &bytesWritten)) {
      success = (bytesWritten == printData.size());
    }
   
    EndPagePrinter(hPrinter);
    EndDocPrinter(hPrinter);
    ClosePrinter(hPrinter);
   
    return success;
}

关键修改点:

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 小助理,如未能正确解答您的问题,请继续追问。
页: [1]
查看完整版本: 如果打印图像