add cropper snapping points image

This commit is contained in:
MarcZierle 2022-05-04 18:38:52 +02:00
parent 2add4c42a2
commit 82cd293f59
6 changed files with 313 additions and 10 deletions

47
src/assets/handler.svg Normal file
View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="540.398px" height="540.398px" viewBox="0 0 540.398 540.398" style="enable-background:new 0 0 540.398 540.398;"
xml:space="preserve">
<g>
<g>
<path d="M242.207,276.315H88.398c-12.087,0-14.951,6.928-6.407,15.473l29.357,29.357L2.565,429.929
c-3.421,3.414-3.421,8.959,0,12.373l95.527,95.533c3.421,3.416,8.96,3.416,12.375,0L219.25,429.054l29.358,29.357
c8.543,8.537,15.471,5.672,15.471-6.414V298.194C264.079,286.114,254.281,276.315,242.207,276.315z"/>
<path d="M442.306,2.562c-3.421-3.415-8.96-3.415-12.374,0L321.143,111.345l-29.358-29.357c-8.537-8.544-15.465-5.673-15.465,6.407
v153.802c0,12.081,9.798,21.879,21.873,21.879H452c12.087,0,14.951-6.928,6.407-15.472l-29.357-29.357l108.783-108.783
c3.421-3.415,3.421-8.959,0-12.375L442.306,2.562z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

(image error) Size: 1.3 KiB

View File

@ -0,0 +1,136 @@
<template>
<div
class="circle-stencil"
:style="style"
>
<draggable-element
class="circle-stencil__handler"
@drag="onResize"
@drag-end="onResizeEnd"
>
<img :src="require('../assets/handler.svg')" @mousedown.prevent>
</draggable-element>
<draggable-area @move="onMove" @move-end="onMoveEnd">
<stencil-preview
class="circle-stencil__preview"
:image="image"
:coordinates="coordinates"
:width="stencilCoordinates.width"
:height="stencilCoordinates.height"
:transitions="transitions"
/>
</draggable-area>
</div>
</template>
<script>
import {
DraggableElement,
DraggableArea,
StencilPreview,
ResizeEvent
} from 'vue-advanced-cropper'
export default {
name: 'FreeSnapStencil',
components: {
StencilPreview,
DraggableArea,
DraggableElement
},
props: {
image: {
type: Object
},
coordinates: {
type: Object,
},
transitions: {
type: Object,
},
stencilCoordinates: {
type: Object,
},
intersections: {
type: Array,
}
},
computed: {
style() {
const { height, width, left, top } = this.stencilCoordinates
const style = {
width: `${width}px`,
height: `${height}px`,
transform: `translate(${left}px, ${top}px)`
}
if (this.transitions && this.transitions.enabled) {
style.transition = `${this.transitions.time}ms ${this.transitions.timingFunction}`
}
return style
}
},
methods: {
onMove(moveEvent) {
this.$emit('move', moveEvent)
},
onMoveEnd() {
this.$emit('move-end')
},
onResize(dragEvent) {
const shift = dragEvent.shift()
const widthResize = shift.left
const heightResize = -shift.top
this.$emit('resize', new ResizeEvent(
{
left: widthResize,
right: widthResize,
top: heightResize,
bottom: heightResize,
},
{
compensate: true,
},
))
},
onResizeEnd() {
this.$emit('resize-end')
},
aspectRatios() {
return {
minimum: 1,
maximum: 1
}
}
}
}
</script>
<style lang="scss" scoped>
.circle-stencil {
border-radius: 50%;
cursor: move;
position: absolute;
border: dashed 2px white;
box-sizing: border-box;
&__handler {
position: absolute;
right: 15%;
top: 14%;
z-index: 1;
cursor: ne-resize;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
transform: translate(50%, -50%);
}
&__preview {
border-radius: 50%;
overflow: hidden;
}
}
</style>

View File

@ -3,8 +3,9 @@
<n-space justify="start">
<cropper
class="cropper"
:src="src"
:stencil-props="aspect_ratio"
:src="snap_to_inters ? srcImageIntersections : src"
:stencil-component="this.$options.components.FreeSnapStencil"
:stencil-props="stencilProps"
:auto-zoom="true"
@change="change"
ref="cropper"
@ -27,6 +28,7 @@
<n-slider v-model:value="rotate_angle" @update:value="update_rotate" :step="1" :min="-180" :max="180" />
<n-checkbox :disabled="free_transform" v-model:checked="lock_aspect_ratio">lock aspect ratio to flip chart size</n-checkbox>
<n-checkbox v-model:checked="free_transform">free transform handles</n-checkbox>
<n-checkbox v-model:checked="snap_to_inters">snap to intersections</n-checkbox>
</n-space>
</n-space>
</div>
@ -34,39 +36,103 @@
<script>
import {Cropper} from 'vue-advanced-cropper'
import FreeSnapStencil from '../components/FreeSnapStencil.vue'
import 'vue-advanced-cropper/dist/style.css'
/* eslint-disable vue/no-unused-components */
export default {
components: {
Cropper,
FreeSnapStencil,
},
props: {
src: {
type: String,
required: true,
}
},
inters: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
srcImgElement: null,
srcImgLoaded: false,
preview_data_url: null,
lock_aspect_ratio: true,
free_transform: false,
snap_to_inters: false,
bbox: [],
rotate_angle: 0,
}
},
created() {
this.srcImgElement = new Image()
this.srcImgElement.src = this.src
this.srcImgElement.crossOrigin = 'Anonymous'
this.srcImgElement.onload = () => {
this.srcImgLoaded = true
}
},
computed: {
aspect_ratio() {
srcImageIntersections() {
if (!this.srcImgLoaded) {
return this.src
}
var canvasElement = document.createElement('canvas')
canvasElement.width = this.srcImgElement.width
canvasElement.height = this.srcImgElement.height
var ctx = canvasElement.getContext('2d')
ctx.drawImage(this.srcImgElement, 0,0)
this.drawIntersections(ctx)
try {
return ctx.canvas.toDataURL()
}
catch(e) {
return this.src
}
},
stencilProps() {
if (this.lock_aspect_ratio) {
return {
aspectRatio: 1/1.41421356
aspectRatio: 1/1.41421356,
intersections: this.inters
}
}
return {}
return {intersections: this.inters}
}
},
methods: {
drawIntersections(ctx) {
for (let i=0; i < this.inters.length; i++) {
let x = this.inters[i][0]
let y = this.inters[i][1]
this.drawCircle(
ctx, x,y,
8, 'rgba(255,255,255,0.9)',
'rgba(255,255,255,0.4)', 25)
}
},
drawCircle(ctx, x, y, radius, fill, stroke, strokeWidth) {
ctx.beginPath()
ctx.arc(x, y, radius, 0, 2 * Math.PI, false)
if (fill) {
ctx.fillStyle = fill
ctx.fill()
}
if (stroke) {
ctx.lineWidth = strokeWidth
ctx.strokeStyle = stroke
ctx.stroke()
}
},
coordinates_to_bbox(coordinates) {
let top_left = [
coordinates.left,
@ -88,8 +154,10 @@ export default {
return [top_left, top_right, bottom_right, bottom_left]
},
change({ coordinates, canvas }) {
this.preview_data_url = canvas.toDataURL()
this.bbox = this.coordinates_to_bbox(coordinates)
try {
this.preview_data_url = canvas.toDataURL()
this.bbox = this.coordinates_to_bbox(coordinates)
} catch (e) {return}
},
update_rotate() {
this.$refs.cropper.appliedImageTransforms= {

View File

@ -10,6 +10,7 @@ import LogsList from '../views/LogsList.vue'
import CreateLog from '../views/CreateLog.vue'
import CameraCapture from '../views/CameraCapture.vue'
import ManagePhotos from '../views/ManagePhotos.vue'
import DevView from '../views/DevView.vue'
const routes = [
{
@ -44,6 +45,11 @@ const routes = [
name: 'ManagePhotos',
component: ManagePhotos
},
{
path: '/dev',
name: 'Development',
component: DevView
},
]
const router = createRouter({

35
src/views/DevView.vue Normal file
View File

@ -0,0 +1,35 @@
<template>
<div>
<h1 class="text-2xl font-bold">Development</h1>
<PhotoCropper
style="max-width:75vw;margin:auto;"
:src="photo_src"
ref="cropper"
/>
</div>
</template>
<script>
import { useMeta } from 'vue-meta'
import { useMessage } from 'naive-ui'
import PhotoCropper from '@/components/PhotoCropper'
export default {
name: 'DevView',
components: {
PhotoCropper,
},
setup() {
useMeta({ title: 'Development' })
const message = useMessage()
return { message }
},
data() {
return {
photo_src: 'https://server.riezel.com/static/original_images/9b5be3b7-7cf9-4720-ad5e-7d516c7e03c1.jpg'
}
},
}
</script>

View File

@ -11,7 +11,10 @@
:name="group.id">
<template #header-extra>{{ group.date }}</template>
<p v-if="!photos[group.id]">loading...</p>
<div style="text-align: center">
<p v-if="is_group_loading.get(group.id)">loading...</p>
<p v-else-if="!photos[group.id]">no photos in group found</p>
</div>
<n-space>
<PhotoItem
@ -44,6 +47,7 @@
<PhotoCropper
style="max-width:75vw;margin:auto;"
:src="current_photo_src"
:inters="current_photo_intersections"
ref="cropper"
/>
@ -148,6 +152,7 @@ export default {
data() {
return {
current_photo: null,
current_photo_intersections: null,
showChangeGroupModal: false,
showCropModal: false,
@ -157,6 +162,8 @@ export default {
new_group: null,
current_ocr_text: '',
current_photo_src: null,
is_group_loading: new Map(),
}
},
computed: {
@ -199,8 +206,10 @@ export default {
},
methods: {
loadPhotosInGroup(group_data) {
this.is_group_loading.set(group_data.name, true)
if (group_data.expanded)
this.$store.dispatch('loadPhotosInGroup', group_data.name)
.finally(() => this.is_group_loading.set(group_data.name, false))
},
getPhotoSrcById(photo_id){
for (const index in this.photos) {
@ -221,7 +230,9 @@ export default {
change_crop_modal(photo_id) {
this.current_photo = photo_id
this.showCropModal = true
this.current_photo_src = this.$store.getters.photoById(photo_id).original_image
let photo = this.$store.getters.photoById(photo_id)
this.current_photo_src = photo.original_image
this.current_photo_intersections = photo.intersections
},
change_ocr_modal(photo_id) {
this.current_photo = photo_id