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