googlefonts / colr-gradients-spec

63 stars 8 forks source link

COLR for non-color use cases #88

Open Lorp opened 4 years ago

Lorp commented 4 years ago

In the context of COLRv1 being a superset of the features of composite glyphs, I wonder if it is too late to consider adding the possibility of rotation by a numeric value, rather than using the affine transformation matrix? Especially when combined with a variation axis, it would be useful not just for arrows, ornaments and clock faces, but also to enable chained rotations, so that mechanical objects, such as animal skeletons, can be represented efficiently. Such freedom of movement must currently be represented by multiple keyframes or by higher order interpolation (HOI). The former suffers from inaccuracy, the latter from an opaque build process, lack of support in UI, and effective limitation to 90°.

arrowtype commented 4 years ago

YES. Proper rotation would be huge!

PeterCon commented 4 years ago

Better support for rotation in variation (continuous variation of an angle θ) sounds useful, but it doesn't seem like burying it within the COLR table would be exactly what you want. I mean, it would be possible to have behaviour like a non-colour font—the glyph fill is only ever text foreground colour; but needing to add COLR and CPAL to get that seems kludgy.

What makes rotation as variation difficult now is that the basis function used to calculate variation scalar coefficients is strictly linear (in a single active segment—a "hockey stick" function—or in two segments going from 0 to 1 to 0—a "tooth" function). What if the format were extended to support non-linear basis functions?

Lorp commented 4 years ago

Future-proofing this new format, with capabilities that are called for in ‘glyf’, is reasonable I think. COLRv1 in fact already does this to an extent by enabling proper handling of white-on-black shapes, which is nothing to do with “colour”. As @davelab6 wrote here https://github.com/googlefonts/colr-gradients-spec/issues/3#issuecomment-694888194 : “I don't see an issue with CPAL v2 [sic.] edging ahead, and then updating glyf to match the capability set”.

So I would propose rotation in turns or degrees. I would submit that rotation and reflection are the most common use of the affine matrix, and should have been in ‘glyf’ all along. Reflection is satisfactorily represented in Affine2x2, but for rotation the representation is poor. If rotation was recorded directly in turns or degrees, many of the most common rotations are represented exactly, without semantic loss and therefore with variation potential.

PeterConstable commented 4 years ago

So, perhaps you have in mind something like the following?

PaintTransformed table: Type Field name Description
uint8 format Set to 6.
uint8 flags See below for flag details.
Offset24 paintOffset Offset to a Paint subtable, from start of PaintTransformed table.
Affine2x3 or
VarFixed
transform An Affine2x3 record (inline), or a rotation angle, depending on flags—see below.
Flags: Mask Name Description
0x01 TRANSFORM_AFFINE_2X3 transform is an Affine2x3 record
0x02 TRANSFORM_ROTATION transform is a VarFixed value for a rotation angle, in clockwise radians
0x04 TRANSFORM_X_REFLECTION mirror in x-axis—ignored if an Affine2x3 is used
0x08 TRANSFORM_Y_REFLECTION mirror in y-axis—ignored if an Affine2x3 is used
0xF0 RESERVED reserved flag bits: must not be set

A down-side of this design is that the size of the PaintTransformed table varies according to the flags set. On the other hand, if rotations are, indeed, the most commonly used, there'd be some size savings: 9 bytes vs. 56 bytes.

Lorp commented 4 years ago

A (dx,dy) translation is still needed for reflections and rotations. It may be a good idea to reproduce the option in glyf composites for deciding whether translation occurs before or after transforms, to help glyf-style composites migrate easily to this structure when adding colour.

I would vote against radians, since it leads to irrational numbers for common rotations, and would recommend instead degrees (first choice, because of the excellent divisibility of 360) or turns (second choice).

PeterConstable commented 4 years ago

A revision: this would add a variable-sized record, comparable to the ValueRecord used in GPOS:

PaintTransformed table: Type Field name Description
uint8 format Set to 6.
uint16 flags See below for flag details.
Offset24 paintOffset Offset to a Paint subtable, from start of PaintTransformed table.
Affine2x3 or
TransformParam
transform An Affine2x3 or TransformParam record (inline), depending on flags—see below.

Affine2x3 record, as currently spec'd.

TransformParam record:

Type Field name Description
VarFixed scaleX
VarFixed scaleY
VarFixed rotation rotation angle, in clockwise degrees
VarFixed dx
VarFixed dy

The size of a TransformParam record instance is variable, depending on flags that are set.

Flags: Mask Name Description
0x0001 TRANSFORM_AFFINE_2X3 transform is an Affine2x3 record
0x0002 TRANSFORM_SCALE_X transform is TransformParam record, with scaleX field
0x0004 TRANSFORM_SCALE_Y transform is TransformParam record, with scaleY field
0x0008 TRANSFORM_ROTATION transform is TransformParam record, with rotation field
0x0010 TRANSFORM_TRANSLATE_X transform is TransformParam record, with dx field
0x0020 TRANSFORM_TRANSLATE_Y transform is TransformParam record, with dy field
0x0040 TRANSFORM_X_REFLECTION mirror in x-axis—ignored if an Affine2x3 is used
0x0080 TRANSFORM_Y_REFLECTION mirror in y-axis—ignored if an Affine2x3 is used
0xFF00 RESERVED reserved flag bits: must not be set

Flag bit 0 (0x0001) is mutually exclusive with other bits: if bit 0 is set, other bits are ignored.

arrowtype commented 4 years ago

Thanks for the continued conversation! True, the COLR table doesn’t really make sense as a place to deal with rotated components. Yes, using the glyf table does seem reasonable, as it would seem to sit nicely beside existing components attributes like x, y, and scale (as labeled in ttx – I’m not very familiar with the formal TTF labeling, and feel mostly out of my depth in this conversation).

I do agree that degrees would be (by far) the most direct & widely-understood way to specify this.

drott commented 3 years ago

In a discussion with @jfkthame, Jonathan suggested that we might want to specify skew in degrees as well. This would require something like TRANFORM_SKEW_X_DEGREES, TRANFORM_SKEW_Y_DEGREES.

I would be in favour of reducing the flags to only Affine2x3 and separate ones for those where degrees are needed (TRANSFORM_ROTATE, plus the two above) as the linear transformations for scale, translate reflection could be expressed as nested PaintTransforms if they need to be varied separately and otherwise appear to be duplicating what Affine2x3 does.

Lorp commented 3 years ago

@jfkthame good addition.

@drott sounds good, Affine2x3 respresents reflection, translate and scale without semantic loss.

rsheeter commented 3 years ago

IIUC the key thing we want to provide rotation with the angle as something you can directly vary, as opposed to indirectly by forming a PaintTransformed to rotate?

If so I think we should add a new Paint, not glue this onto PaintTransformed which cleanly represents a 2x3 transform.

anthrotype commented 3 years ago

I also vote for a new Paint format, with flags that select the meaning of the VarFixed field

PeterConstable commented 3 years ago

PaintRotateSkew, with one VarFixed and flags?

jfkthame commented 3 years ago

It needs three values: angle of rotation, and x/y coords of the center of rotation.

(I'd lean toward separate PaintRotated and PaintSkewed; I don't see any benefit to merging them and requiring a flag to indicate which transform is desired.)

anthrotype commented 3 years ago

x/y coords of the center of rotation

good point 👍

separate PaintRotated and PaintSkewed

I'm also ok with two different paints.

rsheeter commented 3 years ago

Pardon my density [kids up all night...] but I'm not clear why skew isn't adequately represented by a PaintTransform? - a static skew at an angle should be fine so I suppose it's specifically to make variation of angle of skew easier?

+1 to PaintRotate {angle, center} and if we need it, to Paint At Angular Skew as it's own paint.

jfkthame commented 3 years ago

Sure, it can be represented by PaintTransform, but so can rotation. The point is that it's hard to control variation of the angles in this form.

rsheeter commented 3 years ago

Got it, ty for confirming. For whatever reason the issue with controlling rotation angle made perfect sense and the issue with skew angle didn't quite connect in my head :)

jfkthame commented 3 years ago

If we're going this way, IMO it'd make sense to also have PaintScaled (four values, for x/y scale factors and origin of scaling), and PaintTranslated (two values), for ease of authoring (and slightly more compact representation, in the case where just a single transform is required). It's fine to still have the general PaintTransformed that can express an arbitrary combination of the transform building blocks, and tools can generate that when it makes sense (e.g. for a complex but static transformation), but it'll often be more author-friendly to work with the individual components.

PeterConstable commented 3 years ago

For rotation, expressing the angle in degrees is clearly more amenable to smooth variation. But what's the particular benefit for expressing skews using angle rather than the matrix?

