Thursday, 15 July 2010

ios - How to draw a smooth circle with CAShapeLayer and UIBezierPath? -



ios - How to draw a smooth circle with CAShapeLayer and UIBezierPath? -

i attempting draw stroked circle using cashapelayer , setting circular path on it. however, method consistently less accurate when rendered screen using borderradius or drawing path in cgcontextref directly.

here results of 3 methods:

notice 3rd poorly rendered, within stroke on top , bottom.

i have set contentsscale property [uiscreen mainscreen].scale.

here drawing code these 3 circles. what’s missing create cashapelayer draw smoothly?

@interface bcviewcontroller () @end @interface bcdrawingview : uiview @end @implementation bcdrawingview - (id)initwithframe:(cgrect)frame { if ((self = [super initwithframe:frame])) { self.backgroundcolor = nil; self.opaque = yes; } homecoming self; } - (void)drawrect:(cgrect)rect { [super drawrect:rect]; [[uicolor whitecolor] setfill]; cgcontextfillrect(uigraphicsgetcurrentcontext(), rect); cgcontextsetfillcolorwithcolor(uigraphicsgetcurrentcontext(), null); [[uicolor redcolor] setstroke]; cgcontextsetlinewidth(uigraphicsgetcurrentcontext(), 1); [[uibezierpath bezierpathwithovalinrect:cgrectinset(self.bounds, 4, 4)] stroke]; } @end @interface bcshapeview : uiview @end @implementation bcshapeview + (class)layerclass { homecoming [cashapelayer class]; } - (id)initwithframe:(cgrect)frame { if ((self = [super initwithframe:frame])) { self.backgroundcolor = nil; cashapelayer *layer = (id)self.layer; layer.linewidth = 1; layer.fillcolor = null; layer.path = [uibezierpath bezierpathwithovalinrect:cgrectinset(self.bounds, 4, 4)].cgpath; layer.strokecolor = [uicolor redcolor].cgcolor; layer.contentsscale = [uiscreen mainscreen].scale; layer.shouldrasterize = no; } homecoming self; } @end @implementation bcviewcontroller - (void)viewdidload { [super viewdidload]; uiview *borderview = [[uiview alloc] initwithframe:cgrectmake(24, 104, 36, 36)]; borderview.layer.bordercolor = [uicolor redcolor].cgcolor; borderview.layer.borderwidth = 1; borderview.layer.cornerradius = 18; [self.view addsubview:borderview]; bcdrawingview *drawingview = [[bcdrawingview alloc] initwithframe:cgrectmake(20, 40, 44, 44)]; [self.view addsubview:drawingview]; bcshapeview *shapeview = [[bcshapeview alloc] initwithframe:cgrectmake(20, 160, 44, 44)]; [self.view addsubview:shapeview]; uilabel *borderlabel = [uilabel new]; borderlabel.text = @"calayer borderradius"; [borderlabel sizetofit]; borderlabel.center = cgpointmake(borderview.center.x + 26 + borderlabel.bounds.size.width/2.0, borderview.center.y); [self.view addsubview:borderlabel]; uilabel *drawinglabel = [uilabel new]; drawinglabel.text = @"drawrect: uibezierpath"; [drawinglabel sizetofit]; drawinglabel.center = cgpointmake(drawingview.center.x + 26 + drawinglabel.bounds.size.width/2.0, drawingview.center.y); [self.view addsubview:drawinglabel]; uilabel *shapelabel = [uilabel new]; shapelabel.text = @"cashapelayer uibezierpath"; [shapelabel sizetofit]; shapelabel.center = cgpointmake(shapeview.center.x + 26 + shapelabel.bounds.size.width/2.0, shapeview.center.y); [self.view addsubview:shapelabel]; } @end

edit: cannot see difference, i've drawn circles on top of each other , zoomed in:

here i've drawn reddish circle drawrect:, , drawn identical circle drawrect: 1 time again in greenish on top of it. note limited bleed of red. both of these circles "smooth" (and identical cornerradius implementation):

in sec example, you'll see issue. i've drawn 1 time using cashapelayer in red, , 1 time again on top drawrect: implementation of same path, in green. note can see lot more inconsistency more bleed reddish circle underneath. it's beingness drawn in different (and worse) fashion.

who knew there many ways draw circle?

tl;dr: if want utilize cashapelayer , still smooth circles, you'll need utilize shouldrasterize , rasterizationscale carefully.

original

here's original cashapelayer , diff drawrect version. made screenshot off ipad mini retina display, massaged in photoshop, , blew 200%. can see, cashapelayer version has visible differences, on left , right edges (darkest pixels in diff).

rasterize @ screen scale

let's rasterize @ screen scale, should 2.0 on retina devices. add together code:

layer.rasterizationscale = [uiscreen mainscreen].scale; layer.shouldrasterize = yes;

note rasterizationscale defaults 1.0 on retina devices, accounts fuzziness of default shouldrasterize.

the circle little smoother, bad bits (darkest pixels in diff) have moved top , bottom edges. not appreciably improve no rasterizing!

rasterize @ 2x screen scale

layer.rasterizationscale = 2.0 * [uiscreen mainscreen].scale; layer.shouldrasterize = yes;

this rasterizes path @ 2x screen scale, or 4.0 on retina devices.

the circle visibly smoother, diffs much lighter , spread out evenly.

i ran in instruments: core animation , didn't see major differences in core animation debug options. may slower since it's downscaling not blitting offscreen bitmap screen. may need temporarily set shouldrasterize = no while animating.

what doesn't work

set shouldrasterize = yes itself. on retina devices, looks fuzzy because rasterizationscale != screenscale.

set contentscale = screenscale. since cashapelayer doesn't draw contents, whether or not rasterizing, doesn't impact rendition.

credit jay hollywood of humaan, sharp graphic designer first pointed out me.

ios uikit core-graphics calayer cashapelayer

No comments:

Post a Comment