How to efficiently create a large amount of QGraphicsItem? - qt

I am developing a ground control station for a small drone, where I am trying to add a function to load waypoints from a file.
Each waypoint is a QGraphicsItem on the QGraphicsScene.
However, when there are more than 100 points in the file, the creation takes more than 2 seconds...
Is there any way to do this faster?

The 40K Chips example shows off the population of a scene with a large number of elements.
http://doc.qt.io/qt-5/qtwidgets-graphicsview-chip-mainwindow-cpp.html
void MainWindow::populateScene()
{
scene = new QGraphicsScene;
QImage image(":/qt4logo.png");
// Populate scene
int xx = 0;
int nitems = 0;
for (int i = -11000; i < 11000; i += 110) {
++xx;
int yy = 0;
for (int j = -7000; j < 7000; j += 70) {
++yy;
qreal x = (i + 11000) / 22000.0;
qreal y = (j + 7000) / 14000.0;
QColor color(image.pixel(int(image.width() * x), int(image.height() * y)));
QGraphicsItem *item = new Chip(color, xx, yy);
item->setPos(QPointF(i, j));
scene->addItem(item);
++nitems;
}
}
}
If you have a lot of initialization code going on in here, then it may take a long time. You may want to look into subclassing your QGraphicsItem if it is slow loading. The level of detail argument in the custom item here (see chip.cpp) is pretty cool how it handles everything.
Hope that helps.

Related

How to apply an effect to an image using the mouse coordinates?

I coded a program on Processing where all the pixels on the screen are scrambled, but around the cursor. The code works by replacing the pixels with a random pixel between 0 and the pixel the loop is currently on. To find that pixel, I used the code (y*width+x)-1. This code, however, is taking pixels from the entire screen. I want the code to instead take the pixels from a 40m square around the mouse coordinates. How can I do this?
import processing.video.*;
Capture video;
void setup() {
size(640, 480);
video = new Capture(this, 640, 480);
video.start();
}
void draw() {
loadPixels();
if (video.available()){
video.read();
video.loadPixels();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y*width+x] = video.pixels[y*video.width+(width-x-1)];
// the code should only be applied 20 pixels around the mouse
if (dist(mouseX, mouseY, x, y) < 20){
int d = int(random(0, y*width+x-1));
pixels[y*width+x] = video.pixels[d];
}
}
}
}
updatePixels();
}
You don't need to iterate through all the pixels to only change a few.
Luckily your sketch is the same size as the webcam feed, so you're on the right track using the x + (y + width) arithmetic to convert from a 2D array index to the 1D pixels[] index. Remember that you're sampling from a 1D array currently (random 0, coords). Even if you upate the start/end index that's still a range that will span a few full image rows which means pixels to the left and right of the effect selection. I recommend picking the random x, y indices in 2D, then converting these random values to 1D (as opposed to a single index from the 1D array).
Here's what I mean:
import processing.video.*;
Capture video;
void setup() {
size(640, 480);
video = new Capture(this, 640, 480);
video.start();
}
void draw() {
loadPixels();
if (video.available()) {
video.read();
video.loadPixels();
//for (int y = 0; y < height; y++) {
// for (int x = 0; x < width; x++) {
// pixels[y*width+x] = video.pixels[y*video.width+(width-x-1)];
// // the code should only be applied 20 pixels around the mouse
// if (dist(mouseX, mouseY, x, y) < 20) {
// int d = int(random(0, y*width+x-1));
// pixels[y*width+x] = video.pixels[d];
// }
// }
//}
// mouse x, y shorthand
int mx = mouseX;
int my = mouseY;
// random pixels effect size
int size = 40;
// half of size
int hsize = size / 2;
// 2D pixel coordinates of the effect's bounding box
int minX = mx - hsize;
int maxX = mx + hsize;
int minY = my - hsize;
int maxY = my + hsize;
// apply the effect only where the bounding can be applied
// e.g. avoid a border (of hsize) around edges of the image
if (mx >= hsize && mx < width - hsize &&
my >= hsize && my < height - hsize) {
for(int y = minY; y < maxY; y++){
for(int x = minX; x < maxX; x++){
// pick random x,y coordinates to sample a pixel from
int rx = (int)random(minX, maxX);
int ry = (int)random(minY, maxY);
// convert the 2D random coordinates to a 1D pixel[] index
int ri = rx + (ry * width);
// replace current pixel with randomly sampled pixel (within effect bbox)
pixels[x + (y * width)] = video.pixels[ri];
}
}
}
}
updatePixels();
}
(Note that the above isn't tested, but hopefully the point gets across)

how to make a map in xna 4 with matrix from text file

I am trying to make a map by reading a text file line by line (because i cant find how to do that word by word). So I make a map00.txt that looks like "33000000111" (every number is one row, first 2 rows are number of columns and rows so matrix that I load it into looks like
000
000
111
). Now I am supposed to draw 3 tiles at the bottom (1=draw tile). I do so by drawing tile at its position in matrix * window height(width) / matrix number of rows(columns).
PROBLEM: i cant get the right parameters for current window width and height.
Code for loading tiles:
public int[,] LoadMatrix(string path)
{
StreamReader sr = new StreamReader(path);
int[,] a = new int[int.Parse(sr.ReadLine().ToString()),
int.Parse(sr.ReadLine().ToString())];
for(int i = 0; i < a.GetLength(0); i++)
for (int j = 0; j < a.GetLength(1); j++)
{ a[i, j] =int.Parse(sr.ReadLine().ToString()); }
sr.Close();
return a;
}
Code for drawing tiles:
public void DrawTiles(SpriteBatch sp, GraphicsDeviceManager gdm)
{
for(int i = 0; i < matrix.GetLength(0); i++)
for(int j = 0; j < matrix.GetLength(1); j++)
{
if (i == 1)
{
sp.Draw(tile,
new Rectangle(j * (gdm.PreferredBackBufferWidth / 3),//matrix.GetLength(1),
i * (gdm.PreferredBackBufferWidth / 3),//matrix.GetLength(0),
gdm.PreferredBackBufferWidth / matrix.GetLength(1),
gdm.PreferredBackBufferHeight / matrix.GetLength(0)),
Color.White);
}
}
}
but the result is that they are drawn about 40 pixels above the bottom of the screen!
and i tried with GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height(Width) but i get the same result. And when i put calculated numbers that should (in theory) be width/columns and heigth/rows i get what i want. So any suggestions would be VERY appriciated because i am stuck at this for a long time on google and Stack Overflow.
Here is a reworked version of your Draw code, which should work:
public void DrawTiles(SpriteBatch sp, GraphicsDeviceManager gdm)
{
//You would typically pre-compute these in a load function
int tileWidth = gdm.PreferredBackBufferWidth / matrix.GetLength(0);
int tileHeight = gdm.PreferredBackBufferWidth / matrix.GetLength(1);
//Loop through all tiles
for(int i = 0; i < matrix.GetLength(0); i++)
{
for(int j = 0; j < matrix.GetLength(1); j++)
{
//If tile value is not 0
if (matrix[i,j] != 0)
{
sp.Draw(tile, new Rectangle(i * tileWidth, j * tileHeight, tileWidth, tileHeight), Color.White);
}
}
}
}

How do I optimize displaying a large number of quads in OpenGL?

I am trying to display a mathematical surface f(x,y) defined on a XY regular mesh using OpenGL and C++ in an effective manner:
struct XYRegularSurface {
double x0, y0;
double dx, dy;
int nx, ny;
XYRegularSurface(int nx_, int ny_) : nx(nx_), ny(ny_) {
z = new float[nx*ny];
}
~XYRegularSurface() {
delete [] z;
}
float& operator()(int ix, int iy) {
return z[ix*ny + iy];
}
float x(int ix, int iy) {
return x0 + ix*dx;
}
float y(int ix, int iy) {
return y0 + iy*dy;
}
float zmin();
float zmax();
float* z;
};
Here is my OpenGL paint code so far:
void color(QColor & col) {
float r = col.red()/255.0f;
float g = col.green()/255.0f;
float b = col.blue()/255.0f;
glColor3f(r,g,b);
}
void paintGL_XYRegularSurface(XYRegularSurface &surface, float zmin, float zmax) {
float x, y, z;
QColor col;
glBegin(GL_QUADS);
for(int ix = 0; ix < surface.nx - 1; ix++) {
for(int iy = 0; iy < surface.ny - 1; iy++) {
x = surface.x(ix,iy);
y = surface.y(ix,iy);
z = surface(ix,iy);
col = rainbow(zmin, zmax, z);color(col);
glVertex3f(x, y, z);
x = surface.x(ix + 1, iy);
y = surface.y(ix + 1, iy);
z = surface(ix + 1,iy);
col = rainbow(zmin, zmax, z);color(col);
glVertex3f(x, y, z);
x = surface.x(ix + 1, iy + 1);
y = surface.y(ix + 1, iy + 1);
z = surface(ix + 1,iy + 1);
col = rainbow(zmin, zmax, z);color(col);
glVertex3f(x, y, z);
x = surface.x(ix, iy + 1);
y = surface.y(ix, iy + 1);
z = surface(ix,iy + 1);
col = rainbow(zmin, zmax, z);color(col);
glVertex3f(x, y, z);
}
}
glEnd();
}
The problem is that this is slow, nx=ny=1000 and fps ~= 1.
How do I optimize this to be faster?
EDIT: following your suggestion (thanks!) regarding VBO
I added:
float* XYRegularSurface::xyz() {
float* data = new float[3*nx*ny];
long i = 0;
for(int ix = 0; ix < nx; ix++) {
for(int iy = 0; iy < ny; iy++) {
data[i++] = x(ix,iy);
data[i++] = y(ix,iy);
data[i] = z[i]; i++;
}
}
return data;
}
I think I understand how I can create a VBO, initialize it to xyz() and send it to the GPU in one go, but how do I use the VBO when drawing. I understand that this can either be done in the vertex shader or by glDrawElements? I assume the latter is easier? If so: I do not see any QUAD mode in the documentation for glDrawElements!?
Edit2:
So I can loop trough all nx*ny quads and draw each by:
GL_UNSIGNED_INT indices[4];
// ... set indices
glDrawElements(GL_QUADS, 1, GL_UNSIGNED_INT, indices);
?
1/. Use display lists, to cache GL commands - avoiding recalculation of the vertices and the expensive per-vertex call overhead. If the data is updated, you need to look at client-side vertex arrays (not to be confused with VAOs). Now ignore this option...
2/. Use vertex buffer objects. Available as of GL 1.5.
Since you need VBOs for core profile anyway (i.e., modern GL), you can at least get to grips with this first.
Well, you've asked a rather open ended question. I'd suggest using modern (3.0+) OpenGL for everything. The point of just about any new OpenGL feature is to provide a faster way to do things. Like everyone else is suggesting, use array (vertex) buffer objects and vertex array objects. Use an element array (index) buffer object too. Most GPUs have a 'post-transform cache', which stores the last few transformed vertices, but this can only be used when you call the glDraw*Elements family of functions. I also suggest you store a flat mesh in your VBO, where y=0 for each vertex. Sample the y from a heightmap texture in your vertex shader. If you do this, whenever the surface changes you will only need to update the heightmap texture, which is easier than updating the VBO. Use one of the floating point or integer texture formats for a heightmap, so you aren't restricted to having your values be between 0 and 1.
If so: I do not see any QUAD mode in the documentation for glDrawElements!?
If you want quads make sure you're looking at the GL 2.1-era docs, not the new stuff.

Scaling in QGraphicsItemAnimation

I was looking a proper way to implment a "ScaleAnimation". My purpose is to animate a QImage:
timeline = new QTimeLine(time);
timeline->setFrameRange(0, 100);
animation = new QGraphicsItemAnimation;
QRectF rect = item->boundingRect();
int h = rect.bottom() - rect.top();
int w = rect.right() - rect.left();
animation->setItem(item);
animation->setTimeLine(timeline);
for (int i = 0; i < 100; i++) {
int x = w + (int)((float)w*(float)(i/100.0));
qreal xx = (qreal)(x)/(qreal)w;
int y = (h) + (int)((float)h*(float)(i/100.0));
qreal yy = (qreal)(y)/(qreal)h;
animation->setScaleAt(i/100, xx, yy);
}
it seems to work but the origin of the animation seems to be (0, 0). Is there any way to apply the animation in (w/2, h/2)? Is there a better and more efficent (or correct) way to rewrite the animation? I am quit newbee in the Qt world.
thank you for your patience.
If you're using a QGraphicsPixmapItem, simply set its offset to the midpoint, and move it by the same amount to counter the effects of setting the offset.
const QSizeF size = item->boundingRect().size()*0.5;
item->setOffset(size.width(), size.height());
item->moveBy(size.width(), size.height());

Tournament Brackets algorithm

I need to create an asp.net page that auto generate a brackets tournament tennis style.
Regarding the managing of match in database, it's not a problem.
The problem is the dynamic graphics creation of brackets.
The user will be able to create tournament by 2-4...32 players.
And i don't know ho to create the graphics bracket in html or gdi...
Using Silverlight, and a Grid, You can produce something like this:
To do it, define a regular UserControl containing a Grid. (This is the default when you build a silverlight app in VS2008 with the Silverlight 3.0 SDK).
Then, add a call to the following in the constructor for the user control:
private void SetupBracket(int n)
{
var black = new SolidColorBrush(Colors.Gray);
// number of levels, or rounds, in the single-elim tourney
int levels = (int)Math.Log(n, 2) + 1;
// number of columns in the Grid. There's a "connector"
// column between round n and round n+1.
int nColumns = levels * 2 - 1;
// add the necessary columns to the grid
var cdc = LayoutRoot.ColumnDefinitions;
for (int i = 0; i < nColumns; i++)
{
var cd = new ColumnDefinition();
// the width of the connector is half that of the regular columns
int width = ((i % 2) == 1) ? 1 : 2;
cd.Width = new GridLength(width, GridUnitType.Star);
cdc.Add(cd);
}
var rdc = LayoutRoot.RowDefinitions;
// in the grid, there is one row for each player, and
// an interleaving row between each pair of players.
int totalSlots = 2 * n - 1;
for (int i = 0; i < totalSlots; i++)
{
rdc.Add(new RowDefinition());
}
// Now we have a grid of the proper geometry.
// Next: fill it.
List<int> slots = new List<int>();
ImageBrush brush = new ImageBrush();
brush.ImageSource = new BitmapImage(new Uri("Bridge.png", UriKind.Relative));
// one loop for each level, or "round" in the tourney.
for (int j = 0; j < levels; j++)
{
// Figure the number of players in the current round.
// Since we insert the rounds in the reverse order,
// think of j as the "number of rounds remaining."
// Therefore, when j==0, playersThisRound=1.
// When j == 1, playersThisRound = 2. etc.
int playersThisRound = (int)Math.Pow(2, j);
int x = levels - j;
int f = (int)Math.Pow(2, x - 1);
for (int i = 0; i < playersThisRound; i++)
{
// do this in reverse order. The innermost round is
// inserted first.
var r = new TextBox();
r.Background = black;
if (j == levels - 1)
r.Text = "player " + (i + 1).ToString();
else
r.Text = "player ??";
// for j == 0, this is the last column in the grid.
// for j == levels-1, this is the first column.
// The grid column is not the same as the current
// round, because of the columns used for the
// interleaved connectors.
int k = 2 * (x - 1);
r.SetValue(Grid.ColumnProperty, k);
int m = (i * 2 + 1) * f - 1;
r.SetValue(Grid.RowProperty, m);
LayoutRoot.Children.Add(r);
// are we not on the last round?
if (j > 0)
{
slots.Add(m);
// Have we just inserted two rows? Then we need
// a connector between these two and the next
// round (the round previously added).
if (slots.Count == 2)
{
string xamlTriangle = "<Path xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' "+
"xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' " +
"Data='M0,0 L 100 50 0 100 Z' Fill='LightBlue' Stretch='Fill'/>";
Path path = (Path)System.Windows.Markup.XamlReader.Load(xamlTriangle);
path.SetValue(Grid.ColumnProperty, 2 * (x - 1) + 1);
path.SetValue(Grid.RowProperty, slots[0]);
path.SetValue(Grid.RowSpanProperty, slots[1] - slots[0] + 1);
this.LayoutRoot.Children.Add(path);
slots.Clear();
}
}
}
}
}
In the above, the connector is just an isosceles triangle, with the apex pointing to the right. It is generated by XamlReader.Load() on a string.
You would also want to pretty it up, style it with different colors and fonts, I guess.
You can insert this silverlight "user control" into any HTML web page, something like embedding a flash app into a page. There are silverlight plugins for IE, Firefox, Opera, Safari, and Chrome.
If you don't want to use Silverlight, you could use a similar approach to construct an HTML table.

Resources