initial commit
This commit is contained in:
commit
82ae18ce43
7 changed files with 2836 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
2427
Cargo.lock
generated
Normal file
2427
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "image_viewer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.98"
|
||||
image = "0.25.6"
|
||||
softbuffer = { version = "0.4.6", default-features = false, features = ["wayland", "wayland-dlopen", "kms"] }
|
||||
winit = { version = "0.30.9", default-features = false, features = ["wayland", "wayland-dlopen", "rwh_06"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 2
|
||||
debug = false
|
||||
incremental = false
|
||||
lto = true # Enable link-time optimization
|
||||
codegen-units = 1 # Reduce number of codegen units to increase optimizations
|
||||
panic = 'abort' # Abort on panic
|
||||
strip = true # Strip symbols from binary*
|
||||
|
||||
[profile.dev]
|
||||
debug = true
|
||||
opt-level = 0
|
||||
|
||||
[features]
|
||||
gpu = []
|
||||
96
flake.lock
generated
Normal file
96
flake.lock
generated
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1762111121,
|
||||
"narHash": "sha256-4vhDuZ7OZaZmKKrnDpxLZZpGIJvAeMtK6FKLJYUtAdw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b3d51a0365f6695e7dd5cdf3e180604530ed33b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1744536153,
|
||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762396738,
|
||||
"narHash": "sha256-BarSecuxtzp1boERdABLkkoxQTi6s/V33lJwUbWLrLY=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "c63598992afd54d215d54f2b764adc0484c2b159",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
49
flake.nix
Normal file
49
flake.nix
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
description = "i dont know what I'm doing";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
nixpkgs,
|
||||
rust-overlay,
|
||||
flake-utils,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
overlays = [ (import rust-overlay) ];
|
||||
pkgs = import nixpkgs { inherit system overlays; };
|
||||
in
|
||||
with pkgs;
|
||||
{
|
||||
devShells.default = mkShell rec {
|
||||
|
||||
packages = [
|
||||
cargo-bloat
|
||||
upx
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
rust-bin.stable.latest.default
|
||||
libxkbcommon
|
||||
wayland
|
||||
|
||||
vulkan-loader
|
||||
];
|
||||
|
||||
LD_LIBRARY_PATH = "${lib.makeLibraryPath buildInputs}";
|
||||
};
|
||||
|
||||
packages = {
|
||||
image_viewer = pkgs.callPackage ./package.nix { };
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
33
package.nix
Normal file
33
package.nix
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
pkg-config,
|
||||
libxkbcommon,
|
||||
wayland,
|
||||
vulkan-loader,
|
||||
}:
|
||||
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "image_viewer";
|
||||
version = "0.0.1";
|
||||
|
||||
src = lib.fileset.toSource {
|
||||
root = ./.;
|
||||
fileset = ./.;
|
||||
};
|
||||
|
||||
strictDeps = true;
|
||||
|
||||
cargoDeps = rustPlatform.importCargoLock { lockFile = ./Cargo.lock; };
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
libxkbcommon
|
||||
wayland
|
||||
vulkan-loader
|
||||
];
|
||||
|
||||
}
|
||||
204
src/main.rs
Normal file
204
src/main.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Rc;
|
||||
use std::{env, time::*};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use image::codecs::gif::GifDecoder;
|
||||
use image::{AnimationDecoder, Delay, Frame, RgbImage};
|
||||
use image::{DynamicImage, ImageFormat, ImageReader, imageops::FilterType};
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
use softbuffer::Surface;
|
||||
|
||||
#[derive(Default)]
|
||||
struct App {
|
||||
window: Option<Rc<Window>>,
|
||||
surface: Option<Surface<OwnedDisplayHandle, Rc<Window>>>,
|
||||
image: DynamicImage,
|
||||
resized_image: RgbImage,
|
||||
|
||||
// animated images stuff
|
||||
animation: Option<Animation>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Animation {
|
||||
frames: Vec<Frame>,
|
||||
resized_frames: Vec<Option<RgbImage>>,
|
||||
frame_dur: Vec<Delay>,
|
||||
//current frame
|
||||
frame: usize,
|
||||
last_update: Option<Instant>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for App {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let window_attributes = Window::default_attributes().with_title("A fantastic window!");
|
||||
let window = Rc::new(event_loop.create_window(window_attributes).unwrap());
|
||||
let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
let surface = Surface::new(&context, window.clone()).unwrap();
|
||||
|
||||
let size = window.inner_size();
|
||||
|
||||
self.resized_image = self
|
||||
.image
|
||||
.resize(size.width, size.height, FilterType::Nearest)
|
||||
.to_rgb8();
|
||||
|
||||
if let Some(anim) = self.animation.as_mut() {
|
||||
anim.frame_dur = anim.frames.iter().map(|frame| frame.delay()).collect();
|
||||
anim.last_update = Some(Instant::now());
|
||||
anim.resized_frames = vec![None; anim.frames.len()];
|
||||
}
|
||||
|
||||
self.surface = Some(surface);
|
||||
self.window = Some(window);
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Close was requested; stopping");
|
||||
event_loop.exit();
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
let window = self
|
||||
.window
|
||||
.as_ref()
|
||||
.expect("redraw request without a window");
|
||||
let surface = self
|
||||
.surface
|
||||
.as_mut()
|
||||
.expect("redraw request without a surface");
|
||||
|
||||
let size = window.inner_size();
|
||||
|
||||
if let Some(anim) = self.animation.as_mut() {
|
||||
let curr_frame = anim.frame;
|
||||
match anim.resized_frames[curr_frame].as_ref() {
|
||||
Some(frame) => {
|
||||
self.resized_image = frame.clone();
|
||||
}
|
||||
None => {
|
||||
let original_buffer = anim.frames[curr_frame].buffer().clone();
|
||||
let resized_frame = DynamicImage::ImageRgba8(original_buffer)
|
||||
.resize(size.width, size.height, FilterType::Nearest)
|
||||
.into_rgb8();
|
||||
anim.resized_frames[curr_frame] = Some(resized_frame);
|
||||
self.resized_image = anim.resized_frames[curr_frame].clone().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut buffer = surface.buffer_mut().unwrap();
|
||||
|
||||
let x_offset = ((size.width - self.resized_image.width()) / 2) as usize;
|
||||
let y_offset = ((size.height - self.resized_image.height()) / 2) as usize;
|
||||
|
||||
for (y, row) in buffer.chunks_exact_mut(size.width as usize).enumerate() {
|
||||
if y < y_offset || y >= y_offset + self.resized_image.height() as usize {
|
||||
row.fill(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (x, buf_pix) in row.iter_mut().enumerate() {
|
||||
if x < x_offset || x >= x_offset + self.resized_image.width() as usize {
|
||||
*buf_pix = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
let img_pix = self
|
||||
.resized_image
|
||||
.get_pixel((x - x_offset) as u32, (y - y_offset) as u32);
|
||||
|
||||
*buf_pix =
|
||||
u32::from_be_bytes([0, img_pix.0[0], img_pix.0[1], img_pix.0[2]]);
|
||||
}
|
||||
}
|
||||
|
||||
window.pre_present_notify();
|
||||
|
||||
buffer.present().unwrap();
|
||||
|
||||
if let Some(anim) = self.animation.as_mut() {
|
||||
if anim.last_update.unwrap().elapsed() > anim.frame_dur[anim.frame].into() {
|
||||
anim.last_update = Some(Instant::now());
|
||||
anim.frame += 1;
|
||||
if anim.frame >= anim.frames.len() {
|
||||
anim.frame = 0;
|
||||
}
|
||||
}
|
||||
//TODO don't fucking spam redraws
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(new_size) => {
|
||||
let surface = self
|
||||
.surface
|
||||
.as_mut()
|
||||
.expect("resize request without a surface");
|
||||
|
||||
surface
|
||||
.resize(
|
||||
NonZeroU32::new(new_size.width).unwrap(),
|
||||
NonZeroU32::new(new_size.height).unwrap(),
|
||||
)
|
||||
.expect("surface resize gone wrong");
|
||||
|
||||
if let Some(anim) = self.animation.as_mut() {
|
||||
anim.resized_frames = vec![None; anim.frames.len()];
|
||||
} else {
|
||||
self.resized_image = self
|
||||
.image
|
||||
.resize(new_size.width, new_size.height, FilterType::Nearest)
|
||||
.to_rgb8();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let event_loop = EventLoop::new().context("Failed to create event loop")?;
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() != 2 {
|
||||
println!("No file provided!\n");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut app = App::default();
|
||||
let image = ImageReader::open(args[1].clone())
|
||||
.context(format!("Failed to open image at {}", args[1]))?;
|
||||
match image.format().unwrap() {
|
||||
ImageFormat::Gif => {
|
||||
let mut animation = Animation::default();
|
||||
let file = BufReader::new(
|
||||
File::open(args[1].clone())
|
||||
.context(format!("Failed to open GIF file at {}", args[1]))?,
|
||||
);
|
||||
let decoder = GifDecoder::new(file).context("Failed to create GIF decoder")?;
|
||||
let frames = decoder
|
||||
.into_frames()
|
||||
.collect_frames()
|
||||
.context("Failed to grab GIF frames")?;
|
||||
animation.frames = frames;
|
||||
|
||||
app.animation = Some(animation);
|
||||
}
|
||||
_ => {
|
||||
app.image = image.decode().context("Failed to decode image")?;
|
||||
}
|
||||
};
|
||||
event_loop.run_app(&mut app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue