function createPngDataTable ( ) { const crcTable = new Int32Array ( 256 ) ; for ( let n = 0 ; n < 256 ; n ++ ) { let c = n ; for ( let k = 0 ; k < 8 ; k ++ ) { c = ( c & 1 ) ? 0xedb88320 ^ ( c >>> 1 ) : c >>> 1 ; } crcTable [ n ] = c ; } return crcTable ; } let pngDataTable ; function calcCrc ( buf ) { let c = - 1 ; if ( ! pngDataTable ) { pngDataTable = createPngDataTable ( ) ; } for ( let n = 0 ; n < buf . length ; n ++ ) { c = pngDataTable [ ( c ^ buf [ n ] ) & 0xFF ] ^ ( c >>> 8 ) ; } return c ^ - 1 ; } const PNG = 'image/png' ; const JPEG = 'image/jpeg' ; const b64PhysSignature1 = 'AAlwSFlz' ; const b64PhysSignature2 = 'AAAJcEhZ' ; const b64PhysSignature3 = 'AAAACXBI' ; const _P = 'p' . charCodeAt ( 0 ) ; const _H = 'H' . charCodeAt ( 0 ) ; const _Y = 'Y' . charCodeAt ( 0 ) ; const _S = 's' . charCodeAt ( 0 ) ; export function changeDpiBlob ( blob , dpi ) { const headerChunk = blob . slice ( 0 , 33 ) ; return new Promise ( resolve => { const fileReader = new FileReader ( ) ; fileReader . onload = ( ) => { const dataArray = new Uint8Array ( fileReader . result ) ; const tail = blob . slice ( 33 ) ; const changedArray = changeDpiOnArray ( dataArray , dpi , blob . type ) ; resolve ( new Blob ( [ changedArray , tail ] , { type : blob . type } ) ) ; } ; fileReader . readAsArrayBuffer ( headerChunk ) ; } ) ; } export function changeDpiDataUrl ( base64Image , dpi ) { const dataSplitted = base64Image . split ( ',' ) ; const format = dataSplitted [ 0 ] ; const body = dataSplitted [ 1 ] ; let type ; let headerLength ; let overwritepHYs = false ; if ( format . indexOf ( PNG ) !== - 1 ) { type = PNG ; const b64Index = detectPhysChunkFromDataUrl ( body ) ; if ( b64Index >= 0 ) { headerLength = Math . ceil ( ( b64Index + 28 ) / 3 ) * 4 ; overwritepHYs = true ; } else { headerLength = 33 / 3 * 4 ; } } if ( format . indexOf ( JPEG ) !== - 1 ) { type = JPEG ; headerLength = 18 / 3 * 4 ; } const stringHeader = body . substring ( 0 , headerLength ) ; const restOfData = body . substring ( headerLength ) ; const headerBytes = atob ( stringHeader ) ; const dataArray = new Uint8Array ( headerBytes . length ) ; for ( let i = 0 ; i < dataArray . length ; i ++ ) { dataArray [ i ] = headerBytes . charCodeAt ( i ) ; } const finalArray = changeDpiOnArray ( dataArray , dpi , type , overwritepHYs ) ; const base64Header = btoa ( String . fromCharCode ( ... finalArray ) ) ; return [ format , ',' , base64Header , restOfData ] . join ( '' ) ; } export function readDpi ( base64Image ) { const dataSplitted = base64Image . split ( ',' ) ; const format = dataSplitted [ 0 ] ; const body = dataSplitted [ 1 ] ; let headerLength ; if ( format . indexOf ( PNG ) !== - 1 ) { const b64Index = detectPhysChunkFromDataUrl ( body ) ; if ( b64Index >= 0 ) { headerLength = Math . ceil ( ( b64Index + 28 ) / 3 ) * 4 ; } else { headerLength = 33 / 3 * 4 ; } } const stringHeader = body . substring ( 0 , headerLength ) ; const headerBytes = atob ( stringHeader ) ; const dataArray = new Uint8Array ( headerBytes . length ) ; for ( let i = 0 ; i < dataArray . length ; i ++ ) { dataArray [ i ] = headerBytes . charCodeAt ( i ) ; } return readDpiOnArray ( dataArray ) ; } function detectPhysChunkFromDataUrl ( data ) { let b64index = data . indexOf ( b64PhysSignature1 ) ; if ( b64index === - 1 ) { b64index = data . indexOf ( b64PhysSignature2 ) ; } if ( b64index === - 1 ) { b64index = data . indexOf ( b64PhysSignature3 ) ; } return b64index ; } function searchStartOfPhys ( data ) { const length = data . length - 1 ; for ( let i = length ; i >= 4 ; i -- ) { if ( data [ i - 4 ] === 9 && data [ i - 3 ] === _P && data [ i - 2 ] === _H && data [ i - 1 ] === _Y && data [ i ] === _S ) { return i - 3 ; } } return null ; } function readDpiOnArray ( dataArray ) { const startingIndex = searchStartOfPhys ( dataArray ) + 4 ; if ( dataArray [ startingIndex + 8 ] === 1 ) { let dpi = ( dataArray [ startingIndex + 0 ] << 24 ) + ( dataArray [ startingIndex + 1 ] << 16 ) + ( dataArray [ startingIndex + 2 ] << 8 ) + ( dataArray [ startingIndex + 3 ] ) ; dpi /= 39.3701 ; return dpi ; } return null ; } function changeDpiOnArray ( dataArray , dpi , format , overwritepHYs ) { if ( format === JPEG ) { dataArray [ 13 ] = 1 ; dataArray [ 14 ] = dpi >> 8 ; dataArray [ 15 ] = dpi & 0xff ; dataArray [ 16 ] = dpi >> 8 ; dataArray [ 17 ] = dpi & 0xff ; return dataArray ; } if ( format === PNG ) { const physChunk = new Uint8Array ( 13 ) ; dpi *= 39.3701 ; physChunk [ 0 ] = _P ; physChunk [ 1 ] = _H ; physChunk [ 2 ] = _Y ; physChunk [ 3 ] = _S ; physChunk [ 4 ] = dpi >>> 24 ; physChunk [ 5 ] = dpi >>> 16 ; physChunk [ 6 ] = dpi >>> 8 ; physChunk [ 7 ] = dpi & 0xff ; physChunk [ 8 ] = physChunk [ 4 ] ; physChunk [ 9 ] = physChunk [ 5 ] ; physChunk [ 10 ] = physChunk [ 6 ] ; physChunk [ 11 ] = physChunk [ 7 ] ; physChunk [ 12 ] = 1 ; const crc = calcCrc ( physChunk ) ; const crcChunk = new Uint8Array ( 4 ) ; crcChunk [ 0 ] = crc >>> 24 ; crcChunk [ 1 ] = crc >>> 16 ; crcChunk [ 2 ] = crc >>> 8 ; crcChunk [ 3 ] = crc & 0xff ; if ( overwritepHYs ) { const startingIndex = searchStartOfPhys ( dataArray ) ; dataArray . set ( physChunk , startingIndex ) ; dataArray . set ( crcChunk , startingIndex + 13 ) ; return dataArray ; } const chunkLength = new Uint8Array ( 4 ) ; chunkLength [ 0 ] = 0 ; chunkLength [ 1 ] = 0 ; chunkLength [ 2 ] = 0 ; chunkLength [ 3 ] = 9 ; const finalHeader = new Uint8Array ( 54 ) ; finalHeader . set ( dataArray , 0 ) ; finalHeader . set ( chunkLength , 33 ) ; finalHeader . set ( physChunk , 37 ) ; finalHeader . set ( crcChunk , 50 ) ; return finalHeader ; } return null ; }