From 89ed02a43c85223add1403119a23b4ddeb514cd0 Mon Sep 17 00:00:00 2001 From: ryumon0309 Date: Fri, 17 Jan 2025 10:38:05 +0900 Subject: [PATCH 1/2] fix(android): Setting String Value to Width And Height --- packages/core/image-asset/index.android.ts | 71 +++++++++++++++++++--- packages/core/image-asset/index.d.ts | 6 +- packages/core/image-asset/index.ios.ts | 42 ++++++++++--- 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/packages/core/image-asset/index.android.ts b/packages/core/image-asset/index.android.ts index 055caeff60..8c0a267291 100644 --- a/packages/core/image-asset/index.android.ts +++ b/packages/core/image-asset/index.android.ts @@ -1,17 +1,17 @@ -import { ImageAssetBase, getRequestedImageSize } from './image-asset-common'; -import { path as fsPath, knownFolders } from '../file-system'; -import { ad } from '../utils'; -import { Screen } from '../platform'; -export * from './image-asset-common'; +import { ImageAssetBase, getRequestedImageSize } from "./image-asset-common"; +import { path as fsPath, knownFolders } from "../file-system"; +import { ad } from "../utils"; +import { Screen } from "../platform"; +export * from "./image-asset-common"; export class ImageAsset extends ImageAssetBase { private _android: string; //file name of the image constructor(asset: string) { super(); - let fileName = typeof asset === 'string' ? asset.trim() : ''; - if (fileName.indexOf('~/') === 0) { - fileName = fsPath.join(knownFolders.currentApp().path, fileName.replace('~/', '')); + let fileName = typeof asset === "string" ? asset.trim() : ""; + if (fileName.indexOf("~/") === 0) { + fileName = fsPath.join(knownFolders.currentApp().path, fileName.replace("~/", "")); } this.android = fileName; } @@ -25,7 +25,58 @@ export class ImageAsset extends ImageAssetBase { this._android = value; } - public getImageAsync(callback: (image, error) => void) { + /** + * Validates and adjusts image dimensions to prevent bitmap size exceeding Android's 32-bit limit. + * Android has a limitation where bitmap size (width * height) cannot exceed 2^31-1. + * This method ensures the dimensions are within safe bounds while maintaining aspect ratio. + * + * @param {number|string} width - The desired width of the image + * @param {number|string} height - The desired height of the image + * @returns {{ width: number; height: number }} Object containing validated dimensions + * + * @example + * // Returns safe dimensions that won't exceed Android's bitmap size limit + * const safe = validateDimensions("4000", "3000"); + */ + private _validateDimensions(width: number | string, height: number | string): { width: number; height: number } { + const parseSize = (size: number | string): number => { + return typeof size === "string" ? parseInt(size, 10) : size; + }; + + let w = parseSize(width); + let h = parseSize(height); + + // Check for 32-bit limitation (2^31 - 1, leaving some headroom) + const MAX_DIMENSION = Math.floor(Math.sqrt(Math.pow(2, 31) - 1)); + + // Check if each dimension exceeds MAX_DIMENSION + w = Math.min(w, MAX_DIMENSION); + h = Math.min(h, MAX_DIMENSION); + + // Check the total pixel count + if (w * h > Math.pow(2, 31) - 1) { + const scale = Math.sqrt((Math.pow(2, 31) - 1) / (w * h)); + w = Math.floor(w * scale); + h = Math.floor(h * scale); + } + + return { width: w, height: h }; + } + + /** + * Asynchronously loads an image with the specified dimensions. + * Handles string/number dimension types and applies size validation for Android bitmap limitations. + * + * @param {Function} callback - Callback function that receives (image, error) + * @param {{ width?: number; height?: number }} [options] - Optional dimensions for the image + */ + public getImageAsync(callback: (image, error) => void, options?: { width?: number; height?: number }) { + if (options?.width || options?.height) { + const validDimensions = this._validateDimensions(options.width || this.options.width || 0, options.height || this.options.height || 0); + options.width = validDimensions.width; + options.height = validDimensions.height; + } + org.nativescript.widgets.Utils.loadImageAsync( ad.getApplicationContext(), this.android, @@ -39,7 +90,7 @@ export class ImageAsset extends ImageAssetBase { onError(ex) { callback(null, ex); }, - }) + }), ); } } diff --git a/packages/core/image-asset/index.d.ts b/packages/core/image-asset/index.d.ts index c6a7d247d4..770aecd189 100644 --- a/packages/core/image-asset/index.d.ts +++ b/packages/core/image-asset/index.d.ts @@ -1,4 +1,4 @@ -import { Observable } from '../data/observable'; +import { Observable } from "../data/observable"; export class ImageAsset extends Observable { constructor(asset: any); @@ -10,8 +10,8 @@ export class ImageAsset extends Observable { } export interface ImageAssetOptions { - width?: number; - height?: number; + width?: number | string; + height?: number | string; keepAspectRatio?: boolean; autoScaleFactor?: boolean; } diff --git a/packages/core/image-asset/index.ios.ts b/packages/core/image-asset/index.ios.ts index 072be6fdb1..a992e57aba 100644 --- a/packages/core/image-asset/index.ios.ts +++ b/packages/core/image-asset/index.ios.ts @@ -1,17 +1,17 @@ -import { ImageAssetBase, getRequestedImageSize } from './image-asset-common'; -import { path as fsPath, knownFolders } from '../file-system'; -import { queueGC } from '../utils'; +import { ImageAssetBase, getRequestedImageSize } from "./image-asset-common"; +import { path as fsPath, knownFolders } from "../file-system"; +import { queueGC } from "../utils"; -export * from './image-asset-common'; +export * from "./image-asset-common"; export class ImageAsset extends ImageAssetBase { private _ios: PHAsset; constructor(asset: string | PHAsset | UIImage) { super(); - if (typeof asset === 'string') { - if (asset.indexOf('~/') === 0) { - asset = fsPath.join(knownFolders.currentApp().path, asset.replace('~/', '')); + if (typeof asset === "string") { + if (asset.indexOf("~/") === 0) { + asset = fsPath.join(knownFolders.currentApp().path, asset.replace("~/", "")); } this.nativeImage = UIImage.imageWithContentsOfFile(asset); @@ -31,9 +31,25 @@ export class ImageAsset extends ImageAssetBase { this._ios = value; } - public getImageAsync(callback: (image, error) => void) { + /** + * Asynchronously loads an image and optionally resizes it. + * Handles both string and number type dimensions by converting strings to numbers. + * + * @param {Function} callback - Callback function that receives (image, error) + * @param {{ width?: number|string; height?: number|string }} [options] - Optional dimensions for the image + */ + public getImageAsync(callback: (image, error) => void, options?: { width?: number | string; height?: number | string }) { + if (options) { + if (typeof options.width === "string") { + options.width = parseInt(options.width, 10); + } + if (typeof options.height === "string") { + options.height = parseInt(options.height, 10); + } + } + if (!this.ios && !this.nativeImage) { - callback(null, 'Asset cannot be found.'); + callback(null, "Asset cannot be found."); } const srcWidth = this.nativeImage ? this.nativeImage.size.width : this.ios.pixelWidth; @@ -60,6 +76,14 @@ export class ImageAsset extends ImageAssetBase { }); } + /** + * Scales the image to the requested size while respecting device scale factor settings. + * + * @param {UIImage} image - The source UIImage to scale + * @param {{ width: number; height: number }} requestedSize - Target dimensions + * @returns {UIImage} Scaled image + * @private + */ private scaleImage(image: UIImage, requestedSize: { width: number; height: number }): UIImage { return NativeScriptUtils.scaleImageWidthHeightScaleFactor(image, requestedSize.width, requestedSize.height, this.options?.autoScaleFactor === false ? 1.0 : 0.0); } From 12ba676fd920e36b3d898b323c93df9e10d69756 Mon Sep 17 00:00:00 2001 From: ryumon0309 Date: Fri, 17 Jan 2025 11:34:13 +0900 Subject: [PATCH 2/2] fix to single quote --- .../core/image-asset/image-asset-common.ts | 4 ++-- packages/core/image-asset/index.android.ts | 18 ++++++++--------- packages/core/image-asset/index.d.ts | 2 +- packages/core/image-asset/index.ios.ts | 20 +++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/core/image-asset/image-asset-common.ts b/packages/core/image-asset/image-asset-common.ts index 798727dd3e..10dce92fef 100644 --- a/packages/core/image-asset/image-asset-common.ts +++ b/packages/core/image-asset/image-asset-common.ts @@ -57,7 +57,7 @@ export function getRequestedImageSize(src: { width: number; height: number }, op } return { - width: reqWidth, - height: reqHeight, + width: Number(reqWidth), + height: Number(reqHeight), }; } diff --git a/packages/core/image-asset/index.android.ts b/packages/core/image-asset/index.android.ts index 8c0a267291..d19b653079 100644 --- a/packages/core/image-asset/index.android.ts +++ b/packages/core/image-asset/index.android.ts @@ -1,17 +1,17 @@ -import { ImageAssetBase, getRequestedImageSize } from "./image-asset-common"; -import { path as fsPath, knownFolders } from "../file-system"; -import { ad } from "../utils"; -import { Screen } from "../platform"; -export * from "./image-asset-common"; +import { ImageAssetBase, getRequestedImageSize } from './image-asset-common'; +import { path as fsPath, knownFolders } from '../file-system'; +import { ad } from '../utils'; +import { Screen } from '../platform'; +export * from './image-asset-common'; export class ImageAsset extends ImageAssetBase { private _android: string; //file name of the image constructor(asset: string) { super(); - let fileName = typeof asset === "string" ? asset.trim() : ""; - if (fileName.indexOf("~/") === 0) { - fileName = fsPath.join(knownFolders.currentApp().path, fileName.replace("~/", "")); + let fileName = typeof asset === 'string' ? asset.trim() : ''; + if (fileName.indexOf('~/') === 0) { + fileName = fsPath.join(knownFolders.currentApp().path, fileName.replace('~/', '')); } this.android = fileName; } @@ -40,7 +40,7 @@ export class ImageAsset extends ImageAssetBase { */ private _validateDimensions(width: number | string, height: number | string): { width: number; height: number } { const parseSize = (size: number | string): number => { - return typeof size === "string" ? parseInt(size, 10) : size; + return typeof size === 'string' ? parseInt(size, 10) : size; }; let w = parseSize(width); diff --git a/packages/core/image-asset/index.d.ts b/packages/core/image-asset/index.d.ts index 770aecd189..dc41012fe3 100644 --- a/packages/core/image-asset/index.d.ts +++ b/packages/core/image-asset/index.d.ts @@ -1,4 +1,4 @@ -import { Observable } from "../data/observable"; +import { Observable } from '../data/observable'; export class ImageAsset extends Observable { constructor(asset: any); diff --git a/packages/core/image-asset/index.ios.ts b/packages/core/image-asset/index.ios.ts index a992e57aba..86fcbe307d 100644 --- a/packages/core/image-asset/index.ios.ts +++ b/packages/core/image-asset/index.ios.ts @@ -1,17 +1,17 @@ -import { ImageAssetBase, getRequestedImageSize } from "./image-asset-common"; -import { path as fsPath, knownFolders } from "../file-system"; -import { queueGC } from "../utils"; +import { ImageAssetBase, getRequestedImageSize } from './image-asset-common'; +import { path as fsPath, knownFolders } from '../file-system'; +import { queueGC } from '../utils'; -export * from "./image-asset-common"; +export * from './image-asset-common'; export class ImageAsset extends ImageAssetBase { private _ios: PHAsset; constructor(asset: string | PHAsset | UIImage) { super(); - if (typeof asset === "string") { - if (asset.indexOf("~/") === 0) { - asset = fsPath.join(knownFolders.currentApp().path, asset.replace("~/", "")); + if (typeof asset === 'string') { + if (asset.indexOf('~/') === 0) { + asset = fsPath.join(knownFolders.currentApp().path, asset.replace('~/', '')); } this.nativeImage = UIImage.imageWithContentsOfFile(asset); @@ -40,16 +40,16 @@ export class ImageAsset extends ImageAssetBase { */ public getImageAsync(callback: (image, error) => void, options?: { width?: number | string; height?: number | string }) { if (options) { - if (typeof options.width === "string") { + if (typeof options.width === 'string') { options.width = parseInt(options.width, 10); } - if (typeof options.height === "string") { + if (typeof options.height === 'string') { options.height = parseInt(options.height, 10); } } if (!this.ios && !this.nativeImage) { - callback(null, "Asset cannot be found."); + callback(null, 'Asset cannot be found.'); } const srcWidth = this.nativeImage ? this.nativeImage.size.width : this.ios.pixelWidth;