colr-gradients-spec icon indicating copy to clipboard operation
colr-gradients-spec copied to clipboard

COLR for non-color use cases

Open Lorp opened this issue 5 years ago • 52 comments

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°.

Lorp avatar Oct 31 '20 12:10 Lorp

YES. Proper rotation would be huge!

arrowtype avatar Nov 01 '20 01:11 arrowtype

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?

PeterCon avatar Nov 04 '20 01:11 PeterCon

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.

Lorp avatar Nov 06 '20 23:11 Lorp

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.

PeterConstable avatar Nov 07 '20 00:11 PeterConstable

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).

Lorp avatar Nov 07 '20 01:11 Lorp

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.

PeterConstable avatar Nov 07 '20 02:11 PeterConstable

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.

arrowtype avatar Nov 07 '20 02:11 arrowtype

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.

drott avatar Nov 13 '20 12:11 drott

@jfkthame good addition.

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

Lorp avatar Nov 13 '20 14:11 Lorp

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.

rsheeter avatar Nov 13 '20 17:11 rsheeter

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

anthrotype avatar Nov 13 '20 17:11 anthrotype

PaintRotateSkew, with one VarFixed and flags?

PeterConstable avatar Nov 13 '20 17:11 PeterConstable

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.)

jfkthame avatar Nov 13 '20 17:11 jfkthame

x/y coords of the center of rotation

good point 👍

separate PaintRotated and PaintSkewed

I'm also ok with two different paints.

anthrotype avatar Nov 13 '20 17:11 anthrotype

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.

rsheeter avatar Nov 13 '20 17:11 rsheeter

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.

jfkthame avatar Nov 13 '20 17:11 jfkthame

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 :)

rsheeter avatar Nov 13 '20 17:11 rsheeter

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.

jfkthame avatar Nov 13 '20 17:11 jfkthame

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?

PeterConstable avatar Nov 13 '20 17:11 PeterConstable

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

Lorp avatar Nov 13 '20 17:11 Lorp

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 :)

rsheeter avatar Nov 13 '20 18:11 rsheeter

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.

jfkthame avatar Nov 13 '20 18:11 jfkthame

new commit for #113

PeterConstable avatar Nov 13 '20 18:11 PeterConstable

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?)

Lorp avatar Nov 13 '20 18:11 Lorp

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(θ)).

PeterConstable avatar Nov 13 '20 18:11 PeterConstable

+1 to anti-clockwise

rsheeter avatar Nov 13 '20 18:11 rsheeter

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 [email protected]?

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.

jfkthame avatar Nov 13 '20 18:11 jfkthame

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.

Lorp avatar Nov 13 '20 19:11 Lorp

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

PeterConstable avatar Nov 13 '20 19:11 PeterConstable

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.

bungeman avatar Nov 16 '20 16:11 bungeman