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:
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):
def load(self, data):
return _magick.Image.load(self, bytes(data))
def open(self, path_or_file):
data = path_or_file
if hasattr(data, 'read'):
@ -78,6 +58,19 @@ class Image(_magick.Image):
if len(ext) < 2:
raise ValueError('No format specified')
format = ext[1:]
format = format.upper()
with open(path, 'wb') as f:
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():
constants = []
for x in ('resample', 'image', 'draw', 'distort'):
for x in ('resample', 'image', 'draw', 'distort', 'composite'):
constants += list(parse_enums('magick/%s.h'%x))
base = os.path.dirname(__file__)
constants = [

View File

@ -5,6 +5,7 @@
#include "magick_constants.h"
// magick_set_exception {{{
PyObject* magick_set_exception(MagickWand *wand) {
ExceptionType ext;
char *desc = MagickGetException(wand, &ext);
@ -13,6 +14,7 @@ PyObject* magick_set_exception(MagickWand *wand) {
desc = MagickRelinquishMemory(desc);
return NULL;
}
// }}}
// Image object definition {{{
typedef struct {
@ -22,6 +24,10 @@ typedef struct {
} magick_Image;
// Method declarations {{{
static PyObject* magick_Image_compose(magick_Image *self, PyObject *args, PyObject *kwargs);
// }}}
static void
magick_Image_dealloc(magick_Image* self)
{
@ -48,6 +54,7 @@ magick_Image_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return (PyObject *)self;
}
// Image.load {{{
static PyObject *
magick_Image_load(magick_Image *self, PyObject *args, PyObject *kwargs) {
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;
Py_BEGIN_ALLOW_THREADS
res = MagickReadImageBlob(self->wand, data, dlen);
Py_END_ALLOW_THREADS
if (!res)
return magick_set_exception(self->wand);
@ -66,6 +71,32 @@ magick_Image_load(magick_Image *self, PyObject *args, PyObject *kwargs) {
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 *
magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) {
char *fmt;
@ -80,9 +111,7 @@ magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
data = MagickGetImageBlob(self->wand, &len);
Py_END_ALLOW_THREADS
if (data == NULL || len < 1)
return magick_set_exception(self->wand);
@ -92,15 +121,15 @@ magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) {
return ans;
}
// }}}
// Image.size {{{
static PyObject *
magick_Image_size_getter(magick_Image *self, void *closure) {
size_t width, height;
width = MagickGetImageWidth(self->wand);
height = MagickGetImageHeight(self->wand);
return Py_BuildValue("II", width, height);
return Py_BuildValue("nn", width, height);
}
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));
height = PyInt_AsSsize_t(PySequence_ITEM(val, 1));
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()) {
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;
}
Py_BEGIN_ALLOW_THREADS
res = MagickResizeImage(self->wand, width, height, filter, blur);
Py_END_ALLOW_THREADS
if (!res) {
magick_set_exception(self->wand);
@ -147,8 +174,9 @@ magick_Image_size_setter(magick_Image *self, PyObject *val, void *closure) {
return 0;
}
// }}}
// Image.format {{{
static PyObject *
magick_Image_format_getter(magick_Image *self, void *closure) {
const char *fmt;
@ -176,6 +204,9 @@ magick_Image_format_setter(magick_Image *self, PyObject *val, void *closure) {
return 0;
}
// }}}
// Image attr list {{{
static PyMethodDef magick_Image_methods[] = {
{"load", (PyCFunction)magick_Image_load, METH_VARARGS,
"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"
},
{"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 */
};
@ -202,7 +243,9 @@ static PyGetSetDef magick_Image_getsetters[] = {
{NULL} /* Sentinel */
};
static PyTypeObject magick_ImageType = {
// }}}
static PyTypeObject magick_ImageType = { // {{{
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"magick.Image", /*tp_name*/
@ -242,8 +285,35 @@ static PyTypeObject magick_ImageType = {
0, /* tp_init */
0, /* tp_alloc */
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;
}
static PyMethodDef magick_methods[] = {
{"genesis", magick_genesis, METH_VARARGS,
"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."
},
{NULL} /* Sentinel */
};
// }}}
@ -297,5 +369,6 @@ initmagick(void)
PyModule_AddObject(m, "Image", (PyObject *)&magick_ImageType);
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, "ShepardsColorInterpolate", 14);
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);
}