diagrams / diagrams-lib

Diagrams standard library
https://diagrams.github.io/
Other
138 stars 62 forks source link

Gradient along the spline? #334

Open ghost opened 5 years ago

ghost commented 5 years ago

Is there any general way to specify gradient along the 1d position on spline? For example, this one I made using cubicSpline, it consists of 6 points: https://imgur.com/MOqOKDN

Gradient is: linear = mkLinearGradient (mkStops [(black, 0, 1), (white, 1, 1)]) 0 1 GradPad

What I want to achieve is that spline gradually becomes white along the way from the start to the end, so that point A is visible and B is not.

fryguybob commented 5 years ago

I don't think this is typically supported by graphics packages and I think it is because it can be computationally a bit expensive and maybe difficult to get reliable results. That said, I think diagrams would be a great place to include this feature, though I'm not sure the best way to express it or if it could be supported by all backends. Here is an example of someone achieving this with SVG and JavaScript: https://bl.ocks.org/mbostock/4163057

ghost commented 5 years ago

@fryguybob I've also been thinking about working around by dividing into a lot of small segments and interpolating their colors, I'm surprised this approach looks so good. I just tried it with diagrams and the immediate problem is that I can't find any way to sample points on spline. There are trailPoints and trailVertices, but I can't specify some delta or number of points I want to get.

ghost commented 5 years ago

There is a way to sample points using tracing like in showTrace': https://hackage.haskell.org/package/diagrams-lib-1.4.2.3/docs/src/Diagrams.TwoD.Model.html#showTrace%27 But the problem is that points are out of order and they are not equally distributed on the spline.

fryguybob commented 5 years ago

@ivxvm I think you want atParam and arcLengthToParam both from Diagrams.Parametric.

ghost commented 5 years ago

I've implemented similar approach with diagrams and I'm very satisfied with results. It allows not only gradients, but variation of any style parameters along the trails. Here's an example of decreasing-width-decreasing-opacity: https://imgur.com/GZRmteT

Code is a mess, can be improved for sure:

morphingSpline :: Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram B
morphingSpline numIterations styleFn points =
    mconcat $ map render $ getZipList $ (,,) <$> ZipList points' <*> ZipList (tail points') <*> ZipList ticks
  where
    spline  = cubicSpline False points :: Trail _ _
    points' = map (\t -> spline `atParam` t) ticks
    ticks   = [0 :: Double, 1 / fromIntegral numIterations .. 1]
    render (s, e, t) = strokeLocTrail (fromSegments [straight (e - s)] `at` P s) # styleFn t

styleFn t = opacity (1 - t) . lw (local ((1 - t) / 3))

diagram = morphingSpline 1000 styleFn points # bgFrame 1 white

Ripple texture is a side effect of aliasing (or something else), gonna figure out how to fix it. Not an issue with default line width: https://imgur.com/ym7oJyG

bacchanalia commented 5 years ago

You can simplify your code by using section. I was hoping it might also help with the ripple effect, but it doesn't.

edit: you can fix the ripple texture by overlapping segments (tail became drop 2, fixed below)

morphingSpline :: _ => Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram b
morphingSpline numIterations styleFn points =
    mconcat $ zipWith render ticks (drop 2 ticks)
  where
    spline  = cubicSpline False points :: Located (Trail _ _)
    ticks   = [0, 1 / fromIntegral numIterations .. 1]
    render s e = stroke (section spline s e) # styleFn s
ghost commented 5 years ago

@bacchanalia Thanks, gotta wrap my head around section. For some reason this gives me weird results (looks cool tho): https://imgur.com/abQZSpt (with drop 2 ticks) https://imgur.com/p4pezKW (with tail ticks)

Edit: I've found out setting lineCap LineCapSquare fixes the ripple, but it still looks wrong. Opacity is incorrect because of the overlapping. In this case, spline should already be half transparent in the middle: https://imgur.com/46cpuRN

bacchanalia commented 5 years ago

lineCap LineCapSquare works for basically the same reason as dropping ticks works: the line cap is thick and therefore causes overlap. The pattern still shows up to a lesser extent with drop 2 ticks because the overlap isn't sufficient. You can increase the overlap but have finer control by increasing 2. Increasing the overlap with drop causes not enough opacity at the beginning, so I found changing the first arg to replicate n 0 ++ ticks is better. You still need to to turn down the opacity to compensate for the overlap.

morphingSpline :: _ => Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram b
morphingSpline numIterations styleFn points =
    mconcat $ zipWith render (replicate 10 0 ++ ticks) ticks
  where
    spline = cubicSpline False points :: Located (Trail _ _)
    ticks  = [0, 1 / fromIntegral numIterations .. 1]
    render s e = stroke (section spline s e) # styleFn s
points = map p2 [(0,0), (200,300), (500,-200), (-400,100), (0,300)]
-- using opacity/5 for n = 10 as a guess
styleFn t = opacity ((1 - t)/5) . lw (local (500*(1 - t) / 3))
diagram = morphingSpline 1000 styleFn points # pad 1.3 # bg white

morphingSpline

ghost commented 5 years ago

@bacchanalia Running your code gives me this. What backend do you use? I'm using Rasterific-0.7.4.2, diagrams-rasterific-1.4.1.1, diagrams-lib-1.4.2.3, diagrams-core-1.4.1.1.

cvt_500x500_1139926420499568107

Edit: Ah, I just noticed https://github.com/diagrams/diagrams-lib/issues/322, it's probably not in any released version yet (I've used stack with lts-13.12). Thank you, that looks great!

bacchanalia commented 5 years ago

Right, I should have mentioned that section was fixed in HEAD, but the fix hasn't been released yet. Sorry about that.

ghost commented 5 years ago

@bacchanalia Could you please check if this works fine for you?

morphingSpline :: _ => Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram b
morphingSpline numIterations styleFn points = mconcat $ zipWith render ticks (tail ticks) -- (replicate 10 0 ++ ticks) ticks
  where
    spline = cubicSpline False points :: Located (Trail _ _)
    ticks  = [0, 1 / fromIntegral numIterations .. 1]
    render s e = stroke (section spline s e) # styleFn s

points = map p2 [(0,0), (200,300), (500,-200), (-400,100), (0,300)]
styleFn t = id -- opacity ((1 - t)/5) . lw (local (500*(1 - t) / 3))
diagram = morphingSpline 1000 styleFn points # pad 1.3 # bg white
bacchanalia commented 5 years ago

@ivxvm pentagramearrings