| | import { getArbitraryBaseColor } from '@invoke-ai/ui-library'; |
| | import { $authToken } from 'app/store/nanostores/authToken'; |
| | import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; |
| | import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; |
| | import { TRANSPARENCY_CHECKERBOARD_PATTERN_DARK_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern'; |
| | import { getPrefixedId } from 'features/controlLayers/konva/util'; |
| | import { selectDynamicGrid } from 'features/controlLayers/store/canvasSettingsSlice'; |
| | import Konva from 'konva'; |
| | import type { Logger } from 'roarr'; |
| |
|
| | type CanvasBackgroundModuleConfig = { |
| | GRID_LINE_COLOR_COARSE: string; |
| | GRID_LINE_COLOR_FINE: string; |
| | CHECKERBOARD_PATTERN_DATAURL: string; |
| | }; |
| |
|
| | const DEFAULT_CONFIG: CanvasBackgroundModuleConfig = { |
| | GRID_LINE_COLOR_COARSE: getArbitraryBaseColor(27), |
| | GRID_LINE_COLOR_FINE: getArbitraryBaseColor(18), |
| | CHECKERBOARD_PATTERN_DATAURL: TRANSPARENCY_CHECKERBOARD_PATTERN_DARK_DATAURL, |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export class CanvasBackgroundModule extends CanvasModuleBase { |
| | readonly type = 'background'; |
| | readonly id: string; |
| | readonly path: string[]; |
| | readonly parent: CanvasManager; |
| | readonly manager: CanvasManager; |
| | readonly log: Logger; |
| |
|
| | subscriptions = new Set<() => void>(); |
| | config: CanvasBackgroundModuleConfig = DEFAULT_CONFIG; |
| |
|
| | |
| | |
| | |
| | checkboardPattern = new Image(); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | konva: { |
| | layer: Konva.Layer; |
| | patternRect: Konva.Rect; |
| | linesGroup: Konva.Group; |
| | lines: Konva.Line[]; |
| | }; |
| |
|
| | constructor(manager: CanvasManager) { |
| | super(); |
| | this.id = getPrefixedId(this.type); |
| | this.manager = manager; |
| | this.parent = manager; |
| | this.path = this.manager.buildPath(this); |
| | this.log = this.manager.buildLogger(this); |
| |
|
| | this.log.debug('Creating module'); |
| |
|
| | this.konva = { |
| | layer: new Konva.Layer({ name: `${this.type}:layer`, listening: false, imageSmoothingEnabled: false }), |
| | linesGroup: new Konva.Group({ name: `${this.type}:linesGroup` }), |
| | lines: [], |
| | patternRect: new Konva.Rect({ name: `${this.type}:patternRect`, perfectDrawEnabled: false }), |
| | }; |
| |
|
| | this.konva.layer.add(this.konva.patternRect); |
| | this.konva.layer.add(this.konva.linesGroup); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | this.subscriptions.add(this.manager.stage.$stageAttrs.listen(this.render)); |
| |
|
| | |
| | |
| | |
| | this.subscriptions.add(this.manager.stateApi.createStoreSubscription(selectDynamicGrid, this.render)); |
| | } |
| |
|
| | initialize = () => { |
| | this.log.debug('Initializing module'); |
| | this.checkboardPattern.onload = () => { |
| | this.konva.patternRect.fillPatternImage(this.checkboardPattern); |
| | this.render(); |
| | }; |
| | this.checkboardPattern.src = $authToken.get() ? 'use-credentials' : 'anonymous'; |
| | this.checkboardPattern.src = this.config.CHECKERBOARD_PATTERN_DATAURL; |
| | this.render(); |
| | }; |
| |
|
| | |
| | |
| | |
| | render = () => { |
| | const dynamicGrid = this.manager.stateApi.runSelector(selectDynamicGrid); |
| |
|
| | if (!dynamicGrid) { |
| | this.konva.linesGroup.visible(false); |
| | const patternScale = this.manager.stage.unscale(1); |
| | this.konva.patternRect.setAttrs({ |
| | visible: true, |
| | ...this.manager.stage.getScaledStageRect(), |
| | fillPatternScaleX: patternScale, |
| | fillPatternScaleY: patternScale, |
| | }); |
| | return; |
| | } |
| |
|
| | this.konva.linesGroup.visible(true); |
| | this.konva.patternRect.visible(false); |
| |
|
| | const scale = this.manager.stage.getScale(); |
| | const { x, y } = this.manager.stage.getPosition(); |
| | const { width, height } = this.manager.stage.getSize(); |
| | const gridSpacing = CanvasBackgroundModule.getGridSpacing(scale); |
| | const stageRect = { |
| | x1: 0, |
| | y1: 0, |
| | x2: width, |
| | y2: height, |
| | }; |
| |
|
| | const gridOffset = { |
| | x: Math.ceil(x / scale / gridSpacing) * gridSpacing, |
| | y: Math.ceil(y / scale / gridSpacing) * gridSpacing, |
| | }; |
| |
|
| | const gridRect = { |
| | x1: -gridOffset.x, |
| | y1: -gridOffset.y, |
| | x2: width / scale - gridOffset.x + gridSpacing, |
| | y2: height / scale - gridOffset.y + gridSpacing, |
| | }; |
| |
|
| | const gridFullRect = { |
| | x1: Math.min(stageRect.x1, gridRect.x1), |
| | y1: Math.min(stageRect.y1, gridRect.y1), |
| | x2: Math.max(stageRect.x2, gridRect.x2), |
| | y2: Math.max(stageRect.y2, gridRect.y2), |
| | }; |
| |
|
| | |
| | const xSize = gridFullRect.x2 - gridFullRect.x1; |
| | const ySize = gridFullRect.y2 - gridFullRect.y1; |
| | |
| | const xSteps = Math.round(xSize / gridSpacing) + 1; |
| | const ySteps = Math.round(ySize / gridSpacing) + 1; |
| |
|
| | const strokeWidth = 1 / scale; |
| | let _x = 0; |
| | let _y = 0; |
| |
|
| | this.konva.linesGroup.destroyChildren(); |
| | this.konva.lines = []; |
| |
|
| | for (let i = 0; i < xSteps; i++) { |
| | _x = gridFullRect.x1 + i * gridSpacing; |
| | const line = new Konva.Line({ |
| | x: _x, |
| | y: gridFullRect.y1, |
| | points: [0, 0, 0, ySize], |
| | stroke: _x % 64 ? this.config.GRID_LINE_COLOR_FINE : this.config.GRID_LINE_COLOR_COARSE, |
| | strokeWidth, |
| | listening: false, |
| | perfectDrawEnabled: false, |
| | }); |
| | this.konva.lines.push(line); |
| | this.konva.linesGroup.add(line); |
| | } |
| | for (let i = 0; i < ySteps; i++) { |
| | _y = gridFullRect.y1 + i * gridSpacing; |
| | const line = new Konva.Line({ |
| | x: gridFullRect.x1, |
| | y: _y, |
| | points: [0, 0, xSize, 0], |
| | stroke: _y % 64 ? this.config.GRID_LINE_COLOR_FINE : this.config.GRID_LINE_COLOR_COARSE, |
| | strokeWidth, |
| | listening: false, |
| | perfectDrawEnabled: false, |
| | }); |
| | this.konva.lines.push(line); |
| | this.konva.linesGroup.add(line); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | static getGridSpacing = (scale: number): number => { |
| | if (scale >= 2) { |
| | return 8; |
| | } |
| | if (scale >= 1 && scale < 2) { |
| | return 16; |
| | } |
| | if (scale >= 0.5 && scale < 1) { |
| | return 32; |
| | } |
| | if (scale >= 0.25 && scale < 0.5) { |
| | return 64; |
| | } |
| | if (scale >= 0.125 && scale < 0.25) { |
| | return 128; |
| | } |
| | return 256; |
| | }; |
| |
|
| | repr = () => { |
| | return { |
| | id: this.id, |
| | type: this.type, |
| | path: this.path, |
| | config: this.config, |
| | }; |
| | }; |
| |
|
| | destroy = () => { |
| | this.log.trace('Destroying module'); |
| | this.subscriptions.forEach((unsubscribe) => unsubscribe()); |
| | this.subscriptions.clear(); |
| | this.konva.layer.destroy(); |
| | }; |
| | } |
| |
|