zobrazenie pozicii cisel na mieste kliknutia,
pridane toleracnie 0 pre presny pixel a 1 pre okolie pixela
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { computed, markRaw, reactive, ref, shallowRef } from 'vue'
|
||||
import { computed, markRaw, reactive, ref, shallowRef, watch } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import type {
|
||||
ColorGroup,
|
||||
@ -7,10 +7,16 @@ import type {
|
||||
ProjectFile,
|
||||
ToastMessage,
|
||||
} from '@/types/mosaic'
|
||||
import { floodFillRegion } from '@/utils/floodFill'
|
||||
import { floodFillRegion, getFloodFillReferenceColor } from '@/utils/floodFill'
|
||||
import { groupRegionsByColor, refreshGroupColors, sortGroupsByLightness } from '@/utils/colorGroups'
|
||||
import { rgbToLab } from '@/utils/colors'
|
||||
import { buildOccupancy, createRegionMask } from '@/utils/regionMask'
|
||||
import { colorDistance, rgbToLab } from '@/utils/colors'
|
||||
import {
|
||||
buildOccupancy,
|
||||
createRegionMask,
|
||||
maskContains,
|
||||
subtractRegionMask,
|
||||
summarizeRegionMask,
|
||||
} from '@/utils/regionMask'
|
||||
import { createProjectFile, isProjectFile } from '@/utils/projectExport'
|
||||
import { clearLastProject, loadLastProject, saveLastProject } from '@/utils/projectStorage'
|
||||
|
||||
@ -26,6 +32,7 @@ const DEFAULT_SETTINGS: MosaicSettings = {
|
||||
connectivity: 4,
|
||||
exportOverlay: false,
|
||||
otherGroupsMode: 'dim',
|
||||
useUserClickPositionForLabels: false,
|
||||
}
|
||||
|
||||
const cloneRegions = (regions: MosaicRegion[]): MosaicRegion[] => regions.map((region) => markRaw({
|
||||
@ -34,6 +41,7 @@ const cloneRegions = (regions: MosaicRegion[]): MosaicRegion[] => regions.map((r
|
||||
labColor: { ...region.labColor },
|
||||
boundingBox: { ...region.boundingBox },
|
||||
labelPosition: { ...region.labelPosition },
|
||||
userClickPosition: region.userClickPosition ? { ...region.userClickPosition } : undefined,
|
||||
// Masks are immutable; sharing their typed arrays keeps the 30-step history compact.
|
||||
mask: region.mask,
|
||||
}))
|
||||
@ -170,21 +178,29 @@ export const useMosaicStore = defineStore('mosaic', () => {
|
||||
|
||||
function addRegionAt(x: number, y: number): void {
|
||||
if (!imageData.value || isAnalyzing.value) return
|
||||
const occupancy = buildOccupancy(imageWidth.value, imageHeight.value, regions.value.map((region) => region.mask))
|
||||
if (occupancy[y * imageWidth.value + x]) {
|
||||
const floodTolerance = settings.floodTolerance
|
||||
const minimumRegionPixelCount = floodTolerance <= 1 ? 1 : 12
|
||||
const referenceColor = getFloodFillReferenceColor(imageData.value, x, y, floodTolerance)
|
||||
const existingRegion = regions.value.find((region) => maskContains(region.mask, x, y))
|
||||
if (existingRegion && colorDistance(existingRegion.color, referenceColor) <= floodTolerance) {
|
||||
notify('Táto plocha už bola označená.', 'warning')
|
||||
return
|
||||
}
|
||||
const occupancy = buildOccupancy(
|
||||
imageWidth.value,
|
||||
imageHeight.value,
|
||||
regions.value.filter((region) => region.id !== existingRegion?.id).map((region) => region.mask),
|
||||
)
|
||||
isAnalyzing.value = true
|
||||
window.setTimeout(() => {
|
||||
try {
|
||||
const result = floodFillRegion(imageData.value as ImageData, x, y, {
|
||||
tolerance: settings.floodTolerance,
|
||||
tolerance: floodTolerance,
|
||||
darkBoundaryThreshold: settings.darkBoundaryThreshold,
|
||||
connectivity: settings.connectivity,
|
||||
occupancy,
|
||||
})
|
||||
if (!result || result.pixelCount < 12) {
|
||||
if (!result || result.pixelCount < minimumRegionPixelCount) {
|
||||
notify('Nájdená plocha je príliš malá. Skúste iný bod alebo vyššiu toleranciu.', 'warning')
|
||||
return
|
||||
}
|
||||
@ -200,10 +216,27 @@ export const useMosaicStore = defineStore('mosaic', () => {
|
||||
pixelCount: result.pixelCount,
|
||||
boundingBox: result.boundingBox,
|
||||
labelPosition: result.labelPosition,
|
||||
userClickPosition: { x, y },
|
||||
groupId: '',
|
||||
mask: result.mask,
|
||||
})
|
||||
regions.value = [...regions.value, region]
|
||||
const nextRegions = regions.value.flatMap((item) => {
|
||||
if (item.id !== existingRegion?.id) return [item]
|
||||
const remainingMask = subtractRegionMask(item.mask, result.mask)
|
||||
const summary = remainingMask ? summarizeRegionMask(remainingMask) : null
|
||||
if (!remainingMask || !summary || summary.pixelCount < minimumRegionPixelCount) return []
|
||||
return [markRaw({
|
||||
...item,
|
||||
mask: remainingMask,
|
||||
pixelCount: summary.pixelCount,
|
||||
boundingBox: summary.boundingBox,
|
||||
labelPosition: summary.labelPosition,
|
||||
userClickPosition: item.userClickPosition && maskContains(remainingMask, item.userClickPosition.x, item.userClickPosition.y)
|
||||
? item.userClickPosition
|
||||
: undefined,
|
||||
})]
|
||||
})
|
||||
regions.value = [...nextRegions, region]
|
||||
regroup(false)
|
||||
selectedRegionId.value = region.id
|
||||
selectedGroupId.value = regions.value.find((item) => item.id === region.id)?.groupId ?? null
|
||||
@ -308,12 +341,15 @@ export const useMosaicStore = defineStore('mosaic', () => {
|
||||
}
|
||||
|
||||
async function applyProject(project: ProjectFile, blob: Blob | null = null): Promise<void> {
|
||||
Object.assign(settings, project.settings)
|
||||
Object.assign(settings, DEFAULT_SETTINGS, project.settings, {
|
||||
useUserClickPositionForLabels: project.settings.useUserClickPositionForLabels ?? false,
|
||||
})
|
||||
imageName.value = project.imageName
|
||||
imageWidth.value = project.dimensions.width
|
||||
imageHeight.value = project.dimensions.height
|
||||
regions.value = project.regions.map((region) => markRaw({
|
||||
...region,
|
||||
userClickPosition: region.userClickPosition ? { ...region.userClickPosition } : undefined,
|
||||
mask: createRegionMask(region.mask.width, region.mask.height, region.mask.runs),
|
||||
}))
|
||||
groups.value = cloneGroups(project.groups)
|
||||
@ -373,6 +409,8 @@ export const useMosaicStore = defineStore('mosaic', () => {
|
||||
await clearLastProject().catch(() => undefined)
|
||||
}
|
||||
|
||||
watch(settings, scheduleAutosave)
|
||||
|
||||
return {
|
||||
imageCanvas, imageData, imageBlob, imageName, imageWidth, imageHeight, regions, groups,
|
||||
selectedGroupId, selectedRegionId, settings, toasts, isAnalyzing, hasRecovery, recoveryTimestamp,
|
||||
|
||||
Reference in New Issue
Block a user