I want to create a string that will be printed on the screen. I will update this string every time values of the variables printed will change. The number of digits in each number can be different and sign of each number may change with time. What I want to be constant is numbers precision. The output I expect is sth like this (each line correspond to the same string, only updated):
X: 453.432 Y: 543.432
X: -5.432 Y: 4.432
X: 43.234 Y: -123.423
Namely, the letters and the dot should always stay in the same place on the screen.
At the moment I use this code for string updates but position of the Y and dots vary if number of digits change. Is there a simple way to implement it?
text = QString("X: %1 Y: %2").arg( QString::number( x(), 'f', 3 ),
QString::number( y(), 'f', 3 ) );
------------------ EDIT ------------------
Following the answer I created a string with constant spacing (constant length actually). It didn't solve my problem fully though.
In the next step I take the string text and using QPainter I paint it onto QImage object. The problem that I encounter here is that though the string length is always the same the width of numbers is different than the width of spaces (' ') and '-' sign painted by QPainter. So when the number of digits changes the positions of dots and 'Y' symbol changes as well.
Here is the code that I use for image creation:
QImage img( 100, 100, QImage::Format_ARGB32_Premultiplied );
img.fill( Qt::transparent );
QPainter painter( &img );
QPen pen;
pen.setColor( Qt::darkBlue );
painter.setPen(pen);
QFont font = painter.font();
font.setBold( true );
font.setPointSize( 10 );
painter.setFont( font );
QString text = QString("X:%1 Y:%2")
.arg( x(), 9, 'f', 3, ' ' )
.arg( y(), 9, 'f', 3, ' ' );
painter.drawText( QPointF(0, 50), text );
painter.end();
You can use this signature of the arg method:
// arg(double a, int fieldWidth, char format, int precision, QChar fillChar)
QString formated = QString("X:%1 Y:%2")
.arg(x(), 9, 'f', 3, ' ')
.arg(y(), 9, 'f', 3, ' ');
For your second problem, I solve it by changing the font family.
font.setFamily("Courier");
These are my results:
Default font:
Courier font:
Related
I'm working on a score display for my simple 2d SDL_2 Game.
This is the part of my code where I display the speed (basically just score):
void Renderer::renderText(const char* text, SDL_Rect* destR)
{
SDL_Surface* surfaceText = TTF_RenderText_Solid(Renderer::font, text, { 255,255,255 });
SDL_Texture* textureText = SDL_CreateTextureFromSurface(renderer, surfaceText);
SDL_FreeSurface(surfaceText);
SDL_RenderCopy(renderer, textureText, NULL, destR);
SDL_DestroyTexture(textureText);
}
There is the obvious problem that if I have a width for the number "1", the text would be squished a lot of the number was "10000" etc as you would have to fit 5 characters into an SDL_Rect that is only 1 character wide.
I could multiply the width by the number of characters however that wouldn't be very accurate as different characters have different widths.
How would I solve this?
(I only answered this because I found out how and nobody else answered)
You can do something like this.
SDL_Rect destR = { x, y, 0, 0 };
TTF_SizeText(font, text, &destR.w, &destR.h);
Basically, TTF_SizeText enables you to get the native width and height from the text with font. Then you can just multiply the height and width as you wish.
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.
My QRect object is a fixed-sized containter of plain text, when there is too much text I would truncate the text and trail ... at the end. For example, Longlonglonglong is truncated to Longlong.... But I want to display the full-length text in a bubble when mouse pointer is over the rect.
The bubble is like the Go to Google Home:
Is this possible ?
Unfortunately QPainter can't do that for you, the drawText(..) flags don't support it. Thankfully, you can pre-elide the text for it ("eliding" is where you truncate with an elipsis) using QFontMetrics:
QFontMetrics fontM( QApplication::font() );
QRect r( 0, 0, 30, 10 );
QString text = "Longlonglonglong";
QString elidedText = fontM.elidedText( text, Qt::ElideRight, r.width() );
painter->drawText( r, Qt::AlignLeft, elidedText );
When you say "text in a bubble when mouse pointer is over", I presume you mean a tooltip - in which case implement it for the widget as normal and give the full text rather than the elided.
I'm using QPainter to draw multiline text on QImage. However, I also need to display a colored rectangle around each character's bounding box.
So I need to know the bounding box that each character had when being drawn.
For example, for
painter.drawText(QRect(100, 100, 200, 200), Qt::TextWordWrap, "line\nline2", &r);
I would need to get 10 rectangles, taking into account newlines, word-wrap, tabs, etc.
For example, the rectangle of the second 'l' would be below the rectangle of the first 'l', instead of being to the right of 'e', because of the newline.
Something like the coordinates of the red rectangles in this picture (I've put them by hand so they're not really the correct positions):
This may not be the best solution, but it's the best one I can think of.
I believe you will have to "do it yourself". That is, instead of drawing a block of text, draw each character one at a time. Then you can use QFontMetrics to get the bounding box of each character.
It's a little work, but not too bad. Something like (pseudo code, not code):
QFontMetrics fm(myFont, paintDevice);
int x = startX;
int y = startY;
for (unsigned int i = 0; i < numChars; i++)
{
char myChar = mystr[i]; // get character to print/bound
QRect rect = fm.boundingRect( myChar ); // get that char's bounding box
painter.drawText(x, y, Qt::TextWordWrap, mystr[i], &r); // output char
painter.drawRect(...); // draw char's bounding box using 'rect'
x += rect.width(); // advance current position horizontally
// TODO:
// if y > lineLen // handle cr
// x = startX;
// y += line height
}
Check out QFontMetrics, it has a number of different methods for getting bounding boxes, minimum bounding boxes, etc.
QFontMetrics 4.7
Ahhh... I see now that the overload you're using returns the actual bounding rect. You can just use that and skip the QFontMetrics if you like - otherwise the overall algorithm is the same.
You can retrieve the bounding boxes of individual characters with QFontMetrics::boundingRect(QChar), but they have to be rendered at an offset (QFontMetrics::ascent from the top as well as QFontMetrics::width of the preceding characters from the left) because they are relative to the font’s base line and not to the bottom of the bounding box of the complete string.
Several lines also have to be handled separately.
QFontMetrics::lineSpacing give you their offset.
QPainter painter(this);
painter.setFont(QFont("Arial", 72));
auto pen = painter.pen();
QString text{"line\nline2\ngg\n`"};
QRect boundingRect;
painter.drawText(rect(), Qt::AlignLeft | Qt::AlignTop, text, &boundingRect);
painter.drawRect(boundingRect.adjusted(0, 0, -pen.width(), -pen.width()));
pen.setColor(Qt::red);
painter.setPen(pen);
const auto lines = text.split('\n');
const auto fm = painter.fontMetrics();
for (int linei = 0; linei < lines.size(); ++linei) {
const auto & line = lines[linei];
for (int chi = 0; chi < line.size(); ++chi) {
const auto bounds = fm.boundingRect(line[chi]);
const auto xoffset = bounds.x() + fm.width(line, chi);
const auto lineOffset = linei * fm.lineSpacing() + fm.ascent();
const auto yoffset = lineOffset + bounds.y();
painter.drawRect(QRect{xoffset, yoffset, bounds.width(), bounds.height()});
}
}
results in
which, sadly – isn’t perfect though.
I need such thing because it seems to me that when I do
cvRectangle( CVframe, UL, LR, CV_RGB(0,256,53), CV_FILLED);
string cvtext;
cvtext += timeStr;
cvPutText(CVframe, cvtext.c_str(), cvPoint(0,(h/2+10)), &font , CV_RGB(0,0,0));
each time 24 times per second cvRectangle does not overlay old text...
There's no built-in cvDeleteText or anything like that, and probably for good reason. Whenever you put text on an image, it overwrites the pixels in that image, just as if you had set their values to CV_RGB(0,0,0) individually. If you wanted to undo that operation, you'd need to store whatever had been there beforehand. Since not everyone wants to do that, it would be a waste of space and time if cvPutText automatically kept track of the pixels it wrote over.
Probably the best approach would be to have two frames, one of which is never touched by text. The code would look something like this.
//Initializing, before your loop that executes 24 times per second:
CvArr *CVframe, *CVframeWithText; // make sure they're the same size and format
while (looping) {
cvRectangle( CVframe, UL, LR, CV_RGB(0,256,53), CV_FILLED);
// And anything else non-text-related, do it to CVframe.
// Now we want to copy the frame without text.
cvCopy(CVframe, CVframeWithText);
string cvtext;
cvtext += timeStr;
// And now, notice in the following line that
// we're not overwriting any pixels in CVframe
cvPutText(CVframeWithText, cvtext.c_str(), cvPoint(0,(h/2+10)), &font , CV_RGB(0,0,0));
// And then display CVframeWithText.
// Now, the contents of CVframe are the same as if we'd "deleted" the text;
// in fact, we never wrote text to CVframe in the first place.
Hope this helps!
I don't know, but maybe this can help.
Make two mouse click events, LeftClick and Right Click.
In my example, when I left-click the mouse it gives the pixel coordinate of the image, and when I right-click the mouse, it loads the image from the fresh. It's kindof removing what cv2.putText function placed there.
just import any image, I have used 'lena_color.tiff' as an example.
import cv2
def click_event(event, x, y, flags, params):
global img
if event == cv2.EVENT_LBUTTONDOWN:
print(x, ' ', y)
font = cv2.FONT_HERSHEY_SIMPLEX; fontSize = 2
point = '.'; text = ' (' + str(x) + ', ' + str(y) + ')'
color = (255, 0, 0); thickness=6
cv2.putText(img, point, (x,y), font, fontSize, color, thickness)
cv2.putText(img, text, (x,y), font, 0.5, color, 1)
cv2.imshow('image', img)
if event==cv2.EVENT_RBUTTONDOWN:
img = cv2.imread('lena_color.tiff', 1)
cv2.imshow('image', img)
cv2.setMouseCallback('image', click_event)
img = cv2.imread('lena_color.tiff', 1)
cv2.imshow('image', img)
cv2.setMouseCallback('image', click_event)
cv2.waitKey(0)
cv2.destroyAllWindows()