• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cshum / imagor / 21858410493

10 Feb 2026 09:03AM UTC coverage: 91.819% (-0.1%) from 91.926%
21858410493

push

github

cshum
Merge remote-tracking branch 'origin/master'

35 of 37 new or added lines in 4 files covered. (94.59%)

124 existing lines in 4 files now uncovered.

5791 of 6307 relevant lines covered (91.82%)

1.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.73
/processor/vipsprocessor/filter.go
1
package vipsprocessor
2

3
import (
4
        "context"
5
        "encoding/base64"
6
        "fmt"
7
        "math"
8
        "net/url"
9
        "strconv"
10
        "strings"
11

12
        "github.com/cshum/vipsgen/vips"
13

14
        "github.com/cshum/imagor"
15
        "github.com/cshum/imagor/imagorpath"
16
)
17

18
func (v *Processor) image(ctx context.Context, img *vips.Image, load imagor.LoadFunc, args ...string) (err error) {
1✔
19
        ln := len(args)
1✔
20
        if ln < 1 {
1✔
21
                return
×
22
        }
×
23
        imagorPath := args[0]
1✔
24
        if unescape, e := url.QueryUnescape(args[0]); e == nil {
2✔
25
                imagorPath = unescape
1✔
26
        }
1✔
27
        params := imagorpath.Parse(imagorPath)
1✔
28
        var blob *imagor.Blob
1✔
29
        if blob, err = load(params.Image); err != nil {
1✔
30
                return
×
31
        }
×
32
        // Mark context as nested before processing overlay
33
        ctx = withNestedImage(ctx)
1✔
34
        var overlay *vips.Image
1✔
35
        if overlay, err = v.loadAndProcess(ctx, blob, params, load); err != nil || overlay == nil {
1✔
UNCOV
36
                return
×
UNCOV
37
        }
×
38
        contextDefer(ctx, overlay.Close)
1✔
39

1✔
40
        var xArg, yArg string
1✔
41
        var alpha float64
1✔
42
        var blendMode = vips.BlendModeOver // default to normal
1✔
43

1✔
44
        if ln >= 2 {
2✔
45
                xArg = args[1]
1✔
46
        }
1✔
47
        if ln >= 3 {
2✔
48
                yArg = args[2]
1✔
49
        }
1✔
50
        if ln >= 4 {
2✔
51
                alpha, _ = strconv.ParseFloat(args[3], 64)
1✔
52
        }
1✔
53
        if ln >= 5 {
2✔
54
                // Parse blend mode (5th parameter)
1✔
55
                blendMode = getBlendMode(args[4])
1✔
56
        }
1✔
57

58
        // Transform and composite overlay onto image
59
        return compositeOverlay(img, overlay, xArg, yArg, alpha, blendMode)
1✔
60
}
61

