resize.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package transform
  2. import (
  3. "image"
  4. "math"
  5. "github.com/anthonynsimon/bild/clone"
  6. "github.com/anthonynsimon/bild/math/f64"
  7. "github.com/anthonynsimon/bild/parallel"
  8. )
  9. // Resize returns a new image with its size adjusted to the new width and height. The filter
  10. // param corresponds to the Resampling Filter to be used when interpolating between the sample points.
  11. //
  12. // Usage example:
  13. //
  14. // result := transform.Resize(img, 800, 600, transform.Linear)
  15. func Resize(img image.Image, width, height int, filter ResampleFilter) *image.RGBA {
  16. if width <= 0 || height <= 0 || img.Bounds().Empty() {
  17. return image.NewRGBA(image.Rect(0, 0, 0, 0))
  18. }
  19. src := clone.AsShallowRGBA(img)
  20. var dst *image.RGBA
  21. // NearestNeighbor is a special case, it's faster to compute without convolution matrix.
  22. if filter.Support <= 0 {
  23. dst = nearestNeighbor(src, width, height)
  24. } else {
  25. dst = resampleHorizontal(src, width, filter)
  26. dst = resampleVertical(dst, height, filter)
  27. }
  28. return dst
  29. }
  30. // Crop returns a new image which contains the intersection between the rect and the image provided as params.
  31. // Only the intersection is returned. If a rect larger than the image is provided, no fill is done to
  32. // the 'empty' area.
  33. //
  34. // Usage example:
  35. //
  36. // result := transform.Crop(img, image.Rect(0, 0, 512, 256))
  37. func Crop(img image.Image, rect image.Rectangle) *image.RGBA {
  38. src := clone.AsShallowRGBA(img)
  39. return clone.AsRGBA(src.SubImage(rect))
  40. }
  41. func resampleHorizontal(src *image.RGBA, width int, filter ResampleFilter) *image.RGBA {
  42. srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
  43. srcStride := src.Stride
  44. delta := float64(srcWidth) / float64(width)
  45. // Scale must be at least 1. Special case for image size reduction filter radius.
  46. scale := math.Max(delta, 1.0)
  47. dst := image.NewRGBA(image.Rect(0, 0, width, srcHeight))
  48. dstStride := dst.Stride
  49. filterRadius := math.Ceil(scale * filter.Support)
  50. parallel.Line(srcHeight, func(start, end int) {
  51. for y := start; y < end; y++ {
  52. for x := 0; x < width; x++ {
  53. // value of x from src
  54. ix := (float64(x)+0.5)*delta - 0.5
  55. istart, iend := int(ix-filterRadius+0.5), int(ix+filterRadius)
  56. if istart < 0 {
  57. istart = 0
  58. }
  59. if iend >= srcWidth {
  60. iend = srcWidth - 1
  61. }
  62. var r, g, b, a float64
  63. var sum float64
  64. for kx := istart; kx <= iend; kx++ {
  65. srcPos := y*srcStride + kx*4
  66. // normalize the sample position to be evaluated by the filter
  67. normPos := (float64(kx) - ix) / scale
  68. fValue := filter.Fn(normPos)
  69. r += float64(src.Pix[srcPos+0]) * fValue
  70. g += float64(src.Pix[srcPos+1]) * fValue
  71. b += float64(src.Pix[srcPos+2]) * fValue
  72. a += float64(src.Pix[srcPos+3]) * fValue
  73. sum += fValue
  74. }
  75. dstPos := y*dstStride + x*4
  76. dst.Pix[dstPos+0] = uint8(f64.Clamp((r/sum)+0.5, 0, 255))
  77. dst.Pix[dstPos+1] = uint8(f64.Clamp((g/sum)+0.5, 0, 255))
  78. dst.Pix[dstPos+2] = uint8(f64.Clamp((b/sum)+0.5, 0, 255))
  79. dst.Pix[dstPos+3] = uint8(f64.Clamp((a/sum)+0.5, 0, 255))
  80. }
  81. }
  82. })
  83. return dst
  84. }
  85. func resampleVertical(src *image.RGBA, height int, filter ResampleFilter) *image.RGBA {
  86. srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
  87. srcStride := src.Stride
  88. delta := float64(srcHeight) / float64(height)
  89. scale := math.Max(delta, 1.0)
  90. dst := image.NewRGBA(image.Rect(0, 0, srcWidth, height))
  91. dstStride := dst.Stride
  92. filterRadius := math.Ceil(scale * filter.Support)
  93. parallel.Line(height, func(start, end int) {
  94. for y := start; y < end; y++ {
  95. iy := (float64(y)+0.5)*delta - 0.5
  96. istart, iend := int(iy-filterRadius+0.5), int(iy+filterRadius)
  97. if istart < 0 {
  98. istart = 0
  99. }
  100. if iend >= srcHeight {
  101. iend = srcHeight - 1
  102. }
  103. for x := 0; x < srcWidth; x++ {
  104. var r, g, b, a float64
  105. var sum float64
  106. for ky := istart; ky <= iend; ky++ {
  107. srcPos := ky*srcStride + x*4
  108. normPos := (float64(ky) - iy) / scale
  109. fValue := filter.Fn(normPos)
  110. r += float64(src.Pix[srcPos+0]) * fValue
  111. g += float64(src.Pix[srcPos+1]) * fValue
  112. b += float64(src.Pix[srcPos+2]) * fValue
  113. a += float64(src.Pix[srcPos+3]) * fValue
  114. sum += fValue
  115. }
  116. dstPos := y*dstStride + x*4
  117. dst.Pix[dstPos+0] = uint8(f64.Clamp((r/sum)+0.5, 0, 255))
  118. dst.Pix[dstPos+1] = uint8(f64.Clamp((g/sum)+0.5, 0, 255))
  119. dst.Pix[dstPos+2] = uint8(f64.Clamp((b/sum)+0.5, 0, 255))
  120. dst.Pix[dstPos+3] = uint8(f64.Clamp((a/sum)+0.5, 0, 255))
  121. }
  122. }
  123. })
  124. return dst
  125. }
  126. func nearestNeighbor(src *image.RGBA, width, height int) *image.RGBA {
  127. srcW, srcH := src.Bounds().Dx(), src.Bounds().Dy()
  128. srcStride := src.Stride
  129. dst := image.NewRGBA(image.Rect(0, 0, width, height))
  130. dstStride := dst.Stride
  131. dx := float64(srcW) / float64(width)
  132. dy := float64(srcH) / float64(height)
  133. for y := 0; y < height; y++ {
  134. for x := 0; x < width; x++ {
  135. pos := y*dstStride + x*4
  136. ipos := int((float64(y)+0.5)*dy)*srcStride + int((float64(x)+0.5)*dx)*4
  137. dst.Pix[pos+0] = src.Pix[ipos+0]
  138. dst.Pix[pos+1] = src.Pix[ipos+1]
  139. dst.Pix[pos+2] = src.Pix[ipos+2]
  140. dst.Pix[pos+3] = src.Pix[ipos+3]
  141. }
  142. }
  143. return dst
  144. }