adjust.go 6.3 KB

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