在《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 是应用灰度特效后的截图:

图 3 灰度特效

图 4 是浮雕特效:

图 4 浮雕特效

图 5 是黑白特效:

图 5 黑白特效

图 6 是应用底片特效后的截图:

图 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);
}

这样处理的效果不是特别明显,采用高斯模糊算法可以获取更好的效果。

灰度

把图像变灰,大概有这么三种方法:

  1. 最大值法,即 R = G = B = max(R , G , B),这种方法处理过的图片亮度偏高
  2. 平均值法,即 R = G = B = (R + G + B) / 3 ,这种方法处理过的图片比较柔和
  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 实例,然后应用算法,处理后的图片保存到指定位置。

底片

早些年的相机使用胶卷记录拍摄结果,洗照片比较麻烦,不过如果你拿到底片,逆光去看,效果就很特别。

底片算法其实很简单,取 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_OBJECTQ_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.1Rectangle {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: 70implicitHeight: 25border.width: control.pressed ? 2 : 1border.color: (control.pressed || control.hovered) ? "#00A060" : "#888888"radius: 6gradient: 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 来显示。

界面布局比较简陋,打开和退出两个按钮放在左上角,使用锚布局。关于锚布局,请参考《Qt Quick 布局介绍》或《Qt Quick 简单教程》。图像处理的 6 个按钮使用 Grid 定位器来管理, 2 行 3 列,放在界面左下角。 Grid 定位器的使用请参考《Qt Quick 布局介绍》。

关于图像处理按钮,以黑白特效做下说明,在 onClicked 信号处理器中,调用 processor 的 process() 方法,传入本地图片路径和特效算法。当特效运算异步完成后,就会触发 finished 信号,进而 imageViewer 会更新……

好啦好啦,我们的图像处理实例就到此为止了。秒懂?

实例项目及源代码下载:点这里点这里。需要一点积分啊亲。

回顾一下吧:

  • Qt Quick 简介
  • QML 语言基础
  • Qt Quick 之 Hello World 图文详解
  • Qt Quick 简单教程
  • Qt Quick 事件处理之信号与槽
  • Qt Quick事件处理之鼠标、键盘、定时器
  • Qt Quick 事件处理之捏拉缩放与旋转
  • Qt Quick 组件与对象动态创建详解
  • Qt Quick 布局介绍
  • Qt Quick 之 QML 与 C++ 混合编程详解

本文写作过程中参考了下列文章,特此感谢:

  • winorlose2000 博客( http://vaero.blog.51cto.com/ )中关于图像处理算法的博文
  • ian 的个人博客( http://www.icodelogic.com/ )中关于图像处理算法的博文

Qt Quick 图像处理实例之美图秀秀(附源码下载)相关推荐

  1. Qt Quick 图像处理实例之美图秀秀 附源码下载

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 在< ...

  2. 美图秀秀应用源码下载

    该源码实现了一款非常不错的应用,美图秀秀应用,我们都知道美图秀秀是我们比较喜欢的一种处理图片的应用,喜欢的朋友可以下载看看. 源码下载: http://code.662p.com/view/1518. ...

  3. Cesium专栏-气象卫星云图动图(附源码下载)

    Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...

  4. 美图秀秀3.91.1001下载(图片处理软件去广告纯净绿化版)

    来源:http://fusrc.blog.163.com/blog/static/188012033201351352736229/ 美图秀秀3.91.1001下载(图片处理软件去广告纯净绿色版)根据 ...

  5. Adroid游戏开发实例讲解(三)-小蝌蚪找妈妈附源码

    Adroid游戏开发实例讲解(三)-小蝌蚪找妈妈附源码 程序之美 从小就听着小蝌蚪找妈妈的故事长大,我相信小伙伴们一定都不陌生,因为小学课本中,我们早早的就学过了小蝌蚪找妈妈这篇文章,它既是一篇文章, ...

  6. 安卓机器人做图软件_美图秀秀绘画机器人app下载-美图绘画机器人Andy最新版下载v7.0.0.0-西西软件下载...

    美图绘画机器人Andy最新版是美图秀秀最新推出的AI智能绘画机器人,名叫Andy,这款软件可以让你的照片秒变插画,非常不错的一款软件,非常好玩,效果也非常的棒,欢迎大家前来西西下载美图绘画机器人And ...

  7. php拼音模糊查询,PHP模糊查询技术实例分析【附源码下载】

    本文实例讲述了PHP模糊查询技术.分享给大家供大家参考,具体如下: 简介 从本质上揭密PHP模糊查询技术 功能 根据输入的关键字查找相关用户 PHP用户查询器案例分析 课程目标 掌握PHP模糊技术的应 ...

  8. Android一行代码实现网络加载GIF闪图(附源码)

    最近项目有个需求是要从网络加载GIF闪图, 但是Android原生的ImageView并不支持Gif... 于是从网上看了些Dome, 发现总是有些这样那样的问题, 譬如: ☹ 没有缓存,还要自己写一 ...

  9. 加入百度移动联盟广告SSP美图android安卓源码 详情有演示apk

    1.演示apk: 链接:http://pan.baidu.com/s/1o7KJQem 2.源码100/份 把ID号改成你的就行了. 3.百度移动广告androidSDK及说明书BaiduMobAds ...

最新文章

  1. MySQL cast()函数
  2. Python学习17 Turtle库绘图
  3. 罗永浩开了一家直播界的新东方
  4. 小米8青春版超级夜景安排上了 这个样张我是服气的!
  5. u-boot移植随笔:一些内存地址的研究(gd_t和bd_t结构体)
  6. 运行后闪退_好消息好消息,王者荣耀闪退问题苹果也修复啦
  7. Oracle RAC的Failover
  8. Oracle 建表语句
  9. 盘点五款值得收藏的 Linux 开发板
  10. 系统分析与设计第一次作业
  11. uni-app实现微信小程序一键登录
  12. 员工离职率高如何解决?
  13. python自动化交易 期货_一只股票一天可以撤单单几次
  14. C++ Primer Plus(第六版)编程练习答案 第3章 处理数据
  15. mysql查询5分钟内的数据
  16. 四旋翼无人机学习第15节--PCB Editor简单绘制封装-手动绘制封装
  17. 字符串切片slice操作(字符串)
  18. 渔夫打鱼问题 java思路_心理故事:一个渔夫出海打鱼,怎么会饿死?
  19. U-Net——《U-Net: Convolutional Networks for Biomedical Image Segmentation》(MICCA 2015)
  20. Jenkins 流水线 获取git 分支列表_基于Jenkins的DevOps流水线实践教程|2020全新制作|端到端研发效能提升...

热门文章

  1. 多商户系统更换短信模板操作
  2. HashMap底层实现原理--详细
  3. 31岁转行程序员 目前35了 分享一些我的经历和感受
  4. 关于私有云、专有云、公有云、SaaS、独立部署、多租户
  5. 当红齐天再捧“绽放杯”金奖:全流程算力网络夯实元宇宙“底座”
  6. 柠檬浏览器 for linux,柠檬浏览器官方下载
  7. 糖尿病饮食(常见问题2)
  8. 数据引擎-阿里的ODPS大规模计算引擎
  9. Linux系统C编程资料
  10. 极鹰云融合cdn用在云服务器如何加速