CATextLayer subpixel antialiasing - objective-c

My app draws layer-backed labels over a NSImageView.
The image view displays an image, and a tint color over that image.
This ensures that the contrast between the labels and the background image works.
As you can see, subpixel antialiasing is enabled and works correctly.
When you hover over those labels, they animate the frame property (Actually the view containing them).
While animating, the subpixel antialiasing is disabled, and when done enabled again.
This looks incredibly weird.
The layer is never redrawn, and the subpixel antialiasing doesn't have to change.
So I don't see a good reason why it shouldn't be displayed when animating.
I've tried everything I can think of.
Making the NSTextField opaque
Making the CATextLayer opaque
Giving the NSTextField a background-color
Giving the CATextLayer a background-color
Always the same result.
Disabling subpixel antialiasing for the labels is not an option, since it's not well readable on non-retina devices.
EDIT
I forgot that the layer is replaced with a presentationLayer while animating.
This layer probably does not support subpixel antialiasing, which is why it's disabled.
Now the question is if I can replace this presentationLayer with a CATextLayer.
What I also noticed is that setting shouldRasterize to YES enabled subpixel antialiasing also for animation, but only against the background color. So no background-color will bring no subpixel antialiasing.

Is there any way that you can post a piece of sample code? I quickly mocked up an NSWindow, added an NSImageView, added a background-less NSTextField with setWantsLayer: set to YES. In my applicationDidFinishLaunching: I set a new rect on the NSTextField's Animator frame, but I didn't see any pixelation.

The problem is with positioning of the text layer. Let's presume you use left alignment. The text will look good if x and y coordinates of the layer's frame origin are rounded numbers. For example:
CGFloat x = 10.6;
CGFloat y = 10.3;
textLayer.frame = CGRectMake(x, y, width, height); // the text will be blur.
textLayer.frame = CGRectMake(round(x), round(y), width, height); // the text will not be blur.
So, the first problem may be that the coordinates you assign to the layer's frame are not rounded.
The tricky part is that the edge of the layer may still be not aligned with pixels even if you think you passed rounded coordinates. This may happen if the anchorPoint of your layer is (0.5, 0.5), which is the default value. If you set:
textLayer.position = CGPointMake(10.0, 10.0);
you may think it should draw the text sharp. However, position point is in the center of the layer here, and depending on the layer's width and height the left and top edge's coordinates may be fractional numbers.
If you want to make a quick test do this.
Use textLayer.frame = frame instead of using position and anchor point, it will assign the coordinates directly to the frame.
Make sure the numbers you use in the frame are rounded.
Do not mess with rendering mechanism, remove the code that changes shouldRasterize, antialiasing, etc.
If this makes the text sharp, you can start using the anchor point and position and to see how the result changes.

Related

How to change UITableViewCell Image to Circle in UITableView if width and height are different

My code is
imv.clipsToBounds=YES;
imv.layer.cornerRadius=imv.frame.size.width/2;
imv.layer.borderWidth = 4.0;
The best solution would be to either resize your image view to be square (using autolayout or manually set the frame) or even better you could redraw the image using an image context with a circular clipping mask, then you wouldn't have performance problems on older devices because of corner radii that you recompute on scrolling.
Something like here https://stackoverflow.com/a/17722702/384309

Rotating UILabel is clipping text

I have a UILabel that I am trying to rotate. First I am calling sizeToFit on the label, adding a little padding to the frame and then setting the transform to CGAffineTransformMakeRotation(rotation) (where rotation is a value calculated from a UIRotationGestureRecognizer).
Some of the text is being clipped as the UILabel is rotated (as illustrated in the image linked below). For demonstrative purposes, I made the UILabel's background color red so you can see how the bounds change when it is rotated. How do I prevent this from happening?
http://cl.ly/image/3F0M420R0d14
Thanks.

Resizing CAShapeLayer

I have a simple test app on which my rootViewController's UIView contains a bunch of UIView subviews. Each one of those UIView subview is backed by a CAShapeLayer.
I want the composition created by those subviews ( the four shapes that are within the dotted area .. ) to always stay vertically and horizontally centered with respect to my
UIWindow. (the minimium size of the left/right, top/bottom margins will be subject to be changed at runtime at each orientation change )
So for example when i rotate to portrait i will have to
resize and reposition those single shapes so that the whole figure will be mantained centered and each CAShapeLayer sublayer stays sharp ( i want their path to be resized not just a raster resize )
what would be the best technique to resize/move the shapes to always have a centered composition while maintaining path crisp appearance for the shapes?
Ultimately for me it will be good to have an answer to this: how can i shrink the subviews as a whole? i mean their sizes and relative positions?
Thanks
You can use CGPathCreateMutableCopyByTransformingPath() or -[UIBezierPath applyTransform:] to recalculate all points in the path.

Howto CALayer's image scale independently of CALayer's bounds

