esvg/esvg/render/DynamicColor.cpp

453 lines
15 KiB
C++

/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <esvg/debug.hpp>
#include <esvg/render/DynamicColor.hpp>
#include <esvg/LinearGradient.hpp>
#include <esvg/RadialGradient.hpp>
#include <esvg/esvg.hpp>
esvg::render::DynamicColorSpecial::DynamicColorSpecial(const etk::String& _link, const mat2x3& _mtx) :
m_linear(true),
m_colorName(_link),
m_matrix(_mtx),
m_viewPort(vec2(9999999999.0,9999999999.0),vec2(-9999999999.0,-9999999999.0)) {
}
void esvg::render::DynamicColorSpecial::setViewPort(const etk::Pair<vec2, vec2>& _viewPort) {
m_viewPort = _viewPort;
}
static vec2 getIntersect(const vec2& _point1,
const vec2& _vect1,
const vec2& _point2,
const vec2& _vect2) {
float diviseur = _vect1.x() * _vect2.y() - _vect1.y() * _vect2.x();
if(diviseur != 0.0f) {
float mmm = ( _vect1.x() * _point1.y()
- _vect1.x() * _point2.y()
- _vect1.y() * _point1.x()
+ _vect1.y() * _point2.x()
) / diviseur;
return vec2(_point2 + _vect2 * mmm);
}
ESVG_ERROR("Get divider / 0.0f");
return _point2;
}
etk::Color<float,4> esvg::render::DynamicColorSpecial::getColor(const ivec2& _pos) const {
if (m_data.size() < 2) {
return etk::color::purple;
}
if (m_linear == true) {
return getColorLinear(_pos);
} else {
return getColorRadial(_pos);
}
return etk::color::purple;
}
etk::Color<float,4> esvg::render::DynamicColorSpecial::getColorLinear(const ivec2& _pos) const {
float ratio = 0.0f;
if (m_unit == gradientUnits_userSpaceOnUse) {
vec2 vectorBase = m_pos2 - m_pos1;
vec2 vectorOrtho(vectorBase.y(), -vectorBase.x());
vec2 intersec = getIntersect(m_pos1, vectorBase,
vec2(_pos.x(), _pos.y()), vectorOrtho);
float baseSize = vectorBase.length();
vec2 vectorBaseDraw = intersec - m_pos1;
float baseDraw = vectorBaseDraw.length();
ratio = baseDraw / baseSize;
switch(m_spread) {
case spreadMethod_pad:
if (vectorBase.dot(vectorBaseDraw) < 0) {
ratio *= -1.0;
}
break;
case spreadMethod_reflect:
ratio -= float((int32_t(ratio)>>1)<<1);
if (ratio > 1.0f) {
ratio = 2.0f-ratio;
}
break;
case spreadMethod_repeat:
if (vectorBase.dot(vectorBaseDraw) < 0) {
ratio *= -1.0;
}
ratio -= float(int32_t(ratio));
if (ratio <0.0f) {
#ifndef __STDCPP_LLVM__
ratio = 1.0f-etk::abs(ratio);
#else
ratio = 1.0f-abs(ratio);
#endif
}
break;
}
} else {
// in the basic vertion of the gradient the color is calculated with the ration in X and Y in the bonding box associated (it is rotate with the object..
vec2 intersecX = getIntersect(m_pos1, m_axeX,
vec2(_pos.x(), _pos.y()), m_axeY);
vec2 intersecY = getIntersect(m_pos1, m_axeY,
vec2(_pos.x(), _pos.y()), m_axeX);
vec2 vectorBaseDrawX = intersecX - m_pos1;
vec2 vectorBaseDrawY = intersecY - m_pos1;
float baseDrawX = vectorBaseDrawX.length();
float baseDrawY = vectorBaseDrawY.length();
if (m_axeX.dot(vectorBaseDrawX) < 0) {
baseDrawX *= -1.0f;
}
if (m_axeY.dot(vectorBaseDrawY) < 0) {
baseDrawY *= -1.0f;
}
if (m_baseSize.x()+m_baseSize.y() != 0.0f) {
if ( m_baseSize.x() != 0.0f
&& m_baseSize.y() != 0.0f) {
ratio = (baseDrawX*m_baseSize.y() + baseDrawY*m_baseSize.x())/(m_baseSize.x()*m_baseSize.y()*2.0f);
} else if (m_baseSize.x() != 0.0f) {
ratio = baseDrawX/m_baseSize.x();
} else {
ratio = baseDrawY/m_baseSize.y();
}
} else {
ratio = 1.0f;
}
switch(m_spread) {
case spreadMethod_pad:
// nothing to do ...
break;
case spreadMethod_reflect:
#ifndef __STDCPP_LLVM__
ratio = etk::abs(ratio);
#else
ratio = abs(ratio);
#endif
ratio -= float((int32_t(ratio)>>1)<<1);
if (ratio > 1.0f) {
ratio = 2.0f-ratio;
}
break;
case spreadMethod_repeat:
ratio -= float(int32_t(ratio));
if (ratio <0.0f) {
#ifndef __STDCPP_LLVM__
ratio = 1.0f-etk::abs(ratio);
#else
ratio = 1.0f-abs(ratio);
#endif
}
break;
}
}
if (ratio <= m_data[0].first*0.01f) {
return m_data[0].second;
}
if (ratio >= m_data.back().first*0.01f) {
return m_data.back().second;
}
for (size_t iii=1; iii<m_data.size(); ++iii) {
if (ratio <= m_data[iii].first*0.01f) {
float localRatio = ratio - m_data[iii-1].first*0.01f;
localRatio = localRatio / ((m_data[iii].first - m_data[iii-1].first) * 0.01f);
return etk::Color<float,4>(m_data[iii-1].second.r() * (1.0-localRatio) + m_data[iii].second.r() * localRatio,
m_data[iii-1].second.g() * (1.0-localRatio) + m_data[iii].second.g() * localRatio,
m_data[iii-1].second.b() * (1.0-localRatio) + m_data[iii].second.b() * localRatio,
m_data[iii-1].second.a() * (1.0-localRatio) + m_data[iii].second.a() * localRatio);
}
}
return etk::color::green;
}
static etk::Pair<vec2,vec2> intersectLineToCircle(const vec2& _pos1,
const vec2& _pos2,
const vec2& _center = vec2(0.0f, 0.0f),
float _radius = 1.0f) {
vec2 v1;
vec2 v2;
//vector2D from point 1 to point 2
v1 = _pos2 - _pos1;
//vector2D from point 1 to the circle's center
v2 = _center - _pos1;
float dot = v1.dot(v2);
vec2 proj1 = vec2(((dot / (v1.length2())) * v1.x()),
((dot / (v1.length2())) * v1.y()));
vec2 midpt = _pos1 + proj1;
float distToCenter = (midpt - _center).length2();
if (distToCenter > _radius * _radius) {
return etk::Pair<vec2,vec2>(vec2(0.0,0.0), vec2(0.0,0.0));
}
if (distToCenter == _radius * _radius) {
return etk::Pair<vec2,vec2>(midpt, midpt);
}
float distToIntersection;
if (distToCenter == 0.0f) {
distToIntersection = _radius;
} else {
#ifndef __STDCPP_LLVM__
distToCenter = etk::sqrt(distToCenter);
distToIntersection = etk::sqrt(_radius * _radius - distToCenter * distToCenter);
#else
distToCenter = sqrtf(distToCenter);
distToIntersection = sqrtf(_radius * _radius - distToCenter * distToCenter);
#endif
}
// normalize...
v1.safeNormalize();
v1 *= distToIntersection;
return etk::Pair<vec2,vec2>(midpt + v1, midpt - v1);
}
etk::Color<float,4> esvg::render::DynamicColorSpecial::getColorRadial(const ivec2& _pos) const {
float ratio = 0.0f;
// in the basic vertion of the gradient the color is calculated with the ration in X and Y in the bonding box associated (it is rotate with the object)..
vec2 intersecX = getIntersect(m_pos1, m_axeX,
vec2(_pos.x(), _pos.y()), m_axeY);
vec2 intersecY = getIntersect(m_pos1, m_axeY,
vec2(_pos.x(), _pos.y()), m_axeX);
vec2 vectorBaseDrawX = intersecX - m_pos1;
vec2 vectorBaseDrawY = intersecY - m_pos1;
float baseDrawX = vectorBaseDrawX.length();
float baseDrawY = vectorBaseDrawY.length();
// specal case when focal == center (this is faster ...)
if (m_centerIsFocal == true) {
ratio = vec2(baseDrawX, baseDrawY).length();
if (m_baseSize.x()+m_baseSize.y() != 0.0f) {
if ( m_baseSize.x() != 0.0f
&& m_baseSize.y() != 0.0f) {
ratio = vec2(baseDrawX/m_baseSize.x(), baseDrawY/m_baseSize.y()).length();
} else if (m_baseSize.x() != 0.0f) {
ratio = baseDrawX/m_baseSize.x();
} else {
ratio = baseDrawY/m_baseSize.y();
}
} else {
ratio = 1.0f;
}
} else {
// set the sense of the elements:
if (m_axeX.dot(vectorBaseDrawX) < 0) {
baseDrawX *= -1.0f;
}
if (m_axeY.dot(vectorBaseDrawY) < 0) {
baseDrawY *= -1.0f;
}
if (m_baseSize.y() != 0.0f) {
baseDrawY /= m_baseSize.y();
}
// normalize to 1.0f
baseDrawX /= m_baseSize.x();
if ( m_clipOut == true
&& baseDrawX <= -1.0f) {
ratio = 1.0f;
} else {
float tmpLength = -m_focalLength/m_baseSize.x();
vec2 focalCenter = vec2(tmpLength, 0.0f);
vec2 currentPoint = vec2(baseDrawX, baseDrawY);
if (focalCenter == currentPoint) {
ratio = 0.0f;
} else {
etk::Pair<vec2,vec2> positions = intersectLineToCircle(focalCenter, currentPoint);
float lenghtBase = (currentPoint - focalCenter).length();
float lenghtBorder1 = (positions.first - focalCenter).length();
float lenghtBorder2 = (positions.second - focalCenter).length();
ratio = lenghtBase/lenghtBorder1;
}
}
}
switch(m_spread) {
case spreadMethod_pad:
// nothing to do ...
break;
case spreadMethod_reflect:
ratio -= float((int32_t(ratio)>>1)<<1);
if (ratio > 1.0f) {
ratio = 2.0f-ratio;
}
break;
case spreadMethod_repeat:
ratio -= float(int32_t(ratio));
if (ratio <0.0f) {
#ifndef __STDCPP_LLVM__
ratio = 1.0f-etk::abs(ratio);
#else
ratio = 1.0f-abs(ratio);
#endif
}
break;
}
if (ratio <= m_data[0].first*0.01f) {
return m_data[0].second;
}
if (ratio >= m_data.back().first*0.01f) {
return m_data.back().second;
}
for (size_t iii=1; iii<m_data.size(); ++iii) {
if (ratio <= m_data[iii].first*0.01f) {
float localRatio = ratio - m_data[iii-1].first*0.01f;
localRatio = localRatio / ((m_data[iii].first - m_data[iii-1].first) * 0.01f);
return etk::Color<float,4>(m_data[iii-1].second.r() * (1.0-localRatio) + m_data[iii].second.r() * localRatio,
m_data[iii-1].second.g() * (1.0-localRatio) + m_data[iii].second.g() * localRatio,
m_data[iii-1].second.b() * (1.0-localRatio) + m_data[iii].second.b() * localRatio,
m_data[iii-1].second.a() * (1.0-localRatio) + m_data[iii].second.a() * localRatio);
}
}
return etk::color::green;
}
void esvg::render::DynamicColorSpecial::generate(esvg::Document* _document) {
if (_document == null) {
ESVG_ERROR("Get null input for document");
return;
}
ememory::SharedPtr<esvg::Base> base = _document->getReference(m_colorName);
if (base == null) {
ESVG_ERROR("Can not get base : '" << m_colorName << "'");
return;
}
// Now we can know if we use linear or radial gradient ...
ememory::SharedPtr<esvg::LinearGradient> gradient = ememory::dynamicPointerCast<esvg::LinearGradient>(base);
if (gradient != null) {
m_linear = true;
ESVG_VERBOSE("get for color linear:");
gradient->display(2);
m_unit = gradient->m_unit;
m_spread = gradient->m_spread;
ESVG_VERBOSE(" viewport = {" << m_viewPort.first << "," << m_viewPort.second << "}");
vec2 size = m_viewPort.second - m_viewPort.first;
esvg::Dimension dimPos1 = gradient->getPosition1();
m_pos1 = dimPos1.getPixel(size);
if (dimPos1.getType() == esvg::distance_pourcent) {
m_pos1 += m_viewPort.first;
}
esvg::Dimension dimPos2 = gradient->getPosition2();
m_pos2 = dimPos2.getPixel(size);
if (dimPos2.getType() == esvg::distance_pourcent) {
m_pos2 += m_viewPort.first;
}
// in the basic vertion of the gradient the color is calculated with the ration in X and Y in the bonding box associated (it is rotate with the object..
vec2 delta = m_pos2 - m_pos1;
if (delta.x() < 0.0f) {
m_axeX = vec2(-1.0f, 0.0f);
} else {
m_axeX = vec2(1.0f, 0.0f);
}
if (delta.y() < 0.0f) {
m_axeY = vec2(0.0f, -1.0f);
} else {
m_axeY = vec2(0.0f, 1.0f);
}
// Move the positions ...
m_pos1 = m_matrix * m_pos1;
m_pos2 = m_matrix * m_pos2;
m_axeX = m_matrix.applyScaleRotation(m_axeX);
m_axeY = m_matrix.applyScaleRotation(m_axeY);
// in the basic vertion of the gradient the color is calculated with the ration in X and Y in the bonding box associated (it is rotate with the object..
vec2 intersecX = getIntersect(m_pos1, m_axeX,
m_pos2, m_axeY);
vec2 intersecY = getIntersect(m_pos1, m_axeY,
m_pos2, m_axeX);
m_baseSize = vec2((m_pos1 - intersecX).length(),
(m_pos1 - intersecY).length());
// get all the colors
m_data = gradient->getColors(_document);
} else {
m_linear = false;
ememory::SharedPtr<esvg::RadialGradient> gradient = ememory::dynamicPointerCast<esvg::RadialGradient>(base);
if (gradient == null) {
ESVG_ERROR("Can not cast in a linear gradient: '" << m_colorName << "' ==> wrong type");
return;
}
ESVG_VERBOSE("get for color Radial:");
gradient->display(2);
m_unit = gradient->m_unit;
m_spread = gradient->m_spread;
ESVG_VERBOSE(" viewport = {" << m_viewPort.first << "," << m_viewPort.second << "}");
vec2 size = m_viewPort.second - m_viewPort.first;
esvg::Dimension dimCenter = gradient->getCenter();
vec2 center = dimCenter.getPixel(size);
if (dimCenter.getType() == esvg::distance_pourcent) {
center += m_viewPort.first;
}
esvg::Dimension dimFocal = gradient->getFocal();
vec2 focal = dimFocal.getPixel(size);
if (dimFocal.getType() == esvg::distance_pourcent) {
focal += m_viewPort.first;
}
esvg::Dimension1D dimRadius = gradient->getRadius();
// in the basic vertion of the gradient the color is calculated with the ration in X and Y in the bonding box associated (it is rotate with the object)..
if (center == focal) {
m_centerIsFocal = true;
m_pos2.setX(dimRadius.getPixel(size.x()));
m_pos2.setY(dimRadius.getPixel(size.y()));
m_pos2 += center;
vec2 delta = center - m_pos2;
if (delta.x() < 0.0f) {
m_axeX = vec2(-1.0f, 0.0f);
} else {
m_axeX = vec2(1.0f, 0.0f);
}
if (delta.y() < 0.0f) {
m_axeY = vec2(0.0f, -1.0f);
} else {
m_axeY = vec2(0.0f, 1.0f);
}
m_pos1 = center;
} else {
m_centerIsFocal = false;
m_axeX = (center - focal).safeNormalize();
m_axeY = vec2(m_axeX.y(), -m_axeX.x());
m_pos2 = m_axeX * dimRadius.getPixel(size.x()) + m_axeY * dimRadius.getPixel(size.y());
m_pos2 += center;
m_pos1 = center;
}
// Move the positions ...
m_pos1 = m_matrix * m_pos1;
center = m_matrix * center;
m_pos2 = m_matrix * m_pos2;
m_axeX = m_matrix.applyScaleRotation(m_axeX);
m_axeY = m_matrix.applyScaleRotation(m_axeY);
// in the basic vertion of the gradient the color is calculated with the ration in X and Y in the bonding box associated (it is rotate with the object..
vec2 intersecX = getIntersect(m_pos1, m_axeX,
m_pos2, m_axeY);
vec2 intersecY = getIntersect(m_pos1, m_axeY,
m_pos2, m_axeX);
m_baseSize = vec2((intersecX - m_pos1).length(),
(intersecY - m_pos1).length());
if (m_centerIsFocal == false) {
m_focalLength = (center - m_matrix * focal).length();
if (m_focalLength >= m_baseSize.x()) {
ESVG_DEBUG("Change position of the Focal ... ==> set it inside the circle");
m_focalLength = m_baseSize.x()*0.999998f;
m_clipOut = true;
} else {
m_clipOut = false;
}
}
ESVG_VERBOSE("baseSize=" << m_baseSize << " m_pos1=" << m_pos1 << " dim=" << dimCenter << " m_focal=" << m_focal << " m_pos2=" << m_pos2 << " dim=" << dimRadius);
// get all the colors
m_data = gradient->getColors(_document);
}
}
ememory::SharedPtr<esvg::render::DynamicColor> esvg::render::createColor(etk::Pair<etk::Color<float,4>, etk::String> _color, const mat2x3& _mtx) {
// Check if need to create a color:
if ( _color.first.a() == 0x00
&& _color.second == "") {
return null;
}
if (_color.second != "") {
return ememory::makeShared<esvg::render::DynamicColorSpecial>(_color.second, _mtx);
}
return ememory::makeShared<esvg::render::DynamicColorUni>(_color.first);
}