1
0

process.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. package main
  2. import (
  3. "context"
  4. "math"
  5. "runtime"
  6. "golang.org/x/sync/errgroup"
  7. )
  8. func extractMeta(img *vipsImage) (int, int, int, bool) {
  9. width := img.Width()
  10. height := img.Height()
  11. angle := vipsAngleD0
  12. flip := false
  13. orientation := img.Orientation()
  14. if orientation >= 5 && orientation <= 8 {
  15. width, height = height, width
  16. }
  17. if orientation == 3 || orientation == 4 {
  18. angle = vipsAngleD180
  19. }
  20. if orientation == 5 || orientation == 6 {
  21. angle = vipsAngleD90
  22. }
  23. if orientation == 7 || orientation == 8 {
  24. angle = vipsAngleD270
  25. }
  26. if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
  27. flip = true
  28. }
  29. return width, height, angle, flip
  30. }
  31. func calcScale(width, height int, po *processingOptions, imgtype imageType) float64 {
  32. var scale float64
  33. srcW, srcH := float64(width), float64(height)
  34. if (po.Width == 0 || po.Width == width) && (po.Height == 0 || po.Height == height) {
  35. scale = 1
  36. } else {
  37. wr := float64(po.Width) / srcW
  38. hr := float64(po.Height) / srcH
  39. if po.Width == 0 {
  40. scale = hr
  41. } else if po.Height == 0 {
  42. scale = wr
  43. } else if po.Resize == resizeFit {
  44. scale = math.Min(wr, hr)
  45. } else {
  46. scale = math.Max(wr, hr)
  47. }
  48. }
  49. scale = scale * po.Dpr
  50. if !po.Enlarge && scale > 1 && imgtype != imageTypeSVG {
  51. return 1
  52. }
  53. if srcW*scale < 1 {
  54. scale = 1 / srcW
  55. }
  56. if srcH*scale < 1 {
  57. scale = 1 / srcH
  58. }
  59. return scale
  60. }
  61. func canScaleOnLoad(imgtype imageType, scale float64) bool {
  62. if imgtype == imageTypeSVG {
  63. return true
  64. }
  65. if conf.DisableShrinkOnLoad || scale >= 1 {
  66. return false
  67. }
  68. return imgtype == imageTypeJPEG || imgtype == imageTypeWEBP
  69. }
  70. func calcJpegShink(scale float64, imgtype imageType) int {
  71. shrink := int(1.0 / scale)
  72. switch {
  73. case shrink >= 8:
  74. return 8
  75. case shrink >= 4:
  76. return 4
  77. case shrink >= 2:
  78. return 2
  79. }
  80. return 1
  81. }
  82. func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions) (left, top int) {
  83. if gravity.Type == gravityFocusPoint {
  84. pointX := int(float64(width) * gravity.X)
  85. pointY := int(float64(height) * gravity.Y)
  86. left = maxInt(0, minInt(pointX-cropWidth/2, width-cropWidth))
  87. top = maxInt(0, minInt(pointY-cropHeight/2, height-cropHeight))
  88. return
  89. }
  90. left = (width - cropWidth + 1) / 2
  91. top = (height - cropHeight + 1) / 2
  92. if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
  93. top = 0
  94. }
  95. if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
  96. left = width - cropWidth
  97. }
  98. if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
  99. top = height - cropHeight
  100. }
  101. if gravity.Type == gravityWest || gravity.Type == gravityNorthWest || gravity.Type == gravitySouthWest {
  102. left = 0
  103. }
  104. return
  105. }
  106. func cropImage(img *vipsImage, cropWidth, cropHeight int, gravity *gravityOptions) error {
  107. if cropWidth == 0 && cropHeight == 0 {
  108. return nil
  109. }
  110. imgWidth, imgHeight := img.Width(), img.Height()
  111. if cropWidth == 0 {
  112. cropWidth = imgWidth
  113. } else {
  114. cropWidth = minInt(cropWidth, imgWidth)
  115. }
  116. if cropHeight == 0 {
  117. cropHeight = imgHeight
  118. } else {
  119. cropHeight = minInt(cropHeight, imgHeight)
  120. }
  121. if cropWidth >= imgWidth && cropHeight >= imgHeight {
  122. return nil
  123. }
  124. if gravity.Type == gravitySmart {
  125. if err := img.CopyMemory(); err != nil {
  126. return err
  127. }
  128. if err := img.SmartCrop(cropWidth, cropHeight); err != nil {
  129. return err
  130. }
  131. // Applying additional modifications after smart crop causes SIGSEGV on Alpine
  132. // so we have to copy memory after it
  133. return img.CopyMemory()
  134. }
  135. left, top := calcCrop(imgWidth, imgHeight, cropWidth, cropHeight, gravity)
  136. return img.Crop(left, top, cropWidth, cropHeight)
  137. }
  138. func transformImage(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
  139. var err error
  140. srcWidth, srcHeight, angle, flip := extractMeta(img)
  141. widthToScale, heightToScale := srcWidth, srcHeight
  142. cropWidth, cropHeight := po.Crop.Width, po.Crop.Height
  143. if cropWidth > 0 {
  144. widthToScale = minInt(cropWidth, srcWidth)
  145. }
  146. if cropHeight > 0 {
  147. heightToScale = minInt(cropHeight, srcHeight)
  148. }
  149. scale := calcScale(widthToScale, heightToScale, po, imgtype)
  150. cropWidth = roundToInt(float64(cropWidth) * scale)
  151. cropHeight = roundToInt(float64(cropHeight) * scale)
  152. if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
  153. if imgtype == imageTypeWEBP || imgtype == imageTypeSVG {
  154. // Do some scale-on-load
  155. if err := img.Load(data, imgtype, 1, scale, 1); err != nil {
  156. return err
  157. }
  158. } else if imgtype == imageTypeJPEG {
  159. // Do some shrink-on-load
  160. if shrink := calcJpegShink(scale, imgtype); shrink != 1 {
  161. if err := img.Load(data, imgtype, shrink, 1.0, 1); err != nil {
  162. return err
  163. }
  164. }
  165. }
  166. // Update scale after scale-on-load
  167. newWidth, newHeight, _, _ := extractMeta(img)
  168. widthToScale = roundToInt(float64(widthToScale) * float64(newWidth) / float64(srcWidth))
  169. heightToScale = roundToInt(float64(heightToScale) * float64(newHeight) / float64(srcHeight))
  170. scale = calcScale(widthToScale, heightToScale, po, imgtype)
  171. }
  172. if err = img.Rad2Float(); err != nil {
  173. return err
  174. }
  175. convertToLinear := conf.UseLinearColorspace && (scale != 1 || po.Dpr != 1)
  176. if convertToLinear {
  177. if err = img.ImportColourProfile(true); err != nil {
  178. return err
  179. }
  180. if err = img.LinearColourspace(); err != nil {
  181. return err
  182. }
  183. }
  184. hasAlpha := img.HasAlpha()
  185. if scale != 1 {
  186. if err = img.Resize(scale, hasAlpha); err != nil {
  187. return err
  188. }
  189. }
  190. checkTimeout(ctx)
  191. if angle != vipsAngleD0 || flip {
  192. if err = img.CopyMemory(); err != nil {
  193. return err
  194. }
  195. if angle != vipsAngleD0 {
  196. if err = img.Rotate(angle); err != nil {
  197. return err
  198. }
  199. }
  200. if flip {
  201. if err = img.Flip(); err != nil {
  202. return err
  203. }
  204. }
  205. }
  206. checkTimeout(ctx)
  207. dprWidth := roundToInt(float64(po.Width) * po.Dpr)
  208. dprHeight := roundToInt(float64(po.Height) * po.Dpr)
  209. cropGravity := po.Crop.Gravity
  210. if cropGravity.Type == gravityUnknown {
  211. cropGravity = po.Gravity
  212. }
  213. if cropGravity.Type == po.Gravity.Type && cropGravity.Type != gravityFocusPoint {
  214. if cropWidth == 0 {
  215. cropWidth = dprWidth
  216. } else if dprWidth > 0 {
  217. cropWidth = minInt(cropWidth, dprWidth)
  218. }
  219. if cropHeight == 0 {
  220. cropHeight = dprHeight
  221. } else if dprHeight > 0 {
  222. cropHeight = minInt(cropHeight, dprHeight)
  223. }
  224. if err = cropImage(img, cropWidth, cropHeight, &cropGravity); err != nil {
  225. return err
  226. }
  227. } else {
  228. if err = cropImage(img, cropWidth, cropHeight, &cropGravity); err != nil {
  229. return err
  230. }
  231. if err = cropImage(img, dprWidth, dprHeight, &po.Gravity); err != nil {
  232. return err
  233. }
  234. }
  235. checkTimeout(ctx)
  236. if convertToLinear {
  237. if err = img.RgbColourspace(); err != nil {
  238. return err
  239. }
  240. } else {
  241. if err = img.ImportColourProfile(false); err != nil {
  242. return err
  243. }
  244. }
  245. if po.Expand && (po.Width > img.Width() || po.Height > img.Height()) {
  246. if err = img.EnsureAlpha(); err != nil {
  247. return err
  248. }
  249. hasAlpha = true
  250. if err = img.Embed(gravityCenter, po.Width, po.Height, 0, 0); err != nil {
  251. return err
  252. }
  253. }
  254. if hasAlpha && (po.Flatten || po.Format == imageTypeJPEG) {
  255. if err = img.Flatten(po.Background); err != nil {
  256. return err
  257. }
  258. }
  259. if po.Blur > 0 {
  260. if err = img.Blur(po.Blur); err != nil {
  261. return err
  262. }
  263. }
  264. if po.Sharpen > 0 {
  265. if err = img.Sharpen(po.Sharpen); err != nil {
  266. return err
  267. }
  268. }
  269. checkTimeout(ctx)
  270. if po.Watermark.Enabled {
  271. if err = img.ApplyWatermark(&po.Watermark); err != nil {
  272. return err
  273. }
  274. }
  275. return img.RgbColourspace()
  276. }
  277. func transformAnimated(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
  278. imgWidth := img.Width()
  279. frameHeight, err := img.GetInt("page-height")
  280. if err != nil {
  281. return err
  282. }
  283. framesCount := minInt(img.Height()/frameHeight, conf.MaxGifFrames)
  284. // Double check dimensions because animated image has many frames
  285. if err := checkDimensions(imgWidth, frameHeight*framesCount); err != nil {
  286. return err
  287. }
  288. // Vips 8.8+ supports n-pages and doesn't load the whole animated image on header access
  289. if nPages, _ := img.GetInt("n-pages"); nPages > 0 {
  290. scale := calcScale(imgWidth, frameHeight, po, imgtype)
  291. if nPages > framesCount || canScaleOnLoad(imgtype, scale) {
  292. // Do some scale-on-load
  293. if err := img.Load(data, imgtype, 1, scale, framesCount); err != nil {
  294. return err
  295. }
  296. }
  297. imgWidth = img.Width()
  298. frameHeight, err = img.GetInt("page-height")
  299. if err != nil {
  300. return err
  301. }
  302. }
  303. delay, err := img.GetInt("gif-delay")
  304. if err != nil {
  305. return err
  306. }
  307. loop, err := img.GetInt("gif-loop")
  308. if err != nil {
  309. return err
  310. }
  311. frames := make([]*vipsImage, framesCount)
  312. defer func() {
  313. for _, frame := range frames {
  314. frame.Clear()
  315. }
  316. }()
  317. var errg errgroup.Group
  318. for i := 0; i < framesCount; i++ {
  319. ind := i
  320. errg.Go(func() error {
  321. frame := new(vipsImage)
  322. if err := img.Extract(frame, 0, ind*frameHeight, imgWidth, frameHeight); err != nil {
  323. return err
  324. }
  325. if err := transformImage(ctx, frame, nil, po, imgtype); err != nil {
  326. return err
  327. }
  328. frames[ind] = frame
  329. return nil
  330. })
  331. }
  332. if err := errg.Wait(); err != nil {
  333. return err
  334. }
  335. checkTimeout(ctx)
  336. if err := img.Arrayjoin(frames); err != nil {
  337. return err
  338. }
  339. img.SetInt("page-height", frames[0].Height())
  340. img.SetInt("gif-delay", delay)
  341. img.SetInt("gif-loop", loop)
  342. img.SetInt("n-pages", framesCount)
  343. return nil
  344. }
  345. func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
  346. runtime.LockOSThread()
  347. defer runtime.UnlockOSThread()
  348. if newRelicEnabled {
  349. newRelicCancel := startNewRelicSegment(ctx, "Processing image")
  350. defer newRelicCancel()
  351. }
  352. if prometheusEnabled {
  353. defer startPrometheusDuration(prometheusProcessingDuration)()
  354. }
  355. defer vipsCleanup()
  356. po := getProcessingOptions(ctx)
  357. data := getImageData(ctx).Bytes()
  358. imgtype := getImageType(ctx)
  359. if po.Gravity.Type == gravitySmart && !vipsSupportSmartcrop {
  360. return nil, func() {}, errSmartCropNotSupported
  361. }
  362. if po.Format == imageTypeUnknown {
  363. if vipsTypeSupportSave[imgtype] {
  364. po.Format = imgtype
  365. } else {
  366. po.Format = imageTypeJPEG
  367. }
  368. }
  369. if po.Resize == resizeCrop {
  370. logWarning("`crop` resizing type is deprecated and will be removed in future versions. Use `crop` processing option instead")
  371. po.Crop.Width, po.Crop.Height = po.Width, po.Height
  372. po.Resize = resizeFit
  373. po.Width, po.Height = 0, 0
  374. }
  375. animationSupport := conf.MaxGifFrames > 1 && vipsSupportAnimation(imgtype) && vipsSupportAnimation(po.Format)
  376. pages := 1
  377. if animationSupport {
  378. pages = -1
  379. }
  380. img := new(vipsImage)
  381. defer img.Clear()
  382. if err := img.Load(data, imgtype, 1, 1.0, pages); err != nil {
  383. return nil, func() {}, err
  384. }
  385. if animationSupport && img.IsAnimated() {
  386. if err := transformAnimated(ctx, img, data, po, imgtype); err != nil {
  387. return nil, func() {}, err
  388. }
  389. } else {
  390. if err := transformImage(ctx, img, data, po, imgtype); err != nil {
  391. return nil, func() {}, err
  392. }
  393. }
  394. checkTimeout(ctx)
  395. if po.Format == imageTypeGIF {
  396. if err := img.CastUchar(); err != nil {
  397. return nil, func() {}, err
  398. }
  399. checkTimeout(ctx)
  400. }
  401. return img.Save(po.Format, po.Quality)
  402. }