@akylas/nativescript-app-utils 1.0.0 → 2.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
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.0](https://github.com/akylas/nativescript-app-utils/compare/v2.0.0...v2.1.0) (2024-10-03)
7
+
8
+ **Note:** Version bump only for package @akylas/nativescript-app-utils
9
+
10
+ ## [2.0.0](https://github.com/akylas/nativescript-app-utils/compare/v1.0.0...v2.0.0) (2024-10-03)
11
+
12
+ ### Features
13
+
14
+ * new app utils ([00094ad](https://github.com/akylas/nativescript-app-utils/commit/00094ad84b16d13f565747ea235c05520261076d))
15
+
6
16
  ## 1.0.0 (2024-10-03)
7
17
 
8
18
  **Note:** Version bump only for package @akylas/nativescript-app-utils
@@ -1,14 +1,31 @@
1
+ import { ImageSource } from '@nativescript/core';
1
2
  export declare namespace AppUtilsAndroid {
2
3
  function listenForWindowInsets(onWindowInsetsChange: (result: [number, number, number, number, number]) => void): void;
3
4
  function prepareWindow(window: android.view.Window): void;
4
5
  function getDimensionFromInt(context: android.content.Context, intToGet: number): number;
5
6
  function getColorFromInt(context: android.content.Context, intToGet: number): number;
6
7
  function getColorFromName(context: android.content.Context, name: string): number;
7
- function restartApp(): any;
8
8
  function prepareActivity(activity: androidx.appcompat.app.AppCompatActivity): any;
9
9
  function applyDayNight(activity: androidx.appcompat.app.AppCompatActivity, applyDynamicColors: boolean): any;
10
10
  function applyDynamicColors(activity: androidx.appcompat.app.AppCompatActivity): any;
11
11
  }
12
+ export declare function restartApp(): any;
12
13
  export declare function setWorkerContextValue(key: any, value: any): void;
13
14
  export declare function getWorkerContextValue(key: any): any;
14
15
  export declare function getISO3Language(lang: any): string;
16
+ export declare function loadImageSync(imagePath: any, loadOptions?: {
17
+ width?: any;
18
+ height?: any;
19
+ resizeThreshold?: any;
20
+ sourceWidth?: any;
21
+ sourceHeight?: any;
22
+ jpegQuality?: any;
23
+ }): ImageSource;
24
+ export declare function loadImage(imagePath: any, loadOptions?: {
25
+ width?: any;
26
+ height?: any;
27
+ resizeThreshold?: any;
28
+ sourceWidth?: any;
29
+ sourceHeight?: any;
30
+ jpegQuality?: any;
31
+ }): Promise<any[]>;
package/index.android.js CHANGED
@@ -1,9 +1,31 @@
1
- import { Application, Utils } from '@nativescript/core';
1
+ import { Application, ImageSource, Utils } from '@nativescript/core';
2
+ function functionCallbackPromise(onCallback, transformer = (v) => v, errorHandler = (e) => e) {
3
+ return new Promise((resolve, reject) => {
4
+ const callback = new com.nativescript.apputils.FunctionCallback({
5
+ onResult(e, result) {
6
+ if (e) {
7
+ reject(errorHandler(e));
8
+ }
9
+ else {
10
+ try {
11
+ resolve(transformer(result));
12
+ }
13
+ catch (error) {
14
+ reject(error);
15
+ }
16
+ finally {
17
+ }
18
+ }
19
+ }
20
+ });
21
+ onCallback(callback);
22
+ });
23
+ }
2
24
  const NWorkerContext = com.nativescript.apputils.WorkersContext.Companion;
25
+ const NUtils = com.nativescript.apputils.Utils;
26
+ const UtilsCompanion = NUtils.Companion;
3
27
  export var AppUtilsAndroid;
4
28
  (function (AppUtilsAndroid) {
5
- const NUtils = com.nativescript.apputils.Utils;
6
- const UtilsCompanion = NUtils.Companion;
7
29
  function listenForWindowInsets(onWindowInsetsChange) {
8
30
  const rootView = Application.getRootView();
9
31
  if (rootView) {
@@ -29,10 +51,6 @@ export var AppUtilsAndroid;
29
51
  return UtilsCompanion.getColorFromName(context, name);
30
52
  }
31
53
  AppUtilsAndroid.getColorFromName = getColorFromName;
32
- function restartApp() {
33
- return UtilsCompanion.restartApp(Utils.android.getApplicationContext(), Application.android.startActivity);
34
- }
35
- AppUtilsAndroid.restartApp = restartApp;
36
54
  function prepareActivity(activity) {
37
55
  return UtilsCompanion.prepareActivity(activity);
38
56
  }
@@ -46,6 +64,9 @@ export var AppUtilsAndroid;
46
64
  }
47
65
  AppUtilsAndroid.applyDynamicColors = applyDynamicColors;
48
66
  })(AppUtilsAndroid || (AppUtilsAndroid = {}));
67
+ export function restartApp() {
68
+ return UtilsCompanion.restartApp(Utils.android.getApplicationContext(), Application.android.startActivity);
69
+ }
49
70
  export function setWorkerContextValue(key, value) {
50
71
  NWorkerContext.setValue(key, value);
51
72
  }
@@ -56,4 +77,14 @@ export function getISO3Language(lang) {
56
77
  const locale = java.util.Locale.forLanguageTag(lang);
57
78
  return locale.getISO3Language();
58
79
  }
80
+ export function loadImageSync(imagePath, loadOptions = {}) {
81
+ loadOptions.resizeThreshold ?? (loadOptions.resizeThreshold = 4500);
82
+ return new ImageSource(com.nativescript.apputils.ImageUtils.Companion.readBitmapFromFileSync(Utils.android.getApplicationContext(), imagePath, JSON.stringify(loadOptions)));
83
+ }
84
+ export function loadImage(imagePath, loadOptions = {}) {
85
+ loadOptions.resizeThreshold ?? (loadOptions.resizeThreshold = 4500);
86
+ return functionCallbackPromise((callback) => {
87
+ com.nativescript.apputils.ImageUtils.Companion.readBitmapFromFile(Utils.android.getApplicationContext(), imagePath, callback, JSON.stringify(loadOptions));
88
+ }, (e) => new ImageSource(e));
89
+ }
59
90
  //# sourceMappingURL=index.android.js.map
package/index.ios.d.ts CHANGED
@@ -1,14 +1,31 @@
1
+ import { ImageSource } from '@nativescript/core';
1
2
  export declare namespace AppUtilsAndroid {
2
3
  function listenForWindowInsets(onWindowInsetsChange: (result: [number, number, number, number, number]) => void): void;
3
4
  function prepareWindow(window: any): void;
4
5
  function getDimensionFromInt(context: any, intToGet: number): void;
5
6
  function getColorFromInt(context: any, intToGet: number): void;
6
7
  function getColorFromName(context: any, intToGet: number): void;
7
- function restartApp(): void;
8
8
  function prepareActivity(activity: any): void;
9
9
  function applyDayNight(activity: any, applyDynamicColors: boolean): void;
10
10
  function applyDynamicColors(activity: any): void;
11
11
  }
12
+ export declare function restartApp(): void;
12
13
  export declare function setWorkerContextValue(key: any, value: any): void;
13
14
  export declare function getWorkerContextValue(key: any): any;
14
15
  export declare function getISO3Language(lang: any): any;
16
+ export declare function loadImageSync(imagePath: any, loadOptions?: {
17
+ width?: any;
18
+ height?: any;
19
+ resizeThreshold?: any;
20
+ sourceWidth?: any;
21
+ sourceHeight?: any;
22
+ jpegQuality?: any;
23
+ }): ImageSource;
24
+ export declare function loadImage(imagePath: any, loadOptions?: {
25
+ width?: any;
26
+ height?: any;
27
+ resizeThreshold?: any;
28
+ sourceWidth?: any;
29
+ sourceHeight?: any;
30
+ jpegQuality?: any;
31
+ }): void;
package/index.ios.js CHANGED
@@ -1,3 +1,55 @@
1
+ import { ImageSource } from '@nativescript/core';
2
+ var NCompletionDelegateImpl = /** @class */ (function (_super) {
3
+ __extends(NCompletionDelegateImpl, _super);
4
+ function NCompletionDelegateImpl() {
5
+ return _super !== null && _super.apply(this, arguments) || this;
6
+ }
7
+ NCompletionDelegateImpl.prototype.onCompleteError = function (result, error) {
8
+ if (error) {
9
+ this.reject(error);
10
+ }
11
+ else {
12
+ if (this.shouldParse && typeof result === 'string') {
13
+ this.resolve(result ? JSON.parse(result) : null);
14
+ }
15
+ else {
16
+ this.resolve(result);
17
+ }
18
+ }
19
+ };
20
+ NCompletionDelegateImpl.prototype.onProgress = function (progress) {
21
+ var _a;
22
+ (_a = this.progress) === null || _a === void 0 ? void 0 : _a.call(this, progress);
23
+ };
24
+ NCompletionDelegateImpl.initWithResolveReject = function (resolve, reject, progress, shouldParse) {
25
+ if (shouldParse === void 0) { shouldParse = true; }
26
+ var delegate = NCompletionDelegateImpl.new();
27
+ delegate.resolve = resolve;
28
+ delegate.reject = reject;
29
+ delegate.onProgress = progress;
30
+ delegate.shouldParse = shouldParse;
31
+ return delegate;
32
+ };
33
+ NCompletionDelegateImpl.ObjCProtocols = [NCompletionDelegate];
34
+ return NCompletionDelegateImpl;
35
+ }(NSObject));
36
+ function functionCallbackPromise(onCallback, transformer = (v) => v, errorHandler = (e) => e, shouldParse, onProgress) {
37
+ return new Promise((resolve, reject) => {
38
+ const delegate = NCompletionDelegateImpl.initWithResolveReject((result) => {
39
+ try {
40
+ resolve(transformer(result));
41
+ }
42
+ catch (error) {
43
+ reject(error);
44
+ }
45
+ finally {
46
+ }
47
+ }, (error) => {
48
+ reject(errorHandler(error));
49
+ }, onProgress, shouldParse);
50
+ onCallback(delegate);
51
+ });
52
+ }
1
53
  export var AppUtilsAndroid;
2
54
  (function (AppUtilsAndroid) {
3
55
  function listenForWindowInsets(onWindowInsetsChange) { }
@@ -10,8 +62,6 @@ export var AppUtilsAndroid;
10
62
  AppUtilsAndroid.getColorFromInt = getColorFromInt;
11
63
  function getColorFromName(context, intToGet) { }
12
64
  AppUtilsAndroid.getColorFromName = getColorFromName;
13
- function restartApp() { }
14
- AppUtilsAndroid.restartApp = restartApp;
15
65
  function prepareActivity(activity) { }
16
66
  AppUtilsAndroid.prepareActivity = prepareActivity;
17
67
  function applyDayNight(activity, applyDynamicColors) { }
@@ -19,6 +69,7 @@ export var AppUtilsAndroid;
19
69
  function applyDynamicColors(activity) { }
20
70
  AppUtilsAndroid.applyDynamicColors = applyDynamicColors;
21
71
  })(AppUtilsAndroid || (AppUtilsAndroid = {}));
72
+ export function restartApp() { }
22
73
  export function setWorkerContextValue(key, value) {
23
74
  NWorkerContext.setValue(key, value);
24
75
  }
@@ -28,4 +79,14 @@ export function getWorkerContextValue(key) {
28
79
  export function getISO3Language(lang) {
29
80
  return NSLocale.alloc().initWithLocaleIdentifier(lang).ISO639_2LanguageCode();
30
81
  }
82
+ export function loadImageSync(imagePath, loadOptions = {}) {
83
+ loadOptions.resizeThreshold ?? (loadOptions.resizeThreshold = 4500);
84
+ return new ImageSource(ImageUtils.readImageFromFileSync(imagePath, JSON.stringify(loadOptions)));
85
+ }
86
+ export function loadImage(imagePath, loadOptions = {}) {
87
+ loadOptions.resizeThreshold ?? (loadOptions.resizeThreshold = 4500);
88
+ functionCallbackPromise((delegate) => {
89
+ ImageUtils.readImageFromFile(imagePath, delegate, JSON.stringify(loadOptions));
90
+ }, (e) => new ImageSource(e));
91
+ }
31
92
  //# sourceMappingURL=index.ios.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akylas/nativescript-app-utils",
3
- "version": "1.0.0",
3
+ "version": "2.1.0",
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": "209db3a7039c188b6cf65b97ab6384f70b6f9515"
66
+ "gitHead": "6ce2a1f258fe5424db0b4e4273e9fdd76d51e44c"
67
67
  }
@@ -3,10 +3,13 @@ dependencies {
3
3
  def androidxVersion = project.hasProperty("androidxVersion") ? project.androidxVersion : "1.2.0"
4
4
  def androidXAppCompat = project.hasProperty("androidXAppCompat") ? project.androidXAppCompat : "1.1.0"
5
5
  def androidXMaterial = project.hasProperty("androidXMaterial") ? project.androidXMaterial : "1.8.0"
6
+ def androidXExifInterface = project.hasProperty("androidXExifInterface") ? project.androidXExifInterface : "1.3.7"
6
7
 
7
8
  implementation "com.google.android.material:material:$androidXMaterial"
8
9
  implementation "androidx.appcompat:appcompat:$androidXAppCompat"
9
10
  implementation "androidx.core:core-splashscreen:$androidXSplashscreenVersion"
10
11
  implementation "androidx.core:core:$androidxVersion"
12
+ implementation "androidx.exifinterface:exifinterface:$androidXExifInterface"
13
+
11
14
 
12
15
  }
@@ -0,0 +1,5 @@
1
+ package com.nativescript.apputils
2
+
3
+ interface FunctionCallback {
4
+ fun onResult(e: Exception?, result: Any?)
5
+ }
@@ -0,0 +1,419 @@
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
+
85
+ }
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
+ }
123
+ }
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
+ }
418
+ }
419
+ }
@@ -0,0 +1,222 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ extension UIImage.Orientation {
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
15
+ }
16
+ }
17
+ }
18
+ @objcMembers
19
+ @objc(ImageUtils)
20
+ class ImageUtils : NSObject {
21
+
22
+ static func toJSON(_ str: String?) -> NSDictionary? {
23
+ guard let data = str?.data(using: .utf8, allowLossyConversion: false) else { return nil }
24
+ return try? (JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSDictionary?)
25
+ }
26
+ class LoadImageOptions {
27
+ var width = 0.0
28
+ var maxWidth = 0.0
29
+ var height = 0.0
30
+ var maxHeight = 0.0
31
+ var keepAspectRatio = true
32
+ var autoScaleFactor = true
33
+
34
+ func initWithJSONOptions(_ jsonOpts:NSDictionary?){
35
+ if let jsonOpts = jsonOpts {
36
+ if ((jsonOpts["resizeThreshold"]) != nil) {
37
+ maxWidth = jsonOpts["resizeThreshold"] as! Double
38
+ maxHeight = maxWidth
39
+ } else if ((jsonOpts["maxSize"]) != nil) {
40
+ maxWidth = jsonOpts["maxSize"] as! Double
41
+ maxHeight = maxWidth
42
+ }
43
+ if ((jsonOpts["width"]) != nil) {
44
+ width = jsonOpts["width"] as! Double
45
+ } else if ((jsonOpts["maxWidth"]) != nil) {
46
+ maxWidth = jsonOpts["maxWidth"] as! Double
47
+ }
48
+ if ((jsonOpts["height"]) != nil) {
49
+ height = jsonOpts["height"] as! Double
50
+ } else if ((jsonOpts["maxHeight"]) != nil) {
51
+ maxHeight = jsonOpts["maxHeight"] as! Double
52
+ }
53
+ if ((jsonOpts["keepAspectRatio"]) != nil) {
54
+ keepAspectRatio = jsonOpts["keepAspectRatio"] as! Bool
55
+ }
56
+ if ((jsonOpts["autoScaleFactor"]) != nil) {
57
+ autoScaleFactor = jsonOpts["autoScaleFactor"] as! Bool
58
+ }
59
+ }
60
+ }
61
+
62
+ init(_ optionsStr:String?) {
63
+ initWithJSONOptions(toJSON(optionsStr))
64
+ }
65
+ init( jsonOpts:NSDictionary?) {
66
+ initWithJSONOptions(jsonOpts)
67
+ }
68
+ }
69
+ class ImageAssetOptions {
70
+ var width = 0.0
71
+ var height = 0.0
72
+ var keepAspectRatio = true
73
+ var autoScaleFactor = true
74
+ init (_ size: CGSize, options: LoadImageOptions?) {
75
+ width = size.width
76
+ height = size.height
77
+ if (options != nil) {
78
+ if (options!.width > 0) {
79
+ width = options!.width
80
+ }
81
+ if (options!.height > 0) {
82
+ height = options!.height
83
+ }
84
+ if (options!.maxWidth > 0) {
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
+ let orientation = imageProperties![kCGImagePropertyOrientation] as! Int;
169
+ let uiOrientation = UIImage.Orientation.init(CGImagePropertyOrientation(rawValue: UInt32(orientation))!);
170
+ var degrees: Int = 0
171
+ switch uiOrientation {
172
+ case .down, .downMirrored:
173
+ degrees = 180
174
+ break
175
+ case .right, .rightMirrored:
176
+ degrees = -90
177
+ break
178
+ case .left, .leftMirrored:
179
+ degrees = 90
180
+ break
181
+ default:
182
+ degrees = 0
183
+ }
184
+ result = ["width": width, "height": height, "rotation":degrees];
185
+ }
186
+ return result;
187
+ }
188
+
189
+
190
+ static func readImageFromFileSync(_ src: String, options: NSDictionary?) -> UIImage? {
191
+ let image = UIImage(contentsOfFile: src)
192
+ if let image {
193
+ let size = image.size
194
+ let imageOrientation = image.imageOrientation
195
+ let loadImageOptions = LoadImageOptions(jsonOpts: options)
196
+ let opts = ImageAssetOptions(size, options: loadImageOptions)
197
+ let requestedSize = getRequestedImageSize(size, opts)
198
+ var result: UIImage? = image
199
+ if (requestedSize.width != size.width || requestedSize.height != size.height || imageOrientation != .up) {
200
+ result = scaleImage(image, requestedSize )
201
+ }
202
+ if (options?["jpegQuality"] != nil) {
203
+ result = UIImage.init(data: result!.jpegData(compressionQuality: CGFloat((options!["jpegQuality"] as! Int)) / 100.0)!)
204
+ }
205
+
206
+ return result
207
+ }
208
+ return nil
209
+
210
+ }
211
+
212
+ static func readImageFromFileSync(_ src: String, _ stringOptions: String?) -> UIImage? {
213
+ let options = toJSON(stringOptions)
214
+ return readImageFromFileSync(src, options: options)
215
+ }
216
+ static func readImageFromFile(_ src: String, _ delegate: CompletionDelegate?, _ stringOptions: String?) -> UIImage? {
217
+ DispatchQueue.global(qos: .userInitiated).async {
218
+ let options = toJSON(stringOptions)
219
+ delegate?.onComplete(readImageFromFileSync(src, stringOptions) as NSObject, error: _error as NSError?)
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,8 @@
1
+ import ObjectiveC
2
+ import Foundation
3
+
4
+ @objc(NCompletionDelegate)
5
+ protocol NCompletionDelegate {
6
+ func onComplete(_ result: NSObject?, error:NSError?)
7
+ func onProgress(_ progress: Int)
8
+ }
Binary file