/*
 * \@file evas_filter.c
 *
 * Infrastructure for simple filters applied to RGBA and Alpha buffers.
 * Originally used by font effects.
 *
 * Filters include:
 * - Blur (Gaussian, Box, Motion) and Shadows
 * - Bump maps (light effects)
 * - Displacement maps
 * - Color curves
 * - Blending and masking
 *
 * The reference documentation can be found in evas_filter_parser.c
 */

#include "evas_filter.h"

#include "evas_filter_private.h"
#include <Ector.h>
#include <software/Ector_Software.h>
#include "evas_ector_buffer.eo.h"

#define _assert(a) if (!(a)) CRI("Failed on %s", #a);

static void _buffer_free(Evas_Filter_Buffer *fb);
static void _command_del(Evas_Filter_Context *ctx, Evas_Filter_Command *cmd);
static Evas_Filter_Buffer *_buffer_alloc_new(Evas_Filter_Context *ctx, int w, int h, Eina_Bool alpha_only, Eina_Bool render, Eina_Bool draw);
static void _filter_buffer_unlock_all(Evas_Filter_Context *ctx);

#define DRAW_COLOR_SET(r, g, b, a) do { cmd->draw.R = r; cmd->draw.G = g; cmd->draw.B = b; cmd->draw.A = a; } while (0)
#define DRAW_CLIP_SET(_x, _y, _w, _h) do { cmd->draw.clip.x = _x; cmd->draw.clip.y = _y; cmd->draw.clip.w = _w; cmd->draw.clip.h = _h; } while (0)
#define DRAW_FILL_SET(fmode) do { cmd->draw.fillmode = fmode; } while (0)

/* Main functions */

#define _free(ptr) free(ptr)
//eina_freeq_ptr_main_add(ptr, NULL, sizeof(*ptr))


/* FIXME: This code is come from a log by
   CRI<14853>:eo lib/eo/eo.c:1894 efl_unref() Calling efl_unref instead of efl_del or efl_parent_set(NULL).
   Temporary fallback in place triggered." When u get correct method, please fix this. */
static void
_buffer_del(Eo *buffer)
{
   if (!buffer) return;

   if (efl_parent_get(buffer))
     efl_del(buffer);
   else
     efl_unref(buffer);
}

Evas_Filter_Context *
evas_filter_context_new(Evas_Public_Data *evas, Eina_Bool async, void *user_data)
{
   Evas_Filter_Context *ctx;

   EINA_SAFETY_ON_NULL_RETURN_VAL(evas, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(evas->engine.func->gfx_filter_supports, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(evas->engine.func->gfx_filter_process, NULL);

   ctx = calloc(1, sizeof(Evas_Filter_Context));
   if (!ctx) return NULL;

   ctx->evas = evas;
   ctx->async = async;
   ctx->user_data = user_data;
   ctx->buffer_scaled_get = &evas_filter_buffer_scaled_get;
   ctx->gl = (ENFN->gl_surface_read_pixels != NULL);
   ctx->refcount = 1;

   return ctx;
}

void *
evas_filter_context_data_get(Evas_Filter_Context *ctx)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   return ctx->user_data;
}

Eina_Bool
evas_filter_context_async_get(Evas_Filter_Context *ctx)
{
   return ctx->async;
}

void
evas_filter_context_size_get(Evas_Filter_Context *ctx, int *w, int *h)
{
   if (w) *w = ctx->w;
   if (h) *h = ctx->h;
}

/* Private function to reset the filter context. Used from parser.c */
void
evas_filter_context_clear(Evas_Filter_Context *ctx, Eina_Bool keep_buffers)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *fb;

   if (!ctx) return;

   if (ctx->target.surface) ENFN->image_free(ENC, ctx->target.surface);
   if (ctx->target.mask) ENFN->image_free(ENC, ctx->target.mask);
   ctx->target.surface = NULL;
   ctx->target.mask = NULL;

   if (!keep_buffers)
     {
        ctx->last_buffer_id = 0;
        EINA_LIST_FREE(ctx->buffers, fb)
          _buffer_free(fb);
     }

   ctx->last_command_id = 0;
   EINA_INLIST_FREE(ctx->commands, cmd)
     _command_del(ctx, cmd);

   // Note: don't reset post_run, as it it set by the client
}

static void
_filter_buffer_backing_free(Evas_Filter_Buffer *fb)
{
   if (!fb || !fb->buffer) return;
   _buffer_del((Eo *)fb->buffer);
   fb->buffer = NULL;
}

/** @hidden private render proxy objects */
void
evas_filter_context_proxy_render_all(Evas_Filter_Context *ctx, Eo *eo_obj, void *output,
                                     Eina_Bool do_async)
{
   Evas_Object_Protected_Data *source;
   Evas_Object_Protected_Data *obj;
   void *proxy_surface;
   Evas_Filter_Buffer *fb;
   Eina_List *li;

   if (!ctx->has_proxies) return;
   obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);

   EINA_LIST_FOREACH(ctx->buffers, li, fb)
     if (fb->source)
       {
          // TODO: Lock current object as proxyrendering (see image obj)
          source = efl_data_scope_get(fb->source, EFL_CANVAS_OBJECT_CLASS);
          _assert(fb->w == source->cur->geometry.w);
          _assert(fb->h == source->cur->geometry.h);
          proxy_surface = source->proxy->surface;
          if (source->proxy->surface && !source->proxy->redraw)
            {
               XDBG("Source already rendered: '%s' of type '%s'",
                   fb->source_name, efl_class_name_get(efl_class_get(fb->source)));
            }
          else
            {
               XDBG("Source needs to be rendered: '%s' of type '%s' (%s)",
                   fb->source_name, efl_class_name_get(efl_class_get(fb->source)),
                   source->proxy->redraw ? "redraw" : "no surface");
               evas_render_proxy_subrender(ctx->evas->evas, output, fb->source, eo_obj, obj, EINA_FALSE, do_async);
            }
          if (fb->buffer)
            {
               void *old_surface;

               old_surface = evas_ector_buffer_drawable_image_get(fb->buffer);
               if (old_surface)
                 {
                    evas_ector_buffer_engine_image_release(fb->buffer, old_surface);
                    if (old_surface && (old_surface != proxy_surface))
                      _filter_buffer_backing_free(fb);
                 }
            }
          XDBG("Source #%d '%s' has dimensions %dx%d", fb->id, fb->source_name, fb->w, fb->h);
          if (!fb->buffer) fb->buffer = ENFN->ector_buffer_wrap(ENC, obj->layer->evas->evas, source->proxy->surface);
          fb->alpha_only = EINA_FALSE;
       }
}

void
_evas_filter_context_program_reuse(void *engine, void *output, Evas_Filter_Context *ctx)
{
   Evas_Filter_Buffer *fb;
   Eina_List *li;

   _filter_buffer_unlock_all(ctx);

   EINA_LIST_FOREACH(ctx->buffers, li, fb)
     {
        void *dc, *surface;

        fb->used = EINA_FALSE;
        fb->locked = EINA_FALSE;

        if (!fb->is_render) continue;
        if (fb->source) continue;

        surface = evas_ector_buffer_render_image_get(fb->buffer);
        if (!surface) continue;

        dc = ENFN->context_new(engine);
        ENFN->context_color_set(engine, dc, 0, 0, 0, 0);
        ENFN->context_render_op_set(engine, dc, EVAS_RENDER_COPY);
        ENFN->rectangle_draw(engine, output, dc, surface, 0, 0, fb->w, fb->h, ctx->async);
        ENFN->context_free(engine, dc);
        fb->dirty = EINA_FALSE;

        evas_ector_buffer_engine_image_release(fb->buffer, surface);
     }
}

static void
_context_destroy(void *data)
{
   Evas_Filter_Context *ctx = data;

   EINA_SAFETY_ON_FALSE_RETURN(ctx->refcount == 0);
   evas_filter_context_clear(ctx, EINA_FALSE);
   _free(ctx);
}

int
evas_filter_context_ref(Evas_Filter_Context *ctx)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, -1);

#ifdef FILTERS_DEBUG
   EINA_SAFETY_ON_FALSE_RETURN_VAL(eina_main_loop_is(), -1);
   EINA_SAFETY_ON_FALSE_RETURN_VAL(ctx->refcount > 0, -1);
#endif

   return (++ctx->refcount);
}

void
evas_filter_context_unref(Evas_Filter_Context *ctx)
{
   if (!ctx) return;

#ifdef FILTERS_DEBUG
   EINA_SAFETY_ON_FALSE_RETURN(eina_main_loop_is());
   EINA_SAFETY_ON_FALSE_RETURN(ctx->refcount > 0);
#endif

   if ((--ctx->refcount) != 0) return;

   if (!ctx->running)
     _context_destroy(ctx);
   // else: post_run_cb will be called
}

