vips.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. package main
  2. /*
  3. #cgo pkg-config: vips
  4. #cgo LDFLAGS: -s -w
  5. #cgo CFLAGS: -O3
  6. #include "vips.h"
  7. */
  8. import "C"
  9. import (
  10. "context"
  11. "math"
  12. "os"
  13. "runtime"
  14. "time"
  15. "unsafe"
  16. )
  17. type vipsImage struct {
  18. VipsImage *C.VipsImage
  19. }
  20. var (
  21. vipsSupportSmartcrop bool
  22. vipsTypeSupportLoad = make(map[imageType]bool)
  23. vipsTypeSupportSave = make(map[imageType]bool)
  24. watermark *vipsImage
  25. )
  26. var vipsConf struct {
  27. JpegProgressive C.int
  28. PngInterlaced C.int
  29. PngQuantize C.int
  30. PngQuantizationColors C.int
  31. WatermarkOpacity C.double
  32. }
  33. const (
  34. vipsAngleD0 = C.VIPS_ANGLE_D0
  35. vipsAngleD90 = C.VIPS_ANGLE_D90
  36. vipsAngleD180 = C.VIPS_ANGLE_D180
  37. vipsAngleD270 = C.VIPS_ANGLE_D270
  38. )
  39. func initVips() {
  40. runtime.LockOSThread()
  41. defer runtime.UnlockOSThread()
  42. if err := C.vips_initialize(); err != 0 {
  43. C.vips_shutdown()
  44. logFatal("unable to start vips!")
  45. }
  46. // Disable libvips cache. Since processing pipeline is fine tuned, we won't get much profit from it.
  47. // Enabled cache can cause SIGSEGV on Musl-based systems like Alpine.
  48. C.vips_cache_set_max_mem(0)
  49. C.vips_cache_set_max(0)
  50. C.vips_concurrency_set(1)
  51. if len(os.Getenv("IMGPROXY_VIPS_LEAK_CHECK")) > 0 {
  52. C.vips_leak_set(C.gboolean(1))
  53. }
  54. if len(os.Getenv("IMGPROXY_VIPS_CACHE_TRACE")) > 0 {
  55. C.vips_cache_set_trace(C.gboolean(1))
  56. }
  57. vipsSupportSmartcrop = C.vips_support_smartcrop() == 1
  58. if int(C.vips_type_find_load_go(C.int(imageTypeJPEG))) != 0 {
  59. vipsTypeSupportLoad[imageTypeJPEG] = true
  60. }
  61. if int(C.vips_type_find_load_go(C.int(imageTypePNG))) != 0 {
  62. vipsTypeSupportLoad[imageTypePNG] = true
  63. }
  64. if int(C.vips_type_find_load_go(C.int(imageTypeWEBP))) != 0 {
  65. vipsTypeSupportLoad[imageTypeWEBP] = true
  66. }
  67. if int(C.vips_type_find_load_go(C.int(imageTypeGIF))) != 0 {
  68. vipsTypeSupportLoad[imageTypeGIF] = true
  69. }
  70. if int(C.vips_type_find_load_go(C.int(imageTypeSVG))) != 0 {
  71. vipsTypeSupportLoad[imageTypeSVG] = true
  72. }
  73. if int(C.vips_type_find_load_go(C.int(imageTypeHEIC))) != 0 {
  74. vipsTypeSupportLoad[imageTypeHEIC] = true
  75. }
  76. // we load ICO with github.com/mat/besticon/ico and send decoded data to vips
  77. vipsTypeSupportLoad[imageTypeICO] = true
  78. if int(C.vips_type_find_save_go(C.int(imageTypeJPEG))) != 0 {
  79. vipsTypeSupportSave[imageTypeJPEG] = true
  80. }
  81. if int(C.vips_type_find_save_go(C.int(imageTypePNG))) != 0 {
  82. vipsTypeSupportSave[imageTypePNG] = true
  83. }
  84. if int(C.vips_type_find_save_go(C.int(imageTypeWEBP))) != 0 {
  85. vipsTypeSupportSave[imageTypeWEBP] = true
  86. }
  87. if int(C.vips_type_find_save_go(C.int(imageTypeGIF))) != 0 {
  88. vipsTypeSupportSave[imageTypeGIF] = true
  89. }
  90. if int(C.vips_type_find_save_go(C.int(imageTypeICO))) != 0 {
  91. vipsTypeSupportSave[imageTypeICO] = true
  92. }
  93. if int(C.vips_type_find_save_go(C.int(imageTypeHEIC))) != 0 {
  94. vipsTypeSupportSave[imageTypeHEIC] = true
  95. }
  96. if conf.JpegProgressive {
  97. vipsConf.JpegProgressive = C.int(1)
  98. }
  99. if conf.PngInterlaced {
  100. vipsConf.PngInterlaced = C.int(1)
  101. }
  102. if conf.PngQuantize {
  103. vipsConf.PngQuantize = C.int(1)
  104. }
  105. vipsConf.PngQuantizationColors = C.int(conf.PngQuantizationColors)
  106. vipsConf.WatermarkOpacity = C.double(conf.WatermarkOpacity)
  107. if err := vipsPrepareWatermark(); err != nil {
  108. logFatal(err.Error())
  109. }
  110. vipsCollectMetrics()
  111. }
  112. func shutdownVips() {
  113. if watermark != nil {
  114. watermark.Clear()
  115. }
  116. C.vips_shutdown()
  117. }
  118. func vipsCollectMetrics() {
  119. if prometheusEnabled {
  120. go func() {
  121. for range time.Tick(5 * time.Second) {
  122. prometheusVipsMemory.Set(float64(C.vips_tracked_get_mem()))
  123. prometheusVipsMaxMemory.Set(float64(C.vips_tracked_get_mem_highwater()))
  124. prometheusVipsAllocs.Set(float64(C.vips_tracked_get_allocs()))
  125. }
  126. }()
  127. }
  128. }
  129. func vipsCleanup() {
  130. C.vips_cleanup()
  131. }
  132. func vipsError() error {
  133. return newUnexpectedError(C.GoString(C.vips_error_buffer()), 1)
  134. }
  135. func vipsPrepareWatermark() error {
  136. data, imgtype, cancel, err := watermarkData()
  137. defer cancel()
  138. if err != nil {
  139. return err
  140. }
  141. if data == nil {
  142. return nil
  143. }
  144. watermark = new(vipsImage)
  145. if err = watermark.Load(data, imgtype, 1, 1.0, 1); err != nil {
  146. return err
  147. }
  148. var tmp *C.VipsImage
  149. if C.vips_apply_opacity(watermark.VipsImage, &tmp, C.double(conf.WatermarkOpacity)) != 0 {
  150. return vipsError()
  151. }
  152. C.swap_and_clear(&watermark.VipsImage, tmp)
  153. if err = watermark.CopyMemory(); err != nil {
  154. return err
  155. }
  156. return nil
  157. }
  158. func vipsResizeWatermark(width, height int) (wm *vipsImage, err error) {
  159. wmW := float64(watermark.VipsImage.Xsize)
  160. wmH := float64(watermark.VipsImage.Ysize)
  161. wr := float64(width) / wmW
  162. hr := float64(height) / wmH
  163. scale := math.Min(wr, hr)
  164. if wmW*scale < 1 {
  165. scale = 1 / wmW
  166. }
  167. if wmH*scale < 1 {
  168. scale = 1 / wmH
  169. }
  170. wm = new(vipsImage)
  171. if C.vips_resize_with_premultiply(watermark.VipsImage, &wm.VipsImage, C.double(scale)) != 0 {
  172. err = vipsError()
  173. }
  174. return
  175. }
  176. func (img *vipsImage) Width() int {
  177. return int(img.VipsImage.Xsize)
  178. }
  179. func (img *vipsImage) Height() int {
  180. return int(img.VipsImage.Ysize)
  181. }
  182. func (img *vipsImage) Load(data []byte, imgtype imageType, shrink int, scale float64, pages int) error {
  183. var tmp *C.VipsImage
  184. err := C.int(0)
  185. switch imgtype {
  186. case imageTypeJPEG:
  187. err = C.vips_jpegload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(shrink), &tmp)
  188. case imageTypePNG:
  189. err = C.vips_pngload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &tmp)
  190. case imageTypeWEBP:
  191. err = C.vips_webpload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.double(scale), C.int(pages), &tmp)
  192. case imageTypeGIF:
  193. err = C.vips_gifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(pages), &tmp)
  194. case imageTypeSVG:
  195. err = C.vips_svgload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.double(scale), &tmp)
  196. case imageTypeICO:
  197. rawData, width, height, icoErr := icoData(data)
  198. if icoErr != nil {
  199. return icoErr
  200. }
  201. tmp = C.vips_image_new_from_memory_copy(unsafe.Pointer(&rawData[0]), C.size_t(width*height*4), C.int(width), C.int(height), 4, C.VIPS_FORMAT_UCHAR)
  202. case imageTypeHEIC:
  203. err = C.vips_heifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &tmp)
  204. }
  205. if err != 0 {
  206. return vipsError()
  207. }
  208. C.swap_and_clear(&img.VipsImage, tmp)
  209. return nil
  210. }
  211. func (img *vipsImage) Save(imgtype imageType, quality int) ([]byte, context.CancelFunc, error) {
  212. var ptr unsafe.Pointer
  213. cancel := func() {
  214. C.g_free_go(&ptr)
  215. }
  216. err := C.int(0)
  217. imgsize := C.size_t(0)
  218. switch imgtype {
  219. case imageTypeJPEG:
  220. err = C.vips_jpegsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.JpegProgressive)
  221. case imageTypePNG:
  222. err = C.vips_pngsave_go(img.VipsImage, &ptr, &imgsize, vipsConf.PngInterlaced, vipsConf.PngQuantize, vipsConf.PngQuantizationColors)
  223. case imageTypeWEBP:
  224. err = C.vips_webpsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality))
  225. case imageTypeGIF:
  226. err = C.vips_gifsave_go(img.VipsImage, &ptr, &imgsize)
  227. case imageTypeICO:
  228. err = C.vips_icosave_go(img.VipsImage, &ptr, &imgsize)
  229. case imageTypeHEIC:
  230. err = C.vips_heifsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality))
  231. }
  232. if err != 0 {
  233. C.g_free_go(&ptr)
  234. return nil, cancel, vipsError()
  235. }
  236. const maxBufSize = ^uint32(0)
  237. b := (*[maxBufSize]byte)(ptr)[:int(imgsize):int(imgsize)]
  238. return b, cancel, nil
  239. }
  240. func (img *vipsImage) Clear() {
  241. if img.VipsImage != nil {
  242. C.clear_image(&img.VipsImage)
  243. }
  244. }
  245. func (img *vipsImage) Arrayjoin(in []*vipsImage) error {
  246. var tmp *C.VipsImage
  247. arr := make([]*C.VipsImage, len(in))
  248. for i, im := range in {
  249. arr[i] = im.VipsImage
  250. }
  251. if C.vips_arrayjoin_go(&arr[0], &tmp, C.int(len(arr))) != 0 {
  252. return vipsError()
  253. }
  254. C.swap_and_clear(&img.VipsImage, tmp)
  255. return nil
  256. }
  257. func vipsSupportAnimation(imgtype imageType) bool {
  258. return imgtype == imageTypeGIF ||
  259. (imgtype == imageTypeWEBP && C.vips_support_webp_animation() != 0)
  260. }
  261. func (img *vipsImage) IsAnimated() bool {
  262. return C.vips_is_animated(img.VipsImage) > 0
  263. }
  264. func (img *vipsImage) HasAlpha() bool {
  265. return C.vips_image_hasalpha_go(img.VipsImage) > 0
  266. }
  267. func (img *vipsImage) GetInt(name string) (int, error) {
  268. var i C.int
  269. if C.vips_image_get_int(img.VipsImage, cachedCString(name), &i) != 0 {
  270. return 0, vipsError()
  271. }
  272. return int(i), nil
  273. }
  274. func (img *vipsImage) SetInt(name string, value int) {
  275. C.vips_image_set_int(img.VipsImage, cachedCString(name), C.int(value))
  276. }
  277. func (img *vipsImage) CastUchar() error {
  278. var tmp *C.VipsImage
  279. if C.vips_image_get_format(img.VipsImage) != C.VIPS_FORMAT_UCHAR {
  280. if C.vips_cast_go(img.VipsImage, &tmp, C.VIPS_FORMAT_UCHAR) != 0 {
  281. return vipsError()
  282. }
  283. C.swap_and_clear(&img.VipsImage, tmp)
  284. }
  285. return nil
  286. }
  287. func (img *vipsImage) Rad2Float() error {
  288. var tmp *C.VipsImage
  289. if C.vips_image_get_coding(img.VipsImage) == C.VIPS_CODING_RAD {
  290. if C.vips_rad2float_go(img.VipsImage, &tmp) != 0 {
  291. return vipsError()
  292. }
  293. C.swap_and_clear(&img.VipsImage, tmp)
  294. }
  295. return nil
  296. }
  297. func (img *vipsImage) Resize(scale float64, hasAlpa bool) error {
  298. var tmp *C.VipsImage
  299. if hasAlpa {
  300. if C.vips_resize_with_premultiply(img.VipsImage, &tmp, C.double(scale)) != 0 {
  301. return vipsError()
  302. }
  303. } else {
  304. if C.vips_resize_go(img.VipsImage, &tmp, C.double(scale)) != 0 {
  305. return vipsError()
  306. }
  307. }
  308. C.swap_and_clear(&img.VipsImage, tmp)
  309. return nil
  310. }
  311. func (img *vipsImage) Orientation() C.int {
  312. return C.vips_get_exif_orientation(img.VipsImage)
  313. }
  314. func (img *vipsImage) Rotate(angle int) error {
  315. var tmp *C.VipsImage
  316. if C.vips_rot_go(img.VipsImage, &tmp, C.VipsAngle(angle)) != 0 {
  317. return vipsError()
  318. }
  319. C.swap_and_clear(&img.VipsImage, tmp)
  320. return nil
  321. }
  322. func (img *vipsImage) Flip() error {
  323. var tmp *C.VipsImage
  324. if C.vips_flip_horizontal_go(img.VipsImage, &tmp) != 0 {
  325. return vipsError()
  326. }
  327. C.swap_and_clear(&img.VipsImage, tmp)
  328. return nil
  329. }
  330. func (img *vipsImage) Crop(left, top, width, height int) error {
  331. var tmp *C.VipsImage
  332. if C.vips_extract_area_go(img.VipsImage, &tmp, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
  333. return vipsError()
  334. }
  335. C.swap_and_clear(&img.VipsImage, tmp)
  336. return nil
  337. }
  338. func (img *vipsImage) Extract(out *vipsImage, left, top, width, height int) error {
  339. if C.vips_extract_area_go(img.VipsImage, &out.VipsImage, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
  340. return vipsError()
  341. }
  342. return nil
  343. }
  344. func (img *vipsImage) SmartCrop(width, height int) error {
  345. var tmp *C.VipsImage
  346. if C.vips_smartcrop_go(img.VipsImage, &tmp, C.int(width), C.int(height)) != 0 {
  347. return vipsError()
  348. }
  349. C.swap_and_clear(&img.VipsImage, tmp)
  350. return nil
  351. }
  352. func (img *vipsImage) EnsureAlpha() error {
  353. var tmp *C.VipsImage
  354. if C.vips_ensure_alpha(img.VipsImage, &tmp) != 0 {
  355. return vipsError()
  356. }
  357. C.swap_and_clear(&img.VipsImage, tmp)
  358. return nil
  359. }
  360. func (img *vipsImage) Flatten(bg rgbColor) error {
  361. var tmp *C.VipsImage
  362. if C.vips_flatten_go(img.VipsImage, &tmp, C.double(bg.R), C.double(bg.G), C.double(bg.B)) != 0 {
  363. return vipsError()
  364. }
  365. C.swap_and_clear(&img.VipsImage, tmp)
  366. return nil
  367. }
  368. func (img *vipsImage) Blur(sigma float32) error {
  369. var tmp *C.VipsImage
  370. if C.vips_gaussblur_go(img.VipsImage, &tmp, C.double(sigma)) != 0 {
  371. return vipsError()
  372. }
  373. C.swap_and_clear(&img.VipsImage, tmp)
  374. return nil
  375. }
  376. func (img *vipsImage) Sharpen(sigma float32) error {
  377. var tmp *C.VipsImage
  378. if C.vips_sharpen_go(img.VipsImage, &tmp, C.double(sigma)) != 0 {
  379. return vipsError()
  380. }
  381. C.swap_and_clear(&img.VipsImage, tmp)
  382. return nil
  383. }
  384. func (img *vipsImage) ImportColourProfile(evenSRGB bool) error {
  385. var tmp *C.VipsImage
  386. if img.VipsImage.Coding != C.VIPS_CODING_NONE {
  387. return nil
  388. }
  389. if img.VipsImage.BandFmt != C.VIPS_FORMAT_UCHAR && img.VipsImage.BandFmt != C.VIPS_FORMAT_USHORT {
  390. return nil
  391. }
  392. profile := (*C.char)(nil)
  393. if img.VipsImage.Type == C.VIPS_INTERPRETATION_sRGB {
  394. // No embedded profile for sRGB, ignore
  395. if C.vips_has_embedded_icc(img.VipsImage) == 0 {
  396. return nil
  397. }
  398. // Don't import sRGB IEC61966 2.1 unless evenSRGB
  399. if !evenSRGB && C.vips_icc_is_srgb_iec61966(img.VipsImage) != 0 {
  400. return nil
  401. }
  402. } else if img.VipsImage.Type == C.VIPS_INTERPRETATION_CMYK && C.vips_has_embedded_icc(img.VipsImage) == 0 {
  403. if C.vips_support_builtin_icc() != 0 {
  404. profile = cachedCString("cmyk")
  405. } else {
  406. p, err := cmykProfilePath()
  407. if err != nil {
  408. return err
  409. }
  410. profile = cachedCString(p)
  411. }
  412. }
  413. if C.vips_icc_import_go(img.VipsImage, &tmp, profile) == 0 {
  414. C.swap_and_clear(&img.VipsImage, tmp)
  415. } else {
  416. logWarning("Can't import ICC profile: %s", vipsError())
  417. }
  418. return nil
  419. }
  420. func (img *vipsImage) LinearColourspace() error {
  421. return img.Colorspace(C.VIPS_INTERPRETATION_scRGB)
  422. }
  423. func (img *vipsImage) RgbColourspace() error {
  424. return img.Colorspace(C.VIPS_INTERPRETATION_sRGB)
  425. }
  426. func (img *vipsImage) Colorspace(colorspace C.VipsInterpretation) error {
  427. if C.vips_image_guess_interpretation(img.VipsImage) != colorspace {
  428. var tmp *C.VipsImage
  429. if C.vips_colourspace_go(img.VipsImage, &tmp, colorspace) != 0 {
  430. return vipsError()
  431. }
  432. C.swap_and_clear(&img.VipsImage, tmp)
  433. }
  434. return nil
  435. }
  436. func (img *vipsImage) CopyMemory() error {
  437. var tmp *C.VipsImage
  438. if tmp = C.vips_image_copy_memory(img.VipsImage); tmp == nil {
  439. return vipsError()
  440. }
  441. C.swap_and_clear(&img.VipsImage, tmp)
  442. return nil
  443. }
  444. func (img *vipsImage) Replicate(width, height int) error {
  445. var tmp *C.VipsImage
  446. if C.vips_replicate_go(img.VipsImage, &tmp, C.int(width), C.int(height)) != 0 {
  447. return vipsError()
  448. }
  449. C.swap_and_clear(&img.VipsImage, tmp)
  450. return nil
  451. }
  452. func (img *vipsImage) Embed(gravity gravityType, width, height int, offX, offY int, bg rgbColor) error {
  453. wmWidth := img.Width()
  454. wmHeight := img.Height()
  455. left := (width-wmWidth+1)/2 + offX
  456. top := (height-wmHeight+1)/2 + offY
  457. if gravity == gravityNorth || gravity == gravityNorthEast || gravity == gravityNorthWest {
  458. top = offY
  459. }
  460. if gravity == gravityEast || gravity == gravityNorthEast || gravity == gravitySouthEast {
  461. left = width - wmWidth - offX
  462. }
  463. if gravity == gravitySouth || gravity == gravitySouthEast || gravity == gravitySouthWest {
  464. top = height - wmHeight - offY
  465. }
  466. if gravity == gravityWest || gravity == gravityNorthWest || gravity == gravitySouthWest {
  467. left = offX
  468. }
  469. if left > width {
  470. left = width - wmWidth
  471. } else if left < -wmWidth {
  472. left = 0
  473. }
  474. if top > height {
  475. top = height - wmHeight
  476. } else if top < -wmHeight {
  477. top = 0
  478. }
  479. if err := img.RgbColourspace(); err != nil {
  480. return err
  481. }
  482. var bgc []C.double
  483. if img.HasAlpha() {
  484. bgc = []C.double{C.double(0)}
  485. } else {
  486. bgc = []C.double{C.double(bg.R), C.double(bg.G), C.double(bg.B)}
  487. }
  488. var tmp *C.VipsImage
  489. if C.vips_embed_go(img.VipsImage, &tmp, C.int(left), C.int(top), C.int(width), C.int(height), &bgc[0], C.int(len(bgc))) != 0 {
  490. return vipsError()
  491. }
  492. C.swap_and_clear(&img.VipsImage, tmp)
  493. return nil
  494. }
  495. func (img *vipsImage) ApplyWatermark(opts *watermarkOptions) error {
  496. if watermark == nil {
  497. return nil
  498. }
  499. var (
  500. wm *vipsImage
  501. tmp *C.VipsImage
  502. )
  503. defer func() { wm.Clear() }()
  504. var err error
  505. imgW := img.Width()
  506. imgH := img.Height()
  507. if opts.Scale == 0 {
  508. wm = new(vipsImage)
  509. if C.vips_copy_go(watermark.VipsImage, &wm.VipsImage) != 0 {
  510. return vipsError()
  511. }
  512. } else {
  513. wmW := maxInt(int(float64(imgW)*opts.Scale), 1)
  514. wmH := maxInt(int(float64(imgH)*opts.Scale), 1)
  515. if wm, err = vipsResizeWatermark(wmW, wmH); err != nil {
  516. return err
  517. }
  518. }
  519. if opts.Replicate {
  520. if err = wm.Replicate(imgW, imgH); err != nil {
  521. return err
  522. }
  523. } else {
  524. if err = wm.Embed(opts.Gravity, imgW, imgH, opts.OffsetX, opts.OffsetY, rgbColor{0, 0, 0}); err != nil {
  525. return err
  526. }
  527. }
  528. if C.vips_apply_watermark(img.VipsImage, wm.VipsImage, &tmp, C.double(opts.Opacity)) != 0 {
  529. return vipsError()
  530. }
  531. C.swap_and_clear(&img.VipsImage, tmp)
  532. return nil
  533. }