mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
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:
parent
f30860ff1d
commit
8854982f14
@ -158,7 +158,7 @@ class Image(_magick.Image): # {{{
|
||||
format = ext[1:]
|
||||
format = format.upper()
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
with lopen(path, 'wb') as f:
|
||||
f.write(self.export(format))
|
||||
|
||||
def compose(self, img, left=0, top=0, operation='OverCompositeOp'):
|
||||
|
@ -11,22 +11,57 @@ from calibre.utils.magick import Image, DrawingWand, create_canvas
|
||||
from calibre.constants import __appname__, __version__
|
||||
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,
|
||||
return_data=False):
|
||||
return_data=False, compression_quality=90):
|
||||
'''
|
||||
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.
|
||||
extension. Removes any transparency. If there is no transparency and no
|
||||
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.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:
|
||||
img.size = (resize_to[0], resize_to[1])
|
||||
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
|
||||
canvas.compose(img)
|
||||
changed = True
|
||||
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:
|
||||
return canvas.export(os.path.splitext(path)[1][1:])
|
||||
canvas.save(path)
|
||||
if changed:
|
||||
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'):
|
||||
img = Image()
|
||||
|
@ -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 {{{
|
||||
|
||||
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_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(width, height) \n\n Convert to a thumbnail of specified size."
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user