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 055caeff60..d19b653079 100644 --- a/packages/core/image-asset/index.android.ts +++ b/packages/core/image-asset/index.android.ts @@ -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..dc41012fe3 100644 --- a/packages/core/image-asset/index.d.ts +++ b/packages/core/image-asset/index.d.ts @@ -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..86fcbe307d 100644 --- a/packages/core/image-asset/index.ios.ts +++ b/packages/core/image-asset/index.ios.ts @@ -31,7 +31,23 @@ 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.'); } @@ -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); }