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
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)
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)
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