Двуцветен обект Path

Следното изображение илюстрира какво се опитвам да постигна:

По принцип искам да създам два Path обекти, които се докосват един друг (успоредни пътища). Това е XAML, използван за генериране на това изображение:

<StackPanel Orientation="Horizontal">
    <StackPanel.LayoutTransform>
        <ScaleTransform CenterX="0" CenterY="0" ScaleX="15" ScaleY="15" />
    </StackPanel.LayoutTransform>
    
    <Grid Margin="-5,0,0,0">
        <Path Stroke="Blue">
            <Path.Data>
                <PathGeometry>M10,10 C20,10 10,20 20,20</PathGeometry>
            </Path.Data>
        </Path>
        <Path Stroke="Red">
            <Path.Data>
                <PathGeometry>M10,11 C19,10.85 9,20.80 20,21</PathGeometry>
            </Path.Data>
        </Path>
    </Grid>
    
    <Grid Margin="-5,0,0,0">
        <Path Stroke="Blue">
            <Path.Data>
                <PathGeometry>M10,10 C20,10 10,20 20,20</PathGeometry>
            </Path.Data>
        </Path>
        <Path Stroke="Red">
            <Path.Data>
                <PathGeometry>M10,11 C19,11 9,21 20,21</PathGeometry>
            </Path.Data>
        </Path>
    </Grid>
</StackPanel>

Първата крива има ръчно оптимизирани позиции на точки, втората има позиции на точки, които лесно се изчисляват, като се вземе предвид дебелината на щриха. Можете да видите, че втората крива не е перфектна, защото има разстояние между двете. Как мога да създам две идеално докосващи се криви програмно, без ръчно да оптимизирам всяка крива (което всъщност не е възможно, защото кривите се генерират в код)?

Просто казано, генерирам една крива (съответно Path) в кода и трябва да има два цвята. Така че си помислих, че правенето на втори паралел Path ще свърши работа, но коригирането на Geometry на втория Path (за да стане успореден) се оказа проблематично.

Актуализация #1

Паралелни линии и криви от Чарлз Петцолд може да е един от начините за решаване на този проблем. Всъщност работи доста добре, но изравнява кривите, което създава визуални артефакти при дълбоко увеличение и, разбира се, има недостатък в производителността.

Алгоритъмът обаче не се опитва да намери крива на Безие, която е успоредна на друга крива на Безие. Вместо това алгоритъмът се основава изцяло на полилинии: входът е една или повече полилинии, а изходът се състои от множество полилинии за всяка входна полилиния. Поради тази причина ParallelPath трябва да изравнява входната геометрия, което означава преобразуване на цялата геометрия (включително дъги и криви на Безие) в приближение на полилиния.

Актуализация #2

И така, един мой приятел (доктор по математика inceptor) анализира този проблем и създаде успоредна крива на (трети ред) Кривата на Безие е много сложна и изчислително скъпа. За всяка точка от паралелната крива компютърът трябва да изчисли нещо подобно:

(degree 3 polynomial) + (degree 2 polynomial) / sqrt(degree 4 polynomial)

Може би има начин да се оптимизира този израз, но все пак би било МНОГО ПО-скъпо от стандартна крива на Безие (защото паралелната крива е напълно различна крива от оригиналната крива на Безие). Искам да мога да анимирам кривата, така че това решение вероятно би било твърде скъпо за процесора. Това ни оставя няколко опции:

  1. Използвайте приближението на полилинията на Charles Petzold, което върши чудеса, но има визуални проблеми при дълбоко увеличение.

  2. Изведете нашето собствено приближение въз основа на това на Charles Petzond. Използвайте криви на Безие вместо линии (може би дъгите биха били достатъчни). Това би решило проблема с дълбокото увеличение, но вероятно е доста трудно да се кодира (нямам представа как да направя това).

  3. Може би е възможно да се създаде нещо като двуцветна четка. По този начин можем да използваме само едно Path, за да постигнем желания резултат (както е показано на първото изображение). Не съм го виждал никъде обаче, така че това вероятно не е опция.

Актуализация #3

Намерих някои доста интересни връзки:

Повече информация:

  • Предполага се, че QPainterPathStroker от Qt framework използва алгоритъма на Thomas F. Hain за паралелни криви
  • Това Java Stroker също се предполага, че може да рисува успоредни криви

Може би окончателното решение? (източник тук)

... Разработих всичко, което знаех за теорията на кривата на Безие, и развих несплесканото компенсиране до нещо, което е правилно, и (чудовище) документирах това на Практика за кривите на Безие


Опит №1

Направете втория път малко по-широк и го плъзнете под първия път, докато използвате Z -Индекс. http://i51.tinypic.com/2r5vwjk.png

Това няма да работи, Geometry трябва да се трансформира съответно.


person Paya    schedule 07.04.2011    source източник
comment
Добър, но труден въпрос, чудя се дали някой ще приеме предизвикателството...   -  person H.B.    schedule 08.04.2011
comment
Работата е там, че не можете наистина да използвате един и същ път за двете криви, тъй като те не са еднакви. Чудя се дали има начин вместо това да се създаде двуцветен щрих. По този начин можете да използвате един път, но двата цвята ще бъдат изобразени както искате, като използвате този единичен път.   -  person Jeff Mercado    schedule 08.04.2011


Отговори (6)


Вместо да използваш една крива на Безие от четвърта степен, защо просто не използваш съединение от две квадратични? Запознати ли сте с математиката на кривата на Безие? Те са предпочитани в графиките, защото са доста евтини в изчислителна гледна точка. Наскоро създадох програма, в която анимирах движението на клетките (просто за забавление):

въведете описание на изображението тук

Програмата може лесно да работи на цял екран на HD монитор със 100 анимирани и движещи се петна. И всичко беше GDI+.

Що се отнася до паралелните криви на Безие, според Wikipedia това наистина не може да се направи: http://en.wikipedia.org/wiki/B%C3%A9zier_curve

Така че вероятно ще трябва да сте доволни от евристичен подход.

РЕДАКТИРАНЕ 1:

За да не са вашите криви напълно произволни, защо не създадете очертанията на всяка крива и след това не запълните пътя? „Долната“ крива на единия път ще бъде „горната“ крива на другия.

РЕДАКТИРАНЕ 2:

Добре, както беше поискано, ето как си представям, че може да се изчисли решение, подобно на железопътна линия:

въведете описание на изображението тук

