mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-23 12:09:51 +00:00
2130 lines
73 KiB
Plaintext
2130 lines
73 KiB
Plaintext
#import "O2Context_AntiGrain.h"
|
|
#import <Onyx2D/O2Surface.h>
|
|
#import <Onyx2D/O2GraphicsState.h>
|
|
#import <Onyx2D/O2ClipState.h>
|
|
#import <Onyx2D/O2ClipPhase.h>
|
|
#import <Onyx2D/O2MutablePath.h>
|
|
|
|
#ifdef ANTIGRAIN_PRESENT
|
|
|
|
#include <agg_color_rgba.h>
|
|
#include <agg_span_gradient.h>
|
|
#include <agg_span_interpolator_linear.h>
|
|
#include <agg_span_allocator.h>
|
|
#include <agg_span_converter.h>
|
|
|
|
#include <agg_image_accessors.h>
|
|
#include <agg_span_image_filter_rgba.h>
|
|
#include <agg_span_image_filter_rgb.h>
|
|
|
|
#include <agg_conv_dash.h>
|
|
#include "partial_stack_blur.h"
|
|
#ifdef WINDOWS
|
|
#include <AppKit/KTFont.h>
|
|
|
|
static inline void getcpuid(int info_type, int info[4]) {
|
|
asm volatile (
|
|
"pushl %%ebx \n\t"
|
|
"cpuid \n\t"
|
|
"movl %%ebx, %1 \n\t"
|
|
"popl %%ebx \n\t"
|
|
: "=a"(info[0]), "=r"(info[1]), "=c"(info[2]), "=d"(info[3])
|
|
: "a"(info_type)
|
|
);
|
|
}
|
|
|
|
static inline bool cpuHasSSE2()
|
|
{
|
|
int cpu_info[4] = { 0 };
|
|
getcpuid(1, cpu_info);
|
|
return (cpu_info[3] & (1<<26)) != 0;
|
|
}
|
|
#else
|
|
static inline bool cpuHasSSE2
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#include <agg_pixfmt_rgba.h>
|
|
|
|
#include "o2agg_span_image_filter_rgba.h"
|
|
|
|
typedef enum {
|
|
kImageInterpolationNone,
|
|
kImageInterpolationBilinear,
|
|
kImageInterpolationBicubic,
|
|
kImageInterpolationLanczos,
|
|
kImageInterpolationRepeat,
|
|
} ImageInterpolationType;
|
|
|
|
//#define DEBUG
|
|
|
|
static const int kKFontCacheSize = 100;
|
|
|
|
static inline float fract(float f)
|
|
{
|
|
return f - truncf(f);
|
|
}
|
|
|
|
#ifdef O2AGG_GLYPH_SUPPORT
|
|
// They are shared by all the contexts
|
|
static font_engine_type *font_engine = 0;
|
|
static font_manager_type *font_manager = 0;
|
|
#endif
|
|
|
|
// Like NSLog, but works in C++ code - format is a C strings (""), not a Obj-C string (@"")
|
|
#ifdef DEBUG
|
|
static void O2Log(const char *format,...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, format); //Requires the last fixed parameter (to get the address)
|
|
NSString *f = [NSString stringWithUTF8String:format];
|
|
NSLogv(f, ap);
|
|
va_end(ap);
|
|
}
|
|
#else
|
|
static void O2Log(const char *format,...)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
// Multiply the alpha spans with a ratio - used as a transformer to render alpha for non-plain color
|
|
typedef agg::scanline_u8_am<MaskType> scanline_mask_type;
|
|
|
|
|
|
// render_scanlines_aa_solid with some added translation parameter - we've just added the "+dx, +dy" part
|
|
template<class Rasterizer, class Scanline,
|
|
class BaseRenderer, class ColorT>
|
|
void render_scanlines_aa_solid_translate(Rasterizer& ras, Scanline& sl,
|
|
BaseRenderer& ren, const ColorT& color, int dx, int dy)
|
|
{
|
|
if(ras.rewind_scanlines())
|
|
{
|
|
// Explicitly convert "color" to the BaseRenderer color type.
|
|
// For example, it can be called with color type "rgba", while
|
|
// "rgba8" is needed. Otherwise it will be implicitly
|
|
// converted in the loop many times.
|
|
//----------------------
|
|
typename BaseRenderer::color_type ren_color(color);
|
|
|
|
sl.reset(ras.min_x(), ras.max_x());
|
|
while(ras.sweep_scanline(sl))
|
|
{
|
|
//render_scanline_aa_solid(sl, ren, ren_color);
|
|
//
|
|
// This code is equivalent to the above call (copy/paste).
|
|
// It's just a "manual" optimization for old compilers,
|
|
// like Microsoft Visual C++ v6.0
|
|
//-------------------------------
|
|
int y = sl.y();
|
|
unsigned num_spans = sl.num_spans();
|
|
typename Scanline::const_iterator span = sl.begin();
|
|
|
|
for(;;)
|
|
{
|
|
int x = span->x;
|
|
if(span->len > 0)
|
|
{
|
|
ren.blend_solid_hspan(x+dx, y+dy, (unsigned)span->len,
|
|
ren_color,
|
|
span->covers);
|
|
}
|
|
else
|
|
{
|
|
ren.blend_hline(x+dx, y+dy, (unsigned)(x - span->len - 1),
|
|
ren_color,
|
|
*(span->covers));
|
|
}
|
|
if(--num_spans == 0) break;
|
|
++span;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// render_scanlines_aa with some added translation parameter - we've just added the "+dx, +dy" part
|
|
template<class Scanline, class BaseRenderer,
|
|
class SpanAllocator, class SpanGenerator>
|
|
void render_scanline_aa_translate(const Scanline& sl, BaseRenderer& ren,
|
|
SpanAllocator& alloc, SpanGenerator& span_gen, int dx, int dy)
|
|
{
|
|
int y = sl.y();
|
|
|
|
unsigned num_spans = sl.num_spans();
|
|
typename Scanline::const_iterator span = sl.begin();
|
|
for(;;)
|
|
{
|
|
int x = span->x;
|
|
int len = span->len;
|
|
const typename Scanline::cover_type* covers = span->covers;
|
|
|
|
if(len < 0) len = -len;
|
|
typename BaseRenderer::color_type* colors = alloc.allocate(len);
|
|
span_gen.generate(colors, x, y, len);
|
|
ren.blend_color_hspan(x + dx, y + dy, len, colors,
|
|
(span->len < 0) ? 0 : covers, *covers);
|
|
|
|
if(--num_spans == 0) break;
|
|
++span;
|
|
}
|
|
}
|
|
|
|
// render_scanlines_aa with some added translation parameter
|
|
template<class Rasterizer, class Scanline, class BaseRenderer,
|
|
class SpanAllocator, class SpanGenerator>
|
|
void render_scanlines_aa_translate(Rasterizer& ras, Scanline& sl, BaseRenderer& ren,
|
|
SpanAllocator& alloc, SpanGenerator& span_gen, int dx, int dy)
|
|
{
|
|
if(ras.rewind_scanlines())
|
|
{
|
|
sl.reset(ras.min_x(), ras.max_x());
|
|
span_gen.prepare();
|
|
while(ras.sweep_scanline(sl))
|
|
{
|
|
render_scanline_aa_translate(sl, ren, alloc, span_gen, dx, dy);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply some alpha value to its input spans
|
|
template<class color_type> struct span_alpha_converter
|
|
{
|
|
span_alpha_converter(float alpha, bool premultiply) : m_alpha(alpha), m_premultiply(premultiply) {};
|
|
|
|
void prepare() {}
|
|
|
|
inline void generate(color_type* span, int x, int y, unsigned len)
|
|
{
|
|
if(m_alpha < 1.) {
|
|
if (m_premultiply) {
|
|
do {
|
|
span->demultiply();
|
|
span->opacity(span->opacity()*m_alpha);
|
|
span->premultiply();
|
|
++span;
|
|
} while(--len);
|
|
} else {
|
|
do {
|
|
span->opacity(span->opacity()*m_alpha);
|
|
++span;
|
|
} while(--len);
|
|
}
|
|
}
|
|
}
|
|
private:
|
|
bool m_premultiply;
|
|
double m_alpha;
|
|
};
|
|
|
|
// Replace spans with a fixed color, keeping the original opacity - used as a transformer to render shadow with no blur, for non-plain color
|
|
class span_color_converter
|
|
{
|
|
public:
|
|
span_color_converter(o2agg::rgba8 &color, bool premultiply) : m_color(color), m_premultiply(premultiply) {};
|
|
|
|
void prepare() {}
|
|
|
|
inline void generate(o2agg::rgba8* span, int x, int y, unsigned len)
|
|
{
|
|
if (m_premultiply) {
|
|
do {
|
|
// Replace the span color, with m_color * span opacity
|
|
if (span->opacity() > 0) {
|
|
o2agg::rgba8 color = m_color;
|
|
color.opacity(color.opacity()*span->opacity());
|
|
color.premultiply();
|
|
*span = color;
|
|
}
|
|
++span;
|
|
} while(--len);
|
|
} else {
|
|
do {
|
|
// Replace the span color, with m_color * span opacity
|
|
if (span->opacity() > 0) {
|
|
o2agg::rgba8 color = m_color;
|
|
color.opacity(color.opacity()*span->opacity());
|
|
*span = color;
|
|
}
|
|
++span;
|
|
} while(--len);
|
|
}
|
|
}
|
|
private:
|
|
bool m_premultiply;
|
|
o2agg::rgba8 m_color;
|
|
};
|
|
|
|
class context_renderer
|
|
{
|
|
class context_renderer_helper_base
|
|
{
|
|
protected:
|
|
bool premultiply;
|
|
public:
|
|
virtual ~context_renderer_helper_base() { };
|
|
void setPremultiply(bool pre) { premultiply = pre; };
|
|
virtual bool isRGBA() { return false; }
|
|
virtual bool isBGRA() { return false; }
|
|
virtual bool isARGB() { return false; }
|
|
virtual bool isABGR() { return false; }
|
|
virtual void setBlendMode(int blendMode) = 0;
|
|
virtual void setAlpha(float alpha) = 0;
|
|
virtual void setShadowColor(o2agg::rgba color) = 0;
|
|
virtual void setShadowBlurRadius(float radius) = 0;
|
|
virtual void setShadowOffset(O2Size offset) = 0;
|
|
virtual void clipBox(int a, int b, int c, int d) = 0;
|
|
};
|
|
|
|
template<class blender_type> class context_renderer_helper : public context_renderer_helper_base
|
|
{
|
|
typedef o2agg::pixfmt_custom_blend_rgba<blender_type, agg::rendering_buffer> pixfmt_type;
|
|
|
|
typedef o2agg::pixfmt_bgra32_pre pixfmt_shadow_type;
|
|
|
|
typedef agg::renderer_base<pixfmt_type> renderer_base;
|
|
typedef agg::renderer_base<pixfmt_shadow_type> renderer_shadow;
|
|
|
|
renderer_base *ren_base;
|
|
pixfmt_type *pixelFormat;
|
|
|
|
// Rendering buffer to use for shadow rendering
|
|
uint8_t *pixelShadowBytes;
|
|
agg::rendering_buffer *renderingBufferShadow;
|
|
|
|
renderer_shadow *ren_shadow;
|
|
pixfmt_shadow_type *pixelFormatShadow;
|
|
|
|
float alpha;
|
|
|
|
o2agg::rgba shadowColor;
|
|
O2Size shadowOffset;
|
|
float shadowBlurRadius;
|
|
public:
|
|
context_renderer *renderer;
|
|
context_renderer_helper(agg::rendering_buffer &renderingBuffer)
|
|
{
|
|
pixelFormat=new pixfmt_type(renderingBuffer);
|
|
ren_base=new renderer_base(*pixelFormat);
|
|
|
|
pixelShadowBytes = NULL;
|
|
renderingBufferShadow = NULL;
|
|
ren_shadow = NULL;
|
|
pixelFormatShadow = NULL;
|
|
|
|
shadowColor = o2agg::rgba(0, 0, 0, 0);
|
|
|
|
alpha = 1.;
|
|
}
|
|
|
|
|
|
virtual ~context_renderer_helper()
|
|
{
|
|
delete pixelFormat;
|
|
delete ren_base;
|
|
|
|
if (ren_shadow) {
|
|
delete ren_shadow;
|
|
delete pixelFormatShadow;
|
|
delete renderingBufferShadow;
|
|
delete[] pixelShadowBytes;
|
|
}
|
|
|
|
}
|
|
|
|
void setBlendMode(int blendMode)
|
|
{
|
|
pixelFormat->comp_op(blendMode);
|
|
}
|
|
|
|
void setAlpha(float a)
|
|
{
|
|
alpha = a;
|
|
}
|
|
|
|
void setShadowColor(o2agg::rgba color)
|
|
{
|
|
shadowColor = color;
|
|
}
|
|
|
|
void setShadowBlurRadius(float radius)
|
|
{
|
|
shadowBlurRadius = radius;
|
|
}
|
|
|
|
void setShadowOffset(O2Size offset)
|
|
{
|
|
shadowOffset = offset;
|
|
}
|
|
|
|
void setUpShadow()
|
|
{
|
|
if (ren_shadow == NULL) {
|
|
// Use rgba32
|
|
int height = ren_base->ren().height();
|
|
int width = ren_base->ren().width();
|
|
int bytesPerRow = ren_base->ren().stride();
|
|
pixelShadowBytes = new uint8_t[height*bytesPerRow];
|
|
renderingBufferShadow = new agg::rendering_buffer(pixelShadowBytes,width,height,bytesPerRow);
|
|
|
|
pixelFormatShadow=new pixfmt_shadow_type(*renderingBufferShadow);
|
|
ren_shadow=new renderer_shadow(*pixelFormatShadow);
|
|
}
|
|
ren_shadow->reset_clipping(1);
|
|
ren_shadow->clip_box(ren_base->xmin(), ren_base->ymin(), ren_base->xmax(), ren_base->ymax());
|
|
|
|
}
|
|
|
|
void clipBox(int a, int b, int c, int d)
|
|
{
|
|
ren_base->reset_clipping(1);
|
|
ren_base->clip_box(a, b, c, d);
|
|
}
|
|
|
|
// Blur the (x1,y1,x2,y2) part of the shadow buffer, and copy it to the main buffer
|
|
template<class Ras, class S> void blur(S &sl, float x1, float x2, float y1, float y2)
|
|
{
|
|
float xmin = ren_base->xmin();
|
|
float ymin = ren_base->ymin();
|
|
float xmax = ren_base->xmax();
|
|
float ymax = ren_base->ymax();
|
|
if (xmax <= xmin || ymax <= ymin) {
|
|
O2Log("Nothing to do - skip shadow blur");
|
|
return;
|
|
}
|
|
|
|
// Clip the rect to render - no need to try to blur any clipped area
|
|
xmin = max(xmin, x1);
|
|
xmax = min(xmax, x2);
|
|
ymin = max(ymin, y1);
|
|
ymax = min(ymax, y2);
|
|
|
|
xmin = floorf(xmin);
|
|
ymin = floorf(ymin);
|
|
xmax = ceilf(xmax);
|
|
ymax = ceilf(ymax);
|
|
|
|
// Blur the shadows
|
|
int radius = agg::iround(shadowBlurRadius);
|
|
if (radius > 0) {
|
|
// Add the blur radius to the area to blur
|
|
xmin -= radius;
|
|
xmax += radius;
|
|
ymin -= radius;
|
|
ymax += radius;
|
|
|
|
xmin = max(xmin,0);
|
|
ymin = max(ymin,0);
|
|
O2Log("Shadow: blur shadow - area : ((%.0f,%.0f),(%.0f,%.0f))", xmin, ymin, xmax - xmin, ymax - ymin);
|
|
// Stack blur - not really a gaussian one but much faster and good enough
|
|
// Blur only the current clipped area - no need to process pixels we won't see
|
|
if (xmax > xmin && ymax > ymin) {
|
|
float r = shadowColor.r;
|
|
float g = shadowColor.g;
|
|
float b = shadowColor.b;
|
|
// This is bluring the alpha channel and fill pixels with (r,g,b) premultiplied by the blured alpha * the passed alpha parameter
|
|
partial_stack_blur_rgba32(*pixelFormatShadow, radius, xmin, xmax, ymin, ymax, r, g, b, shadowColor.a * alpha);
|
|
} else {
|
|
O2Log("Skip blurring");
|
|
}
|
|
O2Log("Shadow: done blur");
|
|
}
|
|
|
|
// Add the offset to the area to copy - the translated drawing only take the rounded translation into account
|
|
// So add the fractionary part
|
|
Ras r;
|
|
xmin += fract(shadowOffset.width);
|
|
xmax += fract(shadowOffset.width);
|
|
ymin -= fract(shadowOffset.height);
|
|
ymax -= fract(shadowOffset.height);
|
|
xmin = max(xmin,0);
|
|
ymin = max(ymin,0);
|
|
|
|
xmin = floorf(xmin);
|
|
ymin = floorf(ymin);
|
|
xmax = ceilf(xmax);
|
|
ymax = ceilf(ymax);
|
|
|
|
if (xmax <= xmin || ymax <= ymin) {
|
|
O2Log("Nothing to do - skip shadow drawing");
|
|
return;
|
|
}
|
|
|
|
r.clip_box(ren_base->xmin(), ren_base->ymin(), ren_base->xmax(), ren_base->ymax());
|
|
|
|
// Rasterize the shadow to our main renderer
|
|
agg::path_storage aggPath;
|
|
|
|
O2Log("Shadow: copying to area : ((%.0f,%.0f),(%.0f,%.0f))", xmin, ymin, xmax - xmin, ymax - ymin);
|
|
// We'll use a path that cover the current used rect, and render that path using the shadow image
|
|
aggPath.move_to(xmin, ymin);
|
|
aggPath.line_to(xmax, ymin);
|
|
aggPath.line_to(xmax, ymax);
|
|
aggPath.line_to(xmin, ymax);
|
|
aggPath.end_poly();
|
|
|
|
agg::conv_curve<agg::path_storage> curve(aggPath);
|
|
r.add_path(curve);
|
|
|
|
agg::span_allocator<typename pixfmt_shadow_type::color_type> sa;
|
|
typedef agg::image_accessor_clone<pixfmt_shadow_type> img_accessor_type;
|
|
typedef agg::span_interpolator_linear<agg::trans_affine> interpolator_type;
|
|
|
|
img_accessor_type ia(*pixelFormatShadow);
|
|
agg::trans_affine transform;
|
|
transform.translate(-fract(shadowOffset.width), fract(shadowOffset.height)); // Translate using the fract part of the shadow offset
|
|
interpolator_type interpolator(transform);
|
|
typedef agg::span_image_filter_rgba_nn<img_accessor_type, interpolator_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator);
|
|
|
|
unsigned oldBlendMode = pixelFormat->comp_op();
|
|
pixelFormat->comp_op(o2agg::comp_op_src_over); // Always use src_over when copying the shadow
|
|
agg::render_scanlines_aa(r, sl, *ren_base, sa, sg);
|
|
pixelFormat->comp_op(oldBlendMode);
|
|
}
|
|
|
|
template<class Ras, class S, class T> void render_scanlines(Ras &rasterizer, S &sl, T &type)
|
|
{
|
|
agg::render_scanlines(rasterizer, sl, type);
|
|
}
|
|
|
|
// Used to render images & shadings
|
|
template<class Ras, class S, class SA, class T> void render_scanlines_aa(Ras &rasterizer, S &sl, SA &span_allocator, T &type)
|
|
{
|
|
O2Log("%p:Drawing Image", this);
|
|
if (shadowColor.a > 0.) {
|
|
setUpShadow();
|
|
|
|
O2Log("%p:Drawing shadow Image", this);
|
|
// Clear the shadow buffer
|
|
int x = ren_base->xmin();
|
|
int y = ren_base->ymin();
|
|
int xmax = ren_base->xmax();
|
|
int ymax = ren_base->ymax();
|
|
|
|
ren_shadow->reset_clipping(true);
|
|
ren_shadow->copy_bar(x-shadowBlurRadius, y-shadowBlurRadius, xmax+shadowBlurRadius, ymax+shadowBlurRadius, o2agg::rgba::no_color());
|
|
ren_shadow->clip_box(ren_base->xmin(), ren_base->ymin(), ren_base->xmax(), ren_base->ymax());
|
|
|
|
// Draw to our shadow buffer
|
|
if (shadowBlurRadius == 0) {
|
|
// All colors are being transformed to the shadow one since we can skip the blur pass which is also applying the color
|
|
o2agg::rgba8 color = o2agg::rgba(shadowColor.r, shadowColor.g, shadowColor.b, shadowColor.a * alpha);
|
|
span_color_converter color_converter(color, premultiply);
|
|
agg::span_converter<T, span_color_converter > converter(type, color_converter);
|
|
|
|
// Render to the shadow location (rounded - we are working with pixels here) so we're sure we can properly blur the shadow
|
|
// We'll take into account the fractional part when copying the shadow to the final buffer
|
|
render_scanlines_aa_translate(rasterizer, sl, *ren_shadow, span_allocator, converter, truncf(shadowOffset.width), -truncf(shadowOffset.height));
|
|
} else {
|
|
// The bluring will actually apply the right shadow color - no need to do any transform here
|
|
render_scanlines_aa_translate(rasterizer, sl, *ren_shadow, span_allocator, type, truncf(shadowOffset.width), -truncf(shadowOffset.height));
|
|
}
|
|
|
|
// Blur the shadow buffer and copy it back
|
|
|
|
// Get the used part of the rasterizer - we don't need to blur a bigger area than that
|
|
float x1 = rasterizer.min_x();
|
|
float x2 = rasterizer.max_x();
|
|
float y1 = rasterizer.min_y();
|
|
float y2 = rasterizer.max_y();
|
|
// Add the translation done during the rendering
|
|
x1 += truncf(shadowOffset.width);
|
|
x2 += truncf(shadowOffset.width);
|
|
y1 -= truncf(shadowOffset.height);
|
|
y2 -= truncf(shadowOffset.height);
|
|
|
|
// Blur the shadow buffer
|
|
blur<Ras>(sl, x1, x2, y1, y2);
|
|
|
|
O2Log("%p:Done Drawing shadow Image", this);
|
|
}
|
|
|
|
// And finally do the "normal" drawing
|
|
if (alpha >= 1) {
|
|
// No need for an alpha converter
|
|
agg::render_scanlines_aa(rasterizer, sl, *ren_base, span_allocator, type);
|
|
} else {
|
|
span_alpha_converter<o2agg::rgba8> alpha_converter(alpha, premultiply);
|
|
agg::span_converter<T, span_alpha_converter<o2agg::rgba8> > converter(type, alpha_converter);
|
|
agg::render_scanlines_aa(rasterizer, sl, *ren_base, span_allocator, converter);
|
|
}
|
|
O2Log("%p:Done Drawing Image", this);
|
|
}
|
|
|
|
// Used to render path
|
|
template<class Ras, class S, class T> void render_scanlines_aa_solid(Ras &rasterizer, S &sl, T &type)
|
|
{
|
|
if (shadowColor.a > 0.) {
|
|
setUpShadow();
|
|
|
|
int x = ren_base->xmin();
|
|
int y = ren_base->ymin();
|
|
int xmax = ren_base->xmax();
|
|
int ymax = ren_base->ymax();
|
|
|
|
ren_shadow->reset_clipping(true);
|
|
ren_shadow->copy_bar(x-shadowBlurRadius, y-shadowBlurRadius, xmax+shadowBlurRadius, ymax+shadowBlurRadius, o2agg::rgba::no_color());
|
|
ren_shadow->clip_box(ren_base->xmin(), ren_base->ymin(), ren_base->xmax(), ren_base->ymax());
|
|
|
|
// TODO : render to our scratch mask buffer, then blur should just draw a shadow colored (* alpha) rect using that mask
|
|
///////////////////
|
|
|
|
// Draw using our shadow rendererer using our shadow color (and global alpha)
|
|
T color = o2agg::rgba(shadowColor.r, shadowColor.g, shadowColor.b, shadowColor.a * type.opacity());
|
|
color.premultiply();
|
|
|
|
// Render to the shadow location (rounded - we are working with pixels here) so we're sure we can properly blur the shadow
|
|
// We'll take into account the fractional part when copying the shadow to the final buffer
|
|
render_scanlines_aa_solid_translate(rasterizer, sl, *ren_shadow, color, truncf(shadowOffset.width), -truncf(shadowOffset.height));
|
|
|
|
// Get the used part of the rasterizer - we don't need to blur a bigger area than that
|
|
float x1 = rasterizer.min_x();
|
|
float x2 = rasterizer.max_x();
|
|
float y1 = rasterizer.min_y();
|
|
float y2 = rasterizer.max_y();
|
|
// Add the translation done during the rendering
|
|
x1 += truncf(shadowOffset.width);
|
|
x2 += truncf(shadowOffset.width);
|
|
y1 -= truncf(shadowOffset.height);
|
|
y2 -= truncf(shadowOffset.height);
|
|
|
|
// Blur the shadow buffer
|
|
blur<Ras>(sl, x1, x2, y1, y2);
|
|
}
|
|
// And finally do the "normal" drawing
|
|
T color = type;
|
|
color.opacity(color.opacity() * alpha );
|
|
if (premultiply)
|
|
color.premultiply();
|
|
if (color.opacity() >= 1. && pixelFormat->comp_op() == o2agg::comp_op_src_over) {
|
|
// We'll use copy instead of source over when rendering a opaque path
|
|
pixelFormat->comp_op(o2agg::comp_op_src);
|
|
}
|
|
agg::render_scanlines_aa_solid(rasterizer, sl, *ren_base, color);
|
|
}
|
|
|
|
bool isRGBA() { return NO; };
|
|
bool isBGRA() { return NO; };
|
|
bool isARGB() { return NO; };
|
|
bool isABGR() { return NO; };
|
|
};
|
|
|
|
|
|
context_renderer_helper_base* helper;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba<o2agg::rgba8, agg::order_rgba> > rgba_helper;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba<o2agg::rgba8, agg::order_bgra> > bgra_helper;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba<o2agg::rgba8, agg::order_argb> > argb_helper;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba<o2agg::rgba8, agg::order_abgr> > abgr_helper;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba_pre<o2agg::rgba8, agg::order_rgba> > rgba_helper_pre;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba_pre<o2agg::rgba8, agg::order_bgra> > bgra_helper_pre;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba_pre<o2agg::rgba8, agg::order_argb> > argb_helper_pre;
|
|
typedef class context_renderer_helper<o2agg::comp_op_adaptor_rgba_pre<o2agg::rgba8, agg::order_abgr> > abgr_helper_pre;
|
|
|
|
public:
|
|
bool premultiplied;
|
|
O2Context_AntiGrain *context;
|
|
virtual ~context_renderer()
|
|
{
|
|
delete helper;
|
|
}
|
|
template<class Order> void init(agg::rendering_buffer &renderingBuffer)
|
|
{
|
|
typedef o2agg::comp_op_adaptor_rgba<o2agg::rgba8, Order> blender_type;
|
|
premultiplied = false;
|
|
helper = new context_renderer_helper<blender_type>(renderingBuffer);
|
|
helper->setPremultiply(premultiplied);
|
|
}
|
|
template<class Order> void init_pre(agg::rendering_buffer &renderingBuffer)
|
|
{
|
|
typedef o2agg::comp_op_adaptor_rgba_pre<o2agg::rgba8, Order> blender_type;
|
|
premultiplied = true;
|
|
helper = new context_renderer_helper<blender_type>(renderingBuffer);
|
|
helper->setPremultiply(premultiplied);
|
|
}
|
|
|
|
// Not sure how to better write that since we can't have virtual template methods and we don't know some type
|
|
// at compile time (like pixel format)
|
|
// But there must be some better way...
|
|
template<class Ras, class S, class T> void render_scanlines(Ras &rasterizer, S &sl, T &type)
|
|
{
|
|
if (premultiplied) {
|
|
if (helper->isRGBA()) {
|
|
((rgba_helper_pre *)helper)->render_scanlines(rasterizer, sl, type);
|
|
} else if (helper->isABGR()) {
|
|
((abgr_helper_pre *)helper)->render_scanlines(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper_pre *)helper)->render_scanlines(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper_pre *)helper)->render_scanlines(rasterizer, sl, type);
|
|
}
|
|
} else {
|
|
if (helper->isRGBA()) {
|
|
((rgba_helper *)helper)->render_scanlines(rasterizer, sl, type);
|
|
} else if (helper->isABGR()) {
|
|
((abgr_helper *)helper)->render_scanlines(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper *)helper)->render_scanlines(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper *)helper)->render_scanlines(rasterizer, sl, type);
|
|
}
|
|
}
|
|
}
|
|
template<class Ras, class S, class SA, class T> void render_scanlines_aa(Ras &rasterizer, S &sl, SA &span_allocator, T &type)
|
|
{
|
|
if (premultiplied) {
|
|
if (helper->isRGBA()) {
|
|
((rgba_helper_pre *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
} else if (helper->isABGR()) {
|
|
((abgr_helper_pre *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper_pre *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper_pre *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
}
|
|
} else {
|
|
if (helper->isRGBA()) {
|
|
((rgba_helper *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
} else if (helper->isABGR()) {
|
|
((abgr_helper *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper *)helper)->render_scanlines_aa(rasterizer, sl, span_allocator, type);
|
|
}
|
|
}
|
|
}
|
|
template<class Ras, class S, class T> void render_scanlines_aa_solid(Ras &rasterizer, S &sl, T &type)
|
|
{
|
|
if (premultiplied) {
|
|
if (helper->isRGBA()) {
|
|
((rgba_helper_pre *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
} else if (helper->isABGR()) {
|
|
((abgr_helper_pre *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper_pre *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper_pre *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
}
|
|
} else {
|
|
if (helper->isRGBA()) {
|
|
((rgba_helper *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
} else if (helper->isABGR()) {
|
|
((abgr_helper *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
} else if (helper->isBGRA()) {
|
|
((bgra_helper *)helper)->render_scanlines_aa_solid(rasterizer, sl, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
void setBlendMode(int blendMode)
|
|
{
|
|
helper->setBlendMode(blendMode);
|
|
}
|
|
|
|
void setShadowColor(o2agg::rgba color)
|
|
{
|
|
helper->setShadowColor(color);
|
|
}
|
|
|
|
void setShadowOffset(O2Size shadowOffset)
|
|
{
|
|
helper->setShadowOffset(shadowOffset);
|
|
}
|
|
|
|
void setShadowBlurRadius(float radius)
|
|
{
|
|
helper->setShadowBlurRadius(radius);
|
|
}
|
|
|
|
void setAlpha(float alpha)
|
|
{
|
|
helper->setAlpha(alpha);
|
|
}
|
|
|
|
void clipBox(int a, int b, int c, int d)
|
|
{
|
|
helper->clipBox(a, b, c, d);
|
|
}
|
|
};
|
|
|
|
// Make the right type test to return true when needed
|
|
template<> bool context_renderer::rgba_helper::isRGBA() { return true; };
|
|
template<> bool context_renderer::bgra_helper::isBGRA() { return true; };
|
|
template<> bool context_renderer::argb_helper::isARGB() { return true; };
|
|
template<> bool context_renderer::abgr_helper::isABGR() { return true; };
|
|
template<> bool context_renderer::rgba_helper_pre::isRGBA() { return true; };
|
|
template<> bool context_renderer::bgra_helper_pre::isBGRA() { return true; };
|
|
template<> bool context_renderer::argb_helper_pre::isARGB() { return true; };
|
|
template<> bool context_renderer::abgr_helper_pre::isABGR() { return true; };
|
|
|
|
|
|
class gradient_evaluator
|
|
{
|
|
public:
|
|
gradient_evaluator(O2FunctionRef function, bool premultiply = true, unsigned size = 4096) :
|
|
m_size(size) {
|
|
// Precalculate our colors
|
|
m_colors_lut = new o2agg::rgba[size];
|
|
float invSize = 1./size;
|
|
for (int i = 0; i < size; ++i) {
|
|
O2Float result[4] = { 1 };
|
|
O2FunctionEvaluate(function, i*invSize, result);
|
|
o2agg::rgba color;
|
|
color.r = result[0];
|
|
color.g = result[1];
|
|
color.b = result[2];
|
|
color.a = result[3];
|
|
if (premultiply)
|
|
color.premultiply();
|
|
m_colors_lut[i] = color;
|
|
}
|
|
}
|
|
~gradient_evaluator() { delete[] m_colors_lut; };
|
|
inline int size() const { return m_size; }
|
|
inline o2agg::rgba operator [] (unsigned v) const
|
|
{
|
|
return m_colors_lut[v];
|
|
}
|
|
private:
|
|
o2agg::rgba *m_colors_lut;
|
|
int m_size;
|
|
};
|
|
|
|
|
|
template <class SpanAllocator, class SpanGen> void render_scanlines_aa(O2Context_AntiGrain *self, SpanAllocator &sa, SpanGen &sg)
|
|
{
|
|
if (self->useMask) {
|
|
scanline_mask_type sl(*[self currentMask]);
|
|
self->renderer->render_scanlines_aa(*self->rasterizer, sl, sa, sg);
|
|
} else {
|
|
self->renderer->render_scanlines_aa(*self->rasterizer, *self->scanline_p8, sa, sg);
|
|
}
|
|
}
|
|
|
|
template <class Type> void render_scanlines_aa_solid(O2Context_AntiGrain *self, Type &type, BOOL packed = NO)
|
|
{
|
|
if (self->useMask) {
|
|
scanline_mask_type sl(*[self currentMask]);
|
|
self->renderer->render_scanlines_aa_solid(*self->rasterizer,sl,type);
|
|
} else {
|
|
if (packed) {
|
|
self->renderer->render_scanlines_aa_solid(*self->rasterizer,*self->scanline_p8,type);
|
|
} else {
|
|
self->renderer->render_scanlines_aa_solid(*self->rasterizer,*self->scanline_u8,type);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class gradient_func_type> void O2AGGContextDrawShading(O2Context_AntiGrain *self, O2Shading* shading, float x, float y, float width, float height)
|
|
{
|
|
typedef o2agg::pixfmt_bgra32_pre pixfmt_type; // That should be the same pixel type as the context ?
|
|
typedef pixfmt_type::color_type color_type;
|
|
typedef gradient_evaluator color_func_type;
|
|
typedef agg::span_interpolator_linear<> interpolator_type;
|
|
typedef agg::span_allocator<color_type> span_allocator_type;
|
|
typedef agg::span_gradient<color_type, interpolator_type, gradient_func_type, color_func_type> span_gradient_type;
|
|
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
O2ClipState *clipState=O2GStateClipState(gState);
|
|
O2AffineTransform deviceTransform=gState->_deviceSpaceTransform;
|
|
agg::trans_affine deviceMatrix(deviceTransform.a,deviceTransform.b,deviceTransform.c,deviceTransform.d,deviceTransform.tx,deviceTransform.ty);
|
|
|
|
O2Point deviceStart=O2PointApplyAffineTransform([shading startPoint],deviceTransform);
|
|
O2Point deviceEnd=O2PointApplyAffineTransform([shading endPoint],deviceTransform);
|
|
|
|
double x1=deviceStart.x;
|
|
double y1=deviceStart.y;
|
|
double x2=deviceEnd.x;
|
|
double y2=deviceEnd.y;
|
|
|
|
float startValue = 0;
|
|
float endValue = 0;
|
|
|
|
if ([shading isAxial]) {
|
|
startValue = 0;
|
|
endValue = agg::calc_distance(x1,y1,x2,y2);
|
|
} else {
|
|
double scale = sqrt((deviceTransform.a * deviceTransform.a) + (deviceTransform.c * deviceTransform.c));
|
|
startValue = scale*[shading startRadius];
|
|
endValue = scale*[shading endRadius];
|
|
}
|
|
int gradientSize = ceilf(endValue - startValue);
|
|
if (gradientSize < 1) {
|
|
gradientSize = 1;
|
|
}
|
|
|
|
color_func_type color_func([shading function], [self isPremultiplied], gradientSize);
|
|
|
|
agg::trans_affine gradient_mtx;
|
|
gradient_mtx.reset();
|
|
double angle = atan2(y2-y1, x2-x1);
|
|
gradient_mtx *= agg::trans_affine_rotation(angle);
|
|
gradient_mtx *= agg::trans_affine_translation(x1, y1);
|
|
gradient_mtx.invert();
|
|
|
|
interpolator_type span_interpolator(gradient_mtx);
|
|
span_allocator_type span_allocator;
|
|
gradient_func_type gradient_func;
|
|
span_gradient_type span_gradient(span_interpolator, gradient_func, color_func, startValue, endValue);
|
|
|
|
// The rasterizing/scanline stuff
|
|
//----------------
|
|
agg::path_storage aggPath;
|
|
|
|
// We'll use a path that cover the current clipped rect
|
|
aggPath.move_to(x, y);
|
|
aggPath.line_to(x+width,y);
|
|
aggPath.line_to(x+width,y+height);
|
|
aggPath.line_to(x,y+height);
|
|
aggPath.end_poly();
|
|
|
|
agg::conv_curve<agg::path_storage> curve(aggPath);
|
|
curve.approximation_scale(deviceMatrix.scale());
|
|
|
|
self->rasterizer->add_path(curve);
|
|
|
|
render_scanlines_aa(self, span_allocator, span_gradient);
|
|
}
|
|
|
|
template <class pixfmt> void O2AGGContextDrawImage(O2Context_AntiGrain *self, agg::rendering_buffer &imageBuffer, const agg::trans_affine &transform, ImageInterpolationType interpolationType)
|
|
{
|
|
BOOL resample = NO;
|
|
if (interpolationType != kImageInterpolationNone) {
|
|
// Enable resampling if we have some big downscaling
|
|
double x, y;
|
|
transform.scaling_abs(&x, &y);
|
|
resample = x > 1.125 || y > 1.125;
|
|
}
|
|
|
|
agg::span_allocator<typename pixfmt::color_type> sa;
|
|
pixfmt img_pixf(imageBuffer);
|
|
typedef agg::image_accessor_clone<pixfmt> img_accessor_type;
|
|
img_accessor_type ia(img_pixf);
|
|
|
|
typedef agg::span_interpolator_linear<agg::trans_affine> interpolator_type;
|
|
interpolator_type interpolator(transform);
|
|
|
|
if (resample == NO) {
|
|
// Use a filter according to the wanted interpolation quality - no resampling needed
|
|
switch (interpolationType) {
|
|
case kImageInterpolationNone: {
|
|
typedef agg::span_image_filter_rgba_nn<img_accessor_type, interpolator_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
default:
|
|
case kImageInterpolationBilinear: {
|
|
typedef agg::span_image_filter_rgba_bilinear<img_accessor_type, interpolator_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
case kImageInterpolationBicubic: {
|
|
agg::image_filter_bicubic filter_kernel;
|
|
agg::image_filter_lut filter(filter_kernel, true);
|
|
|
|
typedef agg::span_image_filter_rgba<img_accessor_type, interpolator_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator, filter);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
#if 0
|
|
//unused
|
|
case kImageInterpolationLanczos: {
|
|
agg::image_filter_lanczos filter_kernel(2.f);
|
|
agg::image_filter_lut filter(filter_kernel, true);
|
|
|
|
typedef agg::span_image_filter_rgba<img_accessor_type, interpolator_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator, filter);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
#endif
|
|
case kImageInterpolationRepeat: {
|
|
typedef agg::image_accessor_wrap<pixfmt, agg::wrap_mode_repeat, agg::wrap_mode_repeat> img_accessor_type;
|
|
img_accessor_type ia(img_pixf);
|
|
|
|
typedef agg::span_image_filter_rgba_bilinear<img_accessor_type, interpolator_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
// Use a filter according to the wanted interpolation quality - use resampling
|
|
switch (interpolationType) {
|
|
default:
|
|
case kImageInterpolationBilinear: {
|
|
agg::image_filter_bilinear filter_kernel;
|
|
agg::image_filter_lut filter(filter_kernel, true);
|
|
|
|
typedef o2agg::span_image_resample_rgba_affine<img_accessor_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator, filter);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
case kImageInterpolationBicubic: {
|
|
agg::image_filter_bicubic filter_kernel;
|
|
agg::image_filter_lut filter(filter_kernel, true);
|
|
|
|
typedef o2agg::span_image_resample_rgba_affine<img_accessor_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator, filter);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
#if 0
|
|
//unused
|
|
case kImageInterpolationLanczos: {
|
|
agg::image_filter_lanczos filter_kernel(2.f);
|
|
agg::image_filter_lut filter(filter_kernel, true);
|
|
|
|
typedef o2agg::span_image_resample_rgba_affine<img_accessor_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator, filter);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
#endif
|
|
case kImageInterpolationRepeat: {
|
|
typedef agg::image_accessor_wrap<pixfmt, agg::wrap_mode_repeat, agg::wrap_mode_repeat> img_accessor_type;
|
|
img_accessor_type ia(img_pixf);
|
|
|
|
agg::image_filter_bilinear filter_kernel;
|
|
agg::image_filter_lut filter(filter_kernel, true);
|
|
// !!! don't use the o2agg version with a "repeat" accessor - it seems some y wrapping is missing in the optimized
|
|
// resampling code - so use the slower plain AGG version for now
|
|
typedef agg::span_image_resample_rgba_affine<img_accessor_type> span_gen_type;
|
|
span_gen_type sg(ia, interpolator, filter);
|
|
|
|
render_scanlines_aa(self, sa, sg);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class StrokeType> void O2AGGContextSetStroke(O2Context_AntiGrain *self, StrokeType &stroke, const agg::trans_affine &deviceMatrix)
|
|
{
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
|
|
stroke.approximation_scale(deviceMatrix.scale());
|
|
|
|
switch(gState->_lineJoin){
|
|
case kO2LineJoinMiter:
|
|
stroke.line_join(agg::miter_join);
|
|
break;
|
|
|
|
case kO2LineJoinRound:
|
|
stroke.line_join(agg::round_join);
|
|
break;
|
|
|
|
case kO2LineJoinBevel:
|
|
stroke.line_join(agg::bevel_join);
|
|
break;
|
|
|
|
}
|
|
|
|
switch(gState->_lineCap){
|
|
case kO2LineCapButt:
|
|
stroke.line_cap(agg::butt_cap);
|
|
break;
|
|
|
|
case kO2LineCapRound:
|
|
stroke.line_cap(agg::round_cap);
|
|
break;
|
|
|
|
case kO2LineCapSquare:
|
|
stroke.line_cap(agg::square_cap);
|
|
break;
|
|
}
|
|
|
|
stroke.width(gState->_lineWidth);
|
|
}
|
|
|
|
template <class StrokeType> void O2AGGContextStrokePath(O2Context_AntiGrain *self, StrokeType &stroke, o2agg::rgba color, const agg::trans_affine &deviceMatrix)
|
|
{
|
|
agg::conv_transform<StrokeType, agg::trans_affine> trans(stroke, deviceMatrix);
|
|
self->rasterizer->add_path(trans);
|
|
self->rasterizer->filling_rule(agg::fill_non_zero);
|
|
|
|
render_scanlines_aa_solid(self, color, NO);
|
|
}
|
|
|
|
template <class StrokeType> void O2AGGStrokeToO2Path(O2Context_AntiGrain *self, StrokeType &stroke)
|
|
{
|
|
double x,y;
|
|
int type;
|
|
O2ContextBeginPath(self);
|
|
while ((type = stroke.vertex(&x,&y)) != agg::path_cmd_stop) {
|
|
switch (type & agg::path_cmd_mask) {
|
|
case agg::path_cmd_move_to: {
|
|
O2ContextMoveToPoint(self, x, y);
|
|
}
|
|
break;
|
|
case agg::path_cmd_line_to: {
|
|
O2ContextAddLineToPoint(self, x, y);
|
|
}
|
|
|
|
break;
|
|
case agg::path_cmd_curve3: {
|
|
// x, y are ctrl_x, ctrl_y
|
|
double to_x, to_y;
|
|
stroke.vertex(&to_x,&to_y);
|
|
O2ContextAddQuadCurveToPoint(self, x, y, to_x, to_y);
|
|
}
|
|
|
|
break;
|
|
case agg::path_cmd_curve4: {
|
|
// x, y are ctrl1_x, ctrl1_y
|
|
double ctrl2_x, ctrl2_y;
|
|
stroke.vertex(&ctrl2_x,&ctrl2_y);
|
|
double to_x, to_y;
|
|
stroke.vertex(&to_x,&to_y);
|
|
O2ContextAddCurveToPoint(self, x, y, ctrl2_x, ctrl2_y, to_x, to_y);
|
|
}
|
|
break;
|
|
case agg::path_cmd_end_poly: {
|
|
if (type & agg::path_flags_close) {
|
|
O2ContextClosePath(self);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
@interface O2AGGGState : O2GState
|
|
{
|
|
float alpha;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation O2Context_AntiGrain
|
|
|
|
#ifdef ANTIGRAIN_PRESENT
|
|
// If AntiGrain is not present it will just be a non-overriding subclass of the builtin context, so no problems
|
|
|
|
static void O2AGGContextFillPathWithRule(O2Context_AntiGrain *self,o2agg::rgba color, const agg::trans_affine &deviceMatrix,agg::filling_rule_e fillingRule) {
|
|
agg::conv_curve<agg::path_storage> curve(*(self->path));
|
|
agg::conv_transform<agg::conv_curve<agg::path_storage>, agg::trans_affine> trans(curve, deviceMatrix);
|
|
|
|
curve.approximation_scale(deviceMatrix.scale());
|
|
|
|
self->rasterizer->add_path(trans);
|
|
self->rasterizer->filling_rule(fillingRule);
|
|
|
|
// Use the packed scanline - better for filling solid path
|
|
render_scanlines_aa_solid(self,color,YES);
|
|
}
|
|
|
|
static void O2AGGContextStrokePath(O2Context_AntiGrain *self,o2agg::rgba color, const agg::trans_affine &deviceMatrix) {
|
|
agg::conv_curve<agg::path_storage> curve(*(self->path));
|
|
curve.approximation_scale(deviceMatrix.scale());
|
|
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
if (gState->_dashLengthsCount > 1) {
|
|
agg::conv_dash<agg::conv_curve<agg::path_storage> > dash(curve);
|
|
agg::conv_stroke<agg::conv_dash<agg::conv_curve<agg::path_storage> > > stroke(dash);
|
|
dash.dash_start(gState->_dashPhase);
|
|
for (int i = 0; i < gState->_dashLengthsCount; i += 2) {
|
|
if (i == gState->_dashLengthsCount - 1) {
|
|
// Last dash without an length for the next space
|
|
dash.add_dash(gState->_dashLengths[i], 0);
|
|
} else {
|
|
dash.add_dash(gState->_dashLengths[i], gState->_dashLengths[i+1]);
|
|
}
|
|
}
|
|
O2AGGContextSetStroke<typeof(stroke)>(self,stroke,deviceMatrix);
|
|
O2AGGContextStrokePath<typeof(stroke)>(self,stroke,color,deviceMatrix);
|
|
} else {
|
|
// Just stroke
|
|
agg::conv_stroke<agg::conv_curve<agg::path_storage> > stroke(curve);
|
|
O2AGGContextSetStroke<typeof(stroke)>(self,stroke,deviceMatrix);
|
|
O2AGGContextStrokePath<typeof(stroke)>(self,stroke,color,deviceMatrix);
|
|
}
|
|
}
|
|
|
|
static void O2AGGReplaceStrokedPath(O2Context_AntiGrain *self, const agg::trans_affine &deviceMatrix) {
|
|
agg::conv_curve<agg::path_storage> curve(*(self->path));
|
|
curve.approximation_scale(deviceMatrix.scale());
|
|
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
if (gState->_dashLengthsCount > 1) {
|
|
agg::conv_dash<agg::conv_curve<agg::path_storage> > dash(curve);
|
|
agg::conv_stroke<agg::conv_dash<agg::conv_curve<agg::path_storage> > > stroke(dash);
|
|
dash.dash_start(gState->_dashPhase);
|
|
for (int i = 0; i < gState->_dashLengthsCount; i += 2) {
|
|
if (i == gState->_dashLengthsCount - 1) {
|
|
// Last dash without an length for the next space
|
|
dash.add_dash(gState->_dashLengths[i], 0);
|
|
} else {
|
|
dash.add_dash(gState->_dashLengths[i], gState->_dashLengths[i+1]);
|
|
}
|
|
}
|
|
O2AGGContextSetStroke<typeof(stroke)>(self,stroke,deviceMatrix);
|
|
O2AGGStrokeToO2Path<typeof(stroke)>(self,stroke);
|
|
|
|
} else {
|
|
// Just stroke
|
|
agg::conv_stroke<agg::conv_curve<agg::path_storage> > stroke(curve);
|
|
O2AGGContextSetStroke<typeof(stroke)>(self,stroke,deviceMatrix);
|
|
O2AGGStrokeToO2Path<typeof(stroke)>(self,stroke);
|
|
}
|
|
}
|
|
|
|
|
|
/* Transfer path from Onyx2D to AGG. Not a very expensive operation, tessellation, stroking and rasterization are the expensive pieces
|
|
*/
|
|
static void buildAGGPathFromO2PathAndTransform(agg::path_storage *aggPath,O2PathRef path, const O2AffineTransform &xform){
|
|
const unsigned char *elements=O2PathElements(path);
|
|
const O2Point *points=O2PathPoints(path);
|
|
unsigned i,numberOfElements=O2PathNumberOfElements(path),pointIndex;
|
|
|
|
pointIndex=0;
|
|
|
|
aggPath->remove_all();
|
|
|
|
for(i=0;i<numberOfElements;i++){
|
|
|
|
switch(elements[i]){
|
|
|
|
case kO2PathElementMoveToPoint:{
|
|
O2Point point=O2PointApplyAffineTransform(points[pointIndex++],xform);
|
|
|
|
aggPath->move_to(point.x,point.y);
|
|
}
|
|
break;
|
|
|
|
case kO2PathElementAddLineToPoint:{
|
|
O2Point point=O2PointApplyAffineTransform(points[pointIndex++],xform);
|
|
|
|
aggPath->line_to(point.x,point.y);
|
|
}
|
|
break;
|
|
|
|
case kO2PathElementAddCurveToPoint:{
|
|
O2Point cp1=O2PointApplyAffineTransform(points[pointIndex++],xform);
|
|
O2Point cp2=O2PointApplyAffineTransform(points[pointIndex++],xform);
|
|
O2Point end=O2PointApplyAffineTransform(points[pointIndex++],xform);
|
|
|
|
aggPath->curve4(cp1.x,cp1.y,cp2.x,cp2.y,end.x,end.y);
|
|
}
|
|
break;
|
|
|
|
case kO2PathElementAddQuadCurveToPoint:{
|
|
O2Point cp1=O2PointApplyAffineTransform(points[pointIndex++],xform);
|
|
O2Point cp2=O2PointApplyAffineTransform(points[pointIndex++],xform);
|
|
|
|
aggPath->curve3(cp1.x,cp1.y,cp2.x,cp2.y);
|
|
}
|
|
break;
|
|
|
|
case kO2PathElementCloseSubpath:
|
|
aggPath->end_poly();
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void transferPath(O2Context_AntiGrain *self,O2PathRef path, const O2AffineTransform &xform) {
|
|
buildAGGPathFromO2PathAndTransform(self->path, path, xform);
|
|
}
|
|
|
|
#ifdef O2AGG_GLYPH_SUPPORT
|
|
|
|
- (KTFont*)_cachedKTFontWithFont:(O2Font *)font name:(NSString *)name size:(float)size
|
|
{
|
|
static NSMutableDictionary *kfontCache = nil;
|
|
static NSMutableArray *kfontLRU = nil;
|
|
if (kfontCache == nil) {
|
|
kfontCache = [[NSMutableDictionary alloc] initWithCapacity:kKFontCacheSize];
|
|
}
|
|
if (kfontLRU == nil) {
|
|
kfontLRU = [[NSMutableArray alloc] initWithCapacity:kKFontCacheSize];
|
|
}
|
|
NSString *key=[NSString stringWithFormat:@"%@-%f", name, size];
|
|
KTFont *kfont = [kfontCache objectForKey:key];
|
|
if (kfont) {
|
|
[kfontLRU removeObject:key];
|
|
}
|
|
// Move the font to the top of our LRU list
|
|
[kfontLRU insertObject:key atIndex:0];
|
|
if (kfont == nil) {
|
|
kfont = [[[KTFont alloc] initWithFont:font size:size] autorelease];
|
|
[kfontCache setObject:kfont forKey:key];
|
|
// Remove the LRU if we reach the max size
|
|
if ([kfontLRU count] > kKFontCacheSize) {
|
|
id lastKey = [kfontLRU lastObject];
|
|
[kfontCache removeObjectForKey:lastKey];
|
|
[kfontLRU removeLastObject];
|
|
}
|
|
}
|
|
return kfont;
|
|
}
|
|
|
|
unsigned O2AGGContextShowGlyphs(O2Context_AntiGrain *self, const O2Glyph *glyphs, const O2Size *advances, unsigned count)
|
|
{
|
|
unsigned num_glyphs = 0;
|
|
|
|
// All context use the same font manager - protect acceses
|
|
@synchronized([self class]) {
|
|
if (font_manager == 0) {
|
|
font_engine = new font_engine_type(::GetDC(NULL));
|
|
font_manager = new font_manager_type(*font_engine);
|
|
}
|
|
|
|
// Pipeline to process the vectors glyph paths (curves)
|
|
typedef agg::conv_curve<font_manager_type::path_adaptor_type> conv_curve_type;
|
|
conv_curve_type curves(font_manager->path_adaptor());
|
|
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
|
|
O2Font *font=O2GStateFont(gState);
|
|
NSString *fontName = [(NSString *)O2FontCopyFullName(font) autorelease];
|
|
|
|
float pointSize = O2GStatePointSize(gState);
|
|
O2AffineTransform Trm=self->_textMatrix;
|
|
|
|
O2AffineTransform deviceTransform=gState->_deviceSpaceTransform;
|
|
agg::trans_affine deviceMatrix(deviceTransform.a,deviceTransform.b,deviceTransform.c,deviceTransform.d,deviceTransform.tx,deviceTransform.ty);
|
|
// Round the drawing start point - that helps at killing blury text
|
|
Trm.tx = roundf(Trm.tx);
|
|
Trm.ty = roundf(Trm.ty);
|
|
agg::trans_affine trmMatrix(Trm.a,Trm.b,Trm.c,Trm.d,Trm.tx,Trm.ty);
|
|
|
|
agg::trans_affine transformMatrix = deviceMatrix;
|
|
transformMatrix.premultiply(trmMatrix);
|
|
|
|
O2ColorRef fillColor=O2ColorConvertToDeviceRGB(gState->_fillColor);
|
|
const float *fillComps=O2ColorGetComponents(fillColor);
|
|
o2agg::rgba aggFillColor(fillComps[0],fillComps[1],fillComps[2],fillComps[3]);
|
|
O2ColorRelease(fillColor);
|
|
|
|
self->rasterizer->filling_rule(agg::fill_non_zero);
|
|
|
|
font_engine->hinting(false); // For some reason, it looks better without hinting...
|
|
font_engine->height((int)pointSize);
|
|
font_engine->width(0.); // Automatic width
|
|
font_engine->flip_y(false);
|
|
|
|
self->rasterizer->reset();
|
|
|
|
if(font_engine->create_font([fontName UTF8String], agg::glyph_ren_outline))
|
|
{
|
|
agg::conv_transform<conv_curve_type, agg::trans_affine> trans(curves, transformMatrix);
|
|
|
|
CGSize defaultAdvances[count];
|
|
if(advances == NULL) {
|
|
// Using a KTFont because we need to get the advancements the same way the layout manager is
|
|
// getting them.
|
|
// Of course, it would be better if the layout manager could give us the advances it wants us to use...
|
|
KTFont *kfont = [self _cachedKTFontWithFont:font name:fontName size:pointSize];
|
|
[kfont getAdvancements:defaultAdvances forGlyphs:glyphs count:count];
|
|
for(int i=0;i<count;i++){
|
|
defaultAdvances[i].width+=gState->_characterSpacing;
|
|
}
|
|
advances = defaultAdvances;
|
|
}
|
|
double x = 0;
|
|
double y = 0;
|
|
|
|
const O2Glyph* p = glyphs;
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
const agg::glyph_cache* glyph = font_manager->glyph(*p);
|
|
if(glyph)
|
|
{
|
|
font_manager->init_embedded_adaptors(glyph, x, y);
|
|
switch(glyph->data_type)
|
|
{
|
|
case agg::glyph_data_outline: {
|
|
self->rasterizer->add_path(trans);
|
|
break;
|
|
}
|
|
default:
|
|
// No data to process
|
|
break;
|
|
}
|
|
|
|
// increment pen position
|
|
x += advances[i].width;
|
|
y += advances[i].height;
|
|
|
|
++num_glyphs;
|
|
}
|
|
++p;
|
|
}
|
|
|
|
render_scanlines_aa_solid(self,aggFillColor);
|
|
O2ContextConcatAdvancesToTextMatrix(self,advances,num_glyphs);
|
|
}
|
|
}
|
|
return num_glyphs;
|
|
}
|
|
#endif
|
|
|
|
-initWithSurface:(O2Surface *)surface flipped:(BOOL)flipped {
|
|
[super initWithSurface:surface flipped:flipped];
|
|
|
|
if (o2agg::SSE2checked == false) {
|
|
o2agg::SSE2checked = true;
|
|
o2agg::hasSSE2 = cpuHasSSE2();
|
|
}
|
|
|
|
int bytesPerRow = O2SurfaceGetBytesPerRow(surface);
|
|
renderingBuffer=new agg::rendering_buffer((unsigned char *)O2SurfaceGetPixelBytes(surface),O2SurfaceGetWidth(surface),O2SurfaceGetHeight(surface),bytesPerRow);
|
|
|
|
rasterizer=new RasterizerType();
|
|
scanline_u8=new agg::scanline_u8;
|
|
scanline_p8=new agg::scanline_p8;
|
|
|
|
// Use with the right order depending of the bitmap info of the surface - we'll probably want to pass a pixel type here instead of an order to support non 32 bits surfaces and pre/no pre
|
|
renderer = new context_renderer();
|
|
O2BitmapInfo bitmapInfo = O2ImageGetBitmapInfo(surface);
|
|
renderer->context = self;
|
|
/*
|
|
AlphaFirst => The Alpha channel is next to the Red channel
|
|
(ARGB and BGRA are both Alpha First formats)
|
|
AlphaLast => The Alpha channel is next to the Blue channel
|
|
(RGBA and ABGR are both Alpha Last formats)
|
|
|
|
LittleEndian => Blue comes before Red
|
|
(BGRA and ABGR are Little endian formats)
|
|
BigEndian => Red comes before Blue
|
|
(ARGB and RGBA are Big endian formats).
|
|
*/
|
|
switch (bitmapInfo & kO2BitmapByteOrderMask) {
|
|
case kO2BitmapByteOrder32Big:
|
|
switch (bitmapInfo & kO2BitmapAlphaInfoMask) {
|
|
case kO2ImageAlphaPremultipliedLast:
|
|
renderer->init_pre<agg::order_rgba>(*renderingBuffer);
|
|
break;
|
|
case kO2ImageAlphaLast:
|
|
renderer->init<agg::order_rgba>(*renderingBuffer);
|
|
break;
|
|
case kO2ImageAlphaPremultipliedFirst:
|
|
renderer->init_pre<agg::order_argb>(*renderingBuffer);
|
|
break;
|
|
case kO2ImageAlphaFirst:
|
|
renderer->init<agg::order_argb>(*renderingBuffer);
|
|
break;
|
|
default:
|
|
O2Log("UNKNOW ALPHA : %d", bitmapInfo & kO2BitmapAlphaInfoMask);
|
|
renderer->init<agg::order_bgra>(*renderingBuffer);
|
|
}
|
|
break;
|
|
case kO2BitmapByteOrderDefault:
|
|
case kO2BitmapByteOrder32Little:
|
|
switch (bitmapInfo & kO2BitmapAlphaInfoMask) {
|
|
case kO2ImageAlphaPremultipliedLast:
|
|
renderer->init_pre<agg::order_abgr>(*renderingBuffer);
|
|
break;
|
|
case kO2ImageAlphaLast:
|
|
renderer->init<agg::order_abgr>(*renderingBuffer);
|
|
break;
|
|
case kO2ImageAlphaPremultipliedFirst:
|
|
renderer->init_pre<agg::order_bgra>(*renderingBuffer);
|
|
break;
|
|
case kO2ImageAlphaFirst:
|
|
renderer->init<agg::order_bgra>(*renderingBuffer);
|
|
break;
|
|
default:
|
|
O2Log("UNKNOW ALPHA : %d", bitmapInfo & kO2BitmapAlphaInfoMask);
|
|
renderer->init<agg::order_bgra>(*renderingBuffer);
|
|
}
|
|
break;
|
|
default:
|
|
O2Log("UNKNOW ORDER : %x",bitmapInfo & kO2BitmapByteOrderMask);
|
|
renderer->init<agg::order_bgra>(*renderingBuffer);
|
|
}
|
|
|
|
path=new agg::path_storage();
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void)dealloc {
|
|
[savedClipPhases release];
|
|
|
|
delete renderer;
|
|
delete renderingBuffer;
|
|
delete rasterizer;
|
|
delete path;
|
|
delete scanline_u8;
|
|
delete scanline_p8;
|
|
|
|
if (baseRendererAlphaMask[0]) {
|
|
for (int i = 0; i < 2; ++i) {
|
|
free(rBufAlphaMask[i]->buf());
|
|
delete rBufAlphaMask[i];
|
|
delete alphaMask[i];
|
|
delete pixelFormatAlphaMask[i];
|
|
delete baseRendererAlphaMask[i];
|
|
delete solidScanlineRendererAlphaMask[i];
|
|
}
|
|
}
|
|
[super dealloc];
|
|
}
|
|
|
|
-(BOOL)supportsGlobalAlpha
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)createMaskRenderer
|
|
{
|
|
// We use two gray masks to render our alpha mask - one for the current mask, one to render a new clipping path using the current mask
|
|
for (int i = 0; i < 2; ++i) {
|
|
O2Surface *surface = [self surface];
|
|
// For some still mysterious reason, if we don't allocate one additional line, we sometimes get some crash when rendering the mask
|
|
// So we'll just extend that buffer until we fix the problem at its root
|
|
void *alphaBuffer = malloc(O2SurfaceGetWidth(surface)*(O2SurfaceGetHeight(surface)+1));
|
|
rBufAlphaMask[i] = new agg::rendering_buffer((unsigned char *)alphaBuffer, O2SurfaceGetWidth(surface),O2SurfaceGetHeight(surface), O2SurfaceGetWidth(surface));
|
|
alphaMask[i] = new MaskType(*rBufAlphaMask[i]);
|
|
pixelFormatAlphaMask[i] = new pixfmt_alphaMaskType(*rBufAlphaMask[i]);
|
|
baseRendererAlphaMask[i] = new BaseRendererWithAlphaMaskType(*pixelFormatAlphaMask[i]);
|
|
|
|
solidScanlineRendererAlphaMask[i] = new agg::renderer_scanline_aa_solid<BaseRendererWithAlphaMaskType>(*baseRendererAlphaMask[i]);
|
|
solidScanlineRendererAlphaMask[i]->color(agg::gray8(255, 255));
|
|
}
|
|
}
|
|
|
|
- (RasterizerType *)rasterizer
|
|
{
|
|
return rasterizer;
|
|
}
|
|
|
|
- (context_renderer *)renderer;
|
|
{
|
|
return renderer;
|
|
}
|
|
|
|
- (BOOL)useMask
|
|
{
|
|
return useMask;
|
|
}
|
|
|
|
- (BOOL)isPremultiplied;
|
|
{
|
|
return renderer->premultiplied;
|
|
}
|
|
|
|
- (MaskType*)currentMask
|
|
{
|
|
if (baseRendererAlphaMask[0] == NULL) {
|
|
[self createMaskRenderer];
|
|
}
|
|
return alphaMask[currentMask];
|
|
}
|
|
|
|
- (void)setFillAlpha:(O2Float)alpha
|
|
{
|
|
// It's actually handled by the global alpha - that's the way it's supposed to work in Cocoa
|
|
}
|
|
|
|
- (void)setStrokeAlpha:(O2Float)alpha
|
|
{
|
|
// It's actually handled by the global alpha - that's the way it's supposed to work in Cocoa
|
|
}
|
|
|
|
-(void)updateBlendMode
|
|
{
|
|
// Onyx2D -> AGG blend mode conversion
|
|
// O2Log("Setting blend mode to %d", blendMode);
|
|
enum o2agg::comp_op_e blendModeMap[28]={
|
|
o2agg::comp_op_src_over,
|
|
o2agg::comp_op_multiply,
|
|
o2agg::comp_op_screen,
|
|
o2agg::comp_op_overlay,
|
|
o2agg::comp_op_darken,
|
|
o2agg::comp_op_lighten,
|
|
o2agg::comp_op_color_dodge,
|
|
o2agg::comp_op_color_burn,
|
|
o2agg::comp_op_hard_light,
|
|
o2agg::comp_op_soft_light,
|
|
o2agg::comp_op_difference,
|
|
o2agg::comp_op_exclusion,
|
|
o2agg::comp_op_src_over, // should be Hue
|
|
o2agg::comp_op_src_over, // should be Saturation
|
|
o2agg::comp_op_color,
|
|
o2agg::comp_op_src_over, // should be Luminosity
|
|
o2agg::comp_op_clear,
|
|
o2agg::comp_op_src,
|
|
o2agg::comp_op_src_in,
|
|
o2agg::comp_op_src_out,
|
|
o2agg::comp_op_src_atop,
|
|
o2agg::comp_op_dst_over,
|
|
o2agg::comp_op_dst_in,
|
|
o2agg::comp_op_dst_out,
|
|
o2agg::comp_op_dst_atop,
|
|
o2agg::comp_op_xor,
|
|
o2agg::comp_op_plus_darker,
|
|
o2agg::comp_op_minus, // shoud be MinusLighter
|
|
};
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
|
|
renderer->setBlendMode(blendModeMap[O2GStateBlendMode(gState)]);
|
|
renderer->setAlpha(O2GStateAlpha(gState));
|
|
|
|
O2ColorRef shadowColor = gState->_shadowColor;
|
|
if (shadowColor) {
|
|
shadowColor = O2ColorConvertToDeviceRGB(shadowColor);
|
|
const O2Float *components = O2ColorGetComponents(shadowColor);
|
|
renderer->setShadowColor(o2agg::rgba(components[0], components[1], components[2], components[3]));
|
|
renderer->setShadowBlurRadius(gState->_shadowBlur);
|
|
renderer->setShadowOffset(gState->_shadowOffset);
|
|
O2ColorRelease(shadowColor);
|
|
} else {
|
|
renderer->setShadowColor(o2agg::rgba(0, 0, 0, 0));
|
|
}
|
|
}
|
|
|
|
|
|
// Update the AGG alpha mask from current GState clip phases
|
|
- (void)updateMask
|
|
{
|
|
if (maskValid == YES) {
|
|
return;
|
|
}
|
|
currentMask = 0;
|
|
useMask = NO;
|
|
|
|
if ([savedClipPhases count]) {
|
|
typedef agg::conv_curve<agg::path_storage> conv_crv_type;
|
|
typedef agg::conv_transform<conv_crv_type> transStroke;
|
|
|
|
self->rasterizer->reset();
|
|
self->rasterizer->clip_box(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
O2AffineTransform deviceTransform=gState->_deviceSpaceTransform;
|
|
agg::trans_affine deviceMatrix(deviceTransform.a,deviceTransform.b,deviceTransform.c,deviceTransform.d,deviceTransform.tx,deviceTransform.ty);
|
|
|
|
agg::path_storage aggPath;
|
|
for (int i = 0; i < [savedClipPhases count]; ++i) {
|
|
O2ClipPhase *phase = [savedClipPhases objectAtIndex:i];
|
|
|
|
switch(O2ClipPhasePhaseType(phase)){
|
|
case O2ClipPhaseNonZeroPath:
|
|
case O2ClipPhaseEOPath: {
|
|
O2PathRef maskPath = O2ClipPhaseObject(phase);
|
|
// We can ignore path that are rect - the default clipping takes that into account
|
|
NSRect rect;
|
|
if (!O2PathIsRect(maskPath, &rect)) {
|
|
if (useMask == NO) {
|
|
// Fill the mask content on first use
|
|
useMask = YES;
|
|
if (baseRendererAlphaMask[0] == NULL) {
|
|
[self createMaskRenderer];
|
|
}
|
|
baseRendererAlphaMask[0]->clip_box(self->_vpx-1,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
// We only need to fill the clipped rect area
|
|
baseRendererAlphaMask[0]->copy_bar(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight, agg::gray8(255, 255));
|
|
}
|
|
// Switch masks
|
|
currentMask = !currentMask;
|
|
// Clear the new mask
|
|
baseRendererAlphaMask[currentMask]->clip_box(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
// We only need to clear the clipped rect area
|
|
baseRendererAlphaMask[currentMask]->copy_bar(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight, agg::gray8(0));
|
|
|
|
buildAGGPathFromO2PathAndTransform(&aggPath,maskPath,O2AffineTransformIdentity);
|
|
|
|
agg::conv_curve<agg::path_storage> curve(aggPath);
|
|
curve.approximation_scale(deviceMatrix.scale());
|
|
O2ClipPhaseType phaseType = O2ClipPhasePhaseType(phase);
|
|
if (phaseType == O2ClipPhaseNonZeroPath)
|
|
rasterizer->filling_rule(agg::fill_non_zero);
|
|
else
|
|
rasterizer->filling_rule(agg::fill_even_odd);
|
|
|
|
rasterizer->add_path(curve);
|
|
|
|
// Render the path masked by the previous mask
|
|
scanline_mask_type sl(*self->alphaMask[!currentMask]);
|
|
renderer->render_scanlines(*rasterizer, sl, *solidScanlineRendererAlphaMask[currentMask]);
|
|
}
|
|
break;
|
|
}
|
|
case O2ClipPhaseMask:
|
|
O2Log("%@:Clip to mask unsupported", self);
|
|
// Unsupported for now
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
maskValid = YES;
|
|
}
|
|
|
|
// Clip the viewport with the path bounds
|
|
static void O2ContextClipViewportToPath(O2Context_builtin *self,O2Path *path) {
|
|
O2Rect viewport=O2RectMake(self->_vpx,self->_vpy,self->_vpwidth,self->_vpheight);
|
|
O2Rect rect=O2PathGetBoundingBox(path);
|
|
|
|
rect=O2RectIntegral(rect);
|
|
|
|
viewport=O2RectIntersection(viewport,rect);
|
|
|
|
self->_vpx=viewport.origin.x;
|
|
self->_vpy=viewport.origin.y;
|
|
self->_vpwidth=viewport.size.width;
|
|
self->_vpheight=viewport.size.height;
|
|
}
|
|
|
|
static void O2ContextClipViewportToState(O2Context_builtin *self, O2ClipState *clipState)
|
|
{
|
|
// Note : this is basically what [O2Context_builtin clipToState:] is doing
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
NSArray *phases=[O2GStateClipState(gState) clipPhases];
|
|
|
|
O2ContextDeviceClipReset_builtin(self);
|
|
for(O2ClipPhase *phase in phases) {
|
|
O2Path *path;
|
|
switch(O2ClipPhasePhaseType(phase)){
|
|
|
|
case O2ClipPhaseNonZeroPath:
|
|
case O2ClipPhaseEOPath:
|
|
path=O2ClipPhaseObject(phase);
|
|
O2ContextClipViewportToPath(self,path);
|
|
break;
|
|
|
|
case O2ClipPhaseMask:
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
-(void)clipToState:(O2ClipState *)clipState {
|
|
// No need to change the clipping if the clip phases are the same as before
|
|
NSArray *clipPhases = [clipState clipPhases];
|
|
if ([clipPhases isEqualToArray:savedClipPhases]) {
|
|
return;
|
|
}
|
|
[savedClipPhases release];
|
|
savedClipPhases = [clipPhases copy];
|
|
|
|
/*
|
|
The builtin Onyx2D renderer only supports viewport clipping (one integer rect), so once the superclass has clipped
|
|
the viewport is what we want to clip to also. The base AGG renderer also does viewport clipping, so we just set it.
|
|
*/
|
|
#ifdef O2AGG_GLYPH_SUPPORT
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
if (gState->_shouldSmoothFonts == YES) {
|
|
// We don't call the super method because we don't care about expensive GDI clipping when we use no GDI for the drawing
|
|
O2ContextClipViewportToState(self, clipState);
|
|
} else
|
|
#endif
|
|
{
|
|
[super clipToState:clipState];
|
|
}
|
|
|
|
/// This is setting the clipping rect englobing our clipping states
|
|
// The real precise clipping mask for non-rect clipping will be built on demand
|
|
O2ContextClipViewportToState(self, clipState);
|
|
|
|
|
|
renderer->clipBox(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
// That will force a rebuild of the mask next time we need to draw something - no need to build it now as it might
|
|
// change again before we need it for drawing
|
|
maskValid = NO;
|
|
}
|
|
|
|
-(void)drawPath:(O2PathDrawingMode)drawingMode {
|
|
renderer->premultiplied = YES;
|
|
BOOL doFill=NO;
|
|
BOOL doEOFill=NO;
|
|
BOOL doStroke=NO;
|
|
|
|
[self updateMask];
|
|
[self updateBlendMode];
|
|
|
|
rasterizer->reset();
|
|
rasterizer->clip_box(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
switch(drawingMode){
|
|
|
|
case kO2PathFill:
|
|
doFill=YES;
|
|
break;
|
|
|
|
case kO2PathEOFill:
|
|
doEOFill=YES;
|
|
break;
|
|
|
|
case kO2PathStroke:
|
|
doStroke=YES;
|
|
break;
|
|
|
|
case kO2PathFillStroke:
|
|
doFill=YES;
|
|
doStroke=YES;
|
|
break;
|
|
|
|
case kO2PathEOFillStroke:
|
|
doEOFill=YES;
|
|
doStroke=YES;
|
|
break;
|
|
}
|
|
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
|
|
transferPath(self,(O2PathRef)_path,O2AffineTransformInvert(gState->_userSpaceTransform));
|
|
|
|
O2AffineTransform deviceTransform=gState->_deviceSpaceTransform;
|
|
|
|
agg::trans_affine aggDeviceMatrix(deviceTransform.a,deviceTransform.b,deviceTransform.c,deviceTransform.d,deviceTransform.tx,deviceTransform.ty);
|
|
|
|
if(doFill || doEOFill) {
|
|
O2PatternRef pattern = O2ColorGetPattern(gState->_fillColor);
|
|
if (pattern) {
|
|
// Note: pattern support is incomplete :
|
|
// We don't make much use of the pattern phase, pattern color or pattern bounds
|
|
|
|
// Create a context with the size of our pattern tile
|
|
O2Surface *surface=[self createSurfaceWithWidth:[pattern xstep] height:[pattern ystep]];
|
|
O2Context_AntiGrain *context=[[self->isa alloc] initWithSurface:surface flipped:YES];
|
|
|
|
// Draw the pattern tile
|
|
[pattern drawInContext:context];
|
|
O2ContextRelease(context);
|
|
|
|
// Build the global transform from the pattern to the device
|
|
O2AffineTransform matrix = O2AffineTransformConcat( ([pattern matrix]), O2AffineTransformInvert(gState->_userSpaceTransform));
|
|
|
|
agg::trans_affine globalTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
|
|
globalTransform.multiply(aggDeviceMatrix);
|
|
globalTransform.invert();
|
|
|
|
// Create an agg buffer from the Surface content
|
|
uint8_t *imgBytes = (uint8_t *)[surface pixelBytes];
|
|
agg::rendering_buffer imageBuffer(imgBytes, O2SurfaceGetWidth(surface) , O2SurfaceGetHeight(surface), O2SurfaceGetBytesPerRow(surface));
|
|
|
|
// Fill the current agg path with the repeated tile
|
|
agg::conv_curve<agg::path_storage> curve(*(self->path));
|
|
agg::conv_transform<agg::conv_curve<agg::path_storage>, agg::trans_affine> trans(curve, aggDeviceMatrix);
|
|
self->rasterizer->add_path(trans);
|
|
renderer->premultiplied = YES;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_bgra32_pre>(self, imageBuffer, globalTransform, kImageInterpolationRepeat);
|
|
|
|
[surface release];
|
|
} else {
|
|
O2ColorRef fillColor=O2ColorConvertToDeviceRGB(gState->_fillColor);
|
|
const float *fillComps=O2ColorGetComponents(fillColor);
|
|
o2agg::rgba aggFillColor(fillComps[0],fillComps[1],fillComps[2],fillComps[3]);
|
|
if (doFill) {
|
|
O2AGGContextFillPathWithRule(self,aggFillColor,aggDeviceMatrix,agg::fill_non_zero);
|
|
} else {
|
|
O2AGGContextFillPathWithRule(self,aggFillColor,aggDeviceMatrix,agg::fill_even_odd);
|
|
}
|
|
O2ColorRelease(fillColor);
|
|
}
|
|
}
|
|
|
|
if(doStroke){
|
|
// Note: pattern stroking not supported for now
|
|
O2ColorRef strokeColor=O2ColorConvertToDeviceRGB(gState->_strokeColor);
|
|
const float *strokeComps=O2ColorGetComponents(strokeColor);
|
|
o2agg::rgba aggStrokeColor(strokeComps[0],strokeComps[1],strokeComps[2],strokeComps[3]);
|
|
O2AGGContextStrokePath(self,aggStrokeColor,aggDeviceMatrix);
|
|
O2ColorRelease(strokeColor);
|
|
}
|
|
|
|
O2PathReset(_path);
|
|
}
|
|
|
|
-(void)replacePathWithStrokedPath
|
|
{
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
|
|
transferPath(self,(O2PathRef)_path,O2AffineTransformInvert(gState->_userSpaceTransform));
|
|
|
|
O2AffineTransform deviceTransform=gState->_deviceSpaceTransform;
|
|
agg::trans_affine aggDeviceMatrix(deviceTransform.a,deviceTransform.b,deviceTransform.c,deviceTransform.d,deviceTransform.tx,deviceTransform.ty);
|
|
|
|
O2AGGReplaceStrokedPath(self,aggDeviceMatrix);
|
|
}
|
|
|
|
-(void)drawImage:(O2Image *)image inRect:(O2Rect)rect
|
|
{
|
|
int bpp = O2ImageGetBitsPerPixel(image);
|
|
|
|
if (bpp == 32) { // We don't support other modes for now
|
|
int width = O2ImageGetWidth(image);
|
|
int height = O2ImageGetHeight(image);
|
|
int bytesPerRow = O2ImageGetBytesPerRow(image);
|
|
int bitmapInfo = O2ImageGetBitmapInfo(image);
|
|
|
|
[self updateMask];
|
|
[self updateBlendMode];
|
|
|
|
rasterizer->reset();
|
|
rasterizer->clip_box(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
agg::path_storage p2;
|
|
p2.move_to(rect.origin.x, rect.origin.y);
|
|
p2.line_to(rect.origin.x+rect.size.width, rect.origin.y);
|
|
p2.line_to(rect.origin.x+rect.size.width, rect.origin.y+rect.size.height);
|
|
p2.line_to(rect.origin.x, rect.origin.y+rect.size.height);
|
|
p2.close_polygon();
|
|
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
O2AffineTransform deviceTransform= gState->_deviceSpaceTransform;
|
|
|
|
agg::trans_affine transform(deviceTransform.a,deviceTransform.b,deviceTransform.c,deviceTransform.d,deviceTransform.tx,deviceTransform.ty);
|
|
|
|
agg::conv_transform<agg::path_storage> trans(p2, transform); // Global Affine transformer
|
|
rasterizer->add_path(trans);
|
|
|
|
// Flipped scaled transfrom from the original image bounds to the destination rect
|
|
O2AffineTransform imageTransform = O2AffineTransformMakeTranslation(rect.origin.x, rect.origin.y + rect.size.height);
|
|
imageTransform = O2AffineTransformScale(imageTransform, rect.size.width/float(width), -rect.size.height/float(height));
|
|
|
|
agg::trans_affine globalTransform(imageTransform.a, imageTransform.b, imageTransform.c, imageTransform.d, imageTransform.tx, imageTransform.ty);
|
|
globalTransform.multiply(transform);
|
|
|
|
// We use bilinear scaling for images that need to be shrunk. At 100% scale size it preserves the image better.
|
|
// Remember we don't even think about doing this for images that need to expand; those should always use the bicubic filter; however,
|
|
// that is user-selectable.
|
|
|
|
bool reducedScale = true;
|
|
bool isUnity = true;
|
|
globalTransform.invert();
|
|
|
|
double scaleX;
|
|
double scaleY;
|
|
globalTransform.scaling_abs(&scaleX, &scaleY);
|
|
if (scaleX > .995 || scaleY > .995)
|
|
{
|
|
reducedScale = false;
|
|
}
|
|
if (fabs(scaleX - 1.) > .0001 || fabs(scaleY - 1.) > .0001)
|
|
{
|
|
// printf(">>>> %lf -- %lf\n",scaleX, scaleY);
|
|
isUnity = false;
|
|
}
|
|
|
|
|
|
// Round the physical screen coordinate to an integer boundary so filtering works better. This seems to make images that are 1x1 rasterize
|
|
// exactly. Changing the pixel coordinates on smaller scaled images introuduces unpleasant artifacts.
|
|
if (isUnity)
|
|
{
|
|
globalTransform.tx = round(globalTransform.tx);
|
|
globalTransform.ty = round(globalTransform.ty);
|
|
}
|
|
|
|
uint8_t *imgBytes = (uint8_t *)[image directBytes];
|
|
agg::rendering_buffer imageBuffer(imgBytes, width, height, bytesPerRow);
|
|
|
|
BOOL noInterpolation = isUnity;
|
|
|
|
int interpolationQuality = [gState interpolationQuality];
|
|
if (interpolationQuality == kO2InterpolationNone) {
|
|
noInterpolation = YES;
|
|
}
|
|
|
|
ImageInterpolationType interpolationType = kImageInterpolationBilinear;
|
|
if (interpolationQuality > kO2InterpolationHigh) {
|
|
interpolationType = kImageInterpolationBicubic;
|
|
}
|
|
|
|
if (noInterpolation) {
|
|
interpolationType = kImageInterpolationNone;
|
|
}
|
|
/*
|
|
From David Duncan :
|
|
|
|
AlphaFirst => The Alpha channel is next to the Red channel
|
|
(ARGB and BGRA are both Alpha First formats)
|
|
AlphaLast => The Alpha channel is next to the Blue channel
|
|
(RGBA and ABGR are both Alpha Last formats)
|
|
|
|
LittleEndian => Blue comes before Red
|
|
(BGRA and ABGR are Little endian formats)
|
|
BigEndian => Red comes before Blue
|
|
(ARGB and RGBA are Big endian formats).
|
|
*/
|
|
switch (bitmapInfo & kO2BitmapByteOrderMask) {
|
|
// Big Endian is RB
|
|
case kO2BitmapByteOrder32Big:
|
|
switch (bitmapInfo & kO2BitmapAlphaInfoMask) {
|
|
case kO2ImageAlphaPremultipliedLast:
|
|
renderer->premultiplied = YES;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_rgba32_pre>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
case kO2ImageAlphaLast:
|
|
renderer->premultiplied = NO;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_rgba32>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
case kO2ImageAlphaPremultipliedFirst:
|
|
renderer->premultiplied = YES;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_argb32_pre>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
case kO2ImageAlphaFirst:
|
|
renderer->premultiplied = NO;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_argb32>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
default:
|
|
O2Log("UNKNOW ALPHA");
|
|
// Fallback to the default implementation - that means path clipping, shadow, layers... won't work well on this one
|
|
[super drawImage:image inRect:rect];
|
|
}
|
|
break;
|
|
// Little Endian is BG
|
|
case kO2BitmapByteOrderDefault:
|
|
case kO2BitmapByteOrder32Little:
|
|
switch (bitmapInfo & kO2BitmapAlphaInfoMask) {
|
|
case kO2ImageAlphaPremultipliedLast:
|
|
renderer->premultiplied = YES;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_abgr32_pre>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
case kO2ImageAlphaLast:
|
|
renderer->premultiplied = NO;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_abgr32>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
case kO2ImageAlphaPremultipliedFirst:
|
|
renderer->premultiplied = YES;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_bgra32_pre>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
case kO2ImageAlphaFirst:
|
|
renderer->premultiplied = NO;
|
|
O2AGGContextDrawImage<o2agg::pixfmt_bgra32>(self, imageBuffer, globalTransform, interpolationType);
|
|
break;
|
|
default:
|
|
// Fallback to the default implementation - that means clipping, shadow, layers... won't work on this one
|
|
O2Log("UNKNOW ALPHA");
|
|
[super drawImage:image inRect:rect];
|
|
}
|
|
break;
|
|
default:
|
|
O2Log("UNKNOW ORDER");
|
|
// Fallback to the default implementation - that means path clipping, shadow, layers... won't work well on this one
|
|
[super drawImage:image inRect:rect];
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
O2Log("Unsuported image format");
|
|
// Fallback to the default implementation - that means path clipping, shadow, layers... won't work well on this one
|
|
[super drawImage:image inRect:rect];
|
|
}
|
|
}
|
|
|
|
-(void)drawShading:(O2Shading *)shading
|
|
{
|
|
renderer->premultiplied = YES;
|
|
[self updateMask];
|
|
[self updateBlendMode];
|
|
|
|
rasterizer->reset();
|
|
rasterizer->clip_box(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
if ([shading isAxial]) {
|
|
O2AGGContextDrawShading<agg::gradient_x>(self, shading, _vpx, _vpy, _vpwidth, _vpheight);
|
|
} else {
|
|
// Note: we only support circle gradient for now where startPoint = endPoint
|
|
O2AGGContextDrawShading<agg::gradient_circle>(self, shading, _vpx, _vpy, _vpwidth, _vpheight);
|
|
}
|
|
}
|
|
|
|
-(void)beginTransparencyLayerWithInfo:(NSDictionary *)unused {
|
|
// NOTE: We could create a new context with only the current clipping size, and offset all clipping/drawing from the clipping origin
|
|
O2LayerRef layer=O2LayerCreateWithContext(self,[self size],unused);
|
|
|
|
[self->_layerStack addObject:layer];
|
|
O2LayerRelease(layer);
|
|
|
|
// Attach to the layer surface
|
|
O2Surface *surface = O2LayerGetSurface(layer);
|
|
renderingBuffer->attach((unsigned char *)O2SurfaceGetPixelBytes(surface),O2SurfaceGetWidth(surface),O2SurfaceGetHeight(surface),O2SurfaceGetBytesPerRow(surface));
|
|
|
|
O2ContextSaveGState(self); // Save the pre layer-time context
|
|
|
|
/**
|
|
* From Cocoa doc :
|
|
* graphics state parameters remain unchanged except for alpha (which is set to 1), shadow (which is turned off), blend mode (which is set to normal),
|
|
* and other parameters that affect the final composite.
|
|
*/
|
|
|
|
O2ContextResetClip(self); // Starts with a clean clipping region
|
|
O2GStateSetBlendMode(O2ContextCurrentGState(self), kO2BlendModeNormal);
|
|
O2GStateSetAlpha(O2ContextCurrentGState(self), 1.);
|
|
|
|
// No shadow from now
|
|
[O2ContextCurrentGState(self) setShadowOffset:O2SizeZero blur:0. color:nil];
|
|
|
|
}
|
|
|
|
-(void)endTransparencyLayer {
|
|
O2LayerRef layer=O2LayerRetain([self->_layerStack lastObject]);
|
|
|
|
[self->_layerStack removeLastObject];
|
|
|
|
O2Size size=[self size];
|
|
|
|
// Reattach the renderer to the top surface (either to the new top layer if any or the context one)
|
|
O2LayerRef newTopLayer = [self->_layerStack lastObject];
|
|
O2Surface *surface = newTopLayer?O2LayerGetSurface(newTopLayer):_surface;
|
|
renderingBuffer->attach((unsigned char *)O2SurfaceGetPixelBytes(surface),O2SurfaceGetWidth(surface),O2SurfaceGetHeight(surface),O2SurfaceGetBytesPerRow(surface));
|
|
|
|
O2ContextRestoreGState(self); // Restore the clean state
|
|
|
|
// Draw the layer content into our surface
|
|
O2ContextSaveGState(self);
|
|
// We want no transform here as the layer surface matches the context one
|
|
O2ContextConcatCTM(self, O2AffineTransformInvert(O2ContextGetCTM(self)));
|
|
[self drawImage:O2LayerGetSurface(layer) inRect:O2RectMake(0,0,size.width,size.height)];
|
|
O2ContextRestoreGState(self);
|
|
|
|
O2LayerRelease(layer);
|
|
}
|
|
|
|
#ifdef O2AGG_GLYPH_SUPPORT
|
|
-(void)showGlyphs:(const O2Glyph *)glyphs advances:(const O2Size *)advances count:(unsigned)count
|
|
{
|
|
// Use Win text rendering if the context shouldSmoothFonts isn't set (default value)
|
|
// Note that Win text rendering currently doesn't support some advanced features like
|
|
// non-rect clipping, shadows...
|
|
// There are also some issues when the context is scaled
|
|
// But rendering is faster
|
|
O2GState *gState=O2ContextCurrentGState(self);
|
|
if (gState->_shouldSmoothFonts == NO) {
|
|
[super showGlyphs:glyphs advances:advances count:count];
|
|
return;
|
|
}
|
|
renderer->premultiplied = YES;
|
|
[self updateMask];
|
|
[self updateBlendMode];
|
|
|
|
rasterizer->reset();
|
|
rasterizer->clip_box(self->_vpx,self->_vpy,self->_vpx+self->_vpwidth,self->_vpy+self->_vpheight);
|
|
|
|
O2AGGContextShowGlyphs(self,glyphs,advances,count);
|
|
}
|
|
#endif
|
|
#endif
|
|
@end
|