Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.postscript > #3415

Re: Fierce and scary geometry algorithm for ‘ParallelPath’

Newsgroups comp.lang.postscript
Date 2019-06-28 12:10 -0700
References (7 earlier) <a5fe9001-8442-44a6-9092-14ec7abbc217@googlegroups.com> <839f314e-3b66-4227-b19c-b13ad697f6d8@googlegroups.com> <f33ac85c-3814-49a2-82a5-ee72a8fdd8ff@googlegroups.com> <e01e8168-970d-4c58-a965-6eefb6707534@googlegroups.com> <2be8c81c-3188-407d-a191-3db485e19800@googlegroups.com>
Message-ID <560e57c2-4be3-4c06-853b-3dbd34928190@googlegroups.com> (permalink)
Subject Re: Fierce and scary geometry algorithm for ‘ParallelPath’
From luser droog <luser.droog@gmail.com>

Show all headers | View raw


On Thursday, June 27, 2019 at 10:30:50 AM UTC-5, luser droog wrote:
> On Thursday, June 27, 2019 at 9:48:15 AM UTC-5, luser droog wrote:
> > On Thursday, June 27, 2019 at 8:59:25 AM UTC-5, luser droog wrote:
> > > On Sunday, June 23, 2019 at 5:59:51 PM UTC-5, luser droog wrote:
> > > > 
> > > > Using Calibri looks a little better, although apparently the orientation
> > > > is the reverse of what Palatino-Bold uses. I discovered that by doing
> > > > 
> > > >   /Calibri-Bold 596 selectfont
> > > >   100 100 moveto (9) true charpath
> > > >   1 setlinejoin
> > > >   flattenpath
> > > >   20 setlinewidth strokepath
> > > >   %dumppath                                                                                                                                                                                                                       
> > > >   0 setlinewidth
> > > >   stroke showpage
> > > > 
> > > > Then the dumppath nicely shows where the joins are (they're the curves).
> > > > But I haven't come up with a way to exploit that knowledge yet.
> > > > The image shows little hemispheres at each join.
> > > 
> > > After seeing a nice example of 'arcto', I rewrote my code to try to get
> > > something where I could drop in an 'arcto' to clean up the output.
> > > Sort of a step sideways. This program exhibits the same problems as the
> > > earlier ones, but it no longer uses the result of 'strokepath' so it's
> > > no longer implementation dependant. Instead it's dependent on the font
> > > glyph itself, expecting several closed paths followed by a lone 'moveto'
> > > at the end.
> > > 
> > > This program draws the original glyph, then the 'strokepath' scaffolding,
> > > then my curve at the same width in red, then another curve farther out in
> > > blue. The blue one makes it easier to see the problems going on in the red
> > > one.
> > > 
> > 
> > Much better now. Still something fishy in the lower left corner.
> > This time I take 3 points at a time, get the 2 normals and average
> > them to get a normal to offset the point.
> > 
> > Next step will be consulting the angle between these two vectors
> > to determine whether to attempt a join.
> > 
> 
> "Low Res Illustration"
> 
> Slight change to better illustrate what it's doing and what's going wrong.
> I set the flatness tolerance really high so we get choppy curves made out
> of fewer line segments. And I draw both sides of the offset curve.
> 
> So we can see that taking 3 points and averaging the normals works 
> tolerably (although it's not ideal) for large angles. But for small
> angles with lots of points, it generates these weird inner "ears".
> 

I now have code to detect and surgically replace the ears. All that remains
(I think) is fabricating the replacement points. I think the midpoint of
the outer two points of the ear will work, but we'll have to see what it
looks like.

It detects ears by checking the angular change at each point. Any runs of
two or more points at around 180 degrees +-30 are flagged as ears.
It uses a run-length encoding of the flagged ears to guide the snipping
and recomposition of the array of points representing the path.

I tried just running through the points and eliminating single points but
the result was ugly, and even after 12 iterations the ears remained.

I've reduced the output to one red curve and one blue curve. Output from the 
ear detection:

[[180.279633 2.00844574 0.224014282 3.93617249 0.410858154 4.97277832 0.176799774 8.12938309 1.70695114 8.78822327 0.701179504 4.62898636 3.62308717 358.79422 4.60778809 3.89904785 1.46203613 3.70935059 2.70690918 8.35592651 179.719986 179.853 163.023392 17.1131592 3.13320923 4.04489136 0.712005615 7.02740479 178.143555 189.396683 169.974152 175.828217 177.600082 182.887177 3.90716553 3.72990417 0.349945068 3.03895569 1.14590454 3.84664917 2.58439636 6.73135376 8.44741821 6.67147827 7.84288025 5.83366394 5.58996582 3.79476929 28.0493774 25.0266438 3.40968704 6.83194065 354.626678 356.853027 5.40203857 4.52456665 10.3373413 10.7698364 11.151123 8.07275391 7.5569458 4.2388916 6.66830444 6.71658325 7.65461731 8.71060181 10.1319275 7.57539368 4.35528564 5.24961853 3.06735229 3.64326477 7.19722 5.72450256 8.46220398 6.24998474 7.22940826 3.24757385 5.05388641 1.93174744 3.18948364 1.1917038 1.92375946 179.914886] [24.9522095 3.4453125 1.15289307 7.15319824 2.39425659 5.74685669 7.67160034 2.6307373 9.53170776 357.162567 9.54965782 3.18024826 10.4573441 3.06572342 8.25476837 0.406646729 6.06898499 1.02275085 5.94130707 0.709129333 9.92462158 0.1537323 7.91809082 5.94891357 0.386489868 8.43563843 0.587463379 7.52613831 0.184005737 7.20550537 23.6573792]]
[[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
[[20 3 5 6] []]
ear: [269.057098 111.948341 266.341736 110.966988 256.754395 108.264374]
ear: [190.533295 104.847809 188.945831 105.091476 187.242645 105.257896 177.905411 106.817024 169.435318 108.778946 168.050308 109.137222]
[[180.218292 2.01687622 0.0436096191 3.96809387 0.000625610352 5.02503967 1.02964401 8.28387833 4.94224167 8.95646858 2.01691246 4.68037891 3.62996459 357.561401 5.73220825 4.60473633 1.73522949 4.36611938 4.63208 224.63446 182.775711 177.832932 171.512054 179.457596 3.63217163 4.9083252 1.19036865 211.948822 181.631485 15.9331589 12.1903801 9.78577709 10.0365582 184.513687 6.11184692 3.96517944 0.330780029 2.85295105 1.11465454 3.59619141 2.53746033 6.36784363 7.94700623 6.40586853 7.37348938 5.58110046 5.29910278 3.65362549 31.8732605 330.994293 3.81921959 8.07155323 354.276306 356.622803 5.46618652 5.45623779 14.0754089 20.9076843 19.5395203 10.9311829 9.1210022 4.45806885 7.52981567 7.90560913 9.35531616 11.4260101 13.7461395 8.98088074 4.84397888 5.29974365 3.1807251 3.8860321 8.62216187 7.07295227 12.054184 8.52066 9.13140106 3.43019104 5.57831573 1.92396545 3.34073639 1.19281 1.97290039 179.966583] [23.7032623 3.42803955 1.44546509 7.04550171 2.96081543 5.74115 7.50161743 3.640625 9.2638855 356.477173 9.26171112 4.25038528 10.0725632 3.95078659 8.00562286 1.3163147 5.93536377 0.400894165 5.81770325 1.5835495 9.53955841 1.62148285 7.65376282 5.90643311 1.53013611 8.18559265 1.32310486 7.3684082 0.99836731 7.04521179 22.927063]]
[[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
[[20 4 3 2] []]
ear: [265.617462 121.338165 263.495483 120.553375 254.480942 118.00251 241.449 115.441101]
ear: [211.08754 113.391441 191.306641 114.817863]

So it's finding ears in the red curve that I can't even see.
There also appears to be an ear "around the corner" from the last point to
the first point of the first subpath of both curves which my detector does
not detect ATM.


$ cat stroke1.ps
%!

/closedsubpaths {
  [
    { [ 3 1 roll }
    { }
    { }
    { ] }
    pathforall
  ] ]
} def

/drawpath {
  {
     dup 0 2 getinterval aload pop moveto
     2 1 index length 2 sub getinterval
     0 2 2 index length 1 sub { % A i
       2 copy 2 getinterval aload pop lineto
       pop
     } for pop
     closepath
  } forall
} def

/div {
  dup 0 eq { pop }{//div} ifelse
} def

/normalize {
  2 copy dup mul exch dup mul add sqrt
  2 copy div % x y mag y/mag
  4 1 roll exch pop div exch % x/mag y/mag
} def

/perp {
  {y1 x1 y0 x0}{exch def}forall
  x1 x0 sub y1 y0 sub
  exch neg % dx,dy => dy,-dx
  normalize
} def

/v*n { 
  n mul exch n mul exch % A i dx dy
} def

/v+v {
  exch 4 3 roll add % dy yi xi'
  3 1 roll add % xi' yi'
} def


/offsetvectorsbyn {
  [ exch
    { %forall subpaths
      dup length 2 gt {
        [ exch
          %pstack()=
          0 2 2 index length 4 sub { % A i
              2 copy 4 getinterval aload pop % A i x0 y0 x1 y1
              perp v*n
              4 copy pop pop 2 getinterval aload pop % A i dx dy xi yi
              v+v
              4 2 roll pop
          } for  % [ ... A
          dup dup length 2 sub 2 getinterval aload pop 2 copy
          5 4 roll  0 2 getinterval aload pop
          %counttomark 1 sub index counttomark 2 sub index
          perp v*n
          v+v
        ]
      }{ pop } ifelse
    } forall
  ]
} def

/first2 {
  0 2 getinterval
} def

/first4 {
  0 4 getinterval
} def

/last2 {
  dup length 2 sub 2 getinterval
} def

/last4 {
  dup length 4 sub 4 getinterval
} def

/spill {
  aload pop
} def

/offsetcenterpoint { % ... ( ? ?? ) x_1 y_1 x0 y0 x1 y1 -> x0' y0'
  4 copy perp % xy_1 xy0 xy1 xy01^
  8 2 roll pop pop % xy01^ xy_1 xy0
  4 copy perp % xy01' xy_1 xy0 xy_10^
  8 6 roll v+v normalize % xy_1 xy0 xy^%
  v*n v+v 4 2 roll pop pop
} def

/offsetvectorsbynusingavg {
  [ exch
    {
      dup length 2 gt {
        [ exch
          dup dup last2 spill 2 index first4 spill
          offsetcenterpoint 4 2 roll
          pop
          0 2 2 index length 6 sub {
            2 copy 6 getinterval spill
            offsetcenterpoint 4 2 roll
            pop
          } for
          dup dup last4 spill 4 index first2 spill
          offsetcenterpoint 4 2 roll
          pop pop
        ]
      }{ pop } ifelse
    } forall
  ]
} def

/atan {
  2 copy 0 eq exch 0 eq and { pop }{ //atan } ifelse
} def

/recenter {
  %dup 180 gt { 360 sub } if
  %180 sub
  cvi %180 mod
} def

/dumpangles false def

/checkangle { % [inner 3 points]
  aload pop
  {y2 x2 y1 x1 y0 x0}{exch def}forall
  /dx1 x1 x0 sub def
  /dy1 y1 y0 sub def
  /dx2 x2 x1 sub def
  /dy2 y2 y1 sub def
  dumpangles { x1 =only ( )print y1 =only ( )print } if
  dy2 dx2 atan recenter dumpangles { dup =only ( )print } if
  dy1 dx1 atan recenter dumpangles { dup =only ( )print } if
  %2 copy 0 eq exch 0 eq or {
    %pop pop false 
    %dumpangles { (false)= } if
  %}{
    %2 copy  add dup =only ( )print
    %dup 180 lt exch -180 gt and
    %abs 300 lt
    %3 1 roll 
    sub dumpangles { dup =only ( )print } if
    abs 180 lt %and
    dumpangles { dup = } if
  %} ifelse
} def

/offsetpointbyavgperp { % x y [ xy0 xy1 .. xyn ]
  %dup 8 last 6 first checkangle 
  true
  {
    0 0  3 2 roll
    0 2 2 index length 4 sub {  % 0 0 a i
      2 copy 4 getinterval aload pop perp  % 0 0 a i xy^0
      6 4 roll v+v % a i xy^0+00
      4 2 roll     % 00' a i
      pop
    } for pop
    normalize v*n v+v 4 2 roll
  }{ pop pop pop } ifelse
} def

/cat {
  2 array astore [ exch { spill } forall ] 
} def

/first { %n
  0 exch getinterval
} def

/last { %n
  1 index length 1 index sub exch getinterval
} def

/offsetvectorsbynusingavgs {
  [ exch
    {
      dup length 2 gt {
        [ exch
          dup dup 4 last 1 index 6 first dup 2 first spill 4 2 roll cat
          offsetpointbyavgperp
          dup 2 last 1 index 8 first dup 6 last 2 first spill 4 2 roll cat
          offsetpointbyavgperp
          pop
          0 2 2 index length 10 sub {
            2 copy 10 getinterval dup 6 last 2 first spill 3 2 roll
            offsetpointbyavgperp
            pop
          } for
          dup dup 8 last dup 4 last 2 first spill 3 2 roll 3 index 2 first cat
          offsetpointbyavgperp
          dup 6 last dup 2 last spill 3 2 roll 3 index 4 first cat
          offsetpointbyavgperp
          pop pop
        ]
      }{ pop } ifelse
    } forall
  ]
} def

/filterbyangle {
  [ exch
    {
      dup length 2 gt {
        [ exch
          dup 2 last 1 index 4 first dup 2 first spill 4 2 roll cat
          checkangle { 3 2 roll }{ pop pop } ifelse
          0 2 2 index length 6 sub { % A i
            2 copy 6 getinterval dup 4 last 2 first spill 3 2 roll
            checkangle { 4 2 roll }{ pop pop } ifelse
            pop
          } for
          dup 4 last dup 2 first spill 3 2 roll 3 index 2 first cat
          checkangle { 3 2 roll }{ pop pop } ifelse
          %pstack()=
          pop
        ] 
      }{ pop } ifelse
    } forall
  ]
  dumpangles { ()= } if
} def

/p-p { % x1 y1 x0 y0
  %pstack()=
  exch 3 1 roll sub % x1 x0 y1-y0
  3 1 roll sub exch % x1-x0 y1-y0
} def

/getvectors {
  [ exch
    {
      [ exch
        dup 2 last spill 2 index 2 first spill p-p 3 2 roll
        0 2 2 index length 4 sub {
          2 copy 4 getinterval spill p-p 4 2 roll
          pop
        } for
        pop
      ]
    } forall
  ]
} def

/getangles {
  getvectors
  [ exch
    {
      [ exch
        0 2 2 index length 2 sub {
          2 copy 2 getinterval spill exch atan 3 1 roll
          pop
        } for
        pop
      ]
    } forall
  ]
} def

/getdiffs {
  [ exch
    {
      [ exch
        0 2 2 index length 2 sub {
          2 copy 2 getinterval spill sub abs 3 1 roll
          pop
        } for
        dup 1 last spill 1 index 1 first spill sub abs exch
        pop
      ]
    } forall
  ]
} def

/isbig {
  [ exch
    {
      [ exch
        {
          dup 160 gt exch 220 lt and { 1 }{ 0 } ifelse
        } forall
      ]
    } forall
  ]
} def

/findears {
  [ exch
    {
      dup length string exch % s a
      0 1 2 index length 1 sub { % s a i
        3 copy % s a i s a i
        get 3 2 roll exch % s a s i a_i
        put
      } for pop
    } forall
  ]
  [ exch
    {
      [ exch
        { (\1\1) search not { pop exit } if
          length exch pop exch
          2 exch
          {
             dup 0 get 1 ne { exit } if
             exch 1 add exch
             1 1 index length 1 sub getinterval
          } loop
        } loop
      ]
    } forall
  ]
} def

/tail { % n
  1 index length 1 index sub getinterval
} def

/snipthisear {
  (ear: )=only
  dup ==
} def

/snippoly { % [polygon] [ears]
  [ 3 1 roll
    0 2 2 index length 2 sub { % p e i
      2 copy 2 getinterval % p e i ear==[zeros ones]
      spill 2 mul exch 2 mul % p e i ones zeros
      5 4 roll exch % e i ones p zeros
      2 copy tail 3 1 roll first % e i ones p[zeros:$] p[0:zeros]
      5 1 roll % p-first e i ones p-tail
      exch % p-first e i p-tail ones
      2 copy tail 3 1 roll first % p-zeros e i p-tail p-ones
      snipthisear % p-zeros e i p-tail p-ones'
      4 1 roll 3 1 roll pop % p-zeros p-ones' p-tail e
    } for
    pop
  ] 
  [ exch {spill} forall ]
} def

/dosnips { % [[path][]] [[ears][]]
  [ 3 1 roll
    0 1 2 index length 1 sub { % p e i
      3 copy get % p e i p e_i
      3 1 roll exch get % p e e_i p_i
      exch dup length 0 eq { % p e p_i e_i
        pop 3 1 roll % p_i p e
      }{ % p e p_i e_i
        snippoly % p e p_i'
        3 1 roll % p_i' p e
      } ifelse
    } for
    pop pop
  ]
} def

/snipears {
  dup getangles %dup ==
  getdiffs dup ==
  isbig dup ==
  findears dup ==
  dosnips
} def

/offsetpath {
  /n exch def
  closedsubpaths
  %dup ==
  %pstack quit
  %offsetvectorsbyn
  %offsetvectorsbynusingavg 
  %1 { filterbyangle } repeat
  offsetvectorsbynusingavgs
  %1 { filterbyangle } repeat
  snipears
  %dup ==
  newpath
  drawpath
} def

%currentflat 4 div setflat
%currentflat 4 mul setflat
2 setlinejoin

/Calibri-Bold 596 selectfont
100 100 moveto (9) true charpath
gsave stroke grestore
20 setlinewidth
strokepath
1 setlinewidth
stroke

1 0 0 setrgbcolor
100 100 moveto (9) true charpath
flattenpath
gsave
  10 offsetpath
  3 setlinewidth 
  stroke
grestore
%-10 offsetpath
%stroke

%showpage quit

0 0 1 setrgbcolor
%100 100 moveto (9) true charpath
%flattenpath
%gsave
  20 offsetpath
  stroke
%grestore
%-20 offsetpath
%stroke


showpage quit

Back to comp.lang.postscript | Previous | NextPrevious in thread | Next in thread | Find similar | Unroll thread


Thread

Fierce and scary geometry algorithm for ‘ParallelPath’ jdawiseman.bloomberg@gmail.com - 2019-04-17 13:37 -0700
  Re: Fierce and scary geometry algorithm for ‘ParallelPath’ edspikechapman@gmail.com - 2019-04-19 08:55 -0700
    Re: Fierce and scary geometry algorithm for ‘ParallelPath’ jdawiseman.bloomberg@gmail.com - 2019-04-20 02:44 -0700
      Re: Fierce and scary geometry algorithm for ‘ParallelPath’ Carlos <carlos@cvkm.cz> - 2019-04-29 02:41 +0200
        Re: Fierce and scary geometry algorithm for ‘ParallelPath’ jdaw1 <jdawiseman@gmail.com> - 2019-06-19 15:04 -0700
          Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-22 13:33 -0700
            Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-22 14:54 -0700
              Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-22 15:37 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-23 15:59 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-27 06:59 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-27 07:48 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-27 08:30 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-06-28 12:10 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-07-19 14:00 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-07-20 09:33 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-07-20 13:20 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ jdaw1 <jdawiseman@gmail.com> - 2021-03-14 10:25 -0700
  Re: Fierce and scary geometry algorithm for ‘ParallelPath’ jdawiseman.bloomberg@gmail.com - 2019-04-20 06:54 -0700
    Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-04-20 10:44 -0700
      Re: Fierce and scary geometry algorithm for ‘ParallelPath’ jdawiseman.bloomberg@gmail.com - 2019-04-20 11:01 -0700
        Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-04-20 11:33 -0700
          Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-04-20 11:50 -0700
            Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-04-20 13:01 -0700
              Re: Fierce and scary geometry algorithm for ‘ParallelPath’ jdawiseman.bloomberg@gmail.com - 2019-04-21 03:29 -0700
                Re: Fierce and scary geometry algorithm for ‘ParallelPath’ luser droog <luser.droog@gmail.com> - 2019-04-28 13:33 -0700

csiph-web