Skip to content

Commit

Permalink
✨ frame progress indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidZhang73 committed Feb 27, 2022
1 parent 1107411 commit 63caf78
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 91 deletions.
134 changes: 134 additions & 0 deletions src/hooks/useFrameIndicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useQuasar } from 'quasar'
import { computed } from 'vue'
import utils from '~/libs/utils.js'
import { useAnnotationStore } from '~/store/annotation.js'
import { usePreferenceStore } from '~/store/preference.js'

export const useFrameIndicator = () => {
const ALWAYS_SHOW = true // TODO: load from preferenceStore

const HEIGHT_UNIT = 16
const HEIGHT_MARKER = 8

const COLOR_BACKGROUND = 'linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2))'
const COLOR_BACKGROUND_DARK = 'linear-gradient(rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2))'
const COLOR_KEYFRAME = 'linear-gradient(#000, #000)'
const COLOR_OBJECT = 'linear-gradient(var(--q-primary), var(--q-primary))'
const COLOR_REGION = 'linear-gradient(var(--q-info), var(--q-info))'
const COLOR_SKELETON = 'linear-gradient(var(--q-positive), var(--q-positive))'
const COLOR_ACTION = 'linear-gradient(var(--q-accent), var(--q-accent))'

const annotationStore = useAnnotationStore()
const preferenceStore = usePreferenceStore()
const q = useQuasar()

let bgImageList = []
let bgPositionList = []
let bgSizeList = []

const getBackgroundStyleList = (positionHeightOffset = 0) => {
bgImageList.push(q.dark.isActive ? COLOR_BACKGROUND_DARK : COLOR_BACKGROUND)
bgPositionList.push(
`0% ${12 + positionHeightOffset}px`)
bgSizeList.push(`100% ${HEIGHT_MARKER}px`)
}

const getStyleList = (frameList, color, positionHeightOffset = 0) => {
const markerWidthUnit = 100 / (annotationStore.video.frames - 1)
for (let [frame, width] of frameList) {
const markerWidth = width * markerWidthUnit
bgImageList.push(color)
bgPositionList.push(
`${10000 * frame / (annotationStore.video.frames - 1) / (100 - markerWidth) - 0.5 * markerWidthUnit}% ${12 +
positionHeightOffset}px`)
bgSizeList.push(`${markerWidth}% ${HEIGHT_MARKER}px`)
}
}

const rangeStyle = computed(() => {
bgImageList = []
bgPositionList = []
bgSizeList = []

let positionHeightOffset = 0

getStyleList(
annotationStore.keyframeList.map(keyframe => [keyframe, 1]),
COLOR_KEYFRAME,
positionHeightOffset
)

if (preferenceStore.objects) {
const frameList = Object.entries(annotationStore.objectAnnotationListMap).
filter(([, annotationList]) => annotationList.length).
map(([frame]) => [parseInt(frame), 1])
if (ALWAYS_SHOW || frameList.length) {
positionHeightOffset += HEIGHT_UNIT
getBackgroundStyleList(positionHeightOffset)
getStyleList(
frameList,
COLOR_OBJECT,
positionHeightOffset
)
}
}

if (preferenceStore.regions) {
const frameList = Object.entries(annotationStore.regionAnnotationListMap).
filter(([, annotationList]) => annotationList.length).
map(([frame]) => [parseInt(frame), 1])
if (ALWAYS_SHOW ||frameList.length) {
positionHeightOffset += HEIGHT_UNIT
getBackgroundStyleList(positionHeightOffset)
getStyleList(
frameList,
COLOR_REGION,
positionHeightOffset
)
}
}

if (preferenceStore.skeletons) {
const frameList = Object.entries(annotationStore.skeletonAnnotationListMap).
filter(([, annotationList]) => annotationList.length).
map(([frame]) => [parseInt(frame), 1])
if (ALWAYS_SHOW ||frameList.length) {
positionHeightOffset += HEIGHT_UNIT
getBackgroundStyleList(positionHeightOffset)
getStyleList(
frameList,
COLOR_SKELETON,
positionHeightOffset
)
}
}

if (preferenceStore.actions) {
const frameList = annotationStore.actionAnnotationList.map(
action => {
const startFrame = utils.time2index(action.start)
const endFrame = utils.time2index(action.end)
return [startFrame, (endFrame - startFrame + 1)]
})
if (ALWAYS_SHOW || frameList.length) {
positionHeightOffset += HEIGHT_UNIT
getBackgroundStyleList(positionHeightOffset)
getStyleList(
frameList,
COLOR_ACTION,
positionHeightOffset
)
}
}

return {
'--marker-height': `${32 + positionHeightOffset}px`,
'--marker-bg-image': bgImageList.join(','),
'--marker-bg-position': bgPositionList.join(','),
'--marker-bg-size': bgSizeList.join(',')
}
})
return {
rangeStyle
}
}
168 changes: 77 additions & 91 deletions src/pages/annotation/KeyframePanel.vue
Original file line number Diff line number Diff line change
@@ -1,85 +1,86 @@
<template>
<div class="row justify-evenly items-center q-py-lg">
<q-btn-group flat>
<q-btn
outline
:icon="isPaused ? 'play_arrow' : 'pause'"
@click="handlePlayPause"
>
<q-tooltip>{{ isPaused ? 'pause (p)' : 'play (p)' }}</q-tooltip>
</q-btn>
<q-btn
outline
icon="stop"
:disabled="!showVideoPlayer"
@click="handleStop"
>
<q-tooltip v-if="showVideoPlayer">stop</q-tooltip>
</q-btn>
<q-btn
outline
:icon="showEdit ? 'done' : 'edit'"
@click="showEdit = !showEdit"
>
<q-tooltip>{{ showEdit ? 'done' : 'edit' }}</q-tooltip>
</q-btn>
</q-btn-group>
<div
class="col-grow q-px-lg"
:class="[{'col-12': q.screen.lt.md}]"
:style="{'order': !q.screen.lt.md ? 0 : -1}"
<div class="row justify-evenly items-center q-pt-lg">
<q-btn-group flat>
<q-btn
outline
:icon="isPaused ? 'play_arrow' : 'pause'"
@click="handlePlayPause"
>
<q-range
class="custom-range"
:style="rangeStyle"
label-always
drag-range
snap
track-size="8px"
:min="0"
:max="annotationStore.video.frames - 1"
:step="1"
:readonly="showVideoPlayer"
:left-label-text-color="currentFocus === 'left' || currentFocus === 'range' ? 'blue-grey-1' : 'primary'"
:right-label-text-color="currentFocus === 'right' || currentFocus === 'range' ? 'blue-grey-1' : 'primary'"
:left-label-color="currentFocus === 'left' || currentFocus === 'range' ? 'primary' : 'blue-grey-1'"
:right-label-color="currentFocus === 'right' || currentFocus === 'range' ? 'primary' : 'blue-grey-1'"
:left-label-value="'L: ' + currentFrameRange.min + ' | ' + utils.toFixed2(utils.index2time(currentFrameRange.min)) + ' s'"
:right-label-value="'R: ' + currentFrameRange.max + ' | ' + utils.toFixed2(utils.index2time(currentFrameRange.max)) + ' s'"
:model-value="currentFrameRange"
@update:model-value="handleInput"
/>
</div>
<q-btn-group flat>
<q-btn
outline
icon="keyboard_arrow_left"
@click="handlePreviousKeyframe"
>
<q-tooltip>previous keyframe (&lt)</q-tooltip>
</q-btn>
<q-btn
outline
icon="gps_fixed"
@click="handleNearestKeyframe"
>
<q-tooltip>locate nearest keyframe</q-tooltip>
</q-btn>
<q-btn
outline
icon="keyboard_arrow_right"
@click="handleNextKeyframe"
>
<q-tooltip>next keyframe (&gt)</q-tooltip>
</q-btn>
</q-btn-group>
<q-tooltip>{{ isPaused ? 'pause (p)' : 'play (p)' }}</q-tooltip>
</q-btn>
<q-btn
outline
icon="stop"
:disabled="!showVideoPlayer"
@click="handleStop"
>
<q-tooltip v-if="showVideoPlayer">stop</q-tooltip>
</q-btn>
<q-btn
outline
:icon="showEdit ? 'done' : 'edit'"
@click="showEdit = !showEdit"
>
<q-tooltip>{{ showEdit ? 'done' : 'edit' }}</q-tooltip>
</q-btn>
</q-btn-group>
<div
class="col-grow q-px-lg"
:class="[{'col-12': q.screen.lt.md}]"
:style="{'order': !q.screen.lt.md ? 0 : -1}"
>
<q-range
class="custom-range"
:style="rangeStyle"
label-always
drag-range
snap
track-size="8px"
:min="0"
:max="annotationStore.video.frames - 1"
:step="1"
:readonly="showVideoPlayer"
:left-label-text-color="currentFocus === 'left' || currentFocus === 'range' ? 'blue-grey-1' : 'primary'"
:right-label-text-color="currentFocus === 'right' || currentFocus === 'range' ? 'blue-grey-1' : 'primary'"
:left-label-color="currentFocus === 'left' || currentFocus === 'range' ? 'primary' : 'blue-grey-1'"
:right-label-color="currentFocus === 'right' || currentFocus === 'range' ? 'primary' : 'blue-grey-1'"
:left-label-value="'L: ' + currentFrameRange.min + ' | ' + utils.toFixed2(utils.index2time(currentFrameRange.min)) + ' s'"
:right-label-value="'R: ' + currentFrameRange.max + ' | ' + utils.toFixed2(utils.index2time(currentFrameRange.max)) + ' s'"
:model-value="currentFrameRange"
@update:model-value="handleInput"
/>
</div>
<KeyframeTable v-if="showEdit"/>
<q-btn-group flat>
<q-btn
outline
icon="keyboard_arrow_left"
@click="handlePreviousKeyframe"
>
<q-tooltip>previous keyframe (&lt)</q-tooltip>
</q-btn>
<q-btn
outline
icon="gps_fixed"
@click="handleNearestKeyframe"
>
<q-tooltip>locate nearest keyframe</q-tooltip>
</q-btn>
<q-btn
outline
icon="keyboard_arrow_right"
@click="handleNextKeyframe"
>
<q-tooltip>next keyframe (&gt)</q-tooltip>
</q-btn>
</q-btn-group>
</div>
<KeyframeTable v-if="showEdit"/>
</template>

<script setup>
import { useQuasar } from 'quasar'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useFrameIndicator } from '~/hooks/useFrameIndicator.js'
import utils from '~/libs/utils.js'
import KeyframeTable from '~/pages/annotation/components/KeyframeTable.vue'
import { useAnnotationStore } from '~/store/annotation.js'
Expand Down Expand Up @@ -322,27 +323,12 @@ const currentFrameRange = computed({
annotationStore.rightCurrentFrame = value.max
}
})
const rangeStyle = computed(() => {
const bgImageList = []
const bgPositionList = []
const bgSizeList = []
const markerWidth = 100 / (annotationStore.video.frames - 1)
for (let frame of annotationStore.keyframeList) {
const offset = ((2 * (frame * markerWidth) / 100) - 1) * markerWidth / 2
bgImageList.push('linear-gradient(#000, #000)')
bgPositionList.push(`${frame * markerWidth + offset}% 12px`)
bgSizeList.push(`${markerWidth}% 8px`)
}
return {
'--marker-bg-image': bgImageList.join(','),
'--marker-bg-position': bgPositionList.join(','),
'--marker-bg-size': bgSizeList.join(',')
}
})
const { rangeStyle } = useFrameIndicator()
</script>

<style>
.custom-range .q-slider__track-container--h {
height: var(--marker-height);
background-image: var(--marker-bg-image);
background-position: var(--marker-bg-position);
background-size: var(--marker-bg-size);
Expand Down

0 comments on commit 63caf78

Please sign in to comment.