magick: Compose support

This commit is contained in:
Kovid Goyal 2010-08-03 17:12:01 -06:00
parent 6842319911
commit 34d427dfd5
5 changed files with 213 additions and 37 deletions

View File

@ -14,31 +14,11 @@ _magick, _merr = plugins['magick']
if _magick is None: if _magick is None:
raise RuntimeError('Failed to load ImageMagick: '+_merr) raise RuntimeError('Failed to load ImageMagick: '+_merr)
# class ImageMagick {{{
_initialized = False
def initialize():
global _initialized
if not _initialized:
_magick.genesis()
_initialized = True
def finalize():
global _initialized
if _initialized:
_magick.terminus()
_initialized = False
class ImageMagick(object):
def __enter__(self):
initialize()
def __exit__(self, *args):
finalize()
# }}}
class Image(_magick.Image): class Image(_magick.Image):
def load(self, data):
return _magick.Image.load(self, bytes(data))
def open(self, path_or_file): def open(self, path_or_file):
data = path_or_file data = path_or_file
if hasattr(data, 'read'): if hasattr(data, 'read'):
@ -78,6 +58,19 @@ class Image(_magick.Image):
if len(ext) < 2: if len(ext) < 2:
raise ValueError('No format specified') raise ValueError('No format specified')
format = ext[1:] format = ext[1:]
format = format.upper()
with open(path, 'wb') as f: with open(path, 'wb') as f:
f.write(self.export(format)) f.write(self.export(format))
def compose(self, img, left=0, top=0, operation='OverCompositeOp'):
op = getattr(_magick, operation)
bounds = self.size
if left < 0 or top < 0 or left >= bounds[0] or top >= bounds[1]:
raise ValueError('left and/or top out of bounds')
_magick.Image.compose(self, img, int(left), int(top), op)
def create_canvas(width, height, bgcolor):
canvas = Image()
canvas.create_canvas(int(width), int(height), str(bgcolor))
return canvas

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from calibre.utils.magick import Image, create_canvas
def save_cover_data_to(data, path, bgcolor='white', resize_to=None):
'''
Saves image in data to path, in the format specified by the path
extension. Composes the image onto a blank canvas so as to
properly convert transparent images.
'''
img = Image()
img.load(data)
if resize_to is not None:
img.size = (resize_to[0], resize_to[1])
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
canvas.compose(img)
canvas.save(path)
def identify_data(data):
'''
Identify the image in data. Returns a 3-tuple
(width, height, format)
or raises an Exception if data is not an image.
'''
img = Image()
img.load(data)
width, height = img.size
fmt = img.format
return (width, height, fmt)
def identify(path):
'''
Identify the image at path. Returns a 3-tuple
(width, height, format)
or raises an Exception.
'''
data = open(path, 'rb').read()
return identify_data(data)

View File

@ -49,7 +49,7 @@ def get_value(const):
def main(): def main():
constants = [] constants = []
for x in ('resample', 'image', 'draw', 'distort'): for x in ('resample', 'image', 'draw', 'distort', 'composite'):
constants += list(parse_enums('magick/%s.h'%x)) constants += list(parse_enums('magick/%s.h'%x))
base = os.path.dirname(__file__) base = os.path.dirname(__file__)
constants = [ constants = [

View File

@ -5,6 +5,7 @@
#include "magick_constants.h" #include "magick_constants.h"
// magick_set_exception {{{
PyObject* magick_set_exception(MagickWand *wand) { PyObject* magick_set_exception(MagickWand *wand) {
ExceptionType ext; ExceptionType ext;
char *desc = MagickGetException(wand, &ext); char *desc = MagickGetException(wand, &ext);
@ -13,6 +14,7 @@ PyObject* magick_set_exception(MagickWand *wand) {
desc = MagickRelinquishMemory(desc); desc = MagickRelinquishMemory(desc);
return NULL; return NULL;
} }
// }}}
// Image object definition {{{ // Image object definition {{{
typedef struct { typedef struct {
@ -22,6 +24,10 @@ typedef struct {
} magick_Image; } magick_Image;
// Method declarations {{{
static PyObject* magick_Image_compose(magick_Image *self, PyObject *args, PyObject *kwargs);
// }}}
static void static void
magick_Image_dealloc(magick_Image* self) magick_Image_dealloc(magick_Image* self)
{ {
@ -48,6 +54,7 @@ magick_Image_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return (PyObject *)self; return (PyObject *)self;
} }
// Image.load {{{
static PyObject * static PyObject *
magick_Image_load(magick_Image *self, PyObject *args, PyObject *kwargs) { magick_Image_load(magick_Image *self, PyObject *args, PyObject *kwargs) {
const char *data; const char *data;
@ -56,9 +63,7 @@ magick_Image_load(magick_Image *self, PyObject *args, PyObject *kwargs) {
if (!PyArg_ParseTuple(args, "s#", &data, &dlen)) return NULL; if (!PyArg_ParseTuple(args, "s#", &data, &dlen)) return NULL;
Py_BEGIN_ALLOW_THREADS
res = MagickReadImageBlob(self->wand, data, dlen); res = MagickReadImageBlob(self->wand, data, dlen);
Py_END_ALLOW_THREADS
if (!res) if (!res)
return magick_set_exception(self->wand); return magick_set_exception(self->wand);
@ -66,6 +71,32 @@ magick_Image_load(magick_Image *self, PyObject *args, PyObject *kwargs) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
// }}}
// Image.create_canvas {{{
static PyObject *
magick_Image_create_canvas(magick_Image *self, PyObject *args, PyObject *kwargs)
{
Py_ssize_t width, height;
char *bgcolor;
PixelWand *pw;
MagickBooleanType res = MagickFalse;
if (!PyArg_ParseTuple(args, "nns", &width, &height, &bgcolor)) return NULL;
pw = NewPixelWand();
if (pw == NULL) return PyErr_NoMemory();
PixelSetColor(pw, bgcolor);
res = MagickNewImage(self->wand, width, height, pw);
pw = DestroyPixelWand(pw);
if (!res) return magick_set_exception(self->wand);
Py_RETURN_NONE;
}
// }}}
// Image.export {{{
static PyObject * static PyObject *
magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) { magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) {
char *fmt; char *fmt;
@ -80,9 +111,7 @@ magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) {
return NULL; return NULL;
} }
Py_BEGIN_ALLOW_THREADS
data = MagickGetImageBlob(self->wand, &len); data = MagickGetImageBlob(self->wand, &len);
Py_END_ALLOW_THREADS
if (data == NULL || len < 1) if (data == NULL || len < 1)
return magick_set_exception(self->wand); return magick_set_exception(self->wand);
@ -92,15 +121,15 @@ magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) {
return ans; return ans;
} }
// }}}
// Image.size {{{
static PyObject * static PyObject *
magick_Image_size_getter(magick_Image *self, void *closure) { magick_Image_size_getter(magick_Image *self, void *closure) {
size_t width, height; size_t width, height;
width = MagickGetImageWidth(self->wand); width = MagickGetImageWidth(self->wand);
height = MagickGetImageHeight(self->wand); height = MagickGetImageHeight(self->wand);
return Py_BuildValue("II", width, height); return Py_BuildValue("nn", width, height);
} }
static int static int
@ -123,7 +152,7 @@ magick_Image_size_setter(magick_Image *self, PyObject *val, void *closure) {
width = PyInt_AsSsize_t(PySequence_ITEM(val, 0)); width = PyInt_AsSsize_t(PySequence_ITEM(val, 0));
height = PyInt_AsSsize_t(PySequence_ITEM(val, 1)); height = PyInt_AsSsize_t(PySequence_ITEM(val, 1));
filter = (FilterTypes)PyInt_AsSsize_t(PySequence_ITEM(val, 2)); filter = (FilterTypes)PyInt_AsSsize_t(PySequence_ITEM(val, 2));
blur = PyFloat_AsDouble(PySequence_ITEM(val, 2)); blur = PyFloat_AsDouble(PySequence_ITEM(val, 3));
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError, "Width, height, filter or blur not a number"); PyErr_SetString(PyExc_TypeError, "Width, height, filter or blur not a number");
@ -135,9 +164,7 @@ magick_Image_size_setter(magick_Image *self, PyObject *val, void *closure) {
return -1; return -1;
} }
Py_BEGIN_ALLOW_THREADS
res = MagickResizeImage(self->wand, width, height, filter, blur); res = MagickResizeImage(self->wand, width, height, filter, blur);
Py_END_ALLOW_THREADS
if (!res) { if (!res) {
magick_set_exception(self->wand); magick_set_exception(self->wand);
@ -147,8 +174,9 @@ magick_Image_size_setter(magick_Image *self, PyObject *val, void *closure) {
return 0; return 0;
} }
// }}}
// Image.format {{{
static PyObject * static PyObject *
magick_Image_format_getter(magick_Image *self, void *closure) { magick_Image_format_getter(magick_Image *self, void *closure) {
const char *fmt; const char *fmt;
@ -176,6 +204,9 @@ magick_Image_format_setter(magick_Image *self, PyObject *val, void *closure) {
return 0; return 0;
} }
// }}}
// Image attr list {{{
static PyMethodDef magick_Image_methods[] = { static PyMethodDef magick_Image_methods[] = {
{"load", (PyCFunction)magick_Image_load, METH_VARARGS, {"load", (PyCFunction)magick_Image_load, METH_VARARGS,
"Load an image from a byte buffer (string)" "Load an image from a byte buffer (string)"
@ -185,6 +216,16 @@ static PyMethodDef magick_Image_methods[] = {
"export(format) -> bytestring\n\n Export the image as the specified format" "export(format) -> bytestring\n\n Export the image as the specified format"
}, },
{"create_canvas", (PyCFunction)magick_Image_create_canvas, METH_VARARGS,
"create_canvas(width, height, bgcolor)\n\n"
"Create a blank canvas\n"
"bgcolor should be an ImageMagick color specification (string)"
},
{"compose", (PyCFunction)magick_Image_compose, METH_VARARGS,
"compose(img, left, top, op) \n\n Compose img using operation op at (left, top)"
},
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
@ -202,7 +243,9 @@ static PyGetSetDef magick_Image_getsetters[] = {
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
static PyTypeObject magick_ImageType = { // }}}
static PyTypeObject magick_ImageType = { // {{{
PyObject_HEAD_INIT(NULL) PyObject_HEAD_INIT(NULL)
0, /*ob_size*/ 0, /*ob_size*/
"magick.Image", /*tp_name*/ "magick.Image", /*tp_name*/
@ -242,8 +285,35 @@ static PyTypeObject magick_ImageType = {
0, /* tp_init */ 0, /* tp_init */
0, /* tp_alloc */ 0, /* tp_alloc */
magick_Image_new, /* tp_new */ magick_Image_new, /* tp_new */
}; }; // }}}
// Image.compose {{{
static PyObject *
magick_Image_compose(magick_Image *self, PyObject *args, PyObject *kwargs)
{
PyObject *img, *op_;
ssize_t left, top;
CompositeOperator op;
magick_Image *src;
MagickBooleanType res = MagickFalse;
if (!PyArg_ParseTuple(args, "O!nnO", &magick_ImageType, &img, &left, &top, &op_)) return NULL;
src = (magick_Image*)img;
if (!IsMagickWand(src->wand)) {PyErr_SetString(PyExc_TypeError, "Not a valid ImageMagick wand"); return NULL;}
op = (CompositeOperator)PyInt_AsSsize_t(op_);
if (PyErr_Occurred() || op <= UndefinedCompositeOp) {
PyErr_SetString(PyExc_TypeError, "Invalid composite operator");
return NULL;
}
res = MagickCompositeImage(self->wand, src->wand, op, left, top);
if (!res) return magick_set_exception(self->wand);
Py_RETURN_NONE;
}
// }}}
// }}} // }}}
@ -264,6 +334,7 @@ magick_terminus(PyObject *self, PyObject *args)
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyMethodDef magick_methods[] = { static PyMethodDef magick_methods[] = {
{"genesis", magick_genesis, METH_VARARGS, {"genesis", magick_genesis, METH_VARARGS,
"genesis()\n\n" "genesis()\n\n"
@ -277,6 +348,7 @@ static PyMethodDef magick_methods[] = {
"Must be called after you are done using this module. You can call genesis() again after this to resume using the module." "Must be called after you are done using this module. You can call genesis() again after this to resume using the module."
}, },
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
// }}} // }}}
@ -297,5 +369,6 @@ initmagick(void)
PyModule_AddObject(m, "Image", (PyObject *)&magick_ImageType); PyModule_AddObject(m, "Image", (PyObject *)&magick_ImageType);
magick_add_module_constants(m); magick_add_module_constants(m);
MagickWandGenesis();
} }
// }}} // }}}

View File

@ -162,4 +162,68 @@ static void magick_add_module_constants(PyObject *m) {
PyModule_AddIntConstant(m, "PolynomialColorInterpolate", 8); PyModule_AddIntConstant(m, "PolynomialColorInterpolate", 8);
PyModule_AddIntConstant(m, "ShepardsColorInterpolate", 14); PyModule_AddIntConstant(m, "ShepardsColorInterpolate", 14);
PyModule_AddIntConstant(m, "VoronoiColorInterpolate", 15); PyModule_AddIntConstant(m, "VoronoiColorInterpolate", 15);
PyModule_AddIntConstant(m, "UndefinedCompositeOp", 0);
PyModule_AddIntConstant(m, "NoCompositeOp", 1);
PyModule_AddIntConstant(m, "ModulusAddCompositeOp", 2);
PyModule_AddIntConstant(m, "AtopCompositeOp", 3);
PyModule_AddIntConstant(m, "BlendCompositeOp", 4);
PyModule_AddIntConstant(m, "BumpmapCompositeOp", 5);
PyModule_AddIntConstant(m, "ChangeMaskCompositeOp", 6);
PyModule_AddIntConstant(m, "ClearCompositeOp", 7);
PyModule_AddIntConstant(m, "ColorBurnCompositeOp", 8);
PyModule_AddIntConstant(m, "ColorDodgeCompositeOp", 9);
PyModule_AddIntConstant(m, "ColorizeCompositeOp", 10);
PyModule_AddIntConstant(m, "CopyBlackCompositeOp", 11);
PyModule_AddIntConstant(m, "CopyBlueCompositeOp", 12);
PyModule_AddIntConstant(m, "CopyCompositeOp", 13);
PyModule_AddIntConstant(m, "CopyCyanCompositeOp", 14);
PyModule_AddIntConstant(m, "CopyGreenCompositeOp", 15);
PyModule_AddIntConstant(m, "CopyMagentaCompositeOp", 16);
PyModule_AddIntConstant(m, "CopyOpacityCompositeOp", 17);
PyModule_AddIntConstant(m, "CopyRedCompositeOp", 18);
PyModule_AddIntConstant(m, "CopyYellowCompositeOp", 19);
PyModule_AddIntConstant(m, "DarkenCompositeOp", 20);
PyModule_AddIntConstant(m, "DstAtopCompositeOp", 21);
PyModule_AddIntConstant(m, "DstCompositeOp", 22);
PyModule_AddIntConstant(m, "DstInCompositeOp", 23);
PyModule_AddIntConstant(m, "DstOutCompositeOp", 24);
PyModule_AddIntConstant(m, "DstOverCompositeOp", 25);
PyModule_AddIntConstant(m, "DifferenceCompositeOp", 26);
PyModule_AddIntConstant(m, "DisplaceCompositeOp", 27);
PyModule_AddIntConstant(m, "DissolveCompositeOp", 28);
PyModule_AddIntConstant(m, "ExclusionCompositeOp", 29);
PyModule_AddIntConstant(m, "HardLightCompositeOp", 30);
PyModule_AddIntConstant(m, "HueCompositeOp", 31);
PyModule_AddIntConstant(m, "InCompositeOp", 32);
PyModule_AddIntConstant(m, "LightenCompositeOp", 33);
PyModule_AddIntConstant(m, "LinearLightCompositeOp", 34);
PyModule_AddIntConstant(m, "LuminizeCompositeOp", 35);
PyModule_AddIntConstant(m, "MinusCompositeOp", 36);
PyModule_AddIntConstant(m, "ModulateCompositeOp", 37);
PyModule_AddIntConstant(m, "MultiplyCompositeOp", 38);
PyModule_AddIntConstant(m, "OutCompositeOp", 39);
PyModule_AddIntConstant(m, "OverCompositeOp", 40);
PyModule_AddIntConstant(m, "OverlayCompositeOp", 41);
PyModule_AddIntConstant(m, "PlusCompositeOp", 42);
PyModule_AddIntConstant(m, "ReplaceCompositeOp", 43);
PyModule_AddIntConstant(m, "SaturateCompositeOp", 44);
PyModule_AddIntConstant(m, "ScreenCompositeOp", 45);
PyModule_AddIntConstant(m, "SoftLightCompositeOp", 46);
PyModule_AddIntConstant(m, "SrcAtopCompositeOp", 47);
PyModule_AddIntConstant(m, "SrcCompositeOp", 48);
PyModule_AddIntConstant(m, "SrcInCompositeOp", 49);
PyModule_AddIntConstant(m, "SrcOutCompositeOp", 50);
PyModule_AddIntConstant(m, "SrcOverCompositeOp", 51);
PyModule_AddIntConstant(m, "ModulusSubtractCompositeOp", 52);
PyModule_AddIntConstant(m, "ThresholdCompositeOp", 53);
PyModule_AddIntConstant(m, "XorCompositeOp", 54);
PyModule_AddIntConstant(m, "DivideCompositeOp", 55);
PyModule_AddIntConstant(m, "DistortCompositeOp", 56);
PyModule_AddIntConstant(m, "BlurCompositeOp", 57);
PyModule_AddIntConstant(m, "PegtopLightCompositeOp", 58);
PyModule_AddIntConstant(m, "VividLightCompositeOp", 59);
PyModule_AddIntConstant(m, "PinLightCompositeOp", 60);
PyModule_AddIntConstant(m, "LinearDodgeCompositeOp", 61);
PyModule_AddIntConstant(m, "LinearBurnCompositeOp", 62);
PyModule_AddIntConstant(m, "MathematicsCompositeOp", 63);
} }