CoreGraphics:如果缩放图像,绘制 NSImage 的性能会受到影响
CoreGraphics: performance hit in drawing NSImage if image is scaled
我正在为 macOS 10.14 编写一些 swift 代码。我遇到了性能瓶颈并将其隔离到以下渲染代码,重写以删除所有不相关的部分。
我有一个 NSImage(原为 JPG),我正在使用以下代码调整它的大小:
extension NSImage {
func resized(size: NSSize) -> NSImage {
let cgImage = self.cgImage!
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let colorSpace = cgImage.colorSpace!
let bitmapInfo = CGImageAlphaInfo.noneSkipLast
let context = CGContext(data: nil,
width: Int(cgImage.width / 2),
height: Int(cgImage.height / 2),
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)!
context.interpolationQuality = .high
let newSize = size
context.draw(cgImage,
in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
let img = context.makeImage()!
return NSImage(cgImage: img, size: newSize)
}
var cgImage: CGImage? {
get {
guard let imageData = self.tiffRepresentation else { return nil }
guard let sourceData = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
return CGImageSourceCreateImageAtIndex(sourceData, 0, nil)
}
}
}
然后我 运行 使用以下代码进行性能测试:
导入 XCTest
@testable import TestRendering
class TestRenderingTests: XCTestCase {
static var testImage: NSImage {
let url = Bundle(for: TestRenderingTests.self).url(forResource: "photo", withExtension: ".jpg")
return NSImage(contentsOf: url!)!
}
func testPerformanceExample() {
let originalImage = type(of: self).testImage
let multiFactor: CGFloat = 0.99
let resizedSize = NSSize(
width: originalImage.size.width * multiFactor,
height: originalImage.size.height * multiFactor
)
let resizedImage = originalImage.resized(size: resizedSize)
let baseImage = NSImage(size: resizedSize)
let rect = NSRect(
origin: NSPoint.zero,
size: resizedSize
)
self.measure {
baseImage.lockFocus()
resizedImage.draw(in: rect, from: rect, operation: .copy, fraction: 1)
baseImage.unlockFocus()
}
}
}
如果我 运行 比例因子 multiFactor
为 1
的性能测试,我会得到我用作基线的测量块的特定值。
如果我然后将比例因子 multiFactor
更改为 0.99
,则测量块的性能将变差 59%。
为什么这个性能受到打击?我的理论是,当图像大小不等于原始大小时,图像调整大小功能会以某种方式创建图像的新表示形式,每次渲染时都需要对其进行进一步预处理。如果图片是原始尺寸,不知何故它只是使用原始图像而不需要预处理。
我在分析两个版本的测试时查看堆栈跟踪,想出了这个理论。
以下堆栈跟踪来自比例因子为 1 时(图像大小未更改):
以下堆栈跟踪来自比例因子为 0.99 时的堆栈跟踪:
调用堆栈中的函数不匹配:argb32_image_mark_argb32
与 argb32_sample_argb32
。
是否可以重写 resized(size:)
函数,使其创建一个图像,而无需在每次渲染时都 "sampled"?
作为参考,我在测试中使用了下图:
感谢 Ken Thomases 的提示,我将调整大小代码更新为:
- 确保大小始终为
Int
- 让 CoreGraphics 自动计算正确的
bytesPerRow
- 不要将大小除以 2(不确定它是如何到达那里的)
结果代码是:
func resized(size: NSSize) -> NSImage {
let intSize = NSSize(width: Int(size.width), height: Int(size.height))
let cgImage = self.cgImage!
let bitsPerComponent = cgImage.bitsPerComponent
let colorSpace = cgImage.colorSpace!
let bitmapInfo = CGImageAlphaInfo.noneSkipLast
let context = CGContext(data: nil,
width: Int(intSize.width),
height: Int(intSize.height),
bitsPerComponent: bitsPerComponent,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)!
context.interpolationQuality = .high
context.draw(cgImage,
in: NSRect(x: 0, y: 0, width: intSize.width, height: intSize.height))
let img = context.makeImage()!
return NSImage(cgImage: img, size: intSize)
}
这确实解决了性能问题。
我正在为 macOS 10.14 编写一些 swift 代码。我遇到了性能瓶颈并将其隔离到以下渲染代码,重写以删除所有不相关的部分。
我有一个 NSImage(原为 JPG),我正在使用以下代码调整它的大小:
extension NSImage {
func resized(size: NSSize) -> NSImage {
let cgImage = self.cgImage!
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let colorSpace = cgImage.colorSpace!
let bitmapInfo = CGImageAlphaInfo.noneSkipLast
let context = CGContext(data: nil,
width: Int(cgImage.width / 2),
height: Int(cgImage.height / 2),
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)!
context.interpolationQuality = .high
let newSize = size
context.draw(cgImage,
in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
let img = context.makeImage()!
return NSImage(cgImage: img, size: newSize)
}
var cgImage: CGImage? {
get {
guard let imageData = self.tiffRepresentation else { return nil }
guard let sourceData = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
return CGImageSourceCreateImageAtIndex(sourceData, 0, nil)
}
}
}
然后我 运行 使用以下代码进行性能测试: 导入 XCTest @testable import TestRendering
class TestRenderingTests: XCTestCase {
static var testImage: NSImage {
let url = Bundle(for: TestRenderingTests.self).url(forResource: "photo", withExtension: ".jpg")
return NSImage(contentsOf: url!)!
}
func testPerformanceExample() {
let originalImage = type(of: self).testImage
let multiFactor: CGFloat = 0.99
let resizedSize = NSSize(
width: originalImage.size.width * multiFactor,
height: originalImage.size.height * multiFactor
)
let resizedImage = originalImage.resized(size: resizedSize)
let baseImage = NSImage(size: resizedSize)
let rect = NSRect(
origin: NSPoint.zero,
size: resizedSize
)
self.measure {
baseImage.lockFocus()
resizedImage.draw(in: rect, from: rect, operation: .copy, fraction: 1)
baseImage.unlockFocus()
}
}
}
如果我 运行 比例因子 multiFactor
为 1
的性能测试,我会得到我用作基线的测量块的特定值。
如果我然后将比例因子 multiFactor
更改为 0.99
,则测量块的性能将变差 59%。
为什么这个性能受到打击?我的理论是,当图像大小不等于原始大小时,图像调整大小功能会以某种方式创建图像的新表示形式,每次渲染时都需要对其进行进一步预处理。如果图片是原始尺寸,不知何故它只是使用原始图像而不需要预处理。
我在分析两个版本的测试时查看堆栈跟踪,想出了这个理论。
以下堆栈跟踪来自比例因子为 1 时(图像大小未更改):
以下堆栈跟踪来自比例因子为 0.99 时的堆栈跟踪:
调用堆栈中的函数不匹配:argb32_image_mark_argb32
与 argb32_sample_argb32
。
是否可以重写 resized(size:)
函数,使其创建一个图像,而无需在每次渲染时都 "sampled"?
作为参考,我在测试中使用了下图:
感谢 Ken Thomases 的提示,我将调整大小代码更新为:
- 确保大小始终为
Int
- 让 CoreGraphics 自动计算正确的
bytesPerRow
- 不要将大小除以 2(不确定它是如何到达那里的)
结果代码是:
func resized(size: NSSize) -> NSImage {
let intSize = NSSize(width: Int(size.width), height: Int(size.height))
let cgImage = self.cgImage!
let bitsPerComponent = cgImage.bitsPerComponent
let colorSpace = cgImage.colorSpace!
let bitmapInfo = CGImageAlphaInfo.noneSkipLast
let context = CGContext(data: nil,
width: Int(intSize.width),
height: Int(intSize.height),
bitsPerComponent: bitsPerComponent,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)!
context.interpolationQuality = .high
context.draw(cgImage,
in: NSRect(x: 0, y: 0, width: intSize.width, height: intSize.height))
let img = context.makeImage()!
return NSImage(cgImage: img, size: intSize)
}
这确实解决了性能问题。