Коммит df7d95f5 создал по автору Зарубин Виталий Викторович's avatar Зарубин Виталий Викторович
Просмотр файлов

[flutter_image_picker_aurora][feature] Implement image_picker plugin.

владелец 82a8de62
// SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
// SPDX-License-Identifier: BSD-3-Clause
use libc::c_char;
use models::request_model::RequestModel;
mod models;
#[no_mangle]
/// Main function
pub extern "C" fn resize_images(request: *const c_char) -> *mut c_char {
RequestModel::new(request).resize_images().into_raw()
}
// SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
// SPDX-License-Identifier: BSD-3-Clause
pub mod request_model;
pub mod response_model;
// SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
// SPDX-License-Identifier: BSD-3-Clause
use image::{imageops::FilterType, io::Reader as ImageReader};
use libc::c_char;
use serde::Deserialize;
use std::{
ffi::{CStr, CString},
fs::{self, Metadata},
path::{Path, PathBuf}, time::{SystemTime, UNIX_EPOCH},
};
use super::response_model::{FileModel, ResponseModel};
#[allow(dead_code)]
#[derive(Deserialize)]
pub struct RequestModel {
#[serde(alias = "maxWidth")]
max_height: Option<f64>,
#[serde(alias = "maxHeight")]
max_width: Option<f64>,
#[serde(alias = "imageQuality")]
image_quality: Option<u64>,
#[serde(alias = "filePaths")]
paths: Vec<String>,
}
impl RequestModel {
pub fn new(request: *const c_char) -> RequestModel {
let json = unsafe { CStr::from_ptr(request) }
.to_str()
.expect("error parse request");
serde_json::from_str(json).expect("error parse request")
}
/// Main resize
pub fn resize_images(&self) -> CString {
let mut state = true;
let mut files: Vec<FileModel> = vec![];
for path in &self.paths {
match Self::resize_image(path, &self) {
Ok(value) => files.push(value),
Err(_) => state = false,
}
if !state {
Self::error_clear(files.iter().map(|e| e.path.clone()).collect());
files.clear();
break;
}
}
ResponseModel { state, files }.to_json()
}
/// Resize images to min/max size
fn resize_image(
path: &String,
model: &RequestModel,
) -> Result<FileModel, Box<dyn std::error::Error>> {
let path = Path::new(path);
let metadata = fs::metadata(path)?;
if !metadata.is_file() {
Err("error load file")?
}
// Check if need resize
if model.max_width.is_none() && model.max_height.is_none() {
return Self::read_file(path, &metadata)
}
// Get data image
let image = ImageReader::open(path)?.decode()?;
let size = imagesize::size(path)?;
let width = model.max_width.unwrap_or_else(|| size.width as f64) as u32;
let height = model.max_height.unwrap_or_else(|| size.height as f64) as u32;
// Check if need resize
if size.width as u32 <= width && size.height as u32 <= height {
return Self::read_file(path, &metadata)
}
// Check if exist
let new_path = &Self::get_resize_path(path, width, height)?;
if new_path.exists() {
return Self::read_file(new_path, &metadata)
}
// Resize image
let filter = Self::filter_by_quality(model.image_quality);
let new_image = image.resize(width, height, filter);
// Save image new size
new_image.save(new_path)?;
Self::read_file(new_path, &metadata)
}
/// Read data file
fn read_file(
path: &Path,
metadata: &Metadata
) -> Result<FileModel, Box<dyn std::error::Error>> {
let file_name = match path.file_name() {
Some(value) => value.to_string_lossy().to_string(),
None => Err("error get name file")?,
};
let mime = match mime_guess::from_path(&path).first() {
Some(value) => value.to_string(),
None => Err("not found mime")?,
};
let last_modified = match metadata.modified() {
Ok(value) => value.duration_since(UNIX_EPOCH).unwrap().as_secs(),
Err(_) => SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
};
Ok(FileModel {
name: file_name,
path: path.to_string_lossy().to_string(),
mime,
length: metadata.len(),
last_modified,
})
}
/// Sampling Filters
/// https://docs.rs/image/latest/image/imageops/enum.FilterType.html
fn filter_by_quality(image_quality: Option<u64>) -> FilterType {
if let Some(q) = image_quality {
if q < 20 {
return FilterType::Nearest;
}
if q < 40 {
return FilterType::Triangle;
}
if q < 60 {
return FilterType::CatmullRom;
}
if q < 80 {
return FilterType::Gaussian;
}
return FilterType::Lanczos3;
}
return FilterType::Nearest;
}
/// Path for save resize image
fn get_resize_path(
path: &Path,
width: u32,
height: u32,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
let extension = match path.extension() {
Some(value) => value.to_string_lossy().to_string(),
None => Err("error get extension file")?,
};
let file_name = match path.file_name() {
Some(value) => value.to_string_lossy().replace(&format!(".{}", extension), ""),
None => Err("error get name file")?,
};
let dir = match path.parent() {
Some(value) => value.to_string_lossy().to_string(),
None => Err("error get dir file")?,
};
Ok(Path::new(&format!(
"{}/{}_scaled_{}x{}.{}",
dir,
file_name,
width.to_string(),
height.to_string(),
extension
))
.to_path_buf())
}
/// Remove temp files if resize error
fn error_clear(paths: Vec<String>) {
for path in paths {
let _ = fs::remove_file(path);
}
}
}
// SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
// SPDX-License-Identifier: BSD-3-Clause
use serde::Serialize;
use std::ffi::CString;
#[derive(Serialize)]
pub struct ResponseModel {
pub state: bool,
pub files: Vec<FileModel>,
}
#[derive(Serialize)]
pub struct FileModel {
pub name: String,
pub path: String,
pub mime: String,
pub length: u64,
pub last_modified: u64,
}
impl ResponseModel {
pub fn to_json(&self) -> CString {
let json = serde_json::to_string::<ResponseModel>(self).expect("error load model");
CString::new(json).unwrap()
}
}
## Application configuration file Changeln
## Version config: 0.0.2
## Comment tags by which they will be searched
## and groups by which they will be analyzed.
commits:
Bug: '[flutter_selector_aurora][bug]'
Change: '[flutter_selector_aurora][change]'
Feature: '[flutter_selector_aurora][feature]'
## Path to mako template
template: ~/.changeln/changeln.mako
## Regular expression to break the comment into groups
## to format the comment output in the changelog in the
## "regex" variable.
##
## The empty value is not used.
parse: ''
## Filter tags using regular expressions
##
## The empty value is not used.
filter: '^(?!aurora-flutter_selector_aurora).*$'
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать