drawRect рисовать «прозрачный» текст? - PullRequest
11 голосов
/ 04 января 2012

Я хочу нарисовать UILabel (предпочтительно через подклассы) в качестве прозрачной метки, но с твердым фоном. Я набираю быстрый пример (извините, это некрасиво, но в нем есть точки:)).

В основном у меня есть UILabel, и я хотел бы, чтобы фон был заданным цветом, а текст должен быть прозрачным. Я не хочу окрашивать текст фоном представлений, но вместо этого он должен быть прозрачным на 100%, поскольку у меня есть текстура на заднем плане, и я хочу, чтобы линии располагались внутри и снаружи надписи.

Я провел ночь, просматривая SO и ища в Google, но я не нашел полезных источников. У меня нет большого опыта работы с CG-графикой, поэтому я был бы признателен за любые ссылки, помощь, учебное пособие или пример кода (возможно, у Apple есть какие-то, на которые мне нужно взглянуть?)

Спасибо большое!

enter image description here

Ответы [ 3 ]

12 голосов
/ 08 февраля 2013

Я переписал его как подкласс 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);

}
4 голосов
/ 05 января 2012

Вот метод, похожий на метод Мэтта Галлахера, который генерирует инвертированную текстовую маску с изображением.

Выделите (изменяемый) буфер данных. Создайте растровый контекст с 8-битным альфа-каналом. Настройте параметры для рисования текста. Заполните весь буфер в режиме копирования (цвет по умолчанию предполагается, что альфа-значение равно 1). Напишите текст в чистом режиме (альфа-значение 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 и рисовать текст непосредственно в чистом режиме после того, как суперкласс завершил рисование?).

4 голосов
/ 05 января 2012

Решено с использованием масок CALayer.Создать стандартную маску (например, фоновый текст) очень просто.Чтобы создать выбитый текст, мне пришлось инвертировать альфа-канал моей маски, который включал рендеринг метки в 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
...