resizer.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. package bimg
  2. /*
  3. #cgo pkg-config: vips
  4. #include "vips/vips.h"
  5. */
  6. import "C"
  7. import (
  8. "errors"
  9. "math"
  10. )
  11. // resizer is used to transform a given image as byte buffer
  12. // with the passed options.
  13. func resizer(buf []byte, o Options) ([]byte, error) {
  14. defer C.vips_thread_shutdown()
  15. image, imageType, err := loadImage(buf)
  16. if err != nil {
  17. return nil, err
  18. }
  19. // Clone and define default options
  20. o = applyDefaults(o, imageType)
  21. if !IsTypeSupported(o.Type) {
  22. return nil, errors.New("Unsupported image output type")
  23. }
  24. debug("Options: %#v", o)
  25. // Auto rotate image based on EXIF orientation header
  26. image, rotated, err := rotateAndFlipImage(image, o)
  27. if err != nil {
  28. return nil, err
  29. }
  30. // If JPEG image, retrieve the buffer
  31. if rotated && imageType == JPEG && !o.NoAutoRotate {
  32. buf, err = getImageBuffer(image)
  33. if err != nil {
  34. return nil, err
  35. }
  36. }
  37. inWidth := int(image.Xsize)
  38. inHeight := int(image.Ysize)
  39. // Infer the required operation based on the in/out image sizes for a coherent transformation
  40. normalizeOperation(&o, inWidth, inHeight)
  41. // image calculations
  42. factor := imageCalculations(&o, inWidth, inHeight)
  43. shrink := calculateShrink(factor, o.Interpolator)
  44. residual := calculateResidual(factor, shrink)
  45. // Do not enlarge the output if the input width or height
  46. // are already less than the required dimensions
  47. if !o.Enlarge && !o.Force {
  48. if inWidth < o.Width && inHeight < o.Height {
  49. factor = 1.0
  50. shrink = 1
  51. residual = 0
  52. o.Width = inWidth
  53. o.Height = inHeight
  54. }
  55. }
  56. // Try to use libjpeg shrink-on-load
  57. if imageType == JPEG && shrink >= 2 {
  58. tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink)
  59. if err != nil {
  60. return nil, err
  61. }
  62. image = tmpImage
  63. factor = math.Max(factor, 1.0)
  64. shrink = int(math.Floor(factor))
  65. residual = float64(shrink) / factor
  66. }
  67. // Zoom image, if necessary
  68. image, err = zoomImage(image, o.Zoom)
  69. if err != nil {
  70. return nil, err
  71. }
  72. // Transform image, if necessary
  73. if shouldTransformImage(o, inWidth, inHeight) {
  74. image, err = transformImage(image, o, shrink, residual)
  75. if err != nil {
  76. return nil, err
  77. }
  78. }
  79. // Apply effects, if necessary
  80. if shouldApplyEffects(o) {
  81. image, err = applyEffects(image, o)
  82. if err != nil {
  83. return nil, err
  84. }
  85. }
  86. // Add watermark, if necessary
  87. image, err = watermarkImageWithText(image, o.Watermark)
  88. if err != nil {
  89. return nil, err
  90. }
  91. // Add watermark, if necessary
  92. image, err = watermarkImageWithAnotherImage(image, o.WatermarkImage)
  93. if err != nil {
  94. return nil, err
  95. }
  96. // Flatten image on a background, if necessary
  97. image, err = imageFlatten(image, imageType, o)
  98. if err != nil {
  99. return nil, err
  100. }
  101. return saveImage(image, o)
  102. }
  103. func loadImage(buf []byte) (*C.VipsImage, ImageType, error) {
  104. if len(buf) == 0 {
  105. return nil, JPEG, errors.New("Image buffer is empty")
  106. }
  107. image, imageType, err := vipsRead(buf)
  108. if err != nil {
  109. return nil, JPEG, err
  110. }
  111. return image, imageType, nil
  112. }
  113. func applyDefaults(o Options, imageType ImageType) Options {
  114. if o.Quality == 0 {
  115. o.Quality = Quality
  116. }
  117. if o.Compression == 0 {
  118. o.Compression = 6
  119. }
  120. if o.Type == 0 {
  121. o.Type = imageType
  122. }
  123. if o.Interpretation == 0 {
  124. o.Interpretation = InterpretationSRGB
  125. }
  126. return o
  127. }
  128. func saveImage(image *C.VipsImage, o Options) ([]byte, error) {
  129. saveOptions := vipsSaveOptions{
  130. Quality: o.Quality,
  131. Type: o.Type,
  132. Compression: o.Compression,
  133. Interlace: o.Interlace,
  134. NoProfile: o.NoProfile,
  135. Interpretation: o.Interpretation,
  136. OutputICC: o.OutputICC,
  137. StripMetadata: o.StripMetadata,
  138. }
  139. // Finally get the resultant buffer
  140. return vipsSave(image, saveOptions)
  141. }
  142. func normalizeOperation(o *Options, inWidth, inHeight int) {
  143. if !o.Force && !o.Crop && !o.Embed && !o.Enlarge && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) {
  144. o.Force = true
  145. }
  146. }
  147. func shouldTransformImage(o Options, inWidth, inHeight int) bool {
  148. return o.Force || (o.Width > 0 && o.Width != inWidth) ||
  149. (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 ||
  150. o.Trim
  151. }
  152. func shouldApplyEffects(o Options) bool {
  153. return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 || o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0
  154. }
  155. func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) {
  156. var err error
  157. // Use vips_shrink with the integral reduction
  158. if shrink > 1 {
  159. image, residual, err = shrinkImage(image, o, residual, shrink)
  160. if err != nil {
  161. return nil, err
  162. }
  163. }
  164. residualx, residualy := residual, residual
  165. if o.Force {
  166. residualx = float64(o.Width) / float64(image.Xsize)
  167. residualy = float64(o.Height) / float64(image.Ysize)
  168. }
  169. if o.Force || residual != 0 {
  170. if residualx < 1 && residualy < 1 {
  171. image, err = vipsReduce(image, 1/residualx, 1/residualy)
  172. } else {
  173. image, err = vipsAffine(image, residualx, residualy, o.Interpolator)
  174. }
  175. if err != nil {
  176. return nil, err
  177. }
  178. }
  179. if o.Force {
  180. o.Crop = false
  181. o.Embed = false
  182. }
  183. image, err = extractOrEmbedImage(image, o)
  184. if err != nil {
  185. return nil, err
  186. }
  187. debug("Transform: shrink=%v, residual=%v, interpolator=%v",
  188. shrink, residual, o.Interpolator.String())
  189. return image, nil
  190. }
  191. func applyEffects(image *C.VipsImage, o Options) (*C.VipsImage, error) {
  192. var err error
  193. if o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 {
  194. image, err = vipsGaussianBlur(image, o.GaussianBlur)
  195. if err != nil {
  196. return nil, err
  197. }
  198. }
  199. if o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 {
  200. image, err = vipsSharpen(image, o.Sharpen)
  201. if err != nil {
  202. return nil, err
  203. }
  204. }
  205. debug("Effects: gaussSigma=%v, gaussMinAmpl=%v, sharpenRadius=%v",
  206. o.GaussianBlur.Sigma, o.GaussianBlur.MinAmpl, o.Sharpen.Radius)
  207. return image, nil
  208. }
  209. func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) {
  210. var err error
  211. inWidth := int(image.Xsize)
  212. inHeight := int(image.Ysize)
  213. switch {
  214. case o.Gravity == GravitySmart, o.SmartCrop:
  215. image, err = vipsSmartCrop(image, o.Width, o.Height)
  216. break
  217. case o.Crop:
  218. width := int(math.Min(float64(inWidth), float64(o.Width)))
  219. height := int(math.Min(float64(inHeight), float64(o.Height)))
  220. left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
  221. left, top = int(math.Max(float64(left), 0)), int(math.Max(float64(top), 0))
  222. image, err = vipsExtract(image, left, top, width, height)
  223. break
  224. case o.Embed:
  225. left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
  226. image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend, o.Background)
  227. break
  228. case o.Trim:
  229. left, top, width, height, err := vipsTrim(image)
  230. if err == nil {
  231. image, err = vipsExtract(image, left, top, width, height)
  232. }
  233. break
  234. case o.Top != 0 || o.Left != 0 || o.AreaWidth != 0 || o.AreaHeight != 0:
  235. if o.AreaWidth == 0 {
  236. o.AreaHeight = o.Width
  237. }
  238. if o.AreaHeight == 0 {
  239. o.AreaHeight = o.Height
  240. }
  241. if o.AreaWidth == 0 || o.AreaHeight == 0 {
  242. return nil, errors.New("Extract area width/height params are required")
  243. }
  244. image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
  245. break
  246. }
  247. return image, err
  248. }
  249. func rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, bool, error) {
  250. var err error
  251. var rotated bool
  252. var direction Direction = -1
  253. if o.NoAutoRotate == false {
  254. rotation, flip := calculateRotationAndFlip(image, o.Rotate)
  255. if flip {
  256. o.Flip = flip
  257. }
  258. if rotation > 0 && o.Rotate == 0 {
  259. o.Rotate = rotation
  260. }
  261. }
  262. if o.Rotate > 0 {
  263. rotated = true
  264. image, err = vipsRotate(image, getAngle(o.Rotate))
  265. }
  266. if o.Flip {
  267. direction = Horizontal
  268. } else if o.Flop {
  269. direction = Vertical
  270. }
  271. if direction != -1 {
  272. rotated = true
  273. image, err = vipsFlip(image, direction)
  274. }
  275. return image, rotated, err
  276. }
  277. func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
  278. if w.Text == "" {
  279. return image, nil
  280. }
  281. // Defaults
  282. if w.Font == "" {
  283. w.Font = WatermarkFont
  284. }
  285. if w.Width == 0 {
  286. w.Width = int(math.Floor(float64(image.Xsize / 6)))
  287. }
  288. if w.DPI == 0 {
  289. w.DPI = 150
  290. }
  291. if w.Margin == 0 {
  292. w.Margin = w.Width
  293. }
  294. if w.Opacity == 0 {
  295. w.Opacity = 0.25
  296. } else if w.Opacity > 1 {
  297. w.Opacity = 1
  298. }
  299. image, err := vipsWatermark(image, w)
  300. if err != nil {
  301. return nil, err
  302. }
  303. return image, nil
  304. }
  305. func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) {
  306. if len(w.Buf) == 0 {
  307. return image, nil
  308. }
  309. if w.Opacity == 0.0 {
  310. w.Opacity = 1.0
  311. }
  312. image, err := vipsDrawWatermark(image, w)
  313. if err != nil {
  314. return nil, err
  315. }
  316. return image, nil
  317. }
  318. func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) {
  319. // Only PNG images are supported for now
  320. if imageType != PNG || o.Background == ColorBlack {
  321. return image, nil
  322. }
  323. return vipsFlattenBackground(image, o.Background)
  324. }
  325. func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
  326. if zoom == 0 {
  327. return image, nil
  328. }
  329. return vipsZoom(image, zoom+1)
  330. }
  331. func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) {
  332. // Use vips_shrink with the integral reduction
  333. image, err := vipsShrink(image, shrink)
  334. if err != nil {
  335. return nil, 0, err
  336. }
  337. // Recalculate residual float based on dimensions of required vs shrunk images
  338. residualx := float64(o.Width) / float64(image.Xsize)
  339. residualy := float64(o.Height) / float64(image.Ysize)
  340. if o.Crop {
  341. residual = math.Max(residualx, residualy)
  342. } else {
  343. residual = math.Min(residualx, residualy)
  344. }
  345. return image, residual, nil
  346. }
  347. func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) {
  348. var image *C.VipsImage
  349. var err error
  350. shrinkOnLoad := 1
  351. // Recalculate integral shrink and double residual
  352. switch {
  353. case shrink >= 8:
  354. factor = factor / 8
  355. shrinkOnLoad = 8
  356. case shrink >= 4:
  357. factor = factor / 4
  358. shrinkOnLoad = 4
  359. case shrink >= 2:
  360. factor = factor / 2
  361. shrinkOnLoad = 2
  362. }
  363. // Reload input using shrink-on-load
  364. if shrinkOnLoad > 1 {
  365. image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad)
  366. }
  367. return image, factor, err
  368. }
  369. func imageCalculations(o *Options, inWidth, inHeight int) float64 {
  370. factor := 1.0
  371. xfactor := float64(inWidth) / float64(o.Width)
  372. yfactor := float64(inHeight) / float64(o.Height)
  373. switch {
  374. // Fixed width and height
  375. case o.Width > 0 && o.Height > 0:
  376. if o.Crop {
  377. factor = math.Min(xfactor, yfactor)
  378. } else {
  379. factor = math.Max(xfactor, yfactor)
  380. }
  381. // Fixed width, auto height
  382. case o.Width > 0:
  383. if o.Crop {
  384. o.Height = inHeight
  385. } else {
  386. factor = xfactor
  387. o.Height = roundFloat(float64(inHeight) / factor)
  388. }
  389. // Fixed height, auto width
  390. case o.Height > 0:
  391. if o.Crop {
  392. o.Width = inWidth
  393. } else {
  394. factor = yfactor
  395. o.Width = roundFloat(float64(inWidth) / factor)
  396. }
  397. // Identity transform
  398. default:
  399. o.Width = inWidth
  400. o.Height = inHeight
  401. break
  402. }
  403. return factor
  404. }
  405. func roundFloat(f float64) int {
  406. if f < 0 {
  407. return int(math.Ceil(f - 0.5))
  408. }
  409. return int(math.Floor(f + 0.5))
  410. }
  411. func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) {
  412. left, top := 0, 0
  413. switch gravity {
  414. case GravityNorth:
  415. left = (inWidth - outWidth + 1) / 2
  416. case GravityEast:
  417. left = inWidth - outWidth
  418. top = (inHeight - outHeight + 1) / 2
  419. case GravitySouth:
  420. left = (inWidth - outWidth + 1) / 2
  421. top = inHeight - outHeight
  422. case GravityWest:
  423. top = (inHeight - outHeight + 1) / 2
  424. default:
  425. left = (inWidth - outWidth + 1) / 2
  426. top = (inHeight - outHeight + 1) / 2
  427. }
  428. return left, top
  429. }
  430. func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) {
  431. rotate := D0
  432. flip := false
  433. if angle > 0 {
  434. return rotate, flip
  435. }
  436. switch vipsExifOrientation(image) {
  437. case 6:
  438. rotate = D90
  439. break
  440. case 3:
  441. rotate = D180
  442. break
  443. case 8:
  444. rotate = D270
  445. break
  446. case 2:
  447. flip = true
  448. break // flip 1
  449. case 7:
  450. flip = true
  451. rotate = D270
  452. break // flip 6
  453. case 4:
  454. flip = true
  455. rotate = D180
  456. break // flip 3
  457. case 5:
  458. flip = true
  459. rotate = D90
  460. break // flip 8
  461. }
  462. return rotate, flip
  463. }
  464. func calculateShrink(factor float64, i Interpolator) int {
  465. var shrink float64
  466. // Calculate integral box shrink
  467. windowSize := vipsWindowSize(i.String())
  468. if factor >= 2 && windowSize > 3 {
  469. // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
  470. shrink = float64(math.Floor(factor * 3.0 / windowSize))
  471. } else {
  472. shrink = math.Floor(factor)
  473. }
  474. return int(math.Max(shrink, 1))
  475. }
  476. func calculateResidual(factor float64, shrink int) float64 {
  477. return float64(shrink) / factor
  478. }
  479. func getAngle(angle Angle) Angle {
  480. divisor := angle % 90
  481. if divisor != 0 {
  482. angle = angle - divisor
  483. }
  484. return Angle(math.Min(float64(angle), 270))
  485. }