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

cshum / imagor / 4157321989

12 Feb 2023 04:56PM UTC coverage: 91.017% (-0.05%) from 91.071%
4157321989

Pull #316

github

github-actions[bot]
test: update golden files
Pull Request #316: feat(vips): fill with transparent background

10 of 10 new or added lines in 1 file covered. (100.0%)

5542 of 6089 relevant lines covered (91.02%)

1.07 hits per line

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

88.55
/vips/filter.go
1
package vips
2

3
import (
4
        "context"
5
        "fmt"
6
        "github.com/cshum/imagor"
7
        "github.com/cshum/imagor/imagorpath"
8
        "golang.org/x/image/colornames"
9
        "image/color"
10
        "math"
11
        "net/url"
12
        "strconv"
13
        "strings"
14
)
15

16
func (v *Processor) watermark(ctx context.Context, img *Image, load imagor.LoadFunc, args ...string) (err error) {
17
        ln := len(args)
18
        if ln < 1 {
19
                return
20
        }
21
        image := args[0]
22
        if unescape, e := url.QueryUnescape(args[0]); e == nil {
23
                image = unescape
24
        }
25
        var blob *imagor.Blob
26
        if blob, err = load(image); err != nil {
27
                return
28
        }
29
        var x, y, w, h int
30
        var across = 1
31
        var down = 1
32
        var overlay *Image
33
        var n = 1
34
        if isAnimated(img) {
35
                n = -1
36
        }
37
        // w_ratio h_ratio
38
        if ln >= 6 {
39
                w = img.Width()
40
                h = img.PageHeight()
41
                if args[4] != "none" {
42
                        w, _ = strconv.Atoi(args[4])
43
                        w = img.Width() * w / 100
44
                }
45
                if args[5] != "none" {
46
                        h, _ = strconv.Atoi(args[5])
47
                        h = img.PageHeight() * h / 100
48
                }
49
                if overlay, err = v.NewThumbnail(
50
                        ctx, blob, w, h, InterestingNone, SizeDown, n,
51
                ); err != nil {
52
                        return
53
                }
54
        } else {
55
                if overlay, err = v.NewThumbnail(
56
                        ctx, blob, v.MaxWidth, v.MaxHeight, InterestingNone, SizeDown, n,
57
                ); err != nil {
58
                        return
59
                }
60
        }
61
        var overlayN = overlay.Height() / overlay.PageHeight()
62
        contextDefer(ctx, overlay.Close)
63
        if overlay.Bands() < 3 {
64
                if err = overlay.ToColorSpace(InterpretationSRGB); err != nil {
65
                        return
66
                }
67
        }
68
        if err = overlay.AddAlpha(); err != nil {
69
                return
70
        }
71
        w = overlay.Width()
72
        h = overlay.PageHeight()
73
        // alpha
74
        if ln >= 4 {
75
                alpha, _ := strconv.ParseFloat(args[3], 64)
76
                alpha = 1 - alpha/100
77
                if alpha != 1 {
78
                        if err = overlay.Linear([]float64{1, 1, 1, alpha}, []float64{0, 0, 0, 0}); err != nil {
79
                                return
80
                        }
81
                }
82
        }
83
        // x y
84
        if ln >= 3 {
85
                if args[1] == "center" {
86
                        x = (img.Width() - overlay.Width()) / 2
87
                } else if args[1] == imagorpath.HAlignLeft {
88
                        x = 0
89
                } else if args[1] == imagorpath.HAlignRight {
90
                        x = img.Width() - overlay.Width()
91
                } else if args[1] == "repeat" {
92
                        x = 0
93
                        across = img.Width()/overlay.Width() + 1
94
                } else if strings.HasPrefix(strings.TrimPrefix(args[1], "-"), "0.") {
95
                        pec, _ := strconv.ParseFloat(args[1], 64)
96
                        x = int(pec * float64(img.Width()))
97
                } else if strings.HasSuffix(args[1], "p") {
98
                        x, _ = strconv.Atoi(strings.TrimSuffix(args[1], "p"))
99
                        x = x * img.Width() / 100
100
                } else {
101
                        x, _ = strconv.Atoi(args[1])
102
                }
103
                if args[2] == "center" {
104
                        y = (img.PageHeight() - overlay.PageHeight()) / 2
105
                } else if args[2] == imagorpath.VAlignTop {
106
                        y = 0
107
                } else if args[2] == imagorpath.VAlignBottom {
108
                        y = img.PageHeight() - overlay.PageHeight()
109
                } else if args[2] == "repeat" {
110
                        y = 0
111
                        down = img.PageHeight()/overlay.PageHeight() + 1
112
                } else if strings.HasPrefix(strings.TrimPrefix(args[2], "-"), "0.") {
113
                        pec, _ := strconv.ParseFloat(args[2], 64)
114
                        y = int(pec * float64(img.PageHeight()))
115
                } else if strings.HasSuffix(args[2], "p") {
116
                        y, _ = strconv.Atoi(strings.TrimSuffix(args[2], "p"))
117
                        y = y * img.PageHeight() / 100
118
                } else {
119
                        y, _ = strconv.Atoi(args[2])
120
                }
121
                if x < 0 {
122
                        x += img.Width() - overlay.Width()
123
                }
124
                if y < 0 {
125
                        y += img.PageHeight() - overlay.PageHeight()
126
                }
127
        }
128
        if across*down > 1 {
129
                if err = overlay.Embed(0, 0, across*w, down*h, ExtendRepeat); err != nil {
130
                        return
131
                }
132
        }
133
        if err = overlay.EmbedBackgroundRGBA(
134
                x, y, img.Width(), img.PageHeight(), &ColorRGBA{},
135
        ); err != nil {
136
                return
137
        }
138
        if n := img.Height() / img.PageHeight(); n > overlayN {
139
                cnt := n / overlayN
140
                if n%overlayN > 0 {
141
                        cnt++
142
                }
143
                if err = overlay.Replicate(1, cnt); err != nil {
144
                        return
145
                }
146
        }
147
        if err = img.Composite(overlay, BlendModeOver, 0, 0); err != nil {
148
                return
149
        }
150
        return
151
}
152

