一、实现原理详解
1. 位图上下文与图像叠加
在 iOS 中,UIImage 的底层数据是以位图(Bitmap)形式存储的,每个像素点包含颜色信息。当需要对图片进行叠加、滤镜、裁剪等操作时,我们需要通过 位图图形上下文(Bitmap Graphics Context) 来实现。
- 位图上下文 是一个画布,所有绘图操作(如绘制图片、文字)最终都会渲染到该上下文中。
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)是创建位图上下文的核心方法:size:上下文的尺寸(通常与原图一致)。opaque:是否不透明(设为false以支持透明度)。scale:图像的缩放比例(通常使用原图的scale以避免模糊)。
2. 图像叠加流程
- 创建上下文:通过
UIGraphicsBeginImageContextWithOptions创建一个新的位图上下文。 - 绘制原图:将原始图片绘制到上下文中。
- 绘制水印:在上下文中绘制水印图片或文字,通过设置透明度(
alpha)和混合模式(blendMode)控制叠加效果。 - 生成新图片:从上下文中提取处理后的位图数据,封装为新的
UIImage。 - 释放资源:调用
UIGraphicsEndImageContext()释放上下文。
3. 颜色混合与透明度
- 透明度(
alpha):通过alpha参数控制水印的透明度(0.0 为完全透明,1.0 为不透明)。 - 混合模式(
blendMode):默认使用.normal模式(直接覆盖),其他模式如.overlay可实现更复杂的视觉效果。
二、代码实现步骤
1. 添加图片水印
核心逻辑
func addImageWatermark(waterMarkImage: UIImage,
corner: WaterMarkCorner = .BottomRight,
margin: CGPoint = CGPoint(x: 10, y: 10),
alpha: CGFloat = 0.5) -> UIImage? {
// 1. 创建位图上下文
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
guard UIGraphicsGetCurrentContext() != nil else { return nil }
// 2. 绘制原图
self.draw(in: CGRect(origin: .zero, size: self.size))
// 3. 计算水印位置
var waterMarkRect = CGRect(origin: .zero, size: waterMarkImage.size)
switch corner {
case .TopLeft:
waterMarkRect.origin = margin
case .TopRight:
waterMarkRect.origin = CGPoint(x: self.size.width - waterMarkImage.size.width - margin.x,
y: margin.y)
case .BottomLeft:
waterMarkRect.origin = CGPoint(x: margin.x,
y: self.size.height - waterMarkImage.size.height - margin.y)
case .BottomRight:
waterMarkRect.origin = CGPoint(x: self.size.width - waterMarkImage.size.width - margin.x,
y: self.size.height - waterMarkImage.size.height - margin.y)
}
// 4. 绘制水印图片(叠加到原图上)
waterMarkImage.draw(in: waterMarkRect, blendMode: .normal, alpha: alpha)
// 5. 生成新图片
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
// 6. 关闭上下文
defer {
UIGraphicsEndImageContext()
}
return resultImage
}
2. 添加文字水印
核心逻辑
func addTextWatermark(text: String,
font: UIFont = UIFont.systemFont(ofSize: 30),
color: UIColor = .white,
position: CGPoint = CGPoint(x: 20, y: 20),
alpha: CGFloat = 0.5) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
guard UIGraphicsGetCurrentContext() != nil else { return nil }
// 1. 绘制原图
self.draw(in: CGRect(origin: .zero, size: self.size))
// 2. 设置文字属性
let attrs: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: color,
.backgroundColor: UIColor.clear
]
// 3. 计算文字绘制区域
let textSize = text.size(withAttributes: attrs)
let textRect = CGRect(origin: position, size: textSize)
// 4. 绘制文字
text.draw(in: textRect, withAttributes: attrs)
// 5. 生成新图片
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
// 6. 关闭上下文
defer {
UIGraphicsEndImageContext()
}
return resultImage
}
三、完整示例代码
1. ViewController 实现
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var LOGOImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// 加载原始图片
guard let originalImage = UIImage(named: "gogo") else {
print("原图加载失败")
return
}
// 缩放水印图片
let scaleRatio = 0.3 // 缩小为原图的30%
let scaledWaterMarkSize = CGSize(width: originalImage.size.width * scaleRatio, height: originalImage.size.height * scaleRatio)
let waterMarkImage = UIGraphicsImageRenderer(size: scaledWaterMarkSize).image { _ in
originalImage.draw(in: CGRect(origin: .zero, size: scaledWaterMarkSize))
}
// 添加图片水印
guard let imageWithImageWatermark = originalImage.addImageWatermark(
waterMarkImage: waterMarkImage,
corner: .BottomRight,
margin: CGPoint(x: 10, y: 10),
alpha: 0.5
) else {
print("图片水印添加失败")
return
}
// 添加文字水印
guard let finalImage = imageWithImageWatermark.addTextWatermark(
text: "90 够晨仔出品",
font: UIFont.systemFont(ofSize: 40),
color: .red,
alpha: 0.5
) else {
print("文字水印添加失败")
return
}
// 显示结果
LOGOImage.image = finalImage
LOGOImage.contentMode = .scaleAspectFit
}
}
2. UIImage 扩展
extension UIImage {
/// 添加图片水印
func addImageWatermark(waterMarkImage: UIImage,
corner: WaterMarkCorner = .BottomRight,
margin: CGPoint = CGPoint(x: 10, y: 10),
alpha: CGFloat = 0.5) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
guard UIGraphicsGetCurrentContext() != nil else { return nil }
// 绘制原图
self.draw(in: CGRect(origin: .zero, size: self.size))
// 计算水印位置
var waterMarkRect = CGRect(origin: .zero, size: waterMarkImage.size)
switch corner {
case .TopLeft:
waterMarkRect.origin = margin
case .TopRight:
waterMarkRect.origin = CGPoint(x: self.size.width - waterMarkImage.size.width - margin.x,
y: margin.y)
case .BottomLeft:
waterMarkRect.origin = CGPoint(x: margin.x,
y: self.size.height - waterMarkImage.size.height - margin.y)
case .BottomRight:
waterMarkRect.origin = CGPoint(x: self.size.width - waterMarkImage.size.width - margin.x,
y: self.size.height - waterMarkImage.size.height - margin.y)
}
// 绘制水印
waterMarkImage.draw(in: waterMarkRect, blendMode: .normal, alpha: alpha)
// 生成新图片
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
defer {
UIGraphicsEndImageContext()
}
return resultImage
}
// 水印位置枚举
enum WaterMarkCorner {
case TopLeft, TopRight, BottomLeft, BottomRight
}
/// 添加文字水印
func addTextWatermark(text: String,
font: UIFont = UIFont.systemFont(ofSize: 30),
color: UIColor = .white,
position: CGPoint = CGPoint(x: 20, y: 20),
alpha: CGFloat = 0.5) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
guard UIGraphicsGetCurrentContext() != nil else { return nil }
// 绘制原图
self.draw(in: CGRect(origin: .zero, size: self.size))
// 设置文字属性
let attrs: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: color,
.backgroundColor: UIColor.clear
]
// 计算文字绘制区域
let textSize = text.size(withAttributes: attrs)
let textRect = CGRect(origin: position, size: textSize)
// 绘制文字
text.draw(in: textRect, withAttributes: attrs)
// 生成新图片
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
defer {
UIGraphicsEndImageContext()
}
return resultImage
}
}
四、注意事项与优化建议
-
图片资源加载
- 确保
Assets.xcassets中存在名为gogo的图片。 - 使用
print()调试日志确认图片是否加载成功。
- 确保
-
水印位置调整
- 如果水印被裁剪,可调整
corner参数或增加margin。 - 水印图片过大时,可通过
scaleRatio缩放尺寸。
- 如果水印被裁剪,可调整
-
性能优化
- 避免在主线程处理大尺寸图片。
- 使用
autoreleasepool管理内存(尤其在批量处理时)。
-
透明度与视觉效果
- 调整
alpha值控制水印透明度(推荐 0.3~0.7)。 - 尝试不同
blendMode实现独特视觉效果(如.overlay、.multiply)。
- 调整
五、总结
图像水印的转换流程图
graph TD
A[原始图片] --> B[创建位图上下文]
B --> C[绘制原图]
C --> D{选择水印类型}
D -->|图片水印| E[加载水印图片]
D -->|文字水印| F[设置文字属性]
E --> G[计算水印位置]
F --> H[计算文字绘制区域]
G --> I[绘制图片水印]
H --> J[绘制文字水印]
I --> K[生成带水印的新图片]
J --> K
K --> L[关闭上下文]
L --> M[返回最终图像]
流程图说明
-
原始图片
- 起点是通过
UIImage(named:)加载的原始图片资源。
- 起点是通过
-
创建位图上下文
- 使用
UIGraphicsBeginImageContextWithOptions(...)创建画布,尺寸与原图一致,支持透明度。
- 使用
-
绘制原图
- 将原始图片绘制到位图上下文中,作为水印叠加的基础。
-
选择水印类型
- 分支为 图片水印 和 文字水印 两种处理逻辑。
-
图片水印处理
- 加载水印图片:通过
UIImage(named:)或缩放操作获取水印图片。 - 计算水印位置:根据
WaterMarkCorner枚举和margin参数确定水印绘制区域。 - 绘制图片水印:通过
draw(in:blendMode:alpha:)方法将水印叠加到原图上。
- 加载水印图片:通过
-
文字水印处理
- 设置文字属性:定义字体、颜色、透明度等。
- 计算文字绘制区域:根据文字内容和属性计算绘制位置。
- 绘制文字水印:通过
draw(in:withAttributes:)方法将文字叠加到原图上。
-
生成最终图像
- 从位图上下文中提取处理后的图像数据,封装为新的
UIImage对象。 - 关闭上下文并释放资源。
- 从位图上下文中提取处理后的图像数据,封装为新的
水印叠加的视觉效果示例
graph LR
A[原图] --> B[水印图片]
A --> C[水印文字]
B --> D[叠加后的图片]
C --> D
关键参数对结果的影响
| 参数 | 作用说明 |
|---|---|
alpha | 控制水印透明度(0.0~1.0),值越小越透明。 |
corner | 水印位置(左上、右上、左下、右下),决定水印在图片上的锚点。 |
margin | 水印与图片边缘的距离,避免水印超出可视区域。 |
blendMode | 混合模式(如 .normal、.overlay),影响水印与原图的颜色混合效果。 |