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

cshum / imagor / 21680984187

04 Feb 2026 05:11PM UTC coverage: 91.894% (-0.04%) from 91.931%
21680984187

push

github

web-flow
refactor: vips processor filter logic refacor (#727)

* cleanup logic

* sepapration of concern

178 of 200 new or added lines in 3 files covered. (89.0%)

1 existing line in 1 file now uncovered.

5714 of 6218 relevant lines covered (91.89%)

1.1 hits per line

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

86.44
/processor/vipsprocessor/overlay.go
1
package vipsprocessor
2

3
import (
4
        "strconv"
5
        "strings"
6

7
        "github.com/cshum/imagor/imagorpath"
8
        "github.com/cshum/vipsgen/vips"
9
)
10

11
// blendModeMap maps blend mode names to vips.BlendMode constants
12
var blendModeMap = map[string]vips.BlendMode{
13
        "normal":      vips.BlendModeOver,
14
        "multiply":    vips.BlendModeMultiply,
15
        "color-burn":  vips.BlendModeColourBurn,
16
        "darken":      vips.BlendModeDarken,
17
        "screen":      vips.BlendModeScreen,
18
        "color-dodge": vips.BlendModeColourDodge,
19
        "lighten":     vips.BlendModeLighten,
20
        "add":         vips.BlendModeAdd,
21
        "overlay":     vips.BlendModeOverlay,
22
        "soft-light":  vips.BlendModeSoftLight,
23
        "hard-light":  vips.BlendModeHardLight,
24
        "difference":  vips.BlendModeDifference,
25
        "exclusion":   vips.BlendModeExclusion,
26
        "mask":        vips.BlendModeDestIn,
27
        "mask-out":    vips.BlendModeDestOut,
28
}
29

30
// parseOverlayPosition parses position argument and returns position value and repeat count
31
func parseOverlayPosition(arg string, canvasSize, overlaySize int, hAlign, vAlign string) (pos int, repeat int) {
1✔
32
        repeat = 1
1✔
33
        if arg == "" {
2✔
34
                return 0, 1
1✔
35
        }
1✔
36

37
        if arg == "center" {
2✔
38
                return (canvasSize - overlaySize) / 2, 1
1✔
39
        } else if arg == hAlign || arg == vAlign {
3✔
40
                if arg == imagorpath.HAlignRight || arg == imagorpath.VAlignBottom {
2✔
41
                        return canvasSize - overlaySize, 1
1✔
42
                }
1✔
43
                return 0, 1
1✔
44
        } else if arg == "repeat" {
2✔
45
                return 0, canvasSize/overlaySize + 1
1✔
46
        } else if strings.HasPrefix(strings.TrimPrefix(arg, "-"), "0.") {
3✔
47
                pec, _ := strconv.ParseFloat(arg, 64)
1✔
48
                return int(pec * float64(canvasSize)), 1
1✔
49
        } else if strings.HasSuffix(arg, "p") {
3✔
50
                val, _ := strconv.Atoi(strings.TrimSuffix(arg, "p"))
1✔
51
                return val * canvasSize / 100, 1
1✔
52
        }
1✔
53

54
        pos, _ = strconv.Atoi(arg)
1✔
55
        return pos, 1
1✔
56
}
57

58
// compositeOverlay transforms and composites overlay image onto the base image
59
// Handles color space, alpha channel, positioning, repeat patterns, cropping, and animation frames
60
// Returns early without compositing if overlay is completely outside canvas bounds
61
func compositeOverlay(img *vips.Image, overlay *vips.Image, xArg, yArg string, alpha float64, blendMode vips.BlendMode) error {
1✔
62
        // Ensure overlay has proper color space and alpha
1✔
63
        if overlay.Bands() < 3 {
2✔
64
                if err := overlay.Colourspace(vips.InterpretationSrgb, nil); err != nil {
1✔
NEW
65
                        return err
×
NEW
66
                }
×
67
        }
68
        if !overlay.HasAlpha() {
2✔
69
                if err := overlay.Addalpha(); err != nil {
1✔
NEW
70
                        return err
×
NEW
71
                }
×
72
        }
73

74
        // Apply alpha if provided
75
        if alpha > 0 {
2✔
76
                alphaMultiplier := 1 - alpha/100
1✔
77
                if alphaMultiplier != 1 {
2✔
78
                        if err := overlay.Linear([]float64{1, 1, 1, alphaMultiplier}, []float64{0, 0, 0, 0}, nil); err != nil {
1✔
NEW
79
                                return err
×
NEW
80
                        }
×
81
                }
82
        }
83

84
        // Parse position
85
        overlayWidth := overlay.Width()
1✔
86
        overlayHeight := overlay.PageHeight()
1✔
87

1✔
88
        x, across := parseOverlayPosition(xArg, img.Width(), overlayWidth, imagorpath.HAlignLeft, imagorpath.HAlignRight)
1✔
89
        y, down := parseOverlayPosition(yArg, img.PageHeight(), overlayHeight, imagorpath.VAlignTop, imagorpath.VAlignBottom)
1✔
90

1✔
91
        // Apply negative adjustment for all cases EXCEPT center
1✔
92
        if x < 0 && xArg != "center" {
2✔
93
                x += img.Width() - overlayWidth
1✔
94
        }
1✔
95
        if y < 0 && yArg != "center" {
2✔
96
                y += img.PageHeight() - overlayHeight
1✔
97
        }
1✔
98

99
        // Handle repeat pattern
100
        if across*down > 1 {
2✔
101
                if err := overlay.EmbedMultiPage(0, 0, across*overlayWidth, down*overlayHeight,
1✔
102
                        &vips.EmbedMultiPageOptions{Extend: vips.ExtendRepeat}); err != nil {
1✔
NEW
103
                        return err
×
NEW
104
                }
×
105
                // Update dimensions after repeat
106
                overlayWidth = overlay.Width()
1✔
107
                overlayHeight = overlay.PageHeight()
1✔
108
        }
109

110
        // Position overlay on canvas
111
        // Crop overlay to only the visible portion within canvas bounds
112
        visibleLeft := 0
1✔
113
        visibleTop := 0
1✔
114
        visibleWidth := overlayWidth
1✔
115
        visibleHeight := overlayHeight
1✔
116
        embedX := x
1✔
117
        embedY := y
1✔
118

1✔
119
        // Handle overlay extending beyond right/bottom edges
1✔
120
        if x+overlayWidth > img.Width() {
2✔
121
                visibleWidth = img.Width() - x
1✔
122
        }
1✔
123
        if y+overlayHeight > img.PageHeight() {
2✔
124
                visibleHeight = img.PageHeight() - y
1✔
125
        }
1✔
126

127
        // Handle overlay starting before left/top edges (negative positions)
128
        if x < 0 {
2✔
129
                visibleLeft = -x
1✔
130
                visibleWidth = overlayWidth + x // reduce width
1✔
131
                embedX = 0
1✔
132
        }
1✔
133
        if y < 0 {
2✔
134
                visibleTop = -y
1✔
135
                visibleHeight = overlayHeight + y // reduce height
1✔
136
                embedY = 0
1✔
137
        }
1✔
138

139
        // Crop overlay to visible portion if needed
140
        if visibleLeft > 0 || visibleTop > 0 ||
1✔
141
                visibleWidth < overlayWidth || visibleHeight < overlayHeight {
2✔
142
                if visibleWidth > 0 && visibleHeight > 0 {
2✔
143
                        if err := overlay.ExtractAreaMultiPage(
1✔
144
                                visibleLeft, visibleTop, visibleWidth, visibleHeight,
1✔
145
                        ); err != nil {
1✔
NEW
146
                                return err
×
NEW
147
                        }
×
148
                } else {
1✔
149
                        // Overlay is completely outside canvas bounds, skip it
1✔
150
                        return nil
1✔
151
                }
1✔
152
        }
153

154
        // Embed the cropped overlay at adjusted position
155
        if err := overlay.EmbedMultiPage(
1✔
156
                embedX, embedY, img.Width(), img.PageHeight(), nil,
1✔
157
        ); err != nil {
1✔
NEW
158
                return err
×
NEW
159
        }
×
160

161
        // Handle animation frames
162
        overlayN := overlay.Height() / overlay.PageHeight()
1✔
163
        if n := img.Height() / img.PageHeight(); n > overlayN {
2✔
164
                cnt := n / overlayN
1✔
165
                if n%overlayN > 0 {
2✔
166
                        cnt++
1✔
167
                }
1✔
168
                if err := overlay.Replicate(1, cnt); err != nil {
1✔
NEW
169
                        return err
×
NEW
170
                }
×
171
        }
172

173
        // Composite overlay onto image with specified blend mode
174
        return img.Composite2(overlay, blendMode, nil)
1✔
175
}
176

177
// getBlendMode returns the vips.BlendMode for a given mode string
178
// Defaults to BlendModeOver (normal) if mode is empty or invalid
179
func getBlendMode(mode string) vips.BlendMode {
1✔
180
        if mode == "" {
1✔
NEW
181
                return vips.BlendModeOver
×
NEW
182
        }
×
183
        if blendMode, ok := blendModeMap[strings.ToLower(mode)]; ok {
2✔
184
                return blendMode
1✔
185
        }
1✔
186
        // Default to normal if invalid mode
187
        return vips.BlendModeOver
1✔
188
}
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