Lorp commented 3 years ago

@PeterConstable smooth and geometrically correct faux italics along the slnt axis :)

rsheeter commented 3 years ago

For scale and translate my immediate reaction is to be less enthused; it's only a nuisance if you hand-write your transforms. A tool can present them however it wishes and I don't immediately perceive the same issues with representing smooth variation in the underlying format.

So, might PaintRotate and PaintAngularSkew [that's a fun name that might need a visit to a bike shed] suffice?

@Lorp slnt example much appreciated :)

jfkthame commented 3 years ago

Right, scale and translate don't have the issue with variations; they'd purely be a more compact representation of these particular types of the more general transform. So I don't feel strongly about them one way or the other.

PeterConstable commented 3 years ago

new commit for #113

Lorp commented 3 years ago

Why is the rotation specified in clockwise degrees?

For reference:

(BTW, the SVG and CSS specs really should specify rotation direction. @jfkthame, perhaps you can recommend the best way to add this to the specs?)

PeterConstable commented 3 years ago

Arbitrarily chosen for initial draft. (At least it wasn't left unspecified.) Or, actually, I think maybe influenced by HTML Canvas 2D (probably the last thing I looked at that explicitly gave the angular direction).

But anti-clockwise is probably the better chose, particularly for consistency with slnt, but also because it makes better sense when considering the matrix representation (where the xform of the i basis vector is typically expressed as (cos(θ), sin(θ)).

rsheeter commented 3 years ago

+1 to anti-clockwise

jfkthame commented 3 years ago

The CSS spec does indicate that rotate() is specified clockwise, but I agree it's well hidden (and expressed as an example, rather than directly stated in spec text). I'd suggest filing an issue at https://github.com/w3c/csswg-drafts/ asking for it to be made more explicit.

As for SVG, I don't immediately see any mention of clockwise vs anticlockwise, though the spec does include examples that let one infer that it's clockwise. I'm not sure how best to raise that as an issue; maybe mail to www-svg@w3.org?

It's unfortunate that we have this discrepancy between standards that are likely to be used together, but I guess we're stuck with it. Here, I'd agree that anti-clockwise is probably the right choice.

Lorp commented 3 years ago

I suggest we use VarFixed for the rotation’s centerX and centerY, not VarFWord. It was persuasively argued that dx and dy in the transform matrix should be VarFixed. Even a simple 90° rotation needs a resolution of 0.5 font units for centerX and centerY.

PeterConstable commented 3 years ago

@Lorp VarFWord has already been changed to VarFixed (I saw you comment on this earlier).

bungeman commented 3 years ago

and effective limitation to 90°

I wanted to object to this but then realized that I couldn't find a good explanation as to why this wouldn't be the case. I started writing a comment here about how HOI works (or at least one way it can work), but it got a bit long. So I put my response at https://bungeman.github.io/hoi.html . This shows how to do smooth interpolation along arbitrary contours of bezier curves (and discusses a bit about introducing discontinuities and their limitations).

Note that I'm not against variable rigid rotation of components about a variable point, there are many cases where that is exactly what is wanted so it's probably a good idea to support it. Just pointing out that HOI isn't limited in quite this way and it does have the advantage of being able to exactly follow other bezier contours of the glyph.

I can't argue with lack of editor support though... I created my HOI example font by hand in ttx and even then had to hex edit to get everything right.

Lorp commented 3 years ago

Thanks for this, @bungeman.

Indeed I have misrepresented HOI in terms of its rotation abilities. Apologies for that, Underware. And I’ll bet Underware’s own build processes are pretty slick too, so there’s no theoretical objection there either.

Lorp commented 3 years ago

I have some concerns that this proposal, in particular since it implies changes in how future b/w glyph tables handle component transforms, has not had enough eyes from those who are not following the evolution of COLR. I wonder if a Zoom meeting should be proposed, advertised on the MPEG-OTSPEC and OTFontVar lists at least, where the proposals can be discussed.

behdad commented 3 years ago

I'm extremely uncomfortable with the PaintSkew addition. Use of degrees to represent skew has always been a mistake. It was wrongly carried to the slnt axis of varfonts because the italic angle was represented in degrees. That was an unnecessary mistake. Now we have a situation that any font that has slnt axis needs to provide a avar table to approximate the non-linear behavior of skew-expressed-as-angle.

Now I really want to hear ONE legitimate use of the proposed PaintSkew. It cannot meaningfully be used slnt axis, for two reasons:

I'm also very skeptical for many other reasons. I think this was done without fully thinking through the uses. I think every time we deviate from the OT1.8 variation model, it should be for very good reason and well-considered. I don't think that's the case here.

That said, the way this proposal has been modified since its inception also shows a lack of clear criteria. For example, minimizing the number of paint types seems to be given higher priority than to reduce bytes. Which sounds wrong to me. Given that "translate" transformations are the most common use of PaintTransform, I suggest PaintTranslate be added in.

Finally, I highly recommend that it be spec'ed that a font with COLR table but no CPAL table is a valid non-color font; basically sanctioning COLR table as glyf2 for component use.

PeterConstable commented 3 years ago

I don't think we should conflate rotations or other transforms in COLR with rotations used for components in glyph outlines. Within the COLR table, the transforms will be processed in the context of general 2D graphics operations, not a TrueType rasterizer. Note, for instance, that the COLR transforms affect how gradient fills are painted, not just how outlines are rasterized.

PeterConstable commented 3 years ago

Finally, I highly recommend that it be spec'ed that a font with COLR table but no CPAL table is a valid non-color font; basically sanctioning COLR table as glyf2 for component use.

Interesting.

Lorp commented 3 years ago

Finally, I highly recommend that it be spec'ed that a font with COLR table but no CPAL table is a valid non-color font; basically sanctioning COLR table as glyf2 for component use.

Approved!

PeterConstable commented 3 years ago

@behdad:

COLR table but no CPAL table

What would it mean to use a PaintComposite with a blending mode in the case of a monochromatic rendering? What should be the effect of a PaintLinearGradient or PaintRadialGradient? What should be the effect of ColorIndex.alpha < 1?

rsheeter commented 3 years ago

@Lorp you may be amused that the reason this is on github is specifically to make it more accessible than it would be as something like a document submitted directly to ISO. Why? - so we can get more input from the community. Apologies if we haven't adequately advertised that we're working on it.

Given that "translate" transformations are the most common use of PaintTransform, I suggest PaintTranslate be added in.

I fear I may have overindexed on the angles are tiresome to vary aspect. Agreed that smaller size in bytes matters! I can send a PR for PaintTranslate.

sanctioning COLR table as glyf2 for component use

If we want to pursue this my immediate thought is that we could have a set of requirements that guarantee the only possible paint output is the foreground color. For example, absent CPAL:

JeremieHornus commented 3 years ago

We proposed a similar mechanism for components in the variable-components specifications we have been working on: https://github.com/BlackFoundryCom/variable-components-spec#transformation.

As @Lorp pointed out, there should be a unique way of achieving these transformations for components, we don't need 2 ways for doing the same thing.

How would you suggest we gather efforts on this matter? Our proposal concerns only components in OT so, maybe it's a more appropriate place where this particular rotation feature should go?

justvanrossum commented 3 years ago

Finally, I highly recommend that it be spec'ed that a font with COLR table but no CPAL table is a valid non-color font; basically sanctioning COLR table as glyf2 for component use.

Interesting indeed.

I wonder, is "use COLR" vs "use non-COLR fallback" ever meant to be a possible explicit end-user option? (Or even an implicit option based on the output medium.)

If it is, then how does that affect "sanctioning COLR [...] for component use"? As soon as the component features of COLR are used for non-color use, you can't have that non-color use be used as a non-color fallback for color use (hope this still makes sense).

PeterConstable commented 3 years ago

Of the various paint formats for COLR v1, not all would be relevant for monochrome glyph compositing.

Not relevant:

Partially relevant:

Unclear relevance:

rsheeter commented 3 years ago

@PeterConstable COLR v1 requires the DAG to assemble anything interesting so I think you want PaintColrLayers and PaintColrGlyph still. +1 that you likely don't want blend modes.

@JeremieHornus I took the liberty of editing your comment to enable the link, it was taking me to https://github.com/googlefonts/colr-gradients-spec/issues/url.

As soon as the component features of COLR are used for non-color use, you can't have that non-color use be used as a non-color fallback for color use

I think that's right. The glyph in the outline table (glyf or cff) that corresponds to the base glyph can contain a fallback but you couldn't use COLR capabilities to define it. It's not immediately obvious to me this presents a major problem, @justvanrossum do you forsee it does?

PeterConstable commented 3 years ago

As soon as the component features of COLR are used for non-color use, you can't have that non-color use be used as a non-color fallback for color use

I'm inclined to think that use of COLR table itself for non-colour use would not be such a good idea: if you wanted a color font that also uses the COLR tables for non-colour composites, you'd be combining data for distinct purposes into the same table. That would make things harder for tooling, but would also introduce new validation issues—e.g., one more vector (via PaintGlyph) for introducing cycles into the graph.

But having some of the COLR paint formats be used in common with another table wouldn't be a problem.

justvanrossum commented 3 years ago

I'm inclined to think that use of COLR table itself for non-colour use would not be such a good idea [ ... ]

Only if it is a requirement that one can have color and monochrome versions of glyphs. That is pretty much my question. If the answer to that is yes, then "sanctioning COLRv1 [without CPAL] [...] for component use" imposes a restriction.

@rsheeter: to use COLRv1 only to do more fancy compositing than glyf is capable of, to me implies it makes little to no sense to have a fallback in glyf or CFF.

rsheeter commented 3 years ago

if you wanted a color font that also uses the COLR tables for non-colour composites

I read the suggestion to be to allow an exclusive choice between being a color font and and a non-color font based on presence of CPAL, not a hybrid.

to use COLRv1 only to do more fancy compositing than glyf is capable of, to me implies it makes little to no sense to have a fallback in glyf or CFF.

Fair point. Thinking "aloud" I think I would expect that if we developed a glyf2 it wouldn't have a fallback, it just wouldn't work if the client doesn't support it so ... perhaps that's OK?

justvanrossum commented 3 years ago

Btw. my angle is from our Variable Components proposal, where we add more interpolation-friendly transformations (not unlike COLRv1) as well as the ability to specify the component's variation space location (not unlike Glyphs.app's Smart Components).

As @Lorp pointed out, at least the transformation part overlaps somewhat with COLRv1. But perhaps more importantly, the entire idea of "doing components better" has a lot in common, even if the approaches are quite different. I'm now thinking, what if COLRv1 would allow setting the component's variation space location? It could swallow our proposal whole...

rsheeter commented 3 years ago

It could swallow our proposal whole...

Would that be a desirable positive outcome from your pov?

JeremieHornus commented 3 years ago

I personally think it would be positive. The most important it that the fonctionality exists and is available.

vlevantovsky commented 3 years ago

I'm inclined to think that use of COLR table itself for non-colour use would not be such a good idea: if you wanted a color font that also uses the COLR tables for non-colour composites, you'd be combining data for distinct purposes into the same table.

The distinction between what will end up be a color glyph vs. what is a non-color composite is only relevant to an end user, based on the rendering results. The rendering engine itself will simply process the data encoded in COLR table, and the COLR table can be used as is to encode both color glyphs and non-color composites. If a font developer wants to do this (e.g. to have color glyphs and proper white on black glyphs) - there is nothing in the COLR table today that can stop this from happening.

Only if it is a requirement that one can have color and monochrome versions of glyphs.

Isn't it true for any font that combines regular glyphs with color emoji? One can have regular monochrome glyphs, white on black glyphs, and color emoji encoded in the same font, and both color and white-on-black glyphs can use COLR table to encode color glyphs and solve the contour overlap issues.

I read the suggestion to be to allow an exclusive choice between being a color font and and a non-color font based on presence of CPAL, not a hybrid.

My interpretation was different - to simply allow fonts with COLR table, but without CPAL, be considered as valid fonts. Obviously, this would only work for non-color composites and we'd need to define what the implementation should do if it encounters color glyph descriptions that cannot be rendered without CPAL, but as far as hybrid designs are concerned, it's really up to a designer - I don't see how we can prevent this.

justvanrossum commented 3 years ago

Would that be a desirable positive outcome from your pov?

It could be positive, as it may be easier to get traction for one OT addition than for two. COLRv1 seems well on its way to be accepted, so if we can add "one more thing", that could be a route to get our desired functionality in, with possibly less friction than a completely separate OT addition.

On the other hand, it depends on the willingness of rasterizers to implement/activate COLRv1 even in a monochromatic environment. In other words, can it really catch on as "glyf2 for component use"?

We should probably first clarify whether there is an intention at all among the stakeholders that COLRv1 could legitimately used as "glyf2 for component use"?