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

cshum / imagor / 22352795542

24 Feb 2026 01:24PM UTC coverage: 91.857% (-0.1%) from 91.997%
22352795542

Pull #713

github

mevinbabuc
feat: add in-memory watermark cache with ristretto

Adds an optional in-memory cache for processed watermark images using
dgraph-io/ristretto. When enabled via -imagor-watermark-cache-size,
watermarks are cached after resize, colorspace conversion, and alpha
application as vips.Image objects for fast copy-on-write retrieval.

Also adds singleflight deduplication for concurrent image loads to
prevent redundant fetches of the same watermark.
Pull Request #713: feat: add watermark caching for improved performance

81 of 99 new or added lines in 5 files covered. (81.82%)

1 existing line in 1 file now uncovered.

5900 of 6423 relevant lines covered (91.86%)

1.1 hits per line

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

85.0
/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
        // Check for alignment keyword with negative offset (e.g., left-20, l-20, right-30, r-30, top-20, t-20, bottom-20, b-20)
38
        if strings.HasPrefix(arg, "left-") || strings.HasPrefix(arg, "l-") {
2✔
39
                offset, _ := strconv.Atoi(strings.TrimPrefix(strings.TrimPrefix(arg, "left-"), "l-"))
1✔
40
                return -offset, 1
1✔
41
        } else if strings.HasPrefix(arg, "right-") || strings.HasPrefix(arg, "r-") {
3✔
42
                offset, _ := strconv.Atoi(strings.TrimPrefix(strings.TrimPrefix(arg, "right-"), "r-"))
1✔
43
                return canvasSize - overlaySize + offset, 1
1✔
44
        } else if strings.HasPrefix(arg, "top-") || strings.HasPrefix(arg, "t-") {
3✔
45
                offset, _ := strconv.Atoi(strings.TrimPrefix(strings.TrimPrefix(arg, "top-"), "t-"))
1✔
46
                return -offset, 1
1✔
47
        } else if strings.HasPrefix(arg, "bottom-") || strings.HasPrefix(arg, "b-") {
3✔
48
                offset, _ := strconv.Atoi(strings.TrimPrefix(strings.TrimPrefix(arg, "bottom-"), "b-"))
1✔
49
                return canvasSize - overlaySize + offset, 1
1✔
50
        }
1✔
51

52
        if arg == "center" {
2✔
53
                return (canvasSize - overlaySize) / 2, 1
1✔
54
        } else if arg == hAlign || arg == vAlign {
3✔
55
                if arg == imagorpath.HAlignRight || arg == imagorpath.VAlignBottom {
2✔
56
                        return canvasSize - overlaySize, 1
1✔
57
                }
1✔
58
                return 0, 1
1✔
59
        } else if arg == "repeat" {
2✔
60
                return 0, canvasSize/overlaySize + 1
1✔
61
        } else if strings.HasPrefix(strings.TrimPrefix(arg, "-"), "0.") {
3✔
62
                pec, _ := strconv.ParseFloat(arg, 64)
1✔
63
                return int(pec * float64(canvasSize)), 1
1✔
64
        } else if strings.HasSuffix(arg, "p") {
3✔
65
                val, _ := strconv.Atoi(strings.TrimSuffix(arg, "p"))
1✔
66
                return val * canvasSize / 100, 1
1✔
67
        }
1✔
68

69
        pos, _ = strconv.Atoi(arg)
1✔
70
        return pos, 1
1✔
71
}
72

