├── .DS_Store ├── README.md └── imageEditor ├── Default-568h@2x.png ├── Main ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ImageEditor2Nib.xib └── Info.plist ├── ViewController.swift ├── class ├── .DS_Store └── kernel │ └── .DS_Store ├── icons ├── .DS_Store ├── cancel.png ├── center.png ├── center.selected.png ├── crop.png ├── crop.selected.png ├── crop_169.png ├── crop_43.png ├── crop_canvas.png ├── crop_circle.png ├── crop_free.png ├── crop_free.selected.png ├── crop_image.png ├── crop_oval.png ├── crop_path_c.png ├── crop_path_s.png ├── crop_polygon.png ├── crop_square.png ├── crop_triangle.png ├── done.png ├── fullCanvas.png ├── fullCanvas_scaleFill.png ├── fullCanvas_scaleFit.png ├── index.png ├── mark.png ├── mark2.png ├── redo.png ├── rotate.png ├── rotate.selected.png ├── rotate_90.png ├── rotate_left.png ├── rotate_right.png └── undo.png ├── imageEditor.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── pow.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── pow.xcuserdatad │ └── xcschemes │ ├── imageEditor.xcscheme │ └── xcschememanagement.plist └── kernel ├── WPAngleRuler.swift ├── WPBaseEditorView.swift ├── WPCropEditorView.swift ├── WPExtension.swift ├── WPFreePathView.swift └── WPSideMenu.swift /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WPImageEditor 2 | 3 | 全功能系列裁剪图片。 4 | # 裁剪 5 | 6 | 1. 自由矩形,拖动四角自由边矩形 7 | 8 | 2. 根据图片大小的SIZE 9 | 10 | 3. 根据您需要的画布大小,设置WPBaseEditor.canvas的size大小 11 | 12 | 4. 多边形,自由添加多边形。最少5边形。最大30边形。太大了也显示不了。可以在WPCorpEditor里面设置max&min 值.辅助菜单显示加减的按钮可以加角减角 13 | 14 | 5. 定点自由抠图,点击一个点,再点击下一个点,两点之间会连线,形成矩形之后,就可以切图了。 15 | 16 | 6. 曲线抠图,按下去一个点,拖拽到想到达的点,出现第二点,修改其点弧度可以点击当前点,弹出两根控制弧度的线来拖拽,这个用笔的话比较灵敏。手指不太好监测,有好的实现欢迎补丁。 17 | 18 | 7. 正方形切图, 19 | 20 | 8. 等边三角形, 21 | 22 | 9. 圆形 23 | 24 | 10. 椭圆形 25 | 26 | 11. 4:3的比例切图,有辅助按钮就可以变换4:3或者3:4 27 | 28 | 12. 16:9的比例切图,有辅助按钮就可以变换16:9或者9:16 29 | 30 | # redo&undo 31 | 32 | 再出现了redo&undo按钮的情况下,是支持的。 33 | # 旋转 34 | 35 | 点击旋转,会出现半圆刻度尺,尺度上的度数是算过的精准的,可以向左90度,或者向右,也可以滑动刻度尺来调整角度。 36 | # 等比 37 | 38 | 图片会根据屏幕的大小,横屏与竖屏的请款下,fill屏幕。而保持原始的图片尺寸,不会更改。 39 | 40 | ----------------- 41 | # WPImageEditor 42 | 43 | Full feature series cut pictures. 44 | # cutting 45 | 1. Free rectangle, drag the rectangle with free edges. 46 | 2. SIZE according to the picture SIZE. 47 | 3. Set the size of the WPBaseEditor. Canvas according to the size of the canvas you need. 48 | 4. Polygons, free to add polygons. At least 5 edges. Maximum 30 edges. Too big to show. The max&min value can be set in WPCorpEditor. The auxiliary menu shows the addition and subtraction of the button to add Angle reduction. 49 | 5. Click on a point and click on the next point. After the rectangle is formed, the map can be cut. 50 | 6. Curve cutout, press down a point, drag the point to think of, appear in the second place, change its radian can click on the current points, pop-up control arc two lines to drag and drop, this pen is sensitive. Finger is not very good monitor, have good implementation welcome patch. 51 | 7. Square cut, 52 | Equilateral triangle, 53 | 9. The circular 54 | 10. The oval 55 | 11.4: the proportion of 3 is cut, and the auxiliary button can be changed at 4:3 or 3:4. 56 | 12.16: the ratio of 9 is cut, and the auxiliary button can be changed to 16:9 or 9:16. 57 | # redo&undo 58 | There are redo&undo buttons that are supported. 59 | # rotation 60 | If you click on the rotation, you will see the semicircle scale, the degree of the scale is accurate, can be 90 degrees to the left, or to the right, you can also slide the scale to adjust the Angle. 61 | # geometric 62 | The picture will be based on the size of the screen, the horizontal and vertical screens, fill the screen. Keep the original image size unchanged. 63 | -------------------------------------------------------------------------------- /imageEditor/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/Default-568h@2x.png -------------------------------------------------------------------------------- /imageEditor/Main/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // WPImageEditor2 4 | // 5 | // Created by pow on 2017/11/9. 6 | // Copyright © 2017年 pow. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | 21 | 22 | 23 | 24 | return true 25 | } 26 | 27 | 28 | func applicationWillResignActive(_ application: UIApplication) { 29 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 30 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 31 | } 32 | 33 | func applicationDidEnterBackground(_ application: UIApplication) { 34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | func applicationWillEnterForeground(_ application: UIApplication) { 39 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 40 | } 41 | 42 | func applicationDidBecomeActive(_ application: UIApplication) { 43 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 44 | } 45 | 46 | func applicationWillTerminate(_ application: UIApplication) { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /imageEditor/Main/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /imageEditor/Main/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /imageEditor/Main/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /imageEditor/Main/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSPhotoLibraryUsageDescription 24 | 使用相机 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | UIInterfaceOrientationPortraitUpsideDown 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /imageEditor/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // WPImageEditor2 4 | // 5 | // Created by pow on 2017/11/9. 6 | // Copyright © 2017年 pow. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // addEditorImageView(originalImage: UIImage.init(named: "ape_fwk_all")!) 17 | 18 | 19 | 20 | // Do any additional setup after loading the view, typically from a nib. 21 | } 22 | 23 | override func didReceiveMemoryWarning() { 24 | super.didReceiveMemoryWarning() 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | 29 | func addEditorImageView(originalImage:UIImage) { 30 | 31 | let baseView:WPBaseEditorView = WPBaseEditorView.loadNibFileToSetup(editorImage:originalImage , InSuperView: self.view) 32 | baseView.moveableArea = self.view.frame 33 | baseView.setup() 34 | baseView.pasteBlock = {(image, bounds) in 35 | 36 | self.backImage(image: image, point: bounds) 37 | 38 | } 39 | } 40 | 41 | func backImage(image:UIImage,point:CGRect) { 42 | 43 | let imgView = UIImageView.init(image: image) 44 | 45 | 46 | 47 | imgView.frame = point 48 | 49 | imgView.backgroundColor = UIColor.black 50 | self.view.addSubview(imgView) 51 | 52 | } 53 | 54 | 55 | 56 | } 57 | 58 | //MARK:Import images from Photos Library 59 | extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 60 | 61 | 62 | @IBAction func performImport2(_ sender: UIButton) { 63 | let imagePicker = UIImagePickerController() 64 | imagePicker.sourceType = .savedPhotosAlbum 65 | imagePicker.delegate = self 66 | self.present(imagePicker, animated: true, completion: nil) 67 | } 68 | 69 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 70 | if (info[UIImagePickerControllerOriginalImage] as? UIImage) != nil { 71 | 72 | picker.dismiss(animated: true, completion: { 73 | 74 | 75 | self.addEditorImageView(originalImage:info[UIImagePickerControllerOriginalImage] as! UIImage) 76 | 77 | }) 78 | } 79 | } 80 | 81 | 82 | override var shouldAutorotate: Bool { 83 | return false 84 | } 85 | 86 | 87 | 88 | 89 | 90 | 91 | } 92 | 93 | -------------------------------------------------------------------------------- /imageEditor/class/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/class/.DS_Store -------------------------------------------------------------------------------- /imageEditor/class/kernel/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/class/kernel/.DS_Store -------------------------------------------------------------------------------- /imageEditor/icons/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/.DS_Store -------------------------------------------------------------------------------- /imageEditor/icons/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/cancel.png -------------------------------------------------------------------------------- /imageEditor/icons/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/center.png -------------------------------------------------------------------------------- /imageEditor/icons/center.selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/center.selected.png -------------------------------------------------------------------------------- /imageEditor/icons/crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop.png -------------------------------------------------------------------------------- /imageEditor/icons/crop.selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop.selected.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_169.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_169.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_43.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_canvas.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_circle.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_free.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_free.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_free.selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_free.selected.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_image.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_oval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_oval.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_path_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_path_c.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_path_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_path_s.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_polygon.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_square.png -------------------------------------------------------------------------------- /imageEditor/icons/crop_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/crop_triangle.png -------------------------------------------------------------------------------- /imageEditor/icons/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/done.png -------------------------------------------------------------------------------- /imageEditor/icons/fullCanvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/fullCanvas.png -------------------------------------------------------------------------------- /imageEditor/icons/fullCanvas_scaleFill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/fullCanvas_scaleFill.png -------------------------------------------------------------------------------- /imageEditor/icons/fullCanvas_scaleFit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/fullCanvas_scaleFit.png -------------------------------------------------------------------------------- /imageEditor/icons/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/index.png -------------------------------------------------------------------------------- /imageEditor/icons/mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/mark.png -------------------------------------------------------------------------------- /imageEditor/icons/mark2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/mark2.png -------------------------------------------------------------------------------- /imageEditor/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/redo.png -------------------------------------------------------------------------------- /imageEditor/icons/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/rotate.png -------------------------------------------------------------------------------- /imageEditor/icons/rotate.selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/rotate.selected.png -------------------------------------------------------------------------------- /imageEditor/icons/rotate_90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/rotate_90.png -------------------------------------------------------------------------------- /imageEditor/icons/rotate_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/rotate_left.png -------------------------------------------------------------------------------- /imageEditor/icons/rotate_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/rotate_right.png -------------------------------------------------------------------------------- /imageEditor/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/icons/undo.png -------------------------------------------------------------------------------- /imageEditor/imageEditor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9636DC741FE122CF005B93BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC191FE122CF005B93BD /* AppDelegate.swift */; }; 11 | 9636DC751FE122CF005B93BD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC1A1FE122CF005B93BD /* Assets.xcassets */; }; 12 | 9636DC781FE122CF005B93BD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC1F1FE122CF005B93BD /* LaunchScreen.storyboard */; }; 13 | 9636DC791FE122CF005B93BD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC211FE122CF005B93BD /* Main.storyboard */; }; 14 | 9636DC911FE122CF005B93BD /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC3C1FE122CF005B93BD /* cancel.png */; }; 15 | 9636DC921FE122CF005B93BD /* center.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC3D1FE122CF005B93BD /* center.png */; }; 16 | 9636DC931FE122CF005B93BD /* center.selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC3E1FE122CF005B93BD /* center.selected.png */; }; 17 | 9636DC941FE122CF005B93BD /* crop.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC3F1FE122CF005B93BD /* crop.png */; }; 18 | 9636DC951FE122CF005B93BD /* crop.selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC401FE122CF005B93BD /* crop.selected.png */; }; 19 | 9636DC961FE122CF005B93BD /* crop_169.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC411FE122CF005B93BD /* crop_169.png */; }; 20 | 9636DC971FE122CF005B93BD /* crop_43.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC421FE122CF005B93BD /* crop_43.png */; }; 21 | 9636DC981FE122CF005B93BD /* crop_canvas.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC431FE122CF005B93BD /* crop_canvas.png */; }; 22 | 9636DC991FE122CF005B93BD /* crop_circle.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC441FE122CF005B93BD /* crop_circle.png */; }; 23 | 9636DC9A1FE122CF005B93BD /* crop_free.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC451FE122CF005B93BD /* crop_free.png */; }; 24 | 9636DC9B1FE122CF005B93BD /* crop_free.selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC461FE122CF005B93BD /* crop_free.selected.png */; }; 25 | 9636DC9C1FE122CF005B93BD /* crop_image.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC471FE122CF005B93BD /* crop_image.png */; }; 26 | 9636DC9D1FE122CF005B93BD /* crop_oval.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC481FE122CF005B93BD /* crop_oval.png */; }; 27 | 9636DC9E1FE122CF005B93BD /* crop_path_c.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC491FE122CF005B93BD /* crop_path_c.png */; }; 28 | 9636DC9F1FE122CF005B93BD /* crop_path_s.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC4A1FE122CF005B93BD /* crop_path_s.png */; }; 29 | 9636DCA01FE122CF005B93BD /* crop_polygon.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC4B1FE122CF005B93BD /* crop_polygon.png */; }; 30 | 9636DCA11FE122CF005B93BD /* crop_square.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC4C1FE122CF005B93BD /* crop_square.png */; }; 31 | 9636DCA21FE122CF005B93BD /* crop_triangle.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC4D1FE122CF005B93BD /* crop_triangle.png */; }; 32 | 9636DCA31FE122CF005B93BD /* done.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC4E1FE122CF005B93BD /* done.png */; }; 33 | 9636DCA41FE122CF005B93BD /* fullCanvas.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC4F1FE122CF005B93BD /* fullCanvas.png */; }; 34 | 9636DCA51FE122CF005B93BD /* fullCanvas_scaleFill.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC501FE122CF005B93BD /* fullCanvas_scaleFill.png */; }; 35 | 9636DCA61FE122CF005B93BD /* fullCanvas_scaleFit.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC511FE122CF005B93BD /* fullCanvas_scaleFit.png */; }; 36 | 9636DCA71FE122CF005B93BD /* index.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC521FE122CF005B93BD /* index.png */; }; 37 | 9636DCA81FE122CF005B93BD /* mark.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC531FE122CF005B93BD /* mark.png */; }; 38 | 9636DCA91FE122CF005B93BD /* mark2.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC541FE122CF005B93BD /* mark2.png */; }; 39 | 9636DCAA1FE122CF005B93BD /* redo.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC551FE122CF005B93BD /* redo.png */; }; 40 | 9636DCAB1FE122CF005B93BD /* rotate.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC561FE122CF005B93BD /* rotate.png */; }; 41 | 9636DCAC1FE122CF005B93BD /* rotate.selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC571FE122CF005B93BD /* rotate.selected.png */; }; 42 | 9636DCAD1FE122CF005B93BD /* rotate_90.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC581FE122CF005B93BD /* rotate_90.png */; }; 43 | 9636DCAE1FE122CF005B93BD /* rotate_left.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC591FE122CF005B93BD /* rotate_left.png */; }; 44 | 9636DCAF1FE122CF005B93BD /* rotate_right.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC5A1FE122CF005B93BD /* rotate_right.png */; }; 45 | 9636DCB01FE122CF005B93BD /* undo.png in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC5B1FE122CF005B93BD /* undo.png */; }; 46 | 9636DCB11FE122CF005B93BD /* ImageEditor2Nib.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC5C1FE122CF005B93BD /* ImageEditor2Nib.xib */; }; 47 | 9636DCB31FE122CF005B93BD /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9636DC5E1FE122CF005B93BD /* Info.plist */; }; 48 | 9636DCC01FE122CF005B93BD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC6C1FE122CF005B93BD /* ViewController.swift */; }; 49 | 9636DCC11FE122CF005B93BD /* WPAngleRuler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC6D1FE122CF005B93BD /* WPAngleRuler.swift */; }; 50 | 9636DCC21FE122CF005B93BD /* WPBaseEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC6E1FE122CF005B93BD /* WPBaseEditorView.swift */; }; 51 | 9636DCC31FE122CF005B93BD /* WPCropEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC6F1FE122CF005B93BD /* WPCropEditorView.swift */; }; 52 | 9636DCC41FE122CF005B93BD /* WPExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC701FE122CF005B93BD /* WPExtension.swift */; }; 53 | 9636DCC51FE122CF005B93BD /* WPFreePathView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC711FE122CF005B93BD /* WPFreePathView.swift */; }; 54 | 9636DCC61FE122CF005B93BD /* WPSideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9636DC721FE122CF005B93BD /* WPSideMenu.swift */; }; 55 | 96E3437A201AFEE800AB031E /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 96E34379201AFEE800AB031E /* Default-568h@2x.png */; }; 56 | /* End PBXBuildFile section */ 57 | 58 | /* Begin PBXFileReference section */ 59 | 9636DC191FE122CF005B93BD /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 60 | 9636DC1A1FE122CF005B93BD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 9636DC201FE122CF005B93BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 9636DC221FE122CF005B93BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63 | 9636DC3C1FE122CF005B93BD /* cancel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cancel.png; sourceTree = ""; }; 64 | 9636DC3D1FE122CF005B93BD /* center.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = center.png; sourceTree = ""; }; 65 | 9636DC3E1FE122CF005B93BD /* center.selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = center.selected.png; sourceTree = ""; }; 66 | 9636DC3F1FE122CF005B93BD /* crop.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop.png; sourceTree = ""; }; 67 | 9636DC401FE122CF005B93BD /* crop.selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop.selected.png; sourceTree = ""; }; 68 | 9636DC411FE122CF005B93BD /* crop_169.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_169.png; sourceTree = ""; }; 69 | 9636DC421FE122CF005B93BD /* crop_43.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_43.png; sourceTree = ""; }; 70 | 9636DC431FE122CF005B93BD /* crop_canvas.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_canvas.png; sourceTree = ""; }; 71 | 9636DC441FE122CF005B93BD /* crop_circle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_circle.png; sourceTree = ""; }; 72 | 9636DC451FE122CF005B93BD /* crop_free.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_free.png; sourceTree = ""; }; 73 | 9636DC461FE122CF005B93BD /* crop_free.selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_free.selected.png; sourceTree = ""; }; 74 | 9636DC471FE122CF005B93BD /* crop_image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_image.png; sourceTree = ""; }; 75 | 9636DC481FE122CF005B93BD /* crop_oval.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_oval.png; sourceTree = ""; }; 76 | 9636DC491FE122CF005B93BD /* crop_path_c.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_path_c.png; sourceTree = ""; }; 77 | 9636DC4A1FE122CF005B93BD /* crop_path_s.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_path_s.png; sourceTree = ""; }; 78 | 9636DC4B1FE122CF005B93BD /* crop_polygon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_polygon.png; sourceTree = ""; }; 79 | 9636DC4C1FE122CF005B93BD /* crop_square.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_square.png; sourceTree = ""; }; 80 | 9636DC4D1FE122CF005B93BD /* crop_triangle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = crop_triangle.png; sourceTree = ""; }; 81 | 9636DC4E1FE122CF005B93BD /* done.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = done.png; sourceTree = ""; }; 82 | 9636DC4F1FE122CF005B93BD /* fullCanvas.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = fullCanvas.png; sourceTree = ""; }; 83 | 9636DC501FE122CF005B93BD /* fullCanvas_scaleFill.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = fullCanvas_scaleFill.png; sourceTree = ""; }; 84 | 9636DC511FE122CF005B93BD /* fullCanvas_scaleFit.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = fullCanvas_scaleFit.png; sourceTree = ""; }; 85 | 9636DC521FE122CF005B93BD /* index.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = index.png; sourceTree = ""; }; 86 | 9636DC531FE122CF005B93BD /* mark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mark.png; sourceTree = ""; }; 87 | 9636DC541FE122CF005B93BD /* mark2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mark2.png; sourceTree = ""; }; 88 | 9636DC551FE122CF005B93BD /* redo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = redo.png; sourceTree = ""; }; 89 | 9636DC561FE122CF005B93BD /* rotate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rotate.png; sourceTree = ""; }; 90 | 9636DC571FE122CF005B93BD /* rotate.selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rotate.selected.png; sourceTree = ""; }; 91 | 9636DC581FE122CF005B93BD /* rotate_90.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rotate_90.png; sourceTree = ""; }; 92 | 9636DC591FE122CF005B93BD /* rotate_left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rotate_left.png; sourceTree = ""; }; 93 | 9636DC5A1FE122CF005B93BD /* rotate_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rotate_right.png; sourceTree = ""; }; 94 | 9636DC5B1FE122CF005B93BD /* undo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = undo.png; sourceTree = ""; }; 95 | 9636DC5C1FE122CF005B93BD /* ImageEditor2Nib.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ImageEditor2Nib.xib; sourceTree = ""; }; 96 | 9636DC5E1FE122CF005B93BD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 97 | 9636DC6C1FE122CF005B93BD /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 98 | 9636DC6D1FE122CF005B93BD /* WPAngleRuler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPAngleRuler.swift; sourceTree = ""; }; 99 | 9636DC6E1FE122CF005B93BD /* WPBaseEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPBaseEditorView.swift; sourceTree = ""; }; 100 | 9636DC6F1FE122CF005B93BD /* WPCropEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPCropEditorView.swift; sourceTree = ""; }; 101 | 9636DC701FE122CF005B93BD /* WPExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPExtension.swift; sourceTree = ""; }; 102 | 9636DC711FE122CF005B93BD /* WPFreePathView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPFreePathView.swift; sourceTree = ""; }; 103 | 9636DC721FE122CF005B93BD /* WPSideMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPSideMenu.swift; sourceTree = ""; }; 104 | 966025481FAC45A200BEA651 /* imageEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = imageEditor.app; sourceTree = BUILT_PRODUCTS_DIR; }; 105 | 96E34379201AFEE800AB031E /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 106 | /* End PBXFileReference section */ 107 | 108 | /* Begin PBXFrameworksBuildPhase section */ 109 | 966025451FAC45A200BEA651 /* Frameworks */ = { 110 | isa = PBXFrameworksBuildPhase; 111 | buildActionMask = 2147483647; 112 | files = ( 113 | ); 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | /* End PBXFrameworksBuildPhase section */ 117 | 118 | /* Begin PBXGroup section */ 119 | 9636DC3B1FE122CF005B93BD /* icons */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 9636DC3C1FE122CF005B93BD /* cancel.png */, 123 | 9636DC3D1FE122CF005B93BD /* center.png */, 124 | 9636DC3E1FE122CF005B93BD /* center.selected.png */, 125 | 9636DC3F1FE122CF005B93BD /* crop.png */, 126 | 9636DC401FE122CF005B93BD /* crop.selected.png */, 127 | 9636DC411FE122CF005B93BD /* crop_169.png */, 128 | 9636DC421FE122CF005B93BD /* crop_43.png */, 129 | 9636DC431FE122CF005B93BD /* crop_canvas.png */, 130 | 9636DC441FE122CF005B93BD /* crop_circle.png */, 131 | 9636DC451FE122CF005B93BD /* crop_free.png */, 132 | 9636DC461FE122CF005B93BD /* crop_free.selected.png */, 133 | 9636DC471FE122CF005B93BD /* crop_image.png */, 134 | 9636DC481FE122CF005B93BD /* crop_oval.png */, 135 | 9636DC491FE122CF005B93BD /* crop_path_c.png */, 136 | 9636DC4A1FE122CF005B93BD /* crop_path_s.png */, 137 | 9636DC4B1FE122CF005B93BD /* crop_polygon.png */, 138 | 9636DC4C1FE122CF005B93BD /* crop_square.png */, 139 | 9636DC4D1FE122CF005B93BD /* crop_triangle.png */, 140 | 9636DC4E1FE122CF005B93BD /* done.png */, 141 | 9636DC4F1FE122CF005B93BD /* fullCanvas.png */, 142 | 9636DC501FE122CF005B93BD /* fullCanvas_scaleFill.png */, 143 | 9636DC511FE122CF005B93BD /* fullCanvas_scaleFit.png */, 144 | 9636DC521FE122CF005B93BD /* index.png */, 145 | 9636DC531FE122CF005B93BD /* mark.png */, 146 | 9636DC541FE122CF005B93BD /* mark2.png */, 147 | 9636DC551FE122CF005B93BD /* redo.png */, 148 | 9636DC561FE122CF005B93BD /* rotate.png */, 149 | 9636DC571FE122CF005B93BD /* rotate.selected.png */, 150 | 9636DC581FE122CF005B93BD /* rotate_90.png */, 151 | 9636DC591FE122CF005B93BD /* rotate_left.png */, 152 | 9636DC5A1FE122CF005B93BD /* rotate_right.png */, 153 | 9636DC5B1FE122CF005B93BD /* undo.png */, 154 | ); 155 | path = icons; 156 | sourceTree = ""; 157 | }; 158 | 9660253F1FAC45A200BEA651 = { 159 | isa = PBXGroup; 160 | children = ( 161 | 96E34379201AFEE800AB031E /* Default-568h@2x.png */, 162 | 9636DC6C1FE122CF005B93BD /* ViewController.swift */, 163 | 9636DC3B1FE122CF005B93BD /* icons */, 164 | 96E34378201AFEB600AB031E /* Main */, 165 | 96E34377201AFEA600AB031E /* kernel */, 166 | 966025491FAC45A200BEA651 /* Products */, 167 | ); 168 | sourceTree = ""; 169 | }; 170 | 966025491FAC45A200BEA651 /* Products */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 966025481FAC45A200BEA651 /* imageEditor.app */, 174 | ); 175 | name = Products; 176 | sourceTree = ""; 177 | }; 178 | 96E34377201AFEA600AB031E /* kernel */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 9636DC6D1FE122CF005B93BD /* WPAngleRuler.swift */, 182 | 9636DC6E1FE122CF005B93BD /* WPBaseEditorView.swift */, 183 | 9636DC6F1FE122CF005B93BD /* WPCropEditorView.swift */, 184 | 9636DC701FE122CF005B93BD /* WPExtension.swift */, 185 | 9636DC711FE122CF005B93BD /* WPFreePathView.swift */, 186 | 9636DC721FE122CF005B93BD /* WPSideMenu.swift */, 187 | ); 188 | path = kernel; 189 | sourceTree = ""; 190 | }; 191 | 96E34378201AFEB600AB031E /* Main */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 9636DC5E1FE122CF005B93BD /* Info.plist */, 195 | 9636DC191FE122CF005B93BD /* AppDelegate.swift */, 196 | 9636DC1A1FE122CF005B93BD /* Assets.xcassets */, 197 | 9636DC1F1FE122CF005B93BD /* LaunchScreen.storyboard */, 198 | 9636DC211FE122CF005B93BD /* Main.storyboard */, 199 | 9636DC5C1FE122CF005B93BD /* ImageEditor2Nib.xib */, 200 | ); 201 | path = Main; 202 | sourceTree = ""; 203 | }; 204 | /* End PBXGroup section */ 205 | 206 | /* Begin PBXNativeTarget section */ 207 | 966025471FAC45A200BEA651 /* imageEditor */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = 9660255A1FAC45A200BEA651 /* Build configuration list for PBXNativeTarget "imageEditor" */; 210 | buildPhases = ( 211 | 966025441FAC45A200BEA651 /* Sources */, 212 | 966025451FAC45A200BEA651 /* Frameworks */, 213 | 966025461FAC45A200BEA651 /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | ); 219 | name = imageEditor; 220 | productName = imageEditor; 221 | productReference = 966025481FAC45A200BEA651 /* imageEditor.app */; 222 | productType = "com.apple.product-type.application"; 223 | }; 224 | /* End PBXNativeTarget section */ 225 | 226 | /* Begin PBXProject section */ 227 | 966025401FAC45A200BEA651 /* Project object */ = { 228 | isa = PBXProject; 229 | attributes = { 230 | LastSwiftUpdateCheck = 0830; 231 | LastUpgradeCheck = 0920; 232 | ORGANIZATIONNAME = pow; 233 | TargetAttributes = { 234 | 966025471FAC45A200BEA651 = { 235 | CreatedOnToolsVersion = 8.3.3; 236 | DevelopmentTeam = 387DLK86QQ; 237 | LastSwiftMigration = 0910; 238 | ProvisioningStyle = Automatic; 239 | }; 240 | }; 241 | }; 242 | buildConfigurationList = 966025431FAC45A200BEA651 /* Build configuration list for PBXProject "imageEditor" */; 243 | compatibilityVersion = "Xcode 3.2"; 244 | developmentRegion = English; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | en, 248 | Base, 249 | ); 250 | mainGroup = 9660253F1FAC45A200BEA651; 251 | productRefGroup = 966025491FAC45A200BEA651 /* Products */; 252 | projectDirPath = ""; 253 | projectRoot = ""; 254 | targets = ( 255 | 966025471FAC45A200BEA651 /* imageEditor */, 256 | ); 257 | }; 258 | /* End PBXProject section */ 259 | 260 | /* Begin PBXResourcesBuildPhase section */ 261 | 966025461FAC45A200BEA651 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 9636DC791FE122CF005B93BD /* Main.storyboard in Resources */, 266 | 9636DCB11FE122CF005B93BD /* ImageEditor2Nib.xib in Resources */, 267 | 9636DCA01FE122CF005B93BD /* crop_polygon.png in Resources */, 268 | 9636DCAB1FE122CF005B93BD /* rotate.png in Resources */, 269 | 9636DCA51FE122CF005B93BD /* fullCanvas_scaleFill.png in Resources */, 270 | 9636DCAA1FE122CF005B93BD /* redo.png in Resources */, 271 | 9636DC971FE122CF005B93BD /* crop_43.png in Resources */, 272 | 9636DCAF1FE122CF005B93BD /* rotate_right.png in Resources */, 273 | 9636DC921FE122CF005B93BD /* center.png in Resources */, 274 | 9636DC951FE122CF005B93BD /* crop.selected.png in Resources */, 275 | 9636DC941FE122CF005B93BD /* crop.png in Resources */, 276 | 9636DC781FE122CF005B93BD /* LaunchScreen.storyboard in Resources */, 277 | 9636DCAE1FE122CF005B93BD /* rotate_left.png in Resources */, 278 | 9636DCAD1FE122CF005B93BD /* rotate_90.png in Resources */, 279 | 9636DCA21FE122CF005B93BD /* crop_triangle.png in Resources */, 280 | 9636DCA81FE122CF005B93BD /* mark.png in Resources */, 281 | 9636DCB31FE122CF005B93BD /* Info.plist in Resources */, 282 | 9636DC751FE122CF005B93BD /* Assets.xcassets in Resources */, 283 | 9636DCA41FE122CF005B93BD /* fullCanvas.png in Resources */, 284 | 9636DCB01FE122CF005B93BD /* undo.png in Resources */, 285 | 9636DCA71FE122CF005B93BD /* index.png in Resources */, 286 | 9636DC981FE122CF005B93BD /* crop_canvas.png in Resources */, 287 | 9636DC931FE122CF005B93BD /* center.selected.png in Resources */, 288 | 9636DCA31FE122CF005B93BD /* done.png in Resources */, 289 | 9636DC9E1FE122CF005B93BD /* crop_path_c.png in Resources */, 290 | 9636DC9D1FE122CF005B93BD /* crop_oval.png in Resources */, 291 | 9636DC911FE122CF005B93BD /* cancel.png in Resources */, 292 | 9636DC961FE122CF005B93BD /* crop_169.png in Resources */, 293 | 9636DC9F1FE122CF005B93BD /* crop_path_s.png in Resources */, 294 | 9636DC9A1FE122CF005B93BD /* crop_free.png in Resources */, 295 | 9636DC9B1FE122CF005B93BD /* crop_free.selected.png in Resources */, 296 | 96E3437A201AFEE800AB031E /* Default-568h@2x.png in Resources */, 297 | 9636DCA91FE122CF005B93BD /* mark2.png in Resources */, 298 | 9636DCA61FE122CF005B93BD /* fullCanvas_scaleFit.png in Resources */, 299 | 9636DC9C1FE122CF005B93BD /* crop_image.png in Resources */, 300 | 9636DCAC1FE122CF005B93BD /* rotate.selected.png in Resources */, 301 | 9636DCA11FE122CF005B93BD /* crop_square.png in Resources */, 302 | 9636DC991FE122CF005B93BD /* crop_circle.png in Resources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | /* End PBXResourcesBuildPhase section */ 307 | 308 | /* Begin PBXSourcesBuildPhase section */ 309 | 966025441FAC45A200BEA651 /* Sources */ = { 310 | isa = PBXSourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | 9636DCC51FE122CF005B93BD /* WPFreePathView.swift in Sources */, 314 | 9636DC741FE122CF005B93BD /* AppDelegate.swift in Sources */, 315 | 9636DCC21FE122CF005B93BD /* WPBaseEditorView.swift in Sources */, 316 | 9636DCC11FE122CF005B93BD /* WPAngleRuler.swift in Sources */, 317 | 9636DCC61FE122CF005B93BD /* WPSideMenu.swift in Sources */, 318 | 9636DCC41FE122CF005B93BD /* WPExtension.swift in Sources */, 319 | 9636DCC01FE122CF005B93BD /* ViewController.swift in Sources */, 320 | 9636DCC31FE122CF005B93BD /* WPCropEditorView.swift in Sources */, 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | }; 324 | /* End PBXSourcesBuildPhase section */ 325 | 326 | /* Begin PBXVariantGroup section */ 327 | 9636DC1F1FE122CF005B93BD /* LaunchScreen.storyboard */ = { 328 | isa = PBXVariantGroup; 329 | children = ( 330 | 9636DC201FE122CF005B93BD /* Base */, 331 | ); 332 | name = LaunchScreen.storyboard; 333 | sourceTree = ""; 334 | }; 335 | 9636DC211FE122CF005B93BD /* Main.storyboard */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | 9636DC221FE122CF005B93BD /* Base */, 339 | ); 340 | name = Main.storyboard; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXVariantGroup section */ 344 | 345 | /* Begin XCBuildConfiguration section */ 346 | 966025581FAC45A200BEA651 /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | CLANG_ANALYZER_NONNULL = YES; 351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 353 | CLANG_CXX_LIBRARY = "libc++"; 354 | CLANG_ENABLE_MODULES = YES; 355 | CLANG_ENABLE_OBJC_ARC = YES; 356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 357 | CLANG_WARN_BOOL_CONVERSION = YES; 358 | CLANG_WARN_COMMA = YES; 359 | CLANG_WARN_CONSTANT_CONVERSION = YES; 360 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 361 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 362 | CLANG_WARN_EMPTY_BODY = YES; 363 | CLANG_WARN_ENUM_CONVERSION = YES; 364 | CLANG_WARN_INFINITE_RECURSION = YES; 365 | CLANG_WARN_INT_CONVERSION = YES; 366 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 367 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 368 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 369 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 370 | CLANG_WARN_STRICT_PROTOTYPES = YES; 371 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 374 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 375 | COPY_PHASE_STRIP = NO; 376 | DEBUG_INFORMATION_FORMAT = dwarf; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | ENABLE_TESTABILITY = YES; 379 | GCC_C_LANGUAGE_STANDARD = gnu99; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 394 | MTL_ENABLE_DEBUG_INFO = YES; 395 | ONLY_ACTIVE_ARCH = YES; 396 | SDKROOT = iphoneos; 397 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 398 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 399 | TARGETED_DEVICE_FAMILY = "1,2"; 400 | }; 401 | name = Debug; 402 | }; 403 | 966025591FAC45A200BEA651 /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ALWAYS_SEARCH_USER_PATHS = NO; 407 | CLANG_ANALYZER_NONNULL = YES; 408 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 410 | CLANG_CXX_LIBRARY = "libc++"; 411 | CLANG_ENABLE_MODULES = YES; 412 | CLANG_ENABLE_OBJC_ARC = YES; 413 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 414 | CLANG_WARN_BOOL_CONVERSION = YES; 415 | CLANG_WARN_COMMA = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 425 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 426 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 427 | CLANG_WARN_STRICT_PROTOTYPES = YES; 428 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 432 | COPY_PHASE_STRIP = NO; 433 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 434 | ENABLE_NS_ASSERTIONS = NO; 435 | ENABLE_STRICT_OBJC_MSGSEND = YES; 436 | GCC_C_LANGUAGE_STANDARD = gnu99; 437 | GCC_NO_COMMON_BLOCKS = YES; 438 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 439 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 440 | GCC_WARN_UNDECLARED_SELECTOR = YES; 441 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 442 | GCC_WARN_UNUSED_FUNCTION = YES; 443 | GCC_WARN_UNUSED_VARIABLE = YES; 444 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 445 | MTL_ENABLE_DEBUG_INFO = NO; 446 | SDKROOT = iphoneos; 447 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 448 | TARGETED_DEVICE_FAMILY = "1,2"; 449 | VALIDATE_PRODUCT = YES; 450 | }; 451 | name = Release; 452 | }; 453 | 9660255B1FAC45A200BEA651 /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | CLANG_ENABLE_MODULES = YES; 457 | DEVELOPMENT_TEAM = 387DLK86QQ; 458 | INFOPLIST_FILE = "$(SRCROOT)/Main/Info.plist"; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | PRODUCT_BUNDLE_IDENTIFIER = com.powWong.imageEditor; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 463 | SWIFT_VERSION = 3.0; 464 | }; 465 | name = Debug; 466 | }; 467 | 9660255C1FAC45A200BEA651 /* Release */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | CLANG_ENABLE_MODULES = YES; 471 | DEVELOPMENT_TEAM = 387DLK86QQ; 472 | INFOPLIST_FILE = "$(SRCROOT)/Main/Info.plist"; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 474 | PRODUCT_BUNDLE_IDENTIFIER = com.powWong.imageEditor; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SWIFT_VERSION = 3.0; 477 | }; 478 | name = Release; 479 | }; 480 | /* End XCBuildConfiguration section */ 481 | 482 | /* Begin XCConfigurationList section */ 483 | 966025431FAC45A200BEA651 /* Build configuration list for PBXProject "imageEditor" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | 966025581FAC45A200BEA651 /* Debug */, 487 | 966025591FAC45A200BEA651 /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | 9660255A1FAC45A200BEA651 /* Build configuration list for PBXNativeTarget "imageEditor" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | 9660255B1FAC45A200BEA651 /* Debug */, 496 | 9660255C1FAC45A200BEA651 /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | /* End XCConfigurationList section */ 502 | }; 503 | rootObject = 966025401FAC45A200BEA651 /* Project object */; 504 | } 505 | -------------------------------------------------------------------------------- /imageEditor/imageEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /imageEditor/imageEditor.xcodeproj/project.xcworkspace/xcuserdata/pow.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaineWang/WPImageEditor/1c2cca3a61f7b533f79ecbf346b7cb97910dbdb0/imageEditor/imageEditor.xcodeproj/project.xcworkspace/xcuserdata/pow.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /imageEditor/imageEditor.xcodeproj/xcuserdata/pow.xcuserdatad/xcschemes/imageEditor.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /imageEditor/imageEditor.xcodeproj/xcuserdata/pow.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | imageEditor.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 966025471FAC45A200BEA651 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /imageEditor/kernel/WPAngleRuler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WPAngleRuler.swift 3 | // WPImageEditor2 4 | // 5 | // Created by pow on 2017/11/20. 6 | // Copyright © 2017年 pow. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let markCellIdentifier = "markCellIdentifier" 12 | 13 | protocol WPAngleRulerProtocol { 14 | 15 | func didScrollAngle(angle:CGFloat) 16 | func scrollRulerState(panGresture:UIPanGestureRecognizer) 17 | 18 | } 19 | 20 | class WPAngleRuler: UIView ,UICollectionViewDelegate,UICollectionViewDataSource{ 21 | 22 | 23 | var rulerSources:[Int] = Array() 24 | 25 | var attachView:UIView? 26 | var lastclockwise_Angle:CGFloat = 0 27 | 28 | var delegate:WPAngleRulerProtocol? 29 | 30 | // point convet angle 31 | internal func AngleBetweenPoints(loacationPoint locapoint:CGPoint, startPoint:CGPoint, centerPonit:CGPoint)->CGFloat { 32 | return atan2(locapoint.y - centerPonit.y, locapoint.x - centerPonit.x) - atan2(startPoint.y - centerPonit.y, startPoint.x - centerPonit.x); 33 | } 34 | 35 | lazy var angleCollectionView:CricleCollectionView = { 36 | let layout:RadianLayout = RadianLayout.init() 37 | let coll = CricleCollectionView.init(frame:CGRect.init(origin: CGPoint.zero, size: self.frame.size), collectionViewLayout: layout) 38 | coll.backgroundColor = UIColor.clear 39 | coll.delegate = self 40 | coll.dataSource = self 41 | self.addSubview(coll) 42 | 43 | return coll 44 | }() 45 | 46 | 47 | init(rulerDataSource:[Int],rotatedView:UIView) { 48 | super.init(frame:CGRect.zero) 49 | self.bounds = CGRect.init(x: 0, y: 0, width: compute_Size().width, height: compute_Size().height) 50 | self.center = CGPoint.init(x:UIScreen.main.bounds.width/2, y:UIScreen.main.bounds.height+(self.bounds.height/10)) 51 | // self.center = rotatedView.center 52 | self.rulerSources = rulerDataSource 53 | self.backgroundColor = UIColor.clear 54 | self.angleCollectionView.register(MarkCell.self, forCellWithReuseIdentifier:markCellIdentifier) 55 | let pan:UIPanGestureRecognizer = UIPanGestureRecognizer.init(target: self, action: #selector(panGestureRecognizer(pan:))) 56 | self.addGestureRecognizer(pan) 57 | let arr:UIImageView = UIImageView.init(image: UIImage.init(named: "arrow", in: Bundle.init(for: WPAngleRuler.self), compatibleWith: nil)) 58 | arr.frame = CGRect.zero 59 | arr.center = CGPoint.init(x: self.bounds.width/2, y:50) 60 | arr.bounds = CGRect.init(x: 0, y: 0, width:15, height:15) 61 | self.addSubview(arr) 62 | self.attachView = rotatedView 63 | NotificationCenter.default.addObserver(self, selector: #selector(willChnagngeStatusBarFrame), name: NSNotification.Name.UIApplicationDidChangeStatusBarOrientation, object: nil) 64 | 65 | 66 | } 67 | 68 | @objc func willChnagngeStatusBarFrame() { 69 | self.center = CGPoint.init(x:UIScreen.main.bounds.width/2, y:UIScreen.main.bounds.height+(self.bounds.height/10)) 70 | 71 | } 72 | 73 | func compute_Size() -> CGSize { 74 | var width:CGFloat,height:CGFloat 75 | let min:CGFloat = UIScreen.main.bounds.width > UIScreen.main.bounds.height ? UIScreen.main.bounds.height : UIScreen.main.bounds.width 76 | width = min/2 77 | height = width 78 | return CGSize.init(width: width, height: height) 79 | 80 | } 81 | 82 | required init?(coder aDecoder: NSCoder) { 83 | fatalError("init(coder:) has not been implemented") 84 | } 85 | 86 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 87 | return self.rulerSources.count 88 | } 89 | 90 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 91 | 92 | let cell:MarkCell = collectionView.dequeueReusableCell(withReuseIdentifier: markCellIdentifier, for: indexPath) as! MarkCell 93 | if self.rulerSources[indexPath.row]%18 == 0 { 94 | cell.addMarkNumber(angle:self.rulerSources[indexPath.row]) 95 | if self.rulerSources[indexPath.row] == 90 || self.rulerSources[indexPath.row] == 180||self.rulerSources[indexPath.row] == 270||self.rulerSources[indexPath.row] == 0 { 96 | cell.markNumber.textColor = UIColor.orange 97 | 98 | } 99 | } 100 | cell.backgroundColor = UIColor.clear 101 | return cell 102 | } 103 | 104 | deinit { 105 | NotificationCenter.default.removeObserver(self) 106 | } 107 | 108 | @objc func panGestureRecognizer(pan:UIPanGestureRecognizer){ 109 | 110 | if pan.location(in: self).x >= self.frame.width || pan.location(in: self).x <= 0 { 111 | return 112 | } 113 | let degree:CGFloat = self.frame.width/90 114 | let point:CGPoint = pan.translation(in: self) 115 | let angle = DegreesToRadians(Double(point.x/degree)) 116 | 117 | if pan.state == .changed { 118 | self.angleCollectionView.transform = CGAffineTransform.init(rotationAngle: CGFloat(angle)+lastclockwise_Angle) 119 | delegate?.didScrollAngle(angle:CGFloat(angle)+lastclockwise_Angle) 120 | }else if pan.state == .ended { 121 | lastclockwise_Angle = CGFloat(angle)+lastclockwise_Angle 122 | } 123 | delegate?.scrollRulerState(panGresture: pan) 124 | } 125 | 126 | 127 | 128 | } 129 | 130 | class MarkCell: UICollectionViewCell { 131 | 132 | var markNumber:UILabel = UILabel.init(frame: CGRect.zero) 133 | func addMarkNumber(angle:Int) { 134 | self.markNumber.text = "\(angle)" 135 | self.markNumber.frame = self.bounds 136 | self.markNumber.backgroundColor = UIColor.clear 137 | self.markNumber.textColor = UIColor.WP_Color_Conversion("08006D") 138 | self.markNumber.textAlignment = .center 139 | self.markNumber.font = UIFont.systemFont(ofSize: 12) 140 | self.addSubview(markNumber) 141 | 142 | 143 | } 144 | 145 | } 146 | 147 | class RadianLayout: UICollectionViewLayout { 148 | 149 | private var layoutAttributes: [UICollectionViewLayoutAttributes] = [] 150 | var center:CGPoint = CGPoint.zero 151 | var radius: CGFloat = 0.0 152 | var totalNum:Int = 0 153 | 154 | override func prepare() { 155 | super.prepare() 156 | totalNum = (collectionView?.numberOfItems(inSection: 0))! 157 | layoutAttributes = [] 158 | center = CGPoint(x: Double(collectionView!.bounds.width * 0.5), y: Double(collectionView!.bounds.height * 0.5)) 159 | radius = min(collectionView!.bounds.width, collectionView!.bounds.height)/3 160 | var indexPath: IndexPath 161 | for index in 0.. UICollectionViewLayoutAttributes? { 169 | 170 | let attributes :UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath) 171 | attributes.size = CGSize.init(width: 50, height: 50) 172 | let angle = 2 * CGFloat(Double.pi) * CGFloat(indexPath.row) / CGFloat(totalNum)+CGFloat(Double.pi/2) 173 | attributes.center = CGPoint(x: center.x + radius*cos(-angle), y: center.y + radius*sin(-angle)) 174 | attributes.transform = CGAffineTransform.init(rotationAngle:CGFloat(-angle+CGFloat(DegreesToRadians(90)))) 175 | return attributes 176 | } 177 | 178 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 179 | return layoutAttributes 180 | } 181 | 182 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 183 | return true 184 | } 185 | 186 | 187 | 188 | 189 | } 190 | 191 | class CricleCollectionView: UICollectionView { 192 | 193 | override func draw(_ rect: CGRect) { 194 | 195 | let circlePath:UIBezierPath = UIBezierPath.init(arcCenter:CGPoint.init(x: self.bounds.width/2, y: self.bounds.height/2), radius: min(self.bounds.width, self.bounds.height) / 3.0-12, startAngle: 0, endAngle: CGFloat(Double.pi*2), clockwise: true) 196 | UIColor.WP_Color_Conversion("08006D").setStroke() 197 | circlePath.setLineDash([2], count:1, phase: 0) 198 | circlePath.lineJoinStyle = .round 199 | circlePath.lineWidth = 10 200 | circlePath.stroke() 201 | 202 | } 203 | 204 | 205 | 206 | 207 | } 208 | 209 | 210 | -------------------------------------------------------------------------------- /imageEditor/kernel/WPBaseEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WPBaseEditorView.swift 3 | // WPImageEditor2 4 | // 5 | // Created by pow on 2017/11/9. 6 | // Copyright © 2017年 pow. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | 12 | enum undoKey:String { 13 | case image 14 | case center 15 | case orientation 16 | case transform 17 | } 18 | 19 | public typealias baseBlock = (UIImage,CGRect)->() 20 | public class WPBaseEditorView: UIView,WPSideMenuDelegate,WPAngleRulerProtocol { 21 | 22 | /** 23 | Free path view 24 | */ 25 | @IBOutlet public weak var freePathView: WPFreePathView! 26 | /** 27 | photo library of original image 28 | */ 29 | public var originalImage:UIImage? 30 | /** 31 | oringinalFrame 32 | */ 33 | public var oringinalFrame:CGRect? 34 | /** 35 | rotated angle 36 | */ 37 | public var baseViewangle:CGFloat? 38 | 39 | var sideMenu:WPSideMenu? 40 | 41 | var angleRulerView:WPAngleRuler? 42 | /** 43 | current crop view 44 | */ 45 | public var currentCropView:WPCropEditorView?// current clip view 46 | 47 | @IBOutlet weak var shpaeImageView: WPShapeImageView! 48 | 49 | var undoPackage:[(image:UIImage,orientation:UIInterfaceOrientation,transform:CGAffineTransform,center:CGPoint)] = Array() 50 | var stepBystep:Int = 1 51 | /** 52 | call back block .return after clip image and current frame 53 | */ 54 | public var pasteBlock:baseBlock? 55 | 56 | var moveGestureRecognizer:UIPanGestureRecognizer? 57 | 58 | public var moveableArea:CGRect? 59 | 60 | var undoIndex:Int = 0 61 | 62 | var isRotatedClip:Bool = false 63 | 64 | var willChangeRatio:CGRect = CGRect.zero 65 | 66 | var willChangeFrame:CGRect = CGRect.zero 67 | // isPortrait 68 | var initIsPortrait:Bool = UIApplication.shared.statusBarOrientation.isPortrait 69 | 70 | var imageMaxRatio:CGFloat = 1 71 | 72 | var currentIsPortrait:Bool = UIApplication.shared.statusBarOrientation.isPortrait 73 | 74 | override public func layoutSubviews() { 75 | if let frame = self.oringinalFrame { 76 | if !self.frame.equalTo(frame){ 77 | self.sideMenu?.resetButton.isEnabled = true 78 | } 79 | } 80 | } 81 | } 82 | // MARK: - INIT - 83 | extension WPBaseEditorView { 84 | 85 | public class func loadNibFileToSetup(editorImage:UIImage,InSuperView:UIView)->WPBaseEditorView { 86 | 87 | var baseView:WPBaseEditorView? 88 | _ = UINib.init(nibName: nibName, instantiate: WPBaseEditorView.self) { 89 | (view) in 90 | baseView = view as? WPBaseEditorView 91 | } 92 | InSuperView.addSubview(baseView!) 93 | baseView?.loadsideMenuView() 94 | baseView?.originalImage = editorImage 95 | return baseView! 96 | } 97 | 98 | public func setup(){ 99 | if let editorImage = self.originalImage { 100 | self.shpaeImageView.image = editorImage 101 | let bigBounds = CGRect.init(x: 0, y: 0, width:getSizeToFitScreen().width, height:getSizeToFitScreen().height) 102 | self.bounds = bigBounds 103 | self.center = (superview?.center)! 104 | self.oringinalFrame = self.frame 105 | addMoveGesture() 106 | saveToUndoPackage(image: editorImage) 107 | } 108 | } 109 | func updateLayout() { 110 | let undoTuples = self.undoPackage[undoIndex] 111 | var isMatch:Bool = false 112 | self.bounds = CGRect.init(origin: CGPoint.zero, size: self.willChangeFrame.size) 113 | if self.baseViewangle != nil && !isCut{ 114 | updateScaleFit() 115 | }else { 116 | self.bounds = CGRect.init(origin: CGPoint.zero, size: getSizeToFitScreen(undoTuples.image)) 117 | } 118 | if isRotatedClip { 119 | self.bounds = CGRect.init(origin: CGPoint.zero, size: self.shpaeImageView.frame.size) 120 | self.shpaeImageView.frame = CGRect.init(origin: CGPoint.zero, size: self.bounds.size) 121 | self.shpaeImageView.transform = CGAffineTransform.identity 122 | } 123 | if undoTuples.orientation.isPortrait == UIApplication.shared.statusBarOrientation.isPortrait { 124 | isMatch = true 125 | }else if undoTuples.orientation.isLandscape == UIApplication.shared.statusBarOrientation.isLandscape { 126 | isMatch = true 127 | } 128 | if isMatch { 129 | self.center = undoTuples.center 130 | }else{ 131 | self.center = (superview?.center)! 132 | } 133 | self.oringinalFrame = self.frame 134 | if let crop = self.currentCropView { 135 | crop.frame = computeRatio(didChangeFrame: self.willChangeFrame, withFrame:crop.willChangeFrame) 136 | } 137 | } 138 | func updateScaleFit() { 139 | let canvasSize:CGSize = (self.moveableArea?.size)! 140 | let minCanvasValue:CGFloat = min((canvasSize.width), (canvasSize.height)) 141 | let frameFit:CGSize = self.shpaeImageView.frame.size 142 | var ratio :CGFloat = 1 143 | if !currentIsPortrait { // landscape 144 | ratio = UIApplication.shared.statusBarOrientation.isPortrait ? min(canvasSize.width, canvasSize.height) / frameFit.width : min(canvasSize.width, canvasSize.height) / frameFit.height 145 | }else{ 146 | ratio = UIApplication.shared.statusBarOrientation.isLandscape ? min(canvasSize.width, canvasSize.height)/frameFit.height : minCanvasValue / frameFit.width 147 | } 148 | let maxRatio:CGFloat = getMaxRatio() 149 | if ratio > maxRatio { 150 | ratio = maxRatio 151 | } 152 | self.shpaeImageView.transform = self.shpaeImageView.transform.scaledBy(x:ratio, y:ratio) 153 | } 154 | func updateScalefill() { 155 | let canvasSize:CGSize = (self.moveableArea?.size)! 156 | let minCanvasValue:CGFloat = min((canvasSize.width), (canvasSize.height)) 157 | let minImageViewValue:CGFloat = UIApplication.shared.statusBarOrientation.isPortrait ? (self.shpaeImageView.frame.size.width) : (self.shpaeImageView.frame.size.height) 158 | var ratio:CGFloat = minCanvasValue/minImageViewValue 159 | let maxRatio:CGFloat = getMaxRatio() 160 | if ratio > maxRatio { 161 | ratio = maxRatio 162 | } 163 | self.currentIsPortrait = UIApplication.shared.statusBarOrientation.isPortrait 164 | self.shpaeImageView.transform = self.shpaeImageView.transform.scaledBy(x:ratio, y:ratio) 165 | } 166 | 167 | func getSizeToFitScreen() -> CGSize { 168 | if let image = originalImage { 169 | return getSizeToFitScreen(image) 170 | } 171 | return CGSize.zero 172 | } 173 | 174 | func getSizeToFitScreen(_ image: UIImage) -> CGSize { 175 | let scale = getScaleToFitScreen(image) 176 | return image.size.applying(CGAffineTransform(scaleX: scale, y: scale)) 177 | } 178 | 179 | func getScaleToFitScreen(_ image: UIImage, isHorizontal: Bool = false) -> CGFloat { 180 | var maxWidth = UIScreen.main.bounds.width // Maximum width of image container 181 | var maxHeight = UIScreen.main.bounds.height // Maximum height of image container 182 | let imageOriginalSize = CGSize(width: image.scale * image.size.width, height: image.scale * image.size.height) // Original size of image. 183 | if let canvasSize = self.moveableArea { 184 | maxWidth = min(maxWidth, canvasSize.width) 185 | maxHeight = min(maxHeight, canvasSize.height) 186 | } 187 | let scaleX: CGFloat 188 | if isHorizontal { 189 | scaleX = maxWidth / imageOriginalSize.height 190 | } else { 191 | scaleX = maxWidth / imageOriginalSize.width 192 | } 193 | let scaleY: CGFloat 194 | if isHorizontal { 195 | scaleY = maxHeight / imageOriginalSize.width 196 | } else { 197 | scaleY = maxHeight / imageOriginalSize.height 198 | } 199 | return min(1, min(scaleX, scaleY)) 200 | } 201 | func getImageIsHorizontal() -> Bool { 202 | let size:CGSize = (self.shpaeImageView.image?.size)! 203 | if size.width > size.height { 204 | return true 205 | }else{ 206 | return false 207 | } 208 | } 209 | func getMaxRatio() -> CGFloat{ 210 | let canvasSize:CGSize = (self.moveableArea?.size)! 211 | let imageSize:CGSize = (self.shpaeImageView.image?.size)! 212 | let baseBounds:CGSize = self.shpaeImageView.frame.size 213 | var maxRatio:CGFloat = 1 214 | if self.shpaeImageView.frame.size.width > imageSize.width || self.shpaeImageView.frame.size.height > imageSize.height { 215 | 216 | return maxRatio 217 | } 218 | var width:CGFloat ,height:CGFloat 219 | if UIApplication.shared.statusBarOrientation.isPortrait { 220 | width = canvasSize.height/baseBounds.width 221 | height = canvasSize.height/baseBounds.height 222 | }else{ 223 | width = canvasSize.width/baseBounds.width 224 | height = canvasSize.width/baseBounds.height 225 | } 226 | maxRatio = min(width, height) 227 | return maxRatio 228 | } 229 | func computeRatio(didChangeFrame:CGRect,withFrame:CGRect) -> CGRect { 230 | self.willChangeRatio.size.width = withFrame.size.width/(didChangeFrame.size.width / self.frame.size.width) 231 | self.willChangeRatio.size.height = withFrame.size.height/(didChangeFrame.size.height / self.frame.size.height) 232 | self.willChangeRatio.origin.x = withFrame.origin.x/(didChangeFrame.size.width / self.frame.size.width) 233 | self.willChangeRatio.origin.y = withFrame.origin.y/(didChangeFrame.size.height / self.frame.size.height) 234 | return self.willChangeRatio 235 | } 236 | 237 | func getCurrentScreenSize(moveSize:CGSize) -> CGSize { 238 | var toScaleSize:CGSize? 239 | toScaleSize = UIScreen.main.bounds.width > UIScreen.main.bounds.height ? CGSize.init(width: moveSize.width, height: moveSize.height) : moveSize 240 | return toScaleSize! 241 | } 242 | } 243 | // MARK: - MOVE - 244 | extension WPBaseEditorView { 245 | func addMoveGesture() { 246 | if self.moveGestureRecognizer == nil { 247 | let moveGesture = UIPanGestureRecognizer(target: self, action: #selector(performMoveAction(_:))) 248 | moveGesture.minimumNumberOfTouches = 1 249 | self.moveGestureRecognizer = moveGesture 250 | self.addGestureRecognizer(moveGesture) 251 | } 252 | } 253 | 254 | func setMoveable(_ canMove: Bool) { 255 | self.moveGestureRecognizer?.isEnabled = canMove 256 | } 257 | 258 | @objc func performMoveAction(_ recognizer: UIPanGestureRecognizer) { 259 | let translation = recognizer.translation(in: self) 260 | 261 | switch recognizer.state { 262 | case .ended: 263 | saveToUndoPackage(image: self.shpaeImageView.image!) 264 | self.sideMenu?.undoButton.isEnabled = true 265 | undoIndex = undoPackage.count - 1 266 | break 267 | 268 | default: 269 | break 270 | } 271 | if let visibleRect = moveableArea { 272 | let location = self.frame.origin.applying(CGAffineTransform(translationX: translation.x, y: translation.y)) 273 | var x = location.x 274 | var y = location.y 275 | let width = self.frame.width 276 | let height = self.frame.height 277 | 278 | if x < visibleRect.minX { 279 | x = visibleRect.minX 280 | } 281 | if y < visibleRect.minY { 282 | y = visibleRect.minY 283 | } 284 | if (x + width) > visibleRect.maxX { 285 | x = visibleRect.maxX - width 286 | } 287 | if (y + height) > visibleRect.maxY { 288 | y = visibleRect.maxY - height 289 | } 290 | self.center = CGPoint.init(x: x+(width/2), y: y+(height/2)) 291 | } 292 | 293 | recognizer.setTranslation(CGPoint.zero, in: self) 294 | } 295 | 296 | } 297 | // MARK: PROTOCOL 298 | extension WPBaseEditorView { 299 | 300 | func didScrollAngle(angle: CGFloat) { 301 | baseViewangle = angle 302 | if isCut { 303 | self.shpaeImageView.transform = CGAffineTransform.init(rotationAngle: angle) 304 | }else{ 305 | self.shpaeImageView.transform = CGAffineTransform.init(rotationAngle: angle) 306 | updateScalefill() 307 | } 308 | } 309 | func scrollRulerState(panGresture: UIPanGestureRecognizer) { 310 | if panGresture.state == .ended { 311 | saveToUndoPackage(image: self.shpaeImageView.image!) 312 | self.sideMenu?.undoButton.isEnabled = true 313 | self.sideMenu?.resetButton.isEnabled = true 314 | undoIndex = self.undoPackage.count - 1 315 | } 316 | } 317 | func onClick_SideMenu_SubAction_Events(type: String, sender: UIButton) { 318 | 319 | switch type { 320 | case OperationType.rotation.rawValue: 321 | selectedActionType = type 322 | addRotationView() 323 | break 324 | case OperationType.rotateRatio.rawValue: 325 | selectedActionType = type 326 | if let crop = self.currentCropView { 327 | crop.rotateRatio() 328 | } 329 | break 330 | case SubActionType.rotate_right.rawValue: 331 | selectedActionType = type 332 | if let angle = baseViewangle { 333 | var d_Value:Int = Int(RadiansToDegrees(Double(abs(angle)))) 334 | baseViewangle = CGFloat(DegreesToRadians(Double(d_Value.maxAngleValue()))) 335 | }else{ 336 | baseViewangle = CGFloat(DegreesToRadians(90)) 337 | } 338 | self.angleRulerView?.angleCollectionView.transform = CGAffineTransform.init(rotationAngle:baseViewangle!) 339 | self.angleRulerView?.lastclockwise_Angle = baseViewangle! 340 | if isCut { 341 | self.shpaeImageView.transform = (self.angleRulerView?.angleCollectionView.transform)! 342 | }else{ 343 | self.shpaeImageView.transform = (self.angleRulerView?.angleCollectionView.transform)! 344 | updateScalefill() 345 | } 346 | saveToUndoPackage(image: self.shpaeImageView.image!) 347 | undoIndex = undoPackage.count - 1 348 | if RadiansToDegrees(Double(baseViewangle!)) == 360 { 349 | baseViewangle = nil 350 | } 351 | self.sideMenu?.undoButton.isEnabled = true 352 | self.sideMenu?.resetButton.isEnabled = true 353 | break 354 | case SubActionType.rotate_left.rawValue: 355 | selectedActionType = type 356 | if let angle = baseViewangle { 357 | var d_Value:Int = Int(RadiansToDegrees(Double(abs(angle)))) 358 | baseViewangle = -CGFloat(DegreesToRadians(Double(d_Value.maxAngleValue()))) 359 | }else{ 360 | baseViewangle = -CGFloat(DegreesToRadians(90)) 361 | } 362 | self.angleRulerView?.angleCollectionView.transform = CGAffineTransform.init(rotationAngle:baseViewangle!) 363 | self.angleRulerView?.lastclockwise_Angle = baseViewangle! 364 | if isCut { 365 | self.shpaeImageView.transform = (self.angleRulerView?.angleCollectionView.transform)! 366 | }else{ 367 | self.shpaeImageView.transform = (self.angleRulerView?.angleCollectionView.transform)! 368 | updateScalefill() 369 | } 370 | saveToUndoPackage(image: self.shpaeImageView.image!) 371 | undoIndex = undoPackage.count - 1 372 | if RadiansToDegrees(Double(baseViewangle!)) == -360 { 373 | baseViewangle = nil 374 | } 375 | self.sideMenu?.undoButton.isEnabled = true 376 | self.sideMenu?.resetButton.isEnabled = true 377 | break 378 | case SubActionType.fullCanvas_scaleFit.rawValue: 379 | selectedActionType = type 380 | let undoTuples = self.undoPackage[undoIndex] 381 | self.shpaeImageView.image = UIImage.imageRotatedByDegrees(self.shpaeImageView.image!, degrees:acos(undoTuples.transform.a)) 382 | self.shpaeImageView.image = UIImage.imageWithImage(undoTuples.image , scaledToFitToSize:getCurrentScreenSize(moveSize: (self.moveableArea?.size)!)) 383 | self.bounds = CGRect.init(x: 0, y: 0, width: (self.shpaeImageView.image?.size.width)!, height: (self.shpaeImageView.image?.size.height)!) 384 | self.center = (superview?.center)! 385 | break 386 | case SubActionType.fullCanvas_scaleFill.rawValue: 387 | selectedActionType = type 388 | let undoTuples = self.undoPackage[undoIndex] 389 | self.shpaeImageView.image = UIImage.imageRotatedByDegrees(self.shpaeImageView.image!, degrees:acos(undoTuples.transform.a)) 390 | let fillRect:CGRect = CGRect.init(origin: CGPoint.zero, size: getCurrentScreenSize(moveSize: (self.moveableArea?.size)!)) 391 | self.shpaeImageView.image = UIImage.imageWithImage(undoTuples.image, scaledToSize:fillRect.size, inRect:fillRect) 392 | self.bounds = CGRect.init(x: 0, y: 0, width: (self.shpaeImageView.image?.size.width)!, height: (self.shpaeImageView.image?.size.height)!) 393 | self.center = (superview?.center)! 394 | break 395 | case OperationType.crop.rawValue: 396 | selectedActionType = type 397 | break 398 | case OperationType.center.rawValue: 399 | selectedActionType = type 400 | self.center = (superview?.center)! 401 | break 402 | case SubActionType.subCancel.rawValue: 403 | selectedActionType = type 404 | self.currentCropView?.drawBackgroudView?.removeFromSuperview() 405 | self.currentCropView?.removeFromSuperview() 406 | self.currentCropView = nil 407 | self.freePathView.isHidden = true 408 | NotificationCenter.default.removeObserver(self.angleRulerView as Any) 409 | self.angleRulerView?.removeFromSuperview() 410 | self.angleRulerView = nil 411 | setMoveable(true) 412 | self.shpaeImageView.image = self.originalImage 413 | let bigBounds = CGRect.init(x: 0, y: 0, width:getSizeToFitScreen().width, height:getSizeToFitScreen().height) 414 | self.bounds = bigBounds 415 | self.shpaeImageView.transform = CGAffineTransform.identity 416 | isRotatedClip = false 417 | self.baseViewangle = nil 418 | break 419 | case SubActionType.rotateCancel.rawValue: 420 | selectedActionType = type 421 | NotificationCenter.default.removeObserver(self.angleRulerView as Any) 422 | self.angleRulerView?.removeFromSuperview() 423 | self.angleRulerView = nil 424 | setMoveable(true) 425 | break 426 | case OperationType.cancel.rawValue: 427 | selectedActionType = type 428 | self.pasteBlock!(UIImage(),CGRect.zero) 429 | self.removeAllEvents() 430 | break 431 | case OperationType.reset.rawValue: 432 | self.reset() 433 | break 434 | case SubActionType.clip.rawValue: 435 | selectedActionType = type 436 | if !self.freePathView.isHidden { 437 | self.cropFrreShape() 438 | }else{ 439 | self.cropShpae() 440 | } 441 | saveToUndoPackage(image: self.shpaeImageView.image!) 442 | undoIndex = undoPackage.count - 1 443 | self.sideMenu?.undoButton.isEnabled = true 444 | self.sideMenu?.redoButton.isEnabled = false 445 | self.sideMenu?.rotateRatioButton.isHidden = true 446 | setMoveable(true) 447 | isRotatedClip = false 448 | break 449 | case OperationType.undo.rawValue: 450 | if !self.freePathView.isHidden { 451 | self.freePathView.undoPath(sender: (self.sideMenu?.undoButton)!) 452 | }else{ 453 | undoAction(sender) 454 | } 455 | break 456 | case OperationType.redo.rawValue: 457 | redoAction(sender) 458 | break 459 | case OperationType.paste.rawValue: 460 | pasteImage() 461 | break 462 | default: 463 | selectedActionType = type 464 | self.addCropView(type:type) 465 | } 466 | } 467 | 468 | func pasteImage() { 469 | var pasteImage:UIImage? 470 | self.shpaeImageView.image = UIImage.imageRotatedByDegrees(self.shpaeImageView.image!, degrees: (baseViewangle != nil) ? baseViewangle! : 0) 471 | pasteImage = UIImage.imageWithImage(self.shpaeImageView.image!, scaledToSize: self.shpaeImageView.frame.size, inRect:CGRect.init(origin: CGPoint.zero, size: self.shpaeImageView.frame.size)) 472 | let origin:CGPoint = self.convert(self.shpaeImageView.center, to: self.superview) 473 | let cropSize:CGSize = (pasteImage?.size)! 474 | var cropFrame:CGRect = CGRect.init(origin:CGPoint.init(x: origin.x - (cropSize.width/2), y: origin.y - (cropSize.height/2)), size:cropSize) 475 | if cropFrame.origin.x < 0 || cropFrame.origin.y < 0 { 476 | cropFrame.origin.x = 0 ; cropFrame.origin.y = 0 477 | } 478 | let currentRect:CGSize = getCurrentScreenSize(moveSize: (self.moveableArea?.size)!) 479 | if cropFrame.size.width > currentRect.width { 480 | cropFrame.size.width = currentRect.width 481 | } 482 | if cropFrame.size.height > currentRect.height { 483 | cropFrame.size.height = currentRect.height 484 | } 485 | if let block = self.pasteBlock { 486 | block(pasteImage!,cropFrame) 487 | } 488 | self.removeAllEvents() 489 | } 490 | 491 | } 492 | // MARK: - UNDO & REDO - 493 | extension WPBaseEditorView { 494 | 495 | func saveToUndoPackage(image:UIImage) { 496 | if (self.sideMenu?.redoButton?.isEnabled)! { 497 | self.undoPackage.removeSubrange(Range.init(NSRange.init(location:undoPackage.count - self.stepBystep, length: self.stepBystep))!) 498 | self.stepBystep = 0 499 | } 500 | self.undoPackage.append((image,UIApplication.shared.statusBarOrientation,self.shpaeImageView.transform,self.center)) 501 | self.sideMenu?.redoButton.isEnabled = false 502 | } 503 | 504 | func undoAction(_ sender:UIButton) { 505 | self.sideMenu?.redoButton?.isEnabled = true 506 | self.stepBystep += 1 507 | if self.stepBystep == undoPackage.count { 508 | sender.isEnabled = false 509 | } 510 | updateUI(undoObject:undoPackage[undoPackage.count - self.stepBystep]) 511 | undoIndex = undoPackage.count - self.stepBystep 512 | } 513 | 514 | func redoAction(_ sender:UIButton) { 515 | 516 | self.sideMenu?.undoButton?.isEnabled = true 517 | self.stepBystep -= 1 518 | if self.stepBystep == 1 { 519 | sender.isEnabled = false 520 | } 521 | 522 | updateUI(undoObject: undoPackage[undoPackage.count - self.stepBystep]) 523 | undoIndex = undoPackage.count - self.stepBystep 524 | } 525 | 526 | func updateUI(undoObject:(UIImage,UIInterfaceOrientation,CGAffineTransform,CGPoint)) { 527 | var isMatch:Bool = false 528 | self.shpaeImageView.image = undoObject.0 529 | self.shpaeImageView.transform = undoObject.2 530 | if let angleRuler = self.angleRulerView { 531 | angleRuler.angleCollectionView.transform = CGAffineTransform.init(rotationAngle: acos(undoObject.2.a)) 532 | } 533 | self.baseViewangle = acos(self.shpaeImageView.transform.a) 534 | if undoObject.1.isPortrait == UIApplication.shared.statusBarOrientation.isPortrait { 535 | isMatch = true 536 | } 537 | if undoObject.1.isLandscape == UIApplication.shared.statusBarOrientation.isLandscape { 538 | isMatch = true 539 | } 540 | if isMatch { 541 | let undoCenter:CGPoint = undoObject.3 542 | self.center = undoCenter 543 | }else{ 544 | self.center = (superview?.center)! 545 | } 546 | self.bounds = CGRect.init(origin: CGPoint.zero, size:getSizeToFitScreen(undoObject.0)) 547 | self.sideMenu?.resetButton.isEnabled = (self.sideMenu?.undoButton.isEnabled)! 548 | 549 | } 550 | 551 | } 552 | //MARK: - ADD VIEW - 553 | extension WPBaseEditorView { 554 | // add top menu 555 | func loadsideMenuView() { 556 | 557 | self.sideMenu = WPSideMenu.loadsideMenu(CGRect.init(x:0, y:UIScreen.main.bounds.size.height - 64, width: UIScreen.main.bounds.size.width, height:64)) 558 | superview?.insertSubview(self.sideMenu!, aboveSubview: self) 559 | self.sideMenu?.delegate = self 560 | NotificationCenter.default.addObserver(self, selector: #selector(didChangeStatusBarOrientation), name: NSNotification.Name.UIApplicationDidChangeStatusBarOrientation, object: nil) 561 | 562 | NotificationCenter.default.addObserver(self, selector: #selector(willChangeStatusBarOrientation), name: NSNotification.Name.UIApplicationWillChangeStatusBarOrientation, object: nil) 563 | 564 | } 565 | @objc func didChangeStatusBarOrientation() { 566 | self.moveableArea = self.moveableArea?.applyingInversed() 567 | self.updateLayout() 568 | if let side = self.sideMenu { 569 | side.frame = CGRect.init(x:0, y:UIScreen.main.bounds.height-64, width:UIScreen.main.bounds.width, height:64) 570 | } 571 | } 572 | @objc func willChangeStatusBarOrientation() { 573 | self.willChangeFrame = self.frame 574 | if let crop = self.currentCropView { 575 | crop.willChangeFrame = crop.frame 576 | } 577 | } 578 | 579 | 580 | func addCropView(type:String) { 581 | 582 | setMoveable(false) 583 | isCut = false 584 | self.sideMenu?.rotateRatioButton.isHidden = true 585 | 586 | if type.isEqual(SubActionType.Free_S.rawValue)||type.isEqual(SubActionType.Free_C.rawValue) { 587 | if let cropView = self.currentCropView { 588 | cropView.drawBackgroudView?.removeFromSuperview() 589 | cropView.removeFromSuperview() 590 | self.currentCropView = nil 591 | } 592 | self.freePathView.isHidden = false 593 | self.freePathView.baseImage = self.originalImage 594 | self.freePathView.sideMenu_undoButton = self.sideMenu?.undoButton 595 | self.freePathView.sideMenu_clipButton = self.sideMenu?.doneButton 596 | self.freePathView.freePathName = NSString.init(string: type) 597 | self.sideMenu?.doneButton.isEnabled = false 598 | self.sideMenu?.closePolygonCountView() 599 | }else{ 600 | 601 | if let cropView = self.currentCropView { 602 | cropView.initShapeLayout(type: type) 603 | self.sideMenu?.rotateRatioButton.isHidden = true 604 | if !self.freePathView.isHidden { 605 | self.freePathView.isHidden = true 606 | } 607 | if !type.isEqual(SubActionType.crop_polygon.rawValue) { 608 | cropView.polygonLayer.removeFromSuperlayer() 609 | self.sideMenu?.closePolygonCountView() 610 | } 611 | if type.isEqual(SubActionType.crop_43.rawValue) || type.isEqual(SubActionType.crop_169.rawValue) || type.isEqual(SubActionType.crop_image.rawValue) || type.isEqual(SubActionType.crop_canvas.rawValue) { 612 | self.sideMenu?.rotateRatioButton.isHidden = false 613 | } 614 | return 615 | }else{ 616 | self.sideMenu?.resetButton.isEnabled = true 617 | self.sideMenu?.undoButton.isEnabled = false 618 | self.sideMenu?.redoButton.isEnabled = false 619 | self.freePathView.isHidden = true 620 | self.sideMenu?.doneButton.isEnabled = true 621 | if type.isEqual(SubActionType.crop_43.rawValue) || type.isEqual(SubActionType.crop_169.rawValue) || type.isEqual(SubActionType.crop_image.rawValue) || type.isEqual(SubActionType.crop_canvas.rawValue) { 622 | self.sideMenu?.rotateRatioButton.isHidden = false 623 | } 624 | isRotatedClip = false 625 | if let angle = self.baseViewangle { 626 | self.bounds = CGRect.init(origin: CGPoint.zero, size: self.shpaeImageView.frame.size) 627 | self.shpaeImageView.frame = CGRect.init(origin: CGPoint.zero, size: self.bounds.size) 628 | self.shpaeImageView.image = UIImage.imageRotatedByDegrees(self.shpaeImageView.image!, degrees: angle) 629 | self.shpaeImageView.transform = CGAffineTransform.identity 630 | isRotatedClip = true 631 | } 632 | self.currentCropView = WPCropEditorView.loadNibFileToCropView(onTheSuperView: self) 633 | self.currentCropView?.initShapeLayout(type: type) 634 | } 635 | } 636 | } 637 | 638 | @objc func add_Count_Polygon_Action(_ sender:UIButton) { 639 | self.currentCropView?.addAndDecrease_Polygon_Action(sender) 640 | } 641 | // add rotationview 642 | func addRotationView() { 643 | var sourc:[Int] = Array() 644 | for index in 0..<20 { 645 | sourc.append(index*18) 646 | } 647 | self.angleRulerView = WPAngleRuler.init(rulerDataSource: sourc, rotatedView: self) 648 | angleRulerView?.delegate = self 649 | self.superview?.insertSubview(self.angleRulerView!, belowSubview: self.sideMenu!) 650 | } 651 | 652 | 653 | // add free path clip View to be main view 654 | func cropFrreShape() { 655 | self.shpaeImageView.lastPath = self.freePathView.getCurrentPath() 656 | self.freePathView.isHidden = true 657 | self.shpaeImageView.image = self.shpaeImageView.pasteImage() 658 | let cutImage:UIImage = (self.shpaeImageView.cuting()) 659 | let convertFrame = self.convert((self.shpaeImageView.lastPath?.bounds)!, to: self.superview) 660 | self.frame = convertFrame 661 | self.shpaeImageView.image = cutImage 662 | self.shpaeImageView.backgroundColor = UIColor.clear 663 | self.shpaeImageView.transform = CGAffineTransform.identity 664 | isCut = true 665 | baseViewangle = nil 666 | } 667 | 668 | // add clip view to superView later operation 669 | func cropShpae(){ 670 | 671 | self.currentCropView?.removeFromSuperview() 672 | let sourceImageRef: CGImage = self.shpaeImageView.pasteImage().cgImage! 673 | isCut = true 674 | let newCGImage :CGImage = sourceImageRef.cropping(to:(self.currentCropView?.frame)!)! 675 | let newImage:UIImage = UIImage.init(cgImage: newCGImage) 676 | self.shpaeImageView.image = newImage 677 | self.shpaeImageView.lastPath = self.currentCropView?.crop_shapePath 678 | let cutImage:UIImage = (self.shpaeImageView.cuting()) 679 | self.shpaeImageView.image = cutImage 680 | let convertPoint:CGPoint = self.convert((self.currentCropView?.center)!, to: self.superview) 681 | var convertSize:CGSize = cutImage.size 682 | if (self.currentCropView?.crop_shapeName.isEqual(SubActionType.crop_polygon.rawValue))! { 683 | convertSize = cutImage.size 684 | } 685 | self.bounds = CGRect.init(origin: CGPoint.zero, size: convertSize) 686 | self.center = convertPoint 687 | self.currentCropView?.drawBackgroudView?.removeFromSuperview() 688 | self.currentCropView?.removeFromSuperview() 689 | self.currentCropView = nil 690 | self.shpaeImageView.transform = CGAffineTransform.identity 691 | baseViewangle = nil 692 | 693 | 694 | } 695 | // over method 696 | func removeAllEvents() { 697 | self.sideMenu?.rotateRatioButton.isHidden = true 698 | self.sideMenu?.removeFromSuperview() 699 | self.angleRulerView?.removeFromSuperview() 700 | DispatchQueue._onceTracker = [String]() 701 | NotificationCenter.default.removeObserver(self) 702 | self.removeFromSuperview() 703 | selectedActionType = OperationType.unknown.rawValue 704 | self.angleRulerView = nil 705 | self.baseViewangle = nil 706 | isCut = false 707 | isRotatedClip = false 708 | } 709 | 710 | func reset() { 711 | self.undoPackage.removeAll() 712 | undoIndex = 0 713 | self.sideMenu?.redoButton.isEnabled = false 714 | self.sideMenu?.undoButton.isEnabled = false 715 | self.sideMenu?.rotateRatioButton.isHidden = true 716 | if let cropView = self.currentCropView { 717 | cropView.drawBackgroudView?.removeFromSuperview() 718 | cropView.removeFromSuperview() 719 | self.currentCropView = nil 720 | } 721 | if let editorImage = self.originalImage { 722 | self.shpaeImageView.image = editorImage 723 | self.bounds = CGRect.init(origin: CGPoint.zero, size: getSizeToFitScreen()) 724 | self.center = (superview?.center)! 725 | self.shpaeImageView.transform = CGAffineTransform.identity 726 | saveToUndoPackage(image: editorImage) 727 | } 728 | if !self.freePathView.isHidden { 729 | self.freePathView.isHidden = true 730 | } 731 | if self.angleRulerView != nil { 732 | self.angleRulerView?.angleCollectionView.transform = CGAffineTransform.identity 733 | self.angleRulerView?.lastclockwise_Angle = 0 734 | self.angleRulerView?.removeFromSuperview() 735 | self.angleRulerView = nil 736 | } 737 | self.baseViewangle = nil 738 | setMoveable(true) 739 | isCut = false 740 | } 741 | 742 | } 743 | 744 | class WPShapeImageView: UIImageView { 745 | 746 | public var lastPath:UIBezierPath? 747 | // free path clip image 748 | public func cuting() -> UIImage { 749 | 750 | var imgSize:CGSize = CGSize.zero 751 | imgSize = (self.frame.size) 752 | UIGraphicsBeginImageContextWithOptions(imgSize, false, 1) 753 | lastPath?.addClip() 754 | self.image?.draw(at: CGPoint.zero) 755 | let newImage2 = UIGraphicsGetImageFromCurrentImageContext() 756 | UIGraphicsBeginImageContextWithOptions(imgSize, false, 1) 757 | let sourceImageRef: CGImage = (newImage2!.cgImage!)// next In accordance with the path bouns .clip image szie 758 | let newCGImage :CGImage = sourceImageRef.cropping(to:(self.lastPath?.bounds)!)! 759 | let newImage:UIImage = UIImage.init(cgImage: newCGImage) 760 | return newImage 761 | } 762 | 763 | func pasteImage() -> UIImage { 764 | let imageSize = self.bounds.size 765 | var screen:UIView = self 766 | if !isCut { 767 | screen = self.superview! 768 | } 769 | UIGraphicsBeginImageContextWithOptions(imageSize, false,1) 770 | screen.layer.render(in: UIGraphicsGetCurrentContext()!) 771 | let layerImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()! 772 | UIGraphicsEndImageContext() 773 | return layerImage 774 | } 775 | 776 | } 777 | 778 | 779 | 780 | -------------------------------------------------------------------------------- /imageEditor/kernel/WPCropEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WPCropEditorView.swift 3 | // WPImageEditor2 4 | // 5 | // Created by pow on 2017/11/9. 6 | // Copyright © 2017年 pow. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // FIXME: do make clip drawing path 12 | public class WPCropEditorView: UIView { 13 | 14 | // drawing path type name 15 | var crop_shapeName:String = "" 16 | // drawing path of self 17 | var crop_shapePath:UIBezierPath = UIBezierPath() 18 | // spaceing 19 | let space:CGFloat = 1 20 | // setup min value count of the polygon 21 | var polygonCount = 5 22 | // degree measure of the polygon 23 | var degreeMeasure:Double? 24 | /*------------------------------------------------------------------*/ 25 | /**XIB file in property **/ 26 | @IBOutlet weak var controlSizeButton_0: UIButton!// left top 27 | @IBOutlet weak var controlSizeButton_2: UIButton!// left bottom 28 | @IBOutlet weak var controlSizeButton_5: UIButton!// right top 29 | @IBOutlet weak var controlSizeButton_7: UIButton!// right bottom 30 | /**XIB file in internal hidden property **/ 31 | @IBOutlet weak var leftBottom: UILabel! 32 | @IBOutlet weak var left_leftBottom: UILabel! 33 | @IBOutlet weak var rightBottom: UILabel! 34 | @IBOutlet weak var right_rightBottom: UILabel! 35 | @IBOutlet weak var topRight: UILabel! 36 | @IBOutlet weak var right_topBottom: UILabel! 37 | @IBOutlet weak var leftTop: UILabel! 38 | @IBOutlet weak var left_TopBottom: UILabel! 39 | var isMove:Bool? // is out edge 40 | var edgePoint:CGPoint = CGPoint.zero // edge point 41 | var currentFrame:CGRect = CGRect.zero // current frame 42 | var toScale:Bool = true 43 | var lockX:CGFloat? 44 | var lockY:CGFloat? 45 | var lockWidth:CGFloat = 0 46 | var lockheight:CGFloat = 0 47 | var maxRatio:CGFloat? 48 | var polygonLayer:CAShapeLayer = CAShapeLayer() 49 | var drawBackgroudView:WPDrawingView? 50 | var willChangeFrame:CGRect = CGRect.zero 51 | 52 | public override var bounds: CGRect { 53 | didSet{ 54 | self.setNeedsLayout() 55 | } 56 | } 57 | 58 | override public func draw(_ rect: CGRect) { 59 | crop_shapePath.removeAllPoints() 60 | // update out side obscure view 61 | self.draw_BaseView_Outside_BezierPath() 62 | // main view path update 63 | switch crop_shapeName { 64 | case SubActionType.Cercle.rawValue: 65 | crop_shapePath = UIBezierPath.init(arcCenter:CGPoint.init(x: self.frame.width/2, y: self.frame.height/2), radius:self.frame.width/2-space, startAngle: 0, endAngle: CGFloat(Double.pi)*2, clockwise: true) 66 | break 67 | case SubActionType.Rectangle.rawValue: 68 | crop_shapePath = UIBezierPath(rect: CGRect.init(x: 0, y: 0, width: rect.width, height: rect.height)) 69 | break 70 | case SubActionType.Triangle.rawValue: 71 | crop_shapePath.move(to: CGPoint.init(x: rect.width-space, y: rect.height-space)) 72 | crop_shapePath.addLine(to: CGPoint.init(x: rect.width/2, y:space)) 73 | crop_shapePath.addLine(to: CGPoint.init(x: space, y:rect.height-space)) 74 | crop_shapePath.addLine(to: CGPoint.init(x: rect.width-space, y:rect.height-space)) 75 | break 76 | case SubActionType.crop_43.rawValue: 77 | crop_shapePath = UIBezierPath.init(rect: rect) 78 | break 79 | case SubActionType.crop_169.rawValue: 80 | crop_shapePath = UIBezierPath.init(rect: rect) 81 | break 82 | case SubActionType.crop_image.rawValue: 83 | crop_shapePath = UIBezierPath.init(rect: rect) 84 | break 85 | case SubActionType.crop_canvas.rawValue: 86 | crop_shapePath = UIBezierPath.init(rect: rect) 87 | break 88 | case SubActionType.Oval.rawValue: 89 | crop_shapePath = UIBezierPath.init(ovalIn: CGRect.init(x: space, y: space, width: rect.width-space*2, height: rect.height-space*2)) 90 | break 91 | case SubActionType.crop_polygon.rawValue: 92 | crop_shapePath = getPolygonPath(rect: rect) 93 | polygonLayer.lineWidth = 2 94 | polygonLayer.strokeColor = UIColor.orange.cgColor 95 | polygonLayer.fillColor = UIColor.clear.cgColor 96 | polygonLayer.bounds = crop_shapePath.bounds 97 | polygonLayer.frame = CGRect.init(x: (rect.width - crop_shapePath.bounds.width)/2, y: (rect.height - crop_shapePath.bounds.height)/2, width: crop_shapePath.bounds.width, height: crop_shapePath.bounds.height) 98 | polygonLayer.path = crop_shapePath.cgPath 99 | break 100 | case SubActionType.crop_free.rawValue: 101 | crop_shapePath = UIBezierPath(rect: CGRect.init(x: 0, y: 0, width:rect.size.width, height:rect.height)) 102 | break 103 | default: 104 | print("not type") 105 | } 106 | if !crop_shapeName.isEqual(SubActionType.crop_polygon.rawValue) { 107 | UIColor.orange.setStroke() 108 | crop_shapePath.lineWidth = 2 109 | crop_shapePath.stroke() 110 | } 111 | } 112 | 113 | 114 | override public func layoutSubviews() { 115 | self.drawBackgroudView?.frame = CGRect.init(origin: CGPoint.zero, size: (self.superview?.bounds.size)!) 116 | self.setNeedsDisplay() 117 | } 118 | // init loading nib file to be WPCropEditorView class object 119 | class func loadNibFileToCropView(onTheSuperView:WPBaseEditorView)->WPCropEditorView { 120 | 121 | var crop:WPCropEditorView? 122 | _ = UINib.init(nibName:nibName, instantiate: WPCropEditorView.self) { 123 | (view) in 124 | crop = view as? WPCropEditorView 125 | } 126 | isCut = false 127 | crop?.drawBackgroudView = WPDrawingView(frame:onTheSuperView.bounds) 128 | onTheSuperView.addSubview((crop?.drawBackgroudView!)!) 129 | onTheSuperView.addSubview(crop!) 130 | return crop! 131 | } 132 | // init shape layout 133 | func initShapeLayout(type:String) { 134 | 135 | crop_shapeName = type 136 | self.toScale = true 137 | switch type { 138 | case SubActionType.crop_free.rawValue: 139 | self.toScale = false 140 | self.bounds = CGRect.init(origin: CGPoint.zero, size: (self.superview?.frame.size)!) 141 | break 142 | case SubActionType.crop_image.rawValue: 143 | let baseView:WPBaseEditorView = (self.superview as! WPBaseEditorView) 144 | maxRatio = max((baseView.originalImage?.size.width)!, (baseView.originalImage?.size.height)!)/min ((baseView.originalImage?.size.width)!, (baseView.originalImage?.size.height)!) 145 | let proSize:CGSize = baseView.bounds.size.proportionSize(p: maxRatio!) 146 | self.bounds = CGRect.init(origin: CGPoint.zero, size:proSize) 147 | break 148 | case SubActionType.crop_canvas.rawValue: 149 | let baseView:WPBaseEditorView = (self.superview as! WPBaseEditorView) 150 | maxRatio = max((baseView.moveableArea?.size.width)!, (baseView.moveableArea?.size.height)!)/min ((baseView.moveableArea?.size.width)!, (baseView.moveableArea?.size.height)!) 151 | let proSize:CGSize = baseView.bounds.size.proportionSize(p: maxRatio!) 152 | self.bounds = CGRect.init(origin: CGPoint.zero, size:proSize) 153 | break 154 | case SubActionType.Oval.rawValue: 155 | self.toScale = false 156 | self.bounds = CGRect.init(origin: CGPoint.zero, size:(self.superview?.frame.size)!) 157 | break 158 | case SubActionType.crop_polygon.rawValue: 159 | let minSize = min((self.superview?.frame.size.width)!, (self.superview?.frame.size.height)!) 160 | self.layer.addSublayer(polygonLayer) 161 | self.bounds = CGRect.init(origin: CGPoint.zero, size:CGSize.init(width:minSize, height: minSize)) 162 | break 163 | case SubActionType.crop_43.rawValue: 164 | self.bounds = CGRect.init(origin: CGPoint.zero, size: (self.superview?.frame.size)!) 165 | let proSize = self.frame.size.proportionSize(p: 4/3) 166 | maxRatio = 4/3 167 | self.bounds = CGRect.init(origin: CGPoint.zero, size:CGSize.init(width: proSize.width, height: proSize.height)) 168 | break 169 | case SubActionType.crop_169.rawValue: 170 | self.bounds = CGRect.init(origin: CGPoint.zero, size: (self.superview?.frame.size)!) 171 | let proSize = self.frame.size.proportionSize(p: 16/9) 172 | maxRatio = 16/9 173 | self.bounds = CGRect.init(origin: CGPoint.zero, size:CGSize.init(width: proSize.width, height: proSize.height)) 174 | break 175 | default: 176 | let minSize:CGFloat = min((self.superview?.frame.width)!, (self.superview?.frame.height)!) 177 | self.bounds = CGRect.init(origin: CGPoint.zero, size:CGSize.init(width: minSize, height: minSize)) 178 | } 179 | 180 | self.center = CGPoint.init(x: (self.superview?.frame.width)!/2, y: (self.superview?.frame.height)!/2) 181 | 182 | 183 | } 184 | // MARK: - rotateRatio - 185 | func rotateRatio() { 186 | let scaleSize:CGSize = self.superview!.frame.size 187 | let baseView:WPBaseEditorView = (self.superview as! WPBaseEditorView) 188 | var maxValue:CGFloat?, minValue:CGFloat? 189 | if crop_shapeName.isEqual(SubActionType.crop_169.rawValue) { 190 | maxValue = 16 191 | minValue = 9 192 | }else if crop_shapeName.isEqual(SubActionType.crop_43.rawValue) { 193 | maxValue = 4 194 | minValue = 3 195 | }else if crop_shapeName.isEqual(SubActionType.crop_canvas.rawValue) { 196 | maxValue = max((baseView.moveableArea?.size.width)!, (baseView.moveableArea?.size.height)!) 197 | minValue = min((baseView.moveableArea?.size.width)!, (baseView.moveableArea?.size.height)!) 198 | }else if crop_shapeName.isEqual(SubActionType.crop_image.rawValue) { 199 | maxValue = max((baseView.originalImage?.size.width)!, (baseView.originalImage?.size.height)!) 200 | minValue = min((baseView.originalImage?.size.width)!, (baseView.originalImage?.size.height)!) 201 | } 202 | 203 | if let ratio = maxRatio { 204 | maxRatio = ratio == maxValue!/minValue! ? minValue!/maxValue! : maxValue!/minValue! 205 | let proSize:CGSize = (scaleSize.proportionSize(p:maxRatio!)) 206 | self.bounds = CGRect.init(origin: CGPoint.zero, size:CGSize.init(width:proSize.width, height: proSize.height)) 207 | self.center = CGPoint.init(x: (superview?.frame.width)!/2, y: (superview?.frame.height)!/2) 208 | 209 | } 210 | 211 | } 212 | 213 | override public func touchesBegan(_ touches: Set, with event: UIEvent?) { 214 | super.touchesBegan(touches, with: event) 215 | isMove = false 216 | let touch:UITouch = touches.first! 217 | let point = touch.location(in: superview) 218 | if self.frame.contains(point) { 219 | edgePoint = touch.location(in: self) 220 | isMove = true 221 | } 222 | } 223 | 224 | override public func touchesMoved(_ touches: Set, with event: UIEvent?) { 225 | 226 | if !isMove! { 227 | return 228 | } 229 | let touch = touches.first 230 | var point = touch?.location(in: superview) 231 | point?.x += self.frame.size.width/2 - edgePoint.x 232 | point?.y += self.frame.size.height/2 - edgePoint.y 233 | if (point?.x)! < self.frame.size.width/2 { 234 | point?.x = self.frame.size.width/2 235 | } 236 | if (point?.y)! < self.frame.size.height/2 { 237 | point?.y = self.frame.size.height/2 238 | } 239 | if (point?.x)! > (superview?.frame.size.width)! - self.frame.size.width / 2 { 240 | point?.x = (superview?.frame.size.width)! - self.frame.size.width / 2 241 | } 242 | if (point?.y)! > (superview?.frame.size.height)! - self.frame.size.height / 2 { 243 | point?.y = (superview?.frame.size.height)! - self.frame.size.height / 2 244 | } 245 | self.center = point! 246 | self.setNeedsDisplay() 247 | } 248 | 249 | 250 | 251 | } 252 | // FIXME: update base editor view draw 253 | extension WPCropEditorView{ 254 | 255 | 256 | func draw_BaseView_Outside_BezierPath() { 257 | 258 | let outSidePath = UIBezierPath() 259 | 260 | let super_X:CGFloat = 0 261 | let super_Y:CGFloat = 0 262 | let super_W = (superview?.frame.size.width)! 263 | let super_H = (superview?.frame.size.height)! 264 | let self_X = self.frame.origin.x 265 | let self_Y = self.frame.origin.y 266 | let self_W = self.frame.size.width 267 | let self_H = self.frame.size.height 268 | 269 | outSidePath.move(to: CGPoint.init(x:super_X, y:super_Y)) 270 | outSidePath.addLine(to: CGPoint.init(x:self_X, y:self_Y)) 271 | outSidePath.addLine(to: CGPoint.init(x:self_X+self_W, y:self_Y)) 272 | outSidePath.addLine(to: CGPoint.init(x: super_X+super_W, y: super_Y)) 273 | outSidePath.addLine(to: CGPoint.init(x: super_X, y: super_Y)) 274 | 275 | outSidePath.move(to: CGPoint.init(x: super_X, y: super_H)) 276 | outSidePath.addLine(to: CGPoint.init(x:self_X, y: self_H+self_Y)) 277 | outSidePath.addLine(to: CGPoint.init(x:self_X, y:self_Y)) 278 | outSidePath.addLine(to: CGPoint.zero) 279 | outSidePath.addLine(to: CGPoint.init(x: super_X, y: super_H+super_Y)) 280 | 281 | outSidePath.move(to: CGPoint.init(x: super_X, y: super_Y+super_H)) 282 | outSidePath.addLine(to: CGPoint.init(x:self_X, y:self_Y+self_H)) 283 | outSidePath.addLine(to: CGPoint.init(x:self_X+self_W, y:self_Y+self_H)) 284 | outSidePath.addLine(to: CGPoint.init(x: super_X+super_W, y: super_Y+super_H)) 285 | outSidePath.addLine(to: CGPoint.init(x: super_X, y: super_Y+super_H)) 286 | 287 | outSidePath.move(to: CGPoint.init(x: super_X+super_W, y: super_Y+super_H)) 288 | outSidePath.addLine(to: CGPoint.init(x:self_X+self_W, y: self_H+self_Y)) 289 | outSidePath.addLine(to: CGPoint.init(x: self_W+self_X, y: self_Y)) 290 | outSidePath.addLine(to: CGPoint.init(x: super_W+super_X, y: super_Y)) 291 | outSidePath.addLine(to: CGPoint.init(x: super_X+super_W, y: super_Y+super_H)) 292 | 293 | self.drawBackgroudView?.drawPath = outSidePath 294 | 295 | } 296 | 297 | 298 | 299 | 300 | } 301 | // FIXME: make add polygon 302 | extension WPCropEditorView{ 303 | 304 | func getPolygonPath(rect:CGRect) -> UIBezierPath { 305 | if self.degreeMeasure == nil { 306 | degreeMeasure = degree(count:CGFloat(polygonCount)) 307 | } 308 | crop_shapePath.removeAllPoints() 309 | let beginPoint:CGPoint = CGPoint.init(x: rect.size.width/2, y:0) 310 | let boundsCenter = CGPoint.init(x: rect.size.width/2, y:rect.size.height/2) 311 | var endPoint:CGPoint = pointRotatedAroundAnchorPoint(point:beginPoint,anchorPoint:boundsCenter,angle:degreeMeasure!) 312 | crop_shapePath.move(to:beginPoint) 313 | for _ in 0..Double { 320 | let clipLength:CGFloat = 100*4.13 321 | let cercleLength:CGFloat = 100*CGFloat(Double.pi) 322 | let length:CGFloat = clipLength / count 323 | let measureLength:CGFloat = 360/cercleLength 324 | let degree:Double = Double(length/measureLength) 325 | return Double.pi*degree/180 326 | } 327 | func addAndDecrease_Polygon_Action(_ sender: UIButton) { 328 | // update frame 329 | if sender.tag == 345 {// add 330 | polygonCount += 1 331 | }else{//Decrease 332 | polygonCount -= 1 333 | } 334 | if polygonCount>20 { 335 | polygonCount = 20 336 | } 337 | if polygonCount<5 { 338 | polygonCount = 5 339 | } 340 | degreeMeasure = degree(count:CGFloat(polygonCount)) 341 | self.setNeedsDisplay() 342 | } 343 | func pointRotatedAroundAnchorPoint(point:CGPoint,anchorPoint:CGPoint,angle:Double) ->CGPoint{ 344 | let x:CGFloat = (point.x-anchorPoint.x)*CGFloat(cos(angle))-(point.y-anchorPoint.y)*CGFloat(sin(angle)) + anchorPoint.x 345 | let y:CGFloat = (point.x-anchorPoint.x)*CGFloat(sin(angle)) + (point.y-anchorPoint.y)*CGFloat(cos(angle))+anchorPoint.y 346 | return CGPoint(x:x, y:y) 347 | } 348 | 349 | 350 | 351 | } 352 | // FIXME: --------- WPDrawingView ------------------- 353 | class WPDrawingView: UIView { 354 | 355 | var drawPath:UIBezierPath = UIBezierPath() { 356 | didSet{ 357 | self.setNeedsDisplay() 358 | } 359 | } 360 | override init(frame: CGRect) { 361 | super.init(frame:frame) 362 | self.backgroundColor = UIColor.clear 363 | } 364 | required init?(coder aDecoder: NSCoder) { 365 | fatalError("init(coder:) has not been implemented") 366 | } 367 | override func draw(_ rect: CGRect) { 368 | UIColor.init(red: 102/255, green: 102/255, blue: 102/255, alpha: 0.7).setFill() 369 | drawPath.fill() 370 | } 371 | 372 | 373 | } 374 | extension WPCropEditorView{ 375 | 376 | 377 | //MARK: change view size gesture action 378 | override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 379 | // handle -9 380 | currentFrame = self.frame 381 | if lockX != nil {lockX = nil} 382 | if lockY != nil {lockY = nil} 383 | lockX = self.frame.origin.x 384 | lockY = self.frame.origin.y 385 | lockheight = 0 386 | lockWidth = 0 387 | return true 388 | } 389 | 390 | //MARK: octagonal location frre size 391 | @IBAction func controlSizeButton_collectionManage_Gesture(_ sender: UIPanGestureRecognizer) { 392 | 393 | 394 | var sP:CGPoint = CGPoint.zero;// size point 395 | var aP:CGPoint = CGPoint.zero // appear point 396 | 397 | if (sender.view?.isEqual(self.controlSizeButton_0))! { // left top 398 | 399 | sP = sender.location(in: self.superview) 400 | 401 | sP.x = currentFrame.origin.x - sP.x 402 | 403 | sP.y = currentFrame.origin.y - sP.y 404 | // handle -16 to scale size 405 | if self.toScale { 406 | if sP.x > sP.y {sP.y = sP.x} 407 | if sP.y > sP.x {sP.x = sP.y} 408 | } 409 | aP.x = currentFrame.origin.x - sP.x 410 | aP.y = currentFrame.origin.y - sP.y 411 | 412 | 413 | }else if (sender.view?.isEqual(self.controlSizeButton_2))! {// left bottom 414 | 415 | sP = sender.location(in: self.superview) 416 | 417 | sP.x = currentFrame.origin.x - sP.x 418 | 419 | sP.y = sP.y - currentFrame.origin.y - currentFrame.height 420 | if self.toScale { 421 | if sP.x > sP.y {sP.y = sP.x} 422 | if sP.y > sP.x {sP.x = sP.y} 423 | } 424 | aP.y = currentFrame.origin.y 425 | 426 | aP.x = currentFrame.origin.x - sP.x 427 | 428 | 429 | }else if (sender.view?.isEqual(self.controlSizeButton_5))! {// right top 430 | 431 | 432 | sP = sender.location(in: self.superview) 433 | 434 | sP.x = sP.x - currentFrame.origin.x - currentFrame.width 435 | 436 | sP.y = currentFrame.origin.y - sP.y 437 | if self.toScale { 438 | if sP.x > sP.y {sP.y = sP.x} 439 | if sP.y > sP.x {sP.x = sP.y} 440 | } 441 | aP.y = currentFrame.origin.y - sP.y 442 | 443 | aP.x = currentFrame.origin.x 444 | 445 | }else if (sender.view?.isEqual(self.controlSizeButton_7))! {// right bottom 446 | 447 | 448 | sP = sender.location(in: self.superview) 449 | 450 | aP = currentFrame.origin 451 | 452 | sP.x = sP.x - currentFrame.size.width - currentFrame.origin.x 453 | 454 | sP.y = sP.y - currentFrame.size.height - currentFrame.origin.y 455 | if self.toScale { 456 | if sP.x > sP.y {sP.y = sP.x} 457 | if sP.y > sP.x {sP.x = sP.y} 458 | } 459 | 460 | 461 | } 462 | self.changeViewSizeEDGE(sP , ap:aP) 463 | } 464 | /** 465 | centralized processing view size change 466 | - parameter sp size point 467 | - parameter ap appear point 468 | - parameter orientation 469 | **/ 470 | func changeViewSizeEDGE(_ sp:CGPoint,ap:CGPoint) { 471 | var moveFrame = CGRect.zero 472 | moveFrame.origin.x = ap.x 473 | moveFrame.origin.y = ap.y 474 | moveFrame.size.width = currentFrame.size.width + sp.x 475 | moveFrame.size.height = currentFrame.size.height + sp.y 476 | if crop_shapeName.isEqual(SubActionType.crop_43.rawValue)||crop_shapeName.isEqual(SubActionType.crop_169.rawValue)||crop_shapeName.isEqual(SubActionType.crop_canvas.rawValue)||crop_shapeName.isEqual(SubActionType.crop_image.rawValue) { 477 | moveFrame.size = moveFrame.size.proportionSize(p: maxRatio!) 478 | } 479 | 480 | if moveFrame.size.width <= 100 { 481 | moveFrame.size.width = 100 482 | return 483 | }else if moveFrame.size.height <= 100 { 484 | moveFrame.size.height = 100 485 | return 486 | } 487 | 488 | 489 | /** 490 | size out handle 491 | */ 492 | if moveFrame.origin.x < lockX! && moveFrame.origin.x > 0 { 493 | 494 | lockWidth = (self.superview?.frame.width)! - moveFrame.origin.x - moveFrame.size.width 495 | } 496 | if moveFrame.origin.y < lockY! && moveFrame.origin.y > 0 { 497 | 498 | lockheight = (self.superview?.frame.height)! - moveFrame.origin.y - moveFrame.size.height 499 | } 500 | 501 | if moveFrame.origin.x < 0 { 502 | 503 | moveFrame.origin.x = 0 504 | moveFrame.size.width = (self.superview?.frame.width)! - lockWidth 505 | if crop_shapeName.isEqual(SubActionType.Cercle.rawValue) || crop_shapeName.isEqual(SubActionType.Triangle.rawValue)||crop_shapeName.isEqual(SubActionType.Rectangle.rawValue)||crop_shapeName.isEqual(SubActionType.crop_polygon.rawValue) { 506 | moveFrame.size.height = moveFrame.size.width 507 | }else if crop_shapeName.isEqual(SubActionType.crop_43.rawValue) || crop_shapeName.isEqual(SubActionType.crop_169.rawValue) || crop_shapeName.isEqual(SubActionType.crop_image.rawValue)||crop_shapeName.isEqual(SubActionType.crop_canvas.rawValue){ 508 | moveFrame.size.height = moveFrame.size.proportionSize(p: maxRatio!).height 509 | } 510 | 511 | } 512 | if moveFrame.origin.y < 0 { 513 | moveFrame.origin.y = 0 514 | moveFrame.size.height = (self.superview?.frame.height)! - lockheight 515 | if crop_shapeName.isEqual(SubActionType.Cercle.rawValue) || crop_shapeName.isEqual(SubActionType.Triangle.rawValue)||crop_shapeName.isEqual(SubActionType.Rectangle.rawValue)||crop_shapeName.isEqual(SubActionType.crop_polygon.rawValue) { 516 | moveFrame.size.width = moveFrame.size.height 517 | }else if crop_shapeName.isEqual(SubActionType.crop_43.rawValue) || crop_shapeName.isEqual(SubActionType.crop_169.rawValue) || crop_shapeName.isEqual(SubActionType.crop_image.rawValue)||crop_shapeName.isEqual(SubActionType.crop_canvas.rawValue){ 518 | moveFrame.size.width = moveFrame.size.proportionSize(p: maxRatio!).width 519 | } 520 | } 521 | if moveFrame.size.width + moveFrame.origin.x > (self.superview?.frame.size.width)! { 522 | moveFrame.size.width = (self.superview?.frame.size.width)! - moveFrame.origin.x 523 | if crop_shapeName.isEqual(SubActionType.Cercle.rawValue) || crop_shapeName.isEqual(SubActionType.Triangle.rawValue)||crop_shapeName.isEqual(SubActionType.Rectangle.rawValue)||crop_shapeName.isEqual(SubActionType.crop_polygon.rawValue) { 524 | moveFrame.size.height = moveFrame.size.width 525 | }else if crop_shapeName.isEqual(SubActionType.crop_43.rawValue) || crop_shapeName.isEqual(SubActionType.crop_169.rawValue) || crop_shapeName.isEqual(SubActionType.crop_image.rawValue)||crop_shapeName.isEqual(SubActionType.crop_canvas.rawValue){ 526 | moveFrame.size.height = moveFrame.size.proportionSize(p: maxRatio!).height 527 | } 528 | } 529 | if moveFrame.size.height + moveFrame.origin.y > (self.superview?.frame.size.height)! { 530 | 531 | moveFrame.size.height = (self.superview?.frame.size.height)! - moveFrame.origin.y 532 | if crop_shapeName.isEqual(SubActionType.Cercle.rawValue) || crop_shapeName.isEqual(SubActionType.Triangle.rawValue)||crop_shapeName.isEqual(SubActionType.Rectangle.rawValue)||crop_shapeName.isEqual(SubActionType.crop_polygon.rawValue) { 533 | moveFrame.size.width = moveFrame.size.height 534 | }else if crop_shapeName.isEqual(SubActionType.crop_43.rawValue) || crop_shapeName.isEqual(SubActionType.crop_169.rawValue) || crop_shapeName.isEqual(SubActionType.crop_image.rawValue)||crop_shapeName.isEqual(SubActionType.crop_canvas.rawValue){ 535 | moveFrame.size.width = moveFrame.size.proportionSize(p: maxRatio!).width 536 | } 537 | 538 | } 539 | self.frame = moveFrame 540 | } 541 | 542 | } 543 | 544 | 545 | -------------------------------------------------------------------------------- /imageEditor/kernel/WPExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WPExtension.swift 3 | // WPImageEditor2 4 | // 5 | // Created by pow on 2017/11/9. 6 | // Copyright © 2017年 pow. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | // sub action type 12 | public enum SubActionType : String { 13 | case crop_free 14 | case crop_image 15 | case fullCanvas_scaleFill 16 | case fullCanvas_scaleFit 17 | case crop_canvas 18 | case crop_polygon 19 | case crop_43 20 | case crop_169 21 | case Cercle 22 | case Triangle 23 | case Rectangle 24 | case Oval 25 | case Free_C 26 | case Free_S 27 | case rotate_left 28 | case rotate_right 29 | case subCancel 30 | case centerSelected 31 | case rotateCancel 32 | case clip 33 | } 34 | // main action type 35 | public enum OperationType : String { 36 | case unknown 37 | case crop 38 | case rotation 39 | case full 40 | case center 41 | case undo 42 | case cancel 43 | case done 44 | case redo 45 | case reset 46 | case paste 47 | case rotateRatio 48 | } 49 | var isCut:Bool = false 50 | 51 | var selectedActionType:String = OperationType.unknown.rawValue 52 | 53 | //DegreesToRadians 54 | func DegreesToRadians(_ angle :Double )->Double{ return Double.pi*angle/180 } 55 | func RadiansToDegrees(_ angle :Double )->Double{ return angle*180/Double.pi } 56 | func pointRotatedAroundAnchorPoint(point:CGPoint,anchorPoint:CGPoint,angle:Double) ->CGPoint{ 57 | let x:CGFloat = (point.x-anchorPoint.x)*CGFloat(cos(angle))-(point.y-anchorPoint.y)*CGFloat(sin(angle)) + anchorPoint.x 58 | let y:CGFloat = (point.x-anchorPoint.x)*CGFloat(sin(angle)) + (point.y-anchorPoint.y)*CGFloat(cos(angle))+anchorPoint.y 59 | return CGPoint(x:x, y:y) 60 | } 61 | extension Dictionary { 62 | 63 | func sortKeys() -> Array { 64 | 65 | return self.keys.sorted { (key1, key2) -> Bool in 66 | 67 | return key1.hashValue < key2.hashValue 68 | } 69 | 70 | 71 | 72 | } 73 | 74 | } 75 | extension CGSize { 76 | 77 | func proportionSize(p:CGFloat) -> CGSize { 78 | 79 | let Oriscale = (self.width)/(self.height) 80 | var width:CGFloat = 0 81 | var height:CGFloat = 0 82 | 83 | if Oriscale > p { 84 | height = (self.height) 85 | width = height * p 86 | 87 | }else{ 88 | width = (self.width) 89 | height = width/p 90 | 91 | 92 | } 93 | return CGSize.init(width: width, height: height) 94 | } 95 | 96 | 97 | 98 | } 99 | 100 | extension CGFloat { 101 | 102 | func exchange(x:CGFloat,y:CGFloat) -> CGFloat { 103 | if self == x/y { 104 | return y/x 105 | } 106 | return x/y 107 | } 108 | } 109 | 110 | 111 | 112 | 113 | /**color rgb to convert code string **/ 114 | extension UIColor{ 115 | 116 | 117 | public class func WP_Color_Conversion ( _ Color_Value:NSString)->UIColor{ 118 | var Str :NSString = Color_Value.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() as NSString 119 | if Color_Value.hasPrefix("#"){ 120 | Str=(Color_Value as NSString).substring(from: 1) as NSString 121 | } 122 | let wp_StrRed = (Str as NSString ).substring(to: 2) 123 | let wp_StrGreen = ((Str as NSString).substring(from: 2) as NSString).substring(to: 2) 124 | let wp_StrBlue = ((Str as NSString).substring(from: 4) as NSString).substring(to: 2) 125 | var r:CUnsignedInt = 0, g:CUnsignedInt = 0, b:CUnsignedInt = 0; 126 | Scanner(string:wp_StrRed).scanHexInt32(&r) 127 | Scanner(string: wp_StrGreen).scanHexInt32(&g) 128 | Scanner(string: wp_StrBlue).scanHexInt32(&b) 129 | return UIColor(red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: 1) 130 | 131 | } 132 | 133 | } 134 | /**DispatchQueue once extension**/ 135 | public extension DispatchQueue { 136 | 137 | public static var _onceTracker = [String]() 138 | 139 | /** 140 | Executes a block of code, associated with a unique token, only once. The code is thread safe and will 141 | only execute the code once even in the presence of multithreaded calls. 142 | 143 | - parameter token: A unique reverse DNS style name such as com.vectorform. or a GUID 144 | - parameter block: Block to execute once 145 | */ 146 | public class func once(token: String, block:()->Void) { 147 | objc_sync_enter(self) 148 | defer { objc_sync_exit(self) } 149 | 150 | if _onceTracker.contains(token) { 151 | return 152 | } 153 | 154 | _onceTracker.append(token) 155 | block() 156 | } 157 | } 158 | /** 159 | if to setting imageView of image ,must before adjust image size .to Fit imageView .becase clip relative to self image size .not imageView frame 160 | **/ 161 | extension UIImage{ 162 | 163 | 164 | /** 165 | calculate rotaed 166 | - parameter Radian: 167 | */ 168 | public func imageRotatedByRadians(radinFlaot:Double) -> UIImage { 169 | 170 | 171 | let t:CGAffineTransform = CGAffineTransform.init(rotationAngle: CGFloat(radinFlaot)) 172 | 173 | let rotatedRect = CGRect.init(x: 0, y: 0, width: self.size.width, height: self.size.height).applying(t) 174 | 175 | let rotaedSize = rotatedRect.size 176 | 177 | UIGraphicsBeginImageContextWithOptions(rotaedSize, false, UIScreen.main.scale) 178 | 179 | let context = UIGraphicsGetCurrentContext() 180 | 181 | context?.translateBy(x: (rotaedSize.width)/2, y: (rotaedSize.height)/2) 182 | 183 | context?.rotate(by:CGFloat(radinFlaot)) 184 | 185 | context?.scaleBy(x:1, y:-1) 186 | 187 | context?.draw((self.cgImage!), in:CGRect.init(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height:self.size.height)) 188 | 189 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 190 | 191 | UIGraphicsEndImageContext() 192 | 193 | return newImage! 194 | } 195 | class func imageWithImage(_ image: UIImage, scaledToFillToSize newSize: CGSize) -> UIImage? { 196 | //Determine the scale factors 197 | let widthScale = newSize.width / image.size.width; 198 | let heightScale = newSize.height / image.size.height; 199 | 200 | var scaleFactor: CGFloat 201 | 202 | //The larger scale factor will scale less (0 < scaleFactor < 1) leaving the other dimension hanging outside the newSize rect 203 | widthScale > heightScale ? (scaleFactor = widthScale) : (scaleFactor = heightScale) 204 | let scaledSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor) 205 | 206 | //Create origin point so that the center of the image falls into the drawing context rect (the origin will have negative component). 207 | var imageDrawOrigin = CGPoint(x: 0, y: 0) 208 | widthScale > heightScale ? (imageDrawOrigin.y = (newSize.height - scaledSize.height) * 0.5): 209 | (imageDrawOrigin.x = (newSize.width - scaledSize.width) * 0.5); 210 | 211 | 212 | //Create rect where the image will draw 213 | let imageDrawRect = CGRect(x: imageDrawOrigin.x, y: imageDrawOrigin.y, width: scaledSize.width, height: scaledSize.height); 214 | 215 | //The imageDrawRect is larger than the newSize rect, where the imageDraw origin is located defines what part of 216 | //the image will fall into the newSize rect. 217 | return imageWithImage(image, scaledToSize: newSize, inRect: imageDrawRect) 218 | } 219 | 220 | class func imageWithImage(_ image: UIImage, scaledToFitToSize newSize: CGSize) -> UIImage? { 221 | //Only scale images down 222 | if (image.size.width < newSize.width && image.size.height < newSize.height) { 223 | return image; 224 | } 225 | //Determine the scale factors 226 | let widthScale = newSize.width / image.size.width; 227 | let heightScale = newSize.height / image.size.height; 228 | var scaleFactor: CGFloat; 229 | //The smaller scale factor will scale more (0 < scaleFactor < 1) leaving the other dimension inside the newSize rect 230 | widthScale < heightScale ? (scaleFactor = widthScale) : (scaleFactor = heightScale); 231 | let scaledSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor); 232 | //Scale the image 233 | return imageWithImage(image, scaledToSize: scaledSize, inRect: CGRect(x: 0.0, y: 0.0, width: scaledSize.width, height: scaledSize.height)); 234 | } 235 | class func imageWithImage(_ image: UIImage, scaledToSize newSize: CGSize, inRect rect: CGRect) -> UIImage? { 236 | //Determine whether the screen is retina 237 | if UIScreen.main.scale == 2.0 { 238 | UIGraphicsBeginImageContextWithOptions(newSize, true, 2.0); 239 | } else { 240 | UIGraphicsBeginImageContext(newSize); 241 | } 242 | 243 | //Draw image in provided rect 244 | image.draw(in: rect) 245 | let newImage = UIGraphicsGetImageFromCurrentImageContext(); 246 | 247 | //Pop this context 248 | UIGraphicsEndImageContext(); 249 | 250 | return newImage; 251 | } 252 | class func imageRotatedByDegrees(_ image: UIImage, degrees: CGFloat) -> UIImage? 253 | { 254 | if degrees == 0 { 255 | return image 256 | } 257 | let radians = degrees 258 | // calculate the size of the rotated view's containing box for our drawing space 259 | let rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) 260 | let rotatedRect = rect.applying(CGAffineTransform(rotationAngle: CGFloat(radians))) 261 | let rotatedSize = rotatedRect.size; 262 | 263 | // Create the bitmap context 264 | UIGraphicsBeginImageContextWithOptions(rotatedSize, false, UIScreen.main.scale) 265 | if let bitmap = UIGraphicsGetCurrentContext() { 266 | // Move the origin to the middle of the image so we will rotate and scale around the center. 267 | bitmap.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2); 268 | 269 | // // Rotate the image context 270 | bitmap.rotate(by: CGFloat(radians)); 271 | 272 | // Now, draw the rotated/scaled image into the context 273 | bitmap.scaleBy(x: 1.0, y: -1.0); 274 | 275 | if let cgImage = image.cgImage { 276 | bitmap.draw(cgImage, in: CGRect(x: -image.size.width / 2, y: -image.size.height / 2, width: image.size.width, height: image.size.height)) 277 | } 278 | } 279 | 280 | let newImage = UIGraphicsGetImageFromCurrentImageContext(); 281 | UIGraphicsEndImageContext(); 282 | return newImage; 283 | 284 | } 285 | } 286 | func getAddress(obj:AnyObject) -> String { 287 | let address = Unmanaged.passUnretained(obj).toOpaque() 288 | return "\(address)" 289 | } 290 | let nibName:String = "ImageEditor2Nib" 291 | extension UINib{ 292 | 293 | convenience init(nibName:String,instantiate:Any,viewBlock:@escaping (_ view:Any)->Void) { 294 | self.init(nibName: nibName, bundle: Bundle.init(for:instantiate as! AnyClass)) 295 | let owner = self.instantiate(withOwner: instantiate, options: nil) 296 | for view:Any in owner { 297 | if ((view as AnyObject).isMember(of:instantiate as! AnyClass)) { 298 | viewBlock(view as AnyObject) 299 | } 300 | } 301 | 302 | 303 | } 304 | } 305 | extension CGRect { 306 | func applyingInversed() -> CGRect { 307 | return CGRect.init(x: self.minY, y: self.minX, width: self.height, height: self.width) 308 | } 309 | } 310 | extension Int { 311 | 312 | mutating func maxAngleValue() -> Int { 313 | if self >= 360 { 314 | self = 0 315 | } 316 | switch abs(self) { 317 | case 0...89: 318 | return 90 319 | case 91...179: 320 | return 180 321 | case 181...269: 322 | return 270 323 | case 271...359: 324 | return 360 325 | default: 326 | return self+90 327 | } 328 | 329 | } 330 | } 331 | extension UIImagePickerController { 332 | 333 | open override var supportedInterfaceOrientations: UIInterfaceOrientationMask 334 | { 335 | return UIInterfaceOrientationMask.all 336 | } 337 | 338 | open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation 339 | { 340 | var orinentation: UIInterfaceOrientation = UIInterfaceOrientation.init(rawValue: UIApplication.shared.statusBarOrientation.rawValue)! 341 | if UIApplication.shared.statusBarOrientation.rawValue > 4 || UIApplication.shared.statusBarOrientation.rawValue < 1 { 342 | orinentation = .portrait 343 | } 344 | return orinentation 345 | } 346 | 347 | 348 | 349 | } 350 | // handle -3 undo 351 | class MoveGestureRecognizer: UIPanGestureRecognizer { 352 | 353 | 354 | var newState:Int = 0 355 | 356 | func getState() -> UIGestureRecognizerState { 357 | return UIGestureRecognizerState(rawValue: self.newState)! 358 | } 359 | 360 | override func setTranslation(_ translation: CGPoint, in view: UIView?) { 361 | self.newState = self.state.rawValue 362 | super.setTranslation(translation, in: view) 363 | self.newState = self.state.rawValue 364 | 365 | } 366 | 367 | 368 | } 369 | 370 | 371 | -------------------------------------------------------------------------------- /imageEditor/kernel/WPFreePathView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WPFreePathView.swift 3 | // imageEditor 4 | // 5 | // Created by pow on 2017/9/30. 6 | // Copyright © 2017年 WANG. All rights reserved. 7 | 8 | /** 9 | 10 | -state- 11 | 12 | The current effect is curve and line, cut and finished. Specify shape cutting completion. 13 | 1. Curve cutting, can not be cancelled after completion 14 | 2.The curve is a point-to-point operation 15 | 3.The line is a drag and drop operation 16 | 4.Choose a curve or a straight line. You can't scale 17 | 18 | **/ 19 | 20 | import UIKit 21 | 22 | public class WPFreePathView: UIView { 23 | 24 | enum PathState { 25 | case on_control 26 | case move 27 | case off_control 28 | case Refresh 29 | 30 | } 31 | 32 | /**private property**/ 33 | private var points:NSMutableArray = NSMutableArray() // current all point 34 | private var paths:NSMutableArray = NSMutableArray() // current Frre_S of Bezier path 35 | private var recordArray:[CGPoint] = Array() // record start and end point 36 | var isDone:Bool = false // is done darwing line 37 | var isOpenControl:Bool = false 38 | var currentMark:UILabel? // current move change shape of mark label 39 | var currentStartPoint_1:CGPoint = CGPoint.zero // move point 1 40 | var currentStartPoint_2:CGPoint = CGPoint.zero // move point 2 41 | var movePath_1 = UIBezierPath()// current moveed two point . in the 1 42 | var movePath_2 = UIBezierPath()// current moveed two point . in the 2 43 | var lineCount:Int = 0 // number record 44 | var addPoint:CGPoint = CGPoint.zero // move path add point 45 | var controlMark:UILabel? // control mark 46 | var movePaths:[UIBezierPath] = Array() 47 | var currentEvent:NSString = "" // current evenet in the is began and moveed and enden 48 | var currentOPerationPaths:NSArray? // cureent operation path 49 | 50 | /** 51 | record four point 52 | indexPath 53 | 54 | 1.control point 1 actually is move point 1 55 | 2.control point 2 actually is move point 3 56 | 3.control path 1 first 57 | 4.control path 2 last 58 | **/ 59 | var indexPath:[Int] = Array() 60 | var doneTimer:Timer?// done timer 61 | 62 | // public property 63 | public var sideMenu_undoButton:UIButton? 64 | public var sideMenu_clipButton:UIButton? 65 | public var baseImage:UIImage? // operation image 66 | public var path = UIBezierPath() // operation path 67 | 68 | // monitor Prperty 69 | override public var isHidden: Bool{didSet{removeAllEvents()}}//monitor hidden 70 | public var freePathName:NSString = "" {didSet{removeAllEvents()}}//monitor freePathName 71 | 72 | func changeState(newState:PathState,Whihresource:NSArray) { 73 | 74 | switch newState { 75 | case .on_control: 76 | 77 | indexPath.removeAll() 78 | removeControlMark() 79 | control_Free_C_LinePoint(index:Whihresource[1] as! Int) 80 | 81 | case .off_control: 82 | let operationPath = Whihresource[1] as! UIBezierPath 83 | self.paths.replaceObject(at:indexPath[0], with: [PathState.off_control,operationPath]) 84 | indexPath.removeAll() 85 | removeControlMark() 86 | 87 | case .move: 88 | let operationPath = Whihresource[1] as! UIBezierPath 89 | // all event need remove 90 | self.paths.replaceObject(at:indexPath[0], with: [PathState.move,operationPath]) 91 | removeControlMark() 92 | case .Refresh: 93 | // add new then change old 94 | for index in 0..1 { 203 | 204 | // start moveed time 205 | if !(self.points[self.paths.count] as! CGPoint).equalTo(self.points.lastObject as! CGPoint) { 206 | 207 | path.removeAllPoints() 208 | path.move(to: self.points[self.paths.count] as! CGPoint) 209 | path.addLine(to: self.points.lastObject as! CGPoint) 210 | path.stroke() 211 | let s_path:UIBezierPath = UIBezierPath.init(cgPath: path.cgPath) 212 | self.currentOPerationPaths = [PathState.off_control,s_path] 213 | self.paths.add(self.currentOPerationPaths!) 214 | 215 | } 216 | } 217 | } 218 | 219 | // that is straight line darw operation 220 | }else if (self.freePathName.isEqual(to: "Free_S")) { 221 | 222 | path.removeAllPoints() 223 | for s_path in self.paths { 224 | 225 | (s_path as! UIBezierPath).lineWidth = 2 226 | UIColor.orange.setStroke() 227 | (s_path as! UIBezierPath).stroke() 228 | } 229 | 230 | if isDone { 231 | 232 | if let mark = self.currentMark { 233 | 234 | self.movePath_1.removeAllPoints() 235 | self.movePath_1.move(to: self.currentStartPoint_1) 236 | self.movePath_1.addLine(to: (mark.center)) 237 | 238 | self.movePath_2.removeAllPoints() 239 | self.movePath_2.move(to: self.currentStartPoint_2) 240 | self.movePath_2.addLine(to: (mark.center)) 241 | } 242 | 243 | }else{ 244 | 245 | if self.points.count>0 { 246 | path.move(to: self.points[0] as! CGPoint) 247 | path.addLine(to: self.points.lastObject as! CGPoint) 248 | 249 | } 250 | 251 | } 252 | path.stroke() 253 | if !self.recordArray.isEmpty { 254 | self.sideMenu_undoButton?.isSelected = true; 255 | self.sideMenu_undoButton?.isEnabled = true 256 | } 257 | } 258 | 259 | } 260 | 261 | override public func touchesBegan(_ touches: Set, with event: UIEvent?) { 262 | 263 | let startPoint = touches.first?.location(in: self) 264 | currentEvent = "Began" 265 | 266 | if isDone {// done time do it 267 | 268 | if freePathName.isEqual(to: "Free_C") { 269 | 270 | // do someting 271 | 272 | // MARK :No operation at the moment 273 | 274 | }else if freePathName.isEqual(to: "Free_S") { 275 | 276 | move_Free_S_LinePoint(startPoint: startPoint!) 277 | 278 | } 279 | 280 | }else{ 281 | 282 | if freePathName.isEqual(to: "Free_S") { 283 | 284 | 285 | // not done do it 286 | 287 | if (self.subviews.isEmpty) { 288 | // add start mark to view 289 | self.addStartLineMarkview(point: startPoint!) 290 | 291 | } 292 | // add start point to record array 293 | self.recordArray.append(startPoint!) 294 | self.points.add(startPoint!) 295 | 296 | 297 | 298 | }else if freePathName.isEqual(to: "Free_C"){ 299 | 300 | 301 | self.recordArray.append(startPoint!) 302 | 303 | if isContains(point: startPoint!).0 { 304 | 305 | if (self.subviews.first?.isEqual(isContains(point: startPoint!).1 as? UILabel))! {return} 306 | controlMark = isContains(point: startPoint!).1 as? UILabel 307 | if !(controlMark?.text?.hasPrefix("C"))! { 308 | 309 | isOpenControl = true 310 | } 311 | }else 312 | { 313 | controlMark = isContains(point: startPoint!).1 as? UILabel 314 | controlMark?.text = "" 315 | isOpenControl = false 316 | 317 | } 318 | } 319 | } 320 | 321 | } 322 | 323 | override public func touchesMoved(_ touches: Set, with event: UIEvent?) { 324 | 325 | let movePoint = touches.first?.location(in: self) 326 | currentEvent = "Moved" 327 | 328 | if isDone { 329 | 330 | if freePathName.isEqual(to: "Free_C") { 331 | 332 | // MARK :No operation at the moment 333 | 334 | 335 | }else if freePathName.isEqual(to: "Free_S"){ 336 | 337 | self.currentMark?.center = movePoint! 338 | self.setNeedsDisplay() 339 | 340 | } 341 | }else 342 | { 343 | if freePathName.isEqual(to: "Free_C") { 344 | 345 | if isContains(point: movePoint!).0 { // pencil Too sensitive 346 | 347 | if (self.subviews.first?.isEqual(isContains(point: movePoint!).1 as? UILabel))! {return} 348 | 349 | if (controlMark?.text?.isEqual("C1"))! { 350 | 351 | self.currentStartPoint_1 = movePoint! 352 | controlMark?.center = movePoint! 353 | self.setNeedsDisplay() 354 | 355 | }else if (controlMark?.text?.isEqual("C2"))!{ 356 | 357 | self.currentStartPoint_2 = movePoint! 358 | controlMark?.center = movePoint! 359 | self.setNeedsDisplay() 360 | }else 361 | { 362 | 363 | if let operationPath:NSArray = self.currentOPerationPaths { 364 | 365 | // only last point ,so can move 366 | if indexPath[0] == self.paths.count-1 { 367 | 368 | self.changeState(newState: .move, Whihresource: operationPath) 369 | 370 | addPoint = movePoint! 371 | controlMark?.center = movePoint! 372 | isOpenControl = true 373 | self.setNeedsDisplay() 374 | } 375 | 376 | } 377 | 378 | 379 | } 380 | 381 | } 382 | 383 | 384 | }else if freePathName.isEqual(to: "Free_S"){ 385 | 386 | self.points.add(movePoint!) 387 | self.setNeedsDisplay() 388 | } 389 | } 390 | } 391 | 392 | override public func touchesEnded(_ touches: Set, with event: UIEvent?) { 393 | 394 | 395 | let endPoint = touches.first?.location(in: self) 396 | 397 | 398 | currentEvent = "Ended" 399 | //MARK: --out side view can't add point 400 | if !self.frame.contains(endPoint!) {return} 401 | 402 | if isDone { 403 | 404 | if freePathName.isEqual(to: "Free_C") { 405 | 406 | // MARK :No operation at the moment 407 | 408 | 409 | }else if freePathName.isEqual(to: "Free_S"){ 410 | 411 | 412 | if !indexPath.isEmpty { 413 | 414 | let s_path1:UIBezierPath = UIBezierPath.init(cgPath: self.movePath_1.cgPath) 415 | let s_path2:UIBezierPath = UIBezierPath.init(cgPath: self.movePath_2.cgPath) 416 | 417 | 418 | self.paths.replaceObject(at: indexPath[2], with: s_path1) 419 | self.paths.replaceObject(at: indexPath[3], with: s_path2) 420 | 421 | indexPath.removeAll() 422 | self.currentMark = nil 423 | self.setNeedsDisplay() 424 | 425 | } 426 | } 427 | }else{ 428 | 429 | if freePathName.isEqual(to: "Free_C") { 430 | 431 | 432 | if isContains(point: endPoint!).0 { // is point inside 433 | 434 | currentMark = (isContains(point: endPoint!).1 as? UILabel)! 435 | let mark:UILabel = currentMark! 436 | 437 | 438 | // this is done later operation 439 | if (mark.frame.contains(self.recordArray[0] )) { 440 | 441 | for index in 0..0 { 536 | // Bezier Path move point equal height + origin.y 537 | let firstPoint = CGPoint.init(x: (self.paths[0] as! UIBezierPath).bounds.origin.x, y: (self.paths[0] as! UIBezierPath).bounds.origin.y+(self.paths[0] as! UIBezierPath).bounds.size.height) 538 | 539 | // 1.state : undo later path 540 | if (self.subviews.last?.frame.contains(firstPoint))! { 541 | 542 | if ((self.subviews.first as! MarkLabel).text?.isEqual("1"))! { 543 | 544 | self.subviews.first?.removeFromSuperview() 545 | } 546 | doneAnimation() 547 | } 548 | 549 | } 550 | self.points.removeAllObjects() 551 | let s_path:UIBezierPath = UIBezierPath.init(cgPath: path.cgPath) 552 | self.paths.add(s_path) 553 | } 554 | 555 | // add start point to record array 556 | self.recordArray.append(endPoint!) 557 | } 558 | } 559 | //MARK: ** get current path **/ 560 | public func getCurrentPath()->UIBezierPath { 561 | 562 | 563 | let currentPath = UIBezierPath() 564 | 565 | if freePathName.isEqual(to: "Free_C") { 566 | 567 | currentPath.move(to:self.subviews[0].center) 568 | 569 | for index in 1.. (Bool,UIView){ 704 | 705 | 706 | for mark in self.subviews { 707 | 708 | if mark.frame.contains(point) { 709 | 710 | let index = self.subviews.index(of:mark)! 711 | 712 | if index == self.subviews.count-1 { 713 | if isDone { 714 | 715 | indexPath.append(0)//first 716 | indexPath.append(index)// 717 | }else 718 | { 719 | indexPath.append(index-1)//0 720 | indexPath.append(index)//1 721 | } 722 | 723 | 724 | }else if index == 0 { 725 | indexPath.append(self.subviews.count-1)//0 726 | indexPath.append(index)//1 727 | 728 | }else{ 729 | indexPath.append(index-1)//0 730 | indexPath.append(index)//1 731 | 732 | } 733 | return (true,mark) 734 | 735 | } 736 | 737 | } 738 | 739 | return (false,UILabel()) 740 | 741 | } 742 | /** 743 | remove all views or events 744 | */ 745 | func removeAllEvents() { 746 | 747 | self.points.removeAllObjects() 748 | self.recordArray.removeAll() 749 | for mark in self.subviews { 750 | mark.removeFromSuperview() 751 | } 752 | self.path = UIBezierPath() 753 | self.setNeedsDisplay() 754 | self.paths.removeAllObjects() 755 | lineCount = 0 756 | isDone = false 757 | } 758 | 759 | // MARK: undo action 760 | 761 | /** 762 | undo operation 763 | - parameter sender: sidemenu Undo Button 764 | */ 765 | public func undoPath(sender:UIButton){ 766 | 767 | self.sideMenu_clipButton?.isSelected = false 768 | self.sideMenu_clipButton?.isEnabled = false 769 | currentEvent = "Undo" 770 | if (freePathName.isEqual(to: "Free_S")) { 771 | 772 | self.paths.removeLastObject() 773 | 774 | }else if(freePathName.isEqual(to: "Free_C")){ 775 | self.points.removeLastObject() 776 | self.path.removeAllPoints()// remove all points then afresh draw path 777 | self.paths.removeLastObject()// remove last path line 778 | removeControlMark() 779 | } 780 | 781 | // remove record startPoint last 782 | self.recordArray.removeLast() 783 | self.recordArray.removeLast() 784 | // remove mark label last and first 785 | self.subviews.last?.removeFromSuperview() 786 | lineCount -= 1 787 | isDone = false 788 | if let time = self.doneTimer { 789 | 790 | time.invalidate() 791 | for mark in self.subviews { 792 | 793 | if mark.layer.borderColor == UIColor.orange.cgColor{ 794 | mark.layer.borderColor = UIColor.white.cgColor 795 | } 796 | } 797 | 798 | } 799 | self.layoutSubviews() 800 | self.setNeedsDisplay()// afresh draw 801 | // change undo button state 802 | if self.paths.count == 0 { 803 | self.recordArray.removeAll() 804 | self.points.removeAllObjects() 805 | for marl in self.subviews { 806 | 807 | marl.removeFromSuperview() 808 | 809 | } 810 | sender.isEnabled = false; 811 | sender.isSelected = false; 812 | lineCount = 0 813 | } 814 | 815 | } 816 | 817 | /** 818 | done anmation 819 | */ 820 | func doneAnimation() { 821 | 822 | isDone = true 823 | self.sideMenu_clipButton?.isSelected = true 824 | self.sideMenu_clipButton?.isEnabled = true 825 | self.doneTimer = Timer.scheduledTimer(withTimeInterval:0.5, repeats: true) { (mer) in 826 | for endMark in self.subviews{ 827 | 828 | if endMark.layer.borderColor == UIColor.white.cgColor{ 829 | 830 | endMark.layer.borderColor = UIColor.orange.cgColor 831 | 832 | }else 833 | { 834 | endMark.layer.borderColor = UIColor.white.cgColor 835 | } 836 | 837 | } 838 | } 839 | 840 | } 841 | 842 | /** 843 | add mark label 844 | - parameter point: mark center 845 | */ 846 | func addStartLineMarkview(point:CGPoint) { 847 | 848 | indexPath.removeAll()// the first remove data 849 | 850 | lineCount += 1 851 | let markLabel:MarkLabel = MarkLabel.init(frame: CGRect.init(x: 0, y:0, width: 20, height: 20)) 852 | markLabel.center = point 853 | markLabel.bounds = CGRect.init(x: 0, y: 0, width:20, height: 20) 854 | markLabel.layer.borderWidth = 3 855 | markLabel.layer.borderColor = UIColor.white.cgColor 856 | markLabel.layer.cornerRadius = 10 857 | markLabel.layer.masksToBounds = true 858 | markLabel.currentPathName = self.freePathName 859 | markLabel.backgroundColor = UIColor.black 860 | self.addSubview(markLabel) 861 | markLabel.text = "\(lineCount)" 862 | markLabel.textColor = UIColor.white 863 | markLabel.textAlignment = .center 864 | markLabel.font = UIFont.systemFont(ofSize: 10) 865 | markLabel.isUserInteractionEnabled = true 866 | 867 | if freePathName.isEqual(to: "Free_C") { 868 | 869 | // add every one point 。must to afresh index 870 | let index = self.subviews.index(of:markLabel)! 871 | 872 | if index == self.subviews.count-1 { 873 | if isDone { 874 | 875 | indexPath.append(0)//first 876 | indexPath.append(index)// 877 | }else 878 | { 879 | indexPath.append(index-1)//0 880 | indexPath.append(index)//1 881 | } 882 | 883 | }else if index == 0 { 884 | indexPath.append(self.subviews.count-1)//0 885 | indexPath.append(index)//1 886 | 887 | }else{ 888 | indexPath.append(index-1)//0 889 | indexPath.append(index)//1 890 | 891 | } 892 | } 893 | } 894 | 895 | // add control mark label 896 | func addControllerMark(point:CGPoint,tag:Int) { 897 | 898 | let markLabel:UILabel = UILabel.init(frame: CGRect.init(x: 0, y:0, width: 20, height: 20)) 899 | markLabel.center = point 900 | markLabel.bounds = CGRect.init(x: 0, y: 0, width:20, height: 20) 901 | markLabel.layer.borderWidth = 3 902 | markLabel.layer.borderColor = UIColor.white.cgColor 903 | markLabel.layer.cornerRadius = 10 904 | markLabel.layer.masksToBounds = true 905 | markLabel.backgroundColor = UIColor.black 906 | self.addSubview(markLabel) 907 | markLabel.text = "C\(tag)" 908 | markLabel.textColor = UIColor.white 909 | markLabel.textAlignment = .center 910 | markLabel.font = UIFont.systemFont(ofSize: 8) 911 | 912 | } 913 | 914 | } 915 | 916 | //MARK: ---mark label class 917 | 918 | class MarkLabel: UILabel { 919 | 920 | 921 | 922 | public var currentPathName:NSString = "" 923 | 924 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 925 | superview?.touchesBegan(touches, with: event) 926 | } 927 | 928 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 929 | 930 | if currentPathName.isEqual(to: "Free_S") { 931 | superview?.touchesMoved(touches, with: event) 932 | } 933 | 934 | } 935 | 936 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 937 | superview?.touchesEnded(touches, with: event) 938 | self.isUserInteractionEnabled = false 939 | 940 | } 941 | 942 | 943 | } 944 | 945 | -------------------------------------------------------------------------------- /imageEditor/kernel/WPSideMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WPSideMenu.swift 3 | // imageEditor 4 | // 5 | // Created by pow on 2017/11/7. 6 | // Copyright © 2017年 pow. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | protocol WPSideMenuDelegate { 12 | 13 | 14 | func onClick_SideMenu_SubAction_Events(type:String,sender:UIButton) 15 | 16 | } 17 | 18 | //MARK:internal class *WPSideMenu* 19 | public typealias imagePasteAndClipBlock = (String)->Void 20 | 21 | public class WPSideMenu: UIView { 22 | 23 | open var pasteOrClipImageBlock:imagePasteAndClipBlock? 24 | var delegate:WPSideMenuDelegate? 25 | 26 | 27 | @IBOutlet weak var operationStackView: UIStackView! 28 | 29 | @IBOutlet weak var countControlView: UIView! 30 | 31 | @IBOutlet weak var eventsStackView: UIStackView! 32 | 33 | @IBOutlet weak var centerButton: UIButton! 34 | 35 | @IBOutlet weak var doneButton: UIButton! 36 | 37 | @IBOutlet weak var undoButton: UIButton! 38 | 39 | @IBOutlet weak var resetButton: UIButton! 40 | 41 | @IBOutlet weak var redoButton: UIButton! 42 | 43 | @IBOutlet weak var rotateRatioButton: UIButton! 44 | 45 | 46 | 47 | // instantiation nib file 48 | public class func loadsideMenu(_ frame:CGRect) -> WPSideMenu { 49 | var menu:WPSideMenu? 50 | _ = UINib.init(nibName:nibName, instantiate: WPSideMenu.self) { 51 | (view) in 52 | menu = view as? WPSideMenu 53 | 54 | } 55 | menu?.frame = frame 56 | return menu! 57 | 58 | } 59 | 60 | // MARK: load Actions To StackView 61 | func loadActionsToStackView(type:OperationType) { 62 | // loading button 63 | close_ActionsStackView() 64 | for b:UIButton in loadImageData(type: type) { 65 | self.operationStackView.addArrangedSubview(b) 66 | } 67 | 68 | } 69 | //MARK: close_ActionsStackView 70 | func close_ActionsStackView() { 71 | for view in self.operationStackView.subviews { 72 | self.operationStackView.removeArrangedSubview(view) 73 | view.removeFromSuperview() 74 | } 75 | 76 | } 77 | //MARK: reset_ActionsStackView 78 | func reset_ActionsStackView() { 79 | loadActionsToStackView(type: .unknown) 80 | selectedActionType = OperationType.unknown.rawValue 81 | } 82 | 83 | //MARK: load button image 84 | func loadImageData(type:OperationType) -> Array { 85 | 86 | var datas:[UIButton] = Array() 87 | var images:[String] = Array() 88 | if type == .crop { 89 | // the frsit setup stack view and init install 90 | images = ["crop_free","crop_image","crop_canvas","crop_polygon","crop_path_c","crop_path_s","crop_square","crop_triangle","crop_circle","crop_oval","crop_43","crop_169"] 91 | 92 | }else if type == .full{ 93 | images = ["fullCanvas_scaleFill","fullCanvas_scaleFit"] 94 | }else if type == .center{ 95 | 96 | }else if type == .rotation{ 97 | images = ["rotate_left","rotate_right"] 98 | }else{ 99 | images = ["crop","rotate","center","fullCanvas"] 100 | } 101 | for name:String in images { 102 | let actionsBut:UIButton = UIButton() 103 | actionsBut.setImage(UIImage.init(named: name, in:Bundle.init(for: WPSideMenu.self), compatibleWith: nil), for: .normal) 104 | actionsBut.addTarget(self, action: NSSelectorFromString(name+"_Button_Action:"), for: .touchUpInside) 105 | datas.append(actionsBut) 106 | } 107 | 108 | return datas 109 | 110 | } 111 | } 112 | // FIXME: main operation extension 113 | extension WPSideMenu{ 114 | 115 | // cancel button action 116 | @IBAction func cancelButton_Action(_ sender: UIButton) { 117 | 118 | if selectedActionType == OperationType.unknown.rawValue { 119 | 120 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.cancel.rawValue, sender: sender) 121 | 122 | }else{ 123 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.subCancel.rawValue, sender: sender) 124 | reset_ActionsStackView() 125 | closePolygonCountView() 126 | } 127 | 128 | } 129 | // clip button action 130 | @IBAction func crop_Button_Action(_ sender: UIButton) { 131 | loadActionsToStackView(type: .crop) 132 | doneButton.isEnabled = true 133 | undoButton.isEnabled = false 134 | redoButton.isEnabled = false 135 | rotateRatioButton.isHidden = false 136 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.crop.rawValue, sender: sender) 137 | } 138 | 139 | @IBAction func rotate_Button_Action(_ sender: UIButton) { 140 | loadActionsToStackView(type: .rotation) 141 | selectedActionType = OperationType.rotation.rawValue 142 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.rotation.rawValue, sender: sender) 143 | doneButton.isEnabled = true 144 | rotateRatioButton.isHidden = true 145 | 146 | 147 | } 148 | @IBAction func center_Button_Action(_ sender: UIButton) { 149 | self.centerButton = sender 150 | sender.setImage(UIImage.init(named: "center.selected", in: Bundle.init(for: WPSideMenu.self), compatibleWith: nil), for: .highlighted) 151 | selectedActionType = OperationType.center.rawValue 152 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.center.rawValue, sender: sender) 153 | rotateRatioButton.isHidden = true 154 | } 155 | @IBAction func redoButton_Action(_ sender:UIButton){ 156 | 157 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.redo.rawValue, sender: sender) 158 | rotateRatioButton.isHidden = true 159 | } 160 | @IBAction func undoButton_Action(_ sender: UIButton) { 161 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.undo.rawValue, sender: sender) 162 | rotateRatioButton.isHidden = true 163 | } 164 | @IBAction func fullCanvas_Button_Action(_ sender: UIButton) { 165 | loadActionsToStackView(type: .full) 166 | selectedActionType = OperationType.full.rawValue 167 | doneButton.isEnabled = true 168 | rotateRatioButton.isHidden = true 169 | } 170 | 171 | @IBAction func doneButton_Action(_ sender:UIButton){ 172 | 173 | if selectedActionType.isEqual(SubActionType.rotate_left.rawValue)||selectedActionType.isEqual(OperationType.center.rawValue)||selectedActionType.isEqual(OperationType.rotation.rawValue)||selectedActionType.isEqual(OperationType.unknown.rawValue)||selectedActionType.isEqual(OperationType.full.rawValue)||selectedActionType.isEqual(SubActionType.rotate_right.rawValue)||selectedActionType.isEqual(SubActionType.fullCanvas_scaleFit.rawValue)||selectedActionType.isEqual(SubActionType.fullCanvas_scaleFill.rawValue)||selectedActionType.isEqual(OperationType.crop.rawValue)||selectedActionType.isEqual(OperationType.undo.rawValue)||selectedActionType.isEqual(OperationType.redo.rawValue)||selectedActionType.isEqual(OperationType.reset.rawValue){ 174 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.paste.rawValue, sender: sender) 175 | }else{ 176 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.clip.rawValue, sender: sender) 177 | loadActionsToStackView(type: .unknown) 178 | selectedActionType = OperationType.unknown.rawValue 179 | closePolygonCountView() 180 | 181 | } 182 | } 183 | 184 | @IBAction func resetButton_Action(_ sender:UIButton){ 185 | reset_ActionsStackView() 186 | closePolygonCountView() 187 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.reset.rawValue, sender: sender) 188 | resetButton.isEnabled = false 189 | undoButton.isEnabled = false 190 | redoButton.isEnabled = false 191 | } 192 | 193 | @IBAction func rotationScaleButton_Action(_ sender:UIButton){ 194 | 195 | delegate?.onClick_SideMenu_SubAction_Events(type: OperationType.rotateRatio.rawValue, sender: sender) 196 | } 197 | } 198 | 199 | 200 | 201 | // FIXME: sub actions extension 202 | extension WPSideMenu { 203 | 204 | @objc func crop_free_Button_Action(_ sender:UIButton) { 205 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.crop_free.rawValue, sender: sender) 206 | } 207 | @objc func crop_image_Button_Action(_ sender:UIButton) { 208 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.crop_image.rawValue, sender: sender) 209 | 210 | } 211 | @objc func crop_canvas_Button_Action(_ sender:UIButton) { 212 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.crop_canvas.rawValue, sender: sender) 213 | 214 | } 215 | @objc func crop_polygon_Button_Action(_ sender:UIButton) { 216 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.crop_polygon.rawValue, sender: sender) 217 | openPolygon_countOperationButtonView() 218 | } 219 | @objc func crop_path_c_Button_Action(_ sender:UIButton) { 220 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.Free_C.rawValue, sender: sender) 221 | 222 | } 223 | @objc func crop_path_s_Button_Action(_ sender:UIButton) { 224 | 225 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.Free_S.rawValue, sender: sender) 226 | 227 | } 228 | @objc func crop_square_Button_Action(_ sender:UIButton) { 229 | 230 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.Rectangle.rawValue, sender: sender) 231 | 232 | } 233 | @objc func crop_triangle_Button_Action(_ sender:UIButton) { 234 | 235 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.Triangle.rawValue, sender: sender) 236 | 237 | } 238 | @objc func crop_circle_Button_Action(_ sender:UIButton) { 239 | 240 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.Cercle.rawValue, sender: sender) 241 | 242 | } 243 | @objc func crop_oval_Button_Action(_ sender:UIButton) { 244 | 245 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.Oval.rawValue, sender: sender) 246 | 247 | } 248 | @objc func crop_43_Button_Action(_ sender:UIButton) { 249 | 250 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.crop_43.rawValue, sender: sender) 251 | 252 | 253 | } 254 | @objc func crop_169_Button_Action(_ sender:UIButton) { 255 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.crop_169.rawValue, sender: sender) 256 | 257 | } 258 | @objc func fullCanvas_scaleFill_Button_Action(_ sender:UIButton) { 259 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.fullCanvas_scaleFill.rawValue, sender: sender) 260 | 261 | } 262 | @objc func fullCanvas_scaleFit_Button_Action(_ sender:UIButton) { 263 | 264 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.fullCanvas_scaleFit.rawValue, sender: sender) 265 | } 266 | @objc func rotate_left_Button_Action(_ sender:UIButton) { 267 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.rotate_left.rawValue, sender: sender) 268 | } 269 | @objc func rotate_right_Button_Action(_ sender:UIButton) { 270 | delegate?.onClick_SideMenu_SubAction_Events(type: SubActionType.rotate_right.rawValue, sender: sender) 271 | } 272 | func openPolygon_countOperationButtonView() { 273 | self.countControlView.isHidden = false 274 | let countPolygonSelector:Selector = NSSelectorFromString("add_Count_Polygon_Action:") 275 | let addCount:UIButton = self.countControlView.subviews.first as! UIButton 276 | let decreaseCount:UIButton = self.countControlView.subviews.last as! UIButton 277 | addCount.tag = 345 278 | decreaseCount.tag = 346 279 | addCount.addTarget(delegate, action:countPolygonSelector, for: .touchUpInside) 280 | decreaseCount.addTarget(delegate, action:countPolygonSelector, for: .touchUpInside) 281 | } 282 | 283 | func closePolygonCountView() { 284 | self.countControlView.isHidden = true 285 | } 286 | 287 | 288 | 289 | } 290 | 291 | --------------------------------------------------------------------------------