From bd12bafb4aae8f1c523be4c7e04fa73722092304 Mon Sep 17 00:00:00 2001 From: Ken Southerland Date: Mon, 29 Jun 2020 22:17:39 -0700 Subject: [PATCH] feat(ImageSource): resize method (#8678) --- api-reports/NativeScript.api.md | 2 ++ .../image-source/image-source-common.ts | 27 +++++++++++++++++++ .../image-source/image-source.android.ts | 14 ++++++++++ .../image-source/image-source.d.ts | 13 +++++++++ .../image-source/image-source.ios.ts | 20 ++++++++++++++ tests/app/image-source/image-source-tests.ts | 10 +++++++ 6 files changed, 86 insertions(+) create mode 100644 nativescript-core/image-source/image-source-common.ts diff --git a/api-reports/NativeScript.api.md b/api-reports/NativeScript.api.md index 98a165b84..9c0d3c5d7 100644 --- a/api-reports/NativeScript.api.md +++ b/api-reports/NativeScript.api.md @@ -1198,6 +1198,8 @@ export class ImageSource { // @deprecated (undocumented) loadFromResource(name: string): boolean; + resize(maxSize: number, options?: any): ImageSource; + rotationAngle: number; saveToFile(path: string, format: "png" | "jpeg" | "jpg", quality?: number): boolean; diff --git a/nativescript-core/image-source/image-source-common.ts b/nativescript-core/image-source/image-source-common.ts new file mode 100644 index 000000000..657ee7f9b --- /dev/null +++ b/nativescript-core/image-source/image-source-common.ts @@ -0,0 +1,27 @@ +export function getScaledDimensions( + width: number, + height: number, + maxSize: number +) { + if (height >= width) { + if (height <= maxSize) { + // if image already smaller than the required height + return { width, height }; + } + + return { + width: Math.round((maxSize * width) / height), + height: maxSize + }; + } + + if (width <= maxSize) { + // if image already smaller than the required width + return { width, height }; + } + + return { + width: maxSize, + height: Math.round((maxSize * height) / width) + }; +} diff --git a/nativescript-core/image-source/image-source.android.ts b/nativescript-core/image-source/image-source.android.ts index 18a0fd76e..aaa24fddf 100644 --- a/nativescript-core/image-source/image-source.android.ts +++ b/nativescript-core/image-source/image-source.android.ts @@ -10,6 +10,8 @@ import { getNativeApplication } from "../application"; import { Font } from "../ui/styling/font"; import { Color } from "../color"; +import { getScaledDimensions } from "./image-source-common"; + export { isFileOrResourcePath }; let http: typeof httpModule; @@ -337,6 +339,18 @@ export class ImageSource implements ImageSourceDefinition { return outputStream.toString(); } + + 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); + } } function getTargetFormat(format: "png" | "jpeg" | "jpg"): android.graphics.Bitmap.CompressFormat { diff --git a/nativescript-core/image-source/image-source.d.ts b/nativescript-core/image-source/image-source.d.ts index a33db3123..20c4fbcda 100644 --- a/nativescript-core/image-source/image-source.d.ts +++ b/nativescript-core/image-source/image-source.d.ts @@ -209,6 +209,19 @@ export class ImageSource { * @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; + + /** + * 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. + * @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. + */ + resize(maxSize: number, options?: any): ImageSource; } /** diff --git a/nativescript-core/image-source/image-source.ios.ts b/nativescript-core/image-source/image-source.ios.ts index c0d007a1a..a82b0faeb 100644 --- a/nativescript-core/image-source/image-source.ios.ts +++ b/nativescript-core/image-source/image-source.ios.ts @@ -9,6 +9,8 @@ import { Color } from "../color"; import { path as fsPath, knownFolders } from "../file-system"; import { isFileOrResourcePath, RESOURCE_PREFIX, layout } from "../utils/utils"; +import { getScaledDimensions } from "./image-source-common"; + export { isFileOrResourcePath }; let http: typeof httpModule; @@ -336,6 +338,24 @@ export class ImageSource implements ImageSourceDefinition { return res; } + + public resize(maxSize: number, options?: any): ImageSource { + const size: CGSize = this.ios.size; + const dim = getScaledDimensions( + size.width, + size.height, + maxSize + ); + + const newSize: CGSize = CGSizeMake(dim.width, dim.height); + UIGraphicsBeginImageContextWithOptions(newSize, true, this.ios.scale); + this.ios.drawInRect(CGRectMake(0, 0, newSize.width, newSize.height)); + + const resizedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return new ImageSource(resizedImage); + } } function getFileName(path: string): string { diff --git a/tests/app/image-source/image-source-tests.ts b/tests/app/image-source/image-source-tests.ts index 1e8c66cf2..962b5adda 100644 --- a/tests/app/image-source/image-source-tests.ts +++ b/tests/app/image-source/image-source-tests.ts @@ -295,3 +295,13 @@ export function testLoadFromFontIconCode() { TKUnit.assert(img.width !== null, "img.width"); TKUnit.assert(img.height !== null, "img.width"); } + +export function testResize() { + const img = ImageSource.fromFileSync(imagePath); + + const newSize = Math.floor(Math.max(img.width, img.height) / 2); + + const resized = img.resize(newSize); + + TKUnit.assert(resized.width === newSize || resized.height === newSize, "Image not resized correctly"); +}