ios - CATextLayer 旋转后文本模糊

标签 ios core-animation transform retina-display catextlayer

我有与 question 相关的问题
我设置了 contentsScale 之后,文本看起来不错,但如果我应用 3d 旋转变换,文本就会变得模糊。

图片 here

初始化代码

    // init text
    textLayer_ = [CATextLayer layer];
    …
    textLayer_.contentsScale = [[UIScreen mainScreen] scale];

    // init body path
    pathLayer_ = [CAShapeLayer layer];
    …
    [pathLayer_ addSublayer:textLayer_];

旋转代码

    // make the mirror
    pathLayer_.transform = CATransform3DRotate(pathLayer_.transform, M_PI, 0, 1, 0);
    textLayer_.transform = CATransform3DRotate(textLayer_.transform, M_PI, 0, 1, 0);
    [textLayer_ setNeedsDisplay];

为了测试,我在初始化期间分别旋转了文本。

    // init text
    textLayer_ = [CATextLayer layer];
    …
    textLayer_.transform = CATransform3DRotate(textLayer_.transform, M_PI, 0, 1, 0);
    textLayer_.contentsScale = [[UIScreen mainScreen] scale];

文本可以旋转并保持清晰
图片 here

最佳答案

栅格化

这里可能发生的事情是它决定必须将 textLayer 渲染为像素。注意 shouldRasterize in the CALayer Class Reference 的警告:

When the value of this property is NO, the layer is composited directly into the destination whenever possible. The layer may still be rasterized prior to compositing if certain features of the compositing model (such as the inclusion of filters) require it.

因此,CATextLayer 可能会突然决定栅格化。如果它是旋转层的子层,它决定栅格化。所以,不要让这种情况发生。

单面层

这会将您带回导致文本颠倒的解决方案。您可以通过关闭文本层上的 doubleSided 来防止这种情况。您的标牌现在在远端是空白的,因此添加第二个文本层,相对于第一个旋转 180 度。

声明两个文本层:

@property (retain) CAShapeLayer *pathLayer;
@property (retain) CATextLayer *textLayerFront;
@property (retain) CATextLayer *textLayerBack;

然后,将它们初始化为单面,背面层旋转180度:

CAShapeLayer *pathLayer = [CAShapeLayer layer];
// Also need to store a UIBezierPath in the pathLayer.

CATextLayer *textLayerFront = [CATextLayer layer];
textLayerFront.doubleSided = NO;
textLayerFront.string = @"Front";
textLayerFront.contentsScale = [[UIScreen mainScreen] scale];

CATextLayer *textLayerBack = [CATextLayer layer];
textLayerBack.doubleSided = NO;
// Eventually both sides will have the same text, but for demonstration purposes we will label them differently.
textLayerBack.string = @"Back";
// Rotate the back layer 180 degrees relative to the front layer.
textLayerBack.transform = CATransform3DRotate(textLayerBack.transform, M_PI, 0, 1, 0);
textLayerBack.contentsScale = [[UIScreen mainScreen] scale];

// Make all the layers siblings.  These means they must all be rotated independently of each other.

// The layers can flicker if their Z position is close to the background, so move them forward.
// This will not work if the main layer has a perspective transform on it.
textLayerFront.zPosition = 256;
textLayerBack.zPosition = 256;

// It would make sense to make the text layers siblings of the path layer, but this seems to mean they get pre-rendered, blurring them.
[self.layer addSublayer:pathLayer];
[self.layer addSublayer:textLayerBack];
[self.layer addSublayer:textLayerFront];

// Store the layers constructed at this time for later use.
[self setTextLayerFront:textLayerFront];
[self setTextLayerBack:textLayerBack];
[self setPathLayer:pathLayer];

然后您可以旋转图层。只要您始终旋转相同的量,它们就会显示正确。

CGFloat angle = M_PI;
self.pathLayer.transform = CATransform3DRotate(self.pathLayer.transform, angle, 0, 1, 0);
self.textLayerFront.transform = CATransform3DRotate(self.textLayerFront.transform, angle, 0, 1, 0);
self.textLayerBack.transform = CATransform3DRotate(self.textLayerBack.transform, angle, 0, 1, 0);

然后您应该会发现,您可以将标牌旋转到任意角度,同时文字保持清晰。

Rotating sign with text on each side

文本到路径

如果您真的需要以导致 CATextLayer 栅格化的方式来操纵文本显示,还有一个替代方法:将文本转换为 UIBezierPath 表示。然后可以将其放置在 CAShapeLayer 中。这样做需要深入研究 Core Text,但结果是强大的。例如,您可以 animate the text being drawn .

