[IOS] Swift functional APIs

Source: Internet
Author: User
Tags image filter

[IOS] Swift functional APIs

 

 

 

 

 

In the past, people have summarized many common models and best practices for designing APIs. In general, we can always summarize some development examples from Apple's Foundation, Cocoa, Cocoa Touch and many other frameworks. There is no doubt that different people have different opinions on the question of "how to design an API in a specific situation" and there is a lot of room for discussion. However, many Objective-C developers are familiar with common models.

With the emergence of Swift, designing APIs has caused more problems. In most cases, we can only continue to do the work at hand, and then translate the existing methods into the Swift version. However, this is unfair to Swift because Swift has added many new features compared with Objective-C. Here is a reference to Chris Lattner, the founder of Swift:

Swift introduces the idea of generic and functional programming, which greatly extends the design space.

In this article, we will focus onCore ImageThis is an example to explore how to use these new tools in API design.Core ImageIt is a powerful image processing framework, but its API is sometimes a bit bulky.Core ImageThe API is of the weak type-it uses key-value pairs to set the image filter. In this way, errors are easily made when setting the parameter type and name, resulting in running errors. The new API will be very secure and modular, and such runtime errors will be avoided by using types instead of key-value pairs.

Target

Our goal is to build an API that allows us to easily and securely assemble custom filters. For example, at the end of the article, we can write as follows:

let myFilter = blur(blurRadius) >|> colorOverlay(overlayColor)let result = myFilter(image)

A custom filter is built above. First, blur the image and then add a color mask. To achieve this goal, we will make full use of the Swift function as a first-class citizen. The project source code can be downloaded from this example project on Github.

Filter Type

CIFilterYesCore ImageIs used to create Image filters. When instantiatingCIFilterAfter the object, you (almost) always passkCIInputImageKeyInput the image, and thenkCIOutputImageKeyObtain the returned image. The returned result can be input as a parameter of the next filter.

In the APIS we are about to develop, we will extract the actual content corresponding to these key-value pairs to provide users with a secure and strong-type API. We have defined our own filter type.FilterIt is a function that can input images as parameters and return a new image.

typealias Filter = CIImage -> CIImage

Here we usetypealiasKeyword:CIImage -> CIImageThe Type defines our own name. This type is a function and its parameter isCIImage, The return value is alsoCIImage. This is the basic type required for subsequent development.

If you are not familiar with functional programming, you may name a function typeFilterIt is a bit strange. Generally, we use this name to define a class. If we really want to express this type of functional features in some way, we can name itFilterFunctionOr some other similar names. However, we have made a conscious choice.FilterThis name, because in the core philosophy of functional programming, a function is a value, and there is no difference between a function and a struct, integer, multivariate group, or class. At first, I was not very comfortable with it, but after a while I found that it really makes sense to do so.

Build Filter

Now we have definedFilterType, then you can define the function to build a specific filter. These functions require parameters to set specific filters and return a typeFilter. These functions look like this:

func myFilter(/* parameters */) -> Filter

Note that the returned valueFilterIn itself, it is a function. In the future, we can combine multiple filters to achieve the desired processing effect.

To make subsequent development easier, we have extendedCIFilterClass, added a convenience initialization method, and a computing attribute used to obtain the output image:

typealias Parameters = Dictionary
  
   extension CIFilter {    convenience init(name: String, parameters: Parameters) {        self.init(name: name)        setDefaults()        for (key, value : AnyObject) in parameters {            setValue(value, forKey: key)        }    }    var outputImage: CIImage { return self.valueForKey(kCIOutputImageKey) as CIImage }}
  

This convenience initialization method has two parameters: the first parameter is the filter name, and the second parameter is a dictionary. The key-value pairs in the dictionary are set as parameters of the new filter. The convenience initialization method first calls the specified initialization method, which complies with the Swift development specifications.

Calculation attributeoutputImageYou can easily obtain the output image from the filter object. It findskCIOutputImageKeyAnd convert it intoCIImageObject. By providing this attribute, API users no longer need to manually convert the returned results.

Fuzzy

With these things, we can now define our own simple filters. Gaussian Blur filters only require a blur radius as a parameter. We can easily complete a blur filter:

func blur(radius: Double) -> Filter {    return { image in        let parameters : Parameters = [kCIInputRadiusKey: radius, kCIInputImageKey: image]        let filter = CIFilter(name:CIGaussianBlur, parameters:parameters)        return filter.outputImage    }}

This is simple. This fuzzy function returns a function. The parameter of the new function is of the type:CIImageImage, return value (filter.outputImage) Is a new image. The format of this fuzzy function isCIImage -> CIImageTo meet the requirements we have previously definedFilterType format.

This example is justCore ImageYou can repeat the same pattern multiple times to create our own filter function.

Color Mask

Now let's define a color filter that adds a color mask to an existing image.Core ImageThis filter is not provided by default, but we can assemble it with an existing filter.

We use two modules to do this. One is to generate a color filter (CIConstantColorGenerator), And the other is the resource merging filter (CISourceOverCompositing). Let's first define a filter that generates a constant Color panel:

func colorGenerator(color: UIColor) -> Filter {    return { _ in        let filter = CIFilter(name:CIConstantColorGenerator, parameters: [kCIInputColorKey: color])        return filter.outputImage    }}

This code looks similar to the previous blur filter, but there is a significant difference: the color generation filter does not detect the input image. Therefore, we do not need to name the Input Image Parameters in the function. We use an anonymous parameter._To emphasize that the image parameters of the filter are ignored.

Next, let's define the merging filter:

func compositeSourceOver(overlay: CIImage) -> Filter {    return { image in        let parameters : Parameters = [             kCIInputBackgroundImageKey: image,             kCIInputImageKey: overlay        ]        let filter = CIFilter(name:CISourceOverCompositing, parameters: parameters)        return filter.outputImage.imageByCroppingToRect(image.extent())    }}

Here we crop the output image to the same size as the input image. This is not strictly required. It depends on how we want filters to work. However, we can see in our examples below that this is a wise move.

func colorOverlay(color: UIColor) -> Filter {    return { image in        let overlay = colorGenerator(color)(image)        return compositeSourceOver(overlay)(image)    }}

Once again, we return a function with the parameter image,colorOverlayCalled at the beginningcolorGeneratorFilter.colorGeneratorThe filter requires a color as the parameter and returns a filter. ThereforecolorGenerator(color)YesFilterType. HoweverFilterThe type itself isCIImageDirectionCIImageFor the conversion function, we cancolorGenerator(color)Add a typeCIImageTo obtainCIImage. This is the definitionoverlayWhat happened: we usecolorGeneratorThe function creates a filter and transmits the image as a parameter to the filter to obtain a new image. Return ValuecompositeSourceOver(overlay)(image)Similar to this, it consists of a filter.compositeSourceOver(overlay)And an image Parameterimage.

Filter combination

Now we have defined a blur filter and a color filter. We can combine them when using them: first we will blur the image, then put a red masked layer on the top. Let's load an image first:

let url = NSURL(string: http://tinyurl.com/m74sldb);let image = CIImage(contentsOfURL: url)

Now we can combine the filters and apply them to an image:

let blurRadius = 5.0let overlayColor = UIColor.redColor().colorWithAlphaComponent(0.2)let blurredImage = blur(blurRadius)(image)let overlaidImage = colorOverlay(overlayColor)(blurredImage)

We again assembled the image through a filter. For example, in the last row, we first got the blur filter.blur(blurRadius)And then apply the filter to the image.

Function assembly

However, we can do better than above. We can simply combine the two filters into one line. This is the first thing that can be improved in my mind:

let result = colorOverlay(overlayColor)(blur(blurRadius)(image))

However, these parentheses make this line of code completely unreadable. A better way is to define a function to complete this task:

func composeFilters(filter1: Filter, filter2: Filter) -> Filter {    return { img in filter2(filter1(img)) }}

composeFiltersBoth parameters of the function are filters, and a new Filter is returned. The assembled filter requiresCIImageType parameters, and the parameters are passedfilter1Andfilter2. Now we can usecomposeFiltersTo define our own filter combination:

let myFilter = composeFilters(blur(blurRadius), colorOverlay(overlayColor))let result = myFilter(image)

We can further define a filter operator to make the code more readable,

infix operator >|> { associativity left }func >|> (filter1: Filter, filter2: Filter) -> Filter {    return { img in filter2(filter1(img)) }}

OperatorinfixKeyword definition, indicating that the operator hasLeftAndRightTwo parameters.associativity leftThis operation satisfies the left combination law, that is, f1 >|> f2 >|> f3 is equivalent to (f1 >|> f2 >|> f3. The left combination law is satisfied by this operation, and the filter on the left is applied in the operation. Therefore, the filter sequence is from left to right, just like a Unix pipeline.

The rest is a function, content andcomposeFiltersBasically the same, but the function name is changed>|>.

Next we will apply this combined filter splitter to the previous example:

let myFilter = blur(blurRadius) >|> colorOverlay(overlayColor)let result = myFilter(image)

Operators make the code easier to read and understand the order in which filters are used, making it easier to call filters. It's like1 + 2 + 3 + 4Aspect Ratioadd(add(add(1, 2), 3), 4)Clearer and easier to understand.

Custom Operators

Many Objective-C developers are skeptical about custom operators. When Swift was just released, it was not very popular. Many people have experienced (or even abused) User-Defined operators in C ++, some of which are personal experience and some are heard by others.

You may>|>I have the same skeptical attitude. After all, if everyone defines their own operators, isn't the code hard to understand? Fortunately, there are many operations in functional programming. Defining an operator for these operations is not uncommon.

The filter combination operator we define is an example of a function combination, which is widely used in functional programming. In mathematics, two functionsfAndgThe combination is sometimes writtenf ° gIn this way, a new function is definedxMapf(g(x)). This happens to be ours.>|>The work done (except the reverse call of the function ).

Generic

If you think about it, there is no need to define an operator to specifically assemble filters. We can use a generic operator to assemble functions. Currently, our>|>Yes:

func >|> (filter1: Filter, filter2: Filter) -> Filter

After this definition, the input parameters can only beFilterType Filter.

However, we can use the common features of Swift to define a generic function combination operator:

func >|> (lhs: A -> B, rhs: B -> C) -> A -> C {    return { x in rhs(lhs(x)) }}

It may be hard to understand at the beginning-at least for me. But after reading each part separately, everything becomes clearer.

First, let's look at the angle brackets behind the function name. Angle brackets define the generic type that this function applies. In this example, we define three types: A, B, and C. Because these types are not specified, they can represent anything.

Next let's take A look at the function parameters: the first parameter: lhs (left-hand side abbreviation) is A function of type A-> B. This indicates that the parameter of A function is A, and the type of the returned value is B. The second parameter: rhs (short for right-hand side) is a function of type B-> C. The parameters are named lhs and rhs because they correspond to the values on the left and right of the operators respectively.

NoFilterAfter the filter combination operator, we soon discovered that the previously implemented combination operator is only a special case in generic functions:

func >|> (filter1: CIImage -> CIImage, filter2: CIImage -> CIImage) -> CIImage -> CIImage

Replace all generic types A, B, and C in our mindsCIImageIn this way, we can clearly understand how useful it is to replace the filter combination operator with a common operator.

Conclusion

So far, we have successfully encapsulated it using functional APIs.Core Image. We hope this example can be well illustrated. For Objective-C developers, there is a completely different world outside of the well-known API design patterns. With Swift, we can now explore new areas and make full use of them.


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.