0

我正在尝试使用 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()
        }
    }
}

如您所见,图像位于左下角

屏幕样本

4

3 回答 3

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/

于 2020-07-06T10:25:48.153 回答
0

感谢 Tristan Hume 的 MetalTest2,我现在可以在两个同步的滚动视图中很好地工作。基础知识在下面的子类中——渲染器和着色器可以在 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()
        }
    }
}

在此处输入图像描述

于 2020-07-08T04:39:44.610 回答
0

问题是你打电话给context.render- 你打电话renderbounds:origin .zero。那是左下角。

将图纸放置在正确的位置取决于您。您需要根据图像尺寸和可绘制尺寸确定正确的边界原点应该在哪里,并在那里进行渲染。如果大小错误,还需要先应用缩放变换。

于 2020-07-06T12:24:22.223 回答