github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/app/imaging/decode.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package imaging 5 6 import ( 7 "errors" 8 "fmt" 9 "image" 10 _ "image/gif" 11 _ "image/jpeg" 12 _ "image/png" 13 "io" 14 "sync" 15 16 _ "github.com/oov/psd" 17 _ "golang.org/x/image/bmp" 18 _ "golang.org/x/image/tiff" 19 ) 20 21 // DecoderOptions holds configuration options for an image decoder. 22 type DecoderOptions struct { 23 // The level of concurrency for the decoder. This defines a limit on the 24 // number of concurrently running encoding goroutines. 25 ConcurrencyLevel int 26 } 27 28 func (o *DecoderOptions) validate() error { 29 if o.ConcurrencyLevel < 0 { 30 return errors.New("ConcurrencyLevel must be non-negative") 31 } 32 return nil 33 } 34 35 // Decoder holds the necessary state to decode images. 36 // This is safe to be used from multiple goroutines. 37 type Decoder struct { 38 sem chan struct{} 39 opts DecoderOptions 40 } 41 42 // NewDecoder creates and returns a new image decoder with the given options. 43 func NewDecoder(opts DecoderOptions) (*Decoder, error) { 44 var d Decoder 45 if err := opts.validate(); err != nil { 46 return nil, fmt.Errorf("imaging: error validating decoder options: %w", err) 47 } 48 if opts.ConcurrencyLevel > 0 { 49 d.sem = make(chan struct{}, opts.ConcurrencyLevel) 50 } 51 d.opts = opts 52 return &d, nil 53 } 54 55 // Decode decodes the given encoded data and returns the decoded image. 56 func (d *Decoder) Decode(rd io.Reader) (img image.Image, format string, err error) { 57 if d.opts.ConcurrencyLevel != 0 { 58 d.sem <- struct{}{} 59 defer func() { <-d.sem }() 60 } 61 62 img, format, err = image.Decode(rd) 63 if err != nil { 64 return nil, "", fmt.Errorf("imaging: failed to decode image: %w", err) 65 } 66 67 return img, format, nil 68 } 69 70 // DecodeMemBounded works similarly to Decode but also returns a release function that 71 // must be called when access to the raw image is not needed anymore. 72 // This sets the raw image data pointer to nil in an attempt to help the GC to re-use the underlying data as soon as possible. 73 func (d *Decoder) DecodeMemBounded(rd io.Reader) (img image.Image, format string, releaseFunc func(), err error) { 74 if d.opts.ConcurrencyLevel != 0 { 75 d.sem <- struct{}{} 76 defer func() { 77 if err != nil { 78 <-d.sem 79 } 80 }() 81 } 82 83 img, format, err = image.Decode(rd) 84 if err != nil { 85 return nil, "", nil, fmt.Errorf("imaging: failed to decode image: %w", err) 86 } 87 88 var once sync.Once 89 releaseFunc = func() { 90 if d.opts.ConcurrencyLevel == 0 { 91 return 92 } 93 once.Do(func() { 94 if img != nil { 95 releaseImageData(img) 96 } 97 <-d.sem 98 }) 99 } 100 101 return img, format, releaseFunc, nil 102 } 103 104 // DecodeConfig returns the image config for the given data. 105 func (d *Decoder) DecodeConfig(rd io.Reader) (image.Config, string, error) { 106 img, format, err := image.DecodeConfig(rd) 107 if err != nil { 108 return image.Config{}, "", fmt.Errorf("imaging: failed to decode image config: %w", err) 109 } 110 return img, format, nil 111 } 112 113 // GetDimensions returns the dimensions for the given encoded image data. 114 func GetDimensions(imageData io.Reader) (int, int, error) { 115 cfg, _, err := image.DecodeConfig(imageData) 116 if seeker, ok := imageData.(io.ReadSeeker); ok { 117 defer seeker.Seek(0, 0) 118 } 119 return cfg.Width, cfg.Height, err 120 } 121 122 // This is only needed to try and simplify GC work. 123 func releaseImageData(img image.Image) { 124 switch raw := img.(type) { 125 case *image.Alpha: 126 raw.Pix = nil 127 case *image.Alpha16: 128 raw.Pix = nil 129 case *image.Gray: 130 raw.Pix = nil 131 case *image.Gray16: 132 raw.Pix = nil 133 case *image.NRGBA: 134 raw.Pix = nil 135 case *image.NRGBA64: 136 raw.Pix = nil 137 case *image.Paletted: 138 raw.Pix = nil 139 case *image.RGBA: 140 raw.Pix = nil 141 case *image.RGBA64: 142 raw.Pix = nil 143 default: 144 return 145 } 146 }