implement photo cropping - simple bbox

This commit is contained in:
MarcZierle 2022-01-22 18:39:08 +01:00
parent a9c8e2cbda
commit 2fa9b70bc2
8 changed files with 136 additions and 29 deletions

View File

@ -88,4 +88,8 @@ export function updatePhoto(photo) {
export function deletePhoto(id) { export function deletePhoto(id) {
return apiClient.delete('/deletephoto/'+id+'/') return apiClient.delete('/deletephoto/'+id+'/')
}
export function cropPhoto(id, mode) {
return apiClient.get('/cropphoto/'+id+'/?mode='+mode)
} }

View File

@ -1,13 +1,28 @@
<template> <template>
<div> <div>
<cropper <n-space justify="start">
class="cropper" <cropper
:src="src" class="cropper"
:stencil-props="{ :src="src"
aspectRatio: 1/1.41421356 :stencil-props="aspect_ratio"
}" :auto-zoom="true"
@change="change" @change="change"
/> ref="cropper"
/>
<n-space vertical justify="space-between">
<div>
<h2>Preview</h2>
<n-image
class="preview"
height="200"
:src="preview_data_url"
/>
</div>
<n-checkbox v-model:checked="lock_aspect_ratio">lock aspect ratio to flip chart size?</n-checkbox>
</n-space>
</n-space>
</div> </div>
</template> </template>
@ -28,15 +43,48 @@ export default {
}, },
data() { data() {
return { return {
preview_data_url: null,
lock_aspect_ratio: true,
bbox: []
} }
}, },
mounted() { computed: {
console.log(this.src) aspect_ratio() {
if (this.lock_aspect_ratio) {
return {
aspectRatio: 1/1.41421356
}
}
return {}
}
}, },
methods: { methods: {
coordinates_to_bbox(coordinates) {
let top_left = [
coordinates.left,
coordinates.top
]
let top_right = [
coordinates.left + coordinates.width,
coordinates.top
]
let bottom_right = [
coordinates.left + coordinates.width,
coordinates.top + coordinates.height
]
let bottom_left = [
coordinates.left,
coordinates.top + coordinates.height
]
return [top_left, top_right, bottom_right, bottom_left]
},
change({ coordinates, canvas }) { change({ coordinates, canvas }) {
console.log(coordinates, canvas) this.preview_data_url = canvas.toDataURL()
this.bbox = this.coordinates_to_bbox(coordinates)
},
getResult() {
return JSON.parse(JSON.stringify(this.bbox))
} }
} }
} }
@ -48,4 +96,8 @@ export default {
width: 600px; width: 600px;
background: #DDD; background: #DDD;
} }
.preview {
max-width: 300px;
}
</style> </style>

View File

@ -105,11 +105,16 @@ export default {
type: Number, type: Number,
required: false, required: false,
default: 150, default: 150,
} },
init_selection: {
type: Boolean,
required: false,
default: false,
},
}, },
emits: [ emits: [
'update:select', 'update:select',
'update:delet', 'update:delete',
'update:crop', 'update:crop',
'update:ocr', 'update:ocr',
'update:group', 'update:group',
@ -126,6 +131,9 @@ export default {
beforeMount() { beforeMount() {
this.imgSrcObj = new Image() this.imgSrcObj = new Image()
this.imgSrcObj.src = this.src this.imgSrcObj.src = this.src
if (this.can_select && this.init_selection) {
this.selected = true
}
}, },
watch: { watch: {
imgSrcObj: { imgSrcObj: {
@ -202,6 +210,7 @@ export default {
toggleSelection() { toggleSelection() {
if (this.can_select) { if (this.can_select) {
this.selected = !this.selected this.selected = !this.selected
this.$emit('update:select', this.selected)
} }
} }
}, },

View File

@ -27,18 +27,18 @@
:title="group.name" :title="group.name"
:name="group.id"> :name="group.id">
<n-image-group>
<n-space> <n-space>
<n-image <PhotoItem
v-for="photo in photos[group.id]" v-for="photo in photos[group.id]"
:key="photo.id" :key="photo.id"
width="100" width="100"
:src="photo.cropped_image !== null ? photo.cropped_image : photo.original_image" :src="photo.cropped_image !== null ? photo.cropped_image : photo.original_image"
@click="toggle_select_photo(photo.id)" :can_select="true"
:class="{selected: is_photo_selected(photo.id)}" :init_selection="is_photo_selected(photo.id)"
preview-disabled /> @update:select="toggle_select_photo(photo.id)"
:ref="'photoitem-'+photo.id"
/>
</n-space> </n-space>
</n-image-group>
<template #header-extra>{{group.date}}</template> <template #header-extra>{{group.date}}</template>
</n-collapse-item> </n-collapse-item>
@ -57,13 +57,13 @@
<script> <script>
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
//import PhotoItem from '@/components/PhotoItem' import PhotoItem from '@/components/PhotoItem'
export default { export default {
name: 'PhotoSelectModal', name: 'PhotoSelectModal',
emits: [ 'closed', 'selected' ], emits: [ 'closed', 'selected' ],
components: { components: {
//PhotoItem PhotoItem
}, },
props: { props: {
showSelection: { showSelection: {
@ -140,6 +140,7 @@ export default {
if (this.selecions_left > 0) { if (this.selecions_left > 0) {
this.selection.push(photo_id) this.selection.push(photo_id)
} else { } else {
this.$refs['photoitem-'+photo_id][0].selected = false
this.message.error('You can only select ' + this.max_select + ' photo(s)') this.message.error('You can only select ' + this.max_select + ' photo(s)')
} }
} }

View File

@ -9,6 +9,7 @@ import {
getPhotosByGroup, getPhotosByGroup,
updatePhoto, updatePhoto,
deletePhoto, deletePhoto,
cropPhoto,
} from '@/api' } from '@/api'
export default createStore({ export default createStore({
@ -167,7 +168,20 @@ export default createStore({
reject(error) reject(error)
}) })
}) })
} },
cropPhoto({commit}, {id, mode}) {
new Promise((resolve, reject) => {
cropPhoto(id, mode).then((response) => {
commit('UPDATE_PHOTO', {
id: response.data.id,
cropped_image: response.data.cropped_image
})
resolve()
}).catch((error) => {
reject(error)
})
})
},
}, },
getters: { getters: {
photoLogById: (state) => (id) => { photoLogById: (state) => (id) => {

View File

@ -16,7 +16,9 @@
<n-space justify="space-around" > <n-space justify="space-around" >
<n-space vertical> <n-space vertical>
<h2>Documents</h2> <h2>Documents</h2>
<n-button type="primary">All Documents</n-button> <a target="_blank" href='https://dev.marczierle.com/zierle-training/generate_document/modify_document.php?selection=0'>
<n-button type="primary">All Documents</n-button>
</a>
</n-space> </n-space>
<n-space vertical> <n-space vertical>
<h2>Photo Logs</h2> <h2>Photo Logs</h2>

View File

@ -4,6 +4,7 @@
<n-table :bordered="false" :single-line="true"> <n-table :bordered="false" :single-line="true">
<thead> <thead>
<th>Photo Log Title</th> <th>Photo Log Title</th>
<th>PDF</th>
<th>Date created</th> <th>Date created</th>
<th>delete</th> <th>delete</th>
</thead> </thead>
@ -12,6 +13,9 @@
<router-link :to="{name: 'CreateLog', params: {e: photolog.id}}"> <router-link :to="{name: 'CreateLog', params: {e: photolog.id}}">
<td>{{ photolog.title }}</td> <td>{{ photolog.title }}</td>
</router-link> </router-link>
<td>
<a :href="photolog.pdf" target="_blank">PDF</a>
</td>
<td>{{ photolog.date }}</td> <td>{{ photolog.date }}</td>
<td> <td>
<n-button type="error" @click="askDeleteLog(photolog.id)">Delete</n-button> <n-button type="error" @click="askDeleteLog(photolog.id)">Delete</n-button>

View File

@ -40,15 +40,17 @@
title="Adjust Photo Cropping" title="Adjust Photo Cropping"
:bordered="true" :bordered="true"
> >
<template #header-extra> Header </template>
<PhotoCropper :src="current_photo_src" /> <PhotoCropper
style="max-width:900px;margin:auto;"
<template #footer> Footer </template> :src="current_photo_src"
ref="cropper"
/>
<template #action> <template #action>
<n-space justify="end"> <n-space justify="end">
<n-button @click="showCropModal = false">Cancel</n-button> <n-button @click="showCropModal = false">Cancel</n-button>
<n-button type="primary">Save</n-button> <n-button type="primary" @click="changeCrop">Save</n-button>
</n-space> </n-space>
</template> </template>
</n-card> </n-card>
@ -242,6 +244,25 @@ export default {
}) })
} }
}, },
changeCrop() {
let bbox = this.$refs.cropper.getResult()
if (bbox && this.current_photo) {
this.$store.dispatch('updatePhoto', {
id: this.current_photo,
bbox_coords: bbox
}).then(()=>{
setTimeout(()=>{
this.$store.dispatch('cropPhoto', {
id: this.current_photo,
mode: 'bbox'
}).then(()=>{
this.current_photo = null
this.showCropModal = false
})
}, 100)
})
}
},
changeOCRText(){ changeOCRText(){
if (this.current_ocr_text.length !== null && this.current_photo) { if (this.current_ocr_text.length !== null && this.current_photo) {
this.$store.dispatch('updatePhoto', { this.$store.dispatch('updatePhoto', {