Skip to content

Commit 54db19e

Browse files
committed
Merge pull request #16 from KallynGowdy/full-image-loading
Full image loading
2 parents d7bb54d + 82d8d79 commit 54db19e

File tree

6 files changed

+249
-30
lines changed

6 files changed

+249
-30
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="__PACKAGE__"
4+
android:versionCode="1"
5+
android:versionName="1.0">
6+
7+
<supports-screens
8+
android:smallScreens="true"
9+
android:normalScreens="true"
10+
android:largeScreens="true"
11+
android:xlargeScreens="true"/>
12+
13+
<uses-sdk
14+
android:minSdkVersion="17"
15+
android:targetSdkVersion="__APILEVEL__"/>
16+
17+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
18+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
19+
<uses-permission android:name="android.permission.INTERNET"/>
20+
21+
<application
22+
android:name="com.tns.NativeScriptApplication"
23+
android:allowBackup="true"
24+
android:icon="@drawable/icon"
25+
android:label="@string/app_name"
26+
android:theme="@style/AppTheme" >
27+
<activity
28+
android:name="com.tns.NativeScriptActivity"
29+
android:label="@string/title_activity_kimera"
30+
android:configChanges="keyboardHidden|orientation|screenSize">
31+
32+
<intent-filter>
33+
<action android:name="android.intent.action.MAIN" />
34+
35+
<category android:name="android.intent.category.LAUNCHER" />
36+
</intent-filter>
37+
</activity>
38+
<activity android:name="com.tns.ErrorReportActivity"/>
39+
</application>
40+
</manifest>

examples/ExampleImgPick/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}
1818
},
1919
"dependencies": {
20-
"nativescript-imagepicker": "file:../../dist/package",
20+
"nativescript-imagepicker": "file:..\\..\\dist\\package",
2121
"tns-core-modules": "1.5.0"
2222
}
2323
}

source/imagepicker.d.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
declare module "imagepicker" {
1+
declare module "nativescript-imagepicker" {
22

33
import observable = require("data/observable");
44
import imagesource = require("image-source");
55

6+
export interface ImageOptions {
7+
/**
8+
* The maximum width that the image is allowed to be.
9+
*/
10+
maxWidth?: number;
11+
12+
/**
13+
* The maximum height that the image is allowed to be.
14+
*/
15+
maxHeight?: number;
16+
}
17+
18+
619
export class SelectedAsset extends observable.Observable {
720
/**
821
* A 100x100 pixels thumb of the selected image.
@@ -25,8 +38,20 @@ declare module "imagepicker" {
2538
*/
2639
fileUri: string;
2740

41+
/**
42+
* Asynchronously retrieves an ImageSource object that represents this selected image.
43+
* Scaled to the given size. (Aspect-ratio is preserved by default)
44+
*/
45+
getImage(options?: ImageOptions): Promise<imagesource.ImageSource>;
46+
47+
/**
48+
* Asynchronously retrieves an ArrayBuffer that represents the raw byte data from this selected image.
49+
*/
50+
getImageData(): Promise<ArrayBuffer>;
51+
2852
/**
2953
* For iOS Returns a promise with NSData representation of the asset.
54+
* On Android, Returns a promise with a java.io.InputStream.
3055
* Note that in future versions it should return ArrayBuffer.
3156
*/
3257
data(): Thenable<any>;
@@ -53,7 +78,7 @@ declare module "imagepicker" {
5378
/**
5479
* Set the picker mode. Supported modes: "single" or "multiple" (default).
5580
*/
56-
selectionMode?: string;
81+
mode?: string;
5782
}
5883

5984
export function create(options?: Options): ImagePicker;

source/images.ios.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"use strict";
12
var ui_frame = require("ui/frame");
23
var page;
34
var list;

source/viewmodel.android.ts

Lines changed: 106 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@ import observable = require("data/observable");
22
import imagesource = require("image-source");
33
import application = require("application");
44

5+
interface ArrayBufferStatic extends ArrayBufferConstructor {
6+
from(buffer: java.nio.ByteBuffer): ArrayBuffer;
7+
}
8+
59
var Intent = android.content.Intent;
610
var Activity = android.app.Activity;
711
var MediaStore = android.provider.MediaStore;
812
var BitmapFactory = android.graphics.BitmapFactory;
13+
var StaticArrayBuffer = <ArrayBufferStatic>ArrayBuffer;
914

1015
export class SelectedAsset extends observable.Observable {
1116
private _uri: android.net.Uri;
1217
private _thumb: imagesource.ImageSource;
1318
private _thumbRequested: boolean;
1419
private _fileUri: string;
20+
private _data: ArrayBuffer;
1521

1622
constructor(uri: android.net.Uri) {
1723
super();
@@ -23,6 +29,30 @@ export class SelectedAsset extends observable.Observable {
2329
return Promise.reject(new Error("Not implemented."));
2430
}
2531

32+
getImage(options?: { maxWidth: number, maxHeight: number }): Promise<imagesource.ImageSource> {
33+
return new Promise<imagesource.ImageSource>((resolve, reject) => {
34+
try {
35+
resolve(this.decodeUri(this._uri, options));
36+
} catch (ex) {
37+
reject(ex);
38+
}
39+
});
40+
}
41+
42+
getImageData(): Promise<ArrayBuffer> {
43+
return new Promise<ArrayBuffer>((resolve, reject) => {
44+
try {
45+
if (!this._data) {
46+
var bb = this.getByteBuffer(this._uri);
47+
this._data = StaticArrayBuffer.from(bb);
48+
}
49+
resolve(this._data);
50+
} catch (ex) {
51+
reject(ex);
52+
}
53+
});
54+
}
55+
2656
get thumb(): imagesource.ImageSource {
2757
if (!this._thumbRequested) {
2858
this.decodeThumbUri();
@@ -35,7 +65,7 @@ export class SelectedAsset extends observable.Observable {
3565
}
3666

3767
get fileUri(): string {
38-
if (!this._fileUri){
68+
if (!this._fileUri) {
3969
this._fileUri = this._calculateFileUri();
4070
}
4171
return this._fileUri;
@@ -44,7 +74,7 @@ export class SelectedAsset extends observable.Observable {
4474
private _calculateFileUri(): string {
4575
var cursor: android.database.ICursor;
4676
var columns = [MediaStore.MediaColumns.DATA];
47-
if (android.os.Build.VERSION.SDK_INT >= 19){
77+
if (android.os.Build.VERSION.SDK_INT >= 19) {
4878
var wholeID: string = (<any>android.provider).DocumentsContract.getDocumentId(this._uri);
4979
var id = wholeID.split(":")[1];
5080
cursor = this.getContentResolver().query(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, "_id=?", [id], null);
@@ -87,34 +117,91 @@ export class SelectedAsset extends observable.Observable {
87117

88118
private decodeThumbUri(): void {
89119
// Decode image size
120+
var REQUIRED_SIZE = {
121+
maxWidth: 100,
122+
maxHeight: 100
123+
};
124+
125+
// Decode with scale
126+
this._thumb = this.decodeUri(this._uri, REQUIRED_SIZE);
127+
this.notifyPropertyChange("thumb", this._thumb);
128+
}
129+
130+
/**
131+
* Discovers the sample size that a BitmapFactory.Options object should have
132+
* to scale the retrieved image to the given max size.
133+
* @param uri The URI of the image that should be scaled.
134+
* @param options The options that should be used to produce the correct image scale.
135+
*/
136+
private getSampleSize(uri: android.net.Uri, options?: { maxWidth: number, maxHeight: number }): number {
90137
var boundsOptions = new BitmapFactory.Options();
91138
boundsOptions.inJustDecodeBounds = true;
92-
BitmapFactory.decodeStream(this.getContentResolver().openInputStream(this._uri), null, boundsOptions);
93-
94-
var REQUIRED_SIZE = 100;
139+
BitmapFactory.decodeStream(this.openInputStream(uri), null, boundsOptions);
95140

96141
// Find the correct scale value. It should be the power of 2.
97142
var outWidth = boundsOptions.outWidth;
98143
var outHeight = boundsOptions.outHeight;
99144
var scale = 1;
100-
while (true) {
101-
if (outWidth / 2 < REQUIRED_SIZE
102-
|| outHeight / 2 < REQUIRED_SIZE) {
103-
break;
145+
if (options) {
146+
// TODO: Refactor to accomodate different scaling options
147+
// Right now, it just selects the smallest of the two sizes
148+
// and scales the image proportionally to that.
149+
var targetSize = options.maxWidth < options.maxHeight ? options.maxWidth : options.maxHeight;
150+
while (!(this.matchesSize(targetSize, outWidth) ||
151+
this.matchesSize(targetSize, outHeight))) {
152+
outWidth /= 2;
153+
outHeight /= 2;
154+
scale *= 2;
104155
}
105-
outWidth /= 2;
106-
outHeight /= 2;
107-
scale *= 2;
108156
}
157+
return scale;
158+
}
109159

110-
// Decode with scale
160+
private matchesSize(targetSize: number, actualSize: number): boolean {
161+
return targetSize && actualSize / 2 < targetSize;
162+
}
163+
164+
/**
165+
* Decodes the given URI using the given options.
166+
* @param uri The URI that should be decoded into an ImageSource.
167+
* @param options The options that should be used to decode the image.
168+
*/
169+
private decodeUri(uri: android.net.Uri, options?: { maxWidth: number, maxHeight: number }): imagesource.ImageSource {
111170
var downsampleOptions = new BitmapFactory.Options();
112-
downsampleOptions.inSampleSize = scale;
113-
var bitmap = BitmapFactory.decodeStream(this.getContentResolver().openInputStream(this._uri), null, downsampleOptions);
171+
downsampleOptions.inSampleSize = this.getSampleSize(uri, options);
172+
var bitmap = BitmapFactory.decodeStream(this.openInputStream(uri), null, downsampleOptions);
173+
var image = new imagesource.ImageSource();
174+
image.setNativeSource(bitmap);
175+
return image;
176+
}
114177

115-
this._thumb = new imagesource.ImageSource();
116-
this._thumb.setNativeSource(bitmap);
117-
this.notifyPropertyChange("thumb", this._thumb);
178+
/**
179+
* Retrieves the raw data of the given file and exposes it as a byte buffer.
180+
*/
181+
private getByteBuffer(uri: android.net.Uri): java.nio.ByteBuffer {
182+
var file: android.content.res.AssetFileDescriptor = null;
183+
try {
184+
file = this.getContentResolver().openAssetFileDescriptor(uri, "r");
185+
186+
// Determine how many bytes to allocate in memory based on the file length
187+
var length: number = file.getLength();
188+
var buffer: java.nio.ByteBuffer = java.nio.ByteBuffer.allocateDirect(length);
189+
var bytes = buffer.array();
190+
var stream = file.createInputStream();
191+
192+
// Buffer the data in 4KiB amounts
193+
var reader = new java.io.BufferedInputStream(stream, 4096);
194+
reader.read(bytes, 0, bytes.length);
195+
return buffer;
196+
} finally {
197+
if (file) {
198+
file.close();
199+
}
200+
}
201+
}
202+
203+
private openInputStream(uri: android.net.Uri): java.io.InputStream {
204+
return this.getContentResolver().openInputStream(uri);
118205
}
119206

120207
private getContentResolver(): android.content.ContentResolver {
@@ -179,7 +266,7 @@ export class ImagePicker {
179266
resolve(results);
180267
return;
181268

182-
} catch(e) {
269+
} catch (e) {
183270
application.android.off(application.AndroidApplication.activityResultEvent, onResult);
184271
reject(e);
185272
return;

0 commit comments

Comments
 (0)