Алгоритмы заливки

Applications development, KoOS API questions
  • Sorcerer

    Растровой графике - растровую заливку.
    1) построчный прогон реализовать проще, чем полноценный обход контура.
    2) циклы записи в память оптимизированы именно для горизонтальной растровой заливки.

    вот только что делать с таким контуром:

    Code: Select all

         BBB ? BBB ?
       BB...BBB.?.B
       B.....B..?.B
       BBB......BB
          BBBBBB ?
  • Хм, не самый худший результат. Эти точки (?) просто опускаются и на заливку не влияют. Для монохромных изображений сработает, для цветных пара дополнительных условий, и всё будет работать...

    Почему векторные изображения нужно как-то иначе заливать? Ведь на экране все равно мы будем иметь растр? Можно ведь растрировать векторную картинку, и залить... Или есть более правильные решения?
  • Sorcerer wrote:Почему векторные изображения нужно как-то иначе заливать? Ведь на экране все равно мы будем иметь растр? Можно ведь растрировать векторную картинку, и залить... Или есть более правильные решения?
    Дык и я про то же: перед заливкой картинка так или иначе все равно будет растеризована, так что предложенный подход представляется наиболее оптимальным.

    А вопросительными знаками я обозначил фрагменты, на которых твой алгоритм может сбоить.
    Надо как-то проверять контур на выпуклость/вогнутость.
    И проще всего это делать в векторном режиме :?

    Так что заливку имеет смысл вести либо на этапе растеризации, либо хранить где-то под рукой векторный образ контура для послерастровой заливки.
  • Так контур и проверяется ;) Здесь обводка (т.е. пиксели типа 1) - это действительно обводка, а не просто закрашенные пиксели. Она как раз учитывает изгибы, выпуклые и вогнутые части фигуры, и может закрашивать, например, контуры с волнистым краем.
    Сейчас выложу версию, где можно править карты вручную (правда, сделал это на скорую руку, не судите строго).
    А вот и ссылка для экспериментов. Если еще не работает, то скоро будет
    UPD: хотя, некоторые спорные места в алгоритме все же есть.
  • Хм. А вот если предложенный контур повернуть.... Что будет, интересно.
    Портится вся малина :) Пошел думать над алгоритмом дальше

    (А портится она из-за того, что слежение "внутри контура"/"снаружи контура" ведется только по одной оси)

    Может быть, у кого-нибудь есть идеи лучше?
  • А что, если так?

    1. Обвести заливаемый контур снаружи - так можно определить, где внутренняя часть контура, а где внешняя.
    2. Обвести внутренние полости заливаемого контура
    3. Идти по изображению сверху вниз слева направо и искать точки обводки, справа от которых имеется граница, вдоль которой мы делали обводку. Искать следующую в строке точку обводки. Закрасить строку от первой до второй точек обводки. Повторять пункт 3 до последней строки

    При таком раскладе вроде бы однозначно будет закрашена только внутренняя часть изображения...
    Правильно мыслю?
  • Сделать триангуляцию, закрасить треугольники
  • Для кривых Безье на разрешении 1200x1200 это будет трудно и ресурсоемко.
  • А вот этот алгоритм не подойдет?
  • Удивлен. Проверяю различные алгоритмы (на javascript - потому что понятнее, нагляднее и систему перезагружать постоянно не требуется), решил попробовать сделать "ленивый" вариант нахождения обводки для заливаемого контура. По-хорошему нужно обходить контур снаружи, проверяя, есть ли рядом пиксели контура. Но голова не варила, и я решил поступить так:

    1. Для всех свободных точек рядом с границей контура в памяти устанавливаю метки (условно зову их "синие") - получаю обводку по обе стороны от контура
    2. Далее, иду от края холста к контуру до тех пор, пока не встречу метку. Отмечаю эту точку "зеленой" меткой. Запоминаю ее координаты
    3. Из построения "синих" меток имею, что каждая точка имеет ровно двух соседей по горизонтали и/или вертикали для выпуклого контура (вогнутые пока что не рассматриваем для упрощения жизни).
    Делаю цикл while, в котором перехожу на соседнюю "синюю" точку, одновременно перекрашивая ее в "зеленую" до тех пор, пока не попаду в точку, соседнюю с начальной.
    4. Делаю заливку

    Тесты на участках 8x8 мне порядком надоели, и я растеризовал часть глифа от буквы "о" в массив 300 на 300.
    Получаю примерно ожидаемые результаты: Chromium 8 рендерит изображение за две-три секунды, Firefox 3.6.14 - около десяти (при этом возмущается, что скрипт не отвечает или завис).

    Код участка:

    Code: Select all

    fin=0;x=k;y=100;l=0;
    while (fin!=1) {
    if (m[x+1][y]==2) {m[x+1][y]=3;x=x+1;} //2 - синий, 3 - зеленый
    if (m[x-1][y]==2) {m[x-1][y]=3;x=x-1;} //Можно было бы не проверять прошлую клетку, но это потребовало бы еще один if, так какая разница?
    if (m[x][y+1]==2) {m[x][y+1]=3;y=y+1;}
    if (m[x][y-1]==2) {m[x][y-1]=3;y=y-1;}
    if ((x==k)&&(y==99)) {fin=1;}}
    

    А теперь начинается шаманство. Расставляю контрольные точки, и выясняю, что самый красный тормоз коммунизма - не перебор массива 300 на 300 в 1м или 4м шагах алгоритма (причем в первом шаге на каждую из 90000 итераций от 1й до 8ми проверок - не меньше 90000 операций, а во втором на каждую из 90000 итераций - ровно 3 проверки, то есть минимум 270000 операций), а "безобидный" третий шаг с его примерно 5 проверок*(2*Pi*150 пикселей радиуса)= 5000 операциями.

    Включаю индусский режим, и заменяю шаг 3 на вот такой бред:
    3. Перебрать все "зеленые" точки в памяти , и раскрасить их "синих" соседей в "зеленые". Повторять до просветления, число итераций определено экспериментально и находится между 140 и 160.

    Итого 300*300*160 итераций, в каждой от 1 до 5 проверок - никак не меньше 14400000 операций. Безумная и идиотская затея.

    Code: Select all

    for (z=0;z<160;++z){ //повторять до просветления
    for (y=0;y<299;++y){
    for (x=0;x<299;++x){
    if (m[x][y]==3) {if (m[x-1][y]==2) {m[x-1][y]=3;}; if (m[x+1][y]==2) {m[x+1][y]=3;}; if (m[x][y-1]==2) {m[x][y-1]=3;}; if (m[x][y+1]==2) {m[x][y+1]=3;}}}}}
    
    Chromium 8 - 2 секунды, Firefox 3.6.14 - 2 секунды.

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

    У меня тут вопрос... А в Колибри какой вариант быстрее работать будет-то? Обход контура, или "перекрашивание" многократными проходами? И насколько?

    UPD: Первый вариант в Firefox 4a9 -4 секунды, в Opera 10.60 - 2 секунды. Второй вариант в Firefox 4a9 - 3 секунды, Opera 10.60 - 4 секунды
  • Foldl wrote:А вот этот алгоритм не подойдет?
    Наверное, его используют, но насколько я знаю, в Колибри его реализации не прижились - кушает много памяти и/или не очень быстро работает.
    В принципе, алгоритм, который я использую, это один из представленных в wiki - scanline fill.
  • Не дает покоя мне идея реализации собственного формата шрифтов. Контур символа отобразить легко, а залить - трудно.
    FreeType использует следующий алгоритм: выбирается произвольная точка на контуре, и мы по ней идем (вверх, допустим). Пока мы идем вверх - помещаем в ячейку памяти 1, как только начали идти вниз - помещаем 2. Возвращаемся в начальную точку, и заливаем все между 1 и 2:

    Code: Select all

    ____1_____
    ___1*2____
    __1***2___
    __1****2__
    ___1111*2_
    _______12_
    
    Алгоритм отработает легко и быстро для прямых, но как только встречается кривая Безье, начинаются проблемы. Потому что для прямой определить, идет она вверх или вниз, можно, сравнив координаты Y начальной и конечной точек. С кривыми Безье так нельзя - потому что наивысшая точка кривой может не совпадать ни с начальной, ни с конечной, и, хуже того, может быть даже не посередине кривой.

    Алгоритм растеризатора FreeType (грубо говоря) разбивает кривые Безье на десятки-сотни меньших "прямых", и работает только с "прямыми".

    Мне такой подход не очень нравится, и вот что я придумал.

    Кривая Безье задается уравнениями:
    x=(1-t)^2 A + 2t(1-t) B + t^2 C
    y=(1-t)^2 A + 2t(1-t) B + t^2 C
    Если я закрашиваю буквы горизонтальными прямыми, я всегда знаю номер текущей строки (то есть y). Кроме того, я знаю A, B и С для всех кривых Безье.

    Из уравнения y=(1-t)^2 A + 2t(1-t) B + t^2 C я могу найти t для данной кривой (это займет примерно 50-60 команд ассемблера), и, зная t, могу найти координаты x для текущей строки этой кривой (еще штук 20-30 команд). Эту операцию я провожу для всех кривых (да и прямых), и нахожу все точки контура в закрашиваемой строке. В обычных шрифтах в одном глифе примерно от 10 до 100 кривых.

    Затем расставляю их по величине x, и заливаю между 1 и 2, между 3 и 4, между 5 и 6, и так далее. (Не знаю, сколько памяти и процессорного времени займет сортировка).

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

    Что скажете? Будет ли работать быстро, или нет? Я прикидываю, что на моем компе с 4200 bogomips этот алгоритм сможет отрисовывать в памяти около 10-20 тысяч глифов высотой 24 пиксела в секунду.
  • Здравствуйте!

    Мне кажется что алгоритм с разбиением на прямые самый быстрый и самый менее прожорливый, в зависимости от точности. Я думаю что программисты из FreeType пробывали разные способы и не просто так остановились именно на этом...я помню мы искали площади криволинейных трапеций, так самый быстрый способ был именно разбиение трапеции на много прямоугольников...
  • Sorcerer
    Что-то я не до конца понимаю. Если работать с растром -- то только с растром, а иначе все примитивы должны быть заданы аналитически. Что имеется? Кривые Безье и отрезки? Из них составляется замкнутый контур (фигура), необходимо этот контур залить. Тогда остается только научиться определять, лежит ли данная точка внутри фигуры или нет.

    Если все так, то почти наверняка эта задача уже решена, и описана в литературе.

    Прошу прощения, не знаю как организована информация в шрифтах.
    Но вот допустим, разбили мы все это хозяйство на фигуры. Умеем определять, лежит ли точка внутри фигуры, тогда для буквы "А" вот такой:

    Code: Select all

        _____
       /   __  \
      /   /_1\  \
     /    _ _     \
    /_2/      \ __\
    
    
    нужно закрасить все точки принадлежащие области 2 и одновременно не принадлежащие области 1.
  • Who is online

    Users browsing this forum: No registered users and 1 guest