mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix png encoding, default to ./icon.png, use built-in default
This commit is contained in:
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
@@ -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"
|
||||
|
||||
30
pumpkin-macros/src/icon.rs
Normal file
30
pumpkin-macros/src/icon.rs
Normal 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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user