修复 Zellij 在 foot 上的 Sixel 显示问题

由于 Zellij 的 sixel 写的很差劲, 不遵守 sixel 格式, 导致完全遵守 VT 标准的终端, 比如 foot 无法正确显示. 忍不了了自己修复.

问题主要在于, sixel 序列发送给 zellij 后, zellij 会反序列化一下, 然后根据位置再序列化. 但是他在反序列化和序列化的时候不考虑 pixel aspect ratio, 也不考虑 dcs, 而是直接把他们留空, 并且默认终端都是按照 1:1 的像素来的. 但是 foot 默认是 2:1, 于是就会导致高度被拉长:

Zellij 在 foot 上的 Sixel 显示问题
Zellij 在 foot 上的 Sixel 显示问题

在 zellij 的 sixel-image反序列化 中, 直接不考虑 pan 和 pad, 并且自行将图像填充到 ph $\times$ pv.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
            SixelEvent::RasterAttribute { pan: _, pad: _, ph, pv } => {
                // we ignore pan/pad because (reportedly) no-one uses them
                if !self.transparent_background {
                    if let Some(pv) = pv {
                        self.pad_lines_vertically(pv);
                    }
                    if let Some(ph) = ph {
                        self.pad_lines_horizontally(ph);
                    }
                }
            }

而在 序列化 的时候, dsc 直接留空:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    pub fn serialize(&self) -> String {
        let serialized_image = String::new();
        let serialized_image = self.serialize_empty_dcs(serialized_image);
        let serialized_image = self.serialize_color_registers(serialized_image);
        let serialized_image = self.serialize_pixels(serialized_image, None, None, None, None);
        let serialized_image = self.serialize_end_event(serialized_image);
        serialized_image
    }
    pub fn serialize_range(&self, start_x_index: usize, start_y_index: usize, width: usize, height: usize) -> String {
        let serialized_image = String::new();
        let serialized_image = self.serialize_empty_dcs(serialized_image);
        let serialized_image = self.serialize_color_registers(serialized_image);
        let serialized_image = self.serialize_pixels(serialized_image, Some(start_x_index), Some(start_y_index), Some(width), Some(height));
        let serialized_image = self.serialize_end_event(serialized_image);
        serialized_image
    }
    fn serialize_empty_dcs(&self, mut append_to: String) -> String {
        append_to.push_str("\u{1b}Pq");
        append_to
    }

所以让他在序列化和反序列化的时候考虑 pan 和 pad. 这么做了以后, 部分图片正常了, 但是有些图片右边会有很长一段宽宽的黑色. 在进行一系列 diff 实验之后, 发现只要把正确的 ph 和 pv 加上就可以了 (虽然没搞懂为什么).

加上图片的高和宽之后, 新的问题又出现了. 在用 yazi 测试的时候, 发现会切换图片后会黑一块, 覆盖掉本来应该显示的图片. 经过一些列的实验发现, 是 裁剪 导致的.

1
2
3
4
5
6
7
    pub fn cut_out(&mut self, start_x_index: usize, start_y_index: usize, width: usize, height: usize) {
        for row in self.pixels.iter_mut().skip(start_y_index).take(height) {
            for pixel in row.iter_mut().skip(start_x_index).take(width) {
                pixel.on = false;
            }
        }
    }

这个裁剪非常暴力, 直接把颜色关闭, 这样假如一个图片被全裁剪掉, 图片大小没有改变, 但是内容是空的 (也不完全空, 他的序列化会写一堆换行 -), 按照我直接将 ph 和 pv 写成图像高和宽的方法, 就变成一片黑了.

于是想计算图片右侧和下侧是否都被裁剪了, 是的话就把 pixel 这个二维 vector 给 truncate 掉. 但是, 即便我这么写, 图片计算宽高还是会有问题, 小是小了, 但是没有变成 0 这种, 不知道为什么, 也可能是我写的不太对.

最后想到, 直接把 dsc 中的 P2 强行设置成 1, 即透明背景. 这样传给 foot 的时候就没有黑框了.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
diff --git a/src/lib.rs b/src/lib.rs
index 99e14d9..a745b10 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -37,6 +37,8 @@ use sixel_tokenizer::{ColorCoordinateSystem, Parser};
 pub struct SixelImage {
     color_registers: BTreeMap<u16, SixelColor>,
     pixels: Vec<Vec<Pixel>>,
+    pan: Option<usize>,
+    pad: Option<usize>,
 }
 
 impl SixelImage {
@@ -62,14 +64,14 @@ impl SixelImage {
     }
     /// Serializes the whole image, returning a stringified sixel representation of it
     pub fn serialize(&self) -> String {
-        let sixel_serializer = SixelSerializer::new(&self.color_registers, &self.pixels);
+        let sixel_serializer = SixelSerializer::new(&self.color_registers, &self.pixels, self.pan, self.pad);
         let serialized_image = sixel_serializer.serialize();
         serialized_image
     }
     /// Serializes a specific rectangle of this image without manipulating the image itself, x/y
     /// coordinates as well as width height are in pixels
     pub fn serialize_range(&self, start_x_index: usize, start_y_index: usize, width: usize, height: usize) -> String {
-        let sixel_serializer = SixelSerializer::new(&self.color_registers, &self.pixels);
+        let sixel_serializer = SixelSerializer::new(&self.color_registers, &self.pixels, self.pan, self.pad);
         let serialized_image = sixel_serializer.serialize_range(start_x_index, start_y_index, width, height);
         serialized_image
     }
diff --git a/src/sixel_deserializer.rs b/src/sixel_deserializer.rs
index 92ba43f..ae32a64 100644
--- a/src/sixel_deserializer.rs
+++ b/src/sixel_deserializer.rs
@@ -15,6 +15,8 @@ pub struct SixelDeserializer {
     stop_parsing: bool,
     got_dcs: bool,
     transparent_background: bool,
+    pan: Option<usize>,
+    pad: Option<usize>,
 }
 
 impl SixelDeserializer {
@@ -29,6 +31,8 @@ impl SixelDeserializer {
             stop_parsing: false,
             got_dcs: false,
             transparent_background: false,
+            pan: None,
+            pad: None,
         }
     }
     /// Provide a `max_height` value in pixels, all pixels beyond this max height will not be
@@ -47,6 +51,8 @@ impl SixelDeserializer {
         Ok(SixelImage {
             pixels,
             color_registers,
+            pan: self.pan,
+            pad: self.pad
         })
     }
     /// Handle a [`SixelEvent`], changing the internal state to match
@@ -71,8 +77,9 @@ impl SixelDeserializer {
                     }
                 }
             }
-            SixelEvent::RasterAttribute { pan: _, pad: _, ph, pv } => {
-                // we ignore pan/pad because (reportedly) no-one uses them
+            SixelEvent::RasterAttribute { pan, pad, ph, pv } => {
+                self.pan = Some(pan);
+                self.pad = Some(pad);
                 if !self.transparent_background {
                     if let Some(pv) = pv {
                         self.pad_lines_vertically(pv);
diff --git a/src/sixel_serializer.rs b/src/sixel_serializer.rs
index 72e3d7b..7d20487 100644
--- a/src/sixel_serializer.rs
+++ b/src/sixel_serializer.rs
@@ -5,18 +5,22 @@ use crate::{SixelColor, Pixel};
 pub struct SixelSerializer <'a>{
     color_registers: &'a BTreeMap<u16, SixelColor>,
     pixels: &'a Vec<Vec<Pixel>>,
+    pan: Option<usize>,
+    pad: Option<usize>,
 }
 
 impl <'a>SixelSerializer <'a>{
-    pub fn new(color_registers: &'a BTreeMap<u16, SixelColor>, pixels: &'a Vec<Vec<Pixel>>) -> Self {
+    pub fn new(color_registers: &'a BTreeMap<u16, SixelColor>, pixels: &'a Vec<Vec<Pixel>>, pan: Option<usize>, pad: Option<usize>) -> Self {
         SixelSerializer {
             color_registers,
-            pixels
+            pixels,
+            pan,
+            pad
         }
     }
     pub fn serialize(&self) -> String {
         let serialized_image = String::new();
-        let serialized_image = self.serialize_empty_dcs(serialized_image);
+        let serialized_image = self.serialize_dcs(serialized_image, None, None);
         let serialized_image = self.serialize_color_registers(serialized_image);
         let serialized_image = self.serialize_pixels(serialized_image, None, None, None, None);
         let serialized_image = self.serialize_end_event(serialized_image);
@@ -24,14 +28,17 @@ impl <'a>SixelSerializer <'a>{
     }
     pub fn serialize_range(&self, start_x_index: usize, start_y_index: usize, width: usize, height: usize) -> String {
         let serialized_image = String::new();
-        let serialized_image = self.serialize_empty_dcs(serialized_image);
+        let serialized_image = self.serialize_dcs(serialized_image, Some(width), Some(height));
         let serialized_image = self.serialize_color_registers(serialized_image);
         let serialized_image = self.serialize_pixels(serialized_image, Some(start_x_index), Some(start_y_index), Some(width), Some(height));
         let serialized_image = self.serialize_end_event(serialized_image);
         serialized_image
     }
-    fn serialize_empty_dcs(&self, mut append_to: String) -> String {
-        append_to.push_str("\u{1b}Pq");
+    fn serialize_dcs(&self, mut append_to: String, width: Option<usize>, height: Option<usize>) -> String {
+        append_to.push_str("\u{1b}P0;1;0q");
+        let width = width.unwrap_or_else(|| self.pixels.first().map(|first_line| first_line.len()).unwrap_or(0));
+        let height = height.unwrap_or_else(|| self.pixels.len());
+        append_to.push_str(&format!("\"{};{};{};{}", self.pan.unwrap_or(1), self.pad.unwrap_or(1), width, height));
         append_to
     }
     fn serialize_color_registers(&self, mut append_to: String) -> String {

大概这样改改, 也不去提 issue 了, 非常 dirty hack.

最后修好了是这样的:

修复后效果
修复后效果

不过这性能还是拉胯, 稍微大一点的图片直接崩溃.