【问题标题】:CATextLayer blurry text after rotationCATextLayer 旋转后文字模糊
【发布时间】:2012-04-26 12:15:35
【问题描述】:

我有与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_pathLayer_ 的子层,是否需要同时旋转它们? textLayer_ 是否应该随其附加的路径旋转?
  • 你为什么不直接使用标签?
  • @Dondragmer 不,如果只旋转 pathLayer_(是的,textLayer_ 是 pathLayer_ 的子层),镜像对象上的文本将是相反的顺序
  • @Jenox CATextLayer 允许更改动画文本大小,但我会试试看,谢谢

标签: ios core-animation transform retina-display catextlayer


【解决方案1】:

这对我有用:

myTextLayer.contentsScale = UIScreen.mainScreen.scale;

即使经过转换,文本也会呈现清晰。

【讨论】:

  • 这是唯一对我有用的东西,它消除了 CATextLayer 和其他图层的模糊。
【解决方案2】:

光栅化

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

当此属性的值为 NO 时,图层将尽可能直接合成到目标中。如果合成模型的某些特征(例如包含过滤器)需要,图层仍可以在合成之前进行光栅化。

因此,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);

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

文本到路径

如果您确实需要以导致 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 位置不明显的情况下添加透视。

【讨论】:

  • 非常感谢您的详细回答。对于我的任务,我使用了两个单面层。
  • 多行文本是否可以使用文本路径?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-27
  • 2016-01-18
  • 2013-12-18
  • 2021-08-18
相关资源
最近更新 更多