CATextLayer размытый текст после поворота

У меня проблема, связанная с вопросом
Я установил contentScale, и после этого текст выглядит хорошо, но если я применяю преобразование 3D-вращения, текст получается размытым.

изображение здесь

код инициализации

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

Текст можно поворачивать, и он остается четким
image здесь


person Dmitry    schedule 14.04.2012    source источник
comment
Хорошо бы решить общую проблему. Однако, если textLayer_ является подслоем pathLayer_, нужно ли вращать их оба? Должен ли textLayer_ просто вращаться по пути, к которому он прикреплен?   -  person Dondragmer    schedule 14.04.2012
comment
Почему бы вам просто не использовать ярлыки?   -  person Christian Schnorr    schedule 14.04.2012
comment
@Dondragmer нет, если вращать только pathLayer_ (да, textLayer_ является подслоем pathLayer_), текст на зеркальном объекте будет в обратном порядке   -  person Dmitry    schedule 14.04.2012
comment
@Jenox CATextLayer позволяет изменять размер анимированного текста, но я попробую, спасибо   -  person Dmitry    schedule 14.04.2012


Ответы (2)


Растеризация

Что, вероятно, здесь происходит, так это то, что он решает, что должен отображать textLayer в пикселях. Обратите внимание на предупреждение для shouldRasterize в справочнике по классам CALayer:

Когда значение этого свойства равно 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. Для этого необходимо глубоко погрузиться в основной текст, но результаты будут впечатляющими. Например, вы можете анимировать рисуемый текст.

// - (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 текстовых слоев.

Вращающийся знак, обведенный текстом и перспективой

person Dondragmer    schedule 15.04.2012
comment
Большое спасибо за ваш подробный ответ. Для моей задачи я использовал два односторонних слоя. - person Dmitry; 26.04.2012
comment
Возможен ли текст в пути с многострочным текстом? - person jjxtra; 14.04.2014

Это сработало для меня:

myTextLayer.contentsScale = UIScreen.mainScreen.scale;

Текст будет отображаться четким даже при преобразовании.

person user3335999    schedule 24.03.2019
comment
Это единственная вещь, которая, похоже, работает для меня, которая удаляет размытость из CATextLayer и других слоев. - person t.888; 13.06.2021