#include "config.h"
#include "Gradient.h"
#if USE(CAIRO)
#include "CairoOperations.h"
#include "CairoUtilities.h"
#include "GraphicsContext.h"
#include "PlatformContextCairo.h"
namespace WebCore {
void Gradient::platformDestroy()
{
}
static void addColorStopRGBA(cairo_pattern_t *gradient, Gradient::ColorStop stop, float globalAlpha)
{
if (stop.color.isExtended()) {
cairo_pattern_add_color_stop_rgba(gradient, stop.offset, stop.color.asExtended().red(), stop.color.asExtended().green(),
stop.color.asExtended().blue(), stop.color.asExtended().alpha() * globalAlpha);
} else {
float r, g, b, a;
stop.color.getRGBA(r, g, b, a);
cairo_pattern_add_color_stop_rgba(gradient, stop.offset, r, g, b, a * globalAlpha);
}
}
#if PLATFORM(GTK) || PLATFORM(WPE)
typedef struct point_t {
double x, y;
} point_t;
static void setCornerColorRGBA(cairo_pattern_t* gradient, int id, Gradient::ColorStop stop, float globalAlpha)
{
if (stop.color.isExtended()) {
cairo_mesh_pattern_set_corner_color_rgba(gradient, id, stop.color.asExtended().red(), stop.color.asExtended().green(),
stop.color.asExtended().blue(), stop.color.asExtended().alpha() * globalAlpha);
} else {
float r, g, b, a;
stop.color.getRGBA(r, g, b, a);
cairo_mesh_pattern_set_corner_color_rgba(gradient, id, r, g, b, a * globalAlpha);
}
}
static void addConicSector(cairo_pattern_t *gradient, float cx, float cy, float r, float angleRadians,
Gradient::ColorStop from, Gradient::ColorStop to, float globalAlpha)
{
const double angOffset = 0.25;
double angleStart = ((from.offset - angOffset) * 2 * M_PI) + angleRadians;
double angleEnd = ((to.offset - angOffset) * 2 * M_PI) + angleRadians;
double cxOffset, cyOffset;
if (from.offset >= 0 && from.offset < 0.25) {
cxOffset = 0;
cyOffset = -1;
} else if (from.offset >= 0.25 && from.offset < 0.50) {
cxOffset = 0;
cyOffset = 0;
} else if (from.offset >= 0.50 && from.offset < 0.75) {
cxOffset = -1;
cyOffset = 0;
} else if (from.offset >= 0.75 && from.offset < 1) {
cxOffset = -1;
cyOffset = -1;
} else {
cxOffset = 0;
cyOffset = -1;
}
const double offsetWidth = 0.1;
cx = cx + cxOffset * offsetWidth;
cy = cy + cyOffset * offsetWidth;
double f = 4 * tan((angleEnd - angleStart) / 4) / 3;
point_t p0 = {
x: cx + (r * cos(angleStart)),
y: cy + (r * sin(angleStart))
};
point_t p1 = {
x: cx + (r * cos(angleStart)) - f * (r * sin(angleStart)),
y: cy + (r * sin(angleStart)) + f * (r * cos(angleStart))
};
point_t p2 = {
x: cx + (r * cos(angleEnd)) + f * (r * sin(angleEnd)),
y: cy + (r * sin(angleEnd)) - f * (r * cos(angleEnd))
};
point_t p3 = {
x: cx + (r * cos(angleEnd)),
y: cy + (r * sin(angleEnd))
};
cairo_mesh_pattern_begin_patch(gradient);
cairo_mesh_pattern_move_to(gradient, cx, cy);
cairo_mesh_pattern_line_to(gradient, p0.x, p0.y);
cairo_mesh_pattern_curve_to(gradient, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
setCornerColorRGBA(gradient, 0, from, globalAlpha);
setCornerColorRGBA(gradient, 1, from, globalAlpha);
setCornerColorRGBA(gradient, 2, to, globalAlpha);
setCornerColorRGBA(gradient, 3, to, globalAlpha);
cairo_mesh_pattern_end_patch(gradient);
}
static Gradient::ColorStop interpolateColorStop(Gradient::ColorStop from, Gradient::ColorStop to)
{
float r1, g1, b1, a1;
float r2, g2, b2, a2;
if (from.color.isExtended()) {
r1 = from.color.asExtended().red();
g1 = from.color.asExtended().green();
b1 = from.color.asExtended().blue();
a1 = from.color.asExtended().alpha();
} else
from.color.getRGBA(r1, g1, b1, a1);
if (to.color.isExtended()) {
r2 = to.color.asExtended().red();
g2 = to.color.asExtended().green();
b2 = to.color.asExtended().blue();
a2 = to.color.asExtended().alpha();
} else
to.color.getRGBA(r2, g2, b2, a2);
float offset = from.offset + (to.offset - from.offset) * 0.5f;
float r = r1 + (r2 - r1) * 0.5f;
float g = g1 + (g2 - g1) * 0.5f;
float b = b1 + (b2 - b1) * 0.5f;
float a = a1 + (a2 - a1) * 0.5f;
return Gradient::ColorStop(offset, Color(r, g, b, a));
}
static cairo_pattern_t* createConic(float xo, float yo, float r, float angleRadians,
Gradient::ColorStopVector stops, float globalAlpha)
{
cairo_pattern_t* gradient = cairo_pattern_create_mesh();
Gradient::ColorStop from, to;
if (stops.size() == 2) {
Gradient::ColorStop first = stops.at(0);
Gradient::ColorStop last = stops.at(1);
Gradient::ColorStop third = interpolateColorStop(first, last);
Gradient::ColorStop second = interpolateColorStop(first, third);
Gradient::ColorStop fourth = interpolateColorStop(third, last);
stops.insert(1, fourth);
stops.insert(1, third);
stops.insert(1, second);
}
if (stops.at(0).offset > 0)
stops.insert(0, Gradient::ColorStop(0, stops.at(0).color));
if (stops.at(stops.size() - 1).offset < 1)
stops.append(Gradient::ColorStop(1, stops.at(stops.size() - 1).color));
for (size_t i = 0; i < stops.size() - 1; i++) {
from = stops.at(i), to = stops.at(i + 1);
addConicSector(gradient, xo, yo, r, angleRadians, from, to, globalAlpha);
}
return gradient;
}
#endif
cairo_pattern_t* Gradient::createPlatformGradient(float globalAlpha)
{
cairo_pattern_t* gradient = WTF::switchOn(m_data,
[&] (const LinearData& data) -> cairo_pattern_t* {
return cairo_pattern_create_linear(data.point0.x(), data.point0.y(), data.point1.x(), data.point1.y());
},
[&] (const RadialData& data) -> cairo_pattern_t* {
return cairo_pattern_create_radial(data.point0.x(), data.point0.y(), data.startRadius, data.point1.x(), data.point1.y(), data.endRadius);
},
#if PLATFORM(GTK) || PLATFORM(WPE)
[&] (const ConicData& data) -> cairo_pattern_t* {
const float radius = 4096;
return createConic(data.point0.x(), data.point0.y(), radius, data.angleRadians, stops(), globalAlpha);
#else
[&] (const ConicData&) -> cairo_pattern_t* {
return nullptr;
#endif
}
);
if (type() != Type::Conic) {
for (const auto& stop : stops()) {
addColorStopRGBA(gradient, stop, globalAlpha);
}
}
switch (m_spreadMethod) {
case SpreadMethodPad:
cairo_pattern_set_extend(gradient, CAIRO_EXTEND_PAD);
break;
case SpreadMethodReflect:
cairo_pattern_set_extend(gradient, CAIRO_EXTEND_REFLECT);
break;
case SpreadMethodRepeat:
cairo_pattern_set_extend(gradient, CAIRO_EXTEND_REPEAT);
break;
}
cairo_matrix_t matrix = toCairoMatrix(m_gradientSpaceTransformation);
cairo_matrix_invert(&matrix);
cairo_pattern_set_matrix(gradient, &matrix);
return gradient;
}
void Gradient::fill(GraphicsContext& context, const FloatRect& rect)
{
RefPtr<cairo_pattern_t> platformGradient = adoptRef(createPlatformGradient(1.0));
if (!platformGradient)
return;
ASSERT(context.hasPlatformContext());
auto& platformContext = *context.platformContext();
Cairo::save(platformContext);
Cairo::fillRect(platformContext, rect, platformGradient.get());
Cairo::restore(platformContext);
}
}
#endif // USE(CAIRO)