Растеризация векторных шрифтов

Если вы пишете программы для кофемолок (холодильников, ZX Spectrum, телевизоров, встроенных систем, старых компьютеров — нужное подчеркнуть), и хотите использовать при этом красивые шрифты, не спешите сохранять буквы в растровый формат. Потому что сейчас я расскажу, как сделать растеризатор векторных шрифтов размером в пару килобайт, не уступающий по качеству FreeType 2 с выключенным хинтингом.

Статья будет интересна и тем, кто просто хочет узнать, как работают библиотеки-растеризаторы.


Векторные шрифты в формате SVG


Чтобы сделать растеризатор, нужно сначала понять, как устроены векторные шрифты. Я выбрала в качестве основного формат шрифтов SVG потому, что он опирается на XML и является, пожалуй, самым читаемым и понятным для человека.

Первый шрифт, который я взяла для изучения — DejaVu Sans Mono Bold. Вот как он выглядит изнутри:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-
SVG-20010904/DTD/svg10.dtd">
<svg xmlns="www.w3.org/2000/svg" width="100%" height="100%">
<defs >
<font horiz-adv-x="1233" ><font-face
font-family="DejaVu Sans Mono"
units-per-em="2048"
panose-1="2 11 7 9 3 6 4 2 2 4"
ascent="1901"
descent="-483"
alphabetic="0" />
<glyph unicode=" " glyph-name="space" />
<glyph unicode="$" glyph-name="dollar" d=" M 694 528 V 226 Q 757 235 792 274 T
827 375 Q 827 437 793 476 T 694 528 Z M 553 817 V 1100 Q 491 1092 460 1059 T 428
967 Q 428 910 459 872 T 553 817 Z M 694 -301 H 553 L 552 0 Q 465 3 370 26 T 172
92 V 354 Q 275 293 371 260 T 553 226 V 555 Q 356
594 260 689 T 164 942 Q 164 1109 266 1208 T 553 1319 V 1556 H 694 L 695 1319 Q
766 1315 842 1301 T 999 1262 V 1006 Q 937 1046 861 1070 T 694 1100 V 793 Q 891
762 991 659 T 1092 383 Q 1092 219 984 114 T 695 0 L 694 -301 Z " />

 
<!-- … опущено.... -->
 
<glyph unicode="~" glyph-name="asciitilde" d=" M 1145 811 V 578 Q 1070 518 999
491 T 848 463 Q 758 463 645 514 Q 623 524 612 528 Q 535 562 484 574 T 381 586 Q
303 586 233 557 T 88 465 V 694 Q 166 755 239 782 T 395 809 Q 448 809 498 798 T
622 756 Q 633 751 655 741 Q 771 686 864
686 Q 934 686 1003 716 T 1145 811 Z " />

</font>
</defs>
</svg>
 


Главная часть формата SVG — это контуры (paths). В них хранится большая часть информации об изображении. Тег контура выглядит вот так:

<path
d=" M 1145 811 V 578 Q 1070 518 999 491 T 848 463 Q 758 463 645 514 Q 623
524 612 528 Q 535 562 484 574 T 381 586 Q 303 586 233 557 T 88 465 V 694 Q 166
755 239 782 T 395 809 Q 448 809 498 798 T 622 756 Q 633 751 655 741 Q 771 686
864 686 Q 934 686 1003 716 T 1145 811 Z "
id="path4840"
style="fill:#000000;stroke:#000000;stroke-width:1px;stroke-
linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />


Style описывает цвет заливки и обводки, id указывает имя контура, а d — это и есть сам контур.

Стоп… Минуточку! Тег glyph из шрифта:

<glyph unicode="~" glyph-name="asciitilde" d=" M 1145 811 V 578 Q 1070 518 999
491 T 848 463 Q 758 463 645 514 Q 623 524 612 528 Q 535 562 484 574 T 381 586 Q
303 586 233 557 T 88 465 V 694 Q 166 755 239 782 T 395 809 Q 448 809 498 798 T
622 756 Q 633 751 655 741 Q 771 686 864 686 Q 934 686 1003 716 T 1145 811 Z " />


Вот оно. Форма буквы (глифа) описывается точно так же, как и контуры SVG. Полное
описание параметра d можно найти в спецификации, а краткое описание привожу ниже:


x_prev и y_prev — координаты предыдущей точки
xc_prev и yc_prev — координаты предыдущей контрольной точки

Outline (обводка) глифа и начальная растеризация


Имея под рукой библиотеку для вывода кривых Безье и преобразовывая команды тега SVG path в команды этой библиотеки, можно легко и быстро получить аутлайн (обводку) любого глифа.

Глифы у нас векторные, а экран — растровый. Значит, задача вывода аутлайна сводится к получению из набора отрезков и кривых Безье массива пикселей. Отрезки превратить в пиксели очень легко, алгоритмов много, они хорошие и разные.

Для кривых Безье можно найти множество точек кривой по координатам начальной точки (xz0,yz0), конечной точки (xz2,yz2) и контрольной точки (xz1,yz1). Делается это вот так:

for (step=0;step<10;step++) {
t=step/10;
bx[step]=(1-t)*(1-t)*xz0+2*t*(1-t)*xz1+t*t*xz2;
by[step]=(1-t)*(1-t)*yz0+2*t*(1-t)*yz1+t*t*yz2;
}


В результате имеем массив из 10 координат точек, принадлежащих кривой Безье. В зависимости от длины кривой из сложной системы дифференциальных уравнений находится количество точек, на которые нужно разбить кривую (то есть наибольшее значение step).

Однако превращать кривые Безье в пиксели по точкам — медленно и плохо. Если кривая сложной формы и большой длины, то расчет количества точек займет очень много времени. А если выводить кривую Безье по точкам, и этих точек окажется меньше, чем нужно — в аутлайне глифа будут разрывы, что недопустимо. Поэтому кривые Безье разбивают на отрезки. Начальные и конечные координаты отрезков берут как координаты точек, лежащих на кривой. В большей части случаев такое приближение дает хорошие результаты.

Как определить количество отрезков? Я считаю, достаточно делать максимальное значение step пропорциональным разнице между начальной и конечной координатами (например, step=(xz2-yz2)/100). Кстати, для всех шрифтов, которые я встречала, при высоте глифа менее 32 пикселов достаточно разбивать кривые Безье на два отрезка.

Результат работы растеризатора получится примерно такой:



Заливка контура глифа — главный этап растеризации


Всё это хорошо, но на экране мы видим буквы, которые залиты цветом. Значит, как-то можно? Я потратила около двух месяцев на совершенствование алгоритмов заливки уже растрированного контура, и в конце концов убедилась, что такой подход является в корне неправильным. Для заливки векторных изображений растровая заливка не подходит.

В поисках решения этой веселой задачи я наткнулась на лекции MIT OCW по компьютерной графике. Меня заинтересовала лекция о растеризации трехмерной графики (а именно — треугольных полигонов). Суть метода заключалась в следующем: в векторном виде строилась проекция треугольного полигона на экран, и затем векторный треугольник закрашивался. Закрашивание производилось так: выбиралась область экрана, за которую треугольник однозначно не выходит, и для каждой точки этой области определялось, принадлежит ли она данному треугольнику. Та же лекция утверждает, что метод заливки Sweeping Line (который используют Cairo и FreeType) устарел, но об этом методе — чуть позже.

Осталось научиться определять, принадлежит ли точка контуру глифа, или не принадлежит. Контур глифа всегда замкнут. Значит, если мы проведем в любом направлении из любой точки луч, и он пересечет контур глифа нечетное число раз, то точка глифу принадлежит, а если четное — не принадлежит.

Интересно, что этот метод работает, даже если в контуре есть самопересечения или отверстия. Оказывается, данный алгоритм является хрестоматийным, и одна из его реализаций вот
такая:


xp = new Array (10, 20, 30, 40);
yp = new Array (30, 40, 20, 10);
function inPoly(x,y){
 npol = xp.length;
 j = npol - 1;
 var c = 0;
 for (i = 0; i < npol;i++){
  if ((((yp[i]<=y) && (y<yp[j])) || ((yp[j]<=y) && (y<yp[i]))) &&
  (x > (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i])) {
   c = !c;}
  j = i;}
return c;}


Здесь xp и yp — массивы координат вершин многоугольника. Функция inPoly(x,y)
возвращает 1 или 0 — принадлежит точка многоугольнику или нет. Учитывая, что мы умеем
разбивать контур глифа на отрезки (то есть стороны многоугольника), то эта функция
замечательно нам подходит.

Простейший растеризатор


Теперь, переведя глиф в массив вершин xp и yp, мы можем написать простейший растеризатор. Пусть массив содержит отрезки с координатами от 0 до 2000. Тогда:

function simple(){
for (x=0;x<2000;x++) {
for (y=0;y<2000;y++) {
if (inPoly(x,y)>0) raster[x][y]=1;}}}


Эта простейшая функция создаст в массиве raster[x][y] растрированное изображение глифа. Выглядеть результат, выведенный на экран, будет примерно так (я вывела по очереди несколько глифов, и увеличил изображение примерно в 20 раз):



Глифы разрешением 2000x2000 пикселов на экране вряд ли кому-то нужны, да и алгоритм inPoly будет обрабатывать их довольно медленно. Чтобы вывести на экран глиф поменьше, сделаем примерно так:

function simple(scalefactor){
for (x=0;x<2000/scalefactor;x++) {
for (y=0;y<2000/scalefactor;y++) {
if (inPoly(x*scalefactor,y*scalefactor)>0) raster[x][y]=1;}}}


Если scalefactor=2, то глиф будет выведен в квадрат 1000x1000 пикселов, а если scalefactor=100, то в квадрат 20x20 (вполне нормальный размер для экранных шрифтов).

Данный растеризатор выводит самые четкие (и самые неровные) очертания контуров, и ему сильнее всех других растеризаторов нужны алгоритмы хинтинга и контроля отсечки (они меняют форму контура в зависимости от разрешения). Для хранения одного растеризованного глифа в памяти необходимо x*y/8 байт. Полный набор символов ASCII займет в памяти не более x*y*32 байт (3200 байт для шрифта 10x10 и 8192 байт для шрифта 16x16). В процессе растеризации используется чуть больше, чем points*2*4 байт памяти (где points — количество точек в глифе, points как правило меньше 100, а иногда и меньше 10).

Анти-алиасинг (сглаживание)


Гораздо лучший результат дает растеризация со сглаживанием. В библиотеке FreeType 1 использовалось
5ти-полутоновое сглаживание, сейчас в FreeType2 используется нечто более существенное (10ти- или 17ти-полутоновое сглаживание, я не вдавалась в подробности). Поэкспериментировав, я убедилась, что 10ти-полутоновое сглаживание значительно лучше, чем 5ти-полутоновое.

function aadraw(scalefactor){
//Мы растеризуем изображение в девять (3*3) раз больше, чем хотим получить
for (x=0;x<size*3/scalefactor;x++) {
for (y=0;y<size*3/scalefactor;y++) {
if (inpoly(x*scalefactor/3,y*scalefactor/3)>0) landscape[x][y]=1;}}

//Затем уменьшаем его в три раза с примитивным сглаживанием
for (x=0;x<size/scalefactor;x++) {
for (y=0;y<size/scalefactor;y++) {

//определяем цвет точки, он будет лежать в диапазоне от 00 до FF
color[x][y]=255-28*(landscape[x*3][y*3]+landscape[x*3][y*3+1]+landscape[x*3][y*3+2]+landscape[x*3+1][y*3]+landscape[x*3+1][y*3+1]+landscape[x*3+1][y*3+2]+landscape[x*3+2][y*3]+landscape[x*3+2][y*3+1]+landscape[x*3+2][y*3+2]));}}//Конец уменьшения
}//Конец функции


