adjust.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package imaging
  2. import (
  3. "image"
  4. "image/color"
  5. "math"
  6. )
  7. // AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
  8. //
  9. // Example:
  10. //
  11. // dstImage = imaging.AdjustFunc(
  12. // srcImage,
  13. // func(c color.NRGBA) color.NRGBA {
  14. // // shift the red channel by 16
  15. // r := int(c.R) + 16
  16. // if r > 255 {
  17. // r = 255
  18. // }
  19. // return color.NRGBA{uint8(r), c.G, c.B, c.A}
  20. // }
  21. // )
  22. //
  23. func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
  24. src := toNRGBA(img)
  25. width := src.Bounds().Max.X
  26. height := src.Bounds().Max.Y
  27. dst := image.NewNRGBA(image.Rect(0, 0, width, height))
  28. parallel(height, func(partStart, partEnd int) {
  29. for y := partStart; y < partEnd; y++ {
  30. for x := 0; x < width; x++ {
  31. i := y*src.Stride + x*4
  32. j := y*dst.Stride + x*4
  33. r := src.Pix[i+0]
  34. g := src.Pix[i+1]
  35. b := src.Pix[i+2]
  36. a := src.Pix[i+3]
  37. c := fn(color.NRGBA{r, g, b, a})
  38. dst.Pix[j+0] = c.R
  39. dst.Pix[j+1] = c.G
  40. dst.Pix[j+2] = c.B
  41. dst.Pix[j+3] = c.A
  42. }
  43. }
  44. })
  45. return dst
  46. }
  47. // AdjustGamma performs a gamma correction on the image and returns the adjusted image.
  48. // Gamma parameter must be positive. Gamma = 1.0 gives the original image.
  49. // Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
  50. //
  51. // Example:
  52. //
  53. // dstImage = imaging.AdjustGamma(srcImage, 0.7)
  54. //
  55. func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
  56. e := 1.0 / math.Max(gamma, 0.0001)
  57. lut := make([]uint8, 256)
  58. for i := 0; i < 256; i++ {
  59. lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
  60. }
  61. fn := func(c color.NRGBA) color.NRGBA {
  62. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  63. }
  64. return AdjustFunc(img, fn)
  65. }
  66. func sigmoid(a, b, x float64) float64 {
  67. return 1 / (1 + math.Exp(b*(a-x)))
  68. }
  69. // AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
  70. // It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
  71. // The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
  72. // The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
  73. // If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
  74. //
  75. // Examples:
  76. //
  77. // dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // increase the contrast
  78. // dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // decrease the contrast
  79. //
  80. func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
  81. if factor == 0 {
  82. return Clone(img)
  83. }
  84. lut := make([]uint8, 256)
  85. a := math.Min(math.Max(midpoint, 0.0), 1.0)
  86. b := math.Abs(factor)
  87. sig0 := sigmoid(a, b, 0)
  88. sig1 := sigmoid(a, b, 1)
  89. e := 1.0e-6
  90. if factor > 0 {
  91. for i := 0; i < 256; i++ {
  92. x := float64(i) / 255.0
  93. sigX := sigmoid(a, b, x)
  94. f := (sigX - sig0) / (sig1 - sig0)
  95. lut[i] = clamp(f * 255.0)
  96. }
  97. } else {
  98. for i := 0; i < 256; i++ {
  99. x := float64(i) / 255.0
  100. arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
  101. f := a - math.Log(1.0/arg-1.0)/b
  102. lut[i] = clamp(f * 255.0)
  103. }
  104. }
  105. fn := func(c color.NRGBA) color.NRGBA {
  106. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  107. }
  108. return AdjustFunc(img, fn)
  109. }
  110. // AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
  111. // The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
  112. // The percentage = -100 gives solid grey image.
  113. //
  114. // Examples:
  115. //
  116. // dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10%
  117. // dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20%
  118. //
  119. func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
  120. percentage = math.Min(math.Max(percentage, -100.0), 100.0)
  121. lut := make([]uint8, 256)
  122. v := (100.0 + percentage) / 100.0
  123. for i := 0; i < 256; i++ {
  124. if 0 <= v && v <= 1 {
  125. lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
  126. } else if 1 < v && v < 2 {
  127. lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
  128. } else {
  129. lut[i] = uint8(float64(i)/255.0+0.5) * 255
  130. }
  131. }
  132. fn := func(c color.NRGBA) color.NRGBA {
  133. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  134. }
  135. return AdjustFunc(img, fn)
  136. }
  137. // AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
  138. // The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
  139. // The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
  140. //
  141. // Examples:
  142. //
  143. // dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15%
  144. // dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10%
  145. //
  146. func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
  147. percentage = math.Min(math.Max(percentage, -100.0), 100.0)
  148. lut := make([]uint8, 256)
  149. shift := 255.0 * percentage / 100.0
  150. for i := 0; i < 256; i++ {
  151. lut[i] = clamp(float64(i) + shift)
  152. }
  153. fn := func(c color.NRGBA) color.NRGBA {
  154. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  155. }
  156. return AdjustFunc(img, fn)
  157. }
  158. // Grayscale produces grayscale version of the image.
  159. func Grayscale(img image.Image) *image.NRGBA {
  160. fn := func(c color.NRGBA) color.NRGBA {
  161. f := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
  162. y := uint8(f + 0.5)
  163. return color.NRGBA{y, y, y, c.A}
  164. }
  165. return AdjustFunc(img, fn)
  166. }
  167. // Invert produces inverted (negated) version of the image.
  168. func Invert(img image.Image) *image.NRGBA {
  169. fn := func(c color.NRGBA) color.NRGBA {
  170. return color.NRGBA{255 - c.R, 255 - c.G, 255 - c.B, c.A}
  171. }
  172. return AdjustFunc(img, fn)
  173. }