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

cshum / imagor / 19076656044

04 Nov 2025 05:03PM UTC coverage: 92.442% (-0.04%) from 92.479%
19076656044

push

github

web-flow
feat: crop(left,top,width,height) filter (#648)

* feat: crop(left,top,width,height) filter

* test: update golden files

---------

Co-authored-by: cshum <293790+cshum@users.noreply.github.com>

34 of 38 new or added lines in 2 files covered. (89.47%)

1 existing line in 1 file now uncovered.

5394 of 5835 relevant lines covered (92.44%)

1.11 hits per line

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

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

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

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

15
        "github.com/cshum/imagor"
16
        "github.com/cshum/imagor/imagorpath"
17
        "golang.org/x/image/colornames"
18
)
19

20
func (v *Processor) watermark(ctx context.Context, img *vips.Image, load imagor.LoadFunc, args ...string) (err error) {
1✔
21
        ln := len(args)
1✔
22
        if ln < 1 {
2✔
23
                return
1✔
24
        }
1✔
25
        image := args[0]
1✔
26

1✔
27
        if unescape, e := url.QueryUnescape(args[0]); e == nil {
2✔
28
                image = unescape
1✔
29
        }
1✔
30

31
        if strings.HasPrefix(image, "b64:") {
2✔
32
                // if image URL starts with b64: prefix, Base64 decode it according to "base64url" in RFC 4648 (Section 5).
1✔
33
                result := make([]byte, base64.RawURLEncoding.DecodedLen(len(image[4:])))
1✔
34
                // in case decoding fails, use original image URL (possible that filename starts with b64: prefix, but as part of the file name)
1✔
35
                if _, e := base64.RawURLEncoding.Decode(result, []byte(image[4:])); e == nil {
2✔
36
                        image = string(result)
1✔
37
                }
1✔
38
        }
39

40
        var blob *imagor.Blob
1✔
41
        if blob, err = load(image); err != nil {
1✔
42
                return
×
43
        }
×
44
        var x, y, w, h int
1✔
45
        var across = 1
1✔
46
        var down = 1
1✔
47
        var overlay *vips.Image
1✔
48
        var n = 1
1✔
49
        if isAnimated(img) {
2✔
50
                n = -1
1✔
51
        }
1✔
52
        // w_ratio h_ratio
53
        if ln >= 6 {
2✔
54
                w = img.Width()
1✔
55
                h = img.PageHeight()
1✔
56
                if args[4] != "none" {
2✔
57
                        w, _ = strconv.Atoi(args[4])
1✔
58
                        w = img.Width() * w / 100
1✔
59
                }
1✔
60
                if args[5] != "none" {
2✔
61
                        h, _ = strconv.Atoi(args[5])
1✔
62
                        h = img.PageHeight() * h / 100
1✔
63
                }
1✔
64
                if overlay, err = v.NewThumbnail(
1✔
65
                        ctx, blob, w, h, vips.InterestingNone, vips.SizeBoth, n, 1, 0,
1✔
66
                ); err != nil {
1✔
67
                        return
×
68
                }
×
69
        } else {
1✔
70
                if overlay, err = v.NewThumbnail(
1✔
71
                        ctx, blob, v.MaxWidth, v.MaxHeight, vips.InterestingNone, vips.SizeDown, n, 1, 0,
1✔
72
                ); err != nil {
1✔
73
                        return
×
74
                }
×
75
        }
76
        var overlayN = overlay.Height() / overlay.PageHeight()
1✔
77
        contextDefer(ctx, overlay.Close)
1✔
78
        if overlay.Bands() < 3 {
2✔
79
                if err = overlay.Colourspace(vips.InterpretationSrgb, nil); err != nil {
1✔
80
                        return
×
81
                }
×
82
        }
83
        if !overlay.HasAlpha() {
2✔
84
                if err = overlay.Addalpha(); err != nil {
1✔
85
                        return
×
86
                }
×
87
        }
88
        w = overlay.Width()
1✔
89
        h = overlay.PageHeight()
1✔
90
        // alpha
1✔
91
        if ln >= 4 {
2✔
92
                alpha, _ := strconv.ParseFloat(args[3], 64)
1✔
93
                alpha = 1 - alpha/100
1✔
94
                if alpha != 1 {
2✔
95
                        if err = overlay.Linear([]float64{1, 1, 1, alpha}, []float64{0, 0, 0, 0}, nil); err != nil {
1✔
96
                                return
×
97
                        }
×
98
                }
99
        }
100
        // x y
101
        if ln >= 3 {
2✔
102
                if args[1] == "center" {
2✔
103
                        x = (img.Width() - overlay.Width()) / 2
1✔
104
                } else if args[1] == imagorpath.HAlignLeft {
3✔
105
                        x = 0
1✔
106
                } else if args[1] == imagorpath.HAlignRight {
3✔
107
                        x = img.Width() - overlay.Width()
1✔
108
                } else if args[1] == "repeat" {
3✔
109
                        x = 0
1✔
110
                        across = img.Width()/overlay.Width() + 1
1✔
111
                } else if strings.HasPrefix(strings.TrimPrefix(args[1], "-"), "0.") {
3✔
112
                        pec, _ := strconv.ParseFloat(args[1], 64)
1✔
113
                        x = int(pec * float64(img.Width()))
1✔
114
                } else if strings.HasSuffix(args[1], "p") {
3✔
115
                        x, _ = strconv.Atoi(strings.TrimSuffix(args[1], "p"))
1✔
116
                        x = x * img.Width() / 100
1✔
117
                } else {
2✔
118
                        x, _ = strconv.Atoi(args[1])
1✔
119
                }
1✔
120
                if args[2] == "center" {
2✔
121
                        y = (img.PageHeight() - overlay.PageHeight()) / 2
1✔
122
                } else if args[2] == imagorpath.VAlignTop {
3✔
123
                        y = 0
1✔
124
                } else if args[2] == imagorpath.VAlignBottom {
3✔
125
                        y = img.PageHeight() - overlay.PageHeight()
1✔
126
                } else if args[2] == "repeat" {
3✔
127
                        y = 0
1✔
128
                        down = img.PageHeight()/overlay.PageHeight() + 1
1✔
129
                } else if strings.HasPrefix(strings.TrimPrefix(args[2], "-"), "0.") {
3✔
130
                        pec, _ := strconv.ParseFloat(args[2], 64)
1✔
131
                        y = int(pec * float64(img.PageHeight()))
1✔
132
                } else if strings.HasSuffix(args[2], "p") {
3✔
133
                        y, _ = strconv.Atoi(strings.TrimSuffix(args[2], "p"))
1✔
134
                        y = y * img.PageHeight() / 100
1✔
135
                } else {
2✔
136
                        y, _ = strconv.Atoi(args[2])
1✔
137
                }
1✔
138
                if x < 0 {
2✔
139
                        x += img.Width() - overlay.Width()
1✔
140
                }
1✔
141
                if y < 0 {
2✔
142
                        y += img.PageHeight() - overlay.PageHeight()
1✔
143
                }
1✔
144
        }
145
        if across*down > 1 {
2✔
146
                if err = overlay.EmbedMultiPage(0, 0, across*w, down*h,
1✔
147
                        &vips.EmbedMultiPageOptions{Extend: vips.ExtendRepeat}); err != nil {
1✔
148
                        return
×
149
                }
×
150
        }
151
        if err = overlay.EmbedMultiPage(
1✔
152
                x, y, img.Width(), img.PageHeight(), nil,
1✔
153
        ); err != nil {
1✔
154
                return
×
155
        }
×
156
        if n := img.Height() / img.PageHeight(); n > overlayN {
2✔
157
                cnt := n / overlayN
1✔
158
                if n%overlayN > 0 {
2✔
159
                        cnt++
1✔
160
                }
1✔
161
                if err = overlay.Replicate(1, cnt); err != nil {
1✔
162
                        return
×
163
                }
×
164
        }
165
        if err = img.Composite2(overlay, vips.BlendModeOver, nil); err != nil {
1✔
166
                return
×
167
        }
×
168
        return
1✔
169
}
170

171
func (v *Processor) fill(ctx context.Context, img *vips.Image, w, h int, pLeft, pTop, pRight, pBottom int, colour string) (err error) {
1✔
172
        if isRotate90(ctx) {
2✔
173
                tmpW := w
1✔
174
                w = h
1✔
175
                h = tmpW
1✔
176
                tmpPLeft := pLeft
1✔
177
                pLeft = pTop
1✔
178
                pTop = tmpPLeft
1✔
179
                tmpPRight := pRight
1✔
180
                pRight = pBottom
1✔
181
                pBottom = tmpPRight
1✔
182
        }
1✔
183
        c := getColor(img, colour)
1✔
184
        left := (w-img.Width())/2 + pLeft
1✔
185
        top := (h-img.PageHeight())/2 + pTop
1✔
186
        width := w + pLeft + pRight
1✔
187
        height := h + pTop + pBottom
1✔
188
        if colour != "blur" || v.DisableBlur || isAnimated(img) {
2✔
189
                // fill color
1✔
190
                isTransparent := colour == "none" || colour == "transparent"
1✔
191
                if img.HasAlpha() && !isTransparent {
2✔
192
                        c := getColor(img, colour)
1✔
193
                        if err = img.Flatten(&vips.FlattenOptions{Background: c}); err != nil {
1✔
194
                                return
×
195
                        }
×
196
                }
197
                if isTransparent {
2✔
198
                        if img.Bands() < 3 {
2✔
199
                                if err = img.Colourspace(vips.InterpretationSrgb, nil); err != nil {
1✔
200
                                        return
×
201
                                }
×
202
                        }
203
                        if !img.HasAlpha() {
2✔
204
                                if err = img.Addalpha(); err != nil {
1✔
205
                                        return
×
206
                                }
×
207
                        }
208
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{Extend: vips.ExtendBlack}); err != nil {
1✔
209
                                return
×
210
                        }
×
211
                } else if isBlack(c) {
2✔
212
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{Extend: vips.ExtendBlack}); err != nil {
1✔
213
                                return
×
214
                        }
×
215
                } else if isWhite(c) {
2✔
216
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{Extend: vips.ExtendWhite}); err != nil {
1✔
217
                                return
×
218
                        }
×
219
                } else {
1✔
220
                        if err = img.EmbedMultiPage(left, top, width, height, &vips.EmbedMultiPageOptions{
1✔
221
                                Extend:     vips.ExtendBackground,
1✔
222
                                Background: c,
1✔
223
                        }); err != nil {
1✔
224
                                return
×
225
                        }
×
226
                }
227
        } else {
1✔
228
                // fill blur
1✔
229
                var cp *vips.Image
1✔
230
                if cp, err = img.Copy(nil); err != nil {
1✔
231
                        return
×
232
                }
×
233
                contextDefer(ctx, cp.Close)
1✔
234
                if err = img.ThumbnailImage(
1✔
235
                        width, &vips.ThumbnailImageOptions{
1✔
236
                                Height: height,
1✔
237
                                Crop:   vips.InterestingNone,
1✔
238
                                Size:   vips.SizeForce,
1✔
239
                        },
1✔
240
                ); err != nil {
1✔
241
                        return
×
242
                }
×
243
                if err = img.Gaussblur(50, nil); err != nil {
1✔
244
                        return
×
245
                }
×
246
                if err = img.Composite2(
1✔
247
                        cp, vips.BlendModeOver,
1✔
248
                        &vips.Composite2Options{X: left, Y: top}); err != nil {
1✔
249
                        return
×
250
                }
×
251
        }
252
        return
1✔
253
}
254