// - (UIBezierPath*) bezierPathWithString:(NSString*) string font:(UIFont*) font inRect:(CGRect) rect;
// Requires CoreText.framework
// This creates a graphical version of the input screen, line wrapped to the input rect.
// Core Text involves a whole hierarchy of objects, all requiring manual management.
- (UIBezierPath*) bezierPathWithString:(NSString*) string font:(UIFont*) font inRect:(CGRect) rect;
{
    UIBezierPath *combinedGlyphsPath = nil;
    CGMutablePathRef combinedGlyphsPathRef = CGPathCreateMutable();
    if (combinedGlyphsPathRef)
    {
        // It would be easy to wrap the text into a different shape, including arbitrary bezier paths, if needed.
        UIBezierPath *frameShape = [UIBezierPath bezierPathWithRect:rect];

        // If the font name wasn't found while creating the font object, the result is a crash.
        // Avoid this by falling back to the system font.
        CTFontRef fontRef;
        if ([font fontName])
            fontRef = CTFontCreateWithName((__bridge CFStringRef) [font fontName], [font pointSize], NULL);
        else if (font)
            fontRef = CTFontCreateUIFontForLanguage(kCTFontUserFontType, [font pointSize], NULL);
        else
            fontRef = CTFontCreateUIFontForLanguage(kCTFontUserFontType, [UIFont systemFontSize], NULL);

        if (fontRef)
        {
            CGPoint basePoint = CGPointMake(0, CTFontGetAscent(fontRef));
            CFStringRef keys[] = { kCTFontAttributeName };
            CFTypeRef values[] = { fontRef };
            CFDictionaryRef attributesRef = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values,
                                                               sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

            if (attributesRef)
            {
                CFAttributedStringRef attributedStringRef = CFAttributedStringCreate(NULL, (__bridge CFStringRef) string, attributesRef);

                if (attributedStringRef)
                {
                    CTFramesetterRef frameSetterRef = CTFramesetterCreateWithAttributedString(attributedStringRef);

                    if (frameSetterRef)
                    {
                        CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetterRef, CFRangeMake(0,0), [frameShape CGPath], NULL);

                        if (frameRef)
                        {
                            CFArrayRef lines = CTFrameGetLines(frameRef);
                            CFIndex lineCount = CFArrayGetCount(lines);
                            CGPoint lineOrigins[lineCount];
                            CTFrameGetLineOrigins(frameRef, CFRangeMake(0, lineCount), lineOrigins);

                            for (CFIndex lineIndex = 0; lineIndex<lineCount; lineIndex++)
                            {
                                CTLineRef lineRef = CFArrayGetValueAtIndex(lines, lineIndex);
                                CGPoint lineOrigin = lineOrigins[lineIndex];

                                CFArrayRef runs = CTLineGetGlyphRuns(lineRef);

                                CFIndex runCount = CFArrayGetCount(runs);
                                for (CFIndex runIndex = 0; runIndex<runCount; runIndex++)
                                {
                                    CTRunRef runRef = CFArrayGetValueAtIndex(runs, runIndex);

                                    CFIndex glyphCount = CTRunGetGlyphCount(runRef);
                                    CGGlyph glyphs[glyphCount];
                                    CGSize glyphAdvances[glyphCount];
                                    CGPoint glyphPositions[glyphCount];

                                    CFRange runRange = CFRangeMake(0, glyphCount);
                                    CTRunGetGlyphs(runRef, CFRangeMake(0, glyphCount), glyphs);
                                    CTRunGetPositions(runRef, runRange, glyphPositions);

                                    CTFontGetAdvancesForGlyphs(fontRef, kCTFontDefaultOrientation, glyphs, glyphAdvances, glyphCount);

                                    for (CFIndex glyphIndex = 0; glyphIndex<glyphCount; glyphIndex++)
                                    {
                                        CGGlyph glyph = glyphs[glyphIndex];

                                        // For regular UIBezierPath drawing, we need to invert around the y axis.
                                        CGAffineTransform glyphTransform = CGAffineTransformMakeTranslation(lineOrigin.x+glyphPositions[glyphIndex].x, rect.size.height-lineOrigin.y-glyphPositions[glyphIndex].y);
                                        glyphTransform = CGAffineTransformScale(glyphTransform, 1, -1);

                                        CGPathRef glyphPathRef = CTFontCreatePathForGlyph(fontRef, glyph, &glyphTransform);
                                        if (glyphPathRef)
                                        {
                                            // Finally carry out the appending.
                                            CGPathAddPath(combinedGlyphsPathRef, NULL, glyphPathRef);

                                            CFRelease(glyphPathRef);
                                        }

                                        basePoint.x += glyphAdvances[glyphIndex].width;
                                        basePoint.y += glyphAdvances[glyphIndex].height;
                                    }
                                }
                                basePoint.x = 0;
                                basePoint.y += CTFontGetAscent(fontRef) + CTFontGetDescent(fontRef) + CTFontGetLeading(fontRef);
                            }

                            CFRelease(frameRef);
                        }

                        CFRelease(frameSetterRef);
                    }
                    CFRelease(attributedStringRef);
                }
                CFRelease(attributesRef);
            }
            CFRelease(fontRef);
        }
        // Casting a CGMutablePathRef to a CGPathRef seems to be the only way to convert what was just built into a UIBezierPath.
        combinedGlyphsPath = [UIBezierPath bezierPathWithCGPath:(CGPathRef) combinedGlyphsPathRef];

        CGPathRelease(combinedGlyphsPathRef);
    }
    return combinedGlyphsPath;
}

这是旋转轮廓文本,使用上述方法创建。也可以在文本层的 z 位置不明显的情况下添加透视图。

Rotating sign outlined text and perspective

关于ios - CATextLayer 旋转后文本模糊,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10152574/

相关文章:

ios - 表格单元格约束无法正确加载

控制台 "CIKernel' s ROI 函数中的 Swift 错误不允许平铺”

haskell - 你能根据 `Comonads` 定义 `Monads` 吗?

css - 当我使用转换 :rotate on a fixed div it leaves an empty space at the top of the page?

ios - rac_signalForSelector fromProtocol,选择器不存在

ios - Firebase 存储下载到本地文件错误

ios - CATransaction 设置动画持续时间不起作用

ios - 在示例核心数据项目中出错

ios - 如何更改 UITabBarItem 文本的颜色

ios - UIView,非常缓慢地动画背景颜色,挂起 ScrollView (!)