person Pedery    schedule 12.04.2011
comment
В предишните ми опити за това опитах две квадратни криви и, разбира се, успях да ги направя перфектни. Единственият проблем беше, че нямаше същия вид, трябваше да се мащабира вертикално за по-добро съответствие. - person Jeff Mercado; 12.04.2011
comment
Всъщност мисля, че беше дъга, дори не квадратна. - person Jeff Mercado; 12.04.2011
comment
@Pedery: Добре е да използвам комбинация от две квадратни криви. Истинското предизвикателство тук е да се изчислят всичките 3 точки на всяка крива, за да изглеждат идеално успоредни. И това трябва да се направи в код, не мога да оптимизирам на ръка всяка крива... Тази идея всъщност е нещо, за което вече съм мислил, вижте Update #2/Option 2. - person Paya; 12.04.2011
comment
Защо гласуването против? Коментирах скоростта на изобразяване (която е достатъчно бърза) и че според wikipedia създаването на паралелни криви на Безие не е възможно. Въпреки това в квадратичния случай всяка крива е полином от втора степен. Ако евристичните подходи не са подходящи, опитайте да изчислите производната на всяка крива и да ги компенсирате по обратната допирателна, като железопътна линия. Може да стане грозно, но може да е точно това, което търсите. - person Pedery; 13.04.2011
comment
@Jeff: Можете да накарате кривите на Безие да изглеждат доста подобни. Adobe Flash използва Beziers за показване на кръгове. Всички TrueType шрифтове се изобразяват с Beziers. Основно е въпрос на избор на правилните сегменти и задаване на контролните точки. - person Pedery; 13.04.2011
comment
@Pedery: Не съм гласувал против (много рядко го правя в собствените си въпроси), така че не знам защо. :-) Какво точно имате предвид под евристичен подход? Аз всъщност не съм математик, така че трябва да знам малко повече. - person Paya; 13.04.2011
comment
ААА разбирам. Също така съм съгласен с вашия военнопленник относно гласуването против, тъй като има тенденция да обезсърчава хората да идват със съвети. Както и да е, евристиката е начин за решаване на сложни проблеми по по-лесен начин, където не сте толкова точни или приемате, че решението понякога намира само локални максимуми/минимуми. Вижте връзката в публикацията ми за съвети. Изчисляването на Безие обикновено се нуждае само от малко ум и математика за 16-годишните. Отново вижте връзката ми. - person Pedery; 13.04.2011
comment
@Pedery: Да, проверих връзката и няма нищо полезно за евристиката. И така, какво точно предлагате като евристичен алгоритъм? Аз съм повече програмист, отколкото математик, така че наистина не мога да си представя какво трябва да направя, за да премина от намиране на локални максимуми/минимуми до начертаване на крайната успоредна линия. Вашата редакция не е ли същата като отговора на Timwi? Както и да е, актуализирах въпроса си с някои полезни връзки, но ще ги проверя по-късно, нямам време в момента. - person Paya; 13.04.2011
comment
Е, вие трябва да знаете как работят Безиерите, ако искате да играете с тях. Всъщност са доста готини. Както и да е, ето го: За всеки даден сегмент от линия между P0 и P1, точка от този сегмент може да бъде представена като tP0 + (1-t)P1, t:[0,1]. Направете същото за точки P1 и P2, като използвате същия t. След това го направете отново между 1. и 2. линейни сегменти. Сега ще имате полином от втора степен, който можете да изведете. Сега намерете допирателната във всички точки [още...] Вижте също: math.stackexchange.com/questions/32225/ - person Pedery; 19.04.2011
comment
От допирателната линия в точка можете да изчислите завъртяната на 90 градуса версия на тази линия, след което да получите други линии, изместени от железопътната линия, като преместите по тази линия фиксиран брой единици. Тъй като ще използвате Beziers, трябва да знаете, че трите криви ще бъдат различни. Квадратният Безие ще свърши добре работата и е лесно да ги направите непрекъснати в крайните точки. - person Pedery; 19.04.2011
comment
@Pedery: Ако разбирам правилно предложеното от вас решение, тогава това е приближение на полилиния, нали? Тъй като кривата, успоредна на крива на Безие, НЕ Е крива на Безие (в повечето случаи). Така че единственото реално решение е апроксимацията на полилиния (за която вече намерих код), но има вече споменатия проблем с дълбокото мащабиране. Или намерих алгоритъм за multiple-joined-bezier паралелни криви, който е в състояние да създаде множество съединени криви на Безие за компенсиране на дадена единична крива на Безие. - person Paya; 24.04.2011
comment
@Pedery: Или вашето решение е различно от двете споменати решения? Получаването на точки, успоредни на крива, не е проблем, получаването на допирателни, вертикали и т.н. е просто. Истинският проблем е да се изрази паралелната крива във формат Безие (вижте успоредни криви на множество съединени безие), а не в линии (апроксимация на полилиния). - person Paya; 24.04.2011
comment
Докато можете да изразите една от трите криви като крива на Безие, а другите две като полилинии, получени от кривата на Безие, дори дълбокото мащабиране не би трябвало да е проблем. Защото без значение колко дълбоко увеличавате, все пак ще получите гладка крива от Безие и след това можете да извлечете другите две криви на железопътна линия от това. - person Pedery; 24.04.2011
comment
@Pedery: Може ли да прикачите изображение, описващо за какво говорите? Какви три криви? Изучавал съм апроксимацията на полилинията на Charles Petzold и дълбокото мащабиране е проблем и винаги ще бъде, ако апроксимирате криви с този метод. Изображението, което включих в OP, е просто демонстрация на това как трябва да изглеждат успоредните линии, но имам нужда решението да е възможно най-общо. - person Paya; 24.04.2011
comment
Редактирането е добавено. Вижте приложеното изображение. - person Pedery; 25.04.2011
comment
@Pedery: Всъщност дълбокото мащабиране е проблем. Изпълнението на Charles Petzold е точно това, за което говорите, и ако мащабирате дълбоко успоредните криви, ще видите проблема. Може би, ако можех да извадя повторно паралелните криви в зависимост от текущото ниво на увеличение, може да накара това да работи, но това вероятно не е възможно (как контролата ще разбере дали е увеличена?). И не мога просто да използвам безкрайни честоти на семплиране, трябва да има ограничение, поради което има проблем с дълбокото увеличение. - person Paya; 26.04.2011
comment
Да, мислех, че е очевидно, че ще трябва да преизчислите квазипаралелните криви на Безие, когато увеличавате. Добавете събитие за него, ако желаете. След това преизчислете и прерисувайте за всяко увеличение. Контролът може да разбере дали се увеличава, ако го позволите. - person Pedery; 26.04.2011

Казахте, че искате да създадете два обекта Path, които се допират един до друг, но не посочихте как се генерират пътищата. Моят отговор ще приеме, че вече имате път, генериран от някакъв алгоритъм, и искате да го превърнете в два нови пътя.

Бих преминал от опити да използвам щрихи към използване на запълвания. Ако можете автоматично да създадете червената пътека във втората си картина, можете също така да създадете обединена пътека, състояща се от двете, и след това да я запълните, вместо да я рисувате с щрих. След това правите същото в две посоки.

Резултатът, който получавам за вашия пример, изглежда така:

Изображение, показващо съединените пътища

<StackPanel Orientation="Horizontal">
    <StackPanel.LayoutTransform>
        <ScaleTransform CenterX="0" CenterY="0" ScaleX="15" ScaleY="15" />
    </StackPanel.LayoutTransform>

    <Grid Margin="-5,0,0,0">
        <Path Fill="Blue" Stroke="Transparent">
            <Path.Data>
                <PathGeometry>M10,10 C20,10 10,20 20,20 L20,19 C11,19 21,9 10,9</PathGeometry>
                <!--          |←    original path    →| |←  generated part   →| -->
            </Path.Data>
        </Path>
        <Path Fill="Red" Stroke="Transparent">
            <Path.Data>
                <PathGeometry>M10,10 C20,10 10,20 20,20 L20,21 C9,21 19,11 10,11</PathGeometry>
                <!--          |←    original path    →| |←   generated part   →| -->
            </Path.Data>
        </Path>
    </Grid>
</StackPanel>
person Timwi    schedule 09.04.2011
comment
@Timwi: Благодаря, +1. Това наистина е хубав прагматичен подход. Проблемът е, че искам да генерирам 2 пътеки с щрихи (или запълвания, всъщност няма значение), които се допират една до друга, където общата им ширина е 2 (във всяка отделна точка), а ширината на всяка крива е точно 1. Кривите във вашето решение имат различни ширини в различни точки. - person Paya; 10.04.2011
comment
@Paja: Е, както вече открихте, това е математически много труден проблем, така че не мисля, че ще намерите лесно решение, което да изпълни този критерий... - person Timwi; 10.04.2011
comment
@Timwi: Вярвам, че не съм първият, който се сблъсква с този проблем, може би някой ще дойде да сподели мъдростта си с нас. Всъщност съм доста изненадан, че WPF няма никаква поддръжка за това. Какво правят всички WPF разработчици, когато искат двуцветен правоъгълник със заоблени ъгли? Те просто използват две Borders с CornerRadius, игнорирайки факта, че това ще изглежда доста грозно при дълбоко увеличение? - person Paya; 11.04.2011
comment
@Paja: Предполагам, че използват само запълване и щрих. Можете да нарисувате заоблен правоъгълник със синьо запълване и дебел червен контур. - person Timwi; 11.04.2011
comment
@Paja: Не мисля, че дълбокото мащабиране е целеви сценарий за WPF като цяло (според мен с право). Висококачественото дълбоко мащабиране ще трябва да се обработва в логиката на приложението, а не в слоя за изобразяване. - person Serguei; 12.04.2011
comment
@Serguei: Мисля, че това зависи от това какво се опитвате да увеличите. Ако растерни изображения, тогава разбира се, но ако просто искате да увеличите векторна графика (или графика, създадена само в XAML), тогава не виждам проблем тук. - person Paya; 12.04.2011

