Added JS wrappers for ImageStore and ImageEditor

Summary:
public
Added JS wrappers for ImageStore(Manager) and ImageEditor(Manager) so they can be required in the normal way instead of accessed directly via NativeModules.

Reviewed By: dmmiller

Differential Revision: D2773822

fb-gh-sync-id: 6eeafd3f80a87b1b91a04a2aebad6e2fd31b0e98
This commit is contained in:
Nick Lockwood 2015-12-22 13:36:37 -08:00 committed by facebook-github-bot-3
parent b7e939b38d
commit 83c2e0303b
8 changed files with 186 additions and 25 deletions

View File

@ -20,6 +20,7 @@ var React = require('react-native');
var {
CameraRoll,
Image,
ImageEditor,
NativeModules,
ScrollView,
StyleSheet,
@ -28,7 +29,7 @@ var {
UIManager,
View,
} = React;
var ImageEditingManager = NativeModules.ImageEditingManager;
var RCTScrollViewConsts = UIManager.RCTScrollView.Constants;
var PAGE_SIZE = 20;
@ -43,14 +44,16 @@ type ImageSize = {
height: number;
};
type TransformData = {
type ImageCropData = {
offset: ImageOffset;
size: ImageSize;
}
displaySize?: ?ImageSize;
resizeMode?: ?any;
};
class SquareImageCropper extends React.Component {
_isMounted: boolean;
_transformData: TransformData;
_transformData: ImageCropData;
constructor(props) {
super(props);
@ -167,7 +170,7 @@ class SquareImageCropper extends React.Component {
}
_crop() {
ImageEditingManager.cropImage(
ImageEditor.cropImage(
this.state.randomPhoto.uri,
this._transformData,
(croppedImageURI) => this.setState({croppedImageURI}),
@ -231,7 +234,7 @@ class ImageCropper extends React.Component {
var sizeRatioX = croppedImageSize.width / scaledImageSize.width;
var sizeRatioY = croppedImageSize.height / scaledImageSize.height;
this.props.onTransformDataChange && this.props.onTransformDataChange({
var cropData: ImageCropData = {
offset: {
x: this.props.image.width * offsetRatioX,
y: this.props.image.height * offsetRatioY,
@ -240,7 +243,8 @@ class ImageCropper extends React.Component {
width: this.props.image.width * sizeRatioX,
height: this.props.image.height * sizeRatioY,
},
});
};
this.props.onTransformDataChange && this.props.onTransformDataChange(cropData);
}
render() {
@ -271,8 +275,8 @@ class ImageCropper extends React.Component {
}
exports.framework = 'React';
exports.title = 'ImageEditingManager';
exports.description = 'Cropping and scaling with ImageEditingManager';
exports.title = 'ImageEditor';
exports.description = 'Cropping and scaling with ImageEditor';
exports.examples = [{
title: 'Image Cropping',
render() {

View File

@ -10,6 +10,7 @@
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* @flow
*/
'use strict';

View File

@ -0,0 +1,72 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ImageEditor
* @flow
*/
'use strict';
const RCTImageEditingManager = require('NativeModules').ImageEditingManager;
type ImageCropData = {
/**
* The top-left corner of the cropped image, specified in the original
* image's coordinate space.
*/
offset: {
x: number;
y: number;
};
/**
* The size (dimensions) of the cropped image, specified in the original
* image's coordinate space.
*/
size: {
width: number;
height: number;
};
/**
* (Optional) size to scale the cropped image to.
*/
displaySize?: ?{
width: number;
height: number;
};
/**
* (Optional) the resizing mode to use when scaling the image. If the
* `displaySize` param is not specified, this has no effect.
*/
resizeMode?: ?$Enum<{
contain: string;
cover: string;
stretch: string;
}>;
};
class ImageEditor {
/**
* Crop the image specified by the URI param. If URI points to a remote
* image, it will be downloaded automatically. If the image cannot be
* loaded/downloaded, the failure callback will be called.
*
* If the cropping process is successful, the resultant cropped image
* will be stored in the ImageStore, and the URI returned in the success
* callback will point to the image in the store. Remember to delete the
* cropped image from the ImageStore when you are done with it.
*/
static cropImage(
uri: string,
cropData: ImageCropData,
success: (uri: string) => void,
failure: (error: Object) => void
) {
RCTImageEditingManager.cropImage(uri, cropData, success, failure);
}
}
module.exports = ImageEditor;

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ImageStore
* @flow
*/
'use strict';
const RCTImageStoreManager = require('NativeModules').ImageStoreManager;
class ImageStore {
/**
* Check if the ImageStore contains image data for the specified URI.
* @platform ios
*/
static hasImageForTag(uri: string, callback: (hasImage: bool) => void) {
if (RCTImageStoreManager.hasImageForTag) {
RCTImageStoreManager.hasImageForTag(uri, callback);
} else {
console.warn('hasImageForTag() not implemented');
}
}
/**
* Delete an image from the ImageStore. Images are stored in memory and
* must be manually removed when you are finished with them, otherwise they
* will continue to use up RAM until the app is terminated. It is safe to
* call `removeImageForTag()` without first calling `hasImageForTag()`, it
* will simply fail silently.
* @platform ios
*/
static removeImageForTag(uri: string) {
if (RCTImageStoreManager.removeImageForTag) {
RCTImageStoreManager.removeImageForTag(uri);
} else {
console.warn('removeImageForTag() not implemented');
}
}
/**
* Stores a base64-encoded image in the ImageStore, and returns a URI that
* can be used to access or display the image later. Images are stored in
* memory only, and must be manually deleted when you are finished with
* them by calling `removeImageForTag()`.
*
* Note that it is very inefficient to transfer large quantities of binary
* data between JS and native code, so you should avoid calling this more
* than necessary.
*/
static addImageFromBase64(
base64ImageData: string,
success: (uri: string) => void,
failure: (error: any) => void
) {
RCTImageStoreManager.addImageFromBase64(base64ImageData, success, failure);
}
/**
* Retrieves the base64-encoded data for an image in the ImageStore. If the
* specified URI does not match an image in the store, the failure callback
* will be called.
*
* Note that it is very inefficient to transfer large quantities of binary
* data between JS and native code, so you should avoid calling this more
* than necessary. To display an image in the ImageStore, you can just pass
* the URI to an `<Image/>` component; there is no need to retrieve the
* base64 data.
*/
static getBase64ForTag(
uri: string,
success: (base64ImageData: string) => void,
failure: (error: any) => void
) {
RCTImageStoreManager.getBase64ForTag(uri, success, failure);
}
}
module.exports = ImageStore;

View File

@ -39,27 +39,18 @@ RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag
successCallback:(RCTResponseSenderBlock)successCallback
errorCallback:(RCTResponseErrorBlock)errorCallback)
{
NSDictionary *offset = cropData[@"offset"];
NSDictionary *size = cropData[@"size"];
NSDictionary *displaySize = cropData[@"displaySize"];
NSString *resizeMode = cropData[@"resizeMode"] ?: @"contain";
CGRect rect = {
[RCTConvert CGPoint:cropData[@"offset"]],
[RCTConvert CGSize:cropData[@"size"]]
};
if (!offset[@"x"] || !offset[@"y"] || !size[@"width"] || !size[@"height"]) {
NSString *errorMessage = [NSString stringWithFormat:@"Invalid cropData: %@", cropData];
RCTLogError(@"%@", errorMessage);
errorCallback(RCTErrorWithMessage(errorMessage));
return;
}
NSString *resizeMode = cropData[@"resizeMode"] ?: @"contain";
[_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *error, UIImage *image) {
if (error) {
errorCallback(error);
return;
}
CGRect rect = (CGRect){
[RCTConvert CGPoint:offset],
[RCTConvert CGSize:size]
};
// Crop image
CGRect rectToDrawIn = {{-rect.origin.x, -rect.origin.y}, image.size};
@ -68,8 +59,8 @@ RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (displaySize && displaySize[@"width"] && displaySize[@"height"]) {
CGSize targetSize = [RCTConvert CGSize:displaySize];
if (cropData[@"displaySize"]) {
CGSize targetSize = [RCTConvert CGSize:cropData[@"displaySize"]];
croppedImage = [self scaleImage:croppedImage targetSize:targetSize resizeMode:resizeMode];
}

View File

@ -86,6 +86,12 @@ RCT_EXPORT_METHOD(removeImageForTag:(NSString *)imageTag)
[_store removeObjectForKey:imageTag];
}
RCT_EXPORT_METHOD(hasImageForTag:(NSString *)imageTag
callback:(RCTResponseSenderBlock)callback)
{
callback(@[@(_store[imageTag] != nil)]);
}
// TODO (#5906496): Name could be more explicit - something like getBase64EncodedDataForTag:?
RCT_EXPORT_METHOD(getBase64ForTag:(NSString *)imageTag
successCallback:(RCTResponseSenderBlock)successCallback

View File

@ -18,6 +18,8 @@ var ReactNative = {
get DatePickerIOS() { return require('DatePickerIOS'); },
get DrawerLayoutAndroid() { return require('DrawerLayoutAndroid'); },
get Image() { return require('Image'); },
get ImageEditor() { return require('ImageEditor'); },
get ImageStore() { return require('ImageStore'); },
get ListView() { return require('ListView'); },
get MapView() { return require('MapView'); },
get Modal() { return require('Modal'); },

View File

@ -30,6 +30,8 @@ var ReactNative = Object.assign(Object.create(require('React')), {
DatePickerIOS: require('DatePickerIOS'),
DrawerLayoutAndroid: require('DrawerLayoutAndroid'),
Image: require('Image'),
ImageEditor: require('ImageEditor'),
ImageStore: require('ImageStore'),
ListView: require('ListView'),
MapView: require('MapView'),
Modal: require('Modal'),