mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-14 13:15:34 -05:00
* Add Rule of 4 composition guide to canvas settings and rendering Co-authored-by: kent <kent@invoke.ai> * Rename Rule of 4 Guide to Rule of Thirds in canvas composition guide Co-authored-by: kent <kent@invoke.ai> * Updates to comp guide and naming * Fix reference * Update translation keys and organize settings. * revert to previous canvas manager for conflict * Re-add composition guide. * Fix lint * prettier * feat(ui): improve markup in canvas settings popover * feat(ui): use brand colors for canvas rule of thirds guide --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
|
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
|
import { selectRuleOfThirds } from 'features/controlLayers/store/canvasSettingsSlice';
|
|
import { selectBbox } from 'features/controlLayers/store/selectors';
|
|
import Konva from 'konva';
|
|
import type { Logger } from 'roarr';
|
|
|
|
/**
|
|
* Renders the rule of thirds composition guide overlay on the canvas.
|
|
* The guide shows a 3x3 grid within the bounding box to help with composition.
|
|
*/
|
|
export class CanvasCompositionGuideModule extends CanvasModuleBase {
|
|
readonly type = 'composition_guide';
|
|
readonly id: string;
|
|
readonly path: string[];
|
|
readonly parent: CanvasManager;
|
|
readonly manager: CanvasManager;
|
|
readonly log: Logger;
|
|
|
|
subscriptions: Set<() => void> = new Set();
|
|
|
|
/**
|
|
* The Konva objects that make up the composition guide:
|
|
* - A group to hold all the guide lines
|
|
* - Individual line objects for the rule of thirds grid
|
|
*/
|
|
konva: {
|
|
group: Konva.Group;
|
|
verticalLine1: Konva.Line;
|
|
verticalLine2: Konva.Line;
|
|
horizontalLine1: Konva.Line;
|
|
horizontalLine2: Konva.Line;
|
|
};
|
|
|
|
constructor(manager: CanvasManager) {
|
|
super();
|
|
this.id = getPrefixedId(this.type);
|
|
this.parent = manager;
|
|
this.manager = manager;
|
|
this.path = this.manager.buildPath(this);
|
|
this.log = this.manager.buildLogger(this);
|
|
|
|
this.log.debug('Creating composition guide module');
|
|
|
|
this.konva = {
|
|
group: new Konva.Group({
|
|
name: `${this.type}:group`,
|
|
listening: false,
|
|
perfectDrawEnabled: false,
|
|
}),
|
|
verticalLine1: new Konva.Line({
|
|
name: `${this.type}:vertical_line_1`,
|
|
listening: false,
|
|
stroke: 'hsl(220 12% 90% / 0.9)',
|
|
strokeWidth: 1,
|
|
strokeScaleEnabled: false,
|
|
perfectDrawEnabled: false,
|
|
dash: [5, 5],
|
|
}),
|
|
verticalLine2: new Konva.Line({
|
|
name: `${this.type}:vertical_line_2`,
|
|
listening: false,
|
|
stroke: 'hsl(220 12% 90% / 0.9)',
|
|
strokeWidth: 1,
|
|
strokeScaleEnabled: false,
|
|
perfectDrawEnabled: false,
|
|
dash: [5, 5],
|
|
}),
|
|
horizontalLine1: new Konva.Line({
|
|
name: `${this.type}:horizontal_line_1`,
|
|
listening: false,
|
|
stroke: 'hsl(220 12% 90% / 0.9)',
|
|
strokeWidth: 1,
|
|
strokeScaleEnabled: false,
|
|
perfectDrawEnabled: false,
|
|
dash: [5, 5],
|
|
}),
|
|
horizontalLine2: new Konva.Line({
|
|
name: `${this.type}:horizontal_line_2`,
|
|
listening: false,
|
|
stroke: 'hsl(220 12% 90% / 0.9)',
|
|
strokeWidth: 1,
|
|
strokeScaleEnabled: false,
|
|
perfectDrawEnabled: false,
|
|
dash: [5, 5],
|
|
}),
|
|
};
|
|
|
|
this.konva.group.add(this.konva.verticalLine1);
|
|
this.konva.group.add(this.konva.verticalLine2);
|
|
this.konva.group.add(this.konva.horizontalLine1);
|
|
this.konva.group.add(this.konva.horizontalLine2);
|
|
|
|
// Listen for changes to the rule of thirds guide setting
|
|
this.subscriptions.add(this.manager.stateApi.createStoreSubscription(selectRuleOfThirds, this.render));
|
|
|
|
// Listen for changes to the bbox to update guide positioning
|
|
this.subscriptions.add(this.manager.stateApi.createStoreSubscription(selectBbox, this.render));
|
|
}
|
|
|
|
initialize = () => {
|
|
this.log.debug('Initializing composition guide module');
|
|
this.render();
|
|
};
|
|
|
|
/**
|
|
* Renders the composition guide. The guide is only visible when the setting is enabled.
|
|
*/
|
|
render = () => {
|
|
const ruleOfThirds = this.manager.stateApi.getSettings().ruleOfThirds;
|
|
const { x, y, width, height } = this.manager.stateApi.runSelector(selectBbox).rect;
|
|
|
|
this.konva.group.visible(ruleOfThirds);
|
|
|
|
if (!ruleOfThirds) {
|
|
return;
|
|
}
|
|
|
|
// Calculate the thirds positions of the bounding box
|
|
const oneThirdX = x + width / 3;
|
|
const twoThirdsX = x + (2 * width) / 3;
|
|
const oneThirdY = y + height / 3;
|
|
const twoThirdsY = y + (2 * height) / 3;
|
|
|
|
// Update the vertical lines (divide the bbox into thirds vertically)
|
|
this.konva.verticalLine1.points([oneThirdX, y, oneThirdX, y + height]);
|
|
this.konva.verticalLine2.points([twoThirdsX, y, twoThirdsX, y + height]);
|
|
|
|
// Update the horizontal lines (divide the bbox into thirds horizontally)
|
|
this.konva.horizontalLine1.points([x, oneThirdY, x + width, oneThirdY]);
|
|
this.konva.horizontalLine2.points([x, twoThirdsY, x + width, twoThirdsY]);
|
|
};
|
|
|
|
destroy = () => {
|
|
this.log.debug('Destroying composition guide module');
|
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
this.subscriptions.clear();
|
|
this.konva.group.destroy();
|
|
};
|
|
|
|
repr = () => {
|
|
return {
|
|
id: this.id,
|
|
type: this.type,
|
|
path: this.path,
|
|
visible: this.konva.group.visible(),
|
|
};
|
|
};
|
|
}
|