When saving cover images don't re-encode the image data unless absolutely neccessary. This prevents information loss due to JPEG re-compression

This commit is contained in:
Kovid Goyal 2010-10-04 13:17:51 -06:00
parent f30860ff1d
commit 8854982f14
3 changed files with 94 additions and 8 deletions

View File

@ -158,7 +158,7 @@ class Image(_magick.Image): # {{{
format = ext[1:] format = ext[1:]
format = format.upper() format = format.upper()
with open(path, 'wb') as f: with lopen(path, 'wb') as f:
f.write(self.export(format)) f.write(self.export(format))
def compose(self, img, left=0, top=0, operation='OverCompositeOp'): def compose(self, img, left=0, top=0, operation='OverCompositeOp'):

View File

@ -11,22 +11,57 @@ from calibre.utils.magick import Image, DrawingWand, create_canvas
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__
from calibre import fit_image from calibre import fit_image
def normalize_format_name(fmt):
fmt = fmt.lower()
if fmt == 'jpeg':
fmt = 'jpg'
return fmt
def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None, def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None,
return_data=False): return_data=False, compression_quality=90):
''' '''
Saves image in data to path, in the format specified by the path Saves image in data to path, in the format specified by the path
extension. Composes the image onto a blank canvas so as to extension. Removes any transparency. If there is no transparency and no
properly convert transparent images. resize and the input and output image formats are the same, no changes are
made.
:param compression_quality: The quality of the image after compression.
Number between 1 and 100. 1 means highest compression, 100 means no
compression (lossless).
:param bgcolor: The color for transparent pixels. Must be specified in hex.
:param resize_to: A tuple (width, height) or None for no resizing
''' '''
changed = False
img = Image() img = Image()
img.load(data) img.load(data)
orig_fmt = normalize_format_name(img.format)
fmt = os.path.splitext(path)[1]
fmt = normalize_format_name(fmt[1:])
if resize_to is not None: if resize_to is not None:
img.size = (resize_to[0], resize_to[1]) img.size = (resize_to[0], resize_to[1])
canvas = create_canvas(img.size[0], img.size[1], bgcolor) changed = True
canvas.compose(img) if not hasattr(img, 'has_transparent_pixels') or img.has_transparent_pixels():
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
canvas.compose(img)
img = canvas
changed = True
if not changed:
changed = fmt != orig_fmt
if return_data: if return_data:
return canvas.export(os.path.splitext(path)[1][1:]) if changed:
canvas.save(path) if hasattr(img, 'set_compression_quality') and fmt == 'jpg':
img.set_compression_quality(compression_quality)
return img.export(fmt)
return data
if changed:
if hasattr(img, 'set_compression_quality') and fmt == 'jpg':
img.set_compression_quality(compression_quality)
img.save(path)
else:
with lopen(path, 'wb') as f:
f.write(data)
def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'): def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'):
img = Image() img = Image()

View File

@ -725,6 +725,49 @@ magick_Image_set_page(magick_Image *self, PyObject *args, PyObject *kwargs) {
} }
// }}} // }}}
// Image.set_compression_quality {{{
static PyObject *
magick_Image_set_compression_quality(magick_Image *self, PyObject *args, PyObject *kwargs) {
Py_ssize_t quality;
if (!PyArg_ParseTuple(args, "n", &quality)) return NULL;
if (!MagickSetImageCompressionQuality(self->wand, quality)) return magick_set_exception(self->wand);
Py_RETURN_NONE;
}
// }}}
// Image.has_transparent_pixels {{{
static PyObject *
magick_Image_has_transparent_pixels(magick_Image *self, PyObject *args, PyObject *kwargs) {
PixelIterator *pi = NULL;
PixelWand **pixels = NULL;
int found = 0;
size_t r, c, width, height;
double alpha;
height = MagickGetImageHeight(self->wand);
pi = NewPixelIterator(self->wand);
for (r = 0; r < height; r++) {
pixels = PixelGetNextIteratorRow(pi, &width);
for (c = 0; c < width; c++) {
alpha = PixelGetAlpha(pixels[c]);
if (alpha < 1.00) {
found = 1;
c = width; r = height;
}
}
}
pi = DestroyPixelIterator(pi);
if (found) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
// }}}
// Image.normalize {{{ // Image.normalize {{{
static PyObject * static PyObject *
@ -872,6 +915,14 @@ static PyMethodDef magick_Image_methods[] = {
"set_page(width, height, x, y) \n\n Sets the page geometry of the image." "set_page(width, height, x, y) \n\n Sets the page geometry of the image."
}, },
{"set_compression_quality", (PyCFunction)magick_Image_set_compression_quality, METH_VARARGS,
"set_compression_quality(quality) \n\n Sets the compression quality when exporting the image."
},
{"has_transparent_pixels", (PyCFunction)magick_Image_has_transparent_pixels, METH_VARARGS,
"has_transparent_pixels() \n\n Returns True iff image has a (semi-) transparent pixel"
},
{"thumbnail", (PyCFunction)magick_Image_thumbnail, METH_VARARGS, {"thumbnail", (PyCFunction)magick_Image_thumbnail, METH_VARARGS,
"thumbnail(width, height) \n\n Convert to a thumbnail of specified size." "thumbnail(width, height) \n\n Convert to a thumbnail of specified size."
}, },