I want to write a parser for svg files and render vector graphics with gdi+, but when I try to inject data from the svg file into the code,
turns out to be incorrectly rendered
bezier curve. The edges of the curve are uneven.
I tried changing the miterlimit values of the SetMiterLimit function of the Pen class, and tried other functions, but nothing changes. I display the image through opengl. For comparison, I'm attaching screenshots: how the curve looks in browsers and in gdi + with and without anti-aliasing. What could be the problem?
svg code:
<path fill="none" stroke="#000000" stroke-width="32" stroke-miterlimit="10" d="M168,37c0,35.4,39.4,64,88,64"/>
c++ code:
Rect paintRect(0, TbHeight, mw->getClRect().right, mw->getClRect().bottom);
Bitmap bmp(paintRect.Width, paintRect.Height - TbHeight);
Graphics* graphics = new Graphics(&bmp);
graphics->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
graphics->SetSmoothingMode(SmoothingModeAntiAlias);
float _x = 168;
float _y = 37;
PointF p[] =
{
PointF(_x, _y),
PointF(_x + 0, _y + 35.4),
PointF(_x + 39.4, _y + 64),
PointF(_x + 88, _y + 64)
};
pen.SetWidth(32.f);
graphics->DrawBeziers(&pen, p, 4);
screenshots:
svg at browsers
without anti-aliasing gdi+ with anti-aliasing gdi+
Related
On a C++ game, we're using Pango to render text with cairo and from there to an OpenGL texture.
I noticed this problem recently while working on implementing text-wrapping via Pango.
What I'm doing is calculating the width by translating our own coordinate system to pixels and then using that as a fraction of the window width in PANGO UNITS, or in code:
float screenEdge = _w * m * static_cast<float>(PANGO_SCALE);
float tempMaxWidth = _owner->m_wrapping.m_maxWidth;
tempMaxWidth = std::min(_owner->m_wrapping.m_maxWidth, (0.48f - _owner->dimensions().x1()));
float wrapWidth = screenEdge * tempMaxWidth;
maxWidth = static_cast<int>(std::round(wrapWidth));
pango_layout_set_width(_owner->m_pangoLayout.get(), maxWidth);
Then we get a rectangle from the layout an pass that to cairo with:
float border = 2.0f;
PangoRectangle lRec;
pango_layout_get_pixel_extents(_owner->m_pangoLayout.get(), nullptr, &lRec);
m_width = (lRec.width + border); // Add twice half a border for margins
m_height = (lRec.height + border);
std::unique_ptr<cairo_surface_t, decltype(&cairo_surface_destroy)> m_cairoSurface(
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, m_width, m_height),
&cairo_surface_destroy);
std::unique_ptr<cairo_t, decltype(&cairo_destroy)> m_cairoDc(
cairo_create(m_cairoSurface.get()),
&cairo_destroy);
cairo_set_antialias(m_cairoDc.get(), CAIRO_ANTIALIAS_FAST);
cairo_push_group_with_content (m_cairoDc.get(), CAIRO_CONTENT_COLOR_ALPHA);
cairo_set_operator(m_cairoDc.get(), CAIRO_OPERATOR_SOURCE);
// Add Pango line and path to proper position on the DC
cairo_move_to(m_cairoDc.get(), (0.5f * border), (0.5f * border)); // Margins needed for border stroke to fit in
But the output, as you can see below, is cut-off; this does not happen if I use PANGO_ALIGN_LEFT
Searching around here, I found Cairo + Pango layout non-left alignment
Which appears related to my issue but does not quite provide a solution (or I didn't get it properly); what I tried was to calculate the different between the x and y coordinates of the logical and ink rectangles and adding that to cairo_move_to but that made no real difference in the output, so I just pasted the original code here.
I have a QPrinter that prints A4 either directly to a physical printer or a PDF. Now I'd like to use QPainter to draw in millimetres, but the current coordinate system seems to be the width and height of an A4 in inches times the resolution of the printer.
8.26 inch x 1200 res = 9912
11.69 inch x 1200 res = 14028
I have tried the following but text just ended up huge.
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(QRect(0, 0, page.width(), page.height()));
How do I change this so my QPainter can draw to 210 x 297 mm instead of the above system?
This is on Windows 10 and with Qt 5.10.
I tested this method on X11 (ubuntu linux) PDF print, using ScreenResolution printer mode:
painter.begin(printer);
int log_w = 210;
int log_h = 297;
painter.setWindow(0, 0, log_w, log_h);
int phys_w = printer->width();
int phys_h = printer->height();
painter.setViewport(0, 0, phys_w, phys_h);
Basically, set your logical size in mm using the painter window, and give the painter's viewport the printer's physical size.
This line should print a rectangle around the page with a border of 10 mm:
painter.drawRect(10, 10, log_w - 20, log_h -20);
Text should work accordingly. This code should print the word Ok at the top left corner of the rectangle:
QFont font = painter.font();
font.setPointSize(10); //1 cm height
painter.setFont(font);
painter.drawText(10, 20, "Ok");
painter.end();
Using HighResolution printer mode, font size must be set using
font.setPixelSize(10); //1 cm height
and a QPen must be set to the painter:
QPen pen(Qt::black);
pen.setWidthF(0.2);
painter.setPen(pen);
painter.drawRect(10, 10, log_w - 20, log_h - 20);
About loss of device dependency using setPixelSize, I'm aware that here is stated:
It is possible to set the height of characters shown on the screen to
a specified number of pixels with setPixelSize(); however using
setPointSize() has a similar effect and provides device independence.
but I think it refers to screen only, given that here is stated:
When rendering text on a QPrinter device, it is important to realize
that the size of text, when specified in points, is independent of the
resolution specified for the device itself. Therefore, it may be
useful to specify the font size in pixels when combining text with
graphics to ensure that their relative sizes are what you expect.
I think that you are looking for the QTransform class, according to the official doc:
The QTransform class specifies 2D transformations of a coordinate
system. A transformation specifies how to translate, scale, shear,
rotate or project the coordinate system, and is typically used when
rendering graphics.
You can initialise your custom transform class:
QTransform transform = QTransform::fromScale(painter.device()->physicalDpiX() / scale, painter.device()->physicalDpiY() / scale);
A think that this could be helpfull, the number of dots per militmeter:
const int dot_per_millimeter = qRound(qApp->primaryScreen()->physicalDotsPerInch() / 25.40);
Customise then your scale & apply it using a QPainter:
QPainter painter(parent);
painter.setWorldTransform(transform, false);
Your approach is correct. Here is an example of how to set up a printer/painter pair. I don't fiddle around with the transformation matrix since it's sufficient to specify a window/viewport pair. I don't even specify the viewport explicitly, since it is automatically set to the metrics of the paint device (in this case the QPrinter object).
#include <QPrinter>
#include <QPainter>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QPrinter printer(QPrinter::PrinterResolution);
printer.setOrientation(QPrinter::Portrait);
printer.setPageSize(QPageSize(QPageSize::A4));
printer.setResolution(300 /*dpi*/);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("ellipse.pdf");
QPainter painter(&printer);
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(page.toRect());
// Draw a 5mm thick ellipse across the whole page.
painter.setPen(QPen(Qt::black, 5.0));
painter.drawEllipse(0, 0, 210, 297);
return 0;
}
It is hard to tell what goes wrong in your case without seeing the rest of the code
The question is in the title. For example how can I save g in a file in the following snippet ?
public void paints(Graphics g, Image background, Image watermark, int width, int height) {
g.drawImage(background, 0, 0);
g.drawImage(watermark, 0, 0);
g.setColor(0xFF0000);
// Upper left corner
g.fillRect(0, 0, 10, 10);
// Lower right corner
g.setColor(0x00FF00);
g.fillRect(width - 10, height - 10, 10, 10);
g.setColor(0xFF0000);
Font f = Font.createTrueTypeFont("Geometos", "Geometos.ttf").derive(220, Font.STYLE_BOLD);
g.setFont(f);
// Draw a string right below the M from Mercedes on the car windscreen (measured in Gimp)
g.drawString("HelloWorld",
(int) (848 ),
(int) (610)
);
// NOW how can I save g in a file ?
}
The reaseon why I don't want to take a screenshot is because I want to keep the full resolution of g (eg : 2000 x 1500).
I would be so grateful to anyone that can tell me how to do that with Codename one. If not possible then it is already good to know it!
Cheers,
What you could do is to create an Image as buffer, get the graphics object from the image an do all your drawings operations on it. Then draw the whole image to the display and save it as a file:
int height = 2000;
int width = 1500;
float saveQuality = 0.7f;
// Create image as buffer
Image imageBuffer = Image.createImage(width, height, 0xffffff);
// Create graphics out of image object
Graphics imageGraphics = imageBuffer.getGraphics();
// Do your drawing operations on the graphics from the image
imageGraphics.drawWhaterver(...);
// Draw the complete image on your Graphics object g (the screen I guess)
g.drawImage(imageBuffer, w, h);
// Save the image with the ImageIO class
OutputStream os = Storage.getInstance().createOutputStream("storagefilename.png");
ImageIO.getImageIO().save(imageBuffer, os, ImageIO.FORMAT_PNG, saveQuality);
Note, that I have not tested it, but it should work like that.
Graphics is just a proxy to a surface, it has no knowledge or access to the underlying surface to which it is drawing and the reason for that is quite simple. It can draw to a hardware accelerated "surface" where there is physically no underlying image.
This is the case both on iOS and Android where the "screen" is natively drawn and has no buffer.
Context
I try to draw pie chart for statistic in my game. I'm using Cocos2d-x ver.3.8.1. Size of the game is important, so I won't to use third-party frameworks to create pie charts.
Problem
I could not find any suitable method in Cocos2d-x for drawing part of the circle.
I tried to do
I tried to find a solution to this problem in Internet, but without success.
As is known, sector of a circle = triangle + segment. So, I tried to use the method drawSegment() from DrawNode also.
Although it has parameter radius ("The segment radius" written in API reference), radius affects only the thickness of the line.
drawSegment() method draw a simple line, the thickness of which is set by a method call.
Question
Please prompt me, how can I draw a segment or a sector of a circle in Cocos2d-x?
Any advice will be appreciated, thanks.
I think the one of the ways to draw a sector of a circle in Cocos2d-X is the way to use drawPolygon on DrawNode. I wrote little sample.
void drawSector(cocos2d::DrawNode* node, cocos2d::Vec2 origin, float radius, float angle_degree,
cocos2d::Color4F fillColor, float borderWidth, cocos2d::Color4F bordercolor,
unsigned int num_of_points = 100)
{
if (!node)
{
return;
}
const cocos2d::Vec2 start = origin + cocos2d::Vec2{radius, 0};
const auto angle_step = 2 * M_PI * angle_degree / 360.f / num_of_points;
std::vector<cocos2d::Point> circle;
circle.emplace_back(origin);
for (int i = 0; i <= num_of_points; i++)
{
auto rads = angle_step * i;
auto x = origin.x + radius * cosf(rads);
auto y = origin.y + radius * sinf(rads);
circle.emplace_back(x, y);
}
node->drawPolygon(circle.data(), circle.size(), fillColor, borderWidth, bordercolor);
}
This is the function to calculate the position of edge point of circle and draw polygon. If you want to use it, you need to call like following,
auto canvas = DrawNode::create();
drawSector(canvas, cocos2d::Vec2(400, 400), 100, 60, cocos2d::Color4F::GREEN, 2, cocos2d::Color4F::BLUE, 100);
this->addChild(triangle);
The result would be like this. I think the code will help your problem.
I try to draw a round rectangle with drawRoundedRect method directly in a QPixmap (no render engine involve here exept pure Qt one ...), I double check the size of the rectangle versus the size of my pixmap :
Pixmap : QSize(50, 73)
Rectangle: QRect(0,0 48x11)
See plenty of space ...
EDIT: some code
pixmap = QPixmap(50,73); //example size that match my case
QRectF rect(0,0,48,11);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setWorldMatrixEnabled(false);
painter.setPen(QPen()); //no pen
painter.setBrush(QBrush(color));
painter.drawRoundedRect(rect, 2.0, 2.0);
I disabled world transformation ...
I set set transformation to unity ...
I tried several radius (1.0,2.0,3.0,4.0) ...
I change pen width, brush color ...
But it always ends with a rectamgle with 4 diferent corners ! Like that :
I directly ouptut the pixmap to a file to be sure I wasn't scraping it during the display ... same shape.
Anyone know about Qt round rectangle with small radius ? I saw somthing about it a long time ago but I don't remenber how to deal with it !
It looks like you're not using anti-aliasing (i.e. the QPainter::Antialiasing render hint). This is a Qt quirk that occurs without it. From what I've seen/heard, the Qt devs aren't terribly concerned with fixing this (most people want anti-aliasing anyway).
The work-around (besides just using anti-aliasing) is to draw the rect yourself with QPainter::drawLine() and QPainter::drawArc(). You might have to play with numbers until it looks right -- straight calculations tend to come out a pixel or two off. Also, you might find that even with this method the lower right corner is never exactly the same as the other corners.
If you're feeling mildly ambitious, you could try fixing this and submitting a patch to Qt.
Update: Arc drawing results changed in Qt 5. In my experience, it's a big improvement.
I know this is an old problem but for Qt5 users calling setRenderHint(QPainter::Qt4CompatiblePainting); on the QPainter seems to solve the problem.
Edit:
I found a solution for generating a perfect rounded rectangle together with border color and it looks the same as the rounded rectangles used by QPushButton's border for example. This is how I implemented the paintEvent to achieve this:
void MyButtonGroup::paintEvent(QPaintEvent * e)
{
int borderSize = 5;
QColor borderColor = Qt::red;
QColor backgroundColor = Qt::blue;
int borderRadius = 3;
QPen pen;
pen.setWidth(borderSize);
pen.setColor(borderColor);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(pen);
QRectF rect(rect().x() + borderSize / 2,
rect().y() + borderSize / 2,
rect().width() - borderSize,
rect().height() - borderSize);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderSize,
borderSize);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QBrush brush(backgroundColor);
pen.setBrush(brush);
painter.setBrush(brush);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderRadius,
borderRadius);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QWidget::paintEvent(e);
}
I'm posting this because I found it a bit hard to achieve this result:
Try adding half a pixel offset (e.g.: rect.translated(0.5,0.5) ):
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,false);
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0, 2.0 );
I suppose this has to do with the coordinate system placing an integer value between two pixels.
If you draw with antialiasing and use a pen of 1 pixel width then drawing at exact integer coordinates results in lines of 2 pixel width instead.
Only with this 0.5 pixel offset you'll get lines that are exactly 1 pixel wide.
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,true);
painter.setBrush(Qt::NoBrush);
painter.setPen( Qt::white );
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0,2.0 );
Best way do draw RoundRect is Path.
http://developer.nokia.com/community/wiki/Qt_rounded_rect_widget
void fillRoundRect(QPainter& painter, QRect r, int radius)
{
painter.setRenderHint(QPainter::Antialiasing,true);
QPainterPath rounded_rect;
rounded_rect.addRoundRect(r, radius, radius);
painter.setClipPath(rounded_rect);
painter.fillPath(rounded_rect,painter.brush());
painter.drawPath(rounded_rect);
}
try to play with render hints
1) disable antiAliasing;
2) enable SmoothPixmapTransform
but still no guarantee that it will help.
I have tried all tips from answers here but nothing works for me. But based on these code snippets I have found following solution:
As default set m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true) and only for rounded rectangles with width%2==0 disable it.
QRect rect = ConvertRectangle(rectangle);
int nPenWidth = m_pPainter->pen().width();
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, false);
m_pPainter->drawRoundedRect(rect, dbRadiusX, dbRadiusY);
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true);