Skip to content

Gradient Part 2 - Linear gradients#2279

Merged
laurmaedje merged 95 commits intotypst:mainfrom
Dherse:gradient-linear
Oct 3, 2023
Merged

Gradient Part 2 - Linear gradients#2279
laurmaedje merged 95 commits intotypst:mainfrom
Dherse:gradient-linear

Conversation

@Dherse
Copy link
Contributor

@Dherse Dherse commented Oct 1, 2023

Tracking issue: #2282

Changes

This is the second part of gradients, it includes the following changes:

  • Adds the gradient type
  • Adds the gradient.linear constructor for creating a gradient with the following args:
    • stops (variadic): either a color or a color and a ratio in an array
    • dir (defaults to ltr): the direction in which the ratio goes
    • space: the color space in which to interpolate the stops
    • relative: what is the gradient relative to (see later)
  • Layout engine changes
    • Frame can now be FrameKind::Soft or FrameKind::Hard, this is information being kept of simply to determine what is the "container" of an element
    • block, box, columns, page, polygon, and frame produce hard frames, anything else produces a soft frame
    • hard frames are never inlined (we need to keep track of the transform)
  • Adds the following method to the gradient type:
    • stops to get the color stops of the gradient
    • space to get the interpolation color space of the gradient
    • relative returns the "relative-ness" of a gradient (see later)
    • dir returns the angle of the gradient
    • kind returns the constructor used to create this gradient (currently only gradient.linear)
    • sample samples a color at a given ratio t
    • samples samples many colors (variadic) at a series of given ratios t
    • sharp turns the gradient into its sharp variant (see later)
    • repeat repeats the stops of the gradient n times, optionally mirror every other repetition
  • Adds the following presets color maps (see later for an image with all of them): turbo, cividis, rainbow, spectral, viridis, inferno, magma, plasma, rocket, mako, vlag, icefire, flare, and crest. They can all be used (except rainbow) for data visualization.
  • PDF/SVG/PNG export of gradients
  • The following test:
    • aspect ratio correction (see later)
    • directions of gradients
    • all presets
    • relative argument
    • repeat method
    • sharp method
    • gradients are strokes
    • modified methods.typ to add tests to all methods in gradient
    • test that gradient on text (see later) gives an error
    • test that all transforms work well (tested because of a bug I had introduced)
  • Adds a size + gradient operation for creating a stroke
  • Gradient on text causes an error with a hint saying it will be available soon:tm: (see later)

Also adds the following niceties:

  • Adds better code completion for colors (was missing the new color spaces)
  • Adds a quadrant method on angle that allows getting the quadrant in which the angle is, it is used when computing gradients, I figured it was a nice method to expose to the user.
  • Small SVG export file size improvements

Presets

test

Gradients on text

Gradients on text are currently disabled, they will be included in Part 4 of the gradient series of PRs. Currently, they give an error when someone tries to use them:
image

Relative-ness

Gradients can be relative to either the shape they are painted on, or to the nearest parent containers. This is controlled by the relative argument of the constructors. By default, gradients are relative to the shape they are painted on, unless they are painted on text, in which case they are relative to the parent.

The way the parent is determined is as follows:

  • For shapes that are placed at the root/top level of the document, the parent is the page itself.
  • For other shapes, the parent is the first block or box that contains the shape. This includes the boxes and blocks that are implicitly created by show rules. For example, a rotate will not affect the parent of a gradient, but a grid will.

The following code exemplifies this, if they were not in the same relative coordinate space for their fills, the result would not look uniform and continuous.

#set page(width: 100pt, height: 100pt, margin: 0pt)
#set block(spacing: 0pt)

#let fill = gradient.linear(..gradient.icefire, relative: "parent", dir: ttb)

#place(top + left, square(fill: fill, size: 50pt))
#place(center + horizon, rotate(45deg, square(fill: fill, size: 50pt)))
#place(bottom + right, square(fill: fill, size: 50pt))

test

Sharp gradient

Additionally, the user can sample the gradient to make a "sharp" variant (i.e with sharp color bars), this is especially useful for creating a list of colors from a color map. Below a result of such a sharp gradient can be seen along with it's non-sharp version:

test

Aspect ratio correction

Due to the way gradients are handled, they would normally ignore the aspect ratio of the shape and the dir of the gradient would become incorrect for gradients with a width to height ratio (aspect ratio) of anything but 1. This is handled in angle.rs with a small method. The reason why I mention it is because making this (imo necessary) correction leads to a larger file size as it decrease how well the exported gradient data can be re-used in PDF and SVG export.

Copy link
Contributor

@PgBiel PgBiel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick moment

Dherse and others added 18 commits October 1, 2023 12:24
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Co-authored-by: Pg Biel <9021226+PgBiel@users.noreply.github.com>
Dherse and others added 2 commits October 3, 2023 08:21
Co-authored-by: bluebear94 <uruwi@protonmail.com>
@reknih
Copy link
Member

reknih commented Oct 3, 2023

While revising docs I noticed this failure when setting the rect to 100%:

Rainbow gradient wraps around on itself

I also think that two stops for rainbow is not particularly instructive:

Purple box

@Dherse
Copy link
Contributor Author

Dherse commented Oct 3, 2023

For the first one I have a good idea of what causes it, and the other one is me only giving it to points, and the two end points are the same color 😅

@laurmaedje
Copy link
Member

laurmaedje commented Oct 3, 2023

I just pushed a commit with some tidying up. While doing that, I noticed the following things:

  • gradient.linear(dir: 70deg) feels wrong to me. And gradient.dir() -> angle, too. Maybe we can have angle as the main argument and accessor and dir is a separate argument that internally sets the angle? Kinda like set page(paper: _) is a shorthand for width and height.

  • I also think the name relative is not clear as it's an adjective while what we're looking for is really a noun describing an object. Perhaps there is a better name (base, scope, container is the direction I'm thinking although I'm not a huge fan of either of those).

  • I wonder whether the new Angle::quadrant method and the Quadrant enum should be exposed? I haven't seen real motivation for this public-facing addition.

  • In gradient.rs, you have the line let (sinh, cosh) = h.sin_cos();. Isn't sinh different from sin?

  • The bezier bbox logic that was lifted from GitHub could be replaced by a call to kurbo, which is already in our dependency tree. (Would need to add it to typst's Cargo.toml, it's only in typst-library right now). The less copy-pasted complex math logic in the code base, the better, in my opinion.

  • Gradient Part 2 - Linear gradients #2279 (comment)

  • Gradient Part 2 - Linear gradients #2279 (comment)

@Dherse
Copy link
Contributor Author

Dherse commented Oct 3, 2023

gradient.linear(dir: 70deg) feels wrong to me. And gradient.dir() -> angle, too. Maybe we can have angle as the main argument and accessor and dir is a separate argument that internally sets the angle? Kinda like set page(paper: _) is a shorthand for width and height.

I'll do that in a bit, first I'm finishing some fixes for HSV and HSL interpolation

I also think the name relative is not clear as it's an adjective while what we're looking for is really a noun describing an object.

Imo it's fine since it's trying to describe what the coordinate system is relative to. And I agree that the other names are not much better. relative-to is also a bit ugly imo.

I wonder whether the new Angle::quadrant method and the Quadrant enum should be exposed? I haven't seen real motivation for this public-facing addition.

I don't mind either way but since I have a lot of operations depending on quadrants (especially with the other types and on text) it's needed at least internally. I can make it private if you want 🤷‍♂️

In gradient.rs, you have the line let (sinh, cosh) = h.sin_cos();. Isn't sinh different from sin?

The code is correct (doubled checked it like 10 mins ago), it's sin(h) and cos(h) I admit the names are a bit problematic, it will be changed in my commit with corrections for HSL and HSV

The bezier bbox logic that was lifted from GitHub could be replaced by a call to kurbo, which is already in our dependency tree. (Would need to add it to typst's Cargo.toml, it's only in typst-library right now). The less copy-pasted complex math logic in the code base, the better, in my opinion.

I didn't know, will change.

@Dherse
Copy link
Contributor Author

Dherse commented Oct 3, 2023

@laurmaedje @reknih I think I have covered everything you mentioned, apart from:

  • Presets are still arrays
  • relative is still called relative

@Dherse
Copy link
Contributor Author

Dherse commented Oct 3, 2023

As discussed on Discord, I changed the way color maps are handled so that they're purely arrays of colors and no longer use functions with a # of stop param

@Dherse
Copy link
Contributor Author

Dherse commented Oct 3, 2023

As discussed here and on Discord, presets have been moved to a dedicated space color.map.{preset-name}

@Dherse
Copy link
Contributor Author

Dherse commented Oct 3, 2023

Also opened an issue on PDF.js for the compatibility issue I found: mozilla/pdf.js#17065

@laurmaedje laurmaedje merged commit a4e357f into typst:main Oct 3, 2023
@laurmaedje
Copy link
Member

Thank you so much for all your work on this! 🎉

@Dherse
Copy link
Contributor Author

Dherse commented Oct 3, 2023

Just to note that there is a slight regression in one of the later commits that makes sharp gradients a bit blurry in SVG format, will be fixed soon (probably tonight, it's like a 10 lines fix but I need to go make dinner now)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants