Skip to content

Commit

Permalink
Fixing fill and watermark filters to correctly handle images with alp…
Browse files Browse the repository at this point in the history
…ha channels.
  • Loading branch information
cezarsa committed Jun 21, 2012
1 parent 6854d38 commit 80a6877
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 12 deletions.
8 changes: 6 additions & 2 deletions thumbor/engines/graphicsmagick.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,12 @@ def draw_rectangle(self, x, y, width, height):
draw.rectangle(x, y, x + width, y + height)
self.image.draw(draw.drawer)

def paste(self, other_engine, pos):
self.image.composite(other_engine.image, pos[0],pos[1], co.OverCompositeOp)
def paste(self, other_engine, pos, merge=True):
self.enable_alpha()
other_engine.enable_alpha()

operator = co.OverCompositeOp if merge else co.CopyCompositeOp
self.image.composite(other_engine.image, pos[0],pos[1], operator)

def enable_alpha(self):
self.image.type(ImageType.TrueColorMatteType)
Expand Down
17 changes: 12 additions & 5 deletions thumbor/engines/pil.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from thumbor.engines import BaseEngine
from thumbor.utils import logger
from thumbor.ext.filters import _composite

FORMATS = {
'.jpg': 'JPEG',
Expand All @@ -27,7 +28,7 @@
class Engine(BaseEngine):

def gen_image(self, size, color):
img = Image.new("RGB", size, color)
img = Image.new("RGBA", size, color)
return img

def create_image(self, buffer):
Expand All @@ -38,6 +39,7 @@ def create_image(self, buffer):
def draw_rectangle(self, x, y, width, height):
d = ImageDraw.Draw(self.image)
d.rectangle([x, y, x + width, y + height])

del d

def resize(self, width, height):
Expand Down Expand Up @@ -101,16 +103,21 @@ def set_image_data(self, data):
def get_image_mode(self):
return self.image.mode

def paste(self, other_engine, pos):
def paste(self, other_engine, pos, merge=True):
self.enable_alpha()
other_engine.enable_alpha()

image = self.image
other_image = other_engine.image

layer = Image.new('RGBA', image.size, (0,0,0,0))
layer.paste(other_image, pos)
self.image = Image.composite(layer, image, layer)
if merge:
sz = self.size
other_size = other_engine.size
imgdata = _composite.apply(self.get_image_mode(), self.get_image_data(), sz[0], sz[1],
other_engine.get_image_data(), other_size[0], other_size[1], pos[0], pos[1])
self.set_image_data(imgdata)
else:
image.paste(other_image, pos)

def enable_alpha(self):
if self.image.mode != 'RGBA':
Expand Down
100 changes: 100 additions & 0 deletions thumbor/ext/filters/_composite.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "filter.h"

static PyObject*
_composite_apply(PyObject *self, PyObject *args)
{
PyObject *py_image1 = NULL, *py_image2 = NULL, *image_mode = NULL,
*w1, *h1, *w2, *h2, *py_x, *py_y;

if (!PyArg_UnpackTuple(args, "apply", 9, 9, &image_mode, &py_image1, &w1, &h1, &py_image2, &w2, &h2, &py_x, &py_y)) {
return NULL;
}

char *image_mode_str = PyString_AsString(image_mode);

unsigned char *ptr1 = (unsigned char *) PyString_AsString(py_image1), *aux1 = NULL;
unsigned char *ptr2 = (unsigned char *) PyString_AsString(py_image2), *aux2 = NULL;

int width1 = (int) PyInt_AsLong(w1),
width2 = (int) PyInt_AsLong(w2),
height1 = (int) PyInt_AsLong(h1),
height2 = (int) PyInt_AsLong(h2),
x_pos = (int) PyInt_AsLong(py_x),
y_pos = (int) PyInt_AsLong(py_y);

int num_bytes = bytes_per_pixel(image_mode_str);
int r_idx = rgb_order(image_mode_str, 'R'),
g_idx = rgb_order(image_mode_str, 'G'),
b_idx = rgb_order(image_mode_str, 'B'),
a_idx = rgb_order(image_mode_str, 'A');


int r1, g1, b1, a1, r2, g2, b2, a2, x, y, start_x = 0, start_y = 0;

double delta, r, g, b, a;

if (x_pos < 0) {
start_x = -x_pos;
x_pos = 0;
}
if (y_pos < 0) {
start_y = -y_pos;
y_pos = 0;
}

for (y = start_y; y < height2; ++y) {
if (y_pos + y >= height1) {
break;
}
int line_offset1 = ((y_pos + y - start_y) * width1 * num_bytes),
line_offset2 = (y * width2 * num_bytes);

aux1 = ptr1 + line_offset1 + (x_pos * num_bytes);
aux2 = ptr2 + line_offset2 + (start_x * num_bytes);

for (x = start_x; x < width2; ++x, aux1 += num_bytes, aux2 += num_bytes) {
if (x_pos + x >= width1) {
break;
}

r1 = aux1[r_idx];
g1 = aux1[g_idx];
b1 = aux1[b_idx];
a1 = aux1[a_idx];

r2 = aux2[r_idx];
g2 = aux2[g_idx];
b2 = aux2[b_idx];
a2 = aux2[a_idx];

a1 = 255 - a1;
a2 = 255 - a2;

delta = (a2 / MAX_RGB_DOUBLE) * (a1 / MAX_RGB_DOUBLE);

a = MAX_RGB_DOUBLE * delta;

delta = 1.0 - delta;
delta = (delta <= SMALL_DOUBLE) ? 1.0 : (1.0 / delta);

r = delta * ALPHA_COMPOSITE_COLOR_CHANNEL(r2, a2, r1, a1);
g = delta * ALPHA_COMPOSITE_COLOR_CHANNEL(g2, a2, g1, a1);
b = delta * ALPHA_COMPOSITE_COLOR_CHANNEL(b2, a2, b1, a1);
a = 255.0 - a;

aux1[r_idx] = ADJUST_COLOR_DOUBLE(r);
aux1[g_idx] = ADJUST_COLOR_DOUBLE(g);
aux1[b_idx] = ADJUST_COLOR_DOUBLE(b);
aux1[a_idx] = ADJUST_COLOR_DOUBLE(a);
}

}

Py_INCREF(py_image1);
return py_image1;
}

FILTER_MODULE(_composite,
"apply(image_mode, buffer1, width1, height1, buffer2, width2, height2, pos_x, pos_y) -> string\n"
"Merges two images specified by buffer1 and buffer2, taking in consideration both alpha channels."
)
12 changes: 11 additions & 1 deletion thumbor/ext/filters/lib/image_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@

#include <math.h>

#define ADJUST_COLOR(c) ((c > 255) ? 255 : ((c < 0) ? 0 : c))
#define MAX_RGB_DOUBLE 255.0
#define MAX_RGB 255
#define SMALL_DOUBLE 1.0e-12

#define ADJUST_COLOR(c) ((c > MAX_RGB) ? MAX_RGB : ((c < 0) ? 0 : c))
#define ADJUST_COLOR_DOUBLE(c) ((int)((c > MAX_RGB_DOUBLE) ? MAX_RGB : ((c < 0.0) ? 0 : c)))

#define ALPHA_COMPOSITE_COLOR_CHANNEL(color1, alpha1, color2, alpha2) \
( ((1.0 - (alpha1 / MAX_RGB_DOUBLE)) * (double) color1) + \
((1.0 - (alpha2 / MAX_RGB_DOUBLE)) * (double) color2 * (alpha1 / MAX_RGB_DOUBLE)) )


static inline int
bytes_per_pixel(char *mode)
Expand Down
7 changes: 4 additions & 3 deletions thumbor/filters/fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ def fill(self, value):
self.fill_engine = self.engine.__class__(self.context)
bx = self.context.request.width if self.context.request.width != 0 else self.engine.image.size[0]
by = self.context.request.height if self.context.request.height != 0 else self.engine.image.size[1]
(ix, iy) = self.engine.image.size

(ix, iy) = self.engine.size

try:
self.fill_engine.image = self.fill_engine.gen_image((bx,by), value)
except (ValueError, RuntimeError):
Expand All @@ -26,6 +28,5 @@ def fill(self, value):
px = ( bx - ix ) / 2 #top left
py = ( by - iy ) / 2


self.fill_engine.paste(self.engine, (px, py))
self.fill_engine.paste(self.engine, (px, py), merge=False)
self.engine.image = self.fill_engine.image
3 changes: 2 additions & 1 deletion thumbor/filters/watermark.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def on_image_ready(self, buffer):
if inv_y:
y = (sz[1] - watermark_sz[1]) + y

self.engine.paste(self.watermark_engine, (x, y))
self.engine.paste(self.watermark_engine, (x, y), merge=True)

self.callback()

def on_fetch_done(self, buffer):
Expand Down

0 comments on commit 80a6877

Please sign in to comment.