{"version":3,"file":"media.cjs","names":[],"sources":["../src/media/blob-to-base64.ts","../src/media/blob-to-url.ts","../src/media/decode-data-url.ts","../src/media/file-to-blob.ts","../src/media/image-to-blob.ts","../src/media/is-base64-image.ts","../src/media/is-http-url.ts","../src/media/render-image.ts","../src/media/rotate-image.ts"],"sourcesContent":["/**\n * Reads a {@link Blob} as a **data URL** string (`data:<mime>;base64,...`) using {@link FileReader#readAsDataURL}.\n *\n * Useful for previewing uploads, embedding small assets inline, or serializing binary for APIs that\n * expect Base64-in-JSON. The result includes the MIME prefix, not raw Base64 alone — use\n * {@link decodeDataUrl} if you need the payload and type separately.\n *\n * @param blob - Any `Blob` or `File` (files are blobs).\n * @returns Resolves to the data URL string; rejects if reading fails.\n *\n * @example\n * ```ts\n * const dataUrl = await blobToBase64(file);\n * previewImg.src = dataUrl;\n * ```\n *\n * @example\n * ```ts\n * const fromFetch = await fetch('/api/export').then((r) => r.blob());\n * const dataUrl = await blobToBase64(fromFetch);\n * ```\n */\nexport function blobToBase64(blob: Blob): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onloadend = () => resolve(reader.result as string);\n    reader.onerror = reject;\n    reader.readAsDataURL(blob);\n  });\n}\n","/**\n * If `urlOrBlob` is already a string, returns it unchanged. If it is a {@link Blob}, returns\n * `URL.createObjectURL(blob)` — a short-lived `blob:` URL valid in this document until\n * {@link URL.revokeObjectURL} is called.\n *\n * Pair with {@link renderImage} or `<img src>` without re-fetching binary data. **Remember to\n * `revokeObjectURL`** when the URL is no longer needed to avoid retaining blob memory.\n *\n * @param urlOrBlob - Remote/http(s) URL string, data URL, or a `Blob` / `File`.\n * @returns A string suitable for `HTMLImageElement.src` or similar.\n *\n * @example\n * ```ts\n * const src = blobToUrl(uploadedFile);\n * img.src = src;\n * // when done:\n * URL.revokeObjectURL(src);\n * ```\n *\n * @example\n * ```ts\n * blobToUrl('https://cdn.example.com/logo.png'); // passed through as-is\n * ```\n */\nexport const blobToUrl = (urlOrBlob: string | Blob) =>\n  urlOrBlob instanceof Blob ? URL.createObjectURL(urlOrBlob) : urlOrBlob;\n","interface DecodedDataUrl {\n  mimeType?: string;\n  data?: string;\n}\n\n/**\n * Parses a `data:` URL of the form `data:<mime>;base64,<payload>` into its MIME type and raw\n * Base64 body (without the `data:...;base64,` prefix). Non-matching strings yield `undefined`\n * fields in the result object.\n *\n * @param url - Full data URL string.\n * @returns `{ mimeType, data }` — both optional when the regex does not match.\n *\n * @example\n * ```ts\n * const { mimeType, data } = decodeDataUrl('data:image/png;base64,iVBORw0KGgo=');\n * // mimeType === 'image/png', data === 'iVBORw0KGgo='\n * ```\n *\n * @example\n * ```ts\n * decodeDataUrl('not-a-data-url'); // { mimeType: undefined, data: undefined }\n * ```\n */\nexport function decodeDataUrl(url: string): DecodedDataUrl {\n  const regex = /^data:(.*);base64,\\s?(.*)$/;\n  const matches = new RegExp(regex).exec(url);\n\n  return {\n    mimeType: matches?.[1],\n    data: matches?.[2],\n  };\n}\n","/**\n * Creates a new {@link Blob} from a {@link File}, copying the bytes and keeping `file.type` as\n * the blob's MIME type. Handy when an API accepts `Blob` but you only have `File`, or you want a\n * plain blob without the `File` name/lastModified metadata.\n *\n * @param file - Source file from an `<input type=\"file\">` or drag-and-drop.\n * @returns A `Blob` with the same content and `type` as the file.\n *\n * @example\n * ```ts\n * const blob = fileToBlob(fileFromInput);\n * await uploadEndpoint(blob);\n * ```\n *\n * @example\n * ```ts\n * const blob = fileToBlob(imageFile);\n * const url = URL.createObjectURL(blob);\n * ```\n */\nexport const fileToBlob = (file: File) => {\n  return new Blob([file], { type: file.type });\n};\n","/**\n * Draws an {@link HTMLImageElement} onto an offscreen canvas, then builds a {@link Blob} from the\n * raster (via `canvas.toDataURL` + binary decode). Dimensions use `naturalWidth` / `naturalHeight`,\n * falling back to `300×300` if those are zero (e.g. not yet decoded).\n *\n * **CORS:** if the image is cross-origin without proper CORS headers, the canvas may be\n * *tainted* and `toDataURL` can throw — same browser rules as any canvas export.\n *\n * @param imageElement - Loaded image (`complete` / decoded recommended).\n * @param mimeType - Output MIME type, e.g. `'image/png'` (default) or `'image/jpeg'`. Encoder support is browser-dependent.\n * @returns Encoded image as a `Blob` with a matching `type` when possible.\n *\n * @example\n * ```ts\n * const img = await renderImage('/photo.jpg');\n * const jpegBlob = imageToBlob(img, 'image/jpeg');\n * ```\n *\n * @example\n * ```ts\n * const pngBlob = imageToBlob(cachedImg); // default image/png\n * ```\n */\nexport const imageToBlob = (\n  imageElement: HTMLImageElement,\n  mimeType: string = 'image/png',\n) => {\n  const canvas = document.createElement('canvas');\n\n  canvas.width = imageElement.naturalWidth || 300;\n  canvas.height = imageElement.naturalHeight || 300;\n\n  canvas.getContext('2d')!.drawImage(imageElement, 0, 0);\n\n  const dataUri = canvas.toDataURL(mimeType, 1);\n  const base64data = dataUri.split(',')[1];\n  const base64MimeType = dataUri.split(';')[0].slice(5);\n\n  const bytes = globalThis.atob(base64data);\n  const buf = new ArrayBuffer(bytes.length);\n  const array = new Uint8Array(buf);\n\n  for (let index = 0; index < bytes.length; index++) {\n    array[index] = bytes.charCodeAt(index);\n  }\n\n  const blob = new Blob([array], { type: base64MimeType });\n\n  return blob;\n};\n","import { decodeDataUrl } from './decode-data-url.js';\n\n/**\n * Returns `true` when `str` is a data URL that {@link decodeDataUrl} can parse **and** the MIME\n * type starts with `image/` (e.g. `image/png`, `image/jpeg`). Requires both a non-empty Base64\n * payload and an image MIME — arbitrary `data:text/plain;base64,...` is `false`.\n *\n * @param str - Candidate string (often `img.src` or API payload).\n *\n * @example\n * ```ts\n * isBase64Image('data:image/png;base64,iVBORw0KGgo='); // true\n * ```\n *\n * @example\n * ```ts\n * isBase64Image('https://example.com/x.png'); // false\n * isBase64Image('data:text/plain;base64,SGk='); // false\n * ```\n */\nexport const isBase64Image = (str: string): boolean => {\n  const { mimeType, data } = decodeDataUrl(str);\n  return !!(data && mimeType?.startsWith('image/'));\n};\n","/**\n * Returns `true` if `url` starts with `https://` or `http://` (case-sensitive, as in\n * `String#startsWith`). Does not validate that the rest is a well-formed URL — only the scheme\n * prefix. `blob:`, `data:`, and relative paths return `false`.\n *\n * @param url - String to test (often a user-provided or configured href).\n *\n * @example\n * ```ts\n * isHttpUrl('https://example.com/path'); // true\n * isHttpUrl('http://localhost:3000'); // true\n * ```\n *\n * @example\n * ```ts\n * isHttpUrl('//cdn.example.com'); // false — no explicit http(s) prefix\n * isHttpUrl('/assets/logo.png'); // false — relative path\n * ```\n */\nexport const isHttpUrl = (url: string): boolean => {\n  return url.startsWith('https://') || url.startsWith('http://');\n};\n","import { blobToUrl } from './blob-to-url.js';\n\n/**\n * Loads a resource into a new `HTMLImageElement`: `src` is set via {@link blobToUrl} (so blobs get\n * object URLs, strings are used directly). Resolves on `load` with the same element; rejects on\n * `error` (e.g. bad URL, network failure, corrupt image) **with no rejection value**.\n *\n * Does not add the node to the DOM — use the returned element for canvas, measuring, or\n * {@link imageToBlob}.\n *\n * @param urlOrBlob - Remote URL, data URL, or `Blob` / `File`.\n * @returns Promise that fulfills with the loaded `HTMLImageElement`.\n *\n * @example\n * ```ts\n * const img = await renderImage('https://example.com/pic.png');\n * document.body.appendChild(img);\n * ```\n *\n * @example\n * ```ts\n * const img = await renderImage(pickedFile);\n * const blob = imageToBlob(img, 'image/webp');\n * ```\n */\nexport const renderImage = (urlOrBlob: Blob | string) =>\n  new Promise<HTMLImageElement>((resolve, reject) => {\n    const image = new Image();\n    image.src = blobToUrl(urlOrBlob);\n    image.onload = () => resolve(image);\n    image.onerror = () => reject();\n  });\n","import { degToRad } from 'yummies/math';\nimport { renderImage } from './render-image.js';\n\nfunction cropImageFromCanvas(context: CanvasRenderingContext2D) {\n  const canvas = context.canvas;\n  let w = canvas.width;\n  let h = canvas.height;\n  const pix: { x: number[]; y: number[] } = { x: [], y: [] };\n  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);\n  let x: number;\n  let y: number;\n  let index: number;\n\n  for (y = 0; y < h; y++) {\n    for (x = 0; x < w; x++) {\n      index = (y * w + x) * 4;\n      if (imageData.data[index + 3] > 0) {\n        pix.x.push(x);\n        pix.y.push(y);\n      }\n    }\n  }\n  pix.x.sort((a, b) => a - b);\n  pix.y.sort((a, b) => a - b);\n  const n = pix.x.length - 1;\n\n  w = 1 + pix.x[n] - pix.x[0];\n  h = 1 + pix.y[n] - pix.y[0];\n  const cut = context.getImageData(pix.x[0], pix.y[0], w, h);\n\n  canvas.width = w;\n  canvas.height = h;\n  context.putImageData(cut, 0, 0);\n  return canvas;\n}\n\n// TODO: ломает iphone с огромными изображениями\n/**\n * Rotates `image` around its center on a square canvas (side = max(width, height)), then **crops**\n * transparent margins by trimming to the bounding box of pixels with non-zero alpha.\n * Returns a **new** loaded `HTMLImageElement` (PNG data URL under the hood via {@link renderImage}).\n *\n * `angle` is in **degrees**; converted with {@link degToRad} from `yummies/math`.\n *\n * Very large sources can stress memory on some mobile browsers (known iPhone issues — see TODO in source).\n *\n * @param image - Source image (should be decoded; uses `width` / `height`).\n * @param angle - Rotation in degrees (e.g. `90` for quarter turn).\n * @returns Promise of a new `HTMLImageElement` showing the rotated, cropped result.\n *\n * @example\n * ```ts\n * const upright = await rotateImage(landscapeImg, 90);\n * ```\n *\n * @example\n * ```ts\n * const fixed = await rotateImage(await renderImage(file), -15);\n * ```\n */\nexport const rotateImage = (image: HTMLImageElement, angle: number) => {\n  const maxSize = Math.max(image.width, image.height);\n  const canvas = document.createElement('canvas');\n  canvas.width = maxSize;\n  canvas.height = maxSize;\n  const context = canvas.getContext('2d')!;\n  context.save();\n  context.translate(canvas.width / 2, canvas.height / 2);\n  context.rotate(degToRad(angle));\n  context.drawImage(image, -image.width / 2, -image.height / 2);\n  context.restore();\n  cropImageFromCanvas(context);\n  return renderImage(canvas.toDataURL('image/png'));\n};\n"],"mappings":"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"}