1 // Written in the D programming language. 2 3 /** 4 This module implements various _color type conversions. 5 6 Authors: Manu Evans 7 Copyright: Copyright (c) 2015, Manu Evans. 8 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 Source: $(PHOBOSSRC ggplotd/color/conv.d) 10 */ 11 module ggplotd.color.conv; 12 13 import ggplotd.color; 14 import ggplotd.color.rgb; 15 import ggplotd.color.xyz; 16 import ggplotd.color.hsx; 17 18 import std.traits : isNumeric, isIntegral, isFloatingPoint, isSigned, isSomeChar, TemplateOf; 19 import std.typetuple : TypeTuple; 20 21 @safe pure nothrow @nogc: 22 23 24 /** 25 Convert between color types. 26 */ 27 To convertColor(To, From)(From color) if(isColor!To && isColor!From) 28 { 29 // no conversion is necessary 30 static if(is(To == From)) 31 return color; 32 33 // *** XYZ is the root type *** 34 else static if(isXYZ!From && isXYZ!To) 35 { 36 alias F = To.ComponentType; 37 return To(F(color.X), F(color.Y), F(color.Z)); 38 } 39 40 // following conversions come in triplets: 41 // Type!U -> Type!V 42 // Type -> Parent 43 // Parent -> type 44 45 // *** RGB triplet *** 46 else static if(isRGB!From && isRGB!To) 47 { 48 alias ToType = To.ComponentType; 49 alias FromType = From.ComponentType; 50 51 auto src = color.tristimulusWithAlpha; 52 53 static if(false && From.colorSpace == To.colorSpace && isIntegral!FromType && FromType.sizeof <= 2 && 54 (From.linear != To.linear || !is(FromType == ToType))) 55 { 56 alias WorkType = WorkingType!(FromType, ToType); 57 enum NumValues = 1 << (FromType.sizeof*8); 58 59 // <= 16bit type conversion should use a look-up table 60 shared immutable ToType[NumValues] conversionTable = { 61 ToType[NumValues] table = void; 62 foreach(i; 0..NumValues) 63 { 64 WorkType v = convertPixelType!WorkType(cast(FromType)i); 65 static if(From.linear == false) 66 v = toLinear!(From.colorSpace)(v); 67 static if(To.linear == false) 68 v = toGamma!(To.colorSpace)(v); 69 table[i] = convertPixelType!ToType(v); 70 } 71 return table; 72 }(); 73 74 static if(To.hasAlpha) 75 return To(conversionTable[src[0]], conversionTable[src[0]], conversionTable[src[1]], conversionTable[src[2]], convertPixelType!ToType(src[3])); 76 else 77 return To(conversionTable[src[0]], conversionTable[src[0]], conversionTable[src[1]], conversionTable[src[2]]); 78 } 79 else static if(From.colorSpace == To.colorSpace && From.linear == To.linear) 80 { 81 // color space is the same, just do type conversion 82 return To(convertPixelType!ToType(src[0]), convertPixelType!ToType(src[1]), convertPixelType!ToType(src[2]), convertPixelType!ToType(src[3])); 83 } 84 else 85 { 86 // unpack the working values 87 alias WorkType = WorkingType!(FromType, ToType); 88 WorkType r = convertPixelType!WorkType(src[0]); 89 WorkType g = convertPixelType!WorkType(src[1]); 90 WorkType b = convertPixelType!WorkType(src[2]); 91 92 static if(From.linear == false) 93 { 94 r = toLinear!(From.colorSpace)(r); 95 g = toLinear!(From.colorSpace)(g); 96 b = toLinear!(From.colorSpace)(b); 97 } 98 static if(From.colorSpace != To.colorSpace) 99 { 100 enum toXYZ = RGBColorSpaceMatrix!(From.colorSpace, WorkType); 101 enum toRGB = inverse(RGBColorSpaceMatrix!(To.colorSpace, WorkType)); 102 enum mat = multiply(toXYZ, toRGB); 103 WorkType[3] v = multiply(mat, [r, g, b]); 104 r = v[0]; g = v[1]; b = v[2]; 105 } 106 static if(To.linear == false) 107 { 108 r = toGamma!(To.colorSpace)(r); 109 g = toGamma!(To.colorSpace)(g); 110 b = toGamma!(To.colorSpace)(b); 111 } 112 113 // convert and return the output 114 static if(To.hasAlpha) 115 return To(convertPixelType!ToType(r), convertPixelType!ToType(g), convertPixelType!ToType(b), convertPixelType!ToType(src[3])); 116 else 117 return To(convertPixelType!ToType(r), convertPixelType!ToType(g), convertPixelType!ToType(b)); 118 } 119 } 120 else static if(isRGB!From && isXYZ!To) 121 { 122 alias ToType = To.ComponentType; 123 alias FromType = From.ComponentType; 124 alias WorkType = WorkingType!(FromType, ToType); 125 126 // unpack the working values 127 auto src = color.tristimulus; 128 WorkType r = convertPixelType!WorkType(src[0]); 129 WorkType g = convertPixelType!WorkType(src[1]); 130 WorkType b = convertPixelType!WorkType(src[2]); 131 132 static if(From.linear == false) 133 { 134 r = toLinear!(From.colorSpace)(r); 135 g = toLinear!(From.colorSpace)(g); 136 b = toLinear!(From.colorSpace)(b); 137 } 138 139 // transform to XYZ 140 enum toXYZ = RGBColorSpaceMatrix!(From.colorSpace, WorkType); 141 WorkType[3] v = multiply(toXYZ, [r, g, b]); 142 return To(v[0], v[1], v[2]); 143 } 144 else static if(isXYZ!From && isRGB!To) 145 { 146 alias ToType = To.ComponentType; 147 alias FromType = From.ComponentType; 148 alias WorkType = WorkingType!(FromType, ToType); 149 150 enum toRGB = inverse(RGBColorSpaceMatrix!(To.colorSpace, WorkType)); 151 WorkType[3] v = multiply(toRGB, [ WorkType(color.X), WorkType(color.Y), WorkType(color.Z) ]); 152 153 static if(To.linear == false) 154 { 155 v[0] = toGamma!(To.colorSpace)(v[0]); 156 v[1] = toGamma!(To.colorSpace)(v[1]); 157 v[2] = toGamma!(To.colorSpace)(v[2]); 158 } 159 160 return To(convertPixelType!ToType(v[0]), convertPixelType!ToType(v[1]), convertPixelType!ToType(v[2])); 161 } 162 163 // *** xyY triplet *** 164 else static if(isxyY!From && isxyY!To) 165 { 166 alias F = To.ComponentType; 167 return To(F(color.x), F(color.y), F(color.Y)); 168 } 169 else static if(isxyY!From && isXYZ!To) 170 { 171 alias F = To.ComponentType; 172 if(color.y == F(0)) 173 return To(F(0), F(0), F(0)); 174 else 175 return To(F(color.x*color.Y/color.y), F(color.Y), F((F(1)-color.x-color.y)*color.Y/color.y)); 176 } 177 else static if(isXYZ!From && isxyY!To) 178 { 179 alias F = To.ComponentType; 180 auto sum = color.X + color.Y + color.Z; 181 if(sum == F(0)) 182 return To(WhitePoint!F.D65.x, WhitePoint!F.D65.y, F(0)); 183 else 184 return To(F(color.X/sum), F(color.Y/sum), F(color.Y)); 185 } 186 187 // *** HSx triplet *** 188 else static if(isHSx!From && isHSx!To) 189 { 190 // HACK: cast through RGB (this works fine, but could be faster) 191 return cast(To)cast(From.ParentColourSpace)from; 192 } 193 else static if(isHSx!From && isRGB!To) 194 { 195 import std.math : abs; 196 197 alias ToType = To.ComponentType; 198 alias WT = FloatTypeFor!ToType; 199 200 auto c = color.tupleof; 201 WT h = convertPixelType!WT(c[0]); 202 WT s = convertPixelType!WT(c[1]); 203 WT x = convertPixelType!WT(c[2]); 204 205 WT C, m; 206 static if(From.type == HSxType.HSV) 207 { 208 C = x*s; 209 m = x - C; 210 } 211 else static if(From.type == HSxType.HSL) 212 { 213 C = (1 - abs(2*x - 1))*s; 214 m = x - C/2; 215 } 216 else static if(From.type == HSxType.HSI) 217 { 218 C = s; 219 m = x - (r+g+b)*WT(1.0/3.0); 220 } 221 else static if(From.type == HSxType.HCY) 222 { 223 C = s; 224 } 225 226 WT H = h/60; 227 WT X = C*(1 - abs(H%2.0 - 1)); 228 229 WT r, g, b; 230 if(H < 1) 231 r = C, g = X, b = 0; 232 else if(H < 2) 233 r = X, g = C, b = 0; 234 else if(H < 3) 235 r = 0, g = C, b = X; 236 else if(H < 4) 237 r = 0, g = X, b = C; 238 else if(H < 5) 239 r = X, g = 0, b = C; 240 else if(H < 6) 241 r = C, g = 0, b = X; 242 243 static if(From.type == HSxType.HCY) 244 { 245 enum YAxis = RGBColorSpaceMatrix!(From.colorSpace, WT)[1]; 246 m = x - (YAxis[0]*r + YAxis[1]*g + YAxis[2]*b); // Derive from Luma' 247 } 248 249 return To(convertPixelType!ToType(r+m), convertPixelType!ToType(g+m), convertPixelType!ToType(b+m)); 250 } 251 else static if(isRGB!From && isHSx!To) 252 { 253 import std.algorithm : min, max; 254 import std.math : abs; 255 256 alias ToType = To.ComponentType; 257 alias WT = FloatTypeFor!ToType; 258 259 auto c = color.tristimulus; 260 WT r = convertPixelType!WT(c[0]); 261 WT g = convertPixelType!WT(c[1]); 262 WT b = convertPixelType!WT(c[2]); 263 264 WT M = max(r, g, b); 265 WT m = min(r, g, b); 266 WT C = M-m; 267 268 // Calculate Hue 269 WT h; 270 if(C == 0) 271 h = 0; 272 else if(M == r) 273 h = WT(60) * ((g-b)/C % WT(6)); 274 else if(M == g) 275 h = WT(60) * ((b-r)/C + WT(2)); 276 else if(M == b) 277 h = WT(60) * ((r-g)/C + WT(4)); 278 279 WT s, x; 280 static if(To.type == HSxType.HSV) 281 { 282 x = M; // 'Value' 283 s = x == 0 ? WT(0) : C/x; // Saturation 284 } 285 else static if(To.type == HSxType.HSL) 286 { 287 x = (M + m)/WT(2); // Lightness 288 s = (x == 0 || x == 1) ? WT(0) : C/(1 - abs(2*x - 1)); // Saturation 289 } 290 else static if(To.type == HSxType.HSI) 291 { 292 x = (r + g + b)/WT(3); // Intensity 293 s = x == 0 ? WT(0) : 1 - m/x; // Saturation 294 } 295 else static if(To.type == HSxType.HCY) 296 { 297 enum YAxis = RGBColorSpaceMatrix!(To.colorSpace, WT)[1]; 298 x = YAxis[0]*r + YAxis[1]*g + YAxis[2]*b; // Luma' 299 s = C; // Chroma 300 } 301 302 return To(convertPixelType!ToType(h), convertPixelType!ToType(s), convertPixelType!ToType(x)); 303 } 304 305 // *** fallback plan *** 306 else 307 { 308 // cast along a conversion path to reach our target conversion 309 alias Path = ConversionPath!(From, To); 310 return convertColor!To(convertColor!(Path[0])(color)); 311 } 312 } 313 314 unittest 315 { 316 // test RGB format conversions 317 alias UnsignedRGB = RGB!("rgb", ubyte); 318 alias SignedRGBX = RGB!("rgbx", byte); 319 alias FloatRGBA = RGB!("rgba", float); 320 321 static assert(cast(UnsignedRGB)SignedRGBX(0x20,0x30,-10) == UnsignedRGB(0x40,0x60,0)); 322 static assert(cast(UnsignedRGB)FloatRGBA(1,0.5,0,1) == UnsignedRGB(0xFF,0x80,0)); 323 static assert(cast(UnsignedRGB)cast(FloatRGBA)UnsignedRGB(0xFF,0x80,0) == UnsignedRGB(0xFF,0x80,0)); 324 static assert(cast(FloatRGBA)UnsignedRGB(0xFF,0x80,0) == FloatRGBA(1,float(0x80)/float(0xFF),0,0)); 325 static assert(cast(FloatRGBA)SignedRGBX(127,-127,-128) == FloatRGBA(1,-1,-1,0)); 326 327 // test greyscale conversion 328 alias UnsignedL = RGB!("l", ubyte); 329 330 // TODO... we can't test this properly since DMD can't CTFE the '^^' operator! >_< 331 332 alias XYZf = XYZ!float; 333 334 // test RGB conversions 335 alias sRGBA = RGB!("rgba", ubyte, false, RGBColorSpace.sRGB); 336 alias lRGBA = RGB!("rgba", ushort, true, RGBColorSpace.sRGB); 337 alias gRGBA = RGB!("rgba", byte, false, RGBColorSpace.sRGB_Gamma2_2); 338 alias sRGBAf = RGB!("rgba", float, false, RGBColorSpace.sRGB); 339 alias lRGBAf = RGB!("rgba", double, true, RGBColorSpace.sRGB); 340 alias gRGBAf = RGB!("rgba", float, false, RGBColorSpace.sRGB_Gamma2_2); 341 342 assert(cast(lRGBA)sRGBA(0xFF, 0xFF, 0xFF, 0xFF) == lRGBA(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF)); 343 assert(cast(gRGBA)sRGBA(0xFF, 0x80, 0x01, 0xFF) == gRGBA(0x7F, 0x3F, 0x03, 0x7F)); 344 assert(cast(sRGBA)cast(XYZf)sRGBA(0xFF, 0xFF, 0xFF, 0xFF) == sRGBA(0xFF, 0xFF, 0xFF, 0)); 345 //... 346 347 // test PackedRGB conversions 348 //... 349 350 // test xyY conversions 351 alias xyYf = xyY!float; 352 353 static assert(cast(xyYf)XYZf(0.5, 1, 0.5) == xyYf(0.25, 0.5, 1)); 354 static assert(cast(XYZf)xyYf(0.5, 0.5, 1) == XYZf(1, 1, 0)); 355 356 // check the degenerate cases 357 static assert(cast(xyYf)XYZf(0, 0, 0) == xyYf(WhitePoint!float.D65.x, WhitePoint!float.D65.y, 0)); 358 static assert(cast(XYZf)xyYf(0.5, 0, 1) == XYZf(0, 0, 0)); 359 360 // test HSx conversions 361 alias HSVf = HSV!float; 362 alias HSLf = HSL!float; 363 alias HSIf = HSI!float; 364 alias HCYf = HCY!float; 365 366 static assert(cast(HSVf)RGB8(255, 0, 0) == HSVf(0, 1, 1)); 367 static assert(cast(RGB8)HSVf(0, 1, 1) == RGB8(255, 0, 0)); 368 static assert(cast(RGB8)HSVf(60, 0.5, 0.5) == RGB8(128, 128, 64)); 369 370 static assert(cast(HSLf)RGB8(255, 0, 0) == HSLf(0, 1, 0.5)); 371 static assert(cast(RGB8)HSLf(0, 1, 0.5) == RGB8(255, 0, 0)); 372 static assert(cast(RGB8)HSLf(60, 0.5, 0.5) == RGB8(191, 191, 64)); 373 374 static assert(cast(HSIf)RGB8(255, 0, 0) == HSIf(0, 1, 1.0/3)); 375 static assert(cast(HSIf)RGB8(255, 255, 0) == HSIf(60, 1, 2.0/3)); 376 // static assert(cast(RGB8)HSIf(0, 1, 1) == RGB8(1, 0, 0)); 377 378 // pragma(msg, cast(RGB8)HCYf(0, 0, 1)); 379 // static assert(cast(HCYf)RGB8(255, 0, 0) == HCYf(0, 1, 1)); 380 // static assert(cast(RGB8)HCYf(0, 1, 1) == RGB8(1, 0, 0)); 381 382 //... 383 } 384 385 386 /** 387 * Create a color from hex strings in the standard forms: (#/$/0x)rgb/argb/rrggbb/aarrggbb 388 */ 389 Color colorFromString(Color = RGB8, C)(const(C)[] hex) if(isSomeChar!C) 390 { 391 static ubyte val(C c) 392 { 393 if(c >= '0' && c <= '9') 394 return cast(ubyte)(c - '0'); 395 else if(c >= 'a' && c <= 'f') 396 return cast(ubyte)(c - 'a' + 10); 397 else if(c >= 'A' && c <= 'F') 398 return cast(ubyte)(c - 'A' + 10); 399 else 400 assert(false, "Invalid hex string"); 401 } 402 403 if(hex.length > 0 && (hex[0] == '#' || hex[0] == '$')) 404 hex = hex[1..$]; 405 else if(hex.length > 1 && (hex[0] == '0' && hex[1] == 'x')) 406 hex = hex[2..$]; 407 408 if(hex.length == 3) 409 { 410 ubyte r = val(hex[0]); 411 ubyte g = val(hex[1]); 412 ubyte b = val(hex[2]); 413 return cast(Color)RGB8(cast(ubyte)(r | (r << 4)), cast(ubyte)(g | (g << 4)), cast(ubyte)(b | (b << 4))); 414 } 415 if(hex.length == 4) 416 { 417 ubyte a = val(hex[0]); 418 ubyte r = val(hex[1]); 419 ubyte g = val(hex[2]); 420 ubyte b = val(hex[3]); 421 return cast(Color)RGBA8(cast(ubyte)(r | (r << 4)), cast(ubyte)(g | (g << 4)), cast(ubyte)(b | (b << 4)), cast(ubyte)(a | (a << 4))); 422 } 423 if(hex.length == 6) 424 { 425 ubyte r = cast(ubyte)(val(hex[0]) << 4) | val(hex[1]); 426 ubyte g = cast(ubyte)(val(hex[2]) << 4) | val(hex[3]); 427 ubyte b = cast(ubyte)(val(hex[4]) << 4) | val(hex[5]); 428 return cast(Color)RGB8(r, g, b); 429 } 430 if(hex.length == 8) 431 { 432 ubyte a = cast(ubyte)(val(hex[0]) << 4) | val(hex[1]); 433 ubyte r = cast(ubyte)(val(hex[2]) << 4) | val(hex[3]); 434 ubyte g = cast(ubyte)(val(hex[4]) << 4) | val(hex[5]); 435 ubyte b = cast(ubyte)(val(hex[6]) << 4) | val(hex[7]); 436 return cast(Color)RGBA8(r, g, b, a); 437 } 438 else 439 { 440 // TODO: should we look up colors from the W3C color table by name? 441 442 assert(false, "Invalid hex string!"); 443 } 444 } 445 446 /// 447 unittest 448 { 449 // common hex formats supported: 450 451 // 3 digits 452 static assert(colorFromString("F80") == RGB8(0xFF,0x88, 0x00)); 453 static assert(colorFromString("#F80") == RGB8(0xFF,0x88, 0x00)); 454 static assert(colorFromString("$F80") == RGB8(0xFF,0x88, 0x00)); 455 static assert(colorFromString("0xF80") == RGB8(0xFF,0x88, 0x00)); 456 457 // 6 digits 458 static assert(colorFromString("FF8000") == RGB8(0xFF,0x80, 0x00)); 459 static assert(colorFromString("#FF8000") == RGB8(0xFF,0x80, 0x00)); 460 static assert(colorFromString("$FF8000") == RGB8(0xFF,0x80, 0x00)); 461 static assert(colorFromString("0xFF8000") == RGB8(0xFF,0x80, 0x00)); 462 463 // 4/8 digita (/w alpha) 464 static assert(colorFromString!RGBA8("#8C41") == RGBA8(0xCC,0x44, 0x11, 0x88)); 465 static assert(colorFromString!RGBA8("#80CC4401") == RGBA8(0xCC,0x44, 0x01, 0x80)); 466 } 467 468 469 package: 470 471 // convert between pixel data types 472 To convertPixelType(To, From)(From v) if(isNumeric!From && isNumeric!To) 473 { 474 static if(isIntegral!From && isIntegral!To) 475 { 476 // extending normalised integer types is not trivial 477 return convertNormInt!To(v); 478 } 479 else static if(isIntegral!From && isFloatingPoint!To) 480 { 481 import std.algorithm : max; 482 alias FP = FloatTypeFor!(From, To); 483 static if(isSigned!From) // max(c, -1) is the signed conversion followed by D3D, OpenGL, etc. 484 return To(max(v*FP(1.0/From.max), FP(-1.0))); 485 else 486 return To(v*FP(1.0/From.max)); 487 } 488 else static if(isFloatingPoint!From && isIntegral!To) 489 { 490 alias FP = FloatTypeFor!(To, From); 491 // HACK: this is incomplete! 492 // +0.5 rounding only works for positive numbers 493 // we also need to clamp (saturate) [To.min, To.max] 494 return cast(To)(v*FP(To.max) + FP(0.5)); 495 } 496 else 497 return To(v); 498 } 499 500 501 // converts directly between fixed-point color types, without doing float conversions 502 // ** this should be tested for performance; we can optimise the small->large conversions with table lookups 503 To convertNormInt(To, From)(From i) if(isIntegral!To && isIntegral!From) 504 { 505 import std.traits: isUnsigned, Unsigned; 506 template Iota(alias start, alias end) 507 { 508 static if(end == start) 509 alias Iota = TypeTuple!(); 510 else 511 alias Iota = TypeTuple!(Iota!(start, end-1), end-1); 512 } 513 enum Bits(T) = T.sizeof*8; 514 515 static if(isUnsigned!To && isUnsigned!From) 516 { 517 static if(Bits!To <= Bits!From) 518 return To(i >> (Bits!From-Bits!To)); 519 else 520 { 521 To r; 522 enum numReps = Bits!To/Bits!From; 523 foreach(j; Iota!(0, numReps)) 524 r |= To(i) << (j*Bits!From); 525 return r; 526 } 527 } 528 else static if(isUnsigned!To) 529 { 530 if(i < 0) // if i is negative, return 0 531 return 0; 532 else 533 { 534 enum Sig = Bits!From-1; 535 static if(Bits!To < Bits!From) 536 return cast(To)(i >> (Sig-Bits!To)); 537 else 538 { 539 To r; 540 enum numReps = Bits!To/Sig; 541 foreach(j; Iota!(1, numReps+1)) 542 r |= To(cast(Unsigned!From)(i&From.max)) << (Bits!To - j*Sig); 543 enum remain = Bits!To - numReps*Sig; 544 static if(remain) 545 r |= cast(Unsigned!From)(i&From.max) >> (Sig - remain); 546 return r; 547 } 548 } 549 } 550 else static if(isUnsigned!From) 551 { 552 static if(Bits!To <= Bits!From) 553 return To(i >> (Bits!From-Bits!To+1)); 554 else 555 { 556 Unsigned!To r; 557 enum numReps = Bits!To/Bits!From; 558 foreach(j; Iota!(0, numReps)) 559 r |= Unsigned!To(i) << (j*Bits!From); 560 return To(r >> 1); 561 } 562 } 563 else 564 { 565 static if(Bits!To <= Bits!From) 566 return cast(To)(i >> (Bits!From-Bits!To)); 567 else 568 { 569 enum Sig = Bits!From-1; 570 enum Fill = Bits!To - Bits!From; 571 572 To r = To(i) << Fill; 573 enum numReps = Fill/Sig; 574 foreach(j; Iota!(1, numReps+1)) 575 r |= Unsigned!To(cast(Unsigned!From)(i&From.max)) << (Fill - j*Sig); 576 enum remain = Fill - numReps*Sig; 577 static if(remain) 578 r |= cast(Unsigned!From)(i&From.max) >> (Sig - remain); 579 return r; 580 } 581 } 582 } 583 584 unittest 585 { 586 // static asserts since these should all ctfe: 587 588 // unsigned -> unsigned 589 static assert(convertNormInt!ubyte(ushort(0x3765)) == 0x37); 590 static assert(convertNormInt!ushort(ubyte(0x37)) == 0x3737); 591 static assert(convertNormInt!ulong(ubyte(0x35)) == 0x3535353535353535); 592 593 // signed -> unsigned 594 static assert(convertNormInt!ubyte(short(-61)) == 0); 595 static assert(convertNormInt!ubyte(short(0x3795)) == 0x6F); 596 static assert(convertNormInt!ushort(byte(0x37)) == 0x6EDD); 597 static assert(convertNormInt!ulong(byte(0x35)) == 0x6AD5AB56AD5AB56A); 598 599 // unsigned -> signed 600 static assert(convertNormInt!byte(ushort(0x3765)) == 0x1B); 601 static assert(convertNormInt!short(ubyte(0x37)) == 0x1B9B); 602 static assert(convertNormInt!long(ubyte(0x35)) == 0x1A9A9A9A9A9A9A9A); 603 604 // signed -> signed 605 static assert(convertNormInt!byte(short(0x3795)) == 0x37); 606 static assert(convertNormInt!byte(short(-28672)) == -112); 607 static assert(convertNormInt!short(byte(0x37)) == 0x376E); 608 static assert(convertNormInt!short(byte(-109)) == -27866); 609 static assert(convertNormInt!long(byte(-45)) == -3195498973398505005); 610 } 611 612 613 // try and use the preferred float type 614 // if the int type exceeds the preferred float precision, we'll upgrade the float 615 template FloatTypeFor(IntType, RequestedFloat = float) 616 { 617 static if(IntType.sizeof > 2) 618 alias FloatTypeFor = double; 619 else 620 alias FloatTypeFor = RequestedFloat; 621 } 622 623 // find the fastest type to do format conversion without losing precision 624 template WorkingType(From, To) 625 { 626 static if(isIntegral!From && isIntegral!To) 627 { 628 // small integer types can use float and not lose precision 629 static if(From.sizeof <= 2 && To.sizeof <= 2) 630 alias WorkingType = float; 631 else 632 alias WorkingType = double; 633 } 634 else static if(isIntegral!From && isFloatingPoint!To) 635 alias WorkingType = To; 636 else static if(isFloatingPoint!From && isIntegral!To) 637 alias WorkingType = FloatTypeFor!To; 638 else 639 { 640 static if(From.sizeof > To.sizeof) 641 alias WorkingType = From; 642 else 643 alias WorkingType = To; 644 } 645 } 646 647 // find the conversion path from one distant type to another 648 template ConversionPath(From, To) 649 { 650 template isParentType(Parent, Of) 651 { 652 static if(isXYZ!Of) 653 enum isParentType = false; 654 else static if(isInstanceOf!(TemplateOf!Parent, Of.ParentColor)) 655 enum isParentType = true; 656 else 657 enum isParentType = isParentType!(Parent, Of.ParentColor); 658 } 659 660 template FindPath(From, To) 661 { 662 static if(isInstanceOf!(TemplateOf!To, From)) 663 alias FindPath = TypeTuple!(To); 664 else static if(isParentType!(From, To)) 665 alias FindPath = TypeTuple!(FindPath!(From, To.ParentColor), To); 666 else 667 alias FindPath = TypeTuple!(From, FindPath!(From.ParentColor, To)); 668 } 669 670 alias Path = FindPath!(From, To); 671 static if(Path.length == 1 && !is(Path[0] == From)) 672 alias ConversionPath = Path; 673 else 674 alias ConversionPath = Path[1..$]; 675 }