Created
February 15, 2026 21:41
-
-
Save lubosz/a65c6daba314c50ef124a8ce6baabee4 to your computer and use it in GitHub Desktop.
Rust port of minimalized heif_view.cc from libheif examples.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| extern crate sdl3; | |
| use std::env; | |
| use libheif_rs::{HeifContext, DecodingOptions, ColorSpace, Chroma, HeifErrorCode, HeifError}; | |
| fn main() -> Result<(), ()> { | |
| let args: Vec<String> = env::args().collect(); | |
| if args.len() < 2 { | |
| eprintln!("Usage: {} <input_filename>", args[0]); | |
| return Err(()); | |
| } | |
| let ctx = HeifContext::read_from_file(&args[1]).expect("Could not load image."); | |
| let sdl_context = sdl3::init().expect("Failed to init sdl context."); | |
| let video_subsystem = sdl_context.video().expect("Failed to init video subsystem."); | |
| if !ctx.has_sequence() { | |
| eprintln!("File contains no image sequence"); | |
| return Err(()); | |
| } | |
| // Get visual track | |
| let track = ctx.track(0); | |
| let image_resolution = track.image_resolution().expect("Failed to get image resoltion."); | |
| // Reduce image size to a multiple of 8 | |
| let w = (image_resolution.width & !7) as u32; | |
| let h = (image_resolution.height & !7) as u32; | |
| let window = video_subsystem | |
| .window("heif-view", w, h) | |
| .build() | |
| .map_err(|e| e.to_string()).unwrap(); | |
| let mut canvas = window.into_canvas(); | |
| let texture_creator = canvas.texture_creator(); | |
| let mut texture = texture_creator.create_texture(sdl3::pixels::PixelFormat::YV12, sdl3::render::TextureAccess::Streaming, w, h).unwrap(); | |
| // Decoding loop | |
| let start_time = sdl3::timer::ticks(); | |
| let mut next_frame_pts: u64 = 0; | |
| loop { | |
| let mut decoding_options = DecodingOptions::new().unwrap(); | |
| decoding_options.set_convert_hdr_to_8bit(true); | |
| match track.decode_next_image(ColorSpace::YCbCr(Chroma::C420), Some(decoding_options)) { | |
| Ok(image) => { | |
| // Wait for image presentation time | |
| let duration_ms = (image.duration() as u64) * 1000 / (track.timescale() as u64); | |
| let now_time = sdl3::timer::ticks(); | |
| let elapsed_time = now_time - start_time; | |
| if elapsed_time < next_frame_pts { | |
| sdl3::timer::delay((next_frame_pts - elapsed_time) as u32); | |
| } | |
| next_frame_pts += duration_ms; | |
| // Display image | |
| let planes = image.planes(); | |
| let p_y = planes.y.unwrap(); | |
| let p_cb = planes.cb.unwrap(); | |
| let p_cr = planes.cr.unwrap(); | |
| texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { | |
| let rect_w = w as usize; | |
| let rect_h = h as usize; | |
| if p_y.stride == pitch && p_cb.stride == pitch / 2 { | |
| // Fast copy | |
| let y_size = rect_w * rect_h; | |
| let chroma_size = y_size / 4; | |
| buffer[..y_size].copy_from_slice(&p_y.data[..y_size]); | |
| let cr_start = y_size; | |
| let cr_end = cr_start + chroma_size; | |
| buffer[cr_start..cr_end].copy_from_slice(&p_cr.data[..chroma_size]); | |
| let cb_start = cr_end; | |
| let cb_end = cb_start + chroma_size; | |
| buffer[cb_start..cb_end].copy_from_slice(&p_cb.data[..chroma_size]); | |
| } else { | |
| // Copy line by line because sizes are different | |
| let mut offset = 0; | |
| for y in 0..rect_h { | |
| let src_start = y * p_y.stride; | |
| let dest_start = offset; | |
| buffer[dest_start..dest_start + rect_w].copy_from_slice(&p_y.data[src_start..src_start + rect_w]); | |
| offset += pitch; | |
| } | |
| let chroma_w = rect_w / 2; | |
| let chroma_h = rect_h / 2; | |
| let chroma_pitch = pitch / 2; | |
| for y in 0..chroma_h { | |
| let src_start = y * p_cr.stride; | |
| let dest_start = offset; | |
| buffer[dest_start..dest_start + chroma_w].copy_from_slice(&p_cr.data[src_start..src_start + chroma_w]); | |
| offset += chroma_pitch; | |
| } | |
| for y in 0..chroma_h { | |
| let src_start = y * p_cb.stride; | |
| let dest_start = offset; | |
| buffer[dest_start..dest_start + chroma_w].copy_from_slice(&p_cb.data[src_start..src_start + chroma_w]); | |
| offset += chroma_pitch; | |
| } | |
| } | |
| }).expect("Failed to write to locked texture."); | |
| canvas.copy(&texture, None, None).expect("Failed to copy texture to canvas."); | |
| canvas.present(); | |
| } | |
| Err(HeifError { code: HeifErrorCode::EndOfSequence, .. }) => { | |
| break; | |
| } | |
| Err(e) => { | |
| println!("Unexpected error occurred: {:?}", e); | |
| break; | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment