Подделка субпиксельного сглаживания текста с базовой анимацией - PullRequest
31 голосов
/ 28 июля 2011

Для тех, кто работает над проектом с представлениями на основе слоев Core Animation, к сожалению, очевидно, что сглаживание на субпикселях (сглаживание текста) отключено для текста, не отображаемого на заданном непрозрачном фоне. Теперь для людей, которые могут установить непрозрачные фоны для своего текста (либо с помощью вызова setBackgroundColor:, либо с помощью эквивалентных настроек в Интерфейсном Разработчике), эта проблема не представляет большой проблемы. Однако для других, у которых нет абсолютно никакой возможности обойти это, нельзя игнорировать это.

Более двух дней я искал онлайн решение, и ничего полезного не нашлось. Все, что я хочу сделать, это создать текстовую метку (не редактируемую пользователем), которая устанавливается в окне листа (фоны окна листа прозрачны, поэтому представление содержимого листа объявляет wantsLayer как true, отключает сглаживание текста для всех меток на окно); что-то очень простое. Я видел много оспариваемых решений (быстрый поиск в Google поднимет эту тему во многих других местах), но до сих пор все эти решения основаны на способности людей и желании идти на компромисс с непрозрачным фоном (чего вы не можете использовать на листе).

Пока лучшее направление, которое я могу себе представить, это предварительно визуализировать текст с сглаживанием текста на изображении, а затем отобразить изображение, как обычно, в режиме со слоями. Тем не менее, это не представляется возможным. «Обычный» способ, который я бы предположил, можно попробовать вот так:

NSAttributedString *string = [[self cell] attributedStringValue];
NSImage *textImage = [[NSImage alloc] initWithSize:[string size]];
[textImage lockFocus];
[string drawAtPoint:NSZeroPoint];
[textImage unlockFocus];

Но, похоже, это не работает (скорее всего, потому что при вызове из drawRect: в настроенном графическом контексте уже отключено сглаживание текста - субпиксельное сглаживание отключено, и вместо него используется обычное сглаживание), но оно также не работает более сложное решение найдено в этом вопросе (где вы создаете свой собственный графический контекст).

Так как же возможно сделать что-то подобное? Можно ли как-то «подделать» эффект сглаживания текста? Существуют ли даже обходные пути, которые могут что-то настроить? Я действительно не хочу отказываться от Core Animation только из-за этой глупой проблемы; Есть много преимуществ, которые экономят много времени и кода.


Редактировать: После долгих поисков я что-то нашел. Хотя сам поток только подтверждает мои подозрения, я думаю, что, возможно, нашел одно решение проблемы: пользовательский CALayer рисунок. Тимоти Вуд в одном из своих ответов приложил пример проекта, в котором показано сглаживание шрифтов с использованием нескольких методов, некоторые из которых работают. Я посмотрю, как интегрировать это в свой проект.

Редактировать # 2: Оказывается, ссылка выше также перебор. Хотя связанные методы обеспечивают сглаживание на субпикселях, они также не работают на прозрачных фонах.

Ответы [ 5 ]

9 голосов
/ 03 августа 2011

Если вы используете прозрачный лист, вы заранее не знаете, какими будут пиксели под ним. Они могут измениться. Помните, что у вас есть один альфа-канал для всех трех цветов: если вы сделаете его прозрачным, вы не увидите никакого субпиксельного эффекта, но если вы сделаете его непрозрачным, все три субэлемента будут объединены с фоном. Если вы дадите краю правильный цвет для компоновки на белом фоне, он не будет выглядеть правильно, если фон изменится на другой цвет.

Например, допустим, вы рисуете черный текст на белом фоне, а порядок подэлементов - RGB. Правый край может быть слабым синим: высокое значение B (полная яркость на стороне от глифа), немного более низкие, но все еще высокие значения R и G (более низкая яркость на стороне, ближе к глифу). Если вы сейчас скомбинируете этот пиксель с темно-серым фоном, вы сделаете его светлее, чем если бы вы визуализировали черный текст на темно-сером фоне напрямую.

По сути, вы не сталкиваетесь с произвольным ограничением CoreAnimation: просто не имеет смысла использовать субпиксельный рендеринг на прозрачном слое, который может быть скомпонован на произвольном фоне. Вам понадобится отдельная альфа-канал для каждого цветового канала, но, поскольку пиксельный формат вашего буфера - RGBA (или ARGB, или что бы то ни было), у вас его не будет.