void
evas_filter_context_post_run_callback_set(Evas_Filter_Context *ctx,
                                          Evas_Filter_Cb cb, void *data)
{
   EINA_SAFETY_ON_NULL_RETURN(ctx);
   ctx->post_run.cb = cb;
   ctx->post_run.data = data;
}

static Evas_Filter_Buffer *
_buffer_empty_new(Evas_Filter_Context *ctx, int w, int h, Eina_Bool alpha_only,
                  Eina_Bool transient)
{
   Evas_Filter_Buffer *fb;

   fb = calloc(1, sizeof(Evas_Filter_Buffer));
   if (!fb) return NULL;

   fb->id = ++(ctx->last_buffer_id);
   fb->ctx = ctx;
   fb->alpha_only = alpha_only;
   fb->transient = transient;
   fb->w = w;
   fb->h = h;

   ctx->buffers = eina_list_append(ctx->buffers, fb);
   return fb;
}

static Ector_Buffer *
_ector_buffer_create(Evas_Filter_Buffer const *fb, Eina_Bool render, Eina_Bool draw)
{
   Efl_Gfx_Colorspace cspace = EFL_GFX_COLORSPACE_ARGB8888;
   Ector_Buffer_Flag flags;

   // FIXME: Once all filters are GL buffers need not be CPU accessible
   flags = ECTOR_BUFFER_FLAG_CPU_READABLE | ECTOR_BUFFER_FLAG_CPU_WRITABLE;
   if (render) flags |= ECTOR_BUFFER_FLAG_RENDERABLE;
   if (draw) flags |= ECTOR_BUFFER_FLAG_DRAWABLE;
   if (fb->alpha_only) cspace = EFL_GFX_COLORSPACE_GRY8;

   return fb->ENFN->ector_buffer_new(FB_ENC, fb->ctx->evas->evas,
                                     fb->w, fb->h, cspace, flags);
}

Eina_Bool
evas_filter_context_buffers_allocate_all(Evas_Filter_Context *ctx)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *fb;
   Eina_List *li, *li2;
   unsigned w, h;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
   w = ctx->w;
   h = ctx->h;

   XDBG("Allocating all buffers based on output size %ux%u", w, h);

   EINA_LIST_FOREACH(ctx->buffers, li, fb)
     fb->cleanup = EINA_TRUE;

   EINA_INLIST_FOREACH(ctx->commands, cmd)
     {
        Evas_Filter_Fill_Mode fillmode = cmd->draw.fillmode;
        Evas_Filter_Buffer *in, *out;

        in = cmd->input;
        EINA_SAFETY_ON_NULL_GOTO(in, alloc_fail);

        in->cleanup = EINA_FALSE;
        if (!in->w && !in->h)
          {
             in->w = w;
             in->h = h;
          }

        if (cmd->mask)
          cmd->mask->cleanup = EINA_FALSE;

        // FIXME: No need for stretch buffers with GL!
        if (fillmode & EVAS_FILTER_FILL_MODE_STRETCH_XY)
          {
             unsigned sw = w, sh = h;

             switch (cmd->mode)
               {
                case EVAS_FILTER_MODE_BLEND:
                  in = cmd->input;
                  break;
                case EVAS_FILTER_MODE_BUMP:
                case EVAS_FILTER_MODE_DISPLACE:
                case EVAS_FILTER_MODE_MASK:
                  in = cmd->mask;
                  break;
                default:
                  CRI("Invalid fillmode set for command %d", cmd->mode);
                  return EINA_FALSE;
               }

             EINA_SAFETY_ON_NULL_GOTO(in, alloc_fail);
             if (in->w) sw = in->w;
             if (in->h) sh = in->h;

             if ((sw != w) || (sh != h))
               {
                  if (fillmode & EVAS_FILTER_FILL_MODE_STRETCH_X)
                    sw = w;
                  if (fillmode & EVAS_FILTER_FILL_MODE_STRETCH_Y)
                    sh = h;

                  fb = _buffer_alloc_new(ctx, sw, sh, in->alpha_only, 1, 1);
                  XDBG("Allocated temporary buffer #%d of size %ux%u %s",
                       fb ? fb->id : -1, sw, sh, in->alpha_only ? "alpha" : "rgba");
                  if (!fb) goto alloc_fail;
                  fb->transient = EINA_TRUE;
                  fb->cleanup = EINA_FALSE;
               }
          }

        if (cmd->draw.need_temp_buffer)
          {
             unsigned sw = w, sh = h;

             in = cmd->input;
             if (in->w) sw = in->w;
             if (in->h) sh = in->h;

             fb = _buffer_alloc_new(ctx, sw, sh, in->alpha_only, 1, 1);
             XDBG("Allocated temporary buffer #%d of size %ux%u %s",
                  fb ? fb->id : -1, sw, sh, in->alpha_only ? "alpha" : "rgba");
             if (!fb) goto alloc_fail;
             fb->transient = EINA_TRUE;
             fb->cleanup = EINA_FALSE;
          }

        out = cmd->output;
        out->cleanup = EINA_FALSE;
        if (!out->w && !out->h)
          {
             out->w = w;
             out->h = h;
          }
     }

   EINA_LIST_FOREACH(ctx->buffers, li, fb)
     {
        Eina_Bool render = EINA_FALSE, draw = EINA_FALSE;

        if (fb->source)
          {
             fb->cleanup = EINA_FALSE;
             continue;
          }
        if (fb->buffer || fb->cleanup)
          continue;

        if (!fb->w && !fb->h)
          {
             ERR("Size of buffer %d should be known at this point. Is this a dangling buffer?", fb->id);
             continue;
          }

        // Skip input buffer, allocate it in input render phase
        if (fb->id == EVAS_FILTER_BUFFER_INPUT_ID)
          continue;

        render = fb->is_render || fb->transient;
        draw |= (fb->id == EVAS_FILTER_BUFFER_OUTPUT_ID);

        fb->buffer = _ector_buffer_create(fb, render, draw);
        XDBG("Allocated buffer #%d of size %ux%u %s: %p",
             fb->id, fb->w, fb->h, fb->alpha_only ? "alpha" : "rgba", fb->buffer);
        if (!fb->buffer) goto alloc_fail;
     }

   EINA_LIST_FOREACH_SAFE(ctx->buffers, li, li2, fb)
     {
        if (fb->cleanup)
          {
             XDBG("Cleanup buffer #%d %dx%d %s", fb->id, fb->w, fb->h, fb->alpha_only ? "alpha" : "rgba");
             ctx->buffers = eina_list_remove_list(ctx->buffers, li);
             _buffer_free(fb);
          }
     }

   return EINA_TRUE;

alloc_fail:
   ERR("Buffer allocation failed! Context size: %dx%d", w, h);
   return EINA_FALSE;
}

int
evas_filter_buffer_empty_new(Evas_Filter_Context *ctx, int w, int h, Eina_Bool alpha_only)
{
   Evas_Filter_Buffer *fb;
   Eina_List *li;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, -1);

   EINA_LIST_FOREACH(ctx->buffers, li, fb)
     {
        if ((fb->alpha_only == alpha_only) &&
            (fb->w == w) && (fb->h == h) && !fb->dirty && !fb->used)
          {
             fb->used = EINA_TRUE;
             return fb->id;
          }
     }

   fb = _buffer_empty_new(ctx, w, h, alpha_only, EINA_FALSE);
   if (!fb) return -1;

   fb->used = EINA_TRUE;
   return fb->id;
}

int
evas_filter_buffer_proxy_new(Evas_Filter_Context *ctx, Evas_Filter_Proxy_Binding *pb,
                             int *w, int *h)
{
   Evas_Object_Protected_Data *source;
   Evas_Filter_Buffer *fb;
   Eina_List *li;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(pb, -1);

   source = efl_data_scope_get(pb->eo_source, EFL_CANVAS_OBJECT_CLASS);
   if (!source) return -1;

   // FIXME: This is not true if the source is an evas image
   *w = source->cur->geometry.w;
   *h = source->cur->geometry.h;

   EINA_LIST_FOREACH(ctx->buffers, li, fb)
     {
        if (pb->eo_source == fb->source)
          {
             if (fb->used) return -1;
             if (fb->alpha_only) return -1;
             if (!eina_streq(pb->name, fb->source_name)) return -1;
             if ((*w != fb->w) || (*h != fb->h)) return -1;

             fb->used = EINA_TRUE;
             return fb->id;
          }
     }

   fb = _buffer_empty_new(ctx, *w, *h, EINA_FALSE, EINA_FALSE);
   if (!fb) return -1;

   fb->source = efl_ref(pb->eo_source);
   fb->source_name = eina_stringshare_add(pb->name);

   fb->used = EINA_TRUE;
   return fb->id;
}

static Evas_Filter_Buffer *
_buffer_alloc_new(Evas_Filter_Context *ctx, int w, int h, Eina_Bool alpha_only,
                  Eina_Bool render, Eina_Bool draw)
{
   Evas_Filter_Buffer *fb;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
   EINA_SAFETY_ON_FALSE_RETURN_VAL(w > 0 && h > 0, NULL);

   fb = calloc(1, sizeof(Evas_Filter_Buffer));
   if (!fb) return NULL;

   fb->id = ++(ctx->last_buffer_id);
   fb->ctx = ctx;
   fb->w = w;
   fb->h = h;
   fb->alpha_only = alpha_only;
   fb->is_render = render;
   fb->buffer = _ector_buffer_create(fb, render, draw);
   if (!fb->buffer)
     {
        ERR("Failed to create ector buffer!");
        _free(fb);
        return NULL;
     }

   ctx->buffers = eina_list_append(ctx->buffers, fb);
   return fb;
}

static void
_buffer_free(Evas_Filter_Buffer *fb)
{
   _filter_buffer_backing_free(fb);
   eina_stringshare_del(fb->source_name);
   efl_unref(fb->source);
   _free(fb);
}

Evas_Filter_Buffer *
_filter_buffer_get(Evas_Filter_Context *ctx, int bufid)
{
   Evas_Filter_Buffer *buffer;
   Eina_List *l;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   EINA_LIST_FOREACH(ctx->buffers, l, buffer)
     if (buffer->id == bufid) return buffer;

   return NULL;
}

void *
evas_filter_buffer_backing_get(Evas_Filter_Context *ctx, int bufid, Eina_Bool render)
{
   Evas_Filter_Buffer *fb;

   fb = _filter_buffer_get(ctx, bufid);
   if (!fb) return NULL;

   if (!fb->buffer)
     evas_filter_buffer_backing_set(ctx, bufid, NULL);

   if (render)
     return evas_ector_buffer_render_image_get(fb->buffer); // ref++
   else
     return evas_ector_buffer_drawable_image_get(fb->buffer); // ref++
}

Eina_Bool
evas_filter_buffer_backing_set(Evas_Filter_Context *ctx, int bufid,
                               void *engine_buffer)
{
   Evas_Filter_Buffer *fb;
   Eina_Bool ret = EINA_FALSE;
   Eo *buffer = NULL;

   fb = _filter_buffer_get(ctx, bufid);
   if (!fb) return EINA_FALSE;

   if (!engine_buffer)
     {
        buffer = _ector_buffer_create(fb, fb->is_render, EINA_FALSE);
        XDBG("Allocated buffer #%d of size %ux%u %s: %p",
             fb->id, fb->w, fb->h, fb->alpha_only ? "alpha" : "rgba", fb->buffer);
        ret = buffer ? EINA_TRUE : EINA_FALSE;
        goto end;
     }

   if (fb->is_render) goto end;

   buffer = ENFN->ector_buffer_wrap(ENC, ctx->evas->evas, engine_buffer);
   if (!buffer) return EINA_FALSE;

   ret = EINA_TRUE;

end:
   if (fb->buffer != buffer) _buffer_del((Eo *)fb->buffer);
   fb->buffer = buffer;
   return ret;
}

static Evas_Filter_Command *
_command_new(Evas_Filter_Context *ctx, Evas_Filter_Mode mode,
             Evas_Filter_Buffer *input, Evas_Filter_Buffer *mask,
             Evas_Filter_Buffer *output)
{
   Evas_Filter_Command *cmd;

   cmd = calloc(1, sizeof(Evas_Filter_Command));
   if (!cmd) return NULL;

   cmd->id = ++(ctx->last_command_id);
   cmd->ctx = ctx;
   cmd->mode = mode;
   cmd->input = input;
   cmd->mask = mask;
   cmd->output = output;
   cmd->draw.R = 255;
   cmd->draw.G = 255;
   cmd->draw.B = 255;
   cmd->draw.A = 255;
   cmd->draw.rop = EFL_GFX_RENDER_OP_BLEND;
   if (output)
     {
        cmd->draw.output_was_dirty = output->dirty;
        output->is_render = EINA_TRUE;
        output->dirty = EINA_TRUE;
     }

   ctx->commands = eina_inlist_append(ctx->commands, EINA_INLIST_GET(cmd));
   return cmd;
}

static void
_command_del(Evas_Filter_Context *ctx, Evas_Filter_Command *cmd)
{
   if (!ctx || !cmd) return;
   ctx->commands = eina_inlist_remove(ctx->commands, EINA_INLIST_GET(cmd));
   switch (cmd->mode)
     {
      case EVAS_FILTER_MODE_CURVE: _free(cmd->curve.data); break;
      default: break;
     }
   _free(cmd);
}

Evas_Filter_Buffer *
evas_filter_temporary_buffer_get(Evas_Filter_Context *ctx, int w, int h,
                                 Eina_Bool alpha_only, Eina_Bool clean)
{
   Evas_Filter_Buffer *fb = NULL;
   Eina_List *l;

   EINA_LIST_FOREACH(ctx->buffers, l, fb)
     {
        if (fb->transient && !fb->locked && (fb->alpha_only == alpha_only)
            && (!clean || !fb->dirty))
          {
             if ((!w || (w == fb->w)) && (!h || (h == fb->h)))
               {
                  fb->locked = EINA_TRUE;
                  return fb;
               }
          }
     }

   if (ctx->running)
     {
        ERR("Can not create a new buffer while filter is running!");
        return NULL;
     }

   fb = _buffer_empty_new(ctx, w, h, alpha_only, EINA_TRUE);
   fb->locked = EINA_TRUE;
   fb->is_render = EINA_TRUE;
   XDBG("Created temporary buffer %d %s", fb->id, alpha_only ? "alpha" : "rgba");

   return fb;
}

static void
_filter_buffer_unlock_all(Evas_Filter_Context *ctx)
{
   Evas_Filter_Buffer *buf = NULL;
   Eina_List *l;

   EINA_LIST_FOREACH(ctx->buffers, l, buf)
     buf->locked = EINA_FALSE;
}

Evas_Filter_Command *
evas_filter_command_fill_add(Evas_Filter_Context *ctx, void *draw_context,
                             int bufid)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *buf = NULL;
   int R, G, B, A, cx, cy, cw, ch;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(draw_context, NULL);

   buf = _filter_buffer_get(ctx, bufid);
   if (!buf)
     {
        ERR("Buffer %d does not exist.", bufid);
        return NULL;
     }

   cmd = _command_new(ctx, EVAS_FILTER_MODE_FILL, buf, NULL, buf);
   if (!cmd) return NULL;

   ENFN->context_color_get(ENC, draw_context, &R, &G, &B, &A);
   DRAW_COLOR_SET(R, G, B, A);

   ENFN->context_clip_get(ENC, draw_context, &cx, &cy, &cw, &ch);
   DRAW_CLIP_SET(cx, cy, cw, ch);

   XDBG("Add fill %d with color(%d,%d,%d,%d)", buf->id, R, G, B, A);

   if (!R && !G && !B && !A)
     buf->dirty = EINA_FALSE;

   return cmd;
}