Здесь size — это размер глифа (в простом растеризаторе вместо size я подставляла 2000). Для хранения промежуточных данных используется массив landscape, а итоговый результат сохраняется в массив байт color[x][y].



Субпиксельное сглаживание


Для вывода шрифтов на LCD-мониторах часто используют субпиксельное сглаживание. На обычных мониторах субпиксельное сглаживание дает результат почти такой же, как и обычное сглаживание, но на LCD сглаживание по субпикселям позволяет увеличить горизонтальное разрешение в три раза. Суть метода заключается в следующем: человеческий глаз гораздо лучше различает интенсивность, а не оттенок. Подробности есть во многих источниках, и даже на русской странице Википедии описание достаточное для практического использования.

Проблема простого субпиксельного сглаживания в том, что качество изображения на выходе ниже, чем у 10ти-полутонового алгоритма со сглаживанием. Для увеличения качества я использую четырехполутоновое сглаживание по вертикали, и субпиксельное — по горизонтали. Алгоритм субпиксельного сглаживания из справочников дает при этом вот такие ужасные результаты:



Чтобы избавиться от разноцветных «подтеков», я изменила коэффициенты размывания по
горизонтали, и вдобавок немного изменила гамму. Результат выглядит вот так (наилучшее
качество достигается при просмотре на LCD-мониторе):



function xdraw(scalefactor){
//Нам нужно изображение в шесть (3*2) раз больше, чем мы хотим получить
for (x=0;x<size*3/scalefactor;x++) {
for (y=0;y<size*2/scalefactor;y++) {
if (inpoly(x*scalefactor/3,y*scalefactor/2)>0) landscape[x][y]=1;}}

//Применим к этому изображению стандартное размытие перед разбивкой на субпиксели
for (x=0;x<size*3/scalefactor;x++) {
for (y=0;y<size*2/scalefactor;y++) {
if (x>2 && x<size*3/scalefactor-2) landscape1[x][y]=landscape[x-2][y]*(1/9)+landscape[x+2][y]*(1/9)+landscape[x-1][y]*(2/9)+landscape[x+1][y]*(2/9)+landscape[x][y]*(1/3)}}

//Теперь сожмем изображение в два раза, с применением нелинейного размытия
for (x=0;x<size*3/scalefactor;x++) {
for (y=1;y<size/scalefactor;y++) {
landscape2[x][y]=landscape1[x][y*2-1]*(2/9)+landscape1[x][y*2+1]*(2/9)+landscape1[x][y*2]*(4/9);
if (landscape2[x][y]==8/9) landscape2[x][y]=1;}} //и подкорректируем гамму

//Распилим картинку на субпиксели
for (x=0;x<size/scalefactor;x++) {
for (y=0;y<size/scalefactor;y++) {
r[x][y]=Math.floor(255-255*landscape2[x*3][y]);g[x][y]=Math.floor(255-255*landscape2[x*3+1][y]);b[x][y]=Math.floor(255-255*landscape2[x*3+2][y]);}}
}//Конец функции


Метод заметающей линии (sweeping line). Основная идея


Посмотрим на алгоритм inPoly, про который я рассказывала, еще раз. Работает, но на больших символах работает медленно. Почему? Потому что скорость обработки пропорциональна квадрату size. Size вырос вдвое, скорость уменьшилась вчетверо, size вырос вчетверо — скорость упала в 16 раз.

