Color extraction: Add saturation value
This commit is contained in:
parent
5e1210c508
commit
900e8c5e23
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue