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

cshum / imagor / 15233123730

25 May 2025 02:03AM UTC coverage: 92.117% (+2.4%) from 89.678%
15233123730

Pull #544

github

cshum
Merge remote-tracking branch 'origin/vips-vipsgen' into vips-vipsgen
Pull Request #544: feat: vipsgen migration

307 of 345 new or added lines in 6 files covered. (88.99%)

6 existing lines in 1 file now uncovered.

4861 of 5277 relevant lines covered (92.12%)

1.1 hits per line

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

85.21
/processor/vipsprocessor/process.go
1
package vipsprocessor
2

3
import (
4
        "context"
5
        "github.com/cshum/vipsgen/vips"
6
        "math"
7
        "strconv"
8
        "strings"
9
        "time"
10

11
        "github.com/cshum/imagor"
12
        "github.com/cshum/imagor/imagorpath"
13
        "go.uber.org/zap"
14
)
15

16
var imageTypeMap = map[string]vips.ImageType{
17
        "gif":    vips.ImageTypeGif,
18
        "jpeg":   vips.ImageTypeJpeg,
19
        "jpg":    vips.ImageTypeJpeg,
20
        "magick": vips.ImageTypeMagick,
21
        "pdf":    vips.ImageTypePdf,
22
        "png":    vips.ImageTypePng,
23
        "svg":    vips.ImageTypeSvg,
24
        "tiff":   vips.ImageTypeTiff,
25
        "webp":   vips.ImageTypeWebp,
26
        "heif":   vips.ImageTypeHeif,
27
        "bmp":    vips.ImageTypeBmp,
28
        "avif":   vips.ImageTypeAvif,
29
        "jp2":    vips.ImageTypeJp2k,
30
}
31

32
// Color represents an RGB
33
type Color struct {
34
        R, G, B float64
35
}
36

37
// ColorRGBA represents an RGB with alpha channel (A)
38
type ColorRGBA struct {
39
        R, G, B, A float64
40
}
41

42
// IsAnimationSupported indicates if image type supports animation
43
func IsAnimationSupported(imageType vips.ImageType) bool {
1✔
44
        return imageType == vips.ImageTypeGif || imageType == vips.ImageTypeWebp
1✔
45
}
1✔
46

