根据绘制的框架,裁剪工作不完美

Shi*_*thi 4 macos crop nsimage swift

我正在尝试裁剪 NSImage 的选定部分,该部分按照 ProportionallyUpOrDown(AspectFill) 模式安装。我正在使用鼠标拖动事件绘制一个框架,如下所示:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用黑色虚线选择此区域,如图所示:

在此处输入图片说明

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

// 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
    }
}
Run Code Online (Sandbox Code Playgroud)

通过这种裁剪逻辑,我得到了这个输出:

在此处输入图片说明

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

Wil*_*eke 5

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

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

请参阅如何在“方面填充”模式下将 UIImageView 裁剪为新的 UIImage?

以下是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)
Run Code Online (Sandbox Code Playgroud)

修正:cropImgView.finalPoint应该是选区的角落,而不是mouseUp. 在CropImageViewset self.finalPoint = newPointinmouseDragged而不是mouseUp.