ธันวาคม 12, 2018, 01:33:14 pm *
ยินดีต้อนรับคุณ, บุคคลทั่วไป กรุณา เข้าสู่ระบบ หรือ ลงทะเบียน
ส่งอีเมล์ยืนยันการใช้งาน?

เข้าสู่ระบบด้วยชื่อผู้ใช้ รหัสผ่าน และระยะเวลาในเซสชั่น
   หน้าแรก   ช่วยเหลือ เข้าสู่ระบบ สมัครสมาชิก  
หน้า: [1]   ลงล่าง
  พิมพ์  
ผู้เขียน หัวข้อ: Qt : C++ Rendering and QML Animation usig QQuickImageProvider  (อ่าน 2877 ครั้ง)
0 สมาชิก และ 1 บุคคลทั่วไป กำลังดูหัวข้อนี้
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8272


ShadowWares


| |
« เมื่อ: มิถุนายน 12, 2015, 04:00:47 pm »

Qt : C++ Rendering and QML Animation usig QQuickImageProvider

ก่อนจะไปดูรายละเอียดของ code ดูผลกันก่อน;
<a href="https://www.youtube.com/v/mMfF12AyWgA" target="_blank">https://www.youtube.com/v/mMfF12AyWgA</a>
Graphics ที่เห็นถูกสร้างด้วย C++ และนำไปแสดงผลใน QML และใช้ความสามารถของ QML สำหรับการดักจับ Mouse-Click ตามด้วยสั่งให้ Graphic หมุน

*.qml:
Code: (xml)
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.2

Window {
    id: mainWindow
    objectName: "mainWindow"
    title: "QML vs C++ Rendering"
    visible: true
    width: 700;
    height: 650;
    color: "black"

    Item {
        id: container
        Column {
            id:col
            x: 100
            y: 80
            Image {
                id: image1
                height: 300
                source: "image://pixmap_provider/black"
                cache: false

                MouseArea {
                    anchors.fill: image1
                    onClicked: container.state == 'animate' ? container.state = '' : container.state = 'animate'
                }
                function reload() {
                    var oldSource = source;
                    source = "";
                    source = oldSource;
                }
            }
            Image {
                id: image2
                height: 200
                source: "image://pixmap_provider/green"
                cache: false
                function reload() {
                    var oldSource = source;
                    source = "";
                    source = oldSource;
                }
            }
        }

        Connections {
            target: PixmapReceiver
            onUpdateQml:{
                image1.reload();
                image2.reload();
            }
        }
        states: [
            State {
                name: "animate"
                PropertyChanges { target: image1; rotation: 180 }
            }
        ]
        transitions: [
            Transition {
                RotationAnimation {duration: 2000; direction: RotationAnimation.Counterclockwise}
            }
        ]
    }
}


ส่วนที่น่าสนใจใน QML คือ
Code:
source: "image://pixmap_provider/black"
"image://pixmap_provider/" ใช้อ้างถึงตัวกลางในการส่งผ่านข้อมูลภาพ ในที่นี้่คือ QQuickImageProvider
"black" ใช้อ้างถึง element นี้ ในที่นีคือ Image ที่มี id=image1 นั่นเอง


และ
Code:
function reload() {
var oldSource = source;
source = "";
source = oldSource;
}
เป็น JavaScript ถูกเรียกเมื่อ image1 ถูกคลิก ที่น่าสังเกตุคือ "source" ถูก clear และ เขียนค่าลงไปใหม่ด้วยค่าเดิม "oldSource" ที่ต้องทำแบบนี้เพ่อเป็นการบังคับให้ QML Display ทำการ reload/refresh การแสดงผลใหม่

