2024-08-28 19:32:38 +03:00
type PngChunk = {
name : string ,
data : Uint8Array ,
}
export async function pngChunks ( blob : Blob ) : Promise < PngChunk [ ] > {
2024-02-19 05:23:06 +03:00
const uint8arr = new Uint8Array ( await blob . arrayBuffer ( ) ) ;
2024-08-28 19:32:38 +03:00
const chunks : PngChunk [ ] = [ ] ;
2024-02-19 05:23:06 +03:00
if ( uint8arr . length < 12 ) return chunks ;
const view = new DataView ( uint8arr . buffer ) ;
if ( view . getBigUint64 ( 0 ) !== 9894494448401390090 n ) return chunks ;
const decoder = new TextDecoder ( ) ;
let index = 8 ;
while ( index < uint8arr . length ) {
const len = view . getUint32 ( index ) ;
chunks . push ( {
name : decoder.decode ( uint8arr . slice ( index + 4 , index + 8 ) ) ,
data : uint8arr.slice ( index + 8 , index + 8 + len ) ,
} ) ;
index += len + 12 ;
}
return chunks ;
}
2024-08-28 19:32:38 +03:00
type ImageInfo = {
width? : number ,
dppx? : number ,
}
2024-06-27 12:31:49 +03:00
// decode a image and try to obtain width and dppx. It will never throw but instead
2024-02-19 05:23:06 +03:00
// return default values.
2024-08-28 19:32:38 +03:00
export async function imageInfo ( blob : Blob ) : Promise < ImageInfo > {
2024-06-27 12:31:49 +03:00
let width = 0 , dppx = 1 ; // dppx: 1 dot per pixel for non-HiDPI screens
2024-02-19 05:23:06 +03:00
if ( blob . type === 'image/png' ) { // only png is supported currently
try {
for ( const { name , data } of await pngChunks ( blob ) ) {
const view = new DataView ( data . buffer ) ;
if ( name === 'IHDR' && data ? . length ) {
// extract width from mandatory IHDR chunk
width = view . getUint32 ( 0 ) ;
} else if ( name === 'pHYs' && data ? . length ) {
// extract dppx from optional pHYs chunk, assuming pixels are square
const unit = view . getUint8 ( 8 ) ;
if ( unit === 1 ) {
dppx = Math . round ( view . getUint32 ( 0 ) / 39.3701 ) / 72 ; // meter to inch to dppx
}
}
}
} catch { }
2024-06-27 12:31:49 +03:00
} else {
return { } ; // no image info for non-image files
2024-02-19 05:23:06 +03:00
}
return { width , dppx } ;
}