adjust.go raw

   1  package imaging
   2  
   3  import (
   4  	"image"
   5  	"image/color"
   6  	"math"
   7  )
   8  
   9  // Grayscale produces a grayscale version of the image.
  10  func Grayscale(img image.Image) *image.NRGBA {
  11  	src := newScanner(img)
  12  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
  13  	parallel(0, src.h, func(ys <-chan int) {
  14  		for y := range ys {
  15  			i := y * dst.Stride
  16  			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
  17  			for x := 0; x < src.w; x++ {
  18  				d := dst.Pix[i : i+3 : i+3]
  19  				r := d[0]
  20  				g := d[1]
  21  				b := d[2]
  22  				f := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
  23  				y := uint8(f + 0.5)
  24  				d[0] = y
  25  				d[1] = y
  26  				d[2] = y
  27  				i += 4
  28  			}
  29  		}
  30  	})
  31  	return dst
  32  }
  33  
  34  // Invert produces an inverted (negated) version of the image.
  35  func Invert(img image.Image) *image.NRGBA {
  36  	src := newScanner(img)
  37  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
  38  	parallel(0, src.h, func(ys <-chan int) {
  39  		for y := range ys {
  40  			i := y * dst.Stride
  41  			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
  42  			for x := 0; x < src.w; x++ {
  43  				d := dst.Pix[i : i+3 : i+3]
  44  				d[0] = 255 - d[0]
  45  				d[1] = 255 - d[1]
  46  				d[2] = 255 - d[2]
  47  				i += 4
  48  			}
  49  		}
  50  	})
  51  	return dst
  52  }
  53  
  54  // AdjustSaturation changes the saturation of the image using the percentage parameter and returns the adjusted image.
  55  // The percentage must be in the range (-100, 100).
  56  // The percentage = 0 gives the original image.
  57  // The percentage = 100 gives the image with the saturation value doubled for each pixel.
  58  // The percentage = -100 gives the image with the saturation value zeroed for each pixel (grayscale).
  59  //
  60  // Examples:
  61  //  dstImage = imaging.AdjustSaturation(srcImage, 25) // Increase image saturation by 25%.
  62  //  dstImage = imaging.AdjustSaturation(srcImage, -10) // Decrease image saturation by 10%.
  63  //
  64  func AdjustSaturation(img image.Image, percentage float64) *image.NRGBA {
  65  	percentage = math.Min(math.Max(percentage, -100), 100)
  66  	multiplier := 1 + percentage/100
  67  
  68  	return AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
  69  		h, s, l := rgbToHSL(c.R, c.G, c.B)
  70  		s *= multiplier
  71  		if s > 1 {
  72  			s = 1
  73  		}
  74  		r, g, b := hslToRGB(h, s, l)
  75  		return color.NRGBA{r, g, b, c.A}
  76  	})
  77  }
  78  
  79  // AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
  80  // The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
  81  // The percentage = -100 gives solid gray image.
  82  //
  83  // Examples:
  84  //
  85  //	dstImage = imaging.AdjustContrast(srcImage, -10) // Decrease image contrast by 10%.
  86  //	dstImage = imaging.AdjustContrast(srcImage, 20) // Increase image contrast by 20%.
  87  //
  88  func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
  89  	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
  90  	lut := make([]uint8, 256)
  91  
  92  	v := (100.0 + percentage) / 100.0
  93  	for i := 0; i < 256; i++ {
  94  		switch {
  95  		case 0 <= v && v <= 1:
  96  			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
  97  		case 1 < v && v < 2:
  98  			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
  99  		default:
 100  			lut[i] = uint8(float64(i)/255.0+0.5) * 255
 101  		}
 102  	}
 103  
 104  	return adjustLUT(img, lut)
 105  }
 106  
 107  // AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
 108  // The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
 109  // The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
 110  //
 111  // Examples:
 112  //
 113  //	dstImage = imaging.AdjustBrightness(srcImage, -15) // Decrease image brightness by 15%.
 114  //	dstImage = imaging.AdjustBrightness(srcImage, 10) // Increase image brightness by 10%.
 115  //
 116  func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
 117  	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
 118  	lut := make([]uint8, 256)
 119  
 120  	shift := 255.0 * percentage / 100.0
 121  	for i := 0; i < 256; i++ {
 122  		lut[i] = clamp(float64(i) + shift)
 123  	}
 124  
 125  	return adjustLUT(img, lut)
 126  }
 127  
 128  // AdjustGamma performs a gamma correction on the image and returns the adjusted image.
 129  // Gamma parameter must be positive. Gamma = 1.0 gives the original image.
 130  // Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
 131  //
 132  // Example:
 133  //
 134  //	dstImage = imaging.AdjustGamma(srcImage, 0.7)
 135  //
 136  func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
 137  	e := 1.0 / math.Max(gamma, 0.0001)
 138  	lut := make([]uint8, 256)
 139  
 140  	for i := 0; i < 256; i++ {
 141  		lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
 142  	}
 143  
 144  	return adjustLUT(img, lut)
 145  }
 146  
 147  // AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
 148  // It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
 149  // The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
 150  // The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
 151  // If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
 152  //
 153  // Examples:
 154  //
 155  //	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // Increase the contrast.
 156  //	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // Decrease the contrast.
 157  //
 158  func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
 159  	if factor == 0 {
 160  		return Clone(img)
 161  	}
 162  
 163  	lut := make([]uint8, 256)
 164  	a := math.Min(math.Max(midpoint, 0.0), 1.0)
 165  	b := math.Abs(factor)
 166  	sig0 := sigmoid(a, b, 0)
 167  	sig1 := sigmoid(a, b, 1)
 168  	e := 1.0e-6
 169  
 170  	if factor > 0 {
 171  		for i := 0; i < 256; i++ {
 172  			x := float64(i) / 255.0
 173  			sigX := sigmoid(a, b, x)
 174  			f := (sigX - sig0) / (sig1 - sig0)
 175  			lut[i] = clamp(f * 255.0)
 176  		}
 177  	} else {
 178  		for i := 0; i < 256; i++ {
 179  			x := float64(i) / 255.0
 180  			arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
 181  			f := a - math.Log(1.0/arg-1.0)/b
 182  			lut[i] = clamp(f * 255.0)
 183  		}
 184  	}
 185  
 186  	return adjustLUT(img, lut)
 187  }
 188  
 189  func sigmoid(a, b, x float64) float64 {
 190  	return 1 / (1 + math.Exp(b*(a-x)))
 191  }
 192  
 193  // adjustLUT applies the given lookup table to the colors of the image.
 194  func adjustLUT(img image.Image, lut []uint8) *image.NRGBA {
 195  	src := newScanner(img)
 196  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
 197  	lut = lut[0:256]
 198  	parallel(0, src.h, func(ys <-chan int) {
 199  		for y := range ys {
 200  			i := y * dst.Stride
 201  			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
 202  			for x := 0; x < src.w; x++ {
 203  				d := dst.Pix[i : i+3 : i+3]
 204  				d[0] = lut[d[0]]
 205  				d[1] = lut[d[1]]
 206  				d[2] = lut[d[2]]
 207  				i += 4
 208  			}
 209  		}
 210  	})
 211  	return dst
 212  }
 213  
 214  // AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
 215  //
 216  // Example:
 217  //
 218  //	dstImage = imaging.AdjustFunc(
 219  //		srcImage,
 220  //		func(c color.NRGBA) color.NRGBA {
 221  //			// Shift the red channel by 16.
 222  //			r := int(c.R) + 16
 223  //			if r > 255 {
 224  //				r = 255
 225  //			}
 226  //			return color.NRGBA{uint8(r), c.G, c.B, c.A}
 227  //		}
 228  //	)
 229  //
 230  func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
 231  	src := newScanner(img)
 232  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
 233  	parallel(0, src.h, func(ys <-chan int) {
 234  		for y := range ys {
 235  			i := y * dst.Stride
 236  			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
 237  			for x := 0; x < src.w; x++ {
 238  				d := dst.Pix[i : i+4 : i+4]
 239  				r := d[0]
 240  				g := d[1]
 241  				b := d[2]
 242  				a := d[3]
 243  				c := fn(color.NRGBA{r, g, b, a})
 244  				d[0] = c.R
 245  				d[1] = c.G
 246  				d[2] = c.B
 247  				d[3] = c.A
 248  				i += 4
 249  			}
 250  		}
 251  	})
 252  	return dst
 253  }
 254