I have made a simple texture of an outlined box and have the following snippet of code which draws a checkerboard pattern:
scene.setSceneRect(0, 0, 1000, 1000);
ui->g_view->setScene(&scene);
QPixmap texture("block.png");
QBrush brush(texture);
int count = 0;
for(int x=0; x<1000; x += 50) {
for(int y=0; y<1000; y += 50) {
if (count % 2 == 0) {
scene.addRect(x, y, 50, 50, Qt::NoPen, brush);
}
count++;
}
// Offset rows by 1
count++;
}
This works fine:
However, when I modify the code so that the boxes are drawn "off grid":
scene.addRect(x + 5, y + 5, 50, 50, Qt::NoPen, brush);
The following output is produced:
What I expected to happen was that each call to addRect would draw the texture starting from the top corner each time.
However, for some reason qt translates the texture using the location that it is being drawn too, almost like the texture is infinitely tiled in the background and addRect is just cutting away a window.
How can I make drawRect behave as I expected, i.e. no matter what the values of x and y are the texture is always drawn from the top left corner.
Edit: block.png
I solved this issue by instead using the addPixmap method.
//scene.addRect(x + 5, y + 5, 50, 50, Qt::NoPen, brush);
QGraphicsPixmapItem *pix_map = scene.addPixmap(texture);
pix_map->setPos(x + 5, y + 5);
Related
I'm trying to draw a 3-pixel large point with QPainter. But the following code instead draws a horizontal line with width of 3 pixels.
#include <QPainter>
#include <QImage>
int main()
{
const int w=1000, h=1000;
QImage img(w, h, QImage::Format_RGBX8888);
{
QPainter p(&img);
p.fillRect(0,0,w,h,Qt::black);
p.scale(w,h);
p.setPen(QPen(Qt::red, 3./w, Qt::SolidLine, Qt::RoundCap));
p.drawPoint(QPointF(0.1,0.1));
}
img.save("test.png");
}
Here's the top left corner of the resulting image:
I am expecting to get a point which is red circle, or at least a square — but instead I get this line segment. If I comment out p.scale(w,h) and draw the point with width 3 (instead of 3./w) at position (100,100), then I get a small mostly symmetric 3-pixel in height and width point.
What's going on? Why do I get a line segment instead of point as expected? And how to fix it, without resorting to drawing an ellipse or to avoiding QPainter::scale?
I'm using Qt 5.10.0 on Linux x86 with g++ 5.5.0. The same happens on Qt 5.5.1.
It appears that QPaintEngineEx::drawPoints renders points as line segments of length 1/63.. See the following code from qtbase/src/gui/painting/qpaintengineex.cpp in the Qt sources:
void QPaintEngineEx::drawPoints(const QPointF *points, int pointCount)
{
QPen pen = state()->pen;
if (pen.capStyle() == Qt::FlatCap)
pen.setCapStyle(Qt::SquareCap);
if (pen.brush().isOpaque()) {
while (pointCount > 0) {
int count = qMin(pointCount, 16);
qreal pts[64];
int oset = -1;
for (int i=0; i<count; ++i) {
pts[++oset] = points[i].x();
pts[++oset] = points[i].y();
pts[++oset] = points[i].x() + 1/63.;
pts[++oset] = points[i].y();
}
QVectorPath path(pts, count * 2, qpaintengineex_line_types_16, QVectorPath::LinesHint);
stroke(path, pen);
pointCount -= 16;
points += 16;
}
} else {
for (int i=0; i<pointCount; ++i) {
qreal pts[] = { points[i].x(), points[i].y(), points[i].x() + qreal(1/63.), points[i].y() };
QVectorPath path(pts, 2, 0);
stroke(path, pen);
}
}
}
Notice the pts[++oset] = points[i].x() + 1/63.; line in the opaque brush branch. This is the second vertex of the path — shifted with respect to the desired position of the point.
This explains why the line extends to the right of the position requested and why it depends on the scale. So, it seems the code in the OP isn't wrong for an ideal QPainter implementation, but just has come across a Qt bug (be it in the implementation of the method or in its documentation).
So the conclusion: one has to work around this problem by either using different scale, or drawing ellipses, or drawing line segments with much smaller lengths than what QPainter::drawPoints does.
I've reported this as QTBUG-70409.
Though I have not been able to pin point why exactly the problem occurs, I have got relatively close to the solution. The problem lies with scaling. I did a lots of trial and error with different scaling and width of point with the below code.
const int w=500, h=500;
const int scale = 100;
float xPos = 250;
float yPos = 250;
float widthF = 5;
QImage img(w, h, QImage::Format_RGBX8888);
{
QPainter p(&img);
p.setRenderHints(QPainter::Antialiasing);
p.fillRect(0,0,w,h,Qt::black);
p.scale(scale, scale);
p.setPen(QPen(Qt::red, widthF/(scale), Qt::SolidLine, Qt::RoundCap));
p.drawPoint(QPointF(xPos/scale, yPos/scale));
}
img.save("test.png");
The above code produces the image
My observations are
1) Due to high scaling, the point (which is just 3 pixel wide) is not able to scale proportionally at lower width (width of point), if you set width as something like 30 the round shape is visible.
2) If you want to keep the width of point low then you have to decrease the scaling.
Sadly I can not explain why at high scaling it is not expanding proportionally.
I am working through Greg Borenstein's book "Making Things See" and have figured out how to create a cursor that tracks the movement of the closest thing to the Kinect. Right now the cursor is a simple red ball. So I am able to track my finger over
image(kinect.getVideoImage(), 0, 0)
I have also created buttons that apply a filter to the video image when I put the cursor ball in the area of the button.
Its kind of fun but the novelty has run out so now I want to turn the cursor ball into an animated graphic using particles or something fun like that. This animated graphic should still track my finger and be drawn over the video image.
When I try to write this, the graphic comes out wrong because the video image keeps redrawing over the particles so it doesn't look right.
I was thinking I could use the capture() method to draw the video image under the graphics but I can't figure out how to do it with video from the Kinect.
Does anyone have any ideas on how I could do this? Any help would be greatly appreciated.
A sample of my kinect tracker and filter button is below. If you copy and paste it into processing and have a kinect plugged in it should run. my apologies for the code's lack of eloquence. I'm still learning how to make beautiful code.
Instead of filters, I would like to trigger a new graphic for the red ball maybe apply particles or something.
//my kinect tracker
import org.openkinect.freenect.*;
import org.openkinect.processing.*;
Kinect kinect;
boolean ir = true;
boolean colorDepth = true;
boolean mirror = true;
float closestValue;
float closestX;
float closestY;
// create arrays to store recent closest x- and y-coordinates for averaging
int[] recentXValues = new int[3];
int[] recentYValues = new int[3];
// keep track of which is the current value in the array to be changed
int currentIndex = 0;
float circleButtonX, circleButtonY; // position of circle button
float circleButtonSize; // diameter of circle button
color circleButtonColor; // color of circle button
void setup() {
size(640, 480, P3D);
kinect = new Kinect(this);
kinect.initDepth();
kinect.initVideo();
//kinect.enableIR(ir);
kinect.enableMirror(mirror);
kinect.enableColorDepth(colorDepth);
circleButtonColor = color(0, 0, 255);
}
void draw() {
closestValue = 1700;
int[] depthValues = kinect.getRawDepth();
for(int y = 0; y < 480; y++) {
for(int x = 0; x < 640; x++) {
int i = x + y * 640;
int currentDepthValue = depthValues[i];
if(currentDepthValue > 0 && currentDepthValue < closestValue) {
//save its value
closestValue = currentDepthValue;
recentXValues[currentIndex] = x;
recentYValues[currentIndex] = y;
}
}
}
currentIndex++;
if(currentIndex > 2) {
currentIndex = 0;
}
// closetX and ClosestY become a running average
// with currentX and CurrentY
closestX = (recentXValues[0] + recentXValues[1] + recentXValues[2]) / 3;
closestY = (recentYValues[0] + recentYValues[1] + recentYValues[2]) / 3;
//draw the depth image on the screen
image(kinect.getVideoImage(), 0, 0);
fill(0, 0, 250);
ellipse(75, 75, 100, 100);
ellipse(200, 75, 100, 100);
ellipse(75, 200, 100, 100);
rect(540, 25, 75, 100);
//buttons
fill(255,0,0);
textSize(24);
text("Invert", 40, 85);
text("Blur", 50, 210);
textSize(18);
text("Threshold", 155, 85);
text("Stop", 560, 75);
ellipse(closestX, closestY, 25, 25);
if (closestX > 25 && closestX < 125 && closestY > 25 && closestY < 125) {
filter(INVERT);
};
if (closestX > 150 && closestX < 250 && closestY > 25 && closestY < 125) {
filter(THRESHOLD);
};
if (closestX > 25 && closestX < 125 && closestY > 150 && closestY < 250) {
filter(BLUR, 6);
};
if (closestX > 540 && closestX < 615 && closestY > 25 && closestY < 100) {
noLoop();
loop();
background(0);
};
I believe you're asking how to create a particle trail in Processing. This doesn't really have anything to do with kinect- a simple call to background() would also draw over any of your particles. So you currently have something like this:
void setup(){
size(500, 500);
}
void draw(){
background(0);
ellipse(mouseX, mouseY, 10, 10);
}
You're drawing something to the screen (in your code, a red ball, in my code, an ellipse), but it's cleared away (in your code, the kinect video, in my code, a call to background(0)). You're asking how to make it so the ellipse stays on the screen even after you draw the background.
The answer is this: You need to store the positions of your trail in a data structure and then redraw them every frame.
A simple way to do that is to create an ArrayList of PVector instances. To add a particle to the trail, you just add a PVector to the ArrayList. And then you just iterate over the trail and draw every PVector point.
ArrayList<PVector> trail = new ArrayList<PVector>();
void setup(){
size(500, 500);
}
void draw(){
background(0);
//add to the trail
trail.add(new PVector(mouseX, mouseY));
//draw the trail
for(PVector p : trail){
ellipse(p.x, p.y, 10, 10);
}
}
However, this trail will constantly grow, and you'll eventually run out of memory to hold every point. So to keep your trail from doing that, you need to limit its size:
ArrayList<PVector> trail = new ArrayList<PVector>();
void setup(){
size(500, 500);
}
void draw(){
background(0);
//add to the trail
trail.add(new PVector(mouseX, mouseY));
if(trail.size() > 10){
//trail is too long, remove the oldest point
trail.remove(0);
}
//draw the trail
for(PVector p : trail){
ellipse(p.x, p.y, 10, 10);
}
}
From here you can do fancier things: give each point in your trail a momentum or a color, or fade it out, or decrease its width. But the basics are this: you have to store the particles in a data structure, update that data structure to add or remove particles, and then iterate over that data structure to draw the trail.
I am making a small game in C++11 with Qt. However, I am having some issues with scaling.
The background of my map is an image. Each pixel of that image represents a tile, on which a protagonist can walk and enemies/healthpacks can be.
To set the size of a tile, I calculat the maximum amount like so (where imageRows & imageCols is amount of pixels on x- and y-axis of the background image):
QRect rec = QApplication::desktop()->screenGeometry();
int maxRows = rec.height() / imageRows;
int maxCols = rec.width() / imageCols;
if(maxRows < maxCols){
pixSize = maxRows;
} else{
pixSize = maxCols;
}
Now that I have the size of a tile, I add the background-image to the scene (in GameScene ctor, extends from QGraphicsScene):
auto background = new QGraphicsPixmapItem();
background->setPixmap(QPixmap(":/images/map.png").scaledToWidth(imageCols * pixSize));
this->addItem(background);
Then for adding enemies (they extend from a QGraphicsPixMapItem):
Enemy *enemy = new Enemy();
enemy->setPixmap(QPixmap(":/images/enemy.png").scaledToWidth(pixSize));
scene->addItem(enemy);
This all works fine, except that on large maps images get scaled once (to a height of lets say 2 pixels), and when zooming in on that item it does not get more clear, but stays a big pixel. Here is an example: the left one is on a small map where pixSize is pretty big, the second one has a pixSize of pretty small.
So how should I solve this? In general having a pixSize based on the screen resolution is not really useful, since the QGrapicsScene is resized to fit the QGraphicsView it is in, so in the end the view still determines how big the pixels show on the screen.
MyGraphicsView w;
w.setScene(gameScene);
w.fitInView(gameScene->sceneRect(), Qt::KeepAspectRatio);
I think you might want to look at the chip example from Qt (link to Qt5 but also works for Qt4).
The thing that might help you is in the chip.cpp file:
in the paint method:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
where painter is simply a QPainter and option is of type QStyleOptionGraphicsItem. This quantity gives you back a measure of the current zoom level of your QGraphicsView and thus as in the example you can adjust what is being drawn at which level, e.g.
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
[...]
if (lod >= 2) {
QFont font("Times", 10);
font.setStyleStrategy(QFont::ForceOutline);
painter->setFont(font);
painter->save();
painter->scale(0.1, 0.1);
painter->drawText(170, 180, QString("Model: VSC-2000 (Very Small Chip) at %1x%2").arg(x).arg(y));
painter->drawText(170, 200, QString("Serial number: DLWR-WEER-123L-ZZ33-SDSJ"));
painter->drawText(170, 220, QString("Manufacturer: Chip Manufacturer"));
painter->restore();
}
Does this help?
I would like to rotate the text 45 degrees?
QFont font;
font.setPixelSize(12);
//grid
for(int i = 0; i < 10; i++){
painter->drawLine(100, 100 + i * 800/9, 900, 100 + i * 800/9);
str = QString::number((double)9 - i, 'd', 1);
painter->setFont(font);
painter->drawText(75, 100 + i * 800/9 - 6, 40, 40, 1, str);
}
Insert painter->rotate(45); before painter->drawText(75, 100 + i * 800/9 - 6, 40, 40, 1, str); and painter->rotate(-45); after (to restore the rotation angle of the coordinate system):
painter->rotate(45);
painter->drawText(75, 100 + i * 800/9 - 6, 40, 40, 1, str);
painter->rotate(-45);
Depending on if you mean 45 degrees clockwise or anti-clockwise you may need to negate the rotation angles.
After you rotate the coordinate system, everything you paint will be painted rotated until you restore the painter. A convenient way of saving and restoring the state of the painter is using QPainter::save() and QPainter::restore().
painter->save(); // saves current painter state
// painter->rotate(45); clockwise rotation
// painter->rotate(-45); counter clockwise rotation
painter->restore(); // restores painter state
In order to rotate your text (and any other drawable object) drawn by painter just call
painter->rotate(yourAngle);
before
painter->drawText();
If you wish to return to previous state call rotate again.
painter->rotate(-yourAngle);
Why making such a simple task so complicated?!!!
void CustomLabel::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
painter.translate(m_rect.center());
painter.rotate(m_rotation);
painter.translate(-m_rect.center());
painter.drawText(m_rect, Qt::AlignHCenter | Qt::AlignVCenter, m_text);
QWidget::paintEvent(e);
}
any time the container of CustomLabel changes it size you can set the m_rect or use the this->rect() itself.
I want to draw multiple filled ellipses on/in some panel. Drawing single one isnt problem, i am using:
Color aColor = Color::FromArgb( 255, 0, 0 );
SolidBrush^ aBrush = gcnew SolidBrush(aColor);
Rectangle rect = Rectangle(x, y, 10, 10);
e->Graphics->FillEllipse(aBrush, rect);
It draws red ellipse bordered by rectangle, and fills it with red color. (assuming i will give x and y). The problem i met, is when I want to draw multiple ellipses like that, in RANDOM places. So i need to pass random x and y (using rand() % somenumber) but i am not sure, how can i pass these variables into the panel1_paint function and draw them when both numbers are randomized. Also, ofc i dont want the last ellipse to disappear when drawing new one. The only way is using global variables?
Any ideas?
Well, i tried as suggested, to use loop inside panel and i got that:
for(int i=0; i<ile_przeszkod; i++){
int x = rand() % 690; int y = rand() % 690;
Color aColor = Color::FromArgb( 255, 0, 0 );
SolidBrush^ aBrush = gcnew SolidBrush(aColor);
Rectangle rect = Rectangle(x, y, 10, 10);
e->Graphics->FillEllipse(aBrush, rect);
MessageBox::Show("x: "+x+ " y: " +y);
}
ile_przeszkod means how many of them i want to be drawn, and message box showes me what numbers it randomized so i am sure ellipses dont overlap. The problem is, after "invalidating" panel1 i see only 1 ellipse. :/ What should i do to see both of them?
all the x, y coordinates are random , so they don't depend on some other deciding procedure, So that need not to be passed to panel1_paint rather you can run a lpop and generate random number to use them as your x, y coordinates.