IOS painting Learning

Source: Internet
Author: User

Translated from: iOS 7 Programming, by Matt Neuburg, OREILLY.

 

 

Many subclasses of UIView, such as UIButton or UIlabel, all know how to draw yourself. But sooner or later, you will want to draw some effects you want. You can use some existing classes to draw an image in the code and then display it on your own interface, such as UIImageVIew and UIButton. A simple UIView is only related to painting. It gives you a lot of space to paint. Your Code determines how to draw your own view and how to display it on your interface.

 UIImage and UIImageView

The iOS system supports many standard image formats: TIFF, JPEG, GIF, and PNG. When an image is contained in our app package, the iOS system provides more friendly support for PNG files, not only because the system compresses the image, in addition, we have made a lot of work on selecting and displaying images at different resolutions. Therefore, we should first select images in PNG format. You can use the imageNamed: method provided by the UIImage class to obtain the image in the app package. This method searches for the specified image from two places:

  App package top-level directory

    The system will search for the image in the app package by providing the image name, which is case-sensitive and contains the image type. If no type is provided, the default format is png.

Asset catalog Resource Directory

It searches for matched image sets in the Resource Directory by the provided name. If the name has a file suffix, it will not be searched here, so that the original code can still work normally if you move the image to this directory. The Search priority of this directory is higher than the search above, which means that if a matched image is found in this resource directory, the method will return, instead of searching in the app package's top-level directory.

 

 

  Images with adjustable size

You can send the resizableImageWithCapInsets: resizingMode: message to a UIImage to convert the image to an image of adjustable size. The capInsets parameter is a structure of the UIEdgeInsets type. It consists of four floating-point numbers: top, left, bottom, and right. They represent the inner distance from the image edge. In a context that is larger than an Image, you can adjust the size of an Image in two working modes, using resizingMode: value: Specify

  UIImageResizingModeTile

The interior rectangle area specified by capInsets is tiled inside. Each edge is tiled by the rectangle area of the corresponding edge, while the rectangles in the four corners of the outer are unchanged.

  UIImageResizingModeStretch

    The interior rectangle is stretched once to fill the interior. Each edge is drawn from the rectangular area of the strain, while the rectangles in the four corners of the outer are unchanged.

For example, assume that self. iv is a UIImageView with fixed length and width, and contentMode is UIViewContentModeScaleToFill.

(1) set capInsets to UIEdgeInsetsZero.

   UIImage* mars = [UIImage imageNamed:@"Mars"];  UIImage* marsTiled = [mars resizableImageWithCapInsets: UIEdgeInsetsZero                                                          resizingMode: UIImageResizingModeTile];  self.iv.image = marsTiled;

  

(2)

 UIImage* marsTiled = [mars resizableImageWithCapInsets:                          UIEdgeInsetsMake(mars.size.height/4.0,                                           mars.size.width/4.0,                                           mars.size.height/4.0,                                           mars.size.width/4.0)                          resizingMode: UIImageResizingModeTile];

  

(3) The common stretching strategy is to use half of the original image as a capinset, and only set 1 to 2 pixels in the middle to fill the entire interior.

UIImage* marsTiled = [mars resizableImageWithCapInsets:                          UIEdgeInsetsMake(mars.size.height/2.0 - 1,                                           mars.size.width/2.0 - 1,                                           mars.size.height/2.0 - 1,                                           mars.size.width/2.0 - 1)                          resizingMode: UIImageResizingModeStretch];

  

 

In the latest Xcode5, we do not need to use code to configure an image that can be resized. Instead, we only use the asset catalogs function provided by Xcode5, instead of writing the same code multiple times, this function is only available in ios7.0 and later versions.

 

 

  Rendering mode of images

In many parts of the ios application interface, images are automatically treated as transparent masks, also known as templates. This means that the color of the image is ignored, and the transparency (alpha) corresponding to each pixel is kept ). The image displayed on the screen is a combination of a single color tone and image transparency. For example, the image of the button in the tag bar or the image of the button of the UIBarButtonItemSylePlain type in the toolbar is in this mode. In the latest ios7 system, the image class adds a new attribute: renderingMode, indicating the image rendering mode. This attribute is Read-Only. To change the image attribute, we can use the existing image to generate a new image in different rendering modes and call this method: imageWithRendingMode :. There are three rendering modes: UIImageRenderingModeAlwaysOriginal, UIImageRenderingModeAutomatic, and UIImageRenderingModeAlwaysTemplate. UIImageRenderingModeAutomatic is the default UIImageRenderingModeAutomatic mode, that is, in addition to using the transparent template mode in the above-mentioned area, the images are displayed as they are. With this rendering attribute, we can force the image to be drawn in the normal way, even in the context where the image is rendered in the transparent template mode, and vice versa. Apple wants the iOS7 application to use more transparent template modes throughout the interface. The following is an example of ios7 system configuration application: Graphic Context UIImageView will draw an image for you and process all the details. In many cases, this is what you need. Even so, you may want to use code to draw something you want. In this case, you need a graphical context. A graphic context is generally an area that you can draw. On the contrary, you can draw only one graph context in the code. There are multiple ways to obtain a graphic context. Here we will introduce two methods, which are most used in various situations I have encountered: Create an image context by yourself   The uigraphicsbeginimagecontextwitexceptions function generates a graphical context suitable for use as an image. Then you can generate an image in the image context. After drawing, you can call UIGraphicsGetImageFromCurrentImageContext to convert the current image context into a UImage, and finally call UIGraphicsEndImageContext to release the context. Now you have a UIImage object that can be displayed on your interface, drawn in other contexts, or saved as a file. Cocoa gives you a graphical Context     You can subclass UIView and implement drawRect: method. After the drawRect method you implemented is called, Cocoa has created a graphical context for you and asked you to use it to draw immediately. No matter what you draw, will be displayed in the UIView. (A minor variant of this situation is that You subclass CALayer and implement the drawInContext: method, or delegate some objects to the layer and implement the drawLayer: inContext: method, I will discuss this again later ). At any given moment, a graphic context is either the current graphic context or not: * uigraphicsbeginimagecontextwitexceptions not only creates an image context, but also sets this context to the current graphic context. * When the drawRect method is called, the context being drawn by UIView is already the current graphic context. * Callback with a context as the parameter does not set any context as the current graphic context. On the contrary, this parameter is only a reference to the graphic context. What puzzles beginners is that there are two separate tool sets for drawing. They use different parameters for the graphic context During painting: UIKit     Many Objective-C classes know how to draw themselves, including UIImage, NSString (drawing text), UIBezierPath (Drawing Graphics), and UIColor. Some of these classes provide convenient methods and limited functions, while others provide very powerful functions. In many cases, UIKit is all you need. Through UIKit, you can only draw in the context of the current image. Therefore, if you are using uigraphicsbeginimagecontextwitexceptions or drawRect:, you can directly use the convenient method provided by UIKit, which provides a current context, it is also the context you want to draw. If you already have a context parameter, and you want to use the convenient method of UIKit, you will need to change the context to the current context, call UIGraphicsPushContext (remember to restore the context at the end and call UIGraphicsPopContext ). Core Graphics     This is the complete drawing API. Core Graphics is usually referred to as Quartz or Quartz2D. It is a painting system that makes up all iOS paintings-the painting of UIKit is built on it-so it is the underlying framework that contains a large number of C functions. This section will familiarize you with its principles. To get more comprehensive information, you can learn Apple's Quartz 2D Programming Guide (Apple's Quartz 2D Programming Guide ). To use Core Graphics, you must specify a graph context for drawing. Specifically, each function is called. However, in the uigraphicsbegincontextwitexceptions or drawRect: method, you do not have a context reference. To use Core Graphics, you need to get this reference. Since the context you want to draw is the current context, you can call UIGraphicsGetCurrentContext to obtain the required reference. So now we have two sets of tool sets, and the corresponding two contexts provide three methods, so we have a total of six painting methods. I will describe these six types one by one! You don't need to worry about the actual painting commands, just focus on how to specify the context and whether we are using UIKit or Core Graphics. First, I will subclass the UIView and implement the drawRect: Method to draw a blue circle; use the current context that UIKit has provided for me to draw:
- (void) drawRect: (CGRect) rect {        UIBezierPath* p =            [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];        [[UIColor blueColor] setFill];        [p fill];}