static Evas_Filter_Command *
evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx,
                                Evas_Filter_Buffer *in, Evas_Filter_Buffer *out,
                                Evas_Filter_Blur_Type type,
                                int rx, int ry, int ox, int oy, int count,
                                int R, int G, int B, int A, Eina_Bool alphaonly)
{
   Evas_Filter_Command *cmd = NULL;
   Evas_Filter_Buffer *dx_in, *dx_out, *dy_in, *dy_out, *tmp = NULL;
   int down_x = 1, down_y = 1;
   int pad_x = 0, pad_y = 0;
   double dx, dy;

   /* GL blur implementation:
    *
    * - Create intermediate buffer T1, T2
    * - Downscale input to buffer T1
    * - Apply X blur kernel from T1 to T2
    * - Apply Y blur kernel from T2 back to output
    *
    * In order to avoid sampling artifacts when moving or resizing a filtered
    * snapshot, we make sure that we always sample and scale based on the same
    * original pixels positions:
    * - Input pixels must be aligned to down_x,down_y boundaries
    * - T1/T2 buffer size is up to 1px larger than [input / scale_x,y]
    */

   dx = rx;
   dy = ry;
   dx_in = in;
   dy_out = out;

#if 1
   if (type == EVAS_FILTER_BLUR_DEFAULT)
     {
        // Apply downscaling for large enough radii only.
        down_x = 1 << evas_filter_smallest_pow2_larger_than(dx / 2) / 2;
        down_y = 1 << evas_filter_smallest_pow2_larger_than(dy / 2) / 2;

        // Downscaling to max 4 times for perfect picture quality (with
        // the standard scaling fragment shader and SHD_SAM22).
        if (down_x > 4) down_x = 4;
        if (down_y > 4) down_y = 4;

        if (down_x > 1 && down_y > 1)
          {
             int ww, hh;

             pad_x = ctx->x % down_x;
             pad_y = ctx->y % down_y;

             ww = ceil((double) ctx->w / down_x) + 1;
             hh = ceil((double) ctx->h / down_y) + 1;

             tmp = evas_filter_temporary_buffer_get(ctx, ww, hh, in->alpha_only, EINA_TRUE);
             if (!tmp) goto fail;

             dx /= (double) down_x;
             dy /= (double) down_y;

             XDBG("Add GL downscale %d (%dx%d) -> %d (%dx%d)", in->id, in->w, in->h, tmp->id, tmp->w, tmp->h);
             cmd = _command_new(ctx, EVAS_FILTER_MODE_BLEND, in, NULL, tmp);
             if (!cmd) goto fail;
             cmd->draw.fillmode = EVAS_FILTER_FILL_MODE_STRETCH_XY;
             cmd->draw.scale.down = EINA_TRUE;
             cmd->draw.scale.pad_x = pad_x;
             cmd->draw.scale.pad_y = pad_y;
             cmd->draw.scale.factor_x = down_x;
             cmd->draw.scale.factor_y = down_y;
             cmd->draw.alphaonly = alphaonly;
             dx_in = tmp;

             tmp = evas_filter_temporary_buffer_get(ctx, ww, hh, in->alpha_only, EINA_TRUE);
             if (!tmp) goto fail;
             dy_out = tmp;
          }
     }
#endif

   if (EINA_DBL_NONZERO(dx) && EINA_DBL_NONZERO(dy))
     {
        tmp = evas_filter_temporary_buffer_get(ctx, dx_in->w, dx_in->h, in->alpha_only, 1);
        if (!tmp) goto fail;
        dy_in = dx_out = tmp;
     }
   else
     {
        dx_out = out;
        dy_in = in;
     }

   if (EINA_DBL_NONZERO(dx))
     {
        XDBG("Add GL blur %d -> %d (%.2fx%.2f px)", dx_in->id, dx_out->id, dx, 0.0);
        cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, dx_in, NULL, dx_out);
        if (!cmd) goto fail;
        cmd->blur.type = type;
        cmd->blur.dx = dx;
        cmd->blur.count = count;
        cmd->draw.alphaonly = alphaonly;
     }

   if (EINA_DBL_NONZERO(dy))
     {
        XDBG("Add GL blur %d -> %d (%.2fx%.2f px)", dy_in->id, dy_out->id, 0.0, dy);
        cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, dy_in, NULL, dy_out);
        if (!cmd) goto fail;
        cmd->blur.type = type;
        cmd->blur.dy = dy;
        cmd->blur.count = count;
        cmd->draw.alphaonly = alphaonly;
     }

   EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, NULL);
   if (cmd->output != out)
     {
        XDBG("Add GL upscale %d (%dx%d) -> %d (%dx%d)",
             cmd->output->id, cmd->output->w, cmd->output->h, out->id, out->w, out->h);
        cmd = _command_new(ctx, EVAS_FILTER_MODE_BLEND, cmd->output, NULL, out);
        if (!cmd) goto fail;
        cmd->draw.fillmode = EVAS_FILTER_FILL_MODE_STRETCH_XY;
        cmd->draw.scale.down = EINA_FALSE;
        cmd->draw.scale.pad_x = pad_x;
        cmd->draw.scale.pad_y = pad_y;
        cmd->draw.scale.factor_x = down_x;
        cmd->draw.scale.factor_y = down_y;
        cmd->draw.alphaonly = alphaonly;
     }

   cmd->draw.ox = ox;
   cmd->draw.oy = oy;
   DRAW_COLOR_SET(R, G, B, A);
   cmd->draw.rop = (in == out) ? EFL_GFX_RENDER_OP_COPY : EFL_GFX_RENDER_OP_BLEND;

   _filter_buffer_unlock_all(ctx);
   return cmd;

fail:
   ERR("Failed to add blur");
   _filter_buffer_unlock_all(ctx);
   return NULL;
}

static Eina_Bool
_blur_support_gl(Evas_Filter_Context *ctx, Evas_Filter_Buffer *in, Evas_Filter_Buffer *out)
{
   Evas_Filter_Command cmd = {};

   cmd.input = in;
   cmd.output = out;
   cmd.mode = EVAS_FILTER_MODE_BLUR;
   cmd.ctx = ctx;
   cmd.blur.type = EVAS_FILTER_BLUR_GAUSSIAN;
   cmd.blur.dx = 5;

   return cmd.ENFN->gfx_filter_supports(_evas_engine_context(cmd.ctx->evas), &cmd) == EVAS_FILTER_SUPPORT_GL;
}

Evas_Filter_Command *
evas_filter_command_blur_add(Evas_Filter_Context *ctx, void *drawctx,
                             int inbuf, int outbuf, Evas_Filter_Blur_Type type,
                             int dx, int dy, int ox, int oy, int count,
                             Eina_Bool alphaonly)
{
   Evas_Filter_Buffer *in = NULL, *out = NULL, *tmp = NULL, *in_dy = NULL;
   Evas_Filter_Buffer *out_dy = NULL, *out_dx = NULL;
   Evas_Filter_Buffer *copybuf = NULL, *blendbuf = NULL;
   Evas_Filter_Command *cmd = NULL;
   int R, G, B, A, render_op;
   Eina_Bool override;
   DATA32 color;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(drawctx, NULL);

   if (dx < 0) dx = 0;
   if (dy < 0) dy = 0;
   if (!dx && !dy)
     {
        XDBG("Changing 0px blur into simple blend");
        return evas_filter_command_blend_add(ctx, drawctx, inbuf, outbuf, ox, oy, EVAS_FILTER_FILL_MODE_NONE, alphaonly);
     }

   in = _filter_buffer_get(ctx, inbuf);
   EINA_SAFETY_ON_FALSE_GOTO(in, fail);

   out = _filter_buffer_get(ctx, outbuf);
   EINA_SAFETY_ON_FALSE_GOTO(out, fail);

   ENFN->context_color_get(ENC, drawctx, &R, &G, &B, &A);
   color = ARGB_JOIN(A, R, G, B);
   if (!color)
     {
        DBG("Blur with transparent color. Nothing to do.");
        return _command_new(ctx, EVAS_FILTER_MODE_SKIP, NULL, NULL, NULL);
     }

   if (_blur_support_gl(ctx, in, out))
     return evas_filter_command_blur_add_gl(ctx, in, out, type, dx, dy, ox, oy,
                                            count, R, G, B, A, alphaonly);

   // Note (SW engine):
   // The basic blur operation overrides the pixels in the target buffer,
   // only supports one direction (X or Y) and no offset. As a consequence
   // most cases require intermediate work buffers.

   if (in == out) out->dirty = EINA_FALSE;

   render_op = ENFN->context_render_op_get(ENC, drawctx);
   override = (render_op == EVAS_RENDER_COPY);

   switch (type)
     {
      case EVAS_FILTER_BLUR_GAUSSIAN:
        count = 1;
        break;

      case EVAS_FILTER_BLUR_BOX:
        count = MIN(MAX(1, count), 6);
        break;

      case EVAS_FILTER_BLUR_DEFAULT:
        {
           /* In DEFAULT mode we cheat, depending on the size of the kernel:
            * For 1px to 2px, use true Gaussian blur.
            * For 3px to 6px, use two Box blurs.
            * For more than 6px, use three Box blurs.
            * This will give both nicer and MUCH faster results than Gaussian.
            *
            * NOTE: This step should be avoided in GL.
            */

           int tmp_out = outbuf;
           int tmp_in = inbuf;
           int tmp_ox = ox;
           int tmp_oy = oy;

           // For 2D blur: create intermediate buffer
           if (dx && dy)
             {
                tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 1);
                if (!tmp) goto fail;
                tmp_in = tmp_out = tmp->id;
                tmp_ox = tmp_oy = 0;
             }

           // X box blur
           if (dx)
             {
                if (dx <= 2)
                  type = EVAS_FILTER_BLUR_GAUSSIAN;
                else
                  type = EVAS_FILTER_BLUR_BOX;

                if (dy) ENFN->context_color_set(ENC, drawctx, 255, 255, 255, 255);
                cmd = evas_filter_command_blur_add(ctx, drawctx, inbuf, tmp_out,
                                                   type, dx, 0, tmp_ox, tmp_oy, 0,
                                                   alphaonly);
                if (!cmd) goto fail;
                cmd->blur.auto_count = EINA_TRUE;
                if (dy) ENFN->context_color_set(ENC, drawctx, R, G, B, A);
             }

           // Y box blur
           if (dy)
             {
                if (dy <= 2)
                  type = EVAS_FILTER_BLUR_GAUSSIAN;
                else
                  type = EVAS_FILTER_BLUR_BOX;

                if (dx && (inbuf == outbuf))
                  ENFN->context_render_op_set(ENC, drawctx, EVAS_RENDER_COPY);
                cmd = evas_filter_command_blur_add(ctx, drawctx, tmp_in, outbuf,
                                                   type, 0, dy, ox, oy, 0,
                                                   alphaonly);
                if (dx && (inbuf == outbuf))
                  ENFN->context_render_op_set(ENC, drawctx, render_op);
                if (!cmd) goto fail;
                cmd->blur.auto_count = EINA_TRUE;
             }

           return cmd;
        }

      default:
        CRI("Not implemented yet!");
        goto fail;
     }

   // For 2D blur: create intermediate buffer between X and Y passes
   if (dx && dy)
     {
        // If there's an offset: create intermediate buffer before offset blend
        if (ox || oy)
          {
             copybuf = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
             if (!copybuf) goto fail;
          }

        // Intermediate buffer between X and Y passes
        tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
        if (!tmp) goto fail;

        if (in == out)
          {
             // IN = OUT and 2-D blur. IN -blur-> TMP -blur-> IN.
             out_dx = tmp;
             in_dy = tmp;
             out_dy = copybuf ? copybuf : in;
          }
        else
          {
             // IN != OUT and 2-D blur. IN -blur-> TMP -blur-> OUT.
             out_dx = tmp;
             in_dy = tmp;
             out_dy = copybuf ? copybuf : out;
          }
     }
   else if (dx)
     {
        // X blur only
        if (in == out)
          {
             // IN = OUT and 1-D blur. IN -blur-> TMP -copy-> IN.
             tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
             if (!tmp) goto fail;
             copybuf = tmp;
             out_dx = tmp;
          }
        else if (ox || oy || (color != 0xFFFFFFFF))
          {
             // IN != OUT and 1-D blur. IN -blur-> TMP -blend-> OUT.
             tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
             if (!tmp) goto fail;
             blendbuf = tmp;
             out_dx = tmp;
          }
        else if (out->dirty)
          {
             // IN != OUT and 1-D blur. IN -blur-> TMP -blend-> OUT.
             tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
             if (!tmp) goto fail;
             blendbuf = tmp;
             out_dx = tmp;
          }
        else
          {
             // IN != OUT and 1-D blur. IN -blur-> OUT.
             out_dx = out;
          }
     }
   else
     {
        // Y blur only
        if (in == out)
          {
             // IN = OUT and 1-D blur. IN -blur-> TMP -copy-> IN.
             tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
             if (!tmp) goto fail;
             copybuf = tmp;
             in_dy = in;
             out_dy = tmp;
          }
        else if (ox || oy || (color != 0xFFFFFFFF))
          {
             // IN != OUT and 1-D blur. IN -blur-> TMP -blend-> IN.
             tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
             if (!tmp) goto fail;
             if (override)
               copybuf = tmp;
             else
               blendbuf = tmp;
             in_dy = in;
             out_dy = tmp;
          }
        else if (out->dirty && !override)
          {
             // IN != OUT and 1-D blur. IN -blur-> TMP -blend-> OUT.
             tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 0);
             if (!tmp) goto fail;
             blendbuf = tmp;
             in_dy = in;
             out_dy = tmp;
          }
        else
          {
             // IN != OUT and 1-D blur. IN -blur-> OUT.
             in_dy = in;
             out_dy = out;
          }
     }

   if (dx)
     {
        XDBG("Add horizontal blur %d -> %d (%dpx)", in->id, out_dx->id, dx);
        cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, in, NULL, out_dx);
        if (!cmd) goto fail;
        cmd->blur.type = type;
        cmd->blur.dx = dx;
        cmd->blur.dy = 0;
        cmd->blur.count = count;
        if (!dy) DRAW_COLOR_SET(R, G, B, A);
     }

   if (dy)
     {
        XDBG("Add vertical blur %d -> %d (%dpx)", in_dy->id, out_dy->id, dy);
        cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, in_dy, NULL, out_dy);
        if (!cmd) goto fail;
        cmd->blur.type = type;
        cmd->blur.dx = 0;
        cmd->blur.dy = dy;
        cmd->blur.count = count;
        DRAW_COLOR_SET(R, G, B, A);
     }

   if (blendbuf)
     {
        Evas_Filter_Command *blendcmd;

        XDBG("Add extra blend %d -> %d", blendbuf->id, out->id);
        blendcmd = evas_filter_command_blend_add(ctx, drawctx,
                                                 blendbuf->id, out->id, ox, oy,
                                                 EVAS_FILTER_FILL_MODE_NONE,
                                                 alphaonly);
        if (!blendcmd) goto fail;
        ox = oy = 0;
     }
   else if (copybuf)
     {
        Evas_Filter_Command *copycmd;

        XDBG("Add extra copy %d -> %d: offset: %d,%d", copybuf->id, out->id, ox, oy);
        ENFN->context_color_set(ENC, drawctx, 255, 255, 255, 255);
        ENFN->context_render_op_set(ENC, drawctx, EVAS_RENDER_COPY);
        copycmd = evas_filter_command_blend_add(ctx, drawctx,
                                                copybuf->id, out->id, ox, oy,
                                                EVAS_FILTER_FILL_MODE_NONE,
                                                alphaonly);
        ENFN->context_color_set(ENC, drawctx, R, G, B, A);
        ENFN->context_render_op_set(ENC, drawctx, render_op);
        if (!copycmd) goto fail;
        ox = oy = 0;
     }

   out->dirty = EINA_TRUE;
   _filter_buffer_unlock_all(ctx);
   return cmd;

fail:
   ERR("Failed to add blur");
   _filter_buffer_unlock_all(ctx);
   return NULL;
}

Evas_Filter_Command *
evas_filter_command_blend_add(Evas_Filter_Context *ctx, void *drawctx,
                              int inbuf, int outbuf, int ox, int oy,
                              Evas_Filter_Fill_Mode fillmode,
                              Eina_Bool alphaonly)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *in, *out;
   Eina_Bool copy;
   int R, G, B, A;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   if (inbuf == outbuf)
     {
        XDBG("Skipping NOP blend operation %d --> %d", inbuf, outbuf);
        return NULL;
     }

   in = _filter_buffer_get(ctx, inbuf);
   if (!in)
     {
        ERR("Buffer %d does not exist [input].", inbuf);
        return NULL;
     }

   out = _filter_buffer_get(ctx, outbuf);
   if (!out)
     {
        ERR("Buffer %d does not exist [output].", outbuf);
        return NULL;
     }

   cmd = _command_new(ctx, EVAS_FILTER_MODE_BLEND, in, NULL, out);
   if (!cmd) return NULL;

   if (ENFN->context_render_op_get(ENC, drawctx) == EVAS_RENDER_COPY)
     copy = EINA_TRUE;
   else
     copy = EINA_FALSE;

   ENFN->context_color_get(ENC, drawctx, &R, &G, &B, &A);
   DRAW_COLOR_SET(R, G, B, A);
   DRAW_FILL_SET(fillmode);
   cmd->draw.ox = ox;
   cmd->draw.oy = oy;
   cmd->draw.rop = copy ? EFL_GFX_RENDER_OP_COPY : EFL_GFX_RENDER_OP_BLEND;
   cmd->draw.alphaonly = alphaonly;
   cmd->draw.clip_use =
         !!ENFN->context_clip_get(ENC, drawctx,
                                  &cmd->draw.clip.x, &cmd->draw.clip.y,
                                  &cmd->draw.clip.w, &cmd->draw.clip.h);

   XDBG("Add %s %d -> %d: offset %d,%d, color: %d,%d,%d,%d",
        copy ? "copy" : "blend", in->id, out->id, ox, oy, R, G, B, A);
   if (cmd->draw.clip_use)
     XDBG("Draw clip: %d,%d,%d,%d", cmd->draw.clip.x, cmd->draw.clip.y,
         cmd->draw.clip.w, cmd->draw.clip.h);

   out->dirty = EINA_TRUE;
   return cmd;
}

