123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- //
- // Quadrilateral.swift
- // WeScan
- //
- // Created by Boris Emorine on 2/8/18.
- // Copyright © 2018 WeTransfer. All rights reserved.
- //
- import Foundation
- import AVFoundation
- /// A data structure representing a quadrilateral and its position. This class exists to bypass the fact that CIRectangleFeature is read-only.
- public struct Quadrilateral: Transformable {
-
- /// A point that specifies the top left corner of the quadrilateral.
- var topLeft: CGPoint
-
- /// A point that specifies the top right corner of the quadrilateral.
- var topRight: CGPoint
-
- /// A point that specifies the bottom right corner of the quadrilateral.
- var bottomRight: CGPoint
- /// A point that specifies the bottom left corner of the quadrilateral.
- var bottomLeft: CGPoint
-
- init(rectangleFeature: CIRectangleFeature) {
- self.topLeft = rectangleFeature.topLeft
- self.topRight = rectangleFeature.topRight
- self.bottomLeft = rectangleFeature.bottomLeft
- self.bottomRight = rectangleFeature.bottomRight
- }
-
- init(topLeft: CGPoint, topRight: CGPoint, bottomRight: CGPoint, bottomLeft: CGPoint) {
- self.topLeft = topLeft
- self.topRight = topRight
- self.bottomRight = bottomRight
- self.bottomLeft = bottomLeft
- }
-
- /// Generates a `UIBezierPath` of the quadrilateral.
- func path() -> UIBezierPath {
- let path = UIBezierPath()
- path.move(to: topLeft)
- path.addLine(to: topRight)
- path.addLine(to: bottomRight)
- path.addLine(to: bottomLeft)
- path.close()
-
- return path
- }
-
- /// Applies a `CGAffineTransform` to the quadrilateral.
- ///
- /// - Parameters:
- /// - t: the transform to apply.
- /// - Returns: The transformed quadrilateral.
- func applying(_ transform: CGAffineTransform) -> Quadrilateral {
- let quadrilateral = Quadrilateral(topLeft: topLeft.applying(transform), topRight: topRight.applying(transform), bottomRight: bottomRight.applying(transform), bottomLeft: bottomLeft.applying(transform))
-
- return quadrilateral
- }
-
- /// Reorganizes the current quadrilateal, making sure that the points are at their appropriate positions. For example, it ensures that the top left point is actually the top and left point point of the quadrilateral.
- mutating func reorganize() {
- let points = [topLeft, topRight, bottomRight, bottomLeft]
- let ySortedPoints = sortPointsByYValue(points)
-
- guard ySortedPoints.count == 4 else {
- return
- }
-
- let topMostPoints = Array(ySortedPoints[0..<2])
- let bottomMostPoints = Array(ySortedPoints[2..<4])
- let xSortedTopMostPoints = sortPointsByXValue(topMostPoints)
- let xSortedBottomMostPoints = sortPointsByXValue(bottomMostPoints)
-
- guard xSortedTopMostPoints.count > 1,
- xSortedBottomMostPoints.count > 1 else {
- return
- }
-
- topLeft = xSortedTopMostPoints[0]
- topRight = xSortedTopMostPoints[1]
- bottomRight = xSortedBottomMostPoints[1]
- bottomLeft = xSortedBottomMostPoints[0]
- }
-
- /// Scales the quadrilateral based on the ratio of two given sizes, and optionnaly applies a rotation.
- ///
- /// - Parameters:
- /// - fromSize: The size the quadrilateral is currently related to.
- /// - toSize: The size to scale the quadrilateral to.
- /// - rotationAngle: The optional rotation to apply.
- /// - Returns: The newly scaled and potentially rotated quadrilateral.
- func scale(_ fromSize: CGSize, _ toSize: CGSize, withRotationAngle rotationAngle: CGFloat = 0.0) -> Quadrilateral {
- var invertedfromSize = fromSize
- let rotated = rotationAngle != 0.0
-
- if rotated && rotationAngle != CGFloat.pi {
- invertedfromSize = CGSize(width: fromSize.height, height: fromSize.width)
- }
-
- var transformedQuad = self
- let invertedFromSizeWidth = invertedfromSize.width == 0 ? .leastNormalMagnitude : invertedfromSize.width
-
- let scale = toSize.width / invertedFromSizeWidth
- let scaledTransform = CGAffineTransform(scaleX: scale, y: scale)
- transformedQuad = transformedQuad.applying(scaledTransform)
-
- if rotated {
- let rotationTransform = CGAffineTransform(rotationAngle: rotationAngle)
-
- let fromImageBounds = CGRect(x: 0.0, y: 0.0, width: fromSize.width, height: fromSize.height).applying(scaledTransform).applying(rotationTransform)
-
- let toImageBounds = CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height)
- let translationTransform = CGAffineTransform.translateTransform(fromCenterOfRect: fromImageBounds, toCenterOfRect: toImageBounds)
-
- transformedQuad = transformedQuad.applyTransforms([rotationTransform, translationTransform])
- }
-
- return transformedQuad
- }
-
- // Convenience functions
-
- /// Sorts the given `CGPoints` based on their y value.
- /// - Parameters:
- /// - points: The poinmts to sort.
- /// - Returns: The points sorted based on their y value.
- private func sortPointsByYValue(_ points: [CGPoint]) -> [CGPoint] {
- return points.sorted { (point1, point2) -> Bool in
- point1.y < point2.y
- }
- }
-
- /// Sorts the given `CGPoints` based on their x value.
- /// - Parameters:
- /// - points: The poinmts to sort.
- /// - Returns: The points sorted based on their x value.
- private func sortPointsByXValue(_ points: [CGPoint]) -> [CGPoint] {
- return points.sorted { (point1, point2) -> Bool in
- point1.x < point2.x
- }
- }
- }
- extension Quadrilateral {
-
- /// Converts the current to the cartesian coordinate system (where 0 on the y axis is at the bottom).
- ///
- /// - Parameters:
- /// - height: The height of the rect containing the quadrilateral.
- /// - Returns: The same quadrilateral in the cartesian corrdinate system.
- func toCartesian(withHeight height: CGFloat) -> Quadrilateral {
- let topLeft = self.topLeft.cartesian(withHeight: height)
- let topRight = self.topRight.cartesian(withHeight: height)
- let bottomRight = self.bottomRight.cartesian(withHeight: height)
- let bottomLeft = self.bottomLeft.cartesian(withHeight: height)
-
- return Quadrilateral(topLeft: topLeft, topRight: topRight, bottomRight: bottomRight, bottomLeft: bottomLeft)
- }
-
- }
- extension Quadrilateral: Equatable {
-
- public static func == (lhs: Quadrilateral, rhs: Quadrilateral) -> Bool {
- return lhs.topLeft == rhs.topLeft && lhs.topRight == rhs.topRight && lhs.bottomRight == rhs.bottomRight && lhs.bottomLeft == rhs.bottomLeft
- }
-
- }
|