【问题标题】:How to work around poor text rendering in text backed by a CALayer如何解决由 CALayer 支持的文本中文本呈现不佳的问题
【发布时间】:2010-11-27 19:32:34
【问题描述】:

我在NSTextField 中有一些可变文本,在CALayer 背景视图上呈现。由于CALayer 不支持在其上渲染任何文本的子像素别名,因此该文本看起来很垃圾。

用谷歌搜索一下就会发现原因,并且必须将文本渲染到不透明的背景上才能启用 SPA。在这种情况下,如果可能的话,我想避免渲染到不透明的背景上。有更好的解决方法吗?

如果有帮助的话,我完全愿意自己将文本呈现为 NSImage,但我找不到任何证实的报告。

它在 Interface Builder 中看起来非常好,所以我知道秘密就在这台计算机内部的某个地方,只是在竭力想要逃出去。

【问题讨论】:

  • 能贴出修改NSTextView层的代码吗?
  • 实际代码很少,因为我主要在 Interface Builder 中做事。有一个 NSView 可以控制一个 NSImageVie
  • ...控制一个 NSImageView,其中有一个半透明的 PNG。文本位于其之上。
  • 回答这个问题的人不仅可以获得50声望,还有我永远的感激之情。几个月来我一直在寻找解决方案。

标签: objective-c core-animation text-rendering


【解决方案1】:

找到解决方法。 Quartz 中似乎没有任何东西可以在透明背景上渲染带有子像素锯齿的文本。但是,您可以将文本渲染到屏幕外位图缓冲区,前提是已以正确的方式创建了屏幕外位图缓冲区。此缓冲区的背景必须是不透明的。

我以前的视图有一个稍微透明的 PNG 背景。我本可以简单地使这个背景不透明并毫无问题地呈现给它,但是由于这个视图需要淡入淡出,它需要支持 CALayer,因此文本正确呈现一次,然后在没有子像素锯齿的情况下随后呈现.

这是我的代码。它似乎非常冗长,如果有人可以帮助我减少它,我会喜欢它。它假定您有一个名为 _title 的 NSImageView 和一个名为 title 的 NSString。

// 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];

【讨论】:

  • 啊 - CGImageRef imageRef = CGBitmapContextCreateImage(context); 是一种更好的方式来让图像脱离上下文。
  • 很高兴听到您弄明白了!我认为它无法混合的原因是因为在透明背景下它不知道要混合什么,因此是不透明的原因。
  • 差不多,是的。如果只是每个像素都是 ARGB 的问题,它可以将其渲染为 ARGB 并完成它,但子像素混叠实际上使用 ARAGAB,即每种颜色的 alpha 值而不是整个像素。
【解决方案2】:

我不太确定您的限制是什么,或者为什么您绝对必须绘制到一个图层,但 os4.1 中的新功能除其他外,Core Text 已从桌面移植到 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

【讨论】:

  • 这不起作用,因为 Core Text 与 Quartz 的其余部分具有相同的限制。不过,它让我走上了正轨。
【解决方案3】:

这是原始海报不想这样做的方式,但我可以有一个不透明的背景......

当你制作图层时:

    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];

【讨论】:

    【解决方案4】:

    一旦填充了可变字符串,Text 的 X、Y 位置是什么? UILabel 也有类似的问题,这是由于文本的 XY 位置是浮动的。 (它试图将变量数据居中,而 XY 值是半像素)

    我截断了值并修复了模糊的文本。

    【讨论】:

    • 不——当控制容器视图居中时,我已经出现了这个问题,所以调整大小的一半时间会出现在位置 x.5 并搞砸了。这是纯文本渲染,不是定位。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-09
    • 1970-01-01
    • 2022-12-05
    • 2020-03-18
    • 2022-12-15
    • 1970-01-01
    • 2020-05-05
    相关资源
    最近更新 更多