73
// compositeOverlay transforms and composites overlay image onto the base image
74
// Handles color space, alpha channel, positioning, repeat patterns, cropping, and animation frames
75
// Returns early without compositing if overlay is completely outside canvas bounds
76
func compositeOverlay(img *vips.Image, overlay *vips.Image, xArg, yArg string, alpha float64, blendMode vips.BlendMode) error {
1✔
77
        // Ensure overlay has proper color space and alpha
1✔
78
        if overlay.Bands() < 3 {
2✔
79
                if err := overlay.Colourspace(vips.InterpretationSrgb, nil); err != nil {
1✔
80
                        return err
×
81
                }
×
82
        }
83
        if !overlay.HasAlpha() {
1✔
UNCOV
84
                if err := overlay.Addalpha(); err != nil {
×
85
                        return err
×
86
                }
×
87
        }
88

89
        // Apply alpha if provided
90
        if alpha > 0 {
2✔
91
                alphaMultiplier := 1 - alpha/100
1✔
92
                if alphaMultiplier != 1 {
2✔
93
                        if err := overlay.Linear([]float64{1, 1, 1, alphaMultiplier}, []float64{0, 0, 0, 0}, nil); err != nil {
1✔
94
                                return err
×
95
                        }
×
96
                }
97
        }
98

99
        // Parse position
100
        overlayWidth := overlay.Width()
1✔
101
        overlayHeight := overlay.PageHeight()
1✔
102

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

1✔
106
        // Apply negative adjustment for plain numeric values only (not prefixed keywords)
1✔
107
        if x < 0 && xArg != "center" &&
1✔
108
                !strings.HasPrefix(xArg, "left-") && !strings.HasPrefix(xArg, "l-") &&
1✔
109
                !strings.HasPrefix(xArg, "right-") && !strings.HasPrefix(xArg, "r-") {
2✔
110
                x += img.Width() - overlayWidth
1✔
111
        }
1✔
112
        if y < 0 && yArg != "center" &&
1✔
113
                !strings.HasPrefix(yArg, "top-") && !strings.HasPrefix(yArg, "t-") &&
1✔
114
                !strings.HasPrefix(yArg, "bottom-") && !strings.HasPrefix(yArg, "b-") {
2✔
115
                y += img.PageHeight() - overlayHeight
1✔
116
        }
1✔
117

118
        // Handle repeat pattern
119
        if across*down > 1 {
2✔
120
                if err := overlay.EmbedMultiPage(0, 0, across*overlayWidth, down*overlayHeight,
1✔
121
                        &vips.EmbedMultiPageOptions{Extend: vips.ExtendRepeat}); err != nil {
1✔
122
                        return err
×
123
                }
×
124
                // Update dimensions after repeat
125
                overlayWidth = overlay.Width()
1✔
126
                overlayHeight = overlay.PageHeight()
1✔
127
        }
128

129
        // Check if overlay is completely outside canvas bounds
130
        // Skip compositing if there's no intersection with the canvas
131
        if x >= img.Width() || y >= img.PageHeight() ||
1✔
132
                x+overlayWidth <= 0 || y+overlayHeight <= 0 {
2✔
133
                // Overlay is completely outside canvas bounds, skip it
1✔
134
                return nil
1✔
135
        }
1✔
136

137
        // Position overlay on canvas
138
        // Crop overlay to only the visible portion within canvas bounds
139
        visibleLeft := 0
1✔
140
        visibleTop := 0
1✔
141
        visibleWidth := overlayWidth
1✔
142
        visibleHeight := overlayHeight
1✔
143
        embedX := x
1✔
144
        embedY := y
1✔
145

1✔
146
        // Handle overlay extending beyond right/bottom edges
1✔
147
        if x+overlayWidth > img.Width() {
2✔
148
                visibleWidth = img.Width() - x
1✔
149
        }
1✔
150
        if y+overlayHeight > img.PageHeight() {
2✔
151
                visibleHeight = img.PageHeight() - y
1✔
152
        }
1✔
153

154
        // Handle overlay starting before left/top edges (negative positions)
155
        if x < 0 {
2✔
156
                visibleLeft = -x
1✔
157
                visibleWidth = overlayWidth + x // reduce width
1✔
158
                embedX = 0
1✔
159
        }
1✔
160
        if y < 0 {
2✔
161
                visibleTop = -y
1✔
162
                visibleHeight = overlayHeight + y // reduce height
1✔
163
                embedY = 0
1✔
164
        }
1✔
165

166
        // Crop overlay to visible portion if needed
167
        if visibleLeft > 0 || visibleTop > 0 ||
1✔
168
                visibleWidth < overlayWidth || visibleHeight < overlayHeight {
2✔
169
                if visibleWidth > 0 && visibleHeight > 0 {
2✔
170
                        if err := overlay.ExtractAreaMultiPage(
1✔
171
                                visibleLeft, visibleTop, visibleWidth, visibleHeight,
1✔
172
                        ); err != nil {
1✔
173
                                return err
×
174
                        }
×
175
                } else {
×
176
                        // Overlay is completely outside canvas bounds, skip it
×
177
                        return nil
×
178
                }
×
179
        }
180

181
        // Embed the cropped overlay at adjusted position
182
        if err := overlay.EmbedMultiPage(
1✔
183
                embedX, embedY, img.Width(), img.PageHeight(), nil,
1✔
184
        ); err != nil {
1✔
185
                return err
×
186
        }
×
187

188
        // Handle animation frames
189
        overlayN := overlay.Height() / overlay.PageHeight()
1✔
190
        if n := img.Height() / img.PageHeight(); n > overlayN {
2✔
191
                cnt := n / overlayN
1✔
192
                if n%overlayN > 0 {
2✔
193
                        cnt++
1✔
194
                }
1✔
195
                if err := overlay.Replicate(1, cnt); err != nil {
1✔
196
                        return err
×
197
                }
×
198
        }
199

200
        // Composite overlay onto image with specified blend mode
201
        return img.Composite2(overlay, blendMode, nil)
1✔
202
}
203

204
// getBlendMode returns the vips.BlendMode for a given mode string
205
// Defaults to BlendModeOver (normal) if mode is empty or invalid
206
func getBlendMode(mode string) vips.BlendMode {
1✔
207
        if mode == "" {
1✔
208
                return vips.BlendModeOver
×
209
        }
×
210
        if blendMode, ok := blendModeMap[strings.ToLower(mode)]; ok {
2✔
211
                return blendMode
1✔
212
        }
1✔
213
        // Default to normal if invalid mode
214
        return vips.BlendModeOver
1✔
215
}
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