CoreGraphics: снижение производительности при рисовании NSImage при масштабировании изображения - PullRequest
0 голосов
/ 30 ноября 2018

Я пишу быстрый код для MacOS 10.14.Я столкнулся с узким местом в производительности и изолировал его от следующего кода рендеринга, переписанного для удаления всех ненужных частей.

У меня есть 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)
        }
    }
}

Затем я запускаю тест производительности со следующим кодом: import 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%.

performance deterioration

Почему этот спектакль ударил?Моя теория состоит в том, что функция изменения размера изображения, когда размер не равен исходному размеру, каким-то образом создает новое представление изображения, которое необходимо подвергать дальнейшей обработке каждый раз, когда оно отображается.Если изображение имеет исходный размер, каким-то образом оно просто использует исходное изображение и не требует предварительной обработки.

Я придумал эту теорию, просматривая трассировку стека при профилировании двух версий теста.

Следующая трассировка стека получена, когда масштабный коэффициент равен 1 (размер изображения не изменился):

enter image description here

Следующая трассировка стекаэто когда масштабный коэффициент равен 0,99:

enter image description here

Функции в стеке вызовов не совпадают: argb32_image_mark_argb32 против argb32_sample_argb32.

Можно ли переписать функцию resized(size:) таким образом, чтобы она создавала изображение, которое не нужно «отбирать» каждый раз при его рендеринге?

Для справки, яиспользуя следующее изображение в тесте: enter image description here

1 Ответ

0 голосов
/ 30 ноября 2018

Благодаря Кену Томассу за совет, я обновил код изменения размера:

  • , чтобы убедиться, что размер всегда равен 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)
}

И это действительно решает проблему с производительностью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...