| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- export type ClipboardImageResult = {
- png: Buffer
- originalWidth: number
- originalHeight: number
- width: number
- height: number
- }
- // Clipboard functions are macOS-only and only present in darwin binaries;
- // older/non-darwin binaries built before this addition won't export them.
- // Typed as optional so callers can guard. These property names appear only
- // in type-space here; all runtime property access lives in src/ behind
- // feature() so they tree-shake out of builds that don't want them.
- export type NativeModule = {
- processImage: (input: Buffer) => Promise<ImageProcessor>
- readClipboardImage?: (maxWidth: number, maxHeight: number) => ClipboardImageResult | null
- hasClipboardImage?: () => boolean
- }
- // Lazy: defers dlopen until first call. The .node binary links against
- // CoreGraphics/ImageIO on darwin; resolving that at module-eval time blocks
- // startup because imagePaste.ts pulls this into the REPL chunk via static
- // import. Same pattern as audio-capture-src/index.ts.
- let cachedModule: NativeModule | null = null
- let loadAttempted = false
- // Raw binding accessor. Callers that need optional exports (e.g. clipboard
- // functions) reach through this; keeping the wrappers on the caller side lets
- // feature() tree-shake the property access strings out of external builds.
- export function getNativeModule(): NativeModule | null {
- if (loadAttempted) return cachedModule
- loadAttempted = true
- try {
- // eslint-disable-next-line @typescript-eslint/no-require-imports
- cachedModule = require('../../image-processor.node')
- } catch {
- cachedModule = null
- }
- return cachedModule
- }
- interface ImageProcessor {
- metadata(): { width: number; height: number; format: string }
- resize(
- width: number,
- height: number,
- options?: { fit?: string; withoutEnlargement?: boolean },
- ): ImageProcessor
- jpeg(quality?: number): ImageProcessor
- png(options?: {
- compressionLevel?: number
- palette?: boolean
- colors?: number
- }): ImageProcessor
- webp(quality?: number): ImageProcessor
- toBuffer(): Promise<Buffer>
- }
- interface SharpInstance {
- metadata(): Promise<{ width: number; height: number; format: string }>
- resize(
- width: number,
- height: number,
- options?: { fit?: string; withoutEnlargement?: boolean },
- ): SharpInstance
- jpeg(options?: { quality?: number }): SharpInstance
- png(options?: {
- compressionLevel?: number
- palette?: boolean
- colors?: number
- }): SharpInstance
- webp(options?: { quality?: number }): SharpInstance
- toBuffer(): Promise<Buffer>
- }
- // Factory function that matches sharp's API
- export function sharp(input: Buffer): SharpInstance {
- let processorPromise: Promise<ImageProcessor> | null = null
- // Create a chain of operations
- const operations: Array<(proc: ImageProcessor) => void> = []
- // Track how many operations have been applied to avoid re-applying
- let appliedOperationsCount = 0
- // Get or create the processor (without applying operations)
- async function ensureProcessor(): Promise<ImageProcessor> {
- if (!processorPromise) {
- processorPromise = (async () => {
- const mod = getNativeModule()
- if (!mod) {
- throw new Error('Native image processor module not available')
- }
- return mod.processImage(input)
- })()
- }
- return processorPromise
- }
- // Apply any pending operations to the processor
- function applyPendingOperations(proc: ImageProcessor): void {
- for (let i = appliedOperationsCount; i < operations.length; i++) {
- const op = operations[i]
- if (op) {
- op(proc)
- }
- }
- appliedOperationsCount = operations.length
- }
- const instance: SharpInstance = {
- async metadata() {
- const proc = await ensureProcessor()
- return proc.metadata()
- },
- resize(
- width: number,
- height: number,
- options?: { fit?: string; withoutEnlargement?: boolean },
- ) {
- operations.push(proc => {
- proc.resize(width, height, options)
- })
- return instance
- },
- jpeg(options?: { quality?: number }) {
- operations.push(proc => {
- proc.jpeg(options?.quality)
- })
- return instance
- },
- png(options?: {
- compressionLevel?: number
- palette?: boolean
- colors?: number
- }) {
- operations.push(proc => {
- proc.png(options)
- })
- return instance
- },
- webp(options?: { quality?: number }) {
- operations.push(proc => {
- proc.webp(options?.quality)
- })
- return instance
- },
- async toBuffer() {
- const proc = await ensureProcessor()
- applyPendingOperations(proc)
- return proc.toBuffer()
- },
- }
- return instance
- }
- export default sharp
|