Qt Quick 图像处理实例之美图秀秀(附源代码下载)
在《Qt Quick 之 QML 与 C++ 混合编程具体解释》一文中我们解说了 QML 与 C++ 混合编程的方方面面的内容,这次我们通过一个图像处理应用。再来看一下 QML 与 C++ 混合编程的威力,同一时候也为诸君揭开美图秀秀、魔拍之类的相片美化应用的底层原理。
项目的创建过程请參考《Qt Quick 之 Hello World 图文具体解释》,项目名称为 imageProcessor ,创建完毕后须要加入两个文件: imageProcessor.h 和 imageProcessor.cpp 。
本文是作者 Qt Quick 系列文章中的一篇,其他文章在这里:
- Qt Quick 简单介绍
- QML 语言基础
- Qt Quick 之 Hello World 图文具体解释
- Qt Quick 简单教程
- Qt Quick 事件处理之信号与槽
- Qt Quick事件处理之鼠标、键盘、定时器
- Qt Quick 事件处理之捏拉缩放与旋转
- Qt Quick 组件与对象动态创建具体解释
- Qt Quick 布局介绍
- Qt Quick 之 QML 与 C++ 混合编程具体解释
实例效果
先看一下演示样例的实际运行效果。然后我们再来展开。
图 1 是在电脑上打开一个图片后的初始效果:
图 1 初始效果
图 2 是应用柔化特效后的效果:
图 2 柔化特效
图 3 是应用灰度特效后的截图:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZm9ydW9r/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
图 3 灰度特效
图 4 是浮雕特效:
图 4 浮雕特效
图 5 是黑白特效:
图 5 黑白特效
图 6 是应用底片特效后的截图:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZm9ydW9r/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
图 6 底片特效
假设你注意到我博客的头像……嗯,木错,它就是我使用本文实例的底片特效做出来的。
图 7 是应用锐化特效后的截图:
图 7 锐化特效
特效展示完毕。那么它们是怎么实现的呢?这就要说到图像处理算法了。
图像处理算法
imageProcessor 实例提供了"柔化"、"灰度"、"浮雕"、"黑白"、"底片"、"锐化"六种图像效果。算法的实如今 imageProcessor.h / imageProcessor.cpp 两个文件里,我们先简单介绍每种效果相应的算法,然后看代码实现。
柔化
柔化又称模糊,图像模糊算法有非常多种,我们最常见的就是均值模糊。即取一定半径内的像素值之平均值作为当前点的新的像素值。
为了提高计算速度,我们取 3 为半径。就是针对每个像素,将周围 8 个点加上自身的 RGB 值的平均值作为像素新的颜色值置。
代码例如以下:
static void _soften(QString sourceFile, QString destFile)
{
QImage image(sourceFile);
if(image.isNull())
{
qDebug() << "load " << sourceFile << " failed! ";
return;
}
int width = image.width();
int height = image.height();
int r, g, b;
QRgb color;
int xLimit = width - 1;
int yLimit = height - 1;
for(int i = 1; i < xLimit; i++)
{
for(int j = 1; j < yLimit; j++)
{
r = 0;
g = 0;
b = 0;
for(int m = 0; m < 9; m++)
{
int s = 0;
int p = 0;
switch(m)
{
case 0:
s = i - 1;
p = j - 1;
break;
case 1:
s = i;
p = j - 1;
break;
case 2:
s = i + 1;
p = j - 1;
break;
case 3:
s = i + 1;
p = j;
break;
case 4:
s = i + 1;
p = j + 1;
break;
case 5:
s = i;
p = j + 1;
break;
case 6:
s = i - 1;
p = j + 1;
break;
case 7:
s = i - 1;
p = j;
break;
case 8:
s = i;
p = j;
}
color = image.pixel(s, p);
r += qRed(color);
g += qGreen(color);
b += qBlue(color);
} r = (int) (r / 9.0);
g = (int) (g / 9.0);
b = (int) (b / 9.0); r = qMin(255, qMax(0, r));
g = qMin(255, qMax(0, g));
b = qMin(255, qMax(0, b)); image.setPixel(i, j, qRgb(r, g, b));
}
} image.save(destFile);
}
这样处理的效果不是特别明显。採用高斯模糊算法能够获取更好的效果。
灰度
把图像变灰,大概有这么三种方法:
- 最大值法,即 R = G = B = max(R , G , B),这样的方法处理过的图片亮度偏高
- 平均值法,即 R = G = B = (R + G + B) / 3 ,这样的方法处理过的图片比較柔和
- 加权平均值法,即 R = G = B = R*Wr + G*Wg + B*Wb ,由于人眼对不同颜色的敏感度不一样。三种颜色权重也不一样,一般来说绿色最高,红色次之,蓝色最低。这样的方法最合理的取值,红、绿、蓝的权重依次是 0.299 、0.587 、 0.114 。为了避免浮点运算,能够用移位替代。
Qt 框架有一个 qGray() 函数,採取加权平均值法计算灰度。 qGray() 将浮点运算转为整型的乘法和除法,公式是 (r * 11 + g * 16 + b * 5)/32 ,没有使用移位运算。
我使用 qGray() 函数计算灰度,以下是代码:
static void _gray(QString sourceFile, QString destFile)
{
QImage image(sourceFile);
if(image.isNull())
{
qDebug() << "load " << sourceFile << " failed! ";
return;
}
qDebug() << "depth - " << image.depth(); int width = image.width();
int height = image.height();
QRgb color;
int gray;
for(int i = 0; i < width; i++)
{
for(int j= 0; j < height; j++)
{
color = image.pixel(i, j);
gray = qGray(color);
image.setPixel(i, j,
qRgba(gray, gray, gray, qAlpha(color)));
}
} image.save(destFile);
}
qGray() 计算灰度时忽略了 Alpha 值,我在实现时保留原有的 Alpha 值。
浮雕
"浮雕" 图象效果是指图像的前景前向凸出背景。
浮雕的算法相对复杂一些。用当前点的 RGB 值减去相邻点的 RGB 值并加上 128 作为新的 RGB 值。由于图片中相邻点的颜色值是比較接近的。因此这样的算法处理之后。仅仅有颜色的边沿区域。也就是相邻颜色差异较大的部分的结果才会比較明显,而其他平滑区域则值都接近128左右,也就是灰色。这样就具有了浮雕效果。
看代码:
static void _emboss(QString sourceFile, QString destFile)
{
QImage image(sourceFile);
if(image.isNull())
{
qDebug() << "load " << sourceFile << " failed! ";
return;
}
int width = image.width();
int height = image.height();
QRgb color;
QRgb preColor = 0;
QRgb newColor;
int gray, r, g, b, a;
for(int i = 0; i < width; i++)
{
for(int j= 0; j < height; j++)
{
color = image.pixel(i, j);
r = qRed(color) - qRed(preColor) + 128;
g = qGreen(color) - qGreen(preColor) + 128;
b = qBlue(color) - qBlue(preColor) + 128;
a = qAlpha(color);
gray = qGray(r, g, b);
newColor = qRgba(gray, gray, gray, a);
image.setPixel(i, j, newColor);
preColor = newColor;
}
}
image.save(destFile);
}
在实现 _emboss() 函数时 。为避免有些区域残留“彩色”杂点或者条状痕迹,我对新的 RGB 值又做了一次灰度处理。
黑白
黑白图片的处理算法比較简单:对一个像素的 R 、G 、B 求平均值。average = (R + G + B) / 3 ,假设 average 大于等于选定的阈值则将该像素置为白色,小于阈值就把像素置为黑色。
演示样例中我选择的阈值是 128 ,也能够是其他值,依据效果调整就可以。
比方你媳妇儿高圆圆嫌给她拍的照片黑白处理后黑多白少。那能够把阈值调低一些,取 80 。效果肯定就变了。以下是代码:
static void _binarize(QString sourceFile, QString destFile)
{
QImage image(sourceFile);
if(image.isNull())
{
qDebug() << "load " << sourceFile << " failed! ";
return;
}
int width = image.width();
int height = image.height();
QRgb color;
QRgb avg;
QRgb black = qRgb(0, 0, 0);
QRgb white = qRgb(255, 255, 255);
for(int i = 0; i < width; i++)
{
for(int j= 0; j < height; j++)
{
color = image.pixel(i, j);
avg = (qRed(color) + qGreen(color) + qBlue(color))/3;
image.setPixel(i, j, avg >= 128 ? white : black);
}
}
image.save(destFile);
}
代码的逻辑简单,从文件载入图片。生成一个 QImage 实例,然后应用算法,处理后的图片保存到指定位置。
底片
早些年的相机使用胶卷记录拍摄结果,洗照片比較麻烦,只是假设你拿究竟片。逆光去看,效果就非常特别。
底片算法事实上非常easy,取 255 与像素的 R 、 G、 B 分量之差作为新的 R、 G、 B 值。
实现代码:
static void _negative(QString sourceFile, QString destFile)
{
QImage image(sourceFile);
if(image.isNull())
{
qDebug() << "load " << sourceFile << " failed! ";
return;
}
int width = image.width();
int height = image.height();
QRgb color;
QRgb negative;
for(int i = 0; i < width; i++)
{
for(int j= 0; j < height; j++)
{
color = image.pixel(i, j);
negative = qRgba(255 - qRed(color),
255 - qGreen(color),
255 - qBlue(color),
qAlpha(color));
image.setPixel(i, j, negative);
}
}
image.save(destFile);
}
锐化
图像锐化的主要目的是增强图像边缘,使模糊的图像变得更加清晰。颜色变得鲜明突出。图像的质量有所改善,产生更适合人眼观察和识别的图像。
常见的锐化算法有微分法和高通滤波法。微分法又以梯度锐化和拉普拉斯锐化较为经常使用。本演示样例採用微分法中的梯度锐化,用差分近似微分。则图像在点(i,j)处的梯度幅度计算公式例如以下:
G[f(i,j)] = abs(f(i,j) - f(i+1,j)) + abs(f(i,j) - f(i,j+1))
为了更好的增强图像边缘,我们引入一个阈值,仅仅有像素点的梯度值大于阈值时才对该像素点进行锐化,将像素点的 R 、 G、 B 值设置为相应的梯度值与一个常数之和。
常数值的选取应当參考图像的具体特点。
我们的演示样例为简单起见。将常数设定为 100 ,梯度阈值取 80 ,写死在算法函数中。更好的做法是通过參数传入,以便客户程序能够调整这些变量来观察效果。
好啦,看代码:
static void _sharpen(QString sourceFile, QString destFile)
{
QImage image(sourceFile);
if(image.isNull())
{
qDebug() << "load " << sourceFile << " failed! ";
return;
}
int width = image.width();
int height = image.height();
int threshold = 80;
QImage sharpen(width, height, QImage::Format_ARGB32);
int r, g, b, gradientR, gradientG, gradientB;
QRgb rgb00, rgb01, rgb10;
for(int i = 0; i < width; i++)
{
for(int j= 0; j < height; j++)
{
if(image.valid(i, j) &&
image.valid(i+1, j) &&
image.valid(i, j+1))
{
rgb00 = image.pixel(i, j);
rgb01 = image.pixel(i, j+1);
rgb10 = image.pixel(i+1, j);
r = qRed(rgb00);
g = qGreen(rgb00);
b = qBlue(rgb00);
gradientR = abs(r - qRed(rgb01)) + abs(r - qRed(rgb10));
gradientG = abs(g - qGreen(rgb01)) +
abs(g - qGreen(rgb10));
gradientB = abs(b - qBlue(rgb01)) +
abs(b - qBlue(rgb10)); if(gradientR > threshold)
{
r = qMin(gradientR + 100, 255);
} if(gradientG > threshold)
{
g = qMin( gradientG + 100, 255);
} if(gradientB > threshold)
{
b = qMin( gradientB + 100, 255);
} sharpen.setPixel(i, j, qRgb(r, g, b));
}
}
} sharpen.save(destFile);
}
演示样例用到的图像处理算法和 Qt 代码实现已经介绍完毕。您看得累吗?累就对了,舒服是留给死人的。擦。睡着了,我……
源代码情景分析
上一节介绍了图像特效算法,如今我们先看应用与管理这些特效的 C++ 类 ImageProcessor ,然后再来看 QML 代码。
ImageProcessor
在设计 ImageProcessor 类时,我希望它能够在 QML 环境中使用,因此有用了信号、槽、 Q_ENUMS 、 Q_PROPERTY 等特性,感兴趣的话请參考《Qt Quick 之 QML 与 C++ 混合编程具体解释》进一步熟悉。
先看 imageProcessor.h :
#ifndef IMAGEPROCESSOR_H
#define IMAGEPROCESSOR_H
#include <QObject>
#include <QString> class ImageProcessorPrivate;
class ImageProcessor : public QObject
{
Q_OBJECT
Q_ENUMS(ImageAlgorithm)
Q_PROPERTY(QString sourceFile READ sourceFile)
Q_PROPERTY(ImageAlgorithm algorithm READ algorithm) public:
ImageProcessor(QObject *parent = 0);
~ImageProcessor(); enum ImageAlgorithm{
Gray = 0,
Binarize,
Negative,
Emboss,
Sharpen,
Soften,
AlgorithmCount
}; QString sourceFile() const;
ImageAlgorithm algorithm() const;
void setTempPath(QString tempPath); signals:
void finished(QString newFile);
void progress(int value); public slots:
void process(QString file, ImageAlgorithm algorithm);
void abort(QString file, ImageAlgorithm algorithm);
void abortAll(); private:
ImageProcessorPrivate *m_d;
}; #endif
ImageProcessor 类的声明比較简单,它通过 finished() 信号通知关注者图像处理完毕,提供 process() 方法供客户程序调用,还有 setTempPath() 设置暂时文件夹。也同意你取消待运行的任务……
以下是实现文件 imageProcessor.cpp :
#include "imageProcessor.h"
#include <QThreadPool>
#include <QList>
#include <QFile>
#include <QFileInfo>
#include <QRunnable>
#include <QEvent>
#include <QCoreApplication>
#include <QPointer>
#include <QUrl>
#include <QImage>
#include <QDebug>
#include <QDir> typedef void (*AlgorithmFunction)(QString sourceFile,
QString destFile); class AlgorithmRunnable;
class ExcutedEvent : public QEvent
{
public:
ExcutedEvent(AlgorithmRunnable *r)
: QEvent(evType()), m_runnable(r)
{
}
AlgorithmRunnable *m_runnable; static QEvent::Type evType()
{
if(s_evType == QEvent::None)
{
s_evType = (QEvent::Type)registerEventType();
}
return s_evType;
} private:
static QEvent::Type s_evType;
};
QEvent::Type ExcutedEvent::s_evType = QEvent::None; static void _gray(QString sourceFile, QString destFile);
static void _binarize(QString sourceFile, QString destFile);
static void _negative(QString sourceFile, QString destFile);
static void _emboss(QString sourceFile, QString destFile);
static void _sharpen(QString sourceFile, QString destFile);
static void _soften(QString sourceFile, QString destFile); static AlgorithmFunction g_functions[ImageProcessor::AlgorithmCount] = {
_gray,
_binarize,
_negative,
_emboss,
_sharpen,
_soften
}; class AlgorithmRunnable : public QRunnable
{
public:
AlgorithmRunnable(
QString sourceFile,
QString destFile,
ImageProcessor::ImageAlgorithm algorithm,
QObject * observer)
: m_observer(observer)
, m_sourceFilePath(sourceFile)
, m_destFilePath(destFile)
, m_algorithm(algorithm)
{
}
~AlgorithmRunnable(){} void run()
{
g_functions[m_algorithm](m_sourceFilePath, m_destFilePath);
QCoreApplication::postEvent(m_observer,
new ExcutedEvent(this));
} QPointer<QObject> m_observer;
QString m_sourceFilePath;
QString m_destFilePath;
ImageProcessor::ImageAlgorithm m_algorithm;
}; class ImageProcessorPrivate : public QObject
{
public:
ImageProcessorPrivate(ImageProcessor *processor)
: QObject(processor), m_processor(processor),
m_tempPath(QDir::currentPath())
{
ExcutedEvent::evType();
}
~ImageProcessorPrivate()
{
} bool event(QEvent * e)
{
if(e->type() == ExcutedEvent::evType())
{
ExcutedEvent *ee = (ExcutedEvent*)e;
if(m_runnables.contains(ee->m_runnable))
{
m_notifiedAlgorithm = ee->m_runnable->m_algorithm;
m_notifiedSourceFile =
ee->m_runnable->m_sourceFilePath;
emit m_processor->finished(ee->m_runnable->m_destFilePath);
m_runnables.removeOne(ee->m_runnable);
}
delete ee->m_runnable;
return true;
}
return QObject::event(e);
} void process(QString sourceFile, ImageProcessor::ImageAlgorithm algorithm)
{
QFileInfo fi(sourceFile);
QString destFile = QString("%1/%2_%3").arg(m_tempPath)
.arg((int)algorithm).arg(fi.fileName());
AlgorithmRunnable *r = new AlgorithmRunnable(sourceFile,
destFile, algorithm, this);
m_runnables.append(r);
r->setAutoDelete(false);
QThreadPool::globalInstance()->start(r);
} ImageProcessor * m_processor;
QList<AlgorithmRunnable*> m_runnables;
QString m_notifiedSourceFile;
ImageProcessor::ImageAlgorithm m_notifiedAlgorithm;
QString m_tempPath;
}; ImageProcessor::ImageProcessor(QObject *parent)
: QObject(parent)
, m_d(new ImageProcessorPrivate(this))
{}
ImageProcessor::~ImageProcessor()
{
delete m_d;
} QString ImageProcessor::sourceFile() const
{
return m_d->m_notifiedSourceFile;
} ImageProcessor::ImageAlgorithm ImageProcessor::algorithm() const
{
return m_d->m_notifiedAlgorithm;
} void ImageProcessor::setTempPath(QString tempPath)
{
m_d->m_tempPath = tempPath;
} void ImageProcessor::process(QString file, ImageAlgorithm algorithm)
{
m_d->process(file, algorithm);
} void ImageProcessor::abort(QString file, ImageAlgorithm algorithm)
{
int size = m_d->m_runnables.size();
AlgorithmRunnable *r;
for(int i = 0; i < size; i++)
{
r = m_d->m_runnables.at(i);
if(r->m_sourceFilePath == file && r->m_algorithm == algorithm)
{
m_d->m_runnables.removeAt(i);
break;
}
}
}
为避免堵塞 UI 线程,我把图像处理部分放到线程池内完毕,依据 QThreadPool 的要求,从 QRunnable 继承,实现了 AlgorithmRunnable 。当 run() 函数运行完时发送自己定义的 ExecutedEvent 给 ImageProcessor ,而 ImageProcessor 就在处理事件时发出 finished() 信号。
关于 QThreadPool 和自己定义事件,请參考 Qt 帮助了解详情。
算法函数放在一个全局的函数指针数组中, AlgorithmRunnable 则依据算法枚举值从数组中取出相应的函数来处理图像。
其他的代码一看就可以明确,不再多说。
要想在 QML 中有用 ImageProcessor 类,须要导出一个 QML 类型。这个工作是在 main() 函数中完毕的。
main() 函数
main() 函数就在 main.cpp 中,以下是 main.cpp 的所有代码:
#include <QApplication>
#include "qtquick2applicationviewer.h"
#include <QtQml>
#include "imageProcessor.h"
#include <QQuickItem>
#include <QDebug> int main(int argc, char *argv[])
{
QApplication app(argc, argv); qmlRegisterType<ImageProcessor>("an.qt.ImageProcessor", 1, 0,"ImageProcessor"); QtQuick2ApplicationViewer viewer;
viewer.rootContext()->setContextProperty("imageProcessor", new ImageProcessor); viewer.setMainQmlFile(QStringLiteral("qml/imageProcessor/main.qml"));
viewer.showExpanded(); return app.exec();
}
我使用 qmlRegisterType() 注冊了 ImageProcessor 类,包名是 an.qt.ImageProcessor ,版本号是 1.0 ,所以你在稍后的 main.qml 文档中能够看到以下的导入语句:
import an.qt.ImageProcessor 1.0
上了贼船,就跟贼走,是时候看看 main.qml 了 。
main.qml
main.qml 还是比較长的哈。有 194 行代码:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.1
import an.qt.ImageProcessor 1.0
import QtQuick.Controls.Styles 1.1 Rectangle {
width: 640;
height: 480;
color: "#121212"; BusyIndicator {
id: busy;
running: false;
anchors.centerIn: parent;
z: 2;
} Label {
id: stateLabel;
visible: false;
anchors.centerIn: parent;
} Image {
objectName: "imageViewer";
id: imageViewer;
asynchronous: true;
anchors.fill: parent;
fillMode: Image.PreserveAspectFit;
onStatusChanged: {
if (imageViewer.status === Image.Loading) {
busy.running = true;
stateLabel.visible = false;
}
else if(imageViewer.status === Image.Ready){
busy.running = false;
}
else if(imageViewer.status === Image.Error){
busy.running = false;
stateLabel.visible = true;
stateLabel.text = "ERROR";
}
}
} ImageProcessor {
id: processor;
onFinished: {
imageViewer.source = "file:///" +newFile;
}
} FileDialog {
id: fileDialog;
title: "Please choose a file";
nameFilters: ["Image Files (*.jpg *.png *.gif)"];
onAccepted: {
console.log(fileDialog.fileUrl);
imageViewer.source = fileDialog.fileUrl;
}
} Component{
id: btnStyle;
ButtonStyle {
background: Rectangle {
implicitWidth: 70
implicitHeight: 25
border.width: control.pressed ? 2 : 1
border.color: (control.pressed || control.hovered) ? "#00A060" : "#888888"
radius: 6
gradient: Gradient {
GradientStop { position: 0 ; color: control.pressed ? "#cccccc" : "#e0e0e0" }
GradientStop { position: 1 ; color: control.pressed ? "#aaa" : "#ccc" }
}
}
}
} Button {
id: openFile;
text: "打开";
anchors.left: parent.left;
anchors.leftMargin: 6;
anchors.top: parent.top;
anchors.topMargin: 6;
onClicked: {
fileDialog.visible = true;
}
style: btnStyle;
z: 1;
} Button {
id: quit;
text: "退出";
anchors.left: openFile.right;
anchors.leftMargin: 4;
anchors.bottom: openFile.bottom;
onClicked: {
Qt.quit()
}
style: btnStyle;
z: 1;
} Rectangle {
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: openFile.bottom;
anchors.bottomMargin: -6;
anchors.right: quit.right;
anchors.rightMargin: -6;
color: "#404040";
opacity: 0.7;
}
Grid {
id: op;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
rows: 2;
columns: 3;
rowSpacing: 4;
columnSpacing: 4;
z: 1; Button {
text: "柔化";
style: btnStyle;
onClicked: {
busy.running = true;
processor.process(fileDialog.fileUrl, ImageProcessor.Soften);
}
} Button {
text: "灰度";
style: btnStyle;
onClicked: {
busy.running = true;
processor.process(fileDialog.fileUrl, ImageProcessor.Gray);
}
} Button {
text: "浮雕";
style: btnStyle;
onClicked: {
busy.running = true;
processor.process(fileDialog.fileUrl, ImageProcessor.Emboss);
}
}
Button {
text: "黑白";
style: btnStyle;
onClicked: {
busy.running = true;
processor.process(fileDialog.fileUrl, ImageProcessor.Binarize);
}
} Button {
text: "底片";
style: btnStyle;
onClicked: {
busy.running = true;
processor.process(fileDialog.fileUrl, ImageProcessor.Negative);
}
} Button {
text: "锐化";
style: btnStyle;
onClicked: {
busy.running = true;
processor.process(fileDialog.fileUrl, ImageProcessor.Sharpen);
}
}
} Rectangle {
anchors.left: parent.left;
anchors.top: op.top;
anchors.topMargin: -4;
anchors.bottom: parent.bottom;
anchors.right: op.right;
anchors.rightMargin: -4;
color: "#404040";
opacity: 0.7;
}
}
图片的显示使用一个充满窗体的 Image 对象,在 onStatusChanged 信号处理器中控制载入提示对象 BusyIndicator 是否显示。我通过 Z 序来保证 busy 总是在 imageViewer 上面。
你看到了。我像使用 QML 内建对象那样使用了 ImageProcessor 对象。为它的 finished 信号定义了 onFinished 信号处理器。在信号处理器中把应用图像特效后的中间文件传递给 imageViewer 来显示。
界面布局比較简陋。打开和退出两个button放在左上角,使用锚布局。关于锚布局。请參考《Qt Quick 布局介绍》或《Qt Quick 简单教程》。图像处理的 6 个button使用 Grid 定位器来管理。 2 行 3 列,放在界面左下角。
Grid 定位器的使用请參考《Qt Quick 布局介绍》。
关于图像处理button。以黑白特效做下说明。在 onClicked 信号处理器中。调用 processor 的 process() 方法。传入本地图片路径和特效算法。当特效运算异步完毕后,就会触发 finished 信号,进而 imageViewer 会更新……
好啦好啦,我们的图像处理实例就到此为止了。
秒懂?
实例项目及源代码下载:点这里点这里。须要一点积分啊亲。
回想一下吧:
本文写作过程中參考了下列文章,特此感谢:
- winorlose2000 博客( http://vaero.blog.51cto.com/ )中关于图像处理算法的博文
- ian 的个人博客( http://www.icodelogic.com/ )中关于图像处理算法的博文
Qt Quick 图像处理实例之美图秀秀(附源代码下载)的更多相关文章
- Qt Quick综合实例之文件查看器
假设你基于Qt SDK 5.3.1来创建一个Qt Quick App项目,项目模板为你准备的main.qml文档的根元素是ApplicationWindow或Window.这次我们就以Applicat ...
- iOS开发系列--打造自己的“美图秀秀”
--绘图与滤镜全面解析 概述 在iOS中可以很容易的开发出绚丽的界面效果,一方面得益于成功系统的设计,另一方面得益于它强大的开发框架.今天我们将围绕iOS中两大图形.图像绘图框架进行介绍:Quartz ...
- 利用Photos 框架搭建美图秀秀相册选择器
简介:Photos框架是iOS8.0后推出的一个新的用于对系统相册进行相关操作的,在iOS8.0之前,开发中只能使用AssetsLibrary框架来访问移动设备的图片库.本文中不再对AssetsLib ...
- iOS开发系列--打造自己的“美图秀秀”
概述 在iOS中可以很容易的开发出绚丽的界面效果,一方面得益于成功系统的设计,另一方面得益于它强大的开发框架.今天我们将围绕iOS中两大图形.图像绘图框架进行介绍:Quartz 2D绘制2D图形和Co ...
- 美图秀秀DBA谈MySQL运维及优化
美图秀秀DBA谈MySQL运维及优化 https://mp.weixin.qq.com/s?__biz=MzI4NTA1MDEwNg==&mid=401797597&idx=2& ...
- iOS:iOS开发系列–打造自己的“美图秀秀”(下)
来源: KenshinCui 链接:http://www.cnblogs.com/kenshincui/p/3959951.html 运行效果: 其他图形上下文 前面我们也说过,Quartz 2D的图 ...
- PHP流式上传和表单上传(美图秀秀)
最近需要开发一个头像上传的功能,找了很多都需要授权的,后来找到了美图秀秀,功能非常好用. <?php /** * Note:for octet-stream upload * 这个是流式上传PH ...
- thinkphp + 美图秀秀api 实现图片裁切上传,带数据库
思路: 1.数据库 创建test2 创建表img,字段id,url,addtime 2.前台页: 1>我用的是bootstrap 引入必要的js,css 2>引入美图秀秀的js 3.后台: ...
- 强大的修图app--美图秀秀
美图秀秀的强大之处 市面上有很多图形处理软件,最专业的是ps,但是ps做起来需要的专业技术很高,而美图秀秀可以说用起来并不需要很专业,操作起来非常方便,而且界面可爱.所以说美图秀秀是一款很好用的免 ...
随机推荐
- 服务器禁用ping
linux禁ping.这里操作的是centos6.5内核参数禁ping禁用ping #echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all启用ping ...
- OpenCV2:总结篇 工具方法函数
一.简介 OpenCV提供了一些工具方法函数来辅助完成图像运算 二.时间相关 1.getTickCount()和getTickFrequency() double tTime; tTime = (do ...
- IIS实现HTTPS的主机名绑定
默认情况下,IIS中HTTPS 绑定是无法指定主机名的解决办法:通过手工修改 IIS 配置来实现主机头绑定.打开如下位置的文件. C:\Windows\system32\inetsrv\config\ ...
- WEB 前端模块化,读文笔记
文章链接 WEB 前端模块化都有什么? 知识点 根据平台划分 浏览器 AMD.CMD 存在网络瓶颈,使用异步加载 非浏览器 CommonJS 直接操作 IO,同步加载 浏览器 AMD 依赖前置 req ...
- Ext 6.5.3 classic版本,自定义实现togglefield开关控件
1,在Ext 6.5.3的classic版中没有提供开关控件,参照modern版中 togglefield开关的实现,继承滑动器(sliderfield),自定义一个开关按钮.支持value绑定和点击 ...
- CF161D Distance in Tree 点分治
题目: 输入点数为N一棵树,求树上长度恰好为K的路径个数 分析: 题目的数据范围不是很紧,点分治也可以过,树形dp也可以过.这里采用点分治做法. 我们只需要单开一个类似于桶的数组,跑点分治套路,统计即 ...
- Luogu-P1020(导弹拦截)(DP,LIS ,二分优化)
Luogu-P1020(导弹拦截)(DP) 题意: 给n(n<=100000) 个数字,求最长不上升子序列的长度和最少的不上升子序列的个数. 分析: 第一问: 求最长不上升子序列有 O(n^2) ...
- URAL1966 Cipher Message 3
题目描述 题解: 能看出来的是,每一组数只能改最后一位,所以前$7$位动不了. 所以$KMP$跑一跑. 重点在于最后一位怎么搞. 如果$KMP$跑完了还没找到合适的位置,直接$puts("N ...
- noi.ac NOIP2018 全国热身赛 第四场 T2 sort
[题解] 跟51nod 1105差不多. 二分答案求出第L个数和第R个数,check的时候再套一个二分或者用two pointers. 最后枚举ai在b里面二分,找到所有范围内的数,排序后输出. 注意 ...
- 大数据学习——hadoop2.x集群搭建
1.准备Linux环境 1.0先将虚拟机的网络模式选为NAT 1.1修改主机名 vi /etc/sysconfig/network NETWORKING=yes HOSTNAME=itcast ### ...