imgutil.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. package drawimg
  2. import (
  3. "errors"
  4. "image"
  5. "image/color"
  6. "image/gif"
  7. "image/jpeg"
  8. "image/png"
  9. "io"
  10. "math"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "strings"
  15. "sync"
  16. "sync/atomic"
  17. )
  18. // Format is an image file format.
  19. type Format int
  20. // Image file formats.
  21. const (
  22. JPEG Format = iota
  23. PNG
  24. GIF
  25. )
  26. func (f Format) String() string {
  27. switch f {
  28. case JPEG:
  29. return "JPEG"
  30. case PNG:
  31. return "PNG"
  32. case GIF:
  33. return "GIF"
  34. default:
  35. return "Unsupported"
  36. }
  37. }
  38. var (
  39. // ErrUnsupportedFormat means the given image format (or file extension) is unsupported.
  40. ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
  41. )
  42. // Decode reads an image from r.
  43. func Decode(r io.Reader) (image.Image, error) {
  44. img, _, err := image.Decode(r)
  45. if err != nil {
  46. return nil, err
  47. }
  48. return toNRGBA(img), nil
  49. }
  50. // Open loads an image from file
  51. func Open(filename string) (image.Image, error) {
  52. file, err := os.Open(filename)
  53. if err != nil {
  54. return nil, err
  55. }
  56. defer file.Close()
  57. img, err := Decode(file)
  58. return img, err
  59. }
  60. // Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
  61. func Encode(w io.Writer, img image.Image, format Format) error {
  62. var err error
  63. switch format {
  64. case JPEG:
  65. var rgba *image.RGBA
  66. if nrgba, ok := img.(*image.NRGBA); ok {
  67. if nrgba.Opaque() {
  68. rgba = &image.RGBA{
  69. Pix: nrgba.Pix,
  70. Stride: nrgba.Stride,
  71. Rect: nrgba.Rect,
  72. }
  73. }
  74. }
  75. if rgba != nil {
  76. err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: 95})
  77. } else {
  78. err = jpeg.Encode(w, img, &jpeg.Options{Quality: 95})
  79. }
  80. case PNG:
  81. err = png.Encode(w, img)
  82. case GIF:
  83. err = gif.Encode(w, img, &gif.Options{NumColors: 256})
  84. default:
  85. err = ErrUnsupportedFormat
  86. }
  87. return err
  88. }
  89. // Save saves the image to file with the specified filename.
  90. // The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif" are supported.
  91. func Save(img image.Image, filename string) (err error) {
  92. formats := map[string]Format{
  93. ".jpg": JPEG,
  94. ".jpeg": JPEG,
  95. ".png": PNG,
  96. ".gif": GIF,
  97. }
  98. ext := strings.ToLower(filepath.Ext(filename))
  99. f, ok := formats[ext]
  100. if !ok {
  101. return ErrUnsupportedFormat
  102. }
  103. file, err := os.Create(filename)
  104. if err != nil {
  105. return err
  106. }
  107. defer file.Close()
  108. return Encode(file, img, f)
  109. }
  110. // NewNRGBA creates a new image with the specified width and height, and fills it with the specified color.
  111. func NewNRGBA(width, height int, fillColor color.Color) *image.NRGBA {
  112. if width <= 0 || height <= 0 {
  113. return &image.NRGBA{}
  114. }
  115. dst := image.NewNRGBA(image.Rect(0, 0, width, height))
  116. c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
  117. if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 {
  118. return dst
  119. }
  120. cs := []uint8{c.R, c.G, c.B, c.A}
  121. // fill the first row
  122. for x := 0; x < width; x++ {
  123. copy(dst.Pix[x*4:(x+1)*4], cs)
  124. }
  125. // copy the first row to other rows
  126. for y := 1; y < height; y++ {
  127. copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4])
  128. }
  129. return dst
  130. }
  131. // Clone returns a copy of the given image.
  132. func Clone(img image.Image) *image.NRGBA {
  133. srcBounds := img.Bounds()
  134. srcMinX := srcBounds.Min.X
  135. srcMinY := srcBounds.Min.Y
  136. dstBounds := srcBounds.Sub(srcBounds.Min)
  137. dstW := dstBounds.Dx()
  138. dstH := dstBounds.Dy()
  139. dst := image.NewNRGBA(dstBounds)
  140. switch src := img.(type) {
  141. case *image.NRGBA:
  142. rowSize := srcBounds.Dx() * 4
  143. parallel(dstH, func(partStart, partEnd int) {
  144. for dstY := partStart; dstY < partEnd; dstY++ {
  145. di := dst.PixOffset(0, dstY)
  146. si := src.PixOffset(srcMinX, srcMinY+dstY)
  147. copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
  148. }
  149. })
  150. case *image.NRGBA64:
  151. parallel(dstH, func(partStart, partEnd int) {
  152. for dstY := partStart; dstY < partEnd; dstY++ {
  153. di := dst.PixOffset(0, dstY)
  154. si := src.PixOffset(srcMinX, srcMinY+dstY)
  155. for dstX := 0; dstX < dstW; dstX++ {
  156. dst.Pix[di+0] = src.Pix[si+0]
  157. dst.Pix[di+1] = src.Pix[si+2]
  158. dst.Pix[di+2] = src.Pix[si+4]
  159. dst.Pix[di+3] = src.Pix[si+6]
  160. di += 4
  161. si += 8
  162. }
  163. }
  164. })
  165. case *image.RGBA:
  166. parallel(dstH, func(partStart, partEnd int) {
  167. for dstY := partStart; dstY < partEnd; dstY++ {
  168. di := dst.PixOffset(0, dstY)
  169. si := src.PixOffset(srcMinX, srcMinY+dstY)
  170. for dstX := 0; dstX < dstW; dstX++ {
  171. a := src.Pix[si+3]
  172. dst.Pix[di+3] = a
  173. switch a {
  174. case 0:
  175. dst.Pix[di+0] = 0
  176. dst.Pix[di+1] = 0
  177. dst.Pix[di+2] = 0
  178. case 0xff:
  179. dst.Pix[di+0] = src.Pix[si+0]
  180. dst.Pix[di+1] = src.Pix[si+1]
  181. dst.Pix[di+2] = src.Pix[si+2]
  182. default:
  183. var tmp uint16
  184. tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
  185. dst.Pix[di+0] = uint8(tmp)
  186. tmp = uint16(src.Pix[si+1]) * 0xff / uint16(a)
  187. dst.Pix[di+1] = uint8(tmp)
  188. tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
  189. dst.Pix[di+2] = uint8(tmp)
  190. }
  191. di += 4
  192. si += 4
  193. }
  194. }
  195. })
  196. case *image.RGBA64:
  197. parallel(dstH, func(partStart, partEnd int) {
  198. for dstY := partStart; dstY < partEnd; dstY++ {
  199. di := dst.PixOffset(0, dstY)
  200. si := src.PixOffset(srcMinX, srcMinY+dstY)
  201. for dstX := 0; dstX < dstW; dstX++ {
  202. a := src.Pix[si+6]
  203. dst.Pix[di+3] = a
  204. switch a {
  205. case 0:
  206. dst.Pix[di+0] = 0
  207. dst.Pix[di+1] = 0
  208. dst.Pix[di+2] = 0
  209. case 0xff:
  210. dst.Pix[di+0] = src.Pix[si+0]
  211. dst.Pix[di+1] = src.Pix[si+2]
  212. dst.Pix[di+2] = src.Pix[si+4]
  213. default:
  214. var tmp uint16
  215. tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
  216. dst.Pix[di+0] = uint8(tmp)
  217. tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
  218. dst.Pix[di+1] = uint8(tmp)
  219. tmp = uint16(src.Pix[si+4]) * 0xff / uint16(a)
  220. dst.Pix[di+2] = uint8(tmp)
  221. }
  222. di += 4
  223. si += 8
  224. }
  225. }
  226. })
  227. case *image.Gray:
  228. parallel(dstH, func(partStart, partEnd int) {
  229. for dstY := partStart; dstY < partEnd; dstY++ {
  230. di := dst.PixOffset(0, dstY)
  231. si := src.PixOffset(srcMinX, srcMinY+dstY)
  232. for dstX := 0; dstX < dstW; dstX++ {
  233. c := src.Pix[si]
  234. dst.Pix[di+0] = c
  235. dst.Pix[di+1] = c
  236. dst.Pix[di+2] = c
  237. dst.Pix[di+3] = 0xff
  238. di += 4
  239. si++
  240. }
  241. }
  242. })
  243. case *image.Gray16:
  244. parallel(dstH, func(partStart, partEnd int) {
  245. for dstY := partStart; dstY < partEnd; dstY++ {
  246. di := dst.PixOffset(0, dstY)
  247. si := src.PixOffset(srcMinX, srcMinY+dstY)
  248. for dstX := 0; dstX < dstW; dstX++ {
  249. c := src.Pix[si]
  250. dst.Pix[di+0] = c
  251. dst.Pix[di+1] = c
  252. dst.Pix[di+2] = c
  253. dst.Pix[di+3] = 0xff
  254. di += 4
  255. si += 2
  256. }
  257. }
  258. })
  259. case *image.YCbCr:
  260. parallel(dstH, func(partStart, partEnd int) {
  261. for dstY := partStart; dstY < partEnd; dstY++ {
  262. di := dst.PixOffset(0, dstY)
  263. for dstX := 0; dstX < dstW; dstX++ {
  264. srcX := srcMinX + dstX
  265. srcY := srcMinY + dstY
  266. siy := src.YOffset(srcX, srcY)
  267. sic := src.COffset(srcX, srcY)
  268. r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
  269. dst.Pix[di+0] = r
  270. dst.Pix[di+1] = g
  271. dst.Pix[di+2] = b
  272. dst.Pix[di+3] = 0xff
  273. di += 4
  274. }
  275. }
  276. })
  277. case *image.Paletted:
  278. plen := len(src.Palette)
  279. pnew := make([]color.NRGBA, plen)
  280. for i := 0; i < plen; i++ {
  281. pnew[i] = color.NRGBAModel.Convert(src.Palette[i]).(color.NRGBA)
  282. }
  283. parallel(dstH, func(partStart, partEnd int) {
  284. for dstY := partStart; dstY < partEnd; dstY++ {
  285. di := dst.PixOffset(0, dstY)
  286. si := src.PixOffset(srcMinX, srcMinY+dstY)
  287. for dstX := 0; dstX < dstW; dstX++ {
  288. c := pnew[src.Pix[si]]
  289. dst.Pix[di+0] = c.R
  290. dst.Pix[di+1] = c.G
  291. dst.Pix[di+2] = c.B
  292. dst.Pix[di+3] = c.A
  293. di += 4
  294. si++
  295. }
  296. }
  297. })
  298. default:
  299. parallel(dstH, func(partStart, partEnd int) {
  300. for dstY := partStart; dstY < partEnd; dstY++ {
  301. di := dst.PixOffset(0, dstY)
  302. for dstX := 0; dstX < dstW; dstX++ {
  303. c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
  304. dst.Pix[di+0] = c.R
  305. dst.Pix[di+1] = c.G
  306. dst.Pix[di+2] = c.B
  307. dst.Pix[di+3] = c.A
  308. di += 4
  309. }
  310. }
  311. })
  312. }
  313. return dst
  314. }
  315. // toNRGBA converts any image type to *image.NRGBA with min-point at (0, 0).
  316. func toNRGBA(img image.Image) *image.NRGBA {
  317. srcBounds := img.Bounds()
  318. if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
  319. if src0, ok := img.(*image.NRGBA); ok {
  320. return src0
  321. }
  322. }
  323. return Clone(img)
  324. }
  325. // AdjustFunc performs a gamma correction on the image and returns the adjusted image.
  326. func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
  327. src := toNRGBA(img)
  328. width := src.Bounds().Max.X
  329. height := src.Bounds().Max.Y
  330. dst := image.NewNRGBA(image.Rect(0, 0, width, height))
  331. parallel(height, func(partStart, partEnd int) {
  332. for y := partStart; y < partEnd; y++ {
  333. for x := 0; x < width; x++ {
  334. i := y*src.Stride + x*4
  335. j := y*dst.Stride + x*4
  336. r := src.Pix[i+0]
  337. g := src.Pix[i+1]
  338. b := src.Pix[i+2]
  339. a := src.Pix[i+3]
  340. c := fn(color.NRGBA{r, g, b, a})
  341. dst.Pix[j+0] = c.R
  342. dst.Pix[j+1] = c.G
  343. dst.Pix[j+2] = c.B
  344. dst.Pix[j+3] = c.A
  345. }
  346. }
  347. })
  348. return dst
  349. }
  350. // AdjustGamma performs a gamma correction on the image and returns the adjusted image.
  351. // Gamma parameter must be positive. Gamma = 1.0 gives the original image.
  352. // Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
  353. //
  354. // Example:
  355. //
  356. // dstImage = imaging.AdjustGamma(srcImage, 0.7)
  357. //
  358. func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
  359. e := 1.0 / math.Max(gamma, 0.0001)
  360. lut := make([]uint8, 256)
  361. for i := 0; i < 256; i++ {
  362. lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
  363. }
  364. fn := func(c color.NRGBA) color.NRGBA {
  365. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  366. }
  367. return AdjustFunc(img, fn)
  368. }
  369. func sigmoid(a, b, x float64) float64 {
  370. return 1 / (1 + math.Exp(b*(a-x)))
  371. }
  372. // AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
  373. // It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
  374. // The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
  375. // The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
  376. // If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
  377. //
  378. // Examples:
  379. //
  380. // dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // increase the contrast
  381. // dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // decrease the contrast
  382. //
  383. func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
  384. if factor == 0 {
  385. return Clone(img)
  386. }
  387. lut := make([]uint8, 256)
  388. a := math.Min(math.Max(midpoint, 0.0), 1.0)
  389. b := math.Abs(factor)
  390. sig0 := sigmoid(a, b, 0)
  391. sig1 := sigmoid(a, b, 1)
  392. e := 1.0e-6
  393. if factor > 0 {
  394. for i := 0; i < 256; i++ {
  395. x := float64(i) / 255.0
  396. sigX := sigmoid(a, b, x)
  397. f := (sigX - sig0) / (sig1 - sig0)
  398. lut[i] = clamp(f * 255.0)
  399. }
  400. } else {
  401. for i := 0; i < 256; i++ {
  402. x := float64(i) / 255.0
  403. arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
  404. f := a - math.Log(1.0/arg-1.0)/b
  405. lut[i] = clamp(f * 255.0)
  406. }
  407. }
  408. fn := func(c color.NRGBA) color.NRGBA {
  409. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  410. }
  411. return AdjustFunc(img, fn)
  412. }
  413. // AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
  414. // The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
  415. // The percentage = -100 gives solid grey image.
  416. //
  417. // Examples:
  418. //
  419. // dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10%
  420. // dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20%
  421. //
  422. func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
  423. percentage = math.Min(math.Max(percentage, -100.0), 100.0)
  424. lut := make([]uint8, 256)
  425. v := (100.0 + percentage) / 100.0
  426. for i := 0; i < 256; i++ {
  427. if 0 <= v && v <= 1 {
  428. lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
  429. } else if 1 < v && v < 2 {
  430. lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
  431. } else {
  432. lut[i] = uint8(float64(i)/255.0+0.5) * 255
  433. }
  434. }
  435. fn := func(c color.NRGBA) color.NRGBA {
  436. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  437. }
  438. return AdjustFunc(img, fn)
  439. }
  440. // AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
  441. // The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
  442. // The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
  443. //
  444. // Examples:
  445. //
  446. // dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15%
  447. // dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10%
  448. //
  449. func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
  450. percentage = math.Min(math.Max(percentage, -100.0), 100.0)
  451. lut := make([]uint8, 256)
  452. shift := 255.0 * percentage / 100.0
  453. for i := 0; i < 256; i++ {
  454. lut[i] = clamp(float64(i) + shift)
  455. }
  456. fn := func(c color.NRGBA) color.NRGBA {
  457. return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
  458. }
  459. return AdjustFunc(img, fn)
  460. }
  461. // Grayscale produces grayscale version of the image.
  462. func Grayscale(img image.Image) *image.NRGBA {
  463. fn := func(c color.NRGBA) color.NRGBA {
  464. f := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
  465. y := uint8(f + 0.5)
  466. return color.NRGBA{y, y, y, c.A}
  467. }
  468. return AdjustFunc(img, fn)
  469. }
  470. // Invert produces inverted (negated) version of the image.
  471. func Invert(img image.Image) *image.NRGBA {
  472. fn := func(c color.NRGBA) color.NRGBA {
  473. return color.NRGBA{255 - c.R, 255 - c.G, 255 - c.B, c.A}
  474. }
  475. return AdjustFunc(img, fn)
  476. }
  477. // parallel starts parallel image processing based on the current GOMAXPROCS value.
  478. // If GOMAXPROCS = 1 it uses no parallelization.
  479. // If GOMAXPROCS > 1 it spawns N=GOMAXPROCS workers in separate goroutines.
  480. func parallel(dataSize int, fn func(partStart, partEnd int)) {
  481. numGoroutines := 1
  482. partSize := dataSize
  483. numProcs := runtime.GOMAXPROCS(0)
  484. if numProcs > 1 {
  485. numGoroutines = numProcs
  486. partSize = dataSize / (numGoroutines * 10)
  487. if partSize < 1 {
  488. partSize = 1
  489. }
  490. }
  491. if numGoroutines == 1 {
  492. fn(0, dataSize)
  493. } else {
  494. var wg sync.WaitGroup
  495. wg.Add(numGoroutines)
  496. idx := uint64(0)
  497. for p := 0; p < numGoroutines; p++ {
  498. go func() {
  499. defer wg.Done()
  500. for {
  501. partStart := int(atomic.AddUint64(&idx, uint64(partSize))) - partSize
  502. if partStart >= dataSize {
  503. break
  504. }
  505. partEnd := partStart + partSize
  506. if partEnd > dataSize {
  507. partEnd = dataSize
  508. }
  509. fn(partStart, partEnd)
  510. }
  511. }()
  512. }
  513. wg.Wait()
  514. }
  515. }
  516. // absint returns the absolute value of i.
  517. func absint(i int) int {
  518. if i < 0 {
  519. return -i
  520. }
  521. return i
  522. }
  523. // clamp rounds and clamps float64 value to fit into uint8.
  524. func clamp(x float64) uint8 {
  525. v := int64(x + 0.5)
  526. if v > 255 {
  527. return 255
  528. }
  529. if v > 0 {
  530. return uint8(v)
  531. }
  532. return 0
  533. }