Qml实现类似ImageWatch的功能

本文介绍了如何在QML中开发一个带有图像缩放、颜色显示和鼠标交互功能的组件,使用OpenCV处理图像数据,但提到了缩放坐标存在误差的问题。开发者在处理图像放大和缩小时遇到了坐标定位不准确的情况。
摘要由CSDN通过智能技术生成

Qml实现类似ImageWatch的功能:

实现图像放大缩小,显示颜色值,拖动图像。
存在的问题:缩放坐标存在误差。

QML文件

import QtQuick
import QtQuick.Controls
 
Rectangle {
    property int itemflag: -1; color: "lightGray"; clip: true;
    onItemflagChanged: {
        table.model = opencv.im(itemflag);
    }
    onWidthChanged: {
        if (image.z === 1) {
            scaleImage();
        }
    }
    onHeightChanged: {
        if (image.z === 1) {
            scaleImage();
        }
    }

    Image {
        id: image; cache: false; clip: true; anchors.centerIn: parent; fillMode: Image.PreserveAspectFit; z: 1;
    }

    TableView {
        id: table; columnSpacing: 0; rowSpacing: 0; z: -1; anchors.fill: parent;
        property real cellsize: 7;
        property bool textshow: false;
        columnWidthProvider: function(column) {
            return cellsize;
        }
        rowHeightProvider: function(row) {
            return cellsize;
        }
        delegate: Rectangle {
            color: bgcolor; //implicitWidth: table.cellsize; implicitHeight: table.cellsize; border.width: 1; border.color: "black"; 
            Text {
                text: display; visible: table.textshow; color: fgcolor;
                horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; anchors.centerIn: parent;
            }
        }
        ScrollBar.vertical: ScrollBar {
            id: tableVB; policy: ScrollBar.AlwaysOff; orientation: Qt.Vertical; anchors.top: parent.top;  anchors.right: parent.right; anchors.bottom: parent.bottom;
        }
        
        ScrollBar.horizontal: ScrollBar {
            id: tableHB; policy: ScrollBar.AlwaysOff; orientation: Qt.Horizontal; anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: parent.bottom;
        }
    }
    Text {
        id: xyval; anchors.right: parent.right; anchors.bottom: parent.bottom; width: 240; height: 30; font.pixelSize: 20; z: 3; visible: itemflag === 0;
    }

    MouseArea {
        id: marea; anchors.fill: parent; acceptedButtons: Qt.LeftButton | Qt.RightButton; hoverEnabled: true; z: 2;
        property bool  pressflag:  false;
        property point pressPoint: Qt.point(0.0);
        onClicked: function(mouse) {
            if (mouse.button === Qt.LeftButton) {
                
            }
            if (mouse.button === Qt.RightButton) {

            }
        }
        onDoubleClicked: function(mouse) {
            if (mouse.button === Qt.LeftButton) {

            }
            if (mouse.button === Qt.RightButton) {

            }
        }
        onWheel: function(wheel) {
            if (wheel.angleDelta.y > 0) {
                if (table.z === -1) {
                    image.z = -1; table.z = 1; table.contentX = 0; table.contentY = 0;
                    
                    let zoomPrev = Qt.size(table.width, table.height);
                        
                    let zoomLast = Qt.size(table.contentWidth, table.contentHeight);
                    // 定位单元格
                    tablepositions(Qt.point(wheel.x, wheel.y), zoomPrev, zoomLast);
                }
                else {
                    if (table.cellsize > 57) {
                        table.textshow = true; table.columnSpacing = 1; table.rowSpacing = 1;
                    }

                    if (table.cellsize+1 < 60) {
                        let zoomPrev = Qt.size(table.contentWidth, table.contentHeight);
                        
                        table.cellsize += 1; table.forceLayout();
                        
                        let zoomLast = Qt.size(table.contentWidth, table.contentHeight);
                        // 定位单元格
                        tablepositions(Qt.point(wheel.x, wheel.y), zoomPrev, zoomLast);
                    }
                }
            } 
            else {
                if (table.z === 1) {
                    if (table.cellsize < 57) {
                        table.textshow = false; table.columnSpacing = 0; table.rowSpacing = 0;
                    }
                    
                    if (table.cellsize-1 > 7) {                            
                        let zoomPrev = Qt.size(table.contentWidth, table.contentHeight);
                        
                        table.cellsize -= 1;  table.forceLayout();
                        
                        let zoomLast = Qt.size(table.contentWidth, table.contentHeight);
                        // 定位单元格
                        tablepositions(Qt.point(wheel.x, wheel.y), zoomPrev, zoomLast);
                    }
                    else {
                         image.z = 1; table.z = -1;
                    }
                }
            }
        }
        onPressed: function(mouse) {
            marea.pressflag = true;
            
            let xTable = mouse.x / table.width  * table.contentWidth ;
            let yTable = mouse.y / table.height * table.contentHeight;
            
            marea.pressPoint = Qt.point(xTable, yTable);
        }
        onReleased: function(mouse) {
            marea.pressflag = false;
        }
        onPositionChanged: function(mouse) {
            xyval.text = "X:" + mouse.x.toFixed(2) + "    Y:" + mouse.y.toFixed(2);
            
            if (!marea.pressflag) {
                return;
            }
            
            let xTable = mouse.x / table.width  * table.contentWidth ;
            let yTable = mouse.y / table.height * table.contentHeight;
            
            table.contentX += (marea.pressPoint.x - xTable) / (xTable / mouse.x);
            table.contentY += (marea.pressPoint.y - yTable) / (yTable / mouse.y);
            
            marea.pressPoint.x = xTable;
            marea.pressPoint.y = yTable;
        }
    }

    Connections {
        target: opencv;
        function onImageUpdate(target) {
            if (target === itemflag) {
                image.source = "";
                image.source = "image://cv/" + target;
                scaleImage();
            }
        }
    }
    
    function scaleImage() {       
        let dstSize = Qt.size(image.parent.width, image.parent.height);
        let srcSize = image.sourceSize;
        
        let wRatio = dstSize.width  / srcSize.width;
        let hRatio = dstSize.height / srcSize.height;
        
        image.scale = Math.min(wRatio, hRatio);
    }
    
    function tablepositions(mousepos, zoomPrev, zoomLast) { 
        // 缩放前坐标
        let xTablePrev = mousepos.x / table.width  * zoomPrev.width ;
        let yTablePrev = mousepos.y / table.height * zoomPrev.height;
        // 缩放后坐标
        let xTableLast = mousepos.x / table.width  * zoomLast.width ;
        let yTableLast = mousepos.y / table.height * zoomLast.height;
        
        table.contentX += (xTableLast - xTablePrev) / (xTablePrev / mousepos.x);
        table.contentY += (yTableLast - yTablePrev) / (yTablePrev / mousepos.y);
    }
}

表格模型头文件

#pragma once

#include <QAbstractTableModel>
#include <qdebug.h>

#include <opencv2/opencv.hpp>
using namespace cv;

class ImageModel : public QAbstractTableModel
{
	Q_OBJECT

public:
    ImageModel(QObject* parent = nullptr) : QAbstractTableModel(parent) {};
    ~ImageModel() {};

public:
    Q_INVOKABLE void setImage(Mat &mat) {
        beginResetModel();
        m_mat = mat.clone();
        endResetModel();
    };

public:
    int rowCount(const QModelIndex & = QModelIndex()) const override
    {
        return m_mat.rows;
    }

    int columnCount(const QModelIndex & = QModelIndex()) const override
    {
        return m_mat.cols;
    }

    QVariant data(const QModelIndex& index, int role) const override;

    QHash<int, QByteArray> roleNames() const override
    {
        return { {Qt::DisplayRole, "display"}, {Qt::UserRole + 1, "bgcolor"}, {Qt::UserRole + 2, "fgcolor"} };
    }

private:
    Mat m_mat;
};

表格模型源文件

#include "ImageModel.h"
#include <qcolor.h>

QVariant ImageModel::data(const QModelIndex & index, int role) const
{
	QVariant result;

	if (m_mat.empty()) {
		return result;
	}
	switch (m_mat.channels())
	{
	case 1:
	{
		uchar uval = *m_mat.ptr<uchar>(index.row(), index.column());
		switch (role)
		{
			case Qt::DisplayRole:
			{
				result = QString("%1").arg(uval);
			}break;
			case Qt::UserRole + 1:
			{
				result = QColor::fromRgb(uval,uval,uval);
			}break;
			case Qt::UserRole + 2:
			{
				result = QColor::fromRgb(255 - uval, 255 - uval, 255 - uval);
			}break;
		}
		
	}break;
	case 3:
	{
		Mat_<Vec3b> vval = m_mat;
		uchar v1 = vval(index.row(), index.column())[0];
		uchar v2 = vval(index.row(), index.column())[1];
		uchar v3 = vval(index.row(), index.column())[2];
		switch (role)
		{
			case Qt::DisplayRole:
			{
				result = QString("%1\n%2\n%3").arg(v1).arg(v2).arg(v3);
				//result = QString("%1, %2").arg(index.column()).arg(index.row());
			}break;
			case Qt::UserRole + 1:
			{
				result = QColor::fromRgb(v3, v2, v1);
			}break;
			case Qt::UserRole + 2:
			{
				result = QColor::fromRgb(255 - v3, 255 - v2, 255 - v1);
			}break;
		}
	}break;
	}
	return result;
}

