根据绘制的框架,裁剪效果不佳

Cropping is not working perfectly as per the frame drawn

我正在尝试裁剪根据 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 并不完美。或者可能我没有正确计算这些坐标。让我知道可能是我计算错误的错误。

注意:问题中的CropImageViewclass是NSImageView的子class,但是view是layer-hosting,图像是由layer绘制的,而不是 NSImageViewimageScaling 未使用。

在决定使用哪个比例因子时,您必须考虑图像视图的大小。如果图像大小为 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。计算水平和垂直比例因子,取最大的。

这里是 croppedRect 的计算,假设 cropImgView.size returns 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 中,在 mouseDragged 中设置 self.finalPoint = newPoint 而不是 mouseUp