{"version":3,"file":"jimp.mjs","names":[],"sources":["../../../../src/batteries/media/engines/jimp.ts"],"sourcesContent":["/**\n * A cross-environment image {@link @nhtio/adk/batteries/media/contracts!MediaEngine}\n * backed by Jimp (pure JavaScript — no native bindings, no binaries).\n *\n * @module @nhtio/adk/batteries/media/engines/jimp\n *\n * @remarks\n * The local-by-default raster engine: runs anywhere TypeScript runs. Declares one mutate\n * capability over png/jpeg/bmp/gif/tiff with resize (cover/contain approximations), rotate,\n * flip, quality, and metadata stripping (Jimp re-encodes pixels, so EXIF never survives —\n * `strip_metadata` is inherently satisfied), plus a generation edge: `EMPTY_MIME` → a blank\n * 1024×1024 white canvas in any supported encoding (resize in the same statement —\n * `empty:png | image resize width=64`). For webp/avif output or native-speed processing,\n * compose a sharp-backed engine instead — the capability declaration is the seam.\n *\n * `jimp` is an optional peer dependency, lazily imported on first actual use (constructing\n * the engine — and therefore the pipeline — never loads it).\n */\n\nimport { EMPTY_MIME } from '../contracts'\nimport { isError } from '@nhtio/adk/guards'\nimport { E_INVALID_MEDIA_PIPELINE_CONFIG } from '../exceptions'\nimport type * as JimpNS from 'jimp'\nimport type {\n  MediaEngine,\n  MutateRequest,\n  EngineBytesResult,\n  ConvertRequest,\n  ConvertResult,\n} from '../contracts'\n\ntype JimpModule = typeof JimpNS\n\n/** Options for {@link jimpEngine}. */\nexport interface JimpEngineOptions {\n  /** Override the module resolution (tests / custom builds). Default: `import('jimp')`. */\n  jimp?: () => JimpModule | Promise<JimpModule>\n}\n\nconst SUPPORTED_OUTPUT = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'tiff'] as const\n\nconst MIME_BY_FORMAT: Record<string, string> = {\n  png: 'image/png',\n  jpg: 'image/jpeg',\n  jpeg: 'image/jpeg',\n  bmp: 'image/bmp',\n  gif: 'image/gif',\n  tiff: 'image/tiff',\n}\n\n/**\n * Construct the Jimp-backed image engine.\n *\n * @param options - Optional module resolver override.\n * @returns The engine.\n */\nexport const jimpEngine = (options: JimpEngineOptions = {}): MediaEngine => {\n  let modPromise: Promise<JimpModule> | undefined\n  const getJimp = (): Promise<JimpModule> => {\n    modPromise ??= Promise.resolve(options.jimp ? options.jimp() : import('jimp')).catch((err) => {\n      const detail = isError(err) ? err.message : String(err)\n      throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n        `the jimp engine could not load its peer dependency \"jimp\": ${detail} — install it (pnpm add jimp)`,\n      ])\n    })\n    return modPromise\n  }\n\n  const mutate = async (request: MutateRequest): Promise<EngineBytesResult> => {\n    const { Jimp } = await getJimp()\n    const image = await Jimp.read(\n      request.bytes.buffer.slice(\n        request.bytes.byteOffset,\n        request.bytes.byteOffset + request.bytes.byteLength\n      ) as ArrayBuffer\n    )\n\n    if (request.resize) {\n      const { width, height, fit } = request.resize\n      if (width !== undefined && height !== undefined) {\n        if (fit === 'cover') image.cover({ w: width, h: height })\n        else if (fit === 'contain') image.contain({ w: width, h: height })\n        else image.resize({ w: width, h: height })\n      } else if (width !== undefined) {\n        image.resize({ w: width })\n      } else if (height !== undefined) {\n        image.resize({ h: height })\n      }\n    }\n    if (request.rotate) {\n      image.rotate(request.rotate)\n    }\n    if (request.flip) {\n      image.flip({\n        horizontal: request.flip.horizontal === true,\n        vertical: request.flip.vertical === true,\n      })\n    }\n\n    const targetFormat = request.format?.to ?? mimeToFormat(request.mimeType)\n    const mimeType = MIME_BY_FORMAT[targetFormat]\n    if (!mimeType) {\n      throw new Error(\n        `jimp cannot encode \"${targetFormat}\"; supported: ${SUPPORTED_OUTPUT.join(', ')} (compose a sharp engine for webp/avif)`\n      )\n    }\n    const quality = request.format?.quality\n    const buffer =\n      mimeType === 'image/jpeg' && quality !== undefined\n        ? await image.getBuffer('image/jpeg', { quality })\n        : await image.getBuffer(mimeType as 'image/png')\n    return { bytes: new Uint8Array(buffer), mimeType }\n  }\n\n  // Generation: a blank 1024×1024 white canvas, encoded to the requested format.\n  const convert = async (request: ConvertRequest): Promise<ConvertResult> => {\n    const mimeType = MIME_BY_FORMAT[request.to]\n    if (!mimeType) {\n      throw new Error(\n        `jimp cannot generate \"${request.to}\"; supported: ${SUPPORTED_OUTPUT.join(', ')}`\n      )\n    }\n    const { Jimp } = await getJimp()\n    const image = new Jimp({ width: 1024, height: 1024, color: 0xffffffff })\n    const buffer = await image.getBuffer(mimeType as 'image/png')\n    return { outputs: [{ bytes: new Uint8Array(buffer), mimeType }] }\n  }\n\n  return {\n    id: 'jimp',\n    converts: [{ from: [EMPTY_MIME], to: SUPPORTED_OUTPUT, convert }],\n    mutates: [\n      {\n        over: ['image/png', 'image/jpeg', 'image/bmp', 'image/gif', 'image/tiff'],\n        ops: ['resize', 'rotate', 'flip', 'strip_metadata'],\n        encodes: SUPPORTED_OUTPUT,\n        mutate,\n      },\n    ],\n  }\n}\n\nconst mimeToFormat = (mimeType: string): string => {\n  const sub = mimeType.toLowerCase().split(';')[0].trim().split('/')[1] ?? 'png'\n  return sub === 'jpeg' ? 'jpg' : sub\n}\n"],"mappings":"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"}