255
func roundCorner(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
256
        var rx, ry int
1✔
257
        var c []float64
1✔
258
        if len(args) == 0 {
2✔
259
                return
1✔
260
        }
1✔
261
        if a, e := url.QueryUnescape(args[0]); e == nil {
2✔
262
                args[0] = a
1✔
263
        }
1✔
264
        if len(args) == 3 {
2✔
265
                // rx,ry,color
1✔
266
                c = getColor(img, args[2])
1✔
267
                args = args[:2]
1✔
268
        }
1✔
269
        rx, _ = strconv.Atoi(args[0])
1✔
270
        ry = rx
1✔
271
        if len(args) > 1 {
2✔
272
                ry, _ = strconv.Atoi(args[1])
1✔
273
        }
1✔
274

275
        var rounded *vips.Image
1✔
276
        var w = img.Width()
1✔
277
        var h = img.PageHeight()
1✔
278
        if rounded, err = vips.NewSvgloadBuffer([]byte(fmt.Sprintf(`
1✔
279
                <svg viewBox="0 0 %d %d">
1✔
280
                        <rect rx="%d" ry="%d" 
1✔
281
                         x="0" y="0" width="%d" height="%d" 
1✔
282
                         fill="#fff"/>
1✔
283
                </svg>
1✔
284
        `, w, h, rx, ry, w, h)), nil); err != nil {
1✔
285
                return
×
286
        }
×
287
        contextDefer(ctx, rounded.Close)
1✔
288
        if n := img.Height() / img.PageHeight(); n > 1 {
2✔
289
                if err = rounded.Replicate(1, n); err != nil {
1✔
290
                        return
×
291
                }
×
292
        }
293
        if err = img.Composite2(rounded, vips.BlendModeDestIn, nil); err != nil {
1✔
294
                return
×
295
        }
×
296
        if c != nil {
2✔
297
                if err = img.Flatten(&vips.FlattenOptions{Background: c}); err != nil {
1✔
298
                        return
×
299
                }
×
300
        }
301
        return nil
1✔
302
}
303

304
func label(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
305
        ln := len(args)
1✔
306
        if ln == 0 {
1✔
307
                return
×
308
        }
×
309
        if a, e := url.QueryUnescape(args[0]); e == nil {
2✔
310
                args[0] = a
1✔
311
        }
1✔
312
        var text = args[0]
1✔
313
        var font = "tahoma"
1✔
314
        var x, y int
1✔
315
        var c []float64
1✔
316
        var alpha float64
1✔
317
        var align = vips.AlignLow
1✔
318
        var size = 20
1✔
319
        var width = img.Width()
1✔
320
        if ln > 3 {
2✔
321
                size, _ = strconv.Atoi(args[3])
1✔
322
        }
1✔
323
        if ln > 1 {
2✔
324
                if args[1] == "center" {
2✔
325
                        align = vips.AlignCentre
1✔
326
                        x = width / 2
1✔
327
                } else if args[1] == imagorpath.HAlignRight {
3✔
328
                        align = vips.AlignHigh
1✔
329
                        x = width
1✔
330
                } else if strings.HasPrefix(strings.TrimPrefix(args[1], "-"), "0.") {
3✔
331
                        pec, _ := strconv.ParseFloat(args[1], 64)
1✔
332
                        x = int(pec * float64(width))
1✔
333
                } else if strings.HasSuffix(args[1], "p") {
3✔
334
                        x, _ = strconv.Atoi(strings.TrimSuffix(args[1], "p"))
1✔
335
                        x = x * width / 100
1✔
336
                } else {
2✔
337
                        x, _ = strconv.Atoi(args[1])
1✔
338
                }
1✔
339
                if x < 0 {
2✔
340
                        align = vips.AlignHigh
1✔
341
                        x += width
1✔
342
                }
1✔
343
        }
344
        if ln > 2 {
2✔
345
                if args[2] == "center" {
2✔
346
                        y = (img.PageHeight() - size) / 2
1✔
347
                } else if args[2] == imagorpath.VAlignTop {
3✔
348
                        y = 0
1✔
349
                } else if args[2] == imagorpath.VAlignBottom {
3✔
350
                        y = img.PageHeight() - size
1✔
351
                } else if strings.HasPrefix(strings.TrimPrefix(args[2], "-"), "0.") {
3✔
352
                        pec, _ := strconv.ParseFloat(args[2], 64)
1✔
353
                        y = int(pec * float64(img.PageHeight()))
1✔
354
                } else if strings.HasSuffix(args[2], "p") {
3✔
355
                        y, _ = strconv.Atoi(strings.TrimSuffix(args[2], "p"))
1✔
356
                        y = y * img.PageHeight() / 100
1✔
357
                } else {
2✔
358
                        y, _ = strconv.Atoi(args[2])
1✔
359
                }
1✔
360
                if y < 0 {
2✔
361
                        y += img.PageHeight() - size
1✔
362
                }
1✔
363
        }
364
        if ln > 4 {
2✔
365
                c = getColor(img, args[4])
1✔
366
        }
1✔
367
        if ln > 5 {
2✔
368
                alpha, _ = strconv.ParseFloat(args[5], 64)
1✔
369
                alpha /= 100
1✔
370
        }
1✔
371
        if ln > 6 {
2✔
372
                if a, e := url.QueryUnescape(args[6]); e == nil {
2✔
373
                        font = a
1✔
374
                } else {
1✔
375
                        font = args[6]
×
376
                }
×
377
        }
378
        if img.Bands() < 3 {
2✔
379
                if err = img.Colourspace(vips.InterpretationSrgb, nil); err != nil {
1✔
380
                        return
×
381
                }
×
382
        }
383
        if !img.HasAlpha() {
2✔
384
                if err = img.Addalpha(); err != nil {
1✔
385
                        return
×
386
                }
×
387
        }
388
        return img.Label(text, x, y, &vips.LabelOptions{
1✔
389
                Font:    font,
1✔
390
                Size:    size,
1✔
391
                Align:   align,
1✔
392
                Opacity: 1 - alpha,
1✔
393
                Color:   c,
1✔
394
        })
1✔
395
}
396

