1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 05:12:40 +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"
[dependencies]
cairo-sys-rs = "0.5.0"
failure = "0.1.1"
freetype = "0.3.0"
harfbuzz-sys = "0.1.15"
resize = "0.3.0"
servo-fontconfig = "0.4.0"
unicode-width = "0.1.4"
vte = "0.3.2"
xcb = "0.8.1"
[dependencies.cairo-rs]
features = ["xcb"]
version = "0.3.0"
[dependencies.xcb-util]
features = [
"image",
"icccm",
"ewmh",
"misc",
"shm",
]
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
use self::super::cairo;
use failure::{self, Error};
pub use fontconfig::fontconfig::*;
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 {

View File

@ -1,6 +1,5 @@
//! Higher level freetype bindings
use self::super::cairo;
use failure::Error;
pub use freetype::freetype::*;
use std::ffi::CString;
@ -93,15 +92,6 @@ impl Face {
(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 {

View File

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

View File

@ -2,12 +2,11 @@
extern crate failure;
extern crate unicode_width;
extern crate harfbuzz_sys;
extern crate cairo;
extern crate cairo_sys;
#[cfg(not(target_os = "macos"))]
extern crate fontconfig; // from servo-fontconfig
#[cfg(not(target_os = "macos"))]
extern crate freetype;
extern crate resize;
#[macro_use]
pub mod log;
@ -19,29 +18,21 @@ extern crate xcb_util;
use std::mem;
use std::slice;
use cairo::XCBSurface;
use cairo::prelude::*;
mod xgfx;
mod font;
use font::{Font, FontPattern, ftwrap};
fn cairo_err(status: cairo::Status) -> Error {
format_err!("cairo status: {:?}", status)
}
struct TerminalWindow<'a> {
window_id: u32,
screen_num: i32,
window: xgfx::Window<'a>,
conn: &'a xcb::Connection,
width: u16,
height: u16,
font: Font,
cell_height: f64,
cell_width: f64,
descender: f64,
cairo_context: cairo::Context,
window_surface: cairo::Surface,
buffer_surface: cairo::ImageSurface,
descender: isize,
window_context: xgfx::Context<'a>,
buffer_image: xgfx::Image,
need_paint: bool,
}
@ -53,7 +44,7 @@ impl<'a> TerminalWindow<'a> {
height: u16,
) -> 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)?;
let mut font = Font::new(pattern)?;
// we always load the cell_height for font 0,
@ -61,55 +52,22 @@ impl<'a> TerminalWindow<'a> {
// so that we can scale glyphs appropriately
let (cell_height, cell_width, descender) = font.get_metrics()?;
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();
let window = xgfx::Window::new(&conn, screen_num, width, height)?;
window.set_title("wterm");
let window_context = xgfx::Context::new(conn, &window);
xcb::create_window(
&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 buffer_image = xgfx::Image::new(width as usize, height as usize);
let descender = if descender.is_positive() {
((descender as f64) / 64.0).ceil()
((descender as f64) / 64.0).ceil() as isize
} else {
((descender as f64) / 64.0).floor()
((descender as f64) / 64.0).floor() as isize
};
Ok(TerminalWindow {
window_id,
screen_num,
window,
window_context,
buffer_image,
conn,
width,
height,
@ -117,48 +75,20 @@ impl<'a> TerminalWindow<'a> {
cell_height,
cell_width,
descender,
cairo_context,
buffer_surface,
window_surface,
need_paint: true,
})
}
fn show(&self) {
xcb::map_window(self.conn, self.window_id);
}
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,
))
self.window.show();
}
fn resize_surfaces(&mut self, width: u16, height: u16) -> Result<bool, Error> {
if width != self.width || height != self.height {
debug!("resize {},{}", width, height);
self.buffer_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, width as i32, height as i32)
.map_err(cairo_err)?;
self.window_surface.set_size(width as i32, height as i32);
let mut buffer = xgfx::Image::new(width as usize, height as usize);
buffer.draw_image(0, 0, &self.buffer_image);
self.buffer_image = buffer;
self.width = width;
self.height = height;
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> {
debug!("expose {},{}, {},{}", x, y, width, height);
self.cairo_context.reset_clip();
self.cairo_context.set_source_surface(
&self.buffer_surface,
0.0,
0.0,
);
self.cairo_context.rectangle(
x as f64,
y as f64,
width as f64,
height as f64,
);
self.cairo_context.clip();
self.cairo_context.paint();
if x == 0 && y == 0 && width == self.width && height == self.height {
self.window_context.put_image(0, 0, &self.buffer_image);
} else {
let mut im = xgfx::Image::new(width as usize, height as usize);
im.draw_image_subset(
0,
0,
x as usize,
y as usize,
width as usize,
height as usize,
&self.buffer_image,
);
self.window_context.put_image(x as i16, y as i16, &im);
}
self.conn.flush();
Ok(())
@ -194,15 +124,13 @@ impl<'a> TerminalWindow<'a> {
fn paint(&mut self) -> Result<(), Error> {
debug!("paint");
self.need_paint = false;
let ctx = cairo::Context::new(&self.buffer_surface);
let message = "x_advance != foo->bar(); ❤ 😍🤢";
ctx.set_source_rgb(0.0, 0.0, 0.0);
ctx.paint();
self.buffer_image.clear(xgfx::Color::rgb(0, 0, 0));
let mut x = 0.0;
let mut y = self.cell_height.ceil();
let mut x = 0 as isize;
let mut y = self.cell_height.ceil() as isize;
let glyph_info = self.font.shape(0, message)?;
for info in glyph_info {
let has_color = self.font.has_color(info.font_idx)?;
@ -215,6 +143,16 @@ impl<'a> TerminalWindow<'a> {
} else {
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 {
// 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 => {
// The FT rasterization is often not aligned in the way that
// cairo would like, so let's allocate a surface of the correct
// size and fill that up.
let mut surface = cairo::ImageSurface::create(
cairo::Format::Rgb24,
(ft_glyph.bitmap.width / 3) as i32,
ft_glyph.bitmap.rows as i32,
).map_err(cairo_err)?;
{
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 dest_offset = y * dest_pitch;
let src_offset = y * pitch;
dest_data[dest_offset..dest_offset + pitch]
.copy_from_slice(&data[src_offset..src_offset + pitch]);
}
}
Ok(surface)
xgfx::Image::with_bgr24(
ft_glyph.bitmap.width as usize / 3,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
xgfx::Image::with_bgra32(
ft_glyph.bitmap.width as usize,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
let mut surface = cairo::ImageSurface::create(
cairo::Format::Rgb24,
ft_glyph.bitmap.width as i32,
ft_glyph.bitmap.rows as i32,
).map_err(cairo_err)?;
{
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,
),
xgfx::Image::with_8bpp(
ft_glyph.bitmap.width as usize,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
},
}
mode @ _ => bail!("unhandled pixel mode: {:?}", mode),
}.map_err(cairo_err)?;
};
let bearing_x = ft_glyph.bitmap_left as f64;
let bearing_y = ft_glyph.bitmap_top as f64;
let bearing_x = (ft_glyph.bitmap_left as f64 * scale) as isize;
let bearing_y = (ft_glyph.bitmap_top as f64 * scale) as isize;
debug!(
"x,y: {},{} desc={} bearing:{},{} off={},{} adv={},{} scale={} width={}",
"x,y: {},{} desc={} bearing:{},{} off={},{} adv={},{} scale={}",
x,
y,
self.descender,
bearing_x,
bearing_y,
info.x_offset,
info.y_offset,
info.x_advance,
info.y_advance,
x_offset,
y_offset,
x_advance,
y_advance,
scale,
cairo_surface.get_width(),
);
ctx.translate(x, y + self.descender);
ctx.scale(scale, scale);
ctx.set_source_surface(
&cairo_surface,
// Destination for the paint operation
info.x_offset + bearing_x,
-(info.y_offset + bearing_y),
let image = if scale != 1.0 {
image.scale_by(scale)
} else {
image
};
// 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
ctx.set_source_rgb(0.2, 0.2, 0.2);
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;
x += x_advance as isize;
y += y_advance as isize;
}
let width = self.width;
let height = self.height;
self.expose(0, 0, width, height)?;
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> {
let (conn, screen_num) = xcb::Connection::connect(None)?;
println!("Connected screen {}", screen_num);
let mut window = TerminalWindow::new(&conn, screen_num, 1024, 300)?;
window.show();
conn.flush();
loop {
@ -384,6 +253,7 @@ fn run() -> Result<(), Error> {
match conn.poll_for_queued_event() {
None => {
window.paint()?;
conn.flush();
continue;
}
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);
}
}
}
}