Color extraction: Add saturation value

This commit is contained in:
Michael Mayer 2019-04-29 17:12:24 +02:00
parent 5e1210c508
commit 900e8c5e23
5 changed files with 66 additions and 64 deletions

View file

@ -19,7 +19,7 @@ type Photo struct {
PhotoColors string PhotoColors string
PhotoColor string PhotoColor string
PhotoLuminance string PhotoLuminance string
PhotoMonochrome bool PhotoSaturation uint
PhotoCanonicalName string PhotoCanonicalName string
PhotoFavorite bool PhotoFavorite bool
PhotoLat float64 PhotoLat float64

View file

@ -14,6 +14,7 @@ import (
type MaterialColor uint16 type MaterialColor uint16
type MaterialColors []MaterialColor type MaterialColors []MaterialColor
type Saturation uint8
type Luminance uint8 type Luminance uint8
type LightMap []Luminance type LightMap []Luminance
@ -92,6 +93,18 @@ func (c MaterialColors) Hex() (result string) {
return result return result
} }
func (s Saturation) Hex() string {
return fmt.Sprintf("%X", s)
}
func (s Saturation) Uint() uint {
return uint(s)
}
func (s Saturation) Int() int {
return int(s)
}
func (l Luminance) Hex() string { func (l Luminance) Hex() string {
return fmt.Sprintf("%X", l) return fmt.Sprintf("%X", l)
} }
@ -156,18 +169,19 @@ func (m *MediaFile) Resize(width, height int) (result *image.NRGBA, err error) {
} }
// Colors returns color information for a media file. // Colors returns color information for a media file.
func (m *MediaFile) Colors() (colors MaterialColors, mainColor MaterialColor, luminance LightMap, monochrome bool, err error) { func (m *MediaFile) Colors() (colors MaterialColors, mainColor MaterialColor, luminance LightMap, saturation Saturation, err error) {
img, err := m.Resize(ColorSampleSize, ColorSampleSize) img, err := m.Resize(ColorSampleSize, ColorSampleSize)
if err != nil { if err != nil {
log.Printf("can't open image: %s", err.Error()) log.Printf("can't open image: %s", err.Error())
return colors, mainColor, luminance, monochrome, err return colors, mainColor, luminance, saturation, err
} }
bounds := img.Bounds() bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y width, height := bounds.Max.X, bounds.Max.Y
monochrome = true pixels := float64(width * height)
saturationSum := 0.0
colorCount := make(map[MaterialColor]uint16) colorCount := make(map[MaterialColor]uint16)
var mainColorCount uint16 var mainColorCount uint16
@ -192,13 +206,13 @@ func (m *MediaFile) Colors() (colors MaterialColors, mainColor MaterialColor, lu
_, s, l := rgbColor.Hsl() _, s, l := rgbColor.Hsl()
if s != 0 { saturationSum += s
monochrome = false
}
luminance = append(luminance, Luminance(math.Round(l * 16))) luminance = append(luminance, Luminance(math.Round(l * 16)))
} }
} }
return colors, mainColor, luminance, monochrome, nil saturation = Saturation(math.Ceil((saturationSum / pixels) * 16))
return colors, mainColor, luminance, saturation, nil
} }

View file

@ -1,46 +0,0 @@
// +build slow
package photoprism
import (
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/stretchr/testify/assert"
)
func TestMediaFile_GetColors_Slow(t *testing.T) {
conf := test.NewConfig()
conf.InitializeTestData(t)
if mediaFile2, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG"); err == nil {
colors, main, l, m, err := mediaFile2.Colors()
t.Log(colors, main, l, m, err)
assert.Nil(t, err)
assert.False(t, m)
assert.IsType(t, MaterialColors{}, colors)
assert.Equal(t, "grey", main.Name())
assert.Equal(t, MaterialColors{0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2}, colors)
} else {
t.Error(err)
}
if mediaFile3, err := NewMediaFile(conf.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg"); err == nil {
colors, main, l, m, err := mediaFile3.Colors()
t.Log(colors, main, l, m, err)
assert.Nil(t, err)
assert.False(t, m)
assert.IsType(t, MaterialColors{}, colors)
assert.Equal(t, "grey", main.Name())
assert.Equal(t, MaterialColors{0x3, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1}, colors)
} else {
t.Error(err)
}
}

View file

@ -13,12 +13,12 @@ func TestMediaFile_GetColors(t *testing.T) {
conf.InitializeTestData(t) conf.InitializeTestData(t)
if mediaFile1, err := NewMediaFile(conf.ImportPath() + "/dog.jpg"); err == nil { if mediaFile1, err := NewMediaFile(conf.ImportPath() + "/dog.jpg"); err == nil {
colors, main, l, m, err := mediaFile1.Colors() colors, main, l, s, err := mediaFile1.Colors()
t.Log(colors, main, l, m, err) t.Log(colors, main, l, s, err)
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, m) assert.Equal(t, 2, s.Int())
assert.IsType(t, MaterialColors{}, colors) assert.IsType(t, MaterialColors{}, colors)
assert.Equal(t, "grey", main.Name()) assert.Equal(t, "grey", main.Name())
assert.Equal(t, MaterialColors{0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0}, colors) assert.Equal(t, MaterialColors{0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0}, colors)
@ -28,12 +28,12 @@ func TestMediaFile_GetColors(t *testing.T) {
} }
if mediaFile2, err := NewMediaFile(conf.ImportPath() + "/ape.jpeg"); err == nil { if mediaFile2, err := NewMediaFile(conf.ImportPath() + "/ape.jpeg"); err == nil {
colors, main, l, m, err := mediaFile2.Colors() colors, main, l, s, err := mediaFile2.Colors()
t.Log(colors, main, l, m, err) t.Log(colors, main, l, s, err)
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, m) assert.Equal(t, 3, s.Int())
assert.IsType(t, MaterialColors{}, colors) assert.IsType(t, MaterialColors{}, colors)
assert.Equal(t, "teal", main.Name()) assert.Equal(t, "teal", main.Name())
assert.Equal(t, MaterialColors{0x8, 0x8, 0x2, 0x8, 0x2, 0x1, 0x8, 0x1, 0x2}, colors) assert.Equal(t, MaterialColors{0x8, 0x8, 0x2, 0x8, 0x2, 0x1, 0x8, 0x1, 0x2}, colors)
@ -41,4 +41,38 @@ func TestMediaFile_GetColors(t *testing.T) {
} else { } else {
t.Error(err) t.Error(err)
} }
if testing.Short() {
return
}
if mediaFile2, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG"); err == nil {
colors, main, l, s, err := mediaFile2.Colors()
t.Log(colors, main, l, s, err)
assert.Nil(t, err)
assert.Equal(t, 3, s.Int())
assert.IsType(t, MaterialColors{}, colors)
assert.Equal(t, "grey", main.Name())
assert.Equal(t, MaterialColors{0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2}, colors)
} else {
t.Error(err)
}
if mediaFile3, err := NewMediaFile(conf.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg"); err == nil {
colors, main, l, s, err := mediaFile3.Colors()
t.Log(colors, main, l, s, err)
assert.Nil(t, err)
assert.Equal(t, 2, s.Int())
assert.IsType(t, MaterialColors{}, colors)
assert.Equal(t, "grey", main.Name())
assert.Equal(t, MaterialColors{0x3, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1}, colors)
} else {
t.Error(err)
}
} }

View file

@ -94,12 +94,12 @@ func (i *Indexer) indexMediaFile(mediaFile *MediaFile) string {
} }
// PhotoColors // PhotoColors
photoColors, photoColor, luminance, monochrome, _ := jpeg.Colors() photoColors, photoColor, luminance, saturation, _ := jpeg.Colors()
photo.PhotoColor = photoColor.Name() photo.PhotoColor = photoColor.Name()
photo.PhotoColors = photoColors.Hex() photo.PhotoColors = photoColors.Hex()
photo.PhotoLuminance = luminance.Hex() photo.PhotoLuminance = luminance.Hex()
photo.PhotoMonochrome = monochrome photo.PhotoSaturation = saturation.Uint()
// Tags (TensorFlow) // Tags (TensorFlow)
tags = i.getImageTags(jpeg) tags = i.getImageTags(jpeg)
@ -165,12 +165,12 @@ func (i *Indexer) indexMediaFile(mediaFile *MediaFile) string {
} else if time.Now().Sub(photo.UpdatedAt).Minutes() > 10 { // If updated more than 10 minutes ago } else if time.Now().Sub(photo.UpdatedAt).Minutes() > 10 { // If updated more than 10 minutes ago
if jpeg, err := mediaFile.GetJpeg(); err == nil { if jpeg, err := mediaFile.GetJpeg(); err == nil {
// PhotoColors // PhotoColors
photoColors, photoColor, luminance, monochrome, _ := jpeg.Colors() photoColors, photoColor, luminance, saturation, _ := jpeg.Colors()
photo.PhotoColor = photoColor.Name() photo.PhotoColor = photoColor.Name()
photo.PhotoColors = photoColors.Hex() photo.PhotoColors = photoColors.Hex()
photo.PhotoLuminance = luminance.Hex() photo.PhotoLuminance = luminance.Hex()
photo.PhotoMonochrome = monochrome photo.PhotoSaturation = saturation.Uint()
photo.Camera = models.NewCamera(mediaFile.GetCameraModel(), mediaFile.GetCameraMake()).FirstOrCreate(i.db) photo.Camera = models.NewCamera(mediaFile.GetCameraModel(), mediaFile.GetCameraMake()).FirstOrCreate(i.db)
photo.Lens = models.NewLens(mediaFile.GetLensModel(), mediaFile.GetLensMake()).FirstOrCreate(i.db) photo.Lens = models.NewLens(mediaFile.GetLensModel(), mediaFile.GetLensMake()).FirstOrCreate(i.db)