使用 NSBezierPath 绘制一个点

Draw a Point using NSBezierPath

我想创建一个像素大小的点。我假设这样做的方法是使用 NSBezierPath 画一条线,起点等于终点(见下面的代码),但这样做会使一个完全空的 window。

func drawPoint() {
    let path = NSBezierPath()
    NSColor.blue.set()
    let fromPoint = NSMakePoint(CGFloat(100) , CGFloat(100))
    let toPoint = NSMakePoint(CGFloat(100) , CGFloat(100))
    path.move(to: fromPoint)
    path.line(to: toPoint)
    path.lineWidth = 1.0
    path.stroke()
    path.fill()
}

但是,如果我将 toPoint 坐标从 100,100 更改为 101,101(或 100,100 以外的任何一组坐标),则会显示蓝色形状。有谁知道为什么会这样吗?

我发现的另一个问题是,如果你让 toPoint 的坐标等于(例如)101,101,那么它会创建一个长度为两个像素的正方形,但还会添加另一个像素长度的模糊层(见图下面,放大查看详细信息)。

然而,我想做的只是放一个只有一个像素大小的蓝色小方块,没有模糊。有谁知道我该怎么做?

你有几个问题:

I want to create a single pixel-size dot.

你确定吗?如果你在 Retina 显示屏上怎么办?如果您正在绘制将在 600 dpi 激光打印机上打印的 PDF 怎么办?

I assumed the way to do so was to draw a line using NSBezierPath with the starting point being equal to the end point

这是一种绘制 lineWidth 大小的点的方法,如果 您将 lineCap 属性 设置为 .round.square。默认的 lineCap.butt,这会导致空笔画。如果您将 lineCap 设置为 .round.square,您将得到一个非空笔划。

let fromPoint = NSMakePoint(CGFloat(100) , CGFloat(100))
let toPoint = NSMakePoint(CGFloat(100) , CGFloat(100))
path.move(to: fromPoint)
path.line(to: toPoint)

您需要了解坐标网格与像素的关系:

  • 在非 Retina 屏幕上,整数坐标默认位于像素的边缘,而不是像素的中心。沿整数坐标的笔画横跨像素之间的边界,因此(使用 lineWidth = 1.0)你会得到多个部分填充的像素。要填充单个像素,您需要使用以 .5(像素中心)结尾的坐标。

  • 在 Retina 屏幕上,整数和半整数坐标默认位于像素的边缘,而不是像素的中心。沿整数或半整数坐标的笔画横跨像素之间的边界。使用lineWidth = 1.0,两边的像素被完全填满。如果只想填充单个像素,则需要使用以 .25.75 结尾的坐标和 0.5lineWidth

  • 在未缩放的 PDF 上下文中,1.0lineWidth 名义上对应于 1/72 英寸,填充像素的数量取决于输出设备。

path.lineWidth = 1.0

这只能在非 Retina 屏幕上为您提供“单个像素大小的点”(或者如果您缩放了图形上下文)。如果您真的想要在 Retina 屏幕上显示单个像素点,则需要对其进行调整。但是你最好坚持使用点而不是像素。

综上所述,以下是创建和描边 NSBezierPath 以填充一个像素的方法:

import AppKit

func dotImage() -> CGImage {
    let gc = CGContext(data: nil, width: 20, height: 20, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
    let nsGc = NSGraphicsContext(cgContext: gc, flipped: false)
    NSGraphicsContext.current = nsGc; do {

        let path = NSBezierPath()
        path.move(to: .init(x: 10.5, y: 10.5))
        path.line(to: .init(x: 10.5, y: 10.5))
        path.lineWidth = 1
        path.lineCapStyle = .round
        NSColor.blue.set()
        path.stroke()

    }; NSGraphicsContext.current = nil
    return gc.makeImage()!
}

let image = dotImage()

结果:

但是,您可能更愿意直接简单地填充一个矩形:

func dotImage2() -> CGImage {
    let gc = CGContext(data: nil, width: 20, height: 20, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
    let nsGc = NSGraphicsContext(cgContext: gc, flipped: false)
    NSGraphicsContext.current = nsGc; do {

        NSColor.blue.set()
        CGRect(x: 10, y: 10, width: 1, height: 1).fill()

    }; NSGraphicsContext.current = nil
    return gc.makeImage()!
}

let image2 = dotImage2()

结果:

在 rob mayoff 的解决方案之上,我想出了一个使用 NSGraphicsContext 的不同解决方案。我刚刚获取了 Core Graphics 上下文并关闭了抗锯齿(见下文)

 let context = NSGraphicsContext.current?.cgContext
 context?.setShouldAntialias(false)

然后,我创建了一个像素大小的路径:

 context.setLineWidth(0.5)
 context.setStrokeColor(color)
 context.beginPath()
 context.move(to: CGPoint(x: 100, y: 100))
 context.addLine(to: CGPoint(x: 100.15, y: 100.15))
 context.strokePath()

向直线添加端点时,我注意到通过给它一个大于 100 但小于 100.5 的值,您可以准确地得到一个像素的阴影。

要在:x,y 处画一个点,大小:宽,高。

NSBezierPath(ovalIn: CGRect(x: 5.5, y: 5.5, width: 4.0, height: 4.0)).fill()

要微调 "pixel" 位置和大小,请在 DoubleCGFloat 参数中使用小数。