class QtReader(BaseReader):
    """Image reader using Qt's QImageReader implementation under the hood."""
    def __init__(self, path: str, file_format: str):
        super().__init__(path, file_format)
        self._handler = QImageReader(path, file_format.encode())
        self._handler.setAutoTransform(True)
        if not self._handler.canRead():
            # TODO
            raise ValueError(f"'{path}' cannot be read as image")

    @classmethod
    def supports(cls, file_format: str) -> bool:
        return file_format in QImageReader.supportedImageFormats()

    @property
    def is_animation(self) -> bool:
        return self._handler.supportsAnimation()

    def get_pixmap(self) -> QPixmap:
        """Retrieve the pixmap directly from the image reader."""
        pixmap = QPixmap.fromImageReader(self._handler)
        if self._handler.error():
            raise ValueError(
                f"Error reading image '{self.path}': {self._handler.errorString()}"
            )
        return pixmap

    def get_image(self, size: int) -> QImage:
        """Retrieve the down-scaled image directly from the image reader."""
        qsize = self._handler.size()
        qsize.scale(size, size, Qt.KeepAspectRatio)
        self._handler.setScaledSize(qsize)
        return self._handler.read()
Example #2
0
    def load(self, source):
        """Load anything that QImageReader or QMovie constructors accept"""

        # Use QImageReader to identify animated GIFs for separate handling
        # (Thanks to https://stackoverflow.com/a/20674469/435253 for this)
        image_reader = QImageReader(source)
        from PyQt5.QtGui import QImageIOHandler
        if image_reader.supportsAnimation() and image_reader.imageCount() > 1:
            movie = QMovie(source)

            # Calculate the aspect ratio and adjust the widget size
            movie.jumpToFrame(0)
            movie_size = movie.currentImage().size()
            self.movie_aspect = movie_size.width() / movie_size.height()
            self.resizeEvent()

            self.label.setMovie(movie)
            movie.start()

            # Free memory if the previous image was non-animated
            self.orig_pixmap = None
        else:
            self.orig_pixmap = QPixmap(image_reader.read())
            self.label.setPixmap(self.orig_pixmap)

            # Fail quickly if our violated invariants result in stale
            # aspect-ratio information getting reused
            self.movie_aspect = None

        # Keep the image from preventing downscaling
        self.setMinimumSize(1, 1)
Example #3
0
    def __init__(self, file_path, make_opengl_textures=True):
        image_reader = QImageReader(file_path)
        self.is_animated = image_reader.supportsAnimation()
        if self.is_animated:
            self.num_frames = image_reader.imageCount()
            # -1 means loop infinitely, 0 means no loop, > 0 is finite # of loops
            self.loop_count = image_reader.loopCount()
            self.loops_remaining = 0
            self.frames = []
            self.delays = []
            while image_reader.currentImageNumber() < image_reader.imageCount() - 1:
                self.frames.append(image_reader.read())
                self.delays.append(image_reader.nextImageDelay())

            if make_opengl_textures:
                self.open_gl_textures = [QOpenGLTexture(this_frame.mirrored()) for this_frame in self.frames]
                self.made_opengl_textures = True

            self.frames_and_delays = zip(self.frames, self.delays)

            self.current_frame = 0
            self.animating = False
        else:
            self.image = image_reader.read()
            assert isinstance(self.image, QImage)
            if make_opengl_textures:
                self.open_gl_texture = QOpenGLTexture(self.image.mirrored())
                self.made_opengl_textures = True
Example #4
0
    def _load(self, path: str, reload_only: bool):
        """Load proper displayable QWidget for a path.

        This reads the image using QImageReader and then emits the appropriate
        *_loaded signal to tell the image to display a new object.
        """
        # Pass file format explicitly as imghdr does a much better job at this than the
        # file name based approach of QImageReader
        file_format = files.imghdr.what(path)
        if file_format is None:
            log.error("%s is not a valid image", path)
            return
        reader = QImageReader(path, file_format.encode("utf-8"))
        reader.setAutoTransform(True)  # Automatically apply exif orientation
        if not reader.canRead():
            log.error("Cannot read image %s", path)
            return
        # SVG
        if file_format == "svg" and QSvgWidget:
            # Do not store image and only emit with the path as the
            # VectorGraphic widget needs the path in the constructor
            self.original = None
            api.signals.svg_loaded.emit(path, reload_only)
            self._image_type = ImageType.Svg
        # Gif
        elif reader.supportsAnimation():
            movie = QMovie(path)
            if not movie.isValid() or movie.frameCount() == 0:
                log.error("Error reading animation %s: invalid data", path)
                return
            self.original = movie
            api.signals.movie_loaded.emit(self.current, reload_only)
            self._image_type = ImageType.Movie
        # Regular image
        else:
            pixmap = QPixmap.fromImageReader(reader)
            if reader.error():
                log.error("Error reading image %s: %s", path, reader.errorString())
                return
            self.original = pixmap
            api.signals.pixmap_loaded.emit(self.current, reload_only)
            self._image_type = ImageType.Pixmap
        self._path = path
Example #5
0
    def load(self, source, adaptSize=True):
        """Load anything that QImageReader or QMovie constructors accept
            adaptSize=True: Initial image size to fit container
            adaptSize=False: Set container's size the same as image
        """

        # Use QImageReader to identify animated GIFs for separate handling
        # (Thanks to https://stackoverflow.com/a/20674469/435253 for this)
        size = QSize(self.width(), self.height())
        image_reader = QImageReader(source)
        if image_reader.supportsAnimation() and image_reader.imageCount() > 1:
            # Set content as Movie
            self.content = SaneQMovie(source)
            # Adjust the widget size
            if adaptSize:
                self.content.adaptScale(size)
            else:
                # Resizing container will trigger resizeEvent()
                self.resize(self.content.size())
            # Start Movie replay
            self.setMovie(self.content)
            self.content.start()

        else:
            # Set content as Image
            self.content = SaneQPixmap(image_reader.read())
            # Adjust the widget size
            if adaptSize:
                self.setPixmap(self.content.adaptScale(size))
            else:
                # Resizing container will trigger resizeEvent()
                self.resize(self.content.size())
                self.setPixmap(self.content)

        # Keep the image from preventing downscaling
        self.setMinimumSize(1, 1)