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.

cutter.go 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /*
  2. Package cutter provides a function to crop image.
  3. By default, the original image will be cropped at the
  4. given size from the top left corner.
  5. croppedImg, err := cutter.Crop(img, cutter.Config{
  6. Width: 250,
  7. Height: 500,
  8. })
  9. Most of the time, the cropped image will share some memory
  10. with the original, so it should be used read only. You must
  11. ask explicitely for a copy if nedded.
  12. croppedImg, err := cutter.Crop(img, cutter.Config{
  13. Width: 250,
  14. Height: 500,
  15. Options: Copy,
  16. })
  17. It is possible to specify the top left position:
  18. croppedImg, err := cutter.Crop(img, cutter.Config{
  19. Width: 250,
  20. Height: 500,
  21. Anchor: image.Point{100, 100},
  22. Mode: TopLeft, // optional, default value
  23. })
  24. The Anchor property can represents the center of the cropped image
  25. instead of the top left corner:
  26. croppedImg, err := cutter.Crop(img, cutter.Config{
  27. Width: 250,
  28. Height: 500,
  29. Mode: Centered,
  30. })
  31. The default crop use the specified dimension, but it is possible
  32. to use Width and Heigth as a ratio instead. In this case,
  33. the resulting image will be as big as possible to fit the asked ratio
  34. from the anchor position.
  35. croppedImg, err := cutter.Crop(baseImage, cutter.Config{
  36. Width: 4,
  37. Height: 3,
  38. Mode: Centered,
  39. Options: Ratio,
  40. })
  41. */
  42. package cutter
  43. import (
  44. "image"
  45. "image/draw"
  46. )
  47. // Config is used to defined
  48. // the way the crop should be realized.
  49. type Config struct {
  50. Width, Height int
  51. Anchor image.Point // The Anchor Point in the source image
  52. Mode AnchorMode // Which point in the resulting image the Anchor Point is referring to
  53. Options Option
  54. }
  55. // AnchorMode is an enumeration of the position an anchor can represent.
  56. type AnchorMode int
  57. const (
  58. // TopLeft defines the Anchor Point
  59. // as the top left of the cropped picture.
  60. TopLeft AnchorMode = iota
  61. // Centered defines the Anchor Point
  62. // as the center of the cropped picture.
  63. Centered = iota
  64. )
  65. // Option flags to modify the way the crop is done.
  66. type Option int
  67. const (
  68. // Ratio flag is use when Width and Height
  69. // must be used to compute a ratio rather
  70. // than absolute size in pixels.
  71. Ratio Option = 1 << iota
  72. // Copy flag is used to enforce the function
  73. // to retrieve a copy of the selected pixels.
  74. // This disable the use of SubImage method
  75. // to compute the result.
  76. Copy = 1 << iota
  77. )
  78. // An interface that is
  79. // image.Image + SubImage method.
  80. type subImageSupported interface {
  81. SubImage(r image.Rectangle) image.Image
  82. }
  83. // Crop retrieves an image that is a
  84. // cropped copy of the original img.
  85. //
  86. // The crop is made given the informations provided in config.
  87. func Crop(img image.Image, c Config) (image.Image, error) {
  88. maxBounds := c.maxBounds(img.Bounds())
  89. size := c.computeSize(maxBounds, image.Point{c.Width, c.Height})
  90. cr := c.computedCropArea(img.Bounds(), size)
  91. cr = img.Bounds().Intersect(cr)
  92. if c.Options&Copy == Copy {
  93. return cropWithCopy(img, cr)
  94. }
  95. if dImg, ok := img.(subImageSupported); ok {
  96. return dImg.SubImage(cr), nil
  97. }
  98. return cropWithCopy(img, cr)
  99. }
  100. func cropWithCopy(img image.Image, cr image.Rectangle) (image.Image, error) {
  101. result := image.NewRGBA(cr)
  102. draw.Draw(result, cr, img, cr.Min, draw.Src)
  103. return result, nil
  104. }
  105. func (c Config) maxBounds(bounds image.Rectangle) (r image.Rectangle) {
  106. if c.Mode == Centered {
  107. anchor := c.centeredMin(bounds)
  108. w := min(anchor.X-bounds.Min.X, bounds.Max.X-anchor.X)
  109. h := min(anchor.Y-bounds.Min.Y, bounds.Max.Y-anchor.Y)
  110. r = image.Rect(anchor.X-w, anchor.Y-h, anchor.X+w, anchor.Y+h)
  111. } else {
  112. r = image.Rect(c.Anchor.X, c.Anchor.Y, bounds.Max.X, bounds.Max.Y)
  113. }
  114. return
  115. }
  116. // computeSize retrieve the effective size of the cropped image.
  117. // It is defined by Height, Width, and Ratio option.
  118. func (c Config) computeSize(bounds image.Rectangle, ratio image.Point) (p image.Point) {
  119. if c.Options&Ratio == Ratio {
  120. // Ratio option is on, so we take the biggest size available that fit the given ratio.
  121. if float64(ratio.X)/float64(bounds.Dx()) > float64(ratio.Y)/float64(bounds.Dy()) {
  122. p = image.Point{bounds.Dx(), (bounds.Dx() / ratio.X) * ratio.Y}
  123. } else {
  124. p = image.Point{(bounds.Dy() / ratio.Y) * ratio.X, bounds.Dy()}
  125. }
  126. } else {
  127. p = image.Point{ratio.X, ratio.Y}
  128. }
  129. return
  130. }
  131. // computedCropArea retrieve the theorical crop area.
  132. // It is defined by Height, Width, Mode and
  133. func (c Config) computedCropArea(bounds image.Rectangle, size image.Point) (r image.Rectangle) {
  134. min := bounds.Min
  135. switch c.Mode {
  136. case Centered:
  137. rMin := c.centeredMin(bounds)
  138. r = image.Rect(rMin.X-size.X/2, rMin.Y-size.Y/2, rMin.X-size.X/2+size.X, rMin.Y-size.Y/2+size.Y)
  139. default: // TopLeft
  140. rMin := image.Point{min.X + c.Anchor.X, min.Y + c.Anchor.Y}
  141. r = image.Rect(rMin.X, rMin.Y, rMin.X+size.X, rMin.Y+size.Y)
  142. }
  143. return
  144. }
  145. func (c *Config) centeredMin(bounds image.Rectangle) (rMin image.Point) {
  146. if c.Anchor.X == 0 && c.Anchor.Y == 0 {
  147. rMin = image.Point{
  148. X: bounds.Dx() / 2,
  149. Y: bounds.Dy() / 2,
  150. }
  151. } else {
  152. rMin = image.Point{
  153. X: c.Anchor.X,
  154. Y: c.Anchor.Y,
  155. }
  156. }
  157. return
  158. }
  159. func min(a, b int) (r int) {
  160. if a < b {
  161. r = a
  162. } else {
  163. r = b
  164. }
  165. return
  166. }