Если немного посмотреть на алгоритм и подумать, можно обнаружить, что для каждой точки из тех, которые мы перебираем, испускается горизонтальный луч, и определяются его пересечения с контурами. Получается, что куча вычислений делается напрасно: для всех точек с одинаковой координатой Y уравнение горизонтальной прямой, проходящей через эти точки, будет одинаковым.

Раньше мы находили для каждой точки луч, и считали, сколько раз он пересекает контуры. Координаты пересечения мы находили, но не сохраняли. А теперь попробуем сделать вот что: будем находить луч только для точек (0,y), и сохранять координаты пересечения лучей с контурами.

Обращаю внимание:
1) Если часть контура горизонтальная и совпадает с заметающими линиями, то пересечения с этой частью контура не учитываются.
2) На одной заметающей всегда четное количество точек пересечения, потому что контур замкнут.

Остается лишь закрасить места между нечетными и четными точками, и оставить незакрашенными места межу четными и нечетными точками пересечения (при проходе линии слева направо). Шрифты должны быть хорошо спроектированы, и разрешение глифов не должно быть крохотным, тогда и качество страдать не будет.

Сравним скорость. Наш старый алгоритм должен был бы испустить лучи из 9x9 точек и найти координаты пересечения их с контурами. Новый алгоритм испустил всего 9 лучей — то есть он работает на порядок быстрее. И в то же время алгоритм sweeping line очень близок к тому алгоритму, что у нас был.

Sweeping line. Изменения в коде inPoly


Итак, у нас есть функция inPoly. Сделаем из нее функцию sweep.

function inPoly(x,y){

npol = xp.length;
j = npol - 1;
var c = 0;
for (i = 0; i < npol;i++){
if ((((yp[i]<=y) && (y<yp[j])) || ((yp[j]<=y) && (y<yp[i]))) &&
(x > (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i])) {
c = !c;}
j = i;}
return c;}


Очевидно, что входной параметр x больше не нужен. Функция sweep на входе получает только номер текущей строки. Зато пригодится входной параметр scale — чтобы получать x сразу в нужном масштабе.

Координату x пересечения текущей заметающей линии с некоторой прямой контура находили вот так:
(x >(xp[j]- xp[i])*(y - yp[i])/(yp[j]- yp[i])+ xp[i]))


То есть теперь будет:
x =(xp[j]- xp[i])*(y - yp[i])/(yp[j]- yp[i])+ xp[i])


На выходе функции мы должны получить массив из координат точек пересечения. Неплохо было бы узнать длину этого массива. Так что теперь будет не c = !c, а c++.

В итоге имеем вот что:

function sweep(y,scale){
npol = xp.length; //все как обычно....
j = npol -1;
var c =0;
for(i =0; i < npol;i++){
if(((yp[i]<=y)&&(y<yp[j]))||((yp[j]<=y)&&(y<yp[i])))
{ x =(xp[j]- xp[i])*(y - yp[i])/(yp[j]- yp[i])+ xp[i];
curline[c]=x/scale; c++;} //только теперь условия меньше, а точки пересечения сохраняются в массив
j = i;}
return c;} //пусть функция возвращает количество пересечений


И снова — простой растеризатор


Итак, для каждой строки мы вызываем sweep, находим пересечения заметающей линии и по этим пересечениям заполняем массив. Однако, функция sweep может вернуть нам например вот такой массив координат:

14, 8, 16, 7

Чтобы правильно залить контур, нужно заполнять его линиями (7,y)-(8,y) и (14,y)-(16,y). Одним словом, нужно результаты функции sweep отсортировать. Хорошо работает quicksort, и с большим количеством координат желательно использовать его. Однако для шрифтов, которые у меня были под рукой, в массиве curline в основном было от 2 до 8 координат пересечений, и чтобы не усложнять себе жизнь, я использовала сортировку пузырьком. Моя функция bubble принимает на вход массив и его длину, ничего явно не возвращает, но массив становится отсортированным.

Перейдем непосредственно к растеризатору. Сначала нам нужно для каждой линии вызвать sweep, затем отсортировать массив curline командой bubble:

