1 /** 2 Color type and operations. Port of ae.utils.graphics. 3 4 License: 5 This Source Code Form is subject to the terms of 6 the Mozilla Public License, v. 2.0. If a copy of 7 the MPL was not distributed with this file, You 8 can obtain one at http://mozilla.org/MPL/2.0/. 9 10 Copyright: Vladimir Panteleev <vladimir@thecybershadow.net> 11 Copyright: Guillaume Piolat <contact@auburnsounds.com> 12 */ 13 14 module dplug.graphics.color; 15 16 import std.traits: Signed, isSigned, isNumeric; 17 18 import inteli.emmintrin; 19 20 import dplug.core.math; 21 22 23 /// Evaluates to array of strings with name for each field. 24 @property string[] structFields(T)() 25 if (is(T == struct) || is(T == class)) 26 { 27 import std.string : split; 28 29 string[] fields; 30 foreach (i, f; T.init.tupleof) 31 { 32 string field = T.tupleof[i].stringof; 33 field = field.split(".")[$-1]; 34 fields ~= field; 35 } 36 return fields; 37 } 38 39 40 T itpl(T, U)(T low, T high, U r, U rLow, U rHigh) 41 { 42 return cast(T)(low + (cast(Signed!T)high-cast(Signed!T)low) * (cast(Signed!U)r - cast(Signed!U)rLow) / (cast(Signed!U)rHigh - cast(Signed!U)rLow)); 43 } 44 45 auto sqr(T)(T x) { return x*x; } 46 47 48 void sort2(T)(ref T x, ref T y) 49 { 50 if (x > y) 51 { 52 T z = x; 53 x = y; 54 y = z; 55 } 56 } 57 58 byte sign(T)(T x) 59 { 60 return x<0 ? -1 : (x>0 ? 1 : 0); 61 } 62 63 /// Integer log2. 64 private ubyte ilog2(T)(T n) 65 { 66 ubyte result = 0; 67 while (n >>= 1) 68 result++; 69 return result; 70 } 71 72 private T nextPowerOfTwo(T)(T x) 73 { 74 x |= x >> 1; 75 x |= x >> 2; 76 x |= x >> 4; 77 static if (T.sizeof > 1) 78 x |= x >> 8; 79 static if (T.sizeof > 2) 80 x |= x >> 16; 81 static if (T.sizeof > 4) 82 x |= x >> 32; 83 return x + 1; 84 } 85 86 /// Like std.typecons.Tuple, but a template mixin. 87 /// Unlike std.typecons.Tuple, names may not be omitted - but repeating types may be. 88 /// Example: FieldList!(ubyte, "r", "g", "b", ushort, "a"); 89 mixin template FieldList(Fields...) 90 { 91 mixin(GenFieldList!(void, Fields)); 92 } 93 94 template GenFieldList(T, Fields...) 95 { 96 static if (Fields.length == 0) 97 enum GenFieldList = ""; 98 else 99 { 100 static if (is(typeof(Fields[0]) == string)) 101 enum GenFieldList = T.stringof ~ " " ~ Fields[0] ~ ";\n" ~ GenFieldList!(T, Fields[1..$]); 102 else 103 enum GenFieldList = GenFieldList!(Fields[0], Fields[1..$]); 104 } 105 } 106 107 unittest 108 { 109 struct S 110 { 111 mixin FieldList!(ubyte, "r", "g", "b", ushort, "a"); 112 } 113 S s; 114 static assert(is(typeof(s.r) == ubyte)); 115 static assert(is(typeof(s.g) == ubyte)); 116 static assert(is(typeof(s.b) == ubyte)); 117 static assert(is(typeof(s.a) == ushort)); 118 } 119 120 121 /// Return the number of bits used to store the value part, i.e. 122 /// T.sizeof*8 for integer parts and the mantissa size for 123 /// floating-point types. 124 template valueBits(T) 125 { 126 static if (is(T : ulong)) 127 enum valueBits = T.sizeof * 8; 128 else 129 static if (is(T : real)) 130 enum valueBits = T.mant_dig; 131 else 132 static assert(false, "Don't know how many value bits there are in " ~ T.stringof); 133 } 134 135 static assert(valueBits!uint == 32); 136 static assert(valueBits!double == 53); 137 138 139 140 /// Instantiates to a color type. 141 /// FieldTuple is the color specifier, as parsed by 142 /// the FieldList template from ae.utils.meta. 143 /// By convention, each field's name indicates its purpose: 144 /// - x: padding 145 /// - a: alpha 146 /// - l: lightness (or grey, for monochrome images) 147 /// - others (r, g, b, etc.): color information 148 149 // MAYDO: figure out if we need all these methods in the color type itself 150 // - code such as gamma conversion needs to create color types 151 // - ReplaceType can't copy methods 152 // - even if we move out all conventional methods, that still leaves operator overloading 153 154 struct Color(FieldTuple...) 155 { 156 alias Spec = FieldTuple; 157 mixin FieldList!FieldTuple; 158 159 // A "dumb" type to avoid cyclic references. 160 private struct Fields { mixin FieldList!FieldTuple; } 161 162 /// Whether or not all channel fields have the same base type. 163 // Only "true" supported for now, may change in the future (e.g. for 5:6:5) 164 enum homogenous = true; 165 166 /// The number of fields in this color type. 167 enum channels = Fields.init.tupleof.length; 168 169 static if (homogenous) 170 { 171 alias ChannelType = typeof(Fields.init.tupleof[0]); 172 enum channelBits = valueBits!ChannelType; 173 } 174 175 /// Return a Color instance with all fields set to "value". 176 static typeof(this) monochrome(ChannelType value) 177 { 178 typeof(this) r; 179 foreach (i, f; r.tupleof) 180 r.tupleof[i] = value; 181 return r; 182 } 183 184 /// Interpolate between two colors. 185 static typeof(this) itpl(P)(typeof(this) c0, typeof(this) c1, P p, P p0, P p1) 186 { 187 alias ExpandNumericType!(ChannelType, P.sizeof*8) U; 188 alias Signed!U S; 189 typeof(this) r; 190 foreach (i, f; r.tupleof) 191 static if (r.tupleof[i].stringof != "r.x") // skip padding 192 r.tupleof[i] = cast(ChannelType).itpl(cast(U)c0.tupleof[i], cast(U)c1.tupleof[i], cast(S)p, cast(S)p0, cast(S)p1); 193 return r; 194 } 195 196 /// Construct an RGB color from a typical hex string. 197 static if (is(typeof(this.r) == ubyte) && is(typeof(this.g) == ubyte) && is(typeof(this.b) == ubyte)) 198 { 199 static typeof(this) fromHex(in char[] s) 200 { 201 import std.conv; 202 import std.exception; 203 204 enforce(s.length == 6, "Invalid color string"); 205 typeof(this) c; 206 c.r = s[0..2].to!ubyte(16); 207 c.g = s[2..4].to!ubyte(16); 208 c.b = s[4..6].to!ubyte(16); 209 return c; 210 } 211 212 string toHex() const 213 { 214 import std.string; 215 return format("%02X%02X%02X", r, g, b); 216 } 217 } 218 219 /// Warning: overloaded operators preserve types and may cause overflows 220 typeof(this) opUnary(string op)() 221 if (op=="~" || op=="-") 222 { 223 typeof(this) r; 224 foreach (i, f; r.tupleof) 225 static if(r.tupleof[i].stringof != "r.x") // skip padding 226 r.tupleof[i] = cast(typeof(r.tupleof[i])) mixin(op ~ `this.tupleof[i]`); 227 return r; 228 } 229 230 /// ditto 231 typeof(this) opOpAssign(string op)(int o) 232 { 233 foreach (i, f; this.tupleof) 234 static if(this.tupleof[i].stringof != "this.x") // skip padding 235 this.tupleof[i] = cast(typeof(this.tupleof[i])) mixin(`this.tupleof[i]` ~ op ~ `=o`); 236 return this; 237 } 238 239 /// ditto 240 typeof(this) opOpAssign(string op, T)(T o) 241 if (is(T==struct) && structFields!T == structFields!Fields) 242 { 243 foreach (i, f; this.tupleof) 244 static if(this.tupleof[i].stringof != "this.x") // skip padding 245 this.tupleof[i] = cast(typeof(this.tupleof[i])) mixin(`this.tupleof[i]` ~ op ~ `=o.tupleof[i]`); 246 return this; 247 } 248 249 /// ditto 250 typeof(this) opBinary(string op, T)(T o) 251 if (op != "~") 252 { 253 auto r = this; 254 mixin("r" ~ op ~ "=o;"); 255 return r; 256 } 257 258 /// Apply a custom operation for each channel. Example: 259 /// COLOR.op!q{(a + b) / 2}(colorA, colorB); 260 static typeof(this) op(string expr, T...)(T values) 261 { 262 static assert(values.length <= 10); 263 264 string genVars(string channel) 265 { 266 string result; 267 foreach (j, Tj; T) 268 { 269 static if (is(Tj == struct)) // TODO: tighter constraint (same color channels)? 270 result ~= "auto " ~ cast(char)('a' + j) ~ " = values[" ~ cast(char)('0' + j) ~ "]." ~ channel ~ ";\n"; 271 else 272 result ~= "auto " ~ cast(char)('a' + j) ~ " = values[" ~ cast(char)('0' + j) ~ "];\n"; 273 } 274 return result; 275 } 276 277 typeof(this) r; 278 foreach (i, f; r.tupleof) 279 { 280 mixin(genVars(r.tupleof[i].stringof[2..$])); 281 r.tupleof[i] = mixin(expr); 282 } 283 return r; 284 } 285 286 T opCast(T)() 287 if (is(T==struct) && structFields!T == structFields!Fields) 288 { 289 T t; 290 foreach (i, f; this.tupleof) 291 t.tupleof[i] = cast(typeof(t.tupleof[i])) this.tupleof[i]; 292 return t; 293 } 294 295 /// Sum of all channels 296 ExpandIntegerType!(ChannelType, ilog2(nextPowerOfTwo(channels))) sum() 297 { 298 typeof(return) result; 299 foreach (i, f; this.tupleof) 300 static if (this.tupleof[i].stringof != "this.x") // skip padding 301 result += this.tupleof[i]; 302 return result; 303 } 304 } 305 306 // The "x" has the special meaning of "padding" and is ignored in some circumstances 307 alias Color!(ubyte , "r", "g", "b" ) RGB ; 308 alias Color!(ubyte , "r", "g", "b", "a") RGBA ; 309 alias Color!(ushort , "r", "g", "b", "a") RGBA16 ; 310 311 312 alias Color!(ubyte , "l" ) L8 ; 313 alias Color!(ushort , "l" ) L16 ; 314 alias Color!(float , "l" ) L32f ; 315 316 alias Color!(float , "r", "g", "b" ) RGBf ; 317 alias Color!(float , "r", "g", "b", "a") RGBAf ; 318 319 static assert(L32f.sizeof == 4); 320 static assert(RGBf.sizeof == 12); 321 static assert(RGBAf.sizeof == 16); 322 323 unittest 324 { 325 static assert(RGB.sizeof == 3); 326 RGB[2] arr; 327 static assert(arr.sizeof == 6); 328 329 RGB hex = RGB.fromHex("123456"); 330 assert(hex.r == 0x12 && hex.g == 0x34 && hex.b == 0x56); 331 332 assert(RGB(1, 2, 3) + RGB(4, 5, 6) == RGB(5, 7, 9)); 333 334 RGB c = RGB(1, 1, 1); 335 c += 1; 336 assert(c == RGB(2, 2, 2)); 337 c += c; 338 assert(c == RGB(4, 4, 4)); 339 } 340 341 unittest 342 { 343 import std.conv; 344 345 L8 r; 346 347 r = L8.itpl(L8(100), L8(200), 15, 10, 20); 348 assert(r == L8(150), text(r)); 349 } 350 351 352 unittest 353 { 354 Color!(real, "r", "g", "b") c; 355 } 356 357 /// Obtains the type of each channel for homogenous colors. 358 template ChannelType(T) 359 { 360 static if (is(T == struct)) 361 alias ChannelType = T.ChannelType; 362 else 363 alias ChannelType = T; 364 } 365 366 /// Resolves to a Color instance with a different ChannelType. 367 template ChangeChannelType(COLOR, T) 368 if (isNumeric!COLOR) 369 { 370 alias ChangeChannelType = T; 371 } 372 373 /// ditto 374 template ChangeChannelType(COLOR, T) 375 if (is(COLOR : Color!Spec, Spec...)) 376 { 377 static assert(COLOR.homogenous, "Can't change ChannelType of non-homogenous Color"); 378 alias ChangeChannelType = Color!(T, COLOR.Spec[1..$]); 379 } 380 381 static assert(is(ChangeChannelType!(int, ushort) == ushort)); 382 383 384 /// Expand to a built-in numeric type of the same kind 385 /// (signed integer / unsigned integer / floating-point) 386 /// with at least the indicated number of bits of precision. 387 template ResizeNumericType(T, uint bits) 388 { 389 static if (is(T : ulong)) 390 static if (isSigned!T) 391 alias ResizeNumericType = SignedBitsType!bits; 392 else 393 alias ResizeNumericType = UnsignedBitsType!bits; 394 else 395 static if (is(T : real)) 396 { 397 static if (bits <= float.mant_dig) 398 alias ResizeNumericType = float; 399 else 400 static if (bits <= double.mant_dig) 401 alias ResizeNumericType = double; 402 else 403 static if (bits <= real.mant_dig) 404 alias ResizeNumericType = real; 405 else 406 static assert(0, "No floating-point type big enough to fit " ~ bits.stringof ~ " bits"); 407 } 408 else 409 static assert(false, "Don't know how to resize type: " ~ T.stringof); 410 } 411 412 static assert(is(ResizeNumericType!(float, double.mant_dig) == double)); 413 414 /// Expand to a built-in numeric type of the same kind 415 /// (signed integer / unsigned integer / floating-point) 416 /// with at least additionalBits more bits of precision. 417 alias ExpandNumericType(T, uint additionalBits) = 418 ResizeNumericType!(T, valueBits!T + additionalBits); 419 420 /// Unsigned integer type big enough to fit N bits of precision. 421 template UnsignedBitsType(uint bits) 422 { 423 static if (bits <= 8) 424 alias ubyte UnsignedBitsType; 425 else 426 static if (bits <= 16) 427 alias ushort UnsignedBitsType; 428 else 429 static if (bits <= 32) 430 alias uint UnsignedBitsType; 431 else 432 static if (bits <= 64) 433 alias ulong UnsignedBitsType; 434 else 435 static assert(0, "No integer type big enough to fit " ~ bits.stringof ~ " bits"); 436 } 437 438 template SignedBitsType(uint bits) 439 { 440 alias Signed!(UnsignedBitsType!bits) SignedBitsType; 441 } 442 443 444 /// Wrapper around ExpandNumericType to only expand numeric types. 445 template ExpandIntegerType(T, size_t bits) 446 { 447 static if (is(T:real)) 448 alias ExpandIntegerType = T; 449 else 450 alias ExpandIntegerType = ExpandNumericType!(T, bits); 451 } 452 453 RGBA blendColor(RGBA fg, RGBA bg, ubyte alpha) pure nothrow @nogc 454 { 455 ubyte invAlpha = cast(ubyte)(~cast(int)alpha); 456 version(LDC) 457 { 458 __m128i alphaMask = _mm_set1_epi32( (invAlpha << 16) | alpha ); // [ alpha invAlpha... (4x)] 459 __m128i mmfg = _mm_cvtsi32_si128( *cast(int*)(&fg) ); 460 __m128i mmbg = _mm_cvtsi32_si128( *cast(int*)(&bg) ); 461 __m128i zero = _mm_setzero_si128(); 462 __m128i colorMask = _mm_unpacklo_epi8(mmfg, mmbg); // [fg.r bg.r fg.g bg.g fg.b bg.b fg.a bg.a 0 (8x) ] 463 colorMask = _mm_unpacklo_epi8(colorMask, zero); // [fg.r bg.r fg.g bg.g fg.b bg.b fg.a bg.a ] 464 __m128i product = _mm_madd_epi16(colorMask, alphaMask); // [ fg[i]*alpha+bg[i]*invAlpha (4x) ] 465 466 // To divide a ushort by 255, LLVM suggests to 467 // * sign multiply by 32897 468 // * right-shift logically by 23 469 // Thanks https://godbolt.org/ 470 product *= _mm_set1_epi32(32897); // PERF: this leads to inefficient code with several pmul 471 product = _mm_srli_epi32(product, 23); 472 __m128i c = _mm_packs_epi32(product, zero); 473 c = _mm_packus_epi16(c, zero); 474 RGBA result = void; 475 *cast(int*)(&result) = c[0]; 476 return result; 477 } 478 else 479 { 480 // PERF should be a lot to optimize there 481 482 RGBA c = void; 483 c.r = cast(ubyte) ( ( (fg.r * alpha) + (bg.r * invAlpha) ) / ubyte.max ); 484 c.g = cast(ubyte) ( ( (fg.g * alpha) + (bg.g * invAlpha) ) / ubyte.max ); 485 c.b = cast(ubyte) ( ( (fg.b * alpha) + (bg.b * invAlpha) ) / ubyte.max ); 486 c.a = cast(ubyte) ( ( (fg.a * alpha) + (bg.a * invAlpha) ) / ubyte.max ); 487 return c; 488 } 489 } 490 491 /// Blend two colors, where `fg` is a premultiplied color by its own alpha. 492 /// We consider fg to be already scaled by (alpha/255) 493 /// Return: (255-alpha) 494 RGBA blendColorPremul(RGBA fg, RGBA bg, ubyte alpha) pure nothrow @nogc 495 { 496 ubyte invAlpha = cast(ubyte)(~cast(int)alpha); 497 RGBA c = void; 498 c.r = cast(ubyte) ( fg.r + (bg.r * invAlpha) / 255 ); 499 c.g = cast(ubyte) ( fg.g + (bg.g * invAlpha) / 255 ); // Note: curious lack of rounding 500 c.b = cast(ubyte) ( fg.b + (bg.b * invAlpha) / 255 ); 501 c.a = cast(ubyte) ( fg.a + (bg.a * invAlpha) / 255 ); 502 return c; 503 } 504 505 RGB blendColor(RGB fg, RGB bg, ubyte alpha) pure nothrow @nogc 506 { 507 ubyte invAlpha = cast(ubyte)(~cast(int)alpha); 508 RGB c = void; 509 c.r = cast(ubyte) ( ( (fg.r * alpha) + (bg.r * invAlpha) ) / ubyte.max ); 510 c.g = cast(ubyte) ( ( (fg.g * alpha) + (bg.g * invAlpha) ) / ubyte.max ); 511 c.b = cast(ubyte) ( ( (fg.b * alpha) + (bg.b * invAlpha) ) / ubyte.max ); 512 return c; 513 } 514 515 L16 blendColor(L16 fg, L16 bg, ushort alpha) pure nothrow @nogc 516 { 517 ushort v = cast(ushort) ( ((fg.l * alpha) + (bg.l * cast(ushort)(~cast(int)alpha))) / ushort.max ); 518 return L16(v); 519 } 520 521