mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
Thumb: Skip left_224 and right_224 if the original is square #1474
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
107cb2ef93
commit
a54a46b082
9 changed files with 139 additions and 50 deletions
|
|
@ -15,7 +15,7 @@ func (ind *Index) Labels(jpeg *MediaFile) (results classify.Labels) {
|
|||
|
||||
var sizes []thumb.Name
|
||||
|
||||
if jpeg.AspectRatio() == 1 {
|
||||
if jpeg.Square() {
|
||||
sizes = []thumb.Name{thumb.Tile224}
|
||||
} else {
|
||||
sizes = []thumb.Name{thumb.Tile224, thumb.Left224, thumb.Right224}
|
||||
|
|
|
|||
|
|
@ -1261,6 +1261,18 @@ func (m *MediaFile) AspectRatio() float32 {
|
|||
return aspectRatio
|
||||
}
|
||||
|
||||
// Square checks if the width and height of this media file are the same.
|
||||
func (m *MediaFile) Square() bool {
|
||||
width := m.Width()
|
||||
height := m.Height()
|
||||
|
||||
if width <= 0 || height <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return width == height
|
||||
}
|
||||
|
||||
// Portrait tests if the image is a portrait.
|
||||
func (m *MediaFile) Portrait() bool {
|
||||
return m.Width() < m.Height()
|
||||
|
|
|
|||
|
|
@ -2032,6 +2032,7 @@ func TestMediaFile_AspectRatio(t *testing.T) {
|
|||
|
||||
ratio := mediaFile.AspectRatio()
|
||||
assert.Equal(t, float32(0.75), ratio)
|
||||
assert.False(t, mediaFile.Square())
|
||||
})
|
||||
t.Run("fern_green.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
|
@ -2044,6 +2045,7 @@ func TestMediaFile_AspectRatio(t *testing.T) {
|
|||
|
||||
ratio := mediaFile.AspectRatio()
|
||||
assert.Equal(t, float32(1), ratio)
|
||||
assert.True(t, mediaFile.Square())
|
||||
})
|
||||
t.Run("elephants.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
|
@ -2056,6 +2058,7 @@ func TestMediaFile_AspectRatio(t *testing.T) {
|
|||
|
||||
ratio := mediaFile.AspectRatio()
|
||||
assert.Equal(t, float32(1.5), ratio)
|
||||
assert.False(t, mediaFile.Square())
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@ func (n Name) String() string {
|
|||
|
||||
// Names of thumbnail sizes.
|
||||
const (
|
||||
Colors Name = "colors"
|
||||
Tile50 Name = "tile_50"
|
||||
Tile100 Name = "tile_100"
|
||||
Tile224 Name = "tile_224"
|
||||
Tile500 Name = "tile_500"
|
||||
Tile1080 Name = "tile_1080"
|
||||
Colors Name = "colors"
|
||||
Left224 Name = "left_224"
|
||||
Right224 Name = "right_224"
|
||||
Tile224 Name = "tile_224"
|
||||
Fit720 Name = "fit_720"
|
||||
Tile500 Name = "tile_500"
|
||||
Tile1080 Name = "tile_1080"
|
||||
Fit1280 Name = "fit_1280"
|
||||
Fit1600 Name = "fit_1600"
|
||||
Fit1920 Name = "fit_1920"
|
||||
|
|
@ -45,12 +45,12 @@ var Names = []Name{
|
|||
Fit1280,
|
||||
Tile500,
|
||||
Fit720,
|
||||
Tile224,
|
||||
Right224,
|
||||
Left224,
|
||||
Colors,
|
||||
Tile224,
|
||||
Tile100,
|
||||
Tile50,
|
||||
Colors,
|
||||
}
|
||||
|
||||
// Find returns the largest default thumbnail type for the given size limit.
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
type ResampleOption int
|
||||
|
||||
const (
|
||||
ResampleFillCenter ResampleOption = iota
|
||||
ResampleFillTopLeft
|
||||
|
|
@ -25,6 +23,23 @@ var ResampleMethods = map[ResampleOption]string{
|
|||
ResampleResize: "resize",
|
||||
}
|
||||
|
||||
// ResampleOption represents a thumbnail rendering option.
|
||||
type ResampleOption int
|
||||
|
||||
// Options represents a list of thumbnail rendering options.
|
||||
type Options []ResampleOption
|
||||
|
||||
// Contains checks if the specified option is set.
|
||||
func (o Options) Contains(option ResampleOption) bool {
|
||||
for _, v := range o {
|
||||
if v == option {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ResampleOptions extracts filter, format, and method from resample options.
|
||||
func ResampleOptions(opts ...ResampleOption) (method ResampleOption, filter ResampleFilter, format fs.Type) {
|
||||
method = ResampleFit
|
||||
|
|
|
|||
40
internal/thumb/options_test.go
Normal file
40
internal/thumb/options_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptions_Contains(t *testing.T) {
|
||||
t.Run("Left224", func(t *testing.T) {
|
||||
options := SizeLeft224.Options
|
||||
assert.True(t, options.Contains(ResampleFillTopLeft))
|
||||
assert.False(t, options.Contains(ResampleFillBottomRight))
|
||||
assert.False(t, options.Contains(ResampleFillCenter))
|
||||
})
|
||||
t.Run("Right224", func(t *testing.T) {
|
||||
options := SizeRight224.Options
|
||||
assert.False(t, options.Contains(ResampleFillTopLeft))
|
||||
assert.True(t, options.Contains(ResampleFillBottomRight))
|
||||
assert.False(t, options.Contains(ResampleFillCenter))
|
||||
})
|
||||
t.Run("Tile224", func(t *testing.T) {
|
||||
options := SizeTile224.Options
|
||||
assert.False(t, options.Contains(ResampleFillTopLeft))
|
||||
assert.False(t, options.Contains(ResampleFillBottomRight))
|
||||
assert.True(t, options.Contains(ResampleFillCenter))
|
||||
})
|
||||
t.Run("Tile500", func(t *testing.T) {
|
||||
options := SizeTile500.Options
|
||||
assert.False(t, options.Contains(ResampleFillTopLeft))
|
||||
assert.False(t, options.Contains(ResampleFillBottomRight))
|
||||
assert.True(t, options.Contains(ResampleFillCenter))
|
||||
})
|
||||
t.Run("Fit1600", func(t *testing.T) {
|
||||
options := SizeFit1600.Options
|
||||
assert.False(t, options.Contains(ResampleFillTopLeft))
|
||||
assert.False(t, options.Contains(ResampleFillBottomRight))
|
||||
assert.False(t, options.Contains(ResampleFillCenter))
|
||||
})
|
||||
}
|
||||
|
|
@ -6,16 +6,16 @@ import (
|
|||
|
||||
// Size represents a standard media resolution.
|
||||
type Size struct {
|
||||
Name Name `json:"name"` // Name of the thumbnail size.
|
||||
Source Name `json:"-"` // Larger size this size can be generated from.
|
||||
Usage string `json:"usage"` // Usage example.
|
||||
Width int `json:"w"` // Width in pixels.
|
||||
Height int `json:"h"` // Height in pixels.
|
||||
Public bool `json:"-"` // Size is visible in client applications.
|
||||
Fit bool `json:"-"` // Image is fitted to fill this size.
|
||||
Optional bool `json:"-"` // Size must not be generated by default.
|
||||
Required bool `json:"-"` // Size must always be generated.
|
||||
Options []ResampleOption `json:"-"`
|
||||
Name Name `json:"name"` // Name of the thumbnail size.
|
||||
Source Name `json:"-"` // Larger size this size can be generated from.
|
||||
Usage string `json:"usage"` // Usage example.
|
||||
Width int `json:"w"` // Width in pixels.
|
||||
Height int `json:"h"` // Height in pixels.
|
||||
Public bool `json:"-"` // Size is visible in client applications.
|
||||
Fit bool `json:"-"` // Image is fitted to fill this size.
|
||||
Optional bool `json:"-"` // Size must not be generated by default.
|
||||
Required bool `json:"-"` // Size must always be generated.
|
||||
Options Options `json:"-"`
|
||||
}
|
||||
|
||||
// Bounds returns the thumb size as image.Rectangle.
|
||||
|
|
@ -65,17 +65,30 @@ func (s Size) Skip(img image.Image) bool {
|
|||
|
||||
// Skip tests if the size can be skipped when generating thumbnails, e.g. because it is larger than the original.
|
||||
func Skip(s Size, bounds image.Rectangle) bool {
|
||||
// Always return false if this thumbnail size is always required.
|
||||
if s.Required {
|
||||
// This thumbnail size is always required.
|
||||
return false
|
||||
} else if s.Optional {
|
||||
// Size can be omitted by default, e.g. because it is deprecated or uncommon.
|
||||
}
|
||||
|
||||
// Optional sizes can be skipped by default.
|
||||
if s.Optional {
|
||||
return true
|
||||
} else if !s.Fit || !bounds.In(s.Bounds()) {
|
||||
// Image is within the bounds of this thumbnail size or is fitted to it.
|
||||
}
|
||||
|
||||
// Skip square thumbnails that show a crop on the left or right if the image is square as well.
|
||||
if bounds.Max.X == bounds.Max.Y && s.Width == s.Height {
|
||||
if s.Options.Contains(ResampleFillTopLeft) || s.Options.Contains(ResampleFillBottomRight) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check if image is within the bounds of this thumbnail size or is fitted to it.
|
||||
if !s.Fit || !bounds.In(s.Bounds()) {
|
||||
return false
|
||||
} else if newSize := FitBounds(bounds); newSize.Width < s.Width {
|
||||
// Image is smaller than this thumbnail size.
|
||||
}
|
||||
|
||||
// Skip if the image is smaller than this thumbnail size.
|
||||
if newSize := FitBounds(bounds); newSize.Width < s.Width {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ import (
|
|||
)
|
||||
|
||||
func TestSkip(t *testing.T) {
|
||||
t.Run("Tile224", func(t *testing.T) {
|
||||
bounds := image.Rectangle{Min: image.Point{}, Max: image.Point{X: 1024, Y: 1024}}
|
||||
assert.False(t, Skip(SizeTile224, bounds))
|
||||
assert.True(t, Skip(SizeLeft224, bounds))
|
||||
assert.True(t, Skip(SizeRight224, bounds))
|
||||
})
|
||||
t.Run("Tile500", func(t *testing.T) {
|
||||
bounds := image.Rectangle{Min: image.Point{}, Max: image.Point{X: 1024, Y: 1024}}
|
||||
assert.False(t, Skip(SizeTile500, bounds))
|
||||
|
|
|
|||
|
|
@ -37,42 +37,42 @@ func (m SizeMap) All() SizeList {
|
|||
}
|
||||
|
||||
var (
|
||||
SizeTile50 = Size{Tile50, Fit720, "List View", 50, 50, false, false, false, true, []ResampleOption{ResampleFillCenter, ResampleDefault}}
|
||||
SizeTile100 = Size{Tile100, Fit720, "Places View", 100, 100, false, false, false, true, []ResampleOption{ResampleFillCenter, ResampleDefault}}
|
||||
SizeTile224 = Size{Tile224, Fit720, "TensorFlow, Mosaic View", 224, 224, false, false, false, true, []ResampleOption{ResampleFillCenter, ResampleDefault}}
|
||||
SizeColors = Size{Colors, Fit720, "Color Detection", 3, 3, false, false, false, true, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}}
|
||||
SizeLeft224 = Size{Left224, Fit720, "TensorFlow", 224, 224, false, false, false, true, []ResampleOption{ResampleFillTopLeft, ResampleDefault}}
|
||||
SizeRight224 = Size{Right224, Fit720, "TensorFlow", 224, 224, false, false, false, true, []ResampleOption{ResampleFillBottomRight, ResampleDefault}}
|
||||
SizeFit720 = Size{Fit720, "", "SD TV, Mobile", 720, 720, true, true, false, true, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeTile500 = Size{Tile500, Fit1920, "Cards View", 500, 500, false, false, false, true, []ResampleOption{ResampleFillCenter, ResampleDefault}}
|
||||
SizeFit1280 = Size{Fit1280, Fit1920, "HD TV, SXGA", 1280, 1024, true, true, false, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeFit1600 = Size{Fit1600, Fit1920, "Social Sharing", 1600, 900, false, true, true, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeFit1920 = Size{Fit1920, "", "Full HD", 1920, 1200, true, true, false, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeTile1080 = Size{Tile1080, Fit4096, "Instagram", 1080, 1080, false, false, true, false, []ResampleOption{ResampleFillCenter, ResampleDefault}}
|
||||
SizeFit2048 = Size{Fit2048, Fit4096, "DCI 2K, Tablets", 2048, 2048, false, true, true, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeFit2560 = Size{Fit2560, Fit4096, "Quad HD, Notebooks", 2560, 1600, true, true, false, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeFit3840 = Size{Fit3840, Fit4096, "4K Ultra HD", 3840, 2400, false, true, true, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeFit4096 = Size{Fit4096, "", "DCI 4K, Retina 4K", 4096, 4096, true, true, false, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeFit7680 = Size{Fit7680, "", "8K Ultra HD 2", 7680, 4320, true, true, false, false, []ResampleOption{ResampleFit, ResampleDefault}}
|
||||
SizeColors = Size{Colors, Fit720, "Color Detection", 3, 3, false, false, false, true, Options{ResampleResize, ResampleNearestNeighbor, ResamplePng}}
|
||||
SizeTile50 = Size{Tile50, Fit720, "List View", 50, 50, false, false, false, true, Options{ResampleFillCenter, ResampleDefault}}
|
||||
SizeTile100 = Size{Tile100, Fit720, "Places View", 100, 100, false, false, false, true, Options{ResampleFillCenter, ResampleDefault}}
|
||||
SizeTile224 = Size{Tile224, Fit720, "TensorFlow, Mosaic View", 224, 224, false, false, false, true, Options{ResampleFillCenter, ResampleDefault}}
|
||||
SizeLeft224 = Size{Left224, Fit720, "TensorFlow", 224, 224, false, false, false, false, Options{ResampleFillTopLeft, ResampleDefault}}
|
||||
SizeRight224 = Size{Right224, Fit720, "TensorFlow", 224, 224, false, false, false, false, Options{ResampleFillBottomRight, ResampleDefault}}
|
||||
SizeFit720 = Size{Fit720, "", "SD TV, Mobile", 720, 720, true, true, false, true, Options{ResampleFit, ResampleDefault}}
|
||||
SizeTile500 = Size{Tile500, Fit1920, "Cards View", 500, 500, false, false, false, true, Options{ResampleFillCenter, ResampleDefault}}
|
||||
SizeTile1080 = Size{Tile1080, Fit1920, "Instagram", 1080, 1080, false, false, true, false, Options{ResampleFillCenter, ResampleDefault}}
|
||||
SizeFit1280 = Size{Fit1280, Fit1920, "HD TV, SXGA", 1280, 1024, true, true, false, false, Options{ResampleFit, ResampleDefault}}
|
||||
SizeFit1600 = Size{Fit1600, Fit1920, "Social Media", 1600, 900, false, true, true, false, Options{ResampleFit, ResampleDefault}}
|
||||
SizeFit1920 = Size{Fit1920, "", "Full HD", 1920, 1200, true, true, false, false, Options{ResampleFit, ResampleDefault}}
|
||||
SizeFit2048 = Size{Fit2048, Fit4096, "DCI 2K, Tablets", 2048, 2048, false, true, true, false, Options{ResampleFit, ResampleDefault}}
|
||||
SizeFit2560 = Size{Fit2560, Fit4096, "Quad HD, Notebooks", 2560, 1600, true, true, false, false, Options{ResampleFit, ResampleDefault}}
|
||||
SizeFit3840 = Size{Fit3840, Fit4096, "4K Ultra HD", 3840, 2400, false, true, true, false, Options{ResampleFit, ResampleDefault}}
|
||||
SizeFit4096 = Size{Fit4096, "", "DCI 4K, Retina 4K", 4096, 4096, true, true, false, false, Options{ResampleFit, ResampleDefault}}
|
||||
SizeFit7680 = Size{Fit7680, "", "8K Ultra HD 2", 7680, 4320, true, true, false, false, Options{ResampleFit, ResampleDefault}}
|
||||
)
|
||||
|
||||
// Sizes contains the properties of all thumbnail sizes.
|
||||
var Sizes = SizeMap{
|
||||
Colors: SizeColors,
|
||||
Tile50: SizeTile50,
|
||||
Tile100: SizeTile100,
|
||||
Tile224: SizeTile224,
|
||||
Tile500: SizeTile500,
|
||||
Tile1080: SizeTile1080,
|
||||
Colors: SizeColors,
|
||||
Left224: SizeLeft224,
|
||||
Right224: SizeRight224,
|
||||
Tile224: SizeTile224,
|
||||
Fit720: SizeFit720,
|
||||
Tile500: SizeTile500,
|
||||
Tile1080: SizeTile1080, // Optional
|
||||
Fit1280: SizeFit1280,
|
||||
Fit1600: SizeFit1600,
|
||||
Fit1600: SizeFit1600, // Optional
|
||||
Fit1920: SizeFit1920,
|
||||
Fit2048: SizeFit2048,
|
||||
Fit2048: SizeFit2048, // Deprecated in favor of Fit1920
|
||||
Fit2560: SizeFit2560,
|
||||
Fit3840: SizeFit3840, // Deprecated in favor of fit_4096
|
||||
Fit3840: SizeFit3840, // Deprecated in favor of Fit4096
|
||||
Fit4096: SizeFit4096,
|
||||
Fit7680: SizeFit7680,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue