mirror of
https://github.com/moparisthebest/xeps
synced 2024-12-17 21:32:35 -05:00
330 lines
22 KiB
XML
330 lines
22 KiB
XML
<?xml version='1.0' encoding='UTF-8'?>
|
|
<!DOCTYPE xep SYSTEM 'xep.dtd' [
|
|
<!ENTITY % ents SYSTEM 'xep.ent'>
|
|
%ents;
|
|
<!ENTITY rgblind "<em>Red/Green-Blindness</em>">
|
|
<!ENTITY bblind "<em>Blue-Blindness</em>">
|
|
<!ENTITY cvd "Color Vision Deficiency">
|
|
<!ENTITY cvds "Color Vision Deficiencies">
|
|
]>
|
|
<?xml-stylesheet type='text/xsl' href='xep.xsl'?>
|
|
<xep>
|
|
<header>
|
|
<title>Consistent Color Generation</title>
|
|
<abstract>This specification provides a set of algorithms to consistently generate colors given a string. The string can be a nickname, a JID or any other piece of information. All entities adhering to this specification generate the same color for the same string, which provides a consistent user experience across platforms.</abstract>
|
|
&LEGALNOTICE;
|
|
<number>0392</number>
|
|
<status>Deferred</status>
|
|
<type>Standards Track</type>
|
|
<sig>Standards</sig>
|
|
<approver>Council</approver>
|
|
<dependencies></dependencies>
|
|
<supersedes/>
|
|
<supersededby/>
|
|
<shortname>colors</shortname>
|
|
&jonaswielicki;
|
|
<revision>
|
|
<version>0.7.0</version>
|
|
<date>2019-10-16</date>
|
|
<initials>jsc</initials>
|
|
<remark>
|
|
<p>Remove anything except the Hue generation from the specification. The
|
|
other parts were user interface suggestions which are out of scope for
|
|
the XSF standards process.</p>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.6.0</version>
|
|
<date>2018-10-02</date>
|
|
<initials>jsc</initials>
|
|
<remark>
|
|
<p>Fix CVD rules: they were incorrect in the last update.</p>
|
|
<p>Update test vectors after CVD fix.</p>
|
|
<p>Add note about floating-point modulo operator implementation interoperability.</p>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.5</version>
|
|
<date>2018-10-01</date>
|
|
<initials>jsc</initials>
|
|
<remark>
|
|
<p>Switch from custom YCbCr-based algorithm to HSLuv.</p>
|
|
<p>Prioritize bare JIDs over nicknames.</p>
|
|
<p>Write down normalization rules.</p>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.4.1</version>
|
|
<date>2018-07-28</date>
|
|
<initials>egp</initials>
|
|
<remark>
|
|
<p>Fix a typo. (thanks zinid)</p>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.4</version>
|
|
<date>2017-11-29</date>
|
|
<initials>jwi</initials>
|
|
<remark>
|
|
<p>Use different formulas for Color Vision Deficiency correction, as suggested by Marcus Waldvogel.</p>
|
|
<p>Update test vectors.</p>
|
|
<p>Clarify generation of the angle.</p>
|
|
<p>Prioritize nicknames over bare JIDs.</p>
|
|
<p>Add rationale for new palette mapping algorithm introduced in 0.3.</p>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.3</version>
|
|
<date>2017-11-13</date>
|
|
<initials>jwi</initials>
|
|
<remark><p>
|
|
Fix wording in angle generation section which did still use CRC32.
|
|
Rework palette mapping after with implementation experience.
|
|
</p></remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.2</version>
|
|
<date>2017-10-04</date>
|
|
<initials>jwi</initials>
|
|
<remark><p>
|
|
Move to SHA-1 as mixing function;
|
|
Properly reference BT.601 and include constants in text;
|
|
Prefer bare JID over roster name when selecting the hash function input;
|
|
Editing.
|
|
</p></remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.1</version>
|
|
<date>2017-09-27</date>
|
|
<initials>XEP Editor: jwi</initials>
|
|
<remark><p>Accepted as Experimental by Council.</p></remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.0.1</version>
|
|
<date>2017-09-14</date>
|
|
<initials>jwi</initials>
|
|
<remark><p>First draft.</p></remark>
|
|
</revision>
|
|
</header>
|
|
<section1 topic='Introduction' anchor='intro'>
|
|
<p>Colors provide a valuable visual cue to recognize shapes. Recognition of colors works much faster than recognition of text. Together with the length and overall shape of a piece of text (such as a nickname), a color provides a decent amount of entropy to distinguish a reasonable amount of entities, without having to actually read the text.</p>
|
|
<p>Clients have been using randomly or deterministically chosen colors for users in multi-user situations for a long time already. However, since there has been no standard for how this is implemented, the experience differs across platforms. The goal of this XEP is to provide a uniform, platform-independent, stateless and easy-to-implement way to map arbitrary bytestrings to colors.</p>
|
|
<p>To allow cross-client use, it is important that the color scheme can be adapted to different environments. This specification provides means to adapt colors to different background colors as well as &cvds;.</p>
|
|
<p>In no way is the system presented in this specification a replacement for names. It only serves as an additional visual aid.</p>
|
|
</section1>
|
|
<section1 topic='Requirements' anchor='reqs'>
|
|
<p>The color generation mechanism should provide the following features:</p>
|
|
<ul>
|
|
<li>Consistent generation of color across all platforms depending solely on the identifier used as input for the algorithm.</li>
|
|
<li>The system should be reasonably fast; it must be possible to, for example, apply it to all roster entries even of very large rosters in reasonable amount of time.</li>
|
|
<li>It must be able to provide decent contrast on any background.</li>
|
|
<li>The implementation should be stateless and not be complex.</li>
|
|
<li>A fallback path for users with common types of &cvds; must be provided.</li>
|
|
<li>A fallback path for systems which can only use colors from a restricted palette must be provided.</li>
|
|
</ul>
|
|
</section1>
|
|
<section1 topic='Use Cases' anchor='usecases'>
|
|
<section2 topic='Generating a color' anchor='usecase-textcolor'>
|
|
<p>To generate a color from a string of text, the following algorithms are applied in order:</p>
|
|
<ol>
|
|
<li><link url='#algorithm-angle'>Generate a Hue value from the text</link>.</li>
|
|
<li>If enabled, <link url='#algorithm-cvd'>apply configured corrections for &cvds;</link>.</li>
|
|
<li>If constraints mandate the use of only a small palette of colors, <link url='#algorithm-mappalette'>map the angle to the closest palette color</link>. (Such situations could for example be a UI environment with guidelines to only use a specific set of colors or an output device which only supports a limited amount of colors.)</li>
|
|
<li>If the output device supports RGB output, <link url='#algorithm-rgb'>Convert the angle to a RGB</link>.</li>
|
|
</ol>
|
|
</section2>
|
|
</section1>
|
|
<section1 topic='Business Rules' anchor='rules'>
|
|
<ul>
|
|
<li>Implementations SHOULD allow the user to turn off any colorization completely.</li>
|
|
<li>Implementations SHOULD implement the &cvd; profiles and SHOULD allow the user to choose any of these profiles or to disable the correction.</li>
|
|
<li>Implementations MUST NOT share the &cvd; correction settings with other entities.</li>
|
|
</ul>
|
|
</section1>
|
|
<section1 topic='Algorithms' anchor='algorithm'>
|
|
<p>The algorithms in this document use the &hsluv;⎄ color space. It provides consistent brightness (for a given luminosity) across its entire definition space. There is also widespread library support.</p>
|
|
<section2 topic='Angle generation' anchor='algorithm-angle'>
|
|
<p>Input: An identifier, encoded as octets of UTF-8 (&rfc3269;).</p>
|
|
<p>Output: Hue angle.</p>
|
|
<p>Note: The goal of this algorithm is to convert arbitrary text into a scalar value which can then be used to calculate a color.</p>
|
|
<ol>
|
|
<li>Run the input through SHA-1 (&rfc3174;).</li>
|
|
<li>Treat the output as little endian and extract the least-significant 16 bits. (These are the first two bytes of the output, with the second byte being the most significant one.)</li>
|
|
<li>Divide the value by 65536 (use float division) and multiply it by 360 (to map it to degrees in a full circle).</li>
|
|
</ol>
|
|
</section2>
|
|
<section2 topic='Corrections for &cvds;' anchor='algorithm-cvd'>
|
|
<p>Input: Hue angle.</p>
|
|
<p>Output: Hue angle.</p>
|
|
<p>Note: This algorithm will re-map the angle to map it away from ranges which can not be distinguished by people with the respective &cvds;.</p>
|
|
<p>Note: Some floating-point modulo implementations will return negative outputs for negative inputs. This algorithm assumes that your implementation returns <em>non-negative</em> outputs for all inputs.</p>
|
|
<section3 topic='Red/Green-blindness' anchor='algorithm-cvd-rg'>
|
|
<p>Add 90 to the angle, take it modulo 180 and subtract 90. Take the result modulo 360 to ensure that it's in the range from 0 to 360.</p>
|
|
<p>Note: the same effect can be achieved by forcing the two most-significant bits of the angle to be equal to the second-most-significant bit before converting to a float in <link url="#algorithm-angle">Angle generation</link>. This avoids having to perform a floating-point modulo operation.</p>
|
|
</section3>
|
|
<section3 topic='Blue-blindness' anchor='algorithm-cvd-b'>
|
|
<p>Take the angle modulo 180.</p>
|
|
<p>Note: the same effect can be achieved by setting the most-significant bit to zero before conversion to floating point in <link url="#algorithm-angle">Angle generation</link>. This avoids having to perform a floating-point modulo operation.</p>
|
|
</section3>
|
|
</section2>
|
|
<section2 topic='RGB generation' anchor='algorithm-rgb'>
|
|
<p>Use the HSLuv operation <tt>hsluvToRgb</tt> to convert the Hue angle to a color. The saturation and lightness are to be defined by the implementation (see also the <link url="#accessibility-cr">Contrast Ratio considerations</link>).</p>
|
|
</section2>
|
|
<section2 topic='Conversion of an RGB color palette to a Hue palette' anchor='algorithm-genpalette'>
|
|
<p>Input: A set of RGB colors (each component from 0 to 1).</p>
|
|
<p>Output: A mapping from angles (integer, from 0 to 360) to RGB colors.</p>
|
|
<p>Note: when the algorithm finishes, the mapping maps angles (rounded to two decimal places) to the R, G, B triples which come closest to the desired color and lightness.</p>
|
|
<ol>
|
|
<li>Create an empty mapping M which maps from Hue angles to quadruples of L, R, G and B.</li>
|
|
<li>For each color R, G, B from the input palette:
|
|
<ol>
|
|
<li>If the R, G and B values are equal, skip the color and continue with the next. (Grayscale does not work well, since its saturation and hue are undefined.)</li>
|
|
<li>Calculate H, S and L from R, G, B using HSLuv.</li>
|
|
<li>Round the angle to the next integer value.</li>
|
|
<li>If the angle is not in the mapping M yet, or if the L value of the existing entry is farther away from 73.2 than the new L value, put the L, R, G, and B values as value for the angle into the mapping.</li>
|
|
</ol>
|
|
</li>
|
|
<li>Strip the L values from the values of mapping M.</li>
|
|
<li>Return M as the result of the algorithm.</li>
|
|
</ol>
|
|
<p>Implementations are free to choose a representation for palette colors different from R, G, B triplets. The exact representation does not matter, as long as it can be converted to a Hue angle accordingly.</p>
|
|
</section2>
|
|
<section2 topic='Mapping of a Hue angle to closest palette color' anchor='algorithm-mappalette'>
|
|
<p>Input: (a) A mapping which maps angles to R, G, B triplets and (b) a color to map to the closest palette color as angle alpha.</p>
|
|
<p>Output: A palette color as R, G, B triplet.</p>
|
|
<p>Note: See <link url='#algorithm-genpalette'>Conversion of an RGB color palette to a Hue palette</link> on how to convert an R, G, B triplet to an angle.</p>
|
|
<ol>
|
|
<li>First, check if alpha rounded to an integer. If so, return that match immediately.</li>
|
|
<li>For each angle beta in the palette, calculate the distance metric: <code>D = min((alpha - beta) % 360, (beta - alpha) % 360)</code>.</li>
|
|
<li>Return the R, G, B triplet associated with the angle with the smallest distance metric D.</li>
|
|
</ol>
|
|
<p>Implementations are free to choose a representation for palette colors different from R, G, B triplets. The exact representation does not matter, as long as it can be converted to a Hue angle accordingly.</p>
|
|
</section2>
|
|
</section1>
|
|
<section1 topic='Implementation Notes' anchor='impl'>
|
|
<section2 topic='Gamma Correction' anchor='impl-gamma'>
|
|
<p>Implementations should be aware of Gamma correction and apply it as needed.</p>
|
|
</section2>
|
|
<section2 topic='Normalization' anchor='impl-norm'>
|
|
<p>When processing JIDs as text input, implementations MUST prepare the JID as it would for comparing it to another JID with a case-sensitive comparison function.</p>
|
|
</section2>
|
|
</section1>
|
|
<section1 topic='Accessibility Considerations' anchor='access'>
|
|
<section2 topic='Color Vision Deficiencies' anchor='access-cvds'>
|
|
<p>As outlined above, implementations SHOULD offer the &rgblind; and &bblind; corrections as defined in the <link url='#algorithm-cvd'>Corrections for &cvds;</link> section. Users SHOULD be allowed to choose between:</p>
|
|
<ul>
|
|
<li>disabling all corrections (skip the Corrections for &cvds; step entirely),</li>
|
|
<li>applying one of the &cvd; correction profiles and</li>
|
|
<li>disabling colorization altogether.</li>
|
|
</ul>
|
|
<p>The last option is useful for users with monochromatic view or who find colors distracting.</p>
|
|
<p>Some sources on the internet indicate that people with &cvds; may profit from having larger areas of color to be able to recognize them. This should be taken into consideration when selecting font weights and line widths for colored parts.</p>
|
|
</section2>
|
|
<section2 topic='Contrast Ratio' anchor='access-cr'>
|
|
<p>Implementations should adapt the lightness value according to the background on which the color is rendered.</p>
|
|
</section2>
|
|
</section1>
|
|
<section1 topic='Security Considerations' anchor='security'>
|
|
<p>This specification extracts a bit more information from an entity and shows it alongside the existing information to the user. As the algorithm is likely to produce different colors for look-alikes (see &xep0165; for examples) in JIDs, it may add additional protection against attacks based on those.</p>
|
|
<p>Due to the limited set of distinguishable colors and only extracting 16 bits of the hash function output, possible &cvds; and/or use of palettes, entities MUST NOT rely on colors being unique in any context.</p>
|
|
</section1>
|
|
<section1 topic='Design Considerations' anchor='design'>
|
|
<p>This section provides an overview of design considerations made while writing this specification. It shows alternatives which have been considered, and eventually rejected.</p>
|
|
<section2 topic='The YCbCr color space' anchor='design-ycbcr'>
|
|
<p>The versions up to 0.5 of this document used a variant of the YCbCr color space (namely &BT.601;) along with a custom algorithm to convert from angles to CbCr and from there to RGB. The HSLuv color space provides extremely consistent apparent brightness of the colors which cannot be achieved with simple application of YCbCr. In addition, HSLuv has widespread library support.</p>
|
|
</section2>
|
|
<section2 topic='Hue-Saturation-Value/Lightness color space' anchor='design-hsv'>
|
|
<p>The HSV and HSL color spaces fail to provide uniform luminosity with fixed value/lightness and saturation parameters. Adapting those parameters for uniform luminosity across the hue range would have complicated the algorithm with litte to no gain.</p>
|
|
</section2>
|
|
<section2 topic='Palette-based and context-aware coloring' anchor='design-context'>
|
|
<p>Given a fixed-size and finite palette of colors, it would be possible to ensure that, until the number of entities to color exceeds the number of colors, no color collisions happen.</p>
|
|
<p>There are issues with this approach when the set of entities is dynamic. In such cases, it is possible that an entity changes its associated color (for example by re-joining a colored group chat), which defeats the original purpose.</p>
|
|
<p>In addition, more state needs to be taken into account, increasing the complexity of choosing a color.</p>
|
|
</section2>
|
|
<section2 topic='Choice of mixing function in angle generation' anchor='design-mixing'>
|
|
<p>This specification needs to collapse an arbitrarily long string into just a few bits (the angle in the CbCr plane). To do so, SHA-1 (&rfc3174;) is used.</p>
|
|
<p>CRC32 and Adler32 have been considered as faster alternatives. Downsides of these functions:</p>
|
|
<ul>
|
|
<li>Bad mixing without additional entropy.</li>
|
|
<li>Adler32 is rarely available in standard libraries.</li>
|
|
<li>CRC32 is ambiguous: there are multiple polynomials in widespread use (e.g. the Ethernet and the zlib polynomials). Often it is not clear which polynomial is used by a library.</li>
|
|
</ul>
|
|
<p>SHA-1 is widely available. From a security point of view, the exact choice of hash function does not matter here, since it is truncated to 16 bits. At this length, any cryptographic hash function is weak.</p>
|
|
</section2>
|
|
<section2 topic='Palette-mapping function' anchor='design-palette-mapping'>
|
|
<p>The palette-mapping algorithm operates on angles only and disregards the lightness value except if the angles match. This has the downside that the brightness is not equal over the range of the palette mapped colors.</p>
|
|
<p>The alternative would be to require the lightness to be close to the target lightness. This has several issues:</p>
|
|
<ul>
|
|
<li>We cannot know if a palette can satisfy the given lightness at all.</li>
|
|
<li>Many colors from e.g. the "Web Safe" palette (used in 256 color terminals and the test vectors) will not satisfy any given lightness, reducing the size of the effective palette drastically.</li>
|
|
</ul>
|
|
<p>For the sake of having more colors available, the given algorithm was chosen which prefers many colors with hue conformance over fewer colors with hue and lightness conformance.</p>
|
|
</section2>
|
|
</section1>
|
|
<section1 topic='IANA Considerations' anchor='iana'>
|
|
<p>This document requires no interaction with &IANA;. </p>
|
|
</section1>
|
|
<section1 topic='XMPP Registrar Considerations' anchor='registrar'>
|
|
<p>This document requires no interaction with the ®ISTRAR;. </p>
|
|
</section1>
|
|
<section1 topic='Acknowledgements' anchor='acknowledgements'>
|
|
<p>Thanks to Klaus Herberth, Daniel Gultsch, Georg Lukas, Tobias Markmann, Christian Schudt, and Marcus Waldvogel for their input and feedback on this document.</p>
|
|
</section1>
|
|
<section1 topic='Test Vectors' anchor='vectors'>
|
|
<section2 topic='Test Vectors' anchor='testvectors-fullrange'>
|
|
<p>This section holds test vectors for the different configurations. The test vectors are provided as Comma Separated Values. Strings are enclosed by single quotes ('). The first line contains a header. Each row contains, in that order, the original text, the text encoded as UTF-8 as hexadecimal octets, the angle in degrees, the calculated hue in degrees (differs from angle only for CVD-corrected rows), and the Red, Green, and Blue values.</p>
|
|
<section3 topic='No &cvd; correction' anchor='testvectors-fullrange-no-cvd'>
|
|
<code><![CDATA[text,hextext,angle,hue,r,g,b
|
|
'Romeo','526f6d656f',327.255249,327.255249,0.865,0.000,0.686
|
|
'juliet@capulet.lit','6a756c69657440636170756c65742e6c6974',209.410400,209.410400,0.000,0.515,0.573
|
|
'😺','f09f98ba',331.199341,331.199341,0.872,0.000,0.659
|
|
'council','636f756e63696c',359.994507,359.994507,0.918,0.000,0.394
|
|
'Board','426f617264',171.430664,171.430664,0.000,0.527,0.457]]></code>
|
|
</section3>
|
|
<section3 topic='With Red/Green-blindness correction' anchor='testvectors-fullrange-cvd-redgreen'>
|
|
<code><![CDATA[text,hextext,angle,hue,r,g,b
|
|
'Romeo','526f6d656f',327.255249,327.255249,0.865,0.000,0.686
|
|
'juliet@capulet.lit','6a756c69657440636170756c65742e6c6974',209.410400,29.410400,0.742,0.359,0.000
|
|
'😺','f09f98ba',331.199341,331.199341,0.872,0.000,0.659
|
|
'council','636f756e63696c',359.994507,359.994507,0.918,0.000,0.394
|
|
'Board','426f617264',171.430664,351.430664,0.904,0.000,0.494]]></code>
|
|
</section3>
|
|
<section3 topic='With Blue-blindness correction' anchor='testvectors-fullrange-cvd-blue'>
|
|
<code><![CDATA[text,hextext,angle,hue,r,g,b
|
|
'Romeo','526f6d656f',327.255249,147.255249,0.000,0.535,0.350
|
|
'juliet@capulet.lit','6a756c69657440636170756c65742e6c6974',209.410400,29.410400,0.742,0.359,0.000
|
|
'😺','f09f98ba',331.199341,151.199341,0.000,0.533,0.373
|
|
'council','636f756e63696c',359.994507,179.994507,0.000,0.524,0.485
|
|
'Board','426f617264',171.430664,171.430664,0.000,0.527,0.457]]></code>
|
|
</section3>
|
|
</section2>
|
|
<section2 topic='Test Vectors for mapping to 216 color palette' anchor='testvectors-palette'>
|
|
<p>The used palette can be generated by sampling the RGB cube evenly with six samples on each axis (resulting in 210 colors (grayscales are excluded)). The resulting palette is commonly known as the palette of so-called "Web Safe" colors.</p>
|
|
<section3 topic='No &cvd; correction' anchor='testvectors-palette-no-cvd'>
|
|
<code><![CDATA[text,hextext,hue,best_hue,r,g,b
|
|
'Romeo','526f6d656f',327.255249,327,1.000,0.000,0.800
|
|
'juliet@capulet.lit','6a756c69657440636170756c65742e6c6974',209.410400,226,0.000,0.800,1.000
|
|
'😺','f09f98ba',331.199341,331,1.000,0.400,0.800
|
|
'council','636f756e63696c',359.994507,359,0.800,0.200,0.400
|
|
'Board','426f617264',171.430664,161,0.000,1.000,0.800]]></code>
|
|
</section3>
|
|
<section3 topic='With Red/Green-blindness correction' anchor='testvectors-palette-cvd-redgreen'>
|
|
<code><![CDATA[text,hextext,hue,best_hue,r,g,b
|
|
'Romeo','526f6d656f',327.255249,327,1.000,0.000,0.800
|
|
'juliet@capulet.lit','6a756c69657440636170756c65742e6c6974',29.410400,30,1.000,0.600,0.400
|
|
'😺','f09f98ba',331.199341,331,1.000,0.400,0.800
|
|
'council','636f756e63696c',359.994507,359,0.800,0.200,0.400
|
|
'Board','426f617264',351.430664,353,0.400,0.000,0.200]]></code>
|
|
</section3>
|
|
<section3 topic='With Blue-blindness correction' anchor='testvectors-palette-cvd-blue'>
|
|
<code><![CDATA[text,hextext,hue,best_hue,r,g,b
|
|
'Romeo','526f6d656f',147.255249,147,0.400,0.800,0.600
|
|
'juliet@capulet.lit','6a756c69657440636170756c65742e6c6974',29.410400,30,1.000,0.600,0.400
|
|
'😺','f09f98ba',151.199341,153,0.200,0.800,0.600
|
|
'council','636f756e63696c',179.994507,192,0.000,0.800,0.800
|
|
'Board','426f617264',171.430664,161,0.000,1.000,0.800]]></code>
|
|
</section3>
|
|
</section2>
|
|
</section1>
|
|
</xep>
|