resize.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. package imaging
  2. import (
  3. "image"
  4. "math"
  5. )
  6. type iwpair struct {
  7. i int
  8. w int32
  9. }
  10. type pweights struct {
  11. iwpairs []iwpair
  12. wsum int32
  13. }
  14. func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights {
  15. du := float64(srcSize) / float64(dstSize)
  16. scale := du
  17. if scale < 1.0 {
  18. scale = 1.0
  19. }
  20. ru := math.Ceil(scale * filter.Support)
  21. out := make([]pweights, dstSize)
  22. for v := 0; v < dstSize; v++ {
  23. fu := (float64(v)+0.5)*du - 0.5
  24. startu := int(math.Ceil(fu - ru))
  25. if startu < 0 {
  26. startu = 0
  27. }
  28. endu := int(math.Floor(fu + ru))
  29. if endu > srcSize-1 {
  30. endu = srcSize - 1
  31. }
  32. wsum := int32(0)
  33. for u := startu; u <= endu; u++ {
  34. w := int32(0xff * filter.Kernel((float64(u)-fu)/scale))
  35. if w != 0 {
  36. wsum += w
  37. out[v].iwpairs = append(out[v].iwpairs, iwpair{u, w})
  38. }
  39. }
  40. out[v].wsum = wsum
  41. }
  42. return out
  43. }
  44. // Resize resizes the image to the specified width and height using the specified resampling
  45. // filter and returns the transformed image. If one of width or height is 0, the image aspect
  46. // ratio is preserved.
  47. //
  48. // Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
  49. // CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
  50. //
  51. // Usage example:
  52. //
  53. // dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos)
  54. //
  55. func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
  56. dstW, dstH := width, height
  57. if dstW < 0 || dstH < 0 {
  58. return &image.NRGBA{}
  59. }
  60. if dstW == 0 && dstH == 0 {
  61. return &image.NRGBA{}
  62. }
  63. src := toNRGBA(img)
  64. srcW := src.Bounds().Max.X
  65. srcH := src.Bounds().Max.Y
  66. if srcW <= 0 || srcH <= 0 {
  67. return &image.NRGBA{}
  68. }
  69. // if new width or height is 0 then preserve aspect ratio, minimum 1px
  70. if dstW == 0 {
  71. tmpW := float64(dstH) * float64(srcW) / float64(srcH)
  72. dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
  73. }
  74. if dstH == 0 {
  75. tmpH := float64(dstW) * float64(srcH) / float64(srcW)
  76. dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
  77. }
  78. var dst *image.NRGBA
  79. if filter.Support <= 0.0 {
  80. // nearest-neighbor special case
  81. dst = resizeNearest(src, dstW, dstH)
  82. } else {
  83. // two-pass resize
  84. if srcW != dstW {
  85. dst = resizeHorizontal(src, dstW, filter)
  86. } else {
  87. dst = src
  88. }
  89. if srcH != dstH {
  90. dst = resizeVertical(dst, dstH, filter)
  91. }
  92. }
  93. return dst
  94. }
  95. func resizeHorizontal(src *image.NRGBA, width int, filter ResampleFilter) *image.NRGBA {
  96. srcBounds := src.Bounds()
  97. srcW := srcBounds.Max.X
  98. srcH := srcBounds.Max.Y
  99. dstW := width
  100. dstH := srcH
  101. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  102. weights := precomputeWeights(dstW, srcW, filter)
  103. parallel(dstH, func(partStart, partEnd int) {
  104. for dstY := partStart; dstY < partEnd; dstY++ {
  105. for dstX := 0; dstX < dstW; dstX++ {
  106. var c [4]int32
  107. for _, iw := range weights[dstX].iwpairs {
  108. i := dstY*src.Stride + iw.i*4
  109. c[0] += int32(src.Pix[i+0]) * iw.w
  110. c[1] += int32(src.Pix[i+1]) * iw.w
  111. c[2] += int32(src.Pix[i+2]) * iw.w
  112. c[3] += int32(src.Pix[i+3]) * iw.w
  113. }
  114. j := dstY*dst.Stride + dstX*4
  115. sum := weights[dstX].wsum
  116. dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
  117. dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
  118. dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
  119. dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
  120. }
  121. }
  122. })
  123. return dst
  124. }
  125. func resizeVertical(src *image.NRGBA, height int, filter ResampleFilter) *image.NRGBA {
  126. srcBounds := src.Bounds()
  127. srcW := srcBounds.Max.X
  128. srcH := srcBounds.Max.Y
  129. dstW := srcW
  130. dstH := height
  131. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  132. weights := precomputeWeights(dstH, srcH, filter)
  133. parallel(dstW, func(partStart, partEnd int) {
  134. for dstX := partStart; dstX < partEnd; dstX++ {
  135. for dstY := 0; dstY < dstH; dstY++ {
  136. var c [4]int32
  137. for _, iw := range weights[dstY].iwpairs {
  138. i := iw.i*src.Stride + dstX*4
  139. c[0] += int32(src.Pix[i+0]) * iw.w
  140. c[1] += int32(src.Pix[i+1]) * iw.w
  141. c[2] += int32(src.Pix[i+2]) * iw.w
  142. c[3] += int32(src.Pix[i+3]) * iw.w
  143. }
  144. j := dstY*dst.Stride + dstX*4
  145. sum := weights[dstY].wsum
  146. dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
  147. dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
  148. dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
  149. dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
  150. }
  151. }
  152. })
  153. return dst
  154. }
  155. // fast nearest-neighbor resize, no filtering
  156. func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA {
  157. dstW, dstH := width, height
  158. srcBounds := src.Bounds()
  159. srcW := srcBounds.Max.X
  160. srcH := srcBounds.Max.Y
  161. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  162. dx := float64(srcW) / float64(dstW)
  163. dy := float64(srcH) / float64(dstH)
  164. parallel(dstH, func(partStart, partEnd int) {
  165. for dstY := partStart; dstY < partEnd; dstY++ {
  166. fy := (float64(dstY)+0.5)*dy - 0.5
  167. for dstX := 0; dstX < dstW; dstX++ {
  168. fx := (float64(dstX)+0.5)*dx - 0.5
  169. srcX := int(math.Min(math.Max(math.Floor(fx+0.5), 0.0), float64(srcW)))
  170. srcY := int(math.Min(math.Max(math.Floor(fy+0.5), 0.0), float64(srcH)))
  171. srcOff := srcY*src.Stride + srcX*4
  172. dstOff := dstY*dst.Stride + dstX*4
  173. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  174. }
  175. }
  176. })
  177. return dst
  178. }
  179. // Fit scales down the image using the specified resample filter to fit the specified
  180. // maximum width and height and returns the transformed image.
  181. //
  182. // Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
  183. // CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
  184. //
  185. // Usage example:
  186. //
  187. // dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
  188. //
  189. func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
  190. maxW, maxH := width, height
  191. if maxW <= 0 || maxH <= 0 {
  192. return &image.NRGBA{}
  193. }
  194. srcBounds := img.Bounds()
  195. srcW := srcBounds.Dx()
  196. srcH := srcBounds.Dy()
  197. if srcW <= 0 || srcH <= 0 {
  198. return &image.NRGBA{}
  199. }
  200. if srcW <= maxW && srcH <= maxH {
  201. return Clone(img)
  202. }
  203. srcAspectRatio := float64(srcW) / float64(srcH)
  204. maxAspectRatio := float64(maxW) / float64(maxH)
  205. var newW, newH int
  206. if srcAspectRatio > maxAspectRatio {
  207. newW = maxW
  208. newH = int(float64(newW) / srcAspectRatio)
  209. } else {
  210. newH = maxH
  211. newW = int(float64(newH) * srcAspectRatio)
  212. }
  213. return Resize(img, newW, newH, filter)
  214. }
  215. // Thumbnail scales the image up or down using the specified resample filter, crops it
  216. // to the specified width and hight and returns the transformed image.
  217. //
  218. // Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
  219. // CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
  220. //
  221. // Usage example:
  222. //
  223. // dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos)
  224. //
  225. func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
  226. thumbW, thumbH := width, height
  227. if thumbW <= 0 || thumbH <= 0 {
  228. return &image.NRGBA{}
  229. }
  230. srcBounds := img.Bounds()
  231. srcW := srcBounds.Dx()
  232. srcH := srcBounds.Dy()
  233. if srcW <= 0 || srcH <= 0 {
  234. return &image.NRGBA{}
  235. }
  236. srcAspectRatio := float64(srcW) / float64(srcH)
  237. thumbAspectRatio := float64(thumbW) / float64(thumbH)
  238. var tmp image.Image
  239. if srcAspectRatio > thumbAspectRatio {
  240. tmp = Resize(img, 0, thumbH, filter)
  241. } else {
  242. tmp = Resize(img, thumbW, 0, filter)
  243. }
  244. return CropCenter(tmp, thumbW, thumbH)
  245. }
  246. // Resample filter struct. It can be used to make custom filters.
  247. //
  248. // Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
  249. // CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
  250. //
  251. // General filter recommendations:
  252. //
  253. // - Lanczos
  254. // Probably the best resampling filter for photographic images yielding sharp results,
  255. // but it's slower than cubic filters (see below).
  256. //
  257. // - CatmullRom
  258. // A sharp cubic filter. It's a good filter for both upscaling and downscaling if sharp results are needed.
  259. //
  260. // - MitchellNetravali
  261. // A high quality cubic filter that produces smoother results with less ringing than CatmullRom.
  262. //
  263. // - BSpline
  264. // A good filter if a very smooth output is needed.
  265. //
  266. // - Linear
  267. // Bilinear interpolation filter, produces reasonably good, smooth output. It's faster than cubic filters.
  268. //
  269. // - Box
  270. // Simple and fast resampling filter appropriate for downscaling.
  271. // When upscaling it's similar to NearestNeighbor.
  272. //
  273. // - NearestNeighbor
  274. // Fastest resample filter, no antialiasing at all. Rarely used.
  275. //
  276. type ResampleFilter struct {
  277. Support float64
  278. Kernel func(float64) float64
  279. }
  280. // Nearest-neighbor filter, no anti-aliasing.
  281. var NearestNeighbor ResampleFilter
  282. // Box filter (averaging pixels).
  283. var Box ResampleFilter
  284. // Linear filter.
  285. var Linear ResampleFilter
  286. // Hermite cubic spline filter (BC-spline; B=0; C=0).
  287. var Hermite ResampleFilter
  288. // Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
  289. var MitchellNetravali ResampleFilter
  290. // Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
  291. var CatmullRom ResampleFilter
  292. // Cubic B-spline - smooth cubic filter (BC-spline; B=1; C=0).
  293. var BSpline ResampleFilter
  294. // Gaussian Blurring Filter.
  295. var Gaussian ResampleFilter
  296. // Bartlett-windowed sinc filter (3 lobes).
  297. var Bartlett ResampleFilter
  298. // Lanczos filter (3 lobes).
  299. var Lanczos ResampleFilter
  300. // Hann-windowed sinc filter (3 lobes).
  301. var Hann ResampleFilter
  302. // Hamming-windowed sinc filter (3 lobes).
  303. var Hamming ResampleFilter
  304. // Blackman-windowed sinc filter (3 lobes).
  305. var Blackman ResampleFilter
  306. // Welch-windowed sinc filter (parabolic window, 3 lobes).
  307. var Welch ResampleFilter
  308. // Cosine-windowed sinc filter (3 lobes).
  309. var Cosine ResampleFilter
  310. func bcspline(x, b, c float64) float64 {
  311. x = math.Abs(x)
  312. if x < 1.0 {
  313. return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
  314. }
  315. if x < 2.0 {
  316. return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
  317. }
  318. return 0
  319. }
  320. func sinc(x float64) float64 {
  321. if x == 0 {
  322. return 1
  323. }
  324. return math.Sin(math.Pi*x) / (math.Pi * x)
  325. }
  326. func init() {
  327. NearestNeighbor = ResampleFilter{
  328. Support: 0.0, // special case - not applying the filter
  329. }
  330. Box = ResampleFilter{
  331. Support: 0.5,
  332. Kernel: func(x float64) float64 {
  333. x = math.Abs(x)
  334. if x <= 0.5 {
  335. return 1.0
  336. }
  337. return 0
  338. },
  339. }
  340. Linear = ResampleFilter{
  341. Support: 1.0,
  342. Kernel: func(x float64) float64 {
  343. x = math.Abs(x)
  344. if x < 1.0 {
  345. return 1.0 - x
  346. }
  347. return 0
  348. },
  349. }
  350. Hermite = ResampleFilter{
  351. Support: 1.0,
  352. Kernel: func(x float64) float64 {
  353. x = math.Abs(x)
  354. if x < 1.0 {
  355. return bcspline(x, 0.0, 0.0)
  356. }
  357. return 0
  358. },
  359. }
  360. MitchellNetravali = ResampleFilter{
  361. Support: 2.0,
  362. Kernel: func(x float64) float64 {
  363. x = math.Abs(x)
  364. if x < 2.0 {
  365. return bcspline(x, 1.0/3.0, 1.0/3.0)
  366. }
  367. return 0
  368. },
  369. }
  370. CatmullRom = ResampleFilter{
  371. Support: 2.0,
  372. Kernel: func(x float64) float64 {
  373. x = math.Abs(x)
  374. if x < 2.0 {
  375. return bcspline(x, 0.0, 0.5)
  376. }
  377. return 0
  378. },
  379. }
  380. BSpline = ResampleFilter{
  381. Support: 2.0,
  382. Kernel: func(x float64) float64 {
  383. x = math.Abs(x)
  384. if x < 2.0 {
  385. return bcspline(x, 1.0, 0.0)
  386. }
  387. return 0
  388. },
  389. }
  390. Gaussian = ResampleFilter{
  391. Support: 2.0,
  392. Kernel: func(x float64) float64 {
  393. x = math.Abs(x)
  394. if x < 2.0 {
  395. return math.Exp(-2 * x * x)
  396. }
  397. return 0
  398. },
  399. }
  400. Bartlett = ResampleFilter{
  401. Support: 3.0,
  402. Kernel: func(x float64) float64 {
  403. x = math.Abs(x)
  404. if x < 3.0 {
  405. return sinc(x) * (3.0 - x) / 3.0
  406. }
  407. return 0
  408. },
  409. }
  410. Lanczos = ResampleFilter{
  411. Support: 3.0,
  412. Kernel: func(x float64) float64 {
  413. x = math.Abs(x)
  414. if x < 3.0 {
  415. return sinc(x) * sinc(x/3.0)
  416. }
  417. return 0
  418. },
  419. }
  420. Hann = ResampleFilter{
  421. Support: 3.0,
  422. Kernel: func(x float64) float64 {
  423. x = math.Abs(x)
  424. if x < 3.0 {
  425. return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
  426. }
  427. return 0
  428. },
  429. }
  430. Hamming = ResampleFilter{
  431. Support: 3.0,
  432. Kernel: func(x float64) float64 {
  433. x = math.Abs(x)
  434. if x < 3.0 {
  435. return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
  436. }
  437. return 0
  438. },
  439. }
  440. Blackman = ResampleFilter{
  441. Support: 3.0,
  442. Kernel: func(x float64) float64 {
  443. x = math.Abs(x)
  444. if x < 3.0 {
  445. return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
  446. }
  447. return 0
  448. },
  449. }
  450. Welch = ResampleFilter{
  451. Support: 3.0,
  452. Kernel: func(x float64) float64 {
  453. x = math.Abs(x)
  454. if x < 3.0 {
  455. return sinc(x) * (1.0 - (x * x / 9.0))
  456. }
  457. return 0
  458. },
  459. }
  460. Cosine = ResampleFilter{
  461. Support: 3.0,
  462. Kernel: func(x float64) float64 {
  463. x = math.Abs(x)
  464. if x < 3.0 {
  465. return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
  466. }
  467. return 0
  468. },
  469. }
  470. }