Now I use Core Graphics to achieve the same effect. In this way, I need to first get a reference of the current context:

- (void) drawRect: (CGRect) rect {        CGContextRef con = UIGraphicsGetCurrentContext();        CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));        CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);        CGContextFillPath(con);}

Next, I will implement drawLayer: inContext: In the UIView subclass :. In this case, the context reference in our hands is not the current context, so I need to use UIKit to convert it to the current context:

- (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con {        UIGraphicsPushContext(con);        UIBezierPath* p =            [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];        [[UIColor blueColor] setFill];        [p fill];        UIGraphicsPopContext();}

To use Core Graphics in drawLayer: inContext:, I simply need to keep a context that I hold:

- (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con {        CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));        CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);        CGContextFillPath(con);}

Finally, for integrity, let's create a Blue Circle UIImage object. We can create classes at any time (we do not need to wait for certain methods to be called) and in any class (we do not need to create classes in the subclass of UIView. You can use the created UIImage anywhere. For example, you can put it in a visible UIImageView for image display, or you can save it in a file, or you can use it in other projects (next section ).

 

First, I use UIKit to draw my image:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);    UIBezierPath* p =        [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];    [[UIColor blueColor] setFill];    [p fill];    UIImage* im = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();    // im is the blue circle image, do something with it here ...

The following is implemented using Core Graphics:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); CGContextRef con = UIGraphicsGetCurrentContext();CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);CGContextFillPath(con);UIImage* im = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();// im is the blue circle image, do something with it here ...

You may be confused about the parameters of the uigraphicsbeginimagecontextwitexceptions method. In fact, the first parameter is the size of the image to be created. The second parameter indicates whether the image is not transparent. If I pass YES in the above method but not NO, my image will have a black background, and I don't want this effect. The third parameter specifies the image scaling ratio. If 0 is passed, the system will automatically set the compression ratio based on the current screen size, in this way, my images will be perfectly displayed in single-resolution and dual-resolution screens.

 

You do not need to use UIKit or Core Graphics completely. On the contrary, you can use a combination of UIKit and Core Graphics to operate on the same graphic context. They only represent two different ways to communicate with the same image context.

 

CGImage painting

The version of UIImage in Core Graphics is CGImage (actually CGImageRef ). They can easily convert each other: UIImage has a CGImage attribute, which can access its Quartz image data. You can also convert CGImage to UIImage and use imageWithCGImage: or initWithCGImage: (in practice, you will prefer the more configurable sister method: imageWithCGImage: scale: orientation: And initWithCGImage: scale: orientation :).

A CGImage allows you to create a new image from a rectangular area of the original image, but UIImage cannot. (A CGImage has other powerful functions that are not available in UIImage. For example, you can apply the Image Mask to CGImage ). I will split a Mars image into two halves and draw each side separately.

 

Note: The content must be automatically managed for the CFTypeRef operation:

    UIImage* mars = [UIImage imageNamed:@"Mars"];    // extract each half as a CGImage    CGSize sz = mars.size;    CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],                           CGRectMake(0,0,sz.width/2.0,sz.height));    CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],                            CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height));    // draw each CGImage into an image context    UIGraphicsBeginImageContextWithOptions(        CGSizeMake(sz.width*1.5, sz.height), NO, 0);    CGContextRef con = UIGraphicsGetCurrentContext();    CGContextDrawImage(con,                       CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft);    CGContextDrawImage(con,                       CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight);    UIImage* im = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();    CGImageRelease(marsLeft); CGImageRelease(marsRight)

However, there is a problem in this example: the drawing is reversed! It is not rotated, but mapped from top to bottom, or flipped in professional terms. This occurs when you create a CGImage and draw it through CGContextDrawImage, because the local coordinate system of the source and target context does not match.

There are multiple ways to compensate for the mismatch between different coordinate systems. One of them is to plot CGImage into an intermediate UIImage, and then obtain CGImage from UIImage. The following shows a common function to achieve this conversion:

//  Utility for flipping an image drawingCGImageRef flip (CGImageRef im) {    CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im));    UIGraphicsBeginImageContextWithOptions(sz, NO, 0);    CGContextDrawImage(UIGraphicsGetCurrentContext(),                       CGRectMake(0, 0, sz.width, sz.height), im);    CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage];    UIGraphicsEndImageContext();    return result;}

We can use this tool function to fix the problems generated by calling CGContextDrawImage in the above example, so that they can correctly draw half of Mars.

CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),                       flip(marsLeft));CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),                       flip(marsRight));

However, we still have a problem: on a dual-bandwidth device, if our image has a dual-bandwidth (@2x.png), this rendering will fail. The reason is that we use imageNamed: to obtain the original Mars image, so that we will return an image that is scaled to adapt to the dual resolution to generate a double resolution. However, CGImage does not have the scale attribute and does not know anything about the original resolution! Therefore, on the dual-resolution device, the mars CGImage image we obtained by calling [mars CGImage] is twice the size of the mars image, so all our calculations are wrong.

Therefore, in order to extract the desired part from CGImage, We must multiply all the appropriate values by the scaling scale, or describe the size by the CGImage size. The following is a code version correctly drawn on both single and double split screens, and the flip effect is compensated:

    UIImage* mars = [UIImage imageNamed:@"Mars"];    CGSize sz = mars.size;    // Derive CGImage and use its dimensions to extract its halves    CGImageRef marsCG = [mars CGImage];    CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG),          CGImageGetHeight(marsCG));    CGImageRef marsLeft =        CGImageCreateWithImageInRect(            marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height));    CGImageRef marsRight =        CGImageCreateWithImageInRect(            marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));    UIGraphicsBeginImageContextWithOptions(        CGSizeMake(sz.width*1.5, sz.height), NO, 0);    // The rest is as before, calling flip() to compensate for flipping    CGContextRef con = UIGraphicsGetCurrentContext();    CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),                       flip(marsLeft));    CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),                       flip(marsRight));    UIImage* im = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();    CGImageRelease(marsLeft); CGImageRelease(marsRight);

Another solution is to wrap a CGImage in UIImage and draw the UIImage. UIImage can be implemented by calling imageWithCGImage: scale: orientation: to compensate for the effect of scaling. In addition, by drawing a UIImage instead of a CGImage, we avoid the flip problem. The following is a method for processing both flip and zoom (the above public classes are not called ):

    UIImage* mars = [UIImage imageNamed:@"Mars"];    CGSize sz = mars.size;    // Derive CGImage and use its dimensions to extract its halves    CGImageRef marsCG = [mars CGImage];    CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG),              CGImageGetHeight(marsCG));    CGImageRef marsLeft =        CGImageCreateWithImageInRect(            marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height));    CGImageRef marsRight =        CGImageCreateWithImageInRect(            marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));    UIGraphicsBeginImageContextWithOptions(        CGSizeMake(sz.width*1.5, sz.height), NO, 0);    [[UIImage imageWithCGImage:marsLeft                         scale:mars.scale                   orientation:UIImageOrientationUp]     drawAtPoint:CGPointMake(0,0)];    [[UIImage imageWithCGImage:marsRight                         scale:mars.scale                   orientation:UIImageOrientationUp]     drawAtPoint:CGPointMake(sz.width,0)];    UIImage* im = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();    CGImageRelease(marsLeft); CGImageRelease(marsRight);

Yes, another solution is to linearly convert the image context before drawing CGImage, effectively turning the internal Coordinate System in the image context. This method is simple, but it is hard to understand when there are other linear transformations. I will discuss more about graph context conversion in the following sections.

 

Why does it flip ??

Core Graphics will accidentally flip the history, which comes from the OS x world. The origin of the coordinate system in OS X is in the lower left corner by default, and the positive Y direction is up, while in iOS, by default, the coordinate origin is in the upper left corner, and the positive Y direction is downward. There is no problem in most painting because the coordinate system of the graphic context automatically adapts. In addition, in the context painting in the Core Graphics framework of iOS, the origin of the context coordinate system is in the upper left corner. We all know that, however, creating and plotting CGImage are in two coordinate systems and do not match each other.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.