diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..12ca8a0 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,216 @@ +// Core type definitions for 2eInk + +export type GreyLevel = 2 | 4 | 8 | 16 | 256; +export type OutputFormat = 'png' | 'jpeg'; +export type ImageStatus = 'pending' | 'processing' | 'complete' | 'error'; + +export interface Dimensions { + readonly width: number; + readonly height: number; +} + +export interface Device { + readonly id: string; + readonly name: string; + readonly brand: string; + readonly width: number; + readonly height: number; + readonly greyLevels: GreyLevel; + readonly outputFormat: OutputFormat; +} + +export interface ImageEntry { + readonly id: string; + readonly file: File; + readonly filename: string; + originalDimensions: Dimensions | null; + originalDataUrl: string | null; + processedBlob: Blob | null; + processedDataUrl: string | null; + status: ImageStatus; + error: string | null; + pipelineOverride: PipelineConfig | null; +} + +export interface CustomDeviceFormData { + name: string; + width: number; + height: number; + greyLevels: GreyLevel; + outputFormat: OutputFormat; +} + +export const CONSTRAINTS = { + MAX_FILE_SIZE_MB: 25, + MAX_FILE_SIZE_BYTES: 25 * 1024 * 1024, + MIN_DIMENSION: 100, + MAX_DIMENSION: 5000, + MAX_DEVICE_NAME_LENGTH: 50, + SUPPORTED_FORMATS: ['image/jpeg', 'image/png', 'image/webp'], + GREY_LEVEL_OPTIONS: [2, 4, 8, 16, 256], + OUTPUT_FORMAT_OPTIONS: ['png', 'jpeg'] +} as const; + +export function isSupportedFormat(file: File): boolean { + return CONSTRAINTS.SUPPORTED_FORMATS.includes( + file.type as (typeof CONSTRAINTS.SUPPORTED_FORMATS)[number] + ); +} + +export function getExtension(format: OutputFormat): string { + return format === 'jpeg' ? 'jpg' : 'png'; +} + +// Pipeline Configuration + +export type CropMode = 'center' | 'top' | 'bottom' | 'manual'; +export type ResizeMode = 'cover' | 'contain' | 'stretch'; +export type GreyscaleMethod = 'luminosity' | 'average' | 'lightness' | 'red' | 'green' | 'blue'; +export type DitherAlgorithm = 'floyd-steinberg' | 'ordered' | 'atkinson'; + +export interface CropRegion { + x: number; + y: number; + width: number; + height: number; +} + +export interface CropConfig { + enabled: boolean; + mode: CropMode; + region?: CropRegion; +} + +export interface ResizeConfig { + enabled: boolean; + mode: ResizeMode; +} + +export interface BrightnessConfig { + enabled: boolean; + value: number; +} + +export interface ContrastConfig { + enabled: boolean; + value: number; +} + +export interface GammaConfig { + enabled: boolean; + value: number; +} + +export interface AutoLevelsConfig { + enabled: boolean; + clipPercent: number; +} + +export interface GreyscaleConfig { + enabled: boolean; + method: GreyscaleMethod; +} + +export interface SharpenConfig { + enabled: boolean; + amount: number; +} + +export interface QuantizeConfig { + enabled: boolean; + levels: number; +} + +export interface DitherConfig { + enabled: boolean; + algorithm: DitherAlgorithm; +} + +export interface PipelineConfig { + crop: CropConfig; + resize: ResizeConfig; + brightness: BrightnessConfig; + contrast: ContrastConfig; + gamma: GammaConfig; + autoLevels: AutoLevelsConfig; + greyscale: GreyscaleConfig; + sharpen: SharpenConfig; + quantize: QuantizeConfig; + dither: DitherConfig; +} + +export interface PipelinePreset { + id: string; + name: string; + config: PipelineConfig; + isBuiltIn: boolean; +} + +export const DEFAULT_PIPELINE_CONFIG: PipelineConfig = { + crop: { enabled: true, mode: 'center' }, + resize: { enabled: true, mode: 'cover' }, + brightness: { enabled: false, value: 0 }, + contrast: { enabled: true, value: 10 }, + gamma: { enabled: false, value: 1.0 }, + autoLevels: { enabled: false, clipPercent: 0.5 }, + greyscale: { enabled: true, method: 'luminosity' }, + sharpen: { enabled: false, amount: 0 }, + quantize: { enabled: true, levels: 16 }, + dither: { enabled: true, algorithm: 'floyd-steinberg' } +}; + +export const BUILT_IN_PRESETS: PipelinePreset[] = [ + { + id: 'default', + name: 'Default', + config: DEFAULT_PIPELINE_CONFIG, + isBuiltIn: true + }, + { + id: 'high-contrast', + name: 'High Contrast', + config: { + ...DEFAULT_PIPELINE_CONFIG, + contrast: { enabled: true, value: 30 }, + brightness: { enabled: true, value: 5 }, + gamma: { enabled: true, value: 1.2 } + }, + isBuiltIn: true + }, + { + id: 'no-dither', + name: 'No Dither', + config: { + ...DEFAULT_PIPELINE_CONFIG, + dither: { enabled: false, algorithm: 'floyd-steinberg' }, + contrast: { enabled: false, value: 0 }, + gamma: { enabled: false, value: 1.0 } + }, + isBuiltIn: true + }, + { + id: 'photo', + name: 'Photo (256 levels)', + config: { + ...DEFAULT_PIPELINE_CONFIG, + quantize: { enabled: false, levels: 256 }, + dither: { enabled: false, algorithm: 'floyd-steinberg' }, + contrast: { enabled: true, value: 5 }, + gamma: { enabled: true, value: 1.05 } + }, + isBuiltIn: true + }, + { + id: 'atkinson', + name: 'Atkinson Dither', + config: { + ...DEFAULT_PIPELINE_CONFIG, + dither: { enabled: true, algorithm: 'atkinson' } + }, + isBuiltIn: true + } +]; + +export function clonePipelineConfig(config: PipelineConfig): PipelineConfig { + return JSON.parse(JSON.stringify(config)); +}