47
// Process implements imagor.Processor interface
48
func (v *Processor) Process(
49
        ctx context.Context, blob *imagor.Blob, p imagorpath.Params, load imagor.LoadFunc,
50
) (*imagor.Blob, error) {
1✔
51
        ctx = withContext(ctx)
1✔
52
        defer contextDone(ctx)
1✔
53
        var (
1✔
54
                thumbnailNotSupported bool
1✔
55
                upscale               = true
1✔
56
                stretch               = p.Stretch
1✔
57
                thumbnail             = false
1✔
58
                stripExif             bool
1✔
59
                stripMetadata         = v.StripMetadata
1✔
60
                orient                int
1✔
61
                img                   *vips.Image
1✔
62
                format                = vips.ImageTypeUnknown
1✔
63
                maxN                  = v.MaxAnimationFrames
1✔
64
                maxBytes              int
1✔
65
                page                  = 1
1✔
66
                dpi                   = 0
1✔
67
                focalRects            []focal
1✔
68
                err                   error
1✔
69
        )
1✔
70
        if p.Trim {
2✔
71
                thumbnailNotSupported = true
1✔
72
        }
1✔
73
        if p.FitIn {
2✔
74
                upscale = false
1✔
75
        }
1✔
76
        if maxN == 0 || maxN < -1 {
2✔
77
                maxN = 1
1✔
78
        }
1✔
79
        if blob != nil && !blob.SupportsAnimation() {
2✔
80
                maxN = 1
1✔
81
        }
1✔
82
        for _, p := range p.Filters {
2✔
83
                if v.disableFilters[p.Name] {
2✔
84
                        continue
1✔
85
                }
86
                switch p.Name {
1✔
87
                case "format":
1✔
88
                        if imageType, ok := imageTypeMap[p.Args]; ok {
2✔
89
                                format = supportedSaveFormat(imageType)
1✔
90
                                if !IsAnimationSupported(format) {
2✔
91
                                        // no frames if export format not support animation
1✔
92
                                        maxN = 1
1✔
93
                                }
1✔
94
                        }
95
                        break
1✔
96
                case "max_frames":
1✔
97
                        if n, _ := strconv.Atoi(p.Args); n > 0 && (maxN == -1 || n < maxN) {
2✔
98
                                maxN = n
1✔
99
                        }
1✔
100
                        break
1✔
101
                case "stretch":
1✔
102
                        stretch = true
1✔
103
                        break
1✔
104
                case "upscale":
1✔
105
                        upscale = true
1✔
106
                        break
1✔
107
                case "no_upscale":
1✔
108
                        upscale = false
1✔
109
                        break
1✔
110
                case "fill", "background_color":
1✔
111
                        if args := strings.Split(p.Args, ","); args[0] == "auto" {
2✔
112
                                thumbnailNotSupported = true
1✔
113
                        }
1✔
114
                        break
1✔
115
                case "page":
1✔
116
                        if n, _ := strconv.Atoi(p.Args); n > 0 {
2✔
117
                                page = n
1✔
118
                        }
1✔
119
                        break
1✔
120
                case "dpi":
×
121
                        if n, _ := strconv.Atoi(p.Args); n > 0 {
×
122
                                dpi = n
×
123
                        }
×
124
                        break
×
125
                case "orient":
1✔
126
                        if n, _ := strconv.Atoi(p.Args); n > 0 {
2✔
127
                                orient = n
1✔
128
                                thumbnailNotSupported = true
1✔
129
                        }
1✔
130
                        break
1✔
131
                case "max_bytes":
1✔
132
                        if n, _ := strconv.Atoi(p.Args); n > 0 {
2✔
133
                                maxBytes = n
1✔
134
                                thumbnailNotSupported = true
1✔
135
                        }
1✔
136
                        break
1✔
137
                case "trim", "focal", "rotate":
1✔
138
                        thumbnailNotSupported = true
1✔
139
                        break
1✔
140
                case "strip_exif":
1✔
141
                        stripExif = true
1✔
142
                case "strip_metadata":
1✔
143
                        stripMetadata = true
1✔
144
                        break
1✔
145
                }
146
        }
147

148
        if !thumbnailNotSupported &&
1✔
149
                p.CropBottom == 0.0 && p.CropTop == 0.0 && p.CropLeft == 0.0 && p.CropRight == 0.0 {
2✔
150
                // apply shrink-on-load where possible
1✔
151
                if p.FitIn {
2✔
152
                        if p.Width > 0 || p.Height > 0 {
2✔
153
                                w := p.Width
1✔
154
                                h := p.Height
1✔
155
                                if w == 0 {
2✔
156
                                        w = v.MaxWidth
1✔
157
                                }
1✔
158
                                if h == 0 {
2✔
159
                                        h = v.MaxHeight
1✔
160
                                }
1✔
161
                                size := vips.SizeDown
1✔
162
                                if upscale {
2✔
163
                                        size = vips.SizeBoth
1✔
164
                                }
1✔
165
                                if img, err = v.NewThumbnail(
1✔
166
                                        ctx, blob, w, h, vips.InterestingNone, size, maxN, page, dpi,
1✔
167
                                ); err != nil {
1✔
168
                                        return nil, err
×
169
                                }
×
170
                                thumbnail = true
1✔
171
                        }
172
                } else if stretch {
2✔
173
                        if p.Width > 0 && p.Height > 0 {
2✔
174
                                if img, err = v.NewThumbnail(
1✔
175
                                        ctx, blob, p.Width, p.Height,
1✔
176
                                        vips.InterestingNone, vips.SizeForce, maxN, page, dpi,
1✔
177
                                ); err != nil {
1✔
178
                                        return nil, err
×
179
                                }
×
180
                                thumbnail = true
1✔
181
                        }
182
                } else {
1✔
183
                        if p.Width > 0 && p.Height > 0 {
2✔
184
                                interest := vips.InterestingNone
1✔
185
                                if p.Smart {
2✔
186
                                        interest = vips.InterestingAttention
1✔
187
                                        thumbnail = true
1✔
188
                                } else if (p.VAlign == imagorpath.VAlignTop && p.HAlign == "") ||
2✔
189
                                        (p.HAlign == imagorpath.HAlignLeft && p.VAlign == "") {
2✔
190
                                        interest = vips.InterestingLow
1✔
191
                                        thumbnail = true
1✔
192
                                } else if (p.VAlign == imagorpath.VAlignBottom && p.HAlign == "") ||
2✔
193
                                        (p.HAlign == imagorpath.HAlignRight && p.VAlign == "") {
2✔
194
                                        interest = vips.InterestingHigh
1✔
195
                                        thumbnail = true
1✔
196
                                } else if (p.VAlign == "" || p.VAlign == "middle") &&
2✔
197
                                        (p.HAlign == "" || p.HAlign == "center") {
2✔
198
                                        interest = vips.InterestingCentre
1✔
199
                                        thumbnail = true
1✔
200
                                }
1✔
201
                                if thumbnail {
2✔
202
                                        if img, err = v.NewThumbnail(
1✔
203
                                                ctx, blob, p.Width, p.Height,
1✔
204
                                                interest, vips.SizeBoth, maxN, page, dpi,
1✔
205
                                        ); err != nil {
1✔
206
                                                return nil, err
×
207
                                        }
×
208
                                }
209
                        } else if p.Width > 0 && p.Height == 0 {
2✔
210
                                if img, err = v.NewThumbnail(
1✔
211
                                        ctx, blob, p.Width, v.MaxHeight,
1✔
212
                                        vips.InterestingNone, vips.SizeBoth, maxN, page, dpi,
1✔
213
                                ); err != nil {
1✔
214
                                        return nil, err
×
215
                                }
×
216
                                thumbnail = true
1✔
217
                        } else if p.Height > 0 && p.Width == 0 {
2✔
218
                                if img, err = v.NewThumbnail(
1✔
219
                                        ctx, blob, v.MaxWidth, p.Height,
1✔
220
                                        vips.InterestingNone, vips.SizeBoth, maxN, page, dpi,
1✔
221
                                ); err != nil {
1✔
222
                                        return nil, err
×
223
                                }
×
224
                                thumbnail = true
1✔
225
                        }
226
                }
227
        }
228
        if !thumbnail {
2✔
229
                if thumbnailNotSupported {
2✔
230
                        if img, err = v.NewImage(ctx, blob, maxN, page, dpi); err != nil {
1✔
231
                                return nil, err
×
232
                        }
×
233
                } else {
1✔
234
                        if img, err = v.NewThumbnail(
1✔
235
                                ctx, blob, v.MaxWidth, v.MaxHeight,
1✔
236
                                vips.InterestingNone, vips.SizeDown, maxN, page, dpi,
1✔
237
                        ); err != nil {
2✔
238
                                return nil, err
1✔
239
                        }
1✔
240
                }
241
        }
242
        // this should be called BEFORE vipscontext.contextDone
243
        defer img.Close()
1✔
244

1✔
245
        if orient > 0 {
2✔
246
                // orient rotate before resize
1✔
247
                if err = img.RotMultiPage(getAngle(orient)); err != nil {
1✔
248
                        return nil, err
×
249
                }
×
250
        }
251
        var (
1✔
252
                quality     int
1✔
253
                bitdepth    int
1✔
254
                compression int
1✔
255
                palette     bool
1✔
256
                origWidth   = float64(img.Width())
1✔
257
                origHeight  = float64(img.PageHeight())
1✔
258
        )
1✔
259
        if format == vips.ImageTypeUnknown {
2✔
260
                if blob.BlobType() == imagor.BlobTypeAVIF {
1✔
261
                        // meta loader determined as heif
×
NEW
262
                        format = vips.ImageTypeAvif
×
263
                } else {
1✔
264
                        format = img.Format()
1✔
265
                }
1✔
266
        }
267
        if v.Debug {
2✔
268
                v.Logger.Debug("image",
1✔
269
                        zap.Int("width", img.Width()),
1✔
270
                        zap.Int("height", img.Height()),
1✔
271
                        zap.Int("page_height", img.PageHeight()))
1✔
272
        }
1✔
273
        for _, p := range p.Filters {
2✔
274
                if v.disableFilters[p.Name] {
2✔
275
                        continue
1✔
276
                }
277
                switch p.Name {
1✔
278
                case "quality":
1✔
279
                        quality, _ = strconv.Atoi(p.Args)
1✔
280
                        break
1✔
281
                case "autojpg":
1✔
282
                        format = vips.ImageTypeJpeg
1✔
283
                        break
1✔
284
                case "focal":
1✔
285
                        args := strings.FieldsFunc(p.Args, argSplit)
1✔
286
                        switch len(args) {
1✔
287
                        case 4:
1✔
288
                                f := focal{}
1✔
289
                                f.Left, _ = strconv.ParseFloat(args[0], 64)
1✔
290
                                f.Top, _ = strconv.ParseFloat(args[1], 64)
1✔
291
                                f.Right, _ = strconv.ParseFloat(args[2], 64)
1✔
292
                                f.Bottom, _ = strconv.ParseFloat(args[3], 64)
1✔
293
                                if f.Left < 1 && f.Top < 1 && f.Right <= 1 && f.Bottom <= 1 {
2✔
294
                                        f.Left *= origWidth
1✔
295
                                        f.Right *= origWidth
1✔
296
                                        f.Top *= origHeight
1✔
297
                                        f.Bottom *= origHeight
1✔
298
                                }
1✔
299
                                if f.Right > f.Left && f.Bottom > f.Top {
2✔
300
                                        focalRects = append(focalRects, f)
1✔
301
                                }
1✔
302
                        case 2:
1✔
303
                                f := focal{}
1✔
304
                                f.Left, _ = strconv.ParseFloat(args[0], 64)
1✔
305
                                f.Top, _ = strconv.ParseFloat(args[1], 64)
1✔
306
                                if f.Left < 1 && f.Top < 1 {
2✔
307
                                        f.Left *= origWidth
1✔
308
                                        f.Top *= origHeight
1✔
309
                                }
1✔
310
                                f.Right = f.Left + 1
1✔
311
                                f.Bottom = f.Top + 1
1✔
312
                                focalRects = append(focalRects, f)
1✔
313
                        }
314
                        break
1✔
315
                case "palette":
1✔
316
                        palette = true
1✔
317
                        break
1✔
318
                case "bitdepth":
1✔
319
                        bitdepth, _ = strconv.Atoi(p.Args)
1✔
320
                        break
1✔
321
                case "compression":
1✔
322
                        compression, _ = strconv.Atoi(p.Args)
1✔
323
                        break
1✔
324
                }
325
        }
326
        if err := v.process(ctx, img, p, load, thumbnail, stretch, upscale, focalRects); err != nil {
2✔
327
                return nil, WrapErr(err)
1✔
328
        }
1✔
329
        if p.Meta {
2✔
330
                // metadata without export
1✔
331
                return imagor.NewBlobFromJsonMarshal(metadata(img, format, stripExif)), nil
1✔
332
        }
1✔
333
        format = supportedSaveFormat(format) // convert to supported export format
1✔
334
        for {
2✔
335
                buf, err := v.export(img, format, compression, quality, palette, bitdepth, stripMetadata)
1✔
336
                if err != nil {
1✔
337
                        return nil, WrapErr(err)
×
338
                }
×
339
                if maxBytes > 0 && (quality > 10 || quality == 0) && format != vips.ImageTypePng {
2✔
340
                        ln := len(buf)
1✔
341
                        if v.Debug {
2✔
342
                                v.Logger.Debug("max_bytes",
1✔
343
                                        zap.Int("bytes", ln),
1✔
344
                                        zap.Int("quality", quality),
1✔
345
                                )
1✔
346
                        }
1✔
347
                        if ln > maxBytes {
2✔
348
                                if quality == 0 {
2✔
349
                                        quality = 80
1✔
350
                                }
1✔
351
                                delta := float64(ln) / float64(maxBytes)
1✔
352
                                switch {
1✔
353
                                case delta > 3:
1✔
354
                                        quality = quality * 25 / 100
1✔
355
                                case delta > 1.5:
1✔
356
                                        quality = quality * 50 / 100
1✔
357
                                default:
×
358
                                        quality = quality * 75 / 100
×
359
                                }
360
                                if err := ctx.Err(); err != nil {
1✔
361
                                        return nil, WrapErr(err)
×
362
                                }
×
363
                                continue
1✔
364
                        }
365
                }
366
                blob := imagor.NewBlobFromBytes(buf)
1✔
367
                if typ, ok := vips.ImageMimeTypes[format]; ok {
2✔
368
                        blob.SetContentType(typ)
1✔
369
                }
1✔
370
                return blob, nil
1✔
371
        }
372
}
373

374
func (v *Processor) process(
375
        ctx context.Context, img *vips.Image, p imagorpath.Params, load imagor.LoadFunc, thumbnail, stretch, upscale bool, focalRects []focal,
376
) error {
1✔
377
        var (
1✔
378
                origWidth  = float64(img.Width())
1✔
379
                origHeight = float64(img.PageHeight())
1✔
380
                cropLeft,
1✔
381
                cropTop,
1✔
382
                cropRight,
1✔
383
                cropBottom float64
1✔
384
        )
1✔
385
        if p.CropRight > 0 || p.CropLeft > 0 || p.CropBottom > 0 || p.CropTop > 0 {
2✔
386
                // percentage
1✔
387
                cropLeft = math.Max(p.CropLeft, 0)
1✔
388
                cropTop = math.Max(p.CropTop, 0)
1✔
389
                cropRight = p.CropRight
1✔
390
                cropBottom = p.CropBottom
1✔
391
                if p.CropLeft < 1 && p.CropTop < 1 && p.CropRight <= 1 && p.CropBottom <= 1 {
2✔
392
                        cropLeft = math.Round(cropLeft * origWidth)
1✔
393
                        cropTop = math.Round(cropTop * origHeight)
1✔
394
                        cropRight = math.Round(cropRight * origWidth)
1✔
395
                        cropBottom = math.Round(cropBottom * origHeight)
1✔
396
                }
1✔
397
                if cropRight == 0 {
2✔
398
                        cropRight = origWidth - 1
1✔
399
                }
1✔
400
                if cropBottom == 0 {
2✔
401
                        cropBottom = origHeight - 1
1✔
402
                }
1✔
403
                cropRight = math.Min(cropRight, origWidth-1)
1✔
404
                cropBottom = math.Min(cropBottom, origHeight-1)
1✔
405
        }
406
        if p.Trim {
2✔
407
                if l, t, w, h, err := findTrim(ctx, img, p.TrimBy, p.TrimTolerance); err == nil {
2✔
408
                        cropLeft = math.Max(cropLeft, float64(l))
1✔
409
                        cropTop = math.Max(cropTop, float64(t))
1✔
410
                        if cropRight > 0 {
2✔
411
                                cropRight = math.Min(cropRight, float64(l+w))
1✔
412
                        } else {
2✔
413
                                cropRight = float64(l + w)
1✔
414
                        }
1✔
415
                        if cropBottom > 0 {
2✔
416
                                cropBottom = math.Min(cropBottom, float64(t+h))
1✔
417
                        } else {
2✔
418
                                cropBottom = float64(t + h)
1✔
419
                        }
1✔
420
                }
421
        }
422
        if cropRight > cropLeft && cropBottom > cropTop {
2✔
423
                if err := img.ExtractAreaMultiPage(
1✔
424
                        int(cropLeft), int(cropTop), int(cropRight-cropLeft), int(cropBottom-cropTop),
1✔
425
                ); err != nil {
1✔
426
                        return err
×
427
                }
×
428
        }
429
        var (
1✔
430
                w = p.Width
1✔
431
                h = p.Height
1✔
432
        )
1✔
433
        if w == 0 && h == 0 {
2✔
434
                w = img.Width()
1✔
435
                h = img.PageHeight()
1✔
436
        } else if w == 0 {
3✔
437
                w = img.Width() * h / img.PageHeight()
1✔
438
                if !upscale && w > img.Width() {
1✔
439
                        w = img.Width()
×
440
                }
×
441
        } else if h == 0 {
2✔
442
                h = img.PageHeight() * w / img.Width()
1✔
443
                if !upscale && h > img.PageHeight() {
1✔
444
                        h = img.PageHeight()
×
445
                }
×
446
        }
447
        if !thumbnail {
2✔
448
                if p.FitIn {
2✔
449
                        if upscale || w < img.Width() || h < img.PageHeight() {
2✔
450
                                if err := img.ThumbnailImage(w, &vips.ThumbnailImageOptions{Height: h, Crop: vips.InterestingNone}); err != nil {
1✔
451
                                        return err
×
452
                                }
×
453
                        }
454
                } else if stretch {
2✔
455
                        if upscale || (w < img.Width() && h < img.PageHeight()) {
2✔
456
                                if err := img.ThumbnailImage(
1✔
457
                                        w, &vips.ThumbnailImageOptions{Height: h, Crop: vips.InterestingNone, Size: vips.SizeForce},
1✔
458
                                ); err != nil {
1✔
459
                                        return err
×
460
                                }
×
461
                        }
462
                } else if upscale || w < img.Width() || h < img.PageHeight() {
2✔
463
                        interest := vips.InterestingCentre
1✔
464
                        if p.Smart {
1✔
NEW
465
                                interest = vips.InterestingAttention
×
466
                        } else if float64(w)/float64(h) > float64(img.Width())/float64(img.PageHeight()) {
2✔
467
                                if p.VAlign == imagorpath.VAlignTop {
2✔
468
                                        interest = vips.InterestingLow
1✔
469
                                } else if p.VAlign == imagorpath.VAlignBottom {
3✔
470
                                        interest = vips.InterestingHigh
1✔
471
                                }
1✔
472
                        } else {
1✔
473
                                if p.HAlign == imagorpath.HAlignLeft {
2✔
474
                                        interest = vips.InterestingLow
1✔
475
                                } else if p.HAlign == imagorpath.HAlignRight {
3✔
476
                                        interest = vips.InterestingHigh
1✔
477
                                }
1✔
478
                        }
479
                        if len(focalRects) > 0 {
2✔
480
                                focalX, focalY := parseFocalPoint(focalRects...)
1✔
481
                                if err := v.FocalThumbnail(
1✔
482
                                        img, w, h,
1✔
483
                                        (focalX-cropLeft)/float64(img.Width()),
1✔
484
                                        (focalY-cropTop)/float64(img.PageHeight()),
1✔
485
                                ); err != nil {
1✔
486
                                        return err
×
487
                                }
×
488
                        } else {
1✔
489
                                if err := v.Thumbnail(img, w, h, interest, vips.SizeBoth); err != nil {
1✔
490
                                        return err
×
491
                                }
×
492
                        }
493
                        if _, err := v.CheckResolution(img, nil); err != nil {
2✔
494
                                return err
1✔
495
                        }
1✔
496
                }
497
        }
498
        if p.HFlip {
2✔
499
                if err := img.Flip(vips.DirectionHorizontal); err != nil {
1✔
500
                        return err
×
501
                }
×
502
        }
503
        if p.VFlip {
2✔
504
                if err := img.Flip(vips.DirectionVertical); err != nil {
1✔
505
                        return err
×
506
                }
×
507
        }
508
        for i, filter := range p.Filters {
2✔
509
                if err := ctx.Err(); err != nil {
1✔
510
                        return err
×
511
                }
×
512
                if v.disableFilters[filter.Name] {
2✔
513
                        continue
1✔
514
                }
515
                if v.MaxFilterOps > 0 && i >= v.MaxFilterOps {
2✔
516
                        if v.Debug {
2✔
517
                                v.Logger.Debug("max-filter-ops-exceeded",
1✔
518
                                        zap.String("name", filter.Name), zap.String("args", filter.Args))
1✔
519
                        }
1✔
520
                        break
1✔
521
                }
522
                start := time.Now()
1✔
523
                var args []string
1✔
524
                if filter.Args != "" {
2✔
525
                        args = strings.Split(filter.Args, ",")
1✔
526
                }
1✔
527
                if fn := v.Filters[filter.Name]; fn != nil {
2✔
528
                        if err := fn(ctx, img, load, args...); err != nil {
1✔
529
                                return err
×
530
                        }
×
531
                } else if filter.Name == "fill" {
2✔
532
                        if err := v.fill(ctx, img, w, h,
1✔
533
                                p.PaddingLeft, p.PaddingTop, p.PaddingRight, p.PaddingBottom,
1✔
534
                                filter.Args); err != nil {
1✔
535
                                return err
×
536
                        }
×
537
                }
538
                if v.Debug {
2✔
539
                        v.Logger.Debug("filter",
1✔
540
                                zap.String("name", filter.Name), zap.String("args", filter.Args),
1✔
541
                                zap.Duration("took", time.Since(start)))
1✔
542
                }
1✔
543
        }
544
        return nil
1✔
545
}
546

547
// Metadata image attributes
548
type Metadata struct {
549
        Format      string         `json:"format"`
550
        ContentType string         `json:"content_type"`
551
        Width       int            `json:"width"`
552
        Height      int            `json:"height"`
553
        Orientation int            `json:"orientation"`
554
        Pages       int            `json:"pages"`
555
        Bands       int            `json:"bands"`
556
        Exif        map[string]any `json:"exif"`
557
}
558

559
func metadata(img *vips.Image, format vips.ImageType, stripExif bool) *Metadata {
1✔
560
        pages := 1
1✔
561
        if IsAnimationSupported(format) {
2✔
562
                pages = img.Height() / img.PageHeight()
1✔
563
        }
1✔
564
        if format == vips.ImageTypePdf {
2✔
565
                pages = img.Pages()
1✔
566
        }
1✔
567
        exif := map[string]any{}
1✔
568
        if !stripExif {
2✔
569
                exif = extractExif(img.Exif())
1✔
570
        }
1✔
571
        return &Metadata{
1✔
572
                Format:      string(format),
1✔
573
                ContentType: vips.ImageMimeTypes[format],
1✔
574
                Width:       img.Width(),
1✔
575
                Height:      img.PageHeight(),
1✔
576
                Pages:       pages,
1✔
577
                Bands:       img.Bands(),
1✔
578
                Orientation: img.Orientation(),
1✔
579
                Exif:        exif,
1✔
580
        }
1✔
581
}
582

583
func supportedSaveFormat(format vips.ImageType) vips.ImageType {
1✔
584
        switch format {
1✔
585
        case vips.ImageTypePng, vips.ImageTypeWebp, vips.ImageTypeTiff, vips.ImageTypeGif, vips.ImageTypeAvif, vips.ImageTypeHeif, vips.ImageTypeJp2k:
1✔
586
                return format
1✔
587
        }
588
        return vips.ImageTypeJpeg
1✔
589
}
590

591
func (v *Processor) export(
592
        image *vips.Image, format vips.ImageType, compression int, quality int, palette bool, bitdepth int, stripMetadata bool,
593
) ([]byte, error) {
1✔
594
        switch format {
1✔
595
        case vips.ImageTypePng:
1✔
596
                opts := &vips.PngsaveBufferOptions{
1✔
597
                        Q:           quality,
1✔
598
                        Palette:     palette,
1✔
599
                        Bitdepth:    bitdepth,
1✔
600
                        Compression: compression,
1✔
601
                }
1✔
602
                if stripMetadata {
2✔
603
                        opts.Keep = vips.KeepNone
1✔
604
                }
1✔
605
                return image.PngsaveBuffer(opts)
1✔
606
        case vips.ImageTypeWebp:
1✔
607
                opts := &vips.WebpsaveBufferOptions{
1✔
608
                        Q: quality,
1✔
609
                }
1✔
610
                if stripMetadata {
2✔
611
                        opts.Keep = vips.KeepNone
1✔
612
                }
1✔
613
                return image.WebpsaveBuffer(opts)
1✔
614
        case vips.ImageTypeTiff:
1✔
615
                opts := &vips.TiffsaveBufferOptions{
1✔
616
                        Q: quality,
1✔
617
                }
1✔
618
                if stripMetadata {
2✔
619
                        opts.Keep = vips.KeepNone
1✔
620
                }
1✔
621
                return image.TiffsaveBuffer(opts)
1✔
622
        case vips.ImageTypeGif:
1✔
623
                opts := &vips.GifsaveBufferOptions{}
1✔
624
                if stripMetadata {
2✔
625
                        opts.Keep = vips.KeepNone
1✔
626
                }
1✔
627
                return image.GifsaveBuffer(opts)
1✔
NEW
628
        case vips.ImageTypeAvif:
×
NEW
629
                opts := &vips.HeifsaveBufferOptions{
×
NEW
630
                        Q:           quality,
×
NEW
631
                        Compression: vips.HeifCompressionAv1,
×
632
                }
×
633
                if stripMetadata {
×
NEW
634
                        opts.Keep = vips.KeepNone
×
635
                }
×
NEW
636
                opts.Effort = 9 - v.AvifSpeed
×
NEW
637
                return image.HeifsaveBuffer(opts)
×
NEW
638
        case vips.ImageTypeHeif:
×
NEW
639
                opts := &vips.HeifsaveBufferOptions{
×
NEW
640
                        Q: quality,
×
641
                }
×
642
                if stripMetadata {
×
NEW
643
                        opts.Keep = vips.KeepNone
×
644
                }
×
NEW
645
                return image.HeifsaveBuffer(opts)
×
NEW
646
        case vips.ImageTypeJp2k:
×
NEW
647
                opts := &vips.Jp2ksaveBufferOptions{
×
NEW
648
                        Q: quality,
×
649
                }
×
NEW
650
                if stripMetadata {
×
NEW
651
                        opts.Keep = vips.KeepNone
×
652
                }
×
NEW
653
                return image.Jp2ksaveBuffer(opts)
×
654
        default:
1✔
655
                opts := &vips.JpegsaveBufferOptions{}
1✔
656
                if v.MozJPEG {
1✔
NEW
657
                        opts.Q = 75
×
NEW
658
                        opts.Keep = vips.KeepNone
×
659
                        opts.OptimizeCoding = true
×
660
                        opts.Interlace = true
×
661
                        opts.OptimizeScans = true
×
662
                        opts.TrellisQuant = true
×
663
                        opts.QuantTable = 3
×
664
                }
×
665
                if quality > 0 {
2✔
666
                        opts.Q = quality
1✔
667
                }
1✔
668
                if stripMetadata {
2✔
669
                        opts.Keep = vips.KeepNone
1✔
670
                }
1✔
671
                return image.JpegsaveBuffer(opts)
1✔
672
        }
673
}
674

675
func argSplit(r rune) bool {
1✔
676
        return r == 'x' || r == ',' || r == ':'
1✔
677
}
1✔
678

679
type focal struct {
680
        Left   float64
681
        Right  float64
682
        Top    float64
683
        Bottom float64
684
}
685

686
func parseFocalPoint(focalRects ...focal) (focalX, focalY float64) {
1✔
687
        var sumWeight float64
1✔
688
        for _, f := range focalRects {
2✔
689
                sumWeight += (f.Right - f.Left) * (f.Bottom - f.Top)
1✔
690
        }
1✔
691
        for _, f := range focalRects {
2✔
692
                r := (f.Right - f.Left) * (f.Bottom - f.Top) / sumWeight
1✔
693
                focalX += (f.Left + f.Right) / 2 * r
1✔
694
                focalY += (f.Top + f.Bottom) / 2 * r
1✔
695
        }
1✔
696
        return
1✔
697
}
698

699
func findTrim(
700
        _ context.Context, img *vips.Image, pos string, tolerance int,
701
) (l, t, w, h int, err error) {
1✔
702
        if isAnimated(img) {
2✔
703
                // skip animation support
1✔
704
                return
1✔
705
        }
1✔
706
        var x, y int
1✔
707
        if pos == imagorpath.TrimByBottomRight {
2✔
708
                x = img.Width() - 1
1✔
709
                y = img.PageHeight() - 1
1✔
710
        }
1✔
711
        if tolerance == 0 {
2✔
712
                tolerance = 1
1✔
713
        }
1✔
714
        background, err := img.Getpoint(x, y, nil)
1✔
715
        if err != nil {
1✔
NEW
716
                return
×
NEW
717
        }
×
718
        l, t, w, h, err = img.FindTrim(&vips.FindTrimOptions{
1✔
719
                Threshold:  float64(tolerance),
1✔
720
                Background: background,
1✔
721
        })
1✔
722
        return
1✔
723
}
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