Skip to content

Commit

Permalink
Rounded corner radius can now be adjusted by the image scale.
Browse files Browse the repository at this point in the history
Rounding the corners of an image requires two different approaches. If the image has the same resolution for all screen scales, the radius MUST be divided by the image scale in order to produce the same visual round across all device scales. If the image has multiple resolutions tailored to each screen scale, the same radius can be applied on devices of all screen scales.
  • Loading branch information
cnoon committed Sep 16, 2015
1 parent 7fd034f commit 8ab53f6
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 25 deletions.
56 changes: 45 additions & 11 deletions Source/ImageFilter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,23 +166,41 @@ public struct RoundedCornersFilter: ImageFilter, Roundable {
/// The radius of the filter.
public let radius: CGFloat

/// Whether to divide the radius by the image scale.
public let divideRadiusByImageScale: Bool

/**
Initializes the `RoundedCornersFilter` instance with the given radius.

- parameter radius: The radius.
- parameter radius: The radius.
- parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
image has the same resolution for all screen scales such as @1x, @2x and
@3x (i.e. single image from web server). Set to `false` for images loaded
from an asset catalog with varying resolutions for each screen scale.
`false` by default.

- returns: The new `RoundedCornersFilter` instance.
*/
public init(radius: CGFloat) {
public init(radius: CGFloat, divideRadiusByImageScale: Bool = false) {
self.radius = radius
self.divideRadiusByImageScale = divideRadiusByImageScale
}

/// The filter closure used to create the modified representation of the given image.
public var filter: Image -> Image {
return { image in
return image.af_imageWithRoundedCornerRadius(self.radius)
return image.af_imageWithRoundedCornerRadius(
self.radius,
divideRadiusByImageScale: self.divideRadiusByImageScale
)
}
}

/// The unique idenitifier for an `ImageFilter` conforming to the `Roundable` protocol.
public var identifier: String {
let radius = Int64(round(self.radius))
return "\(self.dynamicType)-radius:(\(radius))-divided:(\(divideRadiusByImageScale))"
}
}

// MARK: -
Expand Down Expand Up @@ -264,13 +282,21 @@ public struct ScaledToSizeWithRoundedCornersFilter: CompositeImageFilter {
/**
Initializes the `ScaledToSizeWithRoundedCornersFilter` instance with the given size and radius.

- parameter size: The size.
- parameter radius: The radius.
- parameter size: The size.
- parameter radius: The radius.
- parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
image has the same resolution for all screen scales such as @1x, @2x and
@3x (i.e. single image from web server). Set to `false` for images loaded
from an asset catalog with varying resolutions for each screen scale.
`false` by default.

- returns: The new `ScaledToSizeWithRoundedCornersFilter` instance.
*/
public init(size: CGSize, radius: CGFloat) {
self.filters = [ScaledToSizeFilter(size: size), RoundedCornersFilter(radius: radius)]
public init(size: CGSize, radius: CGFloat, divideRadiusByImageScale: Bool = false) {
self.filters = [
ScaledToSizeFilter(size: size),
RoundedCornersFilter(radius: radius, divideRadiusByImageScale: divideRadiusByImageScale)
]
}

/// The image filters to apply to the image in sequential order.
Expand All @@ -285,13 +311,21 @@ public struct AspectScaledToFillSizeWithRoundedCornersFilter: CompositeImageFilt
/**
Initializes the `AspectScaledToFillSizeWithRoundedCornersFilter` instance with the given size and radius.

- parameter size: The size.
- parameter radius: The radius.
- parameter size: The size.
- parameter radius: The radius.
- parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
image has the same resolution for all screen scales such as @1x, @2x and
@3x (i.e. single image from web server). Set to `false` for images loaded
from an asset catalog with varying resolutions for each screen scale.
`false` by default.

- returns: The new `AspectScaledToFillSizeWithRoundedCornersFilter` instance.
*/
public init(size: CGSize, radius: CGFloat) {
self.filters = [AspectScaledToFillSizeFilter(size: size), RoundedCornersFilter(radius: radius)]
public init(size: CGSize, radius: CGFloat, divideRadiusByImageScale: Bool = false) {
self.filters = [
AspectScaledToFillSizeFilter(size: size),
RoundedCornersFilter(radius: radius, divideRadiusByImageScale: divideRadiusByImageScale)
]
}

/// The image filters to apply to the image in sequential order.
Expand Down
11 changes: 8 additions & 3 deletions Source/UIImage+AlamofireImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,19 @@ extension UIImage {
/**
Returns a new version of the image with the corners rounded to the specified radius.

- parameter radius: The radius to use when rounding the new image.
- parameter radius: The radius to use when rounding the new image.
- parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
image has the same resolution for all screen scales such as @1x, @2x and
@3x (i.e. single image from web server). Set to `false` for images loaded
from an asset catalog with varying resolutions for each screen scale.
`false` by default.

- returns: A new image object.
*/
public func af_imageWithRoundedCornerRadius(radius: CGFloat) -> UIImage {
public func af_imageWithRoundedCornerRadius(radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)

let scaledRadius = radius / scale
let scaledRadius = divideRadiusByImageScale ? radius / scale : radius

let clippingPath = UIBezierPath(roundedRect: CGRect(origin: CGPointZero, size: size), cornerRadius: scaledRadius)
clippingPath.addClip()
Expand Down
15 changes: 8 additions & 7 deletions Tests/ImageFilterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class ImageFilterTestCase: BaseTestCase {
let identifier = filter.identifier

// Then
XCTAssertEqual(identifier, "RoundedCornersFilter-radius:(12)", "identifier does not match expected value")
let expectedIdentifier = "RoundedCornersFilter-radius:(12)-divided:(false)"
XCTAssertEqual(identifier, expectedIdentifier, "identifier does not match expected value")
}

// MARK: - CompositeImageFilter Protocol Extension Identifiers
Expand All @@ -75,11 +76,8 @@ class ImageFilterTestCase: BaseTestCase {
let identifier = filter.identifier

// Then
XCTAssertEqual(
identifier,
"ScaledToSizeFilter-size:(200x100)_RoundedCornersFilter-radius:(20)",
"identifier does not match expected value"
)
let expectedIdentifier = "ScaledToSizeFilter-size:(200x100)_RoundedCornersFilter-radius:(20)-divided:(false)"
XCTAssertEqual(identifier, expectedIdentifier, "identifier does not match expected value")
}

// MARK: - Single Pass Image Filter Tests
Expand Down Expand Up @@ -126,14 +124,17 @@ class ImageFilterTestCase: BaseTestCase {
func testThatRoundedCornersFilterReturnsCorrectFilteredImage() {
// Given
let image = imageForResource("pirate", withExtension: "jpg")
let filter = RoundedCornersFilter(radius: 20)
let filter = RoundedCornersFilter(radius: 20, divideRadiusByImageScale: true)

// When
let filteredImage = filter.filter(image)

// Then
let expectedFilteredImage = imageForResource("pirate-radius-20", withExtension: "png")
XCTAssertTrue(filteredImage.af_isEqualToImage(expectedFilteredImage), "filtered image pixels do not match")

let expectedIdentifier = "RoundedCornersFilter-radius:(20)-divided:(true)"
XCTAssertEqual(filter.identifier, expectedIdentifier, "filter identifier does not match")
}

func testThatCircleFilterReturnsCorrectFilteredImage() {
Expand Down
8 changes: 4 additions & 4 deletions Tests/UIImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,10 @@ class UIImageTestCase: BaseTestCase {
let r = Int(round(radius))

// When
let roundedAppleImage = appleImage.af_imageWithRoundedCornerRadius(radius)
let roundedPirateImage = pirateImage.af_imageWithRoundedCornerRadius(radius)
let roundedRainbowImage = rainbowImage.af_imageWithRoundedCornerRadius(radius)
let roundedUnicornImage = unicornImage.af_imageWithRoundedCornerRadius(radius)
let roundedAppleImage = appleImage.af_imageWithRoundedCornerRadius(radius, divideRadiusByImageScale: true)
let roundedPirateImage = pirateImage.af_imageWithRoundedCornerRadius(radius, divideRadiusByImageScale: true)
let roundedRainbowImage = rainbowImage.af_imageWithRoundedCornerRadius(radius, divideRadiusByImageScale: true)
let roundedUnicornImage = unicornImage.af_imageWithRoundedCornerRadius(radius, divideRadiusByImageScale: true)

// Then
let expectedAppleImage = imageForResource("apple-radius-\(r)", withExtension: "png")
Expand Down

0 comments on commit 8ab53f6

Please sign in to comment.