expo-image-to-video 0.1.2 → 0.1.3
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.
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'expo.modules.imagetovideo'
|
|
4
|
-
version = '0.1.
|
|
4
|
+
version = '0.1.3'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -35,9 +35,27 @@ android {
|
|
|
35
35
|
namespace "expo.modules.imagetovideo"
|
|
36
36
|
defaultConfig {
|
|
37
37
|
versionCode 1
|
|
38
|
-
versionName "0.1.
|
|
38
|
+
versionName "0.1.3"
|
|
39
39
|
}
|
|
40
40
|
lintOptions {
|
|
41
41
|
abortOnError false
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
dependencies {
|
|
46
|
+
// 2. Kotlin Standard Library (SDK 54 uses Kotlin 1.9+)
|
|
47
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
|
48
|
+
|
|
49
|
+
// 3. Android KTX for cleaner Kotlin code
|
|
50
|
+
implementation "androidx.core:core-ktx:1.12.0"
|
|
51
|
+
|
|
52
|
+
// 4. Coroutines for background processing (Critical for VideoEncoder)
|
|
53
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
|
54
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
55
|
+
|
|
56
|
+
// 5. EXIF support for image rotation handling
|
|
57
|
+
implementation "androidx.exifinterface:exifinterface:1.3.6"
|
|
58
|
+
|
|
59
|
+
// 6. Lifecycle KTX (Useful if you want to scope encoding to the app lifecycle)
|
|
60
|
+
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
|
|
61
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
package expo.modules.imagetovideo
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.graphics.BitmapFactory
|
|
6
|
+
import android.graphics.Matrix
|
|
7
|
+
import android.net.Uri
|
|
8
|
+
import androidx.exifinterface.media.ExifInterface
|
|
9
|
+
import java.io.InputStream
|
|
10
|
+
|
|
11
|
+
object ImageUtils {
|
|
12
|
+
/**
|
|
13
|
+
* Loads a bitmap from a URI, scales it to fit the target dimensions,
|
|
14
|
+
* and corrects the orientation based on EXIF data.
|
|
15
|
+
*/
|
|
16
|
+
fun loadBitmap(context: Context, uriString: String, targetWidth: Int, targetHeight: Int): Bitmap? {
|
|
17
|
+
val uri = Uri.parse(uriString)
|
|
18
|
+
var inputStream: InputStream? = null
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// 1. Get dimensions without loading into memory
|
|
22
|
+
val options = BitmapFactory.Options().apply {
|
|
23
|
+
inJustDecodeBounds = true
|
|
24
|
+
}
|
|
25
|
+
context.contentResolver.openInputStream(uri).use {
|
|
26
|
+
BitmapFactory.decodeStream(it, null, options)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. Calculate scaling to save memory (OOM Prevention)
|
|
30
|
+
options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight)
|
|
31
|
+
options.inJustDecodeBounds = false
|
|
32
|
+
|
|
33
|
+
// 3. Decode the actual bitmap
|
|
34
|
+
val decodedBitmap = context.contentResolver.openInputStream(uri).use {
|
|
35
|
+
BitmapFactory.decodeStream(it, null, options)
|
|
36
|
+
} ?: return null
|
|
37
|
+
|
|
38
|
+
// 4. Correct rotation if needed (common with phone photos)
|
|
39
|
+
return rotateImageIfRequired(context, decodedBitmap, uri)
|
|
40
|
+
} catch (e: Exception) {
|
|
41
|
+
e.printStackTrace()
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
|
47
|
+
val (height: Int, width: Int) = options.outHeight to options.outWidth
|
|
48
|
+
var inSampleSize = 1
|
|
49
|
+
|
|
50
|
+
if (height > reqHeight || width > reqWidth) {
|
|
51
|
+
val halfHeight: Int = height / 2
|
|
52
|
+
val halfWidth: Int = width / 2
|
|
53
|
+
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
|
54
|
+
inSampleSize *= 2
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return inSampleSize
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun rotateImageIfRequired(context: Context, img: Bitmap, selectedImage: Uri): Bitmap {
|
|
61
|
+
val input = context.contentResolver.openInputStream(selectedImage) ?: return img
|
|
62
|
+
val ei = ExifInterface(input)
|
|
63
|
+
val orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
|
64
|
+
|
|
65
|
+
return when (orientation) {
|
|
66
|
+
ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(img, 90f)
|
|
67
|
+
ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(img, 180f)
|
|
68
|
+
ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(img, 270f)
|
|
69
|
+
else -> img
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private fun rotateImage(img: Bitmap, degree: Float): Bitmap {
|
|
74
|
+
val matrix = Matrix()
|
|
75
|
+
matrix.postRotate(degree)
|
|
76
|
+
val rotatedImg = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true)
|
|
77
|
+
img.recycle()
|
|
78
|
+
return rotatedImg
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -20,7 +20,8 @@ Pod::Spec.new do |s|
|
|
|
20
20
|
|
|
21
21
|
s.dependency 'ExpoModulesCore'
|
|
22
22
|
|
|
23
|
-
s.frameworks = 'AVFoundation', 'CoreMedia', 'CoreVideo', 'UIKit'
|
|
23
|
+
#s.frameworks = 'AVFoundation', 'CoreMedia', 'CoreVideo', 'UIKit'
|
|
24
|
+
s.frameworks = 'AVFoundation', 'CoreMedia', 'CoreVideo', 'UIKit', 'CoreGraphics'
|
|
24
25
|
|
|
25
26
|
# Swift/Objective-C compatibility
|
|
26
27
|
s.pod_target_xcconfig = {
|