Use a rendered text cache for faster painting

This commit is contained in:
Kovid Goyal
2016-10-20 12:00:14 +05:30
parent efeb2ebdaa
commit 37ac5d4194
3 changed files with 35 additions and 21 deletions

View File

@@ -120,13 +120,13 @@ def validate_font(opts: Options):
def build_ansi_color_tables(opts: Options) -> Tuple[dict, dict]: def build_ansi_color_tables(opts: Options) -> Tuple[dict, dict]:
def col(i): def col(i):
return QColor(getattr(opts, 'color{}'.format(i))) return QColor(getattr(opts, 'color{}'.format(i))).getRgb()[:3]
fg = {30 + i: col(i) for i in range(8)} fg = {30 + i: col(i) for i in range(8)}
fg[39] = opts.foreground fg[39] = opts.foreground
fg.update({90 + i: col(i + 8) for i in range(8)}) fg.update({90 + i: col(i + 8) for i in range(8)})
fg[99] = opts.foreground_bold fg[99] = opts.foreground_bold.getRgb()[:3]
bg = {40 + i: col(i) for i in range(8)} bg = {40 + i: col(i) for i in range(8)}
bg[49] = opts.background bg[49] = opts.background.getRgb()[:3]
bg.update({100 + i: col(i + 8) for i in range(8)}) bg.update({100 + i: col(i + 8) for i in range(8)})
build_ansi_color_tables.fg, build_ansi_color_tables.bg = fg, bg build_ansi_color_tables.fg, build_ansi_color_tables.bg = fg, bg
build_ansi_color_tables(defaults) build_ansi_color_tables(defaults)

View File

@@ -6,8 +6,6 @@ import array
from typing import Tuple, Dict, Union, Iterator, Sequence from typing import Tuple, Dict, Union, Iterator, Sequence
from itertools import repeat from itertools import repeat
from PyQt5.QtGui import QColor
from pyte.graphics import FG_BG_256 from pyte.graphics import FG_BG_256
from .config import fg_color_table, bg_color_table from .config import fg_color_table, bg_color_table
@@ -305,19 +303,19 @@ class Line:
self.char[i] = (a << ATTRS_SHIFT) | (c & CHAR_MASK) self.char[i] = (a << ATTRS_SHIFT) | (c & CHAR_MASK)
def as_color(entry: int, color_table: Dict[int, QColor]) -> Union[QColor, None]: def as_color(entry: int, color_table: Dict[int, Tuple[int]]) -> Union[Tuple[int], None]:
t = entry & 0xff t = entry & 0xff
if t == 1: if t == 1:
r = (entry >> 8) & 0xff r = (entry >> 8) & 0xff
return color_table.get(r) return color_table.get(r)
if t == 2: if t == 2:
r = (entry >> 8) & 0xff r = (entry >> 8) & 0xff
return QColor(*FG_BG_256[r]) return FG_BG_256[r]
if t == 3: if t == 3:
r = (entry >> 8) & 0xff r = (entry >> 8) & 0xff
g = (entry >> 16) & 0xff g = (entry >> 16) & 0xff
b = (entry >> 24) & 0xff b = (entry >> 24) & 0xff
return QColor(r, g, b) return r, g, b
def copy_char(src: Line, dest: Line, src_pos: int, dest_pos: int) -> None: def copy_char(src: Line, dest: Line, src_pos: int, dest_pos: int) -> None:

View File