ส่วนถัดมาคือส่วนที่ทำการเชื่อมต่อระหว่าง QML กับ C++ (Provider)
Code:
Connections {
target: PixmapReceiver
onUpdateQml:{
image1.reload();
image2.reload();
}
}
PixmapReceiver จะใช้อ้างเป็น Context Property ของ QML และจะถูกผูกติดอยู่กับ ImageProvider มาจาก C++ (ดูใน main.cpp)
onUpdateQml เป็น Slot จะถูกเรียกจาก C++ (ดูในฟังก์ชั่น updateImage())

สำหรับ Code ในส่วน อื่นๆ เป็น Basic QML จะไม่ขอกล่าวถึงในที่นี้

ต่อไปมาดู class ที่ทำหน้าที่เป็นตัวสร้าง Graphics แบบ QPixmap
*.h
Code:
#ifndef IMAGEPROVIDER
#define IMAGEPROVIDER
#include <QQuickImageProvider>
#include <QPixmap>
#include <QObject>
class PixmapProvider : public QObject, public QQuickImageProvider
{
    Q_OBJECT
public:
    PixmapProvider(QObject *parent);
    virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize);
private:
    QPixmap *pixmap;
signals:
    void updateQml();
public slots:
    void updateImage();

};
#endif // IMAGEPROVIDER
ใน class นี้ได้ทำการสืบทอดมาจาก QObject ทั้งนี้เพื่อจะขอใช้ความสามรถของ Signals/Slots และสืบทอดมาจาก QQuickImageProvider ซึ่งการสืบทอดจาก QQuickImageProvider จะต้อง Implement requestPixmap() รายละเอียดตาม *.cpp

*.cpp
Code:
#include "imageprovider.h"
#include <QDebug>
#include <QTimer>
#include <QPixmap>
#include <QtMath>
#include <QPainter>

PixmapProvider::PixmapProvider(QObject *parent): QObject(parent) , QQuickImageProvider(QQuickImageProvider::Pixmap)
{
    pixmap = NULL;
    QTimer *timer = new QTimer();
    connect(timer, SIGNAL(timeout()), this, SLOT(updateImage()));
    timer->start(100);
}

QPixmap PixmapProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
    //static int w = 10;
    //w = (w+1)%300;
    int width  = 490;//+w;
    int height = 100;

    static qreal alpha = 0.0;
    static qreal amplitude = 0.1;
    amplitude += 0.01;
    if(amplitude > 0.9) amplitude = 0.1;
    alpha += M_PI/10;
    if(alpha >= 2.0*M_PI)alpha = alpha-2.0*M_PI;

    if(pixmap != NULL)
        delete pixmap;

    *size = QSize(width, height);

    pixmap = new QPixmap(requestedSize.width()  > 0 ? requestedSize.width()  : width,
                         requestedSize.height() > 0 ? requestedSize.height() : height);

    QPainter *paint = new QPainter(pixmap);
    // Gradient Brush
    QLinearGradient gradient(QPointF(0,0), QPointF(0,height));
    gradient.setSpread(QGradient::PadSpread);
    gradient.setColorAt(0.0,QColor::fromRgbF(1,0,0,1));
    gradient.setColorAt(0.3,QColor::fromRgbF(1,1,0,1));
    gradient.setColorAt(0.7,QColor::fromRgbF(0,1,1,1));
    gradient.setColorAt(1.0,QColor::fromRgbF(0,0,1,1));
    QBrush brush(gradient);

    // Paint on the Draw Board with the Gradient Brush
    paint->fillRect(0, 0, width, height, brush);

    // Generate 10 cycles of sine wave and create Path
    QVector<qreal> y (pixmap->width());
    static qreal nc = 10.0;
    nc += 0.1; if(nc > 20) nc = 2;
    qreal ga = pixmap->height()/2;
    for(int x = 0; x<y.size(); x++) {
        y[x] = ga + amplitude*(-ga * qSin(alpha + 2.0*M_PI * nc * (qreal)x / (qreal)y.size()));
    }

    // Vector to Path object
    QPainterPath path;
    path.moveTo(0,y[0]);
    for(int x = 0; x < y.size(); x++) {
        path.lineTo(QPointF(x, y[x]));
    }

    // Black Pen, 2 pixels width
    QColor pencolor = QColor(id).rgba();
    QPen pen(pencolor, 2);
    paint->setPen(pen);

    // Draw the Path (Sine wave) on the Draw Board
    paint->setRenderHint(QPainter::Antialiasing, true);
    paint->drawPath(path);
    delete paint;

    return *pixmap;
}

