react-native-image-compression-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +562 -0
  3. package/android/build.gradle +63 -0
  4. package/android/src/main/AndroidManifest.xml +1 -0
  5. package/android/src/main/java/com/imagecompressionkit/ImageCompressionKitModule.kt +1127 -0
  6. package/android/src/main/java/com/imagecompressionkit/ImageCompressionKitPackage.kt +33 -0
  7. package/android/src/main/java/com/imagecompressionkit/ImageCompressionOutput.kt +396 -0
  8. package/android/src/main/java/com/imagecompressionkit/JpegExifMetadata.kt +233 -0
  9. package/ios/RCTImageCompressionKit.h +10 -0
  10. package/ios/RCTImageCompressionKit.mm +72 -0
  11. package/lib/NativeImageCompressionKit.d.ts +55 -0
  12. package/lib/NativeImageCompressionKit.d.ts.map +1 -0
  13. package/lib/NativeImageCompressionKit.js +5 -0
  14. package/lib/NativeImageCompressionKit.js.map +1 -0
  15. package/lib/api.d.ts +4 -0
  16. package/lib/api.d.ts.map +1 -0
  17. package/lib/api.js +25 -0
  18. package/lib/api.js.map +1 -0
  19. package/lib/errors.d.ts +9 -0
  20. package/lib/errors.d.ts.map +1 -0
  21. package/lib/errors.js +53 -0
  22. package/lib/errors.js.map +1 -0
  23. package/lib/index.d.ts +5 -0
  24. package/lib/index.d.ts.map +1 -0
  25. package/lib/index.js +14 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/nativeModule.d.ts +7 -0
  28. package/lib/nativeModule.d.ts.map +1 -0
  29. package/lib/nativeModule.js +73 -0
  30. package/lib/nativeModule.js.map +1 -0
  31. package/lib/types.d.ts +59 -0
  32. package/lib/types.d.ts.map +1 -0
  33. package/lib/types.js +23 -0
  34. package/lib/types.js.map +1 -0
  35. package/lib/validation.d.ts +3 -0
  36. package/lib/validation.d.ts.map +1 -0
  37. package/lib/validation.js +108 -0
  38. package/lib/validation.js.map +1 -0
  39. package/package.json +106 -0
  40. package/react-native-image-compression-kit.podspec +21 -0
  41. package/react-native.config.js +12 -0
  42. package/src/NativeImageCompressionKit.ts +81 -0
  43. package/src/api.ts +28 -0
  44. package/src/errors.ts +91 -0
  45. package/src/index.ts +25 -0
  46. package/src/nativeModule.ts +130 -0
  47. package/src/types.ts +88 -0
  48. package/src/validation.ts +181 -0
