Colors: Enforce thumbnail size limit of 3x3 pixels #3976
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
97f4d828a4
commit
6ff747c396
6 changed files with 116 additions and 57 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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}},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue