fix png encoding, default to ./icon.png, use built-in default

This commit is contained in:
kralverde
2024-10-18 15:58:12 -04:00
parent f54f6aa123
commit 6b7d6249de
6 changed files with 90 additions and 15 deletions

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -13,3 +13,7 @@ syn = "2.0"
serde.workspace = true
itertools.workspace = true
serde_json = "1.0.128"
# icon loading
base64 = "0.22.1"
png = "0.17.14"

View File

@@ -0,0 +1,30 @@
use std::{io::Cursor, sync::LazyLock};
use base64::{engine::general_purpose, Engine as _};
use proc_macro::TokenStream;
use quote::quote;
// TODO: This is the same as pumpkin/src/server/connection_cache.rs, but we cannot reference that in
// this crate
fn load_icon(data: &[u8]) -> String {
let icon = png::Decoder::new(Cursor::new(data));
let reader = icon.read_info().unwrap();
let info = reader.info();
assert!(info.width == 64, "Icon width must be 64");
assert!(info.height == 64, "Icon height must be 64");
// Once we validate the dimensions, we can encode the image as-is
let mut result = "data:image/png;base64,".to_owned();
general_purpose::STANDARD.encode_string(data, &mut result);
result
}
static ICON: LazyLock<&[u8]> = LazyLock::new(|| include_bytes!("../../assets/default_icon.png"));
pub fn create_icon_impl() -> TokenStream {
let encoded_icon = load_icon(&ICON);
quote! {
#encoded_icon
}
.into()
}

View File

@@ -40,3 +40,10 @@ pub fn blocks_enum(_item: TokenStream) -> TokenStream {
pub fn block_categories_enum(_item: TokenStream) -> TokenStream {
block_state::block_type_enum_impl()
}
mod icon;
#[proc_macro]
/// Creates the default server icon
pub fn create_icon(_item: TokenStream) -> TokenStream {
icon::create_icon_impl()
}

View File

@@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
# pumpkin
pumpkin-core = { path = "../pumpkin-core" }
pumpkin-macros = { path = "../pumpkin-macros" }
pumpkin-config = { path = "../pumpkin-config" }
pumpkin-inventory = { path = "../pumpkin-inventory" }
pumpkin-world = { path = "../pumpkin-world" }
@@ -53,7 +54,7 @@ thiserror = "1.0"
# icon loading
base64 = "0.22.1"
png = "0.17.14"
png = "0.17.14"
# logging
simple_logger = { version = "5.0.0", features = ["threads"] }

View File

@@ -1,4 +1,9 @@
use std::{fs::File, path::Path};
use core::error;
use std::{
fs::File,
io::{Cursor, Read},
path::Path,
};
use base64::{engine::general_purpose, Engine as _};
use pumpkin_config::{BasicConfiguration, BASIC_CONFIG};
@@ -59,9 +64,37 @@ impl CachedStatus {
pub fn build_response(config: &BasicConfiguration) -> StatusResponse {
let icon_path = &config.favicon_path;
let icon = if !icon_path.is_empty() && Path::new(icon_path).exists() {
Some(Self::load_icon(icon_path))
let icon = if icon_path.is_empty() {
// See if an icon exists at ./icon.png
let default_local_path = "./icon.png";
if Path::new(default_local_path).exists() {
log::info!("Loading server icon from {}", default_local_path);
let maybe_icon = Self::load_icon(default_local_path);
match maybe_icon {
Ok(result) => Some(result),
Err(e) => {
log::warn!("Failed to load icon: {:?}", e);
None
}
}
} else {
log::info!("Using default server icon");
Some(pumpkin_macros::create_icon!().to_string())
}
} else if Path::new(icon_path).exists() {
log::info!("Loading server icon from {}", icon_path);
let maybe_icon = Self::load_icon(icon_path);
match maybe_icon {
Ok(result) => Some(result),
Err(e) => {
log::warn!("Failed to load icon: {:?}", e);
None
}
}
} else {
// TODO: Add definitive option to have no icon?
// Currently can just use a bad path
log::warn!("Failed to load server icon at path {}", icon_path);
None
};
@@ -84,20 +117,20 @@ impl CachedStatus {
}
}
fn load_icon<P: AsRef<Path>>(path: P) -> String {
let icon = png::Decoder::new(File::open(path).expect("Failed to load icon"));
let mut reader = icon.read_info().unwrap();
fn load_icon<P: AsRef<Path>>(path: P) -> Result<String, Box<dyn error::Error>> {
let mut icon_file = File::open(path).expect("Failed to load icon");
let mut buf = Vec::new();
icon_file.read_to_end(&mut buf)?;
let icon = png::Decoder::new(Cursor::new(&buf));
let reader = icon.read_info()?;
let info = reader.info();
assert!(info.width == 64, "Icon width must be 64");
assert!(info.height == 64, "Icon height must be 64");
// Allocate the output buffer.
let mut buf = vec![0; reader.output_buffer_size()];
// Read the next frame. An APNG might contain multiple frames.
let info = reader.next_frame(&mut buf).unwrap();
// Grab the bytes of the image.
let bytes = &buf[..info.buffer_size()];
// Reader consumes the image. Once we verify dimensions, we want to encode the entire raw image
let mut result = "data:image/png;base64,".to_owned();
general_purpose::STANDARD.encode_string(bytes, &mut result);
result
general_purpose::STANDARD.encode_string(&buf, &mut result);
Ok(result)
}
}