From 34d427dfd58af946671bf9d120bfb0b3bc4bb0a4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Aug 2010 17:12:01 -0600 Subject: [PATCH] magick: Compose support --- src/calibre/utils/magick/__init__.py | 39 ++++---- src/calibre/utils/magick/draw.py | 46 ++++++++++ src/calibre/utils/magick/generate.py | 2 +- src/calibre/utils/magick/magick.c | 99 ++++++++++++++++++--- src/calibre/utils/magick/magick_constants.h | 64 +++++++++++++ 5 files changed, 213 insertions(+), 37 deletions(-) create mode 100644 src/calibre/utils/magick/draw.py diff --git a/src/calibre/utils/magick/__init__.py b/src/calibre/utils/magick/__init__.py index b584f2d2f8..68b5ba3f5f 100644 --- a/src/calibre/utils/magick/__init__.py +++ b/src/calibre/utils/magick/__init__.py @@ -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 diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py new file mode 100644 index 0000000000..cf4c1e09ce --- /dev/null +++ b/src/calibre/utils/magick/draw.py @@ -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 ' +__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) + + diff --git a/src/calibre/utils/magick/generate.py b/src/calibre/utils/magick/generate.py index bb5fe177ff..7331152020 100644 --- a/src/calibre/utils/magick/generate.py +++ b/src/calibre/utils/magick/generate.py @@ -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 = [ diff --git a/src/calibre/utils/magick/magick.c b/src/calibre/utils/magick/magick.c index 9389364bdc..f363553d4b 100644 --- a/src/calibre/utils/magick/magick.c +++ b/src/calibre/utils/magick/magick.c @@ -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(); } // }}} diff --git a/src/calibre/utils/magick/magick_constants.h b/src/calibre/utils/magick/magick_constants.h index 060a0017d9..5a5b18ed8f 100644 --- a/src/calibre/utils/magick/magick_constants.h +++ b/src/calibre/utils/magick/magick_constants.h @@ -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); }