Color extraction: Add saturation value
This commit is contained in:
parent
5e1210c508
commit
900e8c5e23
5 changed files with 66 additions and 64 deletions
|
@ -19,7 +19,7 @@ type Photo struct {
|
|||
PhotoColors string
|
||||
PhotoColor string
|
||||
PhotoLuminance string
|
||||
PhotoMonochrome bool
|
||||
PhotoSaturation uint
|
||||
PhotoCanonicalName string
|
||||
PhotoFavorite bool
|
||||
PhotoLat float64
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
type MaterialColor uint16
|
||||
type MaterialColors []MaterialColor
|
||||
|
||||
type Saturation uint8
|
||||
type Luminance uint8
|
||||
type LightMap []Luminance
|
||||
|
||||
|
@ -92,6 +93,18 @@ func (c MaterialColors) Hex() (result string) {
|
|||
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 {
|
||||
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.
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("can't open image: %s", err.Error())
|
||||
|
||||
return colors, mainColor, luminance, monochrome, err
|
||||
return colors, mainColor, luminance, saturation, err
|
||||
}
|
||||
|
||||
bounds := img.Bounds()
|
||||
width, height := bounds.Max.X, bounds.Max.Y
|
||||
monochrome = true
|
||||
pixels := float64(width * height)
|
||||
saturationSum := 0.0
|
||||
|
||||
colorCount := make(map[MaterialColor]uint16)
|
||||
var mainColorCount uint16
|
||||
|
@ -192,13 +206,13 @@ func (m *MediaFile) Colors() (colors MaterialColors, mainColor MaterialColor, lu
|
|||
|
||||
_, s, l := rgbColor.Hsl()
|
||||
|
||||
if s != 0 {
|
||||
monochrome = false
|
||||
}
|
||||
saturationSum += s
|
||||
|
||||
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)
|
||||
|
||||
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.False(t, m)
|
||||
assert.Equal(t, 2, s.Int())
|
||||
assert.IsType(t, MaterialColors{}, colors)
|
||||
assert.Equal(t, "grey", main.Name())
|
||||
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 {
|
||||
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.False(t, m)
|
||||
assert.Equal(t, 3, s.Int())
|
||||
assert.IsType(t, MaterialColors{}, colors)
|
||||
assert.Equal(t, "teal", main.Name())
|
||||
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 {
|
||||
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, photoColor, luminance, monochrome, _ := jpeg.Colors()
|
||||
photoColors, photoColor, luminance, saturation, _ := jpeg.Colors()
|
||||
|
||||
photo.PhotoColor = photoColor.Name()
|
||||
photo.PhotoColors = photoColors.Hex()
|
||||
photo.PhotoLuminance = luminance.Hex()
|
||||
photo.PhotoMonochrome = monochrome
|
||||
photo.PhotoSaturation = saturation.Uint()
|
||||
|
||||
// Tags (TensorFlow)
|
||||
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
|
||||
if jpeg, err := mediaFile.GetJpeg(); err == nil {
|
||||
// PhotoColors
|
||||
photoColors, photoColor, luminance, monochrome, _ := jpeg.Colors()
|
||||
photoColors, photoColor, luminance, saturation, _ := jpeg.Colors()
|
||||
|
||||
photo.PhotoColor = photoColor.Name()
|
||||
photo.PhotoColors = photoColors.Hex()
|
||||
photo.PhotoLuminance = luminance.Hex()
|
||||
photo.PhotoMonochrome = monochrome
|
||||
photo.PhotoSaturation = saturation.Uint()
|
||||
|
||||
photo.Camera = models.NewCamera(mediaFile.GetCameraModel(), mediaFile.GetCameraMake()).FirstOrCreate(i.db)
|
||||
photo.Lens = models.NewLens(mediaFile.GetLensModel(), mediaFile.GetLensMake()).FirstOrCreate(i.db)
|
||||
|
|
Loading…
Reference in a new issue