Skip to content

Commit eacaf94

Browse files
committed
[新增ICO转换器和多图像文件查看器]: 添加FreeImage依赖并实现ICO文件读写功能,新增多图像文件查看器和ICO转换器工具
- 依赖管理: 在vcpkg中添加freeimage依赖,用于处理ICO图像格式的读写 - 功能新增: 新增MultiImageFileViewer多图像文件查看器,支持查看ICO、GIF等多帧图像文件 - 工具实现: 新增IcoConverterWidget ICO转换器工具,支持将图像转换为ICO格式并自定义尺寸和背景色 - 组件开发: 新增SizeSelectorWidget尺寸选择器组件,用于选择ICO文件包含的图像尺寸 - 工具类封装: 新增FreeImageUtils封装FreeImage库的ICO读写功能,修复256x256尺寸写入问题 - 工具函数: 新增icoutils工具类,基于QtIcoHandler实现标准ICO文件写入功能 - 文档更新: 在README中新增ICO转换器和多图像文件查看器的功能说明和已知问题
1 parent cc639da commit eacaf94

29 files changed

Lines changed: 1149 additions & 14 deletions

.github/actions/install-dependencies/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ inputs:
44
vcpkg_libs:
55
description: 'vcpkg libraries'
66
required: false
7-
default: 'breakpad crashpad giflib opencv[contrib,thread]'
7+
default: 'breakpad crashpad freeimage giflib opencv[contrib,thread]'
88
type: string
99
qt_modules:
1010
description: 'qt modules'

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ find_package(crashpad CONFIG REQUIRED)
3434
if(crashpad_FOUND)
3535
message(STATUS "found crashpad")
3636
endif()
37+
find_package(freeimage CONFIG REQUIRED)
38+
if(freeimage_FOUND)
39+
message(STATUS "found freeimage")
40+
endif()
3741
find_package(GIF REQUIRED)
3842
if(GIF_FOUND)
3943
message(STATUS "found GIF")

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@
3232
2. 在Ubuntu下会找不到QVulkanInstance相关的头文件,编译无法通过:
3333
3. 暂时禁止上述情况的编译过程;
3434

