mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -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 = 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'):
|
||||||
|
@ -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()
|
||||||
|
@ -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."
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user