【问题标题】:Cropping is not working perfectly as per the frame drawn根据绘制的框架,裁剪无法正常工作
【发布时间】:2021-06-22 15:03:07
【问题描述】:

我正在尝试裁剪根据 ProportionallyUpOrDown(AspectFill) 模式安装的 NSImage 的选定部分。 我正在使用这样的鼠标拖动事件绘制框架:

class CropImageView: NSImageView {

    var startPoint: NSPoint!
    var shapeLayer: CAShapeLayer!
    var flagCheck = false
    var finalPoint: NSPoint!

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
    }
    
    override var image: NSImage? {
        set {
            self.layer = CALayer()
            self.layer?.contentsGravity = kCAGravityResizeAspectFill
            self.layer?.contents = newValue
            self.wantsLayer = true
            super.image = newValue
        }
        get {
            return super.image
        }
    }

    override func mouseDown(with event: NSEvent) {

        self.startPoint = self.convert(event.locationInWindow, from: nil)
        if self.shapeLayer != nil {
            self.shapeLayer.removeFromSuperlayer()
            self.shapeLayer = nil
        }
        self.flagCheck = true
        var pixelColor: NSColor = NSReadPixel(startPoint) ?? NSColor()
        shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = 1.0
        shapeLayer.fillColor = NSColor.clear.cgColor
        if pixelColor == NSColor.black {
            pixelColor = NSColor.color_white
        } else {
            pixelColor = NSColor.black
        }
        shapeLayer.strokeColor = pixelColor.cgColor
        shapeLayer.lineDashPattern = [1]
        self.layer?.addSublayer(shapeLayer)

        var dashAnimation = CABasicAnimation()
        dashAnimation = CABasicAnimation(keyPath: "lineDashPhase")
        dashAnimation.duration = 0.75
        dashAnimation.fromValue = 0.0
        dashAnimation.toValue = 15.0
        dashAnimation.repeatCount = 0.0
        shapeLayer.add(dashAnimation, forKey: "linePhase")
    }

    override func mouseDragged(with event: NSEvent) {
        let point: NSPoint = self.convert(event.locationInWindow, from: nil)

        var newPoint: CGPoint = self.startPoint
        
        let xDiff = point.x - self.startPoint.x
        let yDiff = point.y - self.startPoint.y
        let dist = min(abs(xDiff), abs(yDiff))
        newPoint.x += xDiff > 0 ? dist : -dist
        newPoint.y += yDiff > 0 ? dist : -dist
        
        let path = CGMutablePath()
        path.move(to: self.startPoint)
        path.addLine(to: NSPoint(x: self.startPoint.x, y: newPoint.y))
        path.addLine(to: newPoint)
        path.addLine(to: NSPoint(x: newPoint.x, y: self.startPoint.y))
        path.closeSubpath()
        self.shapeLayer.path = path
    }

    override func mouseUp(with event: NSEvent) {
        self.finalPoint = self.convert(event.locationInWindow, from: nil)
    }
}

并用黑色虚线选中该区域,如图所示:

我的裁剪代码逻辑是这样的:

// resize Image Methods
extension CropProfileView {

    func resizeImage(image: NSImage) -> Data {

        var scalingFactor: CGFloat = 0.0
        if image.size.width >= image.size.height {
            scalingFactor = image.size.width/cropImgView.size.width
        } else {
            scalingFactor = image.size.height/cropImgView.size.height
        }
        let width = (self.cropImgView.finalPoint.x - self.cropImgView.startPoint.x) * scalingFactor
        let height = (self.cropImgView.startPoint.y - self.cropImgView.finalPoint.y) * scalingFactor
        let xPos = ((image.size.width/2) - (cropImgView.bounds.midX - self.cropImgView.startPoint.x) * scalingFactor)
        let yPos = ((image.size.height/2) - (cropImgView.bounds.midY - (cropImgView.size.height - self.cropImgView.startPoint.y)) * scalingFactor)
        
        var croppedRect: NSRect = NSRect(x: xPos, y: yPos, width: width, height: height)
        let imageRef = image.cgImage(forProposedRect: &croppedRect, context: nil, hints: nil)
        guard let croppedImage = imageRef?.cropping(to: croppedRect) else {return Data()}
        let imageWithNewSize = NSImage(cgImage: croppedImage, size: NSSize(width: width, height: height))

        guard let data = imageWithNewSize.tiffRepresentation,
              let rep = NSBitmapImageRep(data: data),
            let imgData = rep.representation(using: .png, properties: [.compressionFactor: NSNumber(floatLiteral: 0.25)]) else {
            return imageWithNewSize.tiffRepresentation ?? Data()
        }
        return imgData
    }
}

有了这个裁剪逻辑,我得到了这个输出:

我认为图像是 AspectFill,这就是为什么它没有按照选定的帧被裁剪成完美尺寸的原因。在这里,如果您查看输出:xpositon & width & heights 并不完美。或者我可能没有正确计算这些坐标。让我知道可能是我计算错误的错误。

【问题讨论】:

  • stackoverflow.com/questions/43720720/… - 适用于 UIKit 但原理完全相同。
  • @matt 我同意你和你对 iOS 平台的回答,我也试图在提出的问题中遵循同样的方法。但是我们没有 UIGraphicsImageRenderer() 也不能在 CGImage 上使用绘图功能。我没有拖动视图,但它与我提取坐标相同,但我仍然怀疑我的 x 和 y 坐标计算是否正确。另外我会要求它无权对这个问题提出密切请求,因为它是一个不同的问题。
  • NSImageView的宽高一样吗?
  • 没有宽度,高度不一样@Willeke
  • 在决定使用哪个缩放因子时,您必须考虑图像视图的大小。见stackoverflow.com/questions/43720720/…

标签: swift macos crop nsimage


【解决方案1】:

注意:问题中的CropImageView 类是NSImageView 的子类,但视图是层托管的,并且图像是由层绘制的,而不是由NSImageView 绘制的。 imageScaling 未使用。

在决定使用哪个缩放因子时,您必须考虑图像视图的大小。如果图像大小为width:120, height:100,图像视图大小为width:120, height 80,则image.size.width >= image.size.heighttrueimage.size.width/cropImgView.size.width1,但图像已缩放,因为image.size.height/cropImgView.size.height1.25。计算水平和垂直缩放因子并使用最大的。

How to crop a UIImageView to a new UIImage in 'aspect fill' mode?

这是croppedRect 的计算,假设cropImgView.size 返回self.layer!.bounds.size

var scalingWidthFactor: CGFloat = image.size.width/cropImgView.size.width
var scalingHeightFactor: CGFloat = image.size.height/cropImgView.size.height
var xOffset: CGFloat = 0
var yOffset: CGFloat = 0
switch cropImgView.layer?.contentsGravity {
    case CALayerContentsGravity.resize: break
    case CALayerContentsGravity.resizeAspect:
        if scalingWidthFactor > scalingHeightFactor {
            scalingHeightFactor = scalingWidthFactor
            yOffset = (cropImgView.size.height - (image.size.height / scalingHeightFactor)) / 2
        }
        else {
            scalingWidthFactor = scalingHeightFactor
            xOffset = (cropImgView.size.width - (image.size.width / scalingWidthFactor)) / 2
        }
    case CALayerContentsGravity.resizeAspectFill:
        if scalingWidthFactor < scalingHeightFactor {
            scalingHeightFactor = scalingWidthFactor
            yOffset = (cropImgView.size.height - (image.size.height / scalingHeightFactor)) / 2
        }
        else {
            scalingWidthFactor = scalingHeightFactor
            xOffset = (cropImgView.size.width - (image.size.width / scalingWidthFactor)) / 2
        }
    default:
        print("contentsGravity \(String(describing: cropImgView.layer?.contentsGravity)) is not supported")
        return nil
}
let width = (self.cropImgView.finalPoint.x - self.cropImgView.startPoint.x) * scalingWidthFactor
let height = (self.cropImgView.startPoint.y - self.cropImgView.finalPoint.y) * scalingHeightFactor
let xPos = (self.cropImgView.startPoint.x - xOffset) * scalingWidthFactor
let yPos = (cropImgView.size.height - self.cropImgView.startPoint.y - yOffset) * scalingHeightFactor

var croppedRect: NSRect = NSRect(x: xPos, y: yPos, width: width, height: height)

修正:cropImgView.finalPoint 应该是选择的角落,而不是mouseUp 的位置。在CropImageView 中设置self.finalPoint = newPoint 中的mouseDragged 而不是mouseUp

【讨论】:

  • @Willleke 我真的很感谢你花时间思考这个问题,因为我没有得到很多关于 MacOS 的回复,我尝试了这个解决方案,但它没有任何区别。因为我们很少能找到与任何开发人员的 imageView 高度/宽度具有相同宽度/高度属性的图像。我已经从你提到的马特的回答中尝试了很多,并且在问题的 cmets 中也提到了同样的问题,但没有任何结果。在尝试了这些答案之后,提出了这个问题。
猜你喜欢
  • 2012-07-04
  • 2015-03-30
  • 1970-01-01
  • 2012-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
相关资源
最近更新 更多