diff --git a/frontend/src/component/photo/edit/face-editor.vue b/frontend/src/component/photo/edit/face-editor.vue new file mode 100644 index 000000000..46545dffd --- /dev/null +++ b/frontend/src/component/photo/edit/face-editor.vue @@ -0,0 +1,543 @@ + + + + + + + + {{ marker.Name || $gettext("Unnamed") }} + + + mdi-close + + + + + + + + + + + + + + + + {{ isDrawingMode ? $gettext("Cancel") : $gettext("Add Face Marker") }} + + + {{ $gettext("Save Changes") }} + + + + + {{ $gettext("Done") }} + + + + + + + + diff --git a/frontend/src/component/photo/edit/people.vue b/frontend/src/component/photo/edit/people.vue index 202643f7c..3b2c5c8cb 100644 --- a/frontend/src/component/photo/edit/people.vue +++ b/frontend/src/component/photo/edit/people.vue @@ -1,7 +1,24 @@ - + + + + + {{ $gettext(`No people found`) }} @@ -88,6 +105,12 @@ + + + + {{ $gettext("Edit Face Markers") }} + + f.Primary) || this.view.model.Files[0]; + }, + }, watch: { uid: function () { this.refresh(); @@ -411,6 +444,12 @@ export default { this.confirm.visible = false; }); }, + closeManualEditing() { + this.showManualEditing = false; + }, + onMarkersUpdated(newMarkers) { + this.markers = newMarkers; + }, }, }; diff --git a/frontend/src/css/face-markers.css b/frontend/src/css/face-markers.css new file mode 100644 index 000000000..e5ff24da5 --- /dev/null +++ b/frontend/src/css/face-markers.css @@ -0,0 +1,104 @@ +.photo-preview-container { + max-width: 100%; + margin: 0 auto; +} + +.photo-preview-wrapper { + position: relative; + overflow: hidden; + background-color: #f0f0f0; + border: 1px solid #ccc; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + display: inline-block; + max-width: 100%; +} + +.photo-preview { + display: block; + max-width: 100%; + height: auto; + pointer-events: none; +} + +.face-marker { + position: absolute; + box-sizing: border-box; + border: 2px solid rgba(255, 255, 255, 0.7); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); + transition: all 0.1s ease-in-out; + cursor: move; + z-index: 10; +} + +.face-marker:hover { + border-color: rgba(255, 255, 255, 1); +} + +.face-marker-selected { + border-color: #1976d2; + border-width: 3px; + box-shadow: 0 0 10px rgba(25, 118, 210, 0.7); +} + +.face-marker-editing { + border-style: dashed; + border-color: #1976d2; +} + +.face-marker-name { + position: absolute; + bottom: 100%; + left: 0; + background-color: rgba(0, 0, 0, 0.7); + color: white; + padding: 2px 5px; + border-radius: 3px; + font-size: 0.8em; + white-space: nowrap; + display: flex; + align-items: center; +} + +.face-marker-actions { + margin-left: 5px; +} + +.marker-handle { + position: absolute; + width: 10px; + height: 10px; + background-color: #1976d2; + border: 1px solid white; + border-radius: 50%; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.5); + z-index: 15; +} + +.handle-nw { + top: -5px; + left: -5px; + cursor: nwse-resize; +} + +.handle-ne { + top: -5px; + right: -5px; + cursor: nesw-resize; +} + +.handle-sw { + bottom: -5px; + left: -5px; + cursor: nesw-resize; +} + +.handle-se { + bottom: -5px; + right: -5px; + cursor: nwse-resize; +} + + diff --git a/internal/api/markers.go b/internal/api/markers.go index b4d0e75e0..b5cdfefa3 100644 --- a/internal/api/markers.go +++ b/internal/api/markers.go @@ -227,6 +227,20 @@ func UpdateMarker(router *gin.RouterGroup) { return } + // Update marker position and thumb if needed. + if frm.W > 0 && frm.H > 0 && (marker.X != frm.X || marker.Y != frm.Y || marker.W != frm.W || marker.H != frm.H) { + marker.X = frm.X + marker.Y = frm.Y + marker.W = frm.W + marker.H = frm.H + marker.Thumb = crop.NewArea("face", marker.X, marker.Y, marker.W, marker.H).Thumb(file.FileHash) + if err := marker.Save(); err != nil { + log.Errorf("faces: %s (update marker position)", err) + AbortSaveFailed(c) + return + } + } + // Update marker from form values. if changed, saveErr := marker.SaveForm(frm); saveErr != nil { log.Errorf("faces: %s (update marker)", saveErr)