mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-20 05:55:10 +00:00
Add preserveAspectRatio prop for Image
Add preserveAspectRatio prop for Image. Fix touch events for Image on Android. Fix viewBox slice bug on Android.
This commit is contained in:
@@ -7,24 +7,48 @@ import Svg, {
|
|||||||
Defs,
|
Defs,
|
||||||
Circle,
|
Circle,
|
||||||
ClipPath,
|
ClipPath,
|
||||||
|
Rect,
|
||||||
Text
|
Text
|
||||||
} from 'react-native-svg';
|
} from 'react-native-svg';
|
||||||
|
|
||||||
class ImageExample extends Component{
|
class ImageExample extends Component{
|
||||||
static title = 'Image';
|
static title = 'Draw Image with preserveAspectRatio prop';
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Svg
|
return <Svg
|
||||||
height="100"
|
height="100"
|
||||||
width="100"
|
width="100"
|
||||||
|
style={{backgroundColor: 'red'}}
|
||||||
>
|
>
|
||||||
|
<Defs>
|
||||||
|
<ClipPath id="clip">
|
||||||
|
<Circle cx="50%" cy="50%" r="40%"/>
|
||||||
|
</ClipPath>
|
||||||
|
</Defs>
|
||||||
|
<Rect
|
||||||
|
x="5%"
|
||||||
|
y="5%"
|
||||||
|
width="50%"
|
||||||
|
height="90%"
|
||||||
|
/>
|
||||||
<Image
|
<Image
|
||||||
x="5%"
|
x="5%"
|
||||||
y="5%"
|
y="5%"
|
||||||
width="90%"
|
width="50%"
|
||||||
height="90%"
|
height="90%"
|
||||||
|
preserveAspectRatio="xMidYMid slice"
|
||||||
|
opacity="0.5"
|
||||||
href={require('../image.jpg')}
|
href={require('../image.jpg')}
|
||||||
|
clipPath="url(#clip)"
|
||||||
/>
|
/>
|
||||||
|
<Text
|
||||||
|
x="50"
|
||||||
|
y="50"
|
||||||
|
textAnchor="middle"
|
||||||
|
fontWeight="bold"
|
||||||
|
fontSize="16"
|
||||||
|
fill="blue"
|
||||||
|
>HOGWARTS</Text>
|
||||||
</Svg>;
|
</Svg>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +67,7 @@ class ClipImage extends Component{
|
|||||||
</ClipPath>
|
</ClipPath>
|
||||||
</Defs>
|
</Defs>
|
||||||
<Image
|
<Image
|
||||||
|
onPress={() => alert('press on Image')}
|
||||||
x="5%"
|
x="5%"
|
||||||
y="5%"
|
y="5%"
|
||||||
width="90%"
|
width="90%"
|
||||||
|
|||||||
@@ -579,7 +579,7 @@ npm install
|
|||||||
2. more Text features support (textPath, tref, tspan)
|
2. more Text features support (textPath, tref, tspan)
|
||||||
3. Pattern element
|
3. Pattern element
|
||||||
4. implement Animated elements
|
4. implement Animated elements
|
||||||
5. more Image features support
|
5. load Image from url
|
||||||
|
|
||||||
#### Thanks:
|
#### Thanks:
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,16 @@ package com.horcrux.svg;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Region;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.facebook.common.executors.UiThreadImmediateExecutorService;
|
import com.facebook.common.executors.UiThreadImmediateExecutorService;
|
||||||
import com.facebook.common.logging.FLog;
|
import com.facebook.common.logging.FLog;
|
||||||
import com.facebook.common.references.CloseableReference;
|
import com.facebook.common.references.CloseableReference;
|
||||||
@@ -45,6 +51,9 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
private String mW;
|
private String mW;
|
||||||
private String mH;
|
private String mH;
|
||||||
private Uri mUri;
|
private Uri mUri;
|
||||||
|
private float mImageRatio;
|
||||||
|
private String mAlign;
|
||||||
|
private int mMeetOrSlice;
|
||||||
private AtomicBoolean mLoading = new AtomicBoolean(false);
|
private AtomicBoolean mLoading = new AtomicBoolean(false);
|
||||||
|
|
||||||
@ReactProp(name = "x")
|
@ReactProp(name = "x")
|
||||||
@@ -81,12 +90,29 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mImageRatio = (float)src.getInt("width") / (float)src.getInt("height");
|
||||||
mUri = Uri.parse(uriString);
|
mUri = Uri.parse(uriString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ReactProp(name = "align")
|
||||||
|
public void setAlign(String align) {
|
||||||
|
mAlign = align;
|
||||||
|
markUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "meetOrSlice")
|
||||||
|
public void setMeetOrSlice(int meetOrSlice) {
|
||||||
|
mMeetOrSlice = meetOrSlice;
|
||||||
|
markUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(final Canvas canvas, final Paint paint, final float opacity) {
|
public void draw(final Canvas canvas, final Paint paint, final float opacity) {
|
||||||
|
mPath = new Path();
|
||||||
|
mPath.addRect(new RectF(getRect()), Path.Direction.CW);
|
||||||
|
|
||||||
if (!mLoading.get()) {
|
if (!mLoading.get()) {
|
||||||
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build();
|
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build();
|
||||||
|
|
||||||
@@ -140,14 +166,70 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
|
|
||||||
private void doRender(@Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final Bitmap bitmap, final float opacity) {
|
private void doRender(@Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final Bitmap bitmap, final float opacity) {
|
||||||
final int count = saveAndSetupCanvas(canvas);
|
final int count = saveAndSetupCanvas(canvas);
|
||||||
|
canvas.concat(mMatrix);
|
||||||
clip(canvas, paint);
|
|
||||||
|
|
||||||
Paint alphaPaint = new Paint();
|
Paint alphaPaint = new Paint();
|
||||||
alphaPaint.setAlpha((int) (opacity * 255));
|
alphaPaint.setAlpha((int) (opacity * 255));
|
||||||
|
|
||||||
canvas.drawBitmap(bitmap, null, getRect(), alphaPaint);
|
// apply viewBox transform on Image render.
|
||||||
|
Rect rect = getRect();
|
||||||
|
float rectWidth = (float)rect.width();
|
||||||
|
float rectHeight = (float)rect.height();
|
||||||
|
float rectX = (float)rect.left;
|
||||||
|
float rectY = (float)rect.top;
|
||||||
|
float rectRatio = rectWidth / rectHeight;
|
||||||
|
RectF renderRect;
|
||||||
|
|
||||||
|
if (mImageRatio == rectRatio) {
|
||||||
|
renderRect = new RectF(rect);
|
||||||
|
} else if (mImageRatio < rectRatio) {
|
||||||
|
renderRect = new RectF(0, 0, (int)(rectHeight * mImageRatio), (int)rectHeight);
|
||||||
|
} else {
|
||||||
|
renderRect = new RectF(0, 0, (int)rectWidth, (int)(rectWidth / mImageRatio));
|
||||||
|
}
|
||||||
|
|
||||||
|
RNSVGViewBoxShadowNode viewBox = new RNSVGViewBoxShadowNode();
|
||||||
|
viewBox.setMinX("0");
|
||||||
|
viewBox.setMinY("0");
|
||||||
|
viewBox.setVbWidth(renderRect.width() / mScale + "");
|
||||||
|
viewBox.setVbHeight(renderRect.height() / mScale + "");
|
||||||
|
viewBox.setWidth(rectWidth / mScale);
|
||||||
|
viewBox.setHeight(rectHeight / mScale);
|
||||||
|
viewBox.setAlign(mAlign);
|
||||||
|
viewBox.setMeetOrSlice(mMeetOrSlice);
|
||||||
|
viewBox.setupDimensions(new Rect(0, 0, (int) rectWidth, (int) rectHeight));
|
||||||
|
Matrix transform = viewBox.getTransform();
|
||||||
|
|
||||||
|
transform.mapRect(renderRect);
|
||||||
|
Matrix translation = new Matrix();
|
||||||
|
translation.postTranslate(rectX, rectY);
|
||||||
|
translation.mapRect(renderRect);
|
||||||
|
|
||||||
|
Path clip = new Path();
|
||||||
|
|
||||||
|
Path clipPath = getClipPath(canvas, paint);
|
||||||
|
|
||||||
|
if (clipPath != null) {
|
||||||
|
// clip by the common area of clipPath and mPath
|
||||||
|
clip.setFillType(Path.FillType.INVERSE_EVEN_ODD);
|
||||||
|
|
||||||
|
Path inverseWindingPath = new Path();
|
||||||
|
inverseWindingPath.setFillType(Path.FillType.INVERSE_WINDING);
|
||||||
|
inverseWindingPath.addPath(mPath);
|
||||||
|
inverseWindingPath.addPath(clipPath);
|
||||||
|
|
||||||
|
Path evenOddPath = new Path();
|
||||||
|
evenOddPath.setFillType(Path.FillType.EVEN_ODD);
|
||||||
|
evenOddPath.addPath(mPath);
|
||||||
|
evenOddPath.addPath(clipPath);
|
||||||
|
|
||||||
|
canvas.clipPath(evenOddPath, Region.Op.DIFFERENCE);
|
||||||
|
canvas.clipPath(inverseWindingPath, Region.Op.DIFFERENCE);
|
||||||
|
} else {
|
||||||
|
canvas.clipPath(mPath, Region.Op.REPLACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawBitmap(bitmap, null, renderRect, alphaPaint);
|
||||||
restoreCanvas(canvas, count);
|
restoreCanvas(canvas, count);
|
||||||
markUpdateSeen();
|
markUpdateSeen();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode {
|
|||||||
}
|
}
|
||||||
path.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW);
|
path.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW);
|
||||||
} else {
|
} else {
|
||||||
path.addRect(x, y, x + w, y + h, Path.Direction.CW);
|
path.addRect(x, y, x + w, y + h, Path.Direction.CW);
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
package com.horcrux.svg;
|
package com.horcrux.svg;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
@@ -85,8 +86,13 @@ public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||||
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
|
|
||||||
setupDimensions(canvas);
|
setupDimensions(canvas);
|
||||||
|
mMatrix = getTransform();
|
||||||
|
super.draw(canvas, paint, opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix getTransform() {
|
||||||
|
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
|
||||||
|
|
||||||
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively.
|
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively.
|
||||||
float vbX = PropHelper.fromPercentageToFloat(mMinX, mCanvasWidth, 0, mScale);
|
float vbX = PropHelper.fromPercentageToFloat(mMinX, mCanvasWidth, 0, mScale);
|
||||||
@@ -134,7 +140,7 @@ public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode {
|
|||||||
if (!mAlign.equals("none") && mMeetOrSlice == MOS_MEET) {
|
if (!mAlign.equals("none") && mMeetOrSlice == MOS_MEET) {
|
||||||
scaleX = scaleY = Math.min(scaleX, scaleY);
|
scaleX = scaleY = Math.min(scaleX, scaleY);
|
||||||
} else if (!mAlign.equals("none") && mMeetOrSlice == MOS_SLICE) {
|
} else if (!mAlign.equals("none") && mMeetOrSlice == MOS_SLICE) {
|
||||||
scaleX = scaleY = Math.min(scaleX, scaleY);
|
scaleX = scaleY = Math.max(scaleX, scaleY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If align contains 'xMid', minus (e-width / scale-x - vb-width) / 2 from transform-x.
|
// If align contains 'xMid', minus (e-width / scale-x - vb-width) / 2 from transform-x.
|
||||||
@@ -161,10 +167,10 @@ public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode {
|
|||||||
|
|
||||||
// The transform applied to content contained by the element is given by
|
// The transform applied to content contained by the element is given by
|
||||||
// translate(translate-x, translate-y) scale(scale-x, scale-y).
|
// translate(translate-x, translate-y) scale(scale-x, scale-y).
|
||||||
mMatrix.reset();
|
Matrix transform = new Matrix();
|
||||||
mMatrix.postTranslate(-translateX * (mFromSymbol ? scaleX : 1), -translateY * (mFromSymbol ? scaleY : 1));
|
transform.postTranslate(-translateX * (mFromSymbol ? scaleX : 1), -translateY * (mFromSymbol ? scaleY : 1));
|
||||||
mMatrix.postScale(scaleX, scaleY);
|
transform.postScale(scaleX, scaleY);
|
||||||
super.draw(canvas, paint, opacity);
|
return transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -236,16 +236,21 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void clip(Canvas canvas, Paint paint) {
|
protected @Nullable Path getClipPath(Canvas canvas, Paint paint) {
|
||||||
Path clip = mClipPath;
|
Path clip = mClipPath;
|
||||||
if (clip == null && mClipPathRef != null) {
|
if (clip == null && mClipPathRef != null) {
|
||||||
RNSVGVirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPathRef);
|
RNSVGVirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPathRef);
|
||||||
clip = node.getPath(canvas, paint);
|
clip = node.getPath(canvas, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clip(Canvas canvas, Paint paint) {
|
||||||
|
Path clip = getClipPath(canvas, paint);
|
||||||
|
|
||||||
if (clip != null) {
|
if (clip != null) {
|
||||||
canvas.clipPath(clip, Region.Op.REPLACE);
|
canvas.clipPath(clip, Region.Op.REPLACE);
|
||||||
canvas.saveLayer(0f, 0f, 0f, 0f, paint, Canvas.CLIP_SAVE_FLAG);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +291,13 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
|
|||||||
mCanvasHeight = canvas.getHeight();
|
mCanvasHeight = canvas.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setupDimensions(Rect rect) {
|
||||||
|
mCanvasX = rect.left;
|
||||||
|
mCanvasY = rect.top;
|
||||||
|
mCanvasWidth = rect.width();
|
||||||
|
mCanvasHeight = rect.height();
|
||||||
|
}
|
||||||
|
|
||||||
protected void saveDefinition() {
|
protected void saveDefinition() {
|
||||||
if (mName != null) {
|
if (mName != null) {
|
||||||
getSvgShadowNode().defineTemplate(this, mName);
|
getSvgShadowNode().defineTemplate(this, mName);
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import {ImageAttributes} from '../lib/attributes';
|
|||||||
import {numberProp, touchableProps, responderProps} from '../lib/props';
|
import {numberProp, touchableProps, responderProps} from '../lib/props';
|
||||||
import Shape from './Shape';
|
import Shape from './Shape';
|
||||||
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
||||||
|
import {meetOrSliceTypes, alignEnum} from './ViewBox';
|
||||||
|
const spacesRegExp = /\s+/;
|
||||||
|
|
||||||
class Image extends Shape {
|
class Image extends Shape {
|
||||||
static displayName = 'Image';
|
static displayName = 'Image';
|
||||||
@@ -15,15 +16,16 @@ class Image extends Shape {
|
|||||||
y: numberProp,
|
y: numberProp,
|
||||||
width: numberProp.isRequired,
|
width: numberProp.isRequired,
|
||||||
height: numberProp.isRequired,
|
height: numberProp.isRequired,
|
||||||
href: PropTypes.number.isRequired
|
href: PropTypes.number.isRequired,
|
||||||
//preserveAspectRatio: PropTypes.string
|
preserveAspectRatio: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0
|
height: 0,
|
||||||
|
preserveAspectRatio: 'xMidYMid meet'
|
||||||
};
|
};
|
||||||
|
|
||||||
setNativeProps = (...args) => {
|
setNativeProps = (...args) => {
|
||||||
@@ -32,6 +34,10 @@ class Image extends Shape {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {props} = this;
|
let {props} = this;
|
||||||
|
let modes = props.preserveAspectRatio.trim().split(spacesRegExp);
|
||||||
|
let meetOrSlice = meetOrSliceTypes[modes[1]] || 0;
|
||||||
|
let align = alignEnum[modes[0]] || 'xMidYMid';
|
||||||
|
|
||||||
return <RNSVGImage
|
return <RNSVGImage
|
||||||
ref={ele => {this.root = ele;}}
|
ref={ele => {this.root = ele;}}
|
||||||
{...this.extractProps({...props, x: null, y: null}, {responder: true, transform: true})}
|
{...this.extractProps({...props, x: null, y: null}, {responder: true, transform: true})}
|
||||||
@@ -39,6 +45,8 @@ class Image extends Shape {
|
|||||||
y={props.y.toString()}
|
y={props.y.toString()}
|
||||||
width={props.width.toString()}
|
width={props.width.toString()}
|
||||||
height={props.height.toString()}
|
height={props.height.toString()}
|
||||||
|
meetOrSlice={meetOrSlice}
|
||||||
|
align={align}
|
||||||
src={resolveAssetSource(props.href)}
|
src={resolveAssetSource(props.href)}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,3 +73,8 @@ const RNSVGViewBox = createReactNativeComponentClass({
|
|||||||
|
|
||||||
|
|
||||||
export default ViewBox;
|
export default ViewBox;
|
||||||
|
|
||||||
|
export {
|
||||||
|
meetOrSliceTypes,
|
||||||
|
alignEnum
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,12 +9,16 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
#import "RNSVGRenderable.h"
|
#import "RNSVGRenderable.h"
|
||||||
|
#import "RNSVGVBMOS.h"
|
||||||
|
|
||||||
@interface RNSVGImage : RNSVGRenderable
|
@interface RNSVGImage : RNSVGRenderable
|
||||||
|
|
||||||
@property (nonatomic, assign) id src;
|
@property (nonatomic, assign) id src;
|
||||||
@property (nonatomic, strong) NSString* x;
|
@property (nonatomic, strong) NSString* x;
|
||||||
@property (nonatomic, strong) NSString* y;
|
@property (nonatomic, strong) NSString* y;
|
||||||
@property (nonatomic, strong) NSString* width;
|
@property (nonatomic, strong) NSString* width;
|
||||||
@property (nonatomic, strong) NSString* height;
|
@property (nonatomic, strong) NSString* height;
|
||||||
|
@property (nonatomic, strong) NSString *align;
|
||||||
|
@property (nonatomic, assign) RNSVGVBMOS meetOrSlice;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -7,12 +7,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import "RNSVGImage.h"
|
#import "RNSVGImage.h"
|
||||||
|
#import "RCTImageSource.h"
|
||||||
#import "RCTConvert+RNSVG.h"
|
#import "RCTConvert+RNSVG.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
|
#import "RNSVGViewBox.h"
|
||||||
|
|
||||||
@implementation RNSVGImage
|
@implementation RNSVGImage
|
||||||
{
|
{
|
||||||
CGImageRef image;
|
CGImageRef _image;
|
||||||
|
CGFloat _imageRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setSrc:(id)src
|
- (void)setSrc:(id)src
|
||||||
@@ -21,8 +24,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_src = src;
|
_src = src;
|
||||||
CGImageRelease(image);
|
CGImageRelease(_image);
|
||||||
image = CGImageRetain([RCTConvert CGImage:src]);
|
RCTImageSource *source = [RCTConvert RCTImageSource:src];
|
||||||
|
_imageRatio = source.size.width / source.size.height;
|
||||||
|
_image = CGImageRetain([RCTConvert CGImage:src]);
|
||||||
[self invalidate];
|
[self invalidate];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,29 +67,80 @@
|
|||||||
_height = height;
|
_height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setAlign:(NSString *)align
|
||||||
|
{
|
||||||
|
if (align == _align) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[self invalidate];
|
||||||
|
_align = align;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setMeetOrSlice:(RNSVGVBMOS)meetOrSlice
|
||||||
|
{
|
||||||
|
if (meetOrSlice == _meetOrSlice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[self invalidate];
|
||||||
|
_meetOrSlice = meetOrSlice;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
CGImageRelease(image);
|
CGImageRelease(_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)renderLayerTo:(CGContextRef)context
|
- (void)renderLayerTo:(CGContextRef)context
|
||||||
{
|
{
|
||||||
CGRect rect = [self getRect:context];
|
CGRect rect = [self getRect:context];
|
||||||
// add hit area
|
// add hit area
|
||||||
self.hitArea = CGPathCreateWithRect(rect, nil);
|
self.hitArea = CFAutorelease(CGPathCreateWithRect(rect, nil));
|
||||||
[self clip:context];
|
[self clip:context];
|
||||||
|
|
||||||
CGContextSaveGState(context);
|
CGContextSaveGState(context);
|
||||||
CGContextTranslateCTM(context, 0, rect.size.height);
|
CGContextTranslateCTM(context, 0, rect.size.height + 2 * rect.origin.y);
|
||||||
CGContextScaleCTM(context, 1.0, -1.0);
|
CGContextScaleCTM(context, 1, -1);
|
||||||
CGContextDrawImage(context, rect, image);
|
|
||||||
|
// apply viewBox transform on Image render.
|
||||||
|
CGFloat imageRatio = _imageRatio;
|
||||||
|
CGFloat rectWidth = rect.size.width;
|
||||||
|
CGFloat rectHeight = rect.size.height;
|
||||||
|
CGFloat rectX = rect.origin.x;
|
||||||
|
CGFloat rectY = rect.origin.y;
|
||||||
|
CGFloat rectRatio = rectWidth / rectHeight;
|
||||||
|
CGRect renderRect;
|
||||||
|
|
||||||
|
if (imageRatio == rectRatio) {
|
||||||
|
renderRect = rect;
|
||||||
|
} else if (imageRatio < rectRatio) {
|
||||||
|
renderRect = CGRectMake(0, 0, rectHeight * imageRatio, rectHeight);
|
||||||
|
} else {
|
||||||
|
renderRect = CGRectMake(0, 0, rectWidth, rectWidth / imageRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
RNSVGViewBox *viewBox = [[RNSVGViewBox alloc] init];
|
||||||
|
viewBox.minX = viewBox.minY = @"0";
|
||||||
|
viewBox.vbWidth = [NSString stringWithFormat:@"%f", renderRect.size.width];
|
||||||
|
viewBox.vbHeight = [NSString stringWithFormat:@"%f", renderRect.size.height];
|
||||||
|
viewBox.width = [NSString stringWithFormat:@"%f", rectWidth];
|
||||||
|
viewBox.height = [NSString stringWithFormat:@"%f", rectHeight];
|
||||||
|
viewBox.align = self.align;
|
||||||
|
viewBox.meetOrSlice = self.meetOrSlice;
|
||||||
|
[viewBox setBoundingBox:CGRectMake(0, 0, rectWidth, rectHeight)];
|
||||||
|
CGAffineTransform transform = [viewBox getTransform];
|
||||||
|
|
||||||
|
renderRect = CGRectApplyAffineTransform(renderRect, transform);
|
||||||
|
renderRect = CGRectApplyAffineTransform(renderRect, CGAffineTransformMakeTranslation(rectX, rectY));
|
||||||
|
|
||||||
|
CGContextClipToRect(context, rect);
|
||||||
|
CGContextDrawImage(context, renderRect, _image);
|
||||||
CGContextRestoreGState(context);
|
CGContextRestoreGState(context);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGRect)getRect:(CGContextRef)context
|
- (CGRect)getRect:(CGContextRef)context
|
||||||
{
|
{
|
||||||
[self setBoundingBox:context];
|
[self setBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
CGFloat x = [self getWidthRelatedValue:self.x];
|
CGFloat x = [self getWidthRelatedValue:self.x];
|
||||||
CGFloat y = [self getHeightRelatedValue:self.y];
|
CGFloat y = [self getHeightRelatedValue:self.y];
|
||||||
CGFloat width = [self getWidthRelatedValue:self.width];
|
CGFloat width = [self getWidthRelatedValue:self.width];
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
@property (nonatomic, assign) CGPathRef hitArea;
|
@property (nonatomic, assign) CGPathRef hitArea;
|
||||||
@property (nonatomic, copy) NSArray<NSString *> *propList;
|
@property (nonatomic, copy) NSArray<NSString *> *propList;
|
||||||
|
|
||||||
- (void)setBoundingBox:(CGContextRef)context;
|
- (void)setBoundingBox:(CGRect)boundingBox;
|
||||||
- (CGFloat)getWidthRelatedValue:(NSString *)string;
|
- (CGFloat)getWidthRelatedValue:(NSString *)string;
|
||||||
- (CGFloat)getHeightRelatedValue:(NSString *)string;
|
- (CGFloat)getHeightRelatedValue:(NSString *)string;
|
||||||
- (CGFloat)getContextWidth;
|
- (CGFloat)getContextWidth;
|
||||||
|
|||||||
@@ -205,11 +205,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setBoundingBox:(CGContextRef)context
|
- (void)setBoundingBox:(CGRect)boundingBox
|
||||||
{
|
{
|
||||||
_boundingBox = CGContextGetClipBoundingBox(context);
|
_boundingBox = boundingBox;
|
||||||
_widthConverter = [[RNSVGPercentageConverter alloc] initWithRelativeAndOffset:CGRectGetWidth(_boundingBox) offset:0];
|
_widthConverter = [[RNSVGPercentageConverter alloc] initWithRelativeAndOffset:boundingBox.size.width offset:0];
|
||||||
_heightConverter = [[RNSVGPercentageConverter alloc] initWithRelativeAndOffset:CGRectGetHeight(_boundingBox) offset:0];
|
_heightConverter = [[RNSVGPercentageConverter alloc] initWithRelativeAndOffset:boundingBox.size.height offset:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGFloat)getWidthRelatedValue:(NSString *)string
|
- (CGFloat)getWidthRelatedValue:(NSString *)string
|
||||||
|
|||||||
@@ -20,4 +20,6 @@
|
|||||||
@property (nonatomic, strong) NSString *width;
|
@property (nonatomic, strong) NSString *width;
|
||||||
@property (nonatomic, strong) NSString *height;
|
@property (nonatomic, strong) NSString *height;
|
||||||
|
|
||||||
|
- (CGAffineTransform)getTransform;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -70,9 +70,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)renderTo:(CGContextRef)context
|
- (void)renderTo:(CGContextRef)context
|
||||||
|
{
|
||||||
|
[self setBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
|
self.matrix = [self getTransform];
|
||||||
|
[super renderTo:context];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGAffineTransform)getTransform
|
||||||
{
|
{
|
||||||
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
|
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
|
||||||
[self setBoundingBox:context];
|
|
||||||
|
|
||||||
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively.
|
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively.
|
||||||
CGFloat vbX = [self getWidthRelatedValue:self.minX];
|
CGFloat vbX = [self getWidthRelatedValue:self.minX];
|
||||||
@@ -150,9 +156,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.matrix = CGAffineTransformMakeScale(scaleX, scaleY);
|
CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY);
|
||||||
self.matrix = CGAffineTransformTranslate(self.matrix, -translateX * (_fromSymbol ? scaleX : 1), -translateY * (_fromSymbol ? scaleY : 1));
|
return CGAffineTransformTranslate(transform, -translateX * (_fromSymbol ? scaleX : 1), -translateY * (_fromSymbol ? scaleY : 1));
|
||||||
[super renderTo:context];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray<NSString *> *)mergeList
|
- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray<NSString *> *)mergeList
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
- (CGPathRef)getPath:(CGContextRef)context
|
- (CGPathRef)getPath:(CGContextRef)context
|
||||||
{
|
{
|
||||||
[self setBoundingBox:context];
|
[self setBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
CGMutablePathRef path = CGPathCreateMutable();
|
CGMutablePathRef path = CGPathCreateMutable();
|
||||||
RNSVGPercentageConverter* convert = [[RNSVGPercentageConverter alloc] init];
|
RNSVGPercentageConverter* convert = [[RNSVGPercentageConverter alloc] init];
|
||||||
CGFloat cx = [self getWidthRelatedValue:self.cx];
|
CGFloat cx = [self getWidthRelatedValue:self.cx];
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
- (CGPathRef)getPath:(CGContextRef)context
|
- (CGPathRef)getPath:(CGContextRef)context
|
||||||
{
|
{
|
||||||
[self setBoundingBox:context];
|
[self setBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
CGMutablePathRef path = CGPathCreateMutable();
|
CGMutablePathRef path = CGPathCreateMutable();
|
||||||
CGFloat cx = [self getWidthRelatedValue:self.cx];
|
CGFloat cx = [self getWidthRelatedValue:self.cx];
|
||||||
CGFloat cy = [self getHeightRelatedValue:self.cy];
|
CGFloat cy = [self getHeightRelatedValue:self.cy];
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
- (CGPathRef)getPath:(CGContextRef)context
|
- (CGPathRef)getPath:(CGContextRef)context
|
||||||
{
|
{
|
||||||
[self setBoundingBox:context];
|
[self setBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
CGMutablePathRef path = CGPathCreateMutable();
|
CGMutablePathRef path = CGPathCreateMutable();
|
||||||
CGFloat x1 = [self getWidthRelatedValue:self.x1];
|
CGFloat x1 = [self getWidthRelatedValue:self.x1];
|
||||||
CGFloat y1 = [self getHeightRelatedValue:self.y1];
|
CGFloat y1 = [self getHeightRelatedValue:self.y1];
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
- (CGPathRef)getPath:(CGContextRef)context
|
- (CGPathRef)getPath:(CGContextRef)context
|
||||||
{
|
{
|
||||||
[self setBoundingBox:context];
|
[self setBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
CGMutablePathRef path = CGPathCreateMutable();
|
CGMutablePathRef path = CGPathCreateMutable();
|
||||||
CGFloat x = [self getWidthRelatedValue:self.x];
|
CGFloat x = [self getWidthRelatedValue:self.x];
|
||||||
CGFloat y = [self getHeightRelatedValue:self.y];
|
CGFloat y = [self getHeightRelatedValue:self.y];
|
||||||
|
|||||||
@@ -25,5 +25,7 @@ RCT_EXPORT_VIEW_PROPERTY(y, NSString)
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(width, NSString)
|
RCT_EXPORT_VIEW_PROPERTY(width, NSString)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(height, NSString)
|
RCT_EXPORT_VIEW_PROPERTY(height, NSString)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(src, id)
|
RCT_EXPORT_VIEW_PROPERTY(src, id)
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(align, NSString)
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS)
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -160,7 +160,9 @@ const ImageAttributes = merge({
|
|||||||
y: true,
|
y: true,
|
||||||
width: true,
|
width: true,
|
||||||
height: true,
|
height: true,
|
||||||
src: true
|
src: true,
|
||||||
|
align: true,
|
||||||
|
meetOrSlice: true
|
||||||
}, RenderableAttributes);
|
}, RenderableAttributes);
|
||||||
|
|
||||||
const LineAttributes = merge({
|
const LineAttributes = merge({
|
||||||
|
|||||||
Reference in New Issue
Block a user