PDA

View Full Version : Need help in optimizing a drawing code ...



ahmadka
14th June 2010, 20:49
Hello all ... I needed some help in trying to optimize this code portion ... Basically here's the thing .. I'm making this 'calligraphy pen' which gives the calligraphy effect by simply drawing a lot of adjacent slanted lines ... The problem is this: When I update the draw region using update() after every single draw of a slanted line, the output is correct, in the sense that updates are done in a timely manner, so that everything 'drawn' using the pen is immediately 'seen' the drawing.. however, because a *lot* (100s of them) of updates are done, the program slows down a little when run on the N900 ...

When I try to do a little optimization by running update after drawing *all* the slanted lines (so that all lines are updated onto the drawing board through a single update() ), the output is ... odd .... That is, immediately after drawing the lines, they lines seem broken (they have vacant patches where the drawing should have happened as well) ... however, if I trigger a redrawing of the form window (say, by changing the size of the form), the broken patches are immediately fixed !! When I run this program on my N900, it gets the initial broken output and stays like that, since I don't know how to enforce a redraw in this case ...

Here is the first 'optimized' code and output (partially correct/incorrect)


void Canvas::drawLineTo(const QPoint &endPoint)
{
QPainter painter(&image);
painter.setPen(QPen(Qt::black,1,Qt::SolidLine,Qt:: RoundCap,Qt::RoundJoin));
int fx=0,fy=0,k=0;
qPoints.clear();
connectingPointsCalculator2(qPoints,lastPoint.x(), lastPoint.y(),endPoint.x(),endPoint.y());
int i=0;
int x,y;
for(i=0;i<qPoints.size();i++)
{
x=qPoints.at(i).x();
y=qPoints.at(i).y();
painter.setPen(Qt::black);
painter.drawLine(x-5,y-5,x+5,y+5); //Drawing slanted lines
}
//Updating only once after many draws:
update (QRect(QPoint(lastPoint.x()-5,lastPoint.y()-5), QPoint(endPoint.x()+5,endPoint.y()+5)).normalized( ));

modified = true;
lastPoint = endPoint;
}


Image right after scribbling on screen:

http://img823.imageshack.us/img823/8755/59943912.png

Image after slightly re-adjusting the window size:

http://img815.imageshack.us/img815/5374/88732361.png

Here is the second un-optimized code (its output is correct right after drawing, just like in the second picture above):


void Canvas::drawLineTo(const QPoint &endPoint)
{
QPainter painter(&image);
painter.setPen(QPen(Qt::black,1,Qt::SolidLine,Qt:: RoundCap,Qt::RoundJoin));
int fx=0,fy=0,k=0;
qPoints.clear();
connectingPointsCalculator2(qPoints,lastPoint.x(), lastPoint.y(),endPoint.x(),endPoint.y());
int i=0;
int x,y;
for(i=0;i<qPoints.size();i++)
{
x=qPoints.at(i).x();
y=qPoints.at(i).y();
painter.setPen(Qt::black);
painter.drawLine(x-5,y-5,x+5,y+5); // Drawing slanted lines
//Updating repeatedly during the for loop:
update(QRect(QPoint(x-5,y-5), QPoint(x+5,y+5)).normalized());//.adjusted(-rad,-rad,rad,rad));
}
modified = true;
int rad = (myPenWidth / 2) + 2;
lastPoint = endPoint;
}


Can anyone see what the issue might be ?

wysota
15th June 2010, 00:48
One thing I can say is that you are not clearing the image before drawing on it. Another is that it'd be nice to see the paintEvent() implementation as well. Finally I can tell you that QPainter::drawLines() is much faster than calling QPainter::drawLine() multiple times. The number of calls to update() shouldn't matter here, they should all be merged into one update anyway. I would also assume the rect passed to update() in the first case might be incorrect - consider a situation when you draw a counter-clockwise circle, what would the rect be then?

ahmadka
15th June 2010, 10:24
What do you mean by 'clearing the image' .. ? I mean, I don't want to erase the stuff I have drawn before .. I thought about reimplementing the paintEvent() as well but that seems a little difficult for me right now, as I'm still learning Qt without a book :P .. I will try to look into the drawLines() function though ..

The issue is somehow related to updating the display when drawing horizontal stuff ... When drawing vertical lines, no matter how fast, they're always solid, but horizontal lines are always broken, unless you are absolutely drawing at snail speed ..

Here's a picture from my N900:

http://img35.imageshack.us/img35/1446/testbu.png

wysota
15th June 2010, 11:09
What do you mean by 'clearing the image' .. ? I mean, I don't want to erase the stuff I have drawn before ..
Ah... ok, I thought you were redrawing all the points at each update.


I thought about reimplementing the paintEvent() as well but that seems a little difficult for me right now, as I'm still learning Qt without a book :P
If you are not reimplementing the paintEvent then how do you draw the image?

Here's a picture from my N900:
Please don't use third party sites to link images from. This violates the rules of this site (http://www.qtcentre.org/rules).

ahmadka
15th June 2010, 11:41
Well I'm painting directly on 'image' (which is a QImage object) using QPainter painter(&image) ... and then use update and everything ..

The connectingPointsCalculator function gives me all the points between to coordinates .. about each of these the slanted lines are calculated .. I've checked the working for this function too, to see if it gives wrong results in horizontal cases or something .. but this is fine too .. Also, since the problem only exists when running the code on N900, it leads me to believe that maybe the updating/repainting procedure is different for the N900 than for PC ... Would double buffering help here ?

wysota
15th June 2010, 12:37
Well I'm painting directly on 'image' (which is a QImage object) using QPainter painter(&image) ... and then use update and everything ..
What is the base class of your widget? How is "image" related to the widget?


Would double buffering help here ?
Qt widgets are double buffered by default. Save the image to disk and open it in an external viewer, check if the image is correct or not. At worst update the whole widget each time.

ahmadka
15th June 2010, 14:12
Alright here's the breakdown of my program ... There's a Main Window, which is just a main window .. Then there's another class I made called Canvas .. an object of Canvas is stored in Main Window and is initialized from Main Window's contructor .. Canvas is my main class where all the paint events and mouse events are handled .. 'image' is just a QImage object contained within the Canvas class .. Drawing is done on this image ..

Here's a gist of my code ... Thought, only stuff relevant to this issue is presented here:

Here's Canvas.h: (was previously called 'Board' .. by Canvas I mean the widget which contains the image on which drawing is done)



class Canvas : public QWidget
{
Q_OBJECT

public:
Canvas(QWidget *parent = 0);

protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *);
void paintEvent(QPaintEvent *event);

private:
void drawLineTo(const QPoint &endPoint);

QImage image; //this is the image on which drawing is done
QPoint lastPoint;
QVector<QPoint> qPoints;
//MainWindow *mainWindow;
int minX;
int minY;
int maxX;
int maxY;
void connectingPointsCalculator3( QVector<QPoint>& inputqPoints, int x0, int y0, int x1, int y1);
};

ConnectingPointsCalculator3 function .. (used to output a QVector of all the points which need to be connected to make all those lines to give the slanted effect ... only the point coordinates are calculated in this function):


void Canvas::connectingPointsCalculator3( QVector<QPoint>& inputqPoints, int x0, int y0, int x1, int y1)
{
inputqPoints.clear();
bool steep;


int i=2;

if( qAbs(y1-y0)>qAbs(x1-x0))
{
steep = true;
}
if(steep==true)
{
swap(x0,y0);
swap(x1,y1);
}
if(x0>x1)
{
swap(x0,x1);
swap(y0,y1);
}
int deltax = x1-x0;
int deltay = qAbs(y1-y0);
int error = deltax/2;
int ystep=0;
int y=y0;
if(y0<y1)
{
ystep=1;
}
else
{
ystep=-1;
}
inputqPoints.resize(1);
if(steep==true)
{
inputqPoints.insert(0,QPoint(y0-5,x0-5));
inputqPoints.insert(1,QPoint(y0+5,x0+5));
}
else
{
inputqPoints.insert(0,QPoint(x0-5,y0-5));
inputqPoints.insert(1,QPoint(x0+5,y0+5));
}
bool switchedLanes = false;
int x= x0;
while(x<(x1+1))
{
if(switchedLanes==false)
{
error = error - deltay;
if(error<0)
{
y=y+ystep;
error = error + deltax;
switchedLanes = true;
}
}
else
{
switchedLanes=false;
}


if(steep==true)
{
inputqPoints.insert(i,QPoint(y-5,x-5));
inputqPoints.insert(i+1,QPoint(y+5,x+5));
if((y-5)<minX)
{
minX=y-5;
}
if((x-5)<minY)
{
minY=x-5;
}
if((y+5)>maxX)
{
maxX=y+5;
}
if((x+5)>maxY)
{
maxY=x+5;
}
}
else
{
inputqPoints.insert(i,QPoint(x-5,y-5));
inputqPoints.insert(i+1,QPoint(x+5,y+5));
if((x-5)<minX)
{
minX=x-5;
}
if((y-5)<minY)
{
minY=y-5;
}
if((x+5)>maxX)
{
maxX=x+5;
}
if((y+5)>maxY)
{
maxY=y+5;
}
}
if(switchedLanes==false)
{
x++;

}
i++;
i++;

}

}

