JPEG: Convert Apple "Display P3" colors to standard sRGB #1474
Other color profiles and file formats are not supported yet. Should be easy to add though. Main difficulty will be profile name comparison: For example "Adobe RGB (1998)" vs just "Adobe RGB".
This commit is contained in:
parent
dd7caf83ea
commit
5be456a09f
|
@ -154,6 +154,12 @@
|
|||
<translate>{{ file.Orientation }}</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="file.ColorProfile">
|
||||
<td>
|
||||
<translate>Color Profile</translate>
|
||||
</td>
|
||||
<td>{{ file.ColorProfile }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.MainColor">
|
||||
<td>
|
||||
<translate>Main Color</translate>
|
||||
|
|
|
@ -62,6 +62,7 @@ export class File extends RestModel {
|
|||
Orientation: 0,
|
||||
Projection: "",
|
||||
AspectRatio: 1.0,
|
||||
ColorProfile: "",
|
||||
MainColor: "",
|
||||
Colors: "",
|
||||
Luminance: "",
|
||||
|
|
2
go.mod
2
go.mod
|
@ -35,6 +35,7 @@ require (
|
|||
github.com/leonelquinteros/gotext v1.5.0
|
||||
github.com/lib/pq v1.8.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mandykoh/prism v0.34.0
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||
|
@ -76,6 +77,7 @@ require (
|
|||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mandykoh/go-parallel v0.1.0 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
|
|
5
go.sum
5
go.sum
|
@ -213,6 +213,10 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/machinebox/progress v0.2.0/go.mod h1:hl4FywxSjfmkmCrersGhmJH7KwuKl+Ueq9BXkOny+iE=
|
||||
github.com/mandykoh/go-parallel v0.1.0 h1:7vJMNMC4dsbgZdkAb2A8tV5ENY1v7VxIO1wzQWZoT8k=
|
||||
github.com/mandykoh/go-parallel v0.1.0/go.mod h1:lkYHqG1JNTaSS6lG+PgFCnyMd2VDy8pH9jN9pY899ig=
|
||||
github.com/mandykoh/prism v0.34.0 h1:BWwxhdTVMZxhSur2sDmkFFLKzD8mFXOMy6bjJ4WuS4k=
|
||||
github.com/mandykoh/prism v0.34.0/go.mod h1:mqzyMed6kgs8cImi+RoXBi5YtuzmSWShTakMRMD94VE=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
|
@ -329,6 +333,7 @@ golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+o
|
|||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
|
|
|
@ -8,12 +8,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize/english"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/ulule/deepcopier"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/face"
|
||||
"github.com/photoprism/photoprism/pkg/colors"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
@ -32,46 +32,47 @@ type Files []File
|
|||
|
||||
// File represents an image or sidecar file that belongs to a photo.
|
||||
type File struct {
|
||||
ID uint `gorm:"primary_key" json:"-" yaml:"-"`
|
||||
Photo *Photo `json:"-" yaml:"-"`
|
||||
PhotoID uint `gorm:"index;" json:"-" yaml:"-"`
|
||||
PhotoUID string `gorm:"type:VARBINARY(42);index;" json:"PhotoUID" yaml:"PhotoUID"`
|
||||
InstanceID string `gorm:"type:VARBINARY(42);index;" json:"InstanceID,omitempty" yaml:"InstanceID,omitempty"`
|
||||
FileUID string `gorm:"type:VARBINARY(42);unique_index;" json:"UID" yaml:"UID"`
|
||||
FileName string `gorm:"type:VARBINARY(755);unique_index:idx_files_name_root;" json:"Name" yaml:"Name"`
|
||||
FileRoot string `gorm:"type:VARBINARY(16);default:'/';unique_index:idx_files_name_root;" json:"Root" yaml:"Root,omitempty"`
|
||||
OriginalName string `gorm:"type:VARBINARY(755);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||
FileHash string `gorm:"type:VARBINARY(128);index" json:"Hash" yaml:"Hash,omitempty"`
|
||||
FileSize int64 `json:"Size" yaml:"Size,omitempty"`
|
||||
FileCodec string `gorm:"type:VARBINARY(32)" json:"Codec" yaml:"Codec,omitempty"`
|
||||
FileType string `gorm:"type:VARBINARY(32)" json:"Type" yaml:"Type,omitempty"`
|
||||
FileMime string `gorm:"type:VARBINARY(64)" json:"Mime" yaml:"Mime,omitempty"`
|
||||
FilePrimary bool `json:"Primary" yaml:"Primary,omitempty"`
|
||||
FileSidecar bool `json:"Sidecar" yaml:"Sidecar,omitempty"`
|
||||
FileMissing bool `json:"Missing" yaml:"Missing,omitempty"`
|
||||
FilePortrait bool `json:"Portrait" yaml:"Portrait,omitempty"`
|
||||
FileVideo bool `json:"Video" yaml:"Video,omitempty"`
|
||||
FileDuration time.Duration `json:"Duration" yaml:"Duration,omitempty"`
|
||||
FileWidth int `json:"Width" yaml:"Width,omitempty"`
|
||||
FileHeight int `json:"Height" yaml:"Height,omitempty"`
|
||||
FileOrientation int `json:"Orientation" yaml:"Orientation,omitempty"`
|
||||
FileProjection string `gorm:"type:VARBINARY(32);" json:"Projection,omitempty" yaml:"Projection,omitempty"`
|
||||
FileAspectRatio float32 `gorm:"type:FLOAT;" json:"AspectRatio" yaml:"AspectRatio,omitempty"`
|
||||
FileMainColor string `gorm:"type:VARBINARY(16);index;" json:"MainColor" yaml:"MainColor,omitempty"`
|
||||
FileColors string `gorm:"type:VARBINARY(9);" json:"Colors" yaml:"Colors,omitempty"`
|
||||
FileLuminance string `gorm:"type:VARBINARY(9);" json:"Luminance" yaml:"Luminance,omitempty"`
|
||||
FileDiff uint32 `json:"Diff" yaml:"Diff,omitempty"`
|
||||
FileChroma uint8 `json:"Chroma" yaml:"Chroma,omitempty"`
|
||||
FileError string `gorm:"type:VARBINARY(512)" json:"Error" yaml:"Error,omitempty"`
|
||||
ModTime int64 `json:"ModTime" yaml:"-"`
|
||||
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
||||
CreatedIn int64 `json:"CreatedIn" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
||||
UpdatedIn int64 `json:"UpdatedIn" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
|
||||
Share []FileShare `json:"-" yaml:"-"`
|
||||
Sync []FileSync `json:"-" yaml:"-"`
|
||||
markers *Markers
|
||||
ID uint `gorm:"primary_key" json:"-" yaml:"-"`
|
||||
Photo *Photo `json:"-" yaml:"-"`
|
||||
PhotoID uint `gorm:"index;" json:"-" yaml:"-"`
|
||||
PhotoUID string `gorm:"type:VARBINARY(42);index;" json:"PhotoUID" yaml:"PhotoUID"`
|
||||
InstanceID string `gorm:"type:VARBINARY(42);index;" json:"InstanceID,omitempty" yaml:"InstanceID,omitempty"`
|
||||
FileUID string `gorm:"type:VARBINARY(42);unique_index;" json:"UID" yaml:"UID"`
|
||||
FileName string `gorm:"type:VARBINARY(755);unique_index:idx_files_name_root;" json:"Name" yaml:"Name"`
|
||||
FileRoot string `gorm:"type:VARBINARY(16);default:'/';unique_index:idx_files_name_root;" json:"Root" yaml:"Root,omitempty"`
|
||||
OriginalName string `gorm:"type:VARBINARY(755);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||
FileHash string `gorm:"type:VARBINARY(128);index" json:"Hash" yaml:"Hash,omitempty"`
|
||||
FileSize int64 `json:"Size" yaml:"Size,omitempty"`
|
||||
FileCodec string `gorm:"type:VARBINARY(32)" json:"Codec" yaml:"Codec,omitempty"`
|
||||
FileType string `gorm:"type:VARBINARY(32)" json:"Type" yaml:"Type,omitempty"`
|
||||
FileMime string `gorm:"type:VARBINARY(64)" json:"Mime" yaml:"Mime,omitempty"`
|
||||
FilePrimary bool `json:"Primary" yaml:"Primary,omitempty"`
|
||||
FileSidecar bool `json:"Sidecar" yaml:"Sidecar,omitempty"`
|
||||
FileMissing bool `json:"Missing" yaml:"Missing,omitempty"`
|
||||
FilePortrait bool `json:"Portrait" yaml:"Portrait,omitempty"`
|
||||
FileVideo bool `json:"Video" yaml:"Video,omitempty"`
|
||||
FileDuration time.Duration `json:"Duration" yaml:"Duration,omitempty"`
|
||||
FileWidth int `json:"Width" yaml:"Width,omitempty"`
|
||||
FileHeight int `json:"Height" yaml:"Height,omitempty"`
|
||||
FileOrientation int `json:"Orientation" yaml:"Orientation,omitempty"`
|
||||
FileProjection string `gorm:"type:VARBINARY(32);" json:"Projection,omitempty" yaml:"Projection,omitempty"`
|
||||
FileAspectRatio float32 `gorm:"type:FLOAT;" json:"AspectRatio" yaml:"AspectRatio,omitempty"`
|
||||
FileColorProfile string `gorm:"type:VARBINARY(32);" json:"ColorProfile,omitempty" yaml:"ColorProfile,omitempty"`
|
||||
FileMainColor string `gorm:"type:VARBINARY(16);index;" json:"MainColor" yaml:"MainColor,omitempty"`
|
||||
FileColors string `gorm:"type:VARBINARY(9);" json:"Colors" yaml:"Colors,omitempty"`
|
||||
FileLuminance string `gorm:"type:VARBINARY(9);" json:"Luminance" yaml:"Luminance,omitempty"`
|
||||
FileDiff uint32 `json:"Diff" yaml:"Diff,omitempty"`
|
||||
FileChroma uint8 `json:"Chroma" yaml:"Chroma,omitempty"`
|
||||
FileError string `gorm:"type:VARBINARY(512)" json:"Error" yaml:"Error,omitempty"`
|
||||
ModTime int64 `json:"ModTime" yaml:"-"`
|
||||
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
||||
CreatedIn int64 `json:"CreatedIn" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
||||
UpdatedIn int64 `json:"UpdatedIn" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
|
||||
Share []FileShare `json:"-" yaml:"-"`
|
||||
Sync []FileSync `json:"-" yaml:"-"`
|
||||
markers *Markers
|
||||
}
|
||||
|
||||
// TableName returns the entity database table name.
|
||||
|
@ -467,14 +468,29 @@ func (m *File) Panorama() bool {
|
|||
return m.Projection() != ProjDefault || (m.FileWidth/m.FileHeight) >= 2
|
||||
}
|
||||
|
||||
// Projection returns the panorama projection type string.
|
||||
// Projection returns the panorama projection name if any.
|
||||
func (m *File) Projection() string {
|
||||
return SanitizeTypeString(m.FileProjection)
|
||||
}
|
||||
|
||||
// SetProjection sets the panorama projection type string.
|
||||
func (m *File) SetProjection(projType string) {
|
||||
m.FileProjection = SanitizeTypeString(projType)
|
||||
// SetProjection sets the panorama projection name.
|
||||
func (m *File) SetProjection(name string) {
|
||||
m.FileProjection = SanitizeTypeString(name)
|
||||
}
|
||||
|
||||
// ColorProfile returns the ICC color profile name if any.
|
||||
func (m *File) ColorProfile() string {
|
||||
return SanitizeTypeCaseSensitive(m.FileColorProfile)
|
||||
}
|
||||
|
||||
// HasColorProfile tests if the file has a matching color profile.
|
||||
func (m *File) HasColorProfile(profile colors.Profile) bool {
|
||||
return profile.Equal(m.FileColorProfile)
|
||||
}
|
||||
|
||||
// SetColorProfile sets the ICC color profile name such as "Display P3".
|
||||
func (m *File) SetColorProfile(name string) {
|
||||
m.FileColorProfile = SanitizeTypeCaseSensitive(name)
|
||||
}
|
||||
|
||||
// AddFaces adds face markers to the file.
|
||||
|
|
|
@ -30,6 +30,7 @@ func (m *File) MarshalJSON() ([]byte, error) {
|
|||
Orientation int `json:",omitempty"`
|
||||
Projection string `json:",omitempty"`
|
||||
AspectRatio float32 `json:",omitempty"`
|
||||
ColorProfile string `json:",omitempty"`
|
||||
MainColor string `json:",omitempty"`
|
||||
Colors string `json:",omitempty"`
|
||||
Luminance string `json:",omitempty"`
|
||||
|
@ -66,6 +67,7 @@ func (m *File) MarshalJSON() ([]byte, error) {
|
|||
Orientation: m.FileOrientation,
|
||||
Projection: m.FileProjection,
|
||||
AspectRatio: m.FileAspectRatio,
|
||||
ColorProfile: m.FileColorProfile,
|
||||
MainColor: m.FileMainColor,
|
||||
Colors: m.FileColors,
|
||||
Luminance: m.FileLuminance,
|
||||
|
|
|
@ -4,10 +4,11 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/face"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/face"
|
||||
"github.com/photoprism/photoprism/pkg/colors"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
func TestFirstFileByHash(t *testing.T) {
|
||||
|
@ -625,3 +626,25 @@ func TestFile_ReplaceHash(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFile_SetColorProfile(t *testing.T) {
|
||||
t.Run("DisplayP3", func(t *testing.T) {
|
||||
m := FileFixtures.Get("exampleFileName.jpg")
|
||||
|
||||
assert.Equal(t, "", m.ColorProfile())
|
||||
assert.True(t, m.HasColorProfile(colors.Default))
|
||||
assert.False(t, m.HasColorProfile(colors.ProfileDisplayP3))
|
||||
|
||||
m.SetColorProfile(string(colors.ProfileDisplayP3))
|
||||
|
||||
assert.Equal(t, "Display P3", m.ColorProfile())
|
||||
assert.False(t, m.HasColorProfile(colors.Default))
|
||||
assert.True(t, m.HasColorProfile(colors.ProfileDisplayP3))
|
||||
|
||||
m.SetColorProfile("")
|
||||
|
||||
assert.Equal(t, "", m.ColorProfile())
|
||||
assert.True(t, m.HasColorProfile(colors.Default))
|
||||
assert.False(t, m.HasColorProfile(colors.ProfileDisplayP3))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -72,6 +72,11 @@ func SanitizeTypeString(s string) string {
|
|||
return Trim(ToASCII(strings.ToLower(s)), TrimTypeString)
|
||||
}
|
||||
|
||||
// SanitizeTypeCaseSensitive omits invalid runes, ensures a maximum length of 32 characters, and returns the result.
|
||||
func SanitizeTypeCaseSensitive(s string) string {
|
||||
return Trim(ToASCII(s), TrimTypeString)
|
||||
}
|
||||
|
||||
// TypeString returns an entity type string for logging.
|
||||
func TypeString(entityType string) string {
|
||||
if entityType == "" {
|
||||
|
|
|
@ -25,6 +25,7 @@ type Data struct {
|
|||
Description string `meta:"Description"`
|
||||
Copyright string `meta:"Rights,Copyright"`
|
||||
Projection string `meta:"ProjectionType"`
|
||||
ColorProfile string `meta:"ICCProfileName,ProfileDescription"`
|
||||
CameraMake string `meta:"CameraMake,Make"`
|
||||
CameraModel string `meta:"CameraModel,Model"`
|
||||
CameraOwner string `meta:"OwnerName"`
|
||||
|
@ -32,7 +33,7 @@ type Data struct {
|
|||
LensMake string `meta:"LensMake"`
|
||||
LensModel string `meta:"Lens,LensModel"`
|
||||
Flash bool `meta:"-"`
|
||||
FocalLength int `meta:"-"`
|
||||
FocalLength int `meta:"FocalLength"`
|
||||
Exposure string `meta:"ExposureTime"`
|
||||
Aperture float32 `meta:"ApertureValue"`
|
||||
FNumber float32 `meta:"FNumber"`
|
||||
|
|
|
@ -129,7 +129,7 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, 6, data.Orientation)
|
||||
assert.Equal(t, "Apple", data.LensMake)
|
||||
assert.Equal(t, "iPhone 7 back camera 3.99mm f/1.8", data.LensModel)
|
||||
|
||||
assert.Equal(t, "", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("gps-2000.jpg", func(t *testing.T) {
|
||||
|
@ -321,6 +321,7 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, 6, data.FocalLength)
|
||||
assert.Equal(t, 0, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("exif-example.tiff", func(t *testing.T) {
|
||||
|
@ -352,6 +353,7 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, 0, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("out-of-range-500.jpg", func(t *testing.T) {
|
||||
|
@ -383,6 +385,7 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, 29, data.FocalLength)
|
||||
assert.Equal(t, 3, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("digikam.jpg", func(t *testing.T) {
|
||||
|
@ -417,6 +420,7 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, "", data.LensModel)
|
||||
assert.Equal(t, 27, data.FocalLength)
|
||||
assert.Equal(t, 0, int(data.Orientation))
|
||||
assert.Equal(t, "", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("notebook.jpg", func(t *testing.T) {
|
||||
|
@ -480,4 +484,68 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, "EF70-200mm f/4L IS USM", data.LensModel)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
})
|
||||
|
||||
t.Run("Iceland-P3.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/Iceland-P3.jpg", fs.FormatJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// t.Logf("all: %+v", data.All)
|
||||
|
||||
assert.Equal(t, "Nicolas Cornet", data.Artist)
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "", data.Title)
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
assert.Equal(t, "", data.Description)
|
||||
assert.Equal(t, "Nicolas Cornet", data.Copyright)
|
||||
assert.Equal(t, 400, data.Height)
|
||||
assert.Equal(t, 600, data.Width)
|
||||
assert.Equal(t, float32(65.05558), data.Lat)
|
||||
assert.Equal(t, float32(-16.625702), data.Lng)
|
||||
assert.Equal(t, 0, data.Altitude)
|
||||
assert.Equal(t, "1/8", data.Exposure)
|
||||
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
|
||||
assert.Equal(t, "NIKON D800E", data.CameraModel)
|
||||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "", data.CameraSerial)
|
||||
assert.Equal(t, 16, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("Iceland-sRGB.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/Iceland-sRGB.jpg", fs.FormatJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// t.Logf("all: %+v", data.All)
|
||||
|
||||
assert.Equal(t, "Nicolas Cornet", data.Artist)
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "", data.Title)
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
assert.Equal(t, "", data.Description)
|
||||
assert.Equal(t, "Nicolas Cornet", data.Copyright)
|
||||
assert.Equal(t, 400, data.Height)
|
||||
assert.Equal(t, 600, data.Width)
|
||||
assert.Equal(t, float32(65.05558), data.Lat)
|
||||
assert.Equal(t, float32(-16.625702), data.Lng)
|
||||
assert.Equal(t, 0, data.Altitude)
|
||||
assert.Equal(t, "1/8", data.Exposure)
|
||||
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
|
||||
assert.Equal(t, "NIKON D800E", data.CameraModel)
|
||||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "", data.CameraSerial)
|
||||
assert.Equal(t, 16, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "", data.ColorProfile)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -867,7 +867,7 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "Canon EOS 6D", data.CameraModel)
|
||||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "012324001432", data.CameraSerial)
|
||||
assert.Equal(t, 0, data.FocalLength)
|
||||
assert.Equal(t, 35, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
})
|
||||
|
@ -889,4 +889,100 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, 0, data.Altitude)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
})
|
||||
|
||||
t.Run("Iceland-P3.jpg", func(t *testing.T) {
|
||||
data, err := JSON("testdata/Iceland-P3.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// t.Logf("all: %+v", data.All)
|
||||
|
||||
assert.Equal(t, "Nicolas Cornet", data.Artist)
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "", data.Title)
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
assert.Equal(t, "", data.Description)
|
||||
assert.Equal(t, "Nicolas Cornet", data.Copyright)
|
||||
assert.Equal(t, 400, data.Height)
|
||||
assert.Equal(t, 600, data.Width)
|
||||
assert.Equal(t, float32(65.05558), data.Lat)
|
||||
assert.Equal(t, float32(-16.625702), data.Lng)
|
||||
assert.Equal(t, 30, data.Altitude)
|
||||
assert.Equal(t, "1/8", data.Exposure)
|
||||
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
|
||||
assert.Equal(t, "NIKON D800E", data.CameraModel)
|
||||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "6001440", data.CameraSerial)
|
||||
assert.Equal(t, 0, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "Display P3", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("Iceland-P3-n.jpg", func(t *testing.T) {
|
||||
data, err := JSON("testdata/Iceland-P3-n.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// t.Logf("all: %+v", data.All)
|
||||
|
||||
assert.Equal(t, "Nicolas Cornet", data.Artist)
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "", data.Title)
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
assert.Equal(t, "", data.Description)
|
||||
assert.Equal(t, "Nicolas Cornet", data.Copyright)
|
||||
assert.Equal(t, 400, data.Height)
|
||||
assert.Equal(t, 600, data.Width)
|
||||
assert.Equal(t, float32(65.05558), data.Lat)
|
||||
assert.Equal(t, float32(-16.625702), data.Lng)
|
||||
assert.Equal(t, 30, data.Altitude)
|
||||
assert.Equal(t, "0.125", data.Exposure)
|
||||
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
|
||||
assert.Equal(t, "NIKON D800E", data.CameraModel)
|
||||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "6001440", data.CameraSerial)
|
||||
assert.Equal(t, 16, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "Display P3", data.ColorProfile)
|
||||
})
|
||||
|
||||
t.Run("Iceland-sRGB.jpg", func(t *testing.T) {
|
||||
data, err := JSON("testdata/Iceland-sRGB.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// t.Logf("all: %+v", data.All)
|
||||
|
||||
assert.Equal(t, "Nicolas Cornet", data.Artist)
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "2012-08-08T22:07:18Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "", data.Title)
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
assert.Equal(t, "", data.Description)
|
||||
assert.Equal(t, "Nicolas Cornet", data.Copyright)
|
||||
assert.Equal(t, 400, data.Height)
|
||||
assert.Equal(t, 600, data.Width)
|
||||
assert.Equal(t, float32(65.05558), data.Lat)
|
||||
assert.Equal(t, float32(-16.625702), data.Lng)
|
||||
assert.Equal(t, 30, data.Altitude)
|
||||
assert.Equal(t, "1/8", data.Exposure)
|
||||
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
|
||||
assert.Equal(t, "NIKON D800E", data.CameraModel)
|
||||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "6001440", data.CameraSerial)
|
||||
assert.Equal(t, 0, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
assert.Equal(t, "Display P3", data.ColorProfile)
|
||||
})
|
||||
}
|
||||
|
|
179
internal/meta/testdata/Iceland-P3-n.json
vendored
Normal file
179
internal/meta/testdata/Iceland-P3-n.json
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
[{
|
||||
"SourceFile": "Iceland-P3.jpg",
|
||||
"ExifToolVersion": 12.16,
|
||||
"FileName": "Iceland-P3.jpg",
|
||||
"Directory": ".",
|
||||
"FileSize": 102415,
|
||||
"FileModifyDate": "2021:12:09 02:13:29+00:00",
|
||||
"FileAccessDate": "2021:12:09 02:38:24+00:00",
|
||||
"FileInodeChangeDate": "2021:12:09 02:38:22+00:00",
|
||||
"FilePermissions": 664,
|
||||
"FileType": "JPEG",
|
||||
"FileTypeExtension": "JPG",
|
||||
"MIMEType": "image/jpeg",
|
||||
"ExifByteOrder": "MM",
|
||||
"PhotometricInterpretation": 2,
|
||||
"Make": "NIKON CORPORATION",
|
||||
"Model": "NIKON D800E",
|
||||
"Orientation": 1,
|
||||
"SamplesPerPixel": 3,
|
||||
"XResolution": 72,
|
||||
"YResolution": 72,
|
||||
"ResolutionUnit": 2,
|
||||
"Software": "Adobe Photoshop CC 2015.5 (Macintosh)",
|
||||
"ModifyDate": "2016:06:28 09:47:33",
|
||||
"Artist": "Nicolas Cornet",
|
||||
"Copyright": "Nicolas Cornet",
|
||||
"ExposureTime": 0.125,
|
||||
"FNumber": 8,
|
||||
"ExposureProgram": 1,
|
||||
"ISO": 200,
|
||||
"ExifVersion": "0230",
|
||||
"DateTimeOriginal": "2012:08:08 22:07:18",
|
||||
"CreateDate": "2012:08:08 22:07:18",
|
||||
"ComponentsConfiguration": "1 2 3 0",
|
||||
"ShutterSpeedValue": 0.125,
|
||||
"ApertureValue": 8,
|
||||
"ExposureCompensation": 0,
|
||||
"MaxApertureValue": 4,
|
||||
"MeteringMode": 5,
|
||||
"LightSource": 0,
|
||||
"Flash": 16,
|
||||
"FocalLength": 16,
|
||||
"SubSecTimeOriginal": 1,
|
||||
"SubSecTimeDigitized": 1,
|
||||
"FlashpixVersion": "0100",
|
||||
"ColorSpace": 65535,
|
||||
"ExifImageWidth": 600,
|
||||
"ExifImageHeight": 400,
|
||||
"FocalPlaneXResolution": 204.8402062,
|
||||
"FocalPlaneYResolution": 204.8402062,
|
||||
"FocalPlaneResolutionUnit": 4,
|
||||
"SensingMethod": 2,
|
||||
"FileSource": 3,
|
||||
"CustomRendered": 0,
|
||||
"ExposureMode": 1,
|
||||
"WhiteBalance": 0,
|
||||
"DigitalZoomRatio": 1,
|
||||
"FocalLengthIn35mmFormat": 16,
|
||||
"SceneCaptureType": 0,
|
||||
"GainControl": 0,
|
||||
"Contrast": 0,
|
||||
"Saturation": 0,
|
||||
"Sharpness": 0,
|
||||
"SubjectDistanceRange": 0,
|
||||
"GPSVersionID": "2 3 0 0",
|
||||
"GPSLatitudeRef": "N",
|
||||
"GPSLongitudeRef": "W",
|
||||
"GPSAltitude": 904.1,
|
||||
"GPSSpeedRef": "K",
|
||||
"GPSSpeed": 1.4,
|
||||
"GPSImgDirectionRef": "T",
|
||||
"GPSImgDirection": 235.3,
|
||||
"GPSMapDatum": "WGS-84",
|
||||
"Compression": 6,
|
||||
"ThumbnailOffset": 1270,
|
||||
"ThumbnailLength": 3926,
|
||||
"CurrentIPTCDigest": "ffc6b44b107e8aa9f5305f77eb834c2f",
|
||||
"CodedCharacterSet": "\u001B%G",
|
||||
"ApplicationRecordVersion": 2,
|
||||
"By-line": "Nicolas Cornet",
|
||||
"TimeCreated": "22:07:18+00:00",
|
||||
"DigitalCreationDate": "2012:08:08",
|
||||
"CopyrightNotice": "Nicolas Cornet",
|
||||
"IPTCDigest": "ffc6b44b107e8aa9f5305f77eb834c2f",
|
||||
"DisplayedUnitsX": 1,
|
||||
"DisplayedUnitsY": 1,
|
||||
"PrintStyle": 0,
|
||||
"PrintPosition": "0 0",
|
||||
"PrintScale": 1,
|
||||
"GlobalAngle": 30,
|
||||
"GlobalAltitude": 30,
|
||||
"URL_List": [],
|
||||
"SlicesGroupName": "Iceland-sRGB",
|
||||
"NumSlices": 1,
|
||||
"PixelAspectRatio": 1,
|
||||
"PhotoshopThumbnail": "(Binary data 3926 bytes, use -b option to extract)",
|
||||
"HasRealMergedData": 1,
|
||||
"WriterName": "Adobe Photoshop",
|
||||
"ReaderName": "Adobe Photoshop CC 2015.5",
|
||||
"PhotoshopQuality": 6,
|
||||
"PhotoshopFormat": 0,
|
||||
"ProgressiveScans": 1,
|
||||
"XMPToolkit": "Adobe XMP Core 5.6-c132 79.159284, 2016/04/19-13:13:40 ",
|
||||
"LensInfo": "16 35 4 4",
|
||||
"ImageNumber": 9327,
|
||||
"Lens": "16.0-35.0 mm f/4.0",
|
||||
"SerialNumber": 6001440,
|
||||
"CreatorTool": "Aperture 3.4.5",
|
||||
"MetadataDate": "2016:06:28 09:47:33+10:00",
|
||||
"DateCreated": "2012:08:08 22:07:18.001",
|
||||
"LegacyIPTCDigest": "701D986FEA688D5F52057CC7EDA3E4EA",
|
||||
"ColorMode": 3,
|
||||
"ICCProfileName": "Display P3",
|
||||
"Format": "image/jpeg",
|
||||
"DocumentID": "adobe:docid:photoshop:eb7a4192-7d4f-1179-93d3-8c7542d122ed",
|
||||
"InstanceID": "xmp.iid:6ccb51ee-5b2d-4171-a14a-b80873652a9c",
|
||||
"OriginalDocumentID": "15D34E05D6803BC4CD8AAE1D1B1B2BEE",
|
||||
"Creator": "Nicolas Cornet",
|
||||
"Rights": "Nicolas Cornet",
|
||||
"HistoryAction": ["saved","saved"],
|
||||
"HistoryInstanceID": ["xmp.iid:385160c4-d06d-4708-a378-30d3efc40e82","xmp.iid:6ccb51ee-5b2d-4171-a14a-b80873652a9c"],
|
||||
"HistoryWhen": ["2016:06:28 09:45:49+10:00","2016:06:28 09:47:33+10:00"],
|
||||
"HistorySoftwareAgent": ["Adobe Photoshop CC 2015.5 (Macintosh)","Adobe Photoshop CC 2015.5 (Macintosh)"],
|
||||
"HistoryChanged": ["/","/"],
|
||||
"ProfileCMMType": "appl",
|
||||
"ProfileVersion": 1024,
|
||||
"ProfileClass": "mntr",
|
||||
"ColorSpaceData": "RGB ",
|
||||
"ProfileConnectionSpace": "XYZ ",
|
||||
"ProfileDateTime": "2015:10:14 13:08:57",
|
||||
"ProfileFileSignature": "acsp",
|
||||
"PrimaryPlatform": "APPL",
|
||||
"CMMFlags": 0,
|
||||
"DeviceManufacturer": "APPL",
|
||||
"DeviceModel": "",
|
||||
"DeviceAttributes": "0 0",
|
||||
"RenderingIntent": 0,
|
||||
"ConnectionSpaceIlluminant": "0.9642 1 0.82491",
|
||||
"ProfileCreator": "appl",
|
||||
"ProfileID": "229 187 14 152 103 189 70 205 75 190 68 110 189 27 117 152",
|
||||
"ProfileDescription": "Display P3",
|
||||
"ProfileCopyright": "Copyright Apple Inc., 2015",
|
||||
"MediaWhitePoint": "0.95045 1 1.08905",
|
||||
"RedMatrixColumn": "0.51512 0.2412 -0.00105",
|
||||
"GreenMatrixColumn": "0.29198 0.69225 0.04189",
|
||||
"BlueMatrixColumn": "0.1571 0.06657 0.78407",
|
||||
"RedTRC": "(Binary data 32 bytes, use -b option to extract)",
|
||||
"ChromaticAdaptation": "1.04788 0.02292 -0.0502 0.02959 0.99048 -0.01706 -0.00923 0.01508 0.75168",
|
||||
"BlueTRC": "(Binary data 32 bytes, use -b option to extract)",
|
||||
"GreenTRC": "(Binary data 32 bytes, use -b option to extract)",
|
||||
"DCTEncodeVersion": 100,
|
||||
"APP14Flags0": 16384,
|
||||
"APP14Flags1": 0,
|
||||
"ColorTransform": 1,
|
||||
"ImageWidth": 600,
|
||||
"ImageHeight": 400,
|
||||
"EncodingProcess": 0,
|
||||
"BitsPerSample": 8,
|
||||
"ColorComponents": 3,
|
||||
"YCbCrSubSampling": "1 1",
|
||||
"Aperture": 8,
|
||||
"ImageSize": "600 400",
|
||||
"Megapixels": 0.24,
|
||||
"ScaleFactor35efl": 1,
|
||||
"ShutterSpeed": 0.125,
|
||||
"SubSecCreateDate": "2012:08:08 22:07:18.1",
|
||||
"SubSecDateTimeOriginal": "2012:08:08 22:07:18.1",
|
||||
"ThumbnailImage": "(Binary data 3926 bytes, use -b option to extract)",
|
||||
"GPSLatitude": 65.0555777777778,
|
||||
"GPSLongitude": -16.6257027777778,
|
||||
"DateTimeCreated": "2012:08:08 22:07:18+00:00",
|
||||
"LensID": 163,
|
||||
"CircleOfConfusion": 0.0300462606288666,
|
||||
"FOV": 96.7330030337318,
|
||||
"FocalLength35efl": 16,
|
||||
"GPSPosition": "65.0555777777778 -16.6257027777778",
|
||||
"HyperfocalDistance": 1.06502437675244,
|
||||
"LightValue": 8
|
||||
}]
|
BIN
internal/meta/testdata/Iceland-P3.jpg
vendored
Normal file
BIN
internal/meta/testdata/Iceland-P3.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
179
internal/meta/testdata/Iceland-P3.json
vendored
Normal file
179
internal/meta/testdata/Iceland-P3.json
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
[{
|
||||
"SourceFile": "Iceland-P3.jpg",
|
||||
"ExifToolVersion": 12.16,
|
||||
"FileName": "Iceland-P3.jpg",
|
||||
"Directory": ".",
|
||||
"FileSize": "100 KiB",
|
||||
"FileModifyDate": "2021:12:09 02:13:29+00:00",
|
||||
"FileAccessDate": "2021:12:09 02:38:24+00:00",
|
||||
"FileInodeChangeDate": "2021:12:09 02:38:22+00:00",
|
||||
"FilePermissions": "rw-rw-r--",
|
||||
"FileType": "JPEG",
|
||||
"FileTypeExtension": "jpg",
|
||||
"MIMEType": "image/jpeg",
|
||||
"ExifByteOrder": "Big-endian (Motorola, MM)",
|
||||
"PhotometricInterpretation": "RGB",
|
||||
"Make": "NIKON CORPORATION",
|
||||
"Model": "NIKON D800E",
|
||||
"Orientation": "Horizontal (normal)",
|
||||
"SamplesPerPixel": 3,
|
||||
"XResolution": 72,
|
||||
"YResolution": 72,
|
||||
"ResolutionUnit": "inches",
|
||||
"Software": "Adobe Photoshop CC 2015.5 (Macintosh)",
|
||||
"ModifyDate": "2016:06:28 09:47:33",
|
||||
"Artist": "Nicolas Cornet",
|
||||
"Copyright": "Nicolas Cornet",
|
||||
"ExposureTime": "1/8",
|
||||
"FNumber": 8.0,
|
||||
"ExposureProgram": "Manual",
|
||||
"ISO": 200,
|
||||
"ExifVersion": "0230",
|
||||
"DateTimeOriginal": "2012:08:08 22:07:18",
|
||||
"CreateDate": "2012:08:08 22:07:18",
|
||||
"ComponentsConfiguration": "Y, Cb, Cr, -",
|
||||
"ShutterSpeedValue": "1/8",
|
||||
"ApertureValue": 8.0,
|
||||
"ExposureCompensation": 0,
|
||||
"MaxApertureValue": 4.0,
|
||||
"MeteringMode": "Multi-segment",
|
||||
"LightSource": "Unknown",
|
||||
"Flash": "Off, Did not fire",
|
||||
"FocalLength": "16.0 mm",
|
||||
"SubSecTimeOriginal": 1,
|
||||
"SubSecTimeDigitized": 1,
|
||||
"FlashpixVersion": "0100",
|
||||
"ColorSpace": "Uncalibrated",
|
||||
"ExifImageWidth": 600,
|
||||
"ExifImageHeight": 400,
|
||||
"FocalPlaneXResolution": 204.8402062,
|
||||
"FocalPlaneYResolution": 204.8402062,
|
||||
"FocalPlaneResolutionUnit": "mm",
|
||||
"SensingMethod": "One-chip color area",
|
||||
"FileSource": "Digital Camera",
|
||||
"CustomRendered": "Normal",
|
||||
"ExposureMode": "Manual",
|
||||
"WhiteBalance": "Auto",
|
||||
"DigitalZoomRatio": 1,
|
||||
"FocalLengthIn35mmFormat": "16 mm",
|
||||
"SceneCaptureType": "Standard",
|
||||
"GainControl": "None",
|
||||
"Contrast": "Normal",
|
||||
"Saturation": "Normal",
|
||||
"Sharpness": "Normal",
|
||||
"SubjectDistanceRange": "Unknown",
|
||||
"GPSVersionID": "2.3.0.0",
|
||||
"GPSLatitudeRef": "North",
|
||||
"GPSLongitudeRef": "West",
|
||||
"GPSAltitude": "904.1 m",
|
||||
"GPSSpeedRef": "km/h",
|
||||
"GPSSpeed": 1.4,
|
||||
"GPSImgDirectionRef": "True North",
|
||||
"GPSImgDirection": 235.3,
|
||||
"GPSMapDatum": "WGS-84",
|
||||
"Compression": "JPEG (old-style)",
|
||||
"ThumbnailOffset": 1270,
|
||||
"ThumbnailLength": 3926,
|
||||
"CurrentIPTCDigest": "ffc6b44b107e8aa9f5305f77eb834c2f",
|
||||
"CodedCharacterSet": "UTF8",
|
||||
"ApplicationRecordVersion": 2,
|
||||
"By-line": "Nicolas Cornet",
|
||||
"TimeCreated": "22:07:18+00:00",
|
||||
"DigitalCreationDate": "2012:08:08",
|
||||
"CopyrightNotice": "Nicolas Cornet",
|
||||
"IPTCDigest": "ffc6b44b107e8aa9f5305f77eb834c2f",
|
||||
"DisplayedUnitsX": "inches",
|
||||
"DisplayedUnitsY": "inches",
|
||||
"PrintStyle": "Centered",
|
||||
"PrintPosition": "0 0",
|
||||
"PrintScale": 1,
|
||||
"GlobalAngle": 30,
|
||||
"GlobalAltitude": 30,
|
||||
"URL_List": [],
|
||||
"SlicesGroupName": "Iceland-sRGB",
|
||||
"NumSlices": 1,
|
||||
"PixelAspectRatio": 1,
|
||||
"PhotoshopThumbnail": "(Binary data 3926 bytes, use -b option to extract)",
|
||||
"HasRealMergedData": "Yes",
|
||||
"WriterName": "Adobe Photoshop",
|
||||
"ReaderName": "Adobe Photoshop CC 2015.5",
|
||||
"PhotoshopQuality": 10,
|
||||
"PhotoshopFormat": "Standard",
|
||||
"ProgressiveScans": "3 Scans",
|
||||
"XMPToolkit": "Adobe XMP Core 5.6-c132 79.159284, 2016/04/19-13:13:40 ",
|
||||
"LensInfo": "16-35mm f/4",
|
||||
"ImageNumber": 9327,
|
||||
"Lens": "16.0-35.0 mm f/4.0",
|
||||
"SerialNumber": 6001440,
|
||||
"CreatorTool": "Aperture 3.4.5",
|
||||
"MetadataDate": "2016:06:28 09:47:33+10:00",
|
||||
"DateCreated": "2012:08:08 22:07:18.001",
|
||||
"LegacyIPTCDigest": "701D986FEA688D5F52057CC7EDA3E4EA",
|
||||
"ColorMode": "RGB",
|
||||
"ICCProfileName": "Display P3",
|
||||
"Format": "image/jpeg",
|
||||
"DocumentID": "adobe:docid:photoshop:eb7a4192-7d4f-1179-93d3-8c7542d122ed",
|
||||
"InstanceID": "xmp.iid:6ccb51ee-5b2d-4171-a14a-b80873652a9c",
|
||||
"OriginalDocumentID": "15D34E05D6803BC4CD8AAE1D1B1B2BEE",
|
||||
"Creator": "Nicolas Cornet",
|
||||
"Rights": "Nicolas Cornet",
|
||||
"HistoryAction": ["saved","saved"],
|
||||
"HistoryInstanceID": ["xmp.iid:385160c4-d06d-4708-a378-30d3efc40e82","xmp.iid:6ccb51ee-5b2d-4171-a14a-b80873652a9c"],
|
||||
"HistoryWhen": ["2016:06:28 09:45:49+10:00","2016:06:28 09:47:33+10:00"],
|
||||
"HistorySoftwareAgent": ["Adobe Photoshop CC 2015.5 (Macintosh)","Adobe Photoshop CC 2015.5 (Macintosh)"],
|
||||
"HistoryChanged": ["/","/"],
|
||||
"ProfileCMMType": "Apple Computer Inc.",
|
||||
"ProfileVersion": "4.0.0",
|
||||
"ProfileClass": "Display Device Profile",
|
||||
"ColorSpaceData": "RGB ",
|
||||
"ProfileConnectionSpace": "XYZ ",
|
||||
"ProfileDateTime": "2015:10:14 13:08:57",
|
||||
"ProfileFileSignature": "acsp",
|
||||
"PrimaryPlatform": "Apple Computer Inc.",
|
||||
"CMMFlags": "Not Embedded, Independent",
|
||||
"DeviceManufacturer": "Apple Computer Inc.",
|
||||
"DeviceModel": "",
|
||||
"DeviceAttributes": "Reflective, Glossy, Positive, Color",
|
||||
"RenderingIntent": "Perceptual",
|
||||
"ConnectionSpaceIlluminant": "0.9642 1 0.82491",
|
||||
"ProfileCreator": "Apple Computer Inc.",
|
||||
"ProfileID": "e5bb0e9867bd46cd4bbe446ebd1b7598",
|
||||
"ProfileDescription": "Display P3",
|
||||
"ProfileCopyright": "Copyright Apple Inc., 2015",
|
||||
"MediaWhitePoint": "0.95045 1 1.08905",
|
||||
"RedMatrixColumn": "0.51512 0.2412 -0.00105",
|
||||
"GreenMatrixColumn": "0.29198 0.69225 0.04189",
|
||||
"BlueMatrixColumn": "0.1571 0.06657 0.78407",
|
||||
"RedTRC": "(Binary data 32 bytes, use -b option to extract)",
|
||||
"ChromaticAdaptation": "1.04788 0.02292 -0.0502 0.02959 0.99048 -0.01706 -0.00923 0.01508 0.75168",
|
||||
"BlueTRC": "(Binary data 32 bytes, use -b option to extract)",
|
||||
"GreenTRC": "(Binary data 32 bytes, use -b option to extract)",
|
||||
"DCTEncodeVersion": 100,
|
||||
"APP14Flags0": "[14]",
|
||||
"APP14Flags1": "(none)",
|
||||
"ColorTransform": "YCbCr",
|
||||
"ImageWidth": 600,
|
||||
"ImageHeight": 400,
|
||||
"EncodingProcess": "Baseline DCT, Huffman coding",
|
||||
"BitsPerSample": 8,
|
||||
"ColorComponents": 3,
|
||||
"YCbCrSubSampling": "YCbCr4:4:4 (1 1)",
|
||||
"Aperture": 8.0,
|
||||
"ImageSize": "600x400",
|
||||
"Megapixels": 0.240,
|
||||
"ScaleFactor35efl": 1.0,
|
||||
"ShutterSpeed": "1/8",
|
||||
"SubSecCreateDate": "2012:08:08 22:07:18.1",
|
||||
"SubSecDateTimeOriginal": "2012:08:08 22:07:18.1",
|
||||
"ThumbnailImage": "(Binary data 3926 bytes, use -b option to extract)",
|
||||
"GPSLatitude": "65 deg 3' 20.08\" N",
|
||||
"GPSLongitude": "16 deg 37' 32.53\" W",
|
||||
"DateTimeCreated": "2012:08:08 22:07:18+00:00",
|
||||
"LensID": "AF-S Nikkor 16-35mm f/4G ED VR",
|
||||
"CircleOfConfusion": "0.030 mm",
|
||||
"FOV": "96.7 deg",
|
||||
"FocalLength35efl": "16.0 mm (35 mm equivalent: 16.0 mm)",
|
||||
"GPSPosition": "65 deg 3' 20.08\" N, 16 deg 37' 32.53\" W",
|
||||
"HyperfocalDistance": "1.07 m",
|
||||
"LightValue": 8.0
|
||||
}]
|
BIN
internal/meta/testdata/Iceland-sRGB.jpg
vendored
Normal file
BIN
internal/meta/testdata/Iceland-sRGB.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
153
internal/meta/testdata/Iceland-sRGB.json
vendored
Normal file
153
internal/meta/testdata/Iceland-sRGB.json
vendored
Normal file
|
@ -0,0 +1,153 @@
|
|||
[{
|
||||
"SourceFile": "Iceland-sRGB.jpg",
|
||||
"ExifToolVersion": 12.16,
|
||||
"FileName": "Iceland-sRGB.jpg",
|
||||
"Directory": ".",
|
||||
"FileSize": "99 KiB",
|
||||
"FileModifyDate": "2021:12:09 02:12:56+00:00",
|
||||
"FileAccessDate": "2021:12:09 02:38:24+00:00",
|
||||
"FileInodeChangeDate": "2021:12:09 02:38:22+00:00",
|
||||
"FilePermissions": "rw-rw-r--",
|
||||
"FileType": "JPEG",
|
||||
"FileTypeExtension": "jpg",
|
||||
"MIMEType": "image/jpeg",
|
||||
"ExifByteOrder": "Big-endian (Motorola, MM)",
|
||||
"PhotometricInterpretation": "RGB",
|
||||
"Make": "NIKON CORPORATION",
|
||||
"Model": "NIKON D800E",
|
||||
"Orientation": "Horizontal (normal)",
|
||||
"SamplesPerPixel": 3,
|
||||
"XResolution": 72,
|
||||
"YResolution": 72,
|
||||
"ResolutionUnit": "inches",
|
||||
"Software": "Adobe Photoshop CC 2015.5 (Macintosh)",
|
||||
"ModifyDate": "2016:06:28 09:47:23",
|
||||
"Artist": "Nicolas Cornet",
|
||||
"Copyright": "Nicolas Cornet",
|
||||
"ExposureTime": "1/8",
|
||||
"FNumber": 8.0,
|
||||
"ExposureProgram": "Manual",
|
||||
"ISO": 200,
|
||||
"ExifVersion": "0230",
|
||||
"DateTimeOriginal": "2012:08:08 22:07:18",
|
||||
"CreateDate": "2012:08:08 22:07:18",
|
||||
"ComponentsConfiguration": "Y, Cb, Cr, -",
|
||||
"ShutterSpeedValue": "1/8",
|
||||
"ApertureValue": 8.0,
|
||||
"ExposureCompensation": 0,
|
||||
"MaxApertureValue": 4.0,
|
||||
"MeteringMode": "Multi-segment",
|
||||
"LightSource": "Unknown",
|
||||
"Flash": "Off, Did not fire",
|
||||
"FocalLength": "16.0 mm",
|
||||
"SubSecTimeOriginal": 1,
|
||||
"SubSecTimeDigitized": 1,
|
||||
"FlashpixVersion": "0100",
|
||||
"ColorSpace": "Uncalibrated",
|
||||
"ExifImageWidth": 600,
|
||||
"ExifImageHeight": 400,
|
||||
"FocalPlaneXResolution": 204.8402062,
|
||||
"FocalPlaneYResolution": 204.8402062,
|
||||
"FocalPlaneResolutionUnit": "mm",
|
||||
"SensingMethod": "One-chip color area",
|
||||
"FileSource": "Digital Camera",
|
||||
"CustomRendered": "Normal",
|
||||
"ExposureMode": "Manual",
|
||||
"WhiteBalance": "Auto",
|
||||
"DigitalZoomRatio": 1,
|
||||
"FocalLengthIn35mmFormat": "16 mm",
|
||||
"SceneCaptureType": "Standard",
|
||||
"GainControl": "None",
|
||||
"Contrast": "Normal",
|
||||
"Saturation": "Normal",
|
||||
"Sharpness": "Normal",
|
||||
"SubjectDistanceRange": "Unknown",
|
||||
"GPSVersionID": "2.3.0.0",
|
||||
"GPSLatitudeRef": "North",
|
||||
"GPSLongitudeRef": "West",
|
||||
"GPSAltitude": "904.1 m",
|
||||
"GPSSpeedRef": "km/h",
|
||||
"GPSSpeed": 1.4,
|
||||
"GPSImgDirectionRef": "True North",
|
||||
"GPSImgDirection": 235.3,
|
||||
"GPSMapDatum": "WGS-84",
|
||||
"Compression": "JPEG (old-style)",
|
||||
"ThumbnailOffset": 1270,
|
||||
"ThumbnailLength": 3926,
|
||||
"CurrentIPTCDigest": "ffc6b44b107e8aa9f5305f77eb834c2f",
|
||||
"CodedCharacterSet": "UTF8",
|
||||
"ApplicationRecordVersion": 2,
|
||||
"By-line": "Nicolas Cornet",
|
||||
"TimeCreated": "22:07:18+00:00",
|
||||
"DigitalCreationDate": "2012:08:08",
|
||||
"CopyrightNotice": "Nicolas Cornet",
|
||||
"IPTCDigest": "ffc6b44b107e8aa9f5305f77eb834c2f",
|
||||
"DisplayedUnitsX": "inches",
|
||||
"DisplayedUnitsY": "inches",
|
||||
"PrintStyle": "Centered",
|
||||
"PrintPosition": "0 0",
|
||||
"PrintScale": 1,
|
||||
"GlobalAngle": 30,
|
||||
"GlobalAltitude": 30,
|
||||
"URL_List": [],
|
||||
"SlicesGroupName": "Iceland-sRGB",
|
||||
"NumSlices": 1,
|
||||
"PixelAspectRatio": 1,
|
||||
"PhotoshopThumbnail": "(Binary data 3926 bytes, use -b option to extract)",
|
||||
"HasRealMergedData": "Yes",
|
||||
"WriterName": "Adobe Photoshop",
|
||||
"ReaderName": "Adobe Photoshop CC 2015.5",
|
||||
"PhotoshopQuality": 10,
|
||||
"PhotoshopFormat": "Standard",
|
||||
"ProgressiveScans": "3 Scans",
|
||||
"XMPToolkit": "Adobe XMP Core 5.6-c132 79.159284, 2016/04/19-13:13:40 ",
|
||||
"LensInfo": "16-35mm f/4",
|
||||
"ImageNumber": 9327,
|
||||
"Lens": "16.0-35.0 mm f/4.0",
|
||||
"SerialNumber": 6001440,
|
||||
"CreatorTool": "Aperture 3.4.5",
|
||||
"MetadataDate": "2016:06:28 09:47:23+10:00",
|
||||
"DateCreated": "2012:08:08 22:07:18.001",
|
||||
"LegacyIPTCDigest": "701D986FEA688D5F52057CC7EDA3E4EA",
|
||||
"ColorMode": "RGB",
|
||||
"ICCProfileName": "Display P3",
|
||||
"Format": "image/jpeg",
|
||||
"DocumentID": "adobe:docid:photoshop:b989e253-7d4f-1179-93d3-8c7542d122ed",
|
||||
"InstanceID": "xmp.iid:0dc1cf45-beab-4655-8008-5d47a919d5ee",
|
||||
"OriginalDocumentID": "15D34E05D6803BC4CD8AAE1D1B1B2BEE",
|
||||
"Creator": "Nicolas Cornet",
|
||||
"Rights": "Nicolas Cornet",
|
||||
"HistoryAction": ["saved","saved"],
|
||||
"HistoryInstanceID": ["xmp.iid:385160c4-d06d-4708-a378-30d3efc40e82","xmp.iid:0dc1cf45-beab-4655-8008-5d47a919d5ee"],
|
||||
"HistoryWhen": ["2016:06:28 09:45:49+10:00","2016:06:28 09:47:23+10:00"],
|
||||
"HistorySoftwareAgent": ["Adobe Photoshop CC 2015.5 (Macintosh)","Adobe Photoshop CC 2015.5 (Macintosh)"],
|
||||
"HistoryChanged": ["/","/"],
|
||||
"DCTEncodeVersion": 100,
|
||||
"APP14Flags0": "[14]",
|
||||
"APP14Flags1": "(none)",
|
||||
"ColorTransform": "YCbCr",
|
||||
"ImageWidth": 600,
|
||||
"ImageHeight": 400,
|
||||
"EncodingProcess": "Baseline DCT, Huffman coding",
|
||||
"BitsPerSample": 8,
|
||||
"ColorComponents": 3,
|
||||
"YCbCrSubSampling": "YCbCr4:4:4 (1 1)",
|
||||
"Aperture": 8.0,
|
||||
"ImageSize": "600x400",
|
||||
"Megapixels": 0.240,
|
||||
"ScaleFactor35efl": 1.0,
|
||||
"ShutterSpeed": "1/8",
|
||||
"SubSecCreateDate": "2012:08:08 22:07:18.1",
|
||||
"SubSecDateTimeOriginal": "2012:08:08 22:07:18.1",
|
||||
"ThumbnailImage": "(Binary data 3926 bytes, use -b option to extract)",
|
||||
"GPSLatitude": "65 deg 3' 20.08\" N",
|
||||
"GPSLongitude": "16 deg 37' 32.53\" W",
|
||||
"DateTimeCreated": "2012:08:08 22:07:18+00:00",
|
||||
"LensID": "AF-S Nikkor 16-35mm f/4G ED VR",
|
||||
"CircleOfConfusion": "0.030 mm",
|
||||
"FOV": "96.7 deg",
|
||||
"FocalLength35efl": "16.0 mm (35 mm equivalent: 16.0 mm)",
|
||||
"GPSPosition": "65 deg 3' 20.08\" N, 16 deg 37' 32.53\" W",
|
||||
"HyperfocalDistance": "1.07 m",
|
||||
"LightValue": 8.0
|
||||
}]
|
|
@ -112,7 +112,7 @@ type XmpDocument struct {
|
|||
Model string `xml:"Model"` // ELE-L29
|
||||
ExifVersion string `xml:"ExifVersion"` // 0210
|
||||
FlashpixVersion string `xml:"FlashpixVersion"` // 0100
|
||||
ColorSpace string `xml:"ColorSpace"` // 1
|
||||
ColorSpace string `xml:"ColorProfile"` // 1
|
||||
ComponentsConfiguration struct {
|
||||
Text string `xml:",chardata" json:"text,omitempty"`
|
||||
Seq struct {
|
||||
|
|
|
@ -153,7 +153,7 @@ func (c *Convert) ToJson(f *MediaFile) (jsonName string, err error) {
|
|||
|
||||
log.Debugf("exiftool: extracting metadata from %s", relName)
|
||||
|
||||
cmd := exec.Command(c.conf.ExifToolBin(), "-m", "-api", "LargeFileSupport", "-j", f.FileName())
|
||||
cmd := exec.Command(c.conf.ExifToolBin(), "-n", "-m", "-api", "LargeFileSupport", "-j", f.FileName())
|
||||
|
||||
// Fetch command output.
|
||||
var out bytes.Buffer
|
||||
|
|
|
@ -329,6 +329,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
if metaData := m.MetaData(); metaData.Error == nil {
|
||||
file.FileCodec = metaData.Codec
|
||||
file.SetProjection(metaData.Projection)
|
||||
file.SetColorProfile(metaData.ColorProfile)
|
||||
|
||||
if metaData.HasInstanceID() {
|
||||
log.Infof("index: %s has instance_id %s", logName, txt.Quote(metaData.InstanceID))
|
||||
|
@ -387,6 +388,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
file.FileAspectRatio = m.AspectRatio()
|
||||
file.FilePortrait = m.Portrait()
|
||||
file.SetProjection(metaData.Projection)
|
||||
file.SetColorProfile(metaData.ColorProfile)
|
||||
|
||||
if res := m.Megapixels(); res > photo.PhotoResolution {
|
||||
photo.PhotoResolution = res
|
||||
|
@ -437,6 +439,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
file.FilePortrait = m.Portrait()
|
||||
file.FileDuration = metaData.Duration
|
||||
file.SetProjection(metaData.Projection)
|
||||
file.SetColorProfile(metaData.ColorProfile)
|
||||
|
||||
if res := m.Megapixels(); res > photo.PhotoResolution {
|
||||
photo.PhotoResolution = res
|
||||
|
|
|
@ -958,15 +958,13 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||
}
|
||||
|
||||
if originalImg == nil {
|
||||
img, err := imaging.Open(m.FileName())
|
||||
img, err := thumb.Open(m.FileName(), m.Orientation())
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("media: %s in %s", err.Error(), txt.Quote(m.BaseName()))
|
||||
return err
|
||||
}
|
||||
|
||||
img = thumb.Rotate(img, m.Orientation())
|
||||
|
||||
originalImg = img
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
)
|
||||
|
||||
// Suffix returns the thumb cache file suffix.
|
||||
|
@ -86,6 +86,7 @@ func FromFile(imageFilename, hash, thumbPath string, width, height, orientation
|
|||
return "", err
|
||||
}
|
||||
|
||||
// Generate thumb cache filename.
|
||||
fileName, err = FileName(hash, thumbPath, width, height, opts...)
|
||||
|
||||
if err != nil {
|
||||
|
@ -93,17 +94,15 @@ func FromFile(imageFilename, hash, thumbPath string, width, height, orientation
|
|||
return "", err
|
||||
}
|
||||
|
||||
img, err := imaging.Open(imageFilename)
|
||||
// Load image from storage.
|
||||
img, err := Open(imageFilename, orientation)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("resample: %s in %s", err, txt.Quote(filepath.Base(imageFilename)))
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if orientation > 1 {
|
||||
img = Rotate(img, orientation)
|
||||
}
|
||||
|
||||
// Create thumb from image.
|
||||
if _, err := Create(img, fileName, width, height, opts...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
func TestResampleOptions(t *testing.T) {
|
||||
|
|
97
internal/thumb/open.go
Normal file
97
internal/thumb/open.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/mandykoh/prism/meta/autometa"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/colors"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// Open loads an image from disk, rotates it, and converts the color profile if necessary.
|
||||
func Open(fileName string, orientation int) (result image.Image, err error) {
|
||||
if fileName == "" {
|
||||
return result, fmt.Errorf("filename missing")
|
||||
}
|
||||
|
||||
// Open JPEG?
|
||||
if fs.GetFileFormat(fileName) == fs.FormatJpeg {
|
||||
return OpenJpeg(fileName, orientation)
|
||||
}
|
||||
|
||||
// Open file with imaging function.
|
||||
img, err := imaging.Open(fileName)
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Rotate?
|
||||
if orientation > 1 {
|
||||
img = Rotate(img, orientation)
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// OpenJpeg loads a JPEG image from disk, rotates it, and converts the color profile if necessary.
|
||||
func OpenJpeg(fileName string, orientation int) (result image.Image, err error) {
|
||||
if fileName == "" {
|
||||
return result, fmt.Errorf("filename missing")
|
||||
}
|
||||
|
||||
logName := txt.Quote(filepath.Base(fileName))
|
||||
|
||||
// Open file.
|
||||
fileReader, err := os.Open(fileName)
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
defer fileReader.Close()
|
||||
|
||||
// Read color metadata.
|
||||
md, imgStream, err := autometa.Load(fileReader)
|
||||
|
||||
var img image.Image
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("resample: %s in %s (read color metadata)", err, logName)
|
||||
img, err = imaging.Decode(fileReader)
|
||||
} else {
|
||||
img, err = imaging.Decode(imgStream)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Read ICC profile and convert colors if possible.
|
||||
if iccProfile, err := md.ICCProfile(); err != nil || iccProfile == nil {
|
||||
// Do nothing.
|
||||
log.Tracef("resample: detected no color profile in %s", logName)
|
||||
} else if profile, err := iccProfile.Description(); err == nil && profile != "" {
|
||||
log.Debugf("resample: detected color profile %s in %s", txt.Quote(profile), logName)
|
||||
switch {
|
||||
case colors.ProfileDisplayP3.Equal(profile):
|
||||
img = colors.ToSRGB(img, colors.ProfileDisplayP3)
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate?
|
||||
if orientation > 1 {
|
||||
img = Rotate(img, orientation)
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
58
internal/thumb/open_test.go
Normal file
58
internal/thumb/open_test.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
t.Run("JPEG", func(t *testing.T) {
|
||||
img, err := Open("testdata/example.jpg", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
t.Error("img must not be nil")
|
||||
}
|
||||
})
|
||||
t.Run("BMP", func(t *testing.T) {
|
||||
img, err := Open("testdata/example.bmp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
t.Error("img must not be nil")
|
||||
}
|
||||
})
|
||||
t.Run("GIF", func(t *testing.T) {
|
||||
img, err := Open("testdata/example.gif", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
t.Error("img must not be nil")
|
||||
}
|
||||
})
|
||||
t.Run("PNG", func(t *testing.T) {
|
||||
img, err := Open("testdata/example.png", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
t.Error("img must not be nil")
|
||||
}
|
||||
})
|
||||
t.Run("TIFF", func(t *testing.T) {
|
||||
img, err := Open("testdata/example.tif", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
t.Error("img must not be nil")
|
||||
}
|
||||
})
|
||||
}
|
16
pkg/colors/profiles.go
Normal file
16
pkg/colors/profiles.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package colors
|
||||
|
||||
import "strings"
|
||||
|
||||
type Profile string
|
||||
|
||||
// Supported color profiles.
|
||||
const (
|
||||
Default Profile = ""
|
||||
ProfileDisplayP3 Profile = "Display P3"
|
||||
)
|
||||
|
||||
// Equal compares the color profile name case-insensitively.
|
||||
func (p Profile) Equal(s string) bool {
|
||||
return strings.EqualFold(string(p), s)
|
||||
}
|
31
pkg/colors/srgb.go
Normal file
31
pkg/colors/srgb.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package colors
|
||||
|
||||
import (
|
||||
"image"
|
||||
_ "image/jpeg"
|
||||
"runtime"
|
||||
|
||||
"github.com/mandykoh/prism"
|
||||
"github.com/mandykoh/prism/displayp3"
|
||||
"github.com/mandykoh/prism/srgb"
|
||||
)
|
||||
|
||||
// ToSRGB converts an image to sRGB colors.
|
||||
func ToSRGB(img image.Image, profile Profile) image.Image {
|
||||
switch profile {
|
||||
case ProfileDisplayP3:
|
||||
in := prism.ConvertImageToNRGBA(img, runtime.NumCPU())
|
||||
out := image.NewNRGBA(in.Rect)
|
||||
|
||||
for i := in.Rect.Min.Y; i < in.Rect.Max.Y; i++ {
|
||||
for j := in.Rect.Min.X; j < in.Rect.Max.X; j++ {
|
||||
inCol, alpha := displayp3.ColorFromNRGBA(in.NRGBAAt(j, i))
|
||||
outCol := srgb.ColorFromXYZ(inCol.ToXYZ())
|
||||
out.SetNRGBA(j, i, outCol.ToNRGBA(alpha))
|
||||
}
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return img
|
||||
}
|
||||
}
|
61
pkg/colors/srgb_test.go
Normal file
61
pkg/colors/srgb_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package colors
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func writeImage(path string, img image.Image) error {
|
||||
imgFile, err := os.Create(path)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer imgFile.Close()
|
||||
|
||||
opt := jpeg.Options{
|
||||
Quality: 95,
|
||||
}
|
||||
|
||||
return jpeg.Encode(imgFile, img, &opt)
|
||||
}
|
||||
|
||||
func TestToSRGB(t *testing.T) {
|
||||
t.Run("DisplayP3", func(t *testing.T) {
|
||||
testFile, _ := filepath.Abs("./testdata/DisplayP3.jpg")
|
||||
|
||||
t.Logf("testfile: %s", testFile)
|
||||
|
||||
imgFile, err := os.Open(testFile)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer imgFile.Close()
|
||||
|
||||
img, _, err := image.Decode(imgFile)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imgSRGB := ToSRGB(img, ProfileDisplayP3)
|
||||
|
||||
srgbFile := "./testdata/SRGB.jpg"
|
||||
|
||||
if err := writeImage(srgbFile, imgSRGB); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.FileExists(t, srgbFile)
|
||||
|
||||
_ = os.Remove(srgbFile)
|
||||
})
|
||||
}
|
BIN
pkg/colors/testdata/DisplayP3.jpg
vendored
Normal file
BIN
pkg/colors/testdata/DisplayP3.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
Loading…
Reference in a new issue