@@ -0,0 +1,33 @@
1
+ package com.imagecompressionkit
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+
9
+ class ImageCompressionKitPackage : BaseReactPackage() {
10
+ override fun getModule(
11
+ name: String,
12
+ reactContext: ReactApplicationContext
13
+ ): NativeModule? =
14
+ if (name == ImageCompressionKitModule.NAME) {
15
+ ImageCompressionKitModule(reactContext)
16
+ } else {
17
+ null
18
+ }
19
+
20
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
21
+ ReactModuleInfoProvider {
22
+ mapOf(
23
+ ImageCompressionKitModule.NAME to ReactModuleInfo(
24
+ ImageCompressionKitModule.NAME,
25
+ ImageCompressionKitModule.NAME,
26
+ false,
27
+ false,
28
+ false,
29
+ true
30
+ )
31
+ )
32
+ }
33
+ }
@@ -0,0 +1,396 @@
1
+ package com.imagecompressionkit
2
+
3
+ import android.graphics.Bitmap
4
+ import android.net.Uri
5
+ import android.os.Build
6
+ import java.io.File
7
+ import java.io.FileOutputStream
8
+ import java.util.UUID
9
+
10
+ internal data class CompressionOutputDimensions(
11
+ val width: Int,
12
+ val height: Int
13
+ )
14
+
15
+ internal data class CompressionOutputResultMetadata(
16
+ val uri: String,
17
+ val format: String,
18
+ val width: Int,
19
+ val height: Int,
20
+ val byteSize: Long,
21
+ val originalByteSize: Long,
22
+ val compressionRatio: Double
23
+ )
24
+
25
+ internal data class CompressionFormatCapability(
26
+ val format: String,
27
+ val input: Boolean,
28
+ val output: Boolean,
29
+ val supportsAlpha: Boolean,
30
+ val supportsAnimation: Boolean,
31
+ val notes: List<String>
32
+ )
33
+
34
+ internal enum class OutputFormat(
35
+ val value: String,
36
+ val fileExtension: String,
37
+ val supportsTargetSizeCompression: Boolean,
38
+ val supportsJpegExifMetadata: Boolean
39
+ ) {
40
+ JPEG(
41
+ value = "jpeg",
42
+ fileExtension = "jpg",
43
+ supportsTargetSizeCompression = true,
44
+ supportsJpegExifMetadata = true
45
+ ),
46
+ PNG(
47
+ value = "png",
48
+ fileExtension = "png",
49
+ supportsTargetSizeCompression = false,
50
+ supportsJpegExifMetadata = false
51
+ ),
52
+ WEBP(
53
+ value = "webp",
54
+ fileExtension = "webp",
55
+ supportsTargetSizeCompression = true,
56
+ supportsJpegExifMetadata = false
57
+ );
58
+
59
+ val compressFormat: Bitmap.CompressFormat
60
+ get() = when (this) {
61
+ JPEG -> Bitmap.CompressFormat.JPEG
62
+ PNG -> Bitmap.CompressFormat.PNG
63
+ WEBP -> webpCompressFormat()
64
+ }
65
+
66
+ fun compressionQuality(quality: Int): Int =
67
+ if (this == PNG) {
68
+ 100
69
+ } else {
70
+ quality
71
+ }
72
+
73
+ @Suppress("DEPRECATION")
74
+ private fun webpCompressFormat(): Bitmap.CompressFormat =
75
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
76
+ Bitmap.CompressFormat.WEBP_LOSSY
77
+ } else {
78
+ Bitmap.CompressFormat.WEBP
79
+ }
80
+
81
+ companion object {
82
+ fun fromValue(value: String?): OutputFormat? =
83
+ values().firstOrNull { it.value == value }
84
+ }
85
+ }
86
+
87
+ internal object ImageCompressionOutput {
88
+ const val MAX_BYTES_UNSUPPORTED_MESSAGE =
89
+ "Android MVP supports output.maxBytes for JPEG and WebP output only."
90
+
91
+ val FORMAT_VALUES = arrayOf(
92
+ JPEG_FORMAT,
93
+ PNG_FORMAT,
94
+ WEBP_FORMAT,
95
+ HEIC_FORMAT,
96
+ HEIF_FORMAT,
97
+ "avif",
98
+ GIF_FORMAT
99
+ )
100
+
101
+ fun fromValue(value: String?): OutputFormat? =
102
+ OutputFormat.fromValue(value)
103
+
104
+ fun maxBytesValidationError(
105
+ outputFormat: OutputFormat,
106
+ maxBytes: Long?
107
+ ): String? =
108
+ if (maxBytes != null && !outputFormat.supportsTargetSizeCompression) {
109
+ MAX_BYTES_UNSUPPORTED_MESSAGE
110
+ } else {
111
+ null
112
+ }
113
+
114
+ fun createOutputFile(cacheDir: File, outputFormat: OutputFormat): File {
115
+ val outputDir = File(cacheDir, OUTPUT_DIRECTORY_NAME)
116
+ if (!outputDir.exists()) {
117
+ outputDir.mkdirs()
118
+ }
119
+
120
+ return File(
121
+ outputDir,
122
+ "compressed-${System.currentTimeMillis()}-${UUID.randomUUID()}.${outputFormat.fileExtension}"
123
+ )
124
+ }
125
+
126
+ fun encodeBitmap(
127
+ bitmap: Bitmap,
128
+ outputFile: File,
129
+ outputFormat: OutputFormat,
130
+ quality: Int,
131
+ maxBytes: Long?,
132
+ copiedExifMetadata: CopiedExifMetadata? = null
133
+ ): Boolean =
134
+ if (maxBytes == null) {
135
+ encodeBitmapAtQuality(
136
+ bitmap,
137
+ outputFile,
138
+ outputFormat,
139
+ quality,
140
+ copiedExifMetadata
141
+ )
142
+ } else {
143
+ encodeBitmapToTargetSize(
144
+ bitmap,
145
+ outputFile,
146
+ outputFormat,
147
+ quality,
148
+ maxBytes,
149
+ copiedExifMetadata
150
+ )
151
+ }
152
+
153
+ fun createResultMetadata(
154
+ originalByteSize: Long,
155
+ outputFile: File,
156
+ dimensions: CompressionOutputDimensions,
157
+ outputFormat: OutputFormat
158
+ ): CompressionOutputResultMetadata {
159
+ val byteSize = outputFile.length()
160
+
161
+ return CompressionOutputResultMetadata(
162
+ uri = Uri.fromFile(outputFile).toString(),
163
+ format = outputFormat.value,
164
+ width = dimensions.width,
165
+ height = dimensions.height,
166
+ byteSize = byteSize,
167
+ originalByteSize = originalByteSize,
168
+ compressionRatio = if (originalByteSize > 0L) {
169
+ byteSize.toDouble() / originalByteSize.toDouble()
170
+ } else {
171
+ 1.0
172
+ }
173
+ )
174
+ }
175
+
176
+ fun createFormatCapability(format: String): CompressionFormatCapability {
177
+ val outputFormat = OutputFormat.fromValue(format)
178
+ val isSupportedInput = format in SUPPORTED_INPUT_FORMATS
179
+
180
+ return CompressionFormatCapability(
181
+ format = format,
182
+ input = isSupportedInput,
183
+ output = outputFormat != null,
184
+ supportsAlpha = false,
185
+ supportsAnimation = false,
186
+ notes = when (outputFormat) {
187
+ OutputFormat.JPEG -> jpegFormatNotes()
188
+ OutputFormat.PNG -> pngFormatNotes()
189
+ OutputFormat.WEBP -> webpFormatNotes()
190
+ null -> when (format) {
191
+ HEIC_FORMAT -> heicHeifFormatNotes("HEIC")
192
+ HEIF_FORMAT -> heicHeifFormatNotes("HEIF")
193
+ AVIF_FORMAT -> avifFormatNotes()
194
+ GIF_FORMAT -> gifFormatNotes()
195
+ else -> notImplementedNotes()
196
+ }
197
+ }
198
+ )
199
+ }
200
+
201
+ private fun notImplementedNotes(): List<String> =
202
+ listOf("Native codec support has not been implemented yet.")
203
+
204
+ private fun heicHeifFormatNotes(formatLabel: String): List<String> =
205
+ listOf(
206
+ "$formatLabel input is supported on Android 8.0+ when device HEIF decode codecs are present.",
207
+ "Android API 28+ uses ImageDecoder for $formatLabel input.",
208
+ "Android API 26-27 attempts a guarded BitmapFactory HEIF decode fallback.",
209
+ "$formatLabel inputs are decoded without copying EXIF metadata.",
210
+ "$formatLabel output is not implemented."
211
+ )
212
+
213
+ private fun avifFormatNotes(): List<String> =
214
+ listOf(
215
+ "AVIF input is supported on Android 14+ for baseline still images.",
216
+ "Android API 34+ uses ImageDecoder for AVIF input.",
217
+ "AVIF inputs are decoded without copying EXIF metadata.",
218
+ "Animated AVIF preservation is not implemented.",
219
+ "AVIF output is not implemented."
220
+ )
221
+
222
+ private fun encodeBitmapToTargetSize(
223
+ bitmap: Bitmap,
224
+ outputFile: File,
225
+ outputFormat: OutputFormat,
226
+ qualityCap: Int,
227
+ maxBytes: Long,
228
+ copiedExifMetadata: CopiedExifMetadata?
229
+ ): Boolean {
230
+ var currentQuality = qualityCap
231
+
232
+ if (
233
+ !encodeBitmapAtQuality(
234
+ bitmap,
235
+ outputFile,
236
+ outputFormat,
237
+ currentQuality,
238
+ copiedExifMetadata
239
+ )
240
+ ) {
241
+ return false
242
+ }
243
+
244
+ if (outputFile.length() <= maxBytes) {
245
+ return true
246
+ }
247
+
248
+ var lowestAboveTargetQuality = currentQuality
249
+ var lowestAboveTargetSize = outputFile.length()
250
+ var bestWithinTargetQuality: Int? = null
251
+ var low = MIN_QUALITY
252
+ var high = qualityCap - 1
253
+
254
+ while (low <= high) {
255
+ currentQuality = (low + high) / 2
256
+
257
+ if (
258
+ !encodeBitmapAtQuality(
259
+ bitmap,
260
+ outputFile,
261
+ outputFormat,
262
+ currentQuality,
263
+ copiedExifMetadata
264
+ )
265
+ ) {
266
+ return false
267
+ }
268
+
269
+ val byteSize = outputFile.length()
270
+
271
+ if (byteSize <= maxBytes) {
272
+ bestWithinTargetQuality = currentQuality
273
+ low = currentQuality + 1
274
+ } else {
275
+ if (byteSize < lowestAboveTargetSize) {
276
+ lowestAboveTargetQuality = currentQuality
277
+ lowestAboveTargetSize = byteSize
278
+ }
279
+ high = currentQuality - 1
280
+ }
281
+ }
282
+
283
+ val finalQuality = bestWithinTargetQuality ?: lowestAboveTargetQuality
284
+
285
+ return if (currentQuality == finalQuality) {
286
+ true
287
+ } else {
288
+ encodeBitmapAtQuality(
289
+ bitmap,
290
+ outputFile,
291
+ outputFormat,
292
+ finalQuality,
293
+ copiedExifMetadata
294
+ )
295
+ }
296
+ }
297
+
298
+ private fun encodeBitmapAtQuality(
299
+ bitmap: Bitmap,
300
+ outputFile: File,
301
+ outputFormat: OutputFormat,
302
+ quality: Int,
303
+ copiedExifMetadata: CopiedExifMetadata?
304
+ ): Boolean {
305
+ val encoded = FileOutputStream(outputFile).use { outputStream ->
306
+ bitmap.compress(
307
+ outputFormat.compressFormat,
308
+ outputFormat.compressionQuality(quality),
309
+ outputStream
310
+ )
311
+ }
312
+
313
+ if (!encoded) {
314
+ return false
315
+ }
316
+
317
+ if (outputFormat.supportsJpegExifMetadata) {
318
+ writeCopiedExifMetadata(copiedExifMetadata, outputFile)
319
+ }
320
+
321
+ return true
322
+ }
323
+
324
+ private fun writeCopiedExifMetadata(
325
+ copiedExifMetadata: CopiedExifMetadata?,
326
+ outputFile: File
327
+ ) {
328
+ try {
329
+ JpegExifMetadata.write(copiedExifMetadata, outputFile)
330
+ } catch (error: Exception) {
331
+ throw IllegalStateException(
332
+ "Android MVP could not write preserved EXIF metadata.",
333
+ error
334
+ )
335
+ }
336
+ }
337
+
338
+ private fun jpegFormatNotes(): List<String> =
339
+ listOf(
340
+ "Android MVP supports JPEG file:// and content:// sources.",
341
+ "EXIF orientation is applied before resize and selected output encoding.",
342
+ "Resize supports contain, cover, and stretch modes with maxWidth and maxHeight.",
343
+ "Target-size compression supports maxBytes by adjusting JPEG quality.",
344
+ "Metadata preserve copies supported JPEG source EXIF attributes into JPEG output.",
345
+ "Metadata safe copies privacy-filtered JPEG source EXIF attributes.",
346
+ "Metadata safe excludes GPS/location, owner/serial, maker note, user comment, and XMP.",
347
+ "Metadata preserve normalizes output EXIF orientation after pixels are transformed.",
348
+ "Metadata strip re-encodes JPEG output without preserving source metadata.",
349
+ "PNG, WebP, GIF, HEIC, HEIF, and AVIF sources are decoded without copying EXIF metadata."
350
+ )
351
+
352
+ private fun pngFormatNotes(): List<String> =
353
+ listOf(
354
+ "Android MVP supports PNG file:// and content:// sources.",
355
+ "Android can encode decoded JPEG, PNG, WebP, or GIF input to PNG output.",
356
+ "PNG output ignores quality and does not support target-size maxBytes.",
357
+ "Non-JPEG output does not preserve source EXIF metadata."
358
+ )
359
+
360
+ private fun webpFormatNotes(): List<String> =
361
+ listOf(
362
+ "Android MVP supports WebP file:// and content:// sources.",
363
+ "Android can encode decoded JPEG, PNG, WebP, or GIF input to WebP output.",
364
+ "WebP target-size compression supports maxBytes by adjusting WebP quality.",
365
+ "Non-JPEG output does not preserve source EXIF metadata.",
366
+ "Animated WebP input or output is not implemented."
367
+ )
368
+
369
+ private fun gifFormatNotes(): List<String> =
370
+ listOf(
371
+ "Android MVP decodes GIF file:// and content:// sources as a static first frame.",
372
+ "Animated GIF preservation is not implemented.",
373
+ "GIF output is not implemented.",
374
+ "GIF sources are decoded without copying EXIF metadata."
375
+ )
376
+
377
+ private val SUPPORTED_INPUT_FORMATS = setOf(
378
+ JPEG_FORMAT,
379
+ PNG_FORMAT,
380
+ WEBP_FORMAT,
381
+ HEIC_FORMAT,
382
+ HEIF_FORMAT,
383
+ AVIF_FORMAT,
384
+ GIF_FORMAT
385
+ )
386
+
387
+ private const val JPEG_FORMAT = "jpeg"
388
+ private const val PNG_FORMAT = "png"
389
+ private const val WEBP_FORMAT = "webp"
390
+ private const val HEIC_FORMAT = "heic"
391
+ private const val HEIF_FORMAT = "heif"
392
+ private const val AVIF_FORMAT = "avif"
393
+ private const val GIF_FORMAT = "gif"
394
+ private const val OUTPUT_DIRECTORY_NAME = "image-compression-kit"
395
+ private const val MIN_QUALITY = 0
396
+ }
@@ -0,0 +1,233 @@
1
+ package com.imagecompressionkit
2
+
3
+ import androidx.exifinterface.media.ExifInterface
4
+ import java.io.File
5
+ import java.io.InputStream
6
+
7
+ internal data class CopiedExifMetadata(
8
+ val attributes: Map<String, String>,
9
+ val width: Int,
10
+ val height: Int
11
+ )
12
+
13
+ internal object JpegExifMetadata {
14
+ val SAFE_EXIF_TAGS = arrayOf(
15
+ ExifInterface.TAG_APERTURE_VALUE,
16
+ ExifInterface.TAG_BRIGHTNESS_VALUE,
17
+ ExifInterface.TAG_CFA_PATTERN,
18
+ ExifInterface.TAG_COLOR_SPACE,
19
+ ExifInterface.TAG_COMPONENTS_CONFIGURATION,
20
+ ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
21
+ ExifInterface.TAG_CONTRAST,
22
+ ExifInterface.TAG_CUSTOM_RENDERED,
23
+ ExifInterface.TAG_DATETIME,
24
+ ExifInterface.TAG_DATETIME_DIGITIZED,
25
+ ExifInterface.TAG_DATETIME_ORIGINAL,
26
+ ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
27
+ ExifInterface.TAG_EXIF_VERSION,
28
+ ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
29
+ ExifInterface.TAG_EXPOSURE_INDEX,
30
+ ExifInterface.TAG_EXPOSURE_MODE,
31
+ ExifInterface.TAG_EXPOSURE_PROGRAM,
32
+ ExifInterface.TAG_EXPOSURE_TIME,
33
+ ExifInterface.TAG_F_NUMBER,
34
+ ExifInterface.TAG_FILE_SOURCE,
35
+ ExifInterface.TAG_FLASH,
36
+ ExifInterface.TAG_FLASHPIX_VERSION,
37
+ ExifInterface.TAG_FOCAL_LENGTH,
38
+ ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM,
39
+ ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
40
+ ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
41
+ ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
42
+ ExifInterface.TAG_GAIN_CONTROL,
43
+ ExifInterface.TAG_GAMMA,
44
+ ExifInterface.TAG_ISO_SPEED,
45
+ ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY,
46
+ ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ,
47
+ ExifInterface.TAG_LENS_MAKE,
48
+ ExifInterface.TAG_LENS_MODEL,
49
+ ExifInterface.TAG_LENS_SPECIFICATION,
50
+ ExifInterface.TAG_LIGHT_SOURCE,
51
+ ExifInterface.TAG_MAKE,
52
+ ExifInterface.TAG_MAX_APERTURE_VALUE,
53
+ ExifInterface.TAG_METERING_MODE,
54
+ ExifInterface.TAG_MODEL,
55
+ ExifInterface.TAG_OFFSET_TIME,
56
+ ExifInterface.TAG_OFFSET_TIME_DIGITIZED,
57
+ ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
58
+ ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
59
+ ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX,
60
+ ExifInterface.TAG_SATURATION,
61
+ ExifInterface.TAG_SCENE_CAPTURE_TYPE,
62
+ ExifInterface.TAG_SCENE_TYPE,
63
+ ExifInterface.TAG_SENSING_METHOD,
64
+ ExifInterface.TAG_SENSITIVITY_TYPE,
65
+ ExifInterface.TAG_SHARPNESS,
66
+ ExifInterface.TAG_SHUTTER_SPEED_VALUE,
67
+ ExifInterface.TAG_SOFTWARE,
68
+ ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY,
69
+ ExifInterface.TAG_SUBSEC_TIME,
70
+ ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
71
+ ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
72
+ ExifInterface.TAG_WHITE_BALANCE
73
+ )
74
+
75
+ val PRESERVED_EXIF_TAGS = arrayOf(
76
+ ExifInterface.TAG_ARTIST,
77
+ ExifInterface.TAG_BODY_SERIAL_NUMBER,
78
+ ExifInterface.TAG_BRIGHTNESS_VALUE,
79
+ ExifInterface.TAG_CAMERA_OWNER_NAME,
80
+ ExifInterface.TAG_CFA_PATTERN,
81
+ ExifInterface.TAG_COLOR_SPACE,
82
+ ExifInterface.TAG_COMPONENTS_CONFIGURATION,
83
+ ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
84
+ ExifInterface.TAG_CONTRAST,
85
+ ExifInterface.TAG_COPYRIGHT,
86
+ ExifInterface.TAG_CUSTOM_RENDERED,
87
+ ExifInterface.TAG_DATETIME,
88
+ ExifInterface.TAG_DATETIME_DIGITIZED,
89
+ ExifInterface.TAG_DATETIME_ORIGINAL,
90
+ ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
91
+ ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
92
+ ExifInterface.TAG_EXIF_VERSION,
93
+ ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
94
+ ExifInterface.TAG_EXPOSURE_INDEX,
95
+ ExifInterface.TAG_EXPOSURE_MODE,
96
+ ExifInterface.TAG_EXPOSURE_PROGRAM,
97
+ ExifInterface.TAG_EXPOSURE_TIME,
98
+ ExifInterface.TAG_F_NUMBER,
99
+ ExifInterface.TAG_FILE_SOURCE,
100
+ ExifInterface.TAG_FLASH,
101
+ ExifInterface.TAG_FLASH_ENERGY,
102
+ ExifInterface.TAG_FLASHPIX_VERSION,
103
+ ExifInterface.TAG_FOCAL_LENGTH,
104
+ ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM,
105
+ ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
106
+ ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
107
+ ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
108
+ ExifInterface.TAG_GAIN_CONTROL,
109
+ ExifInterface.TAG_GAMMA,
110
+ ExifInterface.TAG_GPS_ALTITUDE,
111
+ ExifInterface.TAG_GPS_ALTITUDE_REF,
112
+ ExifInterface.TAG_GPS_AREA_INFORMATION,
113
+ ExifInterface.TAG_GPS_DATESTAMP,
114
+ ExifInterface.TAG_GPS_DEST_BEARING,
115
+ ExifInterface.TAG_GPS_DEST_BEARING_REF,
116
+ ExifInterface.TAG_GPS_DEST_DISTANCE,
117
+ ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
118
+ ExifInterface.TAG_GPS_DEST_LATITUDE,
119
+ ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
120
+ ExifInterface.TAG_GPS_DEST_LONGITUDE,
121
+ ExifInterface.TAG_GPS_DEST_LONGITUDE_REF,
122
+ ExifInterface.TAG_GPS_DIFFERENTIAL,
123
+ ExifInterface.TAG_GPS_DOP,
124
+ ExifInterface.TAG_GPS_H_POSITIONING_ERROR,
125
+ ExifInterface.TAG_GPS_IMG_DIRECTION,
126
+ ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
127
+ ExifInterface.TAG_GPS_LATITUDE,
128
+ ExifInterface.TAG_GPS_LATITUDE_REF,
129
+ ExifInterface.TAG_GPS_LONGITUDE,
130
+ ExifInterface.TAG_GPS_LONGITUDE_REF,
131
+ ExifInterface.TAG_GPS_MAP_DATUM,
132
+ ExifInterface.TAG_GPS_MEASURE_MODE,
133
+ ExifInterface.TAG_GPS_PROCESSING_METHOD,
134
+ ExifInterface.TAG_GPS_SATELLITES,
135
+ ExifInterface.TAG_GPS_SPEED,
136
+ ExifInterface.TAG_GPS_SPEED_REF,
137
+ ExifInterface.TAG_GPS_STATUS,
138
+ ExifInterface.TAG_GPS_TIMESTAMP,
139
+ ExifInterface.TAG_GPS_TRACK,
140
+ ExifInterface.TAG_GPS_TRACK_REF,
141
+ ExifInterface.TAG_GPS_VERSION_ID,
142
+ ExifInterface.TAG_IMAGE_DESCRIPTION,
143
+ ExifInterface.TAG_IMAGE_UNIQUE_ID,
144
+ ExifInterface.TAG_INTEROPERABILITY_INDEX,
145
+ ExifInterface.TAG_ISO_SPEED,
146
+ ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY,
147
+ ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ,
148
+ ExifInterface.TAG_LENS_MAKE,
149
+ ExifInterface.TAG_LENS_MODEL,
150
+ ExifInterface.TAG_LENS_SERIAL_NUMBER,
151
+ ExifInterface.TAG_LENS_SPECIFICATION,
152
+ ExifInterface.TAG_LIGHT_SOURCE,
153
+ ExifInterface.TAG_MAKE,
154
+ ExifInterface.TAG_MAKER_NOTE,
155
+ ExifInterface.TAG_MAX_APERTURE_VALUE,
156
+ ExifInterface.TAG_METERING_MODE,
157
+ ExifInterface.TAG_MODEL,
158
+ ExifInterface.TAG_OECF,
159
+ ExifInterface.TAG_OFFSET_TIME,
160
+ ExifInterface.TAG_OFFSET_TIME_DIGITIZED,
161
+ ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
162
+ ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
163
+ ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX,
164
+ ExifInterface.TAG_RELATED_SOUND_FILE,
165
+ ExifInterface.TAG_SATURATION,
166
+ ExifInterface.TAG_SCENE_CAPTURE_TYPE,
167
+ ExifInterface.TAG_SCENE_TYPE,
168
+ ExifInterface.TAG_SENSING_METHOD,
169
+ ExifInterface.TAG_SENSITIVITY_TYPE,
170
+ ExifInterface.TAG_SHARPNESS,
171
+ ExifInterface.TAG_SHUTTER_SPEED_VALUE,
172
+ ExifInterface.TAG_SOFTWARE,
173
+ ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
174
+ ExifInterface.TAG_SPECTRAL_SENSITIVITY,
175
+ ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY,
176
+ ExifInterface.TAG_SUBJECT_AREA,
177
+ ExifInterface.TAG_SUBJECT_DISTANCE,
178
+ ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
179
+ ExifInterface.TAG_SUBJECT_LOCATION,
180
+ ExifInterface.TAG_SUBSEC_TIME,
181
+ ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
182
+ ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
183
+ ExifInterface.TAG_USER_COMMENT,
184
+ ExifInterface.TAG_WHITE_BALANCE,
185
+ ExifInterface.TAG_XMP
186
+ )
187
+
188
+ fun read(
189
+ inputStream: InputStream,
190
+ exifTags: Array<String>,
191
+ width: Int,
192
+ height: Int
193
+ ): CopiedExifMetadata {
194
+ val sourceExif = ExifInterface(inputStream)
195
+ val attributes = linkedMapOf<String, String>()
196
+
197
+ exifTags.forEach { tag ->
198
+ val value = sourceExif.getAttribute(tag)
199
+
200
+ if (value != null) {
201
+ attributes[tag] = value
202
+ }
203
+ }
204
+
205
+ return CopiedExifMetadata(
206
+ attributes = attributes,
207
+ width = width,
208
+ height = height
209
+ )
210
+ }
211
+
212
+ fun write(metadata: CopiedExifMetadata?, outputFile: File) {
213
+ if (metadata == null) {
214
+ return
215
+ }
216
+
217
+ val outputExif = ExifInterface(outputFile.absolutePath)
218
+
219
+ metadata.attributes.forEach { (tag, value) ->
220
+ outputExif.setAttribute(tag, value)
221
+ }
222
+
223
+ outputExif.setAttribute(
224
+ ExifInterface.TAG_ORIENTATION,
225
+ ExifInterface.ORIENTATION_NORMAL.toString()
226
+ )
227
+ outputExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, metadata.width.toString())
228
+ outputExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, metadata.height.toString())
229
+ outputExif.setAttribute(ExifInterface.TAG_PIXEL_X_DIMENSION, metadata.width.toString())
230
+ outputExif.setAttribute(ExifInterface.TAG_PIXEL_Y_DIMENSION, metadata.height.toString())
231
+ outputExif.saveAttributes()
232
+ }
233
+ }
@@ -0,0 +1,10 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <RNImageCompressionKitSpec/RNImageCompressionKitSpec.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface RCTImageCompressionKit : NSObject <NativeImageCompressionKitSpec>
7
+
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END