diff options
-rw-r--r-- | src/buffer/cell.nim | 53 | ||||
-rw-r--r-- | src/display/term.nim | 68 | ||||
-rw-r--r-- | src/io/lineedit.nim | 2 | ||||
-rw-r--r-- | src/types/color.nim | 76 |
4 files changed, 126 insertions, 73 deletions
diff --git a/src/buffer/cell.nim b/src/buffer/cell.nim index feaaf668..40d19d1d 100644 --- a/src/buffer/cell.nim +++ b/src/buffer/cell.nim @@ -172,7 +172,7 @@ proc getParam(parser: AnsiCodeParser, i: var int, colon = false): string = template getParamU8(parser: AnsiCodeParser, i: var int, colon = false): uint8 = - if i < parser.params.len: + if i >= parser.params.len: return false let u = parseUInt8(parser.getParam(i)) if u.isNone: @@ -182,59 +182,51 @@ template getParamU8(parser: AnsiCodeParser, i: var int, proc parseSGRDefColor(parser: AnsiCodeParser, format: var Format, i: var int, isfg: bool): bool = let u = parser.getParamU8(i, colon = true) + template set_color(c: CellColor) = + if isfg: + format.fgcolor = c + else: + format.bgcolor = c if u == 2: let param0 = parser.getParamU8(i, colon = true) if i < parser.params.len: let r = param0 let g = parser.getParamU8(i, colon = true) let b = parser.getParamU8(i, colon = true) - if isfg: - format.fgcolor = cellColor(rgb(r, g, b)) - else: - format.bgcolor = cellColor(rgb(r, g, b)) + set_color cellColor(rgb(r, g, b)) else: - let n = param0 - if isfg: - format.fgcolor = cellColor(rgb(n, n, n)) - else: - format.bgcolor = cellColor(rgb(n, n, n)) - else: - if u in 0u8..7u8: - if isfg: - format.fgcolor = cellColor(u + 30) - else: - format.bgcolor = cellColor(u + 40) - elif u in 8u8..15u8: + set_color cellColor(gray(param0)) + elif u == 5: + let param0 = parser.getParamU8(i, colon = true) + if param0 in 0u8..7u8: + set_color cellColor(ANSIColor(param0)) + elif param0 in 8u8..15u8: format.bold = true - if isfg: - format.fgcolor = cellColor(u + 22) - else: - format.bgcolor = cellColor(u + 22) - elif u in 16u8..231u8: - #16 + 36 × r + 6 × g + b - discard - else: - discard + set_color cellColor(ANSIColor(param0 - 8)) + elif param0 in 16u8..255u8: + set_color cellColor(EightBitColor(param0)) + else: + return false proc parseSGRColor(parser: AnsiCodeParser, format: var Format, i: var int, u: uint8): bool = if u in 30u8..37u8: - format.fgcolor = u.cellColor() + format.fgcolor = cellColor(ANSIColor(u - 30)) elif u == 38: return parser.parseSGRDefColor(format, i, isfg = true) elif u == 39: format.fgcolor = defaultColor elif u in 40u8..47u8: - format.bgcolor = u.cellColor() + format.bgcolor = cellColor(ANSIColor(u - 40)) elif u == 48: return parser.parseSGRDefColor(format, i, isfg = false) elif u == 49: format.bgcolor = defaultColor elif u in 90u8..97u8: - format.fgcolor = cellColor(u - 60u8) + format.fgcolor = cellColor(ANSIColor(u - 90u8)) format.bold = true elif u in 100u8..107u8: - format.bgcolor = cellColor(u - 60u8) + format.bgcolor = cellColor(ANSIColor(u - 90u8)) format.bold = true else: return false @@ -252,6 +244,7 @@ proc parseSGRAspect(parser: AnsiCodeParser, format: var Format, return true elif u == 0: format = newFormat() + return true else: return parser.parseSGRColor(format, i, u) diff --git a/src/display/term.nim b/src/display/term.nim index 0cdbfa4f..80fa88f9 100644 --- a/src/display/term.nim +++ b/src/display/term.nim @@ -210,16 +210,18 @@ proc getRGB(a: CellColor, bg: bool): RGBColor = return ColorsRGB["black"] else: return ColorsRGB["white"] + elif a.color >= 16: + return eightBitToRGB(EightBitColor(a.color)) return ANSIColorMap[a.color mod 10] # Use euclidian distance to quantize RGB colors. -proc approximateANSIColor(rgb: RGBColor, exclude = -1): int = +proc approximateANSIColor(rgb: RGBColor): ANSIColor = var a = 0u16 var n = -1 for i in 0 .. ANSIColorMap.high: - if i == exclude: continue let color = ANSIColorMap[i] - if color == rgb: return i + if color == rgb: + return ANSIColor(i) let x = uint16(absSub(color.r, rgb.r)) ^ 2 let y = uint16(absSub(color.g, rgb.b)) ^ 2 let z = uint16(absSub(color.g, rgb.g)) ^ 2 @@ -227,10 +229,11 @@ proc approximateANSIColor(rgb: RGBColor, exclude = -1): int = if n == -1 or b < a: n = i a = b - return n + return ANSIColor(n) # Return a fgcolor contrasted to the background by contrast. -proc correctContrast(bgcolor, fgcolor: CellColor, contrast: int): CellColor = +proc correctContrast(bgcolor, fgcolor: CellColor, contrast: int, + colormode: ColorMode): CellColor = let cfgcolor = fgcolor let bgcolor = getRGB(bgcolor, true) let fgcolor = getRGB(fgcolor, false) @@ -251,9 +254,15 @@ proc correctContrast(bgcolor, fgcolor: CellColor, contrast: int): CellColor = if fgY < 0: fgY = 255 let newrgb = YUV(cast[uint8](fgY), fgcolor.U, fgcolor.V) - if cfgcolor.rgb: - return newrgb.cellColor() - return ColorsANSIFg[approximateANSIColor(newrgb)] + case colormode + of TRUECOLOR: + return cellColor(newrgb) + of ANSI: + return cellColor(approximateANSIColor(newrgb)) + of EIGHT_BIT: + return cellColor(rgbToEightBit(newrgb)) + of MONOCHROME: + doAssert false return cfgcolor proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = @@ -264,33 +273,40 @@ proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = var cellf = cellf case term.colormode - of ANSI, EIGHT_BIT: + of ANSI: if cellf.bgcolor.rgb: let color = approximateANSIColor(cellf.bgcolor.rgbcolor) - if color == 0: # black + if color == ANSI_BLACK: cellf.bgcolor = defaultColor else: - cellf.bgcolor = ColorsANSIBg[color] + cellf.bgcolor = cellColor(color) if cellf.fgcolor.rgb: if cellf.bgcolor == defaultColor: var color = approximateANSIColor(cellf.fgcolor.rgbcolor) - if color == 0: - color = 7 - if color == 7: # white + if color == ANSI_BLACK: + color = ANSI_WHITE + if color == ANSI_WHITE: cellf.fgcolor = defaultColor else: - cellf.fgcolor = ColorsANSIFg[color] + cellf.fgcolor = cellColor(color) else: - cellf.fgcolor = if int(cellf.bgcolor.color) - 40 < 4: + cellf.fgcolor = if cellf.bgcolor.color < 4: defaultColor else: - ColorsANSIFg[7] + cellColor(ANSI_WHITE) # white + of EIGHT_BIT: + if cellf.bgcolor.rgb: + cellf.bgcolor = cellColor(rgbToEightBit(cellf.bgcolor.rgbcolor)) + if cellf.fgcolor.rgb: + cellf.fgcolor = cellColor(rgbToEightBit(cellf.fgcolor.rgbcolor)) of MONOCHROME: cellf.fgcolor = defaultColor cellf.bgcolor = defaultColor of TRUE_COLOR: discard - cellf.fgcolor = correctContrast(cellf.bgcolor, cellf.fgcolor, term.mincontrast) + if term.colormode != MONOCHROME: + cellf.fgcolor = correctContrast(cellf.bgcolor, cellf.fgcolor, + term.mincontrast, term.colormode) if cellf.fgcolor != format.fgcolor and cellf.fgcolor == defaultColor or cellf.bgcolor != format.bgcolor and cellf.bgcolor == defaultColor: result &= term.resetFormat() @@ -299,22 +315,34 @@ proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = if cellf.fgcolor != format.fgcolor: var color = cellf.fgcolor if color.rgb: + assert term.colormode == TRUECOLOR let rgb = color.rgbcolor result &= SGR(38, 2, rgb.r, rgb.g, rgb.b) elif color == defaultColor: discard else: - result &= SGR(color.color) + let n = color.color + if n < 8: + result &= SGR(30 + n) + else: + assert term.colormode in {TRUECOLOR, EIGHT_BIT} + result &= SGR(38, 5, n) if cellf.bgcolor != format.bgcolor: var color = cellf.bgcolor if color.rgb: + assert term.colormode == TRUECOLOR let rgb = color.rgbcolor result &= SGR(48, 2, rgb.r, rgb.g, rgb.b) elif color == defaultColor: discard else: - result &= SGR(color.color) + let n = color.color + if n < 8: + result &= SGR(40 + n) + else: + assert term.colormode in {TRUECOLOR, EIGHT_BIT} + result &= SGR(48, 5, n) for flag in FormatFlags: if flag in term.formatmode: diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim index 7b822d1c..83f98ccb 100644 --- a/src/io/lineedit.nim +++ b/src/io/lineedit.nim @@ -43,7 +43,7 @@ func newLineHistory*(): LineHistory = const colorFormat = (func(): Format = result = newFormat() - result.fgcolor = ColorsANSIFg[4] # blue + result.fgcolor = cellColor(ANSI_BLUE) )() const defaultFormat = newFormat() proc printesc(edit: LineEdit, rs: seq[Rune]) = diff --git a/src/types/color.nim b/src/types/color.nim index b0f3a67f..09847147 100644 --- a/src/types/color.nim +++ b/src/types/color.nim @@ -12,6 +12,10 @@ type RGBAColor* = distinct uint32 + ANSIColor* = distinct uint8 + + EightBitColor* = distinct uint8 + CellColor* = object rgb*: bool n: uint32 @@ -24,6 +28,8 @@ converter toRGBAColor*(i: RGBColor): RGBAColor = func `==`*(a, b: RGBAColor): bool {.borrow.} +func `==`*(a, b: ANSIColor): bool {.borrow.} + func rgbcolor*(color: CellColor): RGBColor = cast[RGBColor](color.n) @@ -33,32 +39,25 @@ func color*(color: CellColor): uint8 = func cellColor*(rgb: RGBColor): CellColor = return CellColor(rgb: true, n: uint32(rgb)) -func cellColor*(c: uint8): CellColor = +func cellColor*(c: ANSIColor): CellColor = + return CellColor(rgb: false, n: uint32(c) mod 10) + +#TODO maybe bright ANSI colors? (8..15) + +func cellColor*(c: EightBitColor): CellColor = return CellColor(rgb: false, n: uint32(c)) const defaultColor* = CellColor(rgb: false, n: 0) -const ColorsANSIFg* = [ - CellColor(rgb: false, n: 30), # black - CellColor(rgb: false, n: 31), # red - CellColor(rgb: false, n: 32), # green - CellColor(rgb: false, n: 33), # yellow - CellColor(rgb: false, n: 34), # blue - CellColor(rgb: false, n: 35), # magenta - CellColor(rgb: false, n: 36), # cyan - CellColor(rgb: false, n: 37), # white -] - -const ColorsANSIBg* = [ - CellColor(rgb: false, n: 40), # black - CellColor(rgb: false, n: 41), # red - CellColor(rgb: false, n: 42), # green - CellColor(rgb: false, n: 43), # yellow - CellColor(rgb: false, n: 44), # blue - CellColor(rgb: false, n: 45), # magenta - CellColor(rgb: false, n: 46), # cyan - CellColor(rgb: false, n: 47), # white -] +const + ANSI_BLACK* = ANSIColor(0u8) + ANSI_RED* = ANSIColor(1u8) + ANSI_GREEN* = ANSIColor(2u8) + ANSI_YELLOW* = ANSIColor(3u8) + ANSI_BLUE* = ANSIColor(4u8) + ANSI_MAGENTA* = ANSIColor(5u8) + ANSI_CYAN* = ANSIColor(6u8) + ANSI_WHITE* = ANSIColor(7u8) const ColorsRGB* = { "aliceblue": 0xf0f8ff, @@ -373,6 +372,39 @@ func rgba*(r, g, b, a: int): RGBAColor = func gray*(n: uint8): RGBColor = return rgb(n, n, n) #TODO use yuv instead? +# NOTE: this assumes n notin 0..15 (which would be ANSI 4-bit) +func eightBitToRGB*(param0: EightBitColor): RGBColor = + doAssert uint8(param0) notin 0u8..15u8 + let u = uint8(param0) + if u in 16u8..231u8: + #16 + 36 * r + 6 * g + b + let n = u - 16 + let r = cast[uint8](int(n div 36) * 255 div 5) + let m = int(n mod 36) + let g = cast[uint8](((m div 6) * 255) div 5) + let b = cast[uint8](((m mod 6) * 255) div 5) + return rgb(r, g, b) + else: # 232..255 + let n = (u - 232) * 10 + 8 + return gray(n) + +func rgbToEightBit*(rgb: RGBColor): EightBitColor = + let r = int(rgb.r) + let g = int(rgb.g) + let b = int(rgb.b) + # Idea from here: https://github.com/Qix-/color-convert/pull/75 + # This seems to work about as well as checking for + # abs(U - 128) < 5 & abs(V - 128 < 5), but is definitely faster. + if r shr 4 == g shr 4 and g shr 4 == b shr 4: + if r < 8: + return EightBitColor(16) + if r > 248: + return EightBitColor(231) + return EightBitColor(cast[uint8](((r - 8 * 24) div 247) + 232)) + #16 + 36 * r + 6 * g + b + return EightBitColor(cast[uint8](16 + 36 * (r * 5 div 255) + + 6 * (g * 5 div 255) + (b * 5 div 255))) + template `$`*(rgbcolor: RGBColor): string = "rgb(" & $rgbcolor.r & ", " & $rgbcolor.g & ", " & $rgbcolor.b & ")" |