diff --git a/src/calibre/utils/magick/__init__.py b/src/calibre/utils/magick/__init__.py index 0eede354a4..914d033b12 100644 --- a/src/calibre/utils/magick/__init__.py +++ b/src/calibre/utils/magick/__init__.py @@ -17,6 +17,46 @@ if _magick is None: _gravity_map = dict([(getattr(_magick, x), x) for x in dir(_magick) if x.endswith('Gravity')]) +# Font metrics {{{ +class Rect(object): + + def __init__(self, left, top, right, bottom): + self.left, self.top, self.right, self.bottom = left, top, right, bottom + + def __str__(self): + return '(%s, %s) -- (%s, %s)'%(self.left, self.top, self.right, + self.bottom) + +class FontMetrics(object): + + def __init__(self, ret): + self._attrs = [] + for i, x in enumerate(('char_width', 'char_height', 'ascender', + 'descender', 'text_width', 'text_height', + 'max_horizontal_advance')): + setattr(self, x, ret[i]) + self._attrs.append(x) + self.bounding_box = Rect(ret[7], ret[8], ret[9], ret[10]) + self.x, self.y = ret[11], ret[12] + self._attrs.extend(['bounding_box', 'x', 'y']) + self._attrs = tuple(self._attrs) + + def __str__(self): + return '''FontMetrics: + char_width: %s + char_height: %s + ascender: %s + descender: %s + text_width: %s + text_height: %s + max_horizontal_advance: %s + bounding_box: %s + x: %s + y: %s + '''%tuple([getattr(self, x) for x in self._attrs]) + +# }}} + class DrawingWand(_magick.DrawingWand): # {{{ @dynamic_property @@ -39,6 +79,14 @@ class DrawingWand(_magick.DrawingWand): # {{{ self.gravity_ = val return property(fget=fget, fset=fset, doc=_magick.DrawingWand.gravity_.__doc__) + @dynamic_property + def font_size(self): + def fget(self): + return self.font_size_ + def fset(self, val): + self.font_size_ = float(val) + return property(fget=fget, fset=fset, doc=_magick.DrawingWand.font_size_.__doc__) + # }}} class Image(_magick.Image): # {{{ @@ -97,6 +145,22 @@ class Image(_magick.Image): # {{{ raise ValueError('left and/or top out of bounds') _magick.Image.compose(self, img, int(left), int(top), op) + def font_metrics(self, drawing_wand, text): + if isinstance(text, unicode): + text = text.encode('UTF-8') + return FontMetrics(_magick.Image.font_metrics(self, drawing_wand, text)) + + def annotate(self, drawing_wand, x, y, angle, text): + if isinstance(text, unicode): + text = text.encode('UTF-8') + return _magick.Image.annotate(self, drawing_wand, x, y, angle, text) + + def distort(self, method, arguments, bestfit): + method = getattr(_magick, method) + arguments = [float(x) for x in arguments] + _magick.Image.distort(self, method, arguments, bestfit) + + # }}} def create_canvas(width, height, bgcolor): diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py index cf4c1e09ce..766a0ad861 100644 --- a/src/calibre/utils/magick/draw.py +++ b/src/calibre/utils/magick/draw.py @@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from calibre.utils.magick import Image, create_canvas +from calibre.utils.magick import Image, DrawingWand, create_canvas def save_cover_data_to(data, path, bgcolor='white', resize_to=None): ''' @@ -43,4 +43,38 @@ def identify(path): data = open(path, 'rb').read() return identify_data(data) +def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0, + border_color='white'): + img = Image() + img.open(path_to_image) + lwidth, lheight = img.size + canvas = create_canvas(lwidth+left+right, lheight+top+bottom, + border_color) + canvas.compose(img, left, top) + canvas.save(path_to_image) + +def create_text_wand(font_size, font_path=None): + if font_path is None: + font_path = P('fonts/liberation/LiberationSerif-Bold.ttf') + ans = DrawingWand() + ans.font = font_path + ans.font_size = font_size + ans.gravity = 'CenterGravity' + ans.text_alias = True + return ans + +def create_text_arc(text, font_size, font=None, bgcolor='white'): + if isinstance(text, unicode): + text = text.encode('utf-8') + + canvas = create_canvas(300, 300, bgcolor) + + tw = create_text_wand(font_size, font_path=font) + m = canvas.font_metrics(tw, text) + canvas = create_canvas(int(m.text_width)+20, int(m.text_height*3.5), bgcolor) + canvas.annotate(tw, 0, 0, 0, text) + canvas.distort("ArcDistortion", [120], True) + canvas.trim(0) + return canvas + diff --git a/src/calibre/utils/magick/magick.c b/src/calibre/utils/magick/magick.c index 552650a086..d6bcc6a68c 100644 --- a/src/calibre/utils/magick/magick.c +++ b/src/calibre/utils/magick/magick.c @@ -165,7 +165,7 @@ static PyGetSetDef magick_DrawingWand_getsetters[] = { (char *)"DrawingWand font path. Absolute path to font file.", NULL}, - {(char *)"font_size", + {(char *)"font_size_", (getter)magick_DrawingWand_fontsize_getter, (setter)magick_DrawingWand_fontsize_setter, (char *)"DrawingWand fontsize", NULL}, @@ -309,6 +309,53 @@ magick_Image_create_canvas(magick_Image *self, PyObject *args, PyObject *kwargs) } // }}} +// Image.font_metrics {{{ + +static PyObject * +magick_Image_font_metrics(magick_Image *self, PyObject *args, PyObject *kwargs) { + char *text; + PyObject *dw_, *ans, *m; + Py_ssize_t i; + DrawingWand *dw; + double *metrics; + + if (!PyArg_ParseTuple(args, "O!s", &magick_DrawingWandType, &dw_, &text)) return NULL; + dw = ((magick_DrawingWand*)dw_)->wand; + if (!IsDrawingWand(dw)) { PyErr_SetString(PyExc_TypeError, "Invalid drawing wand"); return NULL; } + ans = PyTuple_New(13); + if (ans == NULL) return PyErr_NoMemory(); + + metrics = MagickQueryFontMetrics(self->wand, dw, text); + + for (i = 0; i < 13; i++) { + m = PyFloat_FromDouble(metrics[i]); + if (m == NULL) { return PyErr_NoMemory(); } + PyTuple_SET_ITEM(ans, i, m); + } + + return ans; +} +// }}} + +// Image.annotate {{{ + +static PyObject * +magick_Image_annotate(magick_Image *self, PyObject *args, PyObject *kwargs) { + char *text; + PyObject *dw_; + DrawingWand *dw; + double x, y, angle; + + if (!PyArg_ParseTuple(args, "O!ddds", &magick_DrawingWandType, &dw_, &x, &y, &angle, &text)) return NULL; + dw = ((magick_DrawingWand*)dw_)->wand; + if (!IsDrawingWand(dw)) { PyErr_SetString(PyExc_TypeError, "Invalid drawing wand"); return NULL; } + + if (!MagickAnnotateImage(self->wand, dw, x, y, angle, text)) return magick_set_exception(self->wand); + + Py_RETURN_NONE; +} +// }}} + // Image.export {{{ static PyObject * @@ -420,6 +467,55 @@ magick_Image_format_setter(magick_Image *self, PyObject *val, void *closure) { // }}} +// Image.distort {{{ + +static PyObject * +magick_Image_distort(magick_Image *self, PyObject *args, PyObject *kwargs) { + DistortImageMethod method; + Py_ssize_t i, number; + PyObject *bestfit, *argv, *t; + MagickBooleanType res; + double *arguments = NULL; + + if (!PyArg_ParseTuple(args, "nOO", &method, &argv, &bestfit)) return NULL; + + if (!PySequence_Check(argv)) { PyErr_SetString(PyExc_TypeError, "arguments must be a sequence"); return NULL; } + + number = PySequence_Length(argv); + if (number > 0) { + arguments = (double *)PyMem_Malloc(sizeof(double) * number); + if (arguments == NULL) return PyErr_NoMemory(); + for (i = 0; i < number; i++) { + t = PySequence_ITEM(argv, i); + if (t == NULL || !PyFloat_Check(t)) { PyErr_SetString(PyExc_TypeError, "Arguments must all be floats"); PyMem_Free(arguments); return NULL; } + arguments[i] = PyFloat_AsDouble(t); + } + } + + res = MagickDistortImage(self->wand, method, number, arguments, PyObject_IsTrue(bestfit)); + if (arguments != NULL) PyMem_Free(arguments); + + if (!res) return magick_set_exception(self->wand); + + Py_RETURN_NONE; +} +// }}} + +// Image.trim {{{ + +static PyObject * +magick_Image_trim(magick_Image *self, PyObject *args, PyObject *kwargs) { + double fuzz; + + if (!PyArg_ParseTuple(args, "d", &fuzz)) return NULL; + + if (!MagickTrimImage(self->wand, fuzz)) return magick_set_exception(self->wand); + + Py_RETURN_NONE; +} +// }}} + + // Image attr list {{{ static PyMethodDef magick_Image_methods[] = { {"load", (PyCFunction)magick_Image_load, METH_VARARGS, @@ -440,6 +536,22 @@ static PyMethodDef magick_Image_methods[] = { "compose(img, left, top, op) \n\n Compose img using operation op at (left, top)" }, + {"font_metrics", (PyCFunction)magick_Image_font_metrics, METH_VARARGS, + "font_metrics(drawing_wand, text) \n\n Return font metrics for specified drawing wand and text." + }, + + {"annotate", (PyCFunction)magick_Image_annotate, METH_VARARGS, + "annotate(drawing_wand, x, y, angle, text) \n\n Annotate image with text." + }, + + {"distort", (PyCFunction)magick_Image_distort, METH_VARARGS, + "distort(method, arguments, best_fit) \n\n Distort image." + }, + + {"trim", (PyCFunction)magick_Image_trim, METH_VARARGS, + "trim(fuzz) \n\n Trim image." + }, + {NULL} /* Sentinel */ };