watermark.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package processing
  2. import (
  3. "context"
  4. "math"
  5. "github.com/imgproxy/imgproxy/v3/imagedata"
  6. "github.com/imgproxy/imgproxy/v3/imath"
  7. "github.com/imgproxy/imgproxy/v3/options"
  8. "github.com/imgproxy/imgproxy/v3/options/keys"
  9. "github.com/imgproxy/imgproxy/v3/security"
  10. "github.com/imgproxy/imgproxy/v3/vips"
  11. )
  12. // watermarkPipeline constructs the watermark processing pipeline.
  13. // This pipeline is applied to the watermark image.
  14. func (p *Processor) watermarkPipeline() Pipeline {
  15. return Pipeline{
  16. p.vectorGuardScale,
  17. p.scaleOnLoad,
  18. p.colorspaceToProcessing,
  19. p.scale,
  20. p.rotateAndFlip,
  21. p.padding,
  22. }
  23. }
  24. func shouldReplicateWatermark(gt GravityType) bool {
  25. return gt == GravityReplicate
  26. }
  27. func (p *Processor) prepareWatermark(
  28. ctx context.Context,
  29. wm *vips.Image,
  30. wmData imagedata.ImageData,
  31. po ProcessingOptions,
  32. secops security.Options,
  33. imgWidth, imgHeight int,
  34. offsetScale float64,
  35. framesCount int,
  36. ) error {
  37. if err := wm.Load(wmData, 1.0, 0, 1); err != nil {
  38. return err
  39. }
  40. wmPo := p.NewProcessingOptions(options.New())
  41. wmPo.Set(keys.ResizingType, ResizeFit)
  42. wmPo.Set(keys.Dpr, 1)
  43. wmPo.Set(keys.Enlarge, true)
  44. wmPo.Set(keys.Format, wmData.Format())
  45. if scale := po.WatermarkScale(); scale > 0 {
  46. wmPo.Set(keys.Width, max(imath.ScaleToEven(imgWidth, scale), 1))
  47. wmPo.Set(keys.Height, max(imath.ScaleToEven(imgHeight, scale), 1))
  48. }
  49. shouldReplicate := shouldReplicateWatermark(po.WatermarkPosition())
  50. if shouldReplicate {
  51. offsetX := po.WatermarkXOffset()
  52. offsetY := po.WatermarkYOffset()
  53. var padX, padY int
  54. if math.Abs(offsetX) >= 1.0 {
  55. padX = imath.RoundToEven(offsetX * offsetScale)
  56. } else {
  57. padX = imath.ScaleToEven(imgWidth, offsetX)
  58. }
  59. if math.Abs(offsetY) >= 1.0 {
  60. padY = imath.RoundToEven(offsetY * offsetScale)
  61. } else {
  62. padY = imath.ScaleToEven(imgHeight, offsetY)
  63. }
  64. wmPo.Set(keys.PaddingLeft, padX/2)
  65. wmPo.Set(keys.PaddingRight, padX-padX/2)
  66. wmPo.Set(keys.PaddingTop, padY/2)
  67. wmPo.Set(keys.PaddingBottom, padY-padY/2)
  68. }
  69. if err := p.watermarkPipeline().Run(ctx, wm, wmPo, secops, wmData); err != nil {
  70. return err
  71. }
  72. // We need to copy the image to ensure that it is in memory since we will
  73. // close it after watermark processing is done.
  74. if err := wm.CopyMemory(); err != nil {
  75. return err
  76. }
  77. if shouldReplicate {
  78. if err := wm.Replicate(imgWidth, imgHeight, true); err != nil {
  79. return err
  80. }
  81. }
  82. // We don't want any headers to be copied from the watermark to the image
  83. return wm.StripAll()
  84. }
  85. func (p *Processor) applyWatermark(
  86. ctx context.Context,
  87. img *vips.Image,
  88. po ProcessingOptions,
  89. secops security.Options,
  90. offsetScale float64,
  91. framesCount int,
  92. ) error {
  93. if p.watermarkProvider == nil {
  94. return nil
  95. }
  96. wmData, _, err := p.watermarkProvider.Get(ctx, po.Options)
  97. if err != nil {
  98. return err
  99. }
  100. if wmData == nil {
  101. return nil
  102. }
  103. defer wmData.Close()
  104. wm := new(vips.Image)
  105. defer wm.Clear()
  106. width := img.Width()
  107. height := img.Height()
  108. frameHeight := height / framesCount
  109. if err := p.prepareWatermark(
  110. ctx, wm, wmData, po, secops, width, frameHeight, offsetScale, framesCount,
  111. ); err != nil {
  112. return err
  113. }
  114. if !img.ColourProfileImported() {
  115. if err := img.ImportColourProfile(); err != nil {
  116. return err
  117. }
  118. }
  119. if err := img.RgbColourspace(); err != nil {
  120. return err
  121. }
  122. opacity := po.WatermarkOpacity() * p.config.WatermarkOpacity
  123. position := po.WatermarkPosition()
  124. shouldReplicate := shouldReplicateWatermark(position)
  125. // If we replicated the watermark and need to apply it to an animated image,
  126. // it is faster to replicate the watermark to all the image and apply it single-pass
  127. if shouldReplicate && framesCount > 1 {
  128. if err := wm.Replicate(width, height, false); err != nil {
  129. return err
  130. }
  131. return img.ApplyWatermark(wm, 0, 0, opacity)
  132. }
  133. left, top := 0, 0
  134. wmWidth := wm.Width()
  135. wmHeight := wm.Height()
  136. if !shouldReplicate {
  137. gr := GravityOptions{
  138. Type: position,
  139. X: po.WatermarkXOffset(),
  140. Y: po.WatermarkYOffset(),
  141. }
  142. left, top = calcPosition(width, frameHeight, wmWidth, wmHeight, &gr, offsetScale, true)
  143. }
  144. if left >= width || top >= height || -left >= wmWidth || -top >= wmHeight {
  145. // Watermark is completely outside the image
  146. return nil
  147. }
  148. // if watermark is partially outside the image, it may partially be visible
  149. // on the next frame. We need to crop it vertically.
  150. // We don't care about horizontal overlap, as frames are stacked vertically
  151. if framesCount > 1 {
  152. cropTop := 0
  153. cropHeight := wmHeight
  154. if top < 0 {
  155. cropTop = -top
  156. cropHeight -= cropTop
  157. top = 0
  158. }
  159. if top+cropHeight > frameHeight {
  160. cropHeight = frameHeight - top
  161. }
  162. if cropTop > 0 || cropHeight < wmHeight {
  163. if err := wm.Crop(0, cropTop, wmWidth, cropHeight); err != nil {
  164. return err
  165. }
  166. }
  167. }
  168. for i := 0; i < framesCount; i++ {
  169. if err := img.ApplyWatermark(wm, left, top, opacity); err != nil {
  170. return err
  171. }
  172. top += frameHeight
  173. }
  174. return nil
  175. }
  176. func (p *Processor) watermark(c *Context) error {
  177. if p.watermarkProvider == nil || c.PO.WatermarkOpacity() == 0 {
  178. return nil
  179. }
  180. return p.applyWatermark(c.Ctx, c.Img, c.PO, c.SecOps, c.DprScale, 1)
  181. }