adjust.go (7414B)
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 }