Gradient supports percent value

Gradient supports percent value
This commit is contained in:
Horcrux 2016-01-26 11:36:58 +08:00
Родитель da5a23c5dd
Коммит e181a1a546
12 изменённых файлов: 312 добавлений и 26 удалений

Просмотреть файл

@ -8,7 +8,8 @@ import Svg, {
RadialGradient,
Stop,
Ellipse,
Circle
Circle,
Text
} from 'react-native-art-svg';
class LinearGradientHorizontal extends Component{
@ -20,8 +21,8 @@ class LinearGradientHorizontal extends Component{
>
<Defs>
<LinearGradient id="grad" x1="0" y1="0" x2="170" y2="0">
<Stop offset="0%" stopColor="rgb(255,255,0)" stopOpacity="0" />
<Stop offset="100%" stopColor="red" stopOpacity="1" />
<Stop offset="0" stopColor="rgb(255,255,0)" stopOpacity="0" />
<Stop offset="1" stopColor="red" stopOpacity="1" />
</LinearGradient>
</Defs>
<Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" />
@ -47,6 +48,26 @@ class LinearGradientVertical extends Component{
}
}
class LinearGradientPercent extends Component{
static title = 'Define a linear gradient in percent unit';
render() {
return <Svg
height="150"
width="300"
>
<Defs>
<LinearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
<Stop offset="0%" stopColor="rgb(255,255,0)" stopOpacity="0" />
<Stop offset="100%" stopColor="red" stopOpacity="1" />
</LinearGradient>
</Defs>
<Text x="25" y="70" fill="#333">x1=0%</Text>
<Text x="235" y="70" fill="#333">x2=100%</Text>
<Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" />
</Svg>;
}
}
class RadialGradientExample extends Component{
static title = 'Define an ellipse with a radial gradient from yellow to purple';
render() {
@ -57,12 +78,12 @@ class RadialGradientExample extends Component{
<Defs>
<RadialGradient id="grad" cx="150" cy="75" rx="85" ry="55" fx="150" fy="75">
<Stop
offset="0%"
offset="0"
stopColor="#ff0"
stopOpacity="1"
/>
<Stop
offset="100%"
offset="1"
stopColor="#83a"
stopOpacity="1"
/>
@ -73,7 +94,7 @@ class RadialGradientExample extends Component{
}
}
class RadialGradientExample2 extends Component{
class RadialGradientPart extends Component{
static title = 'Define another ellipse with a radial gradient from white to blue';
render() {
return <Svg
@ -99,6 +120,32 @@ class RadialGradientExample2 extends Component{
}
}
class RadialGradientPercent extends Component{
static title = 'Define a radial gradient in percent unit';
render() {
return <Svg
height="150"
width="300"
>
<Defs>
<RadialGradient id="grad" cx="50%" cy="50%" rx="50%" ry="50%" fx="50%" fy="50%">
<Stop
offset="0%"
stopColor="#fff"
stopOpacity="1"
/>
<Stop
offset="100%"
stopColor="#00f"
stopOpacity="1"
/>
</RadialGradient>
</Defs>
<Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" />
</Svg>;
}
}
const icon = <Svg
height="20"
width="20"
@ -113,7 +160,7 @@ const icon = <Svg
</Svg>;
const samples = [LinearGradientHorizontal, LinearGradientVertical, RadialGradientExample, RadialGradientExample2];
const samples = [LinearGradientHorizontal, LinearGradientVertical, LinearGradientPercent, RadialGradientExample, RadialGradientPercent, RadialGradientPart];
export {
icon,

Просмотреть файл

@ -18,3 +18,7 @@
3. textPath (wait for official supports)
4. pattern
5. animations https://github.com/maxwellito/vivus
### Thanks
[SVG bounding Algorithm](https://github.com/icons8/svg-path-bounding-box)
[Circle drawing with svg arc path](http://stackoverflow.com/questions/5737975/circle-drawing-with-svgs-arc-path/10477334#10477334)

Просмотреть файл

@ -9,9 +9,10 @@ let {
LinearGradient: ARTLinearGradient
} = ART;
import {set, remove} from '../lib/fillFilter';
import percentFactory from '../lib/percentFactory';
import percentToFloat from '../lib/percentToFloat';
import Stop from './Stop';
import Color from 'color';
let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
class LinearGradient extends Component{
static displayName = 'LinearGradient';
@ -49,16 +50,26 @@ class LinearGradient extends Component{
let stops = {};
Children.forEach(this.props.children, child => {
if (child.type === Stop && child.props.stopColor && child.props.offset) {
// convert percent to float.
let matched = child.props.offset.match(percentReg);
let offset = matched[2] ? matched[1] / 100 : matched[1];
let offset = percentToFloat(child.props.offset);
// add stop
stops[offset] = Color(child.props.stopColor).alpha(+child.props.stopOpacity).rgbaString();
// TODO: convert percent to float.
set(this.id, new ARTLinearGradient(stops, x1, y1, x2, y2));
let factories = percentFactory(x1, y1, x2, y2);
if (factories) {
set(this.id, function (boundingBox) {
return new ARTLinearGradient(
stops,
factories[0](boundingBox.width),
factories[1](boundingBox.height),
factories[2](boundingBox.width),
factories[3](boundingBox.height)
);
});
} else {
set(this.id, new ARTLinearGradient(stops, x1, y1, x2, y2));
}
} else {
console.warn(`'LinearGradient' can only receive 'Stop' elements as children`);
}

Просмотреть файл

@ -8,6 +8,7 @@ let {
Shape
} = ART;
import Defs from './Defs';
import calculateBoundingBox from '../lib/calculateBoundingBox';
import fillFilter from '../lib/fillFilter';
import strokeFilter from '../lib/strokeFilter';
@ -25,6 +26,23 @@ class Path extends Component{
strokeJoin: PropTypes.oneOf(['miter', 'bevel', 'round']),
strokeDasharray: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.number)])
};
_dimensions = null;
componentWillReceiveProps = nextProps => {
if (nextProps.d !== this.props.d) {
this._dimensions = null;
}
};
getBoundingBox = () => {
if (!this._dimensions) {
this._dimensions = calculateBoundingBox(this.props.d);
}
return this._dimensions;
};
render() {
let {props} = this;
if (props.id) {
@ -40,7 +58,7 @@ class Path extends Component{
{...props}
{...strokeFilter(props)}
{...transformFilter(props)}
fill={fillFilter(props)}
fill={fillFilter.call(this, props)}
id={null}
/>;
}

Просмотреть файл

@ -9,6 +9,8 @@ let {
RadialGradient: ARTRadialGradient
} = ART;
import {set, remove} from '../lib/fillFilter';
import percentFactory from '../lib/percentFactory';
import percentToFloat from '../lib/percentToFloat';
import Stop from './Stop';
import Color from 'color';
let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
@ -60,17 +62,30 @@ class RadialGradient extends Component{
let stops = {};
Children.forEach(this.props.children, child => {
if (child.type === Stop && child.props.stopColor && child.props.offset) {
// convert percent to float.
let matched = child.props.offset.match(percentReg);
let offset = matched[2] ? matched[1] / 100 : matched[1];
let offset = percentToFloat(child.props.offset);
// add stop
stops[offset] = Color(child.props.stopColor).alpha(+child.props.stopOpacity).rgbaString();
// TODO: convert percent to float.
set(this.id, new ARTRadialGradient(stops, fx, fy, rx, ry, cx, cy));
let factories = percentFactory(fx, fy, rx, ry, cx, cy);
if (factories) {
set(this.id, function (boundingBox) {
let {x1,y1,width, height} = boundingBox;
return new ARTRadialGradient(
stops,
x1 + factories[0](width),
y1 + factories[1](height),
factories[2](width),
factories[3](height),
x1 + factories[4](width),
y1 + factories[5](height)
);
});
} else {
set(this.id, new ARTRadialGradient(stops, fx, fy, rx, ry, cx, cy));
}
} else {
console.warn(`'RadialGradient' can only receive 'Stop' elements as children`);
}

Просмотреть файл

@ -44,6 +44,8 @@ class Text extends Component{
<Text {...this.props} id={null} />
</Defs.Item>;
}
// TODO: support percent gradients
return <ARTText
{...props}
{...transformFilter(props)}

101
lib/BoundingBox.js Normal file
Просмотреть файл

@ -0,0 +1,101 @@
// from https://github.com/gabelerner/canvg/blob/860e418aca67b9a41e858a223d74d375793ec364/canvg.js#L449
class BoundingBox{
constructor(x1, y1, x2, y2) {
this.x1 = NaN;
this.y1 = NaN;
this.x2 = NaN;
this.y2 = NaN;
this.addPoint(x1, y1);
this.addPoint(x2, y2);
}
width = () => this.x2 - this.x1;
height = () => this.y2 - this.y1;
addPoint = (x, y) => {
if (x != null) {
if (isNaN(this.x1) || isNaN(this.x2)) {
this.x1 = x;
this.x2 = x;
}
if (x < this.x1) this.x1 = x;
if (x > this.x2) this.x2 = x;
}
if (y != null) {
if (isNaN(this.y1) || isNaN(this.y2)) {
this.y1 = y;
this.y2 = y;
}
if (y < this.y1) this.y1 = y;
if (y > this.y2) this.y2 = y;
}
};
addX = x => this.addPoint(x, null);
addY = y => this.addPoint(null, y);
addQuadraticCurve = (p0x, p0y, p1x, p1y, p2x, p2y) => {
let cp1x = p0x + 2 / 3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
let cp1y = p0y + 2 / 3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
let cp2x = cp1x + 1 / 3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
let cp2y = cp1y + 1 / 3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
};
addBezierCurve = (p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) => {
// from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
var p0 = [p0x, p0y],
p1 = [p1x, p1y],
p2 = [p2x, p2y],
p3 = [p3x, p3y];
this.addPoint(p0[0], p0[1]);
this.addPoint(p3[0], p3[1]);
[0, 1].forEach(i => {
let f = t => {
return Math.pow(1 - t, 3) * p0[i]
+ 3 * Math.pow(1 - t, 2) * t * p1[i]
+ 3 * (1 - t) * Math.pow(t, 2) * p2[i]
+ Math.pow(t, 3) * p3[i];
};
let b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
let a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
let c = 3 * p1[i] - 3 * p0[i];
if (a == 0) {
if (b != 0) {
let t = -c / b;
if (0 < t && t < 1) {
if (i == 0) this.addX(f(t));
if (i == 1) this.addY(f(t));
}
}
return;
}
let b2ac = Math.pow(b, 2) - 4 * c * a;
if (b2ac < 0) {
return;
}
let t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
if (0 < t1 && t1 < 1) {
i == 0 && this.addX(f(t1));
i == 1 && this.addY(f(t1));
}
let t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
if (0 < t2 && t2 < 1) {
i == 0 && this.addX(f(t2));
i == 1 && this.addY(f(t2));
}
});
};
}
export default BoundingBox;

Просмотреть файл

@ -0,0 +1,59 @@
import SvgPath from 'svgpath';
import BoundingBox from './BoundingBox';
export default function(d) {
let pathDriver = new SvgPath(d);
let boundingBox = new BoundingBox();
pathDriver
.abs()
.unarc()
.unshort()
.iterate((seg, index, x, y) => {
switch(seg[0]) {
case 'M':
case 'L':
boundingBox.addPoint(
seg[1],
seg[2]
);
break;
case 'H':
boundingBox.addX(seg[1]);
break;
case 'V':
boundingBox.addY(seg[1]);
break;
case 'Q':
boundingBox.addQuadraticCurve(
x,
y,
seg[1],
seg[2],
seg[3],
seg[4]
);
break;
case 'C':
boundingBox.addBezierCurve(
x,
y,
seg[1],
seg[2],
seg[3],
seg[4],
seg[5],
seg[6]
);
break;
}
});
return {
width: boundingBox.width(),
height: boundingBox.height(),
x1: boundingBox.x1,
y1: boundingBox.y1,
x2: boundingBox.x2,
y2: boundingBox.y2
};
}

Просмотреть файл

@ -10,11 +10,16 @@ let {
let fillPatterns = {};
let fillReg = /^url\(#(\w+?)\)$/;
function isGradient(obj) {
return obj instanceof LinearGradient || obj instanceof RadialGradient;
}
export default function (props) {
let {fill} = props;
if (fill) {
if (fill instanceof LinearGradient || fill instanceof RadialGradient) {
if (isGradient(fill)) {
return fill;
}
@ -22,11 +27,16 @@ export default function (props) {
let matched = fill.match(fillReg);
if (matched) {
let patternName = `${matched[1]}:${props.svgId}`;
if (fillPatterns[patternName]) {
return fillPatterns[patternName];
} else {
return null;
let pattern = fillPatterns[patternName];
if (pattern) {
if (typeof pattern === 'function') {
let dimensions = this.getBoundingBox();
return pattern(dimensions);
} else {
return pattern;
}
}
return null;
}
}

13
lib/percentFactory.js Normal file
Просмотреть файл

@ -0,0 +1,13 @@
import percentToFloat from './percentToFloat';
let percentReg = /%/;
export default function (...args) {
let hasPercent = percentReg.test(args.join(''));
if (hasPercent) {
return args.map(arg => {
return function (base) {
return percentReg.test(arg) ? percentToFloat(arg) * base : +arg;
};
});
}
}

5
lib/percentToFloat.js Normal file
Просмотреть файл

@ -0,0 +1,5 @@
let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
export default function (percent) {
let matched = percent.match(percentReg);
return matched[2] ? matched[1] / 100 : +matched[1];
}

Просмотреть файл

@ -1,5 +1,5 @@
{
"version": "1.0.3",
"version": "1.0.5",
"name": "react-native-art-svg",
"description": "react native svg library based on `ART`",
"repository": {
@ -17,6 +17,7 @@
"VML"
],
"dependencies": {
"color": "^0.11.1"
"color": "^0.11.1",
"svgpath": "^2.1.5"
}
}