helpers.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package imaging
  2. import (
  3. "bytes"
  4. "errors"
  5. "image"
  6. "image/color"
  7. "image/draw"
  8. "image/gif"
  9. "image/jpeg"
  10. "image/png"
  11. "io"
  12. "os"
  13. "path/filepath"
  14. "strings"
  15. "golang.org/x/image/bmp"
  16. "golang.org/x/image/tiff"
  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. TIFF
  26. BMP
  27. )
  28. func (f Format) String() string {
  29. switch f {
  30. case JPEG:
  31. return "JPEG"
  32. case PNG:
  33. return "PNG"
  34. case GIF:
  35. return "GIF"
  36. case TIFF:
  37. return "TIFF"
  38. case BMP:
  39. return "BMP"
  40. default:
  41. return "Unsupported"
  42. }
  43. }
  44. var formatFromExt = map[string]Format{
  45. ".jpg": JPEG,
  46. ".jpeg": JPEG,
  47. ".png": PNG,
  48. ".tif": TIFF,
  49. ".tiff": TIFF,
  50. ".bmp": BMP,
  51. ".gif": GIF,
  52. }
  53. // FormatFromFilename parses image format from filename extension:
  54. // "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
  55. func FormatFromFilename(filename string) (Format, error) {
  56. ext := strings.ToLower(filepath.Ext(filename))
  57. if f, ok := formatFromExt[ext]; ok {
  58. return f, nil
  59. }
  60. return -1, ErrUnsupportedFormat
  61. }
  62. var (
  63. // ErrUnsupportedFormat means the given image format (or file extension) is unsupported.
  64. ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
  65. )
  66. type fileSystem interface {
  67. Create(string) (io.WriteCloser, error)
  68. Open(string) (io.ReadCloser, error)
  69. }
  70. type localFS struct{}
  71. func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
  72. func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
  73. var fs fileSystem = localFS{}
  74. // Decode reads an image from r.
  75. func Decode(r io.Reader) (image.Image, error) {
  76. img, _, err := image.Decode(r)
  77. return img, err
  78. }
  79. // Open loads an image from file
  80. func Open(filename string) (image.Image, error) {
  81. file, err := fs.Open(filename)
  82. if err != nil {
  83. return nil, err
  84. }
  85. defer file.Close()
  86. return Decode(file)
  87. }
  88. type encodeConfig struct {
  89. jpegQuality int
  90. gifNumColors int
  91. gifQuantizer draw.Quantizer
  92. gifDrawer draw.Drawer
  93. pngCompressionLevel png.CompressionLevel
  94. }
  95. var defaultEncodeConfig = encodeConfig{
  96. jpegQuality: 95,
  97. gifNumColors: 256,
  98. gifQuantizer: nil,
  99. gifDrawer: nil,
  100. pngCompressionLevel: png.DefaultCompression,
  101. }
  102. // EncodeOption sets an optional parameter for the Encode and Save functions.
  103. type EncodeOption func(*encodeConfig)
  104. // JPEGQuality returns an EncodeOption that sets the output JPEG quality.
  105. // Quality ranges from 1 to 100 inclusive, higher is better. Default is 95.
  106. func JPEGQuality(quality int) EncodeOption {
  107. return func(c *encodeConfig) {
  108. c.jpegQuality = quality
  109. }
  110. }
  111. // GIFNumColors returns an EncodeOption that sets the maximum number of colors
  112. // used in the GIF-encoded image. It ranges from 1 to 256. Default is 256.
  113. func GIFNumColors(numColors int) EncodeOption {
  114. return func(c *encodeConfig) {
  115. c.gifNumColors = numColors
  116. }
  117. }
  118. // GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce
  119. // a palette of the GIF-encoded image.
  120. func GIFQuantizer(quantizer draw.Quantizer) EncodeOption {
  121. return func(c *encodeConfig) {
  122. c.gifQuantizer = quantizer
  123. }
  124. }
  125. // GIFDrawer returns an EncodeOption that sets the drawer that is used to convert
  126. // the source image to the desired palette of the GIF-encoded image.
  127. func GIFDrawer(drawer draw.Drawer) EncodeOption {
  128. return func(c *encodeConfig) {
  129. c.gifDrawer = drawer
  130. }
  131. }
  132. // PNGCompressionLevel returns an EncodeOption that sets the compression level
  133. // of the PNG-encoded image. Default is png.DefaultCompression.
  134. func PNGCompressionLevel(level png.CompressionLevel) EncodeOption {
  135. return func(c *encodeConfig) {
  136. c.pngCompressionLevel = level
  137. }
  138. }
  139. // Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
  140. func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error {
  141. cfg := defaultEncodeConfig
  142. for _, option := range opts {
  143. option(&cfg)
  144. }
  145. var err error
  146. switch format {
  147. case JPEG:
  148. var rgba *image.RGBA
  149. if nrgba, ok := img.(*image.NRGBA); ok {
  150. if nrgba.Opaque() {
  151. rgba = &image.RGBA{
  152. Pix: nrgba.Pix,
  153. Stride: nrgba.Stride,
  154. Rect: nrgba.Rect,
  155. }
  156. }
  157. }
  158. if rgba != nil {
  159. err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
  160. } else {
  161. err = jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
  162. }
  163. case PNG:
  164. enc := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
  165. err = enc.Encode(w, img)
  166. case GIF:
  167. err = gif.Encode(w, img, &gif.Options{
  168. NumColors: cfg.gifNumColors,
  169. Quantizer: cfg.gifQuantizer,
  170. Drawer: cfg.gifDrawer,
  171. })
  172. case TIFF:
  173. err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
  174. case BMP:
  175. err = bmp.Encode(w, img)
  176. default:
  177. err = ErrUnsupportedFormat
  178. }
  179. return err
  180. }
  181. // Save saves the image to file with the specified filename.
  182. // The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
  183. //
  184. // Examples:
  185. //
  186. // // Save the image as PNG.
  187. // err := imaging.Save(img, "out.png")
  188. //
  189. // // Save the image as JPEG with optional quality parameter set to 80.
  190. // err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80))
  191. //
  192. func Save(img image.Image, filename string, opts ...EncodeOption) (err error) {
  193. f, err := FormatFromFilename(filename)
  194. if err != nil {
  195. return err
  196. }
  197. file, err := fs.Create(filename)
  198. if err != nil {
  199. return err
  200. }
  201. defer func() {
  202. cerr := file.Close()
  203. if err == nil {
  204. err = cerr
  205. }
  206. }()
  207. return Encode(file, img, f, opts...)
  208. }
  209. // New creates a new image with the specified width and height, and fills it with the specified color.
  210. func New(width, height int, fillColor color.Color) *image.NRGBA {
  211. if width <= 0 || height <= 0 {
  212. return &image.NRGBA{}
  213. }
  214. c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
  215. if (c == color.NRGBA{0, 0, 0, 0}) {
  216. return image.NewNRGBA(image.Rect(0, 0, width, height))
  217. }
  218. return &image.NRGBA{
  219. Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height),
  220. Stride: 4 * width,
  221. Rect: image.Rect(0, 0, width, height),
  222. }
  223. }
  224. // Clone returns a copy of the given image.
  225. func Clone(img image.Image) *image.NRGBA {
  226. src := newScanner(img)
  227. dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
  228. size := src.w * 4
  229. parallel(0, src.h, func(ys <-chan int) {
  230. for y := range ys {
  231. i := y * dst.Stride
  232. src.scan(0, y, src.w, y+1, dst.Pix[i:i+size])
  233. }
  234. })
  235. return dst
  236. }