Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added automaticInitialCoverScale #11

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ render() {
}
```

You can provide `enableTransform`, `enableScale` and `enableTranslate` props to control corresponding features.
You can provide `enableTransform`, `enableScale`, `enableTranslate` and `enableLimits` props to control corresponding features.

#### Other props

* `onTransformGestureReleased` and `onViewTransformed`:

​ inherited from [react-native-view-transformer](https://github.com/ldn0x7dc/react-native-view-transformer)

* `automaticCoverInitialScale`: When set to `true` makes the Image look like if `resizeMode="cover"`

### Attention

* If you are using react-native v0.27 and below, or if the image source is local (`source={require('...')}`), you should provide the **pixels** prop, like `pixels={{width: 3607, height: 2400}}` (ask your API server to provide the pixels info for remote images). This prop is used to align the edge of the image content with the view's boundry and to determine the max scale.
Expand All @@ -51,4 +53,4 @@ You can provide `enableTransform`, `enableScale` and `enableTranslate` props to

## Image Gallery

If you are looking for an image gallery component, please refer to [**react-native-gallery**](https://github.com/ldn0x7dc/react-native-gallery), which is based on this component.
If you are looking for an image gallery component, please refer to [**react-native-gallery**](https://github.com/ldn0x7dc/react-native-gallery), which is based on this component.
187 changes: 151 additions & 36 deletions library/TransformableImage.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use strict';

import React, { Component, PropTypes } from 'react';
import { Image } from 'react-native';
import FastImage from 'react-native-fast-image'

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Image, View, PixelRatio } from 'react-native';

import ViewTransformer from 'react-native-view-transformer';

Expand All @@ -22,26 +25,33 @@ export default class TransformableImage extends Component {
enableTransform: PropTypes.bool,
enableScale: PropTypes.bool,
enableTranslate: PropTypes.bool,
initialScale: PropTypes.number,
automaticInitialCoverScale: PropTypes.bool,
onTransformGestureReleased: PropTypes.func,
onViewTransformed: PropTypes.func
};

static defaultProps = {
enableTransform: true,
enableScale: true,
enableTranslate: true
enableTranslate: true,
initialScale: null,
updateTransform: false,
automaticInitialCoverScale: false,
};

constructor(props) {
super(props);

this.setInitialCoverScale = this.setInitialCoverScale.bind(this);
this.state = {
width: 0,
height: 0,

initialScale: props.initialScale,
imageLoaded: false,
pixels: undefined,
keyAcumulator: 1
keyAccumulator: 1
};
}

Expand All @@ -53,35 +63,65 @@ export default class TransformableImage extends Component {

componentWillReceiveProps(nextProps) {
if (!sameSource(this.props.source, nextProps.source)) {
//image source changed, clear last image's pixels info if any
this.setState({pixels: undefined, keyAcumulator: this.state.keyAcumulator + 1})
this.getImageSize(nextProps.source);
this.setState({ keyAcumulator: this.state.keyAccumulator + 1 })

// Make sure new image resets its initial cover scale
if (nextProps.automaticInitialCoverScale) {
this.setState({ updateTransform: true })
}

// image source changed, clear last image's pixels info if any
this.setInitialCoverScale(this.state.width, this.state.height);
}
}

componentDidUpdate(prevProps, prevState) {
// When we are given the image size and have to calculate initialScale
if (
this.props.pixels != prevProps.pixels &&
this.state.initialScale == null &&
this.props.automaticInitialCoverScale
) {
this.setInitialCoverScale(this.state.width, this.state.height);
}
}

render() {
let maxScale = 1;
let contentAspectRatio = undefined;
let width, height; //pixels

if (this.props.pixels) {
//if provided via props
width = this.props.pixels.width;
height = this.props.pixels.height;
} else if (this.state.pixels) {
//if got using Image.getSize()
width = this.state.pixels.width;
height = this.state.pixels.height;
}
let { width, height } = this.getWidthAndHeight();
let initialScale = this.state.initialScale;

if (width && height) {
contentAspectRatio = width / height;

if (this.state.width && this.state.height) {
maxScale = Math.max(width / this.state.width, height / this.state.height);
maxScale = Math.max(1, maxScale);
}

if (maxScale < initialScale && initialScale != null) {
maxScale = initialScale + 2
}
}

var child = null
if (initialScale == null && this.props.automaticInitialCoverScale) {
child = (
<View onLayout={this.onLayout.bind(this)} style={this.props.style}></View>
)
} else {
child = (
<FastImage
{...this.props}
style={[this.props.style, {backgroundColor: 'transparent'}]}
resizeMode={'contain'}
onLoadStart={this.onLoadStart.bind(this)}
onLoad={this.onLoad.bind(this)}
capInsets={{left: 0.1, top: 0.1, right: 0.1, bottom: 0.1}} //on iOS, use capInsets to avoid image downsampling
/>
)
}

return (
<ViewTransformer
Expand All @@ -91,20 +131,16 @@ export default class TransformableImage extends Component {
enableScale={this.props.enableScale}
enableTranslate={this.props.enableTranslate}
enableResistance={true}
enableLimits={true}
onTransformGestureReleased={this.props.onTransformGestureReleased}
onViewTransformed={this.props.onViewTransformed}
maxScale={maxScale}
initialScale={initialScale == null ? 1 : initialScale}
contentAspectRatio={contentAspectRatio}
onLayout={this.onLayout.bind(this)}
style={this.props.style}>
<Image
{...this.props}
style={[this.props.style, {backgroundColor: 'transparent'}]}
resizeMode={'contain'}
onLoadStart={this.onLoadStart.bind(this)}
onLoad={this.onLoad.bind(this)}
capInsets={{left: 0.1, top: 0.1, right: 0.1, bottom: 0.1}} //on iOS, use capInsets to avoid image downsampling
/>
style={this.props.style}
>
{child}
</ViewTransformer>
);
}
Expand All @@ -123,13 +159,87 @@ export default class TransformableImage extends Component {
});
}

getWidthAndHeight() {
let width, height;

if (this.props.pixels) {
// If provided via props
width = this.props.pixels.width;
height = this.props.pixels.height;
} else if (this.state.pixels) {
// If got using Image.getSize()
width = this.state.pixels.width;
height = this.state.pixels.height;
}

return { width, height }
}

setInitialCoverScale(viewWidth, viewHeight) {
// automatic cover scale using a rule of three
let { width, height } = this.getWidthAndHeight();

if (
viewWidth == 0 || viewHeight == 0 ||
typeof width == 'undefined' || typeof height == 'undefined'
) {
return
}

let initialScale = this.props.initialScale;

viewHeight = PixelRatio.getPixelSizeForLayoutSize(viewHeight)
viewWidth = PixelRatio.getPixelSizeForLayoutSize(viewWidth)

if (this.props.automaticInitialCoverScale) {

if (height > width) {
var proportionalImageHeight = (viewHeight * width) / viewWidth;

if (proportionalImageHeight > height) {
initialScale = proportionalImageHeight / height;
} else {
initialScale = height / proportionalImageHeight;
}
} else {
var proportionalImageWidth = (viewWidth * height) / viewHeight;

if (proportionalImageWidth > width) {
initialScale = proportionalImageWidth / width;
} else {
initialScale = width / proportionalImageWidth;
}
}
}

let newState = { initialScale: initialScale }
if (this.state.updateTransform) {
this.refs['viewTransformer'].updateTransform({
scale: initialScale,
translateX: 0,
translateY: 0,
});
newState['updateTransform'] = false
}

this.setState(newState)
return initialScale;
}

onLayout(e) {
let {width, height} = e.nativeEvent.layout;
let { width, height } = e.nativeEvent.layout;

if (this.state.width !== width || this.state.height !== height) {
this.setState({
let newState = {
width: width,
height: height
});
height: height,
};

if (this.state.initialScale == null && this.props.automaticInitialCoverScale) {
this.setInitialCoverScale(width, height);
}

this.setState(newState);
}
}

Expand All @@ -145,10 +255,15 @@ export default class TransformableImage extends Component {
(width, height) => {
DEV && console.log('getImageSize...width=' + width + ', height=' + height);
if (width && height) {
if(this.state.pixels && this.state.pixels.width === width && this.state.pixels.height === height) {
//no need to update state
} else {
this.setState({pixels: {width, height}});
this.setState(
{pixels: {width, height}},
() => {
this.setInitialCoverScale(this.state.width, this.state.height)
}
);

if (this.props.onSizeFound) {
this.props.onSizeFound({width, height});
}
}
},
Expand Down Expand Up @@ -178,4 +293,4 @@ function sameSource(source, nextSource) {
}
}
return false;
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
},
"homepage": "https://github.com/ldn0x7dc/react-native-transformable-image#readme",
"dependencies": {
"react-native-view-transformer": "0.0.28"
"react-native-view-transformer": "git+https://github.com/maraujop/react-native-view-transformer.git#755dfcd09ea01d75244728e69c63d9c89ef69a5c"
}
}