图像提供头文件

#pragma once

#include <QQuickImageProvider>
#include <opencv2/opencv.hpp>
using namespace cv;

class ImageProvider  : public QQuickImageProvider
{
	Q_OBJECT

public:
	ImageProvider() : QQuickImageProvider(QQuickImageProvider::Image) {};
	~ImageProvider() {};

public:
	QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;

public:
	void setImage(Mat &mat, int target);

Q_SIGNALS:
	Q_INVOKABLE void imageUpdate(int target);

private:
	QImage m_sr1_img, m_sr2_img, m_dst_img;
};

图像提供源文件

#include "ImageProvider.h"

#include <qimage.h>
#include <qdebug.h>

QImage ImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize)
{
	int reqid = id.toInt();
	QImage image;

	switch (reqid)
	{
		case 0:
		{
			image = m_dst_img;
		}break;
		case 1:
		{
			image = m_sr1_img;
		}break;
		case 2:
		{
			image = m_sr2_img;
		}break;
	}

	return image;// .scaled(requestedSize, Qt::KeepAspectRatio);
}

void ImageProvider::setImage(Mat &mat, int target)
{
	QImage::Format format = QImage::Format_Invalid;
	switch (mat.type())
	{
		case CV_8UC1:
			format = QImage::Format_Grayscale8;
			break;
		case CV_8UC3:
			format = QImage::Format_BGR888;
	}

	size_t size = mat.rows * mat.step;
	uchar* pdata = (uchar*)malloc(size);
	if (pdata != NULL) {
		memcpy(pdata, mat.data, size);
	}

	switch (target)
	{
		case 0:
		{
			m_dst_img  = QImage((uchar*)pdata, mat.cols, mat.rows, mat.step, format);
		}break;
		case 1:
		{
			m_sr1_img = QImage((uchar*)pdata, mat.cols, mat.rows, mat.step, format);
		}break;
		case 2:
		{
			m_sr2_img = QImage((uchar*)pdata, mat.cols, mat.rows, mat.step, format);
		}break;
	}

	emit imageUpdate(target);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

难得糊涂_^_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值