Ejemplo n.º 1
0
    def read(cls, cur_file: BinaryIO) -> AnimatedCursor:
        """
        Read an xcur or X-Org cursor file from the specified file buffer.

        :param cur_file: The file buffer with xcursor data.
        :return: An AnimatedCursor object, non-animated cursors will contain only 1 frame.
        """
        magic_data = cur_file.read(4)

        cls._assert(cls.check(magic_data), "Not a XCursor File!!!")

        header_size = to_int(cur_file.read(4))
        cls._assert(header_size == cls.HEADER_SIZE,
                    f"Header size is not {cls.HEADER_SIZE}!")
        version = to_int(cur_file.read(4))

        # Number of cursors...
        num_toc = to_int(cur_file.read(4))
        # Used to store cursor offsets per size...
        nominal_sizes = {}

        for i in range(num_toc):
            main_type = to_int(cur_file.read(4))

            if (main_type == cls.CURSOR_TYPE):
                nominal_size = to_int(cur_file.read(4))
                offset = to_int(cur_file.read(4))

                if (nominal_size not in nominal_sizes):
                    nominal_sizes[nominal_size] = [offset]
                else:
                    nominal_sizes[nominal_size].append(offset)

        max_len = max(len(nominal_sizes[size]) for size in nominal_sizes)
        cursors = []
        delays = []

        for i in range(max_len):
            cursor = Cursor()
            sub_delays = []

            for size, offsets in nominal_sizes.items():
                if (i < len(offsets)):
                    img, x_hot, y_hot, delay = cls._read_chunk(
                        offsets[i], cur_file, size)
                    cursor.add(CursorIcon(img, x_hot, y_hot))
                    sub_delays.append(delay)

            cursors.append(cursor)
            delays.append(max(sub_delays))

        return AnimatedCursor(cursors, delays)
Ejemplo n.º 2
0
def load_cursor_from_image(file: BinaryIO) -> AnimatedCursor:
    """
    Load a cursor from an image. Image is expected to be square. If it is not square, this method will slide
    horizontally across the image using a height * height square, loading each following square as next frame of the
    cursor with a delay of 100 and hotspots of (0, 0). Note this method supports all formats supported by the
    PIL or pillow Image library. If the image passed is an animated image format like .gif or .apng, this method
    will avoid loading in horizontal square tiles and will rather load in each frame of the image as each frame of
    the cursor.

    :param file: The file handler pointing to the image data.
    :return: An AnimatedCursor object, representing an animated cursor. Static cursors will only have 1 frame.
    """
    image: Image.Image = Image.open(file)

    if (hasattr(image, "is_animated") and (image.is_animated)):
        # If this is an animated file, load in each frame as the frames of the cursor (Ex, ".gif")
        min_dim = min(image.size)  # We fit the image to a square...
        images_durations = [(ImageOps.fit(image, (min_dim, min_dim)),
                             frame.info.get("duration", 100))
                            for frame in ImageSequence.Iterator(image)]
    else:
        # Separate all frames (Assumed to be stored horizontally)
        height = image.size[1]
        num_frames = image.size[0] // height

        if (num_frames == 0):
            raise ValueError(
                "Image width is smaller then height so this will load as a 0 frame cursor!!!"
            )

        images_durations = [(image.crop(
            (i * height, 0, i * height + height, height)), 100)
                            for i in range(num_frames)]

    # Now convert images into the cursors, resizing them to match all the default sizes...
    final_cursor = AnimatedCursor()

    for img, delay in images_durations:
        frame = Cursor()

        for size in DEFAULT_SIZES:
            frame.add(CursorIcon(img.resize(size, Image.LANCZOS), 0, 0))

        final_cursor.append((frame, delay))

    return final_cursor
Ejemplo n.º 3
0
    def write(cls, cursor: Cursor, out: BinaryIO):
        """
        Writes cursor to a file in the form of the windows .cur format...

        :param cursor: The cursor object to save.
        :param out: The file handle to output the cursor to.
        """
        cursor.add_sizes([(32, 32)])

        out.write(cls.MAGIC)
        out.write(to_bytes(len(cursor), 2))

        offset = out.tell() + len(cursor) * 16
        imgs = []

        for size in sorted(cursor):
            width, height = size

            if (width > 256 or height > 256):
                continue

            hot_x, hot_y = cursor[size].hotspot
            hot_x, hot_y = hot_x if (0 <= hot_x < width) else 0, hot_y if (
                0 <= hot_y < height) else 0

            image_data = cls._to_bmp(cursor[size].image, (width, height))

            width, height = width if (width < 256) else 0, height if (
                height < 256) else 0

            out.write(to_bytes(width, 1))  # Width, 1 byte.
            out.write(to_bytes(height, 1))  # Height, 1 byte.
            out.write(b"\0\0")
            out.write(to_bytes(hot_x, 2))
            out.write(to_bytes(hot_y, 2))
            out.write(to_bytes(len(image_data), 4))
            out.write(to_bytes(offset, 4))

            offset += len(image_data)
            imgs.append(image_data)

        for image_data in imgs:
            out.write(image_data)
Ejemplo n.º 4
0
    def read(cls, cur_file: BinaryIO) -> Cursor:
        """
        Read a cursor file in the windows .cur format.

        :param cur_file: The file or file-like object to read from.
        :return: A Cursor
        """
        magic_header = cur_file.read(4)

        if (not cls.check(magic_header)):
            raise SyntaxError("Not a Cur Type File!!!")

        is_ico = (magic_header == cls.ICO_MAGIC)

        # Dump the file with the ico header to allow to be read by Pillow Ico reader...
        data = BytesIO()
        data.write(cls.ICO_MAGIC)
        data.write(cur_file.read())
        data.seek(0)

        ico_img = IcoFile(data)
        cursor = Cursor()

        for head in ico_img.entry:
            width = head["width"]
            height = head["height"]
            x_hot = 0 if (is_ico) else head["planes"]
            y_hot = 0 if (is_ico) else head["bpp"]

            # Check that hotspots are valid...
            if (not (0 <= x_hot < width)):
                x_hot = 0
            if (not (0 <= y_hot < height)):
                y_hot = 0

            image = ico_img.getimage((width, height))

            cursor.add(CursorIcon(image, x_hot, y_hot))

        return cursor
Ejemplo n.º 5
0
def load_cursor_from_svg(file: BinaryIO) -> AnimatedCursor:
    """
    Load a cursor from an SVG. SVG is expected to be square. If it is not square, this method will slide
    horizontally across the SVG using a height * height square, loading each following square as next frame of the
    cursor with a delay of 100 and hotspots of (0, 0). Note this method uses CairoSVG library to load and render
    SVGs of various sizes to bitmaps. For info on supported SVG features, look at the docs for the CairoSVG library.

    :param file: The file handler pointing to the SVG data.
    :return: An AnimatedCursor object, representing an animated cursor. Static cursors will only have 1 frame.
    """
    # Convert SVG in memory to PNG, and read that in with PIL to get the default size of the SVG...
    mem_png = BytesIO()
    cairosvg.svg2png(file_obj=file, write_to=mem_png)
    file.seek(0)
    size_ref_img = Image.open(mem_png)

    # Compute height to width ratio an the number of frame(Assumes they are stored horizontally)...
    h_to_w_multiplier = size_ref_img.size[0] / size_ref_img.size[1]
    num_frames = int(h_to_w_multiplier)

    if (num_frames == 0):
        raise ValueError(
            "Image width is smaller then height so this will load as a 0 frame cursor!!!"
        )

    # Build empty animated cursor to start stashing frames in...
    ani_cur = AnimatedCursor([Cursor() for i in range(num_frames)],
                             [100] * num_frames)

    for sizes in DEFAULT_SIZES:
        # For each default size, resize svg to it and add all frames to the AnimatedCursor object...
        image = BytesIO()
        cairosvg.svg2png(file_obj=file,
                         write_to=image,
                         output_height=sizes[1],
                         output_width=int(sizes[1] * h_to_w_multiplier))
        file.seek(0)
        image = Image.open(image)

        height = image.size[1]
        for i in range(num_frames):
            ani_cur[i][0].add(
                CursorIcon(
                    image.crop((i * height, 0, i * height + height, height)),
                    0, 0))

    return ani_cur
Ejemplo n.º 6
0
    def __init__(self, parent=None, cursor: AnimatedCursor=None, frame=0, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self._frame = 0
        self._frame_img = None
        self._pressed = False

        if((cursor is None) or (len(cursor) == 0)):
            self._cursor = AnimatedCursor(
                [Cursor([CursorIcon(Image.fromarray(np.zeros(self.VIEW_SIZE + (4,), dtype=np.uint8)), 0, 0)])],
                [100]
            )
        else:
            self._cursor = cursor
            self._cursor.normalize([self.VIEW_SIZE])

        self.__painter = QtGui.QPainter()
        self.frame = frame
        self.setCursor(QtGui.Qt.CrossCursor)

        self.setMinimumSize(QtCore.QSize(*self.VIEW_SIZE))
        self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        self.resize(self.minimumSize())
Ejemplo n.º 7
0
def _icon_chunk(header: Dict[str, Any], data: bytes, data_out: Dict[str, Any]):
    """
    Represents .ani icon chunk, which has an identifier of "icon".
    """
    if (header is None):
        raise SyntaxError("icon chunk became before header!")

    if (header["is_in_ico"]):
        # Cursors are stored as either .cur or .ico, use CurFormat to read them...
        cursor = CurFormat.read(BytesIO(data))
    else:
        # BMP format, load in and then correct the height...
        c_icon = CursorIcon(BmpImagePlugin.DibImageFile(BytesIO(data)), 0, 0)
        c_icon.image._size = (c_icon.image.size[0], c_icon.image.size[1] // 2)
        d, e, o, a = c_icon.image.tile[0]
        c_icon.image.tile[0] = d, (0, 0) + c_icon.image.size, o, a
        # Add the image to the cursor list...
        cursor = Cursor([c_icon])

    if ("list" not in data_out):
        data_out["list"] = []

    data_out["list"].append(cursor)