Как обойти плохой рендеринг текста в тексте, поддерживаемом CALayer

У меня есть переменный текст в NSTextField, который отображается в фоновом режиме CALayer. Поскольку CALayer не поддерживает псевдопиксельное наложение для рендеринга любого текста поверх него, этот текст выглядит ерундой.

Небольшое гугление показывает, почему это так, и этот текст должен отображаться на непрозрачном фоне, чтобы включить SPA. Рендеринг на непрозрачный фон — это то, чего я хотел бы избежать, если это вообще возможно в этом случае. Есть ли лучший обходной путь?

Я полностью готов сам преобразовать текст в NSImage, если это поможет, но я не могу найти никаких подтвержденных сообщений, что это поможет.

В Interface Builder это выглядит абсолютно нормально, поэтому я знаю, что секрет находится где-то внутри этого компьютера, который пытается выбраться наружу.


person Nick Locking    schedule 27.11.2010    source источник
comment
Не могли бы вы опубликовать код, используемый для изменения слоя NSTextView?   -  person Evan Mulawski    schedule 01.12.2010
comment
Там очень мало фактического кода, потому что я в основном делаю что-то в Interface Builder. Есть NSView, который управляет NSImageVie.   -  person Nick Locking    schedule 02.12.2010
comment
... который управляет NSImageView с полупрозрачным PNG в нем. Текст лежит сверху.   -  person Nick Locking    schedule 02.12.2010
comment
Тот, кто ответит на это, получит не только 50 репутации, но и мою вечную благодарность. Я искал решение этой проблемы в течение нескольких месяцев.   -  person vilhalmer    schedule 05.12.2010


Ответы (4)


Найден обходной путь. Казалось бы, ничто в Quartz не может отображать текст с субпиксельным псевдонимом поверх прозрачного фона. Однако вы МОЖЕТЕ визуализировать текст в закадровый буфер растрового изображения, при условии, что внеэкранный буфер растрового изображения был создан правильным образом. Фон этого буфера должен быть непрозрачным.

У моего представления ранее был фон PNG, который был слегка прозрачным. Я мог бы просто сделать этот фон непрозрачным и отобразить его без проблем, но поскольку этот вид должен постепенно появляться и исчезать, он должен был поддерживаться CALayer, чтобы текст отображался правильно один раз, а затем впоследствии отображался без субпиксельного псевдонима. .

Вот мой код. Это кажется невероятно многословным, я был бы рад, если бы кто-нибудь помог мне сократить его. Предполагается, что у вас есть NSImageView с именем _title и NSString с именем title.

// Create the attributed string
NSMutableAttributedString *attStr = [[[NSMutableAttributedString alloc] initWithString: title] autorelease];

NSRange strRange = NSMakeRange(0, [attStr length]);
NSFont *font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize: NSSmallControlSize]];

[attStr addAttribute: NSForegroundColorAttributeName value: [NSColor whiteColor] range: strRange];
[attStr addAttribute: NSFontAttributeName value: font range: strRange];

NSMutableParagraphStyle *paraStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
[paraStyle setAlignment: NSCenterTextAlignment];
[paraStyle setLineBreakMode: NSLineBreakByTruncatingMiddle];
[attStr addAttribute: NSParagraphStyleAttributeName value: paraStyle range: strRange];

// Set up the image context
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * [_title frame].size.width;
NSUInteger bitsPerComponent = 8;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// These constants are necessary to enable sub-pixel aliasing.
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;

// Create the memory buffer to be used as the context's workspace
unsigned char *contextBuffer = malloc([_title frame].size.height * [_title frame].size.width * 4);

CGContextRef context = CGBitmapContextCreate(contextBuffer, 
                                             [_title frame].size.width, 
                                             [_title frame].size.height, 
                                             bitsPerComponent, 
                                             bytesPerRow, 
                                             colorSpace, 
                                             bitmapInfo);


[NSGraphicsContext saveGraphicsState];

[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]];


CGContextSetFillColorWithColor(context, CGColorGetConstantColor(kCGColorBlack));
CGRect rectangle = CGRectMake(0, 0, [_title frame].size.width,[_title frame].size.height);
CGContextAddRect(context, rectangle);
CGContextFillPath(context);

CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attStr);

CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);

// Create a data provider from the context buffer in memory
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, contextBuffer, bytesPerRow * [_title frame].size.height, NULL);

// Create an image from the data provider
CGImageRef imageRef = CGImageCreate ([_title frame].size.width,
                                     [_title frame].size.height,
                                     bitsPerComponent,
                                     bytesPerPixel * 8,
                                     bytesPerRow,
                                     colorSpace,
                                     bitmapInfo,
                                     dataProvider,
                                     NULL,
                                     false,
                                     kCGRenderingIntentDefault
                                     );

// Turn it into an NSImage
NSImage *newImage = [[[NSImage alloc] initWithCGImage:imageRef size: NSZeroSize] autorelease];

CGDataProviderRelease(dataProvider);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
CGImageRelease(imageRef);

free(contextBuffer);

[NSGraphicsContext restoreGraphicsState];

[_title setImage: newImage];
person Nick Locking    schedule 20.12.2010
comment
Ах, CGImageRef imageRef = CGBitmapContextCreateImage(context); — лучший способ вырвать изображение из контекста. - person Nick Locking; 21.12.2010
comment
Рад слышать, что вы разобрались! Я предполагаю, что причина, по которой он не может смешиваться, заключается в том, что с прозрачным фоном он не знал бы, с чем смешиваться, отсюда и причина непрозрачности. - person slf; 22.12.2010
comment
Довольно много, да. Если бы дело было только в том, что каждый пиксель является ARGB, он мог бы просто отобразить его как ARGB и покончить с этим, но Sub-Pixel Aliasing фактически использует ARAGAB, то есть альфа-значение для каждого цвета, а не для пикселя в целом. - person Nick Locking; 23.12.2010

Я не совсем понимаю, каковы ваши ограничения или почему вам обязательно нужно рисовать на слое, но новое в os4.1 среди прочего, Основной текст был перенесен на iOS с рабочего стола. Вы, вероятно, можете воспользоваться его расширенными функциями настройки шрифта для отображения глифов вдоль линий или даже дуг, и он сделает большую часть тяжелой работы за вас. Это относительно низкоуровневый API, но очень быстрый.

- (void)drawLayer:(CALayer *)theLayer
        inContext:(CGContextRef)context
{
    CFStringRef string; CTFontRef font;  // assuming these exist

    // Initialize string, font, and context
    CFStringRef keys[] = { kCTFontAttributeName };
    CFTypeRef values[] = { font };

    CFDictionaryRef attributes =
        CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
            (const void**)&values, sizeof(keys) / sizeof(keys[0]),
            &kCFTypeDictionaryCallBacks,
            &kCFTypeDictionaryValueCallbacks);

    CFAttributedStringRef attrString =
        CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
    CFRelease(string);
    CFRelease(attributes);

    CTLineRef line = CTLineCreateWithAttributedString(attrString);

    // Set text position and draw the line into the graphics context
    CGContextSetTextPosition(context, 10.0, 10.0);
    CTLineDraw(line, context);
    CFRelease(line);
}

Другие примеры включают CoreTextArcCocoa и CoreTextTest

person slf    schedule 07.12.2010
comment
Это не работает, потому что Core Text имеет те же ограничения, что и остальная часть Quartz. Тем не менее, это навело меня на правильный путь. - person Nick Locking; 21.12.2010

Вот как оригинальный постер не хотел этого делать, но мне было нормально иметь непрозрачный фон...

когда вы делаете слой:

    layer.opaque = YES;

Затем в

  - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context

     [NSGraphicsContext saveGraphicsState];
      NSGraphicsContext *nscg = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO];
      [NSGraphicsContext setCurrentContext:nscg];

      NSRect ourframe = self.frame;
      // white background enables antialiasing
      [[NSColor whiteColor] setFill];
      NSRect ourNSFrame = ourframe;
      ourNSFrame.origin = NSZeroPoint;
      NSRectFill(ourNSFrame);

     [sometext drawInRect:ourNSFrame withAttributes:someattrs];

     [NSGraphicsContext restoreGraphicsState];
person Tom Andersen    schedule 09.10.2013

Каковы позиции X, Y текста после заполнения строки переменной? У меня была аналогичная проблема для UILabel, которая была вызвана тем, что позиция XY текста была с плавающей запятой. (он пытался центрировать переменные данные, а значения XY были половиной пикселей)

Я обрезал значения, и это исправило размытый текст.

person Ovi Tisler    schedule 02.12.2010
comment
Нет, у меня уже возникла эта проблема, поскольку вид контейнера управления центрирован, поэтому в половине случаев при изменении размера он отображался в позиции x.5 и искажался. Это чисто рендеринг текста, а не позиционирование. - person Nick Locking; 02.12.2010