I have written a simple Cocoa app for Mac OS X (10.7) using Xcode 4.2. All the app does is create a window with a scrollable array of sub-Views in it, each representing a page to draw stuff on at a very low level. The sub-View's isFlipped method delivers YES, so the origin of every sub-View is the upper left corner. Using various Core Graphics routines, I'm able to draw lines and fill paths and all that fun PostScripty stuff successfully.
It's drawing glyphs from a given font that's got me confused.
Here's the complete code, cut-n-pasted from the program, for the sub-View's -drawRect: method --
- (void)drawRect:(NSRect)dirtyRect
{
// Start with background color for any part of this view
[[NSColor whiteColor] set];
NSRectFill( dirtyRect );
// Drop down to Core Graphics world, ensuring there's no side-effects
context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
{
//CGFontRef theFont = CGFontCreateWithFontName(CFSTR("American Typewriter"));
//CGContextSetFont(context, theFont);
CGContextSelectFont(context, "American Typewriter", 200, kCGEncodingMacRoman);
CGContextSetFontSize(context, 200);
// Adjust the text transform so the text doesn't draw upside down
CGContextSetTextMatrix(context, CGAffineTransformScale(CGAffineTransformIdentity, 1, -1));
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
CGContextSetRGBFillColor(context, 0.0, .3, 0.8, 1.0);
// Find the center of view's (not dirtyRect's) bounds
// View is 612 x 792 (nominally 8.5" by 11")
CGPoint centerPoint;
CGRect bds = [self bounds];
centerPoint.x = bds.origin.x + bds.size.width / 2;
centerPoint.y = bds.origin.y + bds.size.height / 2;
// Create arrays to hold glyph IDs and the positions at which to draw them.
#define glyphCount 1 // For now, just one glyph
CGGlyph glyphs[glyphCount];
CGPoint positions[glyphCount];
glyphs[0] = 40; // Glyph ID for '#' character in above font
positions[0] = centerPoint;
// Draw above center. This works.
CGContextShowGlyphsAtPoint(context, centerPoint.x, centerPoint.y - 200.0, glyphs, glyphCount);
// Draw at center. This works.
CGContextShowGlyphsAtPoint(context, positions[0].x, positions[0].y, glyphs, glyphCount);
// Draw below center. This fails (draws nothing). Why?
positions[0].y += 200.0;
CGContextShowGlyphsAtPositions(context, glyphs, positions, glyphCount);
}
CGContextRestoreGState(context);
}
What's got me pulling my hair out is that the first two glyph-drawing calls using CGContextShowGlyphsAtPoint() work fine as expected, but the third attempt using CGContextShowGlyphsAtPositions() never draws anything. So there are only two # symbols on the page, rather than three. This difference in behaviors doesn't depend on whether I've previously used CGContextSetFont() or CGContextSelectFont().
There must be some hidden change in state going on, or something very different under the hood w/r/t these two almost identical Core Graphics glyph-drawing routines, but all my experiments so far have not demonstrated what that might be.
Sigh. I just want to efficiently draw an array of glyphs at a corresponding array of positions in a view.
Any ideas what I'm getting wrong?
After much experimentation enabled by being whacked upside the head by Peter Hosey's response (even though some of it isn't quite right, many thanks!), here's the source of my confusion and an explanation I'm pretty sure is correct (well, the code is doing what I expect it to, anyway).
In the usual higher-level PostScript path/drawing model, drawing a character updates the current point (path end) to the position where a next character might appear, leaving the current user-space transform the same. But under the hood, the text matrix transform is translated by the glyph's width (or more accurately by an advance vector) so that the next character to be drawn can start at, or with respect to, a new text origin. The text matrix's scale factors remain unchanged after translation.
So the initial setup call to CGContextSetTextMatrix() to flip the vertical sense of the text matrix is still necessary (if user-space is similarly flipped), because otherwise both glyph-collection drawing routines will draw the glyphs upside-down w/r/t path drawing, no matter where the text drawing starts or which drawing routine is used.
Neither of the two glyph collection drawing routines affects the current path. They are lower-level than that. I found that I could intersperse either routine among path construction calls without affecting a path's position or shape.
In the code posted above, the position data that CGContextShowGlyphsAtPositions() uses to draw the glyph collection are all relative to the user-space point corresponding to the current text matrix's origin, which was translated to the right of the previously drawn '#' glyph. Because I was using such a large font size, position[0] was causing the next '#' glyph to be drawn outside the view's bounds, so it wasn't visible, but it was being drawn.
But there's still some nuances among the two routines. CGContextShowGlyphsAtPositions() can never be used to place glyphs at any absolute user-space position. So how do you tell it where to start? The answer (or at least one answer) is that CGContextShowGlyphsAtPoint() updates the origin of the text matrix to the given user-space point even if there are no glyphs to draw. And CGContextShowGlyphsAtPoint() must translate the text matrix after each glyph it draws, because what would be the point (so to speak) of drawing the entire glyph collection on top of one another.
So one can "move" to a non-path point in user-space using CGContextShowGlyphsAtPoint() with a glyph count of 0, and then one can call CGContextShowGlyphsAtPositions() (any number of times) with a vector of positions each of which will be treated relative to the text matrix's origin (or really, the user-space point corresponding to it) without the text matrix origin being updated at all when CGContextShowGlyphsAtPositions() returns.
Finally, note that the position data provided to CGContextShowGlyphsAtPositions() is in user-space coordinates. A comment in Apple's header file for these routines expressly says so.
One possibility is this, from the CGContextShowGlyphsAtPositions document:
The position of each glyph is specified in text space, and, as a consequence, is transformed through the text matrix to user space.
The text matrix is a separate property of the context, distinct from the graphics state's current transformation matrix.
It doesn't say that about CGContextShowGlyphsAtPoint:
This function displays an array of glyphs at the specified position in the user space.
(Emphasis added to both quotes.)
So, your text matrix is not actually used when you show glyphs from a single point.
But then, when you show glyphs at an array of positions, it is used, and you see the symptom of a wrong matrix. Specifically, your matrix to try to flip the text back the other way is wrong: it flips the coordinate system upside down. You are drawing outside of the view.
(Try setting it to scale by 0.5 instead of -1 and you'll see what I mean.)
My recommendation is to take out your CGContextSetTextMatrix call.
Related
When reading source code which draws lines in Windows using GDI it is relatively common to see FillRect() being used despite the only purpose being to draw a line. But the end product of drawing a line with a width value and a filled rectangle are quite similar aren't they?
FillRect() is 1 function call, Using MoveToEx & LineTo requires 2
Which is more efficient when needing to draw a line, using FillRect() or MoveToEx() and LineTo()?
In the most common cases, FillRect will do the same thing as MoveToEx and LineTo for perfectly horizontal and vertical rectangles. Nowadays, there are so many layers of indirection between GDI and the screen that the performance difference is almost certainly not relevant.
Drawing operations in GDI typically depend on the current "state" of the device context (DC). Lines are drawn with whichever pen is currently selected into the DC. The pen determines the color, style (solid, dashed, etc.), thickness, end caps, etc.
FillRect, however, doesn't depend on much of the DC state. All drawing primitives depend on the mapping mode and clipping region, but, unlike lines, FillRect doesn't even depend on the selected brush, since you get to provide one right in the call.
Changing state (which objects are selected into the DC) can be a lot of work. If you know you want a horizontal line, 2 pixels thick, in blue, it's a tad easier to use FillRect than to first create a pen, select it into the DC, draw your line, select the pen back out, and then decide how to manage the lifetime of that pen (when do you delete it?). If the rest of the drawing is a bunch of dashed yellow lines with round endcaps, not having to keep switching state can make the code simpler.
I have a general question (I know I should present specific code with a problem, but in my case the problem is of a more general nature).
In Processing, let's say I make an ellipse:
ellipse(30, 30, 10, 10);
Now, is there a way to get the pixels where this ellipse is on the canvas? The reason would be to have a way of creating user interaction with the mouse (for instance). So when someone clicks the mouse over the ellipse, something happens.
I thought of turning everything into objects and use a constructor to somehow store the position of the shape, but this is easier said than done, particularly for more complex shapes. And that is what I am interested in. It's one thing to calculate the position of an ellipse, but what about more complex shapes? Are there any libraries?
Check out the geomerative library. It has a way to check whether the mouse is inside any SVG shape. I can't remember off the top of my head but it works something like you make a shape:
myShape = RG.loadShape("shape.svg");
and a point:
RPoint p = new RPoint(mouseX, mouseY);
and the boolean function contains() will tell you if the point is inside the shape:
myShape.contains(p);
It's better to use a mathematical formula than pixel-by-pixel checking of the mouse position (it's much faster, and involves less code).
For a perfect circle, you can calculate the Euclidean distance using Pythagoras' theorem. Assume your circle is centred at position (circleX,circleY), and has a radius (not diameter) of circleR. You can check if the mouse is over the circle like this:
if(sq(mouseX-circleX)+sq(mouseY-circleY) <= sq(circleR)) {
// mouse is over circle
} else {
// mouse is not over circle
}
This approach basically imagines a right-angled triangle, where the hypotenuse (the longest side) runs from the centre of the circle to the mouse position. It uses Pythagoras' theorem to calculate the length of that hypotenuse, and if it's less than the circle's radius then the mouse is inside the circle. (It includes a slight optimisation though -- it's comparing squares to avoid doing a square root, as that can be comparatively slow.)
An alternative to my original mathematical answer also occurred to me. If you can afford the memory and processing power of drawing all your UI elements twice then you can get good results by using a secondary buffer.
The principle involves having an off-screen graphics buffer (e.g. using PGraphics). It must be exactly the same size as the main display, and have anti-aliasing disabled. Draw all your interactive UI elements (buttons etc.) to this buffer. However, instead of drawing them the normal way, give each one a unique colour which it uses for fill and stroke (don't add any text or images... just solid colours). For example, one button might be entirely red, and another entirely green. Any other RGB value works, as long as each item has a unique colour. Make sure the background has a unique colour too.
The user never sees that buffer, so don't draw it to the screen (unless you're debugging or something). When you want to detect what item the mouse is over, just lookup the mouse position on that off-screen buffer. Get the pixel colour at that location, and match it to the UI element.
After you've done all that, just go ahead and draw everything to the main display as normal.
It's worth noting that you can cut-down the processing time of this approach a lot if your UI elements never (or rarely) move. You only need to redraw the secondary buffer when something appears/disappears, animates, or changes size/position.
so i have a uiview that is initialized with a frame that has the height and width that is present for the user, i want the user to be able to draw inside this frame but when the user presses a button, i want the view to cut off that extra wasted space so that the frame is only as big as what the user was drawing. I tried to do something like this
CGRect boundbox = CGPathGetBoundingBox([myPath CGPath]);
boundbox.origin.x = self.frame.origin.x;
boundbox.origin.y = self.frame.origin.y;
self.frame = boundbox;
However, this does not remove that extra wasted space, it only resizes the view, so that the drawn content looks smaller than previously. What i would like to do instead is to remove
that "whitespace", i was thinking if it could be possible to scale up the content of the uiview, but im not sure.
To clarify what i mean:
The red border is the area / frame that the user can draw on, the text in the middle is a drawing, when the user presses a button, i want the frame to only encircle the drawing like in figure 2.
Now lets say i have the following scenario, i have drawn a circle on the middle of the screen.
When i then press the button, the scale remains the same but the circle is still in the same position but we have now changed the draw area, so the circle / drawing will look like its cut off like in figure 4.
What i want to do is to move the drawing / bezier path so that it is positioned in the middle of the frame. So that the red area encircles the blue circle.
[EDIT]
Given your drawings. A UIView will not re-position items in it when you change it's frame property (or it's CGRect). In this case you will need to track the items drawn YOURSELF, and then when the button is pressed perform the object translations yourself.
What that means is you will have to find the object that is left most, the object that is topmost, then move all objects left by that amount, and up by that amount so that all objects are (as a grouping) top-left aligned within the view's frame. After this you will need to self recognize which object is the right most touching and which object is the bottom most touching.
NOW, since you have already moved the items left-top, the right most point will define your frame width, and the bottom most point will define your frame height.
IF YOU SO DESIRE, you should be able to zoom in using the properties below after you have done this.
[First Answer]
If I understand your question correctly, you may want to still perform your box frame manipulation, but if you wish to scale you may want to look into the
contentScaleFactor or
contentStretch
properties.
contentScaleFactor should scale both dimensions based upon a singular floating point value (i.e. xWidth * scaleFactor, yHeight * scale factor).
contentStretch is a CGRect which means that it should scale each dimension (axis) separately.
I'm developing a custom control. One of the requirements is to draw lines. Although this works, I noticed that my 1 pixel wide lines do not really look like 1 pixel wide lines - I know, they're not really pixels but you know what I mean. They look more like two or three pixels wide. This becomes very apparent when I draw a dashed line with a 1 pixel dash and a 2 pixel gap. The 1 pixel dashes actually look like tiny lines in stead of dots.
I've read the Cocoa Drawing documentation and although Apple mentions the setLineWidth method, changing the line width to values smaller than 1.0 will only make the line look more vague and not thinner.
So, I suspect there's something else influencing the way my lines look.
Any ideas?
Bezier paths are drawn centered on their path, so if you draw a 1 pixel wide path along the X-coordinate, the line actually draws along Y-coordinates { -0.5, 0.5 } The solution is usually to offset the coordinate by 0.5 so that the line is not drawn in the sub pixel boundaries. You should be able to shift your bounding box by 0.5 to get sharper drawing behavior.
Francis McGrew already gave the right answer, but since I did a presentation on this once, I thought I'd add some pictures.
The problem here is that coordinates in Quartz lie at the intersections between pixels. This is fine when filling a rectangle, because every pixel that lies inside the coordinates gets filled. But lines are technically (mathematically!) invisible. To draw them, Quartz has to actually draw a rectangle with the given line width. This rectangle is centered over the coordinates:
So when you ask Quartz to stroke a rectangle with integral coordinates, it has the problem that it can only draw whole pixels. But here you see that we have half pixels. So what it does is it averages the color. For a 50% black (the line color) and 50% white (the background) line, it simply draws each pixel in grey:
This is where your washed-out drawings come from. The fix is now obvious: Don't draw between pixels, and you achieve that by moving your points by half a pixel, so your coordinate is centered over the desired pixel:
Now of course just offsetting may not be what you wanted. Because if you compare the filled variant to the stroked one, the stroke is one pixel larger towards the lower right. If you're e.g. clipping to the rectangle, this will cut off the lower right:
Since people usually expect the rectangle to stroke inside the specified rectangle, what you usually do is that you offset by 0.5 towards the center, so the lower right effectively moves up one pixel. Alternately, many drawing apps offset by 0.5 away from the center, to avoid overlap between the border and the fill (which can look odd when you're drawing with transparency).
Note that this only holds true for 1x screens. 2x Retina screens actually exhibit this problem differently, because each of the pixels below is actually drawn by 4 Retina pixels, which means they can actually draw the half-pixels. However, you still have the same problem if you want a sharp 0.5pt line. Also, since Apple may in the future introduce other Retina screens where e.g. every pixel is made up of 9 Retina pixels (3x), or whatever, you should really not rely on this. Instead, there are now API calls to convert rectangles to "backing aligned", which does this for you, no matter whether you're running 1x, 2x, or a fictitious 3x.
PS - Since I went to the hassle of writing this all up, I've put this up on my web site: http://orangejuiceliberationfront.com/are-your-rectangles-blurry-pale-and-have-rounded-corners/ where I'll update and revise this description and add more images.
The answer is (buried) in the Apple Docs:
"To avoid antialiasing when you draw a one-point-wide horizontal or vertical line, if the line is an odd number of pixels in width, you must offset the position by 0.5 points to either side of a whole-numbered position"
Hidden in Drawing and Printing Guide for iOS: iOS Drawing Concepts, though nothing that specific to be found in the current, standard (OS X) Cocoa Drawing Guide..
As for the effects of invoking setDefaultLineWidth: the docs also state that:
"A width of 0 is interpreted as the thinnest line that can be rendered on a particular device. The actual rendered line width may vary from the specified width by as much as 2 device pixels, depending on the position of the line with respect to the pixel grid and the current anti-aliasing settings. The width of the line may also be affected by scaling factors specified in the current transformation matrix of the active graphics context."
I found some info suggesting that this is caused by anti aliasing. Turning anti aliasing off temporarily is easy:
[[NSGraphicsContext currentContext] setShouldAntialias: NO];
This gives a crisp, 1 pixel line. After drawing just switch it on again.
I tried the solution suggested by Francis McGrew by offsetting the x coordinate with 0.5, however that did not make any difference to the appearance of my line.
EDIT:
To be more specific, I changed x and y coordinates individually and together with an offset of 0.5.
EDIT 2:
I must have done something wrong, as changing the coordinates with an offset of 0.5 actually does work. The end result is better than the one obtained by switching off the anti aliasing so I'll make Francis MsGrew's answer the accepted answer.
I am using pygtk and cairo (...wonderful stuff I must say. Thanks to all)
I am wondering how to present parts of images on my my cairo surface on a large drawingarea.
I would like to have areas within the displayed surface that appear to act clipped so I can scroll images through these areas without disturbing the surrounding drawn items.
Can I cut images for part drawing onto a surface or must I just get the drawing sequence in the proper order so that the images needing to be clipped are overlain and so part hidden as required and appear clipped?
thanks for any pointers
nick
Cairo is indeed wonderful! ctx.clip() is one way to do it, using a clipping path (shown below in just pycairo, where the final draw rectangle only hits the triangular clipped area).
You could also use a transfer mode of CAIRO_OPERATOR_OUT (I think), but I'm less familiar with the transfer modes. And that would only work on the first draw, since your content would fill the alpha a bit.
(Your suggestion of "Painting Order" will work fine, too, of course!)
import cairo
WIDTH, HEIGHT = 256, 256
surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
ctx = cairo.Context (surface)
ctx.rectangle(0,0,300,300)
ctx.set_source_rgb(0,0,0)
ctx.fill()
ctx.move_to(0,0)
ctx.line_to(200,90)
ctx.line_to(90,200)
ctx.line_to(0,0)
ctx.close_path()
ctx.clip()
ctx.rectangle(0,0,300,300)
ctx.set_source_rgb(1,1,0)
ctx.fill()
surface.write_to_png("clipped.png")