397
func (v *Processor) padding(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) error {
1✔
398
        ln := len(args)
1✔
399
        if ln < 2 {
2✔
400
                return nil
1✔
401
        }
1✔
402
        var (
1✔
403
                c       = args[0]
1✔
404
                left, _ = strconv.Atoi(args[1])
1✔
405
                top     = left
1✔
406
                right   = left
1✔
407
                bottom  = left
1✔
408
        )
1✔
409
        if ln > 2 {
2✔
410
                top, _ = strconv.Atoi(args[2])
1✔
411
                bottom = top
1✔
412
        }
1✔
413
        if ln > 4 {
2✔
414
                right, _ = strconv.Atoi(args[3])
1✔
415
                bottom, _ = strconv.Atoi(args[4])
1✔
416
        }
1✔
417
        return v.fill(ctx, img, img.Width(), img.PageHeight(), left, top, right, bottom, c)
1✔
418
}
419

420
func backgroundColor(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
421
        if len(args) == 0 {
2✔
422
                return
1✔
423
        }
1✔
424
        if !img.HasAlpha() {
2✔
425
                return
1✔
426
        }
1✔
427
        c := getColor(img, args[0])
1✔
428
        return img.Flatten(&vips.FlattenOptions{
1✔
429
                Background: c,
1✔
430
        })
1✔
431
}
432

433
func rotate(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
434
        if len(args) == 0 {
2✔
435
                return
1✔
436
        }
1✔
437
        if angle, _ := strconv.Atoi(args[0]); angle > 0 {
2✔
438
                switch angle {
1✔
439
                case 90, 270:
1✔
440
                        setRotate90(ctx)
1✔
441
                }
442
                if err = img.RotMultiPage(getAngle(angle)); err != nil {
1✔
443
                        return err
×
444
                }
×
445
        }
446
        return
1✔
447
}
448

449
func getAngle(angle int) vips.Angle {
1✔
450
        switch angle {
1✔
451
        case 90:
1✔
452
                return vips.AngleD270
1✔
453
        case 180:
1✔
454
                return vips.AngleD180
1✔
455
        case 270:
1✔
456
                return vips.AngleD90
1✔
457
        default:
×
458
                return vips.AngleD0
×
459
        }
460
}
461