62
func (v *Processor) watermark(ctx context.Context, img *vips.Image, load imagor.LoadFunc, args ...string) (err error) {
1✔
63
        ln := len(args)
1✔
64
        if ln < 1 {
2✔
65
                return
1✔
66
        }
1✔
67
        image := args[0]
1✔
68

1✔
69
        if unescape, e := url.QueryUnescape(args[0]); e == nil {
2✔
70
                image = unescape
1✔
71
        }
1✔
72

73
        if strings.HasPrefix(image, "b64:") {
2✔
74
                // if image URL starts with b64: prefix, Base64 decode it according to "base64url" in RFC 4648 (Section 5).
1✔
75
                result := make([]byte, base64.RawURLEncoding.DecodedLen(len(image[4:])))
1✔
76
                // in case decoding fails, use original image URL (possible that filename starts with b64: prefix, but as part of the file name)
1✔
77
                if _, e := base64.RawURLEncoding.Decode(result, []byte(image[4:])); e == nil {
2✔
78
                        image = string(result)
1✔
79
                }
1✔
80
        }
81

82
        var blob *imagor.Blob
1✔
83
        if blob, err = load(image); err != nil {
1✔
UNCOV
84
                return
×
UNCOV
85
        }
×
86
        var w, h int
1✔
87
        var overlay *vips.Image
1✔
88
        var n = 1
1✔
89
        if isAnimated(img) {
2✔
90
                n = -1
1✔
91
        }
1✔
92
        // w_ratio h_ratio
93
        if ln >= 6 {
2✔
94
                w = img.Width()
1✔
95
                h = img.PageHeight()
1✔
96
                if args[4] != "none" {
2✔
97
                        w, _ = strconv.Atoi(args[4])
1✔
98
                        w = img.Width() * w / 100
1✔
99
                }
1✔
100
                if args[5] != "none" {
2✔
101
                        h, _ = strconv.Atoi(args[5])
1✔
102
                        h = img.PageHeight() * h / 100
1✔
103
                }
1✔
104
                if overlay, err = v.NewThumbnail(
1✔
105
                        ctx, blob, w, h, vips.InterestingNone, vips.SizeBoth, n, 1, 0,
1✔
106
                ); err != nil {
1✔
UNCOV
107
                        return
×
UNCOV
108
                }
×
109
        } else {
1✔
110
                if overlay, err = v.NewThumbnail(
1✔
111
                        ctx, blob, v.MaxWidth, v.MaxHeight, vips.InterestingNone, vips.SizeDown, n, 1, 0,
1✔
112
                ); err != nil {
1✔
UNCOV
113
                        return
×
UNCOV
114
                }
×
115
        }
116
        contextDefer(ctx, overlay.Close)
1✔
117

1✔
118
        // Parse arguments
1✔
119
        var xArg, yArg string
1✔
120
        var alpha float64
1✔
121
        if ln >= 3 {
2✔
122
                xArg = args[1]
1✔
123
                yArg = args[2]
1✔
124
        }
1✔
125
        if ln >= 4 {
2✔
126
                alpha, _ = strconv.ParseFloat(args[3], 64)
1✔
127
        }
1✔
128

129
        // Transform and composite overlay onto image
130
        return compositeOverlay(img, overlay, xArg, yArg, alpha, vips.BlendModeOver)
1✔
131
}
132

133
func (v *Processor) fill(ctx context.Context, img *vips.Image, w, h int, pLeft, pTop, pRight, pBottom int, colour string) (err error) {
1✔
134
        if isRotate90(ctx) {
2✔
135
                tmpW := w
1✔
136
                w = h
1✔
137
                h = tmpW
1✔
138
                tmpPLeft := pLeft
1✔
139
                pLeft = pTop
1✔
140
                pTop = tmpPLeft
1✔
141
                tmpPRight := pRight
1✔
142
                pRight = pBottom
1✔
143
                pBottom = tmpPRight
1✔
144
        }
1✔
145
        c := getColor(img, colour)
1✔
146
        left := (w-img.Width())/2 + pLeft
1✔
147
        top := (h-img.PageHeight())/2 + pTop
1✔
148
        width := w + pLeft + pRight
1✔
149
        height := h + pTop + pBottom
1✔
150
        if colour != "blur" || v.DisableBlur || isAnimated(img) {
2✔
151
                // fill color
1✔
152
                isTransparent := colour == "none" || colour == "transparent"
1✔
153
                if img.HasAlpha() && !isTransparent {
2✔
154
                        c := getColor(img, colour)
1✔
155
                        if err = img.Flatten(&vips.FlattenOptions{Background: c}); err != nil {
1✔
UNCOV
156
                                return
×
UNCOV
157
                        }
×
158
                }
159
                if isTransparent {
2✔
160
                        if img.Bands() < 3 {
2✔
161
                                if err = img.Colourspace(vips.InterpretationSrgb, nil); err != nil {
1✔
UNCOV
162
                                        return
×
UNCOV
163
                                }
×
164
                        }
165
                        if !img.HasAlpha() {
2✔
166
                                if err = img.Addalpha(); err != nil {
1✔
UNCOV
167
                                        return
×
UNCOV
168
                                }
×
169
                        }
170
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{Extend: vips.ExtendBlack}); err != nil {
1✔
UNCOV
171
                                return
×
UNCOV
172
                        }
×
173
                } else if isBlack(c) {
2✔
174
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{Extend: vips.ExtendBlack}); err != nil {
1✔
UNCOV
175
                                return
×
UNCOV
176
                        }
×
177
                } else if isWhite(c) {
2✔
178
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{Extend: vips.ExtendWhite}); err != nil {
1✔
UNCOV
179
                                return
×
UNCOV
180
                        }
