marrub
/
grope
Archived
1
0
Fork 0

initial commit

master
an 2018-11-09 22:10:14 -05:00
commit f193f4fd30
9 changed files with 572 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.png
*.swp
target
Cargo.lock

15
Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "grope"
version = "0.1.0"
authors = ["Alison Sanderson"]
edition = "2018"
[dependencies]
cairo-rs = {version = "0.5", features = ["png"]}
gdk = "0.9"
gdk-pixbuf = "0.5"
glib = "0.6"
gtk = "0.5"
gtk-sys = "0.7"
libc = "0.2"
memmap = "0.7"

62
src/bin.rs Normal file
View File

@ -0,0 +1,62 @@
//! Binary data conversion utilities.
pub type Ident = [u8; 4];
pub type ResultS<T> = Result<T, &'static str>;
pub trait BinToNum
{
// Checked
fn c_iden(&self, i: usize) -> ResultS<Ident>;
fn c_u32b(&self, i: usize) -> ResultS<u32>;
fn c_u16b(&self, i: usize) -> ResultS<u16>;
fn c_i32b(&self, i: usize) -> ResultS<i32>
{match self.c_u32b(i) {Ok(n) => Ok(n as i32), Err(e) => Err(e)}}
fn c_i16b(&self, i: usize) -> ResultS<i16>
{match self.c_u16b(i) {Ok(n) => Ok(n as i16), Err(e) => Err(e)}}
// Optional
fn o_iden(&self, i: usize) -> Option<Ident> {self.c_iden(i).ok()}
fn o_u32b(&self, i: usize) -> Option<u32> {self.c_u32b(i).ok()}
fn o_u16b(&self, i: usize) -> Option<u16> {self.c_u16b(i).ok()}
fn o_i32b(&self, i: usize) -> Option<i32> {self.c_i32b(i).ok()}
fn o_i16b(&self, i: usize) -> Option<i16> {self.c_i16b(i).ok()}
// Unchecked
fn b_iden(&self, i: usize) -> Ident {self.c_iden(i).unwrap()}
fn b_u32b(&self, i: usize) -> u32 {self.c_u32b(i).unwrap()}
fn b_u16b(&self, i: usize) -> u16 {self.c_u16b(i).unwrap()}
fn b_i32b(&self, i: usize) -> i32 {self.c_i32b(i).unwrap()}
fn b_i16b(&self, i: usize) -> i16 {self.c_i16b(i).unwrap()}
}
impl BinToNum for [u8]
{
fn c_iden(&self, i: usize) -> ResultS<Ident>
{
if i + 3 >= self.len() {return Err("not enough data")}
Ok([self[i], self[i+1], self[i+2], self[i+3]])
}
fn c_u32b(&self, i: usize) -> ResultS<u32>
{
if i + 3 >= self.len() {return Err("not enough data")}
Ok(self[i ] as (u32) << 24 | self[i+1] as (u32) << 16 |
self[i+2] as (u32) << 8 | self[i+3] as (u32))
}
fn c_u16b(&self, i: usize) -> ResultS<u16>
{
if i + 1 >= self.len() {return Err("not enough data")}
Ok(self[i] as (u16) << 8 | self[i+1] as (u16))
}
}
pub fn d_u32b(n: u32) -> [u8; 4] {[(n >> 24) as u8, (n >> 16) as u8,
(n >> 8) as u8, (n >> 0) as u8]}
pub fn d_u16b(n: u16) -> [u8; 2] {[(n >> 8) as u8, (n >> 0) as u8]}
pub fn d_i32b(n: i32) -> [u8; 4] {d_u32b(n as u32)}
pub fn d_i16b(n: i16) -> [u8; 2] {d_u16b(n as u16)}
// EOF

18
src/crc.rs Normal file
View File

@ -0,0 +1,18 @@
fn crc_init() -> [u32; 256]
{
let mut t = [0; 256];
for n in 0..256
{
t[n] = (0..8).fold(n as u32, |a, _|
{if a & 1 == 1 {0xedb88320 ^ a >> 1} else {a >> 1}});
}
t
}
pub fn crc32(b: &[u8], s: u32) -> u32
{
let t = crc_init();
!b.iter().fold(!s, |a, &o| {a >> 8 ^ t[(a & 0xff ^ o as u32) as usize]})
}
// EOF

8
src/err.rs Normal file
View File

@ -0,0 +1,8 @@
#[derive(Debug)] pub struct StrErr(pub String);
impl<T: ToString> From<T> for StrErr
{fn from(e: T) -> Self {StrErr(e.to_string())}}
impl StrErr {pub fn new(e: impl ToString) -> Self {StrErr(e.to_string())}}
// EOF

51
src/gtkcrap.rs Normal file
View File

@ -0,0 +1,51 @@
use crate::err;
use glib::object::Downcast;
use glib::translate::{FromGlibPtrNone, Stash, ToGlib, ToGlibPtr};
use gtk::{FileChooserAction, prelude::*};
use gtk_sys::*;
use libc::{c_char, c_void};
use std::ptr;
pub fn file_chooser(title: &str, action: FileChooserAction, parent: &gtk::Window) -> gtk::FileChooserDialog
{
unsafe
{
let title = Some(title);
let title = title.to_glib_none();
let parent = parent.to_glib_none();
let first = Some("_Cancel");
let first = first.to_glib_none();
let second = Some(match action {
FileChooserAction::Open => "_Open",
FileChooserAction::Save => "_Save",
FileChooserAction::SelectFolder => "_Select",
FileChooserAction::CreateFolder => "_Create",
FileChooserAction::__Unknown(_) => "_Choose",
});
let second: Stash<*const c_char, Option<&str>> = second.to_glib_none();
gtk::Widget::from_glib_none(gtk_file_chooser_dialog_new(
title.0, parent.0,
action.to_glib(),
first.0, GTK_RESPONSE_CANCEL,
second.0, GTK_RESPONSE_ACCEPT,
ptr::null() as *const c_void)).downcast_unchecked()
}
}
pub fn err_dlg(w: &gtk::Window, r: Result<(), err::StrErr>)
{
match r {
Ok(()) => (),
Err(e) => {
let dlg = gtk::MessageDialog::new(Some(w),
gtk::DialogFlags::MODAL,
gtk::MessageType::Error,
gtk::ButtonsType::Ok,
&e.0);
dlg.run();
dlg.destroy();
}
}
}
// EOF

38
src/icon.rs Normal file
View File

@ -0,0 +1,38 @@
pub static ICON: [&str; 35] = [
"32 32 2 1 ",
" c black",
". c white",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ... . . .. ... .. ",
" . . .. . . . . . . ",
" . . . . . . . .... ",
" . . . . . . . . ",
" ... . .. ... ... ",
" . . ",
" ... . ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" "];
// EOF

299
src/main.rs Normal file
View File

@ -0,0 +1,299 @@
#![windows_subsystem="windows"]
#[allow(dead_code)] mod bin;
#[allow(dead_code)] mod crc;
mod err;
mod gtkcrap;
mod icon;
#[allow(dead_code)] mod png;
use cairo::ImageSurface;
use crate::{bin::*, err::*, gtkcrap::*, png::*};
use gdk::{EventMask, enums::key};
use gtk::{FileChooserAction, prelude::*};
use memmap::Mmap;
use std::{cell::RefCell, fs::File, path::{Path, PathBuf}, rc::Rc};
fn drag(e: &gdk::EventMotion, v: &mut Stuff) -> Inhibit
{
if e.get_state().contains(gdk::ModifierType::BUTTON3_MASK)
{
let p = e.get_position();
if let Some(c) = &mut v.c
{
v.v.0 -= (c.0 - p.0) / 2.0;
v.v.1 -= (c.1 - p.1) / 2.0;
v.update();
}
v.c = Some(p);
Inhibit(true)
}
else if e.get_state().contains(gdk::ModifierType::BUTTON1_MASK)
{
if let None = v.png {return Inhibit(false)}
let p = e.get_position();
if let Some(c) = &mut v.c
{
v.p.0 += (c.0 - p.0) as i32;
v.p.1 += (c.1 - p.1) as i32;
v.update();
}
v.c = Some(p);
Inhibit(true)
}
else
{Inhibit(false)}
}
fn drop(_e: &gdk::EventButton, v: &mut Stuff) -> Inhibit
{
v.c = None;
Inhibit(false)
}
fn draw(c: &cairo::Context, v: &mut Stuff) -> Inhibit
{
const VW: f64 = 320.0;
const VH: f64 = 200.0;
const HW: f64 = 160.0;
const HH: f64 = 100.0;
// scale to 320x200 (1.2x height)
c.scale(1.0, 1.2);
// draw background
c.set_source_rgb(167.0/255.0, 107.0/255.0, 107.0/255.0);
c.rectangle(0.0, 0.0, VW, VH);
c.fill();
// center
c.translate(HW + v.v.0, HH + v.v.1);
// guide lines
c.set_source_rgb(0.5, 0.0, 0.0);
c.set_line_width(1.0);
// center
c.move_to(-VW, 0.0); c.line_to( VW, 0.0); c.stroke();
c.move_to(0.0, -VH); c.line_to(0.0, VH); c.stroke();
// hud borders
c.move_to(0.0, VH); c.line_to(VW, VH); c.stroke();
c.move_to( VW, 0.0); c.line_to(VW, VH); c.stroke();
// statusbar
c.move_to(0.0, VH-32.0); c.line_to(VW, VH-32.0); c.stroke(); // doom
c.move_to(0.0, VH-38.0); c.line_to(VW, VH-38.0); c.stroke(); // hexen
c.move_to(0.0, VH-42.0); c.line_to(VW, VH-42.0); c.stroke(); // heretic
// draw image
if let Some(img) = &v.img
{
c.set_source_surface(img, 0.0 - v.p.0 as f64, 0.0 - v.p.1 as f64);
c.paint();
}
Inhibit(false)
}
fn key(k: &gdk::EventKey, v: &mut Stuff) -> Inhibit
{
if let None = v.png {return Inhibit(false)}
match k.get_keyval() {
key::Left => {v.p.0 += 1; v.update(); Inhibit(true)},
key::Right => {v.p.0 -= 1; v.update(); Inhibit(true)},
key::Up => {v.p.1 += 1; v.update(); Inhibit(true)},
key::Down => {v.p.1 -= 1; v.update(); Inhibit(true)},
_ => Inhibit(false),
}
}
fn open(v: &mut Stuff)
{
let fc = file_chooser("Open Image", FileChooserAction::Open, &v.win);
if fc.run() == gtk_sys::GTK_RESPONSE_ACCEPT {
if let Some(p) = fc.get_filename() {
let r = v.set_img(&p); err_dlg(&v.win, r);
}
}
fc.destroy();
}
fn save(v: &mut Stuff)
{if let Some(p) = &v.path {let r = v.save_img(&p); err_dlg(&v.win, r)}}
fn save_as(v: &mut Stuff)
{
let fc = file_chooser("Save Image", FileChooserAction::Save, &v.win);
if fc.run() == gtk_sys::GTK_RESPONSE_ACCEPT {
if let Some(p) = fc.get_filename() {
let r = v.save_img(&p); err_dlg(&v.win, r);
}
}
fc.destroy();
}
#[allow(dead_code)]
struct Stuff
{
p: (i32, i32),
s: (u32, u32),
png: Option<PngFile>,
img: Option<ImageSurface>,
v: (f64, f64),
c: Option<(f64, f64)>,
path: Option<PathBuf>,
lx: gtk::Label,
ly: gtk::Label,
xh: gtk::Box,
area: gtk::DrawingArea,
xv: gtk::Box,
win: gtk::Window,
}
impl Stuff
{
fn update(&self)
{
self.lx.set_text(&(-self.p.0).to_string());
self.ly.set_text(&(-self.p.1).to_string());
self.area.queue_draw();
}
fn set_img(&mut self, fname: &Path) -> Result<(), StrErr>
{
let mut fp = File::open(fname)?;
let png = PngFile::new(&unsafe{Mmap::map(&fp)?})?;
let img = ImageSurface::create_from_png(&mut fp)?;
let xy =
if let Some(xy) = png.chunk(*b"grAb", |c| (c.b_i32b(0), c.b_i32b(4)))
{xy}
else
{(0, 0)};
let wh = png.chunk(*b"IHDR", |c| (c.b_u32b(0), c.b_u32b(4)))
.ok_or(StrErr::new("no IHDR"))?;
self.p = xy;
self.s = wh;
self.png = Some(png);
self.img = Some(img);
self.path = Some(fname.canonicalize()?);
self.update();
Ok(())
}
fn save_img(&self, fname: &Path) -> Result<(), StrErr>
{
match &self.png {
Some(png) => {
let mut png = png.clone();
let mut c = PngChunk{typ: *b"grAb", dat: Vec::new()};
for b in &d_i32b(self.p.0) {c.dat.push(*b)}
for b in &d_i32b(self.p.1) {c.dat.push(*b)}
if let Some(n) = png.find_chunk(*b"grAb") {png.chnk[n] = c}
else {png.chnk[1] = c}
png.write(&mut File::create(fname)?)?;
Ok(())
}
None => Err("no image loaded".into())
}
}
}
fn btn<F>(b: &gtk::Button, r: Rc<RefCell<Stuff>>, f: F)
where F: Fn(&mut Stuff) + 'static
{
b.connect_clicked(move |_|
{if let Ok(mut v) = r.try_borrow_mut() {f(&mut v)}});
}
fn cb<T, U: gtk::WidgetExt>(r: Rc<RefCell<Stuff>>,
f: impl Fn(&T, &mut Stuff) -> Inhibit + 'static)
-> impl Fn(&U, &T) -> Inhibit + 'static
{
move |_, e| {
if let Ok(mut v) = r.try_borrow_mut() {f(e, &mut v)}
else {Inhibit(false)}
}
}
fn main() -> Result<(), StrErr>
{
gtk::init()?;
let ly = gtk::Label::new(None);
let lx = gtk::Label::new(None);
let bs = gtk::Button::new_with_mnemonic("_As");
let bq = gtk::Button::new_with_mnemonic("_Save");
let bo = gtk::Button::new_with_mnemonic("_Open");
let xh = gtk::Box::new(gtk::Orientation::Horizontal, 4);
xh.add(&bo);
xh.add(&bq);
xh.add(&bs);
xh.add(&lx);
xh.add(&ly);
let area = gtk::DrawingArea::new();
area.set_size_request(320, 240);
let xv = gtk::Box::new(gtk::Orientation::Vertical, 4);
xv.add(&area);
xv.add(&xh);
let win = gtk::Window::new(gtk::WindowType::Toplevel);
win.set_title("grAb editor");
win.set_resizable(false);
win.set_icon(Some(&gdk_pixbuf::Pixbuf::new_from_xpm_data(&icon::ICON)));
win.add(&xv);
let v = Rc::new(RefCell::new(
Stuff{p:(0,0),s:(0,0),v:(0.0,0.0),c:None,
path:None, png:None,img:None, lx,ly, xh,xv, area,win}));
btn(&bo, v.clone(), open);
btn(&bq, v.clone(), save);
btn(&bs, v.clone(), save_as);
{
let vr = &mut *v.borrow_mut();
vr.update();
vr.area.connect_draw (cb(v.clone(), draw));
vr.win.connect_key_press_event (cb(v.clone(), key));
vr.win.connect_motion_notify_event (cb(v.clone(), drag));
vr.win.connect_button_release_event(cb(v.clone(), drop));
vr.win.connect_delete_event(|_, _| {gtk::main_quit(); Inhibit(false)});
vr.win.set_events((EventMask::KEY_PRESS_MASK |
EventMask::BUTTON1_MOTION_MASK |
EventMask::BUTTON3_MOTION_MASK |
EventMask::BUTTON_RELEASE_MASK).bits() as i32);
vr.win.show_all();
}
gtk::main();
Ok(())
}
// EOF

77
src/png.rs Normal file
View File

@ -0,0 +1,77 @@
use crate::{bin::*, crc::*};
use std::{fs::File, io::prelude::*};
#[derive(Clone)]
#[derive(Debug)]
pub struct PngChunk
{
pub typ: Ident,
pub dat: Vec<u8>,
}
#[derive(Clone)]
#[derive(Debug)]
pub struct PngFile
{
pub chnk: Vec<PngChunk>,
}
impl PngFile
{
pub fn new(b: &[u8]) -> Result<PngFile, &'static str>
{
if &b[0..8] != b"\x89PNG\r\n\x1a\n" {return Err("invalid header")}
let mut chnk = Vec::new();
let mut p = 8;
loop
{
let len = b.c_u32b(p )? as usize;
let typ = b.c_iden(p+4 )?;
let crc = b.c_u32b(p+8+len)?;
if crc32(&b[p+4..p+8+len], 0) != crc
{return Err("invalid CRC in chunk")}
chnk.push(PngChunk{typ, dat: b[p+8..p+8+len].to_vec()});
if typ == *b"IEND" {break} else {p += len + 12}
}
Ok(PngFile{chnk})
}
pub fn write(&self, fp: &mut File) -> std::io::Result<()>
{
fp.write_all(b"\x89PNG\r\n\x1a\n")?;
for c in &self.chnk
{
fp.write_all(&d_u32b(c.dat.len() as u32))?;
fp.write_all(&c.typ)?;
fp.write_all(&c.dat)?;
fp.write_all(&d_u32b(crc32(&c.dat, crc32(&c.typ, 0))))?;
}
Ok(())
}
pub fn has_chunk(&self, t: Ident) -> bool
{
for c in &self.chnk {if c.typ == t {return true}}
false
}
pub fn find_chunk(&self, t: Ident) -> Option<usize>
{
for i in 0..self.chnk.len() {if self.chnk[i].typ == t {return Some(i)}}
None
}
pub fn chunk<Ret, F: Fn(&[u8]) -> Ret>(&self, t: Ident, f: F) -> Option<Ret>
{
for c in &self.chnk {if c.typ == t {return Some(f(&c.dat))}}
None
}
}
// EOF