├── .gitignore ├── Example ├── PSImageEditors.xcodeproj │ └── project.pbxproj ├── PSImageEditors │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── PSAppDelegate.h │ ├── PSAppDelegate.m │ ├── PSNavigationController.h │ ├── PSNavigationController.m │ ├── PSViewController.h │ ├── PSViewController.m │ ├── localImages │ │ ├── localImage_01@2x.jpg │ │ ├── localImage_02@2x.gif │ │ ├── localImage_03@2x.jpg │ │ ├── localImage_04@2x.gif │ │ ├── localImage_05@2x.jpg │ │ └── localImage_06@2x.jpg │ └── main.m ├── PSImageEditorsTests │ ├── Info.plist │ └── PSImageEditorsTests.m ├── Podfile └── Podfile.lock ├── LICENSE ├── PSImageEditors.podspec ├── PSImageEditors ├── Assets │ ├── btn_brush_normal@2x.png │ ├── btn_brush_normal@3x.png │ ├── btn_brush_selected@2x.png │ ├── btn_brush_selected@3x.png │ ├── btn_cancel@2x.png │ ├── btn_cancel@3x.png │ ├── btn_changeTextBgColor_normal@2x.png │ ├── btn_changeTextBgColor_normal@3x.png │ ├── btn_changeTextBgColor_selected@2x.png │ ├── btn_changeTextBgColor_selected@3x.png │ ├── btn_clipping_normal@2x.png │ ├── btn_clipping_normal@3x.png │ ├── btn_clipping_revocation_normal@2x.png │ ├── btn_clipping_revocation_normal@3x.png │ ├── btn_close@2x.png │ ├── btn_close@3x.png │ ├── btn_delete_normal@2x.png │ ├── btn_delete_normal@3x.png │ ├── btn_delete_selected@2x.png │ ├── btn_delete_selected@3x.png │ ├── btn_done@2x.png │ ├── btn_done@3x.png │ ├── btn_mosaic_grindArenaceous_normal@2x.png │ ├── btn_mosaic_grindArenaceous_normal@3x.png │ ├── btn_mosaic_grindArenaceous_selected@2x.png │ ├── btn_mosaic_grindArenaceous_selected@3x.png │ ├── btn_mosaic_normal@2x.png │ ├── btn_mosaic_normal@3x.png │ ├── btn_mosaic_rectangular_normal@2x.png │ ├── btn_mosaic_rectangular_normal@3x.png │ ├── btn_mosaic_rectangular_selected@3x.png │ ├── btn_mosaic_rectangular_selectedc@2x.png │ ├── btn_mosaic_selected@2x.png │ ├── btn_mosaic_selected@3x.png │ ├── btn_revocation_normal@2x.png │ ├── btn_revocation_normal@3x.png │ ├── btn_texItem_delete@2x.png │ ├── btn_texItem_delete@3x.png │ ├── btn_texItem_drag@2x.png │ ├── btn_texItem_drag@3x.png │ ├── btn_text_item_close@2x.png │ ├── btn_text_item_close@3x.png │ ├── btn_text_item_zoom@2x.png │ ├── btn_text_item_zoom@3x.png │ ├── btn_text_normal@2x.png │ ├── btn_text_normal@3x.png │ ├── btn_text_selected@2x.png │ ├── btn_text_selected@3x.png │ ├── icon_mask_bottom@2x.png │ ├── icon_mask_bottom@3x.png │ ├── icon_mask_top@2x.png │ ├── icon_mask_top@3x.png │ └── icon_mosaic_smear@2x.png └── Classes │ ├── ImageTools │ ├── Mosaic │ │ ├── PSBrush.h │ │ ├── PSBrush.m │ │ ├── PSBrushCache.h │ │ ├── PSBrushCache.m │ │ ├── PSDrawView.h │ │ ├── PSDrawView.m │ │ ├── PSMosaicBrush.h │ │ ├── PSMosaicBrush.m │ │ ├── PSMosaicTool.h │ │ ├── PSMosaicTool.m │ │ ├── PSPaintBrush.h │ │ ├── PSPaintBrush.m │ │ ├── PSSmearBrush.h │ │ └── PSSmearBrush.m │ ├── PSClippingTool.h │ ├── PSClippingTool.m │ ├── PSDrawTool.h │ ├── PSDrawTool.m │ ├── PSImageToolBase.h │ ├── PSImageToolBase.m │ └── Text │ │ ├── PSMovingView.h │ │ ├── PSMovingView.m │ │ ├── PSStickerItem.h │ │ ├── PSStickerItem.m │ │ ├── PSTexTool.h │ │ └── PSTexTool.m │ ├── PSImageEditor.h │ ├── PSImageEditor.m │ ├── PSImageEditorDefine.h │ ├── ToolBar │ ├── PSBottomToolBar.h │ ├── PSBottomToolBar.m │ ├── PSColorToolBar.h │ ├── PSColorToolBar.m │ ├── PSEditorToolBar.h │ ├── PSEditorToolBar.m │ ├── PSMosaicToolBar.h │ ├── PSMosaicToolBar.m │ ├── PSTopToolBar.h │ └── PSTopToolBar.m │ ├── Utils │ ├── CALayer+PSBrush.h │ ├── CALayer+PSBrush.m │ ├── NSAttributedString+PSCoreText.h │ ├── NSAttributedString+PSCoreText.m │ ├── PSBrush+PSCreate.h │ ├── PSBrush+PSCreate.m │ ├── PSColorFullButton.h │ ├── PSColorFullButton.m │ ├── PSExpandClickAreaButton.h │ ├── PSExpandClickAreaButton.m │ ├── UIImage+PSImageEditors.h │ ├── UIImage+PSImageEditors.m │ ├── UIView+PSImageEditors.h │ └── UIView+PSImageEditors.m │ └── ViewController │ ├── PSCropViewController.h │ ├── PSCropViewController.m │ ├── _PSImageEditorViewController.h │ └── _PSImageEditorViewController.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | *.xcworkspace 20 | !default.xcworkspace 21 | 22 | #CocoaPods 23 | Pods 24 | !Podfile 25 | !Podfile.lock 26 | -------------------------------------------------------------------------------- /Example/PSImageEditors/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/PSImageEditors/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/PSImageEditors/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Example/PSImageEditors/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | zh_CN 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 | LSApplicationCategoryType 22 | 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSCameraUsageDescription 31 | App需要您的同意,才能访问相机 32 | NSPhotoLibraryAddUsageDescription 33 | App需要您的同意,才能访问相册 34 | NSPhotoLibraryUsageDescription 35 | App需要您的同意,才能访问相册 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UIStatusBarStyle 45 | UIStatusBarStyleDarkContent 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | UIViewControllerBasedStatusBarAppearance 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/PSImageEditors/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 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/PSImageEditors/PSAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSAppDelegate.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/24. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PSAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Example/PSImageEditors/PSAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/24. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import "PSAppDelegate.h" 10 | 11 | @interface PSAppDelegate () 12 | 13 | @end 14 | 15 | @implementation PSAppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | 20 | // Override point for customization after application launch. 21 | return YES; 22 | } 23 | 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application { 26 | // 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. 27 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 28 | } 29 | 30 | 31 | - (void)applicationDidEnterBackground:(UIApplication *)application { 32 | // 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. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | 37 | - (void)applicationWillEnterForeground:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application { 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 | 47 | - (void)applicationWillTerminate:(UIApplication *)application { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Example/PSImageEditors/PSNavigationController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSNavigationController.h 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/8/25. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PSNavigationController : UINavigationController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/PSImageEditors/PSNavigationController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSNavigationController.m 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/8/25. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import "PSNavigationController.h" 10 | 11 | #define UIColorFromRGBA(rgbValue, alphaValue) [UIColor \ 12 | colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ 13 | green:((float)((rgbValue & 0x00FF00) >> 8))/255.0 \ 14 | blue:((float)(rgbValue & 0x0000FF))/255.0 \ 15 | alpha:alphaValue] 16 | 17 | @interface PSNavigationController () 18 | 19 | @end 20 | 21 | @implementation PSNavigationController 22 | 23 | + (void)initialize { 24 | 25 | UINavigationBar *navigationBarAppearance = [UINavigationBar appearance]; 26 | [navigationBarAppearance setTitleTextAttributes:@{ 27 | NSForegroundColorAttributeName:[UIColor whiteColor], 28 | NSFontAttributeName:[UIFont systemFontOfSize:17.0f] 29 | }]; 30 | navigationBarAppearance.tintColor = UIColorFromRGBA(0x000000, 1.0f); 31 | navigationBarAppearance.barTintColor = UIColorFromRGBA(0x000000, 1.0f); 32 | 33 | } 34 | 35 | #pragma mark - 拦截控制器跳转 36 | 37 | - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { 38 | 39 | if (self.viewControllers.count > 0) { 40 | viewController.hidesBottomBarWhenPushed = YES; 41 | UIButton *btn = [[UIButton alloc] init]; 42 | [btn setFrame:CGRectMake(0, 0, 44, 44)]; 43 | btn.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0); 44 | //[btn setImage:[UIImage ps_imageNamed:@"btn_navBar_back"] forState:UIControlStateNormal]; 45 | [btn setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft]; 46 | [btn addTarget:self action:@selector(backButtonDidClick) forControlEvents:UIControlEventTouchUpInside]; 47 | viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:btn]; 48 | } 49 | [self.viewControllers.lastObject.view endEditing:YES]; 50 | 51 | CGRect frame = self.tabBarController.tabBar.frame; 52 | frame.origin.y = [UIScreen mainScreen].bounds.size.height - frame.size.height; 53 | self.tabBarController.tabBar.frame = frame; 54 | 55 | [super pushViewController:viewController animated:animated]; 56 | } 57 | 58 | - (UIViewController *)popViewControllerAnimated:(BOOL)animated { 59 | 60 | return [super popViewControllerAnimated:animated]; 61 | } 62 | 63 | - (UIStatusBarStyle)preferredStatusBarStyle { 64 | 65 | return [self.topViewController preferredStatusBarStyle]; 66 | } 67 | 68 | - (BOOL)prefersStatusBarHidden { 69 | 70 | return [self.topViewController prefersStatusBarHidden];; 71 | } 72 | 73 | - (void)backButtonDidClick { 74 | 75 | [self popViewControllerAnimated:YES]; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Example/PSImageEditors/PSViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSViewController.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/24. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PSViewController : UIViewController 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Example/PSImageEditors/PSViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSViewController.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/24. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import "PSViewController.h" 10 | #import 11 | #import "UIImage+PSImageEditors.h" 12 | 13 | @interface PSViewController () 14 | 15 | @property (nonatomic, copy) NSArray *images; 16 | @property (nonatomic, copy) NSArray *urls; 17 | @property (weak, nonatomic) IBOutlet UIImageView *imageView; 18 | 19 | @end 20 | 21 | @implementation PSViewController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | self.navigationController.navigationBar.hidden = YES; 26 | } 27 | 28 | - (UIStatusBarStyle)preferredStatusBarStyle { 29 | 30 | return UIStatusBarStyleLightContent; 31 | } 32 | 33 | - (IBAction)imageEditorsDidClicked { 34 | 35 | UIImage *image = [UIImage imageNamed:@"localImage_06@2x.jpg"]; 36 | PSImageEditor *imageEditor = [[PSImageEditor alloc] initWithImage:image 37 | delegate:self 38 | dataSource:self]; 39 | [self.navigationController pushViewController:imageEditor animated:YES]; 40 | } 41 | 42 | #pragma mark - PSImageEditorDelegate 43 | 44 | - (void)imageEditor:(PSImageEditor *)editor didFinishEdittingWithImage:(UIImage *)image { 45 | 46 | self.imageView.image = image; 47 | [editor dismiss]; 48 | 49 | UIImageWriteToSavedPhotosAlbum(image, self, nil, (__bridge void *)self); 50 | 51 | NSLog(@"%s",__func__); 52 | } 53 | 54 | - (void)imageEditorDidCancel { 55 | 56 | NSLog(@"%s",__func__); 57 | } 58 | 59 | #pragma mark - PSImageEditorDelegate 60 | 61 | - (UIColor *)imageEditorDefaultColor { 62 | 63 | return [UIColor redColor]; 64 | } 65 | 66 | - (PSImageEditorMode)imageEditorDefalutEditorMode { 67 | return PSImageEditorModeDraw; 68 | } 69 | 70 | - (CGFloat)imageEditorDrawPathWidth { 71 | 72 | return 3; 73 | } 74 | 75 | - (UIFont *)imageEditorTextFont { 76 | 77 | return [UIFont boldSystemFontOfSize:24]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Example/PSImageEditors/localImages/localImage_01@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/Example/PSImageEditors/localImages/localImage_01@2x.jpg -------------------------------------------------------------------------------- /Example/PSImageEditors/localImages/localImage_02@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/Example/PSImageEditors/localImages/localImage_02@2x.gif -------------------------------------------------------------------------------- /Example/PSImageEditors/localImages/localImage_03@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/Example/PSImageEditors/localImages/localImage_03@2x.jpg -------------------------------------------------------------------------------- /Example/PSImageEditors/localImages/localImage_04@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/Example/PSImageEditors/localImages/localImage_04@2x.gif -------------------------------------------------------------------------------- /Example/PSImageEditors/localImages/localImage_05@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/Example/PSImageEditors/localImages/localImage_05@2x.jpg -------------------------------------------------------------------------------- /Example/PSImageEditors/localImages/localImage_06@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/Example/PSImageEditors/localImages/localImage_06@2x.jpg -------------------------------------------------------------------------------- /Example/PSImageEditors/main.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // main.m 4 | // PSImageEditors 5 | // 6 | // Created by rsf on 2018/8/24. 7 | // Copyright © 2018年 paintingStyle. All rights reserved. 8 | // 9 | 10 | #import 11 | #import "PSAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([PSAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/PSImageEditorsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSPhotoLibraryAddUsageDescription 22 | 如果不允许,你将无法保存照片到系统相册里 23 | NSPhotoLibraryUsageDescription 24 | 如果不允许,你将无法发送系统相册里的照片给朋友 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/PSImageEditorsTests/PSImageEditorsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSImageEditorsTests.m 3 | // PSImageEditorsTests 4 | // 5 | // Created by rsf on 2018/8/24. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PSImageEditorsTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PSImageEditorsTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | inhibit_all_warnings! 3 | 4 | target 'PSImageEditors' do 5 | 6 | pod 'PSImageEditors', :path => '../' 7 | 8 | target 'PSImageEditorsTests' do 9 | inherit! :search_paths 10 | end 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Masonry (1.1.0) 3 | - PSImageEditors (0.2.4): 4 | - Masonry 5 | - TOCropViewController 6 | - TOCropViewController (2.5.4) 7 | 8 | DEPENDENCIES: 9 | - PSImageEditors (from `../`) 10 | 11 | SPEC REPOS: 12 | https://github.com/cocoapods/specs.git: 13 | - Masonry 14 | - TOCropViewController 15 | 16 | EXTERNAL SOURCES: 17 | PSImageEditors: 18 | :path: "../" 19 | 20 | SPEC CHECKSUMS: 21 | Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 22 | PSImageEditors: d270016b3dac82fc42100849d05a5b12242f3580 23 | TOCropViewController: 2a1ae1242600b1f2d996fd91a5268b2309a33b5c 24 | 25 | PODFILE CHECKSUM: 973cbd8a8398b725c4010e7b3cb62ecab9daeb41 26 | 27 | COCOAPODS: 1.7.5 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /PSImageEditors.podspec: -------------------------------------------------------------------------------- 1 | # pod trunk push PSImageEditors.podspec --allow-warnings --verbose 2 | 3 | Pod::Spec.new do |s| 4 | 5 | s.name = "PSImageEditors" 6 | s.version = "0.2.5" 7 | s.summary = "一个简而至美的图片编辑器" 8 | s.homepage = "https://github.com/paintingStyle/PSImageEditors" 9 | s.license = "MIT" 10 | s.author = { "paintingStyle" => "sfdeveloper@163.com" } 11 | s.platform = :ios,'8.0' 12 | s.source = { :git => "https://github.com/paintingStyle/PSImageEditors.git", :tag => "#{s.version}" } 13 | s.source_files = "PSImageEditors/Classes/**/*.{h,m}" 14 | 15 | prefix_header_contents = <<-EOS 16 | #import 17 | #import "PSImageEditorDefine.h" 18 | #import "UIImage+PSImageEditors.h" 19 | EOS 20 | s.prefix_header_contents = prefix_header_contents 21 | 22 | s.resource_bundles = { 23 | 'PSImageEditors' => ['PSImageEditors/Assets/**/*'] 24 | } 25 | 26 | s.framework = "UIKit" 27 | s.dependency 'Masonry' 28 | s.dependency "TOCropViewController" 29 | s.requires_arc = true 30 | end 31 | -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_brush_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_brush_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_brush_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_brush_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_brush_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_brush_selected@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_brush_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_brush_selected@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_cancel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_cancel@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_cancel@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_cancel@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_changeTextBgColor_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_changeTextBgColor_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_changeTextBgColor_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_changeTextBgColor_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_changeTextBgColor_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_changeTextBgColor_selected@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_changeTextBgColor_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_changeTextBgColor_selected@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_clipping_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_clipping_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_clipping_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_clipping_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_clipping_revocation_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_clipping_revocation_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_clipping_revocation_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_clipping_revocation_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_close@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_close@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_delete_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_delete_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_delete_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_delete_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_delete_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_delete_selected@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_delete_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_delete_selected@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_done@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_done@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_done@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_done@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_grindArenaceous_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_grindArenaceous_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_grindArenaceous_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_grindArenaceous_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_grindArenaceous_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_grindArenaceous_selected@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_grindArenaceous_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_grindArenaceous_selected@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_rectangular_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_rectangular_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_rectangular_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_rectangular_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_rectangular_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_rectangular_selected@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_rectangular_selectedc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_rectangular_selectedc@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_selected@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_mosaic_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_mosaic_selected@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_revocation_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_revocation_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_revocation_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_revocation_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_texItem_delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_texItem_delete@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_texItem_delete@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_texItem_delete@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_texItem_drag@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_texItem_drag@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_texItem_drag@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_texItem_drag@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_item_close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_item_close@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_item_close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_item_close@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_item_zoom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_item_zoom@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_item_zoom@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_item_zoom@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_normal@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_normal@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_selected@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/btn_text_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/btn_text_selected@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/icon_mask_bottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/icon_mask_bottom@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/icon_mask_bottom@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/icon_mask_bottom@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/icon_mask_top@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/icon_mask_top@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/icon_mask_top@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/icon_mask_top@3x.png -------------------------------------------------------------------------------- /PSImageEditors/Assets/icon_mosaic_smear@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paintingStyle/PSImageEditors/e0d804bfb19a43e48eed8e635b094355463a51a3/PSImageEditors/Assets/icon_mosaic_smear@2x.png -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSBrush.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSBrush.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import 9 | #import "CALayer+PSBrush.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | OBJC_EXTERN NSString *const PSBrushClassName; 14 | OBJC_EXTERN NSString *const PSBrushAllPoints; 15 | OBJC_EXTERN NSString *const PSBrushLineWidth; 16 | OBJC_EXTERN NSString *const PSBrushBundle; 17 | 18 | // 为CGPoint{inf, inf} 19 | OBJC_EXTERN const CGPoint PSBrushPointNull; 20 | // 点是否为CGPoint{inf, inf} 21 | OBJC_EXTERN bool PSBrushPointIsNull(CGPoint point); 22 | // 2点的中点 23 | OBJC_EXTERN CGPoint PSBrushMidPoint(CGPoint p0, CGPoint p1); 24 | // 2点的距离 25 | OBJC_EXTERN CGFloat PSBrushDistancePoint(CGPoint p0, CGPoint p1); 26 | // 2点的角度 27 | OBJC_EXTERN CGFloat PSBrushAngleBetweenPoint(CGPoint p0, CGPoint p1); 28 | 29 | @interface PSBrush : NSObject 30 | 31 | /** 线粗 默认5 */ 32 | @property (nonatomic, assign) CGFloat lineWidth; 33 | /** 绘画图层的层级 默认0, 层级越大, 图层越低 */ 34 | @property (nonatomic, assign) NSInteger level; 35 | 36 | /** 37 | 1、创建点与画笔结合的绘画层(意味着重新绘画,重置轨迹数据);应在手势开始时调用,例如:touchesBegan,若需要忽略轨迹坐标,入参修改为CGPoint{inf, inf} 38 | */ 39 | - (CALayer * __nullable)createDrawLayerWithPoint:(CGPoint)point; 40 | /** 41 | 2、结合手势的坐标(手势移动时产生的坐标);应在手势移动时调用,例如:touchesMoved 42 | */ 43 | - (void)addPoint:(CGPoint)point; 44 | 45 | /** 46 | 当前点。如果没值,回调CGPoint{inf, inf} 47 | */ 48 | @property (nonatomic, readonly) CGPoint currentPoint; 49 | /** 50 | 上一个点。如果没值,回调CGPoint{inf, inf} 51 | */ 52 | @property (nonatomic, readonly) CGPoint previousPoint; 53 | 54 | /** 55 | 所有轨迹数据;应在手势结束时调用,例如:touchesEnded、touchesCancelled 56 | */ 57 | @property (nonatomic, readonly, nullable) NSDictionary *allTracks; 58 | 59 | /** 60 | 使用轨迹数据恢复绘画层,持有所有轨迹数据,轻松实现undo、redo操作。 61 | */ 62 | + (CALayer *__nullable)drawLayerWithTrackDict:(NSDictionary *)trackDict; 63 | 64 | @end 65 | 66 | @interface PSBrush (NSBundle) 67 | 68 | /** NSBundle 资源 */ 69 | @property (nonatomic, strong) NSBundle *bundle; 70 | 71 | @end 72 | 73 | NS_ASSUME_NONNULL_END 74 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSBrush.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSBrush.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSBrush.h" 9 | 10 | NSString *const PSBrushClassName = @"PSBrushClassName"; 11 | NSString *const PSBrushAllPoints = @"PSBrushAllPoints"; 12 | NSString *const PSBrushLineWidth = @"PSBrushLineWidth"; 13 | NSString *const PSBrushLevel = @"PSBrushLevel"; 14 | NSString *const PSBrushBundle = @"PSBrushBundle"; 15 | 16 | const CGPoint PSBrushPointNull = {INFINITY, INFINITY}; 17 | 18 | bool PSBrushPointIsNull(CGPoint point) 19 | { 20 | return isinf(point.x) || isinf(point.y); 21 | } 22 | 23 | CGPoint PSBrushMidPoint(CGPoint p0, CGPoint p1) { 24 | if (PSBrushPointIsNull(p0) || PSBrushPointIsNull(p1)) { 25 | return CGPointZero; 26 | } 27 | return (CGPoint) { 28 | (p0.x + p1.x) / 2.0, 29 | (p0.y + p1.y) / 2.0 30 | }; 31 | } 32 | 33 | CGFloat PSBrushDistancePoint(CGPoint p0, CGPoint p1) { 34 | if (PSBrushPointIsNull(p0) || PSBrushPointIsNull(p1)) { 35 | return 0; 36 | } 37 | return sqrt(pow(p0.x - p1.x, 2) + pow(p0.y - p1.y, 2)); 38 | } 39 | 40 | CGFloat PSBrushAngleBetweenPoint(CGPoint p0, CGPoint p1) { 41 | 42 | if (PSBrushPointIsNull(p0) || PSBrushPointIsNull(p1)) { 43 | return 0; 44 | } 45 | 46 | CGPoint p = CGPointMake(p0.x, p0.y+100); 47 | 48 | CGFloat x1 = p.x - p0.x; 49 | CGFloat y1 = p.y - p0.y; 50 | CGFloat x2 = p1.x - p0.x; 51 | CGFloat y2 = p1.y - p0.y; 52 | 53 | CGFloat x = x1 * x2 + y1 * y2; 54 | CGFloat y = x1 * y2 - x2 * y1; 55 | 56 | CGFloat angle = acos(x/sqrt(x*x+y*y)); 57 | 58 | if (p1.x < p0.x) { 59 | angle = M_PI*2 - angle; 60 | } 61 | 62 | return (180.0 * angle / M_PI); 63 | } 64 | 65 | @interface PSBrush () 66 | 67 | @property (nonatomic, strong) NSMutableArray *allPoints; 68 | 69 | /** NSBundle 资源 */ 70 | @property (nonatomic, strong) NSBundle *bundle; 71 | 72 | @end 73 | 74 | @implementation PSBrush 75 | 76 | - (instancetype)init 77 | { 78 | self = [super init]; 79 | if (self) { 80 | _lineWidth = 5.f; 81 | _level = 0; 82 | } 83 | return self; 84 | } 85 | 86 | - (void)addPoint:(CGPoint)point 87 | { 88 | [self.allPoints addObject:NSStringFromCGPoint(point)]; 89 | } 90 | 91 | - (CALayer *)createDrawLayerWithPoint:(CGPoint)point 92 | { 93 | 94 | if (![self isMemberOfClass:[PSBrush class]]) { 95 | NSLog(@"Use subclasses of PSBrush."); 96 | } 97 | 98 | self.allPoints = [NSMutableArray array]; 99 | if (PSBrushPointIsNull(point)) { 100 | return nil; 101 | } 102 | [self.allPoints addObject:NSStringFromCGPoint(point)]; 103 | return nil; 104 | } 105 | 106 | - (CGPoint)currentPoint 107 | { 108 | NSString *pointStr = self.allPoints.lastObject; 109 | if (pointStr) { 110 | return CGPointFromString(pointStr); 111 | } 112 | return PSBrushPointNull; 113 | } 114 | 115 | - (CGPoint)previousPoint 116 | { 117 | if (self.allPoints.count > 1) { 118 | NSString *pointStr = [self.allPoints objectAtIndex:self.allPoints.count-2]; 119 | return CGPointFromString(pointStr); 120 | } 121 | return PSBrushPointNull; 122 | } 123 | 124 | - (NSDictionary *)allTracks 125 | { 126 | if (self.allPoints.count) { 127 | NSMutableDictionary *trackDict = [NSMutableDictionary dictionaryWithDictionary:@{ 128 | PSBrushClassName:NSStringFromClass(self.class), 129 | PSBrushAllPoints:self.allPoints, 130 | PSBrushLineWidth:@(self.lineWidth), 131 | PSBrushLevel:@(self.level) 132 | }]; 133 | if (self.bundle) { 134 | [trackDict setObject:self.bundle forKey:PSBrushBundle]; 135 | } 136 | return trackDict; 137 | } 138 | return nil; 139 | } 140 | 141 | + (CALayer *__nullable)drawLayerWithTrackDict:(NSDictionary *)trackDict 142 | { 143 | NSString *className = trackDict[PSBrushClassName]; 144 | NSInteger level = [trackDict[PSBrushLevel] integerValue]; 145 | Class class = NSClassFromString(className); 146 | if (class && ![class isMemberOfClass:[self class]]) { 147 | CALayer *layer = [class drawLayerWithTrackDict:trackDict]; 148 | layer.ps_level = level; 149 | return layer; 150 | } 151 | return nil; 152 | } 153 | 154 | @end 155 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSBrushCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSBrushCache.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface PSBrushCache : NSCache 13 | 14 | + (instancetype)share; 15 | + (void)free; 16 | 17 | /** 强制缓存对象,不会因数量超出负荷而自动释放 */ 18 | - (void)setForceObject:(id)obj forKey:(id)key; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSBrushCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSBrushCache.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSBrushCache.h" 9 | 10 | @interface PSBrushCache () 11 | 12 | @property (nonatomic, strong) NSMutableDictionary *forceCache; 13 | 14 | @end 15 | 16 | @implementation PSBrushCache 17 | 18 | static PSBrushCache *ps_BrushCacheShare = nil; 19 | + (instancetype)share 20 | { 21 | if (ps_BrushCacheShare == nil) { 22 | ps_BrushCacheShare = [[PSBrushCache alloc] init]; 23 | ps_BrushCacheShare.name = @"BrushCache"; 24 | } 25 | return ps_BrushCacheShare; 26 | } 27 | 28 | + (void)free 29 | { 30 | [ps_BrushCacheShare removeAllObjects]; 31 | ps_BrushCacheShare = nil; 32 | } 33 | 34 | - (instancetype)init { 35 | self = [super init]; 36 | if (self) { 37 | _forceCache = [NSMutableDictionary dictionary]; 38 | //收到系统内存警告后直接调用 removeAllObjects 删除所有缓存对象 39 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)setForceObject:(id)obj forKey:(id)key 45 | { 46 | [self.forceCache setObject:obj forKey:key]; 47 | } 48 | 49 | - (void)removeObjectForKey:(id)key 50 | { 51 | [self.forceCache removeObjectForKey:key]; 52 | [super removeObjectForKey:key]; 53 | } 54 | 55 | - (id)objectForKey:(id)key 56 | { 57 | id obj = [self.forceCache objectForKey:key]; 58 | if (obj) { 59 | return obj; 60 | } 61 | return [super objectForKey:key]; 62 | } 63 | 64 | - (void)removeAllObjects 65 | { 66 | [self.forceCache removeAllObjects]; 67 | [super removeAllObjects]; 68 | } 69 | 70 | - (void)dealloc { 71 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSDrawView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSDrawView.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import 9 | #import "PSBrush.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface PSDrawView : UIView 14 | 15 | /** 画笔 */ 16 | @property (nonatomic, strong) PSBrush *brush; 17 | /** 正在绘画 */ 18 | @property (nonatomic, readonly) BOOL isDrawing; 19 | /** 图层数量 */ 20 | @property (nonatomic, readonly) NSUInteger count; 21 | 22 | @property (nonatomic, copy) void(^drawBegan)(void); 23 | @property (nonatomic, copy) void(^drawEnded)(void); 24 | 25 | /** 数据 */ 26 | @property (nonatomic, strong) NSDictionary *data; 27 | 28 | /** 是否可撤销 */ 29 | - (BOOL)canUndo; 30 | //撤销 31 | - (void)undo; 32 | 33 | - (void)removeAllObjects; 34 | 35 | @end 36 | 37 | NS_ASSUME_NONNULL_END 38 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSDrawView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSDrawView.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSDrawView.h" 9 | 10 | NSString *const kPSDrawViewData = @"PSDrawViewData"; 11 | 12 | @interface PSDrawView () 13 | { 14 | BOOL _isWork; 15 | BOOL _isBegan; 16 | } 17 | /** 画笔数据 */ 18 | @property (nonatomic, strong) NSMutableArray *brushData; 19 | /** 图层 */ 20 | @property (nonatomic, strong) NSMutableArray *layerArray; 21 | 22 | @end 23 | 24 | @implementation PSDrawView 25 | 26 | - (instancetype)initWithFrame:(CGRect)frame 27 | { 28 | self = [super initWithFrame:frame]; 29 | if (self) { 30 | [self customInit]; 31 | } 32 | return self; 33 | } 34 | 35 | - (instancetype)initWithCoder:(NSCoder *)coder 36 | { 37 | self = [super initWithCoder:coder]; 38 | if (self) { 39 | [self customInit]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)customInit 45 | { 46 | _layerArray = [@[] mutableCopy]; 47 | _brushData = [@[] mutableCopy]; 48 | self.backgroundColor = [UIColor clearColor]; 49 | self.clipsToBounds = YES; 50 | self.exclusiveTouch = YES; 51 | } 52 | 53 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 54 | 55 | if ([event allTouches].count == 1 && self.brush) { 56 | _isWork = NO; 57 | _isBegan = YES; 58 | 59 | // 画笔落点 60 | UITouch *touch = [touches anyObject]; 61 | CGPoint point = [touch locationInView:self]; 62 | // 1.创建画布 63 | CALayer *layer = [self.brush createDrawLayerWithPoint:point]; 64 | 65 | if (layer) { 66 | /** 使用画笔的图层层级,层级越大,图层越低 */ 67 | [self.layer.sublayers enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 68 | /** 图层层级<=,放在该图层上方 */ 69 | if (layer.ps_level <= obj.ps_level) { 70 | [self.layer insertSublayer:layer above:obj]; 71 | *stop = YES; 72 | } 73 | }]; 74 | /** 没有被加入到显示图层,直接放到最低 */ 75 | if (layer.superlayer == nil) { 76 | [self.layer insertSublayer:layer atIndex:0]; 77 | } 78 | [self.layerArray addObject:layer]; 79 | } else { 80 | _isBegan = NO; 81 | } 82 | } 83 | [super touchesBegan:touches withEvent:event]; 84 | } 85 | 86 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 87 | 88 | if (_isBegan || _isWork) { 89 | UITouch *touch = [touches anyObject]; 90 | CGPoint point = [touch locationInView:self]; 91 | 92 | if (!CGPointEqualToPoint(self.brush.currentPoint, point)) { 93 | if (_isBegan && self.drawBegan) self.drawBegan(); 94 | _isBegan = NO; 95 | _isWork = YES; 96 | // 2.添加画笔路径坐标 97 | [self.brush addPoint:point]; 98 | } 99 | } 100 | 101 | [super touchesMoved:touches withEvent:event]; 102 | } 103 | 104 | - (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event{ 105 | 106 | if (_isWork) { 107 | // 3.1.添加画笔数据 108 | id data = self.brush.allTracks; 109 | if (data) { 110 | [self.brushData addObject:data]; 111 | } 112 | if (self.drawEnded) self.drawEnded(); 113 | } else if (_isBegan) { 114 | // 3.2.移除开始时添加的图层 115 | [self.layerArray.lastObject removeFromSuperlayer]; 116 | [self.layerArray removeLastObject]; 117 | } 118 | _isBegan = NO; 119 | _isWork = NO; 120 | 121 | [super touchesEnded:touches withEvent:event]; 122 | } 123 | 124 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 125 | { 126 | if (_isWork) { 127 | // 3.1.添加画笔数据 128 | id data = self.brush.allTracks; 129 | if (data) { 130 | [self.brushData addObject:data]; 131 | } 132 | if (self.drawEnded) self.drawEnded(); 133 | } else if (_isBegan) { 134 | // 3.2.移除开始时添加的图层 135 | [self.layerArray.lastObject removeFromSuperlayer]; 136 | [self.layerArray removeLastObject]; 137 | } 138 | _isBegan = NO; 139 | _isWork = NO; 140 | 141 | [super touchesCancelled:touches withEvent:event]; 142 | } 143 | 144 | - (BOOL)isDrawing 145 | { 146 | return _isWork; 147 | } 148 | 149 | /** 图层数量 */ 150 | - (NSUInteger)count 151 | { 152 | return self.brushData.count; 153 | } 154 | 155 | /** 是否可撤销 */ 156 | - (BOOL)canUndo 157 | { 158 | return self.count > 0; 159 | } 160 | 161 | //撤销 162 | - (void)undo 163 | { 164 | CALayer *layer = self.layerArray.lastObject; 165 | [layer removeFromSuperlayer]; 166 | [self.layerArray removeLastObject]; 167 | [self.brushData removeLastObject]; 168 | layer = nil; 169 | } 170 | 171 | - (void)removeAllObjects { 172 | 173 | for (CALayer *layer in self.layerArray) { 174 | [layer removeFromSuperlayer]; 175 | } 176 | 177 | [self.layerArray removeAllObjects]; 178 | [self.brushData removeAllObjects]; 179 | } 180 | 181 | #pragma mark - 数据 182 | - (NSDictionary *)data 183 | { 184 | if (self.brushData.count) { 185 | return @{kPSDrawViewData:[self.brushData copy]}; 186 | } 187 | return nil; 188 | } 189 | 190 | - (void)setData:(NSDictionary *)data 191 | { 192 | NSArray *brushData = data[kPSDrawViewData]; 193 | if (brushData.count) { 194 | for (NSDictionary *allTracks in brushData) { 195 | CALayer *layer = [PSBrush drawLayerWithTrackDict:allTracks]; 196 | if (layer) { 197 | /** 使用画笔的图层层级,层级越大,图层越低 */ 198 | [self.layer.sublayers enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 199 | /** 图层层级<=,放在该图层上方 */ 200 | if (layer.ps_level <= obj.ps_level) { 201 | [self.layer insertSublayer:layer above:obj]; 202 | *stop = YES; 203 | } 204 | }]; 205 | /** 没有被加入到显示图层,直接放到最低 */ 206 | if (layer.superlayer == nil) { 207 | [self.layer insertSublayer:layer atIndex:0]; 208 | } 209 | [self.layerArray addObject:layer]; 210 | } 211 | } 212 | [self.brushData addObjectsFromArray:brushData]; 213 | } 214 | } 215 | 216 | @end 217 | 218 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSMosaicBrush.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSMosaicBrush.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSPaintBrush.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface PSMosaicBrush : PSPaintBrush 13 | 14 | /** 15 | 异步加载马赛克画笔 16 | 17 | @param image 图层展示的图片 18 | @param scale 马赛克大小系数。建议15.0 19 | @param canvasSize 画布大小 20 | @param useCache 是否使用缓存。如果image与canvasSize固定,建议使用缓存。 21 | @param complete 回调状态(成功后可以直接使用[[PSMosaicBrush alloc] init]初始化画笔) 22 | */ 23 | + (void)loadBrushImage:(UIImage *)image scale:(CGFloat)scale canvasSize:(CGSize)canvasSize useCache:(BOOL)useCache complete:(void (^ _Nullable )(BOOL success))complete; 24 | 25 | 26 | /** 27 | 马赛克画笔缓存 28 | 29 | @return 是否存在缓存 30 | */ 31 | + (BOOL)mosaicBrushCache; 32 | 33 | /** 34 | 创建马赛克画笔,创建前必须调用“异步加载马赛克画笔”👆 35 | */ 36 | - (instancetype)init; 37 | 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSMosaicBrush.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSMosaicBrush.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSMosaicBrush.h" 9 | #import "PSBrush+PSCreate.h" 10 | #import "PSBrushCache.h" 11 | 12 | NSString *const PSMosaicBrushImageColor = @"PSMosaicBrushImageColor"; 13 | 14 | @interface PSMosaicBrush () 15 | 16 | @end 17 | 18 | @implementation PSMosaicBrush 19 | 20 | @synthesize lineColor = _lineColor; 21 | 22 | - (instancetype)init 23 | { 24 | self = [super init]; 25 | if (self) { 26 | self->_lineColor = nil; 27 | self.level = 5; 28 | self.lineWidth = 25; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)setLineColor:(UIColor *)lineColor 34 | { 35 | NSLog(@"PSMosaicBrush cann't set line color."); 36 | } 37 | 38 | - (UIColor *)lineColor 39 | { 40 | UIColor *color = [[PSBrushCache share] objectForKey:PSMosaicBrushImageColor]; 41 | if (!color) { 42 | NSLog(@"call PSMosaicBrush loadBrushImage:scale:canvasSize:useCache:complete: method."); 43 | } 44 | return color; 45 | } 46 | 47 | + (CALayer *__nullable)drawLayerWithTrackDict:(NSDictionary *)trackDict 48 | { 49 | UIColor *lineColor = trackDict[PSPaintBrushLineColor]; 50 | if (lineColor) { 51 | [[PSBrushCache share] setForceObject:lineColor forKey:PSMosaicBrushImageColor]; 52 | } 53 | return [super drawLayerWithTrackDict:trackDict]; 54 | 55 | } 56 | 57 | + (void)loadBrushImage:(UIImage *)image scale:(CGFloat)scale canvasSize:(CGSize)canvasSize useCache:(BOOL)useCache complete:(void (^ _Nullable )(BOOL success))complete 58 | { 59 | if (!useCache) { 60 | [[PSBrushCache share] removeObjectForKey:PSMosaicBrushImageColor]; 61 | } 62 | UIColor *color = [[PSBrushCache share] objectForKey:PSMosaicBrushImageColor]; 63 | if (color) { 64 | if (complete) { 65 | complete(YES); 66 | } 67 | return; 68 | } 69 | if (image) { 70 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 71 | 72 | UIColor *patternColor = [image patternGaussianColorWithSize:canvasSize filterHandler:^CIFilter *(CIImage *ciimage) { 73 | //高斯模糊滤镜 74 | CIFilter *filter = [CIFilter filterWithName:@"CIPixellate"]; 75 | [filter setDefaults]; 76 | [filter setValue:ciimage forKey:kCIInputImageKey]; 77 | //value 改变马赛克的大小 78 | [filter setValue:@(scale) forKey:kCIInputScaleKey]; 79 | return filter; 80 | }]; 81 | dispatch_async(dispatch_get_main_queue(), ^{ 82 | 83 | if (patternColor) { 84 | [[PSBrushCache share] setForceObject:patternColor forKey:PSMosaicBrushImageColor]; 85 | } 86 | 87 | if (complete) { 88 | complete((BOOL)patternColor); 89 | } 90 | }); 91 | }); 92 | } else { 93 | if (complete) { 94 | complete(NO); 95 | } 96 | } 97 | } 98 | 99 | + (BOOL)mosaicBrushCache 100 | { 101 | UIColor *color = [[PSBrushCache share] objectForKey:PSMosaicBrushImageColor]; 102 | return (BOOL)color; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSMosaicTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSMosaicTool.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSImageToolBase.h" 9 | 10 | @interface PSMosaicTool : PSImageToolBase 11 | 12 | 13 | - (void)undo; 14 | - (BOOL)canUndo; 15 | - (void)changeRectangularMosaic; 16 | - (void)changeGrindArenaceousMosaic; 17 | 18 | - (UIImage *)mosaicImage; 19 | 20 | @end 21 | 22 | 23 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSMosaicTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSMosaicTool.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSMosaicTool.h" 9 | #import "PSMosaicToolBar.h" 10 | #import "PSDrawView.h" 11 | #import "PSMosaicBrush.h" 12 | #import "PSSmearBrush.h" 13 | #import "UIImage+PSImageEditors.h" 14 | #import "UIView+PSImageEditors.h" 15 | #import 16 | 17 | static const CGFloat kMosaiclevel = 55.0f; 18 | static const CGFloat kDrawLineWidth = 30.0f; 19 | 20 | @interface PSMosaicTool () 21 | 22 | @property (nonatomic, strong) PSMosaicToolBar *mosaicToolBar; 23 | @property (nonatomic, strong) PSDrawView *splashView; 24 | 25 | @property (nonatomic, assign) BOOL rectangularMosaic; 26 | 27 | @end 28 | 29 | @implementation PSMosaicTool { 30 | UIImageView *_drawingView; 31 | } 32 | 33 | - (void)initialize { 34 | 35 | if (!_drawingView) { 36 | self.rectangularMosaic = YES; 37 | _drawingView = [[UIImageView alloc] initWithFrame:self.editor.imageView.bounds]; 38 | [self.editor.imageView addSubview:_drawingView]; 39 | } 40 | } 41 | 42 | #pragma mark - Subclasses Override 43 | 44 | - (void)resetRect:(CGRect)rect { 45 | 46 | _drawingView.frame = self.editor.imageView.bounds; 47 | self.splashView.frame = _drawingView.bounds; 48 | 49 | [self.splashView removeAllObjects]; 50 | 51 | if (self.rectangularMosaic) { 52 | [self changeRectangularMosaic]; 53 | }else { 54 | [self changeGrindArenaceousMosaic]; 55 | } 56 | } 57 | 58 | - (void)setup { 59 | 60 | [super setup]; 61 | 62 | _drawingView.userInteractionEnabled = YES; 63 | self.editor.imageView.userInteractionEnabled = YES; 64 | self.editor.scrollView.panGestureRecognizer.enabled = NO; 65 | 66 | if (!_splashView) { 67 | _splashView = [[PSDrawView alloc] initWithFrame:_drawingView.bounds]; 68 | _splashView.brush = [PSMosaicBrush new]; 69 | _splashView.clipsToBounds = YES; 70 | [_drawingView addSubview:_splashView]; 71 | } 72 | if (!self.mosaicToolBar) { 73 | self.mosaicToolBar = [[PSMosaicToolBar alloc] init]; 74 | self.mosaicToolBar.delegate = self; 75 | self.mosaicToolBar.mosaicType = PSMosaicToolBarEventGrindArenaceous; 76 | [self.editor.view addSubview:self.mosaicToolBar]; 77 | [self.mosaicToolBar mas_makeConstraints:^(MASConstraintMaker *make) { 78 | make.bottom.equalTo(self.editor.bottomToolBar.editorItemsView.mas_top); 79 | make.left.right.equalTo(self.editor.bottomToolBar); 80 | make.height.equalTo(@(PSMosaicToolBarHeight)); 81 | }]; 82 | } 83 | 84 | if (![PSMosaicBrush mosaicBrushCache]) { 85 | CGSize canvasSize = AVMakeRectWithAspectRatioInsideRect(self.editor.imageView.image.size, self.editor.imageView.bounds).size; 86 | [PSMosaicBrush loadBrushImage:self.editor.imageView.image scale:15.0 canvasSize:canvasSize useCache:YES complete:^(BOOL success) {}]; 87 | } 88 | if (![PSSmearBrush smearBrushCache]) { 89 | CGSize canvasSize = AVMakeRectWithAspectRatioInsideRect(self.editor.imageView.image.size, self.editor.imageView.bounds).size; 90 | [PSSmearBrush loadBrushImage:self.editor.imageView.image canvasSize:canvasSize useCache:YES complete:^(BOOL success) {}]; 91 | } 92 | if (self.rectangularMosaic) { 93 | [self changeRectangularMosaic]; 94 | }else { 95 | [self changeGrindArenaceousMosaic]; 96 | } 97 | 98 | 99 | @weakify(self); 100 | self.splashView.drawBegan = ^{ 101 | @strongify(self); 102 | [self.editor hiddenToolBar:YES animation:YES]; 103 | }; 104 | self.splashView.drawEnded = ^{ 105 | @strongify(self); 106 | [self.editor hiddenToolBar:NO animation:YES]; 107 | [self refresUndoState]; 108 | }; 109 | 110 | self.splashView.userInteractionEnabled = YES; 111 | [self.mosaicToolBar setToolBarShow:YES animation:NO]; 112 | } 113 | 114 | - (BOOL)produceChanges { 115 | return [self canUndo]; 116 | } 117 | 118 | - (void)refresUndoState { 119 | 120 | if ([self canUndo]) { 121 | [self.editor addTrajectoryName:NSStringFromClass([self class])]; 122 | } 123 | } 124 | 125 | - (void)cleanup { 126 | [super cleanup]; 127 | 128 | _drawingView.userInteractionEnabled = NO; 129 | self.editor.imageView.userInteractionEnabled = NO; 130 | self.splashView.userInteractionEnabled = NO; 131 | self.editor.scrollView.panGestureRecognizer.enabled = YES; 132 | [self.mosaicToolBar setToolBarShow:NO animation:NO]; 133 | } 134 | 135 | - (void)hiddenToolBar:(BOOL)hidden animation:(BOOL)animation { 136 | 137 | [self.mosaicToolBar setToolBarShow:!hidden animation:animation]; 138 | } 139 | 140 | - (UIImage *)mosaicImage { 141 | 142 | UIImage *image = [_drawingView captureImageAtFrame:_drawingView.bounds]; 143 | return image; 144 | } 145 | 146 | - (void)changeRectangularMosaic { 147 | 148 | self.rectangularMosaic = YES; 149 | 150 | PSMosaicBrush *brush = [[PSMosaicBrush alloc] init]; 151 | [self.splashView setBrush:brush]; 152 | self.splashView.brush.lineWidth = 25; 153 | } 154 | 155 | - (void)changeGrindArenaceousMosaic { 156 | 157 | self.rectangularMosaic = NO; 158 | 159 | PSSmearBrush *brush = [[PSSmearBrush alloc] initWithImageName:@"icon_mosaic_smear@2x.png"]; 160 | NSBundle *bundle = [self mediaEditingBundle]; 161 | brush.bundle = bundle; 162 | [self.splashView setBrush:brush]; 163 | self.splashView.brush.lineWidth = 25 *5; 164 | } 165 | 166 | - (NSBundle *)mediaEditingBundle { 167 | static NSBundle *editingBundle = nil; 168 | if (editingBundle == nil) { 169 | editingBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:NSClassFromString(@"_PSImageEditorViewController")] pathForResource:@"PSImageEditors" ofType:@"bundle"]]; 170 | } 171 | return editingBundle; 172 | } 173 | 174 | - (void)undo { 175 | [self.splashView undo]; 176 | } 177 | 178 | - (BOOL)canUndo { 179 | return [self.splashView canUndo]; 180 | } 181 | 182 | #pragma mark - PSMosaicToolBarDelegate 183 | 184 | - (void)mosaicToolBarType:(PSMosaicType)type event:(PSMosaicToolBarEvent)event { 185 | 186 | switch (event) { 187 | case PSMosaicToolBarEventRectangular: 188 | [self changeRectangularMosaic]; 189 | break; 190 | case PSMosaicToolBarEventGrindArenaceous: 191 | [self changeGrindArenaceousMosaic]; 192 | break; 193 | default: 194 | break; 195 | } 196 | } 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSPaintBrush.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSPaintBrush.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSBrush.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | OBJC_EXTERN NSString *const PSPaintBrushLineColor; 13 | 14 | @interface PSPaintBrush : PSBrush 15 | 16 | /** 线颜色 默认红色 */ 17 | @property (nonatomic, strong, nullable) UIColor *lineColor; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Mosaic/PSPaintBrush.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSPaintBrush.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSPaintBrush.h" 9 | #import "PSBrush+PSCreate.h" 10 | 11 | NSString *const PSPaintBrushLineColor = @"PSPaintBrushLineColor"; 12 | 13 | @interface PSPaintBrush () 14 | 15 | @property (nonatomic, strong) UIBezierPath *path; 16 | 17 | @property (nonatomic, weak) CAShapeLayer *layer; 18 | 19 | @end 20 | 21 | @implementation PSPaintBrush 22 | 23 | - (instancetype)init 24 | { 25 | self = [super init]; 26 | if (self) { 27 | _lineColor = [UIColor redColor]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)addPoint:(CGPoint)point 33 | { 34 | [super addPoint:point]; 35 | if (self.path) { 36 | CGPoint midPoint = PSBrushMidPoint(self.previousPoint, point); 37 | // 使用二次曲线方程式 38 | [self.path addQuadCurveToPoint:midPoint controlPoint:self.previousPoint]; 39 | self.layer.path = self.path.CGPath; 40 | } 41 | } 42 | 43 | - (CALayer *)createDrawLayerWithPoint:(CGPoint)point 44 | { 45 | if (self.lineColor) { 46 | [super createDrawLayerWithPoint:point]; 47 | /** 48 | 首次创建UIBezierPath 49 | */ 50 | self.path = [[self class] createBezierPathWithPoint:point]; 51 | 52 | CAShapeLayer *layer = [[self class] createShapeLayerWithPath:self.path lineWidth:self.lineWidth strokeColor:self.lineColor]; 53 | layer.ps_level = self.level; 54 | self.layer = layer; 55 | 56 | return layer; 57 | } 58 | return nil; 59 | } 60 | - (NSDictionary *)allTracks 61 | { 62 | NSDictionary *superAllTracks = [super allTracks]; 63 | 64 | NSMutableDictionary *myAllTracks = nil; 65 | if (superAllTracks && self.lineColor) { 66 | myAllTracks = [NSMutableDictionary dictionary]; 67 | [myAllTracks addEntriesFromDictionary:superAllTracks]; 68 | [myAllTracks addEntriesFromDictionary:@{PSPaintBrushLineColor:self.lineColor}]; 69 | } 70 | return myAllTracks; 71 | } 72 | 73 | + (CALayer *__nullable)drawLayerWithTrackDict:(NSDictionary *)trackDict 74 | { 75 | CGFloat lineWidth = [trackDict[PSBrushLineWidth] floatValue]; 76 | UIColor *lineColor = trackDict[PSPaintBrushLineColor]; 77 | NSArray *allPoints = trackDict[PSBrushAllPoints]; 78 | 79 | if (allPoints) { 80 | CGPoint previousPoint = CGPointFromString(allPoints.firstObject); 81 | UIBezierPath *path = [[self class] createBezierPathWithPoint:previousPoint]; 82 | 83 | for (NSInteger i=1; i*points; 75 | 76 | @end 77 | 78 | @implementation PSSmearBrush 79 | 80 | - (instancetype)init 81 | { 82 | self = [super init]; 83 | if (self) { 84 | self.level = 5; 85 | self.lineWidth = 100; 86 | } 87 | return self; 88 | } 89 | 90 | - (instancetype)initWithImageName:(NSString *)name; 91 | { 92 | self = [self init]; 93 | if (self) { 94 | _name = name; 95 | } 96 | return self; 97 | } 98 | 99 | + (void)loadBrushImage:(UIImage *)image canvasSize:(CGSize)canvasSize useCache:(BOOL)useCache complete:(void (^ _Nullable )(BOOL success))complete 100 | { 101 | if (!useCache) { 102 | [[PSBrushCache share] removeObjectForKey:PSSmearBrushImage]; 103 | } 104 | UIImage *cacheImage = [[PSBrushCache share] objectForKey:PSSmearBrushImage]; 105 | if (cacheImage) { 106 | if (complete) { 107 | complete(YES); 108 | } 109 | return; 110 | } 111 | if (image) { 112 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 113 | 114 | UIImage *patternImage = [image patternGaussianImageWithSize:canvasSize filterHandler:nil]; 115 | dispatch_async(dispatch_get_main_queue(), ^{ 116 | 117 | if (patternImage) { 118 | [[PSBrushCache share] setForceObject:patternImage forKey:PSSmearBrushImage]; 119 | } 120 | 121 | if (complete) { 122 | complete((BOOL)patternImage); 123 | } 124 | }); 125 | }); 126 | } else { 127 | if (complete) { 128 | complete(NO); 129 | } 130 | } 131 | } 132 | 133 | + (BOOL)smearBrushCache 134 | { 135 | UIImage *cacheImage = [[PSBrushCache share] objectForKey:PSSmearBrushImage]; 136 | return (BOOL)cacheImage; 137 | } 138 | 139 | - (void)addPoint:(CGPoint)point 140 | { 141 | [super addPoint:point]; 142 | 143 | UIImage *image = [[self class] cacheImageWithName:self.name bundle:self.bundle]; 144 | 145 | CGFloat angle = PSBrushAngleBetweenPoint(self.previousPoint, point); 146 | // 调整角度,顺着绘画方向。 147 | angle = 360-angle; 148 | // 随机坐标 149 | point.x += floorf(arc4random()%((int)(image.size.width)+1)) - image.size.width/2; 150 | point.y += floorf(arc4random()%((int)(image.size.height)+1)) - image.size.width/2; 151 | 152 | //转换屏幕坐标,获取颜色块 153 | UIColor *color = nil; 154 | UIView *drawView = (UIView *)self.layer.superlayer.delegate; 155 | if ([drawView isKindOfClass:[UIView class]]) { 156 | // CGPoint screenPoint = [drawView convertPoint:point toView:[UIApplication sharedApplication].keyWindow]; 157 | color = [self colorOfPoint:point]; 158 | } 159 | 160 | CALayer *subLayer = [[self class] createSubLayerWithImage:image lineWidth:self.lineWidth point:point angle:angle color:color]; 161 | 162 | [self.layer addSublayer:subLayer]; 163 | 164 | // 记录坐标数据 165 | NSMutableDictionary *pointDict = [NSMutableDictionary dictionaryWithCapacity:2]; 166 | [pointDict setObject:NSStringFromCGPoint(point) forKey:PSSmearBrushPoint]; 167 | [pointDict setObject:@(angle) forKey:PSSmearBrushAngle]; 168 | 169 | if (color) { 170 | [pointDict setObject:color forKey:PSSmearBrushColor]; 171 | } 172 | [self.points addObject:pointDict]; 173 | } 174 | 175 | - (CALayer *)createDrawLayerWithPoint:(CGPoint)point 176 | { 177 | [super createDrawLayerWithPoint:point]; 178 | _points = [NSMutableArray array]; 179 | 180 | CALayer *layer = [[self class] createLayer]; 181 | layer.ps_level = self.level; 182 | self.layer = layer; 183 | return layer; 184 | } 185 | 186 | - (NSDictionary *)allTracks 187 | { 188 | NSDictionary *superAllTracks = [super allTracks]; 189 | 190 | NSMutableDictionary *myAllTracks = nil; 191 | if (superAllTracks && self.points.count) { 192 | myAllTracks = [NSMutableDictionary dictionary]; 193 | [myAllTracks addEntriesFromDictionary:superAllTracks]; 194 | [myAllTracks addEntriesFromDictionary:@{ 195 | PSSmearBrushPoints:self.points, 196 | PSSmearBrushName:self.name 197 | }]; 198 | } 199 | return myAllTracks; 200 | } 201 | 202 | + (CALayer *__nullable)drawLayerWithTrackDict:(NSDictionary *)trackDict 203 | { 204 | CGFloat lineWidth = [trackDict[PSBrushLineWidth] floatValue]; 205 | // NSArray *allPoints = trackDict[PSBrushAllPoints]; 206 | NSArray *points = trackDict[PSSmearBrushPoints]; 207 | NSString *name = trackDict[PSSmearBrushName]; 208 | NSBundle *bundle = trackDict[PSBrushBundle]; 209 | 210 | if (points.count > 0) { 211 | CALayer *layer = [[self class] createLayer]; 212 | UIImage *image = [[self class] cacheImageWithName:name bundle:bundle]; 213 | NSDictionary *pointDict = nil; 214 | for (NSInteger i=0; i 14 | 15 | @property (nonatomic, strong) NSMutableArray *imagesCache; 16 | 17 | @end 18 | 19 | @implementation PSClippingTool 20 | 21 | #pragma mark - Subclasses Override 22 | 23 | - (void)setup { 24 | 25 | [self.editor buildClipImageCallback:^(UIImage *clipedImage) { 26 | [self clippingWithImage:clipedImage]; 27 | }]; 28 | } 29 | 30 | - (void)cleanup { 31 | 32 | } 33 | 34 | #pragma mark - Method 35 | 36 | - (BOOL)canUndo { 37 | return self.imagesCache.count; 38 | } 39 | 40 | - (void)undo { 41 | 42 | UIImage *image = self.imagesCache.lastObject; 43 | self.editor.imageView.image = image; 44 | [self.editor refreshImageView]; 45 | } 46 | 47 | - (void)clippingWithImage:(UIImage *)image { 48 | 49 | [self.imagesCache addObject:image]; 50 | 51 | PSCropViewController *cropController = [[PSCropViewController alloc] initWithCroppingStyle: 52 | TOCropViewCroppingStyleDefault image:image]; 53 | cropController.aspectRatioPickerButtonHidden = YES; 54 | cropController.delegate = self; 55 | CGRect viewFrame = [self.editor.view convertRect:self.editor.imageView.frame 56 | toView:self.editor.navigationController.view]; 57 | [cropController presentAnimatedFromParentViewController:self.editor 58 | fromImage:image 59 | fromView:self.editor.imageView 60 | fromFrame:viewFrame 61 | angle:0 62 | toImageFrame:CGRectZero 63 | setup:^{ 64 | [UIApplication sharedApplication].statusBarHidden = YES; 65 | } completion:nil]; 66 | } 67 | 68 | - (UIImage *)doneImage 69 | { 70 | UIImage *doneImage = nil; 71 | 72 | UIGraphicsBeginImageContextWithOptions((CGSize){17,14}, NO, 0.0f); 73 | { 74 | //// Rectangle Drawing 75 | UIBezierPath* rectanglePath = UIBezierPath.bezierPath; 76 | [rectanglePath moveToPoint: CGPointMake(1, 7)]; 77 | [rectanglePath addLineToPoint: CGPointMake(6, 12)]; 78 | [rectanglePath addLineToPoint: CGPointMake(16, 1)]; 79 | [UIColor.whiteColor setStroke]; 80 | rectanglePath.lineWidth = 2; 81 | [rectanglePath stroke]; 82 | 83 | 84 | doneImage = UIGraphicsGetImageFromCurrentImageContext(); 85 | } 86 | UIGraphicsEndImageContext(); 87 | 88 | return doneImage; 89 | } 90 | 91 | - (UIImage *)cancelImage 92 | { 93 | UIImage *cancelImage = nil; 94 | 95 | UIGraphicsBeginImageContextWithOptions((CGSize){16,16}, NO, 0.0f); 96 | { 97 | UIBezierPath* bezierPath = UIBezierPath.bezierPath; 98 | [bezierPath moveToPoint: CGPointMake(15, 15)]; 99 | [bezierPath addLineToPoint: CGPointMake(1, 1)]; 100 | [UIColor.whiteColor setStroke]; 101 | bezierPath.lineWidth = 2; 102 | [bezierPath stroke]; 103 | 104 | 105 | //// Bezier 2 Drawing 106 | UIBezierPath* bezier2Path = UIBezierPath.bezierPath; 107 | [bezier2Path moveToPoint: CGPointMake(1, 15)]; 108 | [bezier2Path addLineToPoint: CGPointMake(15, 1)]; 109 | [UIColor.whiteColor setStroke]; 110 | bezier2Path.lineWidth = 2; 111 | [bezier2Path stroke]; 112 | 113 | cancelImage = UIGraphicsGetImageFromCurrentImageContext(); 114 | } 115 | UIGraphicsEndImageContext(); 116 | 117 | return cancelImage; 118 | } 119 | 120 | 121 | #pragma mark - TOCropViewControllerDelegate 122 | 123 | 124 | - (void)cropViewController:(TOCropViewController *)cropViewController 125 | didCropToImage:(UIImage *)image 126 | withRect:(CGRect)cropRect 127 | angle:(NSInteger)angle { 128 | 129 | UIImage *rectImage = image; 130 | self.produceChanges = YES; 131 | if (self.clipedCompleteBlock) { self.clipedCompleteBlock(rectImage, cropRect); } 132 | 133 | if (cropViewController.croppingStyle != TOCropViewCroppingStyleCircular) { 134 | [cropViewController dismissAnimatedFromParentViewController:self.editor 135 | withCroppedImage:image 136 | toView:self.editor.imageView 137 | toFrame:CGRectZero 138 | setup:^{ 139 | [UIApplication sharedApplication].statusBarHidden = NO; 140 | } completion:nil]; 141 | }else { 142 | [cropViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 143 | } 144 | 145 | if (cropViewController.cropView.canBeReset) { 146 | [self.editor addTrajectoryName:NSStringFromClass([self class])]; 147 | }else { 148 | [self.imagesCache removeLastObject]; 149 | } 150 | 151 | [self cleanup]; 152 | } 153 | 154 | - (void)cropViewController:(nonnull TOCropViewController *)cropViewController 155 | didFinishCancelled:(BOOL)cancelled { 156 | 157 | if (self.dismiss && cancelled) { 158 | self.dismiss(cancelled); 159 | [cropViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 160 | } 161 | 162 | if (cancelled) { 163 | [self.imagesCache removeLastObject]; 164 | } 165 | } 166 | 167 | - (NSMutableArray *)imagesCache { 168 | if (!_imagesCache) { 169 | _imagesCache = [NSMutableArray array]; 170 | } 171 | return _imagesCache; 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/PSDrawTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSDrawTool.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSImageToolBase.h" 9 | 10 | @interface PSDrawTool : PSImageToolBase { 11 | @public UIImageView *_drawingView; 12 | } 13 | 14 | - (BOOL)canUndo; 15 | - (void)undo; 16 | 17 | @end 18 | 19 | @interface PSDrawPath : UIBezierPath 20 | 21 | @property (nonatomic, strong) CAShapeLayer *shape; 22 | 23 | /// 画笔颜色 24 | @property (nonatomic, strong) UIColor *pathColor; 25 | 26 | + (instancetype)pathToPoint:(CGPoint)beginPoint 27 | pathWidth:(CGFloat)pathWidth; 28 | 29 | /// 画 30 | - (void)pathLineToPoint:(CGPoint)movePoint; 31 | /// 绘制 32 | - (void)drawPath; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/PSDrawTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSDrawTool.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSDrawTool.h" 9 | #import "PSColorToolBar.h" 10 | 11 | @interface PSDrawTool() 12 | 13 | @property (nonatomic, assign) CGFloat drawLineWidth; 14 | @property (nonatomic, strong) UIColor *drawLineColor; 15 | @property (nonatomic, strong) UIPanGestureRecognizer *panGesture; 16 | 17 | @property (nonatomic, strong) PSColorToolBar *colorToolBar; 18 | @property (nonatomic, strong) NSMutableArray *drawPaths; 19 | 20 | @end 21 | 22 | @implementation PSDrawTool { 23 | CGSize _originalImageSize; 24 | } 25 | 26 | - (instancetype)initWithImageEditor:(_PSImageEditorViewController *)editor 27 | withOption:(NSDictionary *)option { 28 | 29 | if (self = [super initWithImageEditor:editor withOption:option]) { 30 | _drawPaths = [NSMutableArray array]; 31 | } 32 | return self; 33 | } 34 | 35 | #pragma mark - Subclasses Override 36 | 37 | - (void)initialize { 38 | 39 | [super initialize]; 40 | 41 | if (!_drawingView) { 42 | _originalImageSize = self.editor.imageView.image.size; 43 | _drawingView = [[UIImageView alloc] initWithFrame:self.editor.imageView.bounds]; 44 | _drawingView.layer.shouldRasterize = YES; 45 | _drawingView.layer.minificationFilter = kCAFilterTrilinear; 46 | [self.editor.imageView addSubview:_drawingView]; 47 | } 48 | } 49 | 50 | - (void)resetRect:(CGRect)rect { 51 | 52 | _drawingView.image = nil; 53 | _originalImageSize = self.editor.imageView.image.size; 54 | _drawingView.frame = self.editor.imageView.bounds; 55 | [_drawPaths removeAllObjects]; 56 | } 57 | 58 | - (void)setup { 59 | 60 | _originalImageSize = self.editor.imageView.image.size; 61 | _drawingView.frame = self.editor.imageView.bounds; 62 | 63 | _drawingView.userInteractionEnabled = YES; 64 | self.editor.imageView.userInteractionEnabled = YES; 65 | self.editor.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2; 66 | self.editor.scrollView.panGestureRecognizer.delaysTouchesBegan = NO; 67 | self.editor.scrollView.pinchGestureRecognizer.delaysTouchesBegan = NO; 68 | 69 | if (!_drawLineColor) { 70 | _drawLineColor = self.option[kImageToolDrawLineColorKey]; 71 | } 72 | _drawLineWidth = [self.option[kImageToolDrawLineWidthKey] floatValue]; 73 | 74 | if (!self.panGesture) { 75 | self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(drawingViewDidPan:)]; 76 | self.panGesture.maximumNumberOfTouches = 1; 77 | [_drawingView addGestureRecognizer:self.panGesture]; 78 | } 79 | if (!self.panGesture.isEnabled) { 80 | self.panGesture.enabled = YES; 81 | } 82 | 83 | if (!self.colorToolBar) { 84 | self.colorToolBar = [[PSColorToolBar alloc] initWithType:PSColorToolBarTypeColor]; 85 | self.colorToolBar.delegate = self; 86 | [self.editor.view addSubview:self.colorToolBar]; 87 | [self.colorToolBar mas_makeConstraints:^(MASConstraintMaker *make) { 88 | make.bottom.equalTo(self.editor.bottomToolBar.editorItemsView.mas_top); 89 | make.left.right.equalTo(self.editor.view); 90 | make.height.equalTo(@(PSDrawColorToolBarHeight)); 91 | }]; 92 | } 93 | [self.colorToolBar setToolBarShow:YES animation:NO]; 94 | } 95 | 96 | - (void)cleanup { 97 | 98 | _drawingView.userInteractionEnabled = NO; 99 | self.editor.imageView.userInteractionEnabled = NO; 100 | self.editor.scrollView.panGestureRecognizer.minimumNumberOfTouches = 1; 101 | self.panGesture.enabled = NO; 102 | [self.colorToolBar setToolBarShow:NO animation:NO]; 103 | } 104 | 105 | - (void)executeWithCompletionBlock:(void (^)(UIImage *, NSError *, NSDictionary *))completionBlock { 106 | 107 | UIImage *backgroundImage = self.editor.imageView.image; 108 | UIImage *foregroundImage = _drawingView.image; 109 | 110 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 111 | UIImage *image = [self buildImageWithBackgroundImage:backgroundImage foregroundImage:foregroundImage]; 112 | dispatch_async(dispatch_get_main_queue(), ^{ 113 | completionBlock(image, nil, nil); 114 | }); 115 | }); 116 | } 117 | 118 | - (void)hiddenToolBar:(BOOL)hidden animation:(BOOL)animation { 119 | 120 | [self.colorToolBar setToolBarShow:!hidden animation:animation]; 121 | } 122 | 123 | - (UIImage *)buildImageWithBackgroundImage:(UIImage*)backgroundImage 124 | foregroundImage:(UIImage*)foregroundImage { 125 | 126 | UIGraphicsBeginImageContextWithOptions(_originalImageSize, NO, backgroundImage.scale); 127 | [backgroundImage drawAtPoint:CGPointZero]; 128 | [foregroundImage drawInRect:CGRectMake(0, 0, _originalImageSize.width, _originalImageSize.height)]; 129 | UIImage *tmp = UIGraphicsGetImageFromCurrentImageContext(); 130 | UIGraphicsEndImageContext(); 131 | 132 | return tmp; 133 | } 134 | 135 | - (void)undo { 136 | 137 | if (!_drawPaths.count) { return; } 138 | [_drawPaths removeLastObject]; 139 | [self drawLine]; 140 | } 141 | 142 | - (void)refresUndoState { 143 | 144 | if ([self canUndo]) { 145 | [self.editor addTrajectoryName:NSStringFromClass([self class])]; 146 | } 147 | } 148 | 149 | - (BOOL)produceChanges { 150 | return _drawPaths.count; 151 | } 152 | 153 | - (BOOL)canUndo { 154 | return _drawPaths.count; 155 | } 156 | 157 | #pragma mark - PSColorToolBarDelegate 158 | 159 | - (void)colorToolBar:(PSColorToolBar *)toolBar event:(PSColorToolBarEvent)event { 160 | 161 | switch (event) { 162 | case PSColorToolBarEventSelectColor: 163 | _drawLineColor = toolBar.currentColor; 164 | break; 165 | default: 166 | break; 167 | } 168 | } 169 | 170 | #pragma mark - 根据手势路径画线 171 | 172 | - (void)drawingViewDidPan:(UIPanGestureRecognizer*)sender { 173 | 174 | CGPoint currentDraggingPosition = [sender locationInView:_drawingView]; 175 | 176 | if(sender.state == UIGestureRecognizerStateBegan){ 177 | // 初始化一个UIBezierPath对象, 把起始点存储到UIBezierPath对象中, 用来存储所有的轨迹点 178 | PSDrawPath *path = [PSDrawPath pathToPoint:currentDraggingPosition pathWidth:MAX(1, self.drawLineWidth)]; 179 | path.pathColor = self.drawLineColor; 180 | path.shape.strokeColor = self.drawLineColor.CGColor; 181 | [_drawPaths addObject:path]; 182 | [self.editor hiddenToolBar:YES animation:YES]; 183 | } 184 | 185 | if(sender.state == UIGestureRecognizerStateChanged){ 186 | // 获得数组中的最后一个UIBezierPath对象(因为我们每次都把UIBezierPath存入到数组最后一个,因此获取时也取最后一个) 187 | PSDrawPath *path = [_drawPaths lastObject]; 188 | [path pathLineToPoint:currentDraggingPosition];//添加点 189 | [self drawLine]; 190 | } 191 | 192 | if (sender.state == UIGestureRecognizerStateEnded) { 193 | [self refresUndoState]; 194 | [self.editor hiddenToolBar:NO animation:YES]; 195 | } 196 | } 197 | 198 | - (void)drawLine { 199 | 200 | CGSize size = _drawingView.frame.size; 201 | UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); 202 | CGContextRef context = UIGraphicsGetCurrentContext(); 203 | //去掉锯齿 204 | CGContextSetAllowsAntialiasing(context, true); 205 | CGContextSetShouldAntialias(context, true); 206 | 207 | for (PSDrawPath *path in _drawPaths) { 208 | [path drawPath]; 209 | } 210 | _drawingView.image = UIGraphicsGetImageFromCurrentImageContext(); 211 | 212 | UIGraphicsEndImageContext(); 213 | } 214 | 215 | @end 216 | 217 | @interface PSDrawPath() 218 | 219 | @property (nonatomic, strong) UIBezierPath *bezierPath; 220 | @property (nonatomic, assign) CGPoint beginPoint; 221 | @property (nonatomic, assign) CGFloat pathWidth; 222 | 223 | @end 224 | 225 | @implementation PSDrawPath 226 | 227 | + (instancetype)pathToPoint:(CGPoint)beginPoint pathWidth:(CGFloat)pathWidth { 228 | 229 | UIBezierPath *bezierPath = [UIBezierPath bezierPath]; 230 | bezierPath.lineWidth = pathWidth; 231 | bezierPath.lineCapStyle = kCGLineCapRound; 232 | bezierPath.lineJoinStyle = kCGLineJoinRound; 233 | [bezierPath moveToPoint:beginPoint]; 234 | 235 | 236 | CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init]; 237 | shapeLayer.lineCap = kCALineCapRound; 238 | shapeLayer.lineJoin = kCALineJoinRound; 239 | shapeLayer.lineWidth = pathWidth; 240 | shapeLayer.fillColor = [UIColor clearColor].CGColor; 241 | shapeLayer.path = bezierPath.CGPath; 242 | 243 | PSDrawPath *path = [[PSDrawPath alloc] init]; 244 | path.beginPoint = beginPoint; 245 | path.pathWidth = pathWidth; 246 | path.bezierPath = bezierPath; 247 | path.shape = shapeLayer; 248 | 249 | return path; 250 | } 251 | 252 | - (void)pathLineToPoint:(CGPoint)movePoint { 253 | 254 | [self.bezierPath addLineToPoint:movePoint]; 255 | self.shape.path = self.bezierPath.CGPath; 256 | } 257 | 258 | - (void)drawPath { 259 | 260 | [self.pathColor set]; 261 | [self.bezierPath stroke]; 262 | } 263 | 264 | @end 265 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/PSImageToolBase.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSImageToolBase.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "_PSImageEditorViewController.h" 11 | 12 | static const CGFloat kImageToolAnimationDuration = 0.3; 13 | static const CGFloat kImageToolBaseFadeoutDuration = 0.2; 14 | 15 | static NSString *kImageToolDrawLineWidthKey = @"drawLineWidth"; 16 | static NSString *kImageToolDrawLineColorKey = @"drawLineColor"; 17 | 18 | static NSString *kImageToolTextColorKey = @"textColor"; 19 | static NSString *kImageToolTextFontKey = @"textFont"; 20 | 21 | @interface PSImageToolBase : NSObject 22 | 23 | @property (nonatomic, weak) _PSImageEditorViewController *editor; 24 | @property (nonatomic, strong) NSDictionary *option; 25 | @property (nonatomic, assign) BOOL produceChanges; // 是否使用过该工具,即图片产生了编辑操作 26 | 27 | - (instancetype)initWithImageEditor:(_PSImageEditorViewController *)editor 28 | withOption:(NSDictionary *)option; 29 | 30 | - (void)initialize; 31 | - (void)setup; 32 | - (void)cleanup; 33 | - (void)resetRect:(CGRect)rect; 34 | - (void)executeWithCompletionBlock:(void(^)(UIImage *image, NSError *error, NSDictionary *userInfo))completionBlock; 35 | 36 | - (void)hiddenToolBar:(BOOL)hidden animation:(BOOL)animation; 37 | 38 | @end 39 | 40 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/PSImageToolBase.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSImageToolBase.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // Copyright © 2018年 paintingStyle. All rights reserved. 7 | // 8 | 9 | #import "PSImageToolBase.h" 10 | 11 | @implementation PSImageToolBase 12 | 13 | - (instancetype)initWithImageEditor:(_PSImageEditorViewController *)editor 14 | withOption:(NSDictionary *)option { 15 | if(self = [super init]){ 16 | self.editor = editor; 17 | self.option = option; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)initialize {} 23 | 24 | - (void)setup {} 25 | 26 | - (void)cleanup {} 27 | 28 | - (void)resetRect:(CGRect)rect {}; 29 | 30 | - (void)executeWithCompletionBlock:(void(^)(UIImage *image, NSError *error, NSDictionary *userInfo))completionBlock { 31 | completionBlock(self.editor.imageView.image, nil, nil); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Text/PSMovingView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSMovingView.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/18. 6 | // 7 | 8 | #import 9 | #import "PSStickerItem.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface PSMovingView : UIView 14 | 15 | /** active sticker view */ 16 | + (void)setActiveEmoticonView:(PSMovingView * __nullable)view; 17 | 18 | /** 初始化 */ 19 | - (instancetype)initWithItem:(PSStickerItem *)item; 20 | 21 | /** 缩放率 0.2~3.0 */ 22 | - (void)setScale:(CGFloat)scale; 23 | - (void)setScale:(CGFloat)scale rotation:(CGFloat)rotation; 24 | 25 | /** 最小缩放率 默认0.2 */ 26 | @property (nonatomic, assign) CGFloat minScale; 27 | /** 最大缩放率 默认3.0 */ 28 | @property (nonatomic, assign) CGFloat maxScale; 29 | 30 | /** 显示界面的缩放率,例如在UIScrollView上显示,scrollView放大了5倍,movingView的视图控件会显得较大,这个属性是适配当前屏幕的比例调整控件大小 */ 31 | @property (nonatomic, assign) CGFloat screenScale; 32 | 33 | /** Delayed deactivated time */ 34 | //@property (nonatomic, assign) CGFloat deactivatedDelay; 35 | 36 | @property (nonatomic, assign) CGFloat bottomSafeDistance; 37 | 38 | @property (nonatomic, weak) UIImageView *imageView; 39 | 40 | @property (nonatomic, readonly) UIView *view; 41 | @property (nonatomic, strong) PSStickerItem *item; 42 | @property (nonatomic, readonly) CGFloat scale; 43 | @property (nonatomic, readonly) CGFloat rotation; 44 | @property (nonatomic, readonly) BOOL isActive; 45 | 46 | 47 | @property (nonatomic, copy, nullable) BOOL(^tapEnded)(PSMovingView *view, CGPoint point); 48 | @property (nonatomic, copy, nullable) void(^moveCenter)(UIGestureRecognizerState state); 49 | @property (nonatomic, copy, nullable) void(^delete)(void); 50 | 51 | - (CGFloat)transformScaleX; 52 | - (CGFloat)transformScaleY; 53 | 54 | @end 55 | 56 | NS_ASSUME_NONNULL_END 57 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Text/PSMovingView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSMovingView.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/18. 6 | // 7 | 8 | #import "PSMovingView.h" 9 | 10 | #define kTextMargin 22 11 | #define KContentViewBorderWidth 2 12 | 13 | @interface PSMovingContentView : UIView 14 | 15 | @end 16 | 17 | @implementation PSMovingContentView 18 | 19 | - (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 20 | { 21 | gestureRecognizer.delegate = self; 22 | [super addGestureRecognizer:gestureRecognizer]; 23 | } 24 | 25 | #pragma mark - UIGestureRecognizerDelegate 26 | 27 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 28 | { 29 | if (gestureRecognizer.view == self && otherGestureRecognizer.view == self) { 30 | return YES; 31 | } 32 | return NO; 33 | } 34 | 35 | @end 36 | 37 | 38 | 39 | @interface PSMovingView () 40 | { 41 | PSMovingContentView *_contentView; 42 | UIButton *_deleteButton; 43 | UIImageView *_circleView; 44 | 45 | CGFloat _scale; 46 | CGFloat _arg; 47 | 48 | CGPoint _initialPoint; 49 | CGFloat _initialArg; 50 | CGFloat _initialScale; 51 | } 52 | 53 | @property (nonatomic, assign) BOOL isActive; 54 | 55 | @end 56 | 57 | @implementation PSMovingView 58 | 59 | 60 | + (void)setActiveEmoticonView:(PSMovingView *)view 61 | { 62 | static PSMovingView *activeView = nil; 63 | /** 停止取消激活 */ 64 | [activeView cancelDeactivated]; 65 | if(view != activeView){ 66 | [activeView setActive:NO]; 67 | activeView = view; 68 | [activeView setActive:YES]; 69 | 70 | [activeView.superview bringSubviewToFront:activeView]; 71 | 72 | } 73 | [activeView autoDeactivated]; 74 | } 75 | 76 | - (void)dealloc 77 | { 78 | [self cancelDeactivated]; 79 | } 80 | 81 | #pragma mark - 自动取消激活 82 | - (void)cancelDeactivated 83 | { 84 | [PSMovingView cancelPreviousPerformRequestsWithTarget:self]; 85 | } 86 | 87 | - (void)autoDeactivated 88 | { 89 | // [self performSelector:@selector(setActiveEmoticonView:) withObject:nil afterDelay:self.deactivatedDelay]; 90 | } 91 | 92 | - (void)setActiveEmoticonView:(PSMovingView *)view 93 | { 94 | [PSMovingView setActiveEmoticonView:view]; 95 | } 96 | 97 | - (void)layoutSubviews { 98 | [super layoutSubviews]; 99 | } 100 | 101 | - (CGFloat)transformScaleX { 102 | 103 | CGFloat transformScaleX = [[_contentView.layer valueForKeyPath:@"transform.scale.x"] doubleValue]; 104 | return transformScaleX; 105 | } 106 | 107 | - (CGFloat)transformScaleY { 108 | 109 | CGFloat transformScaleY = [[_contentView.layer valueForKeyPath:@"transform.scale.y"] doubleValue]; 110 | return transformScaleY; 111 | } 112 | 113 | - (instancetype)initWithItem:(PSStickerItem *)item 114 | { 115 | UIView *view = item.displayView; 116 | if (view == nil) { 117 | return nil; 118 | } 119 | self = [super initWithFrame:CGRectMake(0, 0, view.frame.size.width+kTextMargin, view.frame.size.height+kTextMargin)]; 120 | if (self){ 121 | //_deactivatedDelay = 4.f; 122 | _view = view; 123 | _item = item; 124 | _contentView = [[PSMovingContentView alloc] initWithFrame:view.bounds]; 125 | _contentView.layer.borderColor = [[UIColor whiteColor] CGColor]; 126 | _contentView.layer.borderWidth = KContentViewBorderWidth; 127 | 128 | _contentView.center = self.center; 129 | [_contentView addSubview:view]; 130 | view.userInteractionEnabled = self.isActive; 131 | view.frame = _contentView.bounds; 132 | [self addSubview:_contentView]; 133 | 134 | _deleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; 135 | _deleteButton.frame = CGRectMake(0, 0, 22, 22); 136 | _deleteButton.center = _contentView.frame.origin; 137 | [_deleteButton addTarget:self action:@selector(pushedDeleteBtn:) forControlEvents:UIControlEventTouchUpInside]; 138 | [_deleteButton setImage:[UIImage ps_imageNamed:@"btn_text_item_close"] forState:UIControlStateNormal]; 139 | [self addSubview:_deleteButton]; 140 | 141 | _circleView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 22, 22)]; 142 | _circleView.center = CGPointMake(CGRectGetMaxX(_contentView.frame), CGRectGetMaxY(_contentView.frame)); 143 | [_circleView setImage:[UIImage ps_imageNamed:@"btn_text_item_zoom"]]; 144 | [self addSubview:_circleView]; 145 | 146 | _scale = 1.f; 147 | _screenScale = 1.f; 148 | _arg = 0; 149 | _minScale = 1.0f; 150 | _maxScale = 3.0f; 151 | 152 | [self initGestures]; 153 | [self setActive:NO]; 154 | [self setScale:_scale rotation:_arg]; 155 | } 156 | return self; 157 | } 158 | 159 | - (void)setItem:(PSStickerItem *)item 160 | { 161 | _item = item; 162 | [_view removeFromSuperview]; 163 | _view = item.displayView; 164 | if (_view) { 165 | [_contentView addSubview:_view]; 166 | _view.userInteractionEnabled = self.isActive; 167 | [self updateFrameWithViewSize:_view.frame.size]; 168 | } else { 169 | [self removeFromSuperview]; 170 | } 171 | } 172 | 173 | /** 更新坐标 */ 174 | - (void)updateFrameWithViewSize:(CGSize)viewSize 175 | { 176 | /** 记录自身中心点 */ 177 | CGPoint center = self.center; 178 | /** 更新自身大小 */ 179 | CGRect frame = self.frame; 180 | frame.size = CGSizeMake(viewSize.width+kTextMargin, viewSize.height+kTextMargin); 181 | self.frame = frame; 182 | self.center = center; 183 | 184 | /** 还原缩放率 */ 185 | _contentView.transform = CGAffineTransformIdentity; 186 | 187 | /** 更新主体大小 */ 188 | CGRect contentFrame = _contentView.frame; 189 | contentFrame.size = viewSize; 190 | _contentView.frame = contentFrame; 191 | _contentView.center = center; 192 | _deleteButton.center = _contentView.frame.origin; 193 | _circleView.center = CGPointMake(CGRectGetMaxX(_contentView.frame), CGRectGetMaxY(_contentView.frame)); 194 | [self updateShadow]; 195 | /** 更新显示视图大小 */ 196 | _view.frame = _contentView.bounds; 197 | 198 | [self setScale:_scale rotation:_arg]; 199 | } 200 | 201 | - (void)updateShadow 202 | { 203 | CGFloat shadowRadius = 5; 204 | 205 | UIBezierPath *path = [UIBezierPath bezierPath]; 206 | path.lineJoinStyle = kCGLineJoinRound; 207 | 208 | UIBezierPath *leftPath = [UIBezierPath bezierPathWithRect:CGRectMake(-shadowRadius/2, 0, shadowRadius, _contentView.bounds.size.height-shadowRadius)]; 209 | UIBezierPath *topPath = [UIBezierPath bezierPathWithRect:CGRectMake(shadowRadius/2, -shadowRadius/2, _contentView.bounds.size.width-shadowRadius, shadowRadius)]; 210 | UIBezierPath *rightPath = [UIBezierPath bezierPathWithRect:CGRectMake(_contentView.bounds.size.width-shadowRadius/2, shadowRadius, shadowRadius, _contentView.bounds.size.height-shadowRadius)]; 211 | UIBezierPath *bottomPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, _contentView.bounds.size.height-shadowRadius/2, _contentView.bounds.size.width-shadowRadius, shadowRadius)]; 212 | 213 | 214 | [path appendPath:topPath]; 215 | [path appendPath:leftPath]; 216 | [path appendPath:rightPath]; 217 | [path appendPath:bottomPath]; 218 | 219 | _contentView.layer.shadowPath = path.CGPath; 220 | } 221 | 222 | - (void)initGestures 223 | { 224 | self.userInteractionEnabled = YES; 225 | _contentView.userInteractionEnabled = YES; 226 | _circleView.userInteractionEnabled = YES; 227 | [_contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(viewDidTap:)]]; 228 | [_contentView addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(viewDidPan:)]]; 229 | [_circleView addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(circleViewDidPan:)]]; 230 | 231 | /** Add two finger pinching and rotating gestures */ 232 | [_contentView addGestureRecognizer:[[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(viewDidPinch:)]]; 233 | [_contentView addGestureRecognizer:[[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(viewDidRotation:)]]; 234 | } 235 | 236 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 237 | { 238 | UIView* view= [super hitTest:point withEvent:event]; 239 | if(view==self){ 240 | view = nil; 241 | } 242 | if (view == nil) { 243 | [PSMovingView setActiveEmoticonView:nil]; 244 | } 245 | return view; 246 | } 247 | 248 | - (void)setActive:(BOOL)active 249 | { 250 | _isActive = active; 251 | _deleteButton.hidden = !active; 252 | _circleView.hidden = !active; 253 | _contentView.layer.borderWidth = (active) ? KContentViewBorderWidth/_scale/self.screenScale : 0; 254 | _contentView.layer.cornerRadius = (active) ? 3/_scale/self.screenScale : 0; 255 | _contentView.layer.shadowColor = (active) ? [UIColor blackColor].CGColor : [UIColor clearColor].CGColor; 256 | 257 | _view.userInteractionEnabled = active; 258 | } 259 | 260 | - (void)setScale:(CGFloat)scale 261 | { 262 | [self setScale:scale rotation:MAXFLOAT]; 263 | } 264 | 265 | - (void)setScale:(CGFloat)scale rotation:(CGFloat)rotation 266 | { 267 | if (rotation != MAXFLOAT) { 268 | _arg = rotation; 269 | } 270 | _scale = MIN(MAX(scale, _minScale), _maxScale); 271 | 272 | self.transform = CGAffineTransformIdentity; 273 | 274 | _contentView.transform = CGAffineTransformMakeScale(_scale, _scale); 275 | 276 | CGRect rct = self.frame; 277 | rct.origin.x += (rct.size.width - (_contentView.frame.size.width + kTextMargin)) / 2; 278 | rct.origin.y += (rct.size.height - (_contentView.frame.size.height + kTextMargin)) / 2; 279 | rct.size.width = _contentView.frame.size.width + kTextMargin; 280 | rct.size.height = _contentView.frame.size.height + kTextMargin; 281 | self.frame = rct; 282 | 283 | _contentView.center = CGPointMake(rct.size.width/2, rct.size.height/2); 284 | _deleteButton.center = CGPointMake(_contentView.frame.origin.x, _contentView.frame.origin.y); 285 | _circleView.center = CGPointMake(CGRectGetMaxX(_contentView.frame), CGRectGetMaxY(_contentView.frame)); 286 | 287 | self.transform = CGAffineTransformMakeRotation(_arg); 288 | 289 | 290 | // 解决UILabel字体模糊, 这里只处理单行文本 291 | UILabel *label = [_view.subviews.lastObject viewWithTag:kLabelTag]; 292 | if (label.numberOfLines != 0) { 293 | label.font = [UIFont systemFontOfSize:18 *_scale]; 294 | label.transform = CGAffineTransformMakeScale(1 / _scale, 1 / _scale); 295 | label.sizeToFit; 296 | } 297 | 298 | if (_isActive) { 299 | _contentView.layer.borderWidth = KContentViewBorderWidth/_scale/self.screenScale; 300 | _contentView.layer.cornerRadius = 3/_scale/self.screenScale; 301 | } 302 | } 303 | 304 | - (void)setScreenScale:(CGFloat)screenScale 305 | { 306 | _screenScale = screenScale; 307 | CGFloat scale = 1.f/screenScale; 308 | _deleteButton.transform = CGAffineTransformMakeScale(scale, scale); 309 | _circleView.transform = CGAffineTransformMakeScale(scale, scale); 310 | _deleteButton.center = _contentView.frame.origin; 311 | _circleView.center = CGPointMake(CGRectGetMaxX(_contentView.frame), CGRectGetMaxY(_contentView.frame)); 312 | } 313 | 314 | - (CGFloat)scale 315 | { 316 | return _scale; 317 | } 318 | 319 | - (CGFloat)rotation 320 | { 321 | return _arg; 322 | } 323 | 324 | #pragma mark - Touch Event 325 | 326 | - (void)pushedDeleteBtn:(id)sender 327 | { 328 | [self cancelDeactivated]; 329 | [self removeFromSuperview]; 330 | if (self.delete) { 331 | self.delete(); 332 | } 333 | } 334 | 335 | - (void)viewDidTap:(UITapGestureRecognizer*)sender 336 | { 337 | 338 | CGPoint point = [sender locationInView:self.superview]; 339 | if (self.tapEnded) { 340 | BOOL ended = self.tapEnded(self, point); 341 | if (!ended) { return; } 342 | } 343 | [[self class] setActiveEmoticonView:self]; 344 | } 345 | 346 | - (void)viewDidPan:(UIPanGestureRecognizer*)sender 347 | { 348 | 349 | CGPoint p = [sender translationInView:self.superview]; 350 | if(sender.state == UIGestureRecognizerStateBegan){ 351 | [[self class] setActiveEmoticonView:self]; 352 | _initialPoint = self.center; 353 | [self cancelDeactivated]; 354 | } 355 | self.center = CGPointMake(_initialPoint.x + p.x, _initialPoint.y + p.y); 356 | 357 | if (sender.state == UIGestureRecognizerStateEnded) { 358 | 359 | // 超出紧贴边界 360 | CGRect rect = [self convertRect:_contentView.frame toView:self.superview]; 361 | CGRect r = self.frame; 362 | 363 | if (rect.origin.x < 0) { 364 | CGFloat distacn = CGRectGetWidth(_circleView.frame) *0.5 +(7 *_scale); 365 | r.origin.x = -(distacn); 366 | } 367 | if (rect.origin.x > CGRectGetWidth(self.superview.frame) -rect.size.width) { 368 | CGFloat distacn = _scale <= 1.0 ? 0:(7 *_scale)-4; 369 | r.origin.x = CGRectGetWidth(self.superview.frame) -rect.size.width + distacn; 370 | } 371 | if (rect.origin.y < PS_SAFEAREA_TOP_DISTANCE) { 372 | CGFloat distacn = CGRectGetWidth(_circleView.frame) +(7 *_scale); 373 | r.origin.y = PS_IPHONE_X_FUTURE_MODELS ? PS_SAFEAREA_TOP_DISTANCE -(7 *_scale) :distacn; 374 | } 375 | if (rect.origin.y > CGRectGetHeight(self.superview.frame)-self.bottomSafeDistance- rect.size.height -(7 *_scale)) { 376 | r.origin.y = CGRectGetHeight(self.superview.frame) -self.bottomSafeDistance -rect.size.height -(7 *_scale); 377 | } 378 | 379 | if (!CGRectEqualToRect(self.frame, r)) { 380 | [UIView animateWithDuration:0.25f animations:^{ 381 | self.frame = r; 382 | }]; 383 | } 384 | [self autoDeactivated]; 385 | } 386 | 387 | if (self.moveCenter) { 388 | self.moveCenter(sender.state); 389 | } 390 | } 391 | 392 | - (void)viewDidPinch:(UIPinchGestureRecognizer*)sender 393 | { 394 | if(sender.state == UIGestureRecognizerStateBegan){ 395 | [[self class] setActiveEmoticonView:self]; 396 | [self cancelDeactivated]; 397 | _initialScale = _scale; 398 | } else if (sender.state == UIGestureRecognizerStateEnded) { 399 | [self autoDeactivated]; 400 | } 401 | [self setScale:(_initialScale * sender.scale)]; 402 | if(sender.state == UIGestureRecognizerStateBegan && sender.state == UIGestureRecognizerStateChanged){ 403 | sender.scale = 1.0; 404 | } 405 | } 406 | 407 | - (void)viewDidRotation:(UIRotationGestureRecognizer*)sender 408 | { 409 | if(sender.state == UIGestureRecognizerStateBegan){ 410 | [[self class] setActiveEmoticonView:self]; 411 | [self cancelDeactivated]; 412 | _initialArg = _arg; 413 | } else if (sender.state == UIGestureRecognizerStateEnded) { 414 | [self autoDeactivated]; 415 | } 416 | _arg = _initialArg + sender.rotation; 417 | [self setScale:_scale]; 418 | if(sender.state == UIGestureRecognizerStateBegan && sender.state == UIGestureRecognizerStateChanged){ 419 | sender.rotation = 0.0; 420 | } 421 | } 422 | 423 | 424 | - (void)circleViewDidPan:(UIPanGestureRecognizer*)sender 425 | { 426 | CGPoint p = [sender translationInView:self.superview]; 427 | 428 | static CGFloat tmpR = 1; 429 | static CGFloat tmpA = 0; 430 | if(sender.state == UIGestureRecognizerStateBegan){ 431 | [self cancelDeactivated]; 432 | _initialPoint = [self.superview convertPoint:_circleView.center fromView:_circleView.superview]; 433 | 434 | CGPoint p = CGPointMake(_initialPoint.x - self.center.x, _initialPoint.y - self.center.y); 435 | tmpR = sqrt(p.x*p.x + p.y*p.y); 436 | tmpA = atan2(p.y, p.x); 437 | 438 | // _initialArg = _arg; 439 | _initialScale = _scale; 440 | } else if (sender.state == UIGestureRecognizerStateEnded) { 441 | [self autoDeactivated]; 442 | } 443 | 444 | p = CGPointMake(_initialPoint.x + p.x - self.center.x, _initialPoint.y + p.y - self.center.y); 445 | CGFloat R = sqrt(p.x*p.x + p.y*p.y); 446 | 447 | // 屏蔽旋转 448 | //CGFloat arg = atan2(p.y, p.x); 449 | //_arg = _initialArg + arg - tmpA; 450 | [self setScale:(_initialScale * R / tmpR)]; 451 | } 452 | 453 | 454 | @end 455 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Text/PSStickerItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSStickerItem.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/18. 6 | // 7 | 8 | #import 9 | 10 | static const NSInteger kLabelTag = 666666; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface PSStickerItem : NSObject 15 | 16 | @property (nonatomic, strong) UIImage *image; 17 | 18 | @property (nonatomic, strong) NSAttributedString *attributedText; 19 | 20 | @property (nonatomic, assign) CGRect imageRect; 21 | 22 | + (instancetype)mainWithAttributedText:(NSAttributedString *)text 23 | imageRect:(CGRect)imageRect; 24 | 25 | @end 26 | 27 | @interface PSStickerItem (View) 28 | 29 | - (UIView * __nullable)displayView; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Text/PSStickerItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSStickerItem.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/18. 6 | // 7 | 8 | #import "PSStickerItem.h" 9 | #import "NSAttributedString+PSCoreText.h" 10 | 11 | @interface PSStickerItem () 12 | 13 | @property (nonatomic, assign) UIEdgeInsets textInsets; // 控制字体与控件边界的间隙 14 | @property (nonatomic, strong) UIImage *textCacheDisplayImage; 15 | 16 | @end 17 | 18 | @implementation PSStickerItem 19 | 20 | + (instancetype)mainWithAttributedText:(NSAttributedString *)text 21 | imageRect:(CGRect)imageRect 22 | { 23 | PSStickerItem *item = [[self alloc] initMain]; 24 | item.attributedText = text; 25 | item.imageRect = imageRect; 26 | return item; 27 | } 28 | 29 | - (instancetype)initMain 30 | { 31 | self = [self init]; 32 | if (self) { 33 | 34 | } 35 | return self; 36 | } 37 | 38 | - (instancetype)init 39 | { 40 | self = [super init]; 41 | if (self) { 42 | _textInsets = UIEdgeInsetsMake(7.f, 7.f, 7.f, 7.f); 43 | } 44 | return self; 45 | } 46 | 47 | @end 48 | 49 | 50 | 51 | @implementation PSStickerItem (View) 52 | 53 | - (UIView * __nullable)displayView 54 | { 55 | 56 | NSRange range = NSMakeRange(0, 1); 57 | NSDictionary *attrs = [self.attributedText attributesAtIndex:0 effectiveRange:&range]; 58 | UIColor *fillColor = attrs[NSBackgroundColorAttributeName]; 59 | UIColor *strokeColor = attrs[NSForegroundColorAttributeName]; 60 | UIFont *font = attrs[NSFontAttributeName]; 61 | 62 | 63 | CGSize textSize = [self sizeWithFont:font maxSize:CGSizeMake(CGFLOAT_MAX, font.pointSize) text:self.attributedText.string]; 64 | CGFloat limit = ceilf(self.imageRect.size.width *0.6); 65 | NSInteger numberOfLines = 1; 66 | 67 | if (textSize.width > limit) { 68 | CGFloat maxH = ceilf([self sizeWithFont:font maxSize:CGSizeMake(limit, CGFLOAT_MAX) text:self.attributedText.string].height); 69 | textSize = CGSizeMake(limit, maxH); 70 | numberOfLines = 0; 71 | } 72 | 73 | UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, textSize.width +self.textInsets.left+self.textInsets.right, 74 | textSize.height +self.textInsets.top+self.textInsets.bottom)]; 75 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(self.textInsets.left, self.textInsets.top, textSize.width, textSize.height)]; 76 | label.attributedText = self.attributedText; 77 | label.numberOfLines = numberOfLines; 78 | label.tag = kLabelTag; 79 | [contentView addSubview:label]; 80 | 81 | return contentView; 82 | } 83 | 84 | 85 | 86 | 87 | - (CGSize)sizeWithFont:(UIFont *)font 88 | maxSize:(CGSize)maxSize 89 | text:(NSString *)text { 90 | 91 | CGSize size = CGSizeZero; 92 | if (text.length > 0) { 93 | CGRect frame = [text boundingRectWithSize:maxSize options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{ NSFontAttributeName: font } context:nil]; 94 | size = CGSizeMake(frame.size.width, frame.size.height + 1); 95 | } 96 | return size; 97 | } 98 | 99 | @end 100 | 101 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ImageTools/Text/PSTexTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSTexTool.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSImageToolBase.h" 9 | @class PSTexTool, PSTextView, PSTexToolItem; 10 | 11 | @interface PSTexTool : PSImageToolBase{ 12 | @public UIImageView *_drawingView; 13 | } 14 | 15 | @property (nonatomic, strong) PSTextView *textView; 16 | @property (nonatomic, copy) void (^updateUndoBlock) (BOOL undo); 17 | 18 | - (UIImage *)textImage; 19 | - (BOOL)canUndo; 20 | - (void)undo; 21 | 22 | @end 23 | 24 | @interface PSTextView : UIView 25 | 26 | @property (nonatomic, strong) UIVisualEffectView *effectView; 27 | @property (nonatomic, strong) UITextView *inputView; 28 | @property (nonatomic, strong) NSDictionary *attrs; /// 预设属性 29 | 30 | 31 | - (void)addTextItemWithText:(NSString *)text 32 | withAttrs:(NSDictionary *)attrs 33 | withPoint:(CGPoint)point; 34 | 35 | @property (nonatomic, copy) void (^dissmissBlock) (NSString *text, NSDictionary *attrs, BOOL done); 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/PSImageEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSImageEditor.h 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/11/15. 6 | // 7 | 8 | #import 9 | 10 | @protocol PSImageEditorDelegate,PSImageEditorDataSource; 11 | 12 | typedef NS_ENUM(NSInteger, PSImageEditorMode) { 13 | 14 | PSImageEditorModeNone =-1, 15 | PSImageEditorModeDraw, 16 | PSImageEditorModeText, 17 | PSImageEditorModeMosaic, 18 | PSImageEditorModeClipping 19 | }; 20 | 21 | @interface PSImageEditor : UIViewController 22 | 23 | @property (nonatomic, weak) id delegate; 24 | @property (nonatomic, weak) id dataSource; 25 | @property (nonatomic, assign) PSImageEditorMode editorMode; 26 | @property (nonatomic, assign) BOOL produceChanges; // 是否使用过该工具,即图片产生了编辑操作 27 | 28 | - (instancetype)initWithImage:(UIImage*)image; 29 | - (instancetype)initWithImage:(UIImage*)image 30 | delegate:(id)delegate 31 | dataSource:(id)dataSource; 32 | - (void)dismiss; 33 | 34 | @end 35 | 36 | @protocol PSImageEditorDelegate 37 | 38 | @optional 39 | - (void)imageEditor:(PSImageEditor *)editor didFinishEdittingWithImage:(UIImage *)image; 40 | - (void)imageEditorDidCancel; 41 | 42 | @end 43 | 44 | @protocol PSImageEditorDataSource 45 | 46 | @optional 47 | - (UIColor *)imageEditorDefaultColor; 48 | - (PSImageEditorMode)imageEditorDefalutEditorMode; 49 | - (CGFloat)imageEditorDrawPathWidth; 50 | - (UIFont *)imageEditorTextFont; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/PSImageEditor.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSImageEditor.m 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/11/15. 6 | // 7 | 8 | #import "PSImageEditor.h" 9 | #import "_PSImageEditorViewController.h" 10 | 11 | @interface PSImageEditor () 12 | 13 | @end 14 | 15 | @implementation PSImageEditor 16 | 17 | - (instancetype)initWithImage:(UIImage *)image { 18 | return [self initWithImage:image delegate:nil dataSource:nil]; 19 | } 20 | 21 | - (instancetype)initWithImage:(UIImage*)image 22 | delegate:(id)delegate 23 | dataSource:(id)dataSource { 24 | 25 | return [[_PSImageEditorViewController alloc] initWithImage:image delegate:delegate dataSource:dataSource]; 26 | } 27 | 28 | - (void)dismiss {} 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/PSImageEditorDefine.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSImageEditorDefine.h 3 | // Pods 4 | // 5 | // Created by paintingStyle on 2018/11/15. 6 | // 7 | 8 | #ifndef PSImageEditorDefine_h 9 | #define PSImageEditorDefine_h 10 | 11 | #define PSBottomToolBarHeight (PS_SAFEAREA_BOTTOM_DISTANCE +44) 12 | #define PSBottomToolDeleteBarHeight 120 13 | 14 | #define PSTextTopToolBarHeight PS_NAV_BAR_H 15 | #define PSDrawColorToolBarHeight 42 16 | #define PSTextColorToolBarHeight 48 17 | #define PSMosaicToolBarHeight 40 18 | 19 | 20 | #define PS_SCREEN_W [UIScreen mainScreen].bounds.size.width 21 | #define PS_SCREEN_H [UIScreen mainScreen].bounds.size.height 22 | /// 手机机型为iPhoneX或以后的系列机型 23 | #define PS_IPHONE_X_FUTURE_MODELS (PS_SCREEN_H >= 812.0f) 24 | 25 | /// 兼容iPhone X,状态栏 tabar高度 26 | #define PS_STATUS_BAR_H [[UIApplication sharedApplication] statusBarFrame].size.height 27 | #define PS_NAV_BAR_H (PS_IPHONE_X_FUTURE_MODELS ? 88.0f: 64.0f) 28 | #define PS_TAB_BAR_H (PS_IPHONE_X_FUTURE_MODELS ? 83.0f: 49.0f) 29 | #define PS_ELASTIC_LAYOUT(x) ceil(((x)/375.0f * [UIScreen mainScreen].bounds.size.width)) 30 | #define PS_SMALL_IPHONE ([UIScreen mainScreen].bounds.size.width <= 320.0f) 31 | #define PS_COMMON_NUM 44.0f 32 | 33 | /// iPhoneX顶部安全距离 34 | #define PS_SAFEAREA_TOP_DISTANCE (PS_IPHONE_X_FUTURE_MODELS ? 24 : 0) 35 | /// iPhoneX底部安全距离 36 | #define PS_SAFEAREA_BOTTOM_DISTANCE (PS_IPHONE_X_FUTURE_MODELS ? 34 : 0) 37 | 38 | #define LAZY_LOAD(object, assignment) (object = object ?: assignment) 39 | #define PSColorFromRGB(rgbValue) PSColorFromRGBA(rgbValue, 1.0) 40 | #define PSColorFromRGBA(hexValue, alphaValue) [UIColor \ 41 | colorWithRed:((float)((hexValue & 0xFF0000) >> 16))/255.0 \ 42 | green:((float)((hexValue & 0x00FF00) >> 8))/255.0 \ 43 | blue:((float)(hexValue & 0x0000FF))/255.0 \ 44 | alpha:alphaValue] 45 | 46 | #ifndef weakify 47 | #if __has_feature(objc_arc) 48 | #define weakify(x) autoreleasepool{} __weak __typeof__(x) __weak_##x##__ = x; 49 | #else 50 | #define weakify(x) autoreleasepool{} __block __typeof__(x) __block_##x##__ = x; 51 | #endif 52 | #endif 53 | 54 | #ifndef strongify 55 | #if __has_feature(objc_arc) 56 | #define strongify(x) try{} @finally{} __typeof__(x)x = __weak_##x##__; 57 | #else 58 | #define strongify(x) try{} @finally{} __typeof__(x)x = __block_##x##__; 59 | #endif 60 | #endif 61 | 62 | 63 | #define LOG_FRAME(f) NSLog(@"%@",NSStringFromCGRect(f)); 64 | #define DEBUG_VIEW(v)\ 65 | if ([v isKindOfClass:[UIView class]]) {\ 66 | [v.subviews enumerateObjectsUsingBlock:\ 67 | ^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\ 68 | if (![obj isKindOfClass:[UIView class]]) {\ 69 | return;\ 70 | }\ 71 | int r = arc4random() % 255;\ 72 | int g = arc4random() % 255;\ 73 | int b = arc4random() % 255;\ 74 | obj.backgroundColor = [UIColor colorWithRed:(r/255.0)\ 75 | green:(g/255.0)\ 76 | blue:(b/255.0)\ 77 | alpha:1.0];\ 78 | }];\ 79 | }\ 80 | 81 | #endif /* PSImageEditorDefine_h */ 82 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSBottomToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSBottomToolBar.h 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/11/17. 6 | // 7 | 8 | #import "PSEditorToolBar.h" 9 | @class PSBottomToolBar; 10 | 11 | typedef NS_ENUM(NSUInteger, PSBottomToolType) { 12 | 13 | PSBottomToolTypeEditor =0, /**< 编辑样式 */ 14 | PSBottomToolTypeDelete, /**< 拖动删除标样式 */ 15 | }; 16 | 17 | typedef NS_ENUM(NSInteger, PSBottomToolBarEvent) { 18 | 19 | PSBottomToolBarEventDraw = 0, 20 | PSBottomToolBarEventText, 21 | PSBottomToolBarEventMosaic, 22 | PSBottomToolBarEventClipping, 23 | PSBottomToolBarEventUndo, 24 | PSBottomToolBarEventDone, 25 | }; 26 | 27 | @protocol PSBottomToolBarDelegate 28 | 29 | - (void)bottomToolBar:(PSBottomToolBar *)toolBar 30 | didClickEvent:(PSBottomToolBarEvent)event; 31 | 32 | @end 33 | 34 | @interface PSBottomToolBar : PSEditorToolBar 35 | 36 | @property (nonatomic, assign, getter=isEditor) BOOL editor; 37 | 38 | /// 用于参照布局 39 | @property (nonatomic, strong) UIView *editorItemsView; 40 | 41 | /// 是否可以撤销 42 | @property (nonatomic, assign) BOOL canUndo; 43 | 44 | @property (nonatomic, weak) id delegate; 45 | 46 | - (instancetype)initWithType:(PSBottomToolType)type; 47 | 48 | - (void)selectIndex:(NSInteger)index; 49 | - (void)reset; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSBottomToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSBottomToolBar.m 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/11/17. 6 | // 7 | 8 | #import "PSBottomToolBar.h" 9 | #import "PSExpandClickAreaButton.h" 10 | 11 | @interface PSBottomToolBar () 12 | 13 | @property (nonatomic, strong) UIImageView *maskImageView; 14 | 15 | @property (nonatomic, strong) PSExpandClickAreaButton *drawButton; 16 | @property (nonatomic, strong) PSExpandClickAreaButton *textButton; 17 | @property (nonatomic, strong) PSExpandClickAreaButton *mosaicButton; 18 | @property (nonatomic, strong) PSExpandClickAreaButton *clippingButton; 19 | 20 | @property (nonatomic, strong) PSExpandClickAreaButton *undoButton; 21 | @property (nonatomic, strong) PSExpandClickAreaButton *doneButton; 22 | 23 | @property (nonatomic, assign) PSBottomToolType type; 24 | 25 | @end 26 | 27 | 28 | @implementation PSBottomToolBar 29 | 30 | - (void)buttonDidClickSender:(UIButton *)btn { 31 | 32 | for (UIButton *button in self.editorItemsView.subviews) { 33 | if ([button isKindOfClass:[UIButton class]] && (button !=btn)) 34 | { button.selected = NO; } 35 | } 36 | 37 | PSBottomToolBarEvent event; 38 | if (btn == self.drawButton) { 39 | event = PSBottomToolBarEventDraw; 40 | }else if (btn == self.textButton) { 41 | event = PSBottomToolBarEventText; 42 | }else if (btn == self.mosaicButton) { 43 | event = PSBottomToolBarEventMosaic; 44 | }else if (btn == self.clippingButton) { 45 | event = PSBottomToolBarEventClipping; 46 | } 47 | btn.selected = !btn.isSelected; 48 | self.editor = btn.selected; 49 | 50 | if (self.delegate && [self.delegate respondsToSelector: 51 | @selector(bottomToolBar:didClickEvent:)]) { 52 | [self.delegate bottomToolBar:self didClickEvent:event]; 53 | } 54 | } 55 | 56 | - (void)undoButtonDidClick { 57 | 58 | if (self.delegate && [self.delegate respondsToSelector: 59 | @selector(bottomToolBar:didClickEvent:)]) { 60 | [self.delegate bottomToolBar:self didClickEvent:PSBottomToolBarEventUndo]; 61 | } 62 | } 63 | 64 | - (void)doneButtonDidClick { 65 | 66 | if (self.delegate && [self.delegate respondsToSelector: 67 | @selector(bottomToolBar:didClickEvent:)]) { 68 | [self.delegate bottomToolBar:self didClickEvent:PSBottomToolBarEventDone]; 69 | } 70 | } 71 | 72 | - (void)selectIndex:(NSInteger)index { 73 | 74 | UIButton *sender = nil; 75 | switch (index) { 76 | case 0: 77 | sender = self.drawButton; 78 | break; 79 | case 1: 80 | sender = self.textButton; 81 | break; 82 | case 2: 83 | sender = self.mosaicButton; 84 | break; 85 | case 3: 86 | sender = self.clippingButton; 87 | break; 88 | default: 89 | break; 90 | } 91 | 92 | if (sender) { 93 | [self buttonDidClickSender:sender]; 94 | } 95 | } 96 | 97 | - (void)reset { 98 | 99 | self.drawButton.selected = NO; 100 | self.textButton.selected = NO; 101 | self.mosaicButton.selected = NO; 102 | self.clippingButton.selected = NO; 103 | } 104 | 105 | 106 | - (instancetype)initWithType:(PSBottomToolType)type { 107 | 108 | if (self = [super init]) { 109 | self.type = type; 110 | switch (type) { 111 | case PSBottomToolTypeEditor: 112 | [self configEditorUI]; 113 | break; 114 | default: 115 | break; 116 | } 117 | } 118 | return self; 119 | } 120 | 121 | - (void)configEditorUI { 122 | 123 | [self addSubview:self.editorItemsView]; 124 | [self.editorItemsView addSubview:self.drawButton]; 125 | [self.editorItemsView addSubview:self.textButton]; 126 | [self.editorItemsView addSubview:self.mosaicButton]; 127 | [self.editorItemsView addSubview:self.clippingButton]; 128 | [self.editorItemsView addSubview:self.undoButton]; 129 | [self.editorItemsView addSubview:self.doneButton]; 130 | 131 | [self.editorItemsView mas_makeConstraints:^(MASConstraintMaker *make) { 132 | make.left.right.bottom.equalTo(self); 133 | make.height.equalTo(@(44+PS_SAFEAREA_BOTTOM_DISTANCE)); 134 | }]; 135 | 136 | CGFloat leadItemMargin = PS_ELASTIC_LAYOUT(24); 137 | CGFloat leadItemWH = PS_ELASTIC_LAYOUT(16); 138 | CGFloat editorItemWH = leadItemWH; 139 | CGFloat editorItemMargin = PS_ELASTIC_LAYOUT(34); 140 | CGFloat editorItemLeadMargin = floor((PS_SCREEN_W - (4 *editorItemWH) -(3 *editorItemMargin)) *0.5); 141 | 142 | [self.undoButton mas_makeConstraints:^(MASConstraintMaker *make) { 143 | make.left.equalTo(@(leadItemMargin)); 144 | make.size.equalTo(@(leadItemWH)); 145 | make.top.equalTo(@14); 146 | }]; 147 | 148 | [self.doneButton mas_makeConstraints:^(MASConstraintMaker *make) { 149 | make.right.equalTo(@(-leadItemMargin)); 150 | make.size.equalTo(@(leadItemWH)); 151 | make.top.equalTo(@14); 152 | }]; 153 | 154 | NSMutableArray *editorItems = [NSMutableArray array]; 155 | [editorItems addObject:self.drawButton]; 156 | [editorItems addObject:self.textButton]; 157 | [editorItems addObject:self.mosaicButton]; 158 | [editorItems addObject:self.clippingButton]; 159 | 160 | [editorItems mas_distributeViewsAlongAxis:MASAxisTypeHorizontal 161 | withFixedItemLength:editorItemWH 162 | leadSpacing:editorItemLeadMargin 163 | tailSpacing:editorItemLeadMargin]; 164 | 165 | [editorItems mas_makeConstraints:^(MASConstraintMaker *make) { 166 | make.centerY.equalTo(self.undoButton); 167 | make.height.equalTo(@(editorItemWH)); 168 | }]; 169 | } 170 | 171 | - (void)setCanUndo:(BOOL)canUndo { 172 | 173 | _canUndo = canUndo; 174 | self.undoButton.enabled = canUndo; 175 | } 176 | 177 | - (PSExpandClickAreaButton *)clippingButton { 178 | 179 | return LAZY_LOAD(_clippingButton, ({ 180 | 181 | _clippingButton = [[PSExpandClickAreaButton alloc] init]; 182 | [_clippingButton setImage:[UIImage ps_imageNamed:@"btn_clipping_normal"] 183 | forState:UIControlStateNormal]; 184 | [_clippingButton addTarget:self action:@selector(buttonDidClickSender:) forControlEvents:UIControlEventTouchUpInside]; 185 | _clippingButton; 186 | })); 187 | } 188 | 189 | - (PSExpandClickAreaButton *)mosaicButton { 190 | 191 | return LAZY_LOAD(_mosaicButton, ({ 192 | 193 | _mosaicButton = [[PSExpandClickAreaButton alloc] init]; 194 | [_mosaicButton setImage:[UIImage ps_imageNamed:@"btn_mosaic_normal"] 195 | forState:UIControlStateNormal]; 196 | [_mosaicButton setImage:[UIImage ps_imageNamed:@"btn_mosaic_selected"] 197 | forState:UIControlStateSelected]; 198 | [_mosaicButton addTarget:self action:@selector(buttonDidClickSender:) forControlEvents:UIControlEventTouchUpInside]; 199 | _mosaicButton; 200 | })); 201 | } 202 | 203 | - (PSExpandClickAreaButton *)textButton { 204 | 205 | return LAZY_LOAD(_textButton, ({ 206 | 207 | _textButton = [[PSExpandClickAreaButton alloc] init]; 208 | [_textButton setImage:[UIImage ps_imageNamed:@"btn_text_normal"] 209 | forState:UIControlStateNormal]; 210 | [_textButton setImage:[UIImage ps_imageNamed:@"btn_text_selected"] 211 | forState:UIControlStateSelected]; 212 | [_textButton addTarget:self action:@selector(buttonDidClickSender:) forControlEvents:UIControlEventTouchUpInside]; 213 | _textButton; 214 | })); 215 | } 216 | 217 | - (PSExpandClickAreaButton *)drawButton { 218 | 219 | return LAZY_LOAD(_drawButton, ({ 220 | 221 | _drawButton = [[PSExpandClickAreaButton alloc] init]; 222 | [_drawButton setImage:[UIImage ps_imageNamed:@"btn_brush_normal"] 223 | forState:UIControlStateNormal]; 224 | [_drawButton setImage:[UIImage ps_imageNamed:@"btn_brush_selected"] 225 | forState:UIControlStateSelected]; 226 | [_drawButton addTarget:self action:@selector(buttonDidClickSender:) forControlEvents:UIControlEventTouchUpInside]; 227 | _drawButton; 228 | })); 229 | } 230 | 231 | - (PSExpandClickAreaButton *)undoButton { 232 | 233 | return LAZY_LOAD(_undoButton, ({ 234 | 235 | _undoButton = [PSExpandClickAreaButton buttonWithType:UIButtonTypeCustom]; 236 | [_undoButton setImage:[UIImage ps_imageNamed:@"btn_revocation_normal"] 237 | forState:UIControlStateNormal]; 238 | _undoButton.imageView.contentMode = UIViewContentModeScaleAspectFill; 239 | _undoButton.enabled = NO; 240 | [_undoButton addTarget:self action:@selector(undoButtonDidClick) forControlEvents:UIControlEventTouchUpInside]; 241 | _undoButton; 242 | })); 243 | } 244 | 245 | - (PSExpandClickAreaButton *)doneButton { 246 | 247 | return LAZY_LOAD(_doneButton, ({ 248 | 249 | _doneButton = [PSExpandClickAreaButton buttonWithType:UIButtonTypeCustom]; 250 | [_doneButton setImage:[UIImage ps_imageNamed:@"btn_done"] 251 | forState:UIControlStateNormal]; 252 | _doneButton.imageView.contentMode = UIViewContentModeScaleAspectFill; 253 | [_doneButton addTarget:self action:@selector(doneButtonDidClick) forControlEvents:UIControlEventTouchUpInside]; 254 | _doneButton; 255 | })); 256 | } 257 | 258 | - (UIView *)editorItemsView { 259 | 260 | return LAZY_LOAD(_editorItemsView, ({ 261 | 262 | _editorItemsView = [[UIView alloc] init]; 263 | _editorItemsView.backgroundColor = [UIColor blackColor]; 264 | _editorItemsView; 265 | })); 266 | } 267 | 268 | - (UIImageView *)maskImageView { 269 | 270 | return LAZY_LOAD(_maskImageView, ({ 271 | 272 | _maskImageView = [[UIImageView alloc] initWithImage:[UIImage ps_imageNamed:@"icon_mask_bottom"]]; 273 | _maskImageView; 274 | })); 275 | } 276 | 277 | @end 278 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSColorToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSColorToolBar.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSEditorToolBar.h" 9 | @class PSColorToolBar; 10 | 11 | typedef NS_ENUM(NSInteger, PSColorToolBarType) { 12 | 13 | PSColorToolBarTypeColor =0, 14 | PSColorToolBarTypeText 15 | }; 16 | 17 | typedef NS_ENUM(NSUInteger, PSColorToolBarEvent) { 18 | 19 | PSColorToolBarEventSelectColor = 0, 20 | PSColorToolBarEventChangeBgColor 21 | }; 22 | 23 | @protocol PSColorToolBarDelegate 24 | 25 | @optional 26 | 27 | - (void)colorToolBar:(PSColorToolBar *)toolBar event:(PSColorToolBarEvent)event; 28 | 29 | @end 30 | 31 | @interface PSColorToolBar : PSEditorToolBar 32 | 33 | - (instancetype)initWithType:(PSColorToolBarType)type; 34 | 35 | // 当前颜色 36 | @property (nonatomic, strong) UIColor *currentColor; 37 | 38 | /// 是否可以改变文字颜色 39 | @property (nonatomic, assign, getter=isChangeBgColor) BOOL changeBgColor; 40 | 41 | @property (nonatomic, weak) id delegate; 42 | 43 | - (BOOL)isWhiteColor; 44 | - (void)setChangeBgColorButtonSelected:(BOOL)selected; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSColorToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSColorToolBar.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSColorToolBar.h" 9 | #import "PSColorFullButton.h" 10 | #import "PSExpandClickAreaButton.h" 11 | 12 | #define kItemLength 24//PS_SMALL_IPHONE ? 34:44 13 | #define kItemRadius 4.5 14 | 15 | @interface PSColorToolBar () 16 | 17 | @property (nonatomic, strong) UIImageView *maskImageView; 18 | 19 | @property (nonatomic, strong) PSColorFullButton *redButton; 20 | @property (nonatomic, strong) PSColorFullButton *blackButton; 21 | @property (nonatomic, strong) PSColorFullButton *whiteButton; 22 | @property (nonatomic, strong) PSColorFullButton *yellowButton; 23 | @property (nonatomic, strong) PSColorFullButton *greenButton; 24 | @property (nonatomic, strong) PSColorFullButton *lightBlueButton; 25 | @property (nonatomic, strong) PSColorFullButton *blueButton; 26 | 27 | @property (nonatomic, strong) UIView *colorFullButtonViews; 28 | @property (nonatomic, strong) UIButton *undoButton; 29 | @property (nonatomic, strong) UIView *bottomLineView; 30 | 31 | @property (nonatomic, strong) UIButton *changeBgColorButton; 32 | 33 | @end 34 | 35 | @implementation PSColorToolBar 36 | 37 | - (instancetype)initWithType:(PSColorToolBarType)type { 38 | 39 | if (self = [super init]) { 40 | switch (type) { 41 | case PSColorToolBarTypeColor: 42 | [self configDrawUI]; 43 | break; 44 | case PSColorToolBarTypeText: 45 | [self configTextUI]; 46 | break; 47 | } 48 | } 49 | return self; 50 | } 51 | 52 | - (void)configDrawUI { 53 | 54 | [self addSubview:self.maskImageView]; 55 | 56 | _colorFullButtonViews = [[UIView alloc] init]; 57 | [self addSubview:_colorFullButtonViews]; 58 | 59 | [_colorFullButtonViews addSubview:self.redButton]; 60 | [_colorFullButtonViews addSubview:self.blackButton]; 61 | [_colorFullButtonViews addSubview:self.whiteButton]; 62 | [_colorFullButtonViews addSubview:self.yellowButton]; 63 | [_colorFullButtonViews addSubview:self.greenButton]; 64 | [_colorFullButtonViews addSubview:self.lightBlueButton]; 65 | [_colorFullButtonViews addSubview:self.blueButton]; 66 | 67 | // 使用手势识别,关闭自带交互 68 | for (PSColorFullButton *button in _colorFullButtonViews.subviews) { 69 | button.userInteractionEnabled = NO; 70 | } 71 | 72 | [self.maskImageView mas_makeConstraints:^(MASConstraintMaker *make) { 73 | make.edges.equalTo(self); 74 | }]; 75 | 76 | [_colorFullButtonViews mas_makeConstraints:^(MASConstraintMaker *make) { 77 | CGFloat offest = PS_ELASTIC_LAYOUT(70); 78 | make.left.equalTo(@(offest)); 79 | make.right.equalTo(@(-offest)); 80 | make.top.equalTo(self); 81 | make.height.equalTo(@24); 82 | }]; 83 | 84 | [_colorFullButtonViews.subviews mas_distributeViewsAlongAxis:MASAxisTypeHorizontal 85 | withFixedItemLength:kItemLength 86 | leadSpacing:0 87 | tailSpacing:0]; 88 | 89 | [_colorFullButtonViews.subviews mas_makeConstraints:^(MASConstraintMaker *make) { 90 | make.centerY.equalTo(_colorFullButtonViews); 91 | make.height.equalTo(@(kItemLength)); 92 | }]; 93 | 94 | // 设置默认选中颜色 95 | [self colorFullButtonDidClick:self.redButton]; 96 | } 97 | 98 | - (void)configTextUI { 99 | 100 | [self addSubview:self.maskImageView]; 101 | 102 | _changeBgColorButton = [PSExpandClickAreaButton buttonWithType:UIButtonTypeCustom]; 103 | [_changeBgColorButton setImage:[UIImage ps_imageNamed:@"btn_changeTextBgColor_normal"] 104 | forState:UIControlStateNormal]; 105 | [_changeBgColorButton setImage:[UIImage ps_imageNamed:@"btn_changeTextBgColor_selected"] 106 | forState:UIControlStateSelected]; 107 | [_changeBgColorButton addTarget:self action:@selector(changeBgColorButtonDidClick) forControlEvents:UIControlEventTouchUpInside]; 108 | [self addSubview:_changeBgColorButton]; 109 | 110 | _colorFullButtonViews = [[UIView alloc] init]; 111 | [self addSubview:_colorFullButtonViews]; 112 | 113 | [_colorFullButtonViews addSubview:self.whiteButton]; 114 | [_colorFullButtonViews addSubview:self.blackButton]; 115 | [_colorFullButtonViews addSubview:self.redButton]; 116 | [_colorFullButtonViews addSubview:self.yellowButton]; 117 | [_colorFullButtonViews addSubview:self.greenButton]; 118 | [_colorFullButtonViews addSubview:self.lightBlueButton]; 119 | [_colorFullButtonViews addSubview:self.blueButton]; 120 | // 使用手势识别,关闭自带交互 121 | for (PSColorFullButton *button in _colorFullButtonViews.subviews) { 122 | button.userInteractionEnabled = NO; 123 | } 124 | 125 | [self.maskImageView mas_makeConstraints:^(MASConstraintMaker *make) { 126 | make.edges.equalTo(self); 127 | }]; 128 | 129 | CGFloat margin = PS_SMALL_IPHONE ? 0:14; 130 | [_changeBgColorButton mas_makeConstraints:^(MASConstraintMaker *make) { 131 | 132 | make.centerY.equalTo(self); 133 | make.left.equalTo(@(margin)); 134 | make.width.equalTo(@(kItemLength+12)); // 与选中按钮大小一致 135 | make.height.equalTo(@(kItemLength+12)); 136 | }]; 137 | 138 | [_colorFullButtonViews mas_makeConstraints:^(MASConstraintMaker *make) { 139 | 140 | CGFloat offest = PS_SMALL_IPHONE ? 10:16; 141 | make.centerY.equalTo(self); 142 | make.left.equalTo(_changeBgColorButton.mas_right).offset(offest); 143 | make.height.equalTo(@(kItemLength)); 144 | make.right.equalTo(@(-(margin))); 145 | }]; 146 | 147 | [_colorFullButtonViews.subviews mas_distributeViewsAlongAxis:MASAxisTypeHorizontal 148 | withFixedItemLength:kItemLength 149 | leadSpacing:0 150 | tailSpacing:0]; 151 | 152 | [_colorFullButtonViews.subviews mas_makeConstraints:^(MASConstraintMaker *make) { 153 | make.centerY.equalTo(_colorFullButtonViews); 154 | make.height.equalTo(@(kItemLength)); 155 | }]; 156 | 157 | // 设置默认选中颜色 158 | [self colorFullButtonDidClick:self.redButton]; 159 | } 160 | 161 | - (BOOL)isWhiteColor { 162 | 163 | return CGColorEqualToColor(self.currentColor.CGColor, 164 | self.whiteButton.color.CGColor); 165 | } 166 | 167 | - (void)setChangeBgColorButtonSelected:(BOOL)selected { 168 | 169 | _changeBgColorButton.selected = selected; 170 | } 171 | 172 | - (void)setCurrentColor:(UIColor *)currentColor { 173 | 174 | _currentColor = currentColor; 175 | for (PSColorFullButton *button in self.colorFullButtonViews.subviews) { 176 | button.isUse = CGColorEqualToColor(button.color.CGColor, currentColor.CGColor); 177 | } 178 | } 179 | 180 | - (void)changeBgColorButtonDidClick { 181 | 182 | self.changeBgColorButton.selected = !self.changeBgColorButton.selected; 183 | self.changeBgColor = self.changeBgColorButton.isSelected; 184 | 185 | if (self.delegate && [self.delegate respondsToSelector: 186 | @selector(colorToolBar:event:)]) { 187 | [self.delegate colorToolBar:self event:PSColorToolBarEventChangeBgColor]; 188 | } 189 | } 190 | 191 | - (void)colorFullButtonDidClick:(PSColorFullButton *)sender { 192 | 193 | for (PSColorFullButton *button in self.colorFullButtonViews.subviews) { 194 | if (button == sender) { 195 | button.isUse = YES; 196 | self.currentColor = sender.color; 197 | } else { 198 | button.isUse = NO; 199 | } 200 | } 201 | 202 | if (self.delegate && [self.delegate respondsToSelector: 203 | @selector(colorToolBar:event:)]) { 204 | [self.delegate colorToolBar:self event:PSColorToolBarEventSelectColor]; 205 | } 206 | } 207 | 208 | #pragma mark - 手动手势识别,兼容滑动选择 209 | 210 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 211 | 212 | UITouch *touch = [touches anyObject]; 213 | CGPoint touchPoint = [touch locationInView:self]; 214 | for (PSColorFullButton *button in self.colorFullButtonViews.subviews) { 215 | CGRect rect = [button convertRect:button.bounds toView:self]; 216 | if (CGRectContainsPoint(rect, touchPoint) && button.isUse == NO) { 217 | [self colorFullButtonDidClick:button]; 218 | } 219 | } 220 | } 221 | 222 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 223 | 224 | UITouch *touch = [touches anyObject]; 225 | CGPoint touchPoint = [touch locationInView:self]; 226 | for (PSColorFullButton *button in self.colorFullButtonViews.subviews) { 227 | CGRect rect = [button convertRect:button.bounds toView:self]; 228 | if (CGRectContainsPoint(rect, touchPoint) && button.isUse == NO) { 229 | [self colorFullButtonDidClick:button]; 230 | } 231 | } 232 | } 233 | 234 | #pragma mark - Getter/Setter 235 | 236 | - (PSColorFullButton *)blueButton { 237 | 238 | return LAZY_LOAD(_blueButton, ({ 239 | 240 | _blueButton = [[PSColorFullButton alloc] initWithFrame:CGRectMake(0, 0, kItemLength, kItemLength) radius:kItemRadius color:PSColorFromRGB(0x8c06ff)]; 241 | _blueButton; 242 | })); 243 | } 244 | 245 | - (PSColorFullButton *)lightBlueButton { 246 | 247 | return LAZY_LOAD(_lightBlueButton, ({ 248 | 249 | _lightBlueButton = [[PSColorFullButton alloc] initWithFrame:CGRectMake(0, 0, kItemLength, kItemLength) radius:kItemRadius color:PSColorFromRGB(0x199bff)]; 250 | _lightBlueButton; 251 | })); 252 | } 253 | 254 | - (PSColorFullButton *)greenButton { 255 | 256 | return LAZY_LOAD(_greenButton, ({ 257 | 258 | _greenButton = [[PSColorFullButton alloc] initWithFrame:CGRectMake(0, 0, kItemLength, kItemLength) radius:kItemRadius color:PSColorFromRGB(0x14e213)]; 259 | _greenButton; 260 | })); 261 | } 262 | 263 | - (PSColorFullButton *)yellowButton { 264 | 265 | return LAZY_LOAD(_yellowButton, ({ 266 | 267 | _yellowButton = [[PSColorFullButton alloc] initWithFrame:CGRectMake(0, 0, kItemLength, kItemLength) radius:kItemRadius color:PSColorFromRGB(0xfbf60f)]; 268 | _yellowButton; 269 | })); 270 | } 271 | 272 | - (PSColorFullButton *)whiteButton { 273 | 274 | return LAZY_LOAD(_whiteButton, ({ 275 | 276 | _whiteButton = [[PSColorFullButton alloc] initWithFrame:CGRectMake(0, 0, kItemLength, kItemLength) radius:kItemRadius color:PSColorFromRGB(0xf9f9f9)]; 277 | _whiteButton; 278 | })); 279 | } 280 | 281 | - (PSColorFullButton *)blackButton { 282 | 283 | return LAZY_LOAD(_blackButton, ({ 284 | 285 | _blackButton = [[PSColorFullButton alloc] initWithFrame:CGRectMake(0, 0, kItemLength, kItemLength) radius:kItemRadius color:PSColorFromRGB(0x26252a)]; 286 | _blackButton; 287 | })); 288 | } 289 | 290 | - (PSColorFullButton *)redButton { 291 | 292 | return LAZY_LOAD(_redButton, ({ 293 | 294 | _redButton = [[PSColorFullButton alloc] initWithFrame:CGRectMake(0, 0, kItemLength, kItemLength) radius:kItemRadius color:PSColorFromRGB(0xff1d12)]; 295 | _redButton; 296 | })); 297 | } 298 | 299 | - (UIImageView *)maskImageView { 300 | 301 | return LAZY_LOAD(_maskImageView, ({ 302 | 303 | _maskImageView = [[UIImageView alloc] initWithImage:[UIImage ps_imageNamed:@"icon_mask_bottom"]]; 304 | _maskImageView; 305 | })); 306 | } 307 | 308 | @end 309 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSEditorToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSEditorToolBar.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import 9 | 10 | static const CGFloat kEditorToolBarAnimationDuration = 0.25; 11 | 12 | @interface PSEditorToolBar : UIView 13 | 14 | @property (nonatomic, assign, getter=isWilShow) BOOL wilShow; 15 | @property (nonatomic, assign, getter=isShow) BOOL show; 16 | 17 | - (void)setToolBarShow:(BOOL)show animation:(BOOL)animation; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSEditorToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSEditorToolBar.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSEditorToolBar.h" 9 | 10 | @interface PSEditorToolBar () 11 | 12 | @end 13 | 14 | @implementation PSEditorToolBar 15 | 16 | - (void)setToolBarShow:(BOOL)show animation:(BOOL)animation { 17 | 18 | self.wilShow = show; 19 | [UIView animateWithDuration:(animation ? kEditorToolBarAnimationDuration:0) 20 | animations:^{ 21 | if (show) { 22 | self.alpha = 1.0; 23 | }else{ 24 | self.alpha = 0.0; 25 | } 26 | } completion:^(BOOL finished) { 27 | self.show = show; 28 | }]; 29 | } 30 | 31 | @end 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSMosaicToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSMosaicToolBar.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSEditorToolBar.h" 9 | 10 | typedef NS_ENUM(NSInteger, PSMosaicType) { 11 | 12 | PSMosaicTypeRectangular = 0, /// !< 矩形马赛克 13 | PSMosaicTypeGrindArenaceous /// !< 磨砂马赛克 14 | }; 15 | 16 | typedef NS_ENUM(NSInteger, PSMosaicToolBarEvent) { 17 | 18 | PSMosaicToolBarEventRectangular = 0, 19 | PSMosaicToolBarEventGrindArenaceous, 20 | }; 21 | 22 | @protocol PSMosaicToolBarDelegate 23 | 24 | @optional 25 | 26 | - (void)mosaicToolBarType:(PSMosaicType)type event:(PSMosaicToolBarEvent)event; 27 | 28 | @end 29 | 30 | @interface PSMosaicToolBar : PSEditorToolBar 31 | 32 | @property (nonatomic, assign) PSMosaicType mosaicType; 33 | @property (nonatomic, weak) id delegate; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSMosaicToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSMosaicToolBar.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSMosaicToolBar.h" 9 | #import "PSExpandClickAreaButton.h" 10 | 11 | @interface PSMosaicToolBar () 12 | 13 | @property (nonatomic, strong) UIImageView *maskImageView; 14 | 15 | /// 矩形马赛克 16 | @property (nonatomic, strong) PSExpandClickAreaButton *rectangularMosaicStyleButton; 17 | /// 磨砂马赛克 18 | @property (nonatomic, strong) PSExpandClickAreaButton *grindArenaceousMosaicStyleButton; 19 | 20 | @end 21 | 22 | @implementation PSMosaicToolBar 23 | 24 | 25 | - (instancetype)init { 26 | 27 | if (self = [super init]) { 28 | 29 | [self addSubview:self.maskImageView]; 30 | [self.maskImageView mas_makeConstraints:^(MASConstraintMaker *make) { 31 | make.edges.equalTo(self); 32 | }]; 33 | 34 | _rectangularMosaicStyleButton = [PSExpandClickAreaButton buttonWithType:UIButtonTypeCustom]; 35 | [_rectangularMosaicStyleButton setImage:[UIImage ps_imageNamed:@"btn_mosaic_rectangular_normal"] 36 | forState:UIControlStateNormal]; 37 | [_rectangularMosaicStyleButton setImage:[UIImage ps_imageNamed:@"btn_mosaic_rectangular_selected"] 38 | forState:UIControlStateSelected]; 39 | [_rectangularMosaicStyleButton addTarget:self action:@selector(buttonDidClick:) 40 | forControlEvents:UIControlEventTouchUpInside]; 41 | [self addSubview:_rectangularMosaicStyleButton]; 42 | 43 | _grindArenaceousMosaicStyleButton = [PSExpandClickAreaButton buttonWithType:UIButtonTypeCustom]; 44 | [_grindArenaceousMosaicStyleButton setImage:[UIImage ps_imageNamed:@"btn_mosaic_grindArenaceous_normal"] 45 | forState:UIControlStateNormal]; 46 | [_grindArenaceousMosaicStyleButton setImage:[UIImage ps_imageNamed:@"btn_mosaic_grindArenaceous_selected"] 47 | forState:UIControlStateSelected]; 48 | [_grindArenaceousMosaicStyleButton addTarget:self action:@selector(buttonDidClick:) 49 | forControlEvents:UIControlEventTouchUpInside]; 50 | [self addSubview:_grindArenaceousMosaicStyleButton]; 51 | 52 | 53 | NSArray *views = @[_rectangularMosaicStyleButton, 54 | _grindArenaceousMosaicStyleButton]; 55 | 56 | [views mas_distributeViewsAlongAxis:MASAxisTypeHorizontal 57 | withFixedSpacing:28 58 | leadSpacing:48 59 | tailSpacing:48]; 60 | [views mas_makeConstraints:^(MASConstraintMaker *make) { 61 | make.bottom.equalTo(@(-10)); 62 | make.height.equalTo(@30); 63 | }]; 64 | 65 | 66 | // 默认选中 67 | _rectangularMosaicStyleButton.selected = YES; 68 | [self buttonDidClick:_rectangularMosaicStyleButton]; 69 | } 70 | return self; 71 | } 72 | 73 | - (void)buttonDidClick:(UIButton *)sender { 74 | 75 | PSMosaicToolBarEvent event; 76 | if (sender == self.rectangularMosaicStyleButton) { 77 | event = PSMosaicToolBarEventRectangular; 78 | self.mosaicType = PSMosaicTypeRectangular; 79 | self.rectangularMosaicStyleButton.selected = YES; 80 | self.grindArenaceousMosaicStyleButton.selected = NO; 81 | }else if (sender == self.grindArenaceousMosaicStyleButton) { 82 | event = PSMosaicToolBarEventGrindArenaceous; 83 | self.mosaicType = PSMosaicTypeGrindArenaceous; 84 | self.grindArenaceousMosaicStyleButton.selected = YES; 85 | self.rectangularMosaicStyleButton.selected = NO; 86 | } 87 | 88 | if (self.delegate && 89 | [self.delegate respondsToSelector:@selector(mosaicToolBarType:event:)]) { 90 | [self.delegate mosaicToolBarType:self.mosaicType event:event]; 91 | } 92 | } 93 | 94 | - (UIImageView *)maskImageView { 95 | 96 | return LAZY_LOAD(_maskImageView, ({ 97 | 98 | _maskImageView = [[UIImageView alloc] initWithImage:[UIImage ps_imageNamed:@"icon_mask_bottom"]]; 99 | _maskImageView; 100 | })); 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSTopToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSTopToolBar.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSEditorToolBar.h" 9 | @class PSTopToolBar; 10 | 11 | typedef NS_ENUM(NSUInteger, PSTopToolBarType) { 12 | 13 | PSTopToolBarTypeClose = 0, 14 | PSTopToolBarTypeCancelAndDoneIcon, 15 | }; 16 | 17 | typedef NS_ENUM(NSUInteger, PSTopToolBarEvent) { 18 | 19 | PSTopToolBarEventCancel = 0, 20 | PSTopToolBarEventDone, 21 | }; 22 | 23 | @protocol PSTopToolBarDelegate 24 | 25 | 26 | - (void)topToolBar:(PSTopToolBar *)toolBar event:(PSTopToolBarEvent)event; 27 | 28 | @end 29 | 30 | @interface PSTopToolBar : PSEditorToolBar 31 | 32 | @property (nonatomic, weak) id delegate; 33 | 34 | @property (nonatomic, assign) PSTopToolBarType type; 35 | 36 | - (instancetype)initWithType:(PSTopToolBarType)type; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ToolBar/PSTopToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSTopToolBar.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/11/16. 6 | // 7 | 8 | #import "PSTopToolBar.h" 9 | 10 | @interface PSTopToolBar () 11 | 12 | @property (nonatomic, strong) UIImageView *maskImageView; 13 | @property (nonatomic, strong) UILabel *titleLabel; 14 | @property (nonatomic, strong) UIButton *backButton; 15 | @property (nonatomic, strong) UIButton *doneButton; 16 | 17 | @end 18 | 19 | @implementation PSTopToolBar 20 | 21 | - (void)buttonDidClickSender:(UIButton *)btn { 22 | 23 | 24 | PSTopToolBarEvent event = (btn == self.backButton ? PSTopToolBarEventCancel:PSTopToolBarEventDone); 25 | if (self.delegate && [self.delegate respondsToSelector:@selector(topToolBar:event:)]) { 26 | [self.delegate topToolBar:self event:event]; 27 | } 28 | } 29 | 30 | - (instancetype)initWithType:(PSTopToolBarType)type { 31 | 32 | if (self = [super init]) { 33 | _type = type; 34 | switch (type) { 35 | case PSTopToolBarTypeClose: 36 | [self configCloseUI]; 37 | break; 38 | case PSTopToolBarTypeCancelAndDoneIcon: 39 | [self configCancelAndDoneIconUI]; 40 | break; 41 | default: 42 | break; 43 | } 44 | } 45 | return self; 46 | } 47 | 48 | - (void)configCloseUI { 49 | 50 | [self.backButton setBackgroundImage:[UIImage ps_imageNamed:@"btn_close"] forState:UIControlStateNormal]; 51 | [self addSubview:self.backButton]; 52 | 53 | [self.backButton mas_makeConstraints:^(MASConstraintMaker *make) { 54 | make.size.equalTo(@30); 55 | make.left.equalTo(@10); 56 | make.centerY.equalTo(self); 57 | }]; 58 | } 59 | 60 | - (void)configCancelAndDoneIconUI { 61 | 62 | 63 | [self.backButton setImage:[UIImage ps_imageNamed:@"btn_cancel"] 64 | forState:UIControlStateNormal]; 65 | [self addSubview:self.backButton]; 66 | 67 | [self.doneButton setImage:[UIImage ps_imageNamed:@"btn_done"] 68 | forState:UIControlStateNormal]; 69 | [self addSubview:self.doneButton]; 70 | 71 | 72 | [self.backButton mas_makeConstraints:^(MASConstraintMaker *make) { 73 | 74 | make.size.equalTo(@44); 75 | make.left.equalTo(@15); 76 | make.centerY.equalTo(self); 77 | }]; 78 | 79 | [self.doneButton mas_makeConstraints:^(MASConstraintMaker *make) { 80 | 81 | make.size.equalTo(@44); 82 | make.right.equalTo(@(-15)); 83 | make.centerY.equalTo(self.backButton); 84 | }]; 85 | } 86 | 87 | #pragma mark - Getter/Setter 88 | 89 | - (UIImageView *)maskImageView { 90 | 91 | return LAZY_LOAD(_maskImageView, ({ 92 | 93 | _maskImageView = [[UIImageView alloc] initWithImage:[UIImage ps_imageNamed:@"icon_mask_top"]]; 94 | _maskImageView; 95 | })); 96 | } 97 | 98 | - (UIButton *)backButton { 99 | 100 | return LAZY_LOAD(_backButton, ({ 101 | 102 | _backButton = [[UIButton alloc] init]; 103 | [_backButton setFrame:CGRectMake(0, 0, 44, 44)]; 104 | _backButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0); 105 | [_backButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft]; 106 | [_backButton addTarget:self action:@selector(buttonDidClickSender:) forControlEvents:UIControlEventTouchUpInside]; 107 | _backButton; 108 | })); 109 | } 110 | 111 | - (UIButton *)doneButton { 112 | 113 | return LAZY_LOAD(_doneButton, ({ 114 | 115 | _doneButton = [[UIButton alloc] init]; 116 | [_doneButton setFrame:CGRectMake(0, 0, 44, 44)]; 117 | _doneButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0); 118 | [_doneButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentRight]; 119 | [_doneButton addTarget:self action:@selector(buttonDidClickSender:) forControlEvents:UIControlEventTouchUpInside]; 120 | _doneButton; 121 | })); 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/CALayer+PSBrush.h: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+PSBrush.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface CALayer (PSBrush) 13 | 14 | /** 层级(区分不同的画笔所画的图层) */ 15 | @property (nonatomic, assign) NSInteger ps_level; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/CALayer+PSBrush.m: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+PSBrush.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "CALayer+PSBrush.h" 9 | #import 10 | 11 | static const char * PSBrushLayerLevelKey = "PSBrushLayerLevelKey"; 12 | 13 | @implementation CALayer (PSBrush) 14 | 15 | - (void)setPs_level:(NSInteger)ps_level 16 | { 17 | objc_setAssociatedObject(self, PSBrushLayerLevelKey, @(ps_level), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 18 | } 19 | 20 | - (NSInteger)ps_level 21 | { 22 | NSNumber *num = objc_getAssociatedObject(self, PSBrushLayerLevelKey); 23 | if (num != nil) { 24 | return [num integerValue]; 25 | } 26 | return 0; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/NSAttributedString+PSCoreText.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+PSCoreText.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/18. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSAttributedString (PSCoreText) 13 | 14 | /** 15 | * 计算文字大小 16 | * 17 | * @param size 最大范围 文字长度、文字高度 18 | * 19 | * @return 文字大小 20 | */ 21 | - (CGSize)sizeWithConstrainedToSize:(CGSize)size; 22 | 23 | 24 | /** 25 | * 绘制文字 26 | * 27 | * @param context 画布 28 | * @param p 坐标 29 | * @param height 高度 30 | * @param width 宽度 31 | */ 32 | - (void)drawInContext:(CGContextRef)context withPosition:(CGPoint)p andHeight:(float)height andWidth:(float)width; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/NSAttributedString+PSCoreText.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+PSCoreText.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/18. 6 | // 7 | 8 | #import "NSAttributedString+PSCoreText.h" 9 | #import 10 | 11 | 12 | @implementation NSAttributedString (PSCoreText) 13 | 14 | - (CGSize)sizeWithConstrainedToSize:(CGSize)size 15 | { 16 | if (self.length == 0) return CGSizeZero; 17 | 18 | CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)self; 19 | 20 | CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString); 21 | CGSize result = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [self length]), NULL, size, NULL); 22 | CFRelease(framesetter); 23 | 24 | return CGSizeMake(ceilf(result.width), ceilf(result.height)); 25 | } 26 | 27 | - (void)drawInContext:(CGContextRef)context withPosition:(CGPoint)p andHeight:(float)height andWidth:(float)width 28 | { 29 | CGSize size = CGSizeMake(width, height); 30 | // 翻转坐标系 31 | CGContextSetTextMatrix(context,CGAffineTransformIdentity); 32 | CGContextTranslateCTM(context,0,height); 33 | CGContextScaleCTM(context,1.0,-1.0); 34 | 35 | // 创建绘制区域(路径) 36 | CGMutablePathRef path = CGPathCreateMutable(); 37 | CGPathAddRect(path,NULL,CGRectMake(p.x, height-p.y-size.height,(size.width),(size.height))); 38 | 39 | // 创建CFAttributedStringRef 40 | CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)self; 41 | 42 | // 绘制frame 43 | CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString); 44 | CTFrameRef ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0),path,NULL); 45 | CTFrameDraw(ctframe,context); 46 | CGPathRelease(path); 47 | CFRelease(framesetter); 48 | CFRelease(ctframe); 49 | 50 | CGContextSetTextMatrix(context,CGAffineTransformIdentity); 51 | CGContextTranslateCTM(context,0, height); 52 | CGContextScaleCTM(context,1.0,-1.0); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/PSBrush+PSCreate.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSBrush+PSCreate.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSBrush.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface PSBrush (PSCreate) 13 | 14 | + (UIBezierPath *)createBezierPathWithPoint:(CGPoint)point; 15 | 16 | + (CAShapeLayer *)createShapeLayerWithPath:(UIBezierPath *)path lineWidth:(CGFloat)lineWidth strokeColor:(UIColor *)strokeColor; 17 | 18 | @end 19 | 20 | @interface UIImage (PSBlurryBrush) 21 | 22 | /** 23 | 创建图案 24 | */ 25 | - (UIImage *)patternGaussianImageWithSize:(CGSize)size filterHandler:(CIFilter *(^ _Nullable )(CIImage *ciimage))filterHandler; 26 | /** 27 | 创建图案颜色 28 | */ 29 | - (UIColor *)patternGaussianColorWithSize:(CGSize)size filterHandler:(CIFilter *(^ _Nullable )(CIImage *ciimage))filterHandler; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/PSBrush+PSCreate.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSBrush+PSCreate.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "PSBrush+PSCreate.h" 9 | 10 | @implementation PSBrush (create) 11 | 12 | #pragma mark - private 13 | + (UIBezierPath *)createBezierPathWithPoint:(CGPoint)point 14 | { 15 | UIBezierPath *path = [UIBezierPath new]; 16 | path.lineCapStyle = kCGLineCapRound; //线条拐角 17 | path.lineJoinStyle = kCGLineJoinRound; //终点处理 18 | [path moveToPoint:point]; 19 | return path; 20 | } 21 | 22 | + (CAShapeLayer *)createShapeLayerWithPath:(UIBezierPath *)path lineWidth:(CGFloat)lineWidth strokeColor:(UIColor *)strokeColor 23 | { 24 | /** 25 | 1、渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。 26 | 2、高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。 27 | 3、不会被图层边界剪裁掉。 28 | 4、不会出现像素化。 29 | */ 30 | CAShapeLayer *slayer = nil; 31 | if (path) { 32 | slayer = [CAShapeLayer layer]; 33 | slayer.path = path.CGPath; 34 | slayer.backgroundColor = [UIColor clearColor].CGColor; 35 | slayer.fillColor = [UIColor clearColor].CGColor; 36 | slayer.lineCap = kCALineCapRound; 37 | slayer.lineJoin = kCALineJoinRound; 38 | slayer.strokeColor = strokeColor.CGColor; 39 | slayer.lineWidth = lineWidth; 40 | } 41 | 42 | return slayer; 43 | } 44 | 45 | @end 46 | 47 | @interface PSBrush (CIContext) 48 | 49 | @end 50 | 51 | static CIContext *PSBrush_CIContext = nil; 52 | @implementation PSBrush (CIContext) 53 | 54 | + (void)initialize 55 | { 56 | static dispatch_once_t onceToken; 57 | dispatch_once(&onceToken, ^{ 58 | PSBrush_CIContext = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer: @(NO)}]; 59 | }); 60 | } 61 | 62 | @end 63 | 64 | @implementation UIImage (PSBlurryBrush) 65 | 66 | /** 67 | 创建图案 68 | */ 69 | - (UIImage *)patternGaussianImageWithSize:(CGSize)size filterHandler:(CIFilter *(^ _Nullable )(CIImage *ciimage))filterHandler 70 | { 71 | return [self patternGaussianImageWithSize:size orientation:0 filterHandler:filterHandler]; 72 | } 73 | /** 74 | 创建图案颜色 75 | */ 76 | - (UIColor *)patternGaussianColorWithSize:(CGSize)size filterHandler:(CIFilter *(^ _Nullable )(CIImage *ciimage))filterHandler 77 | { 78 | //翻转图片(因为图片转换成图像颜色后在layer上使用,layer的画布是反转的,这里需要翻转方向。理应这里不应该调整方向,为了提高效率,这里的方法私有化,仅为PSBlurryBrush/PSMosaicBrush提供。) 79 | UIImage *image = [self patternGaussianImageWithSize:size orientation:kCGImagePropertyOrientationDownMirrored filterHandler:filterHandler]; 80 | return [UIColor colorWithPatternImage:image]; 81 | } 82 | 83 | - (UIImage *)patternGaussianImageWithSize:(CGSize)size orientation:(CGImagePropertyOrientation)orientation filterHandler:(CIFilter *(^ _Nullable )(CIImage *ciimage))filterHandler 84 | { 85 | CIContext *context = PSBrush_CIContext; 86 | if (!context) { 87 | NSLog(@"This method must be called using the PSBrush class."); 88 | } 89 | CIImage *midImage = [CIImage imageWithCGImage:self.CGImage]; 90 | midImage = [midImage imageByApplyingTransform:[self preferredTransform]]; 91 | midImage = [midImage imageByApplyingTransform:CGAffineTransformMakeScale(size.width/midImage.extent.size.width, size.height/midImage.extent.size.height)]; 92 | 93 | if (orientation > 0 && orientation < 9) { 94 | midImage = [midImage imageByApplyingOrientation:orientation]; 95 | } 96 | //图片开始处理 97 | CIImage *result = midImage; 98 | if (filterHandler) { 99 | CIFilter *filter = filterHandler(midImage); 100 | if (filter) { 101 | result = filter.outputImage; 102 | } 103 | } 104 | 105 | CGImageRef outImage = [context createCGImage:result fromRect:[midImage extent]]; 106 | UIImage *image = [UIImage imageWithCGImage:outImage]; 107 | CGImageRelease(outImage); 108 | 109 | return image; 110 | } 111 | 112 | - (CGAffineTransform)preferredTransform { 113 | if (self.imageOrientation == UIImageOrientationUp) { 114 | return CGAffineTransformIdentity; 115 | } 116 | CGAffineTransform transform = CGAffineTransformIdentity; 117 | CGSize imageSize = CGSizeMake(self.size.width*self.scale, self.size.height*self.scale); 118 | switch (self.imageOrientation) { 119 | case UIImageOrientationDown: 120 | case UIImageOrientationDownMirrored: 121 | transform = CGAffineTransformTranslate(transform, imageSize.width, imageSize.height); 122 | transform = CGAffineTransformRotate(transform, M_PI); 123 | break; 124 | 125 | case UIImageOrientationLeft: 126 | case UIImageOrientationLeftMirrored: 127 | transform = CGAffineTransformTranslate(transform, imageSize.width, 0); 128 | transform = CGAffineTransformRotate(transform, M_PI_2); 129 | break; 130 | 131 | case UIImageOrientationRight: 132 | case UIImageOrientationRightMirrored: 133 | transform = CGAffineTransformTranslate(transform, 0, imageSize.height); 134 | transform = CGAffineTransformRotate(transform, -M_PI_2); 135 | break; 136 | case UIImageOrientationUp: 137 | case UIImageOrientationUpMirrored: 138 | break; 139 | } 140 | 141 | switch (self.imageOrientation) { 142 | case UIImageOrientationUpMirrored: 143 | case UIImageOrientationDownMirrored: 144 | transform = CGAffineTransformTranslate(transform, imageSize.width, 0); 145 | transform = CGAffineTransformScale(transform, -1, 1); 146 | break; 147 | 148 | case UIImageOrientationLeftMirrored: 149 | case UIImageOrientationRightMirrored: 150 | transform = CGAffineTransformTranslate(transform, imageSize.height, 0); 151 | transform = CGAffineTransformScale(transform, -1, 1); 152 | break; 153 | case UIImageOrientationUp: 154 | case UIImageOrientationDown: 155 | case UIImageOrientationLeft: 156 | case UIImageOrientationRight: 157 | break; 158 | } 159 | 160 | return transform; 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/PSColorFullButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSColorFullButton.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/29. 6 | // 7 | 8 | #import 9 | 10 | @interface PSColorFullButton : UIButton 11 | 12 | @property (nonatomic, assign) BOOL isUse; 13 | @property (nonatomic, strong, readonly) UIColor *color; 14 | 15 | - (instancetype)initWithFrame:(CGRect)frame radius:(CGFloat)radius color:(UIColor *)color; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/PSColorFullButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSColorFullButton.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/29. 6 | // 7 | 8 | #import "PSColorFullButton.h" 9 | 10 | @interface PSColorFullButton () { 11 | UIColor *_color; 12 | CGFloat _radius; 13 | CAShapeLayer *_layer; 14 | CAShapeLayer *_layer2; 15 | } 16 | 17 | // 绘制需要获取到self.bounds 18 | @property (nonatomic, assign) CGFloat radius; 19 | @property (nonatomic, strong, readwrite) UIColor *color; 20 | 21 | @end 22 | 23 | @implementation PSColorFullButton 24 | 25 | - (instancetype)initWithFrame:(CGRect)frame radius:(CGFloat)radius color:(UIColor *)color { 26 | if (self = [super initWithFrame:frame]) { 27 | 28 | _radius = radius; 29 | _color = color; 30 | 31 | UIGraphicsBeginImageContext(self.bounds.size); 32 | CAShapeLayer *layer = [CAShapeLayer layer]; 33 | layer.frame = self.bounds; 34 | UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width/2.f, self.bounds.size.height/2.f) radius:radius startAngle:0 endAngle:2*M_PI clockwise:YES]; 35 | layer.fillColor = _color.CGColor; 36 | layer.allowsEdgeAntialiasing = YES; 37 | layer.backgroundColor = [UIColor clearColor].CGColor; 38 | layer.path = path.CGPath; 39 | [path fill]; 40 | UIGraphicsEndImageContext(); 41 | [self.layer addSublayer:layer]; 42 | _layer = layer; 43 | } 44 | return self; 45 | } 46 | 47 | 48 | - (void)setIsUse:(BOOL)isUse { 49 | 50 | _isUse = isUse; 51 | [self drawCirle]; 52 | } 53 | 54 | - (void)drawCirle { 55 | 56 | if (!_layer2 && self.isUse) { 57 | UIGraphicsBeginImageContext(self.bounds.size); 58 | CAShapeLayer *layer2 = [CAShapeLayer layer]; 59 | layer2.frame = self.bounds; 60 | UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:CGPointMake((self.bounds.size.width/2.f), self.bounds.size.height/2.f) radius:_radius+8 startAngle:0 endAngle:2*M_PI clockwise:YES]; 61 | layer2.fillColor = [UIColor clearColor].CGColor; 62 | layer2.allowsEdgeAntialiasing = YES; 63 | layer2.backgroundColor = [UIColor clearColor].CGColor; 64 | layer2.strokeColor = _color.CGColor; 65 | layer2.lineWidth = 3; 66 | layer2.path = path2.CGPath; 67 | [path2 stroke]; 68 | UIGraphicsEndImageContext(); 69 | [self.layer insertSublayer:layer2 below:_layer]; 70 | _layer2 = layer2; 71 | } 72 | 73 | _layer2.hidden = !self.isUse; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/PSExpandClickAreaButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSExpandClickAreaButton.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/30. 6 | // 7 | 8 | #import 9 | 10 | @interface PSExpandClickAreaButton : UIButton 11 | 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/PSExpandClickAreaButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSExpandClickAreaButton.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2018/8/30. 6 | // 7 | 8 | #import "PSExpandClickAreaButton.h" 9 | 10 | @interface PSExpandClickAreaButton () 11 | 12 | @end 13 | 14 | @implementation PSExpandClickAreaButton 15 | 16 | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { 17 | 18 | //获取当前button的实际大小 19 | CGRect bounds = self.bounds; 20 | 21 | //若原热区小于44x44,则放大热区,否则保持原大小不变 22 | CGFloat widthDelta = MAX(44.0 - bounds.size.width, 0); 23 | CGFloat heightDelta = MAX(44.0 - bounds.size.height, 0); 24 | 25 | //扩大bounds 左右方向和上下方向扩大或缩小的长度。正值为缩小,负值为扩大 26 | bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta); 27 | 28 | //如果点击的点 在 新的bounds里,就返回YES 29 | return CGRectContainsPoint(bounds, point); 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/UIImage+PSImageEditors.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+PSImageEditors.h 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/8/25. 6 | // 7 | 8 | #import 9 | 10 | @interface UIImage (PSImageEditors) 11 | 12 | + (UIImage *)ps_imageNamed:(NSString *)name; 13 | 14 | + (NSBundle *)bundleForClass:(Class)class withBundleName:(NSString *)name; 15 | 16 | /** 通过遍历像素点实现马赛克效果,level越大,马赛克颗粒越大,若level为0则默认为图片1/20 */ 17 | + (UIImage *)ps_mosaicImage:(UIImage *)image level:(NSInteger)level; 18 | 19 | /** 截屏 */ 20 | + (UIImage *)ps_screenshot:(UIView *)view imageRect:(CGRect)imageRect; 21 | 22 | /** 图片解码 */ 23 | - (UIImage *)ps_decode; 24 | 25 | /** 图片旋转角度 */ 26 | - (UIImage *)ps_imageRotatedByRadians:(CGFloat)radians; 27 | 28 | /** 根据image本身创建指定rect的image */ 29 | - (UIImage *)ps_imageAtRect:(CGRect)rect; 30 | 31 | /** 压缩图片 */ 32 | - (UIImage *)ps_imageCompress; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/UIImage+PSImageEditors.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+PSImageEditors.m 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/8/25. 6 | // 7 | 8 | #import "UIImage+PSImageEditors.h" 9 | #import "PSMovingView.h" 10 | #import 11 | 12 | #define kBitsPerComponent (8) 13 | #define kPixelChannelCount (4) 14 | #define kBitsPerPixel (32) 15 | 16 | @implementation UIImage (PSImageEditors) 17 | 18 | + (UIImage *)ps_imageNamed:(NSString *)name { 19 | 20 | NSString *bundleName = @"PSImageEditors.bundle"; 21 | UIImage *image = [self imageWithName:name 22 | withBundleClass:NSClassFromString(@"_PSImageEditorViewController") 23 | bundleName:bundleName]; 24 | return image; 25 | } 26 | 27 | + (UIImage *)imageWithName:(NSString *)name 28 | withBundleClass:(Class)class 29 | bundleName:(NSString *)bundleName { 30 | 31 | NSBundle *bundle = [self bundleForClass:class withBundleName:bundleName]; 32 | UIImage *image = [UIImage imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil]; 33 | return image; 34 | } 35 | 36 | + (NSBundle *)bundleForClass:(Class)class withBundleName:(NSString *)name { 37 | 38 | NSBundle *frameworkBundle = [NSBundle bundleForClass:class]; 39 | NSURL *kitBundleUrl = [frameworkBundle.resourceURL URLByAppendingPathComponent:name]; 40 | NSBundle *bundle = [NSBundle bundleWithURL:kitBundleUrl]; 41 | return bundle; 42 | } 43 | 44 | + (UIImage *)ps_mosaicImage:(UIImage *)image level:(NSInteger)level { 45 | 46 | //1、这一部分是为了把原始图片转成位图,位图再转成可操作的数据 47 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();//颜色通道 48 | CGImageRef imageRef = image.CGImage;//位图 49 | CGFloat width = CGImageGetWidth(imageRef);//位图宽 50 | CGFloat height = CGImageGetHeight(imageRef);//位图高 51 | CGContextRef context = CGBitmapContextCreate(nil, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast);//生成上下午 52 | CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);//绘制图片到上下文中 53 | unsigned char *bitmapData = CGBitmapContextGetData(context);//获取位图的数据 54 | 55 | 56 | //2、这一部分是往右往下填充色值 57 | NSUInteger index,preIndex; 58 | unsigned char pixel[4] = {0}; 59 | for (int i = 0; i < height; i++) {//表示高,也可以说是行 60 | for (int j = 0; j < width; j++) {//表示宽,也可以说是列 61 | index = i * width + j; 62 | if (i % level == 0) { 63 | if (j % level == 0) { 64 | //把当前的色值数据保存一份,开始为i=0,j=0,所以一开始会保留一份 65 | memcpy(pixel, bitmapData + index * 4, 4); 66 | }else{ 67 | //把上一次保留的色值数据填充到当前的内存区域,这样就起到把前面数据往后挪的作用,也是往右填充 68 | memcpy(bitmapData +index * 4, pixel, 4); 69 | } 70 | }else{ 71 | //这里是把上一行的往下填充 72 | preIndex = (i - 1) * width + j; 73 | memcpy(bitmapData + index * 4, bitmapData + preIndex * 4, 4); 74 | } 75 | } 76 | } 77 | 78 | //把数据转回位图,再从位图转回UIImage 79 | NSUInteger dataLength = width * height * 4; 80 | CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bitmapData, dataLength, NULL); 81 | 82 | CGImageRef mosaicImageRef = CGImageCreate(width, height, 83 | 8, 84 | 32, 85 | width*4 , 86 | colorSpace, 87 | kCGBitmapByteOrderDefault, 88 | provider, 89 | NULL, NO, 90 | kCGRenderingIntentDefault); 91 | CGContextRef outputContext = CGBitmapContextCreate(nil, 92 | width, 93 | height, 94 | 8, 95 | width*4, 96 | colorSpace, 97 | kCGImageAlphaPremultipliedLast); 98 | CGContextDrawImage(outputContext, CGRectMake(0.0f, 0.0f, width, height), mosaicImageRef); 99 | CGImageRef resultImageRef = CGBitmapContextCreateImage(outputContext); 100 | UIImage *resultImage = nil; 101 | if([UIImage respondsToSelector:@selector(imageWithCGImage:scale:orientation:)]) { 102 | float scale = [[UIScreen mainScreen] scale]; 103 | resultImage = [UIImage imageWithCGImage:resultImageRef scale:scale orientation:UIImageOrientationUp]; 104 | } else { 105 | resultImage = [UIImage imageWithCGImage:resultImageRef]; 106 | } 107 | CFRelease(resultImageRef); 108 | CFRelease(mosaicImageRef); 109 | CFRelease(colorSpace); 110 | CFRelease(provider); 111 | CFRelease(context); 112 | CFRelease(outputContext); 113 | return resultImage; 114 | } 115 | 116 | + (UIImage *)ps_screenshot:(UIView *)view imageRect:(CGRect)imageRect { 117 | 118 | CGSize targetSize = CGSizeZero; 119 | 120 | CGFloat transformScaleX = [[view.layer valueForKeyPath:@"transform.scale.x"] doubleValue]; 121 | CGFloat transformScaleY = [[view.layer valueForKeyPath:@"transform.scale.y"] doubleValue]; 122 | 123 | if ([view isKindOfClass:[PSMovingView class]]) { 124 | transformScaleX = ((PSMovingView *)view).transformScaleX; 125 | transformScaleY = ((PSMovingView *)view).transformScaleY; 126 | } 127 | 128 | CGSize size = view.bounds.size; 129 | targetSize = CGSizeMake(size.width * transformScaleX, size.height * transformScaleY); 130 | 131 | UIGraphicsBeginImageContext(targetSize); 132 | //UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0); // 统一为0,方便PC端查看 133 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 134 | CGContextSaveGState(ctx); 135 | [view drawViewHierarchyInRect:CGRectMake(0, 0, targetSize.width, targetSize.height) afterScreenUpdates:NO]; 136 | CGContextRestoreGState(ctx); 137 | 138 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 139 | UIGraphicsEndImageContext(); 140 | 141 | return image; 142 | } 143 | 144 | - (UIImage *)ps_imageRotatedByRadians:(CGFloat)radians { 145 | 146 | //定义一个执行旋转的CGAffineTransform结构体 147 | CGAffineTransform t = CGAffineTransformMakeRotation(radians); 148 | //对图片的原始区域执行旋转,获取旋转后的区域 149 | CGRect rotateRect = CGRectApplyAffineTransform(CGRectMake(0, 0, self.size.width, self.size.height), t); 150 | //获取图片旋转后的大小 151 | CGSize rotatedSize = rotateRect.size; 152 | //创建绘制位图的上下文 153 | UIGraphicsBeginImageContextWithOptions(rotatedSize, NO, [UIScreen mainScreen].scale); 154 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 155 | //指定坐标变换,将坐标中心平移到图片中心 156 | CGContextTranslateCTM(ctx, rotatedSize.width/2.0, rotatedSize.height/2.0); 157 | //执行坐标变换,旋转过radians弧度 158 | CGContextRotateCTM(ctx, radians); 159 | CALayer *layer = [CALayer layer]; 160 | 161 | //执行坐标变换,执行缩放 162 | CGContextScaleCTM(ctx, 1.0, -1.0); 163 | //绘制图片 164 | CGContextDrawImage(ctx, CGRectMake(-self.size.width/2.0, -self.size.height/2.0, self.size.width, self.size.height), self.CGImage); 165 | //获取绘制后生成的新图片 166 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 167 | UIGraphicsEndImageContext(); 168 | return newImage; 169 | } 170 | 171 | - (UIImage *)ps_decode { 172 | 173 | if(!self){ return nil; } 174 | 175 | UIImage *decodeImage = nil; 176 | UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); 177 | [self drawAtPoint:CGPointMake(0, 0)]; 178 | decodeImage = UIGraphicsGetImageFromCurrentImageContext(); 179 | UIGraphicsEndImageContext(); 180 | return decodeImage; 181 | } 182 | 183 | - (UIImage *)ps_imageAtRect:(CGRect)rect { 184 | 185 | //把像 素rect 转化为 点rect(如无转化则按原图像素取部分图片) 186 | CGFloat scale = [UIScreen mainScreen].scale; 187 | CGFloat x= rect.origin.x*scale,y=rect.origin.y*scale,w=rect.size.width*scale,h=rect.size.height*scale; 188 | CGRect dianRect = CGRectMake(x, y, w, h); 189 | 190 | //截取部分图片并生成新图片 191 | CGImageRef newImageRef = CGImageCreateWithImageInRect([self CGImage], dianRect); 192 | UIImage *newImage = [UIImage imageWithCGImage:newImageRef scale:scale orientation:self.imageOrientation]; 193 | CGImageRelease(newImageRef); 194 | return newImage; 195 | } 196 | 197 | - (UIImage *)ps_imageCompress { 198 | 199 | UIImage *image = self; 200 | CGFloat imageWidth = image.size.width; // 2268 201 | CGFloat imageHeight = image.size.height; // 4032 202 | CGFloat boundary = 1280; 203 | 204 | // width, height <= 1280, Size remains the same 205 | if (imageWidth <= boundary && imageHeight <= boundary) { 206 | UIImage *reImage = [self resizedImage:imageWidth withHeight:imageHeight withImage:image]; 207 | return reImage; 208 | } 209 | 210 | // aspect ratio 211 | CGFloat s = MAX(imageWidth, imageHeight) / MIN(imageWidth, imageHeight); 212 | 213 | if (s <= 2) { 214 | // Set the larger value to the boundary, the smaller the value of the compression 215 | CGFloat x = MAX(imageWidth, imageHeight) / boundary; 216 | if (imageWidth > imageHeight) { 217 | imageWidth = boundary ; 218 | imageHeight = imageHeight / x; 219 | }else{ 220 | imageHeight = boundary; 221 | imageWidth = imageWidth / x; 222 | } 223 | }else{ 224 | // width, height > 1280 225 | if (MIN(imageWidth, imageHeight) >= boundary) { 226 | //- parameter type: session image boundary is 800, timeline is 1280 227 | // boundary = type == .session ? 800 : 1280 228 | CGFloat x = MIN(imageWidth, imageHeight) / boundary; 229 | if (imageWidth < imageHeight) { 230 | imageWidth = boundary; 231 | imageHeight = imageHeight / x; 232 | } else { 233 | imageHeight = boundary; 234 | imageWidth = imageWidth / x; 235 | } 236 | } 237 | } 238 | 239 | UIImage *reImage = [self resizedImage:imageWidth withHeight:imageHeight withImage:image]; 240 | return reImage; 241 | } 242 | 243 | - (UIImage *)resizedImage:(CGFloat)imageWidth 244 | withHeight:(CGFloat)imageHeight 245 | withImage:(UIImage *)image { 246 | 247 | CGRect newRect = CGRectMake(0, 0, imageWidth, imageHeight); 248 | UIImage *newImage; 249 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageWidth, imageHeight), NO, image.scale); 250 | newImage = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation]; 251 | [newImage drawInRect:newRect]; 252 | newImage = UIGraphicsGetImageFromCurrentImageContext(); 253 | UIGraphicsEndImageContext(); 254 | return newImage; 255 | } 256 | 257 | @end 258 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/UIView+PSImageEditors.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+PSImageEditors.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface UIView (PSImageEditors) 13 | 14 | - (UIImage *)captureImageAtFrame:(CGRect)rect; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/Utils/UIView+PSImageEditors.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+PSImageEditors.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/8/24. 6 | // 7 | 8 | #import "UIView+PSImageEditors.h" 9 | 10 | @implementation UIView (PSImageEditors) 11 | 12 | - (UIImage *)captureImageAtFrame:(CGRect)rect 13 | { 14 | 15 | UIImage* image = nil; 16 | 17 | if (/* DISABLES CODE */ (YES)) { 18 | CGSize size = self.bounds.size; 19 | CGPoint point = self.bounds.origin; 20 | if (!CGRectEqualToRect(CGRectZero, rect)) { 21 | size = rect.size; 22 | point = CGPointMake(-rect.origin.x, -rect.origin.y); 23 | } 24 | @autoreleasepool { 25 | UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale); 26 | [self drawViewHierarchyInRect:(CGRect){point, self.bounds.size} afterScreenUpdates:YES]; 27 | image = UIGraphicsGetImageFromCurrentImageContext(); 28 | UIGraphicsEndImageContext(); 29 | } 30 | 31 | } else { 32 | 33 | BOOL translateCTM = !CGRectEqualToRect(CGRectZero, rect); 34 | 35 | if (!translateCTM) { 36 | rect = self.frame; 37 | } 38 | 39 | /** 参数取整,否则可能会出现1像素偏差 */ 40 | /** 有小数部分才调整差值 */ 41 | #define lfme_fixDecimal(d) ((fmod(d, (int)d)) > 0.59f ? ((int)(d+0.5)*1.f) : (((fmod(d, (int)d)) < 0.59f && (fmod(d, (int)d)) > 0.1f) ? ((int)(d)*1.f+0.5f) : (int)(d)*1.f)) 42 | rect.origin.x = lfme_fixDecimal(rect.origin.x); 43 | rect.origin.y = lfme_fixDecimal(rect.origin.y); 44 | rect.size.width = lfme_fixDecimal(rect.size.width); 45 | rect.size.height = lfme_fixDecimal(rect.size.height); 46 | #undef lfme_fixDecimal 47 | CGSize size = rect.size; 48 | 49 | @autoreleasepool { 50 | //1.开启上下文 51 | UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale); 52 | 53 | CGContextRef context = UIGraphicsGetCurrentContext(); 54 | 55 | if (translateCTM) { 56 | /** 移动上下文 */ 57 | CGContextTranslateCTM(context, -rect.origin.x, -rect.origin.y); 58 | } 59 | //2.绘制图层 60 | [self.layer renderInContext: context]; 61 | 62 | //3.从上下文中获取新图片 63 | image = UIGraphicsGetImageFromCurrentImageContext(); 64 | 65 | //4.关闭图形上下文 66 | UIGraphicsEndImageContext(); 67 | 68 | // if (translateCTM) { 69 | // UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale); 70 | // [image drawAtPoint:CGPointMake(-rect.origin.x, -rect.origin.y)]; 71 | // image = UIGraphicsGetImageFromCurrentImageContext(); 72 | // UIGraphicsEndImageContext(); 73 | // } 74 | } 75 | 76 | } 77 | 78 | 79 | return image; 80 | } 81 | 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ViewController /PSCropViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PSCropViewController.h 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/9/14. 6 | // 7 | 8 | #import "TOCropViewController.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface PSCropViewController : TOCropViewController 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ViewController /PSCropViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PSCropViewController.m 3 | // PSImageEditors 4 | // 5 | // Created by rsf on 2020/9/14. 6 | // 7 | 8 | #import "PSCropViewController.h" 9 | 10 | @interface PSCropViewController () 11 | 12 | @property (nonatomic, strong) UIButton *closeButton; 13 | 14 | @end 15 | 16 | @implementation PSCropViewController 17 | 18 | #pragma mark - Life Cycle 19 | 20 | - (void)viewDidLoad { 21 | 22 | [super viewDidLoad]; 23 | 24 | TOCropToolbar *toolbar = self.toolbar; 25 | 26 | [toolbar.doneTextButton setImage:[UIImage ps_imageNamed:@"btn_done"] forState:UIControlStateNormal]; 27 | [toolbar.cancelTextButton setImage:[UIImage ps_imageNamed:@"btn_revocation_normal"] forState:UIControlStateNormal]; 28 | [toolbar.doneTextButton setTitle:nil forState:UIControlStateNormal]; 29 | [toolbar.cancelTextButton setTitle:nil forState:UIControlStateNormal]; 30 | [toolbar.doneTextButton setTintColor:[UIColor whiteColor]]; 31 | [toolbar.cancelTextButton setTintColor:[UIColor whiteColor]]; 32 | [toolbar.cancelTextButton addTarget:self action:@selector(undoButtonClick) forControlEvents:UIControlEventTouchUpInside]; 33 | toolbar.cancelTextButton.enabled = NO; 34 | 35 | self.closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; 36 | [self.closeButton setImage:[self cancelImage] forState:UIControlStateNormal]; 37 | [self.closeButton addTarget:self action:@selector(closeButtonClick) forControlEvents:UIControlEventTouchUpInside]; 38 | [self.view addSubview:self.closeButton]; 39 | 40 | [self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) { 41 | make.top.equalTo(@(10)); 42 | make.left.equalTo(@10); 43 | make.size.equalTo(@44); 44 | }]; 45 | 46 | 47 | __weak typeof(self) weakSelf = self; 48 | self.toolbar.cancelButtonTapped = ^{ 49 | [weakSelf undoButtonClick]; 50 | }; 51 | 52 | /* 53 | TOCropView 54 | - (void)checkForCanReset 55 | [[NSNotificationCenter defaultCenter] postNotificationName:@"TOCheckForCanReset" object:@(canReset)]; 56 | */ 57 | [[NSNotificationCenter defaultCenter] addObserver:self 58 | selector:@selector(checkForCanReset:) 59 | name:@"TOCheckForCanReset" 60 | object:nil]; 61 | 62 | 63 | } 64 | 65 | - (void)dealloc { 66 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 67 | } 68 | 69 | - (void)checkForCanReset:(NSNotification *)noti { 70 | 71 | BOOL canBeReset = [noti.object boolValue]; 72 | self.toolbar.cancelTextButton.enabled = canBeReset; 73 | } 74 | 75 | - (void)undoButtonClick { 76 | [self performSelector:@selector(resetCropViewLayout)]; 77 | } 78 | 79 | - (void)closeButtonClick { 80 | [self performSelector:@selector(cancelButtonTapped)]; 81 | } 82 | 83 | - (void)viewDidLayoutSubviews { 84 | 85 | [super viewDidLayoutSubviews]; 86 | } 87 | 88 | #pragma mark - Method 89 | 90 | - (UIImage *)doneImage 91 | { 92 | UIImage *doneImage = nil; 93 | 94 | UIGraphicsBeginImageContextWithOptions((CGSize){17,14}, NO, 0.0f); 95 | { 96 | //// Rectangle Drawing 97 | UIBezierPath* rectanglePath = UIBezierPath.bezierPath; 98 | [rectanglePath moveToPoint: CGPointMake(1, 7)]; 99 | [rectanglePath addLineToPoint: CGPointMake(6, 12)]; 100 | [rectanglePath addLineToPoint: CGPointMake(16, 1)]; 101 | [UIColor.whiteColor setStroke]; 102 | rectanglePath.lineWidth = 2; 103 | [rectanglePath stroke]; 104 | 105 | 106 | doneImage = UIGraphicsGetImageFromCurrentImageContext(); 107 | } 108 | UIGraphicsEndImageContext(); 109 | 110 | return doneImage; 111 | } 112 | 113 | - (UIImage *)cancelImage 114 | { 115 | UIImage *cancelImage = nil; 116 | 117 | UIGraphicsBeginImageContextWithOptions((CGSize){16,16}, NO, 0.0f); 118 | { 119 | UIBezierPath* bezierPath = UIBezierPath.bezierPath; 120 | [bezierPath moveToPoint: CGPointMake(15, 15)]; 121 | [bezierPath addLineToPoint: CGPointMake(1, 1)]; 122 | [UIColor.whiteColor setStroke]; 123 | bezierPath.lineWidth = 2; 124 | [bezierPath stroke]; 125 | 126 | 127 | //// Bezier 2 Drawing 128 | UIBezierPath* bezier2Path = UIBezierPath.bezierPath; 129 | [bezier2Path moveToPoint: CGPointMake(1, 15)]; 130 | [bezier2Path addLineToPoint: CGPointMake(15, 1)]; 131 | [UIColor.whiteColor setStroke]; 132 | bezier2Path.lineWidth = 2; 133 | [bezier2Path stroke]; 134 | 135 | cancelImage = UIGraphicsGetImageFromCurrentImageContext(); 136 | } 137 | UIGraphicsEndImageContext(); 138 | 139 | return cancelImage; 140 | } 141 | 142 | #pragma mark - Delegate 143 | 144 | #pragma mark - InitAndLayout 145 | 146 | #pragma mark - Getter/Setter 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /PSImageEditors/Classes/ViewController /_PSImageEditorViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // _PSImageEditorViewController.h 3 | // PSImageEditors 4 | // 5 | // Created by paintingStyle on 2018/11/15. 6 | // 7 | 8 | #import "PSImageEditor.h" 9 | #import "PSTopToolBar.h" 10 | #import "PSBottomToolBar.h" 11 | 12 | @interface _PSImageEditorViewController : PSImageEditor 13 | 14 | /// 缩放容器 15 | @property (nonatomic, strong) UIScrollView *scrollView; 16 | /// 最底层负责显示的图片 17 | @property (nonatomic, strong) UIImageView *imageView; 18 | 19 | /// 用于布局参照 20 | @property (nonatomic, strong, readonly) PSTopToolBar *topToolBar; 21 | @property (nonatomic, strong, readonly) PSBottomToolBar *bottomToolBar; 22 | 23 | @property (nonatomic, copy) void(^scrollViewDidZoomBlock) (CGFloat zoomScale); 24 | 25 | - (void)buildClipImageCallback:(void(^)(UIImage *clipedImage))callback; 26 | - (void)hiddenToolBar:(BOOL)hidden animation:(BOOL)animation; 27 | - (void)hiddenBottomToolBar:(BOOL)hidden animation:(BOOL)animation; 28 | - (void)refreshImageView; 29 | 30 | - (void)addTrajectoryName:(NSString *)name; 31 | - (void)removeLastTrajectory; 32 | - (void)removeLastTrajectoryName:(NSString *)name; 33 | - (void)removeAllTrajectory; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | PSImageEditors 3 |

