Merge pull request #2822 from NativeScript/nnikolov/CameraRefactoring

Refactored image loading from camera.
This commit is contained in:
Nedyalko Nikolov
2016-10-03 17:54:07 +03:00
committed by GitHub
15 changed files with 297 additions and 26 deletions

View File

@ -61,6 +61,6 @@ export function setPicture(args: observable.EventData) {
var img = parent.getViewById<image.Image>("cameraImage");
camera.takePicture().then(r=> {
img.imageSource = r;
img.src = r;
}).catch(e => dialogs.alert("ERROR: " + e));
}

View File

@ -81,21 +81,6 @@ export var takePicture = function (options?): Promise<any> {
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);
}

View File

@ -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
};
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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));
}
}
);
}
}

View File

@ -0,0 +1,5 @@
{
"name" : "image-asset",
"main" : "image-asset",
"nativescript": {}
}

View File

@ -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<definition.ImageSource> {
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;

View File

@ -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<definition.ImageSource> {
return new Promise<definition.ImageSource>((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 {

View File

@ -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<ImageSource>;
/**
* 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<ImageSource>;
/**
* Creates a new ImageSource instance and loads it from the specified resource name.
* @param name The name of the resource (without its extension).

View File

@ -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<definition.ImageSource>((resolve, reject) => {
asset.getImageAsync((image, err) => {
if (image) {
resolve(common.fromNativeSource(image));
}
else {
reject(err);
}
});
});
}
public loadFromResource(name: string): boolean {
this.ios = (<any>UIImage).tns_safeImageNamed(name) || (<any>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 {

View File

@ -18,6 +18,7 @@
/// <reference path="fps-meter/fps-native.d.ts" />
/// <reference path="http/http-request.d.ts" />
/// <reference path="http/http.d.ts" />
/// <reference path="image-asset/image-asset.d.ts" />
/// <reference path="image-source/image-source.d.ts" />
/// <reference path="js-libs/easysax/easysax.d.ts" />
/// <reference path="js-libs/esprima/esprima.d.ts" />

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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 @@
}
}
}
}
}