diff --git a/apps/app/splashscreen.png b/apps/app/splashscreen.png
new file mode 100644
index 000000000..61fbb7ed0
Binary files /dev/null and b/apps/app/splashscreen.png differ
diff --git a/apps/app/ui-tests-app/image-view/image-asset/image-asset.ts b/apps/app/ui-tests-app/image-view/image-asset/image-asset.ts
new file mode 100644
index 000000000..3d417f208
--- /dev/null
+++ b/apps/app/ui-tests-app/image-view/image-asset/image-asset.ts
@@ -0,0 +1,8 @@
+import * as vmModule from "./view-model";
+
+var viewModel = vmModule.imageViewModel;
+
+export function pageLoaded(args) {
+ let page = args.object;
+ page.bindingContext = viewModel;
+}
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/image-view/image-asset/image-asset.xml b/apps/app/ui-tests-app/image-view/image-asset/image-asset.xml
new file mode 100644
index 000000000..ad4053ae2
--- /dev/null
+++ b/apps/app/ui-tests-app/image-view/image-asset/image-asset.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/image-view/image-asset/view-model.ts b/apps/app/ui-tests-app/image-view/image-asset/view-model.ts
new file mode 100644
index 000000000..053c73d33
--- /dev/null
+++ b/apps/app/ui-tests-app/image-view/image-asset/view-model.ts
@@ -0,0 +1,44 @@
+import * as dialogs from "tns-core-modules/ui/dialogs";
+import * as observable from "tns-core-modules/data/observable";
+import * as imageAssetModule from "tns-core-modules/image-asset";
+import { ImageSource } from 'tns-core-modules/image-source';
+
+let _cameraImageAsset = null;
+let _cameraImageSrc = null;
+
+export class ImageViewModel extends observable.Observable {
+
+ constructor() {
+ super();
+ let asset = new imageAssetModule.ImageAsset('~/splashscreen.png');
+ asset.options = {
+ width: 300,
+ height: 300,
+ keepAspectRatio: true
+ };
+ let source = new ImageSource();
+ source.fromAsset(asset).then((source) => {
+ this.set("cameraImageAsset", asset);
+ this.set("cameraImageSrc", source);
+ }, (error) => {
+ console.log(error);
+ });
+ }
+
+ get cameraImageAsset(): string {
+ return _cameraImageAsset;
+ }
+
+ set cameraImageAsset(value: string) {
+ _cameraImageAsset = value;
+ }
+
+ get cameraImageSrc(): string {
+ return _cameraImageSrc;
+ }
+
+ set cameraImageSrc(value: string) {
+ _cameraImageSrc = value;
+ }
+}
+export var imageViewModel = new ImageViewModel();
diff --git a/apps/app/ui-tests-app/image-view/main-page.ts b/apps/app/ui-tests-app/image-view/main-page.ts
index 2e714d423..40fcf5a3e 100644
--- a/apps/app/ui-tests-app/image-view/main-page.ts
+++ b/apps/app/ui-tests-app/image-view/main-page.ts
@@ -16,6 +16,7 @@ export function loadExamples() {
examples.set("mode-matrix", "image-view/mode-matrix");
examples.set("stretch-modes", "image-view/stretch-modes");
examples.set("missing-image", "image-view/missing-image");
+ examples.set("image-asset", "image-view/image-asset/image-asset");
return examples;
}
\ No newline at end of file
diff --git a/tests/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png b/tests/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png
deleted file mode 100644
index bd53be2ec..000000000
Binary files a/tests/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png and /dev/null differ
diff --git a/tests/app/App_Resources/Android/drawable-nodpi/splashscreen.png b/tests/app/App_Resources/Android/drawable-nodpi/splashscreen.png
new file mode 100644
index 000000000..a157c4dda
Binary files /dev/null and b/tests/app/App_Resources/Android/drawable-nodpi/splashscreen.png differ
diff --git a/tests/app/App_Resources/iOS/splashscreen.png b/tests/app/App_Resources/iOS/splashscreen.png
new file mode 100644
index 000000000..a157c4dda
Binary files /dev/null and b/tests/app/App_Resources/iOS/splashscreen.png differ
diff --git a/tests/app/image-source/image-source-tests.ts b/tests/app/image-source/image-source-tests.ts
index 614bf3a6e..94a52ee2c 100644
--- a/tests/app/image-source/image-source-tests.ts
+++ b/tests/app/image-source/image-source-tests.ts
@@ -1,10 +1,14 @@
import * as imageSource from "tns-core-modules/image-source";
+import * as imageAssetModule from "tns-core-modules/image-asset";
import * as fs from "tns-core-modules/file-system";
import * as app from "tns-core-modules/application";
import * as TKUnit from "../TKUnit";
import * as platform from "tns-core-modules/platform";
const imagePath = "~/logo.png";
+const splashscreenPath = "~/splashscreen.png";
+const splashscreenWidth = 372;
+const splashscreenHeight = 218;
const smallImagePath = "~/small-image.png";
export function testFromResource() {
@@ -65,6 +69,96 @@ export function testFromFile() {
TKUnit.assert(!fs.File.exists(path), "test.png not removed");
}
+export function testFromAssetFileNotFound(done) {
+ let asset = new imageAssetModule.ImageAsset('invalidFile.png');
+ asset.options = {
+ width: 0,
+ height: 0,
+ keepAspectRatio: true
+ };
+
+ let img = imageSource.fromAsset(asset).then((source) => {
+ done('Should not resolve with invalid file name.');
+ }, (error) => {
+ TKUnit.assertNotNull(error);
+ done();
+ });
+}
+
+export function testFromAssetSimple(done) {
+ let asset = new imageAssetModule.ImageAsset(splashscreenPath);
+ asset.options = {
+ width: 0,
+ height: 0,
+ keepAspectRatio: true
+ };
+
+ let img = imageSource.fromAsset(asset).then((source) => {
+ TKUnit.assertEqual(source.width, splashscreenWidth);
+ TKUnit.assertEqual(source.height, splashscreenHeight);
+ done();
+ }, (error) => {
+ done(error);
+ });
+}
+
+export function testFromAssetWithScaling(done) {
+ let asset = new imageAssetModule.ImageAsset(splashscreenPath);
+ let scaleWidth = 10;
+ let scaleHeight = 11;
+ asset.options = {
+ width: scaleWidth,
+ height: scaleHeight,
+ keepAspectRatio: false
+ };
+
+ let img = imageSource.fromAsset(asset).then((source) => {
+ TKUnit.assertEqual(source.width, scaleWidth);
+ TKUnit.assertEqual(source.height, scaleHeight);
+ done();
+ }, (error) => {
+ done(error);
+ });
+}
+
+export function testFromAssetWithScalingAndAspectRatio(done) {
+ let asset = new imageAssetModule.ImageAsset(splashscreenPath);
+ let scaleWidth = 10;
+ let scaleHeight = 11;
+ asset.options = {
+ width: scaleWidth,
+ height: scaleHeight,
+ keepAspectRatio: true
+ };
+
+ let img = imageSource.fromAsset(asset).then((source) => {
+ TKUnit.assertEqual(source.width, scaleWidth);
+ TKUnit.assertEqual(source.height, 5);
+ done();
+ }, (error) => {
+ done(error);
+ });
+}
+
+export function testFromAssetWithBiggerScaling(done) {
+ let asset = new imageAssetModule.ImageAsset(splashscreenPath);
+ let scaleWidth = 600;
+ let scaleHeight = 600;
+ asset.options = {
+ width: scaleWidth,
+ height: scaleHeight,
+ keepAspectRatio: false
+ };
+
+ let img = imageSource.fromAsset(asset).then((source) => {
+ TKUnit.assertEqual(source.width, scaleWidth);
+ TKUnit.assertEqual(source.height, scaleHeight);
+ done();
+ }, (error) => {
+ done(error);
+ });
+}
+
export function testNativeFields() {
const img = imageSource.fromFile(imagePath);
if (app.android) {
diff --git a/tests/app/splashscreen.png b/tests/app/splashscreen.png
new file mode 100644
index 000000000..a157c4dda
Binary files /dev/null and b/tests/app/splashscreen.png differ
diff --git a/tests/app/ui/image/image-tests.ts b/tests/app/ui/image/image-tests.ts
index e0ce57e4f..693ae5670 100644
--- a/tests/app/ui/image/image-tests.ts
+++ b/tests/app/ui/image/image-tests.ts
@@ -138,7 +138,8 @@ export const test_SettingImageSrcToDataURI_async = function (done) {
export function test_imageSourceNotResetAfterCreateUI() {
let image = new ImageModule.Image();
- let imageSource = ImageSourceModule.fromResource("splashscreen.9");
+ let imageSource = ImageSourceModule.fromResource("splashscreen");
+ TKUnit.assertNotEqual(null, imageSource);
image.imageSource = imageSource;
helper.buildUIAndRunTest(image, () => {
TKUnit.waitUntilReady(() => image.isLoaded);
diff --git a/tns-core-modules/image-asset/image-asset-common.ts b/tns-core-modules/image-asset/image-asset-common.ts
index c6e35641d..b50f6594a 100644
--- a/tns-core-modules/image-asset/image-asset-common.ts
+++ b/tns-core-modules/image-asset/image-asset-common.ts
@@ -43,14 +43,10 @@ export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, req
}
export function getRequestedImageSize(src: { width: number, height: number }, options: definition.ImageAssetOptions): { width: number, height: number } {
- let reqWidth = platform.screen.mainScreen.widthDIPs;
- let reqHeight = platform.screen.mainScreen.heightDIPs;
- if (options && options.width) {
- reqWidth = (options.width > 0 && options.width < reqWidth) ? options.width : reqWidth;
- }
- if (options && options.height) {
- reqHeight = (options.height > 0 && options.height < reqHeight) ? options.height : reqHeight;
- }
+ var screen = platform.screen.mainScreen;
+
+ var reqWidth = options.width || Math.min(src.width, screen.widthPixels);
+ var reqHeight = options.height || Math.min(src.height, screen.heightPixels);
if (options && options.keepAspectRatio) {
let safeAspectSize = getAspectSafeDimensions(src.width, src.height, reqWidth, reqHeight);
diff --git a/tns-core-modules/image-asset/image-asset.android.ts b/tns-core-modules/image-asset/image-asset.android.ts
index 282bafea8..eaba56d88 100644
--- a/tns-core-modules/image-asset/image-asset.android.ts
+++ b/tns-core-modules/image-asset/image-asset.android.ts
@@ -1,5 +1,6 @@
import * as platform from "../platform";
import * as common from "./image-asset-common";
+import { path as fsPath, knownFolders } from "../file-system";
global.moduleMerge(common, exports);
@@ -8,7 +9,11 @@ export class ImageAsset extends common.ImageAsset {
constructor(asset: string) {
super();
- this.android = asset;
+ let fileName = typeof asset === "string" ? asset.trim() : "";
+ if (fileName.indexOf("~/") === 0) {
+ fileName = fsPath.join(knownFolders.currentApp().path, fileName.replace("~/", ""));
+ }
+ this.android = fileName;
}
get android(): string {
@@ -22,6 +27,7 @@ export class ImageAsset extends common.ImageAsset {
public getImageAsync(callback: (image, error) => void) {
let bitmapOptions = new android.graphics.BitmapFactory.Options();
bitmapOptions.inJustDecodeBounds = true;
+ // read only the file size
let bitmap = android.graphics.BitmapFactory.decodeFile(this.android, bitmapOptions);
let sourceSize = {
width: bitmapOptions.outWidth,
@@ -29,13 +35,34 @@ export class ImageAsset extends common.ImageAsset {
};
let requestedSize = common.getRequestedImageSize(sourceSize, this.options);
- let sampleSize = calculateInSampleSize(bitmapOptions.outWidth, bitmapOptions.outHeight, requestedSize.width, requestedSize.height);
+ let sampleSize = org.nativescript.widgets.image.Fetcher.calculateInSampleSize(bitmapOptions.outWidth, bitmapOptions.outHeight, requestedSize.width, requestedSize.height);
let finalBitmapOptions = new android.graphics.BitmapFactory.Options();
finalBitmapOptions.inSampleSize = sampleSize;
try {
+ let error = null;
+ // read as minimum bitmap as possible (slightly bigger than the requested size)
bitmap = android.graphics.BitmapFactory.decodeFile(this.android, finalBitmapOptions);
- callback(bitmap, null);
+
+ if (bitmap) {
+ if (requestedSize.width !== bitmap.getWidth() || requestedSize.height !== bitmap.getHeight()) {
+ // scale to exact size
+ bitmap = android.graphics.Bitmap.createScaledBitmap(bitmap, requestedSize.width, requestedSize.height, true);
+ }
+
+ const rotationAngle = calculateAngleFromFile(this.android);
+ if (rotationAngle !== 0) {
+ const matrix = new android.graphics.Matrix();
+ matrix.postRotate(rotationAngle);
+ bitmap = android.graphics.Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ }
+ }
+
+ if (!bitmap) {
+ error = "Asset '" + this.android + "' cannot be found.";
+ }
+
+ callback(bitmap, error);
}
catch (ex) {
callback(null, ex);
@@ -43,6 +70,26 @@ export class ImageAsset extends common.ImageAsset {
}
}
+var calculateAngleFromFile = function (filename: string) {
+ let rotationAngle = 0;
+ const ei = new android.media.ExifInterface(filename);
+ const orientation = ei.getAttributeInt(android.media.ExifInterface.TAG_ORIENTATION, android.media.ExifInterface.ORIENTATION_NORMAL);
+
+ switch (orientation) {
+ case android.media.ExifInterface.ORIENTATION_ROTATE_90:
+ rotationAngle = 90;
+ break;
+ case android.media.ExifInterface.ORIENTATION_ROTATE_180:
+ rotationAngle = 180;
+ break;
+ case android.media.ExifInterface.ORIENTATION_ROTATE_270:
+ rotationAngle = 270;
+ break;
+ }
+
+ return rotationAngle;
+}
+
var calculateInSampleSize = function (imageWidth, imageHeight, reqWidth, reqHeight) {
let sampleSize = 1;
let displayWidth = platform.screen.mainScreen.widthDIPs;
@@ -56,5 +103,16 @@ var calculateInSampleSize = function (imageWidth, imageHeight, reqWidth, reqHeig
sampleSize *= 2;
}
}
+
+ var totalPixels = (imageWidth / sampleSize) * (imageHeight / sampleSize);
+
+ // Anything more than 2x the requested pixels we'll sample down further
+ var totalReqPixelsCap = reqWidth * reqHeight * 2;
+
+ while (totalPixels > totalReqPixelsCap) {
+ sampleSize *= 2;
+ totalPixels = (imageWidth / sampleSize) * (imageHeight / sampleSize);
+ }
+
return sampleSize;
}
diff --git a/tns-core-modules/image-asset/image-asset.ios.ts b/tns-core-modules/image-asset/image-asset.ios.ts
index 48290c1a0..b0f338014 100644
--- a/tns-core-modules/image-asset/image-asset.ios.ts
+++ b/tns-core-modules/image-asset/image-asset.ios.ts
@@ -1,13 +1,21 @@
import * as common from "./image-asset-common";
+import { path as fsPath, knownFolders } from "../file-system";
global.moduleMerge(common, exports);
export class ImageAsset extends common.ImageAsset {
private _ios: PHAsset;
- constructor(asset: PHAsset | UIImage) {
+ constructor(asset: string | PHAsset | UIImage) {
super();
- if (asset instanceof UIImage) {
+ if (typeof asset === "string") {
+ if (asset.indexOf("~/") === 0) {
+ asset = fsPath.join(knownFolders.currentApp().path, asset.replace("~/", ""));
+ }
+
+ this.nativeImage = UIImage.imageWithContentsOfFile(asset);
+ }
+ else if (asset instanceof UIImage) {
this.nativeImage = asset
}
else {
@@ -24,6 +32,10 @@ export class ImageAsset extends common.ImageAsset {
}
public getImageAsync(callback: (image, error) => void) {
+ if (!this.ios && !this.nativeImage) {
+ callback(null, "Asset cannot be found.");
+ }
+
let srcWidth = this.nativeImage ? this.nativeImage.size.width : this.ios.pixelWidth;
let srcHeight = this.nativeImage ? this.nativeImage.size.height : this.ios.pixelHeight;
let requestedSize = common.getRequestedImageSize({ width: srcWidth, height: srcHeight }, this.options);
diff --git a/tns-core-modules/image-source/image-source.android.ts b/tns-core-modules/image-source/image-source.android.ts
index f8847d5b3..0f14b2e4d 100644
--- a/tns-core-modules/image-source/image-source.android.ts
+++ b/tns-core-modules/image-source/image-source.android.ts
@@ -44,7 +44,6 @@ export class ImageSource implements ImageSourceDefinition {
return new Promise((resolve, reject) => {
asset.getImageAsync((image, err) => {
if (image) {
- this.setRotationAngleFromFile(asset.android);
this.setNativeSource(image);
resolve(this);
}
diff --git a/tns-core-modules/ui/image/image.android.ts b/tns-core-modules/ui/image/image.android.ts
index fb03c7825..1e425dd57 100644
--- a/tns-core-modules/ui/image/image.android.ts
+++ b/tns-core-modules/ui/image/image.android.ts
@@ -4,6 +4,7 @@
} from "./image-common";
import { knownFolders } from "../../file-system";
+import * as platform from "../../platform";
export * from "./image-common";
const FILE_PREFIX = "file:///";
@@ -82,12 +83,27 @@ export class Image extends ImageBase {
}
if (!value) {
- imageView.setUri(null, 0, 0, false, true);
+ imageView.setUri(null, 0, 0, false, false, true);
return;
}
- const async = this.loadMode === ASYNC;
+ let screen = platform.screen.mainScreen;
+ let decodeWidth = Math.min(this.decodeWidth, screen.widthPixels);
+ let decodeHeight = Math.min(this.decodeHeight, screen.heightPixels);
+ let keepAspectRatio = this._calculateKeepAspectRatio();
+ if (value instanceof ImageAsset) {
+ if (value.options) {
+ decodeWidth = value.options.width || decodeWidth;
+ decodeHeight = value.options.height || decodeHeight;
+ keepAspectRatio = !!value.options.keepAspectRatio;
+ }
+
+ // handle assets as file paths natively
+ value = value.android;
+ }
+
+ const async = this.loadMode === ASYNC;
if (typeof value === "string" || value instanceof String) {
value = value.trim();
this.isLoading = true;
@@ -97,24 +113,28 @@ export class Image extends ImageBase {
super._createImageSourceFromSrc(value);
} else if (isFileOrResourcePath(value)) {
if (value.indexOf(RESOURCE_PREFIX) === 0) {
- imageView.setUri(value, this.decodeWidth, this.decodeHeight, this.useCache, async);
+ imageView.setUri(value, decodeWidth, decodeHeight, keepAspectRatio, this.useCache, async);
} else {
let fileName = value;
if (fileName.indexOf("~/") === 0) {
fileName = knownFolders.currentApp().path + "/" + fileName.replace("~/", "");
}
- imageView.setUri(FILE_PREFIX + fileName, this.decodeWidth, this.decodeHeight, this.useCache, async);
+ imageView.setUri(FILE_PREFIX + fileName, decodeWidth, decodeHeight, keepAspectRatio, this.useCache, async);
}
} else {
// For backwards compatibility http always use async loading.
- imageView.setUri(value, this.decodeWidth, this.decodeHeight, this.useCache, true);
+ imageView.setUri(value, decodeWidth, decodeHeight, keepAspectRatio, this.useCache, true);
}
} else {
super._createImageSourceFromSrc(value);
}
}
+ private _calculateKeepAspectRatio(): boolean {
+ return this.stretch === "fill" ? false : true;
+ }
+
[stretchProperty.getDefault](): "aspectFit" {
return "aspectFit";
}
diff --git a/tns-platform-declarations/android/org.nativescript.widgets.d.ts b/tns-platform-declarations/android/org.nativescript.widgets.d.ts
index 85701f6d7..1d336d827 100644
--- a/tns-platform-declarations/android/org.nativescript.widgets.d.ts
+++ b/tns-platform-declarations/android/org.nativescript.widgets.d.ts
@@ -347,7 +347,7 @@
getRotationAngle(): number;
setRotationAngle(angle: number): void;
- setUri(uri: string, decodeWidth: number, decodeHeight: number, useCache: boolean, async: boolean): void;
+ setUri(uri: string, decodeWidth: number, decodeHeight: number, keepAspectRatio: boolean, useCache: boolean, async: boolean): void;
setImageLoadedListener(listener: image.Worker.OnImageLoadedListener): void;
}
@@ -409,6 +409,8 @@
export class Fetcher extends Worker {
private constructor();
public static getInstance(context: android.content.Context): Fetcher;
+ public static calculateInSampleSize(imageWidth: number, imageHeight: number,
+ reqWidth: number, reqHeight: number): number;
public addImageCache(cache: Cache): void;
public initCache(): void;
public clearCache(): void;