@akylas/nativescript-app-utils 1.0.0 → 2.1.1
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 +16 -0
- package/index.android.d.ts +19 -2
- package/index.android.js +40 -9
- package/index.ios.d.ts +18 -1
- package/index.ios.js +63 -2
- package/package.json +2 -2
- package/platforms/android/include.gradle +2 -1
- package/platforms/android/java/com/nativescript/apputils/FunctionCallback.kt +5 -0
- package/platforms/android/java/com/nativescript/apputils/ImageUtils.kt +419 -0
- package/platforms/android/java/com/nativescript/apputils/Utils.kt +5 -2
- package/platforms/android/nativescript_app_utils.aar +0 -0
- package/platforms/ios/src/ImageUtils.swift +222 -0
- package/platforms/ios/src/NCompletionDelegate.swift +8 -0
- package/platforms/android/app_utils.aar +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
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.1](https://github.com/akylas/nativescript-app-utils/compare/v2.1.0...v2.1.1) (2024-10-05)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **android:** allow to prepare activity without enabling dynamic colors ([103a466](https://github.com/akylas/nativescript-app-utils/commit/103a4669487767b0674dc06a3d1a991717300b5c))
|
|
11
|
+
|
|
12
|
+
## [2.1.0](https://github.com/akylas/nativescript-app-utils/compare/v2.0.0...v2.1.0) (2024-10-03)
|
|
13
|
+
|
|
14
|
+
**Note:** Version bump only for package @akylas/nativescript-app-utils
|
|
15
|
+
|
|
16
|
+
## [2.0.0](https://github.com/akylas/nativescript-app-utils/compare/v1.0.0...v2.0.0) (2024-10-03)
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* new app utils ([00094ad](https://github.com/akylas/nativescript-app-utils/commit/00094ad84b16d13f565747ea235c05520261076d))
|
|
21
|
+
|
|
6
22
|
## 1.0.0 (2024-10-03)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package @akylas/nativescript-app-utils
|
package/index.android.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: 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
|
|
8
|
-
function prepareActivity(activity: androidx.appcompat.app.AppCompatActivity): any;
|
|
8
|
+
function prepareActivity(activity: androidx.appcompat.app.AppCompatActivity, applyDynamicColors?: boolean): 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,12 +51,8 @@ export var AppUtilsAndroid;
|
|
|
29
51
|
return UtilsCompanion.getColorFromName(context, name);
|
|
30
52
|
}
|
|
31
53
|
AppUtilsAndroid.getColorFromName = getColorFromName;
|
|
32
|
-
function
|
|
33
|
-
return UtilsCompanion.
|
|
34
|
-
}
|
|
35
|
-
AppUtilsAndroid.restartApp = restartApp;
|
|
36
|
-
function prepareActivity(activity) {
|
|
37
|
-
return UtilsCompanion.prepareActivity(activity);
|
|
54
|
+
function prepareActivity(activity, applyDynamicColors = true) {
|
|
55
|
+
return UtilsCompanion.prepareActivity(activity, applyDynamicColors);
|
|
38
56
|
}
|
|
39
57
|
AppUtilsAndroid.prepareActivity = prepareActivity;
|
|
40
58
|
function applyDayNight(activity, applyDynamicColors) {
|
|
@@ -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.
|
|
3
|
+
"version": "2.1.1",
|
|
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": "23dbebb1eed5374be644bb39f8dd2ede6ed43a86"
|
|
67
67
|
}
|
|
@@ -3,10 +3,11 @@ 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"
|
|
11
|
-
|
|
12
|
+
implementation "androidx.exifinterface:exifinterface:$androidXExifInterface"
|
|
12
13
|
}
|
|
@@ -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
|
+
}
|
|
@@ -65,8 +65,11 @@ class Utils {
|
|
|
65
65
|
val systemLocale: Locale?
|
|
66
66
|
get() = ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0]
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
@JvmOverloads
|
|
69
|
+
fun prepareActivity(activity: AppCompatActivity, applyDynamicColors: Boolean = true) {
|
|
70
|
+
if (applyDynamicColors) {
|
|
71
|
+
DynamicColors.applyToActivityIfAvailable(activity)
|
|
72
|
+
}
|
|
70
73
|
activity.installSplashScreen()
|
|
71
74
|
prepareWindow(activity.window)
|
|
72
75
|
}
|
|
Binary file
|
|
@@ -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
|
+
}
|
|
Binary file
|