@@ -44,6 +44,33 @@ function getAndroidStoragePermissionsByAPILevel(permissionModule: typeof Permiss
4444 ] ;
4545}
4646
47+ // HEIC/HEIF brands defined in ISO Base Media File Format
48+ const HEIC_BRANDS = [ 'heic' , 'heix' , 'hevc' , 'hevx' , 'heim' , 'heis' , 'hevm' , 'hevs' , 'mif1' , 'msf1' ] ;
49+
50+ /**
51+ * Detects if a file is HEIC/HEIF by reading its magic bytes.
52+ * HEIC/HEIF files use ISOBMFF container format with 'ftyp' box and specific brand codes.
53+ */
54+ async function detectHeicFromUri ( uri : string ) : Promise < boolean > {
55+ try {
56+ const response = await fetch ( uri ) ;
57+ const buffer = await response . arrayBuffer ( ) ;
58+ const bytes = new Uint8Array ( buffer . slice ( 0 , 24 ) ) ;
59+
60+ // Check for 'ftyp' at bytes 4-7
61+ if ( bytes [ 4 ] === 0x66 && bytes [ 5 ] === 0x74 && bytes [ 6 ] === 0x79 && bytes [ 7 ] === 0x70 ) {
62+ // Read major brand at bytes 8-11
63+ const majorBrand = String . fromCharCode ( bytes [ 8 ] , bytes [ 9 ] , bytes [ 10 ] , bytes [ 11 ] ) . toLowerCase ( ) ;
64+ if ( HEIC_BRANDS . includes ( majorBrand ) ) {
65+ return true ;
66+ }
67+ }
68+ return false ;
69+ } catch {
70+ return false ;
71+ }
72+ }
73+
4774const createNativeFileService = ( {
4875 imagePickerModule,
4976 documentPickerModule,
@@ -151,6 +178,11 @@ const createNativeFileService = ({
151178 const response = await imagePickerModule . launchImageLibrary ( {
152179 presentationStyle : 'fullScreen' ,
153180 selectionLimit,
181+ assetRepresentationMode : 'current' ,
182+ // NOTE: quality must be set to 1 to prevent HEIC/HEIF to JPEG conversion.
183+ // Without this, the native code's condition (quality >= 0 && quality < 1) becomes true
184+ // when quality is undefined (floatValue returns 0.0), triggering unwanted JPEG conversion.
185+ quality : 1 ,
154186 mediaType : ( ( ) => {
155187 switch ( options ?. mediaType ) {
156188 case 'photo' :
@@ -170,11 +202,28 @@ const createNativeFileService = ({
170202 return null ;
171203 }
172204
173- return Promise . all (
174- ( response . assets || [ ] )
175- . slice ( 0 , selectionLimit )
176- . map ( ( { fileName : name , fileSize : size , type, uri } ) => normalizeFile ( { uri, size, name, type } ) ) ,
205+ // Correct HEIC/HEIF file extension and MIME type that react-native-image-picker misidentifies as JPEG
206+ const correctedAssets = await Promise . all (
207+ ( response . assets || [ ] ) . slice ( 0 , selectionLimit ) . map ( async ( asset ) => {
208+ const { fileName, fileSize : size , type : originalType , uri } = asset ;
209+ let name = fileName ;
210+ let type = originalType ;
211+
212+ if ( uri && ( type === 'image/jpeg' || type === 'image/jpg' || name ?. toLowerCase ( ) . endsWith ( '.jpg' ) ) ) {
213+ const isHeic = await detectHeicFromUri ( uri ) ;
214+ if ( isHeic ) {
215+ type = 'image/heic' ;
216+ if ( name ) {
217+ name = name . replace ( / \. j p e ? g $ / i, '.heic' ) ;
218+ }
219+ }
220+ }
221+
222+ return { uri, size, name, type } ;
223+ } ) ,
177224 ) ;
225+
226+ return Promise . all ( correctedAssets . map ( ( { uri, size, name, type } ) => normalizeFile ( { uri, size, name, type } ) ) ) ;
178227 }
179228
180229 async openDocument ( options ?: OpenDocumentOptions ) : Promise < FilePickerResponse > {
0 commit comments