35+
## Multi Image File Viewer
36+
37+
1. 可以从一个指定的文件读取多个图像文件,然后显示在窗口上,常见格式包括`ico``gif`等等,其他格式需要`kimageformats-binaries`中的插件支持;
38+
39+
## ICO Converter ico文件制作
40+
41+
1. [FreeImage](https://github.com/danoli3/FreeImage): 在制作 `ICO` 文件时存在一个已知缺陷:当写入 `256x256` 分辨率的图像时,它会错误地将 `ICO` 文件目录入口(`ICONDIRENTRY`)中的宽和高字段直接设置为 `256`。根据 ICO 文件格式规范,由于历史原因(宽度和高度字段仅为 1 字节),`256x256` 这个尺寸必须用特殊值 `255` (`0xFF`) 或 `0` 来标识,以表明其真实尺寸需要从内嵌的图像数据(如 `PNG` 头)中读取。`FreeImage` 未遵守此规范,而是直接写入实际数值 `256` (`0x100`),这超出了单字节的表示范围。因此,Windows 资源管理器等依赖传统规范解析 `ICO` 文件头的软件无法正确识别该图标的尺寸,导致其“属性->详细信息”中不显示 `256x256` 的分辨率。而 `256x256` 以下尺寸的图像因未超出单字节范围(`0-255`),故不存在此问题,[qfreeimage](/src/qfreeimage/)
42+
2. [icoutils](/src/utils/icoutils.hpp):根据[QtIcoHandler](https://github.com/qt/qtbase/blob/dev/src/plugins/imageformats/ico/qicohandler.h)修改而来,在`QtIcoHandler`中已经封装了下面的函数:
43+
44+
```cpp
45+
bool ICOReader::write(QIODevice*device, const QList<QImage> &images);
46+
```
47+
48+
但是`QT`接口为了统一,只暴露出写一个`QImage`的接口,需要进行一些修改,暴露出多个`QImage`的接口,即`icoutils`;
49+
50+
```cpp
51+
bool write(const QImage &image) override;
52+
```
53+
54+
3. `windows`文件属性->详细信息,只能识别到最多第6个图像的分辨率作为图像属性分辨率,所以程序图标`ico`文件最佳分辨率列表为`16x16``32x32``48x48``64x64``128x128``256x256`
55+
3556
## 看图界面
3657

3758
<div align=center>

examples/graphics/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ set(PROJECT_SOURCES
1515
drawscene.hpp
1616
drawwidget.cpp
1717
drawwidget.h
18+
icoconverterwidget.cc
19+
icoconverterwidget.hpp
1820
imagecaptureview.cc
1921
imagecaptureview.hpp
2022
imageviewer.cpp
@@ -26,6 +28,8 @@ set(PROJECT_SOURCES
2628
mainwindow.h
2729
maskdialog.cpp
2830
maskdialog.h
31+
multiimagefileviewer.cc
32+
multiimagefileviewer.hpp
2933
openglviewer.cc
3034
openglviewer.hpp
3135
recordgifthread.cc
@@ -38,6 +42,8 @@ set(PROJECT_SOURCES
3842
sectionalsubtitlesview.hpp
3943
selectionwidget.cc
4044
selectionwidget.hpp
45+
sizeselectorwidget.cc
46+
sizeselectorwidget.hpp
4147
stretchparamssettingdailog.cc
4248
stretchparamssettingdailog.hpp
4349
subtitlsplicingwidget.cc

examples/graphics/graphics.pro

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,21 @@ SOURCES += \
2626
customlineitem.cc \
2727
drawscene.cc \
2828
drawwidget.cpp \
29+
icoconverterwidget.cc \
2930
imagecaptureview.cc \
3031
imageviewer.cpp \
3132
listiitemview.cc \
3233
main.cpp \
3334
mainwindow.cpp \
3435
maskdialog.cpp \
36+
multiimagefileviewer.cc \
3537
openglviewer.cc \
3638
recordgifthread.cc \
3739
recordwidget.cc \
3840
rounddialog.cc \
3941
sectionalsubtitlesview.cc \
4042
selectionwidget.cc \
43+
sizeselectorwidget.cc \
4144
stretchparamssettingdailog.cc \
4245
subtitlsplicingwidget.cc
4346

@@ -50,17 +53,20 @@ HEADERS += \
5053
customlineitem.hpp \
5154
drawscene.hpp \
5255
drawwidget.h \
56+
icoconverterwidget.hpp \
5357
imagecaptureview.hpp \
5458
imageviewer.h \
5559
listiitemview.hpp \
5660
mainwindow.h \
5761
maskdialog.h \
62+
multiimagefileviewer.hpp \
5863
openglviewer.hpp \
5964
recordgifthread.hpp \
6065
recordwidget.hpp \
6166
rounddialog.hpp \
6267
sectionalsubtitlesview.hpp \
6368
selectionwidget.hpp \
69+
sizeselectorwidget.hpp \
6470
stretchparamssettingdailog.hpp \
6571
subtitlsplicingwidget.hpp
6672

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#include "icoconverterwidget.hpp"
2+
#include "sizeselectorwidget.hpp"
3+
4+
#include <graphics/imageview.h>
5+
#include <utils/icoutils.hpp>
6+
#include <utils/utils.h>
7+
8+
#include <QtWidgets>
9+
10+
class IcoConverterWidget::IcoConverterWidgetPrivate
11+
{
12+
public:
13+
explicit IcoConverterWidgetPrivate(IcoConverterWidget *q)
14+
: q_ptr(q)
15+
{
16+
imageView = new Graphics::ImageView(q_ptr);
17+
18+
sizeSelectorWidget = new SizeSelectorWidget(q_ptr);
19+
sizeSelectorWidget->setSizes(Utils::defaultIcoSizes);
20+
21+
colorLabel = new QLabel(q_ptr);
22+
colorLabel->setFrameShape(QFrame::Box);
23+
colorLabel->setAutoFillBackground(true);
24+
updateColorLabel();
25+
26+
pathLineEdit = new QLineEdit(q_ptr);
27+
auto path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
28+
if (path.isEmpty()) {
29+
path = QDir::homePath();
30+
}
31+
pathLineEdit->setText(path);
32+
33+
convertButton = new QPushButton(IcoConverterWidget::tr("Convert"), q_ptr);
34+
}
35+
~IcoConverterWidgetPrivate() {}
36+
37+
void updateColorLabel()
38+
{
39+
auto pal = colorLabel->palette();
40+
pal.setColor(QPalette::Window, color);
41+
pal.setColor(QPalette::WindowText, color.lightness() > 128 ? Qt::black : Qt::white);
42+
colorLabel->setPalette(pal);
43+
colorLabel->setText(color.name(QColor::HexArgb));
44+
45+
colorLabel->update();
46+
}
47+
48+
IcoConverterWidget *q_ptr;
49+
50+
Graphics::ImageView *imageView;
51+
SizeSelectorWidget *sizeSelectorWidget;
52+
QLabel *colorLabel;
53+
QColor color = Qt::transparent;
54+
QLineEdit *pathLineEdit;
55+
QPushButton *convertButton;
56+
};
57+
58+
IcoConverterWidget::IcoConverterWidget(QWidget *parent)
59+
: Viewer(parent)
60+
, d_ptr(new IcoConverterWidgetPrivate(this))
61+
{
62+
setupUI();
63+
buildConnect();
64+
}
65+
66+
IcoConverterWidget::~IcoConverterWidget() {}
67+
68+
void IcoConverterWidget::onOpenImage()
69+
{
70+
const auto filename = openImage();
71+
if (filename.isEmpty()) {
72+
return;
73+
}
74+
d_ptr->imageView->createScene(filename);
75+
}
76+
77+
void IcoConverterWidget::onChangedImage(int index)
78+
{
79+
if (index < 0 || index >= m_thumbnailList.size()) {
80+
return;
81+
}
82+
const auto data = m_thumbnailList.at(index);
83+
d_ptr->imageView->createScene(data.fileInfo().absoluteFilePath());
84+
}
85+
86+
void IcoConverterWidget::onPickSquareColor()
87+
{
88+
auto color = QColorDialog::getColor(d_ptr->color, this, tr("Select Background Color"));
89+
if (!color.isValid()) {
90+
return;
91+
}
92+
d_ptr->color = color;
93+
d_ptr->updateColorLabel();
94+
}
95+
96+
void IcoConverterWidget::onBrowse()
97+
{
98+
auto dir = QFileDialog::getExistingDirectory(this,
99+
tr("Select Directory"),
100+
d_ptr->pathLineEdit->text());
101+
if (dir.isEmpty()) {
102+
return;
103+
}
104+
d_ptr->pathLineEdit->setText(dir);
105+
}
106+
107+
void IcoConverterWidget::onConvert()
108+
{
109+
auto path = d_ptr->pathLineEdit->text();
110+
if (path.isEmpty()) {
111+
QMessageBox::warning(this, tr("Warning"), tr("Please select a directory."));
112+
return;
113+
}
114+
if (!QDir(path).exists()) {
115+
QMessageBox::warning(this, tr("Warning"), tr("Directory not exist."));
116+
return;
117+
}
118+
auto image = d_ptr->imageView->pixmap().toImage();
119+
if (image.isNull()) {
120+
QMessageBox::warning(this, tr("Warning"), tr("No image loaded."));
121+
return;
122+
}
123+
124+
d_ptr->convertButton->setEnabled(false);
125+
126+
auto filename = QString("%1_%2.ico")
127+
.arg(QFileInfo(m_urlLabel->text().trimmed()).baseName(),
128+
QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"));
129+
path = QDir(path).filePath(filename);
130+
auto sizes = d_ptr->sizeSelectorWidget->sizes();
131+
132+
Utils::asynchronous<bool>([image, path, sizes, color = d_ptr->color]() {
133+
return Utils::writeIcons(image, path, color, sizes);
134+
});
135+
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(path).absolutePath()));
136+
137+
d_ptr->convertButton->setEnabled(true);
138+
}
139+
140+
void IcoConverterWidget::setupUI()
141+
{
142+
auto *splitter = new QSplitter(Qt::Horizontal, this);
143+
splitter->addWidget(d_ptr->imageView);
144+
splitter->addWidget(toolWidget());
145+
splitter->setStretchFactor(0, 1);
146+
splitter->setStretchFactor(1, 1);
147+
splitter->setSizes({INT_MAX, 1});
148+
149+
auto *layout = new QVBoxLayout(this);
150+
layout->setContentsMargins({});
151+
layout->addWidget(splitter);
152+
layout->addWidget(m_imageListView);
153+
}
154+
155+
auto IcoConverterWidget::toolWidget() -> QWidget *
156+
{
157+
auto *pickColorButton = new QPushButton(tr("Pick Color"), this);
158+
connect(pickColorButton, &QPushButton::clicked, this, &IcoConverterWidget::onPickSquareColor);
159+
160+
auto *browserButton = new QPushButton(tr("Browse"), this);
161+
connect(browserButton, &QPushButton::clicked, this, &IcoConverterWidget::onBrowse);
162+
163+
auto *saveBox = new QGroupBox(tr("Save Options"), this);
164+
auto *saveLayout = new QVBoxLayout(saveBox);
165+
saveLayout->addWidget(d_ptr->sizeSelectorWidget);
166+
saveLayout->addWidget(new QLabel(tr("Background Color:"), saveBox));
167+
saveLayout->addWidget(d_ptr->colorLabel);
168+
saveLayout->addWidget(pickColorButton);
169+
saveLayout->addWidget(new QLabel(tr("Save Path:"), saveBox));
170+
saveLayout->addWidget(d_ptr->pathLineEdit);
171+
saveLayout->addWidget(browserButton);
172+
173+
auto *widget = new QWidget(this);
174+
widget->setMaximumWidth(300);
175+
auto *rightLayout = new QVBoxLayout(widget);
176+
rightLayout->addWidget(m_openButton);
177+
rightLayout->addWidget(m_infoBox);
178+
rightLayout->addStretch();
179+
rightLayout->addWidget(saveBox);
180+
rightLayout->addWidget(d_ptr->convertButton);
181+
rightLayout->addStretch();
182+
183+
return widget;
184+
}
185+
186+
void IcoConverterWidget::buildConnect()
187+
{
188+
connect(m_openButton, &QPushButton::clicked, this, &IcoConverterWidget::onOpenImage);
189+
190+
connect(d_ptr->imageView,
191+
&Graphics::ImageView::scaleFactorChanged,
192+
this,
193+
&IcoConverterWidget::onScaleFactorChanged);
194+
connect(d_ptr->imageView,
195+
&Graphics::ImageView::imageSizeChanged,
196+
this,
197+
&IcoConverterWidget::onImageSizeChanged);
198+
connect(d_ptr->imageView,
199+
&Graphics::ImageView::imageUrlChanged,
200+
this,
201+
&IcoConverterWidget::onImageChanged);
202+
connect(m_imageListView, &ImageListView::changeItem, this, &IcoConverterWidget::onChangedImage);
203+
204+
connect(d_ptr->convertButton, &QPushButton::clicked, this, &IcoConverterWidget::onConvert);
205+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include <examples/common/viewer.hpp>
4+
5+
class IcoConverterWidget : public Viewer
6+
{
7+
Q_OBJECT
8+
public:
9+
explicit IcoConverterWidget(QWidget *parent = nullptr);
10+
~IcoConverterWidget() override;
11+
12+
private slots:
13+
void onOpenImage();
14+
void onChangedImage(int index);
15+
void onPickSquareColor();
16+
void onBrowse();
17+
void onConvert();
18+
19+
private:
20+
void setupUI();
21+
auto toolWidget() -> QWidget *;
22+
void buildConnect();
23+
24+
class IcoConverterWidgetPrivate;
25+
QScopedPointer<IcoConverterWidgetPrivate> d_ptr;
26+
};

0 commit comments

Comments
 (0)