NSImage 实现图像绘制的关键在于图像上下文(Graphics Context)机制,配合 NSGraphicsContext 和 NSImageRep 系列类,完成位图或矢量图的渲染。它不是简单的 bitmap 容器,而是一个“绘图容器”,可以组合多个图像表示(NSImageRep),通过绘图命令进行合成。
NSImage 绘制图像的核心机制
1、NSImage 是容器,而不是像素位图本身
NSImage 本身不直接存储像素数据,而是内部包含多个图像表示(NSImageRep 对象),比如:
NSBitmapImageRep:位图表示(像素数据);
NSPDFImageRep:PDF 矢量图;
NSCIImageRep:Core Image 图像;
NSCachedImageRep、NSCustomImageRep 等。
可以使用representations返回所有已添加的图像表示:
image.representations // 返回 [NSImageRep]
2、绘图前需要使用lockFocus() 建立上下文
lockFocus() 会把图像设为当前绘图目标,也就是建立一个图形上下文NSGraphicsContext:
image.lockFocus() // 锁定绘图目标,开始绘图
... // 绘图过程
image.unlockFocus() // 解锁绘图目标,完成绘图
期间的所有绘制(draw()、Core Graphics、NSBezierPath)都会“画到”这张图上。
内部实际上是调用 NSGraphicsContext.current = … 设置当前上下文。
3、调用draw() 实现绘制
例如:
otherImage.draw(at: point, from: .zero, operation: .sourceOver, fraction: 1.0)
这是向当前图形上下文(由 lockFocus() 设置)中绘制一张图像的标准方式,类似 Photoshop 合成图层。
4、unlockFocus() 提交绘图
绘制完成后调用 unlockFocus() 会:
1)恢复上下文(解除绘图绑定);
2)图像数据被写入到 NSBitmapImageRep 或类似的表示中;
如果不调用 unlockFocus(),绘制是不会生效的。
NSImage.draw方法
draw 是 NSImage 中非常常用的方法,用于把图像绘制到当前的图形上下文中(比如:窗口、视图、或另一个 NSImage 上)。
常用的三种draw写法:
1、draw(at:from:operation:fraction:)
func draw(at point: NSPoint, from rect: NSRect, operation: NSCompositingOperation, fraction: CGFloat)
作用:将图像的指定区域(from)绘制到当前位置(at)
参数说明:
1)point:绘制目标位置(左下角为原点);
2)rect:从原图中提取的区域(通常用 .zero 表示整个图);
3)operation:混合模式(如 .sourceOver);
4)fraction:透明度(0.0~1.0)。
示例:把图像绘制在 (50, 50) 位置
let image = NSImage(named: "example")!
image.draw(at: NSPoint(x: 50, y: 50),
from: .zero,
operation: .sourceOver,
fraction: 1.0)
2、draw(in:)
func draw(in dstRect: NSRect)
作用:将整个图像缩放绘制到指定的矩形区域中。
适合做图像缩放(比如绘制成缩略图)。
示例:缩放绘制到 (0,0)-(100,100)
image.draw(in: NSRect(x: 0, y: 0, width: 100, height: 100))
3、draw(in:from:operation:fraction:respectFlipped:hints:)
这是最完整、底层的版本:
func draw(in dstRect: NSRect,
from srcRect: NSRect,
operation: NSCompositingOperation,
fraction: CGFloat,
respectFlipped: Bool,
hints: [NSImageRep.HintKey : Any]?)
支持指定源区域、目标区域、混合方式、是否尊重翻转坐标、绘图提示。
一般用于复杂图像缩放或渲染优化(高性能需求)。
draw方法的注意事项
1、当前上下文:draw() 一定要在有效的绘图上下文中使用,比如 lockFocus() 之后、NSView.draw(_:) 中;
2、坐标系:默认原点在左下角,macOS 是 flipped 视图时可能在左上角;
3、高分屏:绘图时图像分辨率可能会被拉伸,应使用 image.bestRepresentation(for:context:hints:) 获取最优图像;
4、透明度:fraction 可以实现半透明绘制;
5、合成模式:.sourceOver 是最常见的,等同于 Photoshop 的“正常”图层混合。
混合模式 .sourceOver 等类型:
1).clear:清除目标区域像素;
2).copy:直接复制源像素到目标;
3).sourceOver:默认,源像素覆盖到目标上;
4).destinationOver:目标像素在源图像下;
5).multiply / .screen:类似 Photoshop 的混合模式。
代码示例
1、绘制红色圆
let image = NSImage(size: NSSize(width: 100, height: 100))
image.lockFocus()
NSColor.red.setFill()
NSBezierPath(ovalIn: NSRect(x: 10, y: 10, width: 80, height: 80)).fill()
image.unlockFocus()
2、在屏幕截图上绘制鼠标
let finalImage = NSImage(size: screenSize) // 创建合成图片,screenSize 是屏幕尺寸
finalImage.lockFocus()
// 绘制屏幕截图,screenImage是获取的屏幕截图
screenImage.draw(at: .zero, from: .zero, operation: .sourceOver, fraction: 1.0)
// 绘制鼠标图像,cursorImage是获取的默认鼠标图像
let cursorOrigin = NSPoint(x: mouseLocation.x,y: mouseLocation.y) // 鼠标的坐标点
cursorImage.draw(at: cursorOrigin, from: .zero, operation: .sourceOver, fraction: 1.0)
finalImage.unlockFocus()
创建屏幕大小的NSImage图像,在NSImage图像上使用屏幕截图和鼠标图像的draw方法,根据坐标点绘制图像,绘制完成后得到一个屏幕+鼠标的截图。
总结
NSImage 通过内部的图像表示和图形上下文系统,实现图像绘制和组合。可以把它想象成 Photoshop 中的一张画布,lockFocus() 后就可以自由绘制其他内容,绘完 unlockFocus(),这张图就完成了。
在 Retina 屏下,lockFocus() 使用的是像素比例为 1x 的上下文(非 HiDPI),会导致模糊。如果需要支持高分屏、图像保存、透明背景等,更推荐:
1)使用 NSGraphicsContext + NSBitmapImageRep;
2)或者直接使用 Core Graphics 或 Metal。
相关文章
MacOS图像显示类NSImage:https://fangjunyu.com/2024/11/22/macos%e5%9b%be%e5%83%8f%e6%98%be%e7%a4%ba%e7%b1%bbnsimage/