for(y=0;y<size/scale;y++){ //нам больше не нужен вложенный цикл для всех x
every=sweep(y*scale, scale); //every — это количество элементов массива
bubble(curline,every); //сортируем массив
for(point=0;point<every;point=point+2){ //для всех точек массива
for(x=curline[point];x<curline[point+1];x++){landscape[x][y]=1;}
//рисуем линии точками
}}//заканчиваем цикл и функцию


Массив начинается с 0 — поэтому нужно рисовать линии от curline[0+x] до curline[1+x]. Вот и всё. Мы получили массив landscape, и работать с ним нужно точно так же, как и раньше. То есть после этого его можно сгладить антиалиасингом или субпиксельным сглаживанием, или и тем, и другим. Главное — обратить внимание на то, что функции sweep тоже передается масштаб, он определяет растяжение по горизонтали.

Результаты работы


Теперь вы знаете, как можно сделать растеризатор векторных шрифтов своими руками. Рабочий пример растеризатора можно скачать здесь, но он написан для операционной системы Колибри (так что вам и ее придется скачать, если хотите запустить этот пример). Размер скомпилированной программы — 2589 байт, максимальное использование ОЗУ — около 300 килобайт. И это не предел!
Поделиться публикацией
Комментарии 27
    +3
    Было б хорошо если вы приложите в конце файл с отформатированными функциями. А то отформатированный хаброй код не сильно читаем.
    Для тех кто очень сильно хочет разобраться разберется, но почему бы не сделать хоть такую мелочь сделать лучше?

    А вообще красота)
      +2
      В перспективе (после отладки) код будет переписан на flat assembler, оформлен в виде динамической библиотеки (по возможности кросс-платформенной), и доступен всем желающим. А оформлением кода на java's_creep я займусь, если будет свободное время.
        +3
        >кросс-платформенной
        >flat assembler

        это как?
          +2
          Linux, Windows, Dos и прочие системы. Или это как-то иначе называется?
            +2
            я не знаю, как это называется, но последние два с половиной раза, когда мне нужно было рисовать буквы руками по голому битмапу, это были совсем не линупсы-виндовсы и вообще не 86ой процессор.

            я понимаю, что фигачить на асме — это типа круто, но такая специфическая задача, которую решает ваш код, обычно возникает не в таких намоленых средах, а в диких дебрях непаханого адресного пространства, по которым скачет закрузчик встраиваемой системы с процессором более другого набора инструкций.

            оставьте лучше на сишечке.
              +1
              Код не настолько сложный, чтобы нельзя было поддерживать две ветви одновременно. Точнее, он совсем несложный.
                0
                Если тема еще интересна — есть код (не мой), в Public Domain, отлично работающий с TTF. При этом занимает в бинарном виде сущие килобайты (правда, все же раз в 7 больше, чем мой растеризатор, но это, видимо, из-за парсера ttf).
            0
            Очень очень интересно. А можно с этого момента подробнее? На ассемблере, это не страшно, если документируете API.
            На каком объекте планируете рисовать? (битмап, видеопамять, etc.)
            Почему Kolibri OS? Вы имеете отношение к этому проекту?
            И ещё вопрос по числам с плавающей запятой — используете команды математического сопроцессора? К сожалению я не знаком с архитектурой Kolibri — не вызовет ли переключение задач проблем с сопроцессором?
            Можно ли рассчитывать не на динамическую библиотеку, а на объектный файл в формате elf? Желательно, чтобы без обращений к другим библиотекам.
            И самый последний вопрос — под какой лицензией собираетесь распространять растеризатор?

              +3
              На ассемблере совсем не страшно. С помощью старших товарищей, конечно.
              Планирую отрисовывать в ОЗУ и передавать указатель на битмап.
              Колибри потому, что я имею некоторое отношение к проекту (я портировал для Колибри Lua 5.2a и FreeType 2.4.4). Именно результаты портирования FreeType (300-килобайтная библиотека для встраиваемой в ROM BIOS системы подобна смерти) подтолкнули к разработке своего велосипеда.
              Первоначально команды сопроцессора я использовал, но в последних версиях свел все вычисления к целочисленным. Кстати, переключение задач в Колибри проблем с сопроцессором не вызывает, насколько я знаю.
              Динамическая библиотека для Колибри — это объектный файл в формате COFF. Пока что растеризатор еще не является полноценной библиотекой, к сожалению, но даже если и будет являться, то вряд ли будет зависеть от какого-то стороннего кода.
              Собираюсь распространять под LGPL, как только библиотека будет готова.
          0
          Используйте тег source и тогда у вас статья влезет в лимит.
            0
            Спасибо! Сейчас исправлю…
            –3
            крутой чувак! +1
              –8
              как уроки физики в школе? справляешься?
              Помню на пед. практике вел физику — еле дожил :)
              Успехов!
                +1
                Насчет заливки полигона почитайте про алгоритмы тесселяции ;-)
                  +1
                  Слово-то какое страшное! А вообще идея интересная — использовать GPU для растеризации шрифтов. Если вы конечно это имели в виду.
                    +2
                    Кроме аппаратной тесселяции с использованием GPU можно реализовать программную. Тут есть ссылки на описания некоторых алгоритмов.
                  +1
                  Если не секрет сколько глифов сможет отрисовать данный алгоритм за секунду?
                    +2
                    Хм, кажется, я делал замеры когда-то…

                    Тестовая машина — Celeron 2.4GHz. Бенчмарк FreeType 2.4.4: без сглаживания 10x10 пикселей — 10мс/глиф, 24x24 пиксела — 15 мс/глиф, 600x600 — 275мс/глиф. У моего алгоритма (версия на JS) 10x10 пикселей — около 5 мс/глиф с выводом на экран и менее 1 мс/глиф в ОЗУ. 600x600 — около 250мс/глиф.
                    0
                    Очень хотелось бы сравнения с теплым ламповым маковским растеризатором.
                      0
                      Насколько мне известно, мак не использует хинтинг, так что результаты будут примерно одинаковые. У меня, к сожалению, мака нет. Если у вас есть, покажите пожалуйста миру результаты.
                      0
                      Мне на LCD мониторе больше нравится полутональное сглаживание чем субпиксельное, как ни странно — всё таки цвет по контуру мне бросается в глаза.
                        0
                        Дело вкуса. Я например немного дальтоник, так что мне больше нравится сглаживание по субпикселам.
                          0
                          Может зависить от монитора и его настроек, может от алогоритмов и их настроек, может от шрифтов, например, в Win7 я не смог настроить субпиксельное сглаживание, чтобы не видеть ореол, а в Ubuntu из коробки очень приятные и ровные контуры, в Arch'e стоит полутональное сглаживание, т. к. смотрится приятнее, а шрифты с полутональным сглаживанием из Mac OS X на моем мониторе смотрятся размытыми.
                          0
                          А что, если луч пройдет через угол? Непарное ведь количество точек будет.
                            +1
                            Хороший вопрос, я однажды об него споткнулся.
                            Алгоритм ищет пересечения прямых и луча. Угол, как вы понимаете, прямой не является. Так что непарного количества точек не будет.
                            0
                            Вот на этом изображении habreffect.ru/2f9/ece1971c2/sub2.png Я остановился и подумал: ба, а ведь чувак сумел сделать самое лучшее сглаживание шрифтов из всех тех, что Я видел! (Видел ClearType в Windows (просто ужасно, почти что и нет его), GDI++ в Windows (с различными пресетами), Ubuntu 11.04 (весьма приятное), и Mac OS 10.6.7 (весьма хорошо, но немного жирновато).
                              0
                              Спасибо.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое