@@ -1,9 +1,9 @@
//! Terminal renderer module
//!
//! Renders the terminal grid to an ABGR pixel buffer using cached glyphs
//! Renders the terminal grid to an RGBA pixel buffer using cached glyphs
//! and proper ANSI color support from alacritty_terminal.
//!
//! The pixel buffer uses RGBA byte order (A at offset 0, B at 1, G at 2, R at 3)
//! The pixel buffer uses ABGR byte order (A at offset 0, B at 1, G at 2, R at 3)
//! to match Minecraft's NativeImage internal format, avoiding per-pixel conversion
//! on the Java side.
//!
@@ -12,7 +12,7 @@
use crate::glyph_cache::{GlyphCache, GlyphStyle, SubpixelOrder};
/// Color with f32 components in 0.0..1.0 (stored as r, g, b, a internally;
/// converted to ABGR byte order when written to the pixel buffer)
/// converted to RGBA byte order when written to the pixel buffer)
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
pub r: f32,
@@ -114,7 +114,7 @@
glyph_cache: GlyphCache,
/// Color palette
palette: ColorPalette,
/// Image buffer (RGBA pixels)
/// Image buffer (ABGR pixels)
pixel_buffer: Vec<u8>,
/// Image dimensions
width: u32,
@@ -429,7 +429,7 @@
}
}
/// Fill background with terminal background color (ABGR byte order)
/// Fill background with terminal background color (RGBA byte order for memcpy to NativeImage)
fn fill_background(&mut self) {
let bg = self.palette.background;
let r = (bg.r * 255.0) as u8;
@@ -437,14 +437,14 @@
let b = (bg.b * 255.0) as u8;
for i in (0..self.pixel_buffer.len()).step_by(4) {
self.pixel_buffer[i] = 255; // A
self.pixel_buffer[i] = r;
self.pixel_buffer[i + 1] = g;
self.pixel_buffer[i + 2] = b;
self.pixel_buffer[i + 3] = 255;
self.pixel_buffer[i + 1] = b; // B
self.pixel_buffer[i + 2] = g; // G
self.pixel_buffer[i + 3] = r; // R
}
}
/// Fill a rectangle with a color (RGBA byte order)
/// Fill a rectangle with a color (ABGR byte order)
fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) {
let r = (color.r * 255.0) as u8;
let g = (color.g * 255.0) as u8;
@@ -454,10 +454,10 @@
for px in x.max(0)..(x + w).min(self.width as i32) {
let idx = ((py as u32 * self.width + px as u32) * 4) as usize;
if idx + 3 < self.pixel_buffer.len() {
self.pixel_buffer[idx] = r;
self.pixel_buffer[idx + 1] = g;
self.pixel_buffer[idx + 2] = b;
self.pixel_buffer[idx + 3] = 255;
self.pixel_buffer[idx] = 255; // A
self.pixel_buffer[idx + 1] = b; // B
self.pixel_buffer[idx + 2] = g; // G
self.pixel_buffer[idx + 3] = r; // R
}
}
}
@@ -478,7 +478,7 @@
}
}
/// Draw a grayscale anti-aliased glyph (ABGR byte order)
/// Draw a grayscale anti-aliased glyph (RGBA byte order)
fn draw_glyph_grayscale(
&mut self,
glyph: &crate::glyph_cache::RasterizedGlyph,
@@ -514,22 +514,22 @@
continue;
}
// Alpha blending (RGBA: idx=R, idx+1=G, idx+2=B, idx+3=A)
// Alpha blending (ABGR: idx=A, idx+1=B, idx+2=G, idx+3=R)
let alpha_f = alpha as f32 / 255.0;
let inv_alpha = 1.0 - alpha_f;
self.pixel_buffer[idx] =
self.pixel_buffer[idx] = 255; // A
(r as f32 * alpha_f + self.pixel_buffer[idx] as f32 * inv_alpha) as u8;
self.pixel_buffer[idx + 1] =
(b as f32 * alpha_f + self.pixel_buffer[idx + 1] as f32 * inv_alpha) as u8;
(g as f32 * alpha_f + self.pixel_buffer[idx + 1] as f32 * inv_alpha) as u8;
self.pixel_buffer[idx + 2] =
(g as f32 * alpha_f + self.pixel_buffer[idx + 2] as f32 * inv_alpha) as u8;
self.pixel_buffer[idx + 3] =
(r as f32 * alpha_f + self.pixel_buffer[idx + 3] as f32 * inv_alpha) as u8;
(b as f32 * alpha_f + self.pixel_buffer[idx + 2] as f32 * inv_alpha) as u8;
self.pixel_buffer[idx + 3] = 255;
}
}
}
/// Draw a subpixel anti-aliased glyph (ABGR byte order)
/// Draw a subpixel anti-aliased glyph (RGBA byte order)
fn draw_glyph_subpixel(
&mut self,
glyph: &crate::glyph_cache::RasterizedGlyph,
@@ -589,22 +589,22 @@
continue;
}
// RGBA: idx=R, idx+1=G, idx+2=B, idx+3=A
let bg_r = self.pixel_buffer[idx];
let bg_g = self.pixel_buffer[idx + 1];
// ABGR: idx=A, idx+1=B, idx+2=G, idx+3=R
let bg_b = self.pixel_buffer[idx + 1];
let bg_g = self.pixel_buffer[idx + 2];
let bg_r = self.pixel_buffer[idx + 3];
let bg_b = self.pixel_buffer[idx + 2];
let alpha_r = cov_r as f32 / 255.0;
let alpha_g = cov_g as f32 / 255.0;
let alpha_b = cov_b as f32 / 255.0;
self.pixel_buffer[idx] = 255; // A
self.pixel_buffer[idx] =
(fg_r as f32 * alpha_r + bg_r as f32 * (1.0 - alpha_r)) as u8;
self.pixel_buffer[idx + 1] =
(fg_g as f32 * alpha_g + bg_g as f32 * (1.0 - alpha_g)) as u8;
self.pixel_buffer[idx + 2] =
(fg_b as f32 * alpha_b + bg_b as f32 * (1.0 - alpha_b)) as u8;
self.pixel_buffer[idx + 3] = 255;
self.pixel_buffer[idx + 2] =
(fg_g as f32 * alpha_g + bg_g as f32 * (1.0 - alpha_g)) as u8;
self.pixel_buffer[idx + 3] =
(fg_r as f32 * alpha_r + bg_r as f32 * (1.0 - alpha_r)) as u8;
}
}
}
@@ -776,7 +776,7 @@
}
#[test]
fn test_pixel_buffer_abgr_format() {
fn test_pixel_buffer_rgba_format() {
let mut renderer = TerminalRenderer::new(10, 5, 14.0).unwrap();
// Fill a rect with a known color: R=255, G=0, B=128
@@ -785,10 +785,10 @@
let pixels = renderer.pixel_buffer();
// ABGR byte order: [A, B, G, R]
assert_eq!(pixels[0], 255, "offset 0 should be Alpha=255");
assert_eq!(pixels[1], 127, "offset 1 should be Blue=127 (0.5*255.0 truncated)");
assert_eq!(pixels[2], 0, "offset 2 should be Green=0");
assert_eq!(pixels[3], 255, "offset 3 should be Red=255");
// RGBA byte order (matches NativeImage little-endian memory layout)
assert_eq!(pixels[0], 255, "offset 0 should be Red=255");
assert_eq!(pixels[1], 0, "offset 1 should be Green=0");
assert_eq!(pixels[2], 127, "offset 2 should be Blue=127");
assert_eq!(pixels[3], 255, "offset 3 should be Alpha=255");
}
}