×
181
                } else {
1✔
182
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{
1✔
183
                                Extend:     vips.ExtendBackground,
1✔
184
                                Background: c,
1✔
185
                        }); err != nil {
1✔
UNCOV
186
                                return
×
UNCOV
187
                        }
×
188
                }
189
        } else {
1✔
190
                // fill blur
1✔
191
                var cp *vips.Image
1✔
192
                if cp, err = img.Copy(nil); err != nil {
1✔
UNCOV
193
                        return
×
UNCOV
194
                }
×
195
                contextDefer(ctx, cp.Close)
1✔
196
                if err = img.ThumbnailImage(
1✔
197
                        width, &vips.ThumbnailImageOptions{
1✔
198
                                Height: height,
1✔
199
                                Crop:   vips.InterestingNone,
1✔
200
                                Size:   vips.SizeForce,
1✔
201
                        },
1✔
202
                ); err != nil {
1✔
UNCOV
203
                        return
×
UNCOV
204
                }
×
205
                if err = img.Gaussblur(50, nil); err != nil {
1✔
206
                        return
×
UNCOV
207
                }
×
208
                if err = img.Composite2(
1✔
209
                        cp, vips.BlendModeOver,
1✔
210
                        &vips.Composite2Options{X: left, Y: top}); err != nil {
1✔
UNCOV
211
                        return
×
UNCOV
212
                }
×
213
        }
214
        return
1✔
215
}
216

217
func roundCorner(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
218
        var rx, ry int
1✔
219
        var c []float64
1✔
220
        if len(args) == 0 {
2✔
221
                return
1✔
222
        }
1✔
223
        if a, e := url.QueryUnescape(args[0]); e == nil {
2✔
224
                args[0] = a
1✔
225
        }
1✔
226
        if len(args) == 3 {
2✔
227
                // rx,ry,color
1✔
228
                c = getColor(img, args[2])
1✔
229
                args = args[:2]
1✔
230
        }
1✔
231
        rx, _ = strconv.Atoi(args[0])
1✔
232
        ry = rx
1✔
233
        if len(args) > 1 {
2✔
234
                ry, _ = strconv.Atoi(args[1])
1✔
235
        }
1✔
236

237
        var rounded *vips.Image
1✔
238
        var w = img.Width()
1✔
239
        var h = img.PageHeight()
1✔
240
        if rounded, err = vips.NewSvgloadBuffer([]byte(fmt.Sprintf(`
1✔
241
                <svg viewBox="0 0 %d %d">
1✔
242
                        <rect rx="%d" ry="%d" 
1✔
243
                         x="0" y="0" width="%d" height="%d" 
1✔
244
                         fill="#fff"/>
1✔
245
                </svg>
1✔
246
        `, w, h, rx, ry, w, h)), nil); err != nil {
1✔
UNCOV
247
                return
×
UNCOV
248
        }
×
249
        contextDefer(ctx, rounded.Close)
1✔
250
        if n := img.Height() / img.PageHeight(); n > 1 {
2✔
251
                if err = rounded.Replicate(1, n); err != nil {
1✔
UNCOV
252
                        return
×
UNCOV
253
                }
×
254
        }
255
        if err = img.Composite2(rounded, vips.BlendModeDestIn, nil); err != nil {
1✔
UNCOV
256
                return
×
UNCOV
257
        }
×
258
        if c != nil {
2✔
259
                if err = img.Flatten(&vips.FlattenOptions{Background: c}); err != nil {
1✔
UNCOV
260
                        return
×
UNCOV
261
                }
×
262
        }
263
        return nil
1✔
264
}
265

266
func label(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
267
        ln := len(args)
1✔
268
        if ln == 0 {
1✔
UNCOV
269
                return
×
UNCOV
270
        }
×
271
        if a, e := url.QueryUnescape(args[0]); e == nil {
2✔
272
                args[0] = a
1✔
273
        }
1✔
274
        var text = args[0]
1✔
275
        var font = "tahoma"
1✔
276
        var x, y int
1✔
277
        var c []float64
1✔
278
        var alpha float64
1✔
279
        var align = vips.AlignLow
1✔
280
        var size = 20
1✔
281
        var width = img.Width()
1✔
282
        if ln > 3 {
2✔
283
                size, _ = strconv.Atoi(args[3])
1✔
284
        }
1✔
285
        if ln > 1 {
2✔
286
                if args[1] == "center" {
2✔
287
                        align = vips.AlignCentre
1✔
288
                        x = width / 2
1✔
289
                } else if args[1] == imagorpath.HAlignRight {
3✔
290
                        align = vips.AlignHigh
1✔
291
                        x = width
1✔
292
                } else if strings.HasPrefix(strings.TrimPrefix(args[1], "-"), "0.") {
3✔
293
                        pec, _ := strconv.ParseFloat(args[1], 64)
1✔
294
                        x = int(pec * float64(width))
1✔
295
                } else if strings.HasSuffix(args[1], "p") {
3✔
296
                        x, _ = strconv.Atoi(strings.TrimSuffix(args[1], "p"))
1✔
297
                        x = x * width / 100
1✔
298
                } else {
2✔
299
                        x, _ = strconv.Atoi(args[1])
1✔
300
                }
1✔
301
                if x < 0 {
2✔
302
                        align = vips.AlignHigh
1✔
303
                        x += width
1✔
304
                }
1✔
305
        }
306
        if ln > 2 {
2✔
307
                if args[2] == "center" {
2✔
308
                        y = (img.PageHeight() - size) / 2
1✔
309
                } else if args[2] == imagorpath.VAlignTop {
3✔
310
                        y = 0
1✔
311
                } else if args[2] == imagorpath.VAlignBottom {
3✔
312
                        y = img.PageHeight() - size
1✔
313
                } else if strings.HasPrefix(strings.TrimPrefix(args[2], "-"), "0.") {
3✔
314
                        pec, _ := strconv.ParseFloat(args[2], 64)
1✔
315
                        y = int(pec * float64(img.PageHeight()))
1✔
316
                } else if strings.HasSuffix(args[2], "p") {
3✔
317
                        y, _ = strconv.Atoi(strings.TrimSuffix(args[2], "p"))
1✔
318
                        y = y * img.PageHeight() / 100
1✔
319
                } else {
2✔
320
                        y, _ = strconv.Atoi(args[2])
1✔
321
                }
1✔
322
                if y < 0 {
2✔
323
                        y += img.PageHeight() - size
1✔
324
                }
1✔
325
        }
326
        if ln > 4 {
2✔
327
                c = getColor(img, args[4])
1✔
328
        }
1✔
329
        if ln > 5 {
2✔
330
                alpha, _ = strconv.ParseFloat(args[5], 64)
1✔
331
                alpha /= 100
1✔
332
        }
1✔
333
        if ln > 6 {
2✔
334
                if a, e := url.QueryUnescape(args[6]); e == nil {
2✔
335
                        font = a
1✔
336
                } else {
1✔
UNCOV
337
                        font = args[6]
×
UNCOV
338
                }
×
339
        }
340
        if img.Bands() < 3 {
2✔
341
                if err = img.Colourspace(vips.InterpretationSrgb, nil); err != nil {
1✔
UNCOV
342
                        return
×
UNCOV
343
                }
×
344
        }
345
        if !img.HasAlpha() {
2✔
346
                if err = img.Addalpha(); err != nil {
1✔
UNCOV
347
                        return
×
UNCOV
348
                }
×
349
        }
350
        return img.Label(text, x, y, &vips.LabelOptions{
1✔
351
                Font:    font,
1✔
352
                Size:    size,
1✔
353
                Align:   align,
1✔
354
                Opacity: 1 - alpha,
1✔
355
                Color:   c,
1✔
356
        })
1✔
357
}
358

359
func (v *Processor) padding(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) error {
1✔
360
        ln := len(args)
1✔
361
        if ln < 2 {
2✔
362
                return nil
1✔
363
        }
1✔
364
        var (
1✔
365
                c       = args[0]
1✔
366
                left, _ = strconv.Atoi(args[1])
1✔
367
                top     = left
1✔
368
                right   = left
1✔
369
                bottom  = left
1✔
370
        )
1✔
371
        if ln > 2 {
2✔
372
                top, _ = strconv.Atoi(args[2])
1✔
373
                bottom = top
1✔
374
        }
1✔
375
        if ln > 4 {
2✔
376
                right, _ = strconv.Atoi(args[3])
1✔
377
                bottom, _ = strconv.Atoi(args[4])
1✔
378
        }
1✔
379
        return v.fill(ctx, img, img.Width(), img.PageHeight(), left, top, right, bottom, c)
1✔
380
}
381

382
func backgroundColor(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
383
        if len(args) == 0 {
2✔
384
                return
1✔
385
        }
1✔
386
        if !img.HasAlpha() {
2✔
387
                return
1✔
388
        }
1✔
389
        c := getColor(img, args[0])
1✔
390
        return img.Flatten(&vips.FlattenOptions{
1✔
391
                Background: c,
1✔
392
        })
1✔
393
}
394

395
func rotate(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
396
        if len(args) == 0 {
2✔
397
                return
1✔
398
        }
1✔
399
        if angle, _ := strconv.Atoi(args[0]); angle > 0 {
2✔
400
                switch angle {
1✔
401
                case 90, 270:
1✔
402
                        setRotate90(ctx)
1✔
403
                }
404
                if err = img.RotMultiPage(getAngle(angle)); err != nil {
1✔
UNCOV
405
                        return err
×
UNCOV
406
                }
×
407
        }
408
        return
1✔
409
}
410

411
func proportion(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
412
        if len(args) == 0 {
2✔
413
                return
1✔
414
        }
1✔
415
        scale, _ := strconv.ParseFloat(args[0], 64)
1✔
416
        if scale <= 0 {
2✔
417
                return // no ops
1✔
418
        }
1✔
419
        if scale > 100 {
2✔
420
                scale = 100
1✔
421
        }
1✔
422
        if scale > 1 {
2✔
423
                scale /= 100
1✔
424
        }
1✔
425
        width := int(float64(img.Width()) * scale)
1✔
426
        height := int(float64(img.PageHeight()) * scale)
1✔
427
        if width <= 0 || height <= 0 {
2✔
428
                return // op ops
1✔
429
        }
1✔
430
        return img.ThumbnailImage(width, &vips.ThumbnailImageOptions{
1✔
431
                Height: height,
1✔
432
                Crop:   vips.InterestingNone,
1✔
433
        })
1✔
434
}
435

436
func grayscale(_ context.Context, img *vips.Image, _ imagor.LoadFunc, _ ...string) (err error) {
1✔
437
        return img.Colourspace(vips.InterpretationBW, nil)
1✔
438
}
1✔
439

440
func brightness(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
441
        if len(args) == 0 {
2✔
442
                return
1✔
443
        }
1✔
444
        b, _ := strconv.ParseFloat(args[0], 64)
1✔
445
        b = b * 255 / 100
1✔
446
        return linearRGB(img, []float64{1, 1, 1}, []float64{b, b, b})
1✔
447
}
448