Помислете за малко по-различен подход към проблема...

Приемане на „голям“ брой точки върху геометрията. Възможно е да се използва един от методите за интерполация с по-високо качество чрез вземане на проби от геометричните точки при по-ниски нива на увеличение. С увеличаването на мащаба можете да увеличите честотата на дискретизация и вместо това да изобразите само част от кривата; По този начин обемът на изчисленията трябва да остане относително постоянен при всички нива на увеличение. Ключът е, че има постоянен брой пиксели на екрана и можете да започнете да вземате проби, след като точността премине някакъв праг.

person Serguei    schedule 12.04.2011
comment
Благодаря, интересна идея. Но това, което искам, е да направя контролата за многократна употреба и не съм сигурен, че контролата може да определи нивото на увеличение, използвано от родителския контрол. Представете си ситуацията в OP, където ScaleTransform е зададено на StackPanel. Как MyPath ще определи, че е увеличен, така че трябва да семплира повторно част от кривата? Извинете ме, ако това е нещо тривиално, но не съм точно експерт по WPF. - person Paya; 12.04.2011

Прочетете това... Silverlight - Epic Graphical Fail (правоъгълник с два триъгълника) :(

Актуализация

Опитайте това (малко е пресилено, но може да ви помогне)

<Grid Margin="-5,0,0,0">
    <Path Stroke="Blue" Data="M10,10 C20,10 10,20 20,20 M20,21 C9,21 19,11 10,11"
            Fill="Blue" Clip="M10,10 C20,10 10,20 20,20 L20,21 C9,21 19,11 10,11"/>
    <Path Stroke="Blue" Data="M10,10 C20,10 10,20 20,20"/>
    <Path Stroke="Red" Data="M10,11 C19,11 9,21 20,21"/>
</Grid>

въведете описание на изображението тук

person obenjiro    schedule 13.04.2011
comment
Готово, но как това ще ми помогне? Те създават триъгълници, но аз се нуждая от успоредни криви на Безие, това е нещо съвсем различно. - person Paya; 13.04.2011
comment
Ами.. проблемът със Silverlight е следният.. ако искате да начертаете два пътя, които искате да бъдат свързани един с друг (например успоредни криви на Безие), те трябва да са част от ЕДИН обект на пътека... това така работи Silverlight... - person obenjiro; 13.04.2011
comment
Истинското предизвикателство тук е да се получи успоредна крива на Безие. Представете си, че имам само една крива в началото и искам да получа втора успоредна. Да направим кривата наистина успоредна е доста сложна задача, дори не разбирам математиката зад нея. - person Paya; 13.04.2011

Възможно ли е да направите втория път (генериран) малко по-широк и да го плъзнете под (зад) първия път с помощта на z-индекс? По този начин ще получите безпроблемно присъединяване.

person Dave White    schedule 07.04.2011

Да приемем, че имате нужда от два реда с ширина 5:

Ако един пиксел разлика не е твърде много, можете първо да начертаете крива с да кажем ширина 11 в червено, след това да начертаете крива със същия път с ширина 1 в синьо, след което да запълните една от страните със синьо.

Щяхте да разделите линията наполовина и да оцветите едната половина, проблемът е, че разделянето наполовина отнема един пиксел :(

Но какво ще се случи, ако изберете равномерна ширина като 10? Къде ще бъде средният пиксел? Може би можете да използвате нещо...

person Marino Šimić    schedule 13.04.2011
comment
Ако разбирам правилно предложението ви, то ще запълни съдържанието на един от Paths, нали? Което означава, че няма да получите първото изображение в OP, а нещо съвсем различно, тъй като едно от Path ще бъде не само очертано, но и запълнено (вътрешността на Path няма да е прозрачна, както в първото изображение, а изпълнена с цвят ). За съжаление 1 пиксел има значение. :-( - person Paya; 13.04.2011