153
func setFrames(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
154
        ln := len(args)
155
        if ln == 0 {
156
                return
157
        }
158
        newN, _ := strconv.Atoi(args[0])
159
        if newN < 1 {
160
                return
161
        }
162
        if n := img.Height() / img.PageHeight(); n != newN {
163
                height := img.PageHeight()
164
                if err = img.SetPageHeight(img.Height()); err != nil {
165
                        return
166
                }
167
                if err = img.Embed(0, 0, img.Width(), height*newN, ExtendRepeat); err != nil {
168
                        return
169
                }
170
                if err = img.SetPageHeight(height); err != nil {
171
                        return
172
                }
173
        }
174
        var delay int
175
        if ln > 1 {
176
                delay, _ = strconv.Atoi(args[1])
177
        }
178
        if delay == 0 {
179
                delay = 100
180
        }
181
        delays := make([]int, newN)
182
        for i := 0; i < newN; i++ {
183
                delays[i] = delay
184
        }
185
        if err = img.SetPageDelay(delays); err != nil {
186
                return
187
        }
188
        return
189
}
190

191
func (v *Processor) fill(ctx context.Context, img *Image, w, h int, pLeft, pTop, pRight, pBottom int, colour string) (err error) {
192
        if isRotate90(ctx) {
193
                tmpW := w
194
                w = h
195
                h = tmpW
196
                tmpPLeft := pLeft
197
                pLeft = pTop
198
                pTop = tmpPLeft
199
                tmpPRight := pRight
200
                pRight = pBottom
201
                pBottom = tmpPRight
202
        }
203
        c := getColor(img, colour)
204
        left := (w-img.Width())/2 + pLeft
205
        top := (h-img.PageHeight())/2 + pTop
206
        width := w + pLeft + pRight
207
        height := h + pTop + pBottom
208
        if colour != "blur" || (colour == "blur" && v.DisableBlur) || isAnimated(img) {
209
                // fill color
210
                isTransparent := colour == "none" || colour == "transparent"
211
                if img.HasAlpha() && !isTransparent {
212
                        if err = img.Flatten(getColor(img, colour)); err != nil {
213
                                return
214
                        }
215
                }
216
                if isTransparent {
217
                        if err = img.AddAlpha(); err != nil {
218
                                return
219
                        }
220
                        if err = img.EmbedBackgroundRGBA(left, top, width, height, &ColorRGBA{}); err != nil {
221
                                return
222
                        }
223
                } else if isBlack(c) {
224
                        if err = img.Embed(left, top, width, height, ExtendBlack); err != nil {
225
                                return
226
                        }
227
                } else if isWhite(c) {
228
                        if err = img.Embed(left, top, width, height, ExtendWhite); err != nil {
229
                                return
230
                        }
231
                } else {
232
                        if err = img.EmbedBackground(left, top, width, height, c); err != nil {
233
                                return
234
                        }
235
                }
236
        } else {
237
                // fill blur
238
                var cp *Image
239
                if cp, err = img.Copy(); err != nil {
240
                        return
241
                }
242
                contextDefer(ctx, cp.Close)
243
                if err = img.ThumbnailWithSize(
244
                        width, height, InterestingNone, SizeForce,
245
                ); err != nil {
246
                        return
247
                }
248
                if err = img.GaussianBlur(50); err != nil {
249
                        return
250
                }
251
                if err = img.Composite(
252
                        cp, BlendModeOver, left, top); err != nil {
253
                        return
254
                }
255
        }
256
        return
257
}
258

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

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

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

389
func (v *Processor) padding(ctx context.Context, img *Image, _ imagor.LoadFunc, args ...string) error {
390
        ln := len(args)
391
        if ln < 2 {
392
                return nil
393
        }
394
        var (
395
                c       = args[0]
396
                left, _ = strconv.Atoi(args[1])
397
                top     = left
398
                right   = left
399
                bottom  = left
400
        )
401
        if ln > 2 {
402
                top, _ = strconv.Atoi(args[2])
403
                bottom = top
404
        }
405
        if ln > 4 {
406
                right, _ = strconv.Atoi(args[3])
407
                bottom, _ = strconv.Atoi(args[4])
408
        }
409
        return v.fill(ctx, img, img.Width(), img.PageHeight(), left, top, right, bottom, c)
410
}
411

412
func backgroundColor(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
413
        if len(args) == 0 {
414
                return
415
        }
416
        if !img.HasAlpha() {
417
                return
418
        }
419
        return img.Flatten(getColor(img, args[0]))
420
}
421

422
func rotate(ctx context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
423
        if len(args) == 0 {
424
                return
425
        }
426
        if angle, _ := strconv.Atoi(args[0]); angle > 0 {
427
                switch angle {
428
                case 90, 270:
429
                        setRotate90(ctx)
430
                }
431
                if err = img.Rotate(getAngle(angle)); err != nil {
432
                        return err
433
                }
434
        }
435
        return
436
}
437

438
func getAngle(angle int) Angle {
439
        switch angle {
440
        case 90:
441
                return Angle270
442
        case 180:
443
                return Angle180
444
        case 270:
445
                return Angle90
446
        default:
447
                return Angle0
448
        }
449
}
450

451
func proportion(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
452
        if len(args) == 0 {
453
                return
454
        }
455
        scale, _ := strconv.ParseFloat(args[0], 64)
456
        if scale <= 0 {
457
                return // no ops
458
        }
459
        if scale > 100 {
460
                scale = 100
461
        }
462
        if scale > 1 {
463
                scale /= 100
464
        }
465
        width := int(float64(img.Width()) * scale)
466
        height := int(float64(img.PageHeight()) * scale)
467
        if width <= 0 || height <= 0 {
468
                return // op ops
469
        }
470
        return img.Thumbnail(width, height, InterestingNone)
471
}
472

473
func grayscale(_ context.Context, img *Image, _ imagor.LoadFunc, _ ...string) (err error) {
474
        return img.ToColorSpace(InterpretationBW)
475
}
476

477
func brightness(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
478
        if len(args) == 0 {
479
                return
480
        }
481
        b, _ := strconv.ParseFloat(args[0], 64)
482
        b = b * 255 / 100
483
        return linearRGB(img, []float64{1, 1, 1}, []float64{b, b, b})
484
}
485

486
func contrast(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
487
        if len(args) == 0 {
488
                return
489
        }
490
        a, _ := strconv.ParseFloat(args[0], 64)
491
        a = a * 255 / 100
492
        a = math.Min(math.Max(a, -255), 255)
493
        a = (259 * (a + 255)) / (255 * (259 - a))
494
        b := 128 - a*128
495
        return linearRGB(img, []float64{a, a, a}, []float64{b, b, b})
496
}
497

498
func hue(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
499
        if len(args) == 0 {
500
                return
501
        }
502
        h, _ := strconv.ParseFloat(args[0], 64)
503
        return img.Modulate(1, 1, h)
504
}
505

506
func saturation(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
507
        if len(args) == 0 {
508
                return
509
        }
510
        s, _ := strconv.ParseFloat(args[0], 64)
511
        s = 1 + s/100
512
        return img.Modulate(1, s, 0)
513
}
514

515
func rgb(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
516
        if len(args) != 3 {
517
                return
518
        }
519
        r, _ := strconv.ParseFloat(args[0], 64)
520
        g, _ := strconv.ParseFloat(args[1], 64)
521
        b, _ := strconv.ParseFloat(args[2], 64)
522
        r = r * 255 / 100
523
        g = g * 255 / 100
524
        b = b * 255 / 100
525
        return linearRGB(img, []float64{1, 1, 1}, []float64{r, g, b})
526
}
527

528
func modulate(_ context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
529
        if len(args) != 3 {
530
                return
531
        }
532
        b, _ := strconv.ParseFloat(args[0], 64)
533
        s, _ := strconv.ParseFloat(args[1], 64)
534
        h, _ := strconv.ParseFloat(args[2], 64)
535
        b = 1 + b/100
536
        s = 1 + s/100
537
        return img.Modulate(b, s, h)
538
}
539

