You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							204 lines
						
					
					
						
							4.7 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							204 lines
						
					
					
						
							4.7 KiB
						
					
					
				
								package models
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"github.com/lucasb-eyer/go-colorful"
							 | 
						|
									"math"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								const eps = 0.0001
							 | 
						|
								const epsSquare = eps * eps
							 | 
						|
								
							 | 
						|
								type ColorGamut struct {
							 | 
						|
									Red   ColorXY `json:"red"`
							 | 
						|
									Green ColorXY `json:"green"`
							 | 
						|
									Blue  ColorXY `json:"blue"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) side(x1, y1, x2, y2, x, y float64) float64 {
							 | 
						|
									return (y2-y1)*(x-x1) + (-x2+x1)*(y-y1)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) naiveContains(color ColorXY) bool {
							 | 
						|
									x, y := color.X, color.Y
							 | 
						|
									x1, y1 := cg.Red.X, cg.Red.Y
							 | 
						|
									x2, y2 := cg.Green.X, cg.Green.Y
							 | 
						|
									x3, y3 := cg.Blue.X, cg.Blue.Y
							 | 
						|
								
							 | 
						|
									checkSide1 := cg.side(x1, y1, x2, y2, x, y) < 0
							 | 
						|
									checkSide2 := cg.side(x2, y2, x3, y3, x, y) < 0
							 | 
						|
									checkSide3 := cg.side(x3, y3, x1, y1, x, y) < 0
							 | 
						|
								
							 | 
						|
									return checkSide1 && checkSide2 && checkSide3
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) getBounds() (xMin, xMax, yMin, yMax float64) {
							 | 
						|
									x1, y1 := cg.Red.X, cg.Red.Y
							 | 
						|
									x2, y2 := cg.Green.X, cg.Green.Y
							 | 
						|
									x3, y3 := cg.Blue.X, cg.Blue.Y
							 | 
						|
								
							 | 
						|
									xMin = math.Min(x1, math.Min(x2, x3)) - eps
							 | 
						|
									xMax = math.Max(x1, math.Max(x2, x3)) + eps
							 | 
						|
									yMin = math.Min(y1, math.Min(y2, y3)) - eps
							 | 
						|
									yMax = math.Max(y1, math.Max(y2, y3)) + eps
							 | 
						|
								
							 | 
						|
									return
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) isInBounds(color ColorXY) bool {
							 | 
						|
									x, y := color.X, color.Y
							 | 
						|
									xMin, xMax, yMin, yMax := cg.getBounds()
							 | 
						|
								
							 | 
						|
									return !(x < xMin || xMax < x || y < yMin || yMax < y)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) distanceSquarePointToSegment(x1, y1, x2, y2, x, y float64) float64 {
							 | 
						|
									sqLength1 := (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)
							 | 
						|
									dotProduct := ((x-x1)*(x2-x1) + (y-y1)*(y2-y1)) / sqLength1
							 | 
						|
									if dotProduct < 0 {
							 | 
						|
										return (x-x1)*(x-x1) + (y-y1)*(y-y1)
							 | 
						|
									} else if dotProduct <= 1 {
							 | 
						|
										sqLength2 := (x1-x)*(x1-x) + (y1-y)*(y1-y)
							 | 
						|
										return sqLength2 - dotProduct*dotProduct*sqLength1
							 | 
						|
									} else {
							 | 
						|
										return (x-x2)*(x-x2) + (y-y2)*(y-y2)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) atTheEdge(color ColorXY) bool {
							 | 
						|
									x, y := color.X, color.Y
							 | 
						|
									x1, y1 := cg.Red.X, cg.Red.Y
							 | 
						|
									x2, y2 := cg.Green.X, cg.Green.Y
							 | 
						|
									x3, y3 := cg.Blue.X, cg.Blue.Y
							 | 
						|
								
							 | 
						|
									if cg.distanceSquarePointToSegment(x1, y1, x2, y2, x, y) <= epsSquare {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
									if cg.distanceSquarePointToSegment(x2, y2, x3, y3, x, y) <= epsSquare {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
									if cg.distanceSquarePointToSegment(x3, y3, x1, y1, x, y) <= epsSquare {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return false
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) Contains(color ColorXY) bool {
							 | 
						|
									if cg == nil {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return cg.isInBounds(color) && (cg.naiveContains(color) || cg.atTheEdge(color))
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (cg *ColorGamut) Conform(color ColorXY) ColorXY {
							 | 
						|
									if cg.Contains(color) {
							 | 
						|
										return color
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									var best *ColorXY
							 | 
						|
								
							 | 
						|
									xMin, xMax, yMin, yMax := cg.getBounds()
							 | 
						|
								
							 | 
						|
									for x := xMin; x < xMax; x += 0.001 {
							 | 
						|
										for y := yMin; y < yMax; y += 0.001 {
							 | 
						|
											color2 := ColorXY{X: x, Y: y}
							 | 
						|
								
							 | 
						|
											if cg.Contains(color2) {
							 | 
						|
												if best == nil || color.DistanceTo(color2) < color.DistanceTo(*best) {
							 | 
						|
													best = &color2
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if best == nil {
							 | 
						|
										centerX := (cg.Red.X + cg.Green.X + cg.Blue.X) / 3
							 | 
						|
										centerY := (cg.Red.Y + cg.Green.Y + cg.Blue.Y) / 3
							 | 
						|
								
							 | 
						|
										stepX := (centerX - color.X) / 5000
							 | 
						|
										stepY := (centerY - color.Y) / 5000
							 | 
						|
								
							 | 
						|
										for !cg.Contains(color) {
							 | 
						|
											color.X += stepX
							 | 
						|
											color.Y += stepY
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										return color
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for x := best.X - 0.001; x < best.X+0.001; x += 0.0002 {
							 | 
						|
										for y := best.Y - 0.001; y < best.Y+0.001; y += 0.0002 {
							 | 
						|
											color2 := ColorXY{X: x, Y: y}
							 | 
						|
								
							 | 
						|
											if cg.atTheEdge(color2) {
							 | 
						|
												if best == nil || color.DistanceTo(color2) < color.DistanceTo(*best) {
							 | 
						|
													best = &color2
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for x := best.X - 0.0001; x < best.X+0.0001; x += 0.00003 {
							 | 
						|
										for y := best.Y - 0.0001; y < best.Y+0.0001; y += 0.00003 {
							 | 
						|
											color2 := ColorXY{X: x, Y: y}
							 | 
						|
								
							 | 
						|
											if cg.atTheEdge(color2) {
							 | 
						|
												if best == nil || color.DistanceTo(color2) < color.DistanceTo(*best) {
							 | 
						|
													best = &color2
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return *best
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type ColorXY struct {
							 | 
						|
									X float64 `json:"x"`
							 | 
						|
									Y float64 `json:"y"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (xy ColorXY) DistanceTo(other ColorXY) float64 {
							 | 
						|
									return math.Sqrt(math.Pow(xy.X-other.X, 2) + math.Pow(xy.Y-other.Y, 2))
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (xy ColorXY) Round() ColorXY {
							 | 
						|
									return ColorXY{
							 | 
						|
										X: math.Round(xy.X*10000) / 10000,
							 | 
						|
										Y: math.Round(xy.Y*10000) / 10000,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func hsToXY(hue, sat float64) ColorXY {
							 | 
						|
									c := colorful.Hsv(hue, sat, 1)
							 | 
						|
									red255, green255, blue255 := c.RGB255()
							 | 
						|
									red := float64(red255) / 255.0
							 | 
						|
									green := float64(green255) / 255.0
							 | 
						|
									blue := float64(blue255) / 255.0
							 | 
						|
								
							 | 
						|
									return screenRGBToXY(red, green, blue)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func rgbToXY(red float64, green float64, blue float64) ColorXY {
							 | 
						|
									x := red*0.649926 + green*0.103455 + blue*0.197109
							 | 
						|
									y := red*0.234327 + green*0.743075 + blue*0.022598
							 | 
						|
									z := green*0.053077 + blue*1.035763
							 | 
						|
								
							 | 
						|
									return ColorXY{
							 | 
						|
										X: x / (x + y + z),
							 | 
						|
										Y: y / (x + y + z),
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func screenRGBToXY(red, green, blue float64) ColorXY {
							 | 
						|
									for _, component := range []*float64{&red, &green, &blue} {
							 | 
						|
										if *component > 0.04045 {
							 | 
						|
											*component = math.Pow((*component+0.055)/(1.055), 2.4)
							 | 
						|
										} else {
							 | 
						|
											*component /= 12.92
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return rgbToXY(red, green, blue)
							 | 
						|
								}
							 |