Skip to content

Commit

Permalink
feat: support adding border to cropped image (#196)
Browse files Browse the repository at this point in the history
* feat: support adding border to cropped image

* chore: fix a space issue

* chore: restore ViewController
  • Loading branch information
guoyingtao authored Aug 6, 2022
1 parent 560e272 commit f55ae6e
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 24 deletions.
4 changes: 3 additions & 1 deletion Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class ViewController: UIViewController, CropViewControllerDelegate {
guard let image = image else {
return
}
var config = Mantis.Config()
var config = Mantis.Config()
config.cropToolbarConfig = CropToolbarConfig()
config.cropToolbarConfig.backgroundColor = .red
config.cropToolbarConfig.foregroundColor = .white
Expand Down Expand Up @@ -212,6 +212,8 @@ class ViewController: UIViewController, CropViewControllerDelegate {
guard let self = self else {return}
var config = Mantis.Config()
config.cropViewConfig.cropShapeType = item.type
config.cropViewConfig.cropBorderWidth = 40
config.cropViewConfig.cropBorderColor = .red

let cropViewController = Mantis.cropViewController(image: image, config: config)
cropViewController.modalPresentationStyle = .fullScreen
Expand Down
53 changes: 44 additions & 9 deletions Sources/Mantis/CropView/CropView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ class CropView: UIView {
scrollView.resetBy(rect: viewModel.cropBoxFrame)

imageContainer.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
scrollView.contentOffset = CGPoint(x: (imageContainer.frame.width - scrollView.frame.width) / 2, y: (imageContainer.frame.height - scrollView.frame.height) / 2)
scrollView.contentOffset = CGPoint(x: (imageContainer.frame.width - scrollView.frame.width) / 2,
y: (imageContainer.frame.height - scrollView.frame.height) / 2)

gridOverlayView.superview?.bringSubviewToFront(gridOverlayView)

Expand Down Expand Up @@ -588,24 +589,58 @@ extension CropView {
.diamond(maskOnly: true),
.heart(maskOnly: true),
.polygon(_, _, maskOnly: true):
return (croppedImage, transformation, cropInfo)

let outputImage: UIImage?
if cropViewConfig.cropBorderWidth > 0 {
outputImage = croppedImage.rectangleMasked(borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor)
} else {
outputImage = croppedImage
}

return (outputImage, transformation, cropInfo)
case .ellipse:
return (croppedImage.ellipseMasked, transformation, cropInfo)
return (croppedImage.ellipseMasked(borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor),
transformation,
cropInfo)
case .circle:
return (croppedImage.ellipseMasked, transformation, cropInfo)
return (croppedImage.ellipseMasked(borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor),
transformation,
cropInfo)
case .roundedRect(let radiusToShortSide, maskOnly: false):
let radius = min(croppedImage.size.width, croppedImage.size.height) * radiusToShortSide
return (croppedImage.roundRect(radius), transformation, cropInfo)
return (croppedImage.roundRect(radius,
borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor),
transformation,
cropInfo)
case .path(let points, maskOnly: false):
return (croppedImage.clipPath(points), transformation, cropInfo)
return (croppedImage.clipPath(points,
borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor),
transformation,
cropInfo)
case .diamond(maskOnly: false):
let points = [CGPoint(x: 0.5, y: 0), CGPoint(x: 1, y: 0.5), CGPoint(x: 0.5, y: 1), CGPoint(x: 0, y: 0.5)]
return (croppedImage.clipPath(points), transformation, cropInfo)
return (croppedImage.clipPath(points,
borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor),
transformation,
cropInfo)
case .heart(maskOnly: false):
return (croppedImage.heart, transformation, cropInfo)
return (croppedImage.heart(borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor),
transformation,
cropInfo)
case .polygon(let sides, let offset, maskOnly: false):
let points = polygonPointArray(sides: sides, originX: 0.5, originY: 0.5, radius: 0.5, offset: 90 + offset)
return CropOutput(croppedImage.clipPath(points), transformation, cropInfo)
return CropOutput(croppedImage.clipPath(points,
borderWidth: cropViewConfig.cropBorderWidth,
borderColor: cropViewConfig.cropBorderColor),
transformation,
cropInfo)
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/Mantis/CropViewConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ public struct CropViewConfig {

public var cropShapeType: CropShapeType = .rect

public var cropBorderWidth: CGFloat = 0

public var cropBorderColor: UIColor = .clear

public var cropMaskVisualEffectType: CropMaskVisualEffectType = .blurDark

public var presetTransformationType: PresetTransformationType = .none
Expand Down
5 changes: 4 additions & 1 deletion Sources/Mantis/Extensions/CGImageExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
import UIKit

extension CGImage {
func transformedImage(_ transform: CGAffineTransform, outputSize: CGSize, cropSize: CGSize, imageViewSize: CGSize) -> CGImage? {
func transformedImage(_ transform: CGAffineTransform,
outputSize: CGSize,
cropSize: CGSize,
imageViewSize: CGSize) -> CGImage? {
guard var colorSpaceRef = self.colorSpace else {
return self
}
Expand Down
46 changes: 33 additions & 13 deletions Sources/Mantis/Extensions/UIImageExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ extension UIImage {
}
}

func crop(by cropInfo: CropInfo) -> UIImage? {
func crop(by cropInfo: CropInfo, borderWidth: CGFloat = 0, borderColor: UIColor = .clear) -> UIImage? {
guard let fixedImage = self.cgImageWithFixedOrientation() else {
return nil
}
Expand All @@ -135,15 +135,15 @@ extension UIImage {
let cropSize = cropInfo.cropSize
let imageViewSize = cropInfo.imageViewSize

let expectedWidth = floor(size.width / imageViewSize.width * cropSize.width) / zoomScale
let expectedHeight = floor(size.height / imageViewSize.height * cropSize.height) / zoomScale
let expectedWidth = round(size.width / imageViewSize.width * cropSize.width / zoomScale)
let expectedHeight = round(size.height / imageViewSize.height * cropSize.height / zoomScale)

return CGSize(width: expectedWidth, height: expectedHeight)
}
}

extension UIImage {
func getImageWithTransparentBackground(pathBuilder: (CGRect) -> UIBezierPath) -> UIImage? {
func getImageWithTransparentBackground(borderWidth: CGFloat = 0, borderColor: UIColor = .clear, pathBuilder: (CGRect) -> UIBezierPath) -> UIImage? {
guard let cgImage = cgImage else { return nil }

// Because imageRendererFormat is a read only property
Expand All @@ -155,36 +155,56 @@ extension UIImage {
let rect = CGRect(origin: .zero, size: size)

return UIGraphicsImageRenderer(size: size, format: format).image { _ in
pathBuilder(rect).addClip()
let path: UIBezierPath

if borderWidth > 0 {
let edgeInsets = UIEdgeInsets(top: borderWidth, left: borderWidth, bottom: borderWidth, right: borderWidth)
let innerRect = rect.inset(by: edgeInsets)
path = pathBuilder(innerRect)
borderColor.setStroke()
path.lineWidth = borderWidth
path.stroke()
} else {
path = pathBuilder(rect)
}

path.addClip()

UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation)
.draw(in: rect)
}
}

var ellipseMasked: UIImage? {
return getImageWithTransparentBackground {
func rectangleMasked(borderWidth: CGFloat = 0, borderColor: UIColor = .clear) -> UIImage? {
return getImageWithTransparentBackground(borderWidth: borderWidth, borderColor: borderColor) {
UIBezierPath(rect: $0)
}
}

func ellipseMasked(borderWidth: CGFloat = 0, borderColor: UIColor = .clear) -> UIImage? {
return getImageWithTransparentBackground(borderWidth: borderWidth, borderColor: borderColor) {
UIBezierPath(ovalIn: $0)
}
}

func roundRect(_ radius: CGFloat) -> UIImage? {
return getImageWithTransparentBackground {
func roundRect(_ radius: CGFloat, borderWidth: CGFloat = 0, borderColor: UIColor = .clear) -> UIImage? {
return getImageWithTransparentBackground(borderWidth: borderWidth, borderColor: borderColor) {
UIBezierPath(roundedRect: $0, cornerRadius: radius)
}
}

var heart: UIImage? {
return getImageWithTransparentBackground {
func heart(borderWidth: CGFloat = 0, borderColor: UIColor = .clear) -> UIImage? {
return getImageWithTransparentBackground(borderWidth: borderWidth, borderColor: borderColor) {
UIBezierPath(heartIn: $0)
}
}

func clipPath(_ points: [CGPoint]) -> UIImage? {
func clipPath(_ points: [CGPoint], borderWidth: CGFloat = 0, borderColor: UIColor = .clear) -> UIImage? {
guard points.count >= 3 else {
return nil
}

return getImageWithTransparentBackground {rect in
return getImageWithTransparentBackground(borderWidth: borderWidth, borderColor: borderColor) {rect in
let newPoints = points.map { CGPoint(x: rect.origin.x + rect.width * $0.x, y: rect.origin.y + rect.height * $0.y) }

let path = UIBezierPath()
Expand Down

0 comments on commit f55ae6e

Please sign in to comment.