1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/ArrayUtils.h"  // for ArrayLength
8 #include "mozilla/mozalloc.h"    // for operator delete, etc
9 #include "mozilla/MathAlgorithms.h"
10 
11 #include "nsColor.h"
12 #include <sys/types.h>     // for int32_t
13 #include "nsColorNames.h"  // for nsColorNames
14 #include "nsDebug.h"       // for NS_ASSERTION, etc
15 #include "nsStaticNameTable.h"
16 #include "nsString.h"  // for nsAutoCString, nsString, etc
17 #include "nscore.h"    // for nsAString, etc
18 
19 using namespace mozilla;
20 
21 // define an array of all color names
22 #define GFX_COLOR(_name, _value) #_name,
23 static const char* const kColorNames[] = {
24 #include "nsColorNameList.h"
25 };
26 #undef GFX_COLOR
27 
28 // define an array of all color name values
29 #define GFX_COLOR(_name, _value) _value,
30 static const nscolor kColors[] = {
31 #include "nsColorNameList.h"
32 };
33 #undef GFX_COLOR
34 
35 #define eColorName_COUNT (ArrayLength(kColorNames))
36 #define eColorName_UNKNOWN (-1)
37 
38 static nsStaticCaseInsensitiveNameTable* gColorTable = nullptr;
39 
AddRefTable(void)40 void nsColorNames::AddRefTable(void) {
41   NS_ASSERTION(!gColorTable, "pre existing array!");
42   if (!gColorTable) {
43     gColorTable =
44         new nsStaticCaseInsensitiveNameTable(kColorNames, eColorName_COUNT);
45   }
46 }
47 
ReleaseTable(void)48 void nsColorNames::ReleaseTable(void) {
49   if (gColorTable) {
50     delete gColorTable;
51     gColorTable = nullptr;
52   }
53 }
54 
ComponentValue(const char16_t * aColorSpec,int aLen,int color,int dpc)55 static int ComponentValue(const char16_t* aColorSpec, int aLen, int color,
56                           int dpc) {
57   int component = 0;
58   int index = (color * dpc);
59   if (2 < dpc) {
60     dpc = 2;
61   }
62   while (--dpc >= 0) {
63     char16_t ch = ((index < aLen) ? aColorSpec[index++] : '0');
64     if (('0' <= ch) && (ch <= '9')) {
65       component = (component * 16) + (ch - '0');
66     } else if ((('a' <= ch) && (ch <= 'f')) || (('A' <= ch) && (ch <= 'F'))) {
67       // "ch&7" handles lower and uppercase hex alphabetics
68       component = (component * 16) + (ch & 7) + 9;
69     } else {  // not a hex digit, treat it like 0
70       component = (component * 16);
71     }
72   }
73   return component;
74 }
75 
NS_HexToRGBA(const nsAString & aColorSpec,nsHexColorType aType,nscolor * aResult)76 bool NS_HexToRGBA(const nsAString& aColorSpec, nsHexColorType aType,
77                   nscolor* aResult) {
78   const char16_t* buffer = aColorSpec.BeginReading();
79 
80   int nameLen = aColorSpec.Length();
81   bool hasAlpha = false;
82   if (nameLen != 3 && nameLen != 6) {
83     if ((nameLen != 4 && nameLen != 8) || aType == nsHexColorType::NoAlpha) {
84       // Improperly formatted color value
85       return false;
86     }
87     hasAlpha = true;
88   }
89 
90   // Make sure the digits are legal
91   for (int i = 0; i < nameLen; i++) {
92     char16_t ch = buffer[i];
93     if (((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'f')) ||
94         ((ch >= 'A') && (ch <= 'F'))) {
95       // Legal character
96       continue;
97     }
98     // Whoops. Illegal character.
99     return false;
100   }
101 
102   // Convert the ascii to binary
103   int dpc = ((nameLen <= 4) ? 1 : 2);
104   // Translate components from hex to binary
105   int r = ComponentValue(buffer, nameLen, 0, dpc);
106   int g = ComponentValue(buffer, nameLen, 1, dpc);
107   int b = ComponentValue(buffer, nameLen, 2, dpc);
108   int a;
109   if (hasAlpha) {
110     a = ComponentValue(buffer, nameLen, 3, dpc);
111   } else {
112     a = (dpc == 1) ? 0xf : 0xff;
113   }
114   if (dpc == 1) {
115     // Scale single digit component to an 8 bit value. Replicate the
116     // single digit to compute the new value.
117     r = (r << 4) | r;
118     g = (g << 4) | g;
119     b = (b << 4) | b;
120     a = (a << 4) | a;
121   }
122   NS_ASSERTION((r >= 0) && (r <= 255), "bad r");
123   NS_ASSERTION((g >= 0) && (g <= 255), "bad g");
124   NS_ASSERTION((b >= 0) && (b <= 255), "bad b");
125   NS_ASSERTION((a >= 0) && (a <= 255), "bad a");
126   *aResult = NS_RGBA(r, g, b, a);
127   return true;
128 }
129 
130 // This implements part of the algorithm for legacy behavior described in
131 // http://www.whatwg.org/specs/web-apps/current-work/complete/common-microsyntaxes.html#rules-for-parsing-a-legacy-color-value
NS_LooseHexToRGB(const nsString & aColorSpec,nscolor * aResult)132 bool NS_LooseHexToRGB(const nsString& aColorSpec, nscolor* aResult) {
133   if (aColorSpec.EqualsLiteral("transparent")) {
134     return false;
135   }
136 
137   int nameLen = aColorSpec.Length();
138   const char16_t* colorSpec = aColorSpec.get();
139   if (nameLen > 128) {
140     nameLen = 128;
141   }
142 
143   if ('#' == colorSpec[0]) {
144     ++colorSpec;
145     --nameLen;
146   }
147 
148   // digits per component
149   int dpc = (nameLen + 2) / 3;
150   int newdpc = dpc;
151 
152   // Use only the rightmost 8 characters of each component.
153   if (newdpc > 8) {
154     nameLen -= newdpc - 8;
155     colorSpec += newdpc - 8;
156     newdpc = 8;
157   }
158 
159   // And then keep trimming characters at the left until we'd trim one
160   // that would leave a nonzero value, but not past 2 characters per
161   // component.
162   while (newdpc > 2) {
163     bool haveNonzero = false;
164     for (int c = 0; c < 3; ++c) {
165       MOZ_ASSERT(c * dpc < nameLen,
166                  "should not pass end of string while newdpc > 2");
167       char16_t ch = colorSpec[c * dpc];
168       if (('1' <= ch && ch <= '9') || ('A' <= ch && ch <= 'F') ||
169           ('a' <= ch && ch <= 'f')) {
170         haveNonzero = true;
171         break;
172       }
173     }
174     if (haveNonzero) {
175       break;
176     }
177     --newdpc;
178     --nameLen;
179     ++colorSpec;
180   }
181 
182   // Translate components from hex to binary
183   int r = ComponentValue(colorSpec, nameLen, 0, dpc);
184   int g = ComponentValue(colorSpec, nameLen, 1, dpc);
185   int b = ComponentValue(colorSpec, nameLen, 2, dpc);
186   NS_ASSERTION((r >= 0) && (r <= 255), "bad r");
187   NS_ASSERTION((g >= 0) && (g <= 255), "bad g");
188   NS_ASSERTION((b >= 0) && (b <= 255), "bad b");
189 
190   *aResult = NS_RGB(r, g, b);
191   return true;
192 }
193 
NS_ColorNameToRGB(const nsAString & aColorName,nscolor * aResult)194 bool NS_ColorNameToRGB(const nsAString& aColorName, nscolor* aResult) {
195   if (!gColorTable) return false;
196 
197   int32_t id = gColorTable->Lookup(aColorName);
198   if (eColorName_UNKNOWN < id) {
199     NS_ASSERTION(uint32_t(id) < eColorName_COUNT,
200                  "gColorTable->Lookup messed up");
201     if (aResult) {
202       *aResult = kColors[id];
203     }
204     return true;
205   }
206   return false;
207 }
208 
209 // Returns kColorNames, an array of all possible color names, and sets
210 // *aSizeArray to the size of that array. Do NOT call free() on this array.
NS_AllColorNames(size_t * aSizeArray)211 const char* const* NS_AllColorNames(size_t* aSizeArray) {
212   *aSizeArray = ArrayLength(kColorNames);
213   return kColorNames;
214 }
215 
216 // Macro to blend two colors
217 //
218 // equivalent to target = (bg*(255-fgalpha) + fg*fgalpha)/255
219 #define MOZ_BLEND(target, bg, fg, fgalpha) \
220   FAST_DIVIDE_BY_255(target, (bg) * (255 - fgalpha) + (fg) * (fgalpha))
221 
NS_ComposeColors(nscolor aBG,nscolor aFG)222 nscolor NS_ComposeColors(nscolor aBG, nscolor aFG) {
223   // This function uses colors that are non premultiplied alpha.
224   int r, g, b, a;
225 
226   int bgAlpha = NS_GET_A(aBG);
227   int fgAlpha = NS_GET_A(aFG);
228 
229   // Compute the final alpha of the blended color
230   // a = fgAlpha + bgAlpha*(255 - fgAlpha)/255;
231   FAST_DIVIDE_BY_255(a, bgAlpha * (255 - fgAlpha));
232   a = fgAlpha + a;
233   int blendAlpha;
234   if (a == 0) {
235     // In this case the blended color is totally trasparent,
236     // we preserve the color information of the foreground color.
237     blendAlpha = 255;
238   } else {
239     blendAlpha = (fgAlpha * 255) / a;
240   }
241   MOZ_BLEND(r, NS_GET_R(aBG), NS_GET_R(aFG), blendAlpha);
242   MOZ_BLEND(g, NS_GET_G(aBG), NS_GET_G(aFG), blendAlpha);
243   MOZ_BLEND(b, NS_GET_B(aBG), NS_GET_B(aFG), blendAlpha);
244 
245   return NS_RGBA(r, g, b, a);
246 }
247 
248 namespace mozilla {
249 
BlendColorComponent(uint32_t aBg,uint32_t aFg,uint32_t aFgAlpha)250 static uint32_t BlendColorComponent(uint32_t aBg, uint32_t aFg,
251                                     uint32_t aFgAlpha) {
252   return RoundingDivideBy255(aBg * (255 - aFgAlpha) + aFg * aFgAlpha);
253 }
254 
LinearBlendColors(nscolor aBg,nscolor aFg,uint_fast8_t aFgRatio)255 nscolor LinearBlendColors(nscolor aBg, nscolor aFg, uint_fast8_t aFgRatio) {
256   // Common case that either pure background or pure foreground
257   if (aFgRatio == 0) {
258     return aBg;
259   }
260   if (aFgRatio == 255) {
261     return aFg;
262   }
263   // Common case that alpha channel is equal (usually both are opaque)
264   if (NS_GET_A(aBg) == NS_GET_A(aFg)) {
265     auto r = BlendColorComponent(NS_GET_R(aBg), NS_GET_R(aFg), aFgRatio);
266     auto g = BlendColorComponent(NS_GET_G(aBg), NS_GET_G(aFg), aFgRatio);
267     auto b = BlendColorComponent(NS_GET_B(aBg), NS_GET_B(aFg), aFgRatio);
268     return NS_RGBA(r, g, b, NS_GET_A(aFg));
269   }
270 
271   constexpr float kFactor = 1.0f / 255.0f;
272 
273   float p1 = kFactor * (255 - aFgRatio);
274   float a1 = kFactor * NS_GET_A(aBg);
275   float r1 = a1 * NS_GET_R(aBg);
276   float g1 = a1 * NS_GET_G(aBg);
277   float b1 = a1 * NS_GET_B(aBg);
278 
279   float p2 = 1.0f - p1;
280   float a2 = kFactor * NS_GET_A(aFg);
281   float r2 = a2 * NS_GET_R(aFg);
282   float g2 = a2 * NS_GET_G(aFg);
283   float b2 = a2 * NS_GET_B(aFg);
284 
285   float a = p1 * a1 + p2 * a2;
286   if (a == 0.0) {
287     return NS_RGBA(0, 0, 0, 0);
288   }
289 
290   auto r = ClampColor((p1 * r1 + p2 * r2) / a);
291   auto g = ClampColor((p1 * g1 + p2 * g2) / a);
292   auto b = ClampColor((p1 * b1 + p2 * b2) / a);
293   return NS_RGBA(r, g, b, NSToIntRound(a * 255));
294 }
295 
296 }  // namespace mozilla
297 
298 // Functions to convert from HSL color space to RGB color space.
299 // This is the algorithm described in the CSS3 specification
300 
301 // helper
HSL_HueToRGB(float m1,float m2,float h)302 static float HSL_HueToRGB(float m1, float m2, float h) {
303   if (h < 0.0f) h += 1.0f;
304   if (h > 1.0f) h -= 1.0f;
305   if (h < (float)(1.0 / 6.0)) return m1 + (m2 - m1) * h * 6.0f;
306   if (h < (float)(1.0 / 2.0)) return m2;
307   if (h < (float)(2.0 / 3.0))
308     return m1 + (m2 - m1) * ((float)(2.0 / 3.0) - h) * 6.0f;
309   return m1;
310 }
311 
312 // The float parameters are all expected to be in the range 0-1
NS_HSL2RGB(float h,float s,float l)313 nscolor NS_HSL2RGB(float h, float s, float l) {
314   uint8_t r, g, b;
315   float m1, m2;
316   if (l <= 0.5f) {
317     m2 = l * (s + 1);
318   } else {
319     m2 = l + s - l * s;
320   }
321   m1 = l * 2 - m2;
322   // We round, not floor, because that's how we handle
323   // percentage RGB values.
324   r = ClampColor(255 * HSL_HueToRGB(m1, m2, h + 1.0f / 3.0f));
325   g = ClampColor(255 * HSL_HueToRGB(m1, m2, h));
326   b = ClampColor(255 * HSL_HueToRGB(m1, m2, h - 1.0f / 3.0f));
327   return NS_RGB(r, g, b);
328 }
329 
NS_RGBToColorName(nscolor aColor)330 const char* NS_RGBToColorName(nscolor aColor) {
331   for (size_t idx = 0; idx < ArrayLength(kColors); ++idx) {
332     if (kColors[idx] == aColor) {
333       return kColorNames[idx];
334     }
335   }
336 
337   return nullptr;
338 }
339