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:
parent
aef4e340e8
commit
fb53f2c16b
14
Cargo.toml
14
Cargo.toml
@ -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"]
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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()?;
|
||||||
|
314
src/main.rs
314
src/main.rs
@ -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
422
src/xgfx.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user