1 /** 2 Module contains color format convertion operations. 3 4 $(DL Module contains: 5 $(DD 6 $(LINK2 #rgb2gray,rgb2gray) 7 $(LINK2 #gray2rgb,gray2rgb) 8 $(LINK2 #rgb2hsv,rgb2hsv) 9 $(LINK2 #hsv2rgb,hsv2rgb) 10 $(LINK2 #rgb2yuv,rgb2yuv) 11 $(LINK2 #yuv2rgb,yuv2rgb) 12 ) 13 ) 14 15 Copyright: Copyright Relja Ljubobratovic 2016. 16 17 Authors: Relja Ljubobratovic 18 19 License: $(LINK3 http://www.boost.org/LICENSE_1_0.txt, Boost Software License - Version 1.0). 20 */ 21 22 module dcv.imgproc.color; 23 24 /* 25 TODO: redesign functions - one function to iterate, separated format convertions as template alias. 26 Consider grouping color convertion routines into one function. 27 28 v0.1 norm: 29 rgb2gray vice versa (done) 30 hsv2rgb -||- 31 hls2rgb -||- 32 lab2rgb -||- 33 luv2rgb -||- 34 luv2rgb -||- 35 bayer2rgb -||- 36 */ 37 import std.traits : isFloatingPoint, isNumeric; 38 39 import mir.math.common : fastmath; 40 41 import mir.ndslice.slice; 42 import mir.ndslice.topology; 43 import mir.ndslice.algorithm; 44 import mir.ndslice.allocation; 45 46 import dcv.core.utils; 47 48 /** 49 RGB to Grayscale convertion strategy. 50 */ 51 enum Rgb2GrayConvertion 52 { 53 MEAN, /// Mean the RGB values and assign to gray. 54 LUMINANCE_PRESERVE /// Use luminance preservation (0.2126R + 0.715G + 0.0722B). 55 } 56 57 /** 58 Convert RGB image to grayscale. 59 60 Params: 61 input = Input image. Should have 3 channels, represented as R, G and B 62 respectively in that order. 63 prealloc = Pre-allocated buffer, where grayscale image will be copied. Default 64 argument is an empty slice, where new data is allocated and returned. If given 65 slice is not of corresponding shape(range.shape[0], range.shape[1]), it is 66 discarded and allocated anew. 67 conv = Convertion strategy - mean, or luminance preservation. 68 69 Returns: 70 Returns grayscale version of the given RGB image, of the same size. 71 72 Note: 73 Input and pre-allocated slices' strides must be identical. 74 */ 75 Slice!(SliceKind.contiguous, [2], V*) rgb2gray(V)(Slice!(SliceKind.contiguous, [3], V*) input, Slice!(SliceKind.contiguous, [2], V*) prealloc = emptySlice!([2], V), 76 Rgb2GrayConvertion conv = Rgb2GrayConvertion.LUMINANCE_PRESERVE) pure nothrow 77 { 78 return rgbbgr2gray!(false, V)(input, prealloc, conv); 79 } 80 81 unittest 82 { 83 import std.math : approxEqual; 84 85 auto rgb = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3].sliced(2, 2, 3); 86 87 auto gray = rgb.rgb2gray; 88 assert(gray.flattened == [0, 1, 2, 3]); 89 } 90 91 /** 92 Convert BGR image to grayscale. 93 94 Same as rgb2gray, but follows swapped channels if luminance preservation 95 is chosen as convertion strategy. 96 97 Params: 98 input = Input image. Should have 3 channels, represented as B, G and R 99 respectively in that order. 100 prealloc = Pre-allocated range, where grayscale image will be copied. Default 101 argument is an empty slice, where new data is allocated and returned. If given 102 slice is not of corresponding shape(range.shape[0], range.shape[1]), it is 103 discarded and allocated anew. 104 conv = Convertion strategy - mean, or luminance preservation. 105 106 Returns: 107 Returns grayscale version of the given BGR image, of the same size. 108 109 Note: 110 Input and pre-allocated slices' strides must be identical. 111 */ 112 Slice!(SliceKind.contiguous, [2], V*) bgr2gray(V)(Slice!(SliceKind.contiguous, [3], V*) input, Slice!(SliceKind.contiguous, [2], V*) prealloc = emptySlice!([2], V), 113 Rgb2GrayConvertion conv = Rgb2GrayConvertion.LUMINANCE_PRESERVE) pure nothrow 114 { 115 return rgbbgr2gray!(true, V)(input, prealloc, conv); 116 } 117 118 unittest 119 { 120 import std.math : approxEqual; 121 122 auto rgb = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3].sliced(2, 2, 3); 123 124 auto gray = rgb.bgr2gray; 125 assert(gray.flattened == [0, 1, 2, 3]); 126 } 127 128 private Slice!(SliceKind.contiguous, [2], V*) rgbbgr2gray(bool isBGR, V)(Slice!(SliceKind.contiguous, [3], V*) input, Slice!(SliceKind.contiguous, [2], V*) prealloc = emptySlice!([2], V), 129 Rgb2GrayConvertion conv = Rgb2GrayConvertion.LUMINANCE_PRESERVE) pure nothrow 130 in 131 { 132 assert(!input.empty, "Input image is empty."); 133 } 134 body 135 { 136 if (prealloc.shape != input.shape[0 .. 2]) 137 prealloc = uninitializedSlice!V(input.shape[0 .. 2]); 138 139 auto rgb = staticPack!3(input); 140 141 assert(rgb.strides == prealloc.strides, 142 "Input image and pre-allocated buffer strides are not identical."); 143 144 auto pack = zip!true(rgb, prealloc); 145 alias PT = DeepElementType!(typeof(pack)); 146 147 if (conv == Rgb2GrayConvertion.MEAN) 148 pack.each!(rgb2grayImplMean!PT); 149 else 150 static if (isBGR) 151 pack.each!(bgr2grayImplLuminance!(PT)); 152 else 153 pack.each!(rgb2grayImplLuminance!(PT)); 154 155 return prealloc; 156 } 157 158 /** 159 Convert gray image to RGB. 160 161 Uses grayscale value and assigns it's value 162 to each of three channels for the RGB image version. 163 164 Params: 165 input = Grayscale image, to be converted to the RGB. 166 prealloc = Pre-allocated range, where RGB image will be copied. Default 167 argument is an empty slice, where new data is allocated and returned. If given 168 slice is not of corresponding shape(range.shape[0], range.shape[1], 3), it is 169 discarded and allocated anew. 170 171 Returns: 172 Returns RGB version of the given grayscale image. 173 174 Note: 175 Input and pre-allocated slices' strides must be identical. 176 */ 177 Slice!(SliceKind.contiguous, [3], V*) gray2rgb(V)(Slice!(SliceKind.contiguous, [2], V*) input, Slice!(SliceKind.contiguous, [3], V*) prealloc = emptySlice!([3], V)) pure nothrow 178 { 179 Slice!(SliceKind.contiguous, [2], V[3]*) rgb; 180 if (input.shape != prealloc.shape[0 .. 2]) 181 { 182 rgb = uninitializedSlice!(V[3])(input.length!0, input.length!1); 183 } 184 else 185 { 186 rgb = staticPack!3(prealloc); 187 } 188 189 assert(rgb.strides == input.strides, 190 "Input and pre-allocated slices' strides are not identical."); 191 192 auto pack = zip!true(input, rgb); 193 alias PT = DeepElementType!(typeof(pack)); 194 195 pack.each!(gray2rgbImpl!PT); 196 197 return staticUnpack!3(rgb); 198 } 199 200 unittest 201 { 202 import std.math : approxEqual; 203 204 auto gray = [0, 1, 2, 3].sliced(2, 2); 205 206 auto rgb = gray.gray2rgb; 207 208 assert(rgb.flattened == [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]); 209 } 210 211 /** 212 Convert RGB image to HSV color format. 213 214 If HSV is represented as floating point, H is 215 represented as 0-360 (degrees), S and V are 0.0-1.0. 216 If is integral, S, and V are 0-100. 217 218 Depending on the RGB (input) type, values are treated in the 219 algorithm to be ranged as 0-255 for ubyte, 0-65535 for ushort, 220 and 0-1 for floating point types. 221 222 Params: 223 input = RGB image, which gets converted to HSV. 224 prealloc = Pre-allocated range, where HSV image will be copied. Default 225 argument is an empty slice, where new data is allocated and returned. If given 226 slice is not of corresponding shape(range.shape[0], range.shape[1], 3), it is 227 discarded and allocated anew. 228 229 Returns: 230 Returns HSV verion of the given RGB image. 231 232 Note: 233 Input and pre-allocated slices' strides must be identical. 234 */ 235 Slice!(SliceKind.contiguous, [3], R*) rgb2hsv(R, V)(Slice!(SliceKind.contiguous, [3], V*) input, Slice!(SliceKind.contiguous, [3], R*) prealloc = emptySlice!([3], R)) pure nothrow 236 if (isNumeric!R && isNumeric!V) 237 in 238 { 239 static assert(R.max >= 360, "Invalid output type for HSV (R.max >= 360)"); 240 assert(input.length!2 == 3, "Invalid channel count."); 241 } 242 body 243 { 244 if (prealloc.shape != input.shape) 245 prealloc = uninitializedSlice!R(input.shape); 246 247 assert(input.strides == prealloc.strides, 248 "Input image and pre-allocated buffer strides are not identical."); 249 250 auto pack = zip!true(input.staticPack!3, prealloc.staticPack!3); 251 pack.each!(rgb2hsvImpl!(DeepElementType!(typeof(pack)))); 252 253 return prealloc; 254 } 255 256 unittest 257 { 258 // value comparison based on results from http://www.rapidtables.com/convert/color/rgb-to-hsv.htm 259 auto rgb2hsvTest(RGBType, HSVType)(RGBType[] rgb, HSVType[] expectedHSV) 260 { 261 import std.algorithm.comparison : equal; 262 import std.array : array; 263 import std.math : approxEqual; 264 265 assert(rgb.sliced(1, 1, 3).rgb2hsv!HSVType.flattened.array.equal!approxEqual(expectedHSV)); 266 } 267 268 rgb2hsvTest(cast(ubyte[])[255, 0, 0], cast(ushort[])[0, 100, 100]); 269 rgb2hsvTest(cast(ubyte[])[255, 0, 0], cast(float[])[0, 1.0f, 1.0f]); // test float result 270 271 // test same input values as above for 16-bit and 32-bit images 272 rgb2hsvTest(cast(ushort[])[ushort.max, 0, 0], cast(ushort[])[0, 100, 100]); 273 rgb2hsvTest(cast(float[])[1.0f, 0, 0], cast(ushort[])[0, 100, 100]); 274 275 rgb2hsvTest(cast(ubyte[])[0, 255, 0], cast(ushort[])[120, 100, 100]); 276 rgb2hsvTest(cast(ubyte[])[0, 0, 255], cast(ushort[])[240, 100, 100]); 277 rgb2hsvTest(cast(ubyte[])[122, 158, 200], cast(float[])[212, 0.39, 0.784]); 278 } 279 280 /** 281 Convert HSV image to RGB color format. 282 283 If HSV is represented in floating point, H is 0-360 degrees, S and V is 0.0-1.0. 284 If it's of integral type, S and V values are in 0-100 range. 285 286 Output range values are based on the output type cast - ubyte will 287 range RGB values to be 0-255, ushort 0-65535, and floating types 288 0.0-1.0. Other types are not supported. 289 290 Params: 291 input = HSV image, which gets converted to RGB. 292 prealloc = Pre-allocated range, where RGB image will be copied. Default 293 argument is an empty slice, where new data is allocated and returned. If given 294 slice is not of corresponding shape(range.shape[0], range.shape[1], 3), it is 295 discarded and allocated anew. 296 297 Returns: 298 Returns RGB verion of the given HSV image. 299 300 Note: 301 Input and pre-allocated slices' strides must be identical. 302 */ 303 Slice!(SliceKind.contiguous, [3], R*) hsv2rgb(R, V)(Slice!(SliceKind.contiguous, [3], V*) input, Slice!(SliceKind.contiguous, [3], R*) prealloc = emptySlice!([3], R)) pure nothrow 304 if (isNumeric!R && isNumeric!V) 305 in 306 { 307 assert(input.length!2 == 3, "Invalid channel count."); 308 } 309 body 310 { 311 if (prealloc.shape != input.shape) 312 prealloc = uninitializedSlice!R(input.shape); 313 314 assert(input.strides == prealloc.strides, 315 "Input image and pre-allocated buffer strides are not identical."); 316 317 auto pack = zip!true(input.staticPack!3, prealloc.staticPack!3); 318 pack.each!(hsv2rgbImpl!(DeepElementType!(typeof(pack)))); 319 320 return prealloc; 321 } 322 323 unittest 324 { 325 // value comparison based on results from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm 326 auto hsv2rgbTest(HSVType, RGBType)(HSVType[] hsv, RGBType[] expectedRgb) 327 { 328 import std.algorithm.comparison : equal; 329 import std.array : array; 330 import std.math : approxEqual; 331 332 assert(hsv.sliced(1, 1, 3).hsv2rgb!RGBType.flattened.array.equal!approxEqual(expectedRgb)); 333 } 334 335 import std.random : uniform; 336 337 foreach (i; 0 .. 10) 338 { 339 // test any value with value of 0, should give rgb [0, 0, 0] 340 hsv2rgbTest(cast(ushort[])[uniform(0, 359), uniform(0, 99), 0], cast(ubyte[])[0, 0, 0]); 341 } 342 343 hsv2rgbTest(cast(ushort[])[0, 0, 100], cast(ubyte[])[255, 255, 255]); 344 hsv2rgbTest(cast(ushort[])[150, 50, 100], cast(ubyte[])[128, 255, 191]); 345 hsv2rgbTest(cast(ushort[])[150, 50, 80], cast(ubyte[])[102, 204, 153]); 346 347 hsv2rgbTest(cast(float[])[0.0f, 0.0f, 1.0f], cast(ubyte[])[255, 255, 255]); 348 hsv2rgbTest(cast(float[])[150.0f, 0.5f, 1.0f], cast(ubyte[])[127, 255, 191]); 349 hsv2rgbTest(cast(float[])[150.0f, 0.5f, 0.8f], cast(ubyte[])[102, 204, 153]); 350 351 hsv2rgbTest(cast(ushort[])[0, 0, 100], cast(ushort[])[65535, 65535, 65535]); 352 hsv2rgbTest(cast(ushort[])[150, 50, 100], cast(ushort[])[32896, 65535, 49087]); 353 hsv2rgbTest(cast(ushort[])[150, 50, 80], cast(ushort[])[26214, 52428, 39321]); 354 355 hsv2rgbTest(cast(float[])[0.0f, 0.0f, 1.0f], cast(float[])[1.0f, 1.0f, 1.0f]); 356 hsv2rgbTest(cast(float[])[150.0f, 0.5f, 1.0f], cast(float[])[0.5f, 1.0f, 0.75f]); 357 hsv2rgbTest(cast(float[])[150.0f, 0.5f, 0.8f], cast(float[])[0.4f, 0.8f, 0.6f]); 358 } 359 360 /** 361 Convert RGB image format to YUV. 362 363 YUV images in dcv are organized in the same buffer plane 364 where quantity of luma and chroma values are the same (as in 365 YUV444 format). 366 367 Params: 368 input = Input RGB image. 369 prealloc = Optional pre-allocated buffer. If given, has to be 370 of same shape as input image, otherwise gets reallocated. 371 372 Returns: 373 Resulting YUV image slice. 374 375 Note: 376 Input and pre-allocated slices' strides must be identical. 377 */ 378 Slice!(SliceKind.contiguous, [3], V*) rgb2yuv(V)(Slice!(SliceKind.contiguous, [3], V*) input, Slice!(SliceKind.contiguous, [3], V*) prealloc = emptySlice!([3], V)) pure nothrow 379 in 380 { 381 assert(input.length!2 == 3, "Invalid channel count."); 382 } 383 body 384 { 385 if (prealloc.shape != input.shape) 386 prealloc = uninitializedSlice!V(input.shape); 387 388 assert(input.strides == prealloc.strides, 389 "Input image and pre-allocated buffer strides are not identical."); 390 391 auto p = zip!true(input, prealloc).pack!1; 392 p.each!(rgb2yuvImpl!(V, DeepElementType!(typeof(p)))); 393 394 return prealloc; 395 } 396 397 /** 398 Convert YUV image to RGB. 399 400 As in rgb2yuv conversion, YUV format is considered to have 401 same amount of luma and chroma. 402 403 Params: 404 input = Input YUV image. 405 prealloc = Optional pre-allocated buffer. If given, has to be 406 of same shape as input image, otherwise gets reallocated. 407 408 Returns: 409 Resulting RGB image slice. 410 411 Note: 412 Input and pre-allocated slices' strides must be identical. 413 */ 414 Slice!(SliceKind.contiguous, [3], V*) yuv2rgb(V)(Slice!(SliceKind.contiguous, [3], V*) input, Slice!(SliceKind.contiguous, [3], V*) prealloc = emptySlice!([3], V)) pure nothrow 415 in 416 { 417 assert(input.length!2 == 3, "Invalid channel count."); 418 } 419 body 420 { 421 import mir.ndslice.allocation; 422 if (prealloc.shape != input.shape) 423 prealloc = uninitializedSlice!V(input.shape); 424 425 assert(input.strides == prealloc.strides, 426 "Input image and pre-allocated buffer strides are not identical."); 427 428 auto p = zip!true(input, prealloc).pack!1; 429 p.each!(yuv2rgbImpl!(V, DeepElementType!(typeof(p)))); 430 431 return prealloc; 432 } 433 434 unittest 435 { 436 // test rgb to yuv conversion 437 auto rgb2yuvTest(Type)(Type[] rgb, Type[] expectedYuv) 438 { 439 import std.algorithm.comparison : equal; 440 import std.array : array; 441 import std.math : approxEqual; 442 443 assert(rgb.sliced(1, 1, 3).rgb2yuv.flattened.array.equal!approxEqual(expectedYuv)); 444 } 445 446 rgb2yuvTest(cast(ubyte[])[0, 0, 0], cast(ubyte[])[16, 128, 128]); 447 rgb2yuvTest(cast(ubyte[])[255, 0, 0], cast(ubyte[])[82, 90, 240]); 448 rgb2yuvTest(cast(ubyte[])[0, 255, 0], cast(ubyte[])[144, 54, 34]); 449 rgb2yuvTest(cast(ubyte[])[0, 0, 255], cast(ubyte[])[41, 240, 110]); 450 } 451 452 unittest 453 { 454 // test yuv to rgb conversion 455 auto yuv2rgbTest(Type)(Type[] yuv, Type[] expectedRgb) 456 { 457 import std.algorithm.comparison : equal; 458 import std.array : array; 459 import std.math : approxEqual; 460 461 assert(yuv.sliced(1, 1, 3).yuv2rgb.flattened.array.equal!approxEqual(expectedRgb)); 462 } 463 464 yuv2rgbTest(cast(ubyte[])[16, 128, 128], cast(ubyte[])[0, 0, 0]); 465 yuv2rgbTest(cast(ubyte[])[150, 54, 125], cast(ubyte[])[151, 187, 7]); 466 yuv2rgbTest(cast(ubyte[])[144, 54, 34], cast(ubyte[])[0, 255, 0]); 467 yuv2rgbTest(cast(ubyte[])[41, 240, 110], cast(ubyte[])[0, 0, 255]); 468 } 469 470 pure @nogc nothrow @fastmath: 471 472 void rgb2grayImplMean(P)(P pack) 473 { 474 alias V = typeof(pack.b); 475 pack.b = cast(V)((pack.a[0] + pack.a[1] + pack.a[2]) / 3); 476 } 477 478 void rgb2grayImplLuminance(RGBGRAY)(RGBGRAY pack) 479 { 480 alias V = typeof(pack.b); 481 pack.b = cast(V)( 482 cast(float)pack.a[0] * 0.212642529f + 483 cast(float)pack.a[1] * 0.715143029f + 484 cast(float)pack.a[2] * 0.072214443f 485 ); 486 } 487 488 void bgr2grayImplLuminance(RGBGRAY)(RGBGRAY pack) 489 { 490 alias V = typeof(pack.b); 491 pack.b = cast(V)( 492 cast(float)pack.a[2] * 0.212642529f + 493 cast(float)pack.a[1] * 0.715143029f + 494 cast(float)pack.a[0] * 0.072214443f 495 ); 496 } 497 498 void gray2rgbImpl(GRAYRGB)(GRAYRGB pack) 499 { 500 auto v = pack.a; 501 pack.b[0] = v; 502 pack.b[1] = v; 503 pack.b[2] = v; 504 } 505 506 void rgb2hsvImpl(RGBHSV)(RGBHSV pack) 507 { 508 import mir.math.common; 509 510 alias V = typeof(pack.a[0]); 511 alias R = typeof(pack.b[0]); 512 513 static if (is(V == ubyte)) 514 { 515 auto r = cast(float)(pack.a[0]) * (1.0f / 255.0f); 516 auto g = cast(float)(pack.a[1]) * (1.0f / 255.0f); 517 auto b = cast(float)(pack.a[2]) * (1.0f / 255.0f); 518 } 519 else static if (is(V == ushort)) 520 { 521 auto r = cast(float)(pack.a[0]) * (1.0f / 65535.0f); 522 auto g = cast(float)(pack.a[1]) * (1.0f / 65535.0f); 523 auto b = cast(float)(pack.a[2]) * (1.0f / 65535.0f); 524 } 525 else static if (isFloatingPoint!V) 526 { 527 // assumes a value range 0-1 528 auto r = cast(float)(pack.a[0]); 529 auto g = cast(float)(pack.a[1]); 530 auto b = cast(float)(pack.a[2]); 531 } 532 else 533 { 534 static assert(0, "Invalid RGB input type: " ~ V.stringof); 535 } 536 537 auto cmax = fmax(r, fmax(g, b)); 538 auto cmin = fmin(r, fmin(g, b)); 539 auto cdelta = cmax - cmin; // TODO: compute min and max in a lockstep 540 541 auto h = cast(R)((cdelta == 0) ? 0 : (cmax == r) ? 60.0f * ((g - b) / cdelta) : (cmax == g) 542 ? 60.0f * ((b - r) / cdelta + 2) : 60.0f * ((r - g) / cdelta + 4)); 543 544 if (h < 0) 545 h += 360; 546 547 static if (isFloatingPoint!R) 548 { 549 auto s = cast(R)(cmax == 0 ? 0 : cdelta / cmax); 550 auto v = cast(R)(cmax); 551 } 552 else 553 { 554 auto s = cast(R)(100.0f * (cmax == 0 ? 0 : cdelta / cmax)); 555 auto v = cast(R)(100.0f * cmax); 556 } 557 558 pack.b[0] = h; 559 pack.b[1] = s; 560 pack.b[2] = v; 561 } 562 563 void hsv2rgbImpl(HSVRGB)(HSVRGB pack) 564 { 565 alias V = typeof(pack.a[0]); 566 alias R = typeof(pack.b[0]); 567 568 static if (isFloatingPoint!V) 569 { 570 auto h = pack.a[0]; 571 auto s = pack.a[1]; 572 auto v = pack.a[2]; 573 } 574 else 575 { 576 float h = cast(float)pack.a[0]; 577 float s = cast(float)pack.a[1] * 0.01f; 578 float v = cast(float)pack.a[2] * 0.01f; 579 } 580 581 if (s <= 0) 582 { 583 static if (isFloatingPoint!R) 584 { 585 pack.b[0] = cast(R)v; 586 pack.b[1] = cast(R)v; 587 pack.b[2] = cast(R)v; 588 } 589 else 590 { 591 pack.b[0] = cast(R)(v * R.max); 592 pack.b[1] = cast(R)(v * R.max); 593 pack.b[2] = cast(R)(v * R.max); 594 } 595 return; 596 } 597 598 if (v <= 0.0f) 599 { 600 pack.b[0] = cast(R)0; 601 pack.b[1] = cast(R)0; 602 pack.b[2] = cast(R)0; 603 return; 604 } 605 606 if (h >= 360.0f) 607 h = 0.0f; 608 else 609 h /= 60.0; 610 611 auto hh = cast(int)h; 612 auto ff = h - float(hh); 613 614 auto p = v * (1.0f - s); 615 auto q = v * (1.0f - (s * ff)); 616 auto t = v * (1.0f - (s * (1.0f - ff))); 617 618 float r = void; 619 float g = void; 620 float b = void; 621 622 switch (hh) 623 { 624 case 0: 625 r = v; 626 g = t; 627 b = p; 628 break; 629 case 1: 630 r = q; 631 g = v; 632 b = p; 633 break; 634 case 2: 635 r = p; 636 g = v; 637 b = t; 638 break; 639 case 3: 640 r = p; 641 g = q; 642 b = v; 643 break; 644 case 4: 645 r = t; 646 g = p; 647 b = v; 648 break; 649 case 5: 650 default: 651 r = v; 652 g = p; 653 b = q; 654 break; 655 } 656 657 static if (isFloatingPoint!R) 658 { 659 pack.b[0] = r; 660 pack.b[1] = g; 661 pack.b[2] = b; 662 } 663 else 664 { 665 pack.b[0] = cast(R)(r * R.max); 666 pack.b[1] = cast(R)(g * R.max); 667 pack.b[2] = cast(R)(b * R.max); 668 } 669 } 670 671 void rgb2yuvImpl(V, RGBYUV)(RGBYUV pack) 672 { 673 static if (isFloatingPoint!V) 674 { 675 auto r = cast(int)pack[0].a; 676 auto g = cast(int)pack[1].a; 677 auto b = cast(int)pack[2].a; 678 pack[0].b = clip!V((r * .257) + (g * .504) + (b * .098) + 16); 679 pack[1].b = clip!V((r * .439) + (g * .368) + (b * .071) + 128); 680 pack[2].b = clip!V(-(r * .148) - (g * .291) + (b * .439) + 128); 681 } 682 else 683 { 684 auto r = pack[0].a; 685 auto g = pack[1].a; 686 auto b = pack[2].a; 687 pack[0].b = clip!V(((66 * (r) + 129 * (g) + 25 * (b) + 128) >> 8) + 16); 688 pack[1].b = clip!V(((-38 * (r) - 74 * (g) + 112 * (b) + 128) >> 8) + 128); 689 pack[2].b = clip!V(((112 * (r) - 94 * (g) - 18 * (b) + 128) >> 8) + 128); 690 } 691 } 692 693 void yuv2rgbImpl(V, YUVRGB)(YUVRGB pack) 694 { 695 auto y = cast(int)(pack[0].a) - 16; 696 auto u = cast(int)(pack[1].a) - 128; 697 auto v = cast(int)(pack[2].a) - 128; 698 static if (isFloatingPoint!V) 699 { 700 pack[0].b = clip!V(y + 1.4075 * v); 701 pack[1].b = clip!V(y - 0.3455 * u - (0.7169 * v)); 702 pack[2].b = clip!V(y + 1.7790 * u); 703 } 704 else 705 { 706 pack[0].b = clip!V((298 * y + 409 * v + 128) >> 8); 707 pack[1].b = clip!V((298 * y - 100 * u - 208 * v + 128) >> 8); 708 pack[2].b = clip!V((298 * y + 516 * u + 128) >> 8); 709 } 710 } 711