I can't find an answer for this one.
I would like to know how to have the image size in a calayer's to be lower than calayer's bound's size.
I've got several pawns in an iPad game, each is a CALayer and I have them resize simply with a contentsGravity=kCAGravityResizeAspect. Image is 128x128 inside of a CALayer of 30x30 so the image gets resized automatically to 30x30 and because of both being a box, aspect ratio maintains and works.
Here I set CALayer's bounds proportional relative to superview's size, so the Pawns always present the same relative size to the view. This one is inside my sprite class subclass of calayer:
-(void) setSpriteScaleToDice {
CGFloat newSize = [self superlayer].bounds.size.width * 0.066666667f;
self.bounds=CGRectMake(0.0f, 0.0f, newSize, newSize);
self.contentsGravity = kCAGravityResizeAspect;
}
Note that in my case the CALayer bounds gets a maximum of 30x30 which is small for a touch. That's the problem I'm facing, due to this small size it's difficult to "touch" them, sometimes touch fails...
One of the ideas that I'm thinking is to increase the "bounds" of the calayer, while keeping the image at its original size. The problem is that I've search a lot and tried several options with contentsGravity, contentsCenter, contentsScale, etc... without success.
In particular, as per apple docs looks like the way to go is with contentsCenter (and not using contentsGravity), however I get deformation in the bitmap...
Please, any idea is really welcome, and thanks in advance,
Luis
This is probably a silly question, but why are you using CALayers for this instead of UIViews? UIImageView has a contentMode property that lets you do this easily (not to mention being easier to use for touch event handling).
That said, CALayer has a contentsRect property that appears to let you define a sub-rectangle for contents to be drawn within, so that may let you do what you want.
Another option would be to place your image layer inside a larger layer and use that for the hit test.
CAlayer CGFloat contentsScale
/* Defines the scale factor applied to the contents of the layer. If
* the physical size of the contents is '(w, h)' then the logical size
* (i.e. for contentsGravity calculations) is defined as '(w /
* contentsScale, h / contentsScale)'. Applies to both images provided
* explicitly and content provided via -drawInContext: (i.e. if
* contentsScale is two -drawInContext: will draw into a buffer twice
* as large as the layer bounds). Defaults to one. Animatable. */
If you want your image drawn in the CALayer at a size other than the CALayer you need to create your own drawInContext: method and draw the image rather than setting the CALayer's contents property. Do not set the contents property, create your own to track the image you want to draw.

drawInRect behavior with UIView rotation transform

I have a problem understanding how the parameter passed to the drawInRect method is defined when a rotation transformation is performed on a UIView.
To give an example I have a UIView which I rotated with an angle of 307 degree.
In the drawInRect method I log the following:
self.frame: {{103.932, 273.254}, {64.3007, 84.3374}}
rect (the variable passed as parameter:{{0, 0.102913}, {18, 89}}
The problem is that according to the documentation I should not draw outside of rect, but considering what I should draw, there is no way my images will fit there.
Can anyone explain to me how I am supposed to use drawInRect in the case my UIView is rotated ?
To give more detail about my problem, here is what I do:
I have a scrollview with a contentView inside (subclassed). I add my UIViews in the content view.
The views in question are composed of a handler image (bottom left) and the main image (top right). Users are supposed to grab the view by pressing the handler but that's not the point.
The drawInRect method of the UIView contains the following:
[_image drawInRect:CGRectMake(handlerSize.width, 0, _image.size.width, _image.size.height)];
CGSize size = CGSizeMake(kHandPickerWidth/self.scrollViewScale, kHandPickerHeight/self.scrollViewScale);
[_handPickerImage drawInRect:(0, _image.size.height, size.width, size.height)];
The UIViews objects are added at viewWillAppear in the content view doing the following:
first instanciate,
then addSubview:
then I set the scrollViewScale parameter,
then I set the frame parameter (according to the top right image displayed (which may vary)
then I rotate the UIView.
Starting from line three, the code is executed from the
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
to make sure every variable is set properly when displayed is needed.
The line defining the size variable is to adjust the marker's size no mater the zoomScale value of the scroll view.
You basically just draw as you would normally, and the painting will be rotated by iOS for you. You can get this transformation information if you would want to.
You need to get a reference to the currect graphics context:
CGContextRef ctx = UIGraphicsGetCurrentContext();
Then query the transformation matrix directly:
CGAffineTransform tf = CGContextGetCTM(ctx);
NSLog(#"current ctm: %#",NSStringFromCGAffineTransform(tf));
or better, get the transfrom from your drawing function to the device:
CGAffineTransform tf = CGContextGetUserSpaceToDeviceSpaceTransform(ctx);
NSLog(#"user->device transform: %#",NSStringFromCGAffineTransform(tf));
And in drawRect: you should not rely too much on the passed CGRect, because it serves mostly as a hint to what piece of the view needs updating. (e.g. because you called -setNeedsDisplayInRect: on it). To get the actual bounds where your view lives in, use self.bounds.
Drawing outside of the CGRect is no real problem, but will only hurt performance a little.
edit
ps. to clarify: self.frame is the frame of your view in the parent view coordinate system. It changes if you move, rotate or otherwise transform the view. self.bounds is the frame of your view in its own coordinate system, and (therefore) remains constant under changes of position or transformations.
So I found a solution to my problem:
I was setting the frame parameter multiple times with some CGAffineTransformation defined which is not supposed to be done.
Now each time I need to reset the frame I reset the affine transform, change the frame and set the back the affine transform.
Everything works as supposed to this way.