Skip to content

Commit

Permalink
Merge pull request #61 from cshum/focal
Browse files Browse the repository at this point in the history
feat(vips): focal filter
  • Loading branch information
cshum authored May 30, 2022
2 parents 7d8e011 + f1cb175 commit 3cd8c5d
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 11 deletions.
3 changes: 1 addition & 2 deletions imagor.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ func New(options ...Option) *Imagor {
if app.Signer == nil {
app.Signer = imagorpath.NewDefaultSigner("")
}
// cast storages into loaders
app.ResultLoaders = loaderSlice(app.ResultStorages)
app.ResultLoaders = append(loaderSlice(app.ResultStorages), app.ResultLoaders...)
app.Loaders = append(loaderSlice(app.Storages), app.Loaders...)
return app
}
Expand Down
20 changes: 19 additions & 1 deletion processor/vipsprocessor/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vipsprocessor
import (
"github.com/cshum/imagor"
"github.com/davidbyttow/govips/v2/vips"
"math"
)

func (v *VipsProcessor) newThumbnail(
Expand Down Expand Up @@ -115,13 +116,30 @@ func (v *VipsProcessor) thumbnail(
return v.animatedThumbnailWithCrop(img, width, height, crop, size)
}

func (v *VipsProcessor) focalThumbnail(img *vips.ImageRef, w, h int, fx, fy float64) (err error) {
if float64(w)/float64(h) > float64(img.Width())/float64(img.PageHeight()) {
if err = img.Thumbnail(w, v.MaxHeight, vips.InterestingNone); err != nil {
return
}
} else {
if err = img.Thumbnail(v.MaxWidth, h, vips.InterestingNone); err != nil {
return
}
}
var top, left float64
left = float64(img.Width())*fx - float64(w)/2
top = float64(img.PageHeight())*fy - float64(h)/2
left = math.Max(0, math.Min(left, float64(img.Width()-w)))
top = math.Max(0, math.Min(top, float64(img.PageHeight()-h)))
return img.ExtractArea(int(left), int(top), w, h)
}

func (v *VipsProcessor) animatedThumbnailWithCrop(
img *vips.ImageRef, w, h int, crop vips.Interesting, size vips.Size,
) (err error) {
if size == vips.SizeDown && img.Width() < w && img.PageHeight() < h {
return
}
// use ExtractArea for animated cropping
var top, left int
if float64(w)/float64(h) > float64(img.Width())/float64(img.PageHeight()) {
if err = img.ThumbnailWithSize(w, v.MaxHeight, vips.InterestingNone, size); err != nil {
Expand Down
37 changes: 34 additions & 3 deletions processor/vipsprocessor/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

func (v *VipsProcessor) process(
ctx context.Context, img *vips.ImageRef, p imagorpath.Params, load imagor.LoadFunc, thumbnail, stretch, upscale bool,
ctx context.Context, img *vips.ImageRef, p imagorpath.Params, load imagor.LoadFunc, thumbnail, stretch, upscale bool, focalRects []focal,
) error {
if p.Trim {
if err := trim(ctx, img, p.TrimBy, p.TrimTolerance); err != nil {
Expand Down Expand Up @@ -76,8 +76,19 @@ func (v *VipsProcessor) process(
interest = vips.InterestingHigh
}
}
if err := v.thumbnail(img, w, h, interest, vips.SizeBoth); err != nil {
return err
if p.Smart && len(focalRects) > 0 {
focalX, focalY := parseFocalPoint(focalRects...)
if err := v.focalThumbnail(
img, w, h,
focalX/float64(img.Width()),
focalY/float64(img.PageHeight()),
); err != nil {
return err
}
} else {
if err := v.thumbnail(img, w, h, interest, vips.SizeBoth); err != nil {
return err
}
}
}
}
Expand Down Expand Up @@ -124,6 +135,26 @@ func (v *VipsProcessor) process(
return nil
}

type focal struct {
Left float64
Right float64
Top float64
Bottom float64
}

func parseFocalPoint(focalRects ...focal) (focalX, focalY float64) {
var sumWeight float64
for _, f := range focalRects {
sumWeight += (f.Right - f.Left) * (f.Bottom - f.Top)
}
for _, f := range focalRects {
r := (f.Right - f.Left) * (f.Bottom - f.Top) / sumWeight
focalX += (f.Left + f.Right) / 2 * r
focalY += (f.Top + f.Bottom) / 2 * r
}
return
}

func trim(ctx context.Context, img *vips.ImageRef, pos string, tolerance int) error {
if IsAnimated(ctx) {
// skip animation support
Expand Down
30 changes: 29 additions & 1 deletion processor/vipsprocessor/vips.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ func (v *VipsProcessor) Shutdown(_ context.Context) error {
return nil
}

func focalSplit(r rune) bool {
return r == 'x' || r == ',' || r == ':'
}

func (v *VipsProcessor) Process(
ctx context.Context, blob *imagor.Bytes, p imagorpath.Params, load imagor.LoadFunc,
) (*imagor.Bytes, error) {
Expand All @@ -129,6 +133,7 @@ func (v *VipsProcessor) Process(
format = vips.ImageTypeUnknown
maxN = v.MaxAnimationFrames
maxBytes int
focalRects []focal
err error
)
ctx = WithInitImageRefs(ctx)
Expand Down Expand Up @@ -173,6 +178,9 @@ func (v *VipsProcessor) Process(
thumbnailNotSupported = true
}
break
case "focal":
thumbnailNotSupported = true
break
case "trim":
thumbnailNotSupported = true
break
Expand Down Expand Up @@ -276,6 +284,8 @@ func (v *VipsProcessor) Process(
var (
quality int
pageN = img.Height() / img.PageHeight()
dw = float64(img.Width())
dh = float64(img.PageHeight())
)
if format == vips.ImageTypeUnknown {
format = img.Format()
Expand All @@ -296,9 +306,27 @@ func (v *VipsProcessor) Process(
case "autojpg":
format = vips.ImageTypeJPEG
break
case "focal":
if args := strings.FieldsFunc(p.Args, focalSplit); len(args) == 4 {
f := focal{}
f.Left, _ = strconv.ParseFloat(args[0], 64)
f.Top, _ = strconv.ParseFloat(args[1], 64)
f.Right, _ = strconv.ParseFloat(args[2], 64)
f.Bottom, _ = strconv.ParseFloat(args[3], 64)
if f.Left < 1 && f.Top < 1 && f.Right <= 1 && f.Bottom <= 1 {
f.Left *= dw
f.Right *= dw
f.Top *= dh
f.Bottom *= dh
}
if f.Right > f.Left && f.Bottom > f.Top {
focalRects = append(focalRects, f)
}
}
break
}
}
if err := v.process(ctx, img, p, load, thumbnail, stretch, upscale); err != nil {
if err := v.process(ctx, img, p, load, thumbnail, stretch, upscale, focalRects); err != nil {
return nil, wrapErr(err)
}
for {
Expand Down
12 changes: 8 additions & 4 deletions processor/vipsprocessor/vips_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ type test struct {
}

func doTests(t *testing.T, resultDir string, tests []test, opts ...Option) {
resStorage := filestorage.New(
resultDir,
filestorage.WithSaveErrIfExists(true),
)
app := imagor.New(
imagor.WithLoaders(filestorage.New(testDataDir)),
imagor.WithUnsafe(true),
imagor.WithDebug(true),
imagor.WithLogger(zap.NewExample()),
imagor.WithProcessors(New(opts...)),
imagor.WithResultStorages(filestorage.New(
resultDir,
filestorage.WithSaveErrIfExists(true),
)),
)
require.NoError(t, app.Startup(context.Background()))
t.Parallel()
Expand All @@ -84,6 +84,7 @@ func doTests(t *testing.T, resultDir string, tests []test, opts ...Option) {
app.ServeHTTP(w, httptest.NewRequest(
http.MethodGet, fmt.Sprintf("/unsafe/%s", tt.path), nil))
assert.Equal(t, 200, w.Code)
_ = resStorage.Put(context.Background(), tt.path, imagor.NewBytes(w.Body.Bytes()))
path := filepath.Join(resultDir, imagorpath.Normalize(tt.path, nil))

buf, err := ioutil.ReadFile(path)
Expand Down Expand Up @@ -112,6 +113,8 @@ func TestVipsProcessor(t *testing.T) {
{"original", "gopher-front.png"},
{"resize center", "100x100/filters:quality(70):format(jpeg)/gopher.png"},
{"resize smart", "100x100/smart/filters:autojpg()/gopher.png"},
{"resize smart focal", "300x100/smart/filters:fill(white):format(jpeg):focal(589x401:1000x814)/gopher.png"},
{"resize smart focal float", "300x100/smart/filters:fill(white):format(jpeg):focal(0.35x0.25:0.6x0.3)/gopher.png"},
{"resize top", "200x100/top/filters:quality(70):format(tiff)/gopher.png"},
{"resize top", "200x100/right/top/gopher.png"},
{"resize bottom", "200x100/bottom/gopher.png"},
Expand Down Expand Up @@ -151,6 +154,7 @@ func TestVipsProcessor(t *testing.T) {
{"original animated", "dancing-banana.gif"},
{"crop animated", "30x20:100x150/dancing-banana.gif"},
{"crop-percent animated", "0.1x0.2:0.89x0.72/dancing-banana.gif"},
{"smart focal animated", "100x30/smart/filters:focal(0.1x0:0.89x0.72)/dancing-banana.gif"},
//{"resize center animated", "100x100/dancing-banana.gif"},
//{"resize top animated", "200x100/top/dancing-banana.gif"},
//{"resize top animated", "200x100/right/top/dancing-banana.gif"},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 3cd8c5d

Please sign in to comment.