449
func contrast(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
450
        if len(args) == 0 {
2✔
451
                return
1✔
452
        }
1✔
453
        a, _ := strconv.ParseFloat(args[0], 64)
1✔
454
        a = a * 255 / 100
1✔
455
        a = math.Min(math.Max(a, -255), 255)
1✔
456
        a = (259 * (a + 255)) / (255 * (259 - a))
1✔
457
        b := 128 - a*128
1✔
458
        return linearRGB(img, []float64{a, a, a}, []float64{b, b, b})
1✔
459
}
460

461
func hue(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
462
        if len(args) == 0 {
2✔
463
                return
1✔
464
        }
1✔
465
        h, _ := strconv.ParseFloat(args[0], 64)
1✔
466
        return img.Modulate(1, 1, h)
1✔
467
}
468

469
func saturation(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
470
        if len(args) == 0 {
2✔
471
                return
1✔
472
        }
1✔
473
        s, _ := strconv.ParseFloat(args[0], 64)
1✔
474
        s = 1 + s/100
1✔
475
        return img.Modulate(1, s, 0)
1✔
476
}
477

478
func rgb(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
479
        if len(args) != 3 {
2✔
480
                return
1✔
481
        }
1✔
482
        r, _ := strconv.ParseFloat(args[0], 64)
1✔
483
        g, _ := strconv.ParseFloat(args[1], 64)
1✔
484
        b, _ := strconv.ParseFloat(args[2], 64)
1✔
485
        r = r * 255 / 100
1✔
486
        g = g * 255 / 100
1✔
487
        b = b * 255 / 100
1✔
488
        return linearRGB(img, []float64{1, 1, 1}, []float64{r, g, b})
1✔
489
}
490

491
func modulate(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
492
        if len(args) != 3 {
2✔
493
                return
1✔
494
        }
1✔
495
        b, _ := strconv.ParseFloat(args[0], 64)
1✔
496
        s, _ := strconv.ParseFloat(args[1], 64)
1✔
497
        h, _ := strconv.ParseFloat(args[2], 64)
1✔
498
        b = 1 + b/100
1✔
499
        s = 1 + s/100
1✔
500
        return img.Modulate(b, s, h)
1✔
501
}
502

503
func blur(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
504
        if isAnimated(img) {
2✔
505
                // skip animation support
1✔
506
                return
1✔
507
        }
1✔
508
        var sigma float64
1✔
509
        switch len(args) {
1✔
510
        case 2:
1✔
511
                sigma, _ = strconv.ParseFloat(args[1], 64)
1✔
512
                break
1✔
513
        case 1:
1✔
514
                sigma, _ = strconv.ParseFloat(args[0], 64)
1✔
515
                break
1✔
516
        }
517
        sigma /= 2
1✔
518
        if sigma > 0 {
2✔
519
                return img.Gaussblur(sigma, nil)
1✔
520
        }
1✔
UNCOV
521
        return
×
522
}
523

524
func sharpen(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
525
        if isAnimated(img) {
2✔
526
                // skip animation support
1✔
527
                return
1✔
528
        }
1✔
529
        var sigma float64
1✔
530
        switch len(args) {
1✔
531
        case 1:
1✔
532
                sigma, _ = strconv.ParseFloat(args[0], 64)
1✔
533
                break
1✔
534
        case 2, 3:
1✔
535
                sigma, _ = strconv.ParseFloat(args[1], 64)
1✔
536
                break
1✔
537
        }
538
        sigma = 1 + sigma*2
1✔
539
        if sigma > 0 {
2✔
540
                return img.Sharpen(&vips.SharpenOptions{
1✔
541
                        Sigma: sigma,
1✔
542
                        X1:    1,
1✔
543
                        M2:    2,
1✔
544
                })
1✔
545
        }
1✔
546
        return
1✔
547
}
548

