image_data.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. package imagedata
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "net/http"
  7. "sync"
  8. "github.com/imgproxy/imgproxy/v3/asyncbuffer"
  9. "github.com/imgproxy/imgproxy/v3/config"
  10. "github.com/imgproxy/imgproxy/v3/ierrors"
  11. "github.com/imgproxy/imgproxy/v3/imagetype"
  12. )
  13. var (
  14. Watermark ImageData
  15. FallbackImage ImageData
  16. FallbackImageHeaders http.Header // Headers for the fallback image
  17. )
  18. // ImageData represents the data of an image that can be read from a source.
  19. // Please note that this interface can be backed by any reader, including lazy AsyncBuffer.
  20. // There is no other way to guarantee that the data is read without errors except reading it till EOF.
  21. type ImageData interface {
  22. io.Closer // Close closes the image data and releases any resources held by it
  23. Reader() io.ReadSeeker // Reader returns a new ReadSeeker for the image data
  24. Format() imagetype.Type // Format returns the image format from the metadata (shortcut)
  25. Size() (int, error) // Size returns the size of the image data in bytes
  26. Error() error // Error returns any error that occurred during reading data from source
  27. // AddCancel attaches a cancel function to the image data.
  28. // Please note that Cancel functions must be idempotent: for instance, an implementation
  29. // could wrap cancel into sync.Once.
  30. AddCancel(context.CancelFunc)
  31. }
  32. // imageDataBytes represents image data stored in a byte slice in memory
  33. type imageDataBytes struct {
  34. format imagetype.Type
  35. data []byte
  36. cancel []context.CancelFunc
  37. cancelOnce sync.Once
  38. }
  39. // imageDataAsyncBuffer is a struct that implements the ImageData interface backed by an AsyncBuffer
  40. type imageDataAsyncBuffer struct {
  41. b *asyncbuffer.AsyncBuffer
  42. format imagetype.Type
  43. desc string
  44. cancel []context.CancelFunc
  45. cancelOnce sync.Once
  46. }
  47. // Close closes the image data and releases any resources held by it
  48. func (d *imageDataBytes) Close() error {
  49. d.cancelOnce.Do(func() {
  50. for _, cancel := range d.cancel {
  51. cancel()
  52. }
  53. })
  54. return nil
  55. }
  56. // Format returns the image format based on the metadata
  57. func (d *imageDataBytes) Format() imagetype.Type {
  58. return d.format
  59. }
  60. // Reader returns an io.ReadSeeker for the image data
  61. func (d *imageDataBytes) Reader() io.ReadSeeker {
  62. return bytes.NewReader(d.data)
  63. }
  64. // Size returns the size of the image data in bytes.
  65. func (d *imageDataBytes) Size() (int, error) {
  66. return len(d.data), nil
  67. }
  68. // AddCancel attaches a cancel function to the image data
  69. func (d *imageDataBytes) AddCancel(cancel context.CancelFunc) {
  70. d.cancel = append(d.cancel, cancel)
  71. }
  72. func (d *imageDataBytes) Error() error {
  73. // No error handling for in-memory data, return nil
  74. return nil
  75. }
  76. // Reader returns a ReadSeeker for the image data
  77. func (d *imageDataAsyncBuffer) Reader() io.ReadSeeker {
  78. return d.b.Reader()
  79. }
  80. // Close closes the response body (hence, response) and the async buffer itself
  81. func (d *imageDataAsyncBuffer) Close() error {
  82. d.cancelOnce.Do(func() {
  83. d.b.Close()
  84. for _, cancel := range d.cancel {
  85. cancel()
  86. }
  87. })
  88. return nil
  89. }
  90. // Format returns the image format from the metadata
  91. func (d *imageDataAsyncBuffer) Format() imagetype.Type {
  92. return d.format
  93. }
  94. // Size returns the size of the image data in bytes.
  95. // It waits for the async buffer to finish reading.
  96. func (d *imageDataAsyncBuffer) Size() (int, error) {
  97. return d.b.Wait()
  98. }
  99. // AddCancel attaches a cancel function to the image data
  100. func (d *imageDataAsyncBuffer) AddCancel(cancel context.CancelFunc) {
  101. d.cancel = append(d.cancel, cancel)
  102. }
  103. // Error returns any error that occurred during reading data from
  104. // async buffer or the underlying source.
  105. func (d *imageDataAsyncBuffer) Error() error {
  106. if err := d.b.Error(); err != nil {
  107. return wrapDownloadError(err, d.desc)
  108. }
  109. return nil
  110. }
  111. func Init() error {
  112. if err := initDownloading(); err != nil {
  113. return err
  114. }
  115. if err := loadWatermark(); err != nil {
  116. return err
  117. }
  118. if err := loadFallbackImage(); err != nil {
  119. return err
  120. }
  121. return nil
  122. }
  123. func loadWatermark() error {
  124. var err error
  125. switch {
  126. case len(config.WatermarkData) > 0:
  127. Watermark, err = NewFromBase64(config.WatermarkData)
  128. // NOTE: this should be something like err = ierrors.Wrap(err).WithStackDeep(0).WithPrefix("watermark")
  129. // In the NewFromBase64 all errors should be wrapped to something like
  130. // .WithPrefix("load from base64")
  131. if err != nil {
  132. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load watermark from Base64"))
  133. }
  134. case len(config.WatermarkPath) > 0:
  135. Watermark, err = NewFromPath(config.WatermarkPath)
  136. if err != nil {
  137. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't read watermark from file"))
  138. }
  139. case len(config.WatermarkURL) > 0:
  140. Watermark, _, err = DownloadSync(context.Background(), config.WatermarkURL, "watermark", DefaultDownloadOptions())
  141. if err != nil {
  142. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't download from URL"))
  143. }
  144. default:
  145. Watermark = nil
  146. }
  147. return nil
  148. }
  149. func loadFallbackImage() (err error) {
  150. switch {
  151. case len(config.FallbackImageData) > 0:
  152. FallbackImage, err = NewFromBase64(config.FallbackImageData)
  153. if err != nil {
  154. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load fallback image from Base64"))
  155. }
  156. case len(config.FallbackImagePath) > 0:
  157. FallbackImage, err = NewFromPath(config.FallbackImagePath)
  158. if err != nil {
  159. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't read fallback image from file"))
  160. }
  161. case len(config.FallbackImageURL) > 0:
  162. FallbackImage, FallbackImageHeaders, err = DownloadSync(context.Background(), config.FallbackImageURL, "fallback image", DefaultDownloadOptions())
  163. if err != nil {
  164. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't download from URL"))
  165. }
  166. default:
  167. FallbackImage = nil
  168. }
  169. return err
  170. }