540
func blur(ctx context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
541
        if isAnimated(img) {
542
                // skip animation support
543
                return
544
        }
545
        var sigma float64
546
        switch len(args) {
547
        case 2:
548
                sigma, _ = strconv.ParseFloat(args[1], 64)
549
                break
550
        case 1:
551
                sigma, _ = strconv.ParseFloat(args[0], 64)
552
                break
553
        }
554
        sigma /= 2
555
        if sigma > 0 {
556
                return img.GaussianBlur(sigma)
557
        }
558
        return
559
}
560

561
func sharpen(ctx context.Context, img *Image, _ imagor.LoadFunc, args ...string) (err error) {
562
        if isAnimated(img) {
563
                // skip animation support
564
                return
565
        }
566
        var sigma float64
567
        switch len(args) {
568
        case 1:
569
                sigma, _ = strconv.ParseFloat(args[0], 64)
570
                break
571
        case 2, 3:
572
                sigma, _ = strconv.ParseFloat(args[1], 64)
573
                break
574
        }
575
        sigma = 1 + sigma*2
576
        if sigma > 0 {
577
                return img.Sharpen(sigma, 1, 2)
578
        }
579
        return
580
}
581

582
func stripIcc(_ context.Context, img *Image, _ imagor.LoadFunc, _ ...string) (err error) {
583
        return img.RemoveICCProfile()
584
}
585

586
func stripExif(_ context.Context, img *Image, _ imagor.LoadFunc, _ ...string) (err error) {
587
        return img.RemoveExif()
588
}
589

590
func trim(ctx context.Context, img *Image, _ imagor.LoadFunc, args ...string) error {
591
        var (
592
                ln        = len(args)
593
                pos       string
594
                tolerance int
595
        )
596
        if ln > 0 {
597
                tolerance, _ = strconv.Atoi(args[0])
598
        }
599
        if ln > 1 {
600
                pos = args[1]
601
        }
602
        if l, t, w, h, err := findTrim(ctx, img, pos, tolerance); err == nil {
603
                return img.ExtractArea(l, t, w, h)
604
        }
605
        return nil
606
}
607

608
func linearRGB(img *Image, a, b []float64) error {
609
        if img.HasAlpha() {
610
                a = append(a, 1)
611
                b = append(b, 0)
612
        }
613
        return img.Linear(a, b)
614
}
615

616
func isBlack(c *Color) bool {
617
        return c.R == 0x00 && c.G == 0x00 && c.B == 0x00
618
}
619

620
func isWhite(c *Color) bool {
621
        return c.R == 0xff && c.G == 0xff && c.B == 0xff
622
}
623

624
func getColor(img *Image, color string) *Color {
625
        vc := &Color{}
626
        args := strings.Split(strings.ToLower(color), ",")
627
        mode := ""
628
        name := strings.TrimPrefix(args[0], "#")
629
        if len(args) > 1 {
630
                mode = args[1]
631
        }
632
        if name == "auto" {
633
                if img != nil {
634
                        x := 0
635
                        y := 0
636
                        if mode == "bottom-right" {
637
                                x = img.Width() - 1
638
                                y = img.PageHeight() - 1
639
                        }
640
                        p, _ := img.GetPoint(x, y)
641
                        if len(p) >= 3 {
642
                                vc.R = uint8(p[0])
643
                                vc.G = uint8(p[1])
644
                                vc.B = uint8(p[2])
645
                        }
646
                }
647
        } else if c, ok := colornames.Map[name]; ok {
648
                vc.R = c.R
649
                vc.G = c.G
650
                vc.B = c.B
651
        } else if c, ok := parseHexColor(name); ok {
652
                vc.R = c.R
653
                vc.G = c.G
654
                vc.B = c.B
655
        }
656
        return vc
657
}
658

659
func parseHexColor(s string) (c color.RGBA, ok bool) {
660
        c.A = 0xff
661
        switch len(s) {
662
        case 6:
663
                c.R = hexToByte(s[0])<<4 + hexToByte(s[1])
664
                c.G = hexToByte(s[2])<<4 + hexToByte(s[3])
665
                c.B = hexToByte(s[4])<<4 + hexToByte(s[5])
666
                ok = true
667
        case 3:
668
                c.R = hexToByte(s[0]) * 17
669
                c.G = hexToByte(s[1]) * 17
670
                c.B = hexToByte(s[2]) * 17
671
                ok = true
672
        }
673
        return
674
}
675

676
func hexToByte(b byte) byte {
677
        switch {
678
        case b >= '0' && b <= '9':
679
                return b - '0'
680
        case b >= 'a' && b <= 'f':
681
                return b - 'a' + 10
682
        }
683
        return 0
684
}
685

686
func isAnimated(img *Image) bool {
687
        return img.Height() > img.PageHeight()
688
}
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