about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/buffer/cell.nim53
-rw-r--r--src/display/term.nim68
-rw-r--r--src/io/lineedit.nim2
-rw-r--r--src/types/color.nim76
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 & ")"