drawRect绘制'透明'文本?

时间:2012-01-04 01:38:20

标签: uilabel drawrect cgcontext

我希望将UILabel(最好通过子类化)绘制为透明标签,但背景为实心。我画了一个简单的例子(对不起,这很难看,但它得到的结论是:)。)。

基本上我有一个UILabel,我希望背景是一个设定的颜色,文字应该是透明的。我不想用视图背景为文本着色,而是让它是100%透明的,因为我在背景中有一个纹理,我想确保在标签的内部和外部排列。

我一直在深夜浏览SO并在Google上搜索,但我找不到有用的消息来源。我对CG绘图没有太多经验,所以我很感激任何链接,帮助,教程或示例代码(也许Apple有一些我需要看一下?)。

非常感谢!

enter image description here

4 个答案:

答案 0 :(得分:12)

我使用几乎所有代码将其重写为UILabel子类并将其发布在GitHub

它的要点是你覆盖drawRect但是调用[super drawRect:rect]让UILabel正常渲染。使用白色标签颜色可以轻松地将标签本身用作蒙版。

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    // let the superclass draw the label normally
    [super drawRect:rect];

    CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));

    // create a mask from the normally rendered text
    CGImageRef image = CGBitmapContextCreateImage(context);
    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetBitsPerPixel(image), CGImageGetBytesPerRow(image), CGImageGetDataProvider(image), CGImageGetDecode(image), CGImageGetShouldInterpolate(image));

    CFRelease(image); image = NULL;

    // wipe the slate clean
    CGContextClearRect(context, rect);

    CGContextSaveGState(context);
    CGContextClipToMask(context, rect, mask);

    CFRelease(mask);  mask = NULL;

    [self RS_drawBackgroundInRect:rect];

    CGContextRestoreGState(context);

}

答案 1 :(得分:4)

使用CALayer面具解决。创建标准蒙版(例如墙纸文本)很简单。为了创建被淘汰的文本,我不得不反转我的蒙版的alpha通道,其中包括将标签渲染到CGImageRef然后进行像素推送。

sample mask

示例应用程序可在此处获取:https://github.com/robinsenior/RSMaskedLabel

相关代码是为了避免将来链接腐烂:

#import "RSMaskedLabel.h"
#import <QuartzCore/QuartzCore.h>

@interface UIImage (RSAdditions)
+ (UIImage *) imageWithView:(UIView *)view;
- (UIImage *) invertAlpha;
@end

@interface RSMaskedLabel ()
{
    CGImageRef invertedAlphaImage;
}
@property (nonatomic, retain) UILabel *knockoutLabel;
@property (nonatomic, retain) CALayer *textLayer;
- (void) RS_commonInit;
@end

@implementation RSMaskedLabel
@synthesize knockoutLabel, textLayer;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) 
    {
        [self RS_commonInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) 
    {
        [self RS_commonInit];
    }
    return self;
}

+ (Class)layerClass
{
    return [CAGradientLayer class];
}

- (void) RS_commonInit
{
    [self setBackgroundColor:[UIColor clearColor]];

    // create the UILabel for the text
    knockoutLabel = [[UILabel alloc] initWithFrame:[self frame]];
    [knockoutLabel setText:@"booyah"];
    [knockoutLabel setTextAlignment:UITextAlignmentCenter];
    [knockoutLabel setFont:[UIFont boldSystemFontOfSize:72.0]];
    [knockoutLabel setNumberOfLines:1];
    [knockoutLabel setBackgroundColor:[UIColor clearColor]];
    [knockoutLabel setTextColor:[UIColor whiteColor]];

    // create our filled area (in this case a gradient)
    NSArray *colors = [[NSArray arrayWithObjects:
                        (id)[[UIColor colorWithRed:0.349 green:0.365 blue:0.376 alpha:1.000] CGColor],
                        (id)[[UIColor colorWithRed:0.455 green:0.490 blue:0.518 alpha:1.000] CGColor],
                        (id)[[UIColor colorWithRed:0.412 green:0.427 blue:0.439 alpha:1.000] CGColor],
                        (id)[[UIColor colorWithRed:0.208 green:0.224 blue:0.235 alpha:1.000] CGColor],
                        nil] retain];

    NSArray *gradientLocations = [NSArray arrayWithObjects:
                                  [NSNumber numberWithFloat:0.0],
                                  [NSNumber numberWithFloat:0.54],
                                  [NSNumber numberWithFloat:0.55],
                                  [NSNumber numberWithFloat:1], nil];

    // render our label to a UIImage
    // if you remove the call to invertAlpha it will mask the text
    invertedAlphaImage = [[[UIImage imageWithView:knockoutLabel] invertAlpha] CGImage];

    // create a new CALayer to use as the mask
    textLayer = [CALayer layer];
    // stick the image in the layer
    [textLayer setContents:(id)invertedAlphaImage];

    // create a nice gradient layer to use as our fill
    CAGradientLayer *gradientLayer = (CAGradientLayer *)[self layer];

    [gradientLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
    [gradientLayer setColors: colors];
    [gradientLayer setLocations:gradientLocations];
    [gradientLayer setStartPoint:CGPointMake(0.0, 0.0)];
    [gradientLayer setEndPoint:CGPointMake(0.0, 1.0)];
    [gradientLayer setCornerRadius:10];

    // mask the text layer onto our gradient
    [gradientLayer setMask:textLayer];
}

- (void)layoutSubviews
{
    // resize the text layer
    [textLayer setFrame:[self bounds]];
}

- (void)dealloc 
{
    CGImageRelease(invertedAlphaImage);
    [knockoutLabel release];
    [textLayer     release];
    [super         dealloc];
}

@end

@implementation UIImage (RSAdditions)

/*
 create a UIImage from a UIView
 */
+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

/*
 get the image to invert its alpha channel
 */
- (UIImage *)invertAlpha
{
    // scale is needed for retina devices
    CGFloat scale = [self scale];
    CGSize size = self.size;
    int width = size.width * scale;
    int height = size.height * scale;

    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();

    unsigned char *memoryPool = (unsigned char *)calloc(width*height*4, 1);

    CGContextRef context = CGBitmapContextCreate(memoryPool, width, height, 8, width * 4, colourSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast);

    CGColorSpaceRelease(colourSpace);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]);

    for(int y = 0; y < height; y++)
    {
        unsigned char *linePointer = &memoryPool[y * width * 4];

        for(int x = 0; x < width; x++)
        {
            linePointer[3] = 255-linePointer[3];
            linePointer += 4;
        }
    }

    // get a CG image from the context, wrap that into a
    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    UIImage *returnImage = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationUp];

    // clean up
    CGImageRelease(cgImage);
    CGContextRelease(context);
    free(memoryPool);

    // and return
    return returnImage;
}
@end

答案 2 :(得分:4)

这是一种类似于Matt Gallagher的技术,它将生成带图像的反转文本掩码。

分配(可变)数据缓冲区。使用8位Alpha通道创建位图上下文。配置文本绘图的设置。在复制模式下填充整个缓冲区(默认颜色假定为alpha值为1)。以明文模式写入文本(alpha值为0)。从位图上下文创建图像。使用位图作为蒙版从源图像创建新图像。创建一个新的UIImage并清理。

每次textString或sourceImage或size值更改时,都会重新生成最终图像。

CGSize size = /* assume this exists */;
UIImage *sourceImage = /* assume this exists */;
NSString *textString = /* assume this exists */;
char *text = [textString cStringUsingEncoding:NSMacOSRomanStringEncoding];
NSUInteger len = [textString lengthOfBytesUsingEncoding:cStringUsingEncoding:NSMacOSRomanStringEncoding];

NSMutableData *data = [NSMutableData dataWithLength:size.width*size.height*1];
CGContextRef context = CGBitmapContextCreate([data mutableBytes], size.width, size.height, 8, size.width, NULL, kCGImageAlphaOnly);

CGContextSelectFont(context, "Gill Sans Bold", 64.0f, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);

CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextFillRect(context, overlay.bounds);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextShowTextAtPoint(context, 16.0f, 16.0f, text, len);

CGImageRef textImage = CGBitmapContextCreateImage(context);
CGImageRef newImage = CGImageCreateWithMask(sourceImage.CGImage, textImage);

UIImage *finalImage = [UIImage imageWithCGImage:newImage];

CGContextRelease(context);
CFRelease(newImage);
CFRelease(textImage);

另一种方法是将textImage放入新图层并在视图图层上设置该图层。 (删除创建“newImage”和“finalImage”的行。)假设这发生在视图代码中的某处:

CALayer *maskLayer = [[CALayer alloc] init];
CGPoint position = CGPointZero;

// layout the new layer
position = overlay.layer.position;
position.y *= 0.5f;
maskLayer.bounds = overlay.layer.bounds;
maskLayer.position = position;
maskLayer.contents = (__bridge id)textImage;

self.layer.mask = maskLayer;

还有更多选择,有些可能更好(子类UIImage并在超类完成绘图后直接以清除模式绘制文本?)。

答案 3 :(得分:0)

Swift 5 解决方案(Xcode:12.5):

enter image description here

class MaskedLabel: UILabel {
    
    var maskColor : UIColor?

    override init(frame: CGRect) {
        super.init(frame: frame)
        customInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        customInit()
    }

    func customInit() {
        maskColor = self.backgroundColor
        self.textColor = UIColor.white
        backgroundColor = UIColor.clear
        self.isOpaque = false
    }

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()!

        super.draw(rect)

        context.concatenate(__CGAffineTransformMake(1, 0, 0, -1, 0, rect.height))

        let image: CGImage = context.makeImage()!
        let mask: CGImage = CGImage(maskWidth: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, provider: image.dataProvider!, decode: image.decode, shouldInterpolate: image.shouldInterpolate)!

        context.clear(rect)
        
        context.saveGState()
        context.clip(to: rect, mask: mask)
        
        if (self.layer.cornerRadius != 0.0) {
            context.addPath(CGPath(roundedRect: rect, cornerWidth: self.layer.cornerRadius, cornerHeight: self.layer.cornerRadius, transform: nil))
            context.clip()
        }

        drawBackgroundInRect(rect: rect)
        context.restoreGState()
    }

    func drawBackgroundInRect(rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()

        if let _ = maskColor {
            maskColor!.set()
        }

        context!.fill(rect)
    }
}