imaging.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. package imaging
  2. import (
  3. "fmt"
  4. "image"
  5. "image/color"
  6. "image/draw"
  7. _ "image/gif"
  8. "image/jpeg"
  9. "image/png"
  10. "math"
  11. "os"
  12. "strings"
  13. )
  14. // Loads image from file. Returns image.Image
  15. func Open(filename string) (img image.Image, err error) {
  16. file, err := os.Open(filename)
  17. if err != nil {
  18. return
  19. }
  20. defer file.Close()
  21. img, _, err = image.Decode(file)
  22. if err != nil {
  23. return
  24. }
  25. return
  26. }
  27. // Saves image img to file with given filename. Format parameter may be either "jpeg" or "png"
  28. func Save(img image.Image, filename string, format string) (err error) {
  29. file, err := os.Create(filename)
  30. if err != nil {
  31. return
  32. }
  33. defer file.Close()
  34. formatLower := strings.ToLower(format)
  35. switch formatLower {
  36. case "jpeg", "jpg":
  37. err = jpeg.Encode(file, img, nil)
  38. case "png":
  39. err = png.Encode(file, img)
  40. default:
  41. err = fmt.Errorf("unknown image format: %s", format)
  42. }
  43. return
  44. }
  45. // Creates a new image with given size and fills it with given color.
  46. func New(width, height int, fillColor color.Color) draw.Image {
  47. dst := image.NewRGBA(image.Rect(0, 0, width, height))
  48. unf := image.NewUniform(fillColor)
  49. draw.Draw(dst, dst.Bounds(), unf, image.ZP, draw.Src)
  50. return dst
  51. }
  52. // Returns a copy of img. New image bounds will start at (0, 0)
  53. func Copy(img image.Image) draw.Image {
  54. imgBounds := img.Bounds()
  55. newBounds := imgBounds.Sub(imgBounds.Min) // new image bounds start at (0, 0)
  56. dst := image.NewRGBA(newBounds)
  57. draw.Draw(dst, newBounds, img, imgBounds.Min, draw.Src)
  58. return dst
  59. }
  60. // This function is used internally to check if image type is image.RGBA
  61. // If not - converts any image type to image.RGBA for faster pixel access
  62. func convertToRGBA(src image.Image) *image.RGBA {
  63. var dst *image.RGBA
  64. switch src.(type) {
  65. case *image.RGBA:
  66. dst = src.(*image.RGBA)
  67. default:
  68. b := src.Bounds()
  69. dst = image.NewRGBA(b) // converted image have the same bounds as a source
  70. draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src)
  71. }
  72. return dst
  73. }
  74. // Returns a copy of rectangular area of img.
  75. func Crop(img image.Image, rect image.Rectangle) draw.Image {
  76. src := convertToRGBA(img)
  77. sub := src.SubImage(rect)
  78. return Copy(sub) // New image Bounds().Min point will be (0, 0)
  79. }
  80. // Returns a copy of rectangular area of given size from the center of img
  81. func CropCenter(img image.Image, cropW, cropH int) draw.Image {
  82. srcBounds := img.Bounds()
  83. srcW := srcBounds.Dx()
  84. srcH := srcBounds.Dy()
  85. srcMinX := srcBounds.Min.X
  86. srcMinY := srcBounds.Min.Y
  87. centerX := srcMinX + srcW/2
  88. centerY := srcMinY + srcH/2
  89. x0 := centerX - cropW/2
  90. y0 := centerY - cropH/2
  91. x1 := x0 + cropW
  92. y1 := y0 + cropH
  93. return Crop(img, image.Rect(x0, y0, x1, y1))
  94. }
  95. // Pastes image src to image img at given position. Returns resulting image.
  96. func Paste(img, src image.Image, pos image.Point) draw.Image {
  97. dst := Copy(img) // copied image bounds start at (0, 0)
  98. startPt := pos.Sub(img.Bounds().Min) // so we should translate start point
  99. endPt := startPt.Add(src.Bounds().Size())
  100. draw.Draw(dst, image.Rectangle{startPt, endPt}, src, src.Bounds().Min, draw.Src)
  101. return dst
  102. }
  103. // Pastes image src to the center of image img. Returns resulting image.
  104. func PasteCenter(img, src image.Image) draw.Image {
  105. imgBounds := img.Bounds()
  106. imgW := imgBounds.Dx()
  107. imgH := imgBounds.Dy()
  108. imgMinX := imgBounds.Min.X
  109. imgMinY := imgBounds.Min.Y
  110. centerX := imgMinX + imgW/2
  111. centerY := imgMinY + imgH/2
  112. x0 := centerX - src.Bounds().Dx()/2
  113. y0 := centerY - src.Bounds().Dy()/2
  114. return Paste(img, src, image.Pt(x0, y0))
  115. }
  116. // Rotates image img by 90 degrees clockwise
  117. func Rotate90(img image.Image) draw.Image {
  118. src := convertToRGBA(img)
  119. srcBounds := src.Bounds()
  120. srcMaxX := srcBounds.Max.X
  121. srcMinY := srcBounds.Min.Y
  122. dstW := srcBounds.Dy()
  123. dstH := srcBounds.Dx()
  124. dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
  125. for dstY := 0; dstY < dstH; dstY++ {
  126. for dstX := 0; dstX < dstW; dstX++ {
  127. srcX := srcMaxX - dstY - 1
  128. srcY := srcMinY + dstX
  129. srcOff := src.PixOffset(srcX, srcY)
  130. dstOff := dst.PixOffset(dstX, dstY)
  131. dst.Pix[dstOff+0] = src.Pix[srcOff+0]
  132. dst.Pix[dstOff+1] = src.Pix[srcOff+1]
  133. dst.Pix[dstOff+2] = src.Pix[srcOff+2]
  134. dst.Pix[dstOff+3] = src.Pix[srcOff+3]
  135. }
  136. }
  137. return dst
  138. }
  139. // Rotates image img by 180 degrees clockwise
  140. func Rotate180(img image.Image) draw.Image {
  141. src := convertToRGBA(img)
  142. srcBounds := src.Bounds()
  143. srcMaxX := srcBounds.Max.X
  144. srcMaxY := srcBounds.Max.Y
  145. dstW := srcBounds.Dx()
  146. dstH := srcBounds.Dy()
  147. dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
  148. for dstY := 0; dstY < dstH; dstY++ {
  149. for dstX := 0; dstX < dstW; dstX++ {
  150. srcX := srcMaxX - dstX - 1
  151. srcY := srcMaxY - dstY - 1
  152. srcOff := src.PixOffset(srcX, srcY)
  153. dstOff := dst.PixOffset(dstX, dstY)
  154. dst.Pix[dstOff+0] = src.Pix[srcOff+0]
  155. dst.Pix[dstOff+1] = src.Pix[srcOff+1]
  156. dst.Pix[dstOff+2] = src.Pix[srcOff+2]
  157. dst.Pix[dstOff+3] = src.Pix[srcOff+3]
  158. }
  159. }
  160. return dst
  161. }
  162. // Rotates image img by 270 degrees clockwise
  163. func Rotate270(img image.Image) draw.Image {
  164. src := convertToRGBA(img)
  165. srcBounds := src.Bounds()
  166. srcMaxY := srcBounds.Max.Y
  167. srcMinX := srcBounds.Min.X
  168. dstW := srcBounds.Dy()
  169. dstH := srcBounds.Dx()
  170. dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
  171. for dstY := 0; dstY < dstH; dstY++ {
  172. for dstX := 0; dstX < dstW; dstX++ {
  173. srcX := srcMinX + dstY
  174. srcY := srcMaxY - dstX - 1
  175. srcOff := src.PixOffset(srcX, srcY)
  176. dstOff := dst.PixOffset(dstX, dstY)
  177. dst.Pix[dstOff+0] = src.Pix[srcOff+0]
  178. dst.Pix[dstOff+1] = src.Pix[srcOff+1]
  179. dst.Pix[dstOff+2] = src.Pix[srcOff+2]
  180. dst.Pix[dstOff+3] = src.Pix[srcOff+3]
  181. }
  182. }
  183. return dst
  184. }
  185. // Flips image img horizontally (left-to-right)
  186. func FlipH(img image.Image) draw.Image {
  187. src := convertToRGBA(img)
  188. srcBounds := src.Bounds()
  189. srcMaxX := srcBounds.Max.X
  190. srcMinY := srcBounds.Min.Y
  191. dstW := srcBounds.Dx()
  192. dstH := srcBounds.Dy()
  193. dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
  194. for dstY := 0; dstY < dstH; dstY++ {
  195. for dstX := 0; dstX < dstW; dstX++ {
  196. srcX := srcMaxX - dstX - 1
  197. srcY := srcMinY + dstY
  198. srcOff := src.PixOffset(srcX, srcY)
  199. dstOff := dst.PixOffset(dstX, dstY)
  200. dst.Pix[dstOff+0] = src.Pix[srcOff+0]
  201. dst.Pix[dstOff+1] = src.Pix[srcOff+1]
  202. dst.Pix[dstOff+2] = src.Pix[srcOff+2]
  203. dst.Pix[dstOff+3] = src.Pix[srcOff+3]
  204. }
  205. }
  206. return dst
  207. }
  208. // Flips image img vertically (top-to-bottom)
  209. func FlipV(img image.Image) draw.Image {
  210. src := convertToRGBA(img)
  211. srcBounds := src.Bounds()
  212. srcMaxY := srcBounds.Max.Y
  213. srcMinX := srcBounds.Min.X
  214. dstW := srcBounds.Dx()
  215. dstH := srcBounds.Dy()
  216. dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
  217. for dstY := 0; dstY < dstH; dstY++ {
  218. for dstX := 0; dstX < dstW; dstX++ {
  219. srcX := srcMinX + dstX
  220. srcY := srcMaxY - dstY - 1
  221. srcOff := src.PixOffset(srcX, srcY)
  222. dstOff := dst.PixOffset(dstX, dstY)
  223. dst.Pix[dstOff+0] = src.Pix[srcOff+0]
  224. dst.Pix[dstOff+1] = src.Pix[srcOff+1]
  225. dst.Pix[dstOff+2] = src.Pix[srcOff+2]
  226. dst.Pix[dstOff+3] = src.Pix[srcOff+3]
  227. }
  228. }
  229. return dst
  230. }
  231. // Filter for antialias resizing is a basic quadratic function
  232. func antialiasFilter(x float64) float64 {
  233. x = math.Abs(x)
  234. if x <= 1.0 {
  235. return x*x*(1.4*x-2.4) + 1
  236. }
  237. return 0
  238. }
  239. // Resizes image img to width=dstW and height=dstH
  240. func Resize(img image.Image, dstW, dstH int) draw.Image {
  241. // Antialiased resize algorithm. The quality is good, especially at downsizing,
  242. // but the speed is not too good, some optimisations are needed.
  243. if dstW <= 0 || dstH <= 0 {
  244. return &image.RGBA{}
  245. }
  246. srcBounds := img.Bounds()
  247. srcW := srcBounds.Dx()
  248. srcH := srcBounds.Dy()
  249. srcMinX := srcBounds.Min.X
  250. srcMinY := srcBounds.Min.Y
  251. srcMaxX := srcBounds.Max.X
  252. srcMaxY := srcBounds.Max.Y
  253. if srcW <= 0 || srcH <= 0 {
  254. return &image.RGBA{}
  255. }
  256. src := convertToRGBA(img)
  257. dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
  258. dy := float64(srcH) / float64(dstH)
  259. dx := float64(srcW) / float64(dstW)
  260. radiusX := math.Ceil(dx / 1.5)
  261. radiusY := math.Ceil(dy / 1.5)
  262. coefs := make([]float64, int(radiusY+1)*2*4)
  263. xvals := make([]float64, int(radiusX+1)*2*4)
  264. for dstY := 0; dstY < dstH; dstY++ {
  265. fy := float64(srcMinY) + (float64(dstY)+0.5)*dy - 0.5
  266. for dstX := 0; dstX < dstW; dstX++ {
  267. fx := float64(srcMinX) + (float64(dstX)+0.5)*dx - 0.5
  268. startX := int(math.Ceil(fx - radiusX))
  269. if startX < srcMinX {
  270. startX = srcMinX
  271. }
  272. endX := int(math.Floor(fx + radiusX))
  273. if endX > srcMaxX-1 {
  274. endX = srcMaxX - 1
  275. }
  276. startY := int(math.Ceil(fy - radiusY))
  277. if startY < srcMinY {
  278. startY = srcMinY
  279. }
  280. endY := int(math.Floor(fy + radiusY))
  281. if endY > srcMaxY-1 {
  282. endY = srcMaxY - 1
  283. }
  284. // cache y weight coefficients
  285. for y := startY; y <= endY; y++ {
  286. coefs[y-startY] = antialiasFilter((fy - float64(y)) / radiusY)
  287. }
  288. var k, sumk, r, g, b, a float64
  289. var i int
  290. // calculate combined rgba values for each column according to weights
  291. for x := startX; x <= endX; x++ {
  292. r, g, b, a, sumk = 0.0, 0.0, 0.0, 0.0, 0.0
  293. for y := startY; y <= endY; y++ {
  294. k = coefs[y-startY]
  295. sumk += k
  296. i = src.PixOffset(x, y)
  297. r += float64(src.Pix[i+0]) * k
  298. g += float64(src.Pix[i+1]) * k
  299. b += float64(src.Pix[i+2]) * k
  300. a += float64(src.Pix[i+3]) * k
  301. }
  302. i = (x - startX) * 4
  303. xvals[i+0] = r / sumk
  304. xvals[i+1] = g / sumk
  305. xvals[i+2] = b / sumk
  306. xvals[i+3] = a / sumk
  307. }
  308. // calculate final rgba values
  309. r, g, b, a, sumk = 0.0, 0.0, 0.0, 0.0, 0.0
  310. for x := startX; x <= endX; x++ {
  311. k = antialiasFilter((fx - float64(x)) / radiusX)
  312. sumk += k
  313. i = (x - startX) * 4
  314. r += xvals[i+0] * k
  315. g += xvals[i+1] * k
  316. b += xvals[i+2] * k
  317. a += xvals[i+3] * k
  318. }
  319. r = math.Min(r/sumk, 255.0)
  320. g = math.Min(g/sumk, 255.0)
  321. b = math.Min(b/sumk, 255.0)
  322. a = math.Min(a/sumk, 255.0)
  323. i = dst.PixOffset(dstX, dstY)
  324. dst.Pix[i+0], dst.Pix[i+1], dst.Pix[i+2], dst.Pix[i+3] = uint8(r+0.5), uint8(g+0.5), uint8(b+0.5), uint8(a+0.5)
  325. }
  326. }
  327. return dst
  328. }
  329. // Scales image with given scale factor, keeps aspect ratio.
  330. func Scale(img image.Image, scaleFactor float64) draw.Image {
  331. if scaleFactor <= 0.0 {
  332. return &image.RGBA{}
  333. }
  334. if scaleFactor == 1.0 {
  335. return Copy(img)
  336. }
  337. srcBounds := img.Bounds()
  338. srcW := srcBounds.Dx()
  339. srcH := srcBounds.Dy()
  340. if srcW <= 0 || srcH <= 0 {
  341. return &image.RGBA{}
  342. }
  343. dstW := int(float64(srcW) * scaleFactor)
  344. dstH := int(float64(srcH) * scaleFactor)
  345. return Resize(img, dstW, dstH)
  346. }
  347. // Scales image to given width, keeps aspect ratio.
  348. func ScaleToWidth(img image.Image, dstW int) draw.Image {
  349. if dstW <= 0 {
  350. return &image.RGBA{}
  351. }
  352. srcBounds := img.Bounds()
  353. srcW := srcBounds.Dx()
  354. srcH := srcBounds.Dy()
  355. if srcW <= 0 || srcH <= 0 {
  356. return &image.RGBA{}
  357. }
  358. if dstW == srcW {
  359. return Copy(img)
  360. }
  361. srcAspectRatio := float64(srcW) / float64(srcH)
  362. dstH := int(float64(dstW) / srcAspectRatio)
  363. return Resize(img, dstW, dstH)
  364. }
  365. // Scales image to given height, keeps aspect ratio.
  366. func ScaleToHeight(img image.Image, dstH int) draw.Image {
  367. if dstH <= 0 {
  368. return &image.RGBA{}
  369. }
  370. srcBounds := img.Bounds()
  371. srcW := srcBounds.Dx()
  372. srcH := srcBounds.Dy()
  373. if srcW <= 0 || srcH <= 0 {
  374. return &image.RGBA{}
  375. }
  376. if dstH == srcH {
  377. return Copy(img)
  378. }
  379. srcAspectRatio := float64(srcW) / float64(srcH)
  380. dstW := int(float64(dstH) * srcAspectRatio)
  381. return Resize(img, dstW, dstH)
  382. }
  383. // Scales down image to fit given maximum width and height, keeps aspect ratio.
  384. func Fit(img image.Image, maxW, maxH int) draw.Image {
  385. if maxW <= 0 || maxH <= 0 {
  386. return &image.RGBA{}
  387. }
  388. srcBounds := img.Bounds()
  389. srcW := srcBounds.Dx()
  390. srcH := srcBounds.Dy()
  391. if srcW <= 0 || srcH <= 0 {
  392. return &image.RGBA{}
  393. }
  394. if srcW <= maxW && srcH <= maxH {
  395. return Copy(img)
  396. }
  397. srcAspectRatio := float64(srcW) / float64(srcH)
  398. maxAspectRatio := float64(maxW) / float64(maxH)
  399. var newW, newH int
  400. if srcAspectRatio > maxAspectRatio {
  401. newW = maxW
  402. newH = int(float64(newW) / srcAspectRatio)
  403. } else {
  404. newH = maxH
  405. newW = int(float64(newH) * srcAspectRatio)
  406. }
  407. return Resize(img, newW, newH)
  408. }
  409. // Scales image up or down and crops to exact given size.
  410. func Thumbnail(img image.Image, thumbW, thumbH int) draw.Image {
  411. if thumbW <= 0 || thumbH <= 0 {
  412. return &image.RGBA{}
  413. }
  414. srcBounds := img.Bounds()
  415. srcW := srcBounds.Dx()
  416. srcH := srcBounds.Dy()
  417. if srcW <= 0 || srcH <= 0 {
  418. return &image.RGBA{}
  419. }
  420. srcAspectRatio := float64(srcW) / float64(srcH)
  421. thumbAspectRatio := float64(thumbW) / float64(thumbH)
  422. var tmp image.Image
  423. if srcAspectRatio > thumbAspectRatio {
  424. tmp = ScaleToHeight(img, thumbH)
  425. } else {
  426. tmp = ScaleToWidth(img, thumbW)
  427. }
  428. return CropCenter(tmp, thumbW, thumbH)
  429. }