@akylas/nativescript-app-utils 2.1.5 → 2.1.6
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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.1.6](https://github.com/akylas/nativescript-app-utils/compare/v2.1.5...v2.1.6) (2024-11-08)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* regression fix ([88f15db](https://github.com/akylas/nativescript-app-utils/commit/88f15db1e7b5be77f398c659d650c8500f6436e6))
|
|
11
|
+
|
|
6
12
|
## [2.1.5](https://github.com/akylas/nativescript-app-utils/compare/v2.1.4...v2.1.5) (2024-11-08)
|
|
7
13
|
|
|
8
14
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akylas/nativescript-app-utils",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"description": "Provides API for changing the styles of SystemUI (StatusBar, NavigationBar...) on iOS.",
|
|
5
5
|
"main": "index",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"bugs": {
|
|
64
64
|
"url": "https://github.com/akylas/nativescript-app-utils/issues"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "f9d8fa107bd8ef7a0bba2b7cd091e8b53402368d"
|
|
67
67
|
}
|
|
@@ -1,231 +1,419 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
width = min(
|
|
86
|
-
width,
|
|
87
|
-
options!.maxWidth
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
if (options!.maxHeight > 0) {
|
|
91
|
-
height = min(
|
|
92
|
-
height,
|
|
93
|
-
options!.maxHeight
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
keepAspectRatio = options!.keepAspectRatio
|
|
97
|
-
autoScaleFactor = options!.autoScaleFactor
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static func getAspectSafeDimensions(
|
|
103
|
-
_ sourceWidth: Double,
|
|
104
|
-
_ sourceHeight: Double,
|
|
105
|
-
_ reqWidth: Double,
|
|
106
|
-
_ reqHeight: Double
|
|
107
|
-
) -> CGSize {
|
|
108
|
-
let widthCoef = sourceWidth / reqWidth
|
|
109
|
-
let heightCoef = sourceHeight / reqHeight
|
|
110
|
-
let aspectCoef = max(widthCoef, heightCoef)
|
|
111
|
-
return CGSize(width: floor((sourceWidth / aspectCoef)), height: floor((sourceHeight / aspectCoef)))
|
|
112
|
-
}
|
|
113
|
-
static func getRequestedImageSize(_ size: CGSize, _ options: ImageAssetOptions) -> CGSize {
|
|
114
|
-
var reqWidth = options.width
|
|
115
|
-
if (reqWidth <= 0) {
|
|
116
|
-
reqWidth = size.width
|
|
117
|
-
}
|
|
118
|
-
var reqHeight = options.height
|
|
119
|
-
if (reqHeight <= 0) {
|
|
120
|
-
reqHeight = size.height
|
|
121
|
-
}
|
|
122
|
-
if (options.keepAspectRatio) {
|
|
123
|
-
let size2 = getAspectSafeDimensions(
|
|
124
|
-
size.width,
|
|
125
|
-
size.height,
|
|
126
|
-
reqWidth,
|
|
127
|
-
reqHeight
|
|
128
|
-
)
|
|
129
|
-
reqWidth = size2.width
|
|
130
|
-
reqHeight = size2.height
|
|
131
|
-
}
|
|
132
|
-
return CGSize(width: reqWidth, height: reqHeight)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// this scales an image but also return the image "rotated"
|
|
136
|
-
// based on imageOrientation
|
|
137
|
-
static func scaleImage(_ image: UIImage, _ scaledImageSize: CGSize) -> UIImage? {
|
|
138
|
-
// Create a graphics context
|
|
139
|
-
UIGraphicsBeginImageContextWithOptions(scaledImageSize, false, image.scale)
|
|
140
|
-
// Draw the image in the new size
|
|
141
|
-
image.draw(in: CGRect(
|
|
142
|
-
origin: .zero,
|
|
143
|
-
size: scaledImageSize
|
|
144
|
-
))
|
|
145
|
-
// Get the resized, scaled, and rotated image from the context
|
|
146
|
-
let resizedScaledRotatedImage = UIGraphicsGetImageFromCurrentImageContext()
|
|
147
|
-
|
|
148
|
-
// End the graphics context
|
|
149
|
-
UIGraphicsEndImageContext()
|
|
150
|
-
|
|
151
|
-
return resizedScaledRotatedImage
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
static func getImageSize(_ src: String) -> Dictionary<String, Any>? {
|
|
155
|
-
let url = NSURL.fileURL(withPath: src)
|
|
156
|
-
let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil);
|
|
157
|
-
if (imageSource == nil) {
|
|
158
|
-
// Error loading image
|
|
159
|
-
return nil;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let options = [kCGImageSourceShouldCache:false];
|
|
163
|
-
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, options as CFDictionary) as! [NSString: Any]? ;
|
|
164
|
-
var result: Dictionary<String, Any>?;
|
|
165
|
-
if (imageProperties != nil) {
|
|
166
|
-
let width = imageProperties![kCGImagePropertyPixelWidth] as! Double;
|
|
167
|
-
let height = imageProperties![kCGImagePropertyPixelHeight] as! Double;
|
|
168
|
-
var degrees: Int = 0
|
|
169
|
-
let orientation = imageProperties![kCGImagePropertyOrientation];
|
|
170
|
-
if (orientation != nil) {
|
|
171
|
-
let uiOrientation = UIImage.Orientation.init(CGImagePropertyOrientation(rawValue: UInt32(orientation as! Int))!);
|
|
172
|
-
switch uiOrientation {
|
|
173
|
-
case .down, .downMirrored:
|
|
174
|
-
degrees = 180
|
|
175
|
-
break
|
|
176
|
-
case .right, .rightMirrored:
|
|
177
|
-
degrees = -90
|
|
178
|
-
break
|
|
179
|
-
case .left, .leftMirrored:
|
|
180
|
-
degrees = 90
|
|
181
|
-
break
|
|
182
|
-
default:
|
|
183
|
-
degrees = 0
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
result = ["width": width, "height": height, "rotation":degrees];
|
|
1
|
+
package com.nativescript.apputils
|
|
2
|
+
|
|
3
|
+
import android.content.ContentResolver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Bitmap
|
|
6
|
+
import android.graphics.BitmapFactory
|
|
7
|
+
import android.graphics.Matrix
|
|
8
|
+
import android.net.Uri
|
|
9
|
+
import android.os.ParcelFileDescriptor
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import androidx.exifinterface.media.ExifInterface
|
|
12
|
+
import org.json.JSONException
|
|
13
|
+
import org.json.JSONObject
|
|
14
|
+
import java.io.FileDescriptor
|
|
15
|
+
import java.io.FileNotFoundException
|
|
16
|
+
import java.io.IOException
|
|
17
|
+
import kotlin.math.floor
|
|
18
|
+
import kotlin.math.max
|
|
19
|
+
import kotlin.math.min
|
|
20
|
+
import kotlin.concurrent.thread
|
|
21
|
+
|
|
22
|
+
import com.nativescript.apputils.FunctionCallback
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This class contains helper functions for processing images
|
|
26
|
+
*
|
|
27
|
+
* @constructor creates image util
|
|
28
|
+
*/
|
|
29
|
+
class ImageUtils {
|
|
30
|
+
|
|
31
|
+
class LoadImageOptions {
|
|
32
|
+
var options: JSONObject? = null
|
|
33
|
+
var sourceWidth = 0
|
|
34
|
+
var sourceHeight = 0
|
|
35
|
+
var width = 0
|
|
36
|
+
var maxWidth = 0
|
|
37
|
+
var height = 0
|
|
38
|
+
var maxHeight = 0
|
|
39
|
+
var keepAspectRatio = true
|
|
40
|
+
var autoScaleFactor = true
|
|
41
|
+
|
|
42
|
+
fun initWithJSON(jsonOpts: JSONObject)
|
|
43
|
+
{
|
|
44
|
+
options = jsonOpts
|
|
45
|
+
if (jsonOpts.has("resizeThreshold")) {
|
|
46
|
+
maxWidth = jsonOpts.optInt("resizeThreshold", maxWidth)
|
|
47
|
+
maxHeight = maxWidth
|
|
48
|
+
} else if (jsonOpts.has("maxSize")) {
|
|
49
|
+
maxWidth = jsonOpts.optInt("maxSize", maxWidth)
|
|
50
|
+
maxHeight = maxWidth
|
|
51
|
+
}
|
|
52
|
+
if (jsonOpts.has("width")) {
|
|
53
|
+
width = jsonOpts.optInt("width", width)
|
|
54
|
+
} else if (jsonOpts.has("maxWidth")) {
|
|
55
|
+
maxWidth = jsonOpts.optInt("maxWidth", maxWidth)
|
|
56
|
+
}
|
|
57
|
+
if (jsonOpts.has("height")) {
|
|
58
|
+
height = jsonOpts.optInt("height", height)
|
|
59
|
+
} else if (jsonOpts.has("maxHeight")) {
|
|
60
|
+
maxHeight = jsonOpts.optInt("maxHeight", maxHeight)
|
|
61
|
+
}
|
|
62
|
+
sourceWidth = jsonOpts.optInt("sourceWidth", sourceWidth)
|
|
63
|
+
sourceHeight = jsonOpts.optInt("sourceHeight", sourceHeight)
|
|
64
|
+
keepAspectRatio = jsonOpts.optBoolean("keepAspectRatio", keepAspectRatio)
|
|
65
|
+
autoScaleFactor = jsonOpts.optBoolean("autoScaleFactor", autoScaleFactor)
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
constructor(options: String?) {
|
|
69
|
+
if (options != null) {
|
|
70
|
+
try {
|
|
71
|
+
val jsonOpts = JSONObject(options)
|
|
72
|
+
initWithJSON(jsonOpts)
|
|
73
|
+
} catch (ignored: JSONException) {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
constructor(jsonOpts: JSONObject) {
|
|
78
|
+
initWithJSON(jsonOpts)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var resizeThreshold = 0
|
|
82
|
+
get() { return min(maxWidth, maxHeight)}
|
|
83
|
+
|
|
84
|
+
|
|
188
85
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
86
|
+
|
|
87
|
+
class ImageAssetOptions {
|
|
88
|
+
var width = 0
|
|
89
|
+
var height = 0
|
|
90
|
+
var keepAspectRatio = true
|
|
91
|
+
var autoScaleFactor = true
|
|
92
|
+
|
|
93
|
+
constructor(sourceSize: Pair<Int, Int>) {
|
|
94
|
+
width = sourceSize.first
|
|
95
|
+
height = sourceSize.second
|
|
96
|
+
}
|
|
97
|
+
constructor(sourceSize: Pair<Int, Int>, options: LoadImageOptions?) {
|
|
98
|
+
width = sourceSize.first
|
|
99
|
+
height = sourceSize.second
|
|
100
|
+
if (options != null) {
|
|
101
|
+
if (options.width > 0) {
|
|
102
|
+
width = options.width
|
|
103
|
+
}
|
|
104
|
+
if (options.height > 0) {
|
|
105
|
+
height = options.height
|
|
106
|
+
}
|
|
107
|
+
if (options.maxWidth > 0) {
|
|
108
|
+
width = min(
|
|
109
|
+
width,
|
|
110
|
+
options.maxWidth
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
if (options.maxHeight > 0) {
|
|
114
|
+
height = min(
|
|
115
|
+
height,
|
|
116
|
+
options.maxHeight
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
keepAspectRatio = options.keepAspectRatio
|
|
120
|
+
autoScaleFactor = options.autoScaleFactor
|
|
121
|
+
}
|
|
122
|
+
}
|
|
210
123
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
124
|
+
companion object {
|
|
125
|
+
fun getTargetFormat(format: String?): Bitmap.CompressFormat {
|
|
126
|
+
return when (format) {
|
|
127
|
+
"jpeg", "jpg" -> Bitmap.CompressFormat.JPEG
|
|
128
|
+
else -> Bitmap.CompressFormat.PNG
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Calculate an inSampleSize for use in a [BitmapFactory.Options] object when decoding
|
|
133
|
+
* bitmaps using the decode* methods from [BitmapFactory]. This implementation calculates
|
|
134
|
+
* the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
|
|
135
|
+
* having a width and height equal to or larger than the requested width and height.
|
|
136
|
+
*
|
|
137
|
+
* @param imageWidth The original width of the resulting bitmap
|
|
138
|
+
* @param imageHeight The original height of the resulting bitmap
|
|
139
|
+
* @param reqWidth The requested width of the resulting bitmap
|
|
140
|
+
* @param reqHeight The requested height of the resulting bitmap
|
|
141
|
+
* @return The value to be used for inSampleSize
|
|
142
|
+
*/
|
|
143
|
+
fun calculateInSampleSize(
|
|
144
|
+
imageWidth: Int,
|
|
145
|
+
imageHeight: Int,
|
|
146
|
+
reqWidth: Int,
|
|
147
|
+
reqHeight: Int
|
|
148
|
+
): Int {
|
|
149
|
+
// BEGIN_INCLUDE (calculate_sample_size)
|
|
150
|
+
// Raw height and width of image
|
|
151
|
+
var reqWidth = reqWidth
|
|
152
|
+
var reqHeight = reqHeight
|
|
153
|
+
reqWidth = if (reqWidth > 0) reqWidth else imageWidth
|
|
154
|
+
reqHeight = if (reqHeight > 0) reqHeight else imageHeight
|
|
155
|
+
var inSampleSize = 1
|
|
156
|
+
if (imageHeight > reqHeight || imageWidth > reqWidth) {
|
|
157
|
+
val halfHeight = imageHeight / 2
|
|
158
|
+
val halfWidth = imageWidth / 2
|
|
159
|
+
|
|
160
|
+
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
|
161
|
+
// height and width larger than the requested height and width.
|
|
162
|
+
while (halfHeight / inSampleSize > reqHeight && halfWidth / inSampleSize > reqWidth) {
|
|
163
|
+
inSampleSize *= 2
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// This offers some additional logic in case the image has a strange
|
|
167
|
+
// aspect ratio. For example, a panorama may have a much larger
|
|
168
|
+
// width than height. In these cases the total pixels might still
|
|
169
|
+
// end up being too large to fit comfortably in memory, so we should
|
|
170
|
+
// be more aggressive with sample down the image (=larger inSampleSize).
|
|
171
|
+
var totalPixels =
|
|
172
|
+
(imageWidth / inSampleSize * (imageHeight / inSampleSize)).toLong()
|
|
173
|
+
|
|
174
|
+
// Anything more than 2x the requested pixels we'll sample down further
|
|
175
|
+
val totalReqPixelsCap = (reqWidth * reqHeight * 2).toLong()
|
|
176
|
+
while (totalPixels > totalReqPixelsCap) {
|
|
177
|
+
inSampleSize *= 2
|
|
178
|
+
totalPixels =
|
|
179
|
+
(imageWidth / inSampleSize * (imageHeight / inSampleSize)).toLong()
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return inSampleSize
|
|
183
|
+
// END_INCLUDE (calculate_sample_size)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private fun getAspectSafeDimensions(
|
|
187
|
+
sourceWidth: Int,
|
|
188
|
+
sourceHeight: Int,
|
|
189
|
+
reqWidth: Int,
|
|
190
|
+
reqHeight: Int
|
|
191
|
+
): Pair<Int, Int> {
|
|
192
|
+
val widthCoef = sourceWidth.toDouble() / reqWidth.toDouble()
|
|
193
|
+
val heightCoef = sourceHeight.toDouble() / reqHeight.toDouble()
|
|
194
|
+
val imageRatio = sourceWidth.toDouble() / sourceHeight.toDouble()
|
|
195
|
+
// val aspectCoef = max(widthCoef, heightCoef)
|
|
196
|
+
if (widthCoef > heightCoef) {
|
|
197
|
+
return Pair(reqWidth, (reqWidth/imageRatio).toInt())
|
|
198
|
+
} else {
|
|
199
|
+
return Pair((reqHeight*imageRatio).toInt(), reqHeight)
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
// return Pair(
|
|
203
|
+
// ((sourceWidth / aspectCoef)).toInt(),
|
|
204
|
+
// ((sourceHeight / aspectCoef)).toInt()
|
|
205
|
+
// )
|
|
206
|
+
}
|
|
207
|
+
private fun getRequestedImageSize(
|
|
208
|
+
src: Pair<Int, Int>,
|
|
209
|
+
options: ImageAssetOptions
|
|
210
|
+
): Pair<Int, Int> {
|
|
211
|
+
var reqWidth = options.width
|
|
212
|
+
if (reqWidth <= 0) {
|
|
213
|
+
reqWidth = src.first
|
|
214
|
+
}
|
|
215
|
+
var reqHeight = options.height
|
|
216
|
+
if (reqHeight <= 0) {
|
|
217
|
+
reqHeight = src.second
|
|
218
|
+
}
|
|
219
|
+
if (options.keepAspectRatio) {
|
|
220
|
+
val (first, second) = getAspectSafeDimensions(
|
|
221
|
+
src.first,
|
|
222
|
+
src.second,
|
|
223
|
+
reqWidth,
|
|
224
|
+
reqHeight
|
|
225
|
+
)
|
|
226
|
+
reqWidth = first
|
|
227
|
+
reqHeight = second
|
|
228
|
+
}
|
|
229
|
+
return Pair(reqWidth, reqHeight)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private fun closePfd(pfd: ParcelFileDescriptor?) {
|
|
233
|
+
if (pfd != null) {
|
|
234
|
+
try {
|
|
235
|
+
pfd.close()
|
|
236
|
+
} catch (ignored: IOException) {
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private fun calculateAngleFromFile(filename: String): Int {
|
|
242
|
+
var rotationAngle = 0
|
|
243
|
+
val ei: ExifInterface
|
|
244
|
+
try {
|
|
245
|
+
ei = ExifInterface(filename)
|
|
246
|
+
val orientation = ei.getAttributeInt(
|
|
247
|
+
ExifInterface.TAG_ORIENTATION,
|
|
248
|
+
ExifInterface.ORIENTATION_NORMAL
|
|
249
|
+
)
|
|
250
|
+
when (orientation) {
|
|
251
|
+
ExifInterface.ORIENTATION_ROTATE_90 -> rotationAngle = 90
|
|
252
|
+
ExifInterface.ORIENTATION_ROTATE_180 -> rotationAngle = 180
|
|
253
|
+
ExifInterface.ORIENTATION_ROTATE_270 -> rotationAngle = 270
|
|
254
|
+
}
|
|
255
|
+
} catch (ignored: IOException) {
|
|
256
|
+
}
|
|
257
|
+
return rotationAngle
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
private fun calculateAngleFromFileDescriptor(fd: FileDescriptor): Int {
|
|
262
|
+
var rotationAngle = 0
|
|
263
|
+
val ei: ExifInterface
|
|
264
|
+
try {
|
|
265
|
+
ei = ExifInterface(fd)
|
|
266
|
+
val orientation = ei.getAttributeInt(
|
|
267
|
+
ExifInterface.TAG_ORIENTATION,
|
|
268
|
+
ExifInterface.ORIENTATION_NORMAL
|
|
269
|
+
)
|
|
270
|
+
when (orientation) {
|
|
271
|
+
ExifInterface.ORIENTATION_ROTATE_90 -> rotationAngle = 90
|
|
272
|
+
ExifInterface.ORIENTATION_ROTATE_180 -> rotationAngle = 180
|
|
273
|
+
ExifInterface.ORIENTATION_ROTATE_270 -> rotationAngle = 270
|
|
274
|
+
}
|
|
275
|
+
} catch (ignored: IOException) {
|
|
276
|
+
}
|
|
277
|
+
return rotationAngle
|
|
278
|
+
}
|
|
279
|
+
fun getImageSize(context: Context, src: String): IntArray {
|
|
280
|
+
val bitmapOptions = BitmapFactory.Options()
|
|
281
|
+
bitmapOptions.inJustDecodeBounds = true
|
|
282
|
+
var pfd: ParcelFileDescriptor? = null
|
|
283
|
+
if (src.startsWith("content://")) {
|
|
284
|
+
val uri = Uri.parse(src)
|
|
285
|
+
val resolver: ContentResolver = context.getContentResolver()
|
|
286
|
+
pfd = try {
|
|
287
|
+
resolver.openFileDescriptor(uri, "r")
|
|
288
|
+
} catch (e: FileNotFoundException) {
|
|
289
|
+
closePfd(pfd)
|
|
290
|
+
throw e;
|
|
291
|
+
}
|
|
292
|
+
BitmapFactory.decodeFileDescriptor(pfd!!.fileDescriptor, null, bitmapOptions)
|
|
293
|
+
} else {
|
|
294
|
+
BitmapFactory.decodeFile(src, bitmapOptions)
|
|
295
|
+
}
|
|
296
|
+
val rotationAngle: Int
|
|
297
|
+
if (pfd != null) {
|
|
298
|
+
rotationAngle = calculateAngleFromFileDescriptor(pfd.fileDescriptor)
|
|
299
|
+
closePfd(pfd)
|
|
300
|
+
} else {
|
|
301
|
+
rotationAngle = calculateAngleFromFile(src)
|
|
302
|
+
}
|
|
303
|
+
return intArrayOf(bitmapOptions.outWidth, bitmapOptions.outHeight, rotationAngle)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fun readBitmapFromFileSync(context: Context, src: String, options: LoadImageOptions?, sourceSize:Pair<Int, Int>?): Bitmap? {
|
|
307
|
+
// val start = System.currentTimeMillis()
|
|
308
|
+
var sourceSize = sourceSize
|
|
309
|
+
if (sourceSize == null && options?.sourceWidth != 0 && options?.sourceHeight != 0) {
|
|
310
|
+
sourceSize = Pair(options!!.sourceWidth, options!!.sourceHeight)
|
|
311
|
+
}
|
|
312
|
+
var bitmap: Bitmap?
|
|
313
|
+
val bitmapOptions = BitmapFactory.Options()
|
|
314
|
+
var pfd: ParcelFileDescriptor? = null
|
|
315
|
+
if (src.startsWith("content://")) {
|
|
316
|
+
val uri = Uri.parse(src)
|
|
317
|
+
val resolver: ContentResolver = context.getContentResolver()
|
|
318
|
+
pfd = try {
|
|
319
|
+
resolver.openFileDescriptor(uri, "r")
|
|
320
|
+
} catch (e: FileNotFoundException) {
|
|
321
|
+
closePfd(pfd)
|
|
322
|
+
throw e;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (sourceSize == null) {
|
|
326
|
+
bitmapOptions.inJustDecodeBounds = true
|
|
327
|
+
|
|
328
|
+
if (pfd != null) {
|
|
329
|
+
BitmapFactory.decodeFileDescriptor(pfd!!.fileDescriptor, null, bitmapOptions)
|
|
330
|
+
} else {
|
|
331
|
+
BitmapFactory.decodeFile(src, bitmapOptions)
|
|
332
|
+
}
|
|
333
|
+
sourceSize = Pair(bitmapOptions.outWidth, bitmapOptions.outHeight)
|
|
334
|
+
}
|
|
335
|
+
val opts = ImageAssetOptions(sourceSize, options)
|
|
336
|
+
|
|
337
|
+
val (first, second) = getRequestedImageSize(sourceSize, opts)
|
|
338
|
+
val sampleSize: Int = calculateInSampleSize(
|
|
339
|
+
sourceSize.first, sourceSize.second,
|
|
340
|
+
first,
|
|
341
|
+
second
|
|
342
|
+
)
|
|
343
|
+
val finalBitmapOptions = BitmapFactory.Options()
|
|
344
|
+
finalBitmapOptions.inSampleSize = sampleSize
|
|
345
|
+
if (sampleSize != 1) {
|
|
346
|
+
finalBitmapOptions.inScaled = true;
|
|
347
|
+
finalBitmapOptions.inDensity = sourceSize.first;
|
|
348
|
+
finalBitmapOptions.inTargetDensity = first * sampleSize;
|
|
349
|
+
} else {
|
|
350
|
+
finalBitmapOptions.inScaled = false;
|
|
351
|
+
}
|
|
352
|
+
// read as minimum bitmap as possible (slightly bigger than the requested size)
|
|
353
|
+
bitmap = if (pfd != null) {
|
|
354
|
+
BitmapFactory.decodeFileDescriptor(pfd.fileDescriptor, null, finalBitmapOptions)
|
|
355
|
+
} else {
|
|
356
|
+
BitmapFactory.decodeFile(src, finalBitmapOptions)
|
|
357
|
+
}
|
|
358
|
+
// Log.d("ImageAnalysis", "readBitmapFromFile in ${System.currentTimeMillis() - start} ms")
|
|
359
|
+
if (bitmap != null) {
|
|
360
|
+
val rotationAngle: Int
|
|
361
|
+
if (pfd != null) {
|
|
362
|
+
rotationAngle = calculateAngleFromFileDescriptor(pfd.fileDescriptor)
|
|
363
|
+
closePfd(pfd)
|
|
364
|
+
} else {
|
|
365
|
+
rotationAngle = calculateAngleFromFile(src)
|
|
366
|
+
}
|
|
367
|
+
// if (first !== bitmap.getWidth() || second !== bitmap.getHeight() || rotationAngle != 0) {
|
|
368
|
+
//
|
|
369
|
+
// val matrix = Matrix()
|
|
370
|
+
// if (first !== bitmap.getWidth() || second !== bitmap.getHeight()) {
|
|
371
|
+
// val scale = first.toFloat() / bitmap.width
|
|
372
|
+
// matrix.postScale(scale, scale)
|
|
373
|
+
// }
|
|
374
|
+
// if (rotationAngle != 0) {
|
|
375
|
+
// matrix.postRotate(rotationAngle.toFloat())
|
|
376
|
+
// }
|
|
377
|
+
// bitmap = Bitmap.createBitmap(
|
|
378
|
+
// bitmap,
|
|
379
|
+
// 0,
|
|
380
|
+
// 0,
|
|
381
|
+
// bitmap.getWidth(),
|
|
382
|
+
// bitmap.getHeight(),
|
|
383
|
+
// matrix,
|
|
384
|
+
// false
|
|
385
|
+
// )
|
|
386
|
+
// }
|
|
387
|
+
|
|
388
|
+
if (rotationAngle != 0) {
|
|
389
|
+
val matrix = Matrix()
|
|
390
|
+
matrix.postRotate(rotationAngle.toFloat())
|
|
391
|
+
bitmap = Bitmap.createBitmap(
|
|
392
|
+
bitmap,
|
|
393
|
+
0,
|
|
394
|
+
0,
|
|
395
|
+
bitmap.getWidth(),
|
|
396
|
+
bitmap.getHeight(),
|
|
397
|
+
matrix,
|
|
398
|
+
true
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
// Log.d("ImageAnalysis", "readBitmapFromFile2 in ${System.currentTimeMillis() - start} ms")
|
|
402
|
+
}
|
|
403
|
+
return bitmap
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
fun readBitmapFromFileSync(context: Context, src: String, opts: String?): Bitmap? {
|
|
407
|
+
return readBitmapFromFileSync(context, src, LoadImageOptions(opts), null)
|
|
408
|
+
}
|
|
409
|
+
fun readBitmapFromFile(context: Context, src: String, callback: FunctionCallback, opts: String?) {
|
|
410
|
+
thread(start = true) {
|
|
411
|
+
try {
|
|
412
|
+
callback.onResult(null, readBitmapFromFileSync(context, src, opts))
|
|
413
|
+
} catch (e: Exception) {
|
|
414
|
+
callback.onResult(e, null)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
229
418
|
}
|
|
230
|
-
|
|
231
|
-
}
|
|
419
|
+
}
|
|
@@ -2,18 +2,18 @@ import Foundation
|
|
|
2
2
|
import UIKit
|
|
3
3
|
|
|
4
4
|
extension UIImage.Orientation {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
5
|
+
init(_ cgOrientation: CGImagePropertyOrientation) {
|
|
6
|
+
switch cgOrientation {
|
|
7
|
+
case .up: self = .up
|
|
8
|
+
case .upMirrored: self = .upMirrored
|
|
9
|
+
case .down: self = .down
|
|
10
|
+
case .downMirrored: self = .downMirrored
|
|
11
|
+
case .left: self = .left
|
|
12
|
+
case .leftMirrored: self = .leftMirrored
|
|
13
|
+
case .right: self = .right
|
|
14
|
+
case .rightMirrored: self = .rightMirrored
|
|
16
15
|
}
|
|
16
|
+
}
|
|
17
17
|
}
|
|
18
18
|
@objcMembers
|
|
19
19
|
@objc(ImageUtils)
|
|
@@ -165,22 +165,25 @@ class ImageUtils : NSObject {
|
|
|
165
165
|
if (imageProperties != nil) {
|
|
166
166
|
let width = imageProperties![kCGImagePropertyPixelWidth] as! Double;
|
|
167
167
|
let height = imageProperties![kCGImagePropertyPixelHeight] as! Double;
|
|
168
|
-
let orientation = imageProperties![kCGImagePropertyOrientation] as! Int;
|
|
169
|
-
let uiOrientation = UIImage.Orientation.init(CGImagePropertyOrientation(rawValue: UInt32(orientation))!);
|
|
170
168
|
var degrees: Int = 0
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
169
|
+
let orientation = imageProperties![kCGImagePropertyOrientation];
|
|
170
|
+
if (orientation != nil) {
|
|
171
|
+
let uiOrientation = UIImage.Orientation.init(CGImagePropertyOrientation(rawValue: UInt32(orientation as! Int))!);
|
|
172
|
+
switch uiOrientation {
|
|
173
|
+
case .down, .downMirrored:
|
|
174
|
+
degrees = 180
|
|
175
|
+
break
|
|
176
|
+
case .right, .rightMirrored:
|
|
177
|
+
degrees = -90
|
|
178
|
+
break
|
|
179
|
+
case .left, .leftMirrored:
|
|
180
|
+
degrees = 90
|
|
181
|
+
break
|
|
182
|
+
default:
|
|
183
|
+
degrees = 0
|
|
184
|
+
}
|
|
183
185
|
}
|
|
186
|
+
|
|
184
187
|
result = ["width": width, "height": height, "rotation":degrees];
|
|
185
188
|
}
|
|
186
189
|
return result;
|
|
@@ -214,15 +217,15 @@ class ImageUtils : NSObject {
|
|
|
214
217
|
return readImageFromFileSync(src, options: options)
|
|
215
218
|
}
|
|
216
219
|
static func readImageFromFile(_ src: String, _ delegate: NCompletionDelegate?, _ stringOptions: String?) {
|
|
217
|
-
|
|
220
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
218
221
|
let options = toJSON(stringOptions)
|
|
219
|
-
// do {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// } catch {
|
|
223
|
-
// delegate?.onComplete(readImageFromFileSync(src, stringOptions) as NSObject?, error: error as NSError?)
|
|
224
|
-
//
|
|
225
|
-
// }
|
|
226
|
-
|
|
222
|
+
// do {
|
|
223
|
+
delegate?.onComplete(readImageFromFileSync(src, stringOptions) as NSObject?, error: nil)
|
|
224
|
+
|
|
225
|
+
// } catch {
|
|
226
|
+
// delegate?.onComplete(readImageFromFileSync(src, stringOptions) as NSObject?, error: error as NSError?)
|
|
227
|
+
//
|
|
228
|
+
// }
|
|
229
|
+
}
|
|
227
230
|
}
|
|
228
231
|
}
|