Related
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
In my code I cannot draw a String at precise coordinates. Its upper left corner does not start at the given coordinates but somewhere else. However if I draw a rectangle from the same given coordinates it is well placed. How on earth can this behaviour be possible ?
Here is my code I call in the beforeShow() method :
Image photoBase = fetchResourceFile().getImage("Voiture_4_3.jpg");
Image watermark = fetchResourceFile().getImage("Watermark.png");
f.setLayout(new LayeredLayout());
final Label drawing = new Label();
f.addComponent(drawing);
// Image mutable dans laquelle on va dessiner (fond blancpar défaut)
Image mutableImage = Image.createImage(photoBase.getWidth(), photoBase.getHeight());
// Paint all the stuff
paintAll(mutableImage.getGraphics(), photoBase, watermark, photoBase.getWidth(), photoBase.getHeight());
drawing.getUnselectedStyle().setBgImage(mutableImage);
drawing.getUnselectedStyle().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
// Save the graphics
// Save the image with the ImageIO class
long time = new Date().getTime();
OutputStream os;
try {
os = Storage.getInstance().createOutputStream("screenshot_" + Long.toString(time) + ".png");
ImageIO.getImageIO().save(mutableImage, os, ImageIO.FORMAT_PNG, 1.0f);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
And the paintAll method
public void paintAll(Graphics g, Image background, Image watermark, int width, int height) {
// Full quality
float saveQuality = 1.0f;
// 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.drawImage(background, 0, 0);
imageGraphics.drawImage(watermark, 0, 0);
imageGraphics.setColor(0xFF0000);
// Upper left corner
imageGraphics.fillRect(0, 0, 10, 10);
// Lower right corner
imageGraphics.setColor(0x00FF00);
imageGraphics.fillRect(width - 10, height - 10, 10, 10);
imageGraphics.setColor(0xFF0000);
Font f = Font.createTrueTypeFont("Geometos", "Geometos.ttf").derive(220, Font.STYLE_BOLD);
imageGraphics.setFont(f);
// Draw a string right below the M from Mercedes on the car windscreen (measured in Gimp)
int w = 0, h = 0;
imageGraphics.drawString("HelloWorld", w, h);
// Coin haut droit de la string
imageGraphics.setColor(0x0000FF);
imageGraphics.fillRect(w, h, 20, 20);
// Draw the complete image on your Graphics object g (the screen I guess)
g.drawImage(imageBuffer, 0, 0);
}
Result for w = 0, h = 0 (no apparent offset) :
Result for w = 841 , h = 610 (offset appears on both axis : there is an offset between the blue point near Mercedes M on the windscreen and the Hello World String)
EDIT1:
I also read this SO question for Android where it is advised to convert the dpi into pixel. Does it also applies in Codename One ? If so how can I do that ? I tried
Display.getInstance().convertToPixel(measureInMillimeterFromGimp)
without success (I used mm because the javadoc tells that dpi is roughly 1 mm)
Any help would be appreciated,
Cheers
Both g and imageGraphics are the same graphics created twice which might have some implications (not really sure)...
You also set the mutable image to the background of a style before you finished drawing it. I don't know if this will be the reason for the oddities you are seeing but I would suspect that code.
Inspired from Gabriel Hass' answer I finally made it work using another intermediate Image to only write the String at (0 ; 0) and then drawing this image on the the imageBuffer Image now at the right coordinates. It works but to my mind drawString(Image, Coordinates) should directly draw at the given coordinates, shouldn't it #Shai ?
Here is the method paintAll I used to solve my problem (beforeShow code hasn't changed) :
// Full quality
float saveQuality = 1.0f;
String mess = "HelloWorld";
// 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.drawImage(background, 0, 0);
imageGraphics.drawImage(watermark, 0, 0);
imageGraphics.setColor(0xFF0000);
// Upper left corner
imageGraphics.fillRect(0, 0, 10, 10);
// Lower right corner
imageGraphics.setColor(0x00FF00);
imageGraphics.fillRect(width - 10, height - 10, 10, 10);
// Create an intermediate image just with the message string (will be moved to the right coordinates later)
Font f = Font.createTrueTypeFont("Geometos", "Geometos.ttf").derive(150, Font.STYLE_BOLD);
// Get the message dimensions
int messWidth = f.stringWidth(mess);
int messHeight = f.getHeight();
Image messageImageBuffer = Image.createImage(messWidth, messHeight, 0xffffff);
Graphics messageImageGraphics = messageImageBuffer.getGraphics();
messageImageGraphics.setColor(0xFF0000);
messageImageGraphics.setFont(f);
// Write the string at (0; 0)
messageImageGraphics.drawString(mess, 0, 0);
// Move the string to its final location right below the M from Mercedes on the car windscreen (measured in Gimp)
int w = 841, h = 610;
imageGraphics.drawImage(messageImageBuffer, w, h);
// This "point" is expected to be on the lower left corner of the M letter from Mercedes and on the upper left corner of the message string
imageGraphics.setColor(0x0000FF);
imageGraphics.fillRect(w, h, 20, 20);
// Draw the complete image on your Graphics object g
g.drawImage(imageBuffer, 0, 0);
I have an OpenGL application I'm running in Xcode 7.0.1 on a 2011 MacBookPro.
I recently upgraded from OS 10.10 to 10.11 El Capitan, and now windows with NSViews are throwing this error (Deployment Target 10.11, 10.10 or 10.9):
invalid context 0x0. If you want to see the backtrace, please set
CG_CONTEXT_SHOW_BACKTRACE environmental variable.
In the NSView drawRect method, I get the CGContext with the following statement:
CGContextRef context = [[NSGraphicsContext currentContext] CGContext];
Also, my GLSL 4.1 shaders don't write anything to the OpenGL window anymore.
This code was not giving me any problems before I upgraded to El Capitan, and the (almost) exact same code runs fine without errors on a 2012 MacBookPro, OS 10.10, Xcode 6.4, Deployment Target 10.9 or 10.10. The only code difference is that the graphics context in the NSView drawRect method is obtained with:
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
I think my problem might be that the computer that has problems is a 2011 MacBookPro, and Metal requires 2012 or later. I'm not using Metal, but I still think this might be a factor.
Is the computer vintage the problem that generates the error, or is there some other syntax I should be using to get the graphics context?
I have no idea why the GLSL shaders don't work anymore. Any ideas there?
The application main window is an OpenGL view, but I use many NSViews in pop up user interface windows, custom buttons and various other uses. Below is the complete code for one of the simplest of those windows. Again, this all ran fine before I upgraded to EC and Xcode 7.0.1.
#implementation StatusView
// **************************** Init **********************************
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
}
return self;
}
// ************************** Draw Rect ********************************
- (void)drawRect:(NSRect)dirtyRect
{
if(windowManager)
{
CGContextRef context = [[NSGraphicsContext currentContext] CGContext];
// Define a color space variable
CGColorSpaceRef rgbColorspace = CGColorSpaceCreateDeviceRGB();
// Get the bounds
NSRect nsBounds = [self bounds];
// Set the graphics bounds
CGRect cgBounds = NSRectToCGRect(nsBounds);
// ****** Draw the Background in Transparent Black
CGContextSetRGBFillColor(context, 0.0f, 0.0f, 0.0f, 0.0f);
CGContextFillRect(context, cgBounds);
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Create a color that will be added as an attribute to the attrString for normal text.
CGFloat textColorComponents[] = { 1.0, 1.0, 1.0, 1.0 };
CGColorRef whiteColor = CGColorCreate(rgbColorspace, textColorComponents);
// Create a color that will be added as an attribute to the attrString for invisible text.
CGFloat invisibleTextColorComponents[] = { 0.0, 0.0, 0.0, 0.0 };
CGColorRef invisibleColor = CGColorCreate(rgbColorspace, invisibleTextColorComponents);
// Create a font for normal text.
CFStringRef stringFontName = CFSTR("AppleCasual");
CTFontRef stringFont = CTFontCreateWithName(stringFontName, 18.0, NULL);
CGContextSetRGBFillColor(context, 0.0f, 0.0f, 0.0f, 0.0f);
CGContextFillRect(context, cgBounds);
// ************* Box the Window in Gray ***************************
CGContextMoveToPoint(context, 0.0,1.0);
CGContextAddLineToPoint(context,0.0, cgBounds.size.height - 1.0);
CGContextAddLineToPoint(context,cgBounds.size.width - 2.0, cgBounds.size.height - 1.0);
CGContextAddLineToPoint(context,cgBounds.size.width - 2.0, 1.0);
CGContextAddLineToPoint(context,0.0, 1.0);
CGContextSetLineWidth(context, 2.0);
CGContextSetRGBStrokeColor(context, 0.7, 0.7, 0.7, 1.0);
CGContextStrokePath(context);
// *********** Draw String1
CGPoint endingTextPoint;
if(windowManager->statusTextBox1String)
{
// Create a mutable attributed string with a max length of 0 for normal text.
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
// Create a path which will bound the area where you will be drawing text.
CGMutablePathRef invisibleTextPath = CGPathCreateMutable();
// Create a path which will bound the area where you will be drawing text.
CGMutablePathRef string1TextPath = CGPathCreateMutable();
// Initialize a string.
CFStringRef textString = (__bridge CFStringRef)windowManager->statusTextBox1String;
CFIndex textStringLength = CFStringGetLength (textString);
// Measure the string length
CGRect invisibleTextBounds = CGRectMake(0.0, 0.0, cgBounds.size.width, 30.0);
CGPathAddRect(invisibleTextPath, NULL, invisibleTextBounds);
// Copy the textString into attrString
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), textString);
// Set the color and font.
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTForegroundColorAttributeName, invisibleColor);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTFontAttributeName, stringFont);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), invisibleTextPath, NULL);
// Draw the specified frame in the given context.
CTFrameDraw(frame, context);
endingTextPoint = CGContextGetTextPosition(context);
// Draw the Text
// Set a rectangular path.
CGRect textBounds = CGRectMake((cgBounds.size.width / 2.0) - (endingTextPoint.x / 2.0), 140.0, cgBounds.size.width, 30.0);
CGPathAddRect(string1TextPath, NULL, textBounds);
// Copy the textString into attrString
CFAttributedStringReplaceString (attrString, CFRangeMake(0, textStringLength), textString);
// Set the color and fontof the first chars.
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTForegroundColorAttributeName, whiteColor);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTFontAttributeName, stringFont);
// Create the framesetter with the attributed string.
framesetter = CTFramesetterCreateWithAttributedString(attrString);
// Create a frame.
frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), string1TextPath, NULL);
// Draw the specified frame in the given context.
CTFrameDraw(frame, context);
CFRelease(string1TextPath);
CFRelease(invisibleTextPath);
CFRelease(frame);
CFRelease(framesetter);
CFRelease(attrString);
}
// ****************** Draw String 2
if(windowManager->statusTextBox2String)
{
CGContextSetRGBFillColor(context, 0.0f, 0.0f, 0.0f, 0.0f);
CGContextFillRect(context, cgBounds);
// ********** Box the Window in Gray ***********************
CGContextMoveToPoint(context, 0.0,1.0);
CGContextAddLineToPoint(context,0.0, cgBounds.size.height - 1.0);
CGContextAddLineToPoint(context,cgBounds.size.width - 2.0, cgBounds.size.height - 1.0);
CGContextAddLineToPoint(context,cgBounds.size.width - 2.0, 1.0);
CGContextAddLineToPoint(context,0.0, 1.0);
CGContextSetLineWidth(context, 2.0);
CGContextSetRGBStrokeColor(context, 0.7, 0.7, 0.7, 1.0);
CGContextStrokePath(context);
// Create a mutable attributed string with a max length of 0 for normal text.
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
// Create a path which will bound the area where you will be drawing text.
CGMutablePathRef invisibleTextPath = CGPathCreateMutable();
// Create a path which will bound the area where you will be drawing text.
CGMutablePathRef string2TextPath = CGPathCreateMutable();
// Initialize a string.
CFStringRef textString = (__bridge CFStringRef)windowManager->statusTextBox2String;
CFIndex textStringLength = CFStringGetLength (textString);
// Measure the string length
CGRect invisibleTextBounds = CGRectMake(0.0, 0.0, cgBounds.size.width, 130.0);
CGPathAddRect(invisibleTextPath, NULL, invisibleTextBounds);
// Copy the textString into attrString
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), textString);
// Set the color and font
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTForegroundColorAttributeName, invisibleColor);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTFontAttributeName, stringFont);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), invisibleTextPath, NULL);
// Draw the specified frame in the given context.
CTFrameDraw(frame, context);
endingTextPoint = CGContextGetTextPosition(context);
// Draw the Text
// Set a rectangular path.
CGRect textBounds = CGRectMake((cgBounds.size.width / 2.0) - (endingTextPoint.x / 2.0), 100.0, cgBounds.size.width, 30.0);
CGPathAddRect(string2TextPath, NULL, textBounds);
// Copy the textString into attrString
CFAttributedStringReplaceString (attrString, CFRangeMake(0, textStringLength), textString);
// Set the color and fontof the first chars.
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTForegroundColorAttributeName, whiteColor);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLength), kCTFontAttributeName, stringFont);
// Create the framesetter with the attributed string.
framesetter = CTFramesetterCreateWithAttributedString(attrString);
// Create a frame.
frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), string2TextPath, NULL);
// Draw the specified frame in the given context.
CTFrameDraw(frame, context);
CFRelease(string2TextPath);
CFRelease(invisibleTextPath);
CFRelease(frame);
CFRelease(framesetter);
CFRelease(attrString);
}
CFRelease(stringFont);
CFRelease(whiteColor);
CFRelease(invisibleColor);
CGContextFlush(context);
}
return;
}
#end
This is an image of what this particular window looks like:
The behavior is not consistent. For example, this status window should come up for each of several steps in the calculation process, but it looks like only every other window is displayed (e.g. steps 2, 4 and 6, but not 1, 3 or 5).
There are LOTS of errors being generated, but this is a sample backtrace from one of the errors:
Oct 9 10:23:30 WispFractals3D[746] <Error>: CGContextRestoreGState: invalid context 0x0. Backtrace:
<-[StatusWindowController updateStatusProgress:]+228>
<-[AppController updateStatusProgress:]+64>
<-[AppController runTheFractal:]+804>
<_os_activity_initiate+75>
<-[NSApplication sendAction:to:from:]+460>
<-[NSMenuItem _corePerformAction]+336>
<-[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:]+114>
<_os_activity_initiate+75>
<-[NSMenu performActionForItemAtIndex:]+131>
<-[NSMenu _internalPerformActionForItemAtIndex:]+35>
<-[NSCarbonMenuImpl _carbonCommandProcessEvent:handlerCallRef:]+107>
<NSSLMMenuEventHandler+708>
<_ZL23DispatchEventToHandlersP14EventTargetRecP14OpaqueEventRefP14HandlerCallRec+1231>
<_ZL30SendEventToEventTargetInternalP14OpaqueEventRefP20OpaqueEventTargetRefP14HandlerCallRec+404>
<SendEventToEventTarget+40>
<_ZL18SendHICommandEventjPK9HICommandjjhPKvP20OpaqueEventTargetRefS5_PP14OpaqueEventRef+411>
<SendMenuCommandWithContextAndModifiers+59>
<SendMenuItemSelectedEvent+188>
<_ZL19FinishMenuSelectionP13SelectionDataP10MenuResultS2_+96>
<_ZL14MenuSelectCoreP8MenuData5PointdjPP13OpaqueMenuRefPt+711>
<_HandleMenuSelection2+460>
<_NSHandleCarbonMenuEvent+277>
<_DPSNextEvent+1906>
<-[NSApplication _nextEventMatchingEventMask:untilDate:inMode:dequeue:]+454>
<-[NSApplication run]+682>
<NSApplicationMain+1176>
<main+34>
The .xib file looks like this:
There's a custom progress indicator in this window (bounded by the two white squares) that's invisible, but it's there.
Sample code to display this window looks like:
[self showStatusWindowWithString1:#"Calculation Complete" String2:timeMessage ButtonOn:YES AbortOn:NO ProgressOn:NO ProgressMax:100.0 Title:#"Fractal Run Time"];
and the code for the showStatusWindow method is:
- (void)showStatusWindowWithString1:(NSString *)string1 String2:(NSString *)string2 ButtonOn:(BOOL)buttonon AbortOn:(BOOL)aborton ProgressOn:(BOOL)progresson ProgressMax:(double)progressmax Title:(NSString *)title
{
statusWindowTitle = title;
statusTextBox1String = string1;
statusTextBox2String = string2;
statusButtonOn = buttonon;
abortOn = aborton;
statusProgressOn = progresson;
statusProgressMax = progressmax;
if (!statusWindowController)
{
statusWindowController = [[StatusWindowController alloc] initWithWindowNibName:#"StatusWindow" Manager:self];
}
[[statusWindowController window] setTitle:statusWindowTitle];
[statusWindowController showWindow:self];
[fileStatusWindow makeKeyAndOrderFront:self];
appDelegate->fileStatusWindowOpenFlag = YES;
[appDelegate checkFlags];
return;
}
Ken Thomases diagnosed that the backtrace indicated that invalid context errors were being generated in [StatusWindowController updateStatusProgress:] (this updates a custom progress indicator).
I changed this method from:
- (void) updateStatusProgress:(double)statusprogress
{
[statusProgressIndicator setDoubleValue:statusprogress];
[statusProgressIndicator drawRect:[statusProgressIndicator bounds]];
}
To:
- (void) updateStatusProgress:(double)statusprogress
{
[statusProgressIndicator setDoubleValue:statusprogress];
[statusProgressIndicator setNeedsDisplay:YES];
}
It looked to me like the error messages were being generated in multiple places, but this one change stopped all error messages.
My next problem, was that the status window (image above) was not displaying every time it should, only sometimes. Again, this all worked fine in Yosemite, Xcode 6.4.
I've now found that when the status window does not display, I can get it to display by running it modally. Strange.
The last problem I'm working through is that the GLSL shaders are not writing to the OpenGL view as they should. I've tested that the shaders are loading and running by adding a line at the end of the fragment shader: fragColor = vec4(1.0, 0.0, 0.0, 1.0);, which turned the view totally red as it should.
The fragment shader actually should be sampling from a texture, so I loaded this texture with all (255, 0, 0, 255) to test the sampling by putting a simple sampling statement at the end of the fragment shader:
fragColor = texture(Texture, texCoord).rgba;
but nothing gets written, so there must be a problem with loading the texture into the shader. I'm working on that now.
The problem causing the "invalid context 0x0" messages was that you were directly calling -drawRect:. This has never been valid. -drawRect: is called by the framework after it has set up the appropriate graphics context and things like the coordinate transform to put the drawing in the right place in the window and clipping to the bounds of the view. If you call it at other times, then no context has been set up and nothing good can come of it.
If you must redraw immediately, you could call one of the -display... methods.
However, it's usually best to do as you've now done: simply mark the view as needing display and let Cocoa redraw it in due course.
You should probably post separate questions for the other issues.
I just did a quick build of one of my apps which hadn't been rebuilt under EC before. It all worked fine.
Perhaps you can post a bit more detail. The code and crash point?
I'm working on an application that draws handwritten strokes. Strokes are internally stored as vectors of points and they can be transformed into std::vector<Gdiplus::Point>. Points are so close to each other, that simple drawing of each point should result into an image of continual stroke.
I'm using Graphics.DrawEllipse (GDI+) method to draw these points. Here's the code:
// prepare bitmap:
Bitmap *bitmap = new Gdiplus::Bitmap(w, h, PixelFormat32bppRGB);
Graphics graphics(bitmap);
// draw the white background:
SolidBrush myBrush(Color::White);
graphics.FillRectangle(&myBrush, 0, 0, w, h);
Pen blackPen(Color::Black);
blackPen.SetWidth(1.4f);
// draw stroke:
std::vector<Gdiplus::Point> stroke = getStroke();
for (UINT i = 0; i < stroke.size(); ++i)
{
// draw point:
graphics.DrawEllipse(&blackPen, stroke[i].X, stroke[i].Y, 2, 2);
}
At the end I just save this bitmap as a PNG image and sometimes the following problem occurs:
When I saw this "hole" in my stroke, I decided to draw my points again, but this time, by using ellipse with width and height set to 1 by using redPen with width set to 0.1f. So right after the code above I added the following code:
Pen redPen(Color::Red);
redPen.SetWidth(0.1f);
for (UINT i = 0; i < stroke.size(); ++i)
{
// draw point:
graphics.DrawEllipse(&redPen, stroke[i].X, stroke[i].Y, 1, 1);
}
And the new stoke I've got looked like this:
When I use Graphics.DrawRectangle instead of DrawEllipse while drawing this new red stroke, it never happens that this stroke (drawn by drawing rectangles) would have different width or holes in it:
I can't think of any possible reason, why drawing circles would result into this weird behaviour. How come that stroke is always continual and never deformed in any way when I use Graphics.DrawRectangle?
Could anyone explain, what's going on here? Am I missing something?
By the way I'm using Windows XP (e.g. in case it's a known bug). Any help will be appreciated.
I've made the wrong assumption that if I use Graphics.DrawEllipse to draw a circle with radius equal to 2px with pen of width about 2px, it will result in a filled circle with diameter about 4-5 px being drawn.
But I've found out that I actually can't rely on the width of the pen while drawing a circle this way. This method is meant only for drawing of border of this shape, thus for drawing filled ellipse it's much better to use Graphics.FillEllipse.
Another quite important fact to consider is that both of mentioned functions take as parameters coordinates that specify "upper-left corner of the rectangle that specifies the boundaries of the ellipse", so I should subtract half of the radius from both coordinates to make sure the original coordinates specify the middle of this circle.
Here's the new code:
// draw the white background:
SolidBrush whiteBrush(Color::White);
graphics.FillRectangle(&whiteBrush, 0, 0, w, h);
// draw stroke:
Pen blackBrush(Color::Black);
std::vector<Gdiplus::Point> stroke = getStroke();
for (UINT i = 0; i < stroke.size(); ++i)
graphics.FillEllipse(&blackBrush, stroke[i].X - 2, stroke[i].Y - 2, 4, 4);
// draw original points:
Pen redBrush(Color::Red);
std::vector<Gdiplus::Point> origStroke = getOriginalStroke();
for (UINT i = 0; i < origStroke.size(); ++i)
graphics.FillRectangle(&redBrush, origStroke[i].X, origStroke[i].Y, 1, 1);
which yields following result:
So in case someone will face the same problem as I did, the solution is:
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);