def calibre_cover2(title, author_string='', series_string='', prefs=None, as_qimage=False, logo_path=None): init_environment() title, subtitle, footer = '<b>' + escape_formatting( title), '<i>' + escape_formatting( series_string), '<b>' + escape_formatting(author_string) prefs = prefs or cprefs prefs = {k: prefs.get(k) for k in cprefs.defaults} scale = 800. / prefs['cover_height'] scale_cover(prefs, scale) prefs = Prefs(**prefs) img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.white) # colors = to_theme('ffffff ffffff 000000 000000') color_theme = theme_to_colors(fallback_colors) class CalibeLogoStyle(Style): NAME = GUI_NAME = 'calibre' def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): top = title_block.position.y + 10 extra_spacing = subtitle_block.line_spacing // 2 if subtitle_block.line_spacing else title_block.line_spacing // 3 height = title_block.height + subtitle_block.height + extra_spacing + title_block.leading top += height + 25 bottom = footer_block.position.y - 50 logo = QImage(logo_path or I('library.png')) pwidth, pheight = rect.width(), bottom - top scaled, width, height = fit_image(logo.width(), logo.height(), pwidth, pheight) x, y = (pwidth - width) // 2, (pheight - height) // 2 rect = QRect(x, top + y, width, height) painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) painter.drawImage(rect, logo) return self.ccolor1, self.ccolor1, self.ccolor1 style = CalibeLogoStyle(color_theme, prefs) title_block, subtitle_block, footer_block = layout_text( prefs, img, title, subtitle, footer, img.height() // 3, style) p = QPainter(img) rect = QRect(0, 0, img.width(), img.height()) colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block) for block, color in zip((title_block, subtitle_block, footer_block), colors): p.setPen(color) block.draw(p) p.end() img.setText('Generated cover', '%s %s' % (__appname__, __version__)) if as_qimage: return img return pixmap_to_data(img)
def paste(self): clipboard = QApplication.clipboard() md = clipboard.mimeData() if md.hasImage(): img = QImage(md.imageData()) if not img.isNull(): self.undo_stack.push(Replace(img, _('Paste image'), self)) else: error_dialog(self, _('No image'), _('No image available in the clipboard'), show=True)
def __init__(self, dirpath): pictureflow.FlowImages.__init__(self) self.images = [] self.captions = [] self.subtitles = [] for f in os.listdir(dirpath): f = os.path.join(dirpath, f) img = QImage(f) if not img.isNull(): self.images.append(img) self.captions.append(os.path.basename(f)) self.subtitles.append('%d bytes'%os.stat(f).st_size)
def load_jxr_data(data): with TemporaryDirectory() as tdir: if isinstance(tdir, bytes): tdir = os.fsdecode(tdir) with lopen(os.path.join(tdir, 'input.jxr'), 'wb') as f: f.write(data) cmd = [get_exe_path('JxrDecApp'), '-i', 'input.jxr', '-o', 'output.tif'] creationflags = subprocess.DETACHED_PROCESS if iswindows else 0 subprocess.Popen(cmd, cwd=tdir, stdout=lopen(os.devnull, 'wb'), stderr=subprocess.STDOUT, creationflags=creationflags).wait() i = QImage() if not i.load(os.path.join(tdir, 'output.tif')): raise NotImage('Failed to convert JPEG-XR image') return i
def blend_on_canvas(img, width, height, bgcolor='#ffffff'): ' Blend the `img` onto a canvas with the specified background color and size ' w, h = img.width(), img.height() scaled, nw, nh = fit_image(w, h, width, height) if scaled: img = img.scaled(int(nw), int(nh), Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) w, h = nw, nh canvas = QImage(int(width), int(height), QImage.Format.Format_RGB32) canvas.fill(QColor(bgcolor)) overlay_image(img, canvas, (width - w) // 2, (height - h) // 2) return canvas
def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): top = title_block.position.y + 10 extra_spacing = subtitle_block.line_spacing // 2 if subtitle_block.line_spacing else title_block.line_spacing // 3 height = title_block.height + subtitle_block.height + extra_spacing + title_block.leading top += height + 25 bottom = footer_block.position.y - 50 logo = QImage(logo_path or I('library.png')) pwidth, pheight = rect.width(), bottom - top scaled, width, height = fit_image(logo.width(), logo.height(), pwidth, pheight) x, y = (pwidth - width) // 2, (pheight - height) // 2 rect = QRect(x, top + y, width, height) painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) painter.drawImage(rect, logo) return self.ccolor1, self.ccolor1, self.ccolor1
def message_image(text, width=500, height=400, font_size=20): init_environment() img = QImage(width, height, QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.white) p = QPainter(img) f = QFont() f.setPixelSize(font_size) p.setFont(f) r = img.rect().adjusted(10, 10, -10, -10) p.drawText( r, Qt.AlignmentFlag.AlignJustify | Qt.AlignmentFlag.AlignVCenter | Qt.TextFlag.TextWordWrap, text) p.end() return pixmap_to_data(img)
def encode_jpeg(file_path, quality=80): from calibre.utils.speedups import ReadOnlyFileBuffer quality = max(0, min(100, int(quality))) exe = get_exe_path('cjpeg') cmd = [exe] + '-optimize -progressive -maxmemory 100M -quality'.split() + [str(quality)] img = QImage() if not img.load(file_path): raise ValueError('%s is not a valid image file' % file_path) ba = QByteArray() buf = QBuffer(ba) buf.open(QIODevice.OpenModeFlag.WriteOnly) if not img.save(buf, 'PPM'): raise ValueError('Failed to export image to PPM') return run_optimizer(file_path, cmd, as_filter=True, input_data=ReadOnlyFileBuffer(ba.data()))
def add_borders_to_image(img, left=0, top=0, right=0, bottom=0, border_color='#ffffff'): img = image_from_data(img) if not (left > 0 or right > 0 or top > 0 or bottom > 0): return img canvas = QImage(img.width() + left + right, img.height() + top + bottom, QImage.Format.Format_RGB32) canvas.fill(QColor(border_color)) overlay_image(img, canvas, left, top) return canvas
def load_image(self, data): self.is_valid = False try: fmt = identify(data)[0].encode('ascii') except Exception: fmt = b'' self.original_image_format = fmt.decode('ascii').lower() self.selection_state.reset() self.original_image_data = data self.current_image = i = self.original_image = (QImage.fromData( data, format=fmt) if fmt else QImage.fromData(data)) self.is_valid = not i.isNull() self.current_scaled_pixmap = None self.update() self.image_changed.emit(self.current_image)
def render_svg(widget, path): img = QPixmap() rend = QSvgRenderer() if rend.load(path): dpr = getattr(widget, 'devicePixelRatioF', widget.devicePixelRatio)() sz = rend.defaultSize() h = (max_available_height() - 50) w = int(h * sz.height() / float(sz.width())) pd = QImage(w * dpr, h * dpr, QImage.Format.Format_RGB32) pd.fill(Qt.GlobalColor.white) p = QPainter(pd) rend.render(p) p.end() img = QPixmap.fromImage(pd) img.setDevicePixelRatio(dpr) return img
def generate_cover(mi, prefs=None, as_qimage=False): init_environment() prefs = prefs or cprefs prefs = {k: prefs.get(k) for k in cprefs.defaults} prefs = Prefs(**prefs) color_theme = random.choice(load_color_themes(prefs)) style = random.choice(load_styles(prefs))(color_theme, prefs) title, subtitle, footer = format_text(mi, prefs) img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format.Format_ARGB32) title_block, subtitle_block, footer_block = layout_text( prefs, img, title, subtitle, footer, img.height() // 3, style) p = QPainter(img) rect = QRect(0, 0, img.width(), img.height()) colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block) for block, color in zip((title_block, subtitle_block, footer_block), colors): p.setPen(color) block.draw(p) p.end() img.setText('Generated cover', '{} {}'.format(__appname__, __version__)) if as_qimage: return img return pixmap_to_data(img)
class Canvas: def __init__(self, width, height, bgcolor='#ffffff'): self.img = QImage(width, height, QImage.Format.Format_RGB32) self.img.fill(QColor(bgcolor)) def __enter__(self): return self def __exit__(self, *args): pass def compose(self, img, x=0, y=0): img = image_from_data(img) overlay_image(img, self.img, x, y) def export(self, fmt='JPEG', compression_quality=95): return image_to_data(self.img, compression_quality=compression_quality, fmt=fmt)
def __call__(self, canvas): if canvas.has_selection and canvas.selection_state.rect is not None: pimg = self.after_image img = self.after_image = QImage(canvas.current_image) rect = QRectF(*get_selection_rect(img, canvas.selection_state.rect, canvas.target)) p = QPainter(img) p.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) p.drawImage(rect, pimg, QRectF(pimg.rect())) p.end() return self.after_image
def __call__(self, container): from qt.core import QImage from calibre.gui2 import pixmap_to_data ext = container.mime_map[self.name].split('/')[-1].upper() if ext == 'JPG': ext = 'JPEG' if ext not in ('PNG', 'JPEG', 'GIF'): return False with container.open(self.name, 'r+b') as f: raw = f.read() i = QImage() i.loadFromData(raw) if i.isNull(): return False raw = pixmap_to_data(i, format=ext, quality=95) f.seek(0) f.truncate() f.write(raw) return True
def generate_masthead(title, output_path=None, width=600, height=60, as_qimage=False, font_family=None): init_environment() font_family = font_family or cprefs['title_font_family'] or 'Liberation Serif' img = QImage(width, height, QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.white) p = QPainter(img) p.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.TextAntialiasing) f = QFont(font_family) f.setStyleStrategy(QFont.StyleStrategy.PreferAntialias) f.setPixelSize((height * 3) // 4), f.setBold(True) p.setFont(f) p.drawText(img.rect(), Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, sanitize(title)) p.end() if as_qimage: return img data = pixmap_to_data(img) if output_path is None: return data with open(output_path, 'wb') as f: f.write(data)
def create_icon(text, palette=None, sz=None, divider=2, fill='white'): if isinstance(fill, string_or_bytes): fill = QColor(fill) sz = sz or int( math.ceil(tprefs['toolbar_icon_size'] * QApplication.instance().devicePixelRatio())) if palette is None: palette = QApplication.palette() img = QImage(sz, sz, QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.transparent) p = QPainter(img) p.setRenderHints(QPainter.RenderHint.TextAntialiasing | QPainter.RenderHint.Antialiasing) if fill is not None: qDrawShadeRect(p, img.rect(), palette, fill=fill, lineWidth=1, midLineWidth=1) f = p.font() f.setFamily('Liberation Sans'), f.setPixelSize(int( sz // divider)), f.setBold(True) p.setFont(f), p.setPen(QColor('#2271d5')) p.drawText(img.rect().adjusted(2, 2, -2, -2), Qt.AlignmentFlag.AlignCenter, text) p.end() return QIcon(QPixmap.fromImage(img))
def start_show_animation(self): if self.rendered_pixmap is not None: return dpr = getattr(self, 'devicePixelRatioF', self.devicePixelRatio)() p = QImage(dpr * self.size(), QImage.Format.Format_ARGB32_Premultiplied) p.setDevicePixelRatio(dpr) # For some reason, Qt scrolls the book view when rendering this widget, # for the very first time, so manually preserve its position pr = getattr(self.parent(), 'library_view', None) if not hasattr(pr, 'preserve_state'): self.render(p) else: with pr.preserve_state(): self.render(p) self.rendered_pixmap = QPixmap.fromImage(p) self.original_visibility = v = [] for child in self.findChildren(QWidget): if child.isVisible(): child.setVisible(False) v.append(child) self.show_animation.start()
def dropEvent(self, event): event.setDropAction(Qt.DropAction.CopyAction) md = event.mimeData() x, y = dnd_get_image(md) if x is not None: # We have an image, set cover event.accept() if y is None: # Local image self.undo_stack.push(Replace(x.toImage(), _('Drop image'), self)) else: d = DownloadDialog(x, y, self.gui) d.start_download() if d.err is None: with open(d.fpath, 'rb') as f: img = QImage() img.loadFromData(f.read()) if not img.isNull(): self.undo_stack.push(Replace(img, _('Drop image'), self)) event.accept()
def rasterize_svg(self, elem, width=0, height=0, format='PNG'): view_box = elem.get('viewBox', elem.get('viewbox', None)) sizes = None logger = self.oeb.logger if view_box is not None: try: box = [ float(x) for x in filter(None, re.split('[, ]', view_box)) ] sizes = [box[2] - box[0], box[3] - box[1]] except (TypeError, ValueError, IndexError): logger.warn( 'SVG image has invalid viewBox="%s", ignoring the viewBox' % view_box) else: for image in elem.xpath( 'descendant::*[local-name()="image" and ' '@height and contains(@height, "%")]'): logger.info( 'Found SVG image height in %, trying to convert...') try: h = float(image.get('height').replace('%', '')) / 100. image.set('height', str(h * sizes[1])) except: logger.exception( 'Failed to convert percentage height:', image.get('height')) data = QByteArray(xml2str(elem, with_tail=False)) svg = QSvgRenderer(data) size = svg.defaultSize() if size.width() == 100 and size.height() == 100 and sizes: size.setWidth(sizes[0]) size.setHeight(sizes[1]) if width or height: size.scale(int(width), int(height), Qt.AspectRatioMode.KeepAspectRatio) logger.info('Rasterizing %r to %dx%d' % (elem, size.width(), size.height())) image = QImage(size, QImage.Format.Format_ARGB32_Premultiplied) image.fill(QColor("white").rgb()) painter = QPainter(image) svg.render(painter) painter.end() array = QByteArray() buffer = QBuffer(array) buffer.open(QIODevice.OpenModeFlag.WriteOnly) image.save(buffer, format) return array.data()
def failed_img(self): if self._failed_img is None: try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() i = QImage(200, 150, QImage.Format.Format_ARGB32) i.setDevicePixelRatio(dpr) i.fill(Qt.GlobalColor.white) p = QPainter(i) r = i.rect().adjusted(10, 10, -10, -10) n = QPen(Qt.PenStyle.DashLine) n.setColor(Qt.GlobalColor.black) p.setPen(n) p.drawRect(r) p.setPen(Qt.GlobalColor.black) f = self.font() f.setPixelSize(20) p.setFont(f) p.drawText(r.adjusted(10, 0, -10, 0), Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap, _('Image could not be rendered')) p.end() self._failed_img = QPixmap.fromImage(i) return self._failed_img
def rasterize_external(self, elem, style, item, svgitem): width = style['width'] height = style['height'] width = (width / 72) * self.profile.dpi height = (height / 72) * self.profile.dpi data = QByteArray(svgitem.bytes_representation) svg = QSvgRenderer(data) size = svg.defaultSize() size.scale(width, height, Qt.AspectRatioMode.KeepAspectRatio) key = (svgitem.href, size.width(), size.height()) if key in self.images: href = self.images[key] else: logger = self.oeb.logger logger.info('Rasterizing %r to %dx%d' % (svgitem.href, size.width(), size.height())) image = QImage(size, QImage.Format.Format_ARGB32_Premultiplied) image.fill(QColor("white").rgb()) painter = QPainter(image) svg.render(painter) painter.end() array = QByteArray() buffer = QBuffer(array) buffer.open(QIODevice.OpenModeFlag.WriteOnly) image.save(buffer, 'PNG') data = array.data() manifest = self.oeb.manifest href = os.path.splitext(svgitem.href)[0] + '.png' id, href = manifest.generate(svgitem.id, href) manifest.add(id, href, PNG_MIME, data=data) self.images[key] = href elem.tag = XHTML('img') for attr in elem.attrib: if attr not in KEEP_ATTRS: del elem.attrib[attr] elem.attrib['src'] = item.relhref(href) if elem.text: elem.attrib['alt'] = elem.text elem.text = None for child in elem: elem.remove(child)
def render_cover(self, book_id): if self.ignore_render_requests.is_set(): return dpr = self.device_pixel_ratio page_width = int(dpr * self.delegate.cover_size.width()) page_height = int(dpr * self.delegate.cover_size.height()) tcdata, timestamp = self.thumbnail_cache[book_id] use_cache = False if timestamp is None: # Not in cache has_cover, cdata, timestamp = self.model( ).db.new_api.cover_or_cache(book_id, 0) else: has_cover, cdata, timestamp = self.model( ).db.new_api.cover_or_cache(book_id, timestamp) if has_cover and cdata is None: # The cached cover is fresh cdata = tcdata use_cache = True if has_cover: p = QImage() p.loadFromData(cdata, CACHE_FORMAT if cdata is tcdata else 'JPEG') p.setDevicePixelRatio(dpr) if p.isNull() and cdata is tcdata: # Invalid image in cache self.thumbnail_cache.invalidate((book_id, )) self.update_item.emit(book_id) return cdata = None if p.isNull() else p if not use_cache: # cache is stale if cdata is not None: width, height = p.width(), p.height() scaled, nwidth, nheight = fit_image( width, height, page_width, page_height) if scaled: if self.ignore_render_requests.is_set(): return p = p.scaled( nwidth, nheight, Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) p.setDevicePixelRatio(dpr) cdata = p # update cache if cdata is None: self.thumbnail_cache.invalidate((book_id, )) else: try: self.thumbnail_cache.insert(book_id, timestamp, image_to_data(cdata)) except EncodeError as err: self.thumbnail_cache.invalidate((book_id, )) prints(err) except Exception: import traceback traceback.print_exc() elif tcdata is not None: # Cover was removed, but it exists in cache, remove from cache self.thumbnail_cache.invalidate((book_id, )) self.delegate.cover_cache.set(book_id, cdata) self.update_item.emit(book_id)
def __init__(self): pictureflow.FlowImages.__init__(self) self.num = 40000 i1, i2 = QImage(300, 400, QImage.Format.Format_RGB32), QImage(300, 400, QImage.Format.Format_RGB32) i1.fill(Qt.GlobalColor.green), i2.fill(Qt.GlobalColor.blue) self.images = [i1, i2]
def image(self, index): if self.ignore_image_requests: return QImage() return self.model.cover(index)
def render_svg(filepath): must_use_qt(headless=False) pngpath = filepath[:-4] + '.png' i = QImage(filepath) i.save(pngpath)
def decoration_for_style(palette, style, icon_size, device_pixel_ratio, is_dark): style_key = (is_dark, icon_size, device_pixel_ratio, tuple((k, style[k]) for k in sorted(style))) sentinel = object() ans = decoration_cache.get(style_key, sentinel) if ans is not sentinel: return ans ans = None kind = style.get('kind') if kind == 'color': key = 'dark' if is_dark else 'light' val = style.get(key) if val is None: which = style.get('which') val = (builtin_colors_dark if is_dark else builtin_colors_light).get(which) if val is None: val = style.get('background-color') if val is not None: ans = QColor(val) elif kind == 'decoration': which = style.get('which') if which is not None: q = builtin_decorations.get(which) if q is not None: style = q sz = int(math.ceil(icon_size * device_pixel_ratio)) canvas = QImage(sz, sz, QImage.Format.Format_ARGB32) canvas.fill(Qt.GlobalColor.transparent) canvas.setDevicePixelRatio(device_pixel_ratio) p = QPainter(canvas) p.setRenderHint(QPainter.RenderHint.Antialiasing, True) p.setPen(palette.color(QPalette.ColorRole.WindowText)) irect = QRect(0, 0, icon_size, icon_size) adjust = -2 text_rect = p.drawText(irect.adjusted(0, adjust, 0, adjust), Qt.AlignmentFlag.AlignHCenter| Qt.AlignmentFlag.AlignTop, 'a') p.drawRect(irect) fm = p.fontMetrics() pen = p.pen() if 'text-decoration-color' in style: pen.setColor(QColor(style['text-decoration-color'])) lstyle = style.get('text-decoration-style') or 'solid' q = {'dotted': Qt.PenStyle.DotLine, 'dashed': Qt.PenStyle.DashLine, }.get(lstyle) if q is not None: pen.setStyle(q) lw = fm.lineWidth() if lstyle == 'double': lw * 2 pen.setWidth(fm.lineWidth()) q = style.get('text-decoration-line') or 'underline' pos = text_rect.bottom() height = irect.bottom() - pos if q == 'overline': pos = height elif q == 'line-through': pos = text_rect.center().y() - adjust - lw // 2 p.setPen(pen) if lstyle == 'wavy': p.drawPath(wavy_path(icon_size, height, pos)) else: p.drawLine(0, pos, irect.right(), pos) p.end() ans = QPixmap.fromImage(canvas) elif 'background-color' in style: ans = QColor(style['background-color']) decoration_cache[style_key] = ans return ans
def clone_image(img): ''' Returns a shallow copy of the image. However, the underlying data buffer will be automatically copied-on-write ''' return QImage(img)
def create_canvas(width, height, bgcolor='#ffffff'): 'Create a blank canvas of the specified size and color ' img = QImage(width, height, QImage.Format.Format_RGB32) img.fill(QColor(bgcolor)) return img
def blend_image(img, bgcolor='#ffffff'): ' Used to convert images that have semi-transparent pixels to opaque by blending with the specified color ' canvas = QImage(img.size(), QImage.Format.Format_RGB32) canvas.fill(QColor(bgcolor)) overlay_image(img, canvas) return canvas