用图案图像填充多个 UIBezierPath
Fill multiple UIBezierPath with pattern image
我创建了一个绘制了很多 UIBezierPaths 的 UIView。现在我想用绿色纹理图像填充它们。使用以下代码,模式图像将在整个视图中填充和重复,而不是在每个单独的路径中重复。 (一个路径是这些矩形之一,底部有圆圈)。所以我希望图案图像适合每条路径并在路径旋转时旋转它。
var image = UIImage(named: "test.jpg")!
let color = UIColor.lightGray
let fillColor = UIColor.init(patternImage: image)
var lastPoint: CGPoint? = nil
for point in points {
if let lastPointUnwrapped = lastPoint{
let path = BiberBezierPath(leftPoint: lastPointUnwrapped, rightPoint: point, frame: frame, plusHeight: -140)
fillColor.setFill()
path.fill()
color.setStroke()
path.lineWidth = 1
path.lineCapStyle = .butt
path.stroke()
}
lastPoint = point
}
您正在寻找的任务在某些情况下可能会变得复杂。但可能像这样的东西就足够了:
class RoofView: UIView {
var paths: [(path: UIBezierPath, rotationInRadians: CGFloat)]? { didSet { self.refresh() } }
var image: UIImage? { didSet { self.refresh() } }
var preserveImageAspect: Bool = true { didSet { self.refresh() } }
func refresh() {
self.setNeedsDisplay() // This will trigger redraw
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let paths = paths else { return }
guard let image = image else { return }
guard let context = UIGraphicsGetCurrentContext() else { return }
paths.forEach { item in
context.saveGState() // Save state. This includes transformations and clips
item.path.addClip()
let boundingRect: CGRect = {
let rotatedPath = (item.path.copy() as! UIBezierPath) // Create a duplicate path
rotatedPath.apply(CGAffineTransform(rotationAngle: -item.rotationInRadians)) // Rotate it backwards
if preserveImageAspect {
let minimumBounds = rotatedPath.bounds
let center = CGPoint(x: minimumBounds.midX, y: minimumBounds.midY)
let imageRatio: CGFloat = image.size.width / image.size.height
if minimumBounds.width / minimumBounds.height < imageRatio {
// Preserve height, scale width
let size = CGSize(width: minimumBounds.height*imageRatio, height: minimumBounds.height)
return CGRect(x: center.x - size.width*0.5, y: center.y - size.height*0.5, width: size.width, height: size.height)
} else {
// Preserve width, scale height
let size = CGSize(width: minimumBounds.width, height: minimumBounds.width/imageRatio)
return CGRect(x: center.x - size.width*0.5, y: center.y - size.height*0.5, width: size.width, height: size.height)
}
} else {
return rotatedPath.bounds
}
}()
context.rotate(by: item.rotationInRadians)
image.draw(in: boundingRect)
context.restoreGState() // Put back state. This includes transformations and clips
}
}
}
此代码所做的是获取图像并将其绘制在具有给定旋转的所有路径上。
为此,它会使用 addClip
剪切带有路径的上下文,这意味着后面的每个绘制命令都只会在剪切区域内绘制。
接下来绘制需要确定位置的图像。我们尝试在应该绘制图像的地方放置一个最小的矩形。旋转路径以确定正确的边界很重要,否则旋转(例如)45 度时它们可能太小。可以选择将纵横比保留为 "fill" 模式,这需要一点数学...
然后旋转上下文,使图像看起来旋转。然后在给定路径绘制图像。
剩下的就是清理,也就是恢复状态。这将清除剪切蒙版和上下文旋转。
如果对保留纵横比填充的数学感兴趣:
我们正在尝试 "fill" 大小在一个矩形内。大小来自图像,而矩形是我们要绘制它的地方。我们需要比较两个比率 (width/height
) 并且根据哪个更大我们增加宽度或高度来绘制 "out of bounds"。在每种情况下,保留一个坐标,而另一个坐标由图像纵横比确定。这是确定 "fill" 矩形的通用解决方案。有趣的是,如果您需要 "fit",您所做的就是将不等式更改为 if minimumBounds.width / minimumBounds.height > imageRatio {
。
我创建了一个绘制了很多 UIBezierPaths 的 UIView。现在我想用绿色纹理图像填充它们。使用以下代码,模式图像将在整个视图中填充和重复,而不是在每个单独的路径中重复。 (一个路径是这些矩形之一,底部有圆圈)。所以我希望图案图像适合每条路径并在路径旋转时旋转它。
var image = UIImage(named: "test.jpg")!
let color = UIColor.lightGray
let fillColor = UIColor.init(patternImage: image)
var lastPoint: CGPoint? = nil
for point in points {
if let lastPointUnwrapped = lastPoint{
let path = BiberBezierPath(leftPoint: lastPointUnwrapped, rightPoint: point, frame: frame, plusHeight: -140)
fillColor.setFill()
path.fill()
color.setStroke()
path.lineWidth = 1
path.lineCapStyle = .butt
path.stroke()
}
lastPoint = point
}
您正在寻找的任务在某些情况下可能会变得复杂。但可能像这样的东西就足够了:
class RoofView: UIView {
var paths: [(path: UIBezierPath, rotationInRadians: CGFloat)]? { didSet { self.refresh() } }
var image: UIImage? { didSet { self.refresh() } }
var preserveImageAspect: Bool = true { didSet { self.refresh() } }
func refresh() {
self.setNeedsDisplay() // This will trigger redraw
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let paths = paths else { return }
guard let image = image else { return }
guard let context = UIGraphicsGetCurrentContext() else { return }
paths.forEach { item in
context.saveGState() // Save state. This includes transformations and clips
item.path.addClip()
let boundingRect: CGRect = {
let rotatedPath = (item.path.copy() as! UIBezierPath) // Create a duplicate path
rotatedPath.apply(CGAffineTransform(rotationAngle: -item.rotationInRadians)) // Rotate it backwards
if preserveImageAspect {
let minimumBounds = rotatedPath.bounds
let center = CGPoint(x: minimumBounds.midX, y: minimumBounds.midY)
let imageRatio: CGFloat = image.size.width / image.size.height
if minimumBounds.width / minimumBounds.height < imageRatio {
// Preserve height, scale width
let size = CGSize(width: minimumBounds.height*imageRatio, height: minimumBounds.height)
return CGRect(x: center.x - size.width*0.5, y: center.y - size.height*0.5, width: size.width, height: size.height)
} else {
// Preserve width, scale height
let size = CGSize(width: minimumBounds.width, height: minimumBounds.width/imageRatio)
return CGRect(x: center.x - size.width*0.5, y: center.y - size.height*0.5, width: size.width, height: size.height)
}
} else {
return rotatedPath.bounds
}
}()
context.rotate(by: item.rotationInRadians)
image.draw(in: boundingRect)
context.restoreGState() // Put back state. This includes transformations and clips
}
}
}
此代码所做的是获取图像并将其绘制在具有给定旋转的所有路径上。
为此,它会使用 addClip
剪切带有路径的上下文,这意味着后面的每个绘制命令都只会在剪切区域内绘制。
接下来绘制需要确定位置的图像。我们尝试在应该绘制图像的地方放置一个最小的矩形。旋转路径以确定正确的边界很重要,否则旋转(例如)45 度时它们可能太小。可以选择将纵横比保留为 "fill" 模式,这需要一点数学...
然后旋转上下文,使图像看起来旋转。然后在给定路径绘制图像。
剩下的就是清理,也就是恢复状态。这将清除剪切蒙版和上下文旋转。
如果对保留纵横比填充的数学感兴趣:
我们正在尝试 "fill" 大小在一个矩形内。大小来自图像,而矩形是我们要绘制它的地方。我们需要比较两个比率 (width/height
) 并且根据哪个更大我们增加宽度或高度来绘制 "out of bounds"。在每种情况下,保留一个坐标,而另一个坐标由图像纵横比确定。这是确定 "fill" 矩形的通用解决方案。有趣的是,如果您需要 "fit",您所做的就是将不等式更改为 if minimumBounds.width / minimumBounds.height > imageRatio {
。