I want to create a mask using text and need to convert the text into an image. But after converting, I found that it did not match exactly with the original text. I conducted the following tests.
class MyView: NSView{
override func draw(_ dirtyRect: NSRect) {
guard let g = NSGraphicsContext.current?.cgContext else{ return }
let antialiasing = false //true or false is useless
g.setAllowsAntialiasing(antialiasing)
g.setShouldAntialias(antialiasing)
let text: NSString = "HELLO"
let pos: CGPoint = .init(x: 20, y: 20)
let font: NSFont = .init(name: "Arial Black", size: 96)!
var textAttr: [NSAttributedString.Key: Any] = [
.font : font
]
//Draw the blue text in new CGContext, generate an image, draw the image in current CGContext
let image = createImage(dirtyRect: dirtyRect) { g in
g.setAllowsAntialiasing(antialiasing)
g.setShouldAntialias(antialiasing)
textAttr[.foregroundColor] = NSColor.blue
text.draw(at: pos, withAttributes: textAttr)
}
g.draw(image!, in: dirtyRect, byTiling: false)
//Draw the yellow text with all same properties
let attr: [NSAttributedString.Key: Any] = [
.font : font,
.foregroundColor : NSColor.yellow
]
text.draw(at: pos, withAttributes: attr)
}
private func createImage(dirtyRect: NSRect, drawingHandler: ((_ g: CGContext) -> Void)?) -> CGImage?{
NSGraphicsContext.saveGraphicsState()
let g = CGContext(data: nil,
width: Int(dirtyRect.width),
height: Int(dirtyRect.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
NSGraphicsContext.current = NSGraphicsContext(cgContext: g, flipped: false)
drawingHandler?(g)
let image = g.makeImage()
NSGraphicsContext.restoreGraphicsState()
return image
}
}
As can be seen, the two texts cannot completely overlap after being stacked, and the blue text below reveals a circle of edges.
I tried other createImage
methods, such as creating an NSImage
and drawing text on it, or creating a CGLayer
and drawing text on this layer then create the image, and the results were similar.
The only perfect solution is to use the current CGContext
and disable Anti-aliasing to generate text image. But the problem with this method is that the generated image will contain all the drawn content, some of which may not be necessary for me.
private func createImage(dirtyRect: NSRect, drawingHandler:((_ g: CGContext) -> Void)?) -> CGImage?{
drawingHandler?(NSGraphicsContext.current!.cgContext)
return NSGraphicsContext.current?.cgContext.makeImage()
}
Any solutions? Thanks.
Try printing out the g
s in both draw(_:)
and createImage
. They are of different types, so it is not surprising that they behave differently (though I cannot tell you exactly what the difference is).
The two texts do overlap, if you draw into an NSImage
, using this initialiser, then convert that image to CGImage
using the current context.
private func createImage(dirtyRect: NSRect, drawingHandler: @escaping (_ g: CGContext) -> Void) -> CGImage?{
let image = NSImage(size: dirtyRect.size, flipped: false) { _ in
guard let g = NSGraphicsContext.current?.cgContext els { return false }
drawingHandler(g)
return true
}
// presumably passing context: nil uses the current context,
// but you can also use context: NSGraphicsContext.current
return image.cgImage(forProposedRect: nil, context: nil, hints: nil)
}