4 | 5 | # PSImageEditors(简而至美的一个图片编辑器 ) 6 | 开源一个图片编辑组件,样式参照微信与钉钉的图片编辑效果,支持包括涂鸦,添加文字,添加马赛克,裁剪等功能,内部线上项目已使用此组件。 7 | 8 | ## 功能 9 | ##### 画笔 10 | ![1.jpg](https://upload-images.jianshu.io/upload_images/4490624-254d46f57a0d76e2.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 11 | 12 | ##### 文字(支持更换文字背景颜色) 13 | ![2.jpg](https://upload-images.jianshu.io/upload_images/4490624-8dff29894b7c64df.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 14 | ![3.jpg](https://upload-images.jianshu.io/upload_images/4490624-8ab1bcab20b55068.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 15 | 16 | ##### 马赛克(两种马赛克样式) 17 | ![4.jpg](https://upload-images.jianshu.io/upload_images/4490624-146ac352571cf626.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | ##### 裁剪 20 | ![5.jpg](https://upload-images.jianshu.io/upload_images/4490624-c377e3568a7f7b0f.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 21 | 22 | ## Installation 安装 23 | ### 1,手动安装 24 | `下载Demo后,将子文件夹PSImageEditors拖入到项目中, 导入头文件PSImageEditors.h开始使用,注意: 项目中需要有Masonry.1.1.0!` 25 | ### 2,CocoaPods安装 26 | `pod 'PSImageEditors'` 27 | 如果发现pod search PSImageEditors 不是最新版本,可在终端执行 pod repo update 更新本地仓库,更新完成重新搜索即可。 28 | 29 | ### 3,导入头文件 #import "PSImageEditors.h" 30 | 31 | ```` 32 | UIImage *image = [UIImage imageNamed:@"localImage_06@2x.jpg"]; 33 | PSImageEditor *imageEditor = [[PSImageEditor alloc] initWithImage:image delegate:self dataSource:self]; 34 | [self.navigationController pushViewController:imageEditor animated:YES]; 35 | ```` 36 | 37 | ### 4,PSImageEditorDelegate 38 | ```` 39 | #pragma mark - PSImageEditorDelegate 40 | 41 | - (void)imageEditor:(PSImageEditor *)editor didFinishEdittingWithImage:(UIImage *)image { 42 | self.imageView.image = image; 43 | [editor dismiss]; 44 | NSLog(@"%s",__func__); 45 | } 46 | 47 | - (void)imageEditorDidCancel { 48 | NSLog(@"%s",__func__); 49 | } 50 | ```` 51 | 52 | ### 5,参数设置 53 | ```` 54 | #pragma mark - PSImageEditorDelegate 55 | 56 | - (UIColor *)imageEditorDefaultColor { 57 | return [UIColor redColor]; 58 | } 59 | 60 | - (PSImageEditorMode)imageEditorDefalutEditorMode { 61 | return PSImageEditorModeDraw; 62 | } 63 | 64 | - (CGFloat)imageEditorDrawPathWidth { 65 | return 5; 66 | } 67 | 68 | - (UIFont *)imageEditorTextFont { 69 | return [UIFont boldSystemFontOfSize:24]; 70 | } 71 | ```` 72 | 73 | 74 | ## Requirements 要求 75 | * iOS 8+ 76 | * Xcode 8+ 77 | 78 | ## 更新日志 79 | ``` 80 | - 2018.06.14 (tag:0.1.0):提交0.1.0版本 81 | - 2020.07.16 (tag:0.2.0): 修复编辑图片模糊的问题,UI更新 82 | - 2020.07.23 (tag:0.2.1): 增加默认选中编辑选项功能 83 | - 2020.08.19 (tag:0.2.2): 底部ToolBar修改,文字组件优化体验 84 | ``` 85 | 86 | --------------------------------------------------------------------------------