1
0

image_data.go 5.7 KB

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