use predefined resolutions for thumbnail generation
This commit is contained in:
@@ -9,6 +9,20 @@ func TestParseWithEmptyString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWithInvalidWidth(t *testing.T) {
|
||||
_, err := Parse("invalidx42")
|
||||
if err == nil {
|
||||
t.Error("Parse with invalid width should return an error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWithInvalidHeight(t *testing.T) {
|
||||
_, err := Parse("42xinvalid")
|
||||
if err == nil {
|
||||
t.Error("Parse with invalid height should return an error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
rStr := "42x23"
|
||||
r, _ := Parse(rStr)
|
||||
|
||||
@@ -3,6 +3,7 @@ package resolution
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Init creates an instance of Resolutions from resolution strings.
|
||||
@@ -15,6 +16,16 @@ func Init(rStrs []string) (Resolutions, error) {
|
||||
}
|
||||
rs = append(rs, r)
|
||||
}
|
||||
sort.Slice(rs, func(i, j int) bool {
|
||||
left := rs[i]
|
||||
right := rs[j]
|
||||
|
||||
leftSize := left.Width * left.Height
|
||||
rightSize := right.Width * right.Height
|
||||
|
||||
return leftSize < rightSize
|
||||
})
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
@@ -22,31 +33,37 @@ func Init(rStrs []string) (Resolutions, error) {
|
||||
type Resolutions []Resolution
|
||||
|
||||
// ClosestMatch returns the resolution which is closest to the provided resolution.
|
||||
// If there is no exact match the resolution will be the next higher one.
|
||||
// If the given resolution is bigger than all available resolutions the biggest available one is used.
|
||||
func (r Resolutions) ClosestMatch(width, height int) Resolution {
|
||||
if len(r) == 0 {
|
||||
return Resolution{Width: width, Height: height}
|
||||
}
|
||||
|
||||
isLandscape := width > height
|
||||
givenLen := math.Max(float64(width), float64(height))
|
||||
givenLen := int(math.Max(float64(width), float64(height)))
|
||||
|
||||
// Initialize with the first resolution
|
||||
match := r[0]
|
||||
matchLen := dimensionLength(match, isLandscape)
|
||||
minDiff := math.Abs(givenLen - float64(matchLen))
|
||||
var match Resolution
|
||||
minDiff := math.MaxInt32
|
||||
|
||||
for i := 1; i < len(r); i++ {
|
||||
r := r[i]
|
||||
rLen := dimensionLength(r, isLandscape)
|
||||
diff := math.Abs(givenLen - float64(rLen))
|
||||
|
||||
if diff <= minDiff {
|
||||
minDiff = diff
|
||||
match = r
|
||||
current := r[i]
|
||||
len := dimensionLength(current, isLandscape)
|
||||
diff := givenLen - len
|
||||
if diff > 0 {
|
||||
continue
|
||||
}
|
||||
absDiff := int(math.Abs(float64(diff)))
|
||||
if absDiff < minDiff {
|
||||
minDiff = absDiff
|
||||
match = current
|
||||
}
|
||||
}
|
||||
|
||||
if match == (Resolution{}) {
|
||||
match = r[len(r)-1]
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,25 @@ func TestInitWithMultipleResolutions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitWithMultipleResolutionsShouldBeSorted(t *testing.T) {
|
||||
rStrs := []string{"32x32", "64x64", "16x16", "128x128"}
|
||||
rs, err := Init(rStrs)
|
||||
if err != nil {
|
||||
t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error())
|
||||
}
|
||||
|
||||
for i := 0; i < len(rs)-1; i++ {
|
||||
current := rs[i]
|
||||
currentSize := current.Width * current.Height
|
||||
next := rs[i]
|
||||
nextSize := next.Width * next.Height
|
||||
|
||||
if currentSize > nextSize {
|
||||
t.Error("Resolutions are not sorted.")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
func TestClosestMatchWithEmptyResolutions(t *testing.T) {
|
||||
rs, _ := Init(nil)
|
||||
width := 24
|
||||
@@ -66,12 +85,12 @@ func TestClosestMatchWithEmptyResolutions(t *testing.T) {
|
||||
func TestClosestMatch(t *testing.T) {
|
||||
rs, _ := Init([]string{"16x16", "24x24", "32x32", "64x64", "128x128"})
|
||||
table := [][]int{
|
||||
[]int{17, 17, 16, 16},
|
||||
[]int{12, 17, 16, 16},
|
||||
[]int{17, 17, 24, 24},
|
||||
[]int{12, 17, 24, 24},
|
||||
[]int{24, 24, 24, 24},
|
||||
[]int{20, 20, 24, 24},
|
||||
[]int{20, 80, 64, 64},
|
||||
[]int{80, 20, 64, 64},
|
||||
[]int{20, 80, 128, 128},
|
||||
[]int{80, 20, 128, 128},
|
||||
[]int{48, 48, 64, 64},
|
||||
[]int{1024, 1024, 128, 128},
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/owncloud/ocis-pkg/v2/log"
|
||||
"github.com/owncloud/ocis-thumbnails/pkg/config"
|
||||
@@ -68,7 +67,7 @@ func (s FileSystem) Set(key string, img []byte) error {
|
||||
func (s FileSystem) BuildKey(ctx Context) string {
|
||||
etag := ctx.ETag
|
||||
filetype := ctx.Types[0]
|
||||
filename := strconv.Itoa(ctx.Width) + "x" + strconv.Itoa(ctx.Height) + "." + filetype
|
||||
filename := ctx.Resolution.String() + "." + filetype
|
||||
|
||||
key := new(bytes.Buffer)
|
||||
key.WriteString(etag[:2])
|
||||
|
||||
@@ -32,7 +32,7 @@ func (s InMemory) Set(key string, thumbnail []byte) error {
|
||||
func (s InMemory) BuildKey(ctx Context) string {
|
||||
parts := []string{
|
||||
ctx.ETag,
|
||||
string(ctx.Width) + "x" + string(ctx.Height),
|
||||
ctx.Resolution.String(),
|
||||
strings.Join(ctx.Types, ","),
|
||||
}
|
||||
return strings.Join(parts, "+")
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package storage
|
||||
|
||||
import "github.com/owncloud/ocis-thumbnails/pkg/thumbnails/resolution"
|
||||
|
||||
// Context combines different attributes needed for storage operations.
|
||||
type Context struct {
|
||||
ETag string
|
||||
Types []string
|
||||
Width int
|
||||
Height int
|
||||
ETag string
|
||||
Types []string
|
||||
Resolution resolution.Resolution
|
||||
}
|
||||
|
||||
// Storage defines the interface for a thumbnail store.
|
||||
|
||||
@@ -6,16 +6,16 @@ import (
|
||||
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/owncloud/ocis-pkg/v2/log"
|
||||
"github.com/owncloud/ocis-thumbnails/pkg/thumbnails/resolution"
|
||||
"github.com/owncloud/ocis-thumbnails/pkg/thumbnails/storage"
|
||||
)
|
||||
|
||||
// Context bundles information needed to generate a thumbnail for afile
|
||||
type Context struct {
|
||||
Width int
|
||||
Height int
|
||||
ImagePath string
|
||||
Encoder Encoder
|
||||
ETag string
|
||||
Resolution resolution.Resolution
|
||||
ImagePath string
|
||||
Encoder Encoder
|
||||
ETag string
|
||||
}
|
||||
|
||||
// Manager is responsible for generating thumbnails
|
||||
@@ -69,16 +69,15 @@ func (s SimpleManager) GetStored(ctx Context) []byte {
|
||||
}
|
||||
|
||||
func (s SimpleManager) generate(ctx Context, img image.Image) image.Image {
|
||||
thumbnail := resize.Thumbnail(uint(ctx.Width), uint(ctx.Height), img, resize.Lanczos2)
|
||||
thumbnail := resize.Thumbnail(uint(ctx.Resolution.Width), uint(ctx.Resolution.Height), img, resize.Lanczos2)
|
||||
return thumbnail
|
||||
}
|
||||
|
||||
func mapToStorageContext(ctx Context) storage.Context {
|
||||
sCtx := storage.Context{
|
||||
ETag: ctx.ETag,
|
||||
Width: ctx.Width,
|
||||
Height: ctx.Height,
|
||||
Types: ctx.Encoder.Types(),
|
||||
ETag: ctx.ETag,
|
||||
Resolution: ctx.Resolution,
|
||||
Types: ctx.Encoder.Types(),
|
||||
}
|
||||
return sCtx
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user