462
func proportion(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
463
        if len(args) == 0 {
2✔
464
                return
1✔
465
        }
1✔
466
        scale, _ := strconv.ParseFloat(args[0], 64)
1✔
467
        if scale <= 0 {
2✔
468
                return // no ops
1✔
469
        }
1✔
470
        if scale > 100 {
2✔
471
                scale = 100
1✔
472
        }
1✔
473
        if scale > 1 {
2✔
474
                scale /= 100
1✔
475
        }
1✔
476
        width := int(float64(img.Width()) * scale)
1✔
477
        height := int(float64(img.PageHeight()) * scale)
1✔
478
        if width <= 0 || height <= 0 {
2✔
479
                return // op ops
1✔
480
        }
1✔
481
        return img.ThumbnailImage(width, &vips.ThumbnailImageOptions{
1✔
482
                Height: height,
1✔
483
                Crop:   vips.InterestingNone,
1✔
484
        })
1✔
485
}
486

487
func grayscale(_ context.Context, img *vips.Image, _ imagor.LoadFunc, _ ...string) (err error) {
1✔
488
        return img.Colourspace(vips.InterpretationBW, nil)
1✔
489
}
1✔
490

491
func brightness(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
492
        if len(args) == 0 {
2✔
493
                return
1✔
494
        }
1✔
495
        b, _ := strconv.ParseFloat(args[0], 64)
1✔
496
        b = b * 255 / 100
1✔
497
        return linearRGB(img, []float64{1, 1, 1}, []float64{b, b, b})
1✔
498
}
499

500
func contrast(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
501
        if len(args) == 0 {
2✔
502
                return
1✔
503
        }
1✔
504
        a, _ := strconv.ParseFloat(args[0], 64)
1✔
505
        a = a * 255 / 100
1✔
506
        a = math.Min(math.Max(a, -255), 255)
1✔
507
        a = (259 * (a + 255)) / (255 * (259 - a))
1✔
508
        b := 128 - a*128
1✔
509
        return linearRGB(img, []float64{a, a, a}, []float64{b, b, b})
1✔
510
}
511

512
func hue(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
513
        if len(args) == 0 {
2✔
514
                return
1✔
515
        }
1✔
516
        h, _ := strconv.ParseFloat(args[0], 64)
1✔
517
        return img.Modulate(1, 1, h)
1✔
518
}
519

520
func saturation(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
521
        if len(args) == 0 {
2✔
522
                return
1✔
523
        }
1✔
524
        s, _ := strconv.ParseFloat(args[0], 64)
1✔
525
        s = 1 + s/100
1✔
526
        return img.Modulate(1, s, 0)
1✔
527
}
528

529
func rgb(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
530
        if len(args) != 3 {
2✔
531
                return
1✔
532
        }
1✔
533
        r, _ := strconv.ParseFloat(args[0], 64)
1✔
534
        g, _ := strconv.ParseFloat(args[1], 64)
1✔
535
        b, _ := strconv.ParseFloat(args[2], 64)
1✔
536
        r = r * 255 / 100
1✔
537
        g = g * 255 / 100
1✔
538
        b = b * 255 / 100
1✔
539
        return linearRGB(img, []float64{1, 1, 1}, []float64{r, g, b})
1✔
540
}
541

542
func modulate(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
543
        if len(args) != 3 {
2✔
544
                return
1✔
545
        }
1✔
546
        b, _ := strconv.ParseFloat(args[0], 64)
1✔
547
        s, _ := strconv.ParseFloat(args[1], 64)
1✔
548
        h, _ := strconv.ParseFloat(args[2], 64)
1✔
549
        b = 1 + b/100
1✔
550
        s = 1 + s/100
1✔
551
        return img.Modulate(b, s, h)
1✔
552
}
553

