- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我有一个自定义的 QQuickPaintedItem,它可以绘制用户用鼠标在其上绘制的任何内容。到目前为止,实现非常简单,只需绘制整个图像,即使在放大时也是如此。我注意到放大和平移图像时 FPS 真的很慢,所以我决定逐步提高绘画性能。
我当前的步骤只是绘制可见的图像子集。为此,我使用 this overload of QPainter::drawImage()
.这是允许缩放和平移的最小示例(重要部分是 recalculateStuff()
):
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
#include <QQuickItem>
#include <QImage>
#include <QQuickPaintedItem>
#include <QPainter>
#include <QtMath>
class ImageCanvas : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QPoint offset READ offset WRITE setOffset NOTIFY offsetChanged)
Q_PROPERTY(int zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(QRect sourceRect READ sourceRect NOTIFY sourceRectChanged)
Q_PROPERTY(QRect targetRect READ targetRect NOTIFY targetRectChanged)
public:
ImageCanvas() :
mZoom(1)
{
// Construct a test image from coloured squares.
mImage = QImage(500, 500, QImage::Format_ARGB32);
QPainter painter(&mImage);
for (int y = 0; y < mImage.width(); y += 50) {
for (int x = 0; x < mImage.width(); x += 50) {
const QColor colour((x / 500.0) * 255, (y / 500.0) * 255, 0);
painter.fillRect(x, y, 50, 50, colour);
}
}
recalculateStuff();
}
QPoint offset() const {
return mOffset;
}
void setOffset(const QPoint &offset) {
mOffset = offset;
recalculateStuff();
emit offsetChanged();
}
int zoom() const {
return mZoom;
}
void setZoom(int zoom) {
mZoom = qMax(1, zoom);
recalculateStuff();
emit zoomChanged();
}
QRect targetRect() const {
return mTargetRect;
}
QRect sourceRect() const {
return mSourceRect;
}
void recalculateStuff() {
const QRect oldTargetRect = mTargetRect;
const QRect oldSourceRect = mSourceRect;
mTargetRect = QRect(0, 0, mImage.width() * mZoom, mImage.height() * mZoom);
mSourceRect = QRect(0, 0, mImage.width(), mImage.height());
const int contentLeft = mOffset.x();
if (contentLeft < 0) {
// The left edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setX(qAbs(contentLeft));
mSourceRect.setX(qAbs(contentLeft));
}
const int contentTop = mOffset.y();
if (contentTop < 0) {
// The top edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setY(qAbs(contentTop));
mSourceRect.setY(qAbs(contentTop));
}
const int contentRight = mOffset.x() + mImage.width();
const int viewportRight = qFloor(width());
if (contentRight > viewportRight) {
// The right edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setWidth(mTargetRect.width() - (contentRight - viewportRight));
mSourceRect.setWidth(mSourceRect.width() - (contentRight - viewportRight));
}
const int contentBottom = mOffset.y() + mImage.height();
const int viewportBottom = qFloor(height());
if (contentBottom > viewportBottom) {
// The bottom edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setHeight(mTargetRect.height() - (contentBottom - viewportBottom));
mSourceRect.setHeight(mSourceRect.height() - (contentBottom - viewportBottom));
}
if (mTargetRect != oldTargetRect)
emit targetRectChanged();
if (mSourceRect != oldSourceRect)
emit sourceRectChanged();
update();
}
void paint(QPainter *painter) override {
painter->translate(mOffset);
painter->drawImage(mTargetRect, mImage, mSourceRect);
}
protected:
void geometryChanged(const QRectF &, const QRectF &) override {
recalculateStuff();
}
signals:
void offsetChanged();
void zoomChanged();
void sourceRectChanged();
void targetRectChanged();
private:
QPoint mOffset;
int mZoom;
QRect mSourceRect;
QRect mTargetRect;
QImage mImage;
};
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<ImageCanvas>("App", 1, 0, "ImageCanvas");
QQmlApplicationEngine engine;
engine.load(QUrl("qrc:/main.qml"));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.10
import QtQuick.Controls 2.3
import App 1.0
ApplicationWindow {
id: window
width: 600
height: 600
visible: true
title: "targetRect=" + canvas.targetRect + " sourceRect=" + canvas.sourceRect
ImageCanvas {
id: canvas
anchors.fill: parent
offset: Qt.point(xOffsetSlider.value, yOffsetSlider.value)
zoom: zoomSpinBox.value
}
SpinBox {
id: zoomSpinBox
from: 1
to: 8
}
Slider {
id: xOffsetSlider
anchors.bottom: parent.bottom
width: parent.width - height
from: -window.width * canvas.zoom
to: window.width * canvas.zoom
ToolTip {
id: xOffsetToolTip
parent: xOffsetSlider.handle
visible: true
text: xOffsetSlider.value.toFixed(1)
Binding {
target: xOffsetToolTip
property: "visible"
value: !yOffsetToolTip.visible
}
}
}
Slider {
id: yOffsetSlider
anchors.right: parent.right
height: parent.height - width
orientation: Qt.Vertical
from: -window.height * canvas.zoom
scale: -1
to: window.height * canvas.zoom
ToolTip {
id: yOffsetToolTip
parent: yOffsetSlider.handle
text: yOffsetSlider.value.toFixed(1)
Binding {
target: yOffsetToolTip
property: "visible"
value: !xOffsetToolTip.visible
}
}
}
}
这在缩放级别为 1 时效果很好,但是一旦放大,目标和源矩形就错了。我一直在尝试修复它,但我无法完全理解它。例如,一个天真的想法是使用非缩放坐标进行所有计算,然后缩放目标矩形:
diff --git a/main.cpp b/main.cpp
index 8409baf..06841b7 100644
--- a/main.cpp
+++ b/main.cpp
@@ -64,24 +64,24 @@ public:
const QRect oldTargetRect = mTargetRect;
const QRect oldSourceRect = mSourceRect;
- mTargetRect = QRect(0, 0, mImage.width() * mZoom, mImage.height() * mZoom);
+ mTargetRect = QRect(0, 0, mImage.width(), mImage.height());
mSourceRect = QRect(0, 0, mImage.width(), mImage.height());
- const int contentLeft = mOffset.x();
+ const int contentLeft = mOffset.x() / mZoom;
if (contentLeft < 0) {
// The left edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setX(qAbs(contentLeft));
mSourceRect.setX(qAbs(contentLeft));
}
- const int contentTop = mOffset.y();
+ const int contentTop = mOffset.y() / mZoom;
if (contentTop < 0) {
// The top edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setY(qAbs(contentTop));
mSourceRect.setY(qAbs(contentTop));
}
- const int contentRight = mOffset.x() + mImage.width();
+ const int contentRight = (mOffset.x() / mZoom) + mImage.width();
const int viewportRight = qFloor(width());
if (contentRight > viewportRight) {
// The right edge of the content is outside of the viewport, so don't draw that portion.
@@ -89,7 +89,7 @@ public:
mSourceRect.setWidth(mSourceRect.width() - (contentRight - viewportRight));
}
- const int contentBottom = mOffset.y() + mImage.height();
+ const int contentBottom = (mOffset.y() / mZoom) + mImage.height();
const int viewportBottom = qFloor(height());
if (contentBottom > viewportBottom) {
// The bottom edge of the content is outside of the viewport, so don't draw that portion.
@@ -97,6 +97,11 @@ public:
mSourceRect.setHeight(mSourceRect.height() - (contentBottom - viewportBottom));
}
+ mTargetRect.setX(mTargetRect.x() * mZoom);
+ mTargetRect.setY(mTargetRect.y() * mZoom);
+ mTargetRect.setWidth(mTargetRect.width() * mZoom);
+ mTargetRect.setHeight(mTargetRect.height() * mZoom);
+
if (mTargetRect != oldTargetRect)
emit targetRectChanged();
这是行不通的,因为图像会随着您的移动而逐渐拉伸(stretch)。将缩放设置为 2 向下平移,而不是保持相同的比例。
那么,计算目标矩形和源矩形的正确方法是什么,以确保在放大图像时只绘制图像的可见部分?
最佳答案
总体思路是将图像矩形与绘画区域矩形相交,即项目矩形 ({0, 0, width(), height()}
)。这种相交必须在选定的坐标系中完成,并且矩形必须传播到另一个坐标系。让我们在目标坐标系中做交集:
// **private
private:
QImage mImage;
QPointF mOffset;
double mZoom = 1.0;
double mRenderTime = 0.;
bool mRectDraw = true;
QRectF mSourceRect;
QRectF mTargetRect;
static void moveBy(QRectF &r, const QPointF &o) {
r = {r.x() + o.x(), r.y() + o.y(), r.width(), r.height()};
}
static void scaleBy(QRectF &r, qreal s) {
r = {r.x() * s, r.y() * s, r.width() * s, r.height() * s};
}
void recalculate() {
const auto oldTargetRect = mTargetRect;
const auto oldSourceRect = mSourceRect;
mTargetRect = {{}, mImage.size()};
moveBy(mTargetRect, -mOffset);
scaleBy(mTargetRect, mZoom);
mTargetRect = mTargetRect.intersected({{}, size()});
现在我们将该矩形转换回源(图像)坐标系:
mSourceRect = mTargetRect;
scaleBy(mSourceRect, 1.0/mZoom);
moveBy(mSourceRect, mOffset);
if (mTargetRect != oldTargetRect)
emit targetRectChanged(mTargetRect);
if (mSourceRect != oldSourceRect)
emit sourceRectChanged(mSourceRect);
update();
}
然后必须选择如何滚动 - 通常滚动范围只是源图像矩形内的任意位置(即 mImage.rect()
,回想一下它是 {0, 0 , mImage.width(), mImage.height()}
),因此 x/y 滚动 slider 分别在 0 和图像的宽度/高度之间移动。
绘画也可以通过绘制整个图像来实现,但不幸的是支持画家的绘画引擎不知道如何处理裁剪 - 所以即使我们在 drawImage
之前设置裁剪,它不会做任何事情:我们必须与之合作的画家会忽略剪裁。因此,在高缩放值下,使用 mRectDraw = false
的绘画变得低效。这是绘图引擎的一个缺陷,它肯定可以在 Qt 中得到修复。
// **paint
void paint(QPainter *p) override {
QElapsedTimer timer;
timer.start();
if (mRectDraw) {
p->drawImage(mTargetRect, mImage, mSourceRect);
} else {
p->scale(mZoom, mZoom);
p->translate(-mOffset);
p->drawImage(0, 0, mImage);
}
mRenderTime = timer.nsecsElapsed() * 1E-9;
emit renderTimeChanged(mRenderTime);
}
示例的其余部分如下。 zoom spinbox 的含义是 sqrt(2)
的指数,即 value=0 -> zoom=1
, value=-2 -> zoom= 0.5
、`value=4 -> zoom=2' 等。 Canvas 支持正的非零缩放值,即小于 1 的值。
// https://github.com/KubaO/stackoverflown/tree/master/questions/qml-zoom-imagecanvas-51455895
#include <QtQuick>
#include <limits>
class ImageCanvas : public QQuickPaintedItem {
Q_OBJECT
Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)
Q_PROPERTY(QRectF imageRect READ imageRect NOTIFY imageRectChanged)
Q_PROPERTY(QPointF offset READ offset WRITE setOffset NOTIFY offsetChanged)
Q_PROPERTY(double zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(double renderTime READ renderTime NOTIFY renderTimeChanged)
Q_PROPERTY(bool rectDraw READ rectDraw WRITE setRectDraw NOTIFY rectDrawChanged)
Q_PROPERTY(QRectF sourceRect READ sourceRect NOTIFY sourceRectChanged)
Q_PROPERTY(QRectF targetRect READ targetRect NOTIFY targetRectChanged)
public:
ImageCanvas(QQuickItem *parent = {}) : QQuickPaintedItem(parent) {}
QImage image() const { return mImage; }
QRectF imageRect() const { return mImage.rect(); }
void setImage(const QImage &image) {
if (mImage != image) {
auto const oldRect = mImage.rect();
mImage = image;
recalculate();
emit imageChanged(mImage);
if (mImage.rect() != oldRect)
emit imageRectChanged(mImage.rect());
}
}
Q_SIGNAL void imageChanged(const QImage &);
Q_SIGNAL void imageRectChanged(const QRectF &);
QPointF offset() const { return mOffset; }
void setOffset(const QPointF &offset) {
mOffset = offset;
recalculate();
emit offsetChanged(mOffset);
}
Q_SIGNAL void offsetChanged(const QPointF &);
double zoom() const { return mZoom; }
void setZoom(double zoom) {
if (zoom != mZoom) {
mZoom = zoom ? zoom : std::numeric_limits<float>::min();
recalculate();
emit zoomChanged(mZoom);
}
}
Q_SIGNAL void zoomChanged(double);
// **paint
double renderTime() const { return mRenderTime; }
Q_SIGNAL void renderTimeChanged(double);
bool rectDraw() const { return mRectDraw; }
void setRectDraw(bool r) {
if (r != mRectDraw) {
mRectDraw = r;
recalculate();
emit rectDrawChanged(mRectDraw);
}
}
Q_SIGNAL void rectDrawChanged(bool);
QRectF sourceRect() const { return mSourceRect; }
QRectF targetRect() const { return mTargetRect; }
Q_SIGNAL void sourceRectChanged(const QRectF &);
Q_SIGNAL void targetRectChanged(const QRectF &);
protected:
void geometryChanged(const QRectF &, const QRectF &) override {
recalculate();
}
// **private
};
QImage sampleImage() {
QImage image(500, 500, QImage::Format_ARGB32_Premultiplied);
QPainter painter(&image);
for (int y = 0; y < image.height(); y += 50)
for (int x = 0; x < image.width(); x += 50) {
const QColor colour((x / 500.0) * 255, (y / 500.0) * 255, 0);
painter.fillRect(x, y, 50, 50, colour);
}
return image;
}
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<ImageCanvas>("App", 1, 0, "ImageCanvas");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("sampleImage", sampleImage());
engine.load(QUrl("qrc:/main.qml"));
return app.exec();
}
#include "main.moc"
和 qml:
import QtQuick 2.10
import QtQuick.Controls 2.3
import App 1.0
ApplicationWindow {
id: window
width: 600
height: 600
visible: true
title: "T=" + (canvas.renderTime*1E3).toFixed(1) + "ms t=" + canvas.targetRect + " s=" + canvas.sourceRect
ImageCanvas {
id: canvas
image: sampleImage
anchors.fill: parent
offset: Qt.point(xOffsetSlider.value, yOffsetSlider.value)
zoom: Math.pow(Math.SQRT2, zoomSpinBox.value)
rectDraw: rectDrawCheckBox.checked
}
SpinBox {
id: zoomSpinBox
anchors.bottom: xOffsetSlider.top
from: -10
to: 20
}
CheckBox {
id: rectDrawCheckBox
anchors.left: zoomSpinBox.right
anchors.bottom: xOffsetSlider.top
text: "rectDraw"
checked: true
}
Slider {
id: xOffsetSlider
anchors.bottom: parent.bottom
width: parent.width - height
from: 0
to: canvas.imageRect.width
ToolTip {
id: xOffsetToolTip
parent: xOffsetSlider.handle
visible: true
text: xOffsetSlider.value.toFixed(1)
Binding {
target: xOffsetToolTip
property: "visible"
value: !yOffsetToolTip.visible
}
}
}
Slider {
id: yOffsetSlider
anchors.right: parent.right
height: parent.height - width
orientation: Qt.Vertical
from: canvas.imageRect.height
to: 0
ToolTip {
id: yOffsetToolTip
parent: yOffsetSlider.handle
text: yOffsetSlider.value.toFixed(1)
Binding {
target: yOffsetToolTip
property: "visible"
value: !xOffsetToolTip.visible
}
}
}
}
关于c++ - QPainter:仅绘制放大图像的可见区域,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51455895/
我正在开发这样的摄影应用程序: https://play.google.com/store/apps/details?id=com.photo.editor.collage.maker.photobl
我正在尝试创建一个可以像在 Excel 中一样放大和缩小的 QTableView。 此处提出了类似的问题:Zooming function on a QWidget 但是,我在 PyQt 而不是 C
我想找到放大/缩小 TVirtualStringTree 的“最佳方法”。 “放大”的意思是模仿放大镜。 必须通过优先使用 TVirtualStringTree 控件中专门用于此目的的属性/方法来理解
有没有办法在emacs上放大和缩小(动态改变字体大小,相当流畅)? 最佳答案 尝试 C-x C-+ 和 C-x C--;即 Control-x Control-减号/Control-再加上。 在一个组
我在使用 Leaflet 时遇到问题。我在 map 上找到了一堆标记(在我的例子中它代表高尔夫球场)。但是,当我放大/缩小时,标记在 map 上移动。 浏览网页后,解决方案似乎是 iconAnchor
我在使用 Leaflet 时遇到问题。我在 map 上找到了一堆标记(在我的例子中它代表高尔夫球场)。但是,当我放大/缩小时,标记在 map 上移动。 浏览网页后,解决方案似乎是 iconAnchor
我正在开发一款非常低分辨率的游戏,我需要放大它才能看到。我知道我可以使用 Graphics.scale(float x, float y) 但我想放大到中心。如何缩放中心的图形?有没有更简单的方法来制
我有这种方法可以向前/向后和向左/向右平移相机。我不确定为什么,但是是什么导致相机在靠近地形放大时移动得很好,但在缩小时移动得非常慢? 这是我平移相机的方式: void CameraPan(){
我正在尝试像这样进行缩放:zoom the imageview on the double click in viewflipper in android 我尝试将该代码改编为我的项目,但它不起作用.
$(window).height() 获取页面加载时窗口的高度。但是,在放大/缩小时,我想要新的视点高度。这可能吗? 它可以很好地调整普通浏览器的大小,但我想在移动设备上放大/缩小不会触发 $(win
我正在尝试制作一个显示图像、 block 文本和按钮的面板,与下一个模型上显示的面板保持相似的比率,无论页面大小/缩放比例是多少。 我首先尝试使用横写文本的图像(即不对文本、图像和背景使用不同的部分或
Blockquote 大家好,请问是否可以在此代码中添加放大、缩小功能?我对 html 很陌生。任何帮助将不胜感激。 Career Center
我有一个由 3 个元素组成的搜索栏。 首先 a 保存从下拉列表中选择的值。 第二个清除选择的元素 第三个供用户在下拉菜单之间切换。 我的问题 - 当我缩放超过 100% 时,img 和按钮元素变大了一
我目前遇到网站页脚的问题。 当以 100% 大小(正常大小)处理时,页脚对齐得很好。但是,当我调整它的大小时,它完全不对齐并位于左侧,它需要保持居中。 屏幕截图: 相关 CSS: /* Dark bl
我实现了 3 个按钮来允许用户放大、缩小和返回到默认大小。 脚本可以进行放大和缩小,但我还有 2 个问题无法解决。 第一个问题: 每次放大时,要缩放的图像仍然到达我的窗口的限制,当图像左右没有任何空间
即使我在网页上放大或缩小,我也试图让 shadowbox 保持在同一个地方。我尝试以百分比而不是像素为单位来制作尺寸,但我不知道应该使用哪种属性组合。 这是我想要实现的结果:来自这里的外部阴影框效果h
我有点困惑。我按照 Apple 的建议放大 ScrollView ,但没有任何反应。 代码: import UIKit import SpriteKit class ScrollViewControl
我正在尝试通过导航实现类似 Bootstrap 的 header ,但我刚刚发现当我缩小窗口时, header 内的内容变得困惑并且 header 也最小化而不是保持相同的大小。我已经在页眉上使用了
我正在开发一个包含许多 UIView 的 iOS 应用程序。 UINavigation 用于在这些 View 之间导航。 我的一个 UIView 包含 UITextField。我的问题是,当我缩放时,
我正在尝试从 FITS 文件中绘制一些数据,我想知道是否有人知道如何关注绘图轴的某些区域?下面是一些示例代码: import pyfits from matplotlib import pyplot
我是一名优秀的程序员,十分优秀!