github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/colors.go (about)

     1  package gift
     2  
     3  import (
     4  	"image"
     5  	"image/draw"
     6  	"math"
     7  )
     8  
     9  func prepareLut(lutSize int, fn func(float32) float32) []float32 {
    10  	lut := make([]float32, lutSize)
    11  	q := 1 / float32(lutSize-1)
    12  	for v := 0; v < lutSize; v++ {
    13  		u := float32(v) * q
    14  		lut[v] = fn(u)
    15  	}
    16  	return lut
    17  }
    18  
    19  func getFromLut(lut []float32, u float32) float32 {
    20  	v := int(u*float32(len(lut)-1) + 0.5)
    21  	return lut[v]
    22  }
    23  
    24  type colorchanFilter struct {
    25  	fn  func(float32) float32
    26  	lut bool
    27  }
    28  
    29  func (p *colorchanFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
    30  	dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
    31  	return
    32  }
    33  
    34  func (p *colorchanFilter) Draw(dst draw.Image, src image.Image, options *Options) {
    35  	if options == nil {
    36  		options = &defaultOptions
    37  	}
    38  
    39  	srcb := src.Bounds()
    40  	dstb := dst.Bounds()
    41  	pixGetter := newPixelGetter(src)
    42  	pixSetter := newPixelSetter(dst)
    43  
    44  	var useLut bool
    45  	var lut []float32
    46  
    47  	useLut = false
    48  	if p.lut {
    49  		var lutSize int
    50  
    51  		it := pixGetter.imgType
    52  		if it == itNRGBA || it == itRGBA || it == itGray || it == itYCbCr {
    53  			lutSize = 0xff + 1
    54  		} else {
    55  			lutSize = 0xffff + 1
    56  		}
    57  
    58  		numCalculations := srcb.Dx() * srcb.Dy() * 3
    59  		if numCalculations > lutSize*2 {
    60  			useLut = true
    61  			lut = prepareLut(lutSize, p.fn)
    62  		}
    63  	}
    64  
    65  	parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
    66  		for y := pmin; y < pmax; y++ {
    67  			for x := srcb.Min.X; x < srcb.Max.X; x++ {
    68  				px := pixGetter.getPixel(x, y)
    69  				if useLut {
    70  					px.R = getFromLut(lut, px.R)
    71  					px.G = getFromLut(lut, px.G)
    72  					px.B = getFromLut(lut, px.B)
    73  				} else {
    74  					px.R = p.fn(px.R)
    75  					px.G = p.fn(px.G)
    76  					px.B = p.fn(px.B)
    77  				}
    78  				pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, px)
    79  			}
    80  		}
    81  	})
    82  }
    83  
    84  // Invert creates a filter that negates the colors of an image.
    85  func Invert() Filter {
    86  	return &colorchanFilter{
    87  		fn: func(x float32) float32 {
    88  			return 1.0 - x
    89  		},
    90  		lut: false,
    91  	}
    92  }
    93  
    94  // ColorspaceSRGBToLinear creates a filter that converts the colors of an image from sRGB to linear RGB.
    95  func ColorspaceSRGBToLinear() Filter {
    96  	return &colorchanFilter{
    97  		fn: func(x float32) float32 {
    98  			if x <= 0.04045 {
    99  				return x / 12.92
   100  			}
   101  			return float32(math.Pow(float64((x+0.055)/1.055), 2.4))
   102  		},
   103  		lut: true,
   104  	}
   105  }
   106  
   107  // ColorspaceLinearToSRGB creates a filter that converts the colors of an image from linear RGB to sRGB.
   108  func ColorspaceLinearToSRGB() Filter {
   109  	return &colorchanFilter{
   110  		fn: func(x float32) float32 {
   111  			if x <= 0.0031308 {
   112  				return x * 12.92
   113  			}
   114  			return float32(1.055*math.Pow(float64(x), 1.0/2.4) - 0.055)
   115  		},
   116  		lut: true,
   117  	}
   118  }
   119  
   120  // Gamma creates a filter that performs a gamma correction on an image.
   121  // The gamma parameter must be positive. Gamma = 1.0 gives the original image.
   122  // Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
   123  func Gamma(gamma float32) Filter {
   124  	e := 1.0 / maxf32(gamma, 1.0e-5)
   125  	return &colorchanFilter{
   126  		fn: func(x float32) float32 {
   127  			return powf32(x, e)
   128  		},
   129  		lut: true,
   130  	}
   131  }
   132  
   133  func sigmoid(a, b, x float32) float32 {
   134  	return 1 / (1 + expf32(b*(a-x)))
   135  }
   136  
   137  // Sigmoid creates a filter that changes the contrast of an image using a sigmoidal function and returns the adjusted image.
   138  // It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
   139  // The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
   140  // The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
   141  // If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
   142  //
   143  // Example:
   144  //
   145  //	g := gift.New(
   146  //		gift.Sigmoid(0.5, 3.0),
   147  //	)
   148  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   149  //	g.Draw(dst, src)
   150  //
   151  func Sigmoid(midpoint, factor float32) Filter {
   152  	a := minf32(maxf32(midpoint, 0.0), 1.0)
   153  	b := absf32(factor)
   154  	sig0 := sigmoid(a, b, 0)
   155  	sig1 := sigmoid(a, b, 1)
   156  	e := float32(1.0e-5)
   157  
   158  	return &colorchanFilter{
   159  		fn: func(x float32) float32 {
   160  			if factor == 0 {
   161  				return x
   162  			} else if factor > 0 {
   163  				sig := sigmoid(a, b, x)
   164  				return (sig - sig0) / (sig1 - sig0)
   165  			} else {
   166  				arg := minf32(maxf32((sig1-sig0)*x+sig0, e), 1.0-e)
   167  				return a - logf32(1.0/arg-1.0)/b
   168  			}
   169  		},
   170  		lut: true,
   171  	}
   172  }
   173  
   174  // Contrast creates a filter that changes the contrast of an image.
   175  // The percentage parameter must be in range (-100, 100). The percentage = 0 gives the original image.
   176  // The percentage = -100 gives solid grey image. The percentage = 100 gives an overcontrasted image.
   177  func Contrast(percentage float32) Filter {
   178  	if percentage == 0 {
   179  		return &copyimageFilter{}
   180  	}
   181  
   182  	p := 1 + minf32(maxf32(percentage, -100.0), 100.0)/100.0
   183  
   184  	return &colorchanFilter{
   185  		fn: func(x float32) float32 {
   186  			if 0 <= p && p <= 1 {
   187  				return 0.5 + (x-0.5)*p
   188  			} else if 1 < p && p < 2 {
   189  				return 0.5 + (x-0.5)*(1/(2.0-p))
   190  			} else {
   191  				if x < 0.5 {
   192  					return 0.0
   193  				}
   194  				return 1.0
   195  			}
   196  		},
   197  		lut: false,
   198  	}
   199  }
   200  
   201  // Brightness creates a filter that changes the brightness of an image.
   202  // The percentage parameter must be in range (-100, 100). The percentage = 0 gives the original image.
   203  // The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
   204  func Brightness(percentage float32) Filter {
   205  	if percentage == 0 {
   206  		return &copyimageFilter{}
   207  	}
   208  
   209  	shift := minf32(maxf32(percentage, -100.0), 100.0) / 100.0
   210  
   211  	return &colorchanFilter{
   212  		fn: func(x float32) float32 {
   213  			return x + shift
   214  		},
   215  		lut: false,
   216  	}
   217  }
   218  
   219  type colorFilter struct {
   220  	fn func(pixel) pixel
   221  }
   222  
   223  func (p *colorFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
   224  	dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
   225  	return
   226  }
   227  
   228  func (p *colorFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   229  	if options == nil {
   230  		options = &defaultOptions
   231  	}
   232  
   233  	srcb := src.Bounds()
   234  	dstb := dst.Bounds()
   235  	pixGetter := newPixelGetter(src)
   236  	pixSetter := newPixelSetter(dst)
   237  
   238  	parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
   239  		for y := pmin; y < pmax; y++ {
   240  			for x := srcb.Min.X; x < srcb.Max.X; x++ {
   241  				px := pixGetter.getPixel(x, y)
   242  				pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, p.fn(px))
   243  			}
   244  		}
   245  	})
   246  }
   247  
   248  // Grayscale creates a filter that produces a grayscale version of an image.
   249  func Grayscale() Filter {
   250  	return &colorFilter{
   251  		fn: func(px pixel) pixel {
   252  			y := 0.299*px.R + 0.587*px.G + 0.114*px.B
   253  			return pixel{y, y, y, px.A}
   254  		},
   255  	}
   256  }
   257  
   258  // Sepia creates a filter that produces a sepia-toned version of an image.
   259  // The percentage parameter specifies how much the image should be adjusted. It must be in the range (0, 100)
   260  //
   261  // Example:
   262  //
   263  //	g := gift.New(
   264  //		gift.Sepia(100),
   265  //	)
   266  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   267  //	g.Draw(dst, src)
   268  //
   269  func Sepia(percentage float32) Filter {
   270  	adjustAmount := minf32(maxf32(percentage, 0.0), 100.0) / 100.0
   271  	rr := 1.0 - 0.607*adjustAmount
   272  	rg := 0.769 * adjustAmount
   273  	rb := 0.189 * adjustAmount
   274  	gr := 0.349 * adjustAmount
   275  	gg := 1.0 - 0.314*adjustAmount
   276  	gb := 0.168 * adjustAmount
   277  	br := 0.272 * adjustAmount
   278  	bg := 0.534 * adjustAmount
   279  	bb := 1.0 - 0.869*adjustAmount
   280  	return &colorFilter{
   281  		fn: func(px pixel) pixel {
   282  			r := px.R*rr + px.G*rg + px.B*rb
   283  			g := px.R*gr + px.G*gg + px.B*gb
   284  			b := px.R*br + px.G*bg + px.B*bb
   285  			return pixel{r, g, b, px.A}
   286  		},
   287  	}
   288  }
   289  
   290  func convertHSLToRGB(h, s, l float32) (float32, float32, float32) {
   291  	if s == 0.0 {
   292  		return l, l, l
   293  	}
   294  
   295  	_v := func(p, q, t float32) float32 {
   296  		if t < 0.0 {
   297  			t += 1.0
   298  		}
   299  		if t > 1.0 {
   300  			t -= 1.0
   301  		}
   302  		if t < 1.0/6.0 {
   303  			return p + (q-p)*6.0*t
   304  		}
   305  		if t < 1.0/2.0 {
   306  			return q
   307  		}
   308  		if t < 2.0/3.0 {
   309  			return p + (q-p)*(2.0/3.0-t)*6.0
   310  		}
   311  		return p
   312  	}
   313  
   314  	var p, q float32
   315  	if l < 0.5 {
   316  		q = l * (1.0 + s)
   317  	} else {
   318  		q = l + s - l*s
   319  	}
   320  	p = 2.0*l - q
   321  
   322  	r := _v(p, q, h+1.0/3.0)
   323  	g := _v(p, q, h)
   324  	b := _v(p, q, h-1.0/3.0)
   325  
   326  	return r, g, b
   327  }
   328  
   329  func convertRGBToHSL(r, g, b float32) (float32, float32, float32) {
   330  	max := maxf32(r, maxf32(g, b))
   331  	min := minf32(r, minf32(g, b))
   332  
   333  	l := (max + min) / 2.0
   334  
   335  	if max == min {
   336  		return 0.0, 0.0, l
   337  	}
   338  
   339  	var h, s float32
   340  	d := max - min
   341  	if l > 0.5 {
   342  		s = d / (2.0 - max - min)
   343  	} else {
   344  		s = d / (max + min)
   345  	}
   346  
   347  	if r == max {
   348  		h = (g - b) / d
   349  		if g < b {
   350  			h += 6.0
   351  		}
   352  	} else if g == max {
   353  		h = (b-r)/d + 2.0
   354  	} else {
   355  		h = (r-g)/d + 4.0
   356  	}
   357  	h /= 6.0
   358  
   359  	return h, s, l
   360  }
   361  
   362  func normalizeHue(hue float32) float32 {
   363  	hue = hue - float32(int(hue))
   364  	if hue < 0 {
   365  		hue++
   366  	}
   367  	return hue
   368  }
   369  
   370  // Hue creates a filter that rotates the hue of an image.
   371  // The shift parameter is the hue angle shift, typically in range (-180, 180).
   372  // The shift = 0 gives the original image.
   373  func Hue(shift float32) Filter {
   374  	p := normalizeHue(shift / 360.0)
   375  	if p == 0 {
   376  		return &copyimageFilter{}
   377  	}
   378  
   379  	return &colorFilter{
   380  		fn: func(px pixel) pixel {
   381  			h, s, l := convertRGBToHSL(px.R, px.G, px.B)
   382  			h = normalizeHue(h + p)
   383  			r, g, b := convertHSLToRGB(h, s, l)
   384  			return pixel{r, g, b, px.A}
   385  		},
   386  	}
   387  }
   388  
   389  // Saturation creates a filter that changes the saturation of an image.
   390  // The percentage parameter must be in range (-100, 500). The percentage = 0 gives the original image.
   391  func Saturation(percentage float32) Filter {
   392  	p := 1 + minf32(maxf32(percentage, -100.0), 500.0)/100.0
   393  	if p == 1 {
   394  		return &copyimageFilter{}
   395  	}
   396  
   397  	return &colorFilter{
   398  		fn: func(px pixel) pixel {
   399  			h, s, l := convertRGBToHSL(px.R, px.G, px.B)
   400  			s *= p
   401  			if s > 1 {
   402  				s = 1
   403  			}
   404  			r, g, b := convertHSLToRGB(h, s, l)
   405  			return pixel{r, g, b, px.A}
   406  		},
   407  	}
   408  }
   409  
   410  // Colorize creates a filter that produces a colorized version of an image.
   411  // The hue parameter is the angle on the color wheel, typically in range (0, 360).
   412  // The saturation parameter must be in range (0, 100).
   413  // The percentage parameter specifies the strength of the effect, it must be in range (0, 100).
   414  //
   415  // Example:
   416  //
   417  //	g := gift.New(
   418  //		gift.Colorize(240, 50, 100), // blue colorization, 50% saturation
   419  //	)
   420  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   421  //	g.Draw(dst, src)
   422  //
   423  func Colorize(hue, saturation, percentage float32) Filter {
   424  	h := normalizeHue(hue / 360)
   425  	s := minf32(maxf32(saturation, 0), 100) / 100
   426  	p := minf32(maxf32(percentage, 0), 100) / 100
   427  	if p == 0 {
   428  		return &copyimageFilter{}
   429  	}
   430  
   431  	return &colorFilter{
   432  		fn: func(px pixel) pixel {
   433  			_, _, l := convertRGBToHSL(px.R, px.G, px.B)
   434  			r, g, b := convertHSLToRGB(h, s, l)
   435  			px.R += (r - px.R) * p
   436  			px.G += (g - px.G) * p
   437  			px.B += (b - px.B) * p
   438  			return px
   439  		},
   440  	}
   441  }
   442  
   443  // ColorBalance creates a filter that changes the color balance of an image.
   444  // The percentage parameters for each color channel (red, green, blue) must be in range (-100, 500).
   445  //
   446  // Example:
   447  //
   448  //	g := gift.New(
   449  //		gift.ColorBalance(20, -20, 0), // +20% red, -20% green
   450  //	)
   451  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   452  //	g.Draw(dst, src)
   453  //
   454  func ColorBalance(percentageRed, percentageGreen, percentageBlue float32) Filter {
   455  	pr := 1 + minf32(maxf32(percentageRed, -100), 500)/100
   456  	pg := 1 + minf32(maxf32(percentageGreen, -100), 500)/100
   457  	pb := 1 + minf32(maxf32(percentageBlue, -100), 500)/100
   458  
   459  	return &colorFilter{
   460  		fn: func(px pixel) pixel {
   461  			px.R *= pr
   462  			px.G *= pg
   463  			px.B *= pb
   464  			return px
   465  		},
   466  	}
   467  }
   468  
   469  // ColorFunc creates a filter that changes the colors of an image using custom function.
   470  // The fn parameter specifies a function that takes red, green, blue and alpha channels of a pixel
   471  // as float32 values in range (0, 1) and returns the modified channel values.
   472  //
   473  // Example:
   474  //
   475  //	g := gift.New(
   476  //		gift.ColorFunc(
   477  //			func(r0, g0, b0, a0 float32) (r, g, b, a float32) {
   478  //				r = 1 - r0   // invert the red channel
   479  //				g = g0 + 0.1 // shift the green channel by 0.1
   480  //				b = 0        // set the blue channel to 0
   481  //				a = a0       // preserve the alpha channel
   482  //				return
   483  //			},
   484  //		),
   485  //	)
   486  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   487  //	g.Draw(dst, src)
   488  //
   489  func ColorFunc(fn func(r0, g0, b0, a0 float32) (r, g, b, a float32)) Filter {
   490  	return &colorFilter{
   491  		fn: func(px pixel) pixel {
   492  			r, g, b, a := fn(px.R, px.G, px.B, px.A)
   493  			return pixel{r, g, b, a}
   494  		},
   495  	}
   496  }