Colors: Enforce thumbnail size limit of 3x3 pixels #3976

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2024-01-29 22:31:04 +01:00
parent 97f4d828a4
commit 6ff747c396
6 changed files with 116 additions and 57 deletions

View file

@ -27,6 +27,19 @@ func (m *MediaFile) Colors(thumbPath string) (perception colors.ColorPerception,
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
// Enforce thumbnail width limit and warn if it is exceeded.
if maxWidth := thumb.SizeColors.Width; width > maxWidth {
log.Warnf("color: thumbnail width %d exceeds size limit of %d in %s", width, maxWidth, clean.Log(m.RootRelName()))
width = maxWidth
}
// Enforce thumbnail height limit and warn if it is exceeded.
if maxHeight := thumb.SizeColors.Height; height > maxHeight {
log.Warnf("color: thumbnail height %d exceeds size limit of %d in %s", height, maxHeight, clean.Log(m.RootRelName()))
height = maxHeight
}
pixels := float64(width * height)
chromaSum := 0.0

View file

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/colors"
"github.com/photoprism/photoprism/pkg/fastwalk"
)
@ -101,84 +102,105 @@ func TestMediaFile_Colors_Testdata(t *testing.T) {
}
func TestMediaFile_Colors(t *testing.T) {
conf := config.TestConfig()
c := config.TestConfig()
t.Run("cat_brown.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/cat_brown.jpg"); err == nil {
p, err := mediaFile.Colors(conf.ThumbCachePath())
if mediaFile, err := NewMediaFile(c.ExamplesPath() + "/cat_brown.jpg"); err == nil {
file, fileErr := mediaFile.Colors(c.ThumbCachePath())
t.Log(p, err)
t.Log(file, fileErr)
assert.Nil(t, err)
assert.Equal(t, 13, p.Chroma.Int())
assert.Equal(t, "D", p.Chroma.Hex())
assert.IsType(t, colors.Colors{}, p.Colors)
assert.Equal(t, "gold", p.MainColor.Name())
assert.Equal(t, colors.Colors{0x9, 0x3, 0x2, 0x1, 0x1, 0x2, 0x0, 0x6, 0x1}, p.Colors)
assert.Equal(t, colors.LightMap{0x4, 0x5, 0xb, 0x4, 0x7, 0x3, 0x2, 0x5, 0x7}, p.Luminance)
assert.Nil(t, fileErr)
assert.Equal(t, 13, file.Chroma.Int())
assert.Equal(t, "D", file.Chroma.Hex())
assert.IsType(t, colors.Colors{}, file.Colors)
assert.Equal(t, "gold", file.MainColor.Name())
assert.Equal(t, colors.Colors{0x9, 0x3, 0x2, 0x1, 0x1, 0x2, 0x0, 0x6, 0x1}, file.Colors)
assert.Equal(t, colors.LightMap{0x4, 0x5, 0xb, 0x4, 0x7, 0x3, 0x2, 0x5, 0x7}, file.Luminance)
} else {
t.Error(err)
}
})
t.Run("FernRegular", func(t *testing.T) {
if mediaFile, err := NewMediaFile(c.ExamplesPath() + "/fern_green.jpg"); err == nil {
file, fileErr := mediaFile.Colors(c.ThumbCachePath())
t.Run("fern_green.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/fern_green.jpg"); err == nil {
p, err := mediaFile.Colors(conf.ThumbCachePath())
t.Log(file, fileErr)
t.Log(p, err)
assert.Nil(t, err)
assert.Equal(t, 51, p.Chroma.Int())
assert.Equal(t, "33", p.Chroma.Hex())
assert.IsType(t, colors.Colors{}, p.Colors)
assert.Equal(t, "lime", p.MainColor.Name())
assert.Equal(t, colors.Colors{0xa, 0x9, 0xa, 0x9, 0xa, 0xa, 0x9, 0x9, 0x9}, p.Colors)
assert.Equal(t, colors.LightMap{0xb, 0x4, 0xa, 0x6, 0x9, 0x8, 0x2, 0x3, 0x4}, p.Luminance)
assert.Nil(t, fileErr)
assert.Equal(t, 51, file.Chroma.Int())
assert.Equal(t, "33", file.Chroma.Hex())
assert.IsType(t, colors.Colors{}, file.Colors)
assert.Equal(t, "lime", file.MainColor.Name())
assert.Equal(t, colors.Colors{0xa, 0x9, 0xa, 0x9, 0xa, 0xa, 0x9, 0x9, 0x9}, file.Colors)
assert.Equal(t, colors.LightMap{0xb, 0x4, 0xa, 0x6, 0x9, 0x8, 0x2, 0x3, 0x4}, file.Luminance)
} else {
t.Error(err)
}
})
t.Run("FernLarge", func(t *testing.T) {
if mediaFile, err := NewMediaFile(c.ExamplesPath() + "/fern_green.jpg"); err == nil {
thumbLarge := thumb.SizeColors
thumbLarge.Height = 16
thumbLarge.Width = 16
thumbLarge.Name = "color_large"
thumb.Sizes[thumb.Colors] = thumbLarge
file, fileErr := mediaFile.Colors(c.ThumbCachePath())
thumb.Sizes[thumb.Colors] = thumb.SizeColors
t.Log(file, fileErr)
assert.Nil(t, fileErr)
assert.Equal(t, 67, file.Chroma.Int())
assert.Equal(t, "43", file.Chroma.Hex())
assert.IsType(t, colors.Colors{}, file.Colors)
assert.Equal(t, "lime", file.MainColor.Name())
assert.Equal(t, colors.Colors{9, 10, 10, 10, 10, 10, 10, 10, 10}, file.Colors)
assert.Equal(t, colors.LightMap{5, 9, 9, 9, 9, 10, 10, 9, 11}, file.Luminance)
} else {
t.Error(err)
}
})
t.Run("IMG_4120.JPG", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.JPG"); err == nil {
p, err := mediaFile.Colors(conf.ThumbCachePath())
if mediaFile, err := NewMediaFile(c.ExamplesPath() + "/IMG_4120.JPG"); err == nil {
file, fileErr := mediaFile.Colors(c.ThumbCachePath())
t.Log(p, err)
t.Log(file, fileErr)
assert.Nil(t, err)
assert.Equal(t, 7, p.Chroma.Int())
assert.Equal(t, "7", p.Chroma.Hex())
assert.IsType(t, colors.Colors{}, p.Colors)
assert.Equal(t, "blue", p.MainColor.Name())
assert.Equal(t, colors.Colors{0x1, 0x6, 0x6, 0x1, 0x1, 0x9, 0x1, 0x0, 0x0}, p.Colors)
assert.Nil(t, fileErr)
assert.Equal(t, 7, file.Chroma.Int())
assert.Equal(t, "7", file.Chroma.Hex())
assert.IsType(t, colors.Colors{}, file.Colors)
assert.Equal(t, "blue", file.MainColor.Name())
assert.Equal(t, colors.Colors{0x1, 0x6, 0x6, 0x1, 0x1, 0x9, 0x1, 0x0, 0x0}, file.Colors)
} else {
t.Error(err)
}
})
t.Run("leaves_gold.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/leaves_gold.jpg"); err == nil {
p, err := mediaFile.Colors(conf.ThumbCachePath())
if mediaFile, err := NewMediaFile(c.ExamplesPath() + "/leaves_gold.jpg"); err == nil {
file, fileErr := mediaFile.Colors(c.ThumbCachePath())
t.Log(p, err)
t.Log(file, fileErr)
assert.Nil(t, err)
assert.Equal(t, 16, p.Chroma.Int())
assert.Equal(t, "10", p.Chroma.Hex())
assert.IsType(t, colors.Colors{}, p.Colors)
assert.Equal(t, "gold", p.MainColor.Name())
assert.Nil(t, fileErr)
assert.Equal(t, 16, file.Chroma.Int())
assert.Equal(t, "10", file.Chroma.Hex())
assert.IsType(t, colors.Colors{}, file.Colors)
assert.Equal(t, "gold", file.MainColor.Name())
assert.Equal(t, colors.Colors{0x0, 0x0, 0x2, 0x3, 0x3, 0x0, 0x2, 0x3, 0x0}, p.Colors)
assert.Equal(t, colors.Colors{0x0, 0x0, 0x2, 0x3, 0x3, 0x0, 0x2, 0x3, 0x0}, file.Colors)
} else {
t.Error(err)
}
})
t.Run("Random.docx", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/Random.docx")
p, err := mediaFile.Colors(conf.ThumbCachePath())
assert.Error(t, err, "no color information: not a JPEG file")
file, fileErr := NewMediaFile(c.ExamplesPath() + "/Random.docx")
p, fileErr := file.Colors(c.ThumbCachePath())
assert.Error(t, fileErr, "no color information: not a JPEG file")
t.Log(p)
})
}

View file

@ -37,15 +37,25 @@ func (m SizeMap) All() SizeList {
return result
}
var (
SizeTile50 = Size{Tile50, Tile500, "List View", 50, 50, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}}
SizeTile100 = Size{Tile100, Tile500, "Places View", 100, 100, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}}
SizeTile224 = Size{Tile224, Tile500, "TensorFlow, Mosaic View", 224, 224, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}}
SizeTile500 = Size{Tile500, "", "Cards View", 500, 500, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}}
SizeColors = Size{Colors, Fit720, "Color Detection", 3, 3, false, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}}
SizeLeft224 = Size{Left224, Fit720, "TensorFlow", 224, 224, false, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}}
SizeRight224 = Size{Right224, Fit720, "TensorFlow", 224, 224, false, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}}
)
// Sizes contains the properties of all thumbnail sizes.
var Sizes = SizeMap{
Tile50: {Tile50, Tile500, "List View", 50, 50, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
Tile100: {Tile100, Tile500, "Places View", 100, 100, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
Tile224: {Tile224, Tile500, "TensorFlow, Mosaic View", 224, 224, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
Tile500: {Tile500, "", "Cards View", 500, 500, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
Colors: {Colors, Fit720, "Color Detection", 3, 3, false, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}},
Left224: {Left224, Fit720, "TensorFlow", 224, 224, false, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}},
Right224: {Right224, Fit720, "TensorFlow", 224, 224, false, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}},
Tile50: SizeTile50,
Tile100: SizeTile100,
Tile224: SizeTile224,
Tile500: SizeTile500,
Colors: SizeColors,
Left224: SizeLeft224,
Right224: SizeRight224,
Fit720: {Fit720, "", "SD TV, Mobile", 720, 720, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
Fit1280: {Fit1280, Fit2048, "HD TV, SXGA", 1280, 1024, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
Fit1920: {Fit1920, Fit2048, "Full HD", 1920, 1200, true, true, []ResampleOption{ResampleFit, ResampleDefault}},

View file

@ -118,6 +118,10 @@ func (c Color) ID() int16 {
}
func (c Color) Hex() string {
if c < 0 || c > 15 {
return "0"
}
return fmt.Sprintf("%X", c)
}

View file

@ -19,13 +19,23 @@ func TestColors_List(t *testing.T) {
}
func TestColor_Hex(t *testing.T) {
assert.Equal(t, "0", Color(-1).Hex())
assert.Equal(t, "0", Black.Hex())
assert.Equal(t, "C", Magenta.Hex())
assert.Equal(t, "7", Cyan.Hex())
assert.Equal(t, "F", Pink.Hex())
assert.Equal(t, "F", Color(15).Hex())
assert.Equal(t, "0", Color(16).Hex())
assert.Equal(t, "0", Color(17).Hex())
}
func TestColors_Hex(t *testing.T) {
result := Colors{Orange, Lime, Black}.Hex()
assert.Equal(t, "DA0", result)
t.Run("All", func(t *testing.T) {
assert.Equal(t, "5CFED3BA98762410", All.Hex())
})
t.Run("OrangeLimeBlack", func(t *testing.T) {
assert.Equal(t, "DA0", Colors{Orange, Lime, Black}.Hex())
})
}
func TestColor_ID(t *testing.T) {

View file

@ -1,6 +1,6 @@
package colors
// Information on how an image looks like in terms of colors and light.
// ColorPerception provides information on how an image looks in terms of color and light.
type ColorPerception struct {
Colors Colors
MainColor Color