Add circular thumbnail creation for Map view
This commit is contained in:
@@ -186,6 +186,8 @@ pub struct ThumbnailRequest {
|
||||
#[serde(default)]
|
||||
#[allow(dead_code)] // Part of API contract, may be used in future
|
||||
pub(crate) format: Option<ThumbnailFormat>,
|
||||
#[serde(default)]
|
||||
pub(crate) shape: Option<ThumbnailShape>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
@@ -196,6 +198,14 @@ pub enum ThumbnailFormat {
|
||||
Image,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
pub enum ThumbnailShape {
|
||||
#[serde(rename = "circle")]
|
||||
Circle,
|
||||
#[serde(rename = "square")]
|
||||
Square,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginRequest {
|
||||
pub username: String,
|
||||
|
||||
@@ -957,7 +957,6 @@ pub async fn get_gps_summary(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
req: web::Query<FilesRequest>,
|
||||
state: web::Data<AppState>,
|
||||
exif_dao: web::Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
use crate::data::{GpsPhotoSummary, GpsPhotosResponse};
|
||||
|
||||
79
src/main.rs
79
src/main.rs
@@ -116,6 +116,26 @@ async fn get_image(
|
||||
thumb_path.set_extension("gif");
|
||||
}
|
||||
|
||||
// Handle circular thumbnail request
|
||||
if req.shape == Some(ThumbnailShape::Circle) {
|
||||
match create_circular_thumbnail(&thumb_path, &thumbs).await {
|
||||
Ok(circular_path) => {
|
||||
if let Ok(file) = NamedFile::open(&circular_path) {
|
||||
span.set_status(Status::Ok);
|
||||
return file
|
||||
.use_etag(true)
|
||||
.use_last_modified(true)
|
||||
.prefer_utf8(true)
|
||||
.into_response(&request);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to create circular thumbnail: {:?}", e);
|
||||
// Fall through to serve square thumbnail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Thumbnail path: {:?}", thumb_path);
|
||||
if let Ok(file) = NamedFile::open(&thumb_path) {
|
||||
span.set_status(Status::Ok);
|
||||
@@ -153,6 +173,65 @@ fn is_video_file(path: &Path) -> bool {
|
||||
file_types::is_video_file(path)
|
||||
}
|
||||
|
||||
async fn create_circular_thumbnail(thumb_path: &Path, thumbs_dir: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
use image::{ImageBuffer, Rgba, GenericImageView};
|
||||
|
||||
// Create circular thumbnails directory
|
||||
let circular_dir = Path::new(thumbs_dir).join("_circular");
|
||||
|
||||
// Get relative path from thumbs_dir to create same structure
|
||||
let relative_to_thumbs = thumb_path.strip_prefix(thumbs_dir)?;
|
||||
let circular_path = circular_dir.join(relative_to_thumbs).with_extension("png");
|
||||
|
||||
// Check if circular thumbnail already exists
|
||||
if circular_path.exists() {
|
||||
return Ok(circular_path);
|
||||
}
|
||||
|
||||
// Create parent directory if needed
|
||||
if let Some(parent) = circular_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// Load the square thumbnail
|
||||
let img = image::open(thumb_path)?;
|
||||
let (width, height) = img.dimensions();
|
||||
|
||||
// Fixed output size for consistency
|
||||
let output_size = 80u32;
|
||||
let radius = output_size as f32 / 2.0;
|
||||
|
||||
// Calculate crop area to get square center of original image
|
||||
let crop_size = width.min(height);
|
||||
let crop_x = (width - crop_size) / 2;
|
||||
let crop_y = (height - crop_size) / 2;
|
||||
|
||||
// Create a new RGBA image with transparency
|
||||
let output = ImageBuffer::from_fn(output_size, output_size, |x, y| {
|
||||
let dx = x as f32 - radius;
|
||||
let dy = y as f32 - radius;
|
||||
let distance = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
if distance <= radius {
|
||||
// Inside circle - map to cropped source area
|
||||
// Scale from output coordinates to crop coordinates
|
||||
let scale = crop_size as f32 / output_size as f32;
|
||||
let src_x = crop_x + (x as f32 * scale) as u32;
|
||||
let src_y = crop_y + (y as f32 * scale) as u32;
|
||||
let pixel = img.get_pixel(src_x, src_y);
|
||||
Rgba([pixel[0], pixel[1], pixel[2], 255])
|
||||
} else {
|
||||
// Outside circle - transparent
|
||||
Rgba([0, 0, 0, 0])
|
||||
}
|
||||
});
|
||||
|
||||
// Save as PNG (supports transparency)
|
||||
output.save(&circular_path)?;
|
||||
|
||||
Ok(circular_path)
|
||||
}
|
||||
|
||||
#[get("/image/metadata")]
|
||||
async fn get_file_metadata(
|
||||
_: Claims,
|
||||
|
||||
Reference in New Issue
Block a user