Example #1
0
 def paintEvent(self, event):
     QWidget.paintEvent(self, event)
     pmap = self._pixmap
     if pmap.isNull():
         return
     w, h = pmap.width(), pmap.height()
     ow, oh = w, h
     cw, ch = self.rect().width(), self.rect().height()
     scaled, nw, nh = fit_image(w, h, cw, ch)
     if scaled:
         pmap = pmap.scaled(int(nw*pmap.devicePixelRatio()), int(nh*pmap.devicePixelRatio()), Qt.IgnoreAspectRatio,
                 Qt.SmoothTransformation)
     w, h = int(pmap.width()/pmap.devicePixelRatio()), int(pmap.height()/pmap.devicePixelRatio())
     x = int(abs(cw - w)/2.)
     y = int(abs(ch - h)/2.)
     target = QRect(x, y, w, h)
     p = QPainter(self)
     p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
     p.drawPixmap(target, pmap)
     if self.draw_border:
         pen = QPen()
         pen.setWidth(self.BORDER_WIDTH)
         p.setPen(pen)
         p.drawRect(target)
     if self.show_size:
         draw_size(p, target, ow, oh)
     p.end()
Example #2
0
 def paintEvent(self, event):
     QWidget.paintEvent(self, event)
     pmap = self._pixmap
     if pmap.isNull():
         return
     w, h = pmap.width(), pmap.height()
     ow, oh = w, h
     cw, ch = self.rect().width(), self.rect().height()
     scaled, nw, nh = fit_image(w, h, cw, ch)
     if scaled:
         pmap = pmap.scaled(nw, nh, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
     w, h = pmap.width(), pmap.height()
     x = int(abs(cw - w) / 2.0)
     y = int(abs(ch - h) / 2.0)
     target = QRect(x, y, w, h)
     p = QPainter(self)
     p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
     p.drawPixmap(target, pmap)
     if self.draw_border:
         pen = QPen()
         pen.setWidth(self.BORDER_WIDTH)
         p.setPen(pen)
         p.drawRect(target)
     if self.show_size:
         sztgt = target.adjusted(0, 0, 0, -4)
         f = p.font()
         f.setBold(True)
         p.setFont(f)
         sz = u"\u00a0%d x %d\u00a0" % (ow, oh)
         flags = Qt.AlignBottom | Qt.AlignRight | Qt.TextSingleLine
         szrect = p.boundingRect(sztgt, flags, sz)
         p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
         p.setPen(QPen(QColor(255, 255, 255)))
         p.drawText(sztgt, flags, sz)
     p.end()
Example #3
0
 def get_lines_for_image(self, img, view):
     if img.isNull():
         return 0, 0
     w, h = img.width(), img.height()
     scaled, w, h = fit_image(w, h, view.available_width() - 3, int(0.9 * view.height()))
     line_height = view.blockBoundingRect(view.document().begin()).height()
     return int(ceil(h / line_height)) + 1, w
Example #4
0
def create_cover(report, icons=(), cols=5, size=60, padding=8):
    icons = icons or tuple(default_cover_icons(cols))
    rows = int(math.ceil(len(icons) / cols))
    canvas = create_canvas(cols * (size + padding), rows * (size + padding), '#eeeeee')
    y = -size - padding // 2
    x = 0
    for i, icon in enumerate(icons):
        if i % cols == 0:
            y += padding + size
            x = padding // 2
        else:
            x += size + padding
        if report and icon in report.name_map:
            ipath = os.path.join(report.path, report.name_map[icon])
        else:
            ipath = I(icon, allow_user_override=False)
        img = Image()
        with open(ipath, 'rb') as f:
            img.load(f.read())
        scaled, nwidth, nheight = fit_image(img.size[0], img.size[1], size, size)
        img.size = nwidth, nheight
        dx = (size - nwidth) // 2
        canvas.compose(img, x + dx, y)

    return canvas.export('JPEG')
Example #5
0
    def browse_icon(self, name="blank.png"):
        cherrypy.response.headers["Content-Type"] = "image/png"
        cherrypy.response.headers["Last-Modified"] = self.last_modified(self.build_time)

        if not hasattr(self, "__browse_icon_cache__"):
            self.__browse_icon_cache__ = {}
        if name not in self.__browse_icon_cache__:
            if name.startswith("_"):
                name = sanitize_file_name2(name[1:])
                try:
                    with open(os.path.join(config_dir, "tb_icons", name), "rb") as f:
                        data = f.read()
                except:
                    raise cherrypy.HTTPError(404, "no icon named: %r" % name)
            else:
                try:
                    data = I(name, data=True)
                except:
                    raise cherrypy.HTTPError(404, "no icon named: %r" % name)
            img = Image()
            img.load(data)
            width, height = img.size
            scaled, width, height = fit_image(width, height, 48, 48)
            if scaled:
                img.size = (width, height)

            self.__browse_icon_cache__[name] = img.export("png")
        return self.__browse_icon_cache__[name]
Example #6
0
    def browse_icon(self, name='blank.png'):
        cherrypy.response.headers['Content-Type'] = 'image/png'
        cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time)

        if not hasattr(self, '__browse_icon_cache__'):
            self.__browse_icon_cache__ = {}
        if name not in self.__browse_icon_cache__:
            if name.startswith('_'):
                name = sanitize_file_name2(name[1:])
                try:
                    with open(os.path.join(config_dir, 'tb_icons', name), 'rb') as f:
                        data = f.read()
                except:
                    raise cherrypy.HTTPError(404, 'no icon named: %r'%name)
            else:
                try:
                    data = I(name, data=True)
                except:
                    raise cherrypy.HTTPError(404, 'no icon named: %r'%name)
            img = Image()
            img.load(data)
            width, height = img.size
            scaled, width, height = fit_image(width, height, 48, 48)
            if scaled:
                img.size = (width, height)

            self.__browse_icon_cache__[name] = img.export('png')
        return self.__browse_icon_cache__[name]
Example #7
0
def scale_image(data, width=60, height=80, compression_quality=70, as_png=False, preserve_aspect_ratio=True):
    ''' Scale an image, returning it as either JPEG or PNG data (bytestring).
    Transparency is alpha blended with white when converting to JPEG. Is thread
    safe and does not require a QApplication. '''
    # We use Qt instead of ImageMagick here because ImageMagick seems to use
    # some kind of memory pool, causing memory consumption to sky rocket.
    if isinstance(data, QImage):
        img = data
    else:
        img = QImage()
        if not img.loadFromData(data):
            raise ValueError('Could not load image for thumbnail generation')
    if preserve_aspect_ratio:
        scaled, nwidth, nheight = fit_image(img.width(), img.height(), width, height)
        if scaled:
            img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio, Qt.SmoothTransformation)
    else:
        if img.width() != width or img.height() != height:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
    if not as_png and img.hasAlphaChannel():
        nimg = QImage(img.size(), QImage.Format_RGB32)
        nimg.fill(Qt.white)
        p = QPainter(nimg)
        p.drawImage(0, 0, img)
        p.end()
        img = nimg
    ba = QByteArray()
    buf = QBuffer(ba)
    buf.open(QBuffer.WriteOnly)
    fmt = 'PNG' if as_png else 'JPEG'
    if not img.save(buf, fmt, quality=compression_quality):
        raise ValueError('Failed to export thumbnail image to: ' + fmt)
    return img.width(), img.height(), ba.data()
Example #8
0
    def get_cover(self, id, thumbnail=False, thumb_width=60, thumb_height=80):
        try:
            cherrypy.response.headers['Content-Type'] = 'image/jpeg'
            cherrypy.response.timeout = 3600
            cover = self.db.cover(id, index_is_id=True)
            if cover is None:
                cover = self.default_cover
                updated = self.build_time
            else:
                updated = self.db.cover_last_modified(id, index_is_id=True)
            cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)

            if thumbnail:
                return generate_thumbnail(cover,
                        width=thumb_width, height=thumb_height)[-1]

            img = Image()
            img.load(cover)
            width, height = img.size
            scaled, width, height = fit_image(width, height,
                thumb_width if thumbnail else self.max_cover_width,
                thumb_height if thumbnail else self.max_cover_height)
            if not scaled:
                return cover
            return save_cover_data_to(img, 'img.jpg', return_data=True,
                    resize_to=(width, height))
        except Exception as err:
            import traceback
            cherrypy.log.error('Failed to generate cover:')
            cherrypy.log.error(traceback.print_exc())
            raise cherrypy.HTTPError(404, 'Failed to generate cover: %r'%err)
Example #9
0
    def rescale(self):
        from PIL import Image
        from io import BytesIO

        is_image_collection = getattr(self.opts, 'is_image_collection', False)

        if is_image_collection:
            page_width, page_height = self.opts.dest.comic_screen_size
        else:
            page_width, page_height = self.opts.dest.width, self.opts.dest.height
            page_width -= (self.opts.margin_left + self.opts.margin_right) * self.opts.dest.dpi/72.
            page_height -= (self.opts.margin_top + self.opts.margin_bottom) * self.opts.dest.dpi/72.

        for item in self.oeb.manifest:
            if item.media_type.startswith('image'):
                ext = item.media_type.split('/')[-1].upper()
                if ext == 'JPG':
                    ext = 'JPEG'
                if ext not in ('PNG', 'JPEG', 'GIF'):
                    ext = 'JPEG'

                raw = item.data
                if hasattr(raw, 'xpath') or not raw:
                    # Probably an svg image
                    continue
                try:
                    img = Image.open(BytesIO(raw))
                except Exception:
                    continue
                width, height = img.size

                try:
                    if self.check_colorspaces and img.mode == 'CMYK':
                        self.log.warn(
                            'The image %s is in the CMYK colorspace, converting it '
                            'to RGB as Adobe Digital Editions cannot display CMYK' % item.href)
                        img = img.convert('RGB')
                except Exception:
                    self.log.exception('Failed to convert image %s from CMYK to RGB' % item.href)

                scaled, new_width, new_height = fit_image(width, height, page_width, page_height)
                if scaled:
                    new_width = max(1, new_width)
                    new_height = max(1, new_height)
                    self.log('Rescaling image from %dx%d to %dx%d'%(
                        width, height, new_width, new_height), item.href)
                    try:
                        img = img.resize((new_width, new_height))
                    except Exception:
                        self.log.exception('Failed to rescale image: %s' % item.href)
                        continue
                    buf = BytesIO()
                    try:
                        img.save(buf, ext)
                    except Exception:
                        self.log.exception('Failed to rescale image: %s' % item.href)
                    else:
                        item.data = buf.getvalue()
                        item.unload_data_from_memory()
    def _get_goodreader_thumb(self, remote_path):
        '''
        remote_path is relative to /Documents
        GoodReader caches small thumbs of book covers. If we didn't send the book, fetch
        the cached copy from the iDevice. These thumbs will be scaled up to the size we
        use when sending from calibre for consistency.
        '''
        from PIL import Image as PILImage
        from calibre import fit_image

        def _build_local_path():
            '''
            GoodReader stores individual dbs for each book, matching the folder and
            name structure in the Documents folder. Make a local version, renamed to .db
            '''
            path = remote_db_path.split('/')[-1]
            if iswindows:
                from calibre.utils.filenames import shorten_components_to
                plen = len(self.temp_dir)
                path = ''.join(shorten_components_to(245-plen, [path]))

            full_path = os.path.join(self.temp_dir, path)
            base = os.path.splitext(full_path)[0]
            full_path = base + ".db"
            return os.path.normpath(full_path)

        self._log_location(remote_path)
        remote_db_path = '/'.join(['/Library','Application Support', 'com.goodiware.GoodReader.ASRoot',
                                   'Previews', '0', remote_path])

        thumb_data = None

        db_stats = self.ios.stat(remote_db_path)
        if db_stats:
            full_path = _build_local_path()
            with open(full_path, 'wb') as out:
                self.ios.copy_from_idevice(remote_db_path, out)
            local_db_path = out.name
            con = sqlite3.connect(local_db_path)
            with con:
                con.row_factory = sqlite3.Row
                cur = con.cursor()
                cur.execute('''SELECT
                                thumb
                               FROM Pages WHERE pageNum = "1"
                            ''')
                row = cur.fetchone()
                if row:
                    img_data = cStringIO.StringIO(row[b'thumb'])
                    im = PILImage.open(img_data)
                    scaled, width, height = fit_image(im.size[0], im.size[1], self.COVER_WIDTH, self.COVER_HEIGHT)
                    im = im.resize((self.COVER_WIDTH, self.COVER_HEIGHT), PILImage.NEAREST)
                    thumb = cStringIO.StringIO()
                    im.convert('RGB').save(thumb, 'JPEG')
                    thumb_data = thumb.getvalue()
                    img_data.close()
                    thumb.close()

        return thumb_data
Example #11
0
    def rescale(self, qt=True):
        from calibre.utils.magick.draw import Image

        is_image_collection = getattr(self.opts, 'is_image_collection', False)

        if is_image_collection:
            page_width, page_height = self.opts.dest.comic_screen_size
        else:
            page_width, page_height = self.opts.dest.width, self.opts.dest.height
            page_width -= (self.opts.margin_left + self.opts.margin_right) * self.opts.dest.dpi/72.
            page_height -= (self.opts.margin_top + self.opts.margin_bottom) * self.opts.dest.dpi/72.

        for item in self.oeb.manifest:
            if item.media_type.startswith('image'):
                ext = item.media_type.split('/')[-1].upper()
                if ext == 'JPG':
                    ext = 'JPEG'
                if ext not in ('PNG', 'JPEG', 'GIF'):
                    ext = 'JPEG'

                raw = item.data
                if hasattr(raw, 'xpath') or not raw:
                    # Probably an svg image
                    continue
                try:
                    img = Image()
                    img.load(raw)
                except:
                    continue
                width, height = img.size

                try:
                    if self.check_colorspaces and img.colorspace == 'CMYKColorspace':
                        # We cannot do an automatic conversion of CMYK to RGB as
                        # ImageMagick inverts colors if you just set the colorspace
                        # to rgb. See for example: https://bugs.launchpad.net/bugs/1246710
                        self.log.warn(
                            'The image %s is in the CMYK colorspace, you should convert'
                            ' it to sRGB as Adobe Digital Editions cannot render CMYK' % item.href)
                except Exception:
                    pass

                scaled, new_width, new_height = fit_image(width, height,
                        page_width, page_height)
                if scaled:
                    new_width = max(1, new_width)
                    new_height = max(1, new_height)
                    self.log('Rescaling image from %dx%d to %dx%d'%(
                        width, height, new_width, new_height), item.href)
                    try:
                        img.size = (new_width, new_height)
                        data = img.export(ext.lower())
                    except KeyboardInterrupt:
                        raise
                    except:
                        self.log.exception('Failed to rescale image')
                    else:
                        item.data = data
                        item.unload_data_from_memory()
Example #12
0
    def create_image_markup(self, html_img, stylizer, href, as_block=False):
        # TODO: img inside a link (clickable image)
        style = stylizer.style(html_img)
        floating = style['float']
        if floating not in {'left', 'right'}:
            floating = None
        if as_block:
            ml, mr = style._get('margin-left'), style._get('margin-right')
            if ml == 'auto':
                floating = 'center' if mr == 'auto' else 'right'
            if mr == 'auto':
                floating = 'center' if ml == 'auto' else 'right'
        else:
            parent = html_img.getparent()
            if len(parent) == 1 and not (parent.text or '').strip() and not (html_img.tail or '').strip():
                # We have an inline image alone inside a block
                pstyle = stylizer.style(parent)
                if pstyle['text-align'] in ('center', 'right') and 'block' in pstyle['display']:
                    floating = pstyle['text-align']
        fake_margins = floating is None
        self.count += 1
        img = self.images[href]
        name = urlunquote(posixpath.basename(href))
        width, height = style.img_size(img.width, img.height)
        scaled, width, height = fit_image(width, height, self.page_width, self.page_height)
        width, height = map(pt_to_emu, (width, height))

        makeelement, namespaces = self.document_relationships.namespace.makeelement, self.document_relationships.namespace.namespaces

        root = etree.Element('root', nsmap=namespaces)
        ans = makeelement(root, 'w:drawing', append=False)
        if floating is None:
            parent = makeelement(ans, 'wp:inline')
        else:
            parent = makeelement(ans, 'wp:anchor', **get_image_margins(style))
            # The next three lines are boilerplate that Word requires, even
            # though the DOCX specs define defaults for all of them
            parent.set('simplePos', '0'), parent.set('relativeHeight', '1'), parent.set('behindDoc',"0"), parent.set('locked', "0")
            parent.set('layoutInCell', "1"), parent.set('allowOverlap', '1')
            makeelement(parent, 'wp:simplePos', x='0', y='0')
            makeelement(makeelement(parent, 'wp:positionH', relativeFrom='margin'), 'wp:align').text = floating
            makeelement(makeelement(parent, 'wp:positionV', relativeFrom='line'), 'wp:align').text = 'top'
        makeelement(parent, 'wp:extent', cx=str(width), cy=str(height))
        if fake_margins:
            # DOCX does not support setting margins for inline images, so we
            # fake it by using effect extents to simulate margins
            makeelement(parent, 'wp:effectExtent', **{k[-1].lower():v for k, v in get_image_margins(style).iteritems()})
        else:
            makeelement(parent, 'wp:effectExtent', l='0', r='0', t='0', b='0')
        if floating is not None:
            # The idiotic Word requires this to be after the extent settings
            if as_block:
                makeelement(parent, 'wp:wrapTopAndBottom')
            else:
                makeelement(parent, 'wp:wrapSquare', wrapText='bothSides')
        self.create_docx_image_markup(parent, name, html_img.get('alt') or name, img.rid, width, height)
        return ans
Example #13
0
def save_cover_data_to(
    data,
    path=None,
    bgcolor="#ffffff",
    resize_to=None,
    compression_quality=90,
    minify_to=None,
    grayscale=False,
    data_fmt="jpeg",
):
    """
    Saves image in data to path, in the format specified by the path
    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 data: Image data as bytestring
    :param path: If None img data is returned, in JPEG format
    :param data_fmt: The fmt to return data in when path is None. Defaults to JPEG
    :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
    :param minify_to: A tuple (width, height) to specify maximum target size.
        The image will be resized to fit into this target size. If None the
        value from the tweak is used.
    """
    fmt = normalize_format_name(data_fmt if path is None else os.path.splitext(path)[1][1:])
    if isinstance(data, QImage):
        img = data
        changed = True
    else:
        img, orig_fmt = image_and_format_from_data(data)
        orig_fmt = normalize_format_name(orig_fmt)
        changed = fmt != orig_fmt
    if resize_to is not None:
        changed = True
        img = img.scaled(resize_to[0], resize_to[1], Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
    owidth, oheight = img.width(), img.height()
    nwidth, nheight = tweaks["maximum_cover_size"] if minify_to is None else minify_to
    scaled, nwidth, nheight = fit_image(owidth, oheight, nwidth, nheight)
    if scaled:
        changed = True
        img = img.scaled(nwidth, nheight, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
    if img.hasAlphaChannel():
        changed = True
        img = blend_image(img, bgcolor)
    if grayscale:
        if not img.allGray():
            changed = True
            img = grayscale_image(img)
    if path is None:
        return image_to_data(img, compression_quality, fmt) if changed else data
    with lopen(path, "wb") as f:
        f.write(image_to_data(img, compression_quality, fmt) if changed else data)
Example #14
0
    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.IgnoreAspectRatio, Qt.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)
Example #15
0
 def resize_image(self, raw, base, max_width, max_height):
     img = Image()
     img.load(raw)
     resized, nwidth, nheight = fit_image(img.size[0], img.size[1], max_width, max_height)
     if resized:
         img.size = (nwidth, nheight)
         base, ext = os.path.splitext(base)
         base = base + '-%dx%d%s' % (max_width, max_height, ext)
         raw = img.export(ext[1:])
     return raw, base, resized
Example #16
0
def blend_on_canvas(img, width, height, bgcolor="#ffffff"):
    w, h = img.width(), img.height()
    scaled, nw, nh = fit_image(w, h, width, height)
    if scaled:
        img = img.scaled(nw, nh, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
        w, h = nw, nh
    canvas = QImage(width, height, QImage.Format_RGB32)
    canvas.fill(QColor(bgcolor))
    overlay(img, canvas, (width - w) // 2, (height - h) // 2)
    return canvas
Example #17
0
    def paintEvent(self, event):
        w = self.viewport().rect().width()
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        floor = event.rect().bottom()
        ceiling = event.rect().top()
        fv = self.firstVisibleBlock().blockNumber()
        origin = self.contentOffset()
        doc = self.document()
        lines = []

        for num, text in self.headers:
            top, bot = num, num + 3
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            painter.setFont(self.heading_font)
            br = painter.drawText(3, y_top, w, y_bot - y_top - 5, Qt.TextSingleLine, text)
            painter.setPen(QPen(self.palette().text(), 2))
            painter.drawLine(0, br.bottom()+3, w, br.bottom()+3)

        for top, bot, kind in self.changes:
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            if y_top != y_bot:
                painter.fillRect(0,  y_top, w, y_bot - y_top, self.diff_backgrounds[kind])
            lines.append((y_top, y_bot, kind))
            if top in self.images:
                img, maxw = self.images[top][:2]
                if bot > top + 1 and not img.isNull():
                    y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top+1)).translated(origin).y() + 3
                    y_bot -= 3
                    scaled, imgw, imgh = fit_image(img.width(), img.height(), w - 3, y_bot - y_top)
                    painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
                    painter.drawPixmap(QRect(3, y_top, imgw, imgh), img)

        painter.end()
        PlainTextEdit.paintEvent(self, event)
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        for top, bottom, kind in sorted(lines, key=lambda (t, b, k):{'replace':0}.get(k, 1)):
            painter.setPen(QPen(self.diff_foregrounds[kind], 1))
            painter.drawLine(0, top, w, top)
            painter.drawLine(0, bottom - 1, w, bottom - 1)
Example #18
0
def icon(ctx, rd, which):
    sz = rd.query.get("sz")
    if sz != "full":
        try:
            sz = int(rd.query.get("sz", 48))
        except Exception:
            sz = 48
    if which in {"", "_"}:
        raise HTTPNotFound()
    if which.startswith("_"):
        base = os.path.join(config_dir, "tb_icons")
        path = os.path.abspath(os.path.join(base, *which[1:].split("/")))
        if not path.startswith(base) or ":" in which:
            raise HTTPNotFound("Naughty, naughty!")
    else:
        base = P("images", allow_user_override=False)
        path = os.path.abspath(os.path.join(base, *which.split("/")))
        if not path.startswith(base) or ":" in which:
            raise HTTPNotFound("Naughty, naughty!")
        path = os.path.relpath(path, base).replace(os.sep, "/")
        path = P("images/" + path)
    if sz == "full":
        try:
            return share_open(path, "rb")
        except EnvironmentError:
            raise HTTPNotFound()
    with lock:
        tdir = os.path.join(rd.tdir, "icons")
        cached = os.path.join(tdir, "%d-%s.png" % (sz, which))
        try:
            return share_open(cached, "rb")
        except EnvironmentError:
            pass
        try:
            src = share_open(path, "rb")
        except EnvironmentError:
            raise HTTPNotFound()
        with src:
            img = Image()
            img.load(src.read())
        width, height = img.size
        scaled, width, height = fit_image(width, height, sz, sz)
        if scaled:
            img.size = (width, height)
        try:
            ans = share_open(cached, "w+b")
        except EnvironmentError:
            try:
                os.mkdir(tdir)
            except EnvironmentError:
                pass
            ans = share_open(cached, "w+b")
        ans.write(img.export("png"))
        ans.seek(0)
        return ans
Example #19
0
 def do_layout(self):
     if self.rect().width() == 0 or self.rect().height() == 0:
         return
     pixmap = self.pixmap
     pwidth, pheight = pixmap.width(), pixmap.height()
     try:
         self.pwidth, self.pheight = fit_image(pwidth, pheight, self.rect().width(), self.rect().height())[1:]
     except:
         self.pwidth, self.pheight = self.rect().width() - 1, self.rect().height() - 1
     self.current_pixmap_size = QSize(self.pwidth, self.pheight)
     self.animation.setEndValue(self.current_pixmap_size)
Example #20
0
def icon(ctx, rd, which):
    sz = rd.query.get('sz')
    if sz != 'full':
        try:
            sz = int(rd.query.get('sz', 48))
        except Exception:
            sz = 48
    if which in {'', '_'}:
        raise HTTPNotFound()
    if which.startswith('_'):
        base = os.path.join(config_dir, 'tb_icons')
        path = os.path.abspath(os.path.join(base, *which[1:].split('/')))
        if not path.startswith(base) or ':' in which:
            raise HTTPNotFound('Naughty, naughty!')
    else:
        base = P('images', allow_user_override=False)
        path = os.path.abspath(os.path.join(base, *which.split('/')))
        if not path.startswith(base) or ':' in which:
            raise HTTPNotFound('Naughty, naughty!')
        path = os.path.relpath(path, base).replace(os.sep, '/')
        path = P('images/' + path)
    if sz == 'full':
        try:
            return lopen(path, 'rb')
        except EnvironmentError:
            raise HTTPNotFound()
    tdir = os.path.join(rd.tdir, 'icons')
    cached = os.path.join(tdir, '%d-%s.png' % (sz, which))
    try:
        return lopen(cached, 'rb')
    except EnvironmentError:
        pass
    try:
        src = lopen(path, 'rb')
    except EnvironmentError:
        raise HTTPNotFound()
    with src:
        img = Image()
        img.load(src.read())
    width, height = img.size
    scaled, width, height = fit_image(width, height, sz, sz)
    if scaled:
        img.size = (width, height)
    try:
        ans = lopen(cached, 'w+b')
    except EnvironmentError:
        try:
            os.mkdir(tdir)
        except EnvironmentError:
            pass
        ans = lopen(cached, 'w+b')
    ans.write(img.export('png'))
    ans.seek(0)
    return ans
Example #21
0
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(nw, nh, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
        w, h = nw, nh
    canvas = QImage(width, height, QImage.Format_RGB32)
    canvas.fill(QColor(bgcolor))
    overlay_image(img, canvas, (width - w)//2, (height - h)//2)
    return canvas
Example #22
0
def icon(ctx, rd, which):
    sz = rd.query.get('sz')
    if sz != 'full':
        try:
            sz = int(rd.query.get('sz', 48))
        except Exception:
            sz = 48
    if which in {'', '_'}:
        raise HTTPNotFound()
    if which.startswith('_'):
        base = os.path.join(config_dir, 'tb_icons')
        path = os.path.abspath(os.path.join(base, *which[1:].split('/')))
        if not path.startswith(base) or ':' in which:
            raise HTTPNotFound('Naughty, naughty!')
    else:
        base = P('images', allow_user_override=False)
        path = os.path.abspath(os.path.join(base, *which.split('/')))
        if not path.startswith(base) or ':' in which:
            raise HTTPNotFound('Naughty, naughty!')
        path = os.path.relpath(path, base).replace(os.sep, '/')
        path = P('images/' + path)
    if sz == 'full':
        try:
            return share_open(path, 'rb')
        except EnvironmentError:
            raise HTTPNotFound()
    with lock:
        tdir = os.path.join(rd.tdir, 'icons')
        cached = os.path.join(tdir, '%d-%s.png' % (sz, which))
        try:
            return share_open(cached, 'rb')
        except EnvironmentError:
            pass
        try:
            src = share_open(path, 'rb')
        except EnvironmentError:
            raise HTTPNotFound()
        with src:
            idata = src.read()
            img = image_from_data(idata)
        scaled, width, height = fit_image(img.width(), img.height(), sz, sz)
        if scaled:
            idata = scale_image(img, width, height, as_png=True)[-1]
        try:
            ans = share_open(cached, 'w+b')
        except EnvironmentError:
            try:
                os.mkdir(tdir)
            except EnvironmentError:
                pass
            ans = share_open(cached, 'w+b')
        ans.write(idata)
        ans.seek(0)
        return ans
Example #23
0
 def resize_cover(self):
     if self.cover_pixmap is None:
         return
     pixmap = self.cover_pixmap
     if self.fit_cover.isChecked():
         scaled, new_width, new_height = fit_image(pixmap.width(),
                 pixmap.height(), self.cover.size().width()-10,
                 self.cover.size().height()-10)
         if scaled:
             pixmap = pixmap.scaled(new_width, new_height,
                     Qt.KeepAspectRatio, Qt.SmoothTransformation)
     self.cover.set_pixmap(pixmap)
     self.update_cover_tooltip()
Example #24
0
def create_cover_page(
    top_lines,
    logo_path,
    width=590,
    height=750,
    bgcolor="#ffffff",
    output_format="jpg",
    texture_data=None,
    texture_opacity=1.0,
):
    """
    Create the standard calibre cover page and return it as a byte string in
    the specified output_format.
    """
    canvas = create_canvas(width, height, bgcolor)
    if texture_data and hasattr(canvas, "texture"):
        texture = Image()
        texture.load(texture_data)
        texture.set_opacity(texture_opacity)
        canvas.texture(texture)

    bottom = 10
    for line in top_lines:
        twand = create_text_wand(line.font_size, font_path=line.font_path)
        bottom = draw_centered_text(canvas, twand, line.text, bottom)
        bottom += line.bottom_margin
    bottom -= top_lines[-1].bottom_margin

    foot_font = P("fonts/liberation/LiberationMono-Regular.ttf")
    vanity = create_text_arc(__appname__ + " " + __version__, 24, font=foot_font, bgcolor="#00000000")
    lwidth, lheight = vanity.size
    left = int(max(0, (width - lwidth) / 2.0))
    top = height - lheight - 10
    canvas.compose(vanity, left, top)

    available = (width, int(top - bottom) - 20)
    if available[1] > 40:
        logo = Image()
        logo.open(logo_path)
        lwidth, lheight = logo.size
        scaled, lwidth, lheight = fit_image(lwidth, lheight, *available)
        if scaled:
            logo.size = (lwidth, lheight)
        left = int(max(0, (width - lwidth) / 2.0))
        top = bottom + 10
        extra = int((available[1] - lheight) / 2.0)
        if extra > 0:
            top += extra
        canvas.compose(logo, left, top)

    return canvas.export(output_format)
Example #25
0
 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.SmoothPixmapTransform)
     painter.drawImage(rect, logo)
     return self.ccolor1, self.ccolor1, self.ccolor1
Example #26
0
def draw_image_page(page_rect, painter, p, preserve_aspect_ratio=True):
    if preserve_aspect_ratio:
        aspect_ratio = float(p.width()) / p.height()
        nw, nh = page_rect.width(), page_rect.height()
        if aspect_ratio > 1:
            nh = int(page_rect.width() / aspect_ratio)
        else:  # Width is smaller than height
            nw = page_rect.height() * aspect_ratio
        __, nnw, nnh = fit_image(nw, nh, page_rect.width(), page_rect.height())
        dx = int((page_rect.width() - nnw) / 2.0)
        dy = int((page_rect.height() - nnh) / 2.0)
        page_rect.translate(dx, dy)
        page_rect.setHeight(nnh)
        page_rect.setWidth(nnw)
    painter.drawPixmap(page_rect, p, p.rect())
Example #27
0
    def rescale(self, qt=True):
        from calibre.utils.magick.draw import Image

        is_image_collection = getattr(self.opts, 'is_image_collection', False)

        if is_image_collection:
            page_width, page_height = self.opts.dest.comic_screen_size
        else:
            page_width, page_height = self.opts.dest.width, self.opts.dest.height
            page_width -= (self.opts.margin_left + self.opts.margin_right) * self.opts.dest.dpi/72.
            page_height -= (self.opts.margin_top + self.opts.margin_bottom) * self.opts.dest.dpi/72.

        for item in self.oeb.manifest:
            if item.media_type.startswith('image'):
                ext = item.media_type.split('/')[-1].upper()
                if ext == 'JPG': ext = 'JPEG'
                if ext not in ('PNG', 'JPEG', 'GIF'):
                    ext = 'JPEG'

                raw = item.data
                if hasattr(raw, 'xpath') or not raw:
                    # Probably an svg image
                    continue
                try:
                    img = Image()
                    img.load(raw)
                except:
                    continue
                width, height = img.size


                scaled, new_width, new_height = fit_image(width, height,
                        page_width, page_height)
                if scaled:
                    new_width = max(1, new_width)
                    new_height = max(1, new_height)
                    self.log('Rescaling image from %dx%d to %dx%d'%(
                        width, height, new_width, new_height), item.href)
                    try:
                        img.size = (new_width, new_height)
                        data = img.export(ext.lower())
                    except KeyboardInterrupt:
                        raise
                    except:
                        self.log.exception('Failed to rescale image')
                    else:
                        item.data = data
                        item.unload_data_from_memory()
Example #28
0
 def load_pixmap(self):
     canvas_size = self.rect().width(), self.rect().height()
     if self.last_canvas_size != canvas_size:
         if self.last_canvas_size is not None and self.selection_state.rect is not None:
             self.selection_state.reset()
             # TODO: Migrate the selection rect
         self.last_canvas_size = canvas_size
         self.current_scaled_pixmap = None
     if self.current_scaled_pixmap is None:
         pwidth, pheight = self.last_canvas_size
         i = self.current_image
         width, height = i.width(), i.height()
         scaled, width, height = fit_image(width, height, pwidth, pheight)
         if scaled:
             i = self.current_image.scaled(width, height, transformMode=Qt.SmoothTransformation)
         self.current_scaled_pixmap = QPixmap.fromImage(i)
Example #29
0
 def resize_cover(self):
     if self.cover_pixmap is None:
         return
     self.setWindowIcon(QIcon(self.cover_pixmap))
     pixmap = self.cover_pixmap
     if self.fit_cover.isChecked():
         scaled, new_width, new_height = fit_image(pixmap.width(),
                 pixmap.height(), self.cover.size().width()-10,
                 self.cover.size().height()-10)
         if scaled:
             pixmap = pixmap.scaled(new_width, new_height,
                     Qt.KeepAspectRatio, Qt.SmoothTransformation)
     self.cover.set_pixmap(pixmap)
     sz = pixmap.size()
     self.cover.setToolTip(_('Cover size: %(width)d x %(height)d')%dict(
         width=sz.width(), height=sz.height()))
Example #30
0
def scale_image(data, width=60, height=80, compression_quality=70, as_png=False, preserve_aspect_ratio=True):
    ''' Scale an image, returning it as either JPEG or PNG data (bytestring).
    Transparency is alpha blended with white when converting to JPEG. Is thread
    safe and does not require a QApplication. '''
    # We use Qt instead of ImageMagick here because ImageMagick seems to use
    # some kind of memory pool, causing memory consumption to sky rocket.
    img = image_from_data(data)
    if preserve_aspect_ratio:
        scaled, nwidth, nheight = fit_image(img.width(), img.height(), width, height)
        if scaled:
            img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio, Qt.SmoothTransformation)
    else:
        if img.width() != width or img.height() != height:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
    fmt = 'PNG' if as_png else 'JPEG'
    w, h = img.width(), img.height()
    return w, h, image_to_data(img, compression_quality=compression_quality, fmt=fmt)
Example #31
0
 def paintEvent(self, event):
     QWidget.paintEvent(self, event)
     pmap = self._pixmap
     if pmap.isNull():
         return
     w, h = pmap.width(), pmap.height()
     ow, oh = w, h
     cw, ch = self.rect().width(), self.rect().height()
     scaled, nw, nh = fit_image(w, h, cw, ch)
     if scaled:
         pmap = pmap.scaled(int(nw * pmap.devicePixelRatio()),
                            int(nh * pmap.devicePixelRatio()),
                            Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
     w, h = int(pmap.width() / pmap.devicePixelRatio()), int(
         pmap.height() / pmap.devicePixelRatio())
     x = int(abs(cw - w) / 2.)
     y = int(abs(ch - h) / 2.)
     target = QRect(x, y, w, h)
     p = QPainter(self)
     p.setRenderHints(QPainter.Antialiasing
                      | QPainter.SmoothPixmapTransform)
     p.drawPixmap(target, pmap)
     if self.draw_border:
         pen = QPen()
         pen.setWidth(self.BORDER_WIDTH)
         p.setPen(pen)
         p.drawRect(target)
     if self.show_size:
         sztgt = target.adjusted(0, 0, 0, -4)
         f = p.font()
         f.setBold(True)
         p.setFont(f)
         sz = u'\u00a0%d x %d\u00a0' % (ow, oh)
         flags = Qt.AlignBottom | Qt.AlignRight | Qt.TextSingleLine
         szrect = p.boundingRect(sztgt, flags, sz)
         p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
         p.setPen(QPen(QColor(255, 255, 255)))
         p.drawText(sztgt, flags, sz)
     p.end()
Example #32
0
def create_cover(report, icons=(), cols=5, size=120, padding=16):
    icons = icons or tuple(default_cover_icons(cols))
    rows = int(math.ceil(len(icons) / cols))
    with Canvas(cols * (size + padding), rows * (size + padding), bgcolor='#eee') as canvas:
        y = -size - padding // 2
        x = 0
        for i, icon in enumerate(icons):
            if i % cols == 0:
                y += padding + size
                x = padding // 2
            else:
                x += size + padding
            if report and icon in report.name_map:
                ipath = os.path.join(report.path, report.name_map[icon])
            else:
                ipath = I(icon, allow_user_override=False)
            with lopen(ipath, 'rb') as f:
                img = image_from_data(f.read())
            scaled, nwidth, nheight = fit_image(img.width(), img.height(), size, size)
            img = img.scaled(nwidth, nheight, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
            dx = (size - nwidth) // 2
            canvas.compose(img, x + dx, y)
    return canvas.export()
Example #33
0
 def resize_cover(self):
     if self.cover_pixmap is None:
         self.cover.set_marked(self.marked)
         return
     pixmap = self.cover_pixmap
     if self.fit_cover.isChecked():
         scaled, new_width, new_height = fit_image(
             pixmap.width(), pixmap.height(),
             self.cover.size().width() - 10,
             self.cover.size().height() - 10)
         if scaled:
             try:
                 dpr = self.devicePixelRatioF()
             except AttributeError:
                 dpr = self.devicePixelRatio()
             pixmap = pixmap.scaled(
                 int(dpr * new_width), int(dpr * new_height),
                 Qt.AspectRatioMode.KeepAspectRatio,
                 Qt.TransformationMode.SmoothTransformation)
             pixmap.setDevicePixelRatio(dpr)
     self.cover.set_pixmap(pixmap)
     self.cover.set_marked(self.marked)
     self.update_cover_tooltip()
Example #34
0
def thumbnail(data,
              width=120,
              height=120,
              bgcolor='#ffffff',
              fmt='jpg',
              preserve_aspect_ratio=True,
              compression_quality=70):
    img = Image()
    img.load(data)
    owidth, oheight = img.size
    if not preserve_aspect_ratio:
        scaled = owidth > width or oheight > height
        nwidth = width
        nheight = height
    else:
        scaled, nwidth, nheight = fit_image(owidth, oheight, width, height)
    if scaled:
        img.size = (nwidth, nheight)
    canvas = create_canvas(img.size[0], img.size[1], bgcolor)
    canvas.compose(img)
    if fmt == 'jpg':
        canvas.set_compression_quality(compression_quality)
    return (canvas.size[0], canvas.size[1], canvas.export(fmt))
Example #35
0
 def paint(self, painter, option, index):
     name = index.data(Qt.ItemDataRole.DisplayRole)
     sz = human_readable(index.data(Qt.ItemDataRole.UserRole))
     pmap = index.data(Qt.ItemDataRole.UserRole+1)
     irect = option.rect.adjusted(0, 5, 0, -5)
     irect.setRight(irect.left() + 70)
     if pmap is None:
         pmap = QPixmap(current_container().get_file_path_for_processing(name))
         scaled, nwidth, nheight = fit_image(pmap.width(), pmap.height(), irect.width(), irect.height())
         if scaled:
             pmap = pmap.scaled(nwidth, nheight, transformMode=Qt.TransformationMode.SmoothTransformation)
         index.model().setData(index, pmap, Qt.ItemDataRole.UserRole+1)
     x, y = (irect.width() - pmap.width())//2, (irect.height() - pmap.height())//2
     r = irect.adjusted(x, y, -x, -y)
     QStyledItemDelegate.paint(self, painter, option, empty_index)
     painter.drawPixmap(r, pmap)
     trect = irect.adjusted(irect.width() + 10, 0, 0, 0)
     trect.setRight(option.rect.right())
     painter.save()
     if option.state & QStyle.StateFlag.State_Selected:
         painter.setPen(QPen(option.palette.color(QPalette.ColorRole.HighlightedText)))
     painter.drawText(trect, Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft, name + '\n' + sz)
     painter.restore()
Example #36
0
 def load_pixmap(self):
     canvas_size = self.rect().width(), self.rect().height()
     if self.last_canvas_size != canvas_size:
         if self.last_canvas_size is not None and self.selection_state.rect is not None:
             self.selection_state.reset()
             # TODO: Migrate the selection rect
         self.last_canvas_size = canvas_size
         self.current_scaled_pixmap = None
     if self.current_scaled_pixmap is None:
         pwidth, pheight = self.last_canvas_size
         i = self.current_image
         width, height = i.width(), i.height()
         scaled, width, height = fit_image(width, height, pwidth, pheight)
         try:
             dpr = self.devicePixelRatioF()
         except AttributeError:
             dpr = self.devicePixelRatio()
         if scaled:
             i = self.current_image.scaled(
                 int(dpr * width),
                 int(dpr * height),
                 transformMode=Qt.TransformationMode.SmoothTransformation)
         self.current_scaled_pixmap = QPixmap.fromImage(i)
         self.current_scaled_pixmap.setDevicePixelRatio(dpr)
Example #37
0
    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, QModelIndex())  # draw the hover and selection highlights
        name = unicode(index.data(Qt.DisplayRole) or '')
        cover = self.cover_cache.get(name, None)
        if cover is None:
            cover = self.cover_cache[name] = QPixmap()
            try:
                raw = current_container().raw_data(name, decode=False)
            except:
                pass
            else:
                cover.loadFromData(raw)
                if not cover.isNull():
                    scaled, width, height = fit_image(cover.width(), cover.height(), self.cover_size.width(), self.cover_size.height())
                    if scaled:
                        cover = self.cover_cache[name] = cover.scaled(width, height, transformMode=Qt.SmoothTransformation)

        painter.save()
        try:
            rect = option.rect
            rect.adjust(self.MARGIN, self.MARGIN, -self.MARGIN, -self.MARGIN)
            trect = QRect(rect)
            rect.setBottom(rect.bottom() - self.title_height)
            if not cover.isNull():
                dx = max(0, int((rect.width() - cover.width())/2.0))
                dy = max(0, rect.height() - cover.height())
                rect.adjust(dx, dy, -dx, 0)
                painter.drawPixmap(rect, cover)
            rect = trect
            rect.setTop(rect.bottom() - self.title_height + 5)
            painter.setRenderHint(QPainter.TextAntialiasing, True)
            metrics = painter.fontMetrics()
            painter.drawText(rect, Qt.AlignCenter|Qt.TextSingleLine,
                                metrics.elidedText(name, Qt.ElideLeft, rect.width()))
        finally:
            painter.restore()
Example #38
0
    def rescale_image(self, data):
        orig_w, orig_h, ifmt = identify_data(data)
        orig_data = data # save it in case compression fails
        if self.scale_news_images is not None:
            wmax, hmax = self.scale_news_images
            scale, new_w, new_h = fit_image(orig_w, orig_h, wmax, hmax)
            if scale:
                data = thumbnail(data, new_w, new_h, compression_quality=95)[-1]
                orig_w = new_w
                orig_h = new_h
        if self.compress_news_images_max_size is None:
            if self.compress_news_images_auto_size is None: # not compressing
                return data
            else:
                maxsizeb = (orig_w * orig_h)/self.compress_news_images_auto_size
        else:
            maxsizeb = self.compress_news_images_max_size * 1024
        scaled_data = data # save it in case compression fails
        if len(scaled_data) <= maxsizeb: # no compression required
            return scaled_data

        img = Image()
        quality = 95
        img.load(data)
        while len(data) >= maxsizeb and quality >= 5:
            quality -= 5
            img.set_compression_quality(quality)
            data = img.export('jpg')

        if len(data) >= len(scaled_data): # compression failed
            return orig_data if len(orig_data) <= len(scaled_data) else scaled_data

        if len(data) >= len(orig_data): # no improvement
            return orig_data

        return data
Example #39
0
    def get_cover(self, id, thumbnail=False, thumb_width=60, thumb_height=80):
        try:
            cherrypy.response.headers['Content-Type'] = 'image/jpeg'
            cherrypy.response.timeout = 3600
            cover = self.db.cover(id, index_is_id=True)
            if cover is None:
                cover = self.default_cover
                updated = self.build_time
            else:
                updated = self.db.cover_last_modified(id, index_is_id=True)
            cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)

            if thumbnail:
                quality = tweaks['content_server_thumbnail_compression_quality']
                if quality < 50:
                    quality = 50
                elif quality > 99:
                    quality = 99
                return generate_thumbnail(cover, width=thumb_width,
                        height=thumb_height, compression_quality=quality)[-1]

            img = Image()
            img.load(cover)
            width, height = img.size
            scaled, width, height = fit_image(width, height,
                thumb_width if thumbnail else self.max_cover_width,
                thumb_height if thumbnail else self.max_cover_height)
            if not scaled:
                return cover
            return save_cover_data_to(img, 'img.jpg', return_data=True,
                    resize_to=(width, height))
        except Exception as err:
            import traceback
            cherrypy.log.error('Failed to generate cover:')
            cherrypy.log.error(traceback.print_exc())
            raise cherrypy.HTTPError(404, 'Failed to generate cover: %r'%err)
Example #40
0
    def rescale(self):
        from PIL import Image
        from io import BytesIO

        is_image_collection = getattr(self.opts, 'is_image_collection', False)

        if is_image_collection:
            page_width, page_height = self.opts.dest.comic_screen_size
        else:
            page_width, page_height = self.opts.dest.width, self.opts.dest.height
            page_width -= (self.opts.margin_left +
                           self.opts.margin_right) * self.opts.dest.dpi / 72.
            page_height -= (self.opts.margin_top +
                            self.opts.margin_bottom) * self.opts.dest.dpi / 72.

        for item in self.oeb.manifest:
            if item.media_type.startswith('image'):
                ext = item.media_type.split('/')[-1].upper()
                if ext == 'JPG':
                    ext = 'JPEG'
                if ext not in ('PNG', 'JPEG', 'GIF'):
                    ext = 'JPEG'

                raw = item.data
                if hasattr(raw, 'xpath') or not raw:
                    # Probably an svg image
                    continue
                try:
                    img = Image.open(BytesIO(raw))
                except Exception:
                    continue
                width, height = img.size

                try:
                    if self.check_colorspaces and img.mode == 'CMYK':
                        self.log.warn(
                            'The image %s is in the CMYK colorspace, converting it '
                            'to RGB as Adobe Digital Editions cannot display CMYK'
                            % item.href)
                        img = img.convert('RGB')
                except Exception:
                    self.log.exception(
                        'Failed to convert image %s from CMYK to RGB' %
                        item.href)

                scaled, new_width, new_height = fit_image(
                    width, height, page_width, page_height)
                if scaled:
                    new_width = max(1, new_width)
                    new_height = max(1, new_height)
                    self.log(
                        'Rescaling image from %dx%d to %dx%d' %
                        (width, height, new_width, new_height), item.href)
                    img.resize((new_width, new_height))
                    buf = BytesIO()
                    try:
                        img.save(buf, ext)
                    except Exception:
                        self.log.exception('Failed to rescale image')
                    else:
                        item.data = buf.getvalue()
                        item.unload_data_from_memory()
Example #41
0
    def rescale(self, qt=True):
        from calibre.utils.magick.draw import Image

        is_image_collection = getattr(self.opts, 'is_image_collection', False)

        if is_image_collection:
            page_width, page_height = self.opts.dest.comic_screen_size
        else:
            page_width, page_height = self.opts.dest.width, self.opts.dest.height
            page_width -= (self.opts.margin_left + self.opts.margin_right) * self.opts.dest.dpi/72.
            page_height -= (self.opts.margin_top + self.opts.margin_bottom) * self.opts.dest.dpi/72.

        for item in self.oeb.manifest:
            if item.media_type.startswith('image'):
                ext = item.media_type.split('/')[-1].upper()
                if ext == 'JPG':
                    ext = 'JPEG'
                if ext not in ('PNG', 'JPEG', 'GIF'):
                    ext = 'JPEG'

                raw = item.data
                if hasattr(raw, 'xpath') or not raw:
                    # Probably an svg image
                    continue
                try:
                    img = Image()
                    img.load(raw)
                except:
                    continue
                width, height = img.size

                try:
                    if self.check_colorspaces and img.colorspace == 'CMYKColorspace':
                        # We cannot do an imagemagick conversion of CMYK to RGB as
                        # ImageMagick inverts colors if you just set the colorspace
                        # to rgb. See for example: https://bugs.launchpad.net/bugs/1246710
                        from PyQt4.Qt import QImage
                        from calibre.gui2 import pixmap_to_data
                        qimg = QImage()
                        qimg.loadFromData(raw)
                        if not qimg.isNull():
                            raw = item.data = pixmap_to_data(qimg, format=ext, quality=95)
                            img = Image()
                            img.load(raw)
                            self.log.warn(
                                'The image %s is in the CMYK colorspace, converting it '
                                'to RGB as Adobe Digital Editions cannot display CMYK' % item.href)
                        else:
                            self.log.warn(
                                'The image %s is in the CMYK colorspace, you should convert'
                                ' it to sRGB as Adobe Digital Editions cannot render CMYK' % item.href)
                except Exception:
                    pass

                scaled, new_width, new_height = fit_image(width, height,
                        page_width, page_height)
                if scaled:
                    new_width = max(1, new_width)
                    new_height = max(1, new_height)
                    self.log('Rescaling image from %dx%d to %dx%d'%(
                        width, height, new_width, new_height), item.href)
                    try:
                        img.size = (new_width, new_height)
                        data = img.export(ext.lower())
                    except KeyboardInterrupt:
                        raise
                    except:
                        self.log.exception('Failed to rescale image')
                    else:
                        item.data = data
                        item.unload_data_from_memory()
Example #42
0
    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)
Example #43
0
def resize_to_fit(img, width, height):
    img = image_from_data(img)
    resize_needed, nw, nh = fit_image(img.width(), img.height(), width, height)
    if resize_needed:
        resize_image(img, nw, nh)
    return resize_needed, img
Example #44
0
    def create_image_markup(self, html_img, stylizer, href, as_block=False):
        # TODO: img inside a link (clickable image)
        style = stylizer.style(html_img)
        floating = style['float']
        if floating not in {'left', 'right'}:
            floating = None
        if as_block:
            ml, mr = style._get('margin-left'), style._get('margin-right')
            if ml == 'auto':
                floating = 'center' if mr == 'auto' else 'right'
            if mr == 'auto':
                floating = 'center' if ml == 'auto' else 'right'
        else:
            parent = html_img.getparent()
            if len(parent) == 1 and not (parent.text or '').strip() and not (
                    html_img.tail or '').strip():
                pstyle = stylizer.style(parent)
                if 'block' in pstyle['display']:
                    # We have an inline image alone inside a block
                    as_block = True
                    floating = pstyle['float']
                    if floating not in {'left', 'right'}:
                        floating = None
                        if pstyle['text-align'] in ('center', 'right'):
                            floating = pstyle['text-align']
                    floating = floating or 'left'
        fake_margins = floating is None
        self.count += 1
        img = self.images[href]
        name = urlunquote(posixpath.basename(href))
        width, height = style.img_size(img.width, img.height)
        scaled, width, height = fit_image(width, height, self.page_width,
                                          self.page_height)
        width, height = map(pt_to_emu, (width, height))

        makeelement, namespaces = self.document_relationships.namespace.makeelement, self.document_relationships.namespace.namespaces

        root = etree.Element('root', nsmap=namespaces)
        ans = makeelement(root, 'w:drawing', append=False)
        if floating is None:
            parent = makeelement(ans, 'wp:inline')
        else:
            parent = makeelement(ans, 'wp:anchor', **get_image_margins(style))
            # The next three lines are boilerplate that Word requires, even
            # though the DOCX specs define defaults for all of them
            parent.set('simplePos',
                       '0'), parent.set('relativeHeight', '1'), parent.set(
                           'behindDoc', "0"), parent.set('locked', "0")
            parent.set('layoutInCell', "1"), parent.set('allowOverlap', '1')
            makeelement(parent, 'wp:simplePos', x='0', y='0')
            makeelement(
                makeelement(parent, 'wp:positionH', relativeFrom='margin'),
                'wp:align').text = floating
            makeelement(
                makeelement(parent, 'wp:positionV', relativeFrom='line'),
                'wp:align').text = 'top'
        makeelement(parent, 'wp:extent', cx=str(width), cy=str(height))
        if fake_margins:
            # DOCX does not support setting margins for inline images, so we
            # fake it by using effect extents to simulate margins
            makeelement(
                parent, 'wp:effectExtent', **{
                    k[-1].lower(): v
                    for k, v in get_image_margins(style).iteritems()
                })
        else:
            makeelement(parent, 'wp:effectExtent', l='0', r='0', t='0', b='0')
        if floating is not None:
            # The idiotic Word requires this to be after the extent settings
            if as_block:
                makeelement(parent, 'wp:wrapTopAndBottom')
            else:
                makeelement(parent, 'wp:wrapSquare', wrapText='bothSides')
        self.create_docx_image_markup(parent, name,
                                      html_img.get('alt') or name, img.rid,
                                      width, height)
        return ans
Example #45
0
    def paintEvent(self, event):
        w = self.viewport().rect().width()
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        floor = event.rect().bottom()
        ceiling = event.rect().top()
        fv = self.firstVisibleBlock().blockNumber()
        origin = self.contentOffset()
        doc = self.document()
        lines = []

        for num, text in self.headers:
            top, bot = num, num + 3
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(
                doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(
                doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            painter.setFont(self.heading_font)
            br = painter.drawText(3, y_top, w, y_bot - y_top - 5,
                                  Qt.TextSingleLine, text)
            painter.setPen(QPen(self.palette().text(), 2))
            painter.drawLine(0, br.bottom() + 3, w, br.bottom() + 3)

        for top, bot, kind in self.changes:
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(
                doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(
                doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            if y_top != y_bot:
                painter.fillRect(0, y_top, w, y_bot - y_top,
                                 self.diff_backgrounds[kind])
            lines.append((y_top, y_bot, kind))
            if top in self.images:
                img, maxw = self.images[top][:2]
                if bot > top + 1 and not img.isNull():
                    y_top = self.blockBoundingGeometry(
                        doc.findBlockByNumber(top +
                                              1)).translated(origin).y() + 3
                    y_bot -= 3
                    scaled, imgw, imgh = fit_image(img.width(), img.height(),
                                                   w - 3, y_bot - y_top)
                    painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
                    painter.drawPixmap(QRect(3, y_top, imgw, imgh), img)

        painter.end()
        PlainTextEdit.paintEvent(self, event)
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        for top, bottom, kind in sorted(lines,
                                        key=lambda
                                        (t, b, k): {'replace': 0}.get(k, 1)):
            painter.setPen(QPen(self.diff_foregrounds[kind], 1))
            painter.drawLine(0, top, w, top)
            painter.drawLine(0, bottom - 1, w, bottom - 1)
Example #46
0
def save_cover_data_to(data,
                       path,
                       bgcolor='#ffffff',
                       resize_to=None,
                       return_data=False,
                       compression_quality=90,
                       minify_to=None,
                       grayscale=False):
    '''
    Saves image in data to path, in the format specified by the path
    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 data: Image data as bytestring or Image object
    :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
    :param minify_to: A tuple (width, height) to specify maximum target size.
    :param grayscale: If True, the image is grayscaled
    will be resized to fit into this target size. If None the value from the
    tweak is used.

    '''
    changed = False
    img = _data_to_image(data)
    orig_fmt = normalize_format_name(img.format)
    fmt = os.path.splitext(path)[1]
    fmt = normalize_format_name(fmt[1:])

    if grayscale:
        img.type = "GrayscaleType"
        changed = True

    if resize_to is not None:
        img.size = (resize_to[0], resize_to[1])
        changed = True
    owidth, oheight = img.size
    nwidth, nheight = tweaks[
        'maximum_cover_size'] if minify_to is None else minify_to
    scaled, nwidth, nheight = fit_image(owidth, oheight, nwidth, nheight)
    if scaled:
        img.size = (nwidth, nheight)
        changed = True
    if 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

    ret = None
    if return_data:
        ret = data
        if changed or isinstance(ret, Image):
            if hasattr(img, 'set_compression_quality') and fmt == 'jpg':
                img.set_compression_quality(compression_quality)
            ret = img.export(fmt)
    else:
        if changed or isinstance(ret, Image):
            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)
    return ret
    def _generate_thumbnail(self, book):
        '''
        Fetch the cover image, generate a thumbnail, cache
        Extracts covers from zipped epubs
        '''
        self._log_location(book.title)
        #self._log("book_path: %s" % book.path)
        #self._log("book: '%s' by %s uuid: %s" % (book.title, book.author, book.uuid))

        # Parse the cover from the connected device, model Fetch_Annotations:_get_epub_toc()

        thumb_data = None
        thumb_path = book.path.rpartition('.')[0] + '.jpg'

        # Try getting the cover from the cache
        try:
            zfr = ZipFile(self.archive_path)
            thumb_data = zfr.read(thumb_path)
            if thumb_data == 'None':
                self._log("returning None from cover cache")
                zfr.close()
                return None
        except:
            self._log("opening cover cache for appending")
            zfw = ZipFile(self.archive_path, mode='a')
        else:
            self._log("returning thumb from cover cache")
            return thumb_data

        # Get the cover from the book
        try:
            stream = cStringIO.StringIO(self.ios.read(book.path, mode='rb'))
            mi = get_metadata(stream)
            if mi.cover_data is not None:
                img_data = cStringIO.StringIO(mi.cover_data[1])
        except:
            if self.verbose:
                self._log("ERROR: unable to get cover from '%s'" % book.title)
                import traceback
                #traceback.print_exc()
                exc_type, exc_value, exc_traceback = sys.exc_info()
                self._log(traceback.format_exception_only(exc_type, exc_value)[0].strip())
            return thumb_data

        # Generate a thumb
        try:
            im = PILImage.open(img_data)
            scaled, width, height = fit_image(im.size[0], im.size[1], 60, 80)
            im = im.resize((int(width), int(height)), PILImage.ANTIALIAS)

            thumb = cStringIO.StringIO()
            im.convert('RGB').save(thumb, 'JPEG')
            thumb_data = thumb.getvalue()
            thumb.close()
            self._log("SUCCESS: generated thumb for '%s', caching" % book.title)
            # Cache the tagged thumb
            zfw.writestr(thumb_path, thumb_data)
        except:
            if self.verbose:
                self._log("ERROR generating thumb for '%s', caching empty marker" % book.title)
                import traceback
                exc_type, exc_value, exc_traceback = sys.exc_info()
                self._log(traceback.format_exception_only(exc_type, exc_value)[0].strip())
            # Cache the empty cover
            zfw.writestr(thumb_path, 'None')
        finally:
            img_data.close()
            zfw.close()

        return thumb_data
Example #48
0
 def pixmap(self, thumbnail_height, entry):
     pmap = QPixmap(current_container().name_to_abspath(entry.name)) if entry.width > 0 and entry.height > 0 else QPixmap()
     scaled, width, height = fit_image(entry.width, entry.height, thumbnail_height, thumbnail_height)
     if scaled and not pmap.isNull():
         pmap = pmap.scaled(width, height, transformMode=Qt.SmoothTransformation)
     return pmap
Example #49
0
def save_cover_data_to(data,
                       path=None,
                       bgcolor='#ffffff',
                       resize_to=None,
                       compression_quality=90,
                       minify_to=None,
                       grayscale=False,
                       eink=False,
                       letterbox=False,
                       letterbox_color='#000000',
                       data_fmt='jpeg'):
    '''
    Saves image in data to path, in the format specified by the path
    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 data: Image data as bytestring
    :param path: If None img data is returned, in JPEG format
    :param data_fmt: The fmt to return data in when path is None. Defaults to JPEG
    :param compression_quality: The quality of the image after compression.
        Number between 1 and 100. 1 means highest compression, 100 means no
        compression (lossless). When generating PNG this number is divided by 10
        for the png_compression_level.
    :param bgcolor: The color for transparent pixels. Must be specified in hex.
    :param resize_to: A tuple (width, height) or None for no resizing
    :param minify_to: A tuple (width, height) to specify maximum target size.
        The image will be resized to fit into this target size. If None the
        value from the tweak is used.
    :param grayscale: If True, the image is converted to grayscale,
        if that's not already the case.
    :param eink: If True, the image is dithered down to the 16 specific shades
        of gray of the eInk palette.
        Works best with formats that actually support color indexing (i.e., PNG)
    :param letterbox: If True, in addition to fit resize_to inside minify_to,
        the image will be letterboxed (i.e., centered on a black background).
    :param letterbox_color: If letterboxing is used, this is the background color
        used. The default is black.
    '''
    fmt = normalize_format_name(
        data_fmt if path is None else os.path.splitext(path)[1][1:])
    if isinstance(data, QImage):
        img = data
        changed = True
    else:
        img, orig_fmt = image_and_format_from_data(data)
        orig_fmt = normalize_format_name(orig_fmt)
        changed = fmt != orig_fmt
    if resize_to is not None:
        changed = True
        img = img.scaled(resize_to[0], resize_to[1],
                         Qt.AspectRatioMode.IgnoreAspectRatio,
                         Qt.TransformationMode.SmoothTransformation)
    owidth, oheight = img.width(), img.height()
    nwidth, nheight = tweaks[
        'maximum_cover_size'] if minify_to is None else minify_to
    if letterbox:
        img = blend_on_canvas(img, nwidth, nheight, bgcolor=letterbox_color)
        # Check if we were minified
        if oheight != nheight or owidth != nwidth:
            changed = True
    else:
        scaled, nwidth, nheight = fit_image(owidth, oheight, nwidth, nheight)
        if scaled:
            changed = True
            img = img.scaled(nwidth, nheight,
                             Qt.AspectRatioMode.IgnoreAspectRatio,
                             Qt.TransformationMode.SmoothTransformation)
    if img.hasAlphaChannel():
        changed = True
        img = blend_image(img, bgcolor)
    if grayscale and not eink:
        if not img.allGray():
            changed = True
            img = grayscale_image(img)
    if eink:
        # NOTE: Keep in mind that JPG does NOT actually support indexed colors, so the JPG algorithm will then smush everything back into a 256c mess...
        #       Thankfully, Nickel handles PNG just fine, and we potentially generate smaller files to boot, because they can be properly color indexed ;).
        img = eink_dither_image(img)
        changed = True
    if path is None:
        return image_to_data(img, compression_quality, fmt,
                             compression_quality // 10) if changed else data
    with lopen(path, 'wb') as f:
        f.write(
            image_to_data(img, compression_quality, fmt, compression_quality //
                          10) if changed else data)
    def _get_goodreader_thumb(self, remote_path):
        '''
        remote_path is relative to /Documents
        GoodReader caches small thumbs of book covers. If we didn't send the book, fetch
        the cached copy from the iDevice. These thumbs will be scaled up to the size we
        use when sending from calibre for consistency.
        '''
        from PIL import Image as PILImage
        from calibre import fit_image

        def _build_local_path():
            '''
            GoodReader stores individual dbs for each book, matching the folder and
            name structure in the Documents folder. Make a local version, renamed to .db
            '''
            path = remote_db_path.split('/')[-1]
            if iswindows:
                from calibre.utils.filenames import shorten_components_to
                plen = len(self.temp_dir)
                path = ''.join(shorten_components_to(245 - plen, [path]))

            full_path = os.path.join(self.temp_dir, path)
            base = os.path.splitext(full_path)[0]
            full_path = base + ".db"
            return os.path.normpath(full_path)

        self._log_location(remote_path)
        remote_db_path = '/'.join([
            '/Library', 'Application Support',
            'com.goodiware.GoodReader.ASRoot', 'Previews', '0', remote_path
        ])

        thumb_data = None

        db_stats = self.ios.stat(remote_db_path)
        if db_stats:
            full_path = _build_local_path()
            with open(full_path, 'wb') as out:
                self.ios.copy_from_idevice(remote_db_path, out)
            local_db_path = out.name
            con = sqlite3.connect(local_db_path)
            with con:
                con.row_factory = sqlite3.Row
                cur = con.cursor()
                cur.execute('''SELECT
                                thumb
                               FROM Pages WHERE pageNum = "1"
                            ''')
                row = cur.fetchone()
                if row:
                    img_data = cStringIO.StringIO(row[b'thumb'])
                    im = PILImage.open(img_data)
                    scaled, width, height = fit_image(im.size[0], im.size[1],
                                                      self.COVER_WIDTH,
                                                      self.COVER_HEIGHT)
                    im = im.resize((self.COVER_WIDTH, self.COVER_HEIGHT),
                                   PILImage.NEAREST)
                    thumb = cStringIO.StringIO()
                    im.convert('RGB').save(thumb, 'JPEG')
                    thumb_data = thumb.getvalue()
                    img_data.close()
                    thumb.close()

        return thumb_data
def _generate_thumbnail(self, book, cover_path):
    '''
    Fetch the cover image, generate a thumbnail, cache
    Specific implementation for iBooks
    '''
    self._log_location(book.title)
    self._log_diagnostic(" book_path: %s" % book.path)
    self._log_diagnostic("cover_path: %s" % repr(cover_path))

    thumb_data = None
    thumb_path = book.path.rpartition('.')[0] + '.jpg'

    # Try getting the cover from the cache
    try:
        zfr = ZipFile(self.archive_path)
        thumb_data = zfr.read(thumb_path)
        if thumb_data == 'None':
            self._log_diagnostic("returning None from cover cache")
            zfr.close()
            return None
    except:
        self._log_diagnostic("opening cover cache for appending")
        zfw = ZipFile(self.archive_path, mode='a')
    else:
        self._log_diagnostic("returning thumb from cover cache")
        return thumb_data

    '''
    # Is book.path a directory (iBooks) or an epub?
    stats = self.ios.stat(book.path)
    if stats['st_ifmt'] == 'S_IFDIR':
        # ***  This needs to fetch the cover data from the directory  ***
        self._log_diagnostic("returning None, can't read iBooks covers yet")
        return thumb_data

    # Get the cover from the book
    try:
        stream = cStringIO.StringIO(self.ios.read(book.path, mode='rb'))
        mi = get_metadata(stream)
        if mi.cover_data is not None:
            img_data = cStringIO.StringIO(mi.cover_data[1])
    except:
        if self.verbose:
            self._log_diagnostic("ERROR: unable to get cover from '%s'" % book.title)
            import traceback
            #traceback.print_exc()
            exc_type, exc_value, exc_traceback = sys.exc_info()
            self._log_diagnostic(traceback.format_exception_only(exc_type, exc_value)[0].strip())
        return thumb_data
    '''

    try:
        img_data = cStringIO.StringIO(self.ios.read(cover_path, mode='rb'))
    except:
        if self.verbose:
            self._log_diagnostic("ERROR fetching cover data for '%s', caching empty marker" % book.title)
            import traceback
            exc_type, exc_value, exc_traceback = sys.exc_info()
            self._log_diagnostic(traceback.format_exception_only(exc_type, exc_value)[0].strip())
        # Cache the empty cover
        zfw.writestr(thumb_path, 'None')
        return thumb_data


    # Generate a thumb
    try:
        im = PILImage.open(img_data)
        scaled, width, height = fit_image(im.size[0], im.size[1], 60, 80)
        im = im.resize((int(width), int(height)), PILImage.ANTIALIAS)

        thumb = cStringIO.StringIO()
        im.convert('RGB').save(thumb, 'JPEG')
        thumb_data = thumb.getvalue()
        thumb.close()
        self._log_diagnostic("SUCCESS: generated thumb for '%s', caching" % book.title)
        # Cache the tagged thumb
        zfw.writestr(thumb_path, thumb_data)
    except:
        if self.verbose:
            self._log_diagnostic("ERROR generating thumb for '%s', caching empty marker" % book.title)
            import traceback
            exc_type, exc_value, exc_traceback = sys.exc_info()
            self._log_diagnostic(traceback.format_exception_only(exc_type, exc_value)[0].strip())
        # Cache the empty cover
        zfw.writestr(thumb_path, 'None')
    finally:
        #img_data.close()
        zfw.close()

    return thumb_data
Example #52
0
def create_cover_page(top_lines, bottom_lines, display_image, options,
                      image_path, output_format='jpg'):
    from calibre.gui2 import ensure_app
    ensure_app()
    (width, height) = options.get(cfg.KEY_SIZE, (590, 750))
    margins = options.get(cfg.KEY_MARGINS)
    (top_mgn, bottom_mgn, left_mgn, right_mgn, image_mgn) = (
        margins['top'], margins['bottom'], margins['left'],
        margins['right'], margins['image'])
    left_mgn = min([left_mgn, (width / 2) - 10])
    left_text_margin = left_mgn if left_mgn > 0 else 10
    right_mgn = min([right_mgn, (width / 2) - 10])
    right_text_margin = right_mgn if right_mgn > 0 else 10

    colors = options[cfg.KEY_COLORS]
    bgcolor, border_color, fill_color, stroke_color = (
        colors['background'], colors['border'], colors['fill'],
        colors['stroke'])
    if not options.get(cfg.KEY_COLOR_APPLY_STROKE, False):
        stroke_color = None
    auto_reduce_font = options.get(cfg.KEY_FONTS_AUTOREDUCED, False)
    borders = options[cfg.KEY_BORDERS]
    (cover_border_width, image_border_width) = (
        borders['coverBorder'], borders['imageBorder'])
    is_background_image = options.get(cfg.KEY_BACKGROUND_IMAGE, False)
    if image_path:
        if not os.path.exists(image_path) or os.path.getsize(image_path) == 0:
            display_image = is_background_image = False

    canvas = create_canvas(width - cover_border_width * 2,
                           height - cover_border_width * 2, bgcolor)
    if cover_border_width > 0:
        canvas = add_border(canvas, cover_border_width, border_color, bgcolor)

    if is_background_image:
        logo = Image()
        logo.open(image_path)
        outer_margin = 0 if cover_border_width == 0 else cover_border_width
        logo.size = (width - outer_margin * 2, height - outer_margin * 2)
        left = top = outer_margin
        canvas.compose(logo, int(left), int(top))

    top = top_mgn
    if len(top_lines) > 0:
        for line in top_lines:
            twand = create_colored_text_wand(line, fill_color, stroke_color)
            top = draw_sized_text(
                canvas, twand, line, top, left_text_margin,
                right_text_margin, auto_reduce_font)
            top += line.bottom_margin
        top -= top_lines[-1].bottom_margin

    if len(bottom_lines) > 0:
        # Draw this on a fake canvas so can determine the space required
        fake_canvas = create_canvas(width, height, bgcolor)
        footer_height = 0
        for line in bottom_lines:
            line.twand = create_colored_text_wand(
                line, fill_color, stroke_color)
            footer_height = draw_sized_text(
                fake_canvas, line.twand, line, footer_height, left_text_margin,
                right_text_margin, auto_reduce_font)
            footer_height += line.bottom_margin
        footer_height -= bottom_lines[-1].bottom_margin

        footer_top = height - footer_height - bottom_mgn
        bottom = footer_top
        # Re-use the text wand from previously which we will have adjusted the
        # font size on
        for line in bottom_lines:
            bottom = draw_sized_text(
                canvas, line.twand, line, bottom, left_text_margin,
                right_text_margin, auto_reduce_font=False)
            bottom += line.bottom_margin
        available = (width - (left_mgn + right_mgn),
                     int(footer_top - top) - (image_mgn * 2))
    else:
        available = (width - (left_mgn + right_mgn),
                     int(height - top) - bottom_mgn - (image_mgn * 2))

    if not is_background_image and display_image and available[1] > 40:
        logo = Image()
        logo.open(image_path)
        lwidth, lheight = logo.size
        available = (available[0] - image_border_width * 2,
                     available[1] - image_border_width * 2)
        scaled, lwidth, lheight = fit_image(lwidth, lheight, *available)
        if not scaled and options.get(cfg.KEY_RESIZE_IMAGE_TO_FIT, False):
            scaled, lwidth, lheight = scaleup_image(
                lwidth, lheight, *available)
        if scaled:
            logo.size = (lwidth, lheight)
        if image_border_width > 0:
            logo = add_border(logo, image_border_width, border_color, bgcolor)

        left = int(max(0, (width - lwidth) / 2.))
        top = top + image_mgn + ((available[1] - lheight) / 2.)
        canvas.compose(logo, int(left), int(top))

    return canvas.export(output_format)