add thumbnail dimension structure
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
7b0e830f4c
commit
4d42a29c51
7 changed files with 155 additions and 114 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -2975,7 +2975,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.10.1"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
|
@ -2997,7 +2997,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.10.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
@ -3009,7 +3009,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
|
@ -3032,7 +3032,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
|
@ -3062,7 +3062,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.28.1"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap 2.4.0",
|
||||
|
@ -3086,7 +3086,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
|
@ -3104,7 +3104,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.5"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror",
|
||||
|
@ -3113,7 +3113,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
@ -3123,7 +3123,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro-crate",
|
||||
|
@ -3138,7 +3138,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
@ -3150,7 +3150,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-server-util"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"headers",
|
||||
"http",
|
||||
|
@ -3163,7 +3163,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.15.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
|
@ -3179,7 +3179,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-state-res"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=25fbd64b968c5d5088c07750aaa4873e072831b0#25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=a0cc9a80dd5da700fb9b992b6f92cb6be4c27487#a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"js_int",
|
||||
|
|
|
@ -314,7 +314,7 @@ version = "0.1.2"
|
|||
[workspace.dependencies.ruma]
|
||||
git = "https://github.com/girlbossceo/ruwuma"
|
||||
#branch = "conduwuit-changes"
|
||||
rev = "25fbd64b968c5d5088c07750aaa4873e072831b0"
|
||||
rev = "a0cc9a80dd5da700fb9b992b6f92cb6be4c27487"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
|
|
|
@ -14,7 +14,7 @@ use ruma::{
|
|||
},
|
||||
Mxc,
|
||||
};
|
||||
use service::media::{FileMeta, MXC_LENGTH};
|
||||
use service::media::{Dim, FileMeta, MXC_LENGTH};
|
||||
|
||||
use crate::{Ruma, RumaResponse};
|
||||
|
||||
|
@ -326,22 +326,12 @@ pub(crate) async fn get_content_thumbnail_route(
|
|||
media_id: &body.media_id,
|
||||
};
|
||||
|
||||
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
|
||||
if let Some(FileMeta {
|
||||
content,
|
||||
content_type,
|
||||
content_disposition,
|
||||
}) = services
|
||||
.media
|
||||
.get_thumbnail(
|
||||
&mxc,
|
||||
body.width
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?,
|
||||
body.height
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?,
|
||||
)
|
||||
.await?
|
||||
}) = services.media.get_thumbnail(&mxc, &dim).await?
|
||||
{
|
||||
let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use conduit::{
|
|||
use database::{Database, Map};
|
||||
use ruma::{api::client::error::ErrorKind, http_headers::ContentDisposition, Mxc, OwnedMxcUri, UserId};
|
||||
|
||||
use super::preview::UrlPreviewData;
|
||||
use super::{preview::UrlPreviewData, thumbnail::Dim};
|
||||
|
||||
pub(crate) struct Data {
|
||||
mediaid_file: Arc<Map>,
|
||||
|
@ -33,8 +33,8 @@ impl Data {
|
|||
}
|
||||
|
||||
pub(super) fn create_file_metadata(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, width: u32, height: u32,
|
||||
content_disposition: Option<&ContentDisposition>, content_type: Option<&str>,
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, dim: &Dim, content_disposition: Option<&ContentDisposition>,
|
||||
content_type: Option<&str>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut key: Vec<u8> = Vec::new();
|
||||
key.extend_from_slice(b"mxc://");
|
||||
|
@ -42,8 +42,8 @@ impl Data {
|
|||
key.extend_from_slice(b"/");
|
||||
key.extend_from_slice(mxc.media_id.as_bytes());
|
||||
key.push(0xFF);
|
||||
key.extend_from_slice(&width.to_be_bytes());
|
||||
key.extend_from_slice(&height.to_be_bytes());
|
||||
key.extend_from_slice(&dim.width.to_be_bytes());
|
||||
key.extend_from_slice(&dim.height.to_be_bytes());
|
||||
key.push(0xFF);
|
||||
key.extend_from_slice(
|
||||
content_disposition
|
||||
|
@ -128,15 +128,15 @@ impl Data {
|
|||
Ok(keys)
|
||||
}
|
||||
|
||||
pub(super) fn search_file_metadata(&self, mxc: &Mxc<'_>, width: u32, height: u32) -> Result<Metadata> {
|
||||
pub(super) fn search_file_metadata(&self, mxc: &Mxc<'_>, dim: &Dim) -> Result<Metadata> {
|
||||
let mut prefix: Vec<u8> = Vec::new();
|
||||
prefix.extend_from_slice(b"mxc://");
|
||||
prefix.extend_from_slice(mxc.server_name.as_bytes());
|
||||
prefix.extend_from_slice(b"/");
|
||||
prefix.extend_from_slice(mxc.media_id.as_bytes());
|
||||
prefix.push(0xFF);
|
||||
prefix.extend_from_slice(&width.to_be_bytes());
|
||||
prefix.extend_from_slice(&height.to_be_bytes());
|
||||
prefix.extend_from_slice(&dim.width.to_be_bytes());
|
||||
prefix.extend_from_slice(&dim.height.to_be_bytes());
|
||||
prefix.push(0xFF);
|
||||
|
||||
let (key, _) = self
|
||||
|
|
|
@ -13,13 +13,14 @@ use conduit::{
|
|||
utils::{self, MutexMap},
|
||||
warn, Err, Result, Server,
|
||||
};
|
||||
use data::{Data, Metadata};
|
||||
use ruma::{http_headers::ContentDisposition, Mxc, OwnedMxcUri, UserId};
|
||||
use tokio::{
|
||||
fs,
|
||||
io::{AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
};
|
||||
|
||||
use self::data::{Data, Metadata};
|
||||
pub use self::thumbnail::Dim;
|
||||
use crate::{client, globals, sending, Dep};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -78,7 +79,7 @@ impl Service {
|
|||
// Width, Height = 0 if it's not a thumbnail
|
||||
let key = self
|
||||
.db
|
||||
.create_file_metadata(mxc, user, 0, 0, content_disposition, content_type)?;
|
||||
.create_file_metadata(mxc, user, &Dim::default(), content_disposition, content_type)?;
|
||||
|
||||
//TODO: Dangling metadata in database if creation fails
|
||||
let mut f = self.create_media_file(&key).await?;
|
||||
|
@ -141,7 +142,7 @@ impl Service {
|
|||
content_disposition,
|
||||
content_type,
|
||||
key,
|
||||
}) = self.db.search_file_metadata(mxc, 0, 0)
|
||||
}) = self.db.search_file_metadata(mxc, &Dim::default())
|
||||
{
|
||||
let mut content = Vec::new();
|
||||
let path = self.get_media_file(&key);
|
||||
|
@ -350,7 +351,7 @@ impl Service {
|
|||
#[inline]
|
||||
pub fn get_metadata(&self, mxc: &Mxc<'_>) -> Option<FileMeta> {
|
||||
self.db
|
||||
.search_file_metadata(mxc, 0, 0)
|
||||
.search_file_metadata(mxc, &Dim::default())
|
||||
.map(|metadata| FileMeta {
|
||||
content_disposition: metadata.content_disposition,
|
||||
content_type: metadata.content_type,
|
||||
|
|
|
@ -3,6 +3,8 @@ use std::time::Duration;
|
|||
use conduit::{debug_warn, err, implement, utils::content_disposition::make_content_disposition, Err, Error, Result};
|
||||
use ruma::{api::client::media, Mxc};
|
||||
|
||||
use super::Dim;
|
||||
|
||||
#[implement(super::Service)]
|
||||
#[allow(deprecated)]
|
||||
pub async fn fetch_remote_thumbnail_legacy(
|
||||
|
@ -33,20 +35,9 @@ pub async fn fetch_remote_thumbnail_legacy(
|
|||
)
|
||||
.await?;
|
||||
|
||||
self.upload_thumbnail(
|
||||
&mxc,
|
||||
None,
|
||||
None,
|
||||
reponse.content_type.as_deref(),
|
||||
body.width
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?,
|
||||
body.height
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?,
|
||||
&reponse.file,
|
||||
)
|
||||
.await?;
|
||||
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
|
||||
self.upload_thumbnail(&mxc, None, None, reponse.content_type.as_deref(), &dim, &reponse.file)
|
||||
.await?;
|
||||
|
||||
Ok(reponse)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::{cmp, io::Cursor, num::Saturating as Sat};
|
||||
|
||||
use conduit::{checked, Result};
|
||||
use conduit::{checked, err, Result};
|
||||
use image::{imageops::FilterType, DynamicImage};
|
||||
use ruma::{http_headers::ContentDisposition, Mxc, UserId};
|
||||
use ruma::{http_headers::ContentDisposition, media::Method, Mxc, UInt, UserId};
|
||||
use tokio::{
|
||||
fs,
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
|
@ -10,16 +10,24 @@ use tokio::{
|
|||
|
||||
use super::{data::Metadata, FileMeta};
|
||||
|
||||
/// Dimension specification for a thumbnail.
|
||||
#[derive(Debug)]
|
||||
pub struct Dim {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub method: Method,
|
||||
}
|
||||
|
||||
impl super::Service {
|
||||
/// Uploads or replaces a file thumbnail.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn upload_thumbnail(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, content_disposition: Option<&ContentDisposition>,
|
||||
content_type: Option<&str>, width: u32, height: u32, file: &[u8],
|
||||
content_type: Option<&str>, dim: &Dim, file: &[u8],
|
||||
) -> Result<()> {
|
||||
let key = self
|
||||
.db
|
||||
.create_file_metadata(mxc, user, width, height, content_disposition, content_type)?;
|
||||
.create_file_metadata(mxc, user, dim, content_disposition, content_type)?;
|
||||
|
||||
//TODO: Dangling metadata in database if creation fails
|
||||
let mut f = self.create_media_file(&key).await?;
|
||||
|
@ -42,15 +50,14 @@ impl super::Service {
|
|||
/// For width,height <= 96 the server uses another thumbnailing algorithm
|
||||
/// which crops the image afterwards.
|
||||
#[tracing::instrument(skip(self), name = "thumbnail", level = "debug")]
|
||||
pub async fn get_thumbnail(&self, mxc: &Mxc<'_>, width: u32, height: u32) -> Result<Option<FileMeta>> {
|
||||
pub async fn get_thumbnail(&self, mxc: &Mxc<'_>, dim: &Dim) -> Result<Option<FileMeta>> {
|
||||
// 0, 0 because that's the original file
|
||||
let (width, height, crop) = thumbnail_properties(width, height).unwrap_or((0, 0, false));
|
||||
let dim = dim.normalized();
|
||||
|
||||
if let Ok(metadata) = self.db.search_file_metadata(mxc, width, height) {
|
||||
if let Ok(metadata) = self.db.search_file_metadata(mxc, &dim) {
|
||||
self.get_thumbnail_saved(metadata).await
|
||||
} else if let Ok(metadata) = self.db.search_file_metadata(mxc, 0, 0) {
|
||||
self.get_thumbnail_generate(mxc, width, height, crop, metadata)
|
||||
.await
|
||||
} else if let Ok(metadata) = self.db.search_file_metadata(mxc, &Dim::default()) {
|
||||
self.get_thumbnail_generate(mxc, &dim, metadata).await
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -71,9 +78,7 @@ impl super::Service {
|
|||
|
||||
/// Generate a thumbnail
|
||||
#[tracing::instrument(skip(self), name = "generate", level = "debug")]
|
||||
async fn get_thumbnail_generate(
|
||||
&self, mxc: &Mxc<'_>, width: u32, height: u32, crop: bool, data: Metadata,
|
||||
) -> Result<Option<FileMeta>> {
|
||||
async fn get_thumbnail_generate(&self, mxc: &Mxc<'_>, dim: &Dim, data: Metadata) -> Result<Option<FileMeta>> {
|
||||
let mut content = Vec::new();
|
||||
let path = self.get_media_file(&data.key);
|
||||
fs::File::open(path)
|
||||
|
@ -86,20 +91,19 @@ impl super::Service {
|
|||
return Ok(Some(into_filemeta(data, content)));
|
||||
};
|
||||
|
||||
if width > image.width() || height > image.height() {
|
||||
if dim.width > image.width() || dim.height > image.height() {
|
||||
return Ok(Some(into_filemeta(data, content)));
|
||||
}
|
||||
|
||||
let mut thumbnail_bytes = Vec::new();
|
||||
let thumbnail = thumbnail_generate(&image, width, height, crop)?;
|
||||
let thumbnail = thumbnail_generate(&image, dim)?;
|
||||
thumbnail.write_to(&mut Cursor::new(&mut thumbnail_bytes), image::ImageFormat::Png)?;
|
||||
|
||||
// Save thumbnail in database so we don't have to generate it again next time
|
||||
let thumbnail_key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
None,
|
||||
width,
|
||||
height,
|
||||
dim,
|
||||
data.content_disposition.as_ref(),
|
||||
data.content_type.as_deref(),
|
||||
)?;
|
||||
|
@ -111,56 +115,25 @@ impl super::Service {
|
|||
}
|
||||
}
|
||||
|
||||
fn thumbnail_generate(image: &DynamicImage, width: u32, height: u32, crop: bool) -> Result<DynamicImage> {
|
||||
let thumbnail = if crop {
|
||||
image.resize_to_fill(width, height, FilterType::CatmullRom)
|
||||
fn thumbnail_generate(image: &DynamicImage, requested: &Dim) -> Result<DynamicImage> {
|
||||
let thumbnail = if !requested.crop() {
|
||||
let Dim {
|
||||
width,
|
||||
height,
|
||||
..
|
||||
} = requested.scaled(&Dim {
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
..Dim::default()
|
||||
})?;
|
||||
image.thumbnail_exact(width, height)
|
||||
} else {
|
||||
let (exact_width, exact_height) = thumbnail_dimension(image, width, height)?;
|
||||
image.thumbnail_exact(exact_width, exact_height)
|
||||
image.resize_to_fill(requested.width, requested.height, FilterType::CatmullRom)
|
||||
};
|
||||
|
||||
Ok(thumbnail)
|
||||
}
|
||||
|
||||
fn thumbnail_dimension(image: &DynamicImage, width: u32, height: u32) -> Result<(u32, u32)> {
|
||||
let image_width = image.width();
|
||||
let image_height = image.height();
|
||||
|
||||
let width = cmp::min(width, image_width);
|
||||
let height = cmp::min(height, image_height);
|
||||
|
||||
let use_width = Sat(width) * Sat(image_height) < Sat(height) * Sat(image_width);
|
||||
|
||||
let x = if use_width {
|
||||
let dividend = (Sat(height) * Sat(image_width)).0;
|
||||
checked!(dividend / image_height)?
|
||||
} else {
|
||||
width
|
||||
};
|
||||
|
||||
let y = if !use_width {
|
||||
let dividend = (Sat(width) * Sat(image_height)).0;
|
||||
checked!(dividend / image_width)?
|
||||
} else {
|
||||
height
|
||||
};
|
||||
|
||||
Ok((x, y))
|
||||
}
|
||||
|
||||
/// Returns width, height of the thumbnail and whether it should be cropped.
|
||||
/// Returns None when the server should send the original file.
|
||||
fn thumbnail_properties(width: u32, height: u32) -> Option<(u32, u32, bool)> {
|
||||
match (width, height) {
|
||||
(0..=32, 0..=32) => Some((32, 32, true)),
|
||||
(0..=96, 0..=96) => Some((96, 96, true)),
|
||||
(0..=320, 0..=240) => Some((320, 240, false)),
|
||||
(0..=640, 0..=480) => Some((640, 480, false)),
|
||||
(0..=800, 0..=600) => Some((800, 600, false)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_filemeta(data: Metadata, content: Vec<u8>) -> FileMeta {
|
||||
FileMeta {
|
||||
content: Some(content),
|
||||
|
@ -168,3 +141,89 @@ fn into_filemeta(data: Metadata, content: Vec<u8>) -> FileMeta {
|
|||
content_disposition: data.content_disposition,
|
||||
}
|
||||
}
|
||||
|
||||
impl Dim {
|
||||
/// Instantiate a Dim from Ruma integers with optional method.
|
||||
pub fn from_ruma(width: UInt, height: UInt, method: Option<Method>) -> Result<Self> {
|
||||
let width = width
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?;
|
||||
let height = height
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?;
|
||||
|
||||
Ok(Self::new(width, height, method))
|
||||
}
|
||||
|
||||
/// Instantiate a Dim with optional method
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new(width: u32, height: u32, method: Option<Method>) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
method: method.unwrap_or(Method::Scale),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scaled(&self, image: &Self) -> Result<Self> {
|
||||
let image_width = image.width;
|
||||
let image_height = image.height;
|
||||
|
||||
let width = cmp::min(self.width, image_width);
|
||||
let height = cmp::min(self.height, image_height);
|
||||
|
||||
let use_width = Sat(width) * Sat(image_height) < Sat(height) * Sat(image_width);
|
||||
|
||||
let x = if use_width {
|
||||
let dividend = (Sat(height) * Sat(image_width)).0;
|
||||
checked!(dividend / image_height)?
|
||||
} else {
|
||||
width
|
||||
};
|
||||
|
||||
let y = if !use_width {
|
||||
let dividend = (Sat(width) * Sat(image_height)).0;
|
||||
checked!(dividend / image_width)?
|
||||
} else {
|
||||
height
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
width: x,
|
||||
height: y,
|
||||
method: Method::Scale,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns width, height of the thumbnail and whether it should be cropped.
|
||||
/// Returns None when the server should send the original file.
|
||||
/// Ignores the input Method.
|
||||
#[must_use]
|
||||
pub fn normalized(&self) -> Self {
|
||||
match (self.width, self.height) {
|
||||
(0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop)),
|
||||
(0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop)),
|
||||
(0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale)),
|
||||
(0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale)),
|
||||
(0..=800, 0..=600) => Self::new(800, 600, Some(Method::Scale)),
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the method is Crop.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn crop(&self) -> bool { self.method == Method::Crop }
|
||||
}
|
||||
|
||||
impl Default for Dim {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
method: Method::Scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue