I am trying to draw a 10 millisecond grid in a QGraphicsScene in Qt. I am not very familiar with Qt... it's the first time I used it, and only because the application needs to be portable between Windows and Linux.
I actually don't have a problem drawing the grid, it's just the performance when the grid gets big. The grid has to be able to change size to fit the SceneRect if/when new data is loaded into the program to be displayed.
This is how I do it at the moment, I hate this but it's the only way I can think of doing it...
void Plotter::drawGrid() {
unsigned int i;
QGraphicsLineItem *line;
QGraphicsTextItem *text;
char num[11];
QString label;
unsigned int width = scene->sceneRect().width();
unsigned int height = scene->sceneRect().height();
removeGrid();
for (i = 150; i < width; i+= 10) {
line = new QGraphicsLineItem(i, 0, i, scene->sceneRect().height(), 0, scene);
line->setPen(QPen(QColor(0xdd,0xdd,0xdd)));
line->setZValue(0);
_itoa_s(i - 150, num, 10);
label = num;
label += " ms";
text = new QGraphicsTextItem(label, 0, scene);
text->setDefaultTextColor(Qt::white);
text->setX(i);
text->setY(height - 10);
text->setZValue(2);
text->setScale(0.2);
//pointers to items stored in list for removal later.
gridList.append(line);
gridList.append(text);
}
for (i = 0; i < height; i+= 10) {
line = new QGraphicsLineItem(150, i, width, i, 0, scene);
line->setPen(QPen(QColor(0xdd,0xdd,0xdd)));
line->setZValue(0);
gridList.append(line);
}
}
When scene->sceneRect().width() gets too big, however, the application becomes very sluggish. I have tried using a QGLWidget, but the improvements in speed are marginal at best.
I ended up using a 10x10 square pixmap and drew it as the backgroundBrush on my QGraphicsView, as is suggested in the link in the comment under my initial question.
Related
I want to display an image received in a short[] of pixels from a server.
The server(C++) writes the image as an unsigned short[] of pixels (12 bit depth).
My java application gets the image by a CORBA call to this server.
Since java does not have ushort, the pixels are stored as (signed) short[].
This is the code I'm using to obtain a BufferedImage from the array:
private WritableImage loadImage(short[] pixels, int width, int height) {
int[] intPixels = new int[pixels.length];
for (int i = 0; i < pixels.length; i++) {
intPixels[i] = (int) pixels[i];
}
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
WritableRaster raster = (WritableRaster) image.getData();
raster.setPixels(0, 0, width, height, intPixels);
return SwingFXUtils.toFXImage(image, null);
}
And later:
WritableImage orgImage = convertShortArrayToImage2(image.data, image.size_x, image.size_y);
//load it into the widget
Platform.runLater(() -> {
imgViewer.setImage(orgImage);
});
I've checked that width=1280 and height=1024 and the pixels array is 1280x1024, that matches with the raster height and width.
However I'm getting an array out of bounds error in the line:
raster.setPixels(0, 0, width, height, intPixels);
I have try with ALL ImageTypes , and all of them produce the same error except for:
TYPE_USHORT_GRAY: Which I thought it would be the one, but shows an all-black image
TYPE_BYTE_GRAY: which show the image in negative(!) and with a lot of grain(?)
TYPE_BYTE_INDEXED: which likes the above what colorized in a funny way
I also have tried shifting bits when converting from shot to int, without any difference:
intPixels[i] = (int) pixels[i] & 0xffff;
So..I'm quite frustrated after looking for days a solution in the internet. Any help is very welcome
Edit. The following is an example of the images received, converted to jpg on the server side. Not sure if it is useful since I think it is made from has pixel rescaling (sqrt) :
Well, finally I solved it.
Probably not the best solution but it works and could help someone in ether....
Being the image grayscale 12 bit depth, I used BufferedImage of type TYPE_BYTE_GRAY, but I had to downsample to 8 bit scaling the array of pixels. from 0-4095 to 0-255.
I had an issue establishing the higher and lower limits of the scale. I tested with avg of the n higher/lower limits, which worked reasonably well, until someone sent me a link to a java program translating the zscale algorithm (used in DS9 tool for example) for getting the limits of the range of greyscale vlues to be displayed:
find it here
from that point I modified the previous code and it worked like a charm:
//https://github.com/Caltech-IPAC/firefly/blob/dev/src/firefly/java/edu/caltech/ipac/visualize/plot/Zscale.java
Zscale.ZscaleRetval retval = Zscale.cdl_zscale(pixels, width, height,
bitsVal, contrastVal, opt_sizeVal, len_stdlineVal, blankValueVal);
double Z1 = retval.getZ1();
double Z2 = retval.getZ2();
try {
int[] ints = new int[pixels.length];
for (int i = 0; i < pixels.length; i++) {
if (pixels[i] < Z1) {
pixels[i] = (short) Z1;
} else if (pixels[i] > Z2) {
pixels[i] = (short) Z2;
}
ints[i] = ((int) ((pixels[i] - Z1) * 255 / (Z2 - Z1)));
}
BufferedImage bImg
= new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
bImg.getRaster().setPixels(0, 0, width, height, ints);
return SwingFXUtils.toFXImage(bImg, null);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return null;
I have a big image of 7589x5537 that I put in a scene as a QPixmapGraphicsItem.
If I scale the QGraphicsView to 14.2318 and rotate it -35 degrees, the render of the pixmap starts behaving weirdly; tearing or completely disappearing.
This happens also at other rotations and scales, but only if they are big scaling of more than 14.
I've read about X11 limitations but I'm on Windows.
I'm on Qt 5.5
I've tested changing the content of the image to a bucketfill of tree pattern, exactly the same behaviour. The image is indexed, but with a RGB I have the same issue.
Anybody has a clue why this happens and how to fix it? Is the problem reproducible?
The issue seems to be related to the maximum value of unsigned int, dimension independent if not rotated. Creating an untilted image of 1 million by 200 pixels, one can zoom up to 4384x. In my computer the size of unsigned int is 4 bytes, which can handle roughly values up to 4000 million.
I presume Qt doesn't crop the upscaled image to the view before scaling it, or something similar. It is weird, thought, that it tears it instead of crashing exhausting resources, failing to allocate contiguous memory or something else.
Those are suspicions since at the moment I don't know how QGraphicsView implements scaling.
#include <QtWidgets>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
unsigned int w = 7589;
unsigned int h = 5537;
QImage image(w, h, QImage::Format_ARGB32);
for(unsigned int j = 0; j < h; j++)
{
for(unsigned int i = 0; i < w; i++)
{
QRgb rgb = qRgb(i%255,j%255,(i+j)%255);
image.setPixel(i, j, rgb);
}
}
QPixmap imagepm = QPixmap::fromImage(image);
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(imagepm);
item->setTransformationMode(Qt::FastTransformation);
QGraphicsScene* scene = new QGraphicsScene;
scene->addItem(item);
QGraphicsView* view = new QGraphicsView(scene);
view->rotate(-35);
view->scale(14.2318,14.2318);
view->show();
return a.exec();
}
The fix requires cutting the image up into tiles, grouping them under a single parent item, and then proceeding as you did before. The tiles would be an implementation detail that you don't need to worry about.
I have a scene with an inverted y-axis. Everything is correctly drawn except QImages.
I use drawIage() as:
QRectF aWorldRect = ...
QRectF anImageRect = QRectF(0, 0, theQImage.width(), theQImage.height())
thePainter->drawImage(aWorldRect, theQImage, anImageRect;
I get undefined graphics outside (to the top of) where the image should be displayed. This is normal because y-axis is inverted. So I expected something like that may fix the issue:
QRectF anImageRect = QRectF(0, 0, imgWidth, -imgHeight)
It has the same effect. If I do aWorldRect = aWorldRect.noralized() before calling drawImage(), I get the image in the correct rectangle but mirrored so I did aQImage = aQImage.mirrored(). Now the image is correctly displayed in the correct rectangle
I consider this a workaround which I don't like to keep. So, can someone tell me what should be done to get the image displayed, the right way?
Update
Here I put a minimal sample of my problem that is ready to compile:
Update 2014-04-09 10:05 EET
Updated the sample code little bit to make really work using the workaround
#include <QtGui>
const int WIDTH = 640;
const int HEIGHT = 480;
class View : public QGraphicsView
{
protected:
void drawBackground(QPainter *p, const QRectF & rect)
{
QImage img = QImage("/usr/share/backgrounds/images/stone_bird.jpg"); // or any other
/* The next three lines makes everything displayed correctly but
should be considered a workaround */
/* I ignore the rect that is passed to the function on purpose */
QRectF imageRect = QRectF(QPointF(0, 0), QPointF(img.width(), img.height()));
QRectF theSceneRect = sceneRect().normalized();
p->drawImage(theSceneRect, img.mirrored(), imageRect);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
View w;
/* I don't want to change the code below */
w.setScene(new QGraphicsScene(QRectF(QPointF(0, HEIGHT), QPointF(WIDTH, 0))));
w.scale(1, -1);
w.scene()->addLine(0, HEIGHT, WIDTH, 0);
w.showMaximized();
return a.exec();
}
The approach of reversing the Y coordinate value is right but the implementation was faulty.
QRectF's documentation shows that it takes (x, y, width, height). Giving height as negative makes little sense. Instead try the other constructor which takes topLeft and bottomRight.
QRectF anImageRect(QPointF(0.0f, -imgHeight), QPointF(imageWidth, 0.0f));
EDIT:
It seems that the only drawings like line, arc, etc. are affected by the scale (1, -1) transform you set on the view. drawImage continues to render upside down due to the scale set. The simple fix is to set the scale back to (1, -1). Here's the updated code:
void drawBackground(QPainter *p, const QRectF & rect)
{
QImage img = QImage("/usr/share/backgrounds/images/stone_bird.jpg");
// backup the current transform set (which has the earlier scale of (1, -1))
const QTransform oldTransform = p->transform();
// set the transform back to identity to make the Y axis go from top to bottom
p->setTransform(QTransform());
// draw
QRectF theSceneRect = sceneRect().normalized();
p->drawImage(theSceneRect, img);
// revert back to the earlier transform
p->setTransform(oldTransform);
}
Updated on 2014-04-14 14:35 EET
I could finally solve the problem reliably by replacing the two lines
QRectF theSceneRect = sceneRect().normalized();
p->drawImage(theSceneRect, img.mirrored(), imageRect);
of my question to
QRectF theSceneRect = sceneRect(); // Not normalized. It is no more workaround :)
qreal x = theSceneRect.x();
qreal y = theSceneRect.y();
qreal w = theSceneRect.width();
qreal h = theSceneRect.height();
qreal sx = imageRect.x();
qreal sy = imageRect.y();
qreal sw = imageRect.width();
qreal sh = imageRect.height();
p->translate(x, y);
p->scale(w / sw, h / sh);
p->setBackgroundMode(Qt::TransparentMode);
p->setRenderHint(QPainter::Antialiasing, p->renderHints() &
QPainter::SmoothPixmapTransform);
QBrush brush(img);
p->setBrush(brush);
p->setPen(Qt::NoPen);
p->setBrushOrigin(QPointF(-sx, -sy));
p->drawRect(QRectF(0, 0, sw, sh));
p->restore();
This is inspired by the implementation of the QPainter::drawImage() which is not reliable in such cases due to many if statements handling rectangles with negative values of width or height.
It would be better if I made the solution in another function but I kept it this way to be more compatible with the code in my question.
I want to make a dynamic text animation with Qt for arabic and persian texts?can you help me?
You can see an example of what i need.
Trust sample
wrong sample
I suggest to use the following classes: QGraphicsScene and QGraphicsView to handle and display your graphics, QGraphicsTextItem to display each character, QGraphicsItemAnimation to animate characters.
I do not know how exactly acts the example you've posted and which transformations are applied. I wrote a simple example. Here the initial rotation and translation of each item is set randomly, and the final positions are without any transformation.
QString text = "test";
int current_width = 0;
QFont font("", 30);
QTimeLine *timeline = new QTimeLine(2000);
foreach(QChar c, text) {
QGraphicsTextItem* item = scene.addText(c);
item->setFont(font);
item->adjustSize();
item->setPos(current_width, 0);
current_width += item->textWidth();
QGraphicsItemAnimation *animation = new QGraphicsItemAnimation;
animation->setItem(item);
animation->setTimeLine(timeline);
animation->setRotationAt(0, 360.0 * rand() / RAND_MAX);
animation->setTranslationAt(0, 100 * rand() / RAND_MAX,
100 * rand() / RAND_MAX);
animation->setRotationAt(1, 0);
animation->setTranslationAt(1, 0, 0);
}
ui.graphicsView->setScene(&scene);
timeline->start();
Given a .png image with a transparent background, I want to find the bounding box of the non-transparent data. Using nested for loops with QImage.pixel() is painfully slow. Is there a built-in method of doing this in Qt?
There is one option that involves using a QGraphicsPixmapItem and querying for the bounding box of the opaque area (QGraphicsPixmapItem::opaqueArea().boundingRect()). Not sure if it is the best way but it works :) It might be worth digging into Qt's source code to see what code is at the heart of it.
The following code will print out the width and height of the image followed by the width and height of the opaque portions of the image:
QPixmap p("image.png");
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(p);
std::cout << item->boundingRect().width() << "," << item->boundingRect().height() << std::endl;
std::cout << item->opaqueArea().boundingRect().width() << "," << item->opaqueArea().boundingRect().height() << std::endl;
If pixel() is too slow for you, consider more efficient row-wise data adressing, given a QImage p:
int l =p.width(), r = 0, t = p.height(), b = 0;
for (int y = 0; y < p.height(); ++y) {
QRgb *row = (QRgb*)p.scanLine(y);
bool rowFilled = false;
for (int x = 0; x < p.width(); ++x) {
if (qAlpha(row[x])) {
rowFilled = true;
r = std::max(r, x);
if (l > x) {
l = x;
x = r; // shortcut to only search for new right bound from here
}
}
}
if (rowFilled) {
t = std::min(t, y);
b = y;
}
}
I doubt it will get any faster than this.
The easiest and also relatively fast solution is to do as follows:
QRegion(QBitmap::fromImage(image.createMaskFromColor(0x00000000))).boundingRect()
If you have a QPixmap rather than QImage, then you can use:
QRegion(pixmap.createMaskFromColor(Qt::transparent)).boundingRect()
QPixmap::createMaskFromColor internally will convert the pixmap to an image and do the same as above. An even shorter solution for QPixmap is:
QRegion(pixmap.mask()).boundingRect()
In this case, a QPixmap without alpha channel will result in an empty region, so you may need to check for that explicitly. Incidentally, this is also what QGraphicsPixmapItem::opaqueArea mentioned by #Arnold Spence is based on.
You may also want to try QImage::createAlphaMask, though the cutoff point will not be at 0 alpha but rather somewhere at half opacity.