项目中遇到了截图指定区域图片的功能, 比如一张全车图, 用户可以在手机上自由的画圈, 画完后要截取到画圈区域的图片, 然后进行处理. 经过试验, 现在将我做的demo
和思路这这里和大家分享下.
一.首先分析下思路
1.使用
UIImageView
显示要被裁剪的图片;
2.因为需要截取图片, 由于在imageView
上直接截取的话, 会由于图片像素的原因有误差, 因为截图是在layer上
截取, 是cgImage
, 所以需要先获得当前图片区大小像素的图片;
3.因为为了让用户使用直观,涉及到划线, [注]UIImageView
不能划线, 所以要创建一个图片大小的UIView
用于画线;
4.根据画线的区域可以获取到最左右前后四个点, 从而获取到一个区域矩形;
5.根据获取到的画线区域截图图片.
整体思路就是这样的, 大家可以先理解下思路, 接下来详细给大家讲下实现.
二.创建需要的控件
fileprivate var img_Car: UIImageView! //车img
fileprivate var view_GetImgBg: UIView! //图片bg,用于截取当前大小的image
fileprivate var img_GetBg: UIImage! //获取当前的图片, 用于截图, 这样避免图片像素影响, 使用当前显示的大小
fileprivate var view_Crop: LineDrawView! //专门用于画线 因为img不能画线
fileprivate var img_Crop: UIImageView! //根据花圈区域截的图
fileprivate var array_TouchCrop = [CGPoint]() //画圈点 相对于背景view的位置
fileprivate var array_TouchImage = [CGPoint]() //相对于车图的位置
因为需要相对于图片区域截图, 所以要使用
frame
布局, 不要使用自动布局.
1.创建图片的UIImageView
和图片大小的UIView
获取当前大小像素的图片
let width_Img = kScreenW / 375 * 175
let margin_LeftImg = (kScreenW - width_Img) / 2
view_GetImgBg = UIView()
addSubview(view_GetImgBg)
view_GetImgBg.frame = CGRect(x: margin_LeftImg, y: margin_TopMid, width: width_Img, height: kScreenH - margin_TopMid * 2)
img_Car = UIImageView()
img_Car.contentMode = .scaleToFill
img_Car.isUserInteractionEnabled = true
img_Car.frame = CGRect(x: 0, y: 0, width: view_GetImgBg.frame.size.width, height: view_GetImgBg.frame.size.height)
view_GetImgBg.addSubview(img_Car)
2.获取当前图片大小像素的图片用于截取图片
//获取当前显示图片大小的图
func getCurrentFrameImage() -> UIImage {
UIGraphicsBeginImageContext(view_GetImgBg.bounds.size)
view_GetImgBg.layer.render(in: UIGraphicsGetCurrentContext()!)
let imgae = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imgae!
}
3.创建用于画线的UIView
view_Crop = LineDrawView(frame: .zero, arrayPath: array_TouchCrop)
addSubview(view_Crop)
view_Crop.frame = CGRect(x: 0, y: 0, width: kScreenW, height: kScreenH)
4.创建UIImageView
用于显示最后截取的图片
img_Crop = UIImageView()
img_Crop.isHidden = true
view_GetImgBg.addSubview(img_Crop)
三.用于画线的UIView
因为UIImageView
不能画线, 即使用CGGraphics
核心绘画,所以需要专门创建相同大小的背景UIView
用于绘画.
在demo
中我封装的一个UIView
专门用于绘画, 解耦了下.
class LineDrawView: UIView {
var array_Path = [CGPoint]()
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect, arrayPath: [CGPoint]) {
self.init(frame: frame)
array_Path = arrayPath
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
//获取绘图上下文
guard let context = UIGraphicsGetCurrentContext() else{return}
if array_Path.count == 0 {return}
//创建并设置路径
let pathRef: CGMutablePath = CGMutablePath()
pathRef.move(to: array_Path[0])
pathRef.addLines(between: array_Path)
//添加路径到图形上下文
context.addPath(pathRef)
//设置笔触的颜色和宽度
context.setStrokeColor(UIColor.red.cgColor)
context.setLineWidth(4)
//绘制路径
context.strokePath()
}
}
四.在Touch
中处理轨迹
因为是要获取用户画线的区域, 所以在touch
中获取到用户活动的点即可.
因为背景画圈的大小和图片的大小不一致, 所以项目中使用了两个数组来存储相对于图片和画圈背景view的点.
1.两个存储点的数组:
fileprivate var array_TouchCrop = [CGPoint]() //画圈点 相对于背景view的位置
fileprivate var array_TouchImage = [CGPoint]() //相对于车图的位置
2.每次TouchBegin
请空数组, 初始化数据和页面
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
initCropImageStatus()
}
func initCropImageStatus() {
array_TouchCrop.removeAll()
array_TouchImage.removeAll()
createPath()
img_Car.isHidden = false
img_Crop.isHidden = true
}
3.TouchesMove
中使用数组存储移动的点
override func touchesMoved(_ touches: Set, with event: UIEvent?) {
array_TouchImage.append(touches.first!.location(in: img_Car))
array_TouchCrop.append(touches.first!.location(in: view_Crop))
}
4.在画圈结束后, 根据存储的点计算区域, 进行截图
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
if array_TouchCrop.count == 0{return}
createPath()
var topPoint: CGPoint = array_TouchImage[0]
var righttPoint: CGPoint = array_TouchImage[0]
var bottomPoint: CGPoint = array_TouchImage[0]
var leftPoint: CGPoint = array_TouchImage[0]
for item in array_TouchImage{
if item.y < topPoint.y{
topPoint = item
}
if item.x > righttPoint.x{
righttPoint = item
}
if item.y > bottomPoint.y{
bottomPoint = item
}
if item.x < leftPoint.x{
leftPoint = item
}
}
//设置最小的画圈范围, 小于10则不进行处理
if bottomPoint.y - topPoint.y <= 10{
return
}
if righttPoint.x - leftPoint.x <= 10{
return
}
let frame = CGRect(x: leftPoint.x, y: topPoint.y, width: righttPoint.x - leftPoint.x, height: bottomPoint.y - topPoint.y)
img_Crop.frame = frame
img_Crop.image = clipWithImageRect(clipFrame: frame, bgImage: img_GetBg)
img_Car.isHidden = true
img_Crop.isHidden = false
if let block = getImage{
block(img_Crop.image ?? UIImage.init())
}
}
五.绘画及截取图片
1.根据存储的画圈点进行绘制, 绘制其实就是调取UIView
的drawRect
方法进行重绘.
func createPath() {
view_Crop.array_Path = array_TouchCrop
view_Crop.setNeedsDisplay() //重绘
}
2.根据计算的区域frame
和用于裁剪的图片来进行裁剪:
func clipWithImageRect(clipFrame: CGRect, bgImage: UIImage) -> UIImage {
let rect_Scale = CGRect(x: clipFrame.origin.x, y: clipFrame.origin.y, width: clipFrame.size.width, height: clipFrame.size.height)
let cgImageCorpped = bgImage.cgImage?.cropping(to: rect_Scale)
let img_Clip = UIImage.init(cgImage: cgImageCorpped!, scale: 1, orientation: UIImageOrientation.up)
return img_Clip
}
到这里就大功告成了, 因为我为了解耦, 将这个过程封装在一个UIView
里面, 方便使用, 结束了可以通过代理,闭包等方式传出去, 此 demo
采用了闭包的形式, 截图的图片有了, 就可以处理了.
详细的代码可以到github下载: Github下载地址