554
func blur(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
555
        if isAnimated(img) {
2✔
556
                // skip animation support
1✔
557
                return
1✔
558
        }
1✔
559
        var sigma float64
1✔
560
        switch len(args) {
1✔
561
        case 2:
1✔
562
                sigma, _ = strconv.ParseFloat(args[1], 64)
1✔
563
                break
1✔
564
        case 1:
1✔
565
                sigma, _ = strconv.ParseFloat(args[0], 64)
1✔
566
                break
1✔
567
        }
568
        sigma /= 2
1✔
569
        if sigma > 0 {
2✔
570
                return img.Gaussblur(sigma, nil)
1✔
571
        }
1✔
572
        return
×
573
}
574

575
func sharpen(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) (err error) {
1✔
576
        if isAnimated(img) {
2✔
577
                // skip animation support
1✔
578
                return
1✔
579
        }
1✔
580
        var sigma float64
1✔
581
        switch len(args) {
1✔
582
        case 1:
1✔
583
                sigma, _ = strconv.ParseFloat(args[0], 64)
1✔
584
                break
1✔
585
        case 2, 3:
1✔
586
                sigma, _ = strconv.ParseFloat(args[1], 64)
1✔
587
                break
1✔
588
        }
589
        sigma = 1 + sigma*2
1✔
590
        if sigma > 0 {
2✔
591
                return img.Sharpen(&vips.SharpenOptions{
1✔
592
                        Sigma: sigma,
1✔
593
                        X1:    1,
1✔
594
                        M2:    2,
1✔
595
                })
1✔
596
        }
1✔
597
        return
1✔
598
}
599

600
func stripIcc(_ context.Context, img *vips.Image, _ imagor.LoadFunc, _ ...string) (err error) {
1✔
601
        return img.RemoveICCProfile()
1✔
602
}
1✔
603

604
func stripExif(_ context.Context, img *vips.Image, _ imagor.LoadFunc, _ ...string) (err error) {
1✔
605
        return img.RemoveExif()
1✔
606
}
1✔
607

608
func trim(ctx context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) error {
1✔
609
        var (
1✔
610
                ln        = len(args)
1✔
611
                pos       string
1✔
612
                tolerance int
1✔
613
        )
1✔
614
        if ln > 0 {
2✔
615
                tolerance, _ = strconv.Atoi(args[0])
1✔
616
        }
1✔
617
        if ln > 1 {
2✔
618
                pos = args[1]
1✔
619
        }
1✔
620
        if l, t, w, h, err := findTrim(ctx, img, pos, tolerance); err == nil {
2✔
621
                return img.ExtractAreaMultiPage(l, t, w, h)
1✔
622
        }
1✔
623
        return nil
×
624
}
625

626
func linearRGB(img *vips.Image, a, b []float64) error {
1✔
627
        if img.HasAlpha() {
2✔
628
                a = append(a, 1)
1✔
629
                b = append(b, 0)
1✔
630
        }
1✔
631
        return img.Linear(a, b, nil)
1✔
632
}
633

634
func isBlack(c []float64) bool {
1✔
635
        if len(c) < 3 {
1✔
636
                return false
×
637
        }
×
638
        return c[0] == 0x00 && c[1] == 0x00 && c[2] == 0x00
1✔
639
}
640

641
func isWhite(c []float64) bool {
1✔
642
        if len(c) < 3 {
1✔
643
                return false
×
644
        }
×
645
        return c[0] == 0xff && c[1] == 0xff && c[2] == 0xff
1✔
646
}
647

648
func getColor(img *vips.Image, color string) []float64 {
1✔
649
        var vc = make([]float64, 3)
1✔
650
        args := strings.Split(strings.ToLower(color), ",")
1✔
651
        mode := ""
1✔
652
        name := strings.TrimPrefix(args[0], "#")
1✔
653
        if len(args) > 1 {
2✔
654
                mode = args[1]
1✔
655
        }
1✔
656
        if name == "auto" {
2✔
657
                if img != nil {
2✔
658
                        x := 0
1✔
659
                        y := 0
1✔
660
                        if mode == "bottom-right" {
2✔
661
                                x = img.Width() - 1
1✔
662
                                y = img.PageHeight() - 1
1✔
663
                        }
1✔
664
                        p, _ := img.Getpoint(x, y, nil)
1✔
665
                        if len(p) >= 3 {
2✔
666
                                vc[0] = p[0]
1✔
667
                                vc[1] = p[1]
1✔
668
                                vc[2] = p[2]
1✔
669
                        }
1✔
670
                }
671
        } else if c, ok := colornames.Map[name]; ok {
2✔
672
                vc[0] = float64(c.R)
1✔
673
                vc[1] = float64(c.G)
1✔
674
                vc[2] = float64(c.B)
1✔
675
        } else if c, ok := parseHexColor(name); ok {
3✔
676
                vc[0] = float64(c.R)
1✔
677
                vc[1] = float64(c.G)
1✔
678
                vc[2] = float64(c.B)
1✔
679
        }
1✔
680
        return vc
1✔
681
}
682

