├── .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 |
3 |
4 |
5 | # PSImageEditors(简而至美的一个图片编辑器 )
6 | 开源一个图片编辑组件,样式参照微信与钉钉的图片编辑效果,支持包括涂鸦,添加文字,添加马赛克,裁剪等功能,内部线上项目已使用此组件。
7 |
8 | ## 功能
9 | ##### 画笔
10 | 
11 |
12 | ##### 文字(支持更换文字背景颜色)
13 | 
14 | 
15 |
16 | ##### 马赛克(两种马赛克样式)
17 | 
18 |
19 | ##### 裁剪
20 | 
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 |
--------------------------------------------------------------------------------