initial commit
commit
f193f4fd30
|
@ -0,0 +1,4 @@
|
||||||
|
*.png
|
||||||
|
*.swp
|
||||||
|
target
|
||||||
|
Cargo.lock
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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: >k::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: >k::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
|
|
@ -0,0 +1,38 @@
|
||||||
|
pub static ICON: [&str; 35] = [
|
||||||
|
"32 32 2 1 ",
|
||||||
|
" c black",
|
||||||
|
". c white",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ... . . .. ... .. ",
|
||||||
|
" . . .. . . . . . . ",
|
||||||
|
" . . . . . . . .... ",
|
||||||
|
" . . . . . . . . ",
|
||||||
|
" ... . .. ... ... ",
|
||||||
|
" . . ",
|
||||||
|
" ... . ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" "];
|
||||||
|
|
||||||
|
// EOF
|
|
@ -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: >k::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
|
|
@ -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
|
Reference in New Issue