Но это приводит нас к решению. Если вы знаете, что фон останется прежним (например, лист будет отображаться поверх окна, содержимым которого вы управляете), тогда вы можете просто сделать слой непрозрачным, заполнить его копией покрытой области фонового окна и визуализировать субпиксельный сглаженный текст на нем. По сути, вы будете предварительно смешивать свой слой с фоном. Если фон останется прежним, это будет выглядеть так же, как и при обычном альфа-композитинге, за исключением того, что теперь вы можете выполнять субпиксельный рендеринг текста; если фон меняется, то вам все равно придется отказаться от субпиксельного рендеринга текста (хотя я думаю, что вы можете продолжать копировать фон и перерисовывать непрозрачный оверлей всякий раз, когда он меняется).

2 голосов
/ 05 августа 2011

Я не совсем уверен, что понял вопрос, так что это настоящий пунт.

Но я заметил, что ответ LaC зависит от того, как пиксели пишутся поверх друг друга обычным образом. Как в Photoshop вы бы назвали «Normal Blend Mode».

Вы можете объединить два слоя множеством других способов, включая «Умножение»:

enter image description here

Это индивидуально умножает R, G и B каждого пикселя. Так что, если бы у вас был текст на белом фоне, вы могли бы получить дополнительный задний слой, который просвечивал, установив верхний слой на «Умножение», что заставляет оба слоя прожигаться друг с другом следующим образом.

Это должно работать и для вашего варианта использования:

enter image description here

Оба текстовых слоя в этом снимке имеют непрозрачный белый фон, но у правого слоя режим смешивания установлен на " Multiply ".

С «Умножением»:

  • белые пиксели в верхнем слое умножают нижние слои на 1, оставляя их без изменений
  • черные пиксели в верхнем слое умножают нижние слои на 0, уменьшая их до черного
  • оттенки между ними затемняют нижние слои пропорционально

Другими словами, тот же самый результат, который вы получили бы, просто положив текст прямо на фон.

Я не сглаживал субпиксель текста на этом скриншоте, но он одинаково хорошо работал бы с разными значениями R, G и B.


Я отдаленно не знаком с API Mac, но согласно этой ссылке Core Animation имеет такую ​​возможность, которую вы получите, написав:

myLayer.compositingFilter = [CIFilter filterWithName:@"CIMultiplyBlendMode"];

или

myLayer.compositingFilter = [CIFilter filterWithName:@"CIMultiplyCompositing"];

(я понятия не имею, к чему относится «режим наложения» и «наложение» на языке Mac, так что вам придется попробовать оба!)


РЕДАКТИРОВАТЬ: Я не уверен, что вы когда-либо указали, что ваш текст был черным. Если он белый, вы можете использовать слой «белый на черном», установленный в режим смешивания «Screen».

2 голосов
/ 31 июля 2011

В качестве простого хака, если сглаживание текста работает (но не субпиксельно), вы можете подделать его, отрендерировав его в 3 раза шире, а затем уменьшив.Это непереносимо, поскольку я не знаю способа запроса порядка элементов на вашем дисплее, но он должен работать.

Например,

RGB RGB RGB -> RGB
|    |    |    |||  
|    |    +----++^
|    +---------+^
+--------------^
0 голосов
/ 03 декабря 2014

Если вы просто пытаетесь получить четкий текст для отображения на непрозрачном фоне и ударяете головой о стену с помощью CATextLayer - откажитесь и используйте NSTextField с отключенной вставкой и установкой линейного фрагмента на 0. Пример кода быстрый, нодолжен быть в состоянии легко переводить ...

var testV = NSTextView()
testV.backgroundColor = NSColor(calibratedRed: 0.73, green: 0.84, blue: 0.89, alpha: 1)
testV.frame = CGRectMake(120.0, 100.0, 200.0, 30.0)
testV.string = "Hello World!"
testV.textContainerInset = NSZeroSize
testV.textContainer!.lineFragmentPadding = 0
self.addSubview(testV)

для меня отображает текст, эквивалентный:

var testL = CATextLayer()
testL.backgroundColor = NSColor(calibratedRed: 0.73, green: 0.84, blue: 0.89, alpha: 1).CGColor
testL.bounds = CGRectMake(0.0, 0.0, 200.0, 30.0)
testL.position = CGPointMake(100.0, 100.0)
testL.string = "Hello World!"
testL.fontSize = 14
testL.foregroundColor = NSColor.blackColor().CGColor
self.layer!.addSublayer(testL)

Этот класс, очевидно, имеет больше накладных расходов и, возможно, ненужные вещи, такие как выбор текста /копирование и т. д., но текст отображается хорошо.

0 голосов
/ 31 июля 2011

Я не знаю, будет ли это решение работать для вас, но вы можете нарисовать текст в изображение с прозрачным фоном, используя UIGraphicsBeginImageContext (), я уверен, что вы можете настроить псевдоним для этого, если вы не рисуете свой текст в два или в четыре раза больше размера, а затем уменьшите при рисовании изображения в вашем представлении.

...