{"version":3,"file":"browser-image-compression.js","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('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}\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}\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}\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}\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} - 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}","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}\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}\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","require","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":";;;;;;;4pBAEA,kCAA2CA,gBAAAA,OAAAC,QAAAC,gCAC9BC,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,0HAMnBD,iBAAArB,mBAAsCA,mBACtCqB,iBAAAnB,oBACAmB,iBAAAE,oCACAF,iBAAAG,iBAAAA,iBACAH,iBAAAI,0BACAJ,iBAAAK,sCACAL,iBAAAM,8CACAN,iBAAAJ,sBAAAA"}