diff --git a/Cargo.lock b/Cargo.lock index 84f5849..fe278c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1365,6 +1365,8 @@ dependencies = [ "byteorder", "num-traits", "png", + "zune-core", + "zune-jpeg", ] [[package]] @@ -3431,3 +3433,18 @@ dependencies = [ "quote", "syn 2.0.59", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index f4efce9..7794cca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,10 @@ fs4 = { version = "= 0.8.2", features = ["sync"] } futures = "0.3.25" git-version = "0.3.9" graphql_client = { version = "0.14.0" } -image = { version = "0.25.1", default-features = false, features = ["png"] } +image = { version = "0.25.1", default-features = false, features = [ + "jpeg", + "png", +] } ironworks = { git = "https://github.com/ackwell/ironworks.git", features = [ "excel", "sqpack", diff --git a/src/asset/convert.rs b/src/asset/convert.rs index 6cabd5c..5d024c4 100644 --- a/src/asset/convert.rs +++ b/src/asset/convert.rs @@ -1,6 +1,5 @@ -use std::{io::Cursor, path::Path}; +use std::path::Path; -use anyhow::Context; use image::ImageFormat; use crate::data; @@ -24,8 +23,9 @@ impl Converter for Image { .extension() .and_then(|extension| extension.to_str()); - // TODO: add error handling case on this once more than one format exists. + // TODO: add error handling case on this once a failure case actually exists. let output_format = match format { + Format::Jpeg => ImageFormat::Jpeg, Format::Png => ImageFormat::Png, }; @@ -43,12 +43,6 @@ impl Converter for Image { } }?; - // TODO: are there any non-failure cases here? - let mut bytes = Cursor::new(vec![]); - buffer - .write_to(&mut bytes, output_format) - .context("failed to write output buffer")?; - - Ok(bytes.into_inner()) + texture::write(buffer, output_format) } } diff --git a/src/asset/format.rs b/src/asset/format.rs index aaf0b4b..97fe939 100644 --- a/src/asset/format.rs +++ b/src/asset/format.rs @@ -13,18 +13,21 @@ use super::{convert, error::Error}; #[derive(Debug, Clone, Copy, EnumIter)] pub enum Format { + Jpeg, Png, } impl Format { pub fn extension(&self) -> &str { match self { + Self::Jpeg => "jpg", Self::Png => "png", } } pub(super) fn converter(&self) -> &dyn convert::Converter { match self { + Self::Jpeg => &convert::Image, Self::Png => &convert::Image, } } @@ -45,6 +48,7 @@ impl FromStr for Format { fn from_str(input: &str) -> Result { Ok(match input { + "jpg" => Self::Jpeg, "png" => Self::Png, other => return Err(Error::UnknownFormat(other.into())), }) diff --git a/src/asset/service.rs b/src/asset/service.rs index e9811ae..a04a8e1 100644 --- a/src/asset/service.rs +++ b/src/asset/service.rs @@ -1,7 +1,7 @@ -use std::{io::Cursor, sync::Arc}; +use std::sync::Arc; use anyhow::{anyhow, Context}; -use image::{ImageBuffer, Pixel, Rgba}; +use image::{ImageBuffer, Pixel, Rgb}; use ironworks::Ironworks; use crate::{data, version::VersionKey}; @@ -48,12 +48,7 @@ impl Service { let image = self.compose_map(&ironworks, territory, index)?; - let mut bytes = Cursor::new(vec![]); - image - .write_to(&mut bytes, image::ImageFormat::Png) - .context("failed to write output buffer")?; - - Ok(bytes.into_inner()) + texture::write(image, image::ImageFormat::Jpeg) } fn compose_map( @@ -61,14 +56,14 @@ impl Service { ironworks: &Ironworks, territory: &str, index: &str, - ) -> Result, Vec>> { + ) -> Result, Vec>> { let path = format!("ui/map/{territory}/{index}/{territory}{index}"); - let mut buffer_map = texture::read(&ironworks, &format!("{path}_m.tex"))?.into_rgba8(); + let mut buffer_map = texture::read(&ironworks, &format!("{path}_m.tex"))?.into_rgb8(); let buffer_background = match texture::read(&ironworks, &format!("{path}m_m.tex")) { // If the background texture wasn't found, we can assume the map texture is pre-composed. Err(Error::NotFound(_)) => return Ok(buffer_map), - Ok(image) => image.into_rgba8(), + Ok(image) => image.into_rgb8(), Err(error) => Err(error)?, }; diff --git a/src/asset/texture.rs b/src/asset/texture.rs index f871242..3438cc5 100644 --- a/src/asset/texture.rs +++ b/src/asset/texture.rs @@ -1,5 +1,7 @@ +use std::io::Cursor; + use anyhow::Context; -use image::{DynamicImage, ImageBuffer}; +use image::{DynamicImage, ImageBuffer, ImageFormat}; use ironworks::{file::tex, Ironworks}; use itertools::Itertools; @@ -123,3 +125,27 @@ fn read_texture_dxt(texture: tex::Texture, dxt_format: texpresso::Format) -> Res .context("failed to build image buffer")?; Ok(DynamicImage::ImageRgba8(image_buffer)) } + +pub fn write(image: impl Into, format: ImageFormat) -> Result> { + fn inner(mut image: DynamicImage, format: ImageFormat) -> Result> { + // JPEG encoder errors out on anything with an alpha channel. + if format == ImageFormat::Jpeg { + image = match image { + image @ DynamicImage::ImageLumaA8(..) | image @ DynamicImage::ImageLuma16(..) => { + image.into_luma8().into() + } + other => other.into_rgb8().into(), + } + } + + // TODO: are there any non-failure cases here? + let mut bytes = Cursor::new(vec![]); + image + .write_to(&mut bytes, format) + .context("failed to write output buffer")?; + + Ok(bytes.into_inner()) + } + + inner(image.into(), format) +} diff --git a/src/http/api1/asset.rs b/src/http/api1/asset.rs index d6100b7..9da0960 100644 --- a/src/http/api1/asset.rs +++ b/src/http/api1/asset.rs @@ -158,6 +158,7 @@ async fn asset2( fn format_mime(format: Format) -> mime::Mime { match format { + Format::Jpeg => mime::IMAGE_JPEG, Format::Png => mime::IMAGE_PNG, } } @@ -210,10 +211,10 @@ async fn map( let bytes = asset.map(version_key, &territory, &index)?; let response = ( - TypedHeader(ContentType::png()), + TypedHeader(ContentType::jpeg()), [( header::CONTENT_DISPOSITION, - format!("inline; filename=\"{territory}_{index}.png\""), + format!("inline; filename=\"{territory}_{index}.jpg\""), )], bytes, );