1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 13:21:38 +03:00

remove cairo dependency

It's a PITA to make it work with pixels without scaling and blurring
This commit is contained in:
Wez Furlong 2018-01-22 18:48:39 -08:00
parent aef4e340e8
commit fb53f2c16b
7 changed files with 522 additions and 270 deletions

View File

@ -4,19 +4,21 @@ name = "wterm"
version = "0.1.0" version = "0.1.0"
[dependencies] [dependencies]
cairo-sys-rs = "0.5.0"
failure = "0.1.1" failure = "0.1.1"
freetype = "0.3.0" freetype = "0.3.0"
harfbuzz-sys = "0.1.15" harfbuzz-sys = "0.1.15"
resize = "0.3.0"
servo-fontconfig = "0.4.0" servo-fontconfig = "0.4.0"
unicode-width = "0.1.4" unicode-width = "0.1.4"
vte = "0.3.2" vte = "0.3.2"
xcb = "0.8.1" xcb = "0.8.1"
[dependencies.cairo-rs]
features = ["xcb"]
version = "0.3.0"
[dependencies.xcb-util] [dependencies.xcb-util]
features = [
"image",
"icccm",
"ewmh",
"misc",
"shm",
]
version = "0.2.0" version = "0.2.0"
features = ["image", "icccm", "ewmh", "misc"]

View File

@ -1,15 +0,0 @@
use cairo_sys;
use fontconfig::fontconfig::FcPattern;
use freetype::freetype::FT_Face;
pub use cairo::*;
extern "C" {
pub fn cairo_ft_font_face_create_for_ft_face(
face: FT_Face,
load_flags: i32,
) -> *mut cairo_sys::cairo_font_face_t;
pub fn cairo_ft_font_face_create_for_pattern(
pattern: *mut FcPattern,
) -> *mut cairo_sys::cairo_font_face_t;
}

View File

@ -1,6 +1,5 @@
//! Slightly higher level helper for fontconfig //! Slightly higher level helper for fontconfig
use self::super::cairo;
use failure::{self, Error}; use failure::{self, Error};
pub use fontconfig::fontconfig::*; pub use fontconfig::fontconfig::*;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
@ -310,13 +309,6 @@ impl Pattern {
} }
} }
} }
#[allow(dead_code)]
pub fn as_cairo(&self) -> cairo::FontFace {
unsafe {
cairo::FontFace::from_raw_full(cairo::cairo_ft_font_face_create_for_pattern(self.pat))
}
}
} }
impl Drop for Pattern { impl Drop for Pattern {

View File

@ -1,6 +1,5 @@
//! Higher level freetype bindings //! Higher level freetype bindings
use self::super::cairo;
use failure::Error; use failure::Error;
pub use freetype::freetype::*; pub use freetype::freetype::*;
use std::ffi::CString; use std::ffi::CString;
@ -93,15 +92,6 @@ impl Face {
(width / 64.0, height) (width / 64.0, height)
} }
} }
#[allow(dead_code)]
pub fn as_cairo(&self) -> cairo::FontFace {
unsafe {
cairo::FontFace::from_raw_full(
cairo::cairo_ft_font_face_create_for_ft_face(self.face, 0),
)
}
}
} }
pub struct Library { pub struct Library {

View File

@ -5,7 +5,6 @@ use unicode_width::UnicodeWidthStr;
pub mod ftwrap; pub mod ftwrap;
pub mod hbwrap; pub mod hbwrap;
pub mod fcwrap; pub mod fcwrap;
pub mod cairo;
pub use self::fcwrap::Pattern as FontPattern; pub use self::fcwrap::Pattern as FontPattern;
@ -62,7 +61,6 @@ impl GlyphInfo {
struct FontInfo { struct FontInfo {
face: ftwrap::Face, face: ftwrap::Face,
font: hbwrap::Font, font: hbwrap::Font,
cairo_face: cairo::FontFace,
/// nominal monospace cell height /// nominal monospace cell height
cell_height: f64, cell_height: f64,
/// nominal monospace cell width /// nominal monospace cell width
@ -159,22 +157,15 @@ impl Font {
let (cell_width, cell_height) = face.cell_metrics(); let (cell_width, cell_height) = face.cell_metrics();
debug!("metrics: width={} height={}", cell_width, cell_height); debug!("metrics: width={} height={}", cell_width, cell_height);
let cairo_face = face.as_cairo();
self.fonts.push(FontInfo { self.fonts.push(FontInfo {
face, face,
font, font,
cairo_face,
cell_height, cell_height,
cell_width, cell_width,
}); });
Ok(()) Ok(())
} }
pub fn get_cairo_font(&mut self, idx: usize) -> Result<cairo::FontFace, Error> {
let font = self.get_font(idx)?;
Ok(font.cairo_face.clone())
}
fn get_font(&mut self, idx: usize) -> Result<&mut FontInfo, Error> { fn get_font(&mut self, idx: usize) -> Result<&mut FontInfo, Error> {
if idx >= self.fonts.len() { if idx >= self.fonts.len() {
self.load_next_fallback()?; self.load_next_fallback()?;

View File

@ -2,12 +2,11 @@
extern crate failure; extern crate failure;
extern crate unicode_width; extern crate unicode_width;
extern crate harfbuzz_sys; extern crate harfbuzz_sys;
extern crate cairo;
extern crate cairo_sys;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
extern crate fontconfig; // from servo-fontconfig extern crate fontconfig; // from servo-fontconfig
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
extern crate freetype; extern crate freetype;
extern crate resize;
#[macro_use] #[macro_use]
pub mod log; pub mod log;
@ -19,29 +18,21 @@ extern crate xcb_util;
use std::mem; use std::mem;
use std::slice; use std::slice;
use cairo::XCBSurface; mod xgfx;
use cairo::prelude::*;
mod font; mod font;
use font::{Font, FontPattern, ftwrap}; use font::{Font, FontPattern, ftwrap};
fn cairo_err(status: cairo::Status) -> Error {
format_err!("cairo status: {:?}", status)
}
struct TerminalWindow<'a> { struct TerminalWindow<'a> {
window_id: u32, window: xgfx::Window<'a>,
screen_num: i32,
conn: &'a xcb::Connection, conn: &'a xcb::Connection,
width: u16, width: u16,
height: u16, height: u16,
font: Font, font: Font,
cell_height: f64, cell_height: f64,
cell_width: f64, cell_width: f64,
descender: f64, descender: isize,
cairo_context: cairo::Context, window_context: xgfx::Context<'a>,
window_surface: cairo::Surface, buffer_image: xgfx::Image,
buffer_surface: cairo::ImageSurface,
need_paint: bool, need_paint: bool,
} }
@ -53,7 +44,7 @@ impl<'a> TerminalWindow<'a> {
height: u16, height: u16,
) -> Result<TerminalWindow, Error> { ) -> Result<TerminalWindow, Error> {
let mut pattern = FontPattern::parse("Operator Mono SSm:size=16")?; let mut pattern = FontPattern::parse("Operator Mono SSm Lig:size=12")?;
pattern.add_double("dpi", 96.0)?; pattern.add_double("dpi", 96.0)?;
let mut font = Font::new(pattern)?; let mut font = Font::new(pattern)?;
// we always load the cell_height for font 0, // we always load the cell_height for font 0,
@ -61,55 +52,22 @@ impl<'a> TerminalWindow<'a> {
// so that we can scale glyphs appropriately // so that we can scale glyphs appropriately
let (cell_height, cell_width, descender) = font.get_metrics()?; let (cell_height, cell_width, descender) = font.get_metrics()?;
let setup = conn.get_setup(); let window = xgfx::Window::new(&conn, screen_num, width, height)?;
let screen = setup.roots().nth(screen_num as usize).ok_or( window.set_title("wterm");
failure::err_msg( let window_context = xgfx::Context::new(conn, &window);
"no screen?",
),
)?;
let window_id = conn.generate_id();
xcb::create_window( let buffer_image = xgfx::Image::new(width as usize, height as usize);
&conn,
xcb::COPY_FROM_PARENT as u8,
window_id,
screen.root(),
// x, y
0,
0,
// width, height
width,
height,
// border width
0,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&[
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_STRUCTURE_NOTIFY,
),
],
);
xcb_util::icccm::set_wm_name(&conn, window_id, "wterm");
let window_surface =
TerminalWindow::make_cairo_surface(&conn, screen_num, window_id, width, height)?;
let buffer_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, width as i32, height as i32)
.map_err(cairo_err)?;
let cairo_context = cairo::Context::new(&window_surface);
let descender = if descender.is_positive() { let descender = if descender.is_positive() {
((descender as f64) / 64.0).ceil() ((descender as f64) / 64.0).ceil() as isize
} else { } else {
((descender as f64) / 64.0).floor() ((descender as f64) / 64.0).floor() as isize
}; };
Ok(TerminalWindow { Ok(TerminalWindow {
window_id, window,
screen_num, window_context,
buffer_image,
conn, conn,
width, width,
height, height,
@ -117,48 +75,20 @@ impl<'a> TerminalWindow<'a> {
cell_height, cell_height,
cell_width, cell_width,
descender, descender,
cairo_context,
buffer_surface,
window_surface,
need_paint: true, need_paint: true,
}) })
} }
fn show(&self) { fn show(&self) {
xcb::map_window(self.conn, self.window_id); self.window.show();
}
fn make_cairo_surface(
conn: &xcb::Connection,
screen_num: i32,
window_id: u32,
width: u16,
height: u16,
) -> Result<cairo::Surface, Error> {
let screen = conn.get_setup().roots().nth(screen_num as usize).ok_or(
failure::err_msg("no screen?"),
)?;
Ok(cairo::Surface::create(
&cairo::XCBConnection(
unsafe { mem::transmute(conn.get_raw_conn()) },
),
&cairo::XCBDrawable(window_id),
&cairo::XCBVisualType(unsafe {
mem::transmute(&mut visual_for_screen(&screen).base as *mut _)
}),
width as i32,
height as i32,
))
} }
fn resize_surfaces(&mut self, width: u16, height: u16) -> Result<bool, Error> { fn resize_surfaces(&mut self, width: u16, height: u16) -> Result<bool, Error> {
if width != self.width || height != self.height { if width != self.width || height != self.height {
debug!("resize {},{}", width, height); debug!("resize {},{}", width, height);
self.buffer_surface = let mut buffer = xgfx::Image::new(width as usize, height as usize);
cairo::ImageSurface::create(cairo::Format::ARgb32, width as i32, height as i32) buffer.draw_image(0, 0, &self.buffer_image);
.map_err(cairo_err)?; self.buffer_image = buffer;
self.window_surface.set_size(width as i32, height as i32);
self.width = width; self.width = width;
self.height = height; self.height = height;
self.need_paint = true; self.need_paint = true;
@ -171,21 +101,21 @@ impl<'a> TerminalWindow<'a> {
fn expose(&mut self, x: u16, y: u16, width: u16, height: u16) -> Result<(), Error> { fn expose(&mut self, x: u16, y: u16, width: u16, height: u16) -> Result<(), Error> {
debug!("expose {},{}, {},{}", x, y, width, height); debug!("expose {},{}, {},{}", x, y, width, height);
self.cairo_context.reset_clip(); if x == 0 && y == 0 && width == self.width && height == self.height {
self.cairo_context.set_source_surface( self.window_context.put_image(0, 0, &self.buffer_image);
&self.buffer_surface, } else {
0.0, let mut im = xgfx::Image::new(width as usize, height as usize);
0.0, im.draw_image_subset(
); 0,
self.cairo_context.rectangle( 0,
x as f64, x as usize,
y as f64, y as usize,
width as f64, width as usize,
height as f64, height as usize,
); &self.buffer_image,
self.cairo_context.clip(); );
self.cairo_context.paint(); self.window_context.put_image(x as i16, y as i16, &im);
}
self.conn.flush(); self.conn.flush();
Ok(()) Ok(())
@ -194,15 +124,13 @@ impl<'a> TerminalWindow<'a> {
fn paint(&mut self) -> Result<(), Error> { fn paint(&mut self) -> Result<(), Error> {
debug!("paint"); debug!("paint");
self.need_paint = false; self.need_paint = false;
let ctx = cairo::Context::new(&self.buffer_surface);
let message = "x_advance != foo->bar(); ❤ 😍🤢"; let message = "x_advance != foo->bar(); ❤ 😍🤢";
ctx.set_source_rgb(0.0, 0.0, 0.0); self.buffer_image.clear(xgfx::Color::rgb(0, 0, 0));
ctx.paint();
let mut x = 0.0; let mut x = 0 as isize;
let mut y = self.cell_height.ceil(); let mut y = self.cell_height.ceil() as isize;
let glyph_info = self.font.shape(0, message)?; let glyph_info = self.font.shape(0, message)?;
for info in glyph_info { for info in glyph_info {
let has_color = self.font.has_color(info.font_idx)?; let has_color = self.font.has_color(info.font_idx)?;
@ -215,6 +143,16 @@ impl<'a> TerminalWindow<'a> {
} else { } else {
1.0f64 1.0f64
}; };
let (x_offset, y_offset, x_advance, y_advance) = if scale != 1.0 {
(
info.x_offset * scale,
info.y_offset * scale,
info.x_advance * scale,
info.y_advance * scale,
)
} else {
(info.x_offset, info.y_offset, info.x_advance, info.y_advance)
};
if ft_glyph.bitmap.width == 0 || ft_glyph.bitmap.rows == 0 { if ft_glyph.bitmap.width == 0 || ft_glyph.bitmap.rows == 0 {
// a whitespace glyph // a whitespace glyph
@ -232,149 +170,80 @@ impl<'a> TerminalWindow<'a> {
) )
}; };
let cairo_surface = match mode { let image = match mode {
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_LCD => { ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
// The FT rasterization is often not aligned in the way that xgfx::Image::with_bgr24(
// cairo would like, so let's allocate a surface of the correct ft_glyph.bitmap.width as usize / 3,
// size and fill that up. ft_glyph.bitmap.rows as usize,
let mut surface = cairo::ImageSurface::create( pitch as usize,
cairo::Format::Rgb24, data,
(ft_glyph.bitmap.width / 3) as i32, )
ft_glyph.bitmap.rows as i32, }
).map_err(cairo_err)?; ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
{ xgfx::Image::with_bgra32(
let dest_pitch = surface.get_stride() as usize; ft_glyph.bitmap.width as usize,
let mut dest_data = surface.get_data()?; ft_glyph.bitmap.rows as usize,
for y in 0..ft_glyph.bitmap.rows as usize { pitch as usize,
let dest_offset = y * dest_pitch; data,
let src_offset = y * pitch; )
dest_data[dest_offset..dest_offset + pitch]
.copy_from_slice(&data[src_offset..src_offset + pitch]);
}
}
Ok(surface)
} }
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => { ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
let mut surface = cairo::ImageSurface::create( xgfx::Image::with_8bpp(
cairo::Format::Rgb24, ft_glyph.bitmap.width as usize,
ft_glyph.bitmap.width as i32, ft_glyph.bitmap.rows as usize,
ft_glyph.bitmap.rows as i32, pitch as usize,
).map_err(cairo_err)?; data,
{
let dest_pitch = surface.get_stride() as usize;
let mut dest_data = surface.get_data()?;
for y in 0..ft_glyph.bitmap.rows as usize {
let src_offset = y * pitch;
for x in 0..ft_glyph.bitmap.width as usize {
let dest_offset = (y * dest_pitch) + (x * 3);
let gray = data[src_offset + x];
dest_data[dest_offset + 0] = gray;
dest_data[dest_offset + 1] = gray;
dest_data[dest_offset + 2] = gray;
}
}
}
Ok(surface)
}
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => unsafe {
cairo::ImageSurface::from_raw_full(
cairo_sys::cairo_image_surface_create_for_data(
data.as_mut_ptr(),
cairo::Format::ARgb32,
ft_glyph.bitmap.width as i32,
ft_glyph.bitmap.rows as i32,
pitch as i32,
),
) )
}, }
mode @ _ => bail!("unhandled pixel mode: {:?}", mode), mode @ _ => bail!("unhandled pixel mode: {:?}", mode),
}.map_err(cairo_err)?; };
let bearing_x = ft_glyph.bitmap_left as f64; let bearing_x = (ft_glyph.bitmap_left as f64 * scale) as isize;
let bearing_y = ft_glyph.bitmap_top as f64; let bearing_y = (ft_glyph.bitmap_top as f64 * scale) as isize;
debug!( debug!(
"x,y: {},{} desc={} bearing:{},{} off={},{} adv={},{} scale={} width={}", "x,y: {},{} desc={} bearing:{},{} off={},{} adv={},{} scale={}",
x, x,
y, y,
self.descender, self.descender,
bearing_x, bearing_x,
bearing_y, bearing_y,
info.x_offset, x_offset,
info.y_offset, y_offset,
info.x_advance, x_advance,
info.y_advance, y_advance,
scale, scale,
cairo_surface.get_width(),
); );
ctx.translate(x, y + self.descender);
ctx.scale(scale, scale);
ctx.set_source_surface( let image = if scale != 1.0 {
&cairo_surface, image.scale_by(scale)
// Destination for the paint operation } else {
info.x_offset + bearing_x, image
-(info.y_offset + bearing_y), };
// TODO: colorize
self.buffer_image.draw_image(
x + x_offset as isize + bearing_x,
y + self.descender - (y_offset as isize + bearing_y),
&image,
); );
ctx.paint();
if !has_color || false {
// Apply text color.
// TODO: we only do this for non-colored fonts, but
// we could apply it to those also if the current
// cell color is not the default foreground attribute.
ctx.save();
ctx.rectangle(
info.x_offset + bearing_x,
-(info.y_offset + bearing_y),
cairo_surface.get_width() as f64,
cairo_surface.get_height() as f64,
);
ctx.clip();
ctx.set_source_rgb(0.7, 0.7, 0.7);
ctx.set_operator(cairo::Operator::Multiply);
ctx.paint();
ctx.restore();
}
ctx.identity_matrix();
} }
// for debugging purposes, outline the cell x += x_advance as isize;
ctx.set_source_rgb(0.2, 0.2, 0.2); y += y_advance as isize;
ctx.rectangle(x, y - self.cell_height, self.cell_width, self.cell_height);
ctx.stroke();
x += scale * info.x_advance;
y += scale * info.y_advance;
} }
let width = self.width;
let height = self.height;
self.expose(0, 0, width, height)?;
Ok(()) Ok(())
} }
} }
fn visual_for_screen(screen: &xcb::Screen) -> xcb::Visualtype {
for depth in screen.allowed_depths() {
for vis in depth.visuals() {
if vis.visual_id() == screen.root_visual() {
return vis;
}
}
}
unreachable!("screen doesn't have info on its own visual?");
}
fn run() -> Result<(), Error> { fn run() -> Result<(), Error> {
let (conn, screen_num) = xcb::Connection::connect(None)?; let (conn, screen_num) = xcb::Connection::connect(None)?;
println!("Connected screen {}", screen_num); println!("Connected screen {}", screen_num);
let mut window = TerminalWindow::new(&conn, screen_num, 1024, 300)?; let mut window = TerminalWindow::new(&conn, screen_num, 1024, 300)?;
window.show(); window.show();
conn.flush(); conn.flush();
loop { loop {
@ -384,6 +253,7 @@ fn run() -> Result<(), Error> {
match conn.poll_for_queued_event() { match conn.poll_for_queued_event() {
None => { None => {
window.paint()?; window.paint()?;
conn.flush();
continue; continue;
} }
Some(event) => Some(event), Some(event) => Some(event),

422
src/xgfx.rs Normal file
View File

@ -0,0 +1,422 @@
use resize;
use std::result;
use xcb;
use xcb_util;
use failure::{self, Error};
pub type Result<T> = result::Result<T, Error>;
/// The X protocol allows referencing a number of drawable
/// objects. This trait marks those objects here in code.
pub trait Drawable {
fn as_drawable(&self) -> xcb::xproto::Drawable;
fn get_conn(&self) -> &xcb::Connection;
}
/// A Window!
pub struct Window<'a> {
window_id: xcb::xproto::Window,
conn: &'a xcb::Connection,
}
impl<'a> Drawable for Window<'a> {
fn as_drawable(&self) -> xcb::xproto::Drawable {
self.window_id
}
fn get_conn(&self) -> &xcb::Connection {
self.conn
}
}
impl<'a> Window<'a> {
/// Create a new window on the specified screen with the specified dimensions
pub fn new(conn: &xcb::Connection, screen_num: i32, width: u16, height: u16) -> Result<Window> {
let setup = conn.get_setup();
let screen = setup.roots().nth(screen_num as usize).ok_or(
failure::err_msg(
"no screen?",
),
)?;
let window_id = conn.generate_id();
xcb::create_window_checked(
&conn,
xcb::COPY_FROM_PARENT as u8,
window_id,
screen.root(),
// x, y
0,
0,
// width, height
width,
height,
// border width
0,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&[
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_STRUCTURE_NOTIFY,
),
],
).request_check()?;
Ok(Window {
conn,
window_id,
})
}
/// Change the title for the window manager
pub fn set_title(&self, title: &str) {
xcb_util::icccm::set_wm_name(&self.conn, self.window_id, title);
}
/// Display the window
pub fn show(&self) {
xcb::map_window(self.conn, self.window_id);
}
}
impl<'a> Drop for Window<'a> {
fn drop(&mut self) {
xcb::destroy_window(self.conn, self.window_id);
}
}
pub struct Pixmap<'a> {
pixmap_id: xcb::xproto::Pixmap,
conn: &'a xcb::Connection,
}
impl<'a> Drawable for Pixmap<'a> {
fn as_drawable(&self) -> xcb::xproto::Drawable {
self.pixmap_id
}
fn get_conn(&self) -> &xcb::Connection {
self.conn
}
}
impl<'a> Pixmap<'a> {
pub fn new(drawable: &Drawable, depth: u8, width: u16, height: u16) -> Result<Pixmap> {
let conn = drawable.get_conn();
let pixmap_id = conn.generate_id();
xcb::create_pixmap(
&conn,
depth,
pixmap_id,
drawable.as_drawable(),
width,
height,
).request_check()?;
Ok(Pixmap { conn, pixmap_id })
}
}
impl<'a> Drop for Pixmap<'a> {
fn drop(&mut self) {
xcb::free_pixmap(self.conn, self.pixmap_id);
}
}
pub struct Context<'a> {
gc_id: xcb::xproto::Gcontext,
conn: &'a xcb::Connection,
drawable: xcb::xproto::Drawable,
}
impl<'a> Context<'a> {
pub fn new(conn: &'a xcb::Connection, d: &Drawable) -> Context<'a> {
let gc_id = conn.generate_id();
let drawable = d.as_drawable();
xcb::create_gc(&conn, gc_id, drawable, &[]);
Context {
gc_id,
conn,
drawable,
}
}
/// Copy an area from one drawable to another using the settings
/// defined in this context.
pub fn copy_area(
&self,
src: &Drawable,
src_x: i16,
src_y: i16,
dest: &Drawable,
dest_x: i16,
dest_y: i16,
width: u16,
height: u16,
) -> xcb::VoidCookie {
xcb::copy_area(
self.conn,
src.as_drawable(),
dest.as_drawable(),
self.gc_id,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
)
}
/// Send image bytes and render them into the drawable that was used to
/// create this context.
pub fn put_image(&self, dest_x: i16, dest_y: i16, im: &Image) -> xcb::VoidCookie {
println!(
"put_image @{},{} x {},{}",
dest_x,
dest_y,
im.width,
im.height
);
xcb::put_image(
self.conn,
xcb::xproto::IMAGE_FORMAT_Z_PIXMAP as u8,
self.drawable,
self.gc_id,
im.width as u16,
im.height as u16,
dest_x,
dest_y,
0,
24,
&im.data,
)
}
}
impl<'a> Drop for Context<'a> {
fn drop(&mut self) {
xcb::free_gc(self.conn, self.gc_id);
}
}
/// A color stored as big endian bgra32
pub struct Color(u32);
impl Color {
pub fn rgb(red: u8, green: u8, blue: u8) -> Color {
Color::rgba(red, green, blue, 0xff)
}
pub fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
let word = (blue as u32) << 24 | (green as u32) << 16 | (red as u32) << 8 | alpha as u32;
Color(word.to_be())
}
}
/// A bitmap in big endian bgra32 color format
pub struct Image {
data: Vec<u8>,
width: usize,
height: usize,
}
impl Image {
/// Create a new bgra32 image buffer with the specified dimensions.
/// The buffer is initialized to all zeroes.
pub fn new(width: usize, height: usize) -> Image {
let size = height * width * 4;
let mut data = Vec::with_capacity(size);
data.resize(size, 0);
Image {
data,
width,
height,
}
}
/// Create a new bgra32 image buffer with the specified dimensions.
/// The buffer is populated with the source data in bgr24 format.
pub fn with_bgr24(width: usize, height: usize, stride: usize, data: &[u8]) -> Image {
let mut image = Image::new(width, height);
for y in 0..height {
let src_offset = y * stride;
let dest_offset = y * width * 4;
for x in 0..width {
let blue = data[src_offset + (x * 3) + 0];
let green = data[src_offset + (x * 3) + 1];
let red = data[src_offset + (x * 3) + 2];
let alpha = if (red | green | blue) > 0 { 0xff } else { 0 };
image.data[dest_offset + (x * 4) + 0] = blue;
image.data[dest_offset + (x * 4) + 1] = green;
image.data[dest_offset + (x * 4) + 2] = red;
image.data[dest_offset + (x * 4) + 3] = alpha;
}
}
image
}
/// Create a new bgra32 image buffer with the specified dimensions.
/// The buffer is populated with the source data in argb32 format.
pub fn with_bgra32(width: usize, height: usize, stride: usize, data: &[u8]) -> Image {
let mut image = Image::new(width, height);
for y in 0..height {
let src_offset = y * stride;
let dest_offset = y * width * 4;
for x in 0..width {
let blue = data[src_offset + (x * 4) + 0];
let green = data[src_offset + (x * 4) + 1];
let red = data[src_offset + (x * 4) + 2];
let alpha = data[src_offset + (x * 4) + 3];
image.data[dest_offset + (x * 4) + 0] = blue;
image.data[dest_offset + (x * 4) + 1] = green;
image.data[dest_offset + (x * 4) + 2] = red;
image.data[dest_offset + (x * 4) + 3] = alpha;
}
}
image
}
pub fn with_8bpp(width: usize, height: usize, stride: usize, data: &[u8]) -> Image {
let mut image = Image::new(width, height);
for y in 0..height {
let src_offset = y * stride;
let dest_offset = y * width * 4;
for x in 0..width {
let gray = data[src_offset + x];
let alpha = if gray != 0 { 0xff } else { 0 };
image.data[dest_offset + (x * 4) + 0] = gray;
image.data[dest_offset + (x * 4) + 1] = gray;
image.data[dest_offset + (x * 4) + 2] = gray;
image.data[dest_offset + (x * 4) + 3] = alpha;
}
}
image
}
/// Creates a new image with the contents of the current image, but
/// resized to the specified dimensions.
pub fn resize(&self, width: usize, height: usize) -> Image {
let mut dest = Image::new(width, height);
let algo = if (width * height) < (self.width * self.height) {
resize::Type::Lanczos3
} else {
resize::Type::Mitchell
};
resize::new(
self.width,
self.height,
width,
height,
resize::Pixel::RGBA,
algo,
).resize(&self.data, &mut dest.data);
dest
}
pub fn scale_by(&self, scale: f64) -> Image {
let width = (self.width as f64 * scale) as usize;
let height = (self.height as f64 * scale) as usize;
self.resize(width, height)
}
#[inline]
/// Obtain a mutable reference to the raw bgra pixel at the specified coordinates
pub fn pixel_mut(&mut self, x: usize, y: usize) -> &mut u32 {
assert!(x < self.width && y < self.height);
unsafe {
let offset = (y * self.width * 4) + (x * 4);
&mut *(self.data.as_mut_ptr().offset(offset as isize) as *mut u32)
}
}
#[inline]
/// Read the raw bgra pixel at the specified coordinates
pub fn pixel(&self, x: usize, y: usize) -> u32 {
assert!(x < self.width && y < self.height);
unsafe {
let offset = (y * self.width * 4) + (x * 4);
*(self.data.as_ptr().offset(offset as isize) as *const u32)
}
}
/// Clear the entire image to the specific color
pub fn clear(&mut self, color: Color) {
let width = self.width;
let height = self.height;
self.clear_rect(0, 0, width, height, color);
}
pub fn clear_rect(
&mut self,
dest_x: isize,
dest_y: isize,
width: usize,
height: usize,
color: Color,
) {
for y in 0..height {
let dest_y = y as isize + dest_y;
if dest_y < 0 {
continue;
}
if dest_y as usize >= self.height {
break;
}
for x in 0..width {
let dest_x = x as isize + dest_x;
if dest_x < 0 {
continue;
}
if dest_x as usize >= self.width {
break;
}
*self.pixel_mut(dest_x as usize, dest_y as usize) = color.0;
}
}
}
pub fn draw_image(&mut self, dest_x: isize, dest_y: isize, im: &Image) {
self.draw_image_subset(dest_x, dest_y, 0, 0, im.width, im.height, im)
}
pub fn draw_image_subset(
&mut self,
dest_x: isize,
dest_y: isize,
src_x: usize,
src_y: usize,
width: usize,
height: usize,
im: &Image,
) {
assert!(width <= im.width && height <= im.height);
assert!(src_x < im.width && src_y < im.height);
for y in src_y..height {
let dest_y = y as isize + dest_y - src_y as isize;
if dest_y < 0 {
continue;
}
if dest_y as usize >= self.height {
break;
}
for x in src_x..width {
let dest_x = x as isize + dest_x - src_x as isize;
if dest_x < 0 {
continue;
}
if dest_x as usize >= self.width {
break;
}
*self.pixel_mut(dest_x as usize, dest_y as usize) = im.pixel(x, y);
}
}
}
}