Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve color tuple handling #363

Merged
merged 6 commits into from
May 22, 2018
Merged

Improve color tuple handling #363

merged 6 commits into from
May 22, 2018

Conversation

madig
Copy link
Collaborator

@madig madig commented May 17, 2018

Glyphs saves a glyphs' color tuple as a List of type [u8, u8, u8, 1]. The alpha
channel is not supported as of Glyphs 2.5.1 (confirmed by Georg Seifert by
mail) and is always set to 1. The UFO specification uses floats to store color
values, using 3 decimal points is enough to round-trip u8 to float and back.

@madig
Copy link
Collaborator Author

madig commented May 17, 2018

Something tells me this could be simplified.

@madig
Copy link
Collaborator Author

madig commented May 18, 2018

For more pragmatism, the alpha value when going to UFOs could be written as 0.35 or thereabouts, as Glyphs actually desaturates the colours so they don't overpower the rest of the UI. Then again, maybe not.

@schriftgestalt
Copy link
Collaborator

There are different alpha values in different places. (the background of the cells in font view are drawn with alpha = 0.3).

@anthrotype
Copy link
Member

anthrotype commented May 19, 2018

@schriftgestalt even if the alpha channel is not supported in Glyphs.app UI, why do you have to forcibly set it to 1 -- which btw seems weird, given that the other three fields' format is unsigned char 0..255, i would expect the forth alpha channel be set to 255 or 0.

The problem is UFO allows all the four RGBA channels to be set, and if Glyphs is going to arbitrarily change the alpha value when saving a .glyphs this creates a problem, as it makes it impossible to roundtrip between the two formats.

You are free to discard that value when rendering the color if you prefer like that, but you should at least keep the data unchanged so one can store the UFO color in the glyphs file without losing data (besides the float rounding, but that's acceptable).

@schriftgestalt
Copy link
Collaborator

I fixed that. It will now use the same 0–255 based storage for the alpha. It will round trip the "1" for now to not change the files.

@anthrotype
Copy link
Member

thanks for that, Georg 👍

@madig
Copy link
Collaborator Author

madig commented May 19, 2018

@schriftgestalt we decided to treat the alpha channel as an uint8. Please change Glyphs so that it reads and writes the alpha channel, you can still ignore it in the UI.

@schriftgestalt
Copy link
Collaborator

Please change Glyphs so that it reads and writes the alpha channel, you can still ignore it in the UI.

This is what I did.

Copy link
Member

@anthrotype anthrotype left a comment

Choose a reason for hiding this comment

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

I pressed approve too fast

elif isinstance(color_index, int) and color_index in range(len(GLYPHS_COLORS)):
color_tuple = GLYPHS_COLORS[color_index]
color_values_clamped = tuple(
max(min(abs(int(v)), 255), 0) for v in color_index[:4])
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand why you have to clamp here. Raising ValueError when the color integer is not in range 0..255 was the correct thing to do here. The argument that Glyphs.app doesn't crash if a user attempts to set a color integer which is not in range to me does not apply here. In the case of glyphsLib, receiving invalid data must be shouted out loud, not silently fixed as you do here.

why do you take the first four items with the slice color_index[:4]?. Should you not raise if the color tuple does not contain 4 items, and is that even possible? I believe it always contains 4 items in the glyphs source file, and if it doesn't, well it's an error.

# Otherwise, make a Glyphs-formatted color list
return [round(component * 255) for component in list(color)]
# Otherwise, make a Glyphs-formatted color list: [u8, u8, u8, 1].
return [round(component * 255) for component in tuple(color)[:4]]
Copy link
Member

Choose a reason for hiding this comment

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

again, the UFO comma-separated color string always contains 4 items. if it doesn't it's invalid. I think defcon or ufoLib would choke on such an incomplete color string, or one that contains more than 4 items.
Do an assert if you want to be cautious, but remove that slice.


Color is either a fixed color (when coloring something from the UI, see
the GLYPHS_COLORS constant) or a list of the format [u8, u8, u8, 1],
negative numbers are abs()'d. Glyphs does not support an alpha channel
Copy link
Member

Choose a reason for hiding this comment

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

negative numbers are abs()'d

negative numbers are never written to file! this function is supposed to parse a color spec from a string found in a .glyphs file, no? So why should it handle this degenerate case?

rgb = src[1:-1].split(",")
color_template = [0, 0, 0, 255]
for i, v in enumerate(rgb):
if v: # Could be ""
Copy link
Member

Choose a reason for hiding this comment

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

are you sure that one of the items in the color tuple in a .glyphs source file could be ""?
I could not verify this. When I set a glyphs' colorObject from the Macro Panel and save, I always get a tuple of four integers in range 0..255 (with exception of the last which is always 1, but Georg will fix that).

color_template[i] = max(min(abs(int(v)), 255), 0)
return tuple(color_template)

rgba = tuple(int(v) for v in src[1:-1].split(",") if v)
Copy link
Member

Choose a reason for hiding this comment

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

I think you should raise ValueError here if the items are not 4

"(-1)": (1, 0, 0, 255)
"(100, 200)": (100, 200),
"()": (),
"(-1)": (-1,)
Copy link
Member

Choose a reason for hiding this comment

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

and these last three tests don't make sense to me, incomplete or out of range color tuples are not "colors" any more

madig and others added 5 commits May 21, 2018 14:01
Glyphs saves a glyphs' color tuple as a List of type [u8, u8, u8, 1]. The alpha
channel is not supported as of Glyphs 2.5.1 (confirmed by Georg Seifert by
mail) and is always set to 1. The UFO specification uses floats to store color
values, using 3 decimal points is enough to round-trip u8 to float and back.
Copy link
Member

@anthrotype anthrotype left a comment

Choose a reason for hiding this comment

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

lgtm

@madig
Copy link
Collaborator Author

madig commented May 22, 2018

Hah. I just did some Glyphs test and it turns out... Glyphs throws the tuple away or mishandles it unless the tuple is on a single line! So, currently:

color = (
0,
100,
50,
1
);

Will be read and written as (0, 0, 0, 1). This works as expected:

color = (0, 100, 50, 1);

This misinterprets the color:

color = (0, 
100, 
50, 1
);

@anthrotype
Copy link
Member

anthrotype commented May 22, 2018

so what? both us and Glyphs.app write the color tuple on a single line, we don't need to handle all possible edge cases!

@anthrotype
Copy link
Member

argh... I meant "we don't need to handle all possible edge cases"

@madig
Copy link
Collaborator Author

madig commented May 22, 2018

both us and Glyphs.app write the color tuple on a single line

Nope. We apparently do

color = (
0,
100,
50,
1
);

That's how I even found it ;)

@anthrotype
Copy link
Member

ouch.. 🤕

@anthrotype
Copy link
Member

anthrotype commented May 22, 2018

ok then this is definitely a bug in Glyphs.app own parser, because the whitespace in old-style OpenStep property lists is irrelevant, and a valid array can be written with both notations.

@schriftgestalt can you please take a look? thank you

@madig
Copy link
Collaborator Author

madig commented May 22, 2018

Still looking into turning this into a one-liner because we settled on using 2.4.x internally for now.

@anthrotype
Copy link
Member

@madig you can probably work around this and force our own glyphsLib Writer to serialize color tuples in one line, see Writer.writeValue method; it takes a forKey argument; you could check if key is "color" and do whatever hacks is necessary..

@anthrotype
Copy link
Member

As as side note, I'd curious to know if there are other places in the .glyphs file format where it diverges (whether deliberately or unknowingly) from the OpenStep property list format.
I mean, can one expect to be able to use the Apple Foundation API to parse a .glyphs file as an OpenStep property list, and do you intend to keep it that way?

@schriftgestalt
Copy link
Collaborator

Currently this is the only exception in terms of line breaks. Unicode not having quotes is another. There will be some more in the next version (all the "{X, Y}" will be (X,Y)). But I will document that when it is finalised.

I have a look at my parser to be able to read colors from multiple lines.

@anthrotype
Copy link
Member

It is crucial that all the divergences from the standard OpenStep property list are documented. The latter has many shortcomings but at least it is clear and simple to parse, and there are publicly available implementations, like Apple's CoreFoundation or the one from GNUStep.

@anthrotype
Copy link
Member

of course, I'm not talking about the way the data is structured, which you're free to change as you please, but about the format's grammar, i.e. valid tokens and syntactical rules, etc.

Otherwise, Glyphs 2.4.x won't read it correctly.
@madig
Copy link
Collaborator Author

madig commented May 22, 2018

Okay, stuff now seems to pass through Glyphs intact. @anthrotype is the last commit okay? Can I merge then?

Copy link
Member

@anthrotype anthrotype left a comment

Choose a reason for hiding this comment

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

lgtm, thanks for your patience Nikolaus :)

@madig madig merged commit 60026c8 into googlefonts:master May 22, 2018
@madig madig deleted the fix-color-parsing-and-roundtripping branch May 23, 2018 09:05
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.

3 participants