feat: implement crop functionality with self-contained widget
- Inspired by cosmic-viewer's crop implementation (https://codeberg.org/bhh32/cosmic-viewer) - Add crop support for all document types (Raster, Vector, Portable)
This commit is contained in:
parent
9399a008c4
commit
3cf99ad19d
20 changed files with 1042 additions and 103 deletions
|
|
@ -42,14 +42,14 @@ fn cache_key(file_path: &Path, page: usize) -> Option<String> {
|
|||
hasher.update(page.to_le_bytes());
|
||||
|
||||
let hash = hasher.finalize();
|
||||
Some(format!("{:x}", hash))
|
||||
Some(format!("{hash:x}"))
|
||||
}
|
||||
|
||||
/// Get the full path for a cached thumbnail.
|
||||
fn thumbnail_path(file_path: &Path, page: usize) -> Option<PathBuf> {
|
||||
let dir = cache_dir()?;
|
||||
let key = cache_key(file_path, page)?;
|
||||
Some(dir.join(format!("{}.{}", key, THUMBNAIL_EXT)))
|
||||
Some(dir.join(format!("{key}.{THUMBNAIL_EXT}")))
|
||||
}
|
||||
|
||||
/// Load a thumbnail from disk cache.
|
||||
|
|
@ -81,7 +81,7 @@ pub fn load_thumbnail(file_path: &Path, page: usize) -> Option<ImageHandle> {
|
|||
pub fn save_thumbnail(file_path: &Path, page: usize, image: &DynamicImage) -> Option<()> {
|
||||
let dir = ensure_cache_dir()?;
|
||||
let key = cache_key(file_path, page)?;
|
||||
let cache_path = dir.join(format!("{}.{}", key, THUMBNAIL_EXT));
|
||||
let cache_path = dir.join(format!("{key}.{THUMBNAIL_EXT}"));
|
||||
|
||||
log::debug!(
|
||||
"Saving thumbnail to cache: file={}, page={}, path={}",
|
||||
|
|
@ -98,7 +98,7 @@ pub fn save_thumbnail(file_path: &Path, page: usize, image: &DynamicImage) -> Op
|
|||
image::ImageFormat::Png,
|
||||
);
|
||||
match res {
|
||||
Ok(_) => {
|
||||
Ok(()) => {
|
||||
log::debug!(
|
||||
"Thumbnail cached successfully: file={} page={}",
|
||||
file_path.display(),
|
||||
|
|
@ -121,17 +121,16 @@ pub fn save_thumbnail(file_path: &Path, page: usize, image: &DynamicImage) -> Op
|
|||
/// Check if a thumbnail exists in cache.
|
||||
#[allow(dead_code)]
|
||||
pub fn has_thumbnail(file_path: &Path, page: usize) -> bool {
|
||||
thumbnail_path(file_path, page)
|
||||
.map(|p| p.exists())
|
||||
.unwrap_or(false)
|
||||
thumbnail_path(file_path, page).is_some_and(|p| p.exists())
|
||||
}
|
||||
|
||||
/// Clear all cached thumbnails.
|
||||
#[allow(dead_code)]
|
||||
pub fn clear_cache() -> std::io::Result<()> {
|
||||
if let Some(dir) = cache_dir()
|
||||
&& dir.exists() {
|
||||
fs::remove_dir_all(&dir)?;
|
||||
}
|
||||
&& dir.exists()
|
||||
{
|
||||
fs::remove_dir_all(&dir)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use crate::app::model::{AppModel, ViewMode};
|
|||
/// based on enabled codecs (e.g. default-formats).
|
||||
pub fn open_document(path: &Path) -> anyhow::Result<DocumentContent> {
|
||||
let kind = DocumentKind::from_path(path)
|
||||
.ok_or_else(|| anyhow!("Unsupported document type: {:?}", path))?;
|
||||
.ok_or_else(|| anyhow!("Unsupported document type: {}", path.display()))?;
|
||||
|
||||
let content = match kind {
|
||||
DocumentKind::Raster => {
|
||||
|
|
@ -46,11 +46,11 @@ pub fn open_document(path: &Path) -> anyhow::Result<DocumentContent> {
|
|||
/// If `path` is a directory, this will collect supported documents inside it,
|
||||
/// open the first one, and initialize navigation state. If it is a file, the
|
||||
/// file is opened directly and the surrounding folder is scanned.
|
||||
pub fn open_initial_path(model: &mut AppModel, path: PathBuf) {
|
||||
pub fn open_initial_path(model: &mut AppModel, path: &PathBuf) {
|
||||
if path.is_dir() {
|
||||
open_from_directory(model, &path);
|
||||
open_from_directory(model, path);
|
||||
} else {
|
||||
open_single_file(model, &path);
|
||||
open_single_file(model, path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +80,10 @@ pub fn open_single_file(model: &mut AppModel, path: &Path) {
|
|||
|
||||
// Refresh folder listing based on parent directory.
|
||||
if model.document.is_some()
|
||||
&& let Some(parent) = path.parent() {
|
||||
refresh_folder_entries(model, parent, path);
|
||||
}
|
||||
&& let Some(parent) = path.parent()
|
||||
{
|
||||
refresh_folder_entries(model, parent, path);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a document into the model, resetting view state.
|
||||
|
|
@ -200,3 +201,51 @@ pub fn file_size(path: &Path) -> u64 {
|
|||
pub fn read_file_bytes(path: &Path) -> Option<Vec<u8>> {
|
||||
fs::read(path).ok()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Crop operations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Save a cropped version of the document with coordinates in filename.
|
||||
///
|
||||
/// Format: "original_NAME_X_Y.EXT"
|
||||
/// Example: "image.png" → "image_100_200.png"
|
||||
pub fn save_crop_as(
|
||||
doc: &DocumentContent,
|
||||
original_path: &Path,
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<PathBuf, String> {
|
||||
let stem = original_path
|
||||
.file_stem()
|
||||
.ok_or_else(|| "Invalid path".to_string())?
|
||||
.to_string_lossy();
|
||||
let ext = original_path
|
||||
.extension()
|
||||
.ok_or_else(|| "No extension".to_string())?
|
||||
.to_string_lossy();
|
||||
|
||||
let new_filename = format!("{stem}_{x}_{y}");
|
||||
let new_path = original_path
|
||||
.with_file_name(&new_filename)
|
||||
.with_extension(ext.as_ref());
|
||||
|
||||
match doc {
|
||||
DocumentContent::Raster(raster_doc) => {
|
||||
let cropped_image = raster_doc
|
||||
.crop_to_image(x, y, width, height)
|
||||
.map_err(|e| e.to_string())?;
|
||||
cropped_image.save(&new_path).map_err(|e| e.to_string())?;
|
||||
}
|
||||
DocumentContent::Vector(_) => {
|
||||
return Err("Crop not supported for vector documents".to_string());
|
||||
}
|
||||
DocumentContent::Portable(_) => {
|
||||
return Err("Crop not supported for PDF documents".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,14 +38,19 @@ impl BasicMeta {
|
|||
const MB: u64 = KB * 1024;
|
||||
const GB: u64 = MB * 1024;
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
if self.file_size >= GB {
|
||||
format!("{:.2} GB", self.file_size as f64 / GB as f64)
|
||||
let size_gb = self.file_size as f64 / GB as f64;
|
||||
format!("{size_gb:.2} GB")
|
||||
} else if self.file_size >= MB {
|
||||
format!("{:.2} MB", self.file_size as f64 / MB as f64)
|
||||
let size_mb = self.file_size as f64 / MB as f64;
|
||||
format!("{size_mb:.2} MB")
|
||||
} else if self.file_size >= KB {
|
||||
format!("{:.1} KB", self.file_size as f64 / KB as f64)
|
||||
let size_kb = self.file_size as f64 / KB as f64;
|
||||
format!("{size_kb:.1} KB")
|
||||
} else {
|
||||
format!("{} B", self.file_size)
|
||||
let size = self.file_size;
|
||||
format!("{size} B")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +82,7 @@ impl ExifMeta {
|
|||
if model.starts_with(make) {
|
||||
Some(model.clone())
|
||||
} else {
|
||||
Some(format!("{} {}", make, model))
|
||||
Some(format!("{make} {model}"))
|
||||
}
|
||||
}
|
||||
(Some(make), None) => Some(make.clone()),
|
||||
|
|
@ -89,7 +94,7 @@ impl ExifMeta {
|
|||
/// Format GPS coordinates for display.
|
||||
pub fn gps_display(&self) -> Option<String> {
|
||||
match (self.gps_latitude, self.gps_longitude) {
|
||||
(Some(lat), Some(lon)) => Some(format!("{:.5}, {:.5}", lat, lon)),
|
||||
(Some(lat), Some(lon)) => Some(format!("{lat:.5}, {lon:.5}")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -165,9 +170,10 @@ fn extract_exif_from_bytes(data: &[u8]) -> Option<ExifMeta> {
|
|||
}
|
||||
if let Some(field) = exif.get_field(Tag::PhotographicSensitivity, In::PRIMARY)
|
||||
&& let Value::Short(ref vals) = field.value
|
||||
&& let Some(&iso) = vals.first() {
|
||||
meta.iso = Some(iso as u32);
|
||||
}
|
||||
&& let Some(&iso) = vals.first()
|
||||
{
|
||||
meta.iso = Some(u32::from(iso));
|
||||
}
|
||||
if let Some(field) = exif.get_field(Tag::FocalLength, In::PRIMARY) {
|
||||
meta.focal_length = Some(field.display_value().to_string());
|
||||
}
|
||||
|
|
@ -210,7 +216,10 @@ fn extract_gps_coord(exif: &exif::Exif, coord_tag: Tag, ref_tag: Tag) -> Option<
|
|||
|
||||
/// Determine color type string from DynamicImage.
|
||||
fn color_type_string(img: &DynamicImage) -> String {
|
||||
use image::DynamicImage::*;
|
||||
use image::DynamicImage::{
|
||||
ImageLuma8, ImageLumaA8, ImageRgb8, ImageRgba8, ImageLuma16, ImageLumaA16, ImageRgb16,
|
||||
ImageRgba16, ImageRgb32F, ImageRgba32F,
|
||||
};
|
||||
match img {
|
||||
ImageLuma8(_) => "Grayscale 8-bit".to_string(),
|
||||
ImageLumaA8(_) => "Grayscale+Alpha 8-bit".to_string(),
|
||||
|
|
@ -230,8 +239,7 @@ fn color_type_string(img: &DynamicImage) -> String {
|
|||
fn format_from_extension(path: &Path) -> String {
|
||||
path.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.to_uppercase())
|
||||
.unwrap_or_else(|| "Unknown".to_string())
|
||||
.map_or_else(|| "Unknown".to_string(), str::to_uppercase)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -259,7 +267,7 @@ pub fn build_vector_meta(path: &Path, width: u32, height: u32) -> DocumentMeta {
|
|||
|
||||
/// Build metadata for a portable document.
|
||||
pub fn build_portable_meta(path: &Path, width: u32, height: u32, page_count: u32) -> DocumentMeta {
|
||||
let format = format!("PDF ({} pages)", page_count);
|
||||
let format = format!("PDF ({page_count} pages)");
|
||||
let basic = extract_basic_meta(path, width, height, &format, "Rendered".to_string());
|
||||
|
||||
DocumentMeta { basic, exif: None }
|
||||
|
|
|
|||
|
|
@ -6,18 +6,26 @@
|
|||
pub mod cache;
|
||||
pub mod file;
|
||||
pub mod meta;
|
||||
pub mod portable;
|
||||
pub mod raster;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(feature = "portable")]
|
||||
pub mod portable;
|
||||
#[cfg(feature = "image")]
|
||||
pub mod raster;
|
||||
#[cfg(feature = "vector")]
|
||||
pub mod vector;
|
||||
|
||||
use cosmic::iced_renderer::graphics::image::image_rs::ImageFormat as CosmicImageFormat;
|
||||
#[cfg(feature = "image")]
|
||||
use image::GenericImageView;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "portable")]
|
||||
use self::portable::PortableDocument;
|
||||
#[cfg(feature = "image")]
|
||||
use self::raster::RasterDocument;
|
||||
#[cfg(feature = "vector")]
|
||||
use self::vector::VectorDocument;
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -96,8 +104,6 @@ pub struct TransformState {
|
|||
pub flip_v: bool,
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Output of a render operation.
|
||||
///
|
||||
/// Used as return type for the `Renderable::render()` trait method.
|
||||
|
|
@ -360,6 +366,18 @@ impl DocumentContent {
|
|||
self.flip(FlipDirection::Vertical);
|
||||
}
|
||||
|
||||
/// Crop the document to the specified rectangle.
|
||||
///
|
||||
/// Only supported for raster images. Returns an error for vector/PDF documents.
|
||||
/// Coordinates are in pixels relative to current image dimensions.
|
||||
pub fn crop(&mut self, x: u32, y: u32, width: u32, height: u32) -> DocResult<()> {
|
||||
match self {
|
||||
Self::Raster(doc) => doc.crop(x, y, width, height),
|
||||
Self::Vector(_) => Err(anyhow::anyhow!("Crop not supported for vector documents")),
|
||||
Self::Portable(_) => Err(anyhow::anyhow!("Crop not supported for PDF documents")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get document kind.
|
||||
///
|
||||
/// Reserved for future use (format-specific optimizations, statistics).
|
||||
|
|
@ -446,7 +464,9 @@ impl DocumentContent {
|
|||
/// Currently unused - thumbnails are generated incrementally via `generate_thumbnail_page()`.
|
||||
#[allow(dead_code)]
|
||||
pub fn generate_thumbnails(&mut self) {
|
||||
if let Self::Portable(doc) = self { doc.generate_all_thumbnails() }
|
||||
if let Self::Portable(doc) = self {
|
||||
doc.generate_all_thumbnails()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current image handle for display.
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl PortableDocument {
|
|||
/// Open a PDF document and render the first page.
|
||||
pub fn open(path: &Path) -> anyhow::Result<Self> {
|
||||
let document = PopplerDocument::new_from_file(path, None)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse PDF: {}", e))?;
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse PDF: {e}"))?;
|
||||
|
||||
let num_pages = document.get_n_pages();
|
||||
if num_pages == 0 {
|
||||
|
|
@ -107,14 +107,13 @@ impl PortableDocument {
|
|||
return handle;
|
||||
}
|
||||
|
||||
match Self::render_page_at_scale(&self.document, page, Rotation::None, PDF_THUMBNAIL_SIZE)
|
||||
{
|
||||
match Self::render_page_at_scale(&self.document, page, Rotation::None, PDF_THUMBNAIL_SIZE) {
|
||||
Ok(img) => {
|
||||
let _ = cache::save_thumbnail(&self.source_path, page, &img);
|
||||
super::create_image_handle_from_image(&img)
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to generate thumbnail for page {}: {}", page, e);
|
||||
log::warn!("Failed to generate thumbnail for page {page}: {e}");
|
||||
ImageHandle::from_rgba(1, 1, vec![0, 0, 0, 0])
|
||||
}
|
||||
}
|
||||
|
|
@ -138,7 +137,7 @@ impl PortableDocument {
|
|||
) -> anyhow::Result<DynamicImage> {
|
||||
let page = document
|
||||
.get_page(page_index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get page {}", page_index))?;
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get page {page_index}"))?;
|
||||
|
||||
let (page_width, page_height) = page.get_size();
|
||||
let rotation_degrees = rotation.to_degrees();
|
||||
|
|
@ -155,10 +154,10 @@ impl PortableDocument {
|
|||
let scaled_height = (height * scale) as i32;
|
||||
|
||||
let surface = ImageSurface::create(Format::ARgb32, scaled_width, scaled_height)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create Cairo surface: {}", e))?;
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create Cairo surface: {e}"))?;
|
||||
|
||||
let context = Context::new(&surface)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create Cairo context: {}", e))?;
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create Cairo context: {e}"))?;
|
||||
|
||||
// Fill with white background.
|
||||
context.set_source_rgb(1.0, 1.0, 1.0);
|
||||
|
|
@ -182,13 +181,13 @@ impl PortableDocument {
|
|||
let mut png_data: Vec<u8> = Vec::new();
|
||||
surface
|
||||
.write_to_png(&mut png_data)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to write PNG: {}", e))?;
|
||||
.map_err(|e| anyhow::anyhow!("Failed to write PNG: {e}"))?;
|
||||
|
||||
let image = ImageReader::new(Cursor::new(png_data))
|
||||
.with_guessed_format()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to read PNG format: {}", e))?
|
||||
.map_err(|e| anyhow::anyhow!("Failed to read PNG format: {e}"))?
|
||||
.decode()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to decode PNG: {}", e))?;
|
||||
.map_err(|e| anyhow::anyhow!("Failed to decode PNG: {e}"))?;
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
|
|
@ -208,7 +207,7 @@ impl PortableDocument {
|
|||
self.refresh_handle();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to render PDF page: {}", e);
|
||||
log::error!("Failed to render PDF page: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,54 @@ impl RasterDocument {
|
|||
pub fn extract_meta(&self, path: &Path) -> super::meta::DocumentMeta {
|
||||
super::meta::build_raster_meta(path, &self.document, self.native_width, self.native_height)
|
||||
}
|
||||
|
||||
/// Crop the image to the specified rectangle.
|
||||
///
|
||||
/// Coordinates are in pixels relative to the current image dimensions.
|
||||
/// Returns an error if the rectangle is out of bounds.
|
||||
pub fn crop(&mut self, x: u32, y: u32, width: u32, height: u32) -> DocResult<()> {
|
||||
let (img_width, img_height) = self.document.dimensions();
|
||||
|
||||
if x + width > img_width || y + height > img_height {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Crop rectangle out of bounds: {width}x{height} at ({x}, {y}) exceeds image size {img_width}x{img_height}"
|
||||
));
|
||||
}
|
||||
|
||||
let cropped = imageops::crop_imm(&self.document, x, y, width, height).to_image();
|
||||
self.document = DynamicImage::ImageRgba8(cropped);
|
||||
|
||||
self.native_width = width;
|
||||
self.native_height = height;
|
||||
|
||||
self.transform = TransformState::default();
|
||||
|
||||
self.refresh_handle();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Crop the image to the specified rectangle and return as DynamicImage.
|
||||
///
|
||||
/// This does NOT modify the document - it's used for exporting cropped images.
|
||||
pub fn crop_to_image(
|
||||
&self,
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> DocResult<DynamicImage> {
|
||||
let (img_width, img_height) = self.document.dimensions();
|
||||
|
||||
if x + width > img_width || y + height > img_height {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Crop rectangle out of bounds: {width}x{height} at ({x}, {y}) exceeds image size {img_width}x{img_height}"
|
||||
));
|
||||
}
|
||||
|
||||
let cropped = imageops::crop_imm(&self.document, x, y, width, height).to_image();
|
||||
Ok(DynamicImage::ImageRgba8(cropped))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -22,15 +22,12 @@ pub fn set_as_wallpaper(path: &Path) {
|
|||
}
|
||||
};
|
||||
|
||||
let path_str = match abs_path.to_str() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
log::error!("Invalid UTF-8 in path: {}", abs_path.display());
|
||||
return;
|
||||
}
|
||||
let Some(path_str) = abs_path.to_str() else {
|
||||
log::error!("Invalid UTF-8 in path: {}", abs_path.display());
|
||||
return;
|
||||
};
|
||||
|
||||
log::info!("Attempting to set wallpaper: {}", path_str);
|
||||
log::info!("Attempting to set wallpaper: {path_str}");
|
||||
|
||||
// Method 1: Try COSMIC Desktop (direct config file modification).
|
||||
if try_cosmic_wallpaper(path_str) {
|
||||
|
|
@ -69,23 +66,22 @@ fn try_cosmic_wallpaper(path_str: &str) -> bool {
|
|||
let config_content = format!(
|
||||
r#"(
|
||||
output: "all",
|
||||
source: Path("{}"),
|
||||
source: Path("{path_str}"),
|
||||
filter_by_theme: true,
|
||||
rotation_frequency: 300,
|
||||
filter_method: Lanczos,
|
||||
scaling_mode: Zoom,
|
||||
sampling_method: Alphanumeric,
|
||||
)"#,
|
||||
path_str
|
||||
)"#
|
||||
);
|
||||
|
||||
match std::fs::write(&cosmic_config, config_content) {
|
||||
Ok(_) => {
|
||||
Ok(()) => {
|
||||
log::info!("Wallpaper set via COSMIC config");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to write COSMIC config: {}", e);
|
||||
log::warn!("Failed to write COSMIC config: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -94,12 +90,12 @@ fn try_cosmic_wallpaper(path_str: &str) -> bool {
|
|||
/// Try setting wallpaper via wallpaper crate.
|
||||
fn try_wallpaper_crate(path_str: &str) -> bool {
|
||||
match wallpaper::set_from_path(path_str) {
|
||||
Ok(_) => {
|
||||
Ok(()) => {
|
||||
log::info!("Wallpaper set via wallpaper crate");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("wallpaper crate failed: {}", e);
|
||||
log::warn!("wallpaper crate failed: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +103,7 @@ fn try_wallpaper_crate(path_str: &str) -> bool {
|
|||
|
||||
/// Try setting wallpaper via GNOME gsettings.
|
||||
fn try_gsettings_wallpaper(path_str: &str) -> bool {
|
||||
let uri = format!("file://{}", path_str);
|
||||
let uri = format!("file://{path_str}");
|
||||
|
||||
let output = match std::process::Command::new("gsettings")
|
||||
.args(["set", "org.gnome.desktop.background", "picture-uri", &uri])
|
||||
|
|
@ -115,7 +111,7 @@ fn try_gsettings_wallpaper(path_str: &str) -> bool {
|
|||
{
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
log::warn!("gsettings command failed: {}", e);
|
||||
log::warn!("gsettings command failed: {e}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -145,15 +141,12 @@ fn try_gsettings_wallpaper(path_str: &str) -> bool {
|
|||
|
||||
/// Try setting wallpaper via feh.
|
||||
fn try_feh_wallpaper(path_str: &str) -> bool {
|
||||
let output = match std::process::Command::new("feh")
|
||||
let Ok(output) = std::process::Command::new("feh")
|
||||
.args(["--bg-scale", path_str])
|
||||
.output()
|
||||
{
|
||||
Ok(o) => o,
|
||||
Err(_) => {
|
||||
log::warn!("feh not available");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
log::warn!("feh not available");
|
||||
return false;
|
||||
};
|
||||
|
||||
if output.status.success() {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ impl VectorDocument {
|
|||
|
||||
// Render at native scale (1.0).
|
||||
let (rendered, width, height) =
|
||||
render_document(&document, native_width, native_height, 1.0, &transform)?;
|
||||
render_document(&document, native_width, native_height, 1.0, transform)?;
|
||||
let handle = super::create_image_handle_from_image(&rendered);
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -90,7 +90,7 @@ impl VectorDocument {
|
|||
self.native_width,
|
||||
self.native_height,
|
||||
scale,
|
||||
&self.transform,
|
||||
self.transform,
|
||||
) {
|
||||
Ok((rendered, width, height)) => {
|
||||
self.current_scale = scale;
|
||||
|
|
@ -101,7 +101,7 @@ impl VectorDocument {
|
|||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to re-render SVG at scale {}: {}", scale, e);
|
||||
log::error!("Failed to re-render SVG at scale {scale}: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ impl VectorDocument {
|
|||
self.native_width,
|
||||
self.native_height,
|
||||
self.current_scale,
|
||||
&self.transform,
|
||||
self.transform,
|
||||
) {
|
||||
self.rendered = rendered;
|
||||
self.width = width;
|
||||
|
|
@ -178,12 +178,12 @@ fn render_document(
|
|||
native_width: u32,
|
||||
native_height: u32,
|
||||
scale: f64,
|
||||
transform: &TransformState,
|
||||
transform: TransformState,
|
||||
) -> anyhow::Result<(DynamicImage, u32, u32)> {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
let width = (((native_width as f64) * scale).ceil() as u32).max(MIN_PIXMAP_SIZE);
|
||||
let width = ((f64::from(native_width) * scale).ceil() as u32).max(MIN_PIXMAP_SIZE);
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
let height = (((native_height as f64) * scale).ceil() as u32).max(MIN_PIXMAP_SIZE);
|
||||
let height = ((f64::from(native_height) * scale).ceil() as u32).max(MIN_PIXMAP_SIZE);
|
||||
|
||||
let mut pixmap =
|
||||
Pixmap::new(width, height).ok_or_else(|| anyhow::anyhow!("Failed to create pixmap"))?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue