Quartz 2D realizes the effect of text hollowing out

What is the effect of hollowing, the following figure is a text with the effect of hollowing out:

As can be seen from the figure, the text is transparent, and you can see the following picture content, while other parts of the UILabel have a white background.

Using Quartz 2D to draw the hollowing effect, the general idea is as follows:

  1. Implement a subclass of UILabel (this is not necessary. The purpose of this is to use the text drawing function of UILabel to draw text conveniently. You can also use CoreText to draw text)
  2. When initializing the current view, make sure that the backgroundColor = Clear (this is the key to realizing the failed text, because the text part must be transparent)
  3. Use the drawing method of UILabel to draw a text mask (Note: the original drawing must be transparent with white text (ensure textColor is. White). In this way, we can correctly create a text mask we need)
  4. Call clip(to rect: CGRect, mask: CGImage) to clip the context of the current view. The mask is the mask created in 3 (the coordinate system of the context needs to be converted before that)
  5. Fill context background color

Attach Code:

class PiercedLabel: UILabel {
    
    override var textColor: UIColor!{
        didSet {
            if textColor != .white {
                textColor = .white // To achieve a hollowed out text, the color here must be set to white
            }
        }
    }
    
    override var backgroundColor: UIColor?{
        didSet {
            // backgroundColor Be sure to always.clear(Transparent color), so as to achieve a hollowed out effect
            guard let color = backgroundColor else {
                backgroundColor = .clear
                // When set backgroundColor == nil When, our default background is white
                bgColor = .white
                return
            }
            if color != .clear {
                bgColor = color // Keep the background color set so that you can draw the background later
                backgroundColor = .clear
            }
        }
    }
    
    // Used to draw the background color
    private var bgColor: UIColor!
    
    init() {
        super.init(frame: .zero)
        config()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        config()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        config()
    }
    
    private func config(){
        textColor = .white
        bgColor = .white
        backgroundColor = .clear
    }
    
    override func draw(_ rect: CGRect) {
        
        // Create a graphic context for drawing pictures, which must be transparent in order to create a text mask
        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
        guard let maskContext = UIGraphicsGetCurrentContext() else {
            return
        }
        // Note: at this time maskContext Not currently draw Method, but we call UIGraphicsBeginImageContextWithOptions
        //      Build a new graphic context in which we draw a text mask
        // Draw text to maskContext In context
        super.draw(rect)
        // obtain maskContext Contextual bitmap picture
        guard let textImg = maskContext.makeImage() else {
            UIGraphicsEndImageContext() // end maskContext context
            return
        }
        UIGraphicsEndImageContext() // end maskContext context
        // Note: at this time textImg It cannot be used as a text mask. We need to create a mask image by the following methods
        // Create mask layout
        guard let maskImg = CGImage(maskWidth: textImg.width,
                                    height: textImg.height,
                                    bitsPerComponent: textImg.bitsPerComponent,
                                    bitsPerPixel: textImg.bitsPerPixel,
                                    bytesPerRow: textImg.bytesPerRow,
                                    provider: textImg.dataProvider!,
                                    decode: textImg.decode,
                                    shouldInterpolate: textImg.shouldInterpolate) else {
            return
        }
        
        // Get current draw Method, we need to draw the content here
        guard let ctx = UIGraphicsGetCurrentContext() else {
            return
        }
        // Save the current context state first
        ctx.saveGState()
        ctx.clear(rect)
        // Adjust the coordinate system of the current context because ctx.draw The coordinate system is drawn according to the origin in the lower left corner
        // Therefore, we need to change the coordinate system to the upper left corner, so that the drawing result is correct, otherwise the text is reversed
        ctx.translateBy(x: 0, y: rect.height)
        ctx.scaleBy(x: 1, y: -1)
        
        // Using a mask for clipping, it will ctx context rect The text part of the area is cut off
        ctx.clip(to: rect, mask: maskImg)
        
        // Draw the background color (Note: the cut part will not be filled, which will form a hollow effect)
        // because ctx The context is based on the current view backgroundColor Created, and backgroundColor == .clear,Is a transparent
        // therefore ctx It is a context with a transparent layer, and the cropped text is transparent
        // That's why make sure backgroundColor == .clear Yes
        bgColor.setFill()
        ctx.fill(rect)
    }
}

The comments in the code have been written in great detail, and the specific explanation will not be described in detail

The following is a comparison between the original and the mask:

Original text: it is a transparent background with white text

Mask: it is a transparent background with black text

Some people will think, can we directly use the original picture as a mask? Yes, but that will not be the effect we want. It will be the following effect:

The background color becomes the color of the text, while the background color becomes transparent. This is the opposite of what we want.

Therefore, we must create a mask image through CGImageMaskCreate.

For an explanation of ImageMask, see Apple Official documents

Description of interception part:

Image masks must be 1, 2, 4, or 8 bits per component. For a 1-bit mask, a sample value of 1 specifies sections of the mask that are masked out; these sections block the current fill color. A sample value of 0 specifies sections of the mask that are not masked out; these sections show the current fill color of the graphics state when the mask is painted. You can think of the sample values as an inverse alpha. That is, a value of 1 is transparent and 0 is opaque.

 

Keywords: Swift

Added by dgwade on Wed, 12 Jan 2022 12:06:34 +0200