如何在MTKView中正确放置图像?

时间:2020-07-06 08:08:50

标签: swift macos mtkview

我正在尝试使用MTKView和Core Image过滤器实现图像编辑视图,并具有基本的功能,并且可以实时查看应用的过滤器。但是,图像在视图中的位置不正确-有人可以为我指出正确的方向,以完成在视图中正确呈现图像所需的操作。它需要适合视图并保留其原始纵横比。

这是金属绘制函数-和空的drawableSizeWillChange !? - 去搞清楚。它可能还值得一提的是,MTKView是ScrollView中另一个视图的子视图,并且可以由用户调整大小。对我而言,Metals如何处理调整视图大小尚不清楚,但似乎并非免费提供。

我还试图从后台线程调用draw()函数,这似乎可以解决问题。我可以看到使用滤镜将滤镜效果应用于图像的效果。据我了解,这应该是可能的。

似乎渲染的坐标空间在图像坐标空间中-因此,如果图像小于MTKView,则将图像定位在中心,X和Y坐标将为负。

调整视图大小后,一切都会变得疯狂,图像突然变得太大而背景的一部分未被清除。

此外,在静止视图时,图像会被拉伸而不是平滑地重新绘制。

func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
    
}


public func draw(in view: MTKView) {
    if let ciImage = self.ciImage  {
        if let currentDrawable = view.currentDrawable {              // 1
            let commandBuffer = commandQueue.makeCommandBuffer()
            
            let inputImage = ciImage     // 2
            exposureFilter.setValue(inputImage, forKey: kCIInputImageKey)
            exposureFilter.setValue(ev, forKey: kCIInputEVKey)
            
            context.render(exposureFilter.outputImage!,                      
                           to: currentDrawable.texture,
                           commandBuffer: commandBuffer,
                           bounds: CGRect(origin: .zero, size: view.drawableSize),
                           colorSpace: colorSpace)
            
            commandBuffer?.present(currentDrawable)                   
            commandBuffer?.commit()
        }
    }
}

如您所见,图像在左下方

Screen sample

3 个答案:

答案 0 :(得分:0)

let scaleFilter = CIFilter(name: "CILanczosScaleTransform")

那应该可以帮助您。问题是您的CIImage不论其来自何处,其大小都不与您在其中呈现的视图相同。

因此,您可以选择计算比例并将其用作过滤器:

    let scaleFilter = CIFilter(name: "CILanczosScaleTransform")
    scaleFilter?.setValue(ciImage, forKey: kCIInputImageKey)
    scaleFilter?.setValue(scale, forKey: kCIInputScaleKey)

这可以解决您的秤问题;我目前不知道最有效的方法是实际重新定位图像

更多参考:https://nshipster.com/image-resizing/

答案 1 :(得分:0)

问题是您打给context.render的电话-您正在呼叫render来历bounds:的{​​{1}}。那是左下角。

将图形放置在正确的位置完全取决于您。您需要根据图像尺寸和可绘制尺寸确定正确的边界原点应在哪里,然后在此处进行渲染。如果大小错误,则还需要首先应用比例变换。

答案 2 :(得分:0)

感谢Tristan Hume的MetalTest2,现在我可以在两个同步的scrollView中很好地运行它。基础知识在下面的子类中-渲染器和着色器可在Tristan的MetalTest2项目中找到。此类由viewController管理,并且是scrollView的documentView的子视图。看到最终结果的图像。

//
//  MetalLayerView.swift
//  MetalTest2
//
//  Created by Tristan Hume on 2019-06-19.
//  Copyright © 2019 Tristan Hume. All rights reserved.
//

import Cocoa

// Thanks to https://stackoverflow.com/questions/45375548/resizing-mtkview-scales-old-content-before-redraw
// for the recipe behind this, although I had to add presentsWithTransaction and the wait to make it glitch-free
class ImageMetalView: NSView, CALayerDelegate {
    var renderer : Renderer
    var metalLayer : CAMetalLayer!
    var commandQueue: MTLCommandQueue!
    var sourceTexture: MTLTexture!
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    var context: CIContext!
    var ciMgr: CIManager?
    var showEdits: Bool = false
    
    var ciImage: CIImage? {
        didSet {
            self.metalLayer.setNeedsDisplay()
        }
    }
    @objc dynamic var fileUrl: URL? {
        didSet {
            if let url = fileUrl {
                self.ciImage = CIImage(contentsOf: url)
            }
        }
    }
    
    /// Bind to this property from the viewController to receive notifications of changes to CI filter parameters
    @objc dynamic var adjustmentsChanged: Bool = false {
        didSet {
            self.metalLayer.setNeedsDisplay()
        }
    }
    
    override init(frame: NSRect) {
        let _device = MTLCreateSystemDefaultDevice()!
        renderer = Renderer(pixelFormat: .bgra8Unorm, device: _device)
        self.commandQueue = _device.makeCommandQueue()
        self.context = CIContext()
        self.ciMgr = CIManager(context: self.context)
        super.init(frame: frame)
        
        self.wantsLayer = true
        self.layerContentsRedrawPolicy = .duringViewResize
        
        // This property only matters in the case of a rendering glitch, which shouldn't happen anymore
        // The .topLeft version makes glitches less noticeable for normal UIs,
        // while .scaleAxesIndependently matches what MTKView does and makes them very noticeable
        //        self.layerContentsPlacement = .topLeft
        self.layerContentsPlacement = .scaleAxesIndependently
        
        
    }
    
    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func makeBackingLayer() -> CALayer {
        metalLayer = CAMetalLayer()
        metalLayer.pixelFormat = .bgra8Unorm
        metalLayer.device = renderer.device
        metalLayer.delegate = self
        
        // If you're using the strategy of .topLeft placement and not presenting with transaction
        // to just make the glitches less visible instead of eliminating them, it can help to make
        // the background color the same as the background of your app, so the glitch artifacts
        // (solid color bands at the edge of the window) are less visible.
        //        metalLayer.backgroundColor = CGColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
        
        metalLayer.allowsNextDrawableTimeout = false
        
        // these properties are crucial to resizing working
        metalLayer.autoresizingMask = CAAutoresizingMask(arrayLiteral: [.layerHeightSizable, .layerWidthSizable])
        metalLayer.needsDisplayOnBoundsChange = true
        metalLayer.presentsWithTransaction = true
        metalLayer.framebufferOnly = false
        return metalLayer
    }
    
    override func setFrameSize(_ newSize: NSSize) {
        super.setFrameSize(newSize)
        self.size = newSize
        renderer.viewportSize.x = UInt32(newSize.width)
        renderer.viewportSize.y = UInt32(newSize.height)
        // the conversion below is necessary for high DPI drawing
        metalLayer.drawableSize = convertToBacking(newSize)
        self.viewDidChangeBackingProperties()
    }
    var size: CGSize = .zero
    // This will hopefully be called if the window moves between monitors of
    // different DPIs but I haven't tested this part
    override func viewDidChangeBackingProperties() {
        guard let window = self.window else { return }
        // This is necessary to render correctly on retina displays with the topLeft placement policy
        metalLayer.contentsScale = window.backingScaleFactor
    }
    
    func display(_ layer: CALayer) {
        
        if let drawable = metalLayer.nextDrawable(),
           let commandBuffer = commandQueue.makeCommandBuffer() {
            
            let passDescriptor = MTLRenderPassDescriptor()
            let colorAttachment = passDescriptor.colorAttachments[0]!
            colorAttachment.texture = drawable.texture
            colorAttachment.loadAction = .clear
            colorAttachment.storeAction = .store
            colorAttachment.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
            
            if let outputImage = self.ciImage {
                
                let xscale = self.size.width / outputImage.extent.width
                let yscale = self.size.height / outputImage.extent.height
                let scale = min(xscale, yscale)
                
                if let scaledImage = self.ciMgr!.scaleTransformFilter(outputImage, scale: scale, aspectRatio: 1),
                   
                   let processed = self.showEdits ? self.ciMgr!.processImage(inputImage: scaledImage) : scaledImage {
                    
                    let x = self.size.width/2 - processed.extent.width/2
                    let y = self.size.height/2 - processed.extent.height/2
                    context.render(processed,
                                   to: drawable.texture,
                                   commandBuffer: commandBuffer,
                                   bounds: CGRect(x:-x, y:-y, width: self.size.width, height:  self.size.height),
                                   colorSpace: colorSpace)
                    }
                
                
                
            } else {
                print("Image is nil")
            }
            commandBuffer.commit()
            commandBuffer.waitUntilScheduled()
            drawable.present()
        }
    }
}

enter image description here