Function used to actually do all the drawing on the QImage object named 'image':


void Canvas::drawLineTo(const QPoint &endPoint)
{
QPainter painter(&image);

painter.setPen(QPen(Qt::black,1,Qt::SolidLine,Qt:: RoundCap,Qt::RoundJoin));
qPoints.clear();
minX =999999;
minY =999999;
maxX =-999999;
maxY =-999999;
painter.setPen(Qt::blue);
painter.drawEllipse(lastPoint,8,8);
painter.setPen(Qt::red);
painter.drawEllipse(endPoint,4,4);
connectingPointsCalculator3(qPoints,lastPoint.x(), lastPoint.y(),endPoint.x(),endPoint.y());
painter.setPen(Qt::black);
painter.drawLines(qPoints);

update (QRect(QPoint(minX,minY), QPoint(maxX,maxY)).normalized());
//update();
modified = true;
lastPoint = endPoint;
}

3 functions to do the initialization within the Canvas object:


Canvas::Canvas(QWidget *parent)
{
setAttribute(Qt::WA_StaticContents);
reset();
qPoints.clear();
}

void Canvas::reset()
{
resizeImage(&image,QSize(800,400));
image.fill(qRgb(255, 255, 255));
update();
}

void Canvas::resizeImage(QImage *image, const QSize &newSize)
{
if (image->size() == newSize)
return;

QImage newImage(newSize, QImage::Format_RGB32);
QPainter painter(&newImage);
painter.drawImage(QPoint(0, 0), *image);
*image = newImage;
}

Drawing event functions:


void Canvas::mousePressEvent(QMouseEvent *event)
{
if(event->button() & Qt::LeftButton)
{
scribbling=true;
lastPoint=event->pos();
}
}

void Canvas::mouseMoveEvent(QMouseEvent *event)
{
if((event->buttons() & Qt::LeftButton) && scribbling==true)
{
drawLineTo(event->pos());
}
}

void Canvas::mouseReleaseEvent(QMouseEvent *event)
{
if((event->button()&Qt::LeftButton)&&scribbling==true)
{
drawLineTo(event->pos());
scribbling=false;
}
}

Canvas paintEvent():


void Canvas::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QRect dirtyRect = event->rect();
painter.drawImage(dirtyRect, image, dirtyRect);
}

if you need code for my Main Window as well, please let me know ... And please help me figure this silly bug out :P

wysota
15th June 2010, 14:18
So you are implementing the paintEvent after all :)

Please do the tests I asked you to do in the previous post.

ahmadka
19th June 2010, 01:11
Alright I was busy for the past couple of days so I couldn't work on this problem before .. Anyways I implemented an image save feature, and have saved a PNG file directly from the N900 .. Bad news, the image is exactly like I see it on the N900: Broken horizontal lines .. :(

Anyways, here's one picture saved through my program running on the N900 (the blue and red circles indicate the points between slanted lines need to be drawn):

http://img12.imageshack.us/img12/4599/68561738.png

squidge
19th June 2010, 23:53
Firstly, forget drawing for a bit. Look the positions you are trying to draw, and then load them in afterwards and draw them. This will tell you what the problem is.

Secondly, as Wysota says, please don't use imageshack (or any other third party image hosting site).

ahmadka
24th June 2010, 13:45
Okay yeh, I figured out the problem .. problem was in my connecting points code .. fixed it now, and now there are no breaks .. :)

Thanks for your help mate, and i apologize for uploading to imageshack .. forgot :P