How to work around poor text rendering in text backed by a CALayer

前端 未结 4 1822
感动是毒
感动是毒 2021-01-31 23:16

I have some variable text in an NSTextField that renders on a CALayer background view. As a CALayer does not support sub-pixel aliasing fo

相关标签:
4条回答
  • 2021-01-31 23:18

    Here is how the Original poster did not want to do it, but it was OK for me to have an opaque background...

    when you make the layer:

        layer.opaque = YES;
    

    Then in

      - (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];
    
    0 讨论(0)
  • 2021-01-31 23:35

    What are the X, Y positions of the Text once it gets it's variable string filled in? I had a similar problem for a UILabel which was caused by the fact that the XY position of the text were floats. (it was trying to center variable data and the XY vals were half pixels)

    I truncated the values and it fixed the blurry text.

    0 讨论(0)
  • 2021-01-31 23:36

    Workaround found. Nothing in Quartz can render text with Sub-Pixel Aliasing on top of a transparent background, seemingly. However, you CAN render text to an offscreen bitmap buffer, providing that offscreen bitmap buffer has been created in the correct fashion. The background of this buffer must be opaque.

    My view previously had a PNG background that was slightly transparent. I could have simply made this background opaque and rendered to it without problem, but as this view needs to fade in and out, it needed to be CALayer-backed, so the text renders once properly, and then subsequently renders without Sub-Pixel Aliasing.

    Here's my code. It seems incredibly verbose, I'd love it if anyone could help me whittle it down. It assumes you have an NSImageView called _title and an NSString called 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];
    
    0 讨论(0)
  • 2021-01-31 23:41

    I'm not quite sure what your limitations are, or why you absolutely have to draw to a layer, but new in os4.1 among other things, Core Text was ported to iOS from the desktop. You can probably take advantage of it's advanced type setting features to render glyphs along lines or even arcs and have it do most of the heavy lifting for you. It is a relatively low-level API, but it is very fast.

    - (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);
    }
    

    Other examples include CoreTextArcCocoa and CoreTextTest

    0 讨论(0)
提交回复
热议问题