diff --git a/Makefile b/Makefile
index 532713dd2..b9add47eb 100644
--- a/Makefile
+++ b/Makefile
@@ -41,7 +41,6 @@ test-coverage:
go test -tags=slow -timeout 30m -coverprofile=coverage.txt -covermode=atomic -v ./internal/...
go tool cover -html=coverage.txt -o coverage.html
clean:
- go clean
rm -f $(BINARY_NAME)
download:
scripts/download-inception.sh
@@ -68,6 +67,8 @@ fmt:
go fmt ./internal/... ./cmd/...
dep:
go build -v ./...
+tidy:
+ go mod tidy
upgrade:
go mod tidy
go get -u
diff --git a/frontend/src/app/pages/photosEdit.vue b/frontend/src/app/pages/photosEdit.vue
index d3735ced4..dc07d1128 100644
--- a/frontend/src/app/pages/photosEdit.vue
+++ b/frontend/src/app/pages/photosEdit.vue
@@ -63,15 +63,9 @@
>
-
-
@@ -229,6 +223,7 @@
'activator': null,
'attach': null,
'colors': ['green', 'purple', 'indigo', 'primary', 'success', 'orange'],
+ 'color': '',
'editing': null,
'index': -1,
'items': [
diff --git a/frontend/src/app/routes.js b/frontend/src/app/routes.js
index 37707443a..c45c44a37 100644
--- a/frontend/src/app/routes.js
+++ b/frontend/src/app/routes.js
@@ -21,10 +21,10 @@ export default [
{ name: 'Favorites', path: '/favorites', component: Todo },
{ name: 'Places', path: '/places', component: Todo },
{ name: 'Albums', path: '/albums', component: Albums },
- { name: 'Albums', path: '/albums2', component: Albums2 },
+ { name: 'Albums2', path: '/albums2', component: Albums2 },
{ name: 'Import', path: '/import', component: Import },
- { name: 'Import', path: '/import2', component: Import2 },
- { name: 'Import', path: '/import3', component: Import3 },
+ { name: 'Import2', path: '/import2', component: Import2 },
+ { name: 'Import3', path: '/import3', component: Import3 },
{ name: 'Export', path: '/export', component: Export },
{ name: 'Settings', path: '/settings', component: Settings },
{ path: '*', redirect: '/photos' },
diff --git a/frontend/src/model/photo.js b/frontend/src/model/photo.js
index 7955ee11d..11993cfc7 100644
--- a/frontend/src/model/photo.js
+++ b/frontend/src/model/photo.js
@@ -10,6 +10,17 @@ class Photo extends Abstract {
return this.ID;
}
+ getColor() {
+ switch (this.PhotoColor) {
+ case 'brown':
+ case 'black':
+ case 'white':
+ case 'grey':
+ return 'grey lighten-2';
+ default:
+ return this.PhotoColor + ' lighten-4';
+ }
+ }
getColors() {
return this.PhotoColors;
diff --git a/go.mod b/go.mod
index f11941b5c..bd6dadaaf 100644
--- a/go.mod
+++ b/go.mod
@@ -2,8 +2,6 @@ module github.com/photoprism/photoprism
require (
cloud.google.com/go v0.34.0 // indirect
- github.com/EdlinOrg/prominentcolor v0.0.0-20180211183425-27c67d28df53
- github.com/RobCherry/vibrant v0.0.0-20160904011657-0680b8cf1c89
github.com/araddon/dateparse v0.0.0-20181123171228-21df004e09ca
github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b // indirect
github.com/blacktear23/go-proxyprotocol v0.0.0-20180807104634-af7a81e8dd0d // indirect
@@ -47,8 +45,6 @@ require (
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/montanaflynn/stats v0.0.0-20181214052348-945b007cb92f // indirect
github.com/myesui/uuid v1.0.0 // indirect
- github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
- github.com/oliamb/cutter v0.2.2 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/opentracing/opentracing-go v1.0.2
github.com/pingcap/errors v0.11.0
@@ -76,10 +72,12 @@ require (
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
- golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
- golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b
- golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect
- golang.org/x/sys v0.0.0-20181217223516-dcdaa6325bcb // indirect
+ golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d // indirect
+ golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b // indirect
+ golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect
+ golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
+ golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0 // indirect
+ golang.org/x/text v0.3.1 // indirect
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
google.golang.org/appengine v1.3.0 // indirect
google.golang.org/genproto v0.0.0-20181218023534-67d6565462c5 // indirect
diff --git a/go.sum b/go.sum
index 8acf92ca6..1ddb785fc 100644
--- a/go.sum
+++ b/go.sum
@@ -3,10 +3,6 @@ cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/EdlinOrg/prominentcolor v0.0.0-20180211183425-27c67d28df53 h1:l+L2QDgQqz1rdaCRg1ncdFQeEWEbjsymnZRNDS/XHSo=
-github.com/EdlinOrg/prominentcolor v0.0.0-20180211183425-27c67d28df53/go.mod h1:mYmDsxfcmBz6izH/SqtSzfsUiZdPNPpPgUPKCZq70KQ=
-github.com/RobCherry/vibrant v0.0.0-20160904011657-0680b8cf1c89 h1:k8/G7/7+vhkmphbzRSHulomGLxKJnM6Dp5NJ2HePGwY=
-github.com/RobCherry/vibrant v0.0.0-20160904011657-0680b8cf1c89/go.mod h1:xu1tbmzBGes+jcIUU9yATLxmOoxdCZT0hUp5HY1c6/A=
github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7 h1:Fv9bK1Q+ly/ROk4aJsVMeuIwPel4bEnD8EPiI91nZMg=
github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/araddon/dateparse v0.0.0-20181123171228-21df004e09ca h1:7tLEgJZb8/+TI8fLso4lINkuSOI4DqQYwhFB+nRH7RQ=
@@ -156,14 +152,10 @@ github.com/montanaflynn/stats v0.0.0-20181214052348-945b007cb92f h1:r//C+RGlxxi1
github.com/montanaflynn/stats v0.0.0-20181214052348-945b007cb92f/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI=
github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
-github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
-github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/ngaut/pools v0.0.0-20180318154953-b7bc8c42aac7 h1:7KAv7KMGTTqSmYZtNdcNTgsos+vFzULLwyElndwn+5c=
github.com/ngaut/pools v0.0.0-20180318154953-b7bc8c42aac7/go.mod h1:iWMfgwqYW+e8n5lC/jjNEhwcjbRDpl5NT7n2h+4UNcI=
github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef h1:K0Fn+DoFqNqktdZtdV3bPQ/0cuYh2H4rkg0tytX/07k=
github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef/go.mod h1:7WjlapSfwQyo6LNmIvEWzsW1hbBQfpUO4JWnuQRmva8=
-github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k=
-github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -258,8 +250,9 @@ go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180503215945-1f94bef427e3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d h1:adrbvkTDn9rGnXg2IJDKozEpXXLZN89pdIA+Syt4/u0=
+golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM=
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -269,23 +262,31 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
-golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU=
+golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181217223516-dcdaa6325bcb h1:zzdd4xkMwu/GRxhSUJaCPh4/jil9kAbsU7AUmXboO+A=
-golang.org/x/sys v0.0.0-20181217223516-dcdaa6325bcb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0 h1:V+O002es++Mnym06Rj/S6Fl7VCsgRBgVDGb/NoZVHUg=
+golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1 h1:nsUiJHvm6yOoRozW9Tz0siNk9sHieLzR+w814Ihse3A=
+golang.org/x/text v0.3.1/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
diff --git a/internal/models/photo.go b/internal/models/photo.go
index da06e685d..7a2935eb4 100644
--- a/internal/models/photo.go
+++ b/internal/models/photo.go
@@ -17,8 +17,7 @@ type Photo struct {
PhotoNotes string `gorm:"type:text;"`
PhotoArtist string
PhotoColors string
- PhotoVibrantColor string
- PhotoMutedColor string
+ PhotoColor string
PhotoCanonicalName string
PhotoFavorite bool
PhotoLat float64
diff --git a/internal/photoprism/colors.go b/internal/photoprism/colors.go
index 5c05b64be..6ad3ffb2e 100644
--- a/internal/photoprism/colors.go
+++ b/internal/photoprism/colors.go
@@ -2,95 +2,172 @@ package photoprism
import (
"fmt"
- "github.com/EdlinOrg/prominentcolor"
- "github.com/RobCherry/vibrant"
- "github.com/lucasb-eyer/go-colorful"
- "image"
"image/color"
"log"
- "os"
- "sort"
+
+ "github.com/disintegration/imaging"
+ "github.com/lucasb-eyer/go-colorful"
)
-var colorMap = map[string]color.RGBA{
- "red": {0xf4, 0x43, 0x36, 0xff},
- "pink": {0xe9, 0x1e, 0x63, 0xff},
- "purple": {0x9c, 0x27, 0xb0, 0xff},
- "indigo": {0x3F, 0x51, 0xB5, 0xff},
- "blue": {0x21, 0x96, 0xF3, 0xff},
- "cyan": {0x00, 0xBC, 0xD4, 0xff},
- "teal": {0x00, 0x96, 0x88, 0xff},
- "green": {0x4C, 0xAF, 0x50, 0xff},
- "lime": {0xCD, 0xDC, 0x39, 0xff},
- "yellow": {0xFF, 0xEB, 0x3B, 0xff},
- "amber": {0xFF, 0xC1, 0x07, 0xff},
- "orange": {0xFF, 0x98, 0x00, 0xff},
- "brown": {0x79, 0x55, 0x48, 0xff},
- "grey": {0x9E, 0x9E, 0x9E, 0xff},
- "white": {0x00, 0x00, 0x00, 0xff},
- "black": {0xFF, 0xFF, 0xFF, 0xff},
+type MaterialColor uint16
+type MaterialColors []MaterialColor
+
+const ColorSampleSize = 3
+
+const (
+ Black MaterialColor = iota
+ Brown
+ Grey
+ White
+ Purple
+ Indigo
+ Blue
+ Cyan
+ Teal
+ Green
+ Lime
+ Yellow
+ Amber
+ Orange
+ Red
+ Pink
+)
+
+var materialColorNames = map[MaterialColor]string{
+ Black: "black", // 0
+ Brown: "brown", // 1
+ Grey: "grey", // 2
+ White: "white", // 3
+ Purple: "purple", // 4
+ Indigo: "indigo", // 5
+ Blue: "blue", // 6
+ Cyan: "cyan", // 7
+ Teal: "teal", // 8
+ Green: "green", // 9
+ Lime: "lime", // A
+ Yellow: "yellow", // B
+ Amber: "amber", // C
+ Orange: "orange", // D
+ Red: "red", // E
+ Pink: "pink", // F
}
-func deduplicate(s []string) []string {
- seen := make(map[string]struct{}, len(s))
- j := 0
- for _, v := range s {
- if _, ok := seen[v]; ok {
- continue
- }
- seen[v] = struct{}{}
- s[j] = v
- j++
+var materialColorWeight = map[MaterialColor]uint16{
+ Black: 2,
+ Brown: 1,
+ Grey: 2,
+ White: 2,
+ Purple: 5,
+ Indigo: 3,
+ Blue: 3,
+ Cyan: 4,
+ Teal: 4,
+ Green: 3,
+ Lime: 5,
+ Yellow: 5,
+ Amber: 5,
+ Orange: 5,
+ Red: 5,
+ Pink: 5,
+}
+
+func (c MaterialColor) Name() string {
+ return materialColorNames[c]
+}
+
+func (c MaterialColor) Hex() string {
+ return fmt.Sprintf("%X", c)
+}
+
+func (c MaterialColors) Hex() (result string) {
+ for _, materialColor := range c {
+ result += materialColor.Hex()
}
- return s[:j]
+
+ return result
}
-func getColorNames(actualColor colorful.Color) (result []string) {
- var maxDistance = 0.27
+var materialColorMap = map[color.RGBA]MaterialColor{
+ {0x00, 0x00, 0x00, 0xff}: Black,
+ {0x79, 0x55, 0x48, 0xff}: Brown,
+ {0x9E, 0x9E, 0x9E, 0xff}: Grey,
+ {0xFF, 0xFF, 0xFF, 0xff}: White,
+ {0x9c, 0x27, 0xb0, 0xff}: Purple,
+ {0x3F, 0x51, 0xB5, 0xff}: Indigo,
+ {0x21, 0x96, 0xF3, 0xff}: Blue,
+ {0x00, 0xBC, 0xD4, 0xff}: Cyan,
+ {0x00, 0x96, 0x88, 0xff}: Teal,
+ {0x4C, 0xAF, 0x50, 0xff}: Green,
+ {0xCD, 0xDC, 0x39, 0xff}: Lime,
+ {0xFF, 0xEB, 0x3B, 0xff}: Yellow,
+ {0xFF, 0xC1, 0x07, 0xff}: Amber,
+ {0xFF, 0x98, 0x00, 0xff}: Orange,
+ {0xf4, 0x43, 0x36, 0xff}: Red,
+ {0xe9, 0x1e, 0x63, 0xff}: Pink,
+}
- for colorName, colorRGBA := range colorMap {
+func colorfulToMaterialColor(actualColor colorful.Color) (result MaterialColor) {
+ var distance = 1.0
+
+ for colorRGBA, materialColor := range materialColorMap {
colorColorful, _ := colorful.MakeColor(colorRGBA)
currentDistance := colorColorful.DistanceLab(actualColor)
- if maxDistance >= currentDistance {
- result = append(result, fmt.Sprintf("%s", colorName))
+ if distance >= currentDistance {
+ distance = currentDistance
+ result = materialColor
}
}
return result
}
-// GetColors returns color information for a given mediafiles.
-func (m *MediaFile) GetColors() (colors []string, vibrantHex string, mutedHex string) {
- file, _ := os.Open(m.filename)
+// Colors returns color information for a media file.
+func (m *MediaFile) Colors() (colors MaterialColors, mainColor MaterialColor, err error) {
+ jpeg, err := m.GetJpeg()
- defer file.Close()
+ if err != nil {
+ log.Printf("can't find jpeg: %s", err.Error())
- decodedImage, _, _ := image.Decode(file)
-
- palette := vibrant.NewPaletteBuilder(decodedImage).Generate()
-
- if vibrantSwatch := palette.VibrantSwatch(); vibrantSwatch != nil {
- color, _ := colorful.MakeColor(vibrantSwatch.Color())
- vibrantHex = color.Hex()
+ return colors, mainColor, err
}
- if mutedSwatch := palette.MutedSwatch(); mutedSwatch != nil {
- color, _ := colorful.MakeColor(mutedSwatch.Color())
- mutedHex = color.Hex()
+ img, err := imaging.Open(jpeg.GetFilename(), imaging.AutoOrientation(true))
+
+ if err != nil {
+ log.Printf("can't open jpeg: %s", err.Error())
+
+ return colors, mainColor, err
}
- centroids, err := prominentcolor.KmeansWithAll(5, decodedImage, prominentcolor.ArgumentDefault|prominentcolor.ArgumentNoCropping, prominentcolor.DefaultSize, prominentcolor.GetDefaultMasks())
- if err == nil {
- for _, centroid := range centroids {
- colorfulColor, _ := colorful.MakeColor(color.RGBA{R: uint8(centroid.Color.R), G: uint8(centroid.Color.G), B: uint8(centroid.Color.B), A: 0xff})
- colors = append(colors, getColorNames(colorfulColor)...)
+ img = imaging.Resize(img, ColorSampleSize, ColorSampleSize, imaging.Box)
+
+ bounds := img.Bounds()
+ width, height := bounds.Max.X, bounds.Max.Y
+
+ colorCount := make(map[MaterialColor]uint16)
+ var mainColorCount uint16
+
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ r, g, b, a := img.At(x, y).RGBA()
+ rgbColor, _ := colorful.MakeColor(color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)})
+ materialColor := colorfulToMaterialColor(rgbColor)
+ colors = append(colors, materialColor)
+
+ if _, ok := colorCount[materialColor]; ok == true {
+ colorCount[materialColor] += materialColorWeight[materialColor]
+ } else {
+ colorCount[materialColor] = materialColorWeight[materialColor]
+ }
+
+ if colorCount[materialColor] > mainColorCount {
+ mainColorCount = colorCount[materialColor]
+ mainColor = materialColor
+ }
+
}
- colors = deduplicate(colors)
- sort.Strings(colors)
- } else {
- log.Printf("Unable to detect most dominent color in image: %s", err)
}
- return colors, vibrantHex, mutedHex
+ return colors, mainColor, nil
}
diff --git a/internal/photoprism/colors_slow_test.go b/internal/photoprism/colors_slow_test.go
index 73e14476f..9d701e7b0 100644
--- a/internal/photoprism/colors_slow_test.go
+++ b/internal/photoprism/colors_slow_test.go
@@ -15,26 +15,29 @@ func TestMediaFile_GetColors_Slow(t *testing.T) {
conf.InitializeTestData(t)
if mediaFile2, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG"); err == nil {
+ colors, main, err := mediaFile2.Colors()
- names, vibrantHex, mutedHex := mediaFile2.GetColors()
+ t.Log(colors, main, err)
- t.Log(names, vibrantHex, mutedHex)
-
- assert.Equal(t, "#3d85c3", vibrantHex)
- assert.Equal(t, "#988570", mutedHex)
- assert.Equal(t, []string([]string{"black", "brown", "grey", "white"}), names);
+ assert.Nil(t, err)
+ 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, err := mediaFile3.Colors()
- names, vibrantHex, mutedHex := mediaFile3.GetColors()
+ t.Log(colors, main, err)
+
+ assert.Nil(t, err)
+ 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)
- t.Log(names, vibrantHex, mutedHex)
- assert.Equal(t, []string([]string{"black", "brown", "grey"}), names);
- assert.Equal(t, "#d5d437", vibrantHex)
- assert.Equal(t, "#a69f55", mutedHex)
} else {
t.Error(err)
}
diff --git a/internal/photoprism/colors_test.go b/internal/photoprism/colors_test.go
index 21885b6e9..7b42d5de0 100644
--- a/internal/photoprism/colors_test.go
+++ b/internal/photoprism/colors_test.go
@@ -13,27 +13,27 @@ func TestMediaFile_GetColors(t *testing.T) {
conf.InitializeTestData(t)
if mediaFile1, err := NewMediaFile(conf.ImportPath() + "/dog.jpg"); err == nil {
- names, vibrantHex, mutedHex := mediaFile1.GetColors()
+ colors, main, err := mediaFile1.Colors()
- t.Log(names, vibrantHex, mutedHex)
+ t.Log(colors, main, err)
- assert.IsType(t, []string{}, names)
- assert.Equal(t, "#e0ed21", vibrantHex)
- assert.Equal(t, "#977d67", mutedHex)
- assert.Equal(t, []string([]string{"black", "brown", "grey", "white"}), names);
+ assert.Nil(t, err)
+ 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)
} else {
t.Error(err)
}
if mediaFile2, err := NewMediaFile(conf.ImportPath() + "/ape.jpeg"); err == nil {
- names, vibrantHex, mutedHex := mediaFile2.GetColors()
+ colors, main, err := mediaFile2.Colors()
- t.Log(names, vibrantHex, mutedHex)
+ t.Log(colors, main, err)
- assert.IsType(t, []string{}, names)
- assert.Equal(t, "#97c84a", vibrantHex)
- assert.Equal(t, "#6c9a68", mutedHex)
- assert.Equal(t, []string([]string{"grey", "teal", "white"}), names);
+ assert.Nil(t, err)
+ 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)
} else {
t.Error(err)
}
diff --git a/internal/photoprism/indexer.go b/internal/photoprism/indexer.go
index 29ea55d73..4542c4e69 100644
--- a/internal/photoprism/indexer.go
+++ b/internal/photoprism/indexer.go
@@ -76,7 +76,6 @@ func (i *Indexer) indexMediaFile(mediaFile *MediaFile) string {
var photo models.Photo
var file, primaryFile models.File
var isPrimary = false
- var colorNames []string
var tags []*models.Tag
canonicalName := mediaFile.GetCanonicalNameFromFile()
@@ -95,9 +94,10 @@ func (i *Indexer) indexMediaFile(mediaFile *MediaFile) string {
}
// PhotoColors
- colorNames, photo.PhotoVibrantColor, photo.PhotoMutedColor = jpeg.GetColors()
+ photoColors, photoColor, _ := jpeg.Colors()
- photo.PhotoColors = strings.Join(colorNames, ", ")
+ photo.PhotoColor = photoColor.Name()
+ photo.PhotoColors = photoColors.Hex()
// Tags (TensorFlow)
tags = i.getImageTags(jpeg)
@@ -163,9 +163,10 @@ 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
- colorNames, photo.PhotoVibrantColor, photo.PhotoMutedColor = jpeg.GetColors()
+ photoColors, photoColor, _ := jpeg.Colors()
- photo.PhotoColors = strings.Join(colorNames, ", ")
+ photo.PhotoColor = photoColor.Name()
+ photo.PhotoColors = photoColors.Hex()
photo.Camera = models.NewCamera(mediaFile.GetCameraModel(), mediaFile.GetCameraMake()).FirstOrCreate(i.db)
photo.Lens = models.NewLens(mediaFile.GetLensModel(), mediaFile.GetLensMake()).FirstOrCreate(i.db)
diff --git a/internal/photoprism/search.go b/internal/photoprism/search.go
index fe5ef319b..525f76b46 100644
--- a/internal/photoprism/search.go
+++ b/internal/photoprism/search.go
@@ -33,8 +33,7 @@ type PhotoSearchResult struct {
PhotoArtist string
PhotoKeywords string
PhotoColors string
- PhotoVibrantColor string
- PhotoMutedColor string
+ PhotoColor string
PhotoCanonicalName string
PhotoLat float64
PhotoLong float64
@@ -118,7 +117,7 @@ func (s *Search) Photos(form forms.PhotoSearchForm) ([]PhotoSearchResult, error)
if form.Query != "" {
likeString := "%" + strings.ToLower(form.Query) + "%"
- q = q.Where("tags.tag_label LIKE ? OR LOWER(photo_title) LIKE ? OR LOWER(photo_colors) LIKE ?", likeString, likeString, likeString)
+ q = q.Where("tags.tag_label LIKE ? OR LOWER(photo_title) LIKE ? OR LOWER(photo_color) LIKE ?", likeString, likeString, likeString)
}
if form.CameraID > 0 {
diff --git a/internal/photoprism/tensorflow.go b/internal/photoprism/tensorflow.go
index ce57b4863..096b51f70 100644
--- a/internal/photoprism/tensorflow.go
+++ b/internal/photoprism/tensorflow.go
@@ -163,7 +163,7 @@ func (t *TensorFlow) makeTensorFromImage(image string, imageFormat string) (*tf.
// Creates a graph to decode, resize and normalize an image
func (t *TensorFlow) makeTransformImageGraph(imageFormat string) (
- graph *tf.Graph, input, output tf.Output, err error) {
+ graph *tf.Graph, input, output tf.Output, err error) {
const (
H, W = 224, 224
Mean = float32(117)