如何在MTKView中正确放置图像? [英] How do I position an image correctly in MTKView?

查看:95
本文介绍了如何在MTKView中正确放置图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to implement an image editing view using MTKView and Core Image filters and have the basics working and can see the filter applied in realtime. However the image is not positioned correctly in the view - can someone point me in the right direction for what needs to be done to get the image to render correctly in the view. It needs to fit the view and retain its original aspect ratio.

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

Here is the metal draw function - and the empty drawableSizeWillChange!? - go figure. its probably also worth mentioning that the MTKView is a subview of another view in a ScrollView and can be resized by the user. It's not clear to me how Metals handles resizing the view but it seems that doesn't come for free.

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

I am also trying to call the draw() function from a background thread and this appears to sort of work. I can see the filter effects as they are applied to the image using a slider. As I understand it this should be possible.

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

It also seems that the coordinate space for rendering is in the images coordinate space - so if the image is smaller than the MTKView then to position the image in the centre the X and Y coordinates will be negative.

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

When the view is resized then everything gets crazy with the image suddenly becoming way too big and parts of the background not being cleared.

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

Also when resting the view the image gets stretched rather than redrawing smoothly.

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()
        }
    }
}

如您所见,图像在左下方

As you can see the image is on the bottom left

推荐答案

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

Thanks to Tristan Hume's MetalTest2 I now have it working nicely in two synchronised scrollViews. The basics are in the subclass below - the renderer and shaders can be found at Tristan's MetalTest2 project. This class is managed by a viewController and is a subview of the scrollView's documentView. See image of the final result.

//
//  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()
        }
    }
}

这篇关于如何在MTKView中正确放置图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