resize.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  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. // Resize is used to transform a given image as byte buffer
  12. // with the passed options.
  13. func Resize(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. }
  137. // Finally get the resultant buffer
  138. return vipsSave(image, saveOptions)
  139. }
  140. func normalizeOperation(o *Options, inWidth, inHeight int) {
  141. if !o.Force && !o.Crop && !o.Embed && !o.Enlarge && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) {
  142. o.Force = true
  143. }
  144. }
  145. func shouldTransformImage(o Options, inWidth, inHeight int) bool {
  146. return o.Force || (o.Width > 0 && o.Width != inWidth) ||
  147. (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0
  148. }
  149. func shouldApplyEffects(o Options) bool {
  150. return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 || o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0
  151. }
  152. func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) {
  153. var err error
  154. // Use vips_shrink with the integral reduction
  155. if shrink > 1 {
  156. image, residual, err = shrinkImage(image, o, residual, shrink)
  157. if err != nil {
  158. return nil, err
  159. }
  160. }
  161. residualx, residualy := residual, residual
  162. if o.Force {
  163. residualx = float64(o.Width) / float64(image.Xsize)
  164. residualy = float64(o.Height) / float64(image.Ysize)
  165. }
  166. if o.Force || residual != 0 {
  167. image, err = vipsAffine(image, residualx, residualy, o.Interpolator)
  168. if err != nil {
  169. return nil, err
  170. }
  171. }
  172. if o.Force {
  173. o.Crop = false
  174. o.Embed = false
  175. }
  176. image, err = extractOrEmbedImage(image, o)
  177. if err != nil {
  178. return nil, err
  179. }
  180. debug("Transform: shrink=%v, residual=%v, interpolator=%v",
  181. shrink, residual, o.Interpolator.String())
  182. return image, nil
  183. }
  184. func applyEffects(image *C.VipsImage, o Options) (*C.VipsImage, error) {
  185. var err error
  186. if o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 {
  187. image, err = vipsGaussianBlur(image, o.GaussianBlur)
  188. if err != nil {
  189. return nil, err
  190. }
  191. }
  192. if o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 {
  193. image, err = vipsSharpen(image, o.Sharpen)
  194. if err != nil {
  195. return nil, err
  196. }
  197. }
  198. debug("Effects: gaussSigma=%v, gaussMinAmpl=%v, sharpenRadius=%v",
  199. o.GaussianBlur.Sigma, o.GaussianBlur.MinAmpl, o.Sharpen.Radius)
  200. return image, nil
  201. }
  202. func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) {
  203. var err error
  204. inWidth := int(image.Xsize)
  205. inHeight := int(image.Ysize)
  206. switch {
  207. case o.Gravity == GravitySmart, o.SmartCrop:
  208. image, err = vipsSmartCrop(image, o.Width, o.Height)
  209. break
  210. case o.Crop:
  211. width := int(math.Min(float64(inWidth), float64(o.Width)))
  212. height := int(math.Min(float64(inHeight), float64(o.Height)))
  213. left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
  214. left, top = int(math.Max(float64(left), 0)), int(math.Max(float64(top), 0))
  215. image, err = vipsExtract(image, left, top, width, height)
  216. break
  217. case o.Embed:
  218. left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
  219. image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend, o.Background)
  220. break
  221. case o.Top != 0 || o.Left != 0 || o.AreaWidth != 0 || o.AreaHeight != 0:
  222. if o.AreaWidth == 0 {
  223. o.AreaHeight = o.Width
  224. }
  225. if o.AreaHeight == 0 {
  226. o.AreaHeight = o.Height
  227. }
  228. if o.AreaWidth == 0 || o.AreaHeight == 0 {
  229. return nil, errors.New("Extract area width/height params are required")
  230. }
  231. image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
  232. break
  233. }
  234. return image, err
  235. }
  236. func rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, bool, error) {
  237. var err error
  238. var rotated bool
  239. var direction Direction = -1
  240. if o.NoAutoRotate == false {
  241. rotation, flip := calculateRotationAndFlip(image, o.Rotate)
  242. if flip {
  243. o.Flip = flip
  244. }
  245. if rotation > 0 && o.Rotate == 0 {
  246. o.Rotate = rotation
  247. }
  248. }
  249. if o.Rotate > 0 {
  250. rotated = true
  251. image, err = vipsRotate(image, getAngle(o.Rotate))
  252. }
  253. if o.Flip {
  254. direction = Horizontal
  255. } else if o.Flop {
  256. direction = Vertical
  257. }
  258. if direction != -1 {
  259. rotated = true
  260. image, err = vipsFlip(image, direction)
  261. }
  262. return image, rotated, err
  263. }
  264. func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
  265. if w.Text == "" {
  266. return image, nil
  267. }
  268. // Defaults
  269. if w.Font == "" {
  270. w.Font = WatermarkFont
  271. }
  272. if w.Width == 0 {
  273. w.Width = int(math.Floor(float64(image.Xsize / 6)))
  274. }
  275. if w.DPI == 0 {
  276. w.DPI = 150
  277. }
  278. if w.Margin == 0 {
  279. w.Margin = w.Width
  280. }
  281. if w.Opacity == 0 {
  282. w.Opacity = 0.25
  283. } else if w.Opacity > 1 {
  284. w.Opacity = 1
  285. }
  286. image, err := vipsWatermark(image, w)
  287. if err != nil {
  288. return nil, err
  289. }
  290. return image, nil
  291. }
  292. func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) {
  293. if len(w.Buf) == 0 {
  294. return image, nil
  295. }
  296. if w.Opacity == 0.0 {
  297. w.Opacity = 1.0
  298. }
  299. image, err := vipsDrawWatermark(image, w)
  300. if err != nil {
  301. return nil, err
  302. }
  303. return image, nil
  304. }
  305. func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) {
  306. // Only PNG images are supported for now
  307. if imageType != PNG || o.Background == ColorBlack {
  308. return image, nil
  309. }
  310. return vipsFlattenBackground(image, o.Background)
  311. }
  312. func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
  313. if zoom == 0 {
  314. return image, nil
  315. }
  316. return vipsZoom(image, zoom+1)
  317. }
  318. func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) {
  319. // Use vips_shrink with the integral reduction
  320. image, err := vipsShrink(image, shrink)
  321. if err != nil {
  322. return nil, 0, err
  323. }
  324. // Recalculate residual float based on dimensions of required vs shrunk images
  325. residualx := float64(o.Width) / float64(image.Xsize)
  326. residualy := float64(o.Height) / float64(image.Ysize)
  327. if o.Crop {
  328. residual = math.Max(residualx, residualy)
  329. } else {
  330. residual = math.Min(residualx, residualy)
  331. }
  332. return image, residual, nil
  333. }
  334. func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) {
  335. var image *C.VipsImage
  336. var err error
  337. shrinkOnLoad := 1
  338. // Recalculate integral shrink and double residual
  339. switch {
  340. case shrink >= 8:
  341. factor = factor / 8
  342. shrinkOnLoad = 8
  343. case shrink >= 4:
  344. factor = factor / 4
  345. shrinkOnLoad = 4
  346. case shrink >= 2:
  347. factor = factor / 2
  348. shrinkOnLoad = 2
  349. }
  350. // Reload input using shrink-on-load
  351. if shrinkOnLoad > 1 {
  352. image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad)
  353. }
  354. return image, factor, err
  355. }
  356. func imageCalculations(o *Options, inWidth, inHeight int) float64 {
  357. factor := 1.0
  358. xfactor := float64(inWidth) / float64(o.Width)
  359. yfactor := float64(inHeight) / float64(o.Height)
  360. switch {
  361. // Fixed width and height
  362. case o.Width > 0 && o.Height > 0:
  363. if o.Crop {
  364. factor = math.Min(xfactor, yfactor)
  365. } else {
  366. factor = math.Max(xfactor, yfactor)
  367. }
  368. // Fixed width, auto height
  369. case o.Width > 0:
  370. if o.Crop {
  371. o.Height = inHeight
  372. } else {
  373. factor = xfactor
  374. o.Height = roundFloat(float64(inHeight) / factor)
  375. }
  376. // Fixed height, auto width
  377. case o.Height > 0:
  378. if o.Crop {
  379. o.Width = inWidth
  380. } else {
  381. factor = yfactor
  382. o.Width = roundFloat(float64(inWidth) / factor)
  383. }
  384. // Identity transform
  385. default:
  386. o.Width = inWidth
  387. o.Height = inHeight
  388. break
  389. }
  390. return factor
  391. }
  392. func roundFloat(f float64) int {
  393. if f < 0 {
  394. return int(math.Ceil(f - 0.5))
  395. }
  396. return int(math.Floor(f + 0.5))
  397. }
  398. func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) {
  399. left, top := 0, 0
  400. switch gravity {
  401. case GravityNorth:
  402. left = (inWidth - outWidth + 1) / 2
  403. case GravityEast:
  404. left = inWidth - outWidth
  405. top = (inHeight - outHeight + 1) / 2
  406. case GravitySouth:
  407. left = (inWidth - outWidth + 1) / 2
  408. top = inHeight - outHeight
  409. case GravityWest:
  410. top = (inHeight - outHeight + 1) / 2
  411. default:
  412. left = (inWidth - outWidth + 1) / 2
  413. top = (inHeight - outHeight + 1) / 2
  414. }
  415. return left, top
  416. }
  417. func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) {
  418. rotate := D0
  419. flip := false
  420. if angle > 0 {
  421. return rotate, flip
  422. }
  423. switch vipsExifOrientation(image) {
  424. case 6:
  425. rotate = D90
  426. break
  427. case 3:
  428. rotate = D180
  429. break
  430. case 8:
  431. rotate = D270
  432. break
  433. case 2:
  434. flip = true
  435. break // flip 1
  436. case 7:
  437. flip = true
  438. rotate = D90
  439. break // flip 6
  440. case 4:
  441. flip = true
  442. rotate = D180
  443. break // flip 3
  444. case 5:
  445. flip = true
  446. rotate = D270
  447. break // flip 8
  448. }
  449. return rotate, flip
  450. }
  451. func calculateShrink(factor float64, i Interpolator) int {
  452. var shrink float64
  453. // Calculate integral box shrink
  454. windowSize := vipsWindowSize(i.String())
  455. if factor >= 2 && windowSize > 3 {
  456. // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
  457. shrink = float64(math.Floor(factor * 3.0 / windowSize))
  458. } else {
  459. shrink = math.Floor(factor)
  460. }
  461. return int(math.Max(shrink, 1))
  462. }
  463. func calculateResidual(factor float64, shrink int) float64 {
  464. return float64(shrink) / factor
  465. }
  466. func getAngle(angle Angle) Angle {
  467. divisor := angle % 90
  468. if divisor != 0 {
  469. angle = angle - divisor
  470. }
  471. return Angle(math.Min(float64(angle), 270))
  472. }