colorm.go (17039B)
1 // Copyright 2014 Hajime Hoshi 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package affine 16 17 import ( 18 "fmt" 19 "image/color" 20 "math" 21 ) 22 23 // ColorMDim is a dimension of a ColorM. 24 const ColorMDim = 5 25 26 var ( 27 colorMIdentityBody = [...]float32{ 28 1, 0, 0, 0, 29 0, 1, 0, 0, 30 0, 0, 1, 0, 31 0, 0, 0, 1, 32 } 33 colorMIdentityTranslate = [...]float32{ 34 0, 0, 0, 0, 35 } 36 ) 37 38 // ColorM represents a matrix to transform coloring when rendering an image. 39 // 40 // ColorM is applied to the source alpha color 41 // while an Image's pixels' format is alpha premultiplied. 42 // Before applying a matrix, a color is un-multiplied, and after applying the matrix, 43 // the color is multiplied again. 44 type ColorM interface { 45 IsIdentity() bool 46 ScaleOnly() bool 47 At(i, j int) float32 48 Elements(body *[16]float32, translate *[4]float32) 49 Apply(clr color.Color) color.Color 50 51 // IsInvertible returns a boolean value indicating 52 // whether the matrix c is invertible or not. 53 IsInvertible() bool 54 55 // Invert inverts the matrix. 56 // If c is not invertible, Invert panics. 57 Invert() ColorM 58 59 Equals(other ColorM) bool 60 61 // Concat multiplies a color matrix with the other color matrix. 62 // This is same as muptiplying the matrix other and the matrix c in this order. 63 Concat(other ColorM) ColorM 64 65 // Scale scales the matrix by (r, g, b, a). 66 Scale(r, g, b, a float32) ColorM 67 68 // Translate translates the matrix by (r, g, b, a). 69 Translate(r, g, b, a float32) ColorM 70 71 scaleElements() (r, g, b, a float32) 72 } 73 74 func ColorMString(c ColorM) string { 75 var b [16]float32 76 var t [4]float32 77 c.Elements(&b, &t) 78 return fmt.Sprintf("[[%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f]]", 79 b[0], b[4], b[8], b[12], t[0], 80 b[1], b[5], b[9], b[13], t[1], 81 b[2], b[6], b[10], b[14], t[2], 82 b[3], b[7], b[11], b[15], t[3]) 83 } 84 85 type ColorMIdentity struct{} 86 87 type colorMImplScale struct { 88 scale [4]float32 89 } 90 91 type colorMImplBodyTranslate struct { 92 body [16]float32 93 translate [4]float32 94 } 95 96 func clamp(x float32) float32 { 97 if x > 1 { 98 return 1 99 } 100 if x < 0 { 101 return 0 102 } 103 return x 104 } 105 106 func (c ColorMIdentity) IsIdentity() bool { 107 return true 108 } 109 110 func (c colorMImplScale) IsIdentity() bool { 111 return c.scale == [4]float32{1, 1, 1, 1} 112 } 113 114 func (c *colorMImplBodyTranslate) IsIdentity() bool { 115 return c.body == colorMIdentityBody && c.translate == colorMIdentityTranslate 116 } 117 118 func (c ColorMIdentity) ScaleOnly() bool { 119 return true 120 } 121 122 func (c colorMImplScale) ScaleOnly() bool { 123 return true 124 } 125 126 func (c *colorMImplBodyTranslate) ScaleOnly() bool { 127 if c.body[1] != 0 { 128 return false 129 } 130 if c.body[2] != 0 { 131 return false 132 } 133 if c.body[3] != 0 { 134 return false 135 } 136 if c.body[4] != 0 { 137 return false 138 } 139 if c.body[6] != 0 { 140 return false 141 } 142 if c.body[7] != 0 { 143 return false 144 } 145 if c.body[8] != 0 { 146 return false 147 } 148 if c.body[9] != 0 { 149 return false 150 } 151 if c.body[11] != 0 { 152 return false 153 } 154 if c.body[12] != 0 { 155 return false 156 } 157 if c.body[13] != 0 { 158 return false 159 } 160 if c.body[14] != 0 { 161 return false 162 } 163 for _, e := range c.translate { 164 if e != 0 { 165 return false 166 } 167 } 168 return true 169 } 170 171 func (c ColorMIdentity) scaleElements() (r, g, b, a float32) { 172 return 1, 1, 1, 1 173 } 174 175 func (c colorMImplScale) scaleElements() (r, g, b, a float32) { 176 return c.scale[0], c.scale[1], c.scale[2], c.scale[3] 177 } 178 179 func (c *colorMImplBodyTranslate) scaleElements() (r, g, b, a float32) { 180 return c.body[0], c.body[5], c.body[10], c.body[15] 181 } 182 183 func colorToFloat32s(clr color.Color) (float32, float32, float32, float32) { 184 r, g, b, a := clr.RGBA() 185 rf, gf, bf, af := float32(0.0), float32(0.0), float32(0.0), float32(0.0) 186 // Unmultiply alpha 187 if a > 0 { 188 rf = float32(r) / float32(a) 189 gf = float32(g) / float32(a) 190 bf = float32(b) / float32(a) 191 af = float32(a) / 0xffff 192 } 193 return rf, gf, bf, af 194 } 195 196 func (c ColorMIdentity) Apply(clr color.Color) color.Color { 197 rf, gf, bf, af := colorToFloat32s(clr) 198 return color.NRGBA64{ 199 R: uint16(rf * 0xffff), 200 G: uint16(gf * 0xffff), 201 B: uint16(bf * 0xffff), 202 A: uint16(af * 0xffff), 203 } 204 } 205 206 func (c colorMImplScale) Apply(clr color.Color) color.Color { 207 rf, gf, bf, af := colorToFloat32s(clr) 208 rf *= c.scale[0] 209 gf *= c.scale[1] 210 bf *= c.scale[2] 211 af *= c.scale[3] 212 rf = clamp(rf) 213 gf = clamp(gf) 214 bf = clamp(bf) 215 af = clamp(af) 216 return color.NRGBA64{ 217 R: uint16(rf * 0xffff), 218 G: uint16(gf * 0xffff), 219 B: uint16(bf * 0xffff), 220 A: uint16(af * 0xffff), 221 } 222 } 223 224 func (c *colorMImplBodyTranslate) Apply(clr color.Color) color.Color { 225 rf, gf, bf, af := colorToFloat32s(clr) 226 eb := &c.body 227 et := &c.translate 228 rf2 := eb[0]*rf + eb[4]*gf + eb[8]*bf + eb[12]*af + et[0] 229 gf2 := eb[1]*rf + eb[5]*gf + eb[9]*bf + eb[13]*af + et[1] 230 bf2 := eb[2]*rf + eb[6]*gf + eb[10]*bf + eb[14]*af + et[2] 231 af2 := eb[3]*rf + eb[7]*gf + eb[11]*bf + eb[15]*af + et[3] 232 rf2 = clamp(rf2) 233 gf2 = clamp(gf2) 234 bf2 = clamp(bf2) 235 af2 = clamp(af2) 236 return color.NRGBA64{ 237 R: uint16(rf2 * 0xffff), 238 G: uint16(gf2 * 0xffff), 239 B: uint16(bf2 * 0xffff), 240 A: uint16(af2 * 0xffff), 241 } 242 } 243 244 func (c ColorMIdentity) Elements(body *[16]float32, translate *[4]float32) { 245 body[0] = 1 246 body[1] = 0 247 body[2] = 0 248 body[3] = 0 249 body[4] = 0 250 body[5] = 1 251 body[6] = 0 252 body[7] = 0 253 body[8] = 0 254 body[9] = 0 255 body[10] = 1 256 body[11] = 0 257 body[12] = 0 258 body[13] = 0 259 body[14] = 0 260 body[15] = 1 261 translate[0] = 0 262 translate[1] = 0 263 translate[2] = 0 264 translate[3] = 0 265 } 266 267 func (c colorMImplScale) Elements(body *[16]float32, translate *[4]float32) { 268 body[0] = c.scale[0] 269 body[1] = 0 270 body[2] = 0 271 body[3] = 0 272 body[4] = 0 273 body[5] = c.scale[1] 274 body[6] = 0 275 body[7] = 0 276 body[8] = 0 277 body[9] = 0 278 body[10] = c.scale[2] 279 body[11] = 0 280 body[12] = 0 281 body[13] = 0 282 body[14] = 0 283 body[15] = c.scale[3] 284 translate[0] = 0 285 translate[1] = 0 286 translate[2] = 0 287 translate[3] = 0 288 } 289 290 func (c *colorMImplBodyTranslate) Elements(body *[16]float32, translate *[4]float32) { 291 copy(body[:], c.body[:]) 292 copy(translate[:], c.translate[:]) 293 } 294 295 func (c *colorMImplBodyTranslate) det() float32 { 296 m00 := c.body[0] 297 m01 := c.body[1] 298 m02 := c.body[2] 299 m03 := c.body[3] 300 m10 := c.body[4] 301 m11 := c.body[5] 302 m12 := c.body[6] 303 m13 := c.body[7] 304 m20 := c.body[8] 305 m21 := c.body[9] 306 m22 := c.body[10] 307 m23 := c.body[11] 308 m30 := c.body[12] 309 m31 := c.body[13] 310 m32 := c.body[14] 311 m33 := c.body[15] 312 313 b234234 := m22*m33 - m23*m32 314 b134234 := m21*m33 - m23*m31 315 b124234 := m21*m32 - m22*m31 316 b034234 := m20*m33 - m23*m30 317 b024234 := m20*m32 - m22*m30 318 b014234 := m20*m31 - m21*m30 319 320 return m00*(m11*b234234-m12*b134234+m13*b124234) - 321 m01*(m10*b234234-m12*b034234+m13*b024234) + 322 m02*(m10*b134234-m11*b034234+m13*b014234) - 323 m03*(m10*b124234-m11*b024234+m12*b014234) 324 } 325 326 func (c ColorMIdentity) IsInvertible() bool { 327 return true 328 } 329 330 func (c colorMImplScale) IsInvertible() bool { 331 return c.scale[0] != 0 && c.scale[1] != 0 && c.scale[2] != 0 && c.scale[3] != 0 332 } 333 334 func (c *colorMImplBodyTranslate) IsInvertible() bool { 335 return c.det() != 0 336 } 337 338 func (c ColorMIdentity) Invert() ColorM { 339 return c 340 } 341 342 func (c colorMImplScale) Invert() ColorM { 343 return colorMImplScale{ 344 scale: [4]float32{ 345 1 / c.scale[0], 346 1 / c.scale[1], 347 1 / c.scale[2], 348 1 / c.scale[3], 349 }, 350 } 351 } 352 353 func (c *colorMImplBodyTranslate) Invert() ColorM { 354 det := c.det() 355 if det == 0 { 356 panic("affine: c is not invertible") 357 } 358 359 m00 := c.body[0] 360 m01 := c.body[1] 361 m02 := c.body[2] 362 m03 := c.body[3] 363 364 m10 := c.body[4] 365 m11 := c.body[5] 366 m12 := c.body[6] 367 m13 := c.body[7] 368 369 m20 := c.body[8] 370 m21 := c.body[9] 371 m22 := c.body[10] 372 m23 := c.body[11] 373 374 m30 := c.body[12] 375 m31 := c.body[13] 376 m32 := c.body[14] 377 m33 := c.body[15] 378 379 m40 := c.translate[0] 380 m41 := c.translate[1] 381 m42 := c.translate[2] 382 m43 := c.translate[3] 383 384 a2334 := m32*m43 - m33*m42 385 a1334 := m31*m43 - m33*m41 386 a1234 := m31*m42 - m32*m41 387 a0334 := m30*m43 - m33*m40 388 a0234 := m30*m42 - m32*m40 389 a0134 := m30*m41 - m31*m40 390 a2324 := m22*m43 - m23*m42 391 a1324 := m21*m43 - m23*m41 392 a1224 := m21*m42 - m22*m41 393 a0324 := m20*m43 - m23*m40 394 a0224 := m20*m42 - m22*m40 395 a0124 := m20*m41 - m21*m40 396 397 b234234 := m22*m33 - m23*m32 398 b134234 := m21*m33 - m23*m31 399 b124234 := m21*m32 - m22*m31 400 b123234 := m21*a2334 - m22*a1334 + m23*a1234 401 b034234 := m20*m33 - m23*m30 402 b024234 := m20*m32 - m22*m30 403 b023234 := m20*a2334 - m22*a0334 + m23*a0234 404 b014234 := m20*m31 - m21*m30 405 b013234 := m20*a1334 - m21*a0334 + m23*a0134 406 b012234 := m20*a1234 - m21*a0234 + m22*a0134 407 b234134 := m12*m33 - m13*m32 408 b134134 := m11*m33 - m13*m31 409 b124134 := m11*m32 - m12*m31 410 b123134 := m11*a2334 - m12*a1334 + m13*a1234 411 b234124 := m12*m23 - m13*m22 412 b134124 := m11*m23 - m13*m21 413 b124124 := m11*m22 - m12*m21 414 b123124 := m11*a2324 - m12*a1324 + m13*a1224 415 b034134 := m10*m33 - m13*m30 416 b024134 := m10*m32 - m12*m30 417 b023134 := m10*a2334 - m12*a0334 + m13*a0234 418 b034124 := m10*m23 - m13*m20 419 b024124 := m10*m22 - m12*m20 420 b023124 := m10*a2324 - m12*a0324 + m13*a0224 421 b014134 := m10*m31 - m11*m30 422 b013134 := m10*a1334 - m11*a0334 + m13*a0134 423 b014124 := m10*m21 - m11*m20 424 b013124 := m10*a1324 - m11*a0324 + m13*a0124 425 b012134 := m10*a1234 - m11*a0234 + m12*a0134 426 b012124 := m10*a1224 - m11*a0224 + m12*a0124 427 428 m := &colorMImplBodyTranslate{ 429 body: colorMIdentityBody, 430 } 431 432 idet := 1 / det 433 434 m.body[0] = idet * (m11*b234234 - m12*b134234 + m13*b124234) 435 m.body[1] = idet * -(m01*b234234 - m02*b134234 + m03*b124234) 436 m.body[2] = idet * (m01*b234134 - m02*b134134 + m03*b124134) 437 m.body[3] = idet * -(m01*b234124 - m02*b134124 + m03*b124124) 438 m.body[4] = idet * -(m10*b234234 - m12*b034234 + m13*b024234) 439 m.body[5] = idet * (m00*b234234 - m02*b034234 + m03*b024234) 440 m.body[6] = idet * -(m00*b234134 - m02*b034134 + m03*b024134) 441 m.body[7] = idet * (m00*b234124 - m02*b034124 + m03*b024124) 442 m.body[8] = idet * (m10*b134234 - m11*b034234 + m13*b014234) 443 m.body[9] = idet * -(m00*b134234 - m01*b034234 + m03*b014234) 444 m.body[10] = idet * (m00*b134134 - m01*b034134 + m03*b014134) 445 m.body[11] = idet * -(m00*b134124 - m01*b034124 + m03*b014124) 446 m.body[12] = idet * -(m10*b124234 - m11*b024234 + m12*b014234) 447 m.body[13] = idet * (m00*b124234 - m01*b024234 + m02*b014234) 448 m.body[14] = idet * -(m00*b124134 - m01*b024134 + m02*b014134) 449 m.body[15] = idet * (m00*b124124 - m01*b024124 + m02*b014124) 450 m.translate[0] = idet * (m10*b123234 - m11*b023234 + m12*b013234 - m13*b012234) 451 m.translate[1] = idet * -(m00*b123234 - m01*b023234 + m02*b013234 - m03*b012234) 452 m.translate[2] = idet * (m00*b123134 - m01*b023134 + m02*b013134 - m03*b012134) 453 m.translate[3] = idet * -(m00*b123124 - m01*b023124 + m02*b013124 - m03*b012124) 454 455 return m 456 } 457 458 func (c ColorMIdentity) At(i, j int) float32 { 459 if i == j { 460 return 1 461 } 462 return 0 463 } 464 465 func (c colorMImplScale) At(i, j int) float32 { 466 if i == j { 467 return c.scale[i] 468 } 469 return 0 470 } 471 472 func (c *colorMImplBodyTranslate) At(i, j int) float32 { 473 if j < ColorMDim-1 { 474 return c.body[i+j*(ColorMDim-1)] 475 } 476 return c.translate[i] 477 } 478 479 // ColorMSetElement sets an element at (i, j). 480 func ColorMSetElement(c ColorM, i, j int, element float32) ColorM { 481 newImpl := &colorMImplBodyTranslate{ 482 body: colorMIdentityBody, 483 } 484 if !c.IsIdentity() { 485 c.Elements(&newImpl.body, &newImpl.translate) 486 } 487 if j < (ColorMDim - 1) { 488 newImpl.body[i+j*(ColorMDim-1)] = element 489 } else { 490 newImpl.translate[i] = element 491 } 492 return newImpl 493 } 494 495 func (c ColorMIdentity) Equals(other ColorM) bool { 496 return other.IsIdentity() 497 } 498 499 func (c colorMImplScale) Equals(other ColorM) bool { 500 if !other.ScaleOnly() { 501 return false 502 } 503 504 r, g, b, a := other.scaleElements() 505 if c.scale[0] != r { 506 return false 507 } 508 if c.scale[1] != g { 509 return false 510 } 511 if c.scale[2] != b { 512 return false 513 } 514 if c.scale[3] != a { 515 return false 516 } 517 return true 518 } 519 520 func (c *colorMImplBodyTranslate) Equals(other ColorM) bool { 521 var lhsb [16]float32 522 var lhst [4]float32 523 other.Elements(&lhsb, &lhst) 524 rhsb := &c.body 525 rhst := &c.translate 526 return lhsb == *rhsb && lhst == *rhst 527 } 528 529 func (c ColorMIdentity) Concat(other ColorM) ColorM { 530 return other 531 } 532 533 func (c colorMImplScale) Concat(other ColorM) ColorM { 534 if other.IsIdentity() { 535 return c 536 } 537 538 if other.ScaleOnly() { 539 return c.Scale(other.scaleElements()) 540 } 541 542 var lhsb [16]float32 543 var lhst [4]float32 544 other.Elements(&lhsb, &lhst) 545 s := &c.scale 546 return &colorMImplBodyTranslate{ 547 body: [...]float32{ 548 lhsb[0] * s[0], lhsb[1] * s[0], lhsb[2] * s[0], lhsb[3] * s[0], 549 lhsb[4] * s[1], lhsb[5] * s[1], lhsb[6] * s[1], lhsb[7] * s[1], 550 lhsb[8] * s[2], lhsb[9] * s[2], lhsb[10] * s[2], lhsb[11] * s[2], 551 lhsb[12] * s[3], lhsb[13] * s[3], lhsb[14] * s[3], lhsb[15] * s[3], 552 }, 553 translate: lhst, 554 } 555 } 556 557 func (c *colorMImplBodyTranslate) Concat(other ColorM) ColorM { 558 if other.IsIdentity() { 559 return c 560 } 561 562 var lhsb [16]float32 563 var lhst [4]float32 564 other.Elements(&lhsb, &lhst) 565 rhsb := &c.body 566 rhst := &c.translate 567 568 return &colorMImplBodyTranslate{ 569 // TODO: This is a temporary hack to calculate multiply of transposed matrices. 570 // Fix mulSquare implmentation and swap the arguments. 571 body: mulSquare(rhsb, &lhsb, ColorMDim-1), 572 translate: [...]float32{ 573 lhsb[0]*rhst[0] + lhsb[4]*rhst[1] + lhsb[8]*rhst[2] + lhsb[12]*rhst[3] + lhst[0], 574 lhsb[1]*rhst[0] + lhsb[5]*rhst[1] + lhsb[9]*rhst[2] + lhsb[13]*rhst[3] + lhst[1], 575 lhsb[2]*rhst[0] + lhsb[6]*rhst[1] + lhsb[10]*rhst[2] + lhsb[14]*rhst[3] + lhst[2], 576 lhsb[3]*rhst[0] + lhsb[7]*rhst[1] + lhsb[11]*rhst[2] + lhsb[15]*rhst[3] + lhst[3], 577 }, 578 } 579 } 580 581 func (c ColorMIdentity) Scale(r, g, b, a float32) ColorM { 582 if r == 1 && g == 1 && b == 1 && a == 1 { 583 return c 584 } 585 586 return colorMImplScale{ 587 scale: [...]float32{r, g, b, a}, 588 } 589 } 590 591 func (c colorMImplScale) Scale(r, g, b, a float32) ColorM { 592 if r == 1 && g == 1 && b == 1 && a == 1 { 593 return c 594 } 595 596 return colorMImplScale{ 597 scale: [...]float32{ 598 c.scale[0] * r, 599 c.scale[1] * g, 600 c.scale[2] * b, 601 c.scale[3] * a, 602 }, 603 } 604 } 605 606 func (c *colorMImplBodyTranslate) Scale(r, g, b, a float32) ColorM { 607 if r == 1 && g == 1 && b == 1 && a == 1 { 608 return c 609 } 610 611 if c.ScaleOnly() { 612 sr, sg, sb, sa := c.scaleElements() 613 return colorMImplScale{ 614 scale: [...]float32{r * sr, g * sg, b * sb, a * sa}, 615 } 616 } 617 618 eb := c.body 619 for i := 0; i < ColorMDim-1; i++ { 620 eb[i*(ColorMDim-1)] *= r 621 eb[i*(ColorMDim-1)+1] *= g 622 eb[i*(ColorMDim-1)+2] *= b 623 eb[i*(ColorMDim-1)+3] *= a 624 } 625 626 et := [...]float32{ 627 c.translate[0] * r, 628 c.translate[1] * g, 629 c.translate[2] * b, 630 c.translate[3] * a, 631 } 632 633 return &colorMImplBodyTranslate{ 634 body: eb, 635 translate: et, 636 } 637 } 638 639 func (c ColorMIdentity) Translate(r, g, b, a float32) ColorM { 640 if r == 0 && g == 0 && b == 0 && a == 0 { 641 return c 642 } 643 644 return &colorMImplBodyTranslate{ 645 body: colorMIdentityBody, 646 translate: [...]float32{r, g, b, a}, 647 } 648 } 649 650 func (c colorMImplScale) Translate(r, g, b, a float32) ColorM { 651 if r == 0 && g == 0 && b == 0 && a == 0 { 652 return c 653 } 654 655 return &colorMImplBodyTranslate{ 656 body: [...]float32{ 657 c.scale[0], 0, 0, 0, 658 0, c.scale[1], 0, 0, 659 0, 0, c.scale[2], 0, 660 0, 0, 0, c.scale[3], 661 }, 662 translate: [...]float32{r, g, b, a}, 663 } 664 } 665 666 func (c *colorMImplBodyTranslate) Translate(r, g, b, a float32) ColorM { 667 if r == 0 && g == 0 && b == 0 && a == 0 { 668 return c 669 } 670 671 es := c.translate 672 es[0] += r 673 es[1] += g 674 es[2] += b 675 es[3] += a 676 return &colorMImplBodyTranslate{ 677 body: c.body, 678 translate: es, 679 } 680 } 681 682 var ( 683 // The YCbCr value ranges are: 684 // Y: [ 0 - 1 ] 685 // Cb: [-0.5 - 0.5] 686 // Cr: [-0.5 - 0.5] 687 688 rgbToYCbCr = &colorMImplBodyTranslate{ 689 body: [...]float32{ 690 0.2990, -0.1687, 0.5000, 0, 691 0.5870, -0.3313, -0.4187, 0, 692 0.1140, 0.5000, -0.0813, 0, 693 0, 0, 0, 1, 694 }, 695 } 696 yCbCrToRgb = &colorMImplBodyTranslate{ 697 body: [...]float32{ 698 1, 1, 1, 0, 699 0, -0.34414, 1.77200, 0, 700 1.40200, -0.71414, 0, 0, 701 0, 0, 0, 1, 702 }, 703 } 704 ) 705 706 // ChangeHSV changes HSV (Hue-Saturation-Value) elements. 707 // hueTheta is a radian value to ratate hue. 708 // saturationScale is a value to scale saturation. 709 // valueScale is a value to scale value (a.k.a. brightness). 710 // 711 // This conversion uses RGB to/from YCrCb conversion. 712 func ChangeHSV(c ColorM, hueTheta float64, saturationScale float32, valueScale float32) ColorM { 713 sin, cos := math.Sincos(hueTheta) 714 s32, c32 := float32(sin), float32(cos) 715 c = c.Concat(rgbToYCbCr) 716 c = c.Concat(&colorMImplBodyTranslate{ 717 body: [...]float32{ 718 1, 0, 0, 0, 719 0, c32, s32, 0, 720 0, -s32, c32, 0, 721 0, 0, 0, 1, 722 }, 723 }) 724 s := saturationScale 725 v := valueScale 726 c = c.Scale(v, s*v, s*v, 1) 727 c = c.Concat(yCbCrToRgb) 728 return c 729 } 730 731 type cachedScalingColorMKey struct { 732 r, g, b, a float32 733 } 734 735 type cachedScalingColorMValue struct { 736 c *colorMImplScale 737 atime uint64 738 }