Qt Drawing pixel by pixel zoomed - c++

I tried to find some helps around internet, i had some codes to test but none works like i want.
I would draw with Qt something pixel by pixel.
I tried with a QImage within a QLabel with the protected event mousePressEvent it worked, but the pixels are too small to see them.
I tried to scale my Image, it's much better, but the position X, Y are not the same position of the pixels now or they're not synchronized with the pixels locations.
Here a screenshot of what i want do, if someone has an idea to recreate this...
If i can exactly done this via Qt i'll save a lots of time, just a basic drawing pixel by pixel, i created this example with Paint, Black and White format zoomed to 800%.
Thanks.

This is a solution hard-coded for a specific size - just implement relative coordinate mapping between pixmap and draw widget and you are set.
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0) : QWidget(parent), pressed(0) {
color = Qt::black;
pixmap = new QPixmap("h:/small.png");
resize(240, 240);
}
~Widget() { if (pixmap) delete pixmap; }
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.drawPixmap(0, 0, pixmap->scaled(240, 240));
painter.drawPixmap(0, 0, *pixmap);
}
void mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::RightButton)
color = color == Qt::black ? Qt::white : Qt::black;
else {
pressed = 1;
draw(e);
}
}
void mouseReleaseEvent(QMouseEvent *) { pressed = 0; }
void mouseMoveEvent(QMouseEvent *e) { draw(e); }
private:
void draw(QMouseEvent *e) {
if (pressed) {
QPainter painter(pixmap);
painter.setPen(color);
int x = e->pos().x() / 12;
int y = e->pos().y() / 12;
painter.drawPoint(x, y);
repaint();
}
}
QColor color;
QPixmap *pixmap;
bool pressed;
};

Related

How to create a click effect on a QLabel?

How i could create a click effect similar to push buttons on a QLabel?
QPixmap pixmap;
pixmap.load(":/files/hotkeys.png");
int w = 131;
int h = 71;
pixmap = pixmap.scaled(w, h, Qt::KeepAspectRatio);
// Label
ui.label->setGeometry(220, 220, w, h);
ui.label->setPixmap(pixmap);
// Button
QIcon icon(pixmap);
ui.toolButton->setIconSize(QSize(w, h));
ui.toolButton->setIcon(icon);
ui.toolButton->setStyleSheet("QToolButton { background-color: transparent }");
By click effect i mean like when you click on a push button containing a picture:
Ideally you'd just use a QToolButton or QPushButton, but if you must use a QLabel, you could do it by subclassing QLabel with a custom paintEvent() to give the desired effect, something like this:
class MyLabel : public QLabel
{
public:
MyLabel(const QPixmap & pm) : _isMouseDown(false) {setPixmap(pm);}
virtual void mousePressEvent( QMouseEvent * e) {_isMouseDown = true; update(); e->accept();}
virtual void mouseReleaseEvent(QMouseEvent * e) {_isMouseDown = false; update(); e->accept();}
virtual void paintEvent(QPaintEvent * e)
{
QPainter p(this);
const int offset = _isMouseDown ? 2 : 0;
p.drawPixmap(QPoint(offset, offset), *pixmap());
}
private:
bool _isMouseDown;
};

Cover QLabel with QPixmap in C++

My goal
This is an example of how I want the QPixmap to be contained inside the QLabel. The image keeps its aspect ratio and fills the given dimension. It will be clipped to fit.
Here is the CSS equivalent:
object-fit: cover;
Current code
This almost does what I want, but it stretches the image to cover the QLabel, so the aspect ratio isn't reserved.
QPixmap *img = new QPixmap("image.png");
ui->label->setPixmap(*img);
ui->label->setScaledContents(true);
delete img;
Can be implemented as QWidget subclass using QTransform.
class Label : public QWidget
{
Q_OBJECT
public:
explicit Label(QWidget *parent = nullptr);
void setPixmap(const QPixmap& image);
protected:
void paintEvent(QPaintEvent *event);
QPixmap mPixmap;
};
Label::Label(QWidget *parent) : QWidget(parent) {
}
void Label::setPixmap(const QPixmap &pixmap)
{
mPixmap = pixmap;
update();
}
void Label::paintEvent(QPaintEvent *event)
{
if (mPixmap.isNull()) {
return;
}
double width = this->width();
double height = this->height();
double pixmapWidth = mPixmap.width();
double pixmapHeight = mPixmap.height();
double scale = qMax(width / pixmapWidth, height / pixmapHeight);
QTransform transform;
transform.translate(width / 2, height / 2);
transform.scale(scale, scale);
transform.translate(-pixmapWidth / 2, -pixmapHeight / 2);
QPainter painter(this);
painter.setTransform(transform);
painter.drawPixmap(QPoint(0,0), mPixmap);
}

QPainter paints only part of the rectangle over QImage

I'm loading image into QImage from data in memory, I've managed successfully paint it with QPainter onto QWidget. Then I'm trying to change some pixels of this QImage to highlight rectangle area using rubber band. My problem is that I'm getting only part of the rectangle, and sometimes nothing at all (see picture below). Code .h:
class PaintWidget : public QWidget
{
Q_OBJECT
public:
using QWidget::QWidget;
void setSourceSample(Sample src) ;
void drawRectangle(OCRRectangle rect);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *ev) override;
void mouseMoveEvent(QMouseEvent *ev) override;
void mouseReleaseEvent(QMouseEvent *ev) override;
private:
QRectF target=QRectF(0, 0, 1000, 1000);
QImage p_source;
QPoint origin, end;
QRubberBand * rubberBand;
QPair<float, float> rsz_ratio;
void calcROI();
QPoint translateCoordinates(cv::Point point);
};
Code .cpp:
void PaintWidget::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.drawImage(target, p_source);
}
void PaintWidget::mousePressEvent(QMouseEvent *ev) {
origin=ev->pos();
rubberBand=new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void PaintWidget::mouseMoveEvent(QMouseEvent *ev) {
rubberBand->setGeometry(QRect(origin, ev->pos()).normalized());
}
void PaintWidget::mouseReleaseEvent(QMouseEvent *ev) {
end=ev->pos();
rubberBand->hide();
delete rubberBand;
calcROI();
}
void PaintWidget::calcROI() {
int p1x=min(end.x(),origin.x())*rsz_ratio.first;
int p2x=max(end.x(),origin.x())*rsz_ratio.first;
int p1y=min(end.y(), origin.y())*rsz_ratio.second;
int p2y=max(end.y(), origin.y())*rsz_ratio.second;
drawRectangle(OCRRectangle(cv::Point(p1x, p1y),cv::Point(p2x, p2y)));
}
void PaintWidget::setSourceSample(Sample src) {
QImage inter=src.toQImage();
p_source=inter.convertToFormat(QImage::Format::Format_RGB32);
rsz_ratio.first=(float)src.size().width/(float)(target.width());
rsz_ratio.second=(float)src.size().height/(float)target.height();
}
QPoint PaintWidget::translateCoordinates(cv::Point point) {
QPoint qtpoint;
qtpoint.setX(point.x);
qtpoint.setY(point.y);
return qtpoint;
}
void PaintWidget::drawRectangle(OCRRectangle rect) {
QPoint p1(translateCoordinates(rect.p1)), p2(translateCoordinates(rect.p2));
for (int i=p1.x(); i<=p2.x(); i++) {
p_source.setPixelColor(i,p1.y(),QColor(255,0,0));
p_source.setPixelColor(i, p2.y(), QColor(255,0,0));
}
for (int i=p1.y(); i<=p2.y(); i++) {
p_source.setPixelColor(p1.x(),i,QColor(255,0,0));
p_source.setPixelColor(p2.x(),i,QColor(255,0,0));
}
}
UPDATE: it seems that the problem lies within rescaling of original image. When I'm drawing filled red rectangle it goes absolutely fine, my guess is that rescaling 1px border rectangle is just erasing borders. But setting thicker borders (up to 5 pixels) doesn't provide me with solution.
Making thicker borders solved this problem.

how to draw lines able to change color in Qt?

there is a question disturbing me, maybe it's just a simple question for you guys.
i want to draw tow lines in my Qt widget, when the QComboBox cbx is set at the item 1, the first line is set to red, second black, on the contrary, 2, fist black, second red.
i used *paintEvent* to draw my lines. (actually, if you want to draw something, you must draw in paintEvent function) But i dont know how to change the line's color based on the QComboBox item, follows are my codes.
#include "changecolor.h"
#include "ui_changecolor.h"
QString st;
QPainter painter;
ChangeColor::ChangeColor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ChangeColor)
{
ui->setupUi(this);
setFixedSize(2000, 1000);
QComboBox * cbx = new QComboBox(this);
cbx->setSizeAdjustPolicy(QComboBox::AdjustToContents);
cbx->addItem("1", Qt::DisplayRole);
cbx->addItem("2", Qt::DisplayRole);
st = cbx->currentText();
connect(cbx, SIGNAL(currentTextChanged(QString)), this, SIGNAL(changeColorSlot(st, painter)));
}
void ChangeColor::paintEvent(QPaintEvent*)
{
// QPainter painter(this);
// painter.setPen(Qt::black);
// painter.drawLine(QPoint(100,100), QPoint(1100,100));
// painter.drawLine(QPoint(100,100), QPoint(100,600));
// changeColorSlot(painter, );
changeColorSlot(st, painter);
}
void ChangeColor::changeColorSlot(QString& st, QPainter& painter)
{
// QPainter painter(this);
if(st == tr("1"))
{
painter.setPen(Qt::black);
painter.drawLine(QPoint(100,100),QPoint(1100,100));
painter.setPen(Qt::red);
painter.drawLine(QPoint(100,100),QPoint(100,600));
}
else if(st == tr("2"))
{
painter.setPen(Qt::red);
painter.drawLine(QPoint(100,100),QPoint(1100,100));
painter.setPen(Qt::black);
painter.drawLine(QPoint(100,100),QPoint(100,600));
}
update();
}
ChangeColor::~ChangeColor()
{
delete ui;
}
these codes denote my painful coding life, i mean i have tried many times, but there is no right result. thank you guys.
you have to set the logic that controls the state(the color of the lines) outside the widget:
here an example with the relevant parts
.h:
#include
namespace Ui
{
class ChangeColor;
}
class ChangeColor : public QWidget
{
Q_OBJECT
public:
explicit ChangeColor(QWidget *parent = nullptr);
void paintEvent(QPaintEvent* pe);
~ChangeColor();
public slots:
void setColorState(int state);
private:
Ui::ChangeColor* ui{nullptr};
int state{};
};
.cpp
...
void ChangeColor::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter paint(this);
QColor RED_COLOR{255, 0, 0};
QColor BLACK_COLOR{0, 0, 0};
if(state == 0)
{
paint.setPen(QPen(RED_COLOR));
paint.setBrush(RED_COLOR);
}
else
{
paint.setPen(QPen(BLACK_COLOR));
paint.setBrush(BLACK_COLOR);
}
paint.drawLine(0, 0, width(), height());
}
void ChangeColor::setColorState(int state)
{
this->state = state;
update();
}
I suppose you are looking for this feature:
QComboBox* cb = ...;
QAbstractItemView* view = cb->view();
view->setAlternatingRowColors(true);

How to keep the size and position of QGraphicsItem when scaling the view?

I have some QGraphicsItems in the QGraphicsScene which should keep the same size and position when scaling. I've tried QGraphicsItem::ItemIgnoresTransformations but it turns out that the items get wrong positions. Below is a sample code:
I have subclassed QGraphicsView like this:
class Graphics : public QGraphicsView
{
public:
Graphics();
QGraphicsScene *scene;
QGraphicsRectItem *rect;
QGraphicsRectItem *rect2;
protected:
void wheelEvent(QWheelEvent *event);
};
And in its constructor:
Graphics::Graphics()
{
scene = new QGraphicsScene;
rect = new QGraphicsRectItem(100,100,50,50);
rect2 = new QGraphicsRectItem(-100,-100,50,50);
scene->addLine(0,200,200,0);
rect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
scene->addItem(rect);
scene->addItem(rect2);
setScene(scene);
scene->addRect(scene->itemsBoundingRect());
}
The wheelEvent virtual function:
void Graphics::wheelEvent(QWheelEvent *event)
{
if(event->delta() < 0)
scale(1.0/2.0, 1.0/2.0);
else
scale(2, 2);
scene->addRect(scene->itemsBoundingRect());
qDebug() << rect->transform();
qDebug() << rect->boundingRect();
qDebug() << rect2->transform();
qDebug() << rect2->boundingRect();
}
orginal view looks like this:
1
take the line as road and rect aside as a symbol. When zoomed out, the rect maintain its size but jumps out of the scene:
2
which should be that topleft of rect to middle of line. I'm also confused with debug info showing that the boundingRect and transform stays the same, which seems that nothing has changed! What causes the problem and is there any way to solve it? Could someone help? Thank you!
Sorry for delay, now I've solved the problem myself.
I found QGraphicsItem::ItemIgnoresTransformations only works when the point you want stick to is at (0,0) in item's coordinate. You need also update boundingRect manually in this way. Nevertheless, the best solution I've found is subclass QGraphicsItem and set matrix in paint() according to world matrix. Below is my code .
QMatrix stableMatrix(const QMatrix &matrix, const QPointF &p)
{
QMatrix newMatrix = matrix;
qreal scaleX, scaleY;
scaleX = newMatrix.m11();
scaleY = newMatrix.m22();
newMatrix.scale(1.0/scaleX, 1.0/scaleY);
qreal offsetX, offsetY;
offsetX = p.x()*(scaleX-1.0);
offsetY = p.y()*(scaleY-1.0);
newMatrix.translate(offsetX, offsetY);
return newMatrix;
}
And the paint function:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QPointF p(left, top);
painter->setMatrix(stableMatrix(painter->worldMatrix(), p));
painter->drawRect(left, top, width, height);
}
The second argument of stableMatrix is sticked point, in my sample code it's top-left of the item. You can change it to your preference. It works really fine!
Hope this post help :)
The solution to this is even simpler.
QGraphicsItem::ItemIgnoresTransformations
The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). [...]
And that's the key! Item ignores all transformations, but is still bound to its parent. So you need two items: a parent item that will keep the relative position (without any flags set) and a child item that will do the drawing (with QGraphicsItem::ItemIgnoresTransformations flag set) at parent's (0,0) point.
Here is some working code of a crosshair that have constant size and rotation, while keeping the relative position to its parent:
#include <QGraphicsItem>
#include <QPainter>
class CrossHair : public QGraphicsItem
{
private:
class CrossHairImpl : public QGraphicsItem
{
public:
CrossHairImpl (qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_len(len)
{
setFlag(QGraphicsItem::ItemIgnoresTransformations);
}
QRectF boundingRect (void) const override
{
return QRectF(-m_len, -m_len, m_len*2, m_len*2);
}
void paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
painter->setPen(QPen(Qt::red, 1));
painter->drawLine(0, -m_len, 0, m_len);
painter->drawLine(-m_len, 0, m_len, 0);
}
private:
qreal m_len;
};
public:
CrossHair (qreal x, qreal y, qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_impl(len, this) // <-- IMPORTANT!!!
{
setPos(x, y);
}
QRectF boundingRect (void) const override
{
return QRectF();
}
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override
{
// empty
}
private:
CrossHairImpl m_impl;
};