{"version":3,"file":"browser-image-compression.mjs","sources":["../lib/utils.js","../lib/image-compression.js","../lib/web-worker.js","../lib/index.js"],"sourcesContent":["// add support for cordova-plugin-file\nconst moduleMapper = typeof window !== 'undefined' && window.cordova && window.cordova.require && window.cordova.require('cordova/modulemapper');\nexport const CustomFile = (moduleMapper && moduleMapper.getOriginalSymbol(window, 'File')) || File;\nexport const CustomFileReader = (moduleMapper && moduleMapper.getOriginalSymbol(window, 'FileReader')) || FileReader;\n/**\n * getDataUrlFromFile\n *\n * @param {File} file\n * @returns {Promise<string>}\n */\nexport function getDataUrlFromFile (file) {\n  return new Promise((resolve, reject) => {\n    const reader = new CustomFileReader()\n    reader.onload = () => resolve(reader.result)\n    reader.onerror = (e) => reject(e)\n    reader.readAsDataURL(file)\n  })\n}\n\n/**\n * getFilefromDataUrl\n *\n * @param {string} dataurl\n * @param {string} filename\n * @param {number} [lastModified=Date.now()]\n * @returns {Promise<File|Blob>}\n */\nexport function getFilefromDataUrl (dataurl, filename, lastModified = Date.now()) {\n  return new Promise((resolve) => {\n    const arr = dataurl.split(',')\n    const mime = arr[0].match(/:(.*?);/)[1]\n    const bstr = atob(arr[1])\n    let n = bstr.length\n    const u8arr = new Uint8Array(n)\n    while (n--) {\n      u8arr[n] = bstr.charCodeAt(n)\n    }\n    const file = new Blob([u8arr], { type: mime })\n    file.name = filename\n    file.lastModified = lastModified\n    resolve(file)\n\n    // Safari has issue with File constructor not being able to POST in FormData\n    // https://github.com/Donaldcwl/browser-image-compression/issues/8\n    // https://bugs.webkit.org/show_bug.cgi?id=165081\n    // let file\n    // try {\n    //   file = new File([u8arr], filename, { type: mime }) // Edge do not support File constructor\n    // } catch (e) {\n    //   file = new Blob([u8arr], { type: mime })\n    //   file.name = filename\n    //   file.lastModified = lastModified\n    // }\n    // resolve(file)\n  })\n}\n\n/**\n * loadImage\n *\n * @param {string} src\n * @returns {Promise<HTMLImageElement>}\n */\nexport function loadImage (src) {\n  return new Promise((resolve, reject) => {\n    const img = new Image()\n    img.onload = () => resolve(img)\n    img.onerror = (e) => reject(e)\n    img.src = src\n  })\n}\n\n/**\n * drawImageInCanvas\n *\n * @param {HTMLImageElement} img\n * @returns {HTMLCanvasElement}\n */\nexport function drawImageInCanvas (img) {\n  const [canvas, ctx] = getNewCanvasAndCtx(img.width, img.height)\n  ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\n  return canvas\n}\n\n/**\n * drawFileInCanvas\n *\n * @param {File} file\n * @returns {Promise<[ImageBitmap | HTMLImageElement, HTMLCanvasElement]>}\n */\nexport async function drawFileInCanvas (file) {\n  let img\n  try {\n    img = await createImageBitmap(file)\n  } catch (e) {\n    const dataUrl = await getDataUrlFromFile(file)\n    img = await loadImage(dataUrl)\n  }\n  const canvas = drawImageInCanvas(img)\n  return [img, canvas]\n}\n\n/**\n * canvasToFile\n *\n * @param canvas\n * @param {string} fileType\n * @param {string} fileName\n * @param {number} fileLastModified\n * @param {number} [quality]\n * @returns {Promise<File|Blob>}\n */\nexport async function canvasToFile (canvas, fileType, fileName, fileLastModified, quality = 1) {\n  let file\n  if (typeof OffscreenCanvas === 'function' && canvas instanceof OffscreenCanvas) {\n    file = await canvas.convertToBlob({ type: fileType, quality })\n    file.name = fileName\n    file.lastModified = fileLastModified\n  } else {\n    const dataUrl = canvas.toDataURL(fileType, quality)\n    file = await getFilefromDataUrl(dataUrl, fileName, fileLastModified)\n  }\n  return file\n}\n\n/**\n * getExifOrientation\n * get image exif orientation info\n * source: https://stackoverflow.com/a/32490603/10395024\n *\n * @param {File} file\n * @returns {Promise<number>} - orientation id, see https://i.stack.imgur.com/VGsAj.gif\n */\nexport function getExifOrientation (file) {\n  return new Promise((resolve, reject) => {\n    const reader = new CustomFileReader()\n    reader.onload = (e) => {\n      const view = new DataView(e.target.result)\n      if (view.getUint16(0, false) != 0xFFD8) {\n        return resolve(-2)\n      }\n      const length = view.byteLength\n      let offset = 2\n      while (offset < length) {\n        if (view.getUint16(offset + 2, false) <= 8) return resolve(-1)\n        const marker = view.getUint16(offset, false)\n        offset += 2\n        if (marker == 0xFFE1) {\n          if (view.getUint32(offset += 2, false) != 0x45786966) {\n            return resolve(-1)\n          }\n\n          var little = view.getUint16(offset += 6, false) == 0x4949\n          offset += view.getUint32(offset + 4, little)\n          var tags = view.getUint16(offset, little)\n          offset += 2\n          for (var i = 0; i < tags; i++) {\n            if (view.getUint16(offset + (i * 12), little) == 0x0112) {\n              return resolve(view.getUint16(offset + (i * 12) + 8, little))\n            }\n          }\n        } else if ((marker & 0xFF00) != 0xFF00) {\n          break\n        } else {\n          offset += view.getUint16(offset, false)\n        }\n      }\n      return resolve(-1)\n    }\n    reader.onerror = (e) => reject(e)\n    reader.readAsArrayBuffer(file)\n  })\n}\n\n/**\n *\n * @param {HTMLCanvasElement} canvas\n * @param options\n * @returns {HTMLCanvasElement>}\n */\nexport function handleMaxWidthOrHeight (canvas, options) {\n  const width = canvas.width\n  const height = canvas.height\n  const maxWidthOrHeight = options.maxWidthOrHeight\n\n  const needToHandle = Number.isInteger(maxWidthOrHeight) && (width > maxWidthOrHeight || height > maxWidthOrHeight)\n\n  let newCanvas = canvas\n  let ctx\n\n  if (needToHandle) {\n    [newCanvas, ctx] = getNewCanvasAndCtx(width, height)\n    if (width > height) {\n      newCanvas.width = maxWidthOrHeight\n      newCanvas.height = (height / width) * maxWidthOrHeight\n    } else {\n      newCanvas.width = (width / height) * maxWidthOrHeight\n      newCanvas.height = maxWidthOrHeight\n    }\n    ctx.drawImage(canvas, 0, 0, newCanvas.width, newCanvas.height)\n  }\n\n  return newCanvas\n}\n\n/**\n * followExifOrientation\n * source: https://stackoverflow.com/a/40867559/10395024\n *\n * @param {HTMLCanvasElement} canvas\n * @param {number} exifOrientation\n * @returns {HTMLCanvasElement} canvas\n */\nexport function followExifOrientation (canvas, exifOrientation) {\n  const width = canvas.width\n  const height = canvas.height\n\n  const [newCanvas, ctx] = getNewCanvasAndCtx(width, height)\n\n  // set proper canvas dimensions before transform & export\n  if (4 < exifOrientation && exifOrientation < 9) {\n    newCanvas.width = height\n    newCanvas.height = width\n  } else {\n    newCanvas.width = width\n    newCanvas.height = height\n  }\n\n  // transform context before drawing image\n  switch (exifOrientation) {\n    case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;\n    case 3: ctx.transform(-1, 0, 0, -1, width, height); break;\n    case 4: ctx.transform(1, 0, 0, -1, 0, height); break;\n    case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;\n    case 6: ctx.transform(0, 1, -1, 0, height, 0); break;\n    case 7: ctx.transform(0, -1, -1, 0, height, width); break;\n    case 8: ctx.transform(0, -1, 1, 0, 0, width); break;\n    default: break;\n  }\n\n  ctx.drawImage(canvas, 0, 0, width, height)\n\n  return newCanvas\n}\n\n/**\n * get new Canvas and it's context\n * @param width\n * @param height\n * @returns {[HTMLCanvasElement, CanvasRenderingContext2D]}\n */\nexport function getNewCanvasAndCtx (width, height) {\n  let canvas\n  let ctx\n  try {\n    canvas = new OffscreenCanvas(width, height)\n    ctx = canvas.getContext('2d')\n  } catch (e) {\n    canvas = document.createElement('canvas')\n    ctx = canvas.getContext('2d')\n  }\n  canvas.width = width\n  canvas.height = height\n  return [canvas, ctx]\n}\n","import { canvasToFile, drawFileInCanvas, followExifOrientation, getExifOrientation, handleMaxWidthOrHeight, getNewCanvasAndCtx } from './utils'\n\n/**\n * Compress an image file.\n *\n * @param {File} file\n * @param {Object} options - { maxSizeMB=Number.POSITIVE_INFINITY, maxWidthOrHeight, useWebWorker=true, maxIteration = 10, exifOrientation }\n * @param {number} [options.maxSizeMB=Number.POSITIVE_INFINITY]\n * @param {number} [options.maxWidthOrHeight=undefined] * @param {number} [options.maxWidthOrHeight=undefined]\n * @param {number} [options.maxIteration=10]\n * @param {number} [options.exifOrientation=] - default to be the exif orientation from the image file\n * @returns {Promise<File | Blob>}\n */\nexport default async function compress (file, options) {\n  let remainingTrials = options.maxIteration || 10\n\n  const maxSizeByte = options.maxSizeMB * 1024 * 1024\n\n  // drawFileInCanvas\n  let [img, canvas] = await drawFileInCanvas(file)\n\n  // handleMaxWidthOrHeight\n  canvas = handleMaxWidthOrHeight(canvas, options)\n\n  // exifOrientation\n  options.exifOrientation = options.exifOrientation || await getExifOrientation(file)\n  canvas = followExifOrientation(canvas, options.exifOrientation)\n\n  let quality = 1\n\n  let tempFile = await canvasToFile(canvas, file.type, file.name, file.lastModified, quality)\n  // check if we need to compress or resize\n  if (tempFile.size <= maxSizeByte) {\n    // no need to compress\n    return tempFile\n  }\n\n  let compressedFile = tempFile\n  while (remainingTrials-- && compressedFile.size > maxSizeByte) {\n    const newWidth = canvas.width * 0.9\n    const newHeight = canvas.height * 0.9\n    const [newCanvas, ctx] = getNewCanvasAndCtx(newWidth, newHeight)\n\n    ctx.drawImage(canvas, 0, 0, newWidth, newHeight)\n\n    if (file.type === 'image/jpeg') {\n      quality *= 0.9\n    }\n    compressedFile = await canvasToFile(newCanvas, file.type, file.name, file.lastModified, quality)\n\n    canvas = newCanvas\n  }\n\n  return compressedFile\n}","import imageCompression from './index'\nimport compress from './image-compression'\nimport { getNewCanvasAndCtx } from './utils'\n\nlet cnt = 0\nlet imageCompressionLibUrl\n\nfunction createWorker (f) {\n  return new Worker(URL.createObjectURL(new Blob([`(${f})()`])))\n}\n\nconst worker = createWorker(() => {\n  let scriptImported = false\n  self.addEventListener('message', async (e) => {\n    const { file, id, imageCompressionLibUrl, options } = e.data\n    try {\n      if (!scriptImported) {\n        // console.log('[worker] importScripts', imageCompressionLibUrl)\n        importScripts(imageCompressionLibUrl)\n        scriptImported = true\n      }\n      // console.log('[worker] self', self)\n      const compressedFile = await imageCompression(file, options)\n      self.postMessage({ file: compressedFile, id })\n    } catch (e) {\n      // console.error('[worker] error', e)\n      self.postMessage({ error: e.message + '\\n' + e.stack, id })\n    }\n  })\n})\n\nfunction createSourceObject (str) {\n  return URL.createObjectURL(new Blob([str], { type: 'application/javascript' }))\n}\n\nexport function compressOnWebWorker (file, options) {\n  return new Promise(async (resolve, reject) => {\n    if (!imageCompressionLibUrl) {\n      imageCompressionLibUrl = createSourceObject(`\n    function imageCompression (){return (${imageCompression}).apply(null, arguments)}\n\n    imageCompression.getDataUrlFromFile = ${imageCompression.getDataUrlFromFile}\n    imageCompression.getFilefromDataUrl = ${imageCompression.getFilefromDataUrl}\n    imageCompression.loadImage = ${imageCompression.loadImage}\n    imageCompression.drawImageInCanvas = ${imageCompression.drawImageInCanvas}\n    imageCompression.drawFileInCanvas = ${imageCompression.drawFileInCanvas}\n    imageCompression.canvasToFile = ${imageCompression.canvasToFile}\n    imageCompression.getExifOrientation = ${imageCompression.getExifOrientation}\n    imageCompression.handleMaxWidthOrHeight = ${imageCompression.handleMaxWidthOrHeight}\n    imageCompression.followExifOrientation = ${imageCompression.followExifOrientation}\n\n    getDataUrlFromFile = imageCompression.getDataUrlFromFile\n    getFilefromDataUrl = imageCompression.getFilefromDataUrl\n    loadImage = imageCompression.loadImage\n    drawImageInCanvas = imageCompression.drawImageInCanvas\n    drawFileInCanvas = imageCompression.drawFileInCanvas\n    canvasToFile = imageCompression.canvasToFile\n    getExifOrientation = imageCompression.getExifOrientation\n    handleMaxWidthOrHeight = imageCompression.handleMaxWidthOrHeight\n    followExifOrientation = imageCompression.followExifOrientation\n\n    getNewCanvasAndCtx = ${getNewCanvasAndCtx}\n    \n    CustomFileReader = FileReader\n    \n    CustomFile = File\n    \n    function _slicedToArray(arr, n) { return arr }\n\n    function compress (){return (${compress}).apply(null, arguments)}\n    `)\n    }\n    let id = cnt++\n\n    function handler (e) {\n      if (e.data.id === id) {\n        worker.removeEventListener('message', handler)\n        if (e.data.error) {\n          reject(new Error(e.data.error))\n        }\n        resolve(e.data.file)\n      }\n    }\n\n    worker.addEventListener('message', handler)\n    worker.postMessage({ file, id, imageCompressionLibUrl, options })\n  })\n}","import compress from './image-compression'\nimport {\n  canvasToFile,\n  drawFileInCanvas,\n  drawImageInCanvas,\n  getDataUrlFromFile,\n  getFilefromDataUrl,\n  loadImage,\n  getExifOrientation,\n  handleMaxWidthOrHeight,\n  followExifOrientation,\n  CustomFile\n} from './utils'\nimport { compressOnWebWorker } from './web-worker'\n\n/**\n * Compress an image file.\n *\n * @param {File} file\n * @param {Object} options - { maxSizeMB=Number.POSITIVE_INFINITY, maxWidthOrHeight, useWebWorker=true, maxIteration = 10, exifOrientation }\n * @param {number} [options.maxSizeMB=Number.POSITIVE_INFINITY]\n * @param {number} [options.maxWidthOrHeight=undefined] * @param {number} [options.maxWidthOrHeight=undefined]\n * @param {boolean} [options.useWebWorker=true]\n * @param {number} [options.maxIteration=10]\n * @param {number} [options.exifOrientation=] - default to be the exif orientation from the image file\n * @returns {Promise<File | Blob>}\n */\nasync function imageCompression (file, options) {\n\n  let compressedFile\n\n  options.maxSizeMB = options.maxSizeMB || Number.POSITIVE_INFINITY\n  options.useWebWorker = typeof options.useWebWorker === 'boolean' ? options.useWebWorker : true\n\n  if (!(file instanceof Blob || file instanceof CustomFile)) {\n    throw new Error('The file given is not an instance of Blob or File')\n  } else if (!/^image/.test(file.type)) {\n    throw new Error('The file given is not an image')\n  }\n\n  // try run in web worker, fall back to run in main thread\n  const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope\n\n  // if (inWebWorker) {\n  //   console.log('run compression in web worker')\n  // } else {\n  //   console.log('run compression in main thread')\n  // }\n\n  if (options.useWebWorker && typeof Worker === 'function' && !inWebWorker) {\n    try {\n      // \"compressOnWebWorker\" is kind of like a recursion to call \"imageCompression\" again inside web worker\n      compressedFile = await compressOnWebWorker(file, options)\n    } catch (e) {\n      console.warn('Run compression in web worker failed:', e, ', fall back to main thread')\n      compressedFile = await compress(file, options)\n    }\n  } else {\n    compressedFile = await compress(file, options)\n  }\n\n  try {\n    compressedFile.name = file.name\n    compressedFile.lastModified = file.lastModified\n  } catch (e) {}\n\n  return compressedFile\n}\n\nimageCompression.getDataUrlFromFile = getDataUrlFromFile\nimageCompression.getFilefromDataUrl = getFilefromDataUrl\nimageCompression.loadImage = loadImage\nimageCompression.drawImageInCanvas = drawImageInCanvas\nimageCompression.drawFileInCanvas = drawFileInCanvas\nimageCompression.canvasToFile = canvasToFile\nimageCompression.getExifOrientation = getExifOrientation\nimageCompression.handleMaxWidthOrHeight = handleMaxWidthOrHeight\nimageCompression.followExifOrientation = followExifOrientation\n\nexport default imageCompression\n"],"names":["window","cordova","CustomFile","reader","onload","reject","file","getFilefromDataUrl","n","loadImage","fileType","view","result","length","offset","getUint32","marker","readAsArrayBuffer","width","height","canvas","options","ctx","maxWidthOrHeight","followExifOrientation","lastModified","cnt","compressOnWebWorker","imageCompression","name","drawImageInCanvas","drawFileInCanvas","canvasToFile","getExifOrientation","handleMaxWidthOrHeight"],"mappings":";;;;;;;sdAEA,kCAA2CA,gBAAAA,OAAAC,gEAC9BC,2NAyBXC,EAAAC,OAAA,oBAAgCD,wCACVE,sBACHC,cAUTC,0LAuBe,mCAGvBC,wnBA8CkBC,qcAOF,MAAAC,gZAmBdC,wBAAIC,yDAIJC,iBACO,uBAC8CC,EAAS,2CAC9B,oBAEhB,IACoB,wBAAvBA,GAAU,wDAMnBH,EAAAI,UAAWD,iCAEX,4GAMAE,gFASVb,EAAAc,kEAoBIC,IAAAA,MACAC,EAAIC,WACFC,kHASJC,mCAW6CC,YAE9BL,8BAMHE,EAAQ,wCAWNI,sBAAuBJ,mBACnCD,kKAOiCD,mQAiBvBE,sxBCvOmBd,SAAgBmB,kIAW3C,0HAQgBnB,kdCxCtB,MAAIoB,UAIJ,uoBASQC,yrBAmEeC,u+ECtBJC,mHAMnBD,iBAAArB,mBAAsCA,mBACtCqB,iBAAAnB,oBACAmB,iBAAAE,oCACAF,iBAAAG,iBAAAA,iBACAH,iBAAAI,0BACAJ,iBAAAK,sCACAL,iBAAAM,8CACAN,iBAAAJ,sBAAAA"}