683
func parseHexColor(s string) (c color.RGBA, ok bool) {
1✔
684
        c.A = 0xff
1✔
685
        switch len(s) {
1✔
686
        case 6:
1✔
687
                c.R = hexToByte(s[0])<<4 + hexToByte(s[1])
1✔
688
                c.G = hexToByte(s[2])<<4 + hexToByte(s[3])
1✔
689
                c.B = hexToByte(s[4])<<4 + hexToByte(s[5])
1✔
690
                ok = true
1✔
691
        case 3:
1✔
692
                c.R = hexToByte(s[0]) * 17
1✔
693
                c.G = hexToByte(s[1]) * 17
1✔
694
                c.B = hexToByte(s[2]) * 17
1✔
695
                ok = true
1✔
696
        }
697
        return
1✔
698
}
699

700
func hexToByte(b byte) byte {
1✔
701
        switch {
1✔
702
        case b >= '0' && b <= '9':
1✔
703
                return b - '0'
1✔
704
        case b >= 'a' && b <= 'f':
1✔
705
                return b - 'a' + 10
1✔
706
        }
707
        return 0
1✔
708
}
709

710
func crop(_ context.Context, img *vips.Image, _ imagor.LoadFunc, args ...string) error {
1✔
711
        if len(args) < 4 {
1✔
NEW
712
                return nil
×
NEW
713
        }
×
714

715
        // Parse arguments
716
        left, _ := strconv.ParseFloat(args[0], 64)
1✔
717
        top, _ := strconv.ParseFloat(args[1], 64)
1✔
718
        width, _ := strconv.ParseFloat(args[2], 64)
1✔
719
        height, _ := strconv.ParseFloat(args[3], 64)
1✔
720

1✔
721
        imgWidth := float64(img.Width())
1✔
722
        imgHeight := float64(img.PageHeight())
1✔
723

1✔
724
        // Convert relative (0-1) to absolute pixels
1✔
725
        if left > 0 && left < 1 {
2✔
726
                left = left * imgWidth
1✔
727
        }
1✔
728
        if top > 0 && top < 1 {
2✔
729
                top = top * imgHeight
1✔
730
        }
1✔
731
        if width > 0 && width < 1 {
2✔
732
                width = width * imgWidth
1✔
733
        }
1✔
734
        if height > 0 && height < 1 {
2✔
735
                height = height * imgHeight
1✔
736
        }
1✔
737

738
        // Clamp left and top to image bounds
739
        left = math.Max(0, math.Min(left, imgWidth))
1✔
740
        top = math.Max(0, math.Min(top, imgHeight))
1✔
741

1✔
742
        // Adjust width and height to not exceed image bounds
1✔
743
        width = math.Min(width, imgWidth-left)
1✔
744
        height = math.Min(height, imgHeight-top)
1✔
745

1✔
746
        // Skip if invalid crop area
1✔
747
        if width <= 0 || height <= 0 {
1✔
NEW
748
                return nil
×
NEW
749
        }
×
750

751
        return img.ExtractAreaMultiPage(int(left), int(top), int(width), int(height))
1✔
752
}
753

754
func isAnimated(img *vips.Image) bool {
1✔
755
        return img.Height() > img.PageHeight()
1✔
756
}
1✔
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

© 2025 Coveralls, Inc