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