Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(image-source): add saveToFileAsync, toBase64StringAsync & resizeAsync #9404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/toolbox/src/main-page.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Button text="a11y" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
<Button text="css-playground" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
<Button text="visibility-vs-hidden" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
<Button text="image-async" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo"/>
</StackLayout>
</ScrollView>
</StackLayout>
Expand Down
35 changes: 35 additions & 0 deletions apps/toolbox/src/pages/image-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Page, ImageSource, Observable, EventData, knownFolders, path } from '@nativescript/core';

let page: Page;

export function navigatingTo(args: EventData) {
page = <Page>args.object;
page.bindingContext = new SampleData();
}

export class SampleData extends Observable {
src: string = 'https://source.unsplash.com/random';
savedData: string = '';
resizedImage: ImageSource;
async save() {
try {
const imageSource = await ImageSource.fromUrl(this.src);
const tempFile = path.join(knownFolders.temp().path, `${Date.now()}.jpg`);
const base64 = imageSource.toBase64StringAsync('jpg');
const image = imageSource.saveToFileAsync(tempFile, 'jpg');
const resizedImage = imageSource.resizeAsync(50);
const results = await Promise.all([image, base64, resizedImage]);
const saved = results[0];
const base64Result = results[1];
if (saved) {
this.set('savedData', tempFile);
console.log('ImageAsset saved', saved, tempFile);
}
console.log('base64', base64Result);
console.log(results[2].width, results[2].height);
this.set('resizedImage', results[2]);
} catch (e) {
console.log('Failed to save ImageAsset');
}
}
}
13 changes: 13 additions & 0 deletions apps/toolbox/src/pages/image-async.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">

<GridLayout padding="20">
<ScrollView>
<StackLayout>
<Button text="Image Async?" tap="{{save}}" />
<Image width="200" height="200" src="{{ src }}"/>
<Image marginTop="10" width="200" height="200" src="{{ savedData }}" />
<Image marginTop="10" width="50" height="50" imageSource="{{ resizedImage }}" />
</StackLayout>
</ScrollView>
</GridLayout>
</Page>
67 changes: 67 additions & 0 deletions packages/core/image-source/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,29 @@ export class ImageSource implements ImageSourceDefinition {
return res;
}

public saveToFileAsync(path: string, format: 'png' | 'jpeg' | 'jpg', quality = 100): Promise<boolean> {
return new Promise((resolve, reject) => {
org.nativescript.widgets.Utils.saveToFileAsync(
this.android,
path,
format,
quality,
new org.nativescript.widgets.Utils.AsyncImageCallback({
onSuccess(param0: boolean) {
resolve(param0);
},
onError(param0: java.lang.Exception) {
if (param0) {
reject(param0.getMessage());
} else {
reject();
}
},
})
);
});
}

public toBase64String(format: 'png' | 'jpeg' | 'jpg', quality = 100): string {
if (!this.android) {
return null;
Expand All @@ -334,12 +357,56 @@ export class ImageSource implements ImageSourceDefinition {
return outputStream.toString();
}

public toBase64StringAsync(format: 'png' | 'jpeg' | 'jpg', quality = 100): Promise<string> {
return new Promise((resolve, reject) => {
org.nativescript.widgets.Utils.toBase64StringAsync(
this.android,
format,
quality,
new org.nativescript.widgets.Utils.AsyncImageCallback({
onSuccess(param0: string) {
resolve(param0);
},
onError(param0: java.lang.Exception) {
if (param0) {
reject(param0.getMessage());
} else {
reject();
}
},
})
);
});
}

public resize(maxSize: number, options?: any): ImageSource {
const dim = getScaledDimensions(this.android.getWidth(), this.android.getHeight(), maxSize);
const bm: android.graphics.Bitmap = android.graphics.Bitmap.createScaledBitmap(this.android, dim.width, dim.height, options && options.filter);

return new ImageSource(bm);
}

public resizeAsync(maxSize: number, options?: any): Promise<ImageSource> {
return new Promise((resolve, reject) => {
org.nativescript.widgets.Utils.resizeAsync(
this.android,
maxSize,
JSON.stringify(options || {}),
new org.nativescript.widgets.Utils.AsyncImageCallback({
onSuccess(param0: any) {
resolve(new ImageSource(param0));
},
onError(param0: java.lang.Exception) {
if (param0) {
reject(param0.getMessage());
} else {
reject();
}
},
})
);
});
}
}

function getTargetFormat(format: 'png' | 'jpeg' | 'jpg'): android.graphics.Bitmap.CompressFormat {
Expand Down
28 changes: 28 additions & 0 deletions packages/core/image-source/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,28 @@ export class ImageSource {
*/
saveToFile(path: string, format: 'png' | 'jpeg' | 'jpg', quality?: number): boolean;

/**
* Saves this instance to the specified file, using the provided image format and quality asynchronously.
* @param path The path of the file on the file system to save to.
* @param format The format (encoding) of the image.
* @param quality Optional parameter, specifying the quality of the encoding. Defaults to the maximum available quality. Quality varies on a scale of 0 to 100.
*/
saveToFileAsync(path: string, format: 'png' | 'jpeg' | 'jpg', quality?: number): Promise<boolean>;

/**
* Converts the image to base64 encoded string, using the provided image format and quality.
* @param format The format (encoding) of the image.
* @param quality Optional parameter, specifying the quality of the encoding. Defaults to the maximum available quality. Quality varies on a scale of 0 to 100.
*/
toBase64String(format: 'png' | 'jpeg' | 'jpg', quality?: number): string;

/**
* Converts the image to base64 encoded string, using the provided image format and quality asynchronously.
* @param format The format (encoding) of the image.
* @param quality Optional parameter, specifying the quality of the encoding. Defaults to the maximum available quality. Quality varies on a scale of 0 to 100.
*/
toBase64StringAsync(format: 'png' | 'jpeg' | 'jpg', quality?: number): Promise<string>;

/**
* Returns a new ImageSource that is a resized version of this image with the same aspect ratio, but the max dimension set to the provided maxSize.
* @param maxSize The maximum pixel dimension of the resulting image.
Expand All @@ -217,6 +232,19 @@ export class ImageSource {
* bilinear filtering is typically minimal and the improved image quality is significant.
*/
resize(maxSize: number, options?: any): ImageSource;

/**
* Returns a new ImageSource that is a resized version of this image with the same aspect ratio, but the max dimension set to the provided maxSize asynchronously.
* @param maxSize The maximum pixel dimension of the resulting image.
* @param options Optional parameter, Only used for android, options.filter is a boolean which
* determines whether or not bilinear filtering should be used when scaling the bitmap.
* If this is true then bilinear filtering will be used when scaling which has
* better image quality at the cost of worse performance. If this is false then
* nearest-neighbor scaling is used instead which will have worse image quality
* but is faster. Recommended default is to set filter to 'true' as the cost of
* bilinear filtering is typically minimal and the improved image quality is significant.
*/
resizeAsync(maxSize: number, options?: any): Promise<ImageSource>;
}

/**
Expand Down
79 changes: 79 additions & 0 deletions packages/core/image-source/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,33 @@ export class ImageSource implements ImageSourceDefinition {
return false;
}

public saveToFileAsync(path: string, format: 'png' | 'jpeg' | 'jpg', quality?: number): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
if (!this.ios) {
reject(false);
}
let isSuccess = false;
try {
if (quality) {
quality = (quality - 0) / (100 - 0); // Normalize quality on a scale of 0 to 1
}
const main_queue = dispatch_get_current_queue();
const background_queue = dispatch_get_global_queue(qos_class_t.QOS_CLASS_DEFAULT, 0);
dispatch_async(background_queue, () => {
const data = getImageData(this.ios, format, quality);
if (data) {
isSuccess = NSFileManager.defaultManager.createFileAtPathContentsAttributes(path, data, null);
}
dispatch_async(main_queue, () => {
resolve(isSuccess);
});
});
} catch (ex) {
reject(ex);
}
});
}

public toBase64String(format: 'png' | 'jpeg' | 'jpg', quality?: number): string {
let res = null;
if (!this.ios) {
Expand All @@ -335,6 +362,33 @@ export class ImageSource implements ImageSourceDefinition {
return res;
}

public toBase64StringAsync(format: 'png' | 'jpeg' | 'jpg', quality?: number): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (!this.ios) {
reject(null);
}
let result = null;
try {
if (quality) {
quality = (quality - 0) / (100 - 0); // Normalize quality on a scale of 0 to 1
}
const main_queue = dispatch_get_current_queue();
const background_queue = dispatch_get_global_queue(qos_class_t.QOS_CLASS_DEFAULT, 0);
dispatch_async(background_queue, () => {
const data = getImageData(this.ios, format, quality);
if (data) {
result = data.base64Encoding();
}
dispatch_async(main_queue, () => {
resolve(result);
});
});
} catch (ex) {
reject(ex);
}
});
}

public resize(maxSize: number, options?: any): ImageSource {
const size: CGSize = this.ios.size;
const dim = getScaledDimensions(size.width, size.height, maxSize);
Expand All @@ -349,6 +403,31 @@ export class ImageSource implements ImageSourceDefinition {

return new ImageSource(resizedImage);
}

public resizeAsync(maxSize: number, options?: any): Promise<ImageSource> {
return new Promise((resolve, reject) => {
if (!this.ios) {
reject(null);
}
const main_queue = dispatch_get_current_queue();
const background_queue = dispatch_get_global_queue(qos_class_t.QOS_CLASS_DEFAULT, 0);
dispatch_async(background_queue, () => {
const size: CGSize = this.ios.size;
const dim = getScaledDimensions(size.width, size.height, maxSize);

const newSize: CGSize = CGSizeMake(dim.width, dim.height);

UIGraphicsBeginImageContextWithOptions(newSize, options?.opaque ?? false, this.ios.scale);
this.ios.drawInRect(CGRectMake(0, 0, newSize.width, newSize.height));

const resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(main_queue, () => {
resolve(new ImageSource(resizedImage));
});
});
});
}
}

function getFileName(path: string): string {
Expand Down
Binary file modified packages/core/platforms/android/widgets-release.aar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,9 @@ declare module org {
public static class: java.lang.Class<org.nativescript.widgets.Utils>;
public static loadImageAsync(param0: globalAndroid.content.Context, param1: string, param2: string, param3: number, param4: number, param5: org.nativescript.widgets.Utils.AsyncImageCallback): void;
public static drawBoxShadow(param0: globalAndroid.view.View, param1: string): void;
public static saveToFileAsync(param0: globalAndroid.graphics.Bitmap, param1: string, param2: string, param3: number, param4: org.nativescript.widgets.Utils.AsyncImageCallback): void;
public static toBase64StringAsync(param0: globalAndroid.graphics.Bitmap, param1: string, param2: number, param3: org.nativescript.widgets.Utils.AsyncImageCallback): void;
public static resizeAsync(param0: globalAndroid.graphics.Bitmap, param1: number, param2: string, param3: org.nativescript.widgets.Utils.AsyncImageCallback): void;
public constructor();
}
export module Utils {
Expand All @@ -644,11 +647,11 @@ declare module org {
* Constructs a new instance of the org.nativescript.widgets.Utils$AsyncImageCallback interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
*/
public constructor(implementation: {
onSuccess(param0: globalAndroid.graphics.Bitmap): void;
onSuccess(param0: any): void;
onError(param0: java.lang.Exception): void;
});
public constructor();
public onSuccess(param0: globalAndroid.graphics.Bitmap): void;
public onSuccess(param0: any): void;
public onError(param0: java.lang.Exception): void;
}
export class ImageAssetOptions {
Expand Down
Loading