580 lines
20 KiB
C++
580 lines
20 KiB
C++
#include "ItemCommon.h"
|
||
#include "DlgQrcodeFormat.h"
|
||
#include "DlgBarcodeFormat.h"
|
||
#include <QtQREnCode/qrencode.h>
|
||
#include "Matrix/dm200.h"
|
||
#include "BarCode/barcode.h"
|
||
#include <QPainter>
|
||
#include <cstring>
|
||
#include <QDebug>
|
||
|
||
/**
|
||
* @brief 将libqrencode的QRcode结构渲染成QImage
|
||
*
|
||
* 避免依赖文件系统或PostScript输出,直接在内存中生成可用于UI显示或打印的图像。
|
||
* 使用2D像素数组而非向量,支持任意缩放因子。
|
||
*/
|
||
static QImage renderQRCode(const QRcode *qrcode, int moduleSize = 4, QRgb foreground = 0xff000000, QRgb background = 0xffffffff)
|
||
{
|
||
if (!qrcode || qrcode->width <= 0) {
|
||
return QImage();
|
||
}
|
||
|
||
int margin = 1;
|
||
int imageSize = (qrcode->width + margin * 2) * moduleSize;
|
||
QImage image(imageSize, imageSize, QImage::Format_ARGB32);
|
||
image.fill(background);
|
||
|
||
const unsigned char *data = qrcode->data;
|
||
for (int y = 0; y < qrcode->width; ++y) {
|
||
for (int x = 0; x < qrcode->width; ++x) {
|
||
bool moduleOn = (data[y * qrcode->width + x] & 0x01) != 0;
|
||
QRgb color = moduleOn ? foreground : background;
|
||
int px = (margin + x) * moduleSize;
|
||
int py = (margin + y) * moduleSize;
|
||
for (int dy = 0; dy < moduleSize; ++dy) {
|
||
for (int dx = 0; dx < moduleSize; ++dx) {
|
||
image.setPixel(px + dx, py + dy, color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return image;
|
||
}
|
||
|
||
/**
|
||
* @brief 将DataMatrix位图网格渲染成QImage
|
||
*
|
||
* iec16022ecc200返回原始位数组,需转换为可视化图像以供UI或打印使用。
|
||
* 与renderQRCode采用相同的像素化策略,确保两种编码方式的输出一致性。
|
||
*/
|
||
static QImage renderDataMatrix(const unsigned char *grid, int cols, int rows, int moduleSize = 1, QRgb foreground = 0xff000000, QRgb background = 0xffffffff)
|
||
{
|
||
if (!grid || cols <= 0 || rows <= 0) {
|
||
return QImage();
|
||
}
|
||
|
||
int width = rows * moduleSize;
|
||
int height = cols * moduleSize;
|
||
QImage image(width, height, QImage::Format_ARGB32);
|
||
image.fill(background);
|
||
|
||
for (int y = 0; y < rows; ++y) {
|
||
for (int x = 0; x < cols; ++x) {
|
||
bool bit = (grid[y * cols + x] & 0x01) != 0;
|
||
QRgb color = bit ? foreground : background;
|
||
int px = y * moduleSize;
|
||
int py = (cols - 1 - x) * moduleSize;
|
||
for (int dy = 0; dy < moduleSize; ++dy) {
|
||
for (int dx = 0; dx < moduleSize; ++dx) {
|
||
image.setPixel(px + dx, py + dy, color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return image;
|
||
}
|
||
|
||
static QRecLevel qrCodeLevelFromEnum(QRCodeLevel level)
|
||
{
|
||
switch (level) {
|
||
case QR_CODE_LEVEL_M: return QR_ECLEVEL_M;
|
||
case QR_CODE_LEVEL_Q: return QR_ECLEVEL_Q;
|
||
case QR_CODE_LEVEL_H: return QR_ECLEVEL_H;
|
||
default: return QR_ECLEVEL_L;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 根据格式配置生成二维码或DataMatrix图像
|
||
*
|
||
* 支持两种编码方式:
|
||
* - DataMatrix:适合小容量数据和有限空间环境(如工业标签)
|
||
* - QR Code:高容量、高可靠性,广泛应用于商业和物流
|
||
*
|
||
* 直接在内存中渲染,避免对dlgQrCode或外部库的依赖。
|
||
* 颜色反转机制(INK_TYPE_WHITE)用于支持黑底白码的特殊打印场景。
|
||
*/
|
||
static QImage generateQRCodeImage(const QString &content, const DlgQrcodeFormatVM &vm)
|
||
{
|
||
if (content.isEmpty()) {
|
||
return QImage();
|
||
}
|
||
|
||
QRecLevel level = qrCodeLevelFromEnum(vm.level);
|
||
|
||
// 适配黑底白码场景(逆色):打印机某些模式下需要反转前景/背景色
|
||
QRgb foreground = vm.inkType == INK_TYPE_WHITE ? 0x00ffffff : 0xff000000;
|
||
QRgb background = vm.inkType == INK_TYPE_WHITE ? 0xff000000 : 0x00ffffff;
|
||
|
||
QByteArray bytes = content.toUtf8();
|
||
|
||
// DataMatrix:调用者指定尺寸约束(sizeType),库返回实际分配的网格
|
||
if (vm.type == QR_CODE_TYPE_DATA_MATRIX) {
|
||
short cols = 0;
|
||
short rows = 0;
|
||
short sizeType = static_cast<short>(vm.size);
|
||
// iec16022ecc200会根据sizeType调整输出尺寸,支持灵活的条码宽高比
|
||
unsigned char *grid = iec16022ecc200(&cols, &rows, reinterpret_cast<unsigned char *>(bytes.data()), &sizeType);
|
||
if (!grid || cols <= 0 || rows <= 0) {
|
||
if (grid) {
|
||
free(grid);
|
||
}
|
||
return QImage();
|
||
}
|
||
|
||
QImage image = renderDataMatrix(grid, cols, rows, 1, foreground, background);
|
||
free(grid);
|
||
return image;
|
||
}
|
||
|
||
// QR Code:版本0表示自动选择版本(1-40),确保内容总是被编码
|
||
QRcode *qrcode = QRcode_encodeString(bytes.constData(), 0, level, QR_MODE_8, 1);
|
||
if (!qrcode) {
|
||
return QImage();
|
||
}
|
||
|
||
QImage image = renderQRCode(qrcode, 1, foreground, background);
|
||
QRcode_free(qrcode);
|
||
return image;
|
||
}
|
||
|
||
/**
|
||
* @brief 将Barcode_Item的编码数据(partial)渲染成QImage
|
||
*
|
||
* partial字段存储压缩的条形码宽度数据(高4位=空白宽度,低3位=黑条宽度),
|
||
* 需展开为像素数组。barWidth参数控制每个模块的物理宽度(用于放大)。
|
||
*
|
||
* 这种直接渲染方式避免了PostScript生成和文件I/O的开销。
|
||
*/
|
||
static QImage renderBarcodeImage(const Barcode_Item *bc, int barHeight = 12, int barWidth = 1, QRgb foreground = 0xff000000, QRgb background = 0xffffffff)
|
||
{
|
||
if (!bc || !bc->partial) {
|
||
return QImage();
|
||
}
|
||
|
||
// 约束:barHeight和barWidth必须为正值。默认值支持小尺寸标签的视觉清晰度
|
||
if (barHeight <= 0) {
|
||
barHeight = 12;
|
||
}
|
||
if (barWidth <= 0) {
|
||
barWidth = 1;
|
||
}
|
||
|
||
// xoffset=8提供左边距(4像素),用于对齐打印布局和改善美观度
|
||
int xoffset = 8;
|
||
int len = static_cast<int>(strlen(bc->partial));
|
||
// 计算条形码总宽度:每字节的低4位表示条纹宽度单位数之和
|
||
int bcWidth1 = 0;
|
||
for (int i = 0; i < len; ++i) {
|
||
bcWidth1 += (bc->partial[i] & 0x0f);
|
||
}
|
||
// 最终宽度 = 左边距 + 条纹单位总数 * 每单位像素数
|
||
int imageWidth = (xoffset >> 1) + bcWidth1 * barWidth;
|
||
if (imageWidth <= 0 || barHeight <= 0) {
|
||
return QImage();
|
||
}
|
||
|
||
QImage image(imageWidth, barHeight, QImage::Format_ARGB32);
|
||
image.fill(background);
|
||
|
||
for (int y = 0; y < barHeight; ++y) {
|
||
int xpos = xoffset >> 1;
|
||
for (int n = 0; n < len; ++n) {
|
||
// partial数据编码方案:奇数索引=黑条,偶数索引=空白
|
||
// 低3位存储实际宽度,需乘以barWidth得到像素宽度
|
||
int run = (bc->partial[n] & 0x07) * barWidth;
|
||
if (n & 1) {
|
||
// 奇数索引:绘制黑条(前景色)
|
||
for (int k = 0; k < run; ++k) {
|
||
if (xpos >= 0 && xpos < imageWidth) {
|
||
image.setPixel(xpos, y, foreground);
|
||
}
|
||
++xpos;
|
||
}
|
||
} else {
|
||
// 偶数索引:跳过空白区域(已由背景色填充)
|
||
xpos += run;
|
||
}
|
||
}
|
||
}
|
||
|
||
return image;
|
||
}
|
||
|
||
static int barcodeTypeFromString(const QString &type)
|
||
{
|
||
QString t = type.trimmed().toUpper();
|
||
if (t == "BARCODE_128") return BARCODE_128;
|
||
if (t == "BARCODE_93") return BARCODE_93;
|
||
if (t == "BARCODE_ISBN") return BARCODE_ISBN;
|
||
if (t == "BARCODE_EAN") return BARCODE_EAN;
|
||
if (t == "BARCODE_UPC") return BARCODE_UPC;
|
||
if (t == "BARCODE_39") return BARCODE_39;
|
||
if (t == "BARCODE_CBR") return BARCODE_CBR;
|
||
if (t == "BARCODE_PLS") return BARCODE_PLS;
|
||
if (t == "BARCODE_MSI") return BARCODE_MSI;
|
||
return BARCODE_128;
|
||
}
|
||
|
||
static QString barcodeTypeNameFromIndex(int index)
|
||
{
|
||
switch (index) {
|
||
case 0: return QLatin1String("BARCODE_128");
|
||
case 1: return QLatin1String("BARCODE_93");
|
||
case 2: return QLatin1String("BARCODE_ISBN");
|
||
case 3: return QLatin1String("BARCODE_EAN");
|
||
case 4: return QLatin1String("BARCODE_UPC");
|
||
case 5: return QLatin1String("BARCODE_39");
|
||
case 6: return QLatin1String("BARCODE_CBR");
|
||
case 7: return QLatin1String("BARCODE_PLS");
|
||
case 8: return QLatin1String("BARCODE_MSI");
|
||
default: return QLatin1String("BARCODE_128");
|
||
}
|
||
}
|
||
|
||
static QImage generateQRCodeImage(const QString &content, int qrType)
|
||
{
|
||
DlgQrcodeFormatVM vm;
|
||
vm.type = qrType == 0 ? QR_CODE_TYPE_DATA_MATRIX : QR_CODE_TYPE_QRCODE;
|
||
return generateQRCodeImage(content, vm);
|
||
}
|
||
|
||
/**
|
||
* @brief 根据格式配置生成条形码图像
|
||
*
|
||
* 支持多种条形码标准(128、93、EAN等),直接渲染到QImage,无需文件操作。
|
||
* 避免对QBarCode类的依赖,实现自包含的编码流程。
|
||
*/
|
||
static QImage generateBarCodeImage(const QString &content, const DlgBarcodeFormatVM &vm)
|
||
{
|
||
if (content.isEmpty()) {
|
||
return QImage();
|
||
}
|
||
|
||
// UTF-8编码确保国际字符被正确处理,与打印驱动兼容
|
||
QByteArray bytes = content.toUtf8();
|
||
|
||
// Barcode_Create内部用strdup复制字符串,无需手动生命周期管理
|
||
struct Barcode_Item *bc = Barcode_Create(bytes.data());
|
||
if (!bc) {
|
||
return QImage();
|
||
}
|
||
|
||
int encodingFlag = barcodeTypeFromString(vm.type);
|
||
|
||
// BARCODE_OUT_PS: 预期PostScript格式输出(internal flag),BARCODE_OUT_NOHEADERS: 不生成PS头
|
||
int flags = encodingFlag | BARCODE_OUT_PS | BARCODE_OUT_NOHEADERS;
|
||
|
||
// 传递缩放倍数到库,影响条纹宽度和字体大小的计算(510/60为库内部单位转换)
|
||
int width = qMax(1, vm.scale) * 510;
|
||
int height = qMax(1, vm.scale) * 60;
|
||
|
||
if (Barcode_Position(bc, width, height, 0, 0, 0.0) < 0 || Barcode_Encode(bc, flags) < 0) {
|
||
Barcode_Delete(bc);
|
||
return QImage();
|
||
}
|
||
|
||
// 黑底白码支持(INK_TYPE_WHITE表示反向模式)
|
||
QRgb foreground = vm.inkType == INK_TYPE_WHITE ? 0x00ffffff : 0xff000000;
|
||
QRgb background = vm.inkType == INK_TYPE_WHITE ? 0xff000000 : 0x00ffffff;
|
||
QImage image = renderBarcodeImage(bc, vm.height, vm.width, foreground, background);
|
||
Barcode_Delete(bc);
|
||
|
||
// 可选文本叠加:某些应用场景(如物流标签)需要条码旁显示人类可读的内容
|
||
if (vm.txtVisible) {
|
||
QPainter painter(&image);
|
||
painter.setPen(foreground);
|
||
QFont font = painter.font();
|
||
// 字体大小自适应:防止文本溢出图像,同时保持可读性
|
||
font.setPixelSize(qMin(12, image.height()));
|
||
painter.setFont(font);
|
||
painter.drawText(image.rect().adjusted(1, 1, -1, -1), Qt::AlignBottom | Qt::AlignHCenter, content);
|
||
}
|
||
|
||
// 最终放大:scale>1时对整个图像进行像素放大(用于低DPI打印环保模式)
|
||
if (vm.scale > 1) {
|
||
image = image.scaled(image.width() * vm.scale, image.height() * vm.scale, Qt::KeepAspectRatio, Qt::FastTransformation);
|
||
}
|
||
|
||
return image;
|
||
}
|
||
|
||
static QImage generateBarCodeImage(const QString &content, int barCodeType)
|
||
{
|
||
DlgBarcodeFormatVM vm;
|
||
vm.type = barcodeTypeNameFromIndex(barCodeType);
|
||
return generateBarCodeImage(content, vm);
|
||
}
|
||
|
||
QString ItemUtils::generateTxtFromDateItemModel(const DateItemModel &model, const QDateTime &time)
|
||
{
|
||
QString result = model.datetimePattern;
|
||
|
||
if (!time.isValid()) {
|
||
qWarning() << "generateTxtFromDateItemModel: invalid QDateTime passed";
|
||
return result;
|
||
}
|
||
|
||
// 应用偏移量
|
||
QDateTime applyDateTime = time;
|
||
if(model.monthsOffset != 0) {
|
||
applyDateTime = time.addMonths(model.monthsOffset);
|
||
}
|
||
else if(model.daysOffset != 0) {
|
||
applyDateTime = time.addDays(model.daysOffset);
|
||
}
|
||
|
||
// 使用正则表达式匹配并替换各种格式化模式
|
||
|
||
// 匹配四位数年份 yyyy
|
||
int i = result.indexOf("yyyy");
|
||
if(i != -1) {
|
||
int year = applyDateTime.date().year();
|
||
QString yearStr = model.yearsMap.contains(year) ? model.yearsMap[year] : QString::number(year);
|
||
result.replace(i, 4, yearStr);
|
||
}
|
||
|
||
// 匹配两位数年份 yy
|
||
i = result.indexOf("YY");
|
||
if(i != -1) {
|
||
QString yearStr = QString::number(applyDateTime.date().year()).right(2);
|
||
result.replace(i, 2, yearStr);
|
||
}
|
||
|
||
// 匹配两位数月份 MM
|
||
i = result.indexOf("MM");
|
||
if(i != -1) {
|
||
int monthIdx = applyDateTime.date().month() - 1;
|
||
if (monthIdx >= 0 && monthIdx < model.monthsMap.size()) {
|
||
result.replace(i, 2, model.monthsMap[monthIdx]);
|
||
} else {
|
||
qWarning() << "generateTxtFromDateItemModel: monthsMap index out of range:" << monthIdx;
|
||
result.replace(i, 2, QString::number(applyDateTime.date().month()).rightJustified(2, '0'));
|
||
}
|
||
}
|
||
|
||
// 匹配两位数日期 dd
|
||
i = result.indexOf("dd");
|
||
if(i != -1) {
|
||
int dayIdx = applyDateTime.date().day() - 1;
|
||
if (dayIdx >= 0 && dayIdx < model.daysMap.size()) {
|
||
result.replace(i, 2, model.daysMap[dayIdx]);
|
||
} else {
|
||
qWarning() << "generateTxtFromDateItemModel: daysMap index out of range:" << dayIdx;
|
||
result.replace(i, 2, QString::number(applyDateTime.date().day()).rightJustified(2, '0'));
|
||
}
|
||
}
|
||
|
||
// 匹配两位数小时 HH
|
||
i = result.indexOf("HH");
|
||
if(i != -1) {
|
||
int hourIdx = applyDateTime.time().hour();
|
||
if (hourIdx >= 0 && hourIdx < model.hoursMap.size()) {
|
||
result.replace(i, 2, model.hoursMap[hourIdx]);
|
||
} else {
|
||
qWarning() << "generateTxtFromDateItemModel: hoursMap index out of range:" << hourIdx;
|
||
result.replace(i, 2, QString::number(applyDateTime.time().hour()).rightJustified(2, '0'));
|
||
}
|
||
}
|
||
|
||
// 匹配两位数分钟 mm
|
||
QRegExp minutePattern("mm");
|
||
if (result.contains(minutePattern)) {
|
||
int minute = applyDateTime.time().minute();
|
||
result.replace(minutePattern, QString::number(minute).rightJustified(2, '0'));
|
||
}
|
||
|
||
// 匹配两位数秒钟 ss
|
||
QRegExp secondPattern("ss");
|
||
if (result.contains(secondPattern)) {
|
||
int second = applyDateTime.time().second();
|
||
result.replace(secondPattern, QString::number(second).rightJustified(2, '0'));
|
||
}
|
||
|
||
// 格式化周几 w
|
||
i = result.indexOf("w");
|
||
if (i != -1) {
|
||
int weekIdx = applyDateTime.date().dayOfWeek() - 1;
|
||
if (weekIdx >= 0 && weekIdx < model.weeksMap.size()) {
|
||
result.replace(i, 1, model.weeksMap[weekIdx]);
|
||
} else {
|
||
qWarning() << "generateTxtFromDateItemModel: weeksMap index out of range:" << weekIdx;
|
||
result.replace(i, 1, QString::number(applyDateTime.date().dayOfWeek()));
|
||
}
|
||
}
|
||
|
||
// 格式化年中周数 c
|
||
QRegExp weekNumberPattern("c");
|
||
if (result.contains(weekNumberPattern)) {
|
||
result.replace(weekNumberPattern, QString::number(applyDateTime.date().weekNumber()));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
|
||
QString ItemUtils::generateTxtFromBarcodeViewModel(const BarcodeViewModel &vm, const std::array<int, 2> counterValues, const QDateTime &time)
|
||
{
|
||
QString txt = vm.contentPattern;
|
||
auto pos = txt.indexOf("{t}");
|
||
if(pos != -1) {
|
||
txt = txt.replace(pos, 3, generateTxtFromDateItemModel(vm.dateViewModel, time));
|
||
}
|
||
|
||
pos = txt.indexOf("{c}");
|
||
if(pos != -1) {
|
||
auto cntValue = vm.counterViewModel.counterType == COUNTER_TYPE_1 ? counterValues[0] : counterValues[1];
|
||
auto valueStr = QString("%1").arg(cntValue, 8, 10, QLatin1Char('0'));
|
||
valueStr = valueStr.right(vm.counterViewModel.digitalNum);
|
||
txt = txt.replace(pos, 3, valueStr);
|
||
}
|
||
|
||
return txt;
|
||
}
|
||
|
||
/**
|
||
* @brief 统一的条码/二维码生成入口
|
||
*
|
||
* @details
|
||
* 目的:将二维码和条形码编码逻辑独立出来,避免对UI对话框类(dlgQrCode、QBarCode)的依赖,
|
||
* 实现在数据层直接进行编码,支持后端打印任务、异步处理等场景。
|
||
*
|
||
* 多态format参数设计的理由:
|
||
* - 支持便利调用(int):向后兼容现有代码路径,使用库默认配置
|
||
* - 支持细粒度控制(DlgQrcodeFormatVM/DlgBarcodeFormatVM):UI配置直通生成函数
|
||
* - 避免序列化开销:QVariant作为类型安全的通用容器
|
||
*
|
||
* @param content 已格式化的文本内容(由调用者负责模板替换如{t}、{c})
|
||
* @param type 条码类型(BARCODE_TYPE_QRCODE 或 BARCODE_TYPE_BARCODE)
|
||
* @param format 配置参数容器,支持以下类型:
|
||
* - BARCODE_TYPE_QRCODE: DlgQrcodeFormatVM(完整配置)或 int(0=DataMatrix, 1=QRCode,使用默认值)
|
||
* - BARCODE_TYPE_BARCODE: DlgBarcodeFormatVM(完整配置)或 int(条形码类型索引,使用默认值)
|
||
* @return QImage 生成的图像;失败返回空QImage
|
||
*/
|
||
QImage ItemUtils::generateBarcodeImage(const QString &content, BarcodeType type, const QVariant &format)
|
||
{
|
||
if (content.isEmpty()) {
|
||
return QImage();
|
||
}
|
||
|
||
if (type == BARCODE_TYPE_QRCODE || type == BARCODE_TYPE_DMCODE) {
|
||
// 优先级:完整VM配置 > 简化int配置 > 默认配置
|
||
// 原因:支持从UI直接传递结构体,同时保留向后兼容的快速路径
|
||
if (format.canConvert<DlgQrcodeFormatVM>()) {
|
||
DlgQrcodeFormatVM vm = format.value<DlgQrcodeFormatVM>();
|
||
return generateQRCodeImage(content, vm);
|
||
}
|
||
if (format.isValid() && format.canConvert<int>()) {
|
||
return generateQRCodeImage(content, format.toInt());
|
||
}
|
||
return generateQRCodeImage(content, static_cast<int>(QR_CODE_TYPE_QRCODE));
|
||
}
|
||
|
||
if (type == BARCODE_TYPE_BARCODE) {
|
||
if (format.canConvert<DlgBarcodeFormatVM>()) {
|
||
DlgBarcodeFormatVM vm = format.value<DlgBarcodeFormatVM>();
|
||
return generateBarCodeImage(content, vm);
|
||
}
|
||
if (format.isValid() && format.canConvert<int>()) {
|
||
return generateBarCodeImage(content, format.toInt());
|
||
}
|
||
return generateBarCodeImage(content, DlgBarcodeFormatVM());
|
||
}
|
||
|
||
return QImage();
|
||
}
|
||
|
||
/**
|
||
* @brief 将 QImage 中所有等于 bg 的边缘像素裁掉,仅保留有效内容的最小外接矩形
|
||
*
|
||
* 复刻自 PicTextItem 的 clipImage:用四个方向扫描确定 left/top/right/bottom 边界,
|
||
* 然后调用 QImage::copy 输出裁剪后的图像。这样字符串如 "aaa" 不会保留 "j" 才会用到的下行空白,
|
||
* 节省后续打印或合成时的画布空间。
|
||
*/
|
||
static QImage clipTxtImage(const QImage &img, QRgb bg)
|
||
{
|
||
if (img.isNull()) {
|
||
return img;
|
||
}
|
||
|
||
QRect clipRect;
|
||
for (int x = 0; x < img.width(); ++x) {
|
||
for (int y = 0; y < img.height(); ++y) {
|
||
if (img.pixel(x, y) != bg) {
|
||
clipRect.setLeft(x);
|
||
goto top;
|
||
}
|
||
}
|
||
}
|
||
// 整张图都是背景色,直接返回原图(无可裁剪内容)
|
||
return img;
|
||
|
||
top:
|
||
for (int y = 0; y < img.height(); ++y) {
|
||
for (int x = 0; x < img.width(); ++x) {
|
||
if (img.pixel(x, y) != bg) {
|
||
clipRect.setTop(y);
|
||
goto right;
|
||
}
|
||
}
|
||
}
|
||
|
||
right:
|
||
for (int x = img.width() - 1; x >= 0; --x) {
|
||
for (int y = 0; y < img.height(); ++y) {
|
||
if (img.pixel(x, y) != bg) {
|
||
clipRect.setRight(x + 1);
|
||
goto bottom;
|
||
}
|
||
}
|
||
}
|
||
|
||
bottom:
|
||
for (int y = img.height() - 1; y >= 0; --y) {
|
||
for (int x = 0; x < img.width(); ++x) {
|
||
if (img.pixel(x, y) != bg) {
|
||
clipRect.setBottom(y + 1);
|
||
goto end;
|
||
}
|
||
}
|
||
}
|
||
|
||
end:
|
||
return img.copy(clipRect);
|
||
}
|
||
|
||
QImage ItemUtils::txtToImage(const QString &txt, const QFont &font, const QColor &fgColor, const QColor &bgColor)
|
||
{
|
||
if (txt.isEmpty()) {
|
||
return QImage();
|
||
}
|
||
|
||
// 用 QFontMetrics 计算给定字体下文本的水平推进宽度和单行高度,
|
||
// 这样初始画布刚好能容纳整段文本,避免后续裁剪时出现截断
|
||
QFontMetrics fm(font);
|
||
int width = fm.horizontalAdvance(txt);
|
||
int height = fm.height();
|
||
if (width <= 0 || height <= 0) {
|
||
return QImage();
|
||
}
|
||
|
||
QImage img(width, height, QImage::Format_ARGB32);
|
||
|
||
QRgb bgRgb = bgColor.rgba();
|
||
img.fill(bgRgb);
|
||
|
||
QPainter painter;
|
||
painter.begin(&img);
|
||
painter.setFont(font);
|
||
painter.setPen(fgColor);
|
||
painter.drawText(img.rect(), txt);
|
||
painter.end();
|
||
|
||
return img;
|
||
|
||
// 按背景色裁剪,使输出图像紧贴文字像素的最小包围盒(详见函数注释)
|
||
return clipTxtImage(img, bgRgb);
|
||
} |