void PixmapProvider::updateImage()
{
    emit  updateQml();
}
สิ่งที่น่าสนใจ:
เมื่อ QML ต้องการ Update graphics (source ถูกเขียนด้วยค่า ImageProvider) ฟังชั่น requestPixmap() จะถูกเรียกจาก QML และทุกครั้งที่ QML เรียก  QML จะผ่านค่า id (ในที่นี้คือ "black" และ "green") พร้อมกับขนาดที่ต้องการมายัง requestPixmap() เพื่อให้ฟังก์ชั่นรับรู้ว่าเรียกโดยใคร จะได้สร้างภาพใหม่ให้ตรงตามความต้องการ

Code:
QColor pencolor = QColor(id).rgba();
จะเห็นได้ว่า id จะเป็นตัวกำหนดว่าต้องใช้ปากกาสีอะไร (black หรือ green)

อีกส่วนที่ขาดไม่ได้คือ
Code:
void PixmapProvider::updateImage()
{
    emit  updateQml();
}
ฟังก์ชั่นนี้ถูกเรียกโดย Timer ทุกๆ 100mS เพื่อทำการ ส่งสัญญาณไปให้ Qml ทำการร้องขอภาพใหม่ไปแสดงผล

ส่วนสุดท่ายคือ main.cpp
Code:
#include "imageprovider.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickImageProvider>
#include <QQmlContext>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine *engine = new QQmlApplicationEngine();
    PixmapProvider *pixmapProvider = new PixmapProvider(0);
    engine->addImageProvider(QLatin1String("pixmap_provider"), pixmapProvider);
    QQmlContext* ctx = engine->rootContext();
    ctx->setContextProperty("PixmapReceiver", pixmapProvider);
    engine->load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}
Code:
PixmapProvider *pixmapProvider = new PixmapProvider(0);
สร้าง PixmapProvider
Code:
engine->addImageProvider(QLatin1String("pixmap_provider"), pixmapProvider);
เพิ่ม"pixmap_provider" เข้ากับ PixmapProvider  ซึ่งใน QML จะอ้างถึง PixmapProvider ผ่าน "image://pixmap_provider/"
Code:
ctx->setContextProperty("PixmapReceiver", pixmapProvider);
เชื่อมโยง PixmapReceiver (ดูใน Connections ของ QML ด้านบน) กับ PixmapProvider เพื่อให้ทำงานตามแบบแบบของ Signals/Slots ได้

เนื้อหาค่อนข้างยากและชวนสับสนสำหรับคนที่ไม่ทราบรายละเอียดของ QtClasses แต่ละตัว แต่นั่นคือสิ่งที่มันเป็น และต้องเปิดใจยอมรับและจำเป็นต้องตามอ่านเพื่อเก็บรายละเอียด หากคิดว่า Qt เป็นเครื่องมือที่ตอบโจทย์  
ผมไม่มีความสามารถอธิบายได้มากกว่านี้ ทำได้แต่พอเปิดทางให้พอให้ภาพรวมเท่านั้น
winktongue
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
Jondancit
Newbie
*
ออฟไลน์ ออฟไลน์

กระทู้: 2


| |
« ตอบ #1 เมื่อ: พฤศจิกายน 15, 2018, 12:27:45 pm »

code เยอะเลยดูแล้วตาลายมองไม่ทัน
บันทึกการเข้า
หน้า: [1]   ขึ้นบน
  พิมพ์  
 
กระโดดไป: