import type { TransformMatrix, TransformState, TransformBounds, TransformHandle, } from '@/types/transform'; /** * Create an identity transform matrix */ export function createIdentityMatrix(): TransformMatrix { return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0, }; } /** * Create a translation matrix */ export function createTranslationMatrix(x: number, y: number): TransformMatrix { return { a: 1, b: 0, c: 0, d: 1, e: x, f: y, }; } /** * Create a scale matrix */ export function createScaleMatrix( scaleX: number, scaleY: number ): TransformMatrix { return { a: scaleX, b: 0, c: 0, d: scaleY, e: 0, f: 0, }; } /** * Create a rotation matrix */ export function createRotationMatrix(degrees: number): TransformMatrix { const radians = (degrees * Math.PI) / 180; const cos = Math.cos(radians); const sin = Math.sin(radians); return { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0, }; } /** * Multiply two transform matrices */ export function multiplyMatrices( m1: TransformMatrix, m2: TransformMatrix ): TransformMatrix { return { a: m1.a * m2.a + m1.c * m2.b, b: m1.b * m2.a + m1.d * m2.b, c: m1.a * m2.c + m1.c * m2.d, d: m1.b * m2.c + m1.d * m2.d, e: m1.a * m2.e + m1.c * m2.f + m1.e, f: m1.b * m2.e + m1.d * m2.f + m1.f, }; } /** * Create a transform matrix from transform state */ export function createTransformMatrix( state: TransformState, bounds: TransformBounds ): TransformMatrix { const centerX = bounds.x + bounds.width / 2; const centerY = bounds.y + bounds.height / 2; // Translate to origin let matrix = createTranslationMatrix(-centerX, -centerY); // Apply scale matrix = multiplyMatrices( matrix, createScaleMatrix(state.scaleX, state.scaleY) ); // Apply rotation if (state.rotation !== 0) { matrix = multiplyMatrices(matrix, createRotationMatrix(state.rotation)); } // Translate back and apply position offset matrix = multiplyMatrices( matrix, createTranslationMatrix(centerX + state.x, centerY + state.y) ); return matrix; } /** * Apply transform matrix to a point */ export function transformPoint( x: number, y: number, matrix: TransformMatrix ): { x: number; y: number } { return { x: matrix.a * x + matrix.c * y + matrix.e, y: matrix.b * x + matrix.d * y + matrix.f, }; } /** * Calculate transformed bounds */ export function getTransformedBounds( bounds: TransformBounds, state: TransformState ): TransformBounds { const matrix = createTransformMatrix(state, bounds); const corners = [ { x: bounds.x, y: bounds.y }, { x: bounds.x + bounds.width, y: bounds.y }, { x: bounds.x + bounds.width, y: bounds.y + bounds.height }, { x: bounds.x, y: bounds.y + bounds.height }, ]; const transformedCorners = corners.map((corner) => transformPoint(corner.x, corner.y, matrix) ); const xs = transformedCorners.map((c) => c.x); const ys = transformedCorners.map((c) => c.y); const minX = Math.min(...xs); const maxX = Math.max(...xs); const minY = Math.min(...ys); const maxY = Math.max(...ys); return { x: minX, y: minY, width: maxX - minX, height: maxY - minY, }; } /** * Get handle position for transform bounds */ export function getHandlePosition( handle: TransformHandle, bounds: TransformBounds, state: TransformState ): { x: number; y: number } { const matrix = createTransformMatrix(state, bounds); const handleMap: Record = { 'top-left': { x: bounds.x, y: bounds.y }, 'top-center': { x: bounds.x + bounds.width / 2, y: bounds.y }, 'top-right': { x: bounds.x + bounds.width, y: bounds.y }, 'middle-left': { x: bounds.x, y: bounds.y + bounds.height / 2 }, 'middle-right': { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2, }, 'bottom-left': { x: bounds.x, y: bounds.y + bounds.height }, 'bottom-center': { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height }, 'bottom-right': { x: bounds.x + bounds.width, y: bounds.y + bounds.height }, rotate: { x: bounds.x + bounds.width / 2, y: bounds.y - 30, }, }; const point = handleMap[handle]; return transformPoint(point.x, point.y, matrix); } /** * Check if a point is near a handle */ export function isNearHandle( x: number, y: number, handle: TransformHandle, bounds: TransformBounds, state: TransformState, threshold: number = 10 ): boolean { const handlePos = getHandlePosition(handle, bounds, state); const dx = x - handlePos.x; const dy = y - handlePos.y; const distance = Math.sqrt(dx * dx + dy * dy); return distance <= threshold; } /** * Get cursor for transform handle */ export function getHandleCursor(handle: TransformHandle, rotation: number): string { const cursors: Record = { 'top-left': 'nwse-resize', 'top-center': 'ns-resize', 'top-right': 'nesw-resize', 'middle-left': 'ew-resize', 'middle-right': 'ew-resize', 'bottom-left': 'nesw-resize', 'bottom-center': 'ns-resize', 'bottom-right': 'nwse-resize', rotate: 'crosshair', }; // TODO: Adjust cursor based on rotation return cursors[handle]; } /** * Calculate scale from handle drag */ export function calculateScaleFromHandle( handle: TransformHandle, dragStartX: number, dragStartY: number, dragCurrentX: number, dragCurrentY: number, bounds: TransformBounds, maintainAspectRatio: boolean ): { scaleX: number; scaleY: number } { const dx = dragCurrentX - dragStartX; const dy = dragCurrentY - dragStartY; let scaleX = 1; let scaleY = 1; switch (handle) { case 'top-left': scaleX = 1 - dx / bounds.width; scaleY = 1 - dy / bounds.height; break; case 'top-center': scaleY = 1 - dy / bounds.height; break; case 'top-right': scaleX = 1 + dx / bounds.width; scaleY = 1 - dy / bounds.height; break; case 'middle-left': scaleX = 1 - dx / bounds.width; break; case 'middle-right': scaleX = 1 + dx / bounds.width; break; case 'bottom-left': scaleX = 1 - dx / bounds.width; scaleY = 1 + dy / bounds.height; break; case 'bottom-center': scaleY = 1 + dy / bounds.height; break; case 'bottom-right': scaleX = 1 + dx / bounds.width; scaleY = 1 + dy / bounds.height; break; } if (maintainAspectRatio) { const scale = Math.max(Math.abs(scaleX), Math.abs(scaleY)); scaleX = scaleX < 0 ? -scale : scale; scaleY = scaleY < 0 ? -scale : scale; } return { scaleX, scaleY }; } /** * Calculate rotation from handle drag */ export function calculateRotationFromHandle( centerX: number, centerY: number, currentX: number, currentY: number ): number { const dx = currentX - centerX; const dy = currentY - centerY; const radians = Math.atan2(dy, dx); return (radians * 180) / Math.PI + 90; } /** * Apply transform to canvas */ export function applyTransformToCanvas( sourceCanvas: HTMLCanvasElement, state: TransformState, bounds: TransformBounds ): HTMLCanvasElement { const transformedBounds = getTransformedBounds(bounds, state); const canvas = document.createElement('canvas'); canvas.width = Math.ceil(transformedBounds.width); canvas.height = Math.ceil(transformedBounds.height); const ctx = canvas.getContext('2d'); if (!ctx) return canvas; ctx.save(); // Translate to account for new bounds ctx.translate(-transformedBounds.x, -transformedBounds.y); // Apply transform matrix const matrix = createTransformMatrix(state, bounds); ctx.transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f); // Draw source canvas ctx.drawImage(sourceCanvas, 0, 0); ctx.restore(); return canvas; }