Example #1
0
def convert(path: str,
            m: ImageData,
            available_width: int,
            available_height: int,
            scale_up: bool,
            tdir: Optional[str] = None) -> Tuple[str, int, int]:
    from tempfile import NamedTemporaryFile
    width, height = m.width, m.height
    exe = find_exe('convert')
    if exe is None:
        raise OSError(
            'Failed to find the ImageMagick convert executable, make sure it is present in PATH'
        )
    cmd = [exe, '-background', 'none', '--', path]
    scaled = False
    if scale_up:
        if width < available_width:
            r = available_width / width
            width, height = available_width, int(height * r)
            scaled = True
    if scaled or width > available_width or height > available_height:
        width, height = fit_image(width, height, available_width,
                                  available_height)
        cmd += ['-resize', '{}x{}!'.format(width, height)]
    cmd += ['-depth', '8']
    with NamedTemporaryFile(prefix='icat-',
                            suffix='.' + m.mode,
                            delete=False,
                            dir=tdir) as outfile:
        run_imagemagick(path, cmd + [outfile.name])
    # ImageMagick sometimes generated rgba images smaller than the specified
    # size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
    sz = os.path.getsize(outfile.name)
    bytes_per_pixel = 3 if m.mode == 'rgb' else 4
    expected_size = bytes_per_pixel * width * height
    if sz < expected_size:
        missing = expected_size - sz
        if missing % (bytes_per_pixel * width) != 0:
            raise ConvertFailed(
                path, 'ImageMagick failed to convert {} correctly,'
                ' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
                    path, sz, expected_size, width, height, bytes_per_pixel))
        height -= missing // (bytes_per_pixel * width)

    return outfile.name, width, height
Example #2
0
def set_cursor(cmd, width, height, align):
    ss = screen_size()
    cw = int(ss.width / ss.cols)
    num_of_cells_needed = int(ceil(width / cw))
    if num_of_cells_needed > ss.cols:
        w, h = fit_image(width, height, ss.width, height)
        ch = int(ss.height / ss.rows)
        num_of_rows_needed = int(ceil(height / ch))
        cmd['c'], cmd['r'] = ss.cols, num_of_rows_needed
    else:
        cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
        extra_cells = 0
        if align == 'center':
            extra_cells = (ss.cols - num_of_cells_needed) // 2
        elif align == 'right':
            extra_cells = (ss.cols - num_of_cells_needed)
        if extra_cells:
            sys.stdout.buffer.write(b' ' * extra_cells)
Example #3
0
def set_cursor(cmd: GraphicsCommand, width: int, height: int, align: str) -> None:
    ss = get_screen_size()
    cw = int(ss.width / ss.cols)
    num_of_cells_needed = int(ceil(width / cw))
    if num_of_cells_needed > ss.cols:
        w, h = fit_image(width, height, ss.width, height)
        ch = int(ss.height / ss.rows)
        num_of_rows_needed = int(ceil(height / ch))
        cmd.c, cmd.r = ss.cols, num_of_rows_needed
    else:
        cmd.X = calculate_in_cell_x_offset(width, cw, align)
        extra_cells = 0
        if align == 'center':
            extra_cells = (ss.cols - num_of_cells_needed) // 2
        elif align == 'right':
            extra_cells = (ss.cols - num_of_cells_needed)
        if extra_cells:
            sys.stdout.buffer.write(b' ' * extra_cells)
Example #4
0
def render_image(
    path: str, output_prefix: str,
    m: ImageData,
    available_width: int, available_height: int,
    scale_up: bool,
    only_first_frame: bool = False
) -> RenderedImage:
    import tempfile
    has_multiple_frames = len(m) > 1
    get_multiple_frames = has_multiple_frames and not only_first_frame
    exe = find_exe('magick')
    if exe:
        cmd = [exe, 'convert']
    else:
        exe = find_exe('convert')
        if exe is None:
            raise OSError('Failed to find the ImageMagick convert executable, make sure it is present in PATH')
        cmd = [exe]
    cmd += ['-background', 'none', '--', path]
    if only_first_frame and has_multiple_frames:
        cmd[-1] += '[0]'
    scaled = False
    width, height = m.width, m.height
    if scale_up:
        if width < available_width:
            r = available_width / width
            width, height = available_width, int(height * r)
            scaled = True
    if scaled or width > available_width or height > available_height:
        width, height = fit_image(width, height, available_width, available_height)
        resize_cmd = ['-resize', '{}x{}!'.format(width, height)]
        if get_multiple_frames:
            # we have to coalesce, resize and de-coalesce all frames
            resize_cmd = ['-coalesce'] + resize_cmd + ['-deconstruct']
        cmd += resize_cmd
    cmd += ['-depth', '8', '-set', 'filename:f', '%w-%h-%g-%p']
    ans = RenderedImage(m.fmt, width, height, m.mode)
    if only_first_frame:
        ans.frames = [Frame(m.frames[0])]
    else:
        ans.frames = list(map(Frame, m.frames))
    bytes_per_pixel = 3 if m.mode == 'rgb' else 4

    def check_resize(frame: Frame) -> None:
        # ImageMagick sometimes generates RGBA images smaller than the specified
        # size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
        sz = os.path.getsize(frame.path)
        expected_size = bytes_per_pixel * frame.width * frame.height
        if sz < expected_size:
            missing = expected_size - sz
            if missing % (bytes_per_pixel * width) != 0:
                raise ConvertFailed(
                    path, 'ImageMagick failed to convert {} correctly,'
                    ' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
                        path, sz, expected_size, frame.width, frame.height, bytes_per_pixel))
            frame.height -= missing // (bytes_per_pixel * frame.width)
            if frame.index == 0:
                ans.height = frame.height
                ans.width = frame.width

    with tempfile.TemporaryDirectory(dir=os.path.dirname(output_prefix)) as tdir:
        output_template = os.path.join(tdir, f'im-%[filename:f].{m.mode}')
        if get_multiple_frames:
            cmd.append('+adjoin')
        run_imagemagick(path, cmd + [output_template])
        unseen = {x.index for x in m}
        for x in os.listdir(tdir):
            try:
                parts = x.split('.', 1)[0].split('-')
                index = int(parts[-1])
                unseen.discard(index)
                f = ans.frames[index]
                f.width, f.height = map(positive_int, parts[1:3])
                sz, pos = parts[3].split('+', 1)
                f.canvas_width, f.canvas_height = map(positive_int, sz.split('x', 1))
                f.canvas_x, f.canvas_y = map(int, pos.split('+', 1))
            except Exception:
                raise OutdatedImageMagick(f'Unexpected output filename: {x!r} produced by ImageMagick command: {last_imagemagick_cmd}')
            f.path = output_prefix + f'-{index}.{m.mode}'
            os.rename(os.path.join(tdir, x), f.path)
            check_resize(f)
    f = ans.frames[0]
    if f.width != ans.width or f.height != ans.height:
        with open(f.path, 'r+b') as ff:
            data = ff.read()
            ff.seek(0)
            ff.truncate()
            cd = create_canvas(data, f.width, f.canvas_x, f.canvas_y, ans.width, ans.height, 3 if ans.mode == 'rgb' else 4)
            ff.write(cd)
    if get_multiple_frames:
        if unseen:
            raise ConvertFailed(path, f'Failed to render {len(unseen)} out of {len(m)} frames of animation')
    elif not ans.frames[0].path:
        raise ConvertFailed(path, 'Failed to render image')

    return ans