@@ -2,10 +2,11 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from functools import lru_cache
from typing import Tuple, Iterator, Sequence from typing import Tuple, Iterator, Sequence
from PyQt5.QtCore import pyqtSignal, QTimer, QRect, Qt from PyQt5.QtCore import pyqtSignal, QTimer, QRect, Qt
from PyQt5.QtGui import QColor, QPainter, QFont, QFontMetrics, QRegion, QPen from PyQt5.QtGui import QColor, QPainter, QFont, QFontMetrics, QRegion, QPen, QPixmap
from PyQt5.QtWidgets import QWidget from PyQt5.QtWidgets import QWidget
from .config import build_ansi_color_tables, Options, fg_color_table, bg_color_table from .config import build_ansi_color_tables, Options, fg_color_table, bg_color_table
@@ -23,6 +24,20 @@ def ascii_width(fm: QFontMetrics) -> int:
return ans return ans
@lru_cache(maxsize=2**11)
def pixmap_for_text(text, color, default_fg, font, w, h, baseline):
p = QPixmap(w, h)
p.fill(Qt.transparent)
fg = as_color(color & COL_MASK, fg_color_table()) or default_fg
painter = QPainter(p)
painter.setFont(font)
painter.setPen(QPen(QColor(*fg)))
painter.setRenderHints(QPainter.TextAntialiasing | QPainter.Antialiasing)
painter.drawText(0, baseline, text)
painter.end()
return p
class TerminalWidget(QWidget): class TerminalWidget(QWidget):
relayout_lines = pyqtSignal(object, object, object, object) relayout_lines = pyqtSignal(object, object, object, object)
@@ -46,14 +61,15 @@ class TerminalWidget(QWidget):
def apply_opts(self, opts): def apply_opts(self, opts):
self.opts = opts self.opts = opts
pixmap_for_text.cache_clear()
pal = self.palette() pal = self.palette()
pal.setColor(pal.Window, QColor(opts.background)) pal.setColor(pal.Window, QColor(opts.background))
pal.setColor(pal.WindowText, QColor(opts.foreground)) pal.setColor(pal.WindowText, QColor(opts.foreground))
self.setPalette(pal) self.setPalette(pal)
self.current_bg = pal.color(pal.Window) self.current_bg = pal.color(pal.Window)
self.current_fg = pal.color(pal.WindowText) self.current_fg = pal.color(pal.WindowText).getRgb()[:3]
build_ansi_color_tables(opts) build_ansi_color_tables(opts)
f = QFont(opts.font_family) self.current_font = f = QFont(opts.font_family)
f.setPointSizeF(opts.font_size) f.setPointSizeF(opts.font_size)
self.setFont(f) self.setFont(f)
self.font_metrics = fm = QFontMetrics(self.font()) self.font_metrics = fm = QFontMetrics(self.font())
@@ -139,7 +155,6 @@ class TerminalWidget(QWidget):
return return
r = ev.region() r = ev.region()
p = QPainter(self) p = QPainter(self)
p.setRenderHints(p.TextAntialiasing | p.Antialiasing)
try: try:
self.paint_cursor(p) self.paint_cursor(p)
@@ -158,6 +173,12 @@ class TerminalWidget(QWidget):
x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions)) x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions))
r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height) r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
self.last_drew_cursor_at = x, y self.last_drew_cursor_at = x, y
line = self.linebuf[x]
colors = line.basic_cell_data(y)[2]
if colors & HAS_BG_MASK:
bg = as_color(colors >> COL_SHIFT, bg_color_table())
if bg is not None:
painter.fillRect(r, QColor(*bg))
if self.hasFocus(): if self.hasFocus():
painter.fillRect(r, self.cursor_color) painter.fillRect(r, self.cursor_color)
else: else:
@@ -168,23 +189,18 @@ class TerminalWidget(QWidget):
line = self.linebuf[row] line = self.linebuf[row]
ch, attrs, colors = line.basic_cell_data(col) ch, attrs, colors = line.basic_cell_data(col)
x, y = self.cell_positions[col], self.line_positions[row] x, y = self.cell_positions[col], self.line_positions[row]
if colors & HAS_BG_MASK: if colors & HAS_BG_MASK and (col != self.last_drew_cursor_at[0] or row != self.last_drew_cursor_at[1]):
bg = as_color(colors >> COL_SHIFT, bg_color_table()) bg = as_color(colors >> COL_SHIFT, bg_color_table())
if bg is not None: if bg is not None:
r = QRect(x, y, self.cell_width, self.cell_height) r = QRect(x, y, self.cell_width, self.cell_height)
painter.fillRect(r, bg) painter.fillRect(r, QColor(*bg))
if ch == 0 or ch == 32: if ch == 0 or ch == 32:
# An empty cell # An empty cell
pass pass
else: else:
text = chr(ch) + line.combining_chars.get(col, '') text = chr(ch) + line.combining_chars.get(col, '')
fg = as_color(colors & COL_MASK, fg_color_table()) p = pixmap_for_text(text, colors, self.current_fg, self.current_font, self.cell_width * 2, self.cell_height, self.baseline_offset)
if fg is not None: painter.drawPixmap(x, y, p)
painter.save()
painter.setPen(QPen(fg))
painter.drawText(x, y + self.baseline_offset, text)
if fg is not None:
painter.restore()
def keyPressEvent(self, ev): def keyPressEvent(self, ev):
mods = ev.modifiers() mods = ev.modifiers()