mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
magick: Compose support
This commit is contained in:
parent
6842319911
commit
34d427dfd5
@ -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
|
||||
|
46
src/calibre/utils/magick/draw.py
Normal file
46
src/calibre/utils/magick/draw.py
Normal 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)
|
||||
|
||||
|
@ -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 = [
|
||||
|
@ -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();
|
||||
}
|
||||
// }}}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user