1819 lines
69 KiB
Swift
1819 lines
69 KiB
Swift
//
|
||
// ZLEditImageViewController.swift
|
||
// ZLPhotoBrowser
|
||
//
|
||
// Created by long on 2020/8/26.
|
||
//
|
||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
// of this software and associated documentation files (the "Software"), to deal
|
||
// in the Software without restriction, including without limitation the rights
|
||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
// copies of the Software, and to permit persons to whom the Software is
|
||
// furnished to do so, subject to the following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included in
|
||
// all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
// THE SOFTWARE.
|
||
|
||
import UIKit
|
||
|
||
public class ZLEditImageModel: NSObject {
|
||
public let drawPaths: [ZLDrawPath]
|
||
|
||
public let mosaicPaths: [ZLMosaicPath]
|
||
|
||
public let editRect: CGRect?
|
||
|
||
public let angle: CGFloat
|
||
|
||
public let brightness: Float
|
||
|
||
public let contrast: Float
|
||
|
||
public let saturation: Float
|
||
|
||
public let selectRatio: ZLImageClipRatio?
|
||
|
||
public let selectFilter: ZLFilter?
|
||
|
||
public let textStickers: [(state: ZLTextStickerState, index: Int)]?
|
||
|
||
public let imageStickers: [(state: ZLImageStickerState, index: Int)]?
|
||
|
||
public init(
|
||
drawPaths: [ZLDrawPath],
|
||
mosaicPaths: [ZLMosaicPath],
|
||
editRect: CGRect?,
|
||
angle: CGFloat,
|
||
brightness: Float,
|
||
contrast: Float,
|
||
saturation: Float,
|
||
selectRatio: ZLImageClipRatio?,
|
||
selectFilter: ZLFilter,
|
||
textStickers: [(state: ZLTextStickerState, index: Int)]?,
|
||
imageStickers: [(state: ZLImageStickerState, index: Int)]?
|
||
) {
|
||
self.drawPaths = drawPaths
|
||
self.mosaicPaths = mosaicPaths
|
||
self.editRect = editRect
|
||
self.angle = angle
|
||
self.brightness = brightness
|
||
self.contrast = contrast
|
||
self.saturation = saturation
|
||
self.selectRatio = selectRatio
|
||
self.selectFilter = selectFilter
|
||
self.textStickers = textStickers
|
||
self.imageStickers = imageStickers
|
||
super.init()
|
||
}
|
||
}
|
||
|
||
open class ZLEditImageViewController: UIViewController {
|
||
static let maxDrawLineImageWidth: CGFloat = 600
|
||
|
||
static let shadowColorFrom = UIColor.black.withAlphaComponent(0.35).cgColor
|
||
|
||
static let shadowColorTo = UIColor.clear.cgColor
|
||
|
||
static let ashbinSize = CGSize(width: 160, height: 80)
|
||
|
||
private let tools: [ZLEditImageConfiguration.EditTool]
|
||
|
||
private let adjustTools: [ZLEditImageConfiguration.AdjustTool]
|
||
|
||
private var animate = false
|
||
|
||
private var originalImage: UIImage
|
||
|
||
// 图片可编辑rect
|
||
private var editRect: CGRect
|
||
|
||
private var selectRatio: ZLImageClipRatio?
|
||
|
||
private var editImage: UIImage
|
||
|
||
private var editImageWithoutAdjust: UIImage
|
||
|
||
private var editImageAdjustRef: UIImage?
|
||
|
||
private lazy var containerView: UIView = {
|
||
let view = UIView()
|
||
view.clipsToBounds = true
|
||
return view
|
||
}()
|
||
|
||
// Show image.
|
||
private lazy var imageView: UIImageView = {
|
||
let view = UIImageView(image: originalImage)
|
||
view.contentMode = .scaleAspectFit
|
||
view.clipsToBounds = true
|
||
view.backgroundColor = .black
|
||
return view
|
||
}()
|
||
|
||
// Show draw lines.
|
||
private lazy var drawingImageView: UIImageView = {
|
||
let view = UIImageView()
|
||
view.contentMode = .scaleAspectFit
|
||
view.isUserInteractionEnabled = true
|
||
return view
|
||
}()
|
||
|
||
// Show text and image stickers.
|
||
private lazy var stickersContainer = UIView()
|
||
|
||
// 处理好的马赛克图片
|
||
private var mosaicImage: UIImage?
|
||
|
||
// 显示马赛克图片的layer
|
||
private var mosaicImageLayer: CALayer?
|
||
|
||
// 显示马赛克图片的layer的mask
|
||
private var mosaicImageLayerMaskLayer: CAShapeLayer?
|
||
|
||
private var selectedTool: ZLEditImageConfiguration.EditTool?
|
||
|
||
private var selectedAdjustTool: ZLEditImageConfiguration.AdjustTool?
|
||
|
||
private lazy var editToolCollectionView: UICollectionView = {
|
||
let layout = ZLCollectionViewFlowLayout()
|
||
layout.itemSize = CGSize(width: 30, height: 30)
|
||
layout.minimumLineSpacing = 20
|
||
layout.minimumInteritemSpacing = 20
|
||
layout.scrollDirection = .horizontal
|
||
|
||
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||
view.backgroundColor = .clear
|
||
view.delegate = self
|
||
view.dataSource = self
|
||
view.showsHorizontalScrollIndicator = false
|
||
ZLEditToolCell.zl.register(view)
|
||
|
||
return view
|
||
}()
|
||
|
||
private var drawColorCollectionView: UICollectionView?
|
||
|
||
private var filterCollectionView: UICollectionView?
|
||
|
||
private var adjustCollectionView: UICollectionView?
|
||
|
||
private var adjustSlider: ZLAdjustSlider?
|
||
|
||
private let drawColors: [UIColor]
|
||
|
||
private var currentDrawColor = ZLPhotoConfiguration.default().editImageConfiguration.defaultDrawColor
|
||
|
||
private var drawPaths: [ZLDrawPath]
|
||
|
||
private var redoDrawPaths: [ZLDrawPath]
|
||
|
||
private var mosaicPaths: [ZLMosaicPath]
|
||
|
||
private var redoMosaicPaths: [ZLMosaicPath]
|
||
|
||
private let canRedo = ZLPhotoConfiguration.default().editImageConfiguration.canRedo
|
||
|
||
private var hasAdjustedImage = false
|
||
|
||
// collectionview 中的添加滤镜的小图
|
||
private var thumbnailFilterImages: [UIImage] = []
|
||
|
||
// 选择滤镜后对原图添加滤镜后的图片
|
||
private var filterImages: [String: UIImage] = [:]
|
||
|
||
private var currentFilter: ZLFilter
|
||
|
||
private var stickers: [UIView] = []
|
||
|
||
private var isScrolling = false
|
||
|
||
private var shouldLayout = true
|
||
|
||
private var isFirstSetContainerFrame = true
|
||
|
||
private var imageStickerContainerIsHidden = true
|
||
|
||
private var angle: CGFloat
|
||
|
||
private var brightness: Float
|
||
|
||
private var contrast: Float
|
||
|
||
private var saturation: Float
|
||
|
||
private lazy var panGes: UIPanGestureRecognizer = {
|
||
let pan = UIPanGestureRecognizer(target: self, action: #selector(drawAction(_:)))
|
||
pan.maximumNumberOfTouches = 1
|
||
pan.delegate = self
|
||
return pan
|
||
}()
|
||
|
||
private var toolViewStateTimer: Timer?
|
||
|
||
/// 是否允许交换图片宽高
|
||
private var shouldSwapSize: Bool {
|
||
angle.zl.toPi.truncatingRemainder(dividingBy: .pi) != 0
|
||
}
|
||
|
||
// 第一次进入界面时,布局后frame,裁剪dimiss动画使用
|
||
var originalFrame: CGRect = .zero
|
||
|
||
var imageSize: CGSize {
|
||
if shouldSwapSize {
|
||
return CGSize(width: originalImage.size.height, height: originalImage.size.width)
|
||
} else {
|
||
return originalImage.size
|
||
}
|
||
}
|
||
|
||
@objc public var drawColViewH: CGFloat = 50
|
||
|
||
@objc public var filterColViewH: CGFloat = 90
|
||
|
||
@objc public var adjustColViewH: CGFloat = 60
|
||
|
||
@objc public lazy var cancelBtn: ZLEnlargeButton = {
|
||
let btn = ZLEnlargeButton(type: .custom)
|
||
var image = UIImage.zl.getImage("zl_retake")
|
||
if isRTL() {
|
||
image = image?.imageFlippedForRightToLeftLayoutDirection()
|
||
}
|
||
btn.setImage(image, for: .normal)
|
||
btn.addTarget(self, action: #selector(cancelBtnClick), for: .touchUpInside)
|
||
btn.adjustsImageWhenHighlighted = false
|
||
btn.enlargeInset = 30
|
||
return btn
|
||
}()
|
||
|
||
@objc public lazy var mainScrollView: UIScrollView = {
|
||
let view = UIScrollView()
|
||
view.backgroundColor = .black
|
||
view.minimumZoomScale = 1
|
||
view.maximumZoomScale = 3
|
||
view.delegate = self
|
||
return view
|
||
}()
|
||
|
||
// 上方渐变阴影层
|
||
@objc public lazy var topShadowView = UIView()
|
||
|
||
@objc public lazy var topShadowLayer: CAGradientLayer = {
|
||
let layer = CAGradientLayer()
|
||
layer.colors = [ZLEditImageViewController.shadowColorFrom, ZLEditImageViewController.shadowColorTo]
|
||
layer.locations = [0, 1]
|
||
return layer
|
||
}()
|
||
|
||
// 下方渐变阴影层
|
||
@objc public lazy var bottomShadowView = UIView()
|
||
|
||
@objc public lazy var bottomShadowLayer: CAGradientLayer = {
|
||
let layer = CAGradientLayer()
|
||
layer.colors = [ZLEditImageViewController.shadowColorTo, ZLEditImageViewController.shadowColorFrom]
|
||
layer.locations = [0, 1]
|
||
return layer
|
||
}()
|
||
|
||
@objc public lazy var doneBtn: UIButton = {
|
||
let btn = UIButton(type: .custom)
|
||
btn.titleLabel?.font = ZLLayout.bottomToolTitleFont
|
||
btn.backgroundColor = .zl.bottomToolViewBtnNormalBgColor
|
||
btn.setTitle(localLanguageTextValue(.editFinish), for: .normal)
|
||
btn.setTitleColor(.zl.bottomToolViewDoneBtnNormalTitleColor, for: .normal)
|
||
btn.addTarget(self, action: #selector(doneBtnClick), for: .touchUpInside)
|
||
btn.layer.masksToBounds = true
|
||
btn.layer.cornerRadius = ZLLayout.bottomToolBtnCornerRadius
|
||
return btn
|
||
}()
|
||
|
||
@objc public lazy var revokeBtn: UIButton = {
|
||
let btn = UIButton(type: .custom)
|
||
btn.setImage(.zl.getImage("zl_revoke_disable"), for: .disabled)
|
||
btn.setImage(.zl.getImage("zl_revoke"), for: .normal)
|
||
btn.adjustsImageWhenHighlighted = false
|
||
btn.isEnabled = false
|
||
btn.isHidden = true
|
||
btn.addTarget(self, action: #selector(revokeBtnClick), for: .touchUpInside)
|
||
return btn
|
||
}()
|
||
|
||
@objc public var redoBtn: UIButton?
|
||
|
||
@objc public lazy var ashbinView: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = .zl.trashCanBackgroundNormalColor
|
||
view.layer.cornerRadius = 15
|
||
view.layer.masksToBounds = true
|
||
view.isHidden = true
|
||
return view
|
||
}()
|
||
|
||
@objc public lazy var ashbinImgView = UIImageView(image: .zl.getImage("zl_ashbin"), highlightedImage: .zl.getImage("zl_ashbin_open"))
|
||
|
||
@objc public var drawLineWidth: CGFloat = 5
|
||
|
||
@objc public var mosaicLineWidth: CGFloat = 25
|
||
|
||
@objc public var editFinishBlock: ((UIImage, ZLEditImageModel?) -> Void)?
|
||
|
||
@objc public var cancelEditBlock: (() -> Void)?
|
||
|
||
override public var prefersStatusBarHidden: Bool {
|
||
return true
|
||
}
|
||
|
||
override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||
deviceIsiPhone() ? .portrait : .all
|
||
}
|
||
|
||
deinit {
|
||
cleanToolViewStateTimer()
|
||
zl_debugPrint("ZLEditImageViewController deinit")
|
||
}
|
||
|
||
@objc public class func showEditImageVC(
|
||
parentVC: UIViewController?,
|
||
animate: Bool = false,
|
||
image: UIImage,
|
||
editModel: ZLEditImageModel? = nil,
|
||
cancel: (() -> Void)? = nil,
|
||
completion: ((UIImage, ZLEditImageModel?) -> Void)?
|
||
) {
|
||
let tools = ZLPhotoConfiguration.default().editImageConfiguration.tools
|
||
if ZLPhotoConfiguration.default().showClipDirectlyIfOnlyHasClipTool,
|
||
tools.count == 1,
|
||
tools.contains(.clip) {
|
||
let vc = ZLClipImageViewController(image: image, editRect: editModel?.editRect, angle: editModel?.angle ?? 0, selectRatio: editModel?.selectRatio)
|
||
vc.clipDoneBlock = { angle, editRect, ratio in
|
||
let m = ZLEditImageModel(
|
||
drawPaths: [],
|
||
mosaicPaths: [],
|
||
editRect: editRect,
|
||
angle: angle,
|
||
brightness: 0,
|
||
contrast: 0,
|
||
saturation: 0,
|
||
selectRatio: ratio,
|
||
selectFilter: .normal,
|
||
textStickers: nil,
|
||
imageStickers: nil
|
||
)
|
||
completion?(image.zl.clipImage(angle: angle, editRect: editRect, isCircle: ratio.isCircle) ?? image, m)
|
||
}
|
||
vc.cancelClipBlock = cancel
|
||
vc.animate = animate
|
||
vc.modalPresentationStyle = .fullScreen
|
||
parentVC?.present(vc, animated: animate, completion: nil)
|
||
} else {
|
||
let vc = ZLEditImageViewController(image: image, editModel: editModel)
|
||
vc.editFinishBlock = { ei, editImageModel in
|
||
completion?(ei, editImageModel)
|
||
}
|
||
vc.cancelEditBlock = cancel
|
||
vc.animate = animate
|
||
vc.modalPresentationStyle = .fullScreen
|
||
parentVC?.present(vc, animated: animate, completion: nil)
|
||
}
|
||
}
|
||
|
||
@objc public init(image: UIImage, editModel: ZLEditImageModel? = nil) {
|
||
var image = image
|
||
if image.scale != 1,
|
||
let cgImage = image.cgImage {
|
||
image = image.zl.resize_vI(
|
||
CGSize(width: cgImage.width, height: cgImage.height),
|
||
scale: 1
|
||
) ?? image
|
||
}
|
||
|
||
let editConfig = ZLPhotoConfiguration.default().editImageConfiguration
|
||
|
||
originalImage = image.zl.fixOrientation()
|
||
editImage = originalImage
|
||
editImageWithoutAdjust = originalImage
|
||
editRect = editModel?.editRect ?? CGRect(origin: .zero, size: image.size)
|
||
drawColors = editConfig.drawColors
|
||
currentFilter = editModel?.selectFilter ?? .normal
|
||
drawPaths = editModel?.drawPaths ?? []
|
||
redoDrawPaths = drawPaths
|
||
mosaicPaths = editModel?.mosaicPaths ?? []
|
||
redoMosaicPaths = mosaicPaths
|
||
angle = editModel?.angle ?? 0
|
||
brightness = editModel?.brightness ?? 0
|
||
contrast = editModel?.contrast ?? 0
|
||
saturation = editModel?.saturation ?? 0
|
||
selectRatio = editModel?.selectRatio
|
||
|
||
var ts = editConfig.tools
|
||
if ts.contains(.imageSticker), editConfig.imageStickerContainerView == nil {
|
||
ts.removeAll { $0 == .imageSticker }
|
||
}
|
||
tools = ts
|
||
adjustTools = editConfig.adjustTools
|
||
selectedAdjustTool = editConfig.adjustTools.first
|
||
|
||
super.init(nibName: nil, bundle: nil)
|
||
|
||
if !drawColors.contains(currentDrawColor) {
|
||
currentDrawColor = drawColors.first!
|
||
}
|
||
|
||
let teStic = editModel?.textStickers ?? []
|
||
let imStic = editModel?.imageStickers ?? []
|
||
|
||
var stickers: [UIView?] = Array(repeating: nil, count: teStic.count + imStic.count)
|
||
teStic.forEach { cache in
|
||
let v = ZLTextStickerView(state: cache.state)
|
||
stickers[cache.index] = v
|
||
}
|
||
imStic.forEach { cache in
|
||
let v = ZLImageStickerView(state: cache.state)
|
||
stickers[cache.index] = v
|
||
}
|
||
|
||
self.stickers = stickers.compactMap { $0 }
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
public required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override open func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
|
||
setupUI()
|
||
|
||
rotationImageView()
|
||
if tools.contains(.filter) {
|
||
generateFilterImages()
|
||
}
|
||
}
|
||
|
||
override open func viewDidLayoutSubviews() {
|
||
super.viewDidLayoutSubviews()
|
||
guard shouldLayout else {
|
||
return
|
||
}
|
||
shouldLayout = false
|
||
zl_debugPrint("edit image layout subviews")
|
||
var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
|
||
if #available(iOS 11.0, *) {
|
||
insets = self.view.safeAreaInsets
|
||
}
|
||
insets.top = max(20, insets.top)
|
||
|
||
mainScrollView.frame = view.bounds
|
||
resetContainerViewFrame()
|
||
|
||
topShadowView.frame = CGRect(x: 0, y: 0, width: view.zl.width, height: 150)
|
||
topShadowLayer.frame = topShadowView.bounds
|
||
if isRTL() {
|
||
cancelBtn.frame = CGRect(x: view.zl.width - 20 - 28, y: 60, width: 28, height: 28)
|
||
} else {
|
||
cancelBtn.frame = CGRect(x: 20, y: 60, width: 28, height: 28)
|
||
}
|
||
|
||
bottomShadowView.frame = CGRect(x: 0, y: view.zl.height - 150 - insets.bottom, width: view.zl.width, height: 150 + insets.bottom)
|
||
bottomShadowLayer.frame = bottomShadowView.bounds
|
||
|
||
if canRedo, let redoBtn = redoBtn {
|
||
redoBtn.frame = CGRect(x: view.zl.width - 15 - 35, y: 40, width: 35, height: 30)
|
||
revokeBtn.frame = CGRect(x: redoBtn.zl.left - 10 - 35, y: 40, width: 35, height: 30)
|
||
} else {
|
||
revokeBtn.frame = CGRect(x: view.zl.width - 15 - 35, y: 40, width: 35, height: 30)
|
||
}
|
||
drawColorCollectionView?.frame = CGRect(x: 20, y: 30, width: revokeBtn.zl.left - 20 - 10, height: drawColViewH)
|
||
|
||
adjustCollectionView?.frame = CGRect(x: 20, y: 20, width: view.zl.width - 40, height: adjustColViewH)
|
||
if ZLPhotoUIConfiguration.default().adjustSliderType == .vertical {
|
||
adjustSlider?.frame = CGRect(x: view.zl.width - 60, y: view.zl.height / 2 - 100, width: 60, height: 200)
|
||
} else {
|
||
let sliderHeight: CGFloat = 60
|
||
let sliderWidth = UIDevice.current.userInterfaceIdiom == .phone ? view.zl.width - 100 : view.zl.width / 2
|
||
adjustSlider?.frame = CGRect(
|
||
x: (view.zl.width - sliderWidth) / 2,
|
||
y: bottomShadowView.zl.top - sliderHeight,
|
||
width: sliderWidth,
|
||
height: sliderHeight
|
||
)
|
||
}
|
||
|
||
filterCollectionView?.frame = CGRect(x: 20, y: 0, width: view.zl.width - 40, height: filterColViewH)
|
||
|
||
ashbinView.frame = CGRect(
|
||
x: (view.zl.width - Self.ashbinSize.width) / 2,
|
||
y: view.zl.height - Self.ashbinSize.height - 40,
|
||
width: Self.ashbinSize.width,
|
||
height: Self.ashbinSize.height
|
||
)
|
||
|
||
ashbinImgView.frame = CGRect(
|
||
x: (Self.ashbinSize.width - 25) / 2,
|
||
y: 15,
|
||
width: 25,
|
||
height: 25
|
||
)
|
||
|
||
let toolY: CGFloat = 95
|
||
|
||
let doneBtnH = ZLLayout.bottomToolBtnH
|
||
let doneBtnW = localLanguageTextValue(.editFinish).zl.boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: doneBtnH)).width + 20
|
||
doneBtn.frame = CGRect(x: view.zl.width - 20 - doneBtnW, y: toolY - 2, width: doneBtnW, height: doneBtnH)
|
||
|
||
editToolCollectionView.frame = CGRect(x: 20, y: toolY, width: view.zl.width - 20 - 20 - doneBtnW - 20, height: 30)
|
||
|
||
if !drawPaths.isEmpty {
|
||
drawLine()
|
||
}
|
||
if !mosaicPaths.isEmpty {
|
||
generateNewMosaicImage()
|
||
}
|
||
|
||
if let index = drawColors.firstIndex(where: { $0 == self.currentDrawColor }) {
|
||
drawColorCollectionView?.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: false)
|
||
}
|
||
}
|
||
|
||
override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||
super.viewWillTransition(to: size, with: coordinator)
|
||
shouldLayout = true
|
||
}
|
||
|
||
private func generateFilterImages() {
|
||
let size: CGSize
|
||
let ratio = (originalImage.size.width / originalImage.size.height)
|
||
let fixLength: CGFloat = 200
|
||
if ratio >= 1 {
|
||
size = CGSize(width: fixLength * ratio, height: fixLength)
|
||
} else {
|
||
size = CGSize(width: fixLength, height: fixLength / ratio)
|
||
}
|
||
let thumbnailImage = originalImage.zl.resize_vI(size) ?? originalImage
|
||
|
||
DispatchQueue.global().async {
|
||
let filters = ZLPhotoConfiguration.default().editImageConfiguration.filters
|
||
self.thumbnailFilterImages = filters.map { $0.applier?(thumbnailImage) ?? thumbnailImage }
|
||
|
||
ZLMainAsync {
|
||
self.filterCollectionView?.reloadData()
|
||
self.filterCollectionView?.performBatchUpdates {} completion: { _ in
|
||
if let index = filters.firstIndex(where: { $0 == self.currentFilter }) {
|
||
self.filterCollectionView?.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: false)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private func resetContainerViewFrame() {
|
||
mainScrollView.setZoomScale(1, animated: true)
|
||
imageView.image = editImage
|
||
|
||
let editSize = editRect.size
|
||
let scrollViewSize = mainScrollView.frame.size
|
||
let ratio = min(scrollViewSize.width / editSize.width, scrollViewSize.height / editSize.height)
|
||
let w = ratio * editSize.width * mainScrollView.zoomScale
|
||
let h = ratio * editSize.height * mainScrollView.zoomScale
|
||
|
||
let imageRatio = originalImage.size.width / originalImage.size.height
|
||
let y: CGFloat
|
||
// 从相机进入,且竖屏拍照,才做适配
|
||
if isFirstSetContainerFrame,
|
||
presentingViewController is ZLCustomCamera,
|
||
imageRatio < 1 {
|
||
let cameraRatio: CGFloat = 16 / 9
|
||
let layerH = min(view.zl.width * cameraRatio, view.zl.height)
|
||
|
||
if isSmallScreen() {
|
||
y = deviceIsFringeScreen() ? min(94, view.zl.height - layerH) : 0
|
||
} else {
|
||
y = 0
|
||
}
|
||
} else {
|
||
y = max(0, (scrollViewSize.height - h) / 2)
|
||
}
|
||
|
||
isFirstSetContainerFrame = false
|
||
|
||
containerView.frame = CGRect(x: max(0, (scrollViewSize.width - w) / 2), y: y, width: w, height: h)
|
||
mainScrollView.contentSize = containerView.frame.size
|
||
|
||
if selectRatio?.isCircle == true {
|
||
let mask = CAShapeLayer()
|
||
let path = UIBezierPath(arcCenter: CGPoint(x: w / 2, y: h / 2), radius: w / 2, startAngle: 0, endAngle: .pi * 2, clockwise: true)
|
||
mask.path = path.cgPath
|
||
containerView.layer.mask = mask
|
||
} else {
|
||
containerView.layer.mask = nil
|
||
}
|
||
let scaleImageOrigin = CGPoint(x: -editRect.origin.x * ratio, y: -editRect.origin.y * ratio)
|
||
let scaleImageSize = CGSize(width: imageSize.width * ratio, height: imageSize.height * ratio)
|
||
imageView.frame = CGRect(origin: scaleImageOrigin, size: scaleImageSize)
|
||
mosaicImageLayer?.frame = imageView.bounds
|
||
mosaicImageLayerMaskLayer?.frame = imageView.bounds
|
||
drawingImageView.frame = imageView.frame
|
||
stickersContainer.frame = imageView.frame
|
||
|
||
// 针对于长图的优化
|
||
if (editRect.height / editRect.width) > (view.frame.height / view.frame.width * 1.1) {
|
||
let widthScale = view.frame.width / w
|
||
mainScrollView.maximumZoomScale = widthScale
|
||
mainScrollView.zoomScale = widthScale
|
||
mainScrollView.contentOffset = .zero
|
||
} else if editRect.width / editRect.height > 1 {
|
||
mainScrollView.maximumZoomScale = max(3, view.frame.height / h)
|
||
}
|
||
|
||
originalFrame = view.convert(containerView.frame, from: mainScrollView)
|
||
isScrolling = false
|
||
}
|
||
|
||
private func setupUI() {
|
||
view.backgroundColor = .black
|
||
|
||
view.addSubview(mainScrollView)
|
||
mainScrollView.addSubview(containerView)
|
||
containerView.addSubview(imageView)
|
||
containerView.addSubview(drawingImageView)
|
||
containerView.addSubview(stickersContainer)
|
||
|
||
topShadowView.layer.addSublayer(topShadowLayer)
|
||
view.addSubview(topShadowView)
|
||
topShadowView.addSubview(cancelBtn)
|
||
|
||
bottomShadowView.layer.addSublayer(bottomShadowLayer)
|
||
view.addSubview(bottomShadowView)
|
||
bottomShadowView.addSubview(editToolCollectionView)
|
||
bottomShadowView.addSubview(doneBtn)
|
||
|
||
if tools.contains(.draw) {
|
||
let drawColorLayout = ZLCollectionViewFlowLayout()
|
||
let drawColorItemWidth: CGFloat = 36
|
||
drawColorLayout.itemSize = CGSize(width: drawColorItemWidth, height: drawColorItemWidth)
|
||
drawColorLayout.minimumLineSpacing = 0
|
||
drawColorLayout.minimumInteritemSpacing = 0
|
||
drawColorLayout.scrollDirection = .horizontal
|
||
let drawColorTopBottomInset = (drawColViewH - drawColorItemWidth) / 2
|
||
drawColorLayout.sectionInset = UIEdgeInsets(top: drawColorTopBottomInset, left: 0, bottom: drawColorTopBottomInset, right: 0)
|
||
|
||
let drawCV = UICollectionView(frame: .zero, collectionViewLayout: drawColorLayout)
|
||
drawCV.backgroundColor = .clear
|
||
drawCV.delegate = self
|
||
drawCV.dataSource = self
|
||
drawCV.isHidden = true
|
||
bottomShadowView.addSubview(drawCV)
|
||
|
||
ZLDrawColorCell.zl.register(drawCV)
|
||
drawColorCollectionView = drawCV
|
||
}
|
||
|
||
if tools.contains(.filter) {
|
||
if let applier = currentFilter.applier {
|
||
let image = applier(originalImage)
|
||
editImage = image
|
||
editImageWithoutAdjust = image
|
||
filterImages[currentFilter.name] = image
|
||
}
|
||
|
||
let filterLayout = ZLCollectionViewFlowLayout()
|
||
filterLayout.itemSize = CGSize(width: filterColViewH - 30, height: filterColViewH - 10)
|
||
filterLayout.minimumLineSpacing = 15
|
||
filterLayout.minimumInteritemSpacing = 15
|
||
filterLayout.scrollDirection = .horizontal
|
||
filterLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
|
||
|
||
let filterCV = UICollectionView(frame: .zero, collectionViewLayout: filterLayout)
|
||
filterCV.backgroundColor = .clear
|
||
filterCV.delegate = self
|
||
filterCV.dataSource = self
|
||
filterCV.isHidden = true
|
||
bottomShadowView.addSubview(filterCV)
|
||
|
||
ZLFilterImageCell.zl.register(filterCV)
|
||
filterCollectionView = filterCV
|
||
}
|
||
|
||
if tools.contains(.adjust) {
|
||
editImage = editImage.zl.adjust(brightness: brightness, contrast: contrast, saturation: saturation) ?? editImage
|
||
|
||
let adjustLayout = ZLCollectionViewFlowLayout()
|
||
adjustLayout.itemSize = CGSize(width: adjustColViewH, height: adjustColViewH)
|
||
adjustLayout.minimumLineSpacing = 10
|
||
adjustLayout.minimumInteritemSpacing = 10
|
||
adjustLayout.scrollDirection = .horizontal
|
||
|
||
let adjustCV = UICollectionView(frame: .zero, collectionViewLayout: adjustLayout)
|
||
adjustCV.backgroundColor = .clear
|
||
adjustCV.delegate = self
|
||
adjustCV.dataSource = self
|
||
adjustCV.isHidden = true
|
||
adjustCV.showsHorizontalScrollIndicator = false
|
||
bottomShadowView.addSubview(adjustCV)
|
||
|
||
ZLAdjustToolCell.zl.register(adjustCV)
|
||
adjustCollectionView = adjustCV
|
||
|
||
adjustSlider = ZLAdjustSlider()
|
||
if let selectedAdjustTool = selectedAdjustTool {
|
||
changeAdjustTool(selectedAdjustTool)
|
||
}
|
||
adjustSlider?.beginAdjust = {}
|
||
adjustSlider?.valueChanged = { [weak self] value in
|
||
self?.adjustValueChanged(value)
|
||
}
|
||
adjustSlider?.endAdjust = { [weak self] in
|
||
self?.hasAdjustedImage = true
|
||
}
|
||
adjustSlider?.isHidden = true
|
||
view.addSubview(adjustSlider!)
|
||
}
|
||
|
||
bottomShadowView.addSubview(revokeBtn)
|
||
if canRedo {
|
||
let btn = UIButton(type: .custom)
|
||
btn.setImage(.zl.getImage("zl_redo_disable"), for: .disabled)
|
||
btn.setImage(.zl.getImage("zl_redo"), for: .normal)
|
||
btn.adjustsImageWhenHighlighted = false
|
||
btn.isEnabled = false
|
||
btn.isHidden = true
|
||
btn.addTarget(self, action: #selector(redoBtnClick), for: .touchUpInside)
|
||
bottomShadowView.addSubview(btn)
|
||
redoBtn = btn
|
||
}
|
||
|
||
view.addSubview(ashbinView)
|
||
ashbinView.addSubview(ashbinImgView)
|
||
|
||
let asbinTipLabel = UILabel(frame: CGRect(x: 0, y: Self.ashbinSize.height - 34, width: Self.ashbinSize.width, height: 34))
|
||
asbinTipLabel.font = .zl.font(ofSize: 12)
|
||
asbinTipLabel.textAlignment = .center
|
||
asbinTipLabel.textColor = .white
|
||
asbinTipLabel.text = localLanguageTextValue(.textStickerRemoveTips)
|
||
asbinTipLabel.numberOfLines = 2
|
||
asbinTipLabel.lineBreakMode = .byCharWrapping
|
||
ashbinView.addSubview(asbinTipLabel)
|
||
|
||
if tools.contains(.mosaic) {
|
||
mosaicImage = editImage.zl.mosaicImage()
|
||
|
||
mosaicImageLayer = CALayer()
|
||
mosaicImageLayer?.contents = mosaicImage?.cgImage
|
||
imageView.layer.addSublayer(mosaicImageLayer!)
|
||
|
||
mosaicImageLayerMaskLayer = CAShapeLayer()
|
||
mosaicImageLayerMaskLayer?.strokeColor = UIColor.blue.cgColor
|
||
mosaicImageLayerMaskLayer?.fillColor = nil
|
||
mosaicImageLayerMaskLayer?.lineCap = .round
|
||
mosaicImageLayerMaskLayer?.lineJoin = .round
|
||
imageView.layer.addSublayer(mosaicImageLayerMaskLayer!)
|
||
|
||
mosaicImageLayer?.mask = mosaicImageLayerMaskLayer
|
||
}
|
||
|
||
if tools.contains(.imageSticker) {
|
||
let imageStickerView = ZLPhotoConfiguration.default().editImageConfiguration.imageStickerContainerView
|
||
imageStickerView?.hideBlock = { [weak self] in
|
||
self?.setToolView(show: true)
|
||
self?.imageStickerContainerIsHidden = true
|
||
}
|
||
|
||
imageStickerView?.selectImageBlock = { [weak self] image in
|
||
self?.addImageStickerView(image)
|
||
}
|
||
}
|
||
|
||
let tapGes = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
|
||
tapGes.delegate = self
|
||
view.addGestureRecognizer(tapGes)
|
||
|
||
view.addGestureRecognizer(panGes)
|
||
mainScrollView.panGestureRecognizer.require(toFail: panGes)
|
||
|
||
stickers.forEach { view in
|
||
self.stickersContainer.addSubview(view)
|
||
if let tv = view as? ZLTextStickerView {
|
||
tv.frame = tv.originFrame
|
||
self.configTextSticker(tv)
|
||
} else if let iv = view as? ZLImageStickerView {
|
||
iv.frame = iv.originFrame
|
||
self.configImageSticker(iv)
|
||
}
|
||
}
|
||
}
|
||
|
||
private func rotationImageView() {
|
||
let transform = CGAffineTransform(rotationAngle: angle.zl.toPi)
|
||
imageView.transform = transform
|
||
drawingImageView.transform = transform
|
||
stickersContainer.transform = transform
|
||
}
|
||
|
||
@objc private func cancelBtnClick() {
|
||
dismiss(animated: animate) {
|
||
self.cancelEditBlock?()
|
||
}
|
||
}
|
||
|
||
private func drawBtnClick() {
|
||
let isSelected = selectedTool != .draw
|
||
if isSelected {
|
||
selectedTool = .draw
|
||
} else {
|
||
selectedTool = nil
|
||
}
|
||
drawColorCollectionView?.isHidden = !isSelected
|
||
revokeBtn.isHidden = !isSelected
|
||
revokeBtn.isEnabled = !drawPaths.isEmpty
|
||
redoBtn?.isHidden = !isSelected
|
||
redoBtn?.isEnabled = drawPaths.count != redoDrawPaths.count
|
||
filterCollectionView?.isHidden = true
|
||
adjustCollectionView?.isHidden = true
|
||
adjustSlider?.isHidden = true
|
||
}
|
||
|
||
private func clipBtnClick() {
|
||
let currentEditImage = buildImage()
|
||
let vc = ZLClipImageViewController(image: currentEditImage, editRect: editRect, angle: angle, selectRatio: selectRatio)
|
||
let rect = mainScrollView.convert(containerView.frame, to: view)
|
||
vc.presentAnimateFrame = rect
|
||
vc.presentAnimateImage = currentEditImage.zl.clipImage(angle: angle, editRect: editRect, isCircle: selectRatio?.isCircle ?? false)
|
||
vc.modalPresentationStyle = .fullScreen
|
||
|
||
vc.clipDoneBlock = { [weak self] angle, editFrame, selectRatio in
|
||
guard let `self` = self else { return }
|
||
let oldAngle = self.angle
|
||
let oldContainerSize = self.stickersContainer.frame.size
|
||
if self.angle != angle {
|
||
self.angle = angle
|
||
self.rotationImageView()
|
||
}
|
||
self.editRect = editFrame
|
||
self.selectRatio = selectRatio
|
||
self.resetContainerViewFrame()
|
||
self.reCalculateStickersFrame(oldContainerSize, oldAngle, angle)
|
||
}
|
||
|
||
vc.cancelClipBlock = { [weak self] () in
|
||
self?.resetContainerViewFrame()
|
||
}
|
||
|
||
present(vc, animated: false) {
|
||
self.mainScrollView.alpha = 0
|
||
self.topShadowView.alpha = 0
|
||
self.bottomShadowView.alpha = 0
|
||
self.adjustSlider?.alpha = 0
|
||
}
|
||
}
|
||
|
||
private func imageStickerBtnClick() {
|
||
ZLPhotoConfiguration.default().editImageConfiguration.imageStickerContainerView?.show(in: view)
|
||
setToolView(show: false)
|
||
imageStickerContainerIsHidden = false
|
||
}
|
||
|
||
private func textStickerBtnClick() {
|
||
showInputTextVC { [weak self] text, textColor, image, style in
|
||
guard !text.isEmpty, let image = image else { return }
|
||
self?.addTextStickersView(text, textColor: textColor, image: image, style: style)
|
||
}
|
||
}
|
||
|
||
private func mosaicBtnClick() {
|
||
let isSelected = selectedTool != .mosaic
|
||
if isSelected {
|
||
selectedTool = .mosaic
|
||
} else {
|
||
selectedTool = nil
|
||
}
|
||
|
||
generateNewMosaicLayerAfterAdjust()
|
||
|
||
drawColorCollectionView?.isHidden = true
|
||
filterCollectionView?.isHidden = true
|
||
adjustCollectionView?.isHidden = true
|
||
adjustSlider?.isHidden = true
|
||
revokeBtn.isHidden = !isSelected
|
||
revokeBtn.isEnabled = !mosaicPaths.isEmpty
|
||
redoBtn?.isHidden = !isSelected
|
||
redoBtn?.isEnabled = mosaicPaths.count != redoMosaicPaths.count
|
||
}
|
||
|
||
private func filterBtnClick() {
|
||
let isSelected = selectedTool != .filter
|
||
if isSelected {
|
||
selectedTool = .filter
|
||
} else {
|
||
selectedTool = nil
|
||
}
|
||
|
||
drawColorCollectionView?.isHidden = true
|
||
revokeBtn.isHidden = true
|
||
redoBtn?.isHidden = true
|
||
filterCollectionView?.isHidden = !isSelected
|
||
adjustCollectionView?.isHidden = true
|
||
adjustSlider?.isHidden = true
|
||
}
|
||
|
||
private func adjustBtnClick() {
|
||
let isSelected = selectedTool != .adjust
|
||
if isSelected {
|
||
selectedTool = .adjust
|
||
} else {
|
||
selectedTool = nil
|
||
}
|
||
|
||
drawColorCollectionView?.isHidden = true
|
||
revokeBtn.isHidden = true
|
||
redoBtn?.isHidden = true
|
||
filterCollectionView?.isHidden = true
|
||
adjustCollectionView?.isHidden = !isSelected
|
||
adjustSlider?.isHidden = !isSelected
|
||
|
||
generateAdjustImageRef()
|
||
}
|
||
|
||
private func changeAdjustTool(_ tool: ZLEditImageConfiguration.AdjustTool) {
|
||
selectedAdjustTool = tool
|
||
|
||
switch tool {
|
||
case .brightness:
|
||
adjustSlider?.value = brightness
|
||
case .contrast:
|
||
adjustSlider?.value = contrast
|
||
case .saturation:
|
||
adjustSlider?.value = saturation
|
||
}
|
||
|
||
generateAdjustImageRef()
|
||
}
|
||
|
||
@objc private func doneBtnClick() {
|
||
var textStickers: [(ZLTextStickerState, Int)] = []
|
||
var imageStickers: [(ZLImageStickerState, Int)] = []
|
||
for (index, view) in stickersContainer.subviews.enumerated() {
|
||
if let ts = view as? ZLTextStickerView, !ts.text.isEmpty {
|
||
textStickers.append((ts.state, index))
|
||
} else if let ts = view as? ZLImageStickerView {
|
||
imageStickers.append((ts.state, index))
|
||
}
|
||
}
|
||
|
||
var hasEdit = true
|
||
if drawPaths.isEmpty, editRect.size == imageSize, angle == 0, mosaicPaths.isEmpty, imageStickers.isEmpty, textStickers.isEmpty, currentFilter.applier == nil, brightness == 0, contrast == 0, saturation == 0 {
|
||
hasEdit = false
|
||
}
|
||
|
||
var resImage = originalImage
|
||
var editModel: ZLEditImageModel?
|
||
if hasEdit {
|
||
let hud = ZLProgressHUD.show()
|
||
|
||
resImage = buildImage()
|
||
resImage = resImage.zl.clipImage(angle: angle, editRect: editRect, isCircle: selectRatio?.isCircle ?? false) ?? resImage
|
||
editModel = ZLEditImageModel(
|
||
drawPaths: drawPaths,
|
||
mosaicPaths: mosaicPaths,
|
||
editRect: editRect,
|
||
angle: angle,
|
||
brightness: brightness,
|
||
contrast: contrast,
|
||
saturation: saturation,
|
||
selectRatio: selectRatio,
|
||
selectFilter: currentFilter,
|
||
textStickers: textStickers,
|
||
imageStickers: imageStickers
|
||
)
|
||
|
||
hud.hide()
|
||
}
|
||
|
||
dismiss(animated: animate) {
|
||
self.editFinishBlock?(resImage, editModel)
|
||
}
|
||
}
|
||
|
||
@objc private func revokeBtnClick() {
|
||
if selectedTool == .draw {
|
||
guard !drawPaths.isEmpty else {
|
||
return
|
||
}
|
||
drawPaths.removeLast()
|
||
revokeBtn.isEnabled = !drawPaths.isEmpty
|
||
redoBtn?.isEnabled = drawPaths.count != redoDrawPaths.count
|
||
drawLine()
|
||
} else if selectedTool == .mosaic {
|
||
guard !mosaicPaths.isEmpty else {
|
||
return
|
||
}
|
||
mosaicPaths.removeLast()
|
||
revokeBtn.isEnabled = !mosaicPaths.isEmpty
|
||
redoBtn?.isEnabled = mosaicPaths.count != redoMosaicPaths.count
|
||
generateNewMosaicImage()
|
||
}
|
||
}
|
||
|
||
@objc private func redoBtnClick() {
|
||
if selectedTool == .draw {
|
||
guard drawPaths.count < redoDrawPaths.count else {
|
||
return
|
||
}
|
||
let path = redoDrawPaths[drawPaths.count]
|
||
drawPaths.append(path)
|
||
revokeBtn.isEnabled = !drawPaths.isEmpty
|
||
redoBtn?.isEnabled = drawPaths.count != redoDrawPaths.count
|
||
drawLine()
|
||
} else if selectedTool == .mosaic {
|
||
guard mosaicPaths.count < redoMosaicPaths.count else {
|
||
return
|
||
}
|
||
let path = redoMosaicPaths[mosaicPaths.count]
|
||
mosaicPaths.append(path)
|
||
revokeBtn.isEnabled = !mosaicPaths.isEmpty
|
||
redoBtn?.isEnabled = mosaicPaths.count != redoMosaicPaths.count
|
||
generateNewMosaicImage()
|
||
}
|
||
}
|
||
|
||
@objc private func tapAction(_ tap: UITapGestureRecognizer) {
|
||
if bottomShadowView.alpha == 1 {
|
||
setToolView(show: false)
|
||
} else {
|
||
setToolView(show: true)
|
||
}
|
||
}
|
||
|
||
@objc private func drawAction(_ pan: UIPanGestureRecognizer) {
|
||
if selectedTool == .draw {
|
||
let point = pan.location(in: drawingImageView)
|
||
if pan.state == .began {
|
||
setToolView(show: false)
|
||
|
||
let originalRatio = min(mainScrollView.frame.width / originalImage.size.width, mainScrollView.frame.height / originalImage.size.height)
|
||
let ratio = min(mainScrollView.frame.width / editRect.width, mainScrollView.frame.height / editRect.height)
|
||
let scale = ratio / originalRatio
|
||
// 缩放到最初的size
|
||
var size = drawingImageView.frame.size
|
||
size.width /= scale
|
||
size.height /= scale
|
||
if shouldSwapSize {
|
||
swap(&size.width, &size.height)
|
||
}
|
||
|
||
var toImageScale = ZLEditImageViewController.maxDrawLineImageWidth / size.width
|
||
if editImage.size.width / editImage.size.height > 1 {
|
||
toImageScale = ZLEditImageViewController.maxDrawLineImageWidth / size.height
|
||
}
|
||
|
||
let path = ZLDrawPath(pathColor: currentDrawColor, pathWidth: drawLineWidth / mainScrollView.zoomScale, ratio: ratio / originalRatio / toImageScale, startPoint: point)
|
||
drawPaths.append(path)
|
||
redoDrawPaths = drawPaths
|
||
} else if pan.state == .changed {
|
||
let path = drawPaths.last
|
||
path?.addLine(to: point)
|
||
drawLine()
|
||
} else if pan.state == .cancelled || pan.state == .ended {
|
||
setToolView(show: true, delay: 0.5)
|
||
revokeBtn.isEnabled = !drawPaths.isEmpty
|
||
redoBtn?.isEnabled = false
|
||
}
|
||
} else if selectedTool == .mosaic {
|
||
let point = pan.location(in: imageView)
|
||
if pan.state == .began {
|
||
setToolView(show: false)
|
||
|
||
var actualSize = editRect.size
|
||
if shouldSwapSize {
|
||
swap(&actualSize.width, &actualSize.height)
|
||
}
|
||
let ratio = min(mainScrollView.frame.width / editRect.width, mainScrollView.frame.height / editRect.height)
|
||
|
||
let pathW = mosaicLineWidth / mainScrollView.zoomScale
|
||
let path = ZLMosaicPath(pathWidth: pathW, ratio: ratio, startPoint: point)
|
||
|
||
mosaicImageLayerMaskLayer?.lineWidth = pathW
|
||
mosaicImageLayerMaskLayer?.path = path.path.cgPath
|
||
mosaicPaths.append(path)
|
||
redoMosaicPaths = mosaicPaths
|
||
} else if pan.state == .changed {
|
||
let path = mosaicPaths.last
|
||
path?.addLine(to: point)
|
||
mosaicImageLayerMaskLayer?.path = path?.path.cgPath
|
||
} else if pan.state == .cancelled || pan.state == .ended {
|
||
setToolView(show: true, delay: 0.5)
|
||
revokeBtn.isEnabled = !mosaicPaths.isEmpty
|
||
redoBtn?.isEnabled = false
|
||
generateNewMosaicImage()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 生成一个没有调整参数前的图片
|
||
private func generateAdjustImageRef() {
|
||
editImageAdjustRef = generateNewMosaicImage(inputImage: editImageWithoutAdjust, inputMosaicImage: editImageWithoutAdjust.zl.mosaicImage())
|
||
}
|
||
|
||
private func adjustValueChanged(_ value: Float) {
|
||
guard let selectedAdjustTool = selectedAdjustTool, let editImageAdjustRef = editImageAdjustRef else {
|
||
return
|
||
}
|
||
var resultImage: UIImage?
|
||
|
||
switch selectedAdjustTool {
|
||
case .brightness:
|
||
if brightness == value {
|
||
return
|
||
}
|
||
brightness = value
|
||
resultImage = editImageAdjustRef.zl.adjust(brightness: value, contrast: contrast, saturation: saturation)
|
||
case .contrast:
|
||
if contrast == value {
|
||
return
|
||
}
|
||
contrast = value
|
||
resultImage = editImageAdjustRef.zl.adjust(brightness: brightness, contrast: value, saturation: saturation)
|
||
case .saturation:
|
||
if saturation == value {
|
||
return
|
||
}
|
||
saturation = value
|
||
resultImage = editImageAdjustRef.zl.adjust(brightness: brightness, contrast: contrast, saturation: value)
|
||
}
|
||
|
||
guard let resultImage = resultImage else {
|
||
return
|
||
}
|
||
editImage = resultImage
|
||
imageView.image = editImage
|
||
}
|
||
|
||
private func generateNewMosaicLayerAfterAdjust() {
|
||
defer {
|
||
hasAdjustedImage = false
|
||
}
|
||
|
||
guard tools.contains(.mosaic) else { return }
|
||
|
||
generateNewMosaicImageLayer()
|
||
|
||
if !mosaicPaths.isEmpty {
|
||
generateNewMosaicImage()
|
||
}
|
||
}
|
||
|
||
private func setToolView(show: Bool, delay: TimeInterval? = nil) {
|
||
cleanToolViewStateTimer()
|
||
if let delay = delay {
|
||
toolViewStateTimer = Timer.scheduledTimer(timeInterval: delay, target: ZLWeakProxy(target: self), selector: #selector(setToolViewShow_timerFunc(show:)), userInfo: ["show": show], repeats: false)
|
||
RunLoop.current.add(toolViewStateTimer!, forMode: .common)
|
||
} else {
|
||
setToolViewShow_timerFunc(show: show)
|
||
}
|
||
}
|
||
|
||
@objc private func setToolViewShow_timerFunc(show: Bool) {
|
||
var flag = show
|
||
if let toolViewStateTimer = toolViewStateTimer {
|
||
let userInfo = toolViewStateTimer.userInfo as? [String: Any]
|
||
flag = userInfo?["show"] as? Bool ?? true
|
||
cleanToolViewStateTimer()
|
||
}
|
||
topShadowView.layer.removeAllAnimations()
|
||
bottomShadowView.layer.removeAllAnimations()
|
||
adjustSlider?.layer.removeAllAnimations()
|
||
if flag {
|
||
UIView.animate(withDuration: 0.25) {
|
||
self.topShadowView.alpha = 1
|
||
self.bottomShadowView.alpha = 1
|
||
self.adjustSlider?.alpha = 1
|
||
}
|
||
} else {
|
||
UIView.animate(withDuration: 0.25) {
|
||
self.topShadowView.alpha = 0
|
||
self.bottomShadowView.alpha = 0
|
||
self.adjustSlider?.alpha = 0
|
||
}
|
||
}
|
||
}
|
||
|
||
private func cleanToolViewStateTimer() {
|
||
toolViewStateTimer?.invalidate()
|
||
toolViewStateTimer = nil
|
||
}
|
||
|
||
private func showInputTextVC(_ text: String? = nil, textColor: UIColor? = nil, style: ZLInputTextStyle = .normal, completion: @escaping ((String, UIColor, UIImage?, ZLInputTextStyle) -> Void)) {
|
||
// Calculate image displayed frame on the screen.
|
||
var r = mainScrollView.convert(view.frame, to: containerView)
|
||
r.origin.x += mainScrollView.contentOffset.x / mainScrollView.zoomScale
|
||
r.origin.y += mainScrollView.contentOffset.y / mainScrollView.zoomScale
|
||
let scale = imageSize.width / imageView.frame.width
|
||
r.origin.x *= scale
|
||
r.origin.y *= scale
|
||
r.size.width *= scale
|
||
r.size.height *= scale
|
||
let isCircle = selectRatio?.isCircle ?? false
|
||
let bgImage = buildImage()
|
||
.zl.clipImage(angle: angle, editRect: editRect, isCircle: isCircle)?
|
||
.zl.clipImage(angle: 0, editRect: r, isCircle: isCircle)
|
||
let vc = ZLInputTextViewController(image: bgImage, text: text, textColor: textColor, style: style)
|
||
|
||
vc.endInput = { text, textColor, image, style in
|
||
completion(text, textColor, image, style)
|
||
}
|
||
|
||
vc.modalPresentationStyle = .fullScreen
|
||
showDetailViewController(vc, sender: nil)
|
||
}
|
||
|
||
private func getStickerOriginFrame(_ size: CGSize) -> CGRect {
|
||
let scale = mainScrollView.zoomScale
|
||
// Calculate the display rect of container view.
|
||
let x = (mainScrollView.contentOffset.x - containerView.frame.minX) / scale
|
||
let y = (mainScrollView.contentOffset.y - containerView.frame.minY) / scale
|
||
let w = view.frame.width / scale
|
||
let h = view.frame.height / scale
|
||
// Convert to text stickers container view.
|
||
let r = containerView.convert(CGRect(x: x, y: y, width: w, height: h), to: stickersContainer)
|
||
let originFrame = CGRect(x: r.minX + (r.width - size.width) / 2, y: r.minY + (r.height - size.height) / 2, width: size.width, height: size.height)
|
||
return originFrame
|
||
}
|
||
|
||
/// Add image sticker
|
||
private func addImageStickerView(_ image: UIImage) {
|
||
let scale = mainScrollView.zoomScale
|
||
let size = ZLImageStickerView.calculateSize(image: image, width: view.frame.width)
|
||
let originFrame = getStickerOriginFrame(size)
|
||
|
||
let imageSticker = ZLImageStickerView(image: image, originScale: 1 / scale, originAngle: -angle, originFrame: originFrame)
|
||
stickersContainer.addSubview(imageSticker)
|
||
imageSticker.frame = originFrame
|
||
view.layoutIfNeeded()
|
||
|
||
configImageSticker(imageSticker)
|
||
}
|
||
|
||
/// Add text sticker
|
||
private func addTextStickersView(_ text: String, textColor: UIColor, image: UIImage, style: ZLInputTextStyle) {
|
||
guard !text.isEmpty else { return }
|
||
let scale = mainScrollView.zoomScale
|
||
let size = ZLTextStickerView.calculateSize(image: image)
|
||
let originFrame = getStickerOriginFrame(size)
|
||
|
||
let textSticker = ZLTextStickerView(text: text, textColor: textColor, style: style, image: image, originScale: 1 / scale, originAngle: -angle, originFrame: originFrame)
|
||
stickersContainer.addSubview(textSticker)
|
||
textSticker.frame = originFrame
|
||
|
||
configTextSticker(textSticker)
|
||
}
|
||
|
||
private func configTextSticker(_ textSticker: ZLTextStickerView) {
|
||
textSticker.delegate = self
|
||
mainScrollView.pinchGestureRecognizer?.require(toFail: textSticker.pinchGes)
|
||
mainScrollView.panGestureRecognizer.require(toFail: textSticker.panGes)
|
||
panGes.require(toFail: textSticker.panGes)
|
||
}
|
||
|
||
private func configImageSticker(_ imageSticker: ZLImageStickerView) {
|
||
imageSticker.delegate = self
|
||
mainScrollView.pinchGestureRecognizer?.require(toFail: imageSticker.pinchGes)
|
||
mainScrollView.panGestureRecognizer.require(toFail: imageSticker.panGes)
|
||
panGes.require(toFail: imageSticker.panGes)
|
||
}
|
||
|
||
private func reCalculateStickersFrame(_ oldSize: CGSize, _ oldAngle: CGFloat, _ newAngle: CGFloat) {
|
||
let currSize = stickersContainer.frame.size
|
||
let scale: CGFloat
|
||
if (newAngle - oldAngle).zl.toPi.truncatingRemainder(dividingBy: .pi) == 0 {
|
||
scale = currSize.width / oldSize.width
|
||
} else {
|
||
scale = currSize.height / oldSize.width
|
||
}
|
||
|
||
stickersContainer.subviews.forEach { view in
|
||
(view as? ZLStickerViewAdditional)?.addScale(scale)
|
||
}
|
||
}
|
||
|
||
private func drawLine() {
|
||
let originalRatio = min(mainScrollView.frame.width / originalImage.size.width, mainScrollView.frame.height / originalImage.size.height)
|
||
let ratio = min(mainScrollView.frame.width / editRect.width, mainScrollView.frame.height / editRect.height)
|
||
let scale = ratio / originalRatio
|
||
// 缩放到最初的size
|
||
var size = drawingImageView.frame.size
|
||
size.width /= scale
|
||
size.height /= scale
|
||
if shouldSwapSize {
|
||
swap(&size.width, &size.height)
|
||
}
|
||
var toImageScale = ZLEditImageViewController.maxDrawLineImageWidth / size.width
|
||
if editImage.size.width / editImage.size.height > 1 {
|
||
toImageScale = ZLEditImageViewController.maxDrawLineImageWidth / size.height
|
||
}
|
||
size.width *= toImageScale
|
||
size.height *= toImageScale
|
||
|
||
UIGraphicsBeginImageContextWithOptions(size, false, editImage.scale)
|
||
let context = UIGraphicsGetCurrentContext()
|
||
// 去掉锯齿
|
||
context?.setAllowsAntialiasing(true)
|
||
context?.setShouldAntialias(true)
|
||
for path in drawPaths {
|
||
path.drawPath()
|
||
}
|
||
drawingImageView.image = UIGraphicsGetImageFromCurrentImageContext()
|
||
UIGraphicsEndImageContext()
|
||
}
|
||
|
||
private func generateNewMosaicImageLayer() {
|
||
mosaicImage = editImage.zl.mosaicImage()
|
||
|
||
mosaicImageLayer?.removeFromSuperlayer()
|
||
|
||
mosaicImageLayer = CALayer()
|
||
mosaicImageLayer?.frame = imageView.bounds
|
||
mosaicImageLayer?.contents = mosaicImage?.cgImage
|
||
imageView.layer.insertSublayer(mosaicImageLayer!, below: mosaicImageLayerMaskLayer)
|
||
|
||
mosaicImageLayer?.mask = mosaicImageLayerMaskLayer
|
||
}
|
||
|
||
/// 传入inputImage 和 inputMosaicImage则代表仅想要获取新生成的mosaic图片
|
||
@discardableResult
|
||
private func generateNewMosaicImage(inputImage: UIImage? = nil, inputMosaicImage: UIImage? = nil) -> UIImage? {
|
||
let renderRect = CGRect(origin: .zero, size: originalImage.size)
|
||
|
||
UIGraphicsBeginImageContextWithOptions(originalImage.size, false, originalImage.scale)
|
||
if inputImage != nil {
|
||
inputImage?.draw(in: renderRect)
|
||
} else {
|
||
var drawImage: UIImage?
|
||
if tools.contains(.filter), let image = filterImages[currentFilter.name] {
|
||
drawImage = image
|
||
} else {
|
||
drawImage = originalImage
|
||
}
|
||
|
||
if tools.contains(.adjust), brightness != 0 || contrast != 0 || saturation != 0 {
|
||
drawImage = drawImage?.zl.adjust(brightness: brightness, contrast: contrast, saturation: saturation)
|
||
}
|
||
|
||
drawImage?.draw(in: renderRect)
|
||
}
|
||
|
||
let context = UIGraphicsGetCurrentContext()
|
||
|
||
mosaicPaths.forEach { path in
|
||
context?.move(to: path.startPoint)
|
||
path.linePoints.forEach { point in
|
||
context?.addLine(to: point)
|
||
}
|
||
context?.setLineWidth(path.path.lineWidth / path.ratio)
|
||
context?.setLineCap(.round)
|
||
context?.setLineJoin(.round)
|
||
context?.setBlendMode(.clear)
|
||
context?.strokePath()
|
||
}
|
||
|
||
var midImage = UIGraphicsGetImageFromCurrentImageContext()
|
||
UIGraphicsEndImageContext()
|
||
guard let midCgImage = midImage?.cgImage else {
|
||
return nil
|
||
}
|
||
|
||
midImage = UIImage(cgImage: midCgImage, scale: editImage.scale, orientation: .up)
|
||
|
||
UIGraphicsBeginImageContextWithOptions(originalImage.size, false, originalImage.scale)
|
||
// 由于生成的mosaic图片可能在边缘区域出现空白部分,导致合成后会有黑边,所以在最下面先画一张原图
|
||
originalImage.draw(in: renderRect)
|
||
(inputMosaicImage ?? mosaicImage)?.draw(in: renderRect)
|
||
midImage?.draw(in: renderRect)
|
||
|
||
let temp = UIGraphicsGetImageFromCurrentImageContext()
|
||
UIGraphicsEndImageContext()
|
||
guard let cgi = temp?.cgImage else {
|
||
return nil
|
||
}
|
||
let image = UIImage(cgImage: cgi, scale: editImage.scale, orientation: .up)
|
||
|
||
if inputImage != nil {
|
||
return image
|
||
}
|
||
editImage = image
|
||
imageView.image = image
|
||
mosaicImageLayerMaskLayer?.path = nil
|
||
|
||
return image
|
||
}
|
||
|
||
private func buildImage() -> UIImage {
|
||
UIGraphicsBeginImageContextWithOptions(editImage.size, false, editImage.scale)
|
||
editImage.draw(at: .zero)
|
||
|
||
drawingImageView.image?.draw(in: CGRect(origin: .zero, size: originalImage.size))
|
||
|
||
if !stickersContainer.subviews.isEmpty, let context = UIGraphicsGetCurrentContext() {
|
||
let scale = imageSize.width / stickersContainer.frame.width
|
||
stickersContainer.subviews.forEach { view in
|
||
(view as? ZLStickerViewAdditional)?.resetState()
|
||
}
|
||
context.concatenate(CGAffineTransform(scaleX: scale, y: scale))
|
||
stickersContainer.layer.render(in: context)
|
||
context.concatenate(CGAffineTransform(scaleX: 1 / scale, y: 1 / scale))
|
||
}
|
||
|
||
let temp = UIGraphicsGetImageFromCurrentImageContext()
|
||
UIGraphicsEndImageContext()
|
||
guard let cgi = temp?.cgImage else {
|
||
return editImage
|
||
}
|
||
return UIImage(cgImage: cgi, scale: editImage.scale, orientation: .up)
|
||
}
|
||
|
||
func finishClipDismissAnimate() {
|
||
mainScrollView.alpha = 1
|
||
UIView.animate(withDuration: 0.1) {
|
||
self.topShadowView.alpha = 1
|
||
self.bottomShadowView.alpha = 1
|
||
self.adjustSlider?.alpha = 1
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: UIGestureRecognizerDelegate
|
||
|
||
extension ZLEditImageViewController: UIGestureRecognizerDelegate {
|
||
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||
guard imageStickerContainerIsHidden else {
|
||
return false
|
||
}
|
||
if gestureRecognizer is UITapGestureRecognizer {
|
||
if bottomShadowView.alpha == 1 {
|
||
let p = gestureRecognizer.location(in: view)
|
||
return !bottomShadowView.frame.contains(p)
|
||
} else {
|
||
return true
|
||
}
|
||
} else if gestureRecognizer is UIPanGestureRecognizer {
|
||
guard let selectedTool = selectedTool else {
|
||
return false
|
||
}
|
||
return (selectedTool == .draw || selectedTool == .mosaic) && !isScrolling
|
||
}
|
||
|
||
return true
|
||
}
|
||
}
|
||
|
||
// MARK: scroll view delegate
|
||
|
||
extension ZLEditImageViewController: UIScrollViewDelegate {
|
||
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||
return containerView
|
||
}
|
||
|
||
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||
let offsetX = (scrollView.frame.width > scrollView.contentSize.width) ? (scrollView.frame.width - scrollView.contentSize.width) * 0.5 : 0
|
||
let offsetY = (scrollView.frame.height > scrollView.contentSize.height) ? (scrollView.frame.height - scrollView.contentSize.height) * 0.5 : 0
|
||
containerView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX, y: scrollView.contentSize.height * 0.5 + offsetY)
|
||
}
|
||
|
||
public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
|
||
isScrolling = false
|
||
}
|
||
|
||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||
guard scrollView == mainScrollView else {
|
||
return
|
||
}
|
||
isScrolling = true
|
||
}
|
||
|
||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||
guard scrollView == mainScrollView else {
|
||
return
|
||
}
|
||
isScrolling = decelerate
|
||
}
|
||
|
||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||
guard scrollView == mainScrollView else {
|
||
return
|
||
}
|
||
isScrolling = false
|
||
}
|
||
|
||
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||
guard scrollView == mainScrollView else {
|
||
return
|
||
}
|
||
isScrolling = false
|
||
}
|
||
}
|
||
|
||
// MARK: collection view data source & delegate
|
||
|
||
extension ZLEditImageViewController: UICollectionViewDataSource, UICollectionViewDelegate {
|
||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||
if collectionView == editToolCollectionView {
|
||
return tools.count
|
||
} else if collectionView == drawColorCollectionView {
|
||
return drawColors.count
|
||
} else if collectionView == filterCollectionView {
|
||
return thumbnailFilterImages.count
|
||
} else {
|
||
return adjustTools.count
|
||
}
|
||
}
|
||
|
||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||
if collectionView == editToolCollectionView {
|
||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLEditToolCell.zl.identifier, for: indexPath) as! ZLEditToolCell
|
||
|
||
let toolType = tools[indexPath.row]
|
||
cell.icon.isHighlighted = false
|
||
cell.toolType = toolType
|
||
cell.icon.isHighlighted = toolType == selectedTool
|
||
|
||
return cell
|
||
} else if collectionView == drawColorCollectionView {
|
||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLDrawColorCell.zl.identifier, for: indexPath) as! ZLDrawColorCell
|
||
|
||
let c = drawColors[indexPath.row]
|
||
cell.color = c
|
||
if c == currentDrawColor {
|
||
cell.bgWhiteView.layer.transform = CATransform3DMakeScale(1.2, 1.2, 1)
|
||
} else {
|
||
cell.bgWhiteView.layer.transform = CATransform3DIdentity
|
||
}
|
||
|
||
return cell
|
||
} else if collectionView == filterCollectionView {
|
||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLFilterImageCell.zl.identifier, for: indexPath) as! ZLFilterImageCell
|
||
|
||
let image = thumbnailFilterImages[indexPath.row]
|
||
let filter = ZLPhotoConfiguration.default().editImageConfiguration.filters[indexPath.row]
|
||
|
||
cell.nameLabel.text = filter.name
|
||
cell.imageView.image = image
|
||
|
||
if currentFilter === filter {
|
||
cell.nameLabel.textColor = .zl.imageEditorToolTitleTintColor
|
||
} else {
|
||
cell.nameLabel.textColor = .zl.imageEditorToolTitleNormalColor
|
||
}
|
||
|
||
return cell
|
||
} else {
|
||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLAdjustToolCell.zl.identifier, for: indexPath) as! ZLAdjustToolCell
|
||
|
||
let tool = adjustTools[indexPath.row]
|
||
|
||
cell.imageView.isHighlighted = false
|
||
cell.adjustTool = tool
|
||
let isSelected = tool == selectedAdjustTool
|
||
cell.imageView.isHighlighted = isSelected
|
||
|
||
if isSelected {
|
||
cell.nameLabel.textColor = .zl.imageEditorToolTitleTintColor
|
||
} else {
|
||
cell.nameLabel.textColor = .zl.imageEditorToolTitleNormalColor
|
||
}
|
||
|
||
return cell
|
||
}
|
||
}
|
||
|
||
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||
if collectionView == editToolCollectionView {
|
||
let toolType = tools[indexPath.row]
|
||
switch toolType {
|
||
case .draw:
|
||
drawBtnClick()
|
||
case .clip:
|
||
clipBtnClick()
|
||
case .imageSticker:
|
||
imageStickerBtnClick()
|
||
case .textSticker:
|
||
textStickerBtnClick()
|
||
case .mosaic:
|
||
mosaicBtnClick()
|
||
case .filter:
|
||
filterBtnClick()
|
||
case .adjust:
|
||
adjustBtnClick()
|
||
}
|
||
} else if collectionView == drawColorCollectionView {
|
||
currentDrawColor = drawColors[indexPath.row]
|
||
} else if collectionView == filterCollectionView {
|
||
currentFilter = ZLPhotoConfiguration.default().editImageConfiguration.filters[indexPath.row]
|
||
func adjustImage(_ image: UIImage) -> UIImage {
|
||
guard tools.contains(.adjust), brightness != 0 || contrast != 0 || saturation != 0 else {
|
||
return image
|
||
}
|
||
return image.zl.adjust(brightness: brightness, contrast: contrast, saturation: saturation) ?? image
|
||
}
|
||
if let image = filterImages[currentFilter.name] {
|
||
editImage = adjustImage(image)
|
||
editImageWithoutAdjust = image
|
||
} else {
|
||
let image = currentFilter.applier?(originalImage) ?? originalImage
|
||
editImage = adjustImage(image)
|
||
editImageWithoutAdjust = image
|
||
filterImages[currentFilter.name] = image
|
||
}
|
||
if tools.contains(.mosaic) {
|
||
generateNewMosaicImageLayer()
|
||
|
||
if mosaicPaths.isEmpty {
|
||
imageView.image = editImage
|
||
} else {
|
||
generateNewMosaicImage()
|
||
}
|
||
} else {
|
||
imageView.image = editImage
|
||
}
|
||
} else {
|
||
let tool = adjustTools[indexPath.row]
|
||
if tool != selectedAdjustTool {
|
||
changeAdjustTool(tool)
|
||
}
|
||
}
|
||
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
|
||
collectionView.reloadData()
|
||
}
|
||
}
|
||
|
||
// MARK: ZLTextStickerViewDelegate
|
||
|
||
extension ZLEditImageViewController: ZLStickerViewDelegate {
|
||
func stickerBeginOperation(_ sticker: UIView) {
|
||
setToolView(show: false)
|
||
ashbinView.layer.removeAllAnimations()
|
||
ashbinView.isHidden = false
|
||
var frame = ashbinView.frame
|
||
let diff = view.frame.height - frame.minY
|
||
frame.origin.y += diff
|
||
ashbinView.frame = frame
|
||
frame.origin.y -= diff
|
||
UIView.animate(withDuration: 0.25) {
|
||
self.ashbinView.frame = frame
|
||
}
|
||
|
||
stickersContainer.subviews.forEach { view in
|
||
if view !== sticker {
|
||
(view as? ZLStickerViewAdditional)?.resetState()
|
||
(view as? ZLStickerViewAdditional)?.gesIsEnabled = false
|
||
}
|
||
}
|
||
}
|
||
|
||
func stickerOnOperation(_ sticker: UIView, panGes: UIPanGestureRecognizer) {
|
||
let point = panGes.location(in: view)
|
||
if ashbinView.frame.contains(point) {
|
||
ashbinView.backgroundColor = .zl.trashCanBackgroundTintColor
|
||
ashbinImgView.isHighlighted = true
|
||
if sticker.alpha == 1 {
|
||
sticker.layer.removeAllAnimations()
|
||
UIView.animate(withDuration: 0.25) {
|
||
sticker.alpha = 0.5
|
||
}
|
||
}
|
||
} else {
|
||
ashbinView.backgroundColor = .zl.trashCanBackgroundNormalColor
|
||
ashbinImgView.isHighlighted = false
|
||
if sticker.alpha != 1 {
|
||
sticker.layer.removeAllAnimations()
|
||
UIView.animate(withDuration: 0.25) {
|
||
sticker.alpha = 1
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func stickerEndOperation(_ sticker: UIView, panGes: UIPanGestureRecognizer) {
|
||
setToolView(show: true)
|
||
ashbinView.layer.removeAllAnimations()
|
||
ashbinView.isHidden = true
|
||
|
||
let point = panGes.location(in: view)
|
||
if ashbinView.frame.contains(point) {
|
||
(sticker as? ZLStickerViewAdditional)?.moveToAshbin()
|
||
}
|
||
|
||
stickersContainer.subviews.forEach { view in
|
||
(view as? ZLStickerViewAdditional)?.gesIsEnabled = true
|
||
}
|
||
}
|
||
|
||
func stickerDidTap(_ sticker: UIView) {
|
||
stickersContainer.subviews.forEach { view in
|
||
if view !== sticker {
|
||
(view as? ZLStickerViewAdditional)?.resetState()
|
||
}
|
||
}
|
||
}
|
||
|
||
func sticker(_ textSticker: ZLTextStickerView, editText text: String) {
|
||
showInputTextVC(text, textColor: textSticker.textColor, style: textSticker.style) { [weak self] text, textColor, image, style in
|
||
guard let image = image, !text.isEmpty else {
|
||
textSticker.moveToAshbin()
|
||
return
|
||
}
|
||
|
||
textSticker.startTimer()
|
||
guard textSticker.text != text || textSticker.textColor != textColor || textSticker.style != style else {
|
||
return
|
||
}
|
||
textSticker.text = text
|
||
textSticker.textColor = textColor
|
||
textSticker.style = style
|
||
textSticker.image = image
|
||
let newSize = ZLTextStickerView.calculateSize(image: image)
|
||
textSticker.changeSize(to: newSize)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: 涂鸦path
|
||
|
||
public class ZLDrawPath: NSObject {
|
||
private let pathColor: UIColor
|
||
|
||
private let path: UIBezierPath
|
||
|
||
private let ratio: CGFloat
|
||
|
||
private let shapeLayer: CAShapeLayer
|
||
|
||
init(pathColor: UIColor, pathWidth: CGFloat, ratio: CGFloat, startPoint: CGPoint) {
|
||
self.pathColor = pathColor
|
||
path = UIBezierPath()
|
||
path.lineWidth = pathWidth / ratio
|
||
path.lineCapStyle = .round
|
||
path.lineJoinStyle = .round
|
||
path.move(to: CGPoint(x: startPoint.x / ratio, y: startPoint.y / ratio))
|
||
|
||
shapeLayer = CAShapeLayer()
|
||
shapeLayer.lineCap = .round
|
||
shapeLayer.lineJoin = .round
|
||
shapeLayer.lineWidth = pathWidth / ratio
|
||
shapeLayer.fillColor = UIColor.clear.cgColor
|
||
shapeLayer.strokeColor = pathColor.cgColor
|
||
shapeLayer.path = path.cgPath
|
||
|
||
self.ratio = ratio
|
||
|
||
super.init()
|
||
}
|
||
|
||
func addLine(to point: CGPoint) {
|
||
path.addLine(to: CGPoint(x: point.x / ratio, y: point.y / ratio))
|
||
shapeLayer.path = path.cgPath
|
||
}
|
||
|
||
func drawPath() {
|
||
pathColor.set()
|
||
path.stroke()
|
||
}
|
||
}
|
||
|
||
// MARK: 马赛克path
|
||
|
||
public class ZLMosaicPath: NSObject {
|
||
let path: UIBezierPath
|
||
|
||
let ratio: CGFloat
|
||
|
||
let startPoint: CGPoint
|
||
|
||
var linePoints: [CGPoint] = []
|
||
|
||
init(pathWidth: CGFloat, ratio: CGFloat, startPoint: CGPoint) {
|
||
path = UIBezierPath()
|
||
path.lineWidth = pathWidth
|
||
path.lineCapStyle = .round
|
||
path.lineJoinStyle = .round
|
||
path.move(to: startPoint)
|
||
|
||
self.ratio = ratio
|
||
self.startPoint = CGPoint(x: startPoint.x / ratio, y: startPoint.y / ratio)
|
||
|
||
super.init()
|
||
}
|
||
|
||
func addLine(to point: CGPoint) {
|
||
path.addLine(to: point)
|
||
linePoints.append(CGPoint(x: point.x / ratio, y: point.y / ratio))
|
||
}
|
||
}
|