549
func stripIcc(_ context.Context, img *vips.Image, _ imagor.LoadFunc, _ ...string) (err error) {
1✔
550
        if img.HasICCProfile() {
2✔
551
                opts := vips.DefaultIccTransformOptions()
1✔
552
                opts.Embedded = true
1✔
553
                opts.Intent = vips.IntentPerceptual
1✔
554
                if img.Interpretation() == vips.InterpretationRgb16 {
1✔
UNCOV
555
                        opts.Depth = 16
×
UNCOV
556
                }
×
557
                _ = img.IccTransform("srgb", opts)
1✔
558
        }
559
        return img.RemoveICCProfile()
1✔
560
}
561

562
func toColorspace(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
563
        profile := "srgb"
1✔
564
        if len(args) > 0 && args[0] != "" {
2✔
565
                profile = strings.ToLower(args[0])
1✔
566
        }
1✔
567
        if !img.HasICCProfile() {
2✔
568
                return nil
1✔
569
        }
1✔
570
        opts := vips.DefaultIccTransformOptions()
1✔
571
        opts.Embedded = true
1✔
572
        opts.Intent = vips.IntentPerceptual
1✔
573
        if img.Interpretation() == vips.InterpretationRgb16 {
1✔
UNCOV
574
                opts.Depth = 16
×
UNCOV
575
        }
×
576
        return img.IccTransform(profile, opts)
1✔
577
}
578

579
func stripExif(_ context.Context, img *vips.Image, _ imagor.LoadFunc, _ ...string) (err error) {
1✔
580
        return img.RemoveExif()
1✔
581
}
1✔
582

583
func trim(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) error {
1✔
584
        var (
1✔
585
                ln        = len(args)
1✔
586
                pos       string
1✔
587
                tolerance int
1✔
588
        )
1✔
589
        if ln > 0 {
2✔
590
                tolerance, _ = strconv.Atoi(args[0])
1✔
591
        }
1✔
592
        if ln > 1 {
2✔
593
                pos = args[1]
1✔
594
        }
1✔
595
        if l, t, w, h, err := findTrim(ctx, img, pos, tolerance); err == nil {
2✔
596
                return img.ExtractAreaMultiPage(l, t, w, h)
1✔
597
        }
1✔
UNCOV
598
        return nil
×
599
}
600

601
func crop(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) error {
1✔
602
        if len(args) < 4 {
1✔
UNCOV
603
                return nil
×
UNCOV
604
        }
×
605

606
        // Parse arguments
607
        left, _ := strconv.ParseFloat(args[0], 64)
1✔
608
        top, _ := strconv.ParseFloat(args[1], 64)
1✔
609
        width, _ := strconv.ParseFloat(args[2], 64)
1✔
610
        height, _ := strconv.ParseFloat(args[3], 64)
1✔
611

1✔
612
        imgWidth := float64(img.Width())
1✔
613
        imgHeight := float64(img.PageHeight())
1✔
614

1✔
615
        // Convert relative (0-1) to absolute pixels
1✔
616
        if left > 0 && left < 1 {
2✔
617
                left = left * imgWidth
1✔
618
        }
1✔
619
        if top > 0 && top < 1 {
2✔
620
                top = top * imgHeight
1✔
621
        }
1✔
622
        if width > 0 && width < 1 {
2✔
623
                width = width * imgWidth
1✔
624
        }
1✔
625
        if height > 0 && height < 1 {
2✔
626
                height = height * imgHeight
1✔
627
        }
1✔
628

629
        // Clamp left and top to image bounds
630
        left = math.Max(0, math.Min(left, imgWidth))
1✔
631
        top = math.Max(0, math.Min(top, imgHeight))
1✔
632

1✔
633
        // Adjust width and height to not exceed image bounds
1✔
634
        width = math.Min(width, imgWidth-left)
1✔
635
        height = math.Min(height, imgHeight-top)
1✔
636

1✔
637
        // Skip if invalid crop area
1✔
638
        if width <= 0 || height <= 0 {
1✔
UNCOV
639
                return nil
×
UNCOV
640
        }
×
641

642
        return img.ExtractAreaMultiPage(int(left), int(top), int(width), int(height))
1✔
643
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc