implement a resolution handler

This commit is contained in:
David Christofas
2020-03-21 18:50:18 +01:00
parent e0ab0b37a5
commit 43bf107207
4 changed files with 212 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
package resolution
import (
"fmt"
"strconv"
"strings"
)
// Parse parses a resolution string in the form <width>x<height> and returns a resolution instance.
func Parse(s string) (Resolution, error) {
parts := strings.Split(s, "x")
if len(parts) != 2 {
return Resolution{}, fmt.Errorf("failed to parse resolution: %s. Expected format <width>x<height>", s)
}
width, err := strconv.Atoi(parts[0])
if err != nil {
return Resolution{}, fmt.Errorf("width: %s has an invalid value. Expected an integer", parts[0])
}
height, err := strconv.Atoi(parts[1])
if err != nil {
return Resolution{}, fmt.Errorf("height: %s has an invalid value. Expected an integer", parts[1])
}
return Resolution{Width: width, Height: height}, nil
}
// Resolution defines represents the width and height of a thumbnail.
type Resolution struct {
Width int
Height int
}
// String returns the resolution in the format:
//
// <width>x<height>
func (r Resolution) String() string {
return strconv.Itoa(r.Width) + "x" + strconv.Itoa(r.Height)
}
@@ -0,0 +1,26 @@
package resolution
import "testing"
func TestParseWithEmptyString(t *testing.T) {
_, err := Parse("")
if err == nil {
t.Error("Parse with empty string should return an error.")
}
}
func TestParse(t *testing.T) {
rStr := "42x23"
r, _ := Parse(rStr)
if r.Width != 42 || r.Height != 23 {
t.Errorf("Expected resolution %s got %s", rStr, r.String())
}
}
func TestString(t *testing.T) {
r := Resolution{Width: 42, Height: 23}
expected := "42x23"
if r.String() != expected {
t.Errorf("Expected string %s got %s", expected, r.String())
}
}
+58
View File
@@ -0,0 +1,58 @@
package resolution
import (
"fmt"
"math"
)
// Init creates an instance of Resolutions from resolution strings.
func Init(rStrs []string) (Resolutions, error) {
var rs Resolutions
for _, rStr := range rStrs {
r, err := Parse(rStr)
if err != nil {
return nil, fmt.Errorf("failed to initialize resolutions: %s", err.Error())
}
rs = append(rs, r)
}
return rs, nil
}
// Resolutions represents the available thumbnail resolutions.
type Resolutions []Resolution
// ClosestMatch returns the resolution which is closest to the provided resolution.
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))
// Initialize with the first resolution
match := r[0]
matchLen := dimensionLength(match, isLandscape)
minDiff := math.Abs(givenLen - float64(matchLen))
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
continue
}
}
return match
}
func dimensionLength(r Resolution, landscape bool) int {
if landscape {
return r.Width
}
return r.Height
}
@@ -0,0 +1,91 @@
package resolution
import (
"testing"
)
func TestInitWithEmptyArray(t *testing.T) {
rs, err := Init([]string{})
if err != nil {
t.Errorf("Init with an empty array should not fail. Error: %s.\n", err.Error())
}
if len(rs) != 0 {
t.Error("Init with an empty array should return an empty Resolutions instance.\n")
}
}
func TestInitWithNil(t *testing.T) {
rs, err := Init(nil)
if err != nil {
t.Errorf("Init with nil parameter should not fail. Error: %s.\n", err.Error())
}
if len(rs) != 0 {
t.Error("Init with nil parameter should return an empty Resolutions instance.\n")
}
}
func TestInitWithInvalidValuesInArray(t *testing.T) {
_, err := Init([]string{"invalid"})
if err == nil {
t.Error("Init with invalid parameter should fail.\n")
}
}
func TestInit(t *testing.T) {
rs, err := Init([]string{"16x16"})
if err != nil {
t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error())
}
if len(rs) != 1 {
t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), 1)
}
}
func TestInitWithMultipleResolutions(t *testing.T) {
rStrs := []string{"16x16", "32x32", "64x64", "128x128"}
rs, err := Init(rStrs)
if err != nil {
t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error())
}
if len(rs) != len(rStrs) {
t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), len(rStrs))
}
}
func TestClosestMatchWithEmptyResolutions(t *testing.T) {
rs, _ := Init(nil)
width := 24
height := 24
r := rs.ClosestMatch(width, height)
if r.Width != width || r.Height != height {
t.Errorf("ClosestMatch from empty resolutions should return the given resolution")
}
}
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{24, 24, 24, 24},
[]int{20, 20, 24, 24},
[]int{20, 80, 64, 64},
[]int{80, 20, 64, 64},
[]int{48, 48, 64, 64},
[]int{1024, 1024, 128, 128},
}
for _, row := range table {
width := row[0]
height := row[1]
expectedWidth := row[2]
expectedHeight := row[3]
match := rs.ClosestMatch(width, height)
if match.Width != expectedWidth || match.Height != expectedHeight {
t.Errorf("Expected resolution %dx%d got %s", expectedWidth, expectedHeight, match.String())
}
}
}