Evas_Filter_Command *
evas_filter_command_grow_add(Evas_Filter_Context *ctx, void *draw_context,
                             int inbuf, int outbuf, int radius, Eina_Bool smooth,
                             Eina_Bool alphaonly)
{
   Evas_Filter_Command *blurcmd = NULL, *threshcmd = NULL, *blendcmd;
   Evas_Filter_Buffer *tmp, *in, *out;
   int diam = abs(radius) * 2 + 1;
   DATA8 curve[256] = {0};
   int tmin = 0, growbuf;

   EINA_SAFETY_ON_NULL_GOTO(ctx, fail);

   if (!radius)
     {
        XDBG("Changing 0px grow into simple blend");
        return evas_filter_command_blend_add(ctx, draw_context, inbuf, outbuf, 0, 0,
                                             EVAS_FILTER_FILL_MODE_NONE, alphaonly);
     }

   in = _filter_buffer_get(ctx, inbuf);
   EINA_SAFETY_ON_NULL_GOTO(in, fail);

   out = _filter_buffer_get(ctx, outbuf);
   EINA_SAFETY_ON_NULL_GOTO(out, fail);

   if ((inbuf != outbuf) && out->dirty)
     {
        tmp = evas_filter_temporary_buffer_get(ctx, in->w, in->h, in->alpha_only, 1);
        EINA_SAFETY_ON_NULL_GOTO(tmp, fail);
        growbuf = tmp->id;
     }
   else
     growbuf = outbuf;

   blurcmd = evas_filter_command_blur_add(ctx, draw_context, inbuf, growbuf,
                                          EVAS_FILTER_BLUR_DEFAULT,
                                          abs(radius), abs(radius), 0, 0, 0,
                                          alphaonly);
   EINA_SAFETY_ON_NULL_GOTO(blurcmd, fail);

   if (diam > 255) diam = 255;
   if (radius > 0)
     tmin = 255 / diam;
   else if (radius < 0)
     tmin = 256 - (255 / diam);

   if (!smooth)
     memset(curve + tmin, 255, 256 - tmin);
   else
     {
        int k, start, end, range;

        // This is pretty experimental.
        range = MAX(2, 12 - radius);
        start = ((tmin > range) ? (tmin - range) : 0);
        end = ((tmin < (256 - range)) ? (tmin + range) : 256);

        for (k = start; k < end; k++)
          curve[k] = ((k - start) * 255) / (end - start);
        if (end < 256)
          memset(curve + end, 255, 256 - end);
     }

   /* Use a temp buffer here. Becuase curve_add is using a temp buffer as well
      if inbuf and outbuf are same and doing blend_add. Then grow_add will do
      blend_add twice. Using a temp buffer will save a calling blend_add */
   tmp = evas_filter_temporary_buffer_get(ctx, in->w, in->h, in->alpha_only, 1);
   EINA_SAFETY_ON_NULL_GOTO(tmp, fail);

   threshcmd = evas_filter_command_curve_add(ctx, draw_context, growbuf, tmp->id,
                                             curve, EVAS_FILTER_CHANNEL_ALPHA);
   EINA_SAFETY_ON_NULL_GOTO(threshcmd, fail);

   blendcmd = evas_filter_command_blend_add(ctx, draw_context, tmp->id,
                                            outbuf, 0, 0,
                                            EVAS_FILTER_FILL_MODE_NONE,
                                            alphaonly);
   EINA_SAFETY_ON_NULL_GOTO(blendcmd, fail);

   return blurcmd;

fail:
   ERR("Failed to add grow");
   if (threshcmd) _command_del(ctx, threshcmd);
   if (blurcmd) _command_del(ctx, blurcmd);
   return NULL;
}

Evas_Filter_Command *
evas_filter_command_curve_add(Evas_Filter_Context *ctx,
                              void *draw_context EINA_UNUSED,
                              int inbuf, int outbuf, DATA8 *curve,
                              Evas_Filter_Channel channel)
{
   Evas_Filter_Command *cmd, *blendcmd;
   Evas_Filter_Buffer *in, *out, *tmp = NULL, *curve_out;
   DATA8 *copy;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(curve, NULL);

   in = _filter_buffer_get(ctx, inbuf);
   out = _filter_buffer_get(ctx, outbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(in, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);

   if (in->alpha_only != out->alpha_only)
     WRN("Incompatible formats for color curves, implicit conversion will be "
         "slow and may not produce the desired output.");

   if (in == out)
     {
        tmp = evas_filter_temporary_buffer_get(ctx, in->w, in->h, in->alpha_only, 1);
        if (!tmp) return NULL;
        curve_out = tmp;
     }
   else curve_out = out;

   XDBG("Add curve %d -> %d", in->id, curve_out->id);

   copy = malloc(256 * sizeof(DATA8));
   if (!copy) return NULL;

   cmd = _command_new(ctx, EVAS_FILTER_MODE_CURVE, in, NULL, curve_out);
   if (!cmd)
     {
        _free(copy);
        return NULL;
     }

   memcpy(copy, curve, 256 * sizeof(DATA8));
   cmd->curve.data = copy;
   if (cmd->input->alpha_only)
     cmd->curve.channel = EVAS_FILTER_CHANNEL_ALPHA;
   else
     cmd->curve.channel = channel;

   if (tmp)
     {
        blendcmd = evas_filter_command_blend_add(ctx, draw_context, curve_out->id,
                                                out->id, 0, 0,
                                                EVAS_FILTER_FILL_MODE_NONE,
                                                out->alpha_only);
        if (!blendcmd)
          {
             _command_del(ctx, cmd);
             return NULL;
          }
     }

   return cmd;
}

Evas_Filter_Command *
evas_filter_command_displacement_map_add(Evas_Filter_Context *ctx,
                                         void *draw_context EINA_UNUSED,
                                         int inbuf, int outbuf, int dispbuf,
                                         Evas_Filter_Displacement_Flags flags,
                                         int intensity,
                                         Evas_Filter_Fill_Mode fillmode)
{
   Evas_Filter_Buffer *in, *out, *map, *tmp = NULL, *disp_out;
   Evas_Filter_Command *cmd = NULL;
   Eina_Bool alphaonly = EINA_FALSE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
   EINA_SAFETY_ON_FALSE_RETURN_VAL(intensity >= 0, NULL);

   in = _filter_buffer_get(ctx, inbuf);
   out = _filter_buffer_get(ctx, outbuf);
   map = _filter_buffer_get(ctx, dispbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(in, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(map, NULL);

   if (in->alpha_only != out->alpha_only)
     DBG("Different color formats, implicit conversion may be slow");

   if (map->alpha_only)
     {
        WRN("Displacement map is not an RGBA buffer, X and Y axes will be "
            "displaced together.");
     }

   if (in == out)
     {
        tmp = evas_filter_temporary_buffer_get(ctx, in->w, in->h, in->alpha_only, 1);
        if (!tmp) return NULL;
        disp_out = tmp;
     }
   else disp_out = out;

   cmd = _command_new(ctx, EVAS_FILTER_MODE_DISPLACE, in, map, disp_out);
   if (!cmd) goto fail;

   DRAW_FILL_SET(fillmode);
   cmd->displacement.flags = flags & EVAS_FILTER_DISPLACE_BITMASK;
   cmd->displacement.intensity = intensity;
   cmd->draw.rop = _evas_to_gfx_render_op(ENFN->context_render_op_get(ENC, draw_context));

   if (tmp)
     {
        Evas_Filter_Command *fillcmd;

        fillcmd = evas_filter_command_blend_add(ctx, draw_context, disp_out->id,
                                                out->id, 0, 0,
                                                EVAS_FILTER_FILL_MODE_NONE,
                                                alphaonly);
        if (!fillcmd) goto fail;
     }

   _filter_buffer_unlock_all(ctx);
   return cmd;

fail:
   _filter_buffer_unlock_all(ctx);
   _command_del(ctx, cmd);
   return NULL;
}

Evas_Filter_Command *
evas_filter_command_mask_add(Evas_Filter_Context *ctx, void *draw_context,
                             int inbuf, int maskbuf, int outbuf,
                             Evas_Filter_Fill_Mode fillmode)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *in, *out, *mask;
   Efl_Gfx_Render_Op render_op;
   int R, G, B, A;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   render_op = _evas_to_gfx_render_op(ENFN->context_render_op_get(ENC, draw_context));
   ENFN->context_color_get(ENC, draw_context, &R, &G, &B, &A);

   in = _filter_buffer_get(ctx, inbuf);
   out = _filter_buffer_get(ctx, outbuf);
   mask = _filter_buffer_get(ctx, maskbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(in, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(mask, NULL);

   cmd = _command_new(ctx, EVAS_FILTER_MODE_MASK, in, mask, out);
   if (!cmd) return NULL;

   cmd->draw.rop = render_op;
   DRAW_COLOR_SET(R, G, B, A);
   DRAW_FILL_SET(fillmode);

   return cmd;
}

Evas_Filter_Command *
evas_filter_command_bump_map_add(Evas_Filter_Context *ctx,
                                 void *draw_context EINA_UNUSED,
                                 int inbuf, int bumpbuf, int outbuf,
                                 float xyangle, float zangle, float elevation,
                                 float sf,
                                 DATA32 black, DATA32 color, DATA32 white,
                                 Evas_Filter_Bump_Flags flags,
                                 Evas_Filter_Fill_Mode fillmode)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *in, *out, *map;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   in = _filter_buffer_get(ctx, inbuf);
   out = _filter_buffer_get(ctx, outbuf);
   map = _filter_buffer_get(ctx, bumpbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(in, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(map, NULL);

   if (!map->alpha_only)
     DBG("Bump map is not an Alpha buffer, implicit conversion may be slow");

   // FIXME: Boo!
   if (!in->alpha_only)
     WRN("RGBA bump map support is not implemented! This will trigger conversion.");

   // FIXME: Must ensure in != out
   EINA_SAFETY_ON_FALSE_RETURN_VAL(in != out, NULL);
   EINA_SAFETY_ON_FALSE_RETURN_VAL(map != out, NULL);

   cmd = _command_new(ctx, EVAS_FILTER_MODE_BUMP, in, map, out);
   if (!cmd) return NULL;

   DRAW_FILL_SET(fillmode);
   cmd->bump.xyangle = xyangle;
   cmd->bump.zangle = zangle;
   cmd->bump.specular_factor = sf;
   cmd->bump.dark = black;
   cmd->bump.color = color;
   cmd->bump.white = white;
   cmd->bump.elevation = elevation;
   cmd->bump.compensate = !!(flags & EVAS_FILTER_BUMP_COMPENSATE);

   return cmd;
}

Evas_Filter_Command *
evas_filter_command_transform_add(Evas_Filter_Context *ctx,
                                  void *draw_context EINA_UNUSED,
                                  int inbuf, int outbuf,
                                  Evas_Filter_Transform_Flags flags,
                                  int ox, int oy)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *in, *out;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   in = _filter_buffer_get(ctx, inbuf);
   out = _filter_buffer_get(ctx, outbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(in, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);

   cmd = _command_new(ctx, EVAS_FILTER_MODE_TRANSFORM, in, NULL, out);
   if (!cmd) return NULL;

   DRAW_COLOR_SET(255, 255, 255, 255);
   cmd->transform.flags = flags;
   cmd->draw.ox = ox;
   cmd->draw.oy = oy;

   if (in->alpha_only == out->alpha_only)
     {
        DBG("Incompatible buffer formats, will trigger implicit conversion.");
        cmd->draw.rop = EFL_GFX_RENDER_OP_COPY;
     }
   else
     cmd->draw.rop = EFL_GFX_RENDER_OP_BLEND;

   return cmd;
}

Evas_Filter_Command *
evas_filter_command_grayscale_add(Evas_Filter_Context *ctx,
                                  void *draw_context EINA_UNUSED,
                                  int inbuf, int outbuf)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *in, *out;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   in = _filter_buffer_get(ctx, inbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(in, NULL);

   out = _filter_buffer_get(ctx, outbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);

   cmd = _command_new(ctx, EVAS_FILTER_MODE_GRAYSCALE, in, NULL, out);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);

   return cmd;
}

Evas_Filter_Command *
evas_filter_command_inverse_color_add(Evas_Filter_Context *ctx,
                                     void *draw_context EINA_UNUSED,
                                     int inbuf, int outbuf)
{
   Evas_Filter_Command *cmd;
   Evas_Filter_Buffer *in, *out;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   in = _filter_buffer_get(ctx, inbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(in, NULL);

   out = _filter_buffer_get(ctx, outbuf);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);

   cmd = _command_new(ctx, EVAS_FILTER_MODE_INVERSE_COLOR, in, NULL, out);
   EINA_SAFETY_ON_NULL_RETURN_VAL(out, NULL);

   return cmd;
}

void
evas_filter_context_obscured_region_set(Evas_Filter_Context *ctx, Eina_Rectangle rect)
{
   ctx->obscured.real = rect;
}

/* Final target */
Eina_Bool
evas_filter_target_set(Evas_Filter_Context *ctx, void *draw_context,
                       void *surface, int x, int y, const RGBA_Map *map)
{
   void *mask = NULL;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);

   ctx->target.surface = ENFN->image_ref(ENC, surface);
   ctx->target.x = x;
   ctx->target.y = y;
   ctx->target.clip_use = ENFN->context_clip_get
         (ENC, draw_context, &ctx->target.cx, &ctx->target.cy,
          &ctx->target.cw, &ctx->target.ch);
   ctx->target.color_use = ENFN->context_multiplier_get
         (ENC, draw_context, &ctx->target.r, &ctx->target.g,
          &ctx->target.b, &ctx->target.a);
   if (ctx->target.r == 255 && ctx->target.g == 255 &&
       ctx->target.b == 255 && ctx->target.a == 255)
     ctx->target.color_use = EINA_FALSE;
   ctx->target.rop = ENFN->context_render_op_get(ENC, draw_context);

   _free(ctx->target.map);
   if (!map) ctx->target.map = NULL;
   else
     {
        size_t len = sizeof(RGBA_Map) + sizeof(RGBA_Map_Point) * (map->count - 1);
        ctx->target.map = malloc(len);
        memcpy(ctx->target.map, map, len);
     }

   ENFN->context_clip_image_get
      (ENC, draw_context, &mask, &ctx->target.mask_x, &ctx->target.mask_y);
   if (ctx->target.mask)
     ctx->evas->engine.func->image_free(_evas_engine_context(ctx->evas), ctx->target.mask);
   ctx->target.mask = mask; // FIXME: why no ref???

   return EINA_TRUE;
}

static Eina_Bool
_filter_target_render(void *engine, void *output, Evas_Filter_Context *ctx)
{
   Evas_Filter_Buffer *src;
   void *drawctx, *image = NULL, *surface;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx->target.surface, EINA_FALSE);

   drawctx = ENFN->context_new(engine);
   surface = ctx->target.surface;

   src = _filter_buffer_get(ctx, EVAS_FILTER_BUFFER_OUTPUT_ID);
   EINA_SAFETY_ON_NULL_RETURN_VAL(src, EINA_FALSE);

   image = evas_ector_buffer_drawable_image_get(src->buffer);
   EINA_SAFETY_ON_NULL_GOTO(image, fail);

   // FIXME: Use ector buffer RENDERER here

   if (ctx->target.clip_use)
     {
        ENFN->context_clip_set(engine, drawctx, ctx->target.cx, ctx->target.cy,
                               ctx->target.cw, ctx->target.ch);
     }

   if (ctx->target.color_use)
     {
        ENFN->context_multiplier_set(engine, drawctx,
                                     ctx->target.r, ctx->target.g,
                                     ctx->target.b, ctx->target.a);
     }

   if (ctx->target.mask)
     {
        ENFN->context_clip_image_set(engine, drawctx, ctx->target.mask,
                                     ctx->target.mask_x, ctx->target.mask_y,
                                     ctx->evas, EINA_FALSE);
     }

   ENFN->context_render_op_set(engine, drawctx, ctx->target.rop);
   if (ctx->target.map)
     {
        ENFN->image_map_draw(engine, output, drawctx, surface, image,
                             ctx->target.map, EINA_TRUE, 0, EINA_FALSE);
     }
   else
     {
        ENFN->image_draw(engine, output, drawctx, surface, image,
                         0, 0, src->w, src->h,
                         ctx->target.x, ctx->target.y, src->w, src->h,
                         EINA_TRUE, EINA_FALSE);
     }

   ENFN->context_free(engine, drawctx);
   evas_ector_buffer_engine_image_release(src->buffer, image);

   ENFN->image_free(engine, surface);
   ctx->target.surface = NULL;

   return EINA_TRUE;

fail:
   ENFN->image_free(engine, surface);
   ctx->target.surface = NULL;

   ERR("Failed to render filter to target canvas!");
   return EINA_FALSE;
}


/* Font drawing stuff */
Eina_Bool
evas_filter_font_draw(Evas_Filter_Context *ctx,
                      void *engine, void *output, void *draw_context, int bufid,
                      Evas_Font_Set *font, int x, int y,
                      Evas_Text_Props *text_props, Eina_Bool do_async)
{
   Eina_Bool async_unref;
   Evas_Filter_Buffer *fb;
   void *surface = NULL;

   fb = _filter_buffer_get(ctx, bufid);
   EINA_SAFETY_ON_NULL_RETURN_VAL(fb, EINA_FALSE);

   surface = evas_filter_buffer_backing_get(ctx, bufid, EINA_TRUE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(surface, EINA_FALSE);

   // Copied from evas_font_draw_async_check
   async_unref = ENFN->font_draw(engine, output, draw_context, surface,
                                 font, x, y, fb->w, fb->h, fb->w, fb->h,
                                 text_props, do_async);
   if (do_async && async_unref)
     {
        evas_common_font_glyphs_ref(text_props->glyphs);
        evas_unref_queue_glyph_put(ctx->evas, text_props->glyphs);
     }

   evas_ector_buffer_engine_image_release(fb->buffer, surface);
   return EINA_TRUE;
}

/* Clip full input rect (0, 0, sw, sh) to target (dx, dy, dw, dh)
 * and get source's clipped sx, sy as well as destination x, y, cols and rows */
void
_clip_to_target(int *sx /* OUT */, int *sy /* OUT */, int sw, int sh,
                int ox, int oy, int dw, int dh,
                int *dx /* OUT */, int *dy /* OUT */,
                int *rows /* OUT */, int *cols /* OUT */)
{
   if (ox > 0)
     {
        (*sx) = 0;
        (*dx) = ox;
        (*cols) = sw;
        if (((*dx) + (*cols)) > (dw))
          (*cols) = dw - (*dx);
     }
   else if (ox < 0)
     {
        (*dx) = 0;
        (*sx) = (-ox);
        (*cols) = sw - (*sx);
        if ((*cols) > dw) (*cols) = dw;
     }
   else
     {
        (*sx) = 0;
        (*dx) = 0;
        (*cols) = sw;
        if ((*cols) > dw) (*cols) = dw;
     }

   if (oy > 0)
     {
        (*sy) = 0;
        (*dy) = oy;
        (*rows) = sh;
        if (((*dy) + (*rows)) > (dh))
          (*rows) = dh - (*dy);
     }
   else if (oy < 0)
     {
        (*dy) = 0;
        (*sy) = (-oy);
        (*rows) = sh - (*sy);
        if ((*rows) > dh) (*rows) = dh;
     }
   else
     {
        (*sy) = 0;
        (*dy) = 0;
        (*rows) = sh;
        if ((*rows) > dh) (*rows) = dh;
     }
   if ((*cols) < 0) *cols = 0;
   if ((*rows) < 0) *rows = 0;
}

#ifdef FILTERS_DEBUG
static const char *
_filter_name_get(int mode)
{
#define FNAME(a) case EVAS_FILTER_MODE_ ## a: return "EVAS_FILTER_MODE_" #a
   switch (mode)
     {
      FNAME(SKIP);
      FNAME(BLEND);
      FNAME(BLUR);
      FNAME(CURVE);
      FNAME(DISPLACE);
      FNAME(MASK);
      FNAME(BUMP);
      FNAME(FILL);
      default: return "INVALID";
     }
#undef FNAME
}
#endif

static Eina_Bool
_filter_command_run(Evas_Filter_Command *cmd)
{
   Evas_Filter_Support support = EVAS_FILTER_SUPPORT_NONE;

   if (cmd->mode == EVAS_FILTER_MODE_SKIP)
     return EINA_TRUE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input, EINA_FALSE);

#ifdef FILTERS_DEBUG
   XDBG("Command %d (%s): %d [%d] --> %d",
       cmd->id, _filter_name_get(cmd->mode),
       cmd->input->id, cmd->mask ? cmd->mask->id : 0, cmd->output->id);
#endif

   if (!cmd->input->w && !cmd->input->h
       && (cmd->mode != EVAS_FILTER_MODE_FILL))
     {
        XDBG("Skipping processing of empty input buffer (size 0x0)");
        return EINA_TRUE;
     }

   if ((cmd->output->w <= 0) || (cmd->output->h <= 0))
     {
        ERR("Output size invalid: %dx%d", cmd->output->w, cmd->output->h);
        return EINA_FALSE;
     }

   support = cmd->ENFN->gfx_filter_supports(CMD_ENC, cmd);
   if (support == EVAS_FILTER_SUPPORT_NONE)
     {
        ERR("No function to process this filter (mode %d)", cmd->mode);
        return EINA_FALSE;
     }

   return cmd->ENFN->gfx_filter_process(CMD_ENC, cmd);
}

static Eina_Bool
_filter_chain_run(void *engine, void *output, Evas_Filter_Context *ctx)
{
   Evas_Filter_Command *cmd;
   Eina_Bool ok = EINA_FALSE;

   DEBUG_TIME_BEGIN();

   EINA_INLIST_FOREACH(ctx->commands, cmd)
     {
        ok = _filter_command_run(cmd);
        if (!ok)
          {
             ERR("Filter processing failed!");
             goto end;
          }
     }

   ok = _filter_target_render(engine, output, ctx);

end:
   ctx->running = EINA_FALSE;
   DEBUG_TIME_END();

   ctx->post_run.cb(ctx, ctx->post_run.data, ok);
   return ok;
}

typedef struct _Filter_Thread_Data Filter_Thread_Data;
struct _Filter_Thread_Data
{
   void *engine;
   void *output;
   Evas_Filter_Context *ctx;
};

static void
_filter_thread_run_cb(void *data)
{
   Filter_Thread_Data *ftd = data;

   _filter_chain_run(ftd->engine, ftd->output, ftd->ctx);
   _free(ftd);
}

static void
_filter_obscured_region_calc(Evas_Filter_Context *ctx)
{
   Eina_Rectangle rect = ctx->obscured.real;

   // left
   if (rect.x > 0)
     {
        rect.x += ctx->pad.calculated.l;
        rect.w -= ctx->pad.calculated.l;
     }
   else
     {
        rect.w -= (-rect.x);
        rect.x = 0;
     }
   if (rect.w < 0) rect.w = 0;

   // right
   if ((rect.x + rect.w) <= ctx->w)
     rect.w -= ctx->pad.calculated.r;
   else
     rect.w = ctx->w - rect.x;

   // top
   if (rect.y > 0)
     {
        rect.y += ctx->pad.calculated.t;
        rect.h -= ctx->pad.calculated.t;
     }
   else
     {
        rect.h -= (-rect.y);
        rect.y = 0;
     }
   if (rect.h < 0) rect.h = 0;

   // bottom
   if ((rect.y + rect.h) <= ctx->h)
     rect.h -= ctx->pad.calculated.b;
   else
     rect.h = ctx->h - rect.y;

   if ((rect.w <= 0) || (rect.h <= 0))
     memset(&rect, 0, sizeof(rect));

   ctx->obscured.effective = rect;
}

Eina_Bool
evas_filter_context_run(void *engine, void *output, Evas_Filter_Context *ctx)
{
   evas_filter_context_ref(ctx);
   _filter_obscured_region_calc(ctx);

   ctx->run_count++;
   ctx->running = EINA_TRUE;
   if (ctx->async)
     {
        Filter_Thread_Data *ftd;

        ftd = calloc(1, sizeof(*ftd));
        ftd->engine = engine;
        ftd->output = output;
        ftd->ctx = ctx;

        evas_thread_queue_flush(_filter_thread_run_cb, ftd);
        return EINA_TRUE;
     }

   return _filter_chain_run(engine, output, ctx);
}


/* Logging */

static int init_cnt = 0;
int _evas_filter_log_dom = 0;

void
evas_filter_init(void)
{
   if ((init_cnt++) > 0) return;
   _evas_filter_log_dom = eina_log_domain_register("evas_filter", EVAS_FILTER_LOG_COLOR);
   evas_filter_mixin_init();
}

void
evas_filter_shutdown(void)
{
   if ((--init_cnt) > 0) return;
   evas_filter_parser_shutdown();
   evas_filter_mixin_shutdown();
   eina_log_domain_unregister(_evas_filter_log_dom);
   _evas_filter_log_dom = 0;
}
