diff --git a/tests/app/xml-declaration/mainPage.ts b/tests/app/xml-declaration/mainPage.ts index ad2eb702b..848c14cae 100644 --- a/tests/app/xml-declaration/mainPage.ts +++ b/tests/app/xml-declaration/mainPage.ts @@ -61,6 +61,6 @@ export function setPicture(args: observable.EventData) { var img = parent.getViewById("cameraImage"); camera.takePicture().then(r=> { - img.imageSource = r; + img.src = r; }).catch(e => dialogs.alert("ERROR: " + e)); } \ No newline at end of file diff --git a/tns-core-modules/camera/camera.android.ts b/tns-core-modules/camera/camera.android.ts index 4e46a7944..96e1306d1 100644 --- a/tns-core-modules/camera/camera.android.ts +++ b/tns-core-modules/camera/camera.android.ts @@ -81,21 +81,6 @@ export var takePicture = function (options?): Promise { scaledSizeImage = bitmap; } - let ei = new android.media.ExifInterface(picturePath); - let orientation = ei.getAttributeInt(android.media.ExifInterface.TAG_ORIENTATION, android.media.ExifInterface.ORIENTATION_NORMAL); - - switch (orientation) { - case android.media.ExifInterface.ORIENTATION_ROTATE_90: - scaledSizeImage = rotateBitmap(scaledSizeImage, 90); - break; - case android.media.ExifInterface.ORIENTATION_ROTATE_180: - scaledSizeImage = rotateBitmap(scaledSizeImage, 180); - break; - case android.media.ExifInterface.ORIENTATION_ROTATE_270: - scaledSizeImage = rotateBitmap(scaledSizeImage, 270); - break; - } - resolve(imageSource.fromNativeSource(scaledSizeImage)); } }; @@ -144,9 +129,3 @@ var createDateTimeStamp = function () { date.getSeconds().toString(); return result; } - -var rotateBitmap = function (source, angle) { - let matrix = new android.graphics.Matrix(); - matrix.postRotate(angle); - return android.graphics.Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true); -} diff --git a/tns-core-modules/image-asset/image-asset-common.ts b/tns-core-modules/image-asset/image-asset-common.ts new file mode 100644 index 000000000..aec25a5b6 --- /dev/null +++ b/tns-core-modules/image-asset/image-asset-common.ts @@ -0,0 +1,78 @@ +import definition = require("image-asset"); +import platform = require("platform"); + +export class ImageAsset implements definition.ImageAsset { + private _options: definition.ImageAssetOptions; + private _ios: PHAsset; + private _nativeImage: any; + private _android: string; //file name of the image + + get options(): definition.ImageAssetOptions { + return this._options; + } + + set options(value: definition.ImageAssetOptions) { + this._options = value; + } + + get ios(): PHAsset { + return this._ios; + } + + set ios(value: PHAsset) { + this._ios = value; + } + + get android(): string { + return this._android; + } + + set android(value: string) { + this._android = value; + } + + get nativeImage(): any { + return this._nativeImage; + } + + set nativeImage(value: any) { + this._nativeImage = value; + } + + public getImageAsync(callback: (image: any, error: Error) => void) { + // + } +} + +export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, reqHeight) { + let widthCoef = sourceWidth / reqWidth; + let heightCoef = sourceHeight / reqHeight; + + let aspectCoef = widthCoef > heightCoef ? widthCoef : heightCoef; + + return { + width: Math.floor(sourceWidth / aspectCoef), + height: Math.floor(sourceHeight / aspectCoef) + }; +} + +export function getRequestedImageSize(src: {width: number, height: number}): {width: number, height: number} { + let reqWidth = platform.screen.mainScreen.widthDIPs; + let reqHeight = platform.screen.mainScreen.heightDIPs + if (this.options && this.options.width) { + reqWidth = (this.options.width > 0 && this.options.width < reqWidth) ? this.options.width : reqWidth; + } + if (this.options && this.options.height) { + reqWidth = (this.options.height > 0 && this.options.height < reqHeight) ? this.options.height : reqHeight; + } + + if (this.options && this.options.keepAspectRatio) { + let safeAspectSize = getAspectSafeDimensions(src.width, src.height, reqWidth, reqHeight); + reqWidth = safeAspectSize.width; + reqHeight = safeAspectSize.height; + } + return { + width: reqWidth, + height:reqHeight + }; +} \ No newline at end of file diff --git a/tns-core-modules/image-asset/image-asset.android.ts b/tns-core-modules/image-asset/image-asset.android.ts new file mode 100644 index 000000000..187711c37 --- /dev/null +++ b/tns-core-modules/image-asset/image-asset.android.ts @@ -0,0 +1,50 @@ +import * as platform from "platform"; +import common = require("./image-asset-common"); + +global.moduleMerge(common, exports); + +export class ImageAsset extends common.ImageAsset { + constructor(asset: string) { + super(); + this.android = asset; + } + + public getImageAsync(callback: (image, error) => void) { + let bitmapOptions = new android.graphics.BitmapFactory.Options(); + bitmapOptions.inJustDecodeBounds = true; + let bitmap = android.graphics.BitmapFactory.decodeFile(this.android, bitmapOptions); + let sourceSize = { + width: bitmapOptions.outWidth, + height: bitmapOptions.outHeight + }; + let requestedSize = common.getRequestedImageSize(sourceSize); + + let sampleSize = calculateInSampleSize(bitmapOptions.outWidth, bitmapOptions.outHeight, requestedSize.width, requestedSize.height); + + let finalBitmapOptions = new android.graphics.BitmapFactory.Options(); + finalBitmapOptions.inSampleSize = sampleSize; + try { + bitmap = android.graphics.BitmapFactory.decodeFile(this.android, finalBitmapOptions); + callback(bitmap, null); + } + catch (ex) { + callback(null, ex); + } + } +} + +var calculateInSampleSize = function (imageWidth, imageHeight, reqWidth, reqHeight) { + let sampleSize = 1; + let displayWidth = platform.screen.mainScreen.widthDIPs; + let displayHeigth = platform.screen.mainScreen.heightDIPs; + reqWidth = (reqWidth > 0 && reqWidth < displayWidth) ? reqWidth : displayWidth; + reqHeight = (reqHeight > 0 && reqHeight < displayHeigth) ? reqHeight : displayHeigth; + if (imageWidth > reqWidth && imageHeight > reqHeight) { + let halfWidth = imageWidth / 2; + let halfHeight = imageHeight / 2; + while ((halfWidth / sampleSize) > reqWidth && (halfHeight / sampleSize) > reqHeight) { + sampleSize *= 2; + } + } + return sampleSize; +} \ No newline at end of file diff --git a/tns-core-modules/image-asset/image-asset.d.ts b/tns-core-modules/image-asset/image-asset.d.ts new file mode 100644 index 000000000..21adf6aa7 --- /dev/null +++ b/tns-core-modules/image-asset/image-asset.d.ts @@ -0,0 +1,16 @@ +declare module "image-asset" { + export class ImageAsset { + constructor(asset: any); + getImageAsync(callback: (image: any, error: any) => void); //UIImage for iOS and android.graphics.Bitmap for Android + ios: any; //PHAsset + nativeImage: any; //UIImage for iOS and android.graphics.Bitmap for Android + android: any; + options: ImageAssetOptions; + } + + export interface ImageAssetOptions { + width?: number; + height?: number; + keepAspectRatio?: boolean; + } +} \ No newline at end of file diff --git a/tns-core-modules/image-asset/image-asset.ios.ts b/tns-core-modules/image-asset/image-asset.ios.ts new file mode 100644 index 000000000..1f4e6e4cf --- /dev/null +++ b/tns-core-modules/image-asset/image-asset.ios.ts @@ -0,0 +1,41 @@ +import common = require("./image-asset-common"); + +global.moduleMerge(common, exports); + +export class ImageAsset extends common.ImageAsset { + constructor(asset: PHAsset | UIImage) { + super(); + if (asset instanceof UIImage) { + this.nativeImage = asset + } + else { + this.ios = asset; + } + } + + public getImageAsync(callback: (image, error) => void) { + let requestedSize = common.getRequestedImageSize({ + width: this.ios.pixelWidth, + height: this.ios.pixelHeight + }); + + let imageRequestOptions = PHImageRequestOptions.alloc().init(); + imageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryMode.HighQualityFormat; + + if (this.nativeImage) { + callback(this.nativeImage, null); + return; + } + + PHImageManager.defaultManager().requestImageForAssetTargetSizeContentModeOptionsResultHandler(this.ios, requestedSize, PHImageContentMode.AspectFit, imageRequestOptions, + (image, imageResultInfo) => { + if (image) { + callback(image, null); + } + else { + callback(null, imageResultInfo.valueForKey(PHImageErrorKey)); + } + } + ); + } +} \ No newline at end of file diff --git a/tns-core-modules/image-asset/package.json b/tns-core-modules/image-asset/package.json new file mode 100644 index 000000000..4575f2a95 --- /dev/null +++ b/tns-core-modules/image-asset/package.json @@ -0,0 +1,5 @@ +{ + "name" : "image-asset", + "main" : "image-asset", + "nativescript": {} +} \ No newline at end of file diff --git a/tns-core-modules/image-source/image-source-common.ts b/tns-core-modules/image-source/image-source-common.ts index 38aceb9fe..0ba3d3eae 100644 --- a/tns-core-modules/image-source/image-source-common.ts +++ b/tns-core-modules/image-source/image-source-common.ts @@ -1,5 +1,6 @@ import utils = require("utils/utils"); import * as httpModule from "http"; +import * as imageAssetModule from "image-asset"; var http: typeof httpModule; function ensureHttp() { @@ -11,6 +12,11 @@ function ensureHttp() { // This is used for definition purposes only, it does not generate JavaScript for it. import definition = require("image-source"); +export function fromAsset(asset: imageAssetModule.ImageAsset): Promise { + let image = new definition.ImageSource(); + return image.fromAsset(asset); +} + export function fromResource(name: string): definition.ImageSource { var image = new definition.ImageSource(); return image.loadFromResource(name) ? image : null; diff --git a/tns-core-modules/image-source/image-source.android.ts b/tns-core-modules/image-source/image-source.android.ts index 12eec3e90..1792de267 100644 --- a/tns-core-modules/image-source/image-source.android.ts +++ b/tns-core-modules/image-source/image-source.android.ts @@ -4,6 +4,7 @@ import common = require("./image-source-common"); import * as utilsModule from "utils/utils"; import * as fileSystemModule from "file-system"; import * as enumsModule from "ui/enums"; +import * as imageAssetModule from "image-asset"; global.moduleMerge(common, exports); @@ -32,6 +33,21 @@ export class ImageSource implements definition.ImageSource { public android: android.graphics.Bitmap; public ios: UIImage; + public fromAsset(asset: imageAssetModule.ImageAsset): Promise { + return new Promise((resolve, reject) => { + asset.getImageAsync((image, err) => { + if (image) { + this.setRotationAngleFromFile(asset.android); + this.setNativeSource(image); + resolve(this); + } + else { + reject(err); + } + }); + }); + } + public loadFromResource(name: string): boolean { this.android = null; @@ -58,6 +74,24 @@ export class ImageSource implements definition.ImageSource { }); } + private setRotationAngleFromFile(filename: string) { + this.rotationAngle = 0; + let ei = new android.media.ExifInterface(filename); + let orientation = ei.getAttributeInt(android.media.ExifInterface.TAG_ORIENTATION, android.media.ExifInterface.ORIENTATION_NORMAL); + + switch (orientation) { + case android.media.ExifInterface.ORIENTATION_ROTATE_90: + this.rotationAngle = 90; + break; + case android.media.ExifInterface.ORIENTATION_ROTATE_180: + this.rotationAngle = 180; + break; + case android.media.ExifInterface.ORIENTATION_ROTATE_270: + this.rotationAngle = 270; + break; + } + } + public loadFromFile(path: string): boolean { ensureFS(); @@ -66,7 +100,9 @@ export class ImageSource implements definition.ImageSource { fileName = fs.path.join(fs.knownFolders.currentApp().path, fileName.replace("~/", "")); } + this.setRotationAngleFromFile(fileName); this.android = android.graphics.BitmapFactory.decodeFile(fileName, null); + return this.android != null; } @@ -154,6 +190,15 @@ export class ImageSource implements definition.ImageSource { return NaN; } + + private _rotationAngle: number; + get rotationAngle(): number { + return this._rotationAngle; + } + + set rotationAngle(value: number) { + this._rotationAngle = value; + } } function getTargetFormat(format: string): android.graphics.Bitmap.CompressFormat { diff --git a/tns-core-modules/image-source/image-source.d.ts b/tns-core-modules/image-source/image-source.d.ts index 74b9898ac..96f81f95d 100644 --- a/tns-core-modules/image-source/image-source.d.ts +++ b/tns-core-modules/image-source/image-source.d.ts @@ -2,7 +2,7 @@ * Contains the ImageSource class, which encapsulates the common abstraction behind a platform specific object (typically a Bitmap) that is used as a source for images. */ declare module "image-source" { - + import * as imageAssetModule from "image-asset"; /** * Encapsulates the common abstraction behind a platform specific object (typically a Bitmap) that is used as a source for images. */ @@ -17,6 +17,11 @@ declare module "image-source" { */ width: number; + /** + * Gets or sets the rotation angle that should be applied to image. (Used in android) + */ + rotationAngle: number; + /** * The iOS-specific [UIImage](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/) instance. Will be undefined when running on Android. */ @@ -27,6 +32,12 @@ declare module "image-source" { */ android: any /* android.graphics.Bitmap */; + /** + * Loads this instance from the specified asset asynchronously. + * @param asset The ImageAsset instance used to create ImageSource. + */ + fromAsset(asset: imageAssetModule.ImageAsset): Promise; + /** * Loads this instance from the specified resource name. * @param name The name of the resource (without its extension). @@ -98,6 +109,8 @@ declare module "image-source" { toBase64String(format: string, quality?: number): string; } + export function fromAsset(asset: imageAssetModule.ImageAsset): Promise; + /** * Creates a new ImageSource instance and loads it from the specified resource name. * @param name The name of the resource (without its extension). diff --git a/tns-core-modules/image-source/image-source.ios.ts b/tns-core-modules/image-source/image-source.ios.ts index 1121290b1..7bcce45ad 100644 --- a/tns-core-modules/image-source/image-source.ios.ts +++ b/tns-core-modules/image-source/image-source.ios.ts @@ -3,6 +3,7 @@ import types = require("utils/types"); import fs = require("file-system"); import common = require("./image-source-common"); import enums = require("ui/enums"); +import * as imageAssetModule from "image-asset"; global.moduleMerge(common, exports); @@ -10,6 +11,19 @@ export class ImageSource implements definition.ImageSource { public android: android.graphics.Bitmap; public ios: UIImage; + public fromAsset(asset: imageAssetModule.ImageAsset) { + return new Promise((resolve, reject) => { + asset.getImageAsync((image, err) => { + if (image) { + resolve(common.fromNativeSource(image)); + } + else { + reject(err); + } + }); + }); + } + public loadFromResource(name: string): boolean { this.ios = (UIImage).tns_safeImageNamed(name) || (UIImage).tns_safeImageNamed(`${name}.jpg`); return this.ios != null; @@ -157,6 +171,10 @@ export class ImageSource implements definition.ImageSource { return NaN; } + + get rotationAngle(): number { + return NaN; + } } function getImageData(instance: UIImage, format: string, quality = 1.0): NSData { diff --git a/tns-core-modules/tns-core-modules.base.d.ts b/tns-core-modules/tns-core-modules.base.d.ts index 3f22bd059..de12f0f3a 100644 --- a/tns-core-modules/tns-core-modules.base.d.ts +++ b/tns-core-modules/tns-core-modules.base.d.ts @@ -18,6 +18,7 @@ /// /// /// +/// /// /// /// diff --git a/tns-core-modules/ui/image/image-common.ts b/tns-core-modules/ui/image/image-common.ts index 4c694e720..02bf4fec4 100644 --- a/tns-core-modules/ui/image/image-common.ts +++ b/tns-core-modules/ui/image/image-common.ts @@ -2,6 +2,7 @@ import view = require("ui/core/view"); import proxy = require("ui/core/proxy"); import imageSource = require("image-source"); +import imageAssetModule = require("image-asset"); import definition = require("ui/image"); import enums = require("ui/enums"); import platform = require("platform"); @@ -158,6 +159,12 @@ export class Image extends view.View implements definition.Image { this.imageSource = value; this._setValue(Image.isLoadingProperty, false); } + else if (value instanceof imageAssetModule.ImageAsset) { + imageSource.fromAsset(value).then((result) => { + this.imageSource = result; + this._setValue(Image.isLoadingProperty, false); + }); + } else { this.imageSource = imageSource.fromNativeSource(value); this._setValue(Image.isLoadingProperty, false); diff --git a/tns-core-modules/ui/image/image.android.ts b/tns-core-modules/ui/image/image.android.ts index 6bf7bdf3f..3d5ae075e 100644 --- a/tns-core-modules/ui/image/image.android.ts +++ b/tns-core-modules/ui/image/image.android.ts @@ -45,7 +45,7 @@ function onImageSourcePropertyChanged(data: dependencyObservable.PropertyChangeD return; } - image._setNativeImage(data.newValue ? data.newValue.android : null); + image._setNativeImage(data.newValue); } // register the setNativeValue callback @@ -64,7 +64,11 @@ export class Image extends imageCommon.Image { } public _setNativeImage(nativeImage: any) { - this.android.setImageBitmap(nativeImage); + let rotation = (nativeImage && nativeImage.rotationAngle) ? nativeImage.rotationAngle : 0 ; + if (rotation > 0) { + this.android.setRotationAngle(rotation); + } + this.android.setImageBitmap(nativeImage.android); } } diff --git a/tns-platform-declarations/android/org.nativescript.widgets.d.ts b/tns-platform-declarations/android/org.nativescript.widgets.d.ts index 4fe693f0b..a548b214c 100644 --- a/tns-platform-declarations/android/org.nativescript.widgets.d.ts +++ b/tns-platform-declarations/android/org.nativescript.widgets.d.ts @@ -280,6 +280,14 @@ export class ImageView extends android.widget.ImageView { constructor(context: android.content.Context); + getCornerRadius(): number; + setCornerRadius(radius: number): void; + + getBorderWidth(): number; + setBorderWidth(width: number): void; + + getRotationAngle(): number; + setRotationAngle(angle: number): void; } export class TabLayout extends android.widget.HorizontalScrollView { @@ -303,4 +311,4 @@ } } } -} \ No newline at end of file +}