Image transformations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

transform.go 2.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. package transform
  2. import (
  3. "image"
  4. "math"
  5. "github.com/bakerolls/transform/matrix"
  6. "gonum.org/v1/gonum/mat"
  7. )
  8. // Translate an image.
  9. func Translate(img image.Image, x, y int) *image.RGBA {
  10. return Apply(img, matrix.Translate(x, y))
  11. }
  12. // Rotate an image.
  13. func Rotate(img image.Image, deg float64) *image.RGBA {
  14. b := img.Bounds()
  15. w, h := b.Max.X-b.Min.X, b.Max.Y-b.Min.Y
  16. return Apply(img,
  17. matrix.Translate(w/2, h/2),
  18. matrix.Rotate(deg),
  19. matrix.Translate(-w/2, -h/2),
  20. )
  21. }
  22. // RotateInBounds rotates an image and scales the result to fit inside the
  23. // original bounding box.
  24. func RotateInBounds(img image.Image, deg float64) *image.RGBA {
  25. b := img.Bounds()
  26. w, h := float64(b.Max.X-b.Min.X), float64(b.Max.Y-b.Min.Y)
  27. if math.Mod(math.Abs(deg), 180) >= 90 {
  28. w, h = h, w
  29. }
  30. rad := math.Abs(math.Mod(deg, 90)) * (math.Pi / 180)
  31. rotW := w*math.Cos(rad) + h*math.Sin(rad)
  32. rotH := w*math.Sin(rad) + h*math.Cos(rad)
  33. if math.Mod(math.Abs(deg), 180) >= 90 {
  34. w, h = h, w
  35. }
  36. s := math.Min(w/rotW, h/rotH)
  37. return Apply(img,
  38. matrix.Translate(int(w/2), int(h/2)),
  39. matrix.Scale(s, s),
  40. matrix.Rotate(deg),
  41. matrix.Translate(-int(w/2), -int(h/2)),
  42. )
  43. }
  44. // Scale an image
  45. func Scale(img image.Image, x, y float64) *image.RGBA {
  46. b := img.Bounds()
  47. w, h := b.Max.X-b.Min.X, b.Max.Y-b.Min.Y
  48. return Apply(img,
  49. matrix.Translate(int(w/2), int(h/2)),
  50. matrix.Scale(x, y),
  51. matrix.Translate(-int(w/2), -int(h/2)),
  52. )
  53. }
  54. // FlipH flips an image horizontally.
  55. func FlipH(img image.Image) *image.RGBA {
  56. return Scale(img, -1, 1)
  57. }
  58. // FlipV flips an image vertically.
  59. func FlipV(img image.Image) *image.RGBA {
  60. return Scale(img, 1, -1)
  61. }
  62. // Shear an image.
  63. func Shear(img image.Image, x, y float64) *image.RGBA {
  64. return Apply(img, matrix.Shear(x, y))
  65. }
  66. // Apply executes one or more transformation matrix on an image. Additional
  67. // matrices will be multiplied. This allows you to combine transformations
  68. // found inside the matrix subpackage or a custom gonum 3x3 mat.Matrix in any
  69. // way you want, which will be faster than applying one step at a time.
  70. func Apply(img image.Image, m mat.Matrix, ms ...mat.Matrix) *image.RGBA {
  71. m = mul(m, ms...)
  72. b := img.Bounds()
  73. dst := image.NewRGBA(b)
  74. for x := b.Min.X; x < b.Max.X; x++ {
  75. for y := b.Min.Y; y < b.Max.Y; y++ {
  76. x2, y2 := mulXY(x, y, m)
  77. dst.Set(x2, y2, img.At(x, y))
  78. }
  79. }
  80. return dst
  81. }
  82. // mul multiplies matrices.
  83. func mul(m mat.Matrix, ms ...mat.Matrix) mat.Matrix {
  84. r, c := m.Dims()
  85. n := mat.NewDense(r, c, make([]float64, r*c))
  86. n.Add(n, m)
  87. for _, m := range ms {
  88. n.Mul(n, m)
  89. }
  90. return n
  91. }
  92. // mulXY multiplies a new matrix based on (x, y) with a given one, returning
  93. // (x', y').
  94. func mulXY(x, y int, a mat.Matrix) (int, int) {
  95. b := mat.NewDense(3, 1, []float64{float64(x), float64(y), 1})
  96. var c mat.Dense
  97. c.Mul(a, b)
  98. return int(math.Round(c.At(0, 0))), int(math.Round(c.At(1, 0)))
  99. }