helpers.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. /*
  2. Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
  3. This package is based on the standard Go image package and works best along with it.
  4. Image manipulation functions provided by the package take any image type
  5. that implements `image.Image` interface as an input, and return a new image of
  6. `*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
  7. Imaging package uses parallel goroutines for faster image processing.
  8. To achieve maximum performance, make sure to allow Go to utilize all CPU cores:
  9. runtime.GOMAXPROCS(runtime.NumCPU())
  10. */
  11. package imaging
  12. import (
  13. "errors"
  14. "image"
  15. "image/color"
  16. "image/gif"
  17. "image/jpeg"
  18. "image/png"
  19. "io"
  20. "os"
  21. "path/filepath"
  22. "strings"
  23. "golang.org/x/image/bmp"
  24. "golang.org/x/image/tiff"
  25. )
  26. type Format int
  27. const (
  28. JPEG Format = iota
  29. PNG
  30. GIF
  31. TIFF
  32. BMP
  33. )
  34. func (f Format) String() string {
  35. switch f {
  36. case JPEG:
  37. return "JPEG"
  38. case PNG:
  39. return "PNG"
  40. case GIF:
  41. return "GIF"
  42. case TIFF:
  43. return "TIFF"
  44. case BMP:
  45. return "BMP"
  46. default:
  47. return "Unsupported"
  48. }
  49. }
  50. var (
  51. ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
  52. )
  53. // Decode reads an image from r.
  54. func Decode(r io.Reader) (image.Image, error) {
  55. img, _, err := image.Decode(r)
  56. if err != nil {
  57. return nil, err
  58. }
  59. return toNRGBA(img), nil
  60. }
  61. // Open loads an image from file
  62. func Open(filename string) (image.Image, error) {
  63. file, err := os.Open(filename)
  64. if err != nil {
  65. return nil, err
  66. }
  67. defer file.Close()
  68. img, err := Decode(file)
  69. return img, err
  70. }
  71. // Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
  72. func Encode(w io.Writer, img image.Image, format Format) error {
  73. var err error
  74. switch format {
  75. case JPEG:
  76. var rgba *image.RGBA
  77. if nrgba, ok := img.(*image.NRGBA); ok {
  78. if nrgba.Opaque() {
  79. rgba = &image.RGBA{
  80. Pix: nrgba.Pix,
  81. Stride: nrgba.Stride,
  82. Rect: nrgba.Rect,
  83. }
  84. }
  85. }
  86. if rgba != nil {
  87. err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: 95})
  88. } else {
  89. err = jpeg.Encode(w, img, &jpeg.Options{Quality: 95})
  90. }
  91. case PNG:
  92. err = png.Encode(w, img)
  93. case GIF:
  94. err = gif.Encode(w, img, &gif.Options{NumColors: 256})
  95. case TIFF:
  96. err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
  97. case BMP:
  98. err = bmp.Encode(w, img)
  99. default:
  100. err = ErrUnsupportedFormat
  101. }
  102. return err
  103. }
  104. // Save saves the image to file with the specified filename.
  105. // The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
  106. func Save(img image.Image, filename string) (err error) {
  107. formats := map[string]Format{
  108. ".jpg": JPEG,
  109. ".jpeg": JPEG,
  110. ".png": PNG,
  111. ".tif": TIFF,
  112. ".tiff": TIFF,
  113. ".bmp": BMP,
  114. ".gif": GIF,
  115. }
  116. ext := strings.ToLower(filepath.Ext(filename))
  117. f, ok := formats[ext]
  118. if !ok {
  119. return ErrUnsupportedFormat
  120. }
  121. file, err := os.Create(filename)
  122. if err != nil {
  123. return err
  124. }
  125. defer file.Close()
  126. return Encode(file, img, f)
  127. }
  128. // New creates a new image with the specified width and height, and fills it with the specified color.
  129. func New(width, height int, fillColor color.Color) *image.NRGBA {
  130. if width <= 0 || height <= 0 {
  131. return &image.NRGBA{}
  132. }
  133. dst := image.NewNRGBA(image.Rect(0, 0, width, height))
  134. c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
  135. if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 {
  136. return dst
  137. }
  138. cs := []uint8{c.R, c.G, c.B, c.A}
  139. // fill the first row
  140. for x := 0; x < width; x++ {
  141. copy(dst.Pix[x*4:(x+1)*4], cs)
  142. }
  143. // copy the first row to other rows
  144. for y := 1; y < height; y++ {
  145. copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4])
  146. }
  147. return dst
  148. }
  149. // Clone returns a copy of the given image.
  150. func Clone(img image.Image) *image.NRGBA {
  151. srcBounds := img.Bounds()
  152. srcMinX := srcBounds.Min.X
  153. srcMinY := srcBounds.Min.Y
  154. dstBounds := srcBounds.Sub(srcBounds.Min)
  155. dstW := dstBounds.Dx()
  156. dstH := dstBounds.Dy()
  157. dst := image.NewNRGBA(dstBounds)
  158. switch src := img.(type) {
  159. case *image.NRGBA:
  160. rowSize := srcBounds.Dx() * 4
  161. parallel(dstH, func(partStart, partEnd int) {
  162. for dstY := partStart; dstY < partEnd; dstY++ {
  163. di := dst.PixOffset(0, dstY)
  164. si := src.PixOffset(srcMinX, srcMinY+dstY)
  165. copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
  166. }
  167. })
  168. case *image.NRGBA64:
  169. parallel(dstH, func(partStart, partEnd int) {
  170. for dstY := partStart; dstY < partEnd; dstY++ {
  171. di := dst.PixOffset(0, dstY)
  172. si := src.PixOffset(srcMinX, srcMinY+dstY)
  173. for dstX := 0; dstX < dstW; dstX++ {
  174. dst.Pix[di+0] = src.Pix[si+0]
  175. dst.Pix[di+1] = src.Pix[si+2]
  176. dst.Pix[di+2] = src.Pix[si+4]
  177. dst.Pix[di+3] = src.Pix[si+6]
  178. di += 4
  179. si += 8
  180. }
  181. }
  182. })
  183. case *image.RGBA:
  184. parallel(dstH, func(partStart, partEnd int) {
  185. for dstY := partStart; dstY < partEnd; dstY++ {
  186. di := dst.PixOffset(0, dstY)
  187. si := src.PixOffset(srcMinX, srcMinY+dstY)
  188. for dstX := 0; dstX < dstW; dstX++ {
  189. a := src.Pix[si+3]
  190. dst.Pix[di+3] = a
  191. switch a {
  192. case 0:
  193. dst.Pix[di+0] = 0
  194. dst.Pix[di+1] = 0
  195. dst.Pix[di+2] = 0
  196. case 0xff:
  197. dst.Pix[di+0] = src.Pix[si+0]
  198. dst.Pix[di+1] = src.Pix[si+1]
  199. dst.Pix[di+2] = src.Pix[si+2]
  200. default:
  201. dst.Pix[di+0] = uint8(uint16(src.Pix[si+0]) * 0xff / uint16(a))
  202. dst.Pix[di+1] = uint8(uint16(src.Pix[si+1]) * 0xff / uint16(a))
  203. dst.Pix[di+2] = uint8(uint16(src.Pix[si+2]) * 0xff / uint16(a))
  204. }
  205. di += 4
  206. si += 4
  207. }
  208. }
  209. })
  210. case *image.RGBA64:
  211. parallel(dstH, func(partStart, partEnd int) {
  212. for dstY := partStart; dstY < partEnd; dstY++ {
  213. di := dst.PixOffset(0, dstY)
  214. si := src.PixOffset(srcMinX, srcMinY+dstY)
  215. for dstX := 0; dstX < dstW; dstX++ {
  216. a := src.Pix[si+6]
  217. dst.Pix[di+3] = a
  218. switch a {
  219. case 0:
  220. dst.Pix[di+0] = 0
  221. dst.Pix[di+1] = 0
  222. dst.Pix[di+2] = 0
  223. case 0xff:
  224. dst.Pix[di+0] = src.Pix[si+0]
  225. dst.Pix[di+1] = src.Pix[si+2]
  226. dst.Pix[di+2] = src.Pix[si+4]
  227. default:
  228. dst.Pix[di+0] = uint8(uint16(src.Pix[si+0]) * 0xff / uint16(a))
  229. dst.Pix[di+1] = uint8(uint16(src.Pix[si+2]) * 0xff / uint16(a))
  230. dst.Pix[di+2] = uint8(uint16(src.Pix[si+4]) * 0xff / uint16(a))
  231. }
  232. di += 4
  233. si += 8
  234. }
  235. }
  236. })
  237. case *image.Gray:
  238. parallel(dstH, func(partStart, partEnd int) {
  239. for dstY := partStart; dstY < partEnd; dstY++ {
  240. di := dst.PixOffset(0, dstY)
  241. si := src.PixOffset(srcMinX, srcMinY+dstY)
  242. for dstX := 0; dstX < dstW; dstX++ {
  243. c := src.Pix[si]
  244. dst.Pix[di+0] = c
  245. dst.Pix[di+1] = c
  246. dst.Pix[di+2] = c
  247. dst.Pix[di+3] = 0xff
  248. di += 4
  249. si += 1
  250. }
  251. }
  252. })
  253. case *image.Gray16:
  254. parallel(dstH, func(partStart, partEnd int) {
  255. for dstY := partStart; dstY < partEnd; dstY++ {
  256. di := dst.PixOffset(0, dstY)
  257. si := src.PixOffset(srcMinX, srcMinY+dstY)
  258. for dstX := 0; dstX < dstW; dstX++ {
  259. c := src.Pix[si]
  260. dst.Pix[di+0] = c
  261. dst.Pix[di+1] = c
  262. dst.Pix[di+2] = c
  263. dst.Pix[di+3] = 0xff
  264. di += 4
  265. si += 2
  266. }
  267. }
  268. })
  269. case *image.YCbCr:
  270. parallel(dstH, func(partStart, partEnd int) {
  271. for dstY := partStart; dstY < partEnd; dstY++ {
  272. di := dst.PixOffset(0, dstY)
  273. switch src.SubsampleRatio {
  274. case image.YCbCrSubsampleRatio422:
  275. siy0 := dstY * src.YStride
  276. sic0 := dstY * src.CStride
  277. for dstX := 0; dstX < dstW; dstX = dstX + 1 {
  278. siy := siy0 + dstX
  279. sic := sic0 + ((srcMinX+dstX)/2 - srcMinX/2)
  280. r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
  281. dst.Pix[di+0] = r
  282. dst.Pix[di+1] = g
  283. dst.Pix[di+2] = b
  284. dst.Pix[di+3] = 0xff
  285. di += 4
  286. }
  287. case image.YCbCrSubsampleRatio420:
  288. siy0 := dstY * src.YStride
  289. sic0 := ((srcMinY+dstY)/2 - srcMinY/2) * src.CStride
  290. for dstX := 0; dstX < dstW; dstX = dstX + 1 {
  291. siy := siy0 + dstX
  292. sic := sic0 + ((srcMinX+dstX)/2 - srcMinX/2)
  293. r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
  294. dst.Pix[di+0] = r
  295. dst.Pix[di+1] = g
  296. dst.Pix[di+2] = b
  297. dst.Pix[di+3] = 0xff
  298. di += 4
  299. }
  300. case image.YCbCrSubsampleRatio440:
  301. siy0 := dstY * src.YStride
  302. sic0 := ((srcMinY+dstY)/2 - srcMinY/2) * src.CStride
  303. for dstX := 0; dstX < dstW; dstX = dstX + 1 {
  304. siy := siy0 + dstX
  305. sic := sic0 + dstX
  306. r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
  307. dst.Pix[di+0] = r
  308. dst.Pix[di+1] = g
  309. dst.Pix[di+2] = b
  310. dst.Pix[di+3] = 0xff
  311. di += 4
  312. }
  313. default:
  314. siy0 := dstY * src.YStride
  315. sic0 := dstY * src.CStride
  316. for dstX := 0; dstX < dstW; dstX++ {
  317. siy := siy0 + dstX
  318. sic := sic0 + dstX
  319. r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
  320. dst.Pix[di+0] = r
  321. dst.Pix[di+1] = g
  322. dst.Pix[di+2] = b
  323. dst.Pix[di+3] = 0xff
  324. di += 4
  325. }
  326. }
  327. }
  328. })
  329. case *image.Paletted:
  330. plen := len(src.Palette)
  331. pnew := make([]color.NRGBA, plen)
  332. for i := 0; i < plen; i++ {
  333. pnew[i] = color.NRGBAModel.Convert(src.Palette[i]).(color.NRGBA)
  334. }
  335. parallel(dstH, func(partStart, partEnd int) {
  336. for dstY := partStart; dstY < partEnd; dstY++ {
  337. di := dst.PixOffset(0, dstY)
  338. si := src.PixOffset(srcMinX, srcMinY+dstY)
  339. for dstX := 0; dstX < dstW; dstX++ {
  340. c := pnew[src.Pix[si]]
  341. dst.Pix[di+0] = c.R
  342. dst.Pix[di+1] = c.G
  343. dst.Pix[di+2] = c.B
  344. dst.Pix[di+3] = c.A
  345. di += 4
  346. si += 1
  347. }
  348. }
  349. })
  350. default:
  351. parallel(dstH, func(partStart, partEnd int) {
  352. for dstY := partStart; dstY < partEnd; dstY++ {
  353. di := dst.PixOffset(0, dstY)
  354. for dstX := 0; dstX < dstW; dstX++ {
  355. c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
  356. dst.Pix[di+0] = c.R
  357. dst.Pix[di+1] = c.G
  358. dst.Pix[di+2] = c.B
  359. dst.Pix[di+3] = c.A
  360. di += 4
  361. }
  362. }
  363. })
  364. }
  365. return dst
  366. }
  367. // This function used internally to convert any image type to NRGBA if needed.
  368. func toNRGBA(img image.Image) *image.NRGBA {
  369. srcBounds := img.Bounds()
  370. if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
  371. if src0, ok := img.(*image.NRGBA); ok {
  372. return src0
  373. }
  374. }
  375. return Clone(img)
  376. }