mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-07-08 01:31:53 +08:00
Add BGI Image decode support
Fix DSC decompress
This commit is contained in:
@@ -21,7 +21,7 @@ unicode-segmentation = "1.12"
|
|||||||
default = ["bgi", "bgi-arc", "bgi-img", "circus", "escude", "escude-arc", "yaneurao", "yaneurao-itufuru"]
|
default = ["bgi", "bgi-arc", "bgi-img", "circus", "escude", "escude-arc", "yaneurao", "yaneurao-itufuru"]
|
||||||
bgi = []
|
bgi = []
|
||||||
bgi-arc = ["bgi", "utils-bit-stream"]
|
bgi-arc = ["bgi", "utils-bit-stream"]
|
||||||
bgi-img = ["image"]
|
bgi-img = ["bgi", "image"]
|
||||||
circus = []
|
circus = []
|
||||||
escude = ["int-enum"]
|
escude = ["int-enum"]
|
||||||
escude-arc = ["escude", "rand", "utils-bit-stream"]
|
escude-arc = ["escude", "rand", "utils-bit-stream"]
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ pub struct Arg {
|
|||||||
#[arg(short = 'T', long, value_enum, global = true)]
|
#[arg(short = 'T', long, value_enum, global = true)]
|
||||||
/// Output script type
|
/// Output script type
|
||||||
pub output_type: Option<OutputScriptType>,
|
pub output_type: Option<OutputScriptType>,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
#[arg(short = 'i', long, value_enum, global = true)]
|
||||||
|
/// Output image type
|
||||||
|
pub image_type: Option<ImageOutputType>,
|
||||||
#[arg(short = 'e', long, value_enum, global = true, group = "encodingg")]
|
#[arg(short = 'e', long, value_enum, global = true, group = "encodingg")]
|
||||||
/// Script encoding
|
/// Script encoding
|
||||||
pub encoding: Option<TextEncoding>,
|
pub encoding: Option<TextEncoding>,
|
||||||
|
|||||||
@@ -1040,14 +1040,14 @@ impl<'a> CPeek for MemReaderRef<'a> {
|
|||||||
fn cpeek(&self, buf: &mut [u8]) -> Result<usize> {
|
fn cpeek(&self, buf: &mut [u8]) -> Result<usize> {
|
||||||
let len = self.data.len();
|
let len = self.data.len();
|
||||||
let bytes_to_read = std::cmp::min(buf.len(), len - self.pos);
|
let bytes_to_read = std::cmp::min(buf.len(), len - self.pos);
|
||||||
buf.copy_from_slice(&self.data[self.pos..self.pos + bytes_to_read]);
|
buf[..bytes_to_read].copy_from_slice(&self.data[self.pos..self.pos + bytes_to_read]);
|
||||||
Ok(bytes_to_read)
|
Ok(bytes_to_read)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cpeek_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
fn cpeek_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
||||||
let len = self.data.len();
|
let len = self.data.len();
|
||||||
let bytes_to_read = std::cmp::min(buf.len(), len - offset);
|
let bytes_to_read = std::cmp::min(buf.len(), len - offset);
|
||||||
buf.copy_from_slice(&self.data[offset..offset + bytes_to_read]);
|
buf[..bytes_to_read].copy_from_slice(&self.data[offset..offset + bytes_to_read]);
|
||||||
Ok(bytes_to_read)
|
Ok(bytes_to_read)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,21 @@ pub trait VecExt<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Copy> VecExt<T> for Vec<T> {
|
impl<T: Copy> VecExt<T> for Vec<T> {
|
||||||
fn copy_overlapped(&mut self, src: usize, dst: usize, len: usize) {
|
fn copy_overlapped(&mut self, src: usize, dst: usize, mut len: usize) {
|
||||||
let src = src.min(self.len());
|
let mut src = src.min(self.len());
|
||||||
let dst = dst.min(self.len());
|
let mut dst = dst.min(self.len());
|
||||||
if src < dst {
|
if dst > src {
|
||||||
let max_count = len.min(dst - src);
|
while len > 0 {
|
||||||
for i in 0..max_count {
|
let preceding = (dst - src).min(len);
|
||||||
self[dst + i] = self[src + i];
|
for i in 0..preceding {
|
||||||
|
self[dst + i] = self[src + i];
|
||||||
|
}
|
||||||
|
len -= preceding;
|
||||||
|
src += preceding;
|
||||||
|
dst += preceding;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let max_count = len.min(src - dst);
|
for i in 0..len {
|
||||||
for i in (0..max_count).rev() {
|
|
||||||
self[dst + i] = self[src + i];
|
self[dst + i] = self[src + i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/main.rs
32
src/main.rs
@@ -516,6 +516,36 @@ pub fn export_script(
|
|||||||
}
|
}
|
||||||
return Ok(types::ScriptResult::Ok);
|
return Ok(types::ScriptResult::Ok);
|
||||||
}
|
}
|
||||||
|
if script.is_image() {
|
||||||
|
let img_data = script.export_image()?;
|
||||||
|
let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
|
||||||
|
let f = if filename == "-" {
|
||||||
|
String::from("-")
|
||||||
|
} else {
|
||||||
|
match output.as_ref() {
|
||||||
|
Some(output) => {
|
||||||
|
if is_dir {
|
||||||
|
let f = std::path::PathBuf::from(filename);
|
||||||
|
let mut pb = std::path::PathBuf::from(output);
|
||||||
|
if let Some(fname) = f.file_name() {
|
||||||
|
pb.push(fname);
|
||||||
|
}
|
||||||
|
pb.set_extension(out_type.as_ref());
|
||||||
|
pb.to_string_lossy().into_owned()
|
||||||
|
} else {
|
||||||
|
output.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut pb = std::path::PathBuf::from(filename);
|
||||||
|
pb.set_extension(out_type.as_ref());
|
||||||
|
pb.to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
utils::img::encode_img(img_data, out_type, &f)?;
|
||||||
|
return Ok(types::ScriptResult::Ok);
|
||||||
|
}
|
||||||
let mut of = match &arg.output_type {
|
let mut of = match &arg.output_type {
|
||||||
Some(t) => t.clone(),
|
Some(t) => t.clone(),
|
||||||
None => script.default_output_script_type(),
|
None => script.default_output_script_type(),
|
||||||
@@ -1122,6 +1152,8 @@ fn main() {
|
|||||||
bgi_import_duplicate: arg.bgi_import_duplicate,
|
bgi_import_duplicate: arg.bgi_import_duplicate,
|
||||||
#[cfg(feature = "bgi")]
|
#[cfg(feature = "bgi")]
|
||||||
bgi_disable_append: arg.bgi_disable_append,
|
bgi_disable_append: arg.bgi_disable_append,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
image_type: arg.image_type.clone(),
|
||||||
};
|
};
|
||||||
match &arg.command {
|
match &arg.command {
|
||||||
args::Command::Export { input, output } => {
|
args::Command::Export { input, output } => {
|
||||||
|
|||||||
@@ -108,6 +108,34 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
|||||||
let f = std::io::BufWriter::new(f);
|
let f = std::io::BufWriter::new(f);
|
||||||
self.create_file(filename, Box::new(f), encoding, file_encoding)
|
self.create_file(filename, Box::new(f), encoding, file_encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn is_image(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn can_create_image_file(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn create_image_file<'a>(
|
||||||
|
&'a self,
|
||||||
|
_data: ImageData,
|
||||||
|
_writer: Box<dyn WriteSeek + 'a>,
|
||||||
|
) -> Result<()> {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"This script type does not support creating an image file."
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn create_image_file_filename(&self, data: ImageData, filename: &str) -> Result<()> {
|
||||||
|
let f = std::fs::File::create(filename)?;
|
||||||
|
let f = std::io::BufWriter::new(f);
|
||||||
|
self.create_image_file(data, Box::new(f))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ArchiveContent: Read {
|
pub trait ArchiveContent: Read {
|
||||||
@@ -222,6 +250,32 @@ pub trait Script: std::fmt::Debug {
|
|||||||
) -> Result<Box<dyn Iterator<Item = Result<Box<dyn ArchiveContent>>> + 'a>> {
|
) -> Result<Box<dyn Iterator<Item = Result<Box<dyn ArchiveContent>>> + 'a>> {
|
||||||
Ok(Box::new(std::iter::empty()))
|
Ok(Box::new(std::iter::empty()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn is_image(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn export_image(&self) -> Result<ImageData> {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"This script type does not support to export image."
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn import_image<'a>(&'a self, _data: ImageData, _file: Box<dyn WriteSeek + 'a>) -> Result<()> {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"This script type does not support to import image."
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn import_image_filename(&self, data: ImageData, filename: &str) -> Result<()> {
|
||||||
|
let f = std::fs::File::create(filename)?;
|
||||||
|
let f = std::io::BufWriter::new(f);
|
||||||
|
self.import_image(data, Box::new(f))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Archive {
|
pub trait Archive {
|
||||||
|
|||||||
112
src/scripts/bgi/image/img.rs
Normal file
112
src/scripts/bgi/image/img.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
use crate::ext::io::*;
|
||||||
|
use crate::scripts::base::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BgiImageBuilder {}
|
||||||
|
|
||||||
|
impl BgiImageBuilder {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
BgiImageBuilder {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScriptBuilder for BgiImageBuilder {
|
||||||
|
fn default_encoding(&self) -> Encoding {
|
||||||
|
Encoding::Cp932
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_script(
|
||||||
|
&self,
|
||||||
|
data: Vec<u8>,
|
||||||
|
_filename: &str,
|
||||||
|
_encoding: Encoding,
|
||||||
|
_archive_encoding: Encoding,
|
||||||
|
config: &ExtraConfig,
|
||||||
|
) -> Result<Box<dyn Script>> {
|
||||||
|
Ok(Box::new(BgiImage::new(data, config)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extensions(&self) -> &'static [&'static str] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script_type(&self) -> &'static ScriptType {
|
||||||
|
&ScriptType::BGIImg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_image(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BgiImage {
|
||||||
|
data: MemReader,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
color_type: ImageColorType,
|
||||||
|
is_scrambled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BgiImage {
|
||||||
|
pub fn new(buf: Vec<u8>, _config: &ExtraConfig) -> Result<Self> {
|
||||||
|
let mut reader = MemReader::new(buf);
|
||||||
|
let width = reader.read_u16()? as u32;
|
||||||
|
let height = reader.read_u16()? as u32;
|
||||||
|
let bpp = reader.read_u16()?;
|
||||||
|
let color_type = match bpp {
|
||||||
|
8 => ImageColorType::Grayscale,
|
||||||
|
24 => ImageColorType::Bgr,
|
||||||
|
32 => ImageColorType::Bgra,
|
||||||
|
_ => return Err(anyhow::anyhow!("Unsupported BPP: {}", bpp)),
|
||||||
|
};
|
||||||
|
let flag = reader.read_u16()?;
|
||||||
|
let padding = reader.read_u64()?;
|
||||||
|
if padding != 0 {
|
||||||
|
return Err(anyhow::anyhow!("Invalid padding: {}", padding));
|
||||||
|
}
|
||||||
|
let is_scrambled = flag != 0;
|
||||||
|
|
||||||
|
Ok(BgiImage {
|
||||||
|
data: reader,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
color_type,
|
||||||
|
is_scrambled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Script for BgiImage {
|
||||||
|
fn default_output_script_type(&self) -> OutputScriptType {
|
||||||
|
OutputScriptType::Json
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_format_type(&self) -> FormatOptions {
|
||||||
|
FormatOptions::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_image(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn export_image(&self) -> Result<ImageData> {
|
||||||
|
let stride = self.width as usize * ((self.color_type.bbp(8) as usize + 7) / 8);
|
||||||
|
let buf_size = stride * self.height as usize;
|
||||||
|
if self.is_scrambled {
|
||||||
|
return Err(anyhow::anyhow!("Scrambled images are not supported"));
|
||||||
|
}
|
||||||
|
let mut data = Vec::with_capacity(buf_size);
|
||||||
|
data.resize(buf_size, 0);
|
||||||
|
self.data.cpeek_extract_at(0x10, &mut data)?;
|
||||||
|
Ok(ImageData {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
color_type: self.color_type,
|
||||||
|
depth: 8,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/scripts/bgi/image/mod.rs
Normal file
1
src/scripts/bgi/image/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod img;
|
||||||
@@ -2,5 +2,7 @@
|
|||||||
pub mod archive;
|
pub mod archive;
|
||||||
pub mod bp;
|
pub mod bp;
|
||||||
pub mod bsi;
|
pub mod bsi;
|
||||||
|
#[cfg(feature = "bgi-img")]
|
||||||
|
pub mod image;
|
||||||
mod parser;
|
mod parser;
|
||||||
pub mod script;
|
pub mod script;
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ lazy_static::lazy_static! {
|
|||||||
Box::new(bgi::archive::v1::BgiArchiveBuilder::new()),
|
Box::new(bgi::archive::v1::BgiArchiveBuilder::new()),
|
||||||
#[cfg(feature = "bgi-arc")]
|
#[cfg(feature = "bgi-arc")]
|
||||||
Box::new(bgi::archive::v2::BgiArchiveBuilder::new()),
|
Box::new(bgi::archive::v2::BgiArchiveBuilder::new()),
|
||||||
|
#[cfg(feature = "bgi-img")]
|
||||||
|
Box::new(bgi::image::img::BgiImageBuilder::new()),
|
||||||
#[cfg(feature = "escude-arc")]
|
#[cfg(feature = "escude-arc")]
|
||||||
Box::new(escude::archive::EscudeBinArchiveBuilder::new()),
|
Box::new(escude::archive::EscudeBinArchiveBuilder::new()),
|
||||||
#[cfg(feature = "escude")]
|
#[cfg(feature = "escude")]
|
||||||
|
|||||||
46
src/types.rs
46
src/types.rs
@@ -197,6 +197,8 @@ pub struct ExtraConfig {
|
|||||||
pub bgi_import_duplicate: bool,
|
pub bgi_import_duplicate: bool,
|
||||||
#[cfg(feature = "bgi")]
|
#[cfg(feature = "bgi")]
|
||||||
pub bgi_disable_append: bool,
|
pub bgi_disable_append: bool,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub image_type: Option<ImageOutputType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -225,6 +227,10 @@ pub enum ScriptType {
|
|||||||
#[value(alias = "ethornell-arc-v2", alias = "bgi-arc", alias = "ethornell-arc")]
|
#[value(alias = "ethornell-arc-v2", alias = "bgi-arc", alias = "ethornell-arc")]
|
||||||
/// Buriko General Interpreter/Ethornell archive v2
|
/// Buriko General Interpreter/Ethornell archive v2
|
||||||
BGIArcV2,
|
BGIArcV2,
|
||||||
|
#[cfg(feature = "bgi-img")]
|
||||||
|
#[value(alias("ethornell-img"))]
|
||||||
|
/// Buriko General Interpreter/Ethornell image (Image files in sysgrp.arc)
|
||||||
|
BGIImg,
|
||||||
#[cfg(feature = "escude-arc")]
|
#[cfg(feature = "escude-arc")]
|
||||||
/// Escude bin archive
|
/// Escude bin archive
|
||||||
EscudeArc,
|
EscudeArc,
|
||||||
@@ -301,13 +307,49 @@ pub struct ReplacementTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum ImageColorType {
|
pub enum ImageColorType {
|
||||||
Grayscale,
|
Grayscale,
|
||||||
Rgb24,
|
Rgb,
|
||||||
Rgba32,
|
Rgba,
|
||||||
|
Bgr,
|
||||||
|
Bgra,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
|
impl ImageColorType {
|
||||||
|
pub fn bbp(&self, depth: u8) -> u16 {
|
||||||
|
match self {
|
||||||
|
ImageColorType::Grayscale => depth as u16,
|
||||||
|
ImageColorType::Rgb => depth as u16 * 3,
|
||||||
|
ImageColorType::Rgba => depth as u16 * 4,
|
||||||
|
ImageColorType::Bgr => depth as u16 * 3,
|
||||||
|
ImageColorType::Bgra => depth as u16 * 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum ImageOutputType {
|
pub enum ImageOutputType {
|
||||||
Png,
|
Png,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
impl AsRef<str> for ImageOutputType {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ImageOutputType::Png => "png",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ImageData {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub color_type: ImageColorType,
|
||||||
|
pub depth: u8,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|||||||
126
src/utils/img.rs
Normal file
126
src/utils/img.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use crate::types::*;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn reverse_alpha_values(data: &mut ImageData) -> Result<()> {
|
||||||
|
if data.color_type != ImageColorType::Rgba && data.color_type != ImageColorType::Bgra {
|
||||||
|
return Err(anyhow::anyhow!("Image is not RGBA or BGRA"));
|
||||||
|
}
|
||||||
|
if data.depth != 8 {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Alpha value reversal only supports 8-bit depth"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for i in (0..data.data.len()).step_by(4) {
|
||||||
|
data.data[i + 3] = 255 - data.data[i + 3];
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_bgr_to_rgb(data: &mut ImageData) -> Result<()> {
|
||||||
|
if data.color_type != ImageColorType::Bgr {
|
||||||
|
return Err(anyhow::anyhow!("Image is not BGR"));
|
||||||
|
}
|
||||||
|
if data.depth != 8 {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"BGR to RGB conversion only supports 8-bit depth"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for i in (0..data.data.len()).step_by(3) {
|
||||||
|
let b = data.data[i];
|
||||||
|
data.data[i] = data.data[i + 2];
|
||||||
|
data.data[i + 2] = b;
|
||||||
|
}
|
||||||
|
data.color_type = ImageColorType::Rgb;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_bgra_to_rgba(data: &mut ImageData) -> Result<()> {
|
||||||
|
if data.color_type != ImageColorType::Bgra {
|
||||||
|
return Err(anyhow::anyhow!("Image is not BGRA"));
|
||||||
|
}
|
||||||
|
if data.depth != 8 {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"BGRA to RGBA conversion only supports 8-bit depth"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for i in (0..data.data.len()).step_by(4) {
|
||||||
|
let b = data.data[i];
|
||||||
|
data.data[i] = data.data[i + 2];
|
||||||
|
data.data[i + 2] = b;
|
||||||
|
}
|
||||||
|
data.color_type = ImageColorType::Rgba;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_img(mut data: ImageData, typ: ImageOutputType, filename: &str) -> Result<()> {
|
||||||
|
match typ {
|
||||||
|
ImageOutputType::Png => {
|
||||||
|
let mut file = crate::utils::files::write_file(filename)?;
|
||||||
|
let color_type = match data.color_type {
|
||||||
|
ImageColorType::Grayscale => png::ColorType::Grayscale,
|
||||||
|
ImageColorType::Rgb => png::ColorType::Rgb,
|
||||||
|
ImageColorType::Rgba => png::ColorType::Rgba,
|
||||||
|
ImageColorType::Bgr => {
|
||||||
|
convert_bgr_to_rgb(&mut data)?;
|
||||||
|
png::ColorType::Rgb
|
||||||
|
}
|
||||||
|
ImageColorType::Bgra => {
|
||||||
|
convert_bgra_to_rgba(&mut data)?;
|
||||||
|
png::ColorType::Rgba
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let bit_depth = match &data.depth {
|
||||||
|
1 => png::BitDepth::One,
|
||||||
|
2 => png::BitDepth::Two,
|
||||||
|
4 => png::BitDepth::Four,
|
||||||
|
8 => png::BitDepth::Eight,
|
||||||
|
16 => png::BitDepth::Sixteen,
|
||||||
|
_ => return Err(anyhow::anyhow!("Unsupported bit depth: {}", data.depth)),
|
||||||
|
};
|
||||||
|
let mut encoder = png::Encoder::new(&mut file, data.width, data.height);
|
||||||
|
encoder.set_color(color_type);
|
||||||
|
encoder.set_depth(bit_depth);
|
||||||
|
let mut writer = encoder.write_header()?;
|
||||||
|
writer.write_image_data(&data.data)?;
|
||||||
|
writer.finish()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_img(typ: ImageOutputType, filename: &str) -> Result<ImageData> {
|
||||||
|
match typ {
|
||||||
|
ImageOutputType::Png => {
|
||||||
|
let file = crate::utils::files::read_file(filename)?;
|
||||||
|
let decoder = png::Decoder::new(&file[..]);
|
||||||
|
let mut reader = decoder.read_info()?;
|
||||||
|
let bit_depth = match reader.info().bit_depth {
|
||||||
|
png::BitDepth::One => 1,
|
||||||
|
png::BitDepth::Two => 2,
|
||||||
|
png::BitDepth::Four => 4,
|
||||||
|
png::BitDepth::Eight => 8,
|
||||||
|
png::BitDepth::Sixteen => 16,
|
||||||
|
};
|
||||||
|
let color_type = match reader.info().color_type {
|
||||||
|
png::ColorType::Grayscale => ImageColorType::Grayscale,
|
||||||
|
png::ColorType::Rgb => ImageColorType::Rgb,
|
||||||
|
png::ColorType::Rgba => ImageColorType::Rgba,
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Unsupported color type: {:?}",
|
||||||
|
reader.info().color_type
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut data = vec![0; reader.info().raw_bytes()];
|
||||||
|
reader.next_frame(&mut data)?;
|
||||||
|
Ok(ImageData {
|
||||||
|
width: reader.info().width,
|
||||||
|
height: reader.info().height,
|
||||||
|
depth: bit_depth,
|
||||||
|
color_type,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,7 @@ pub mod encoding;
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod encoding_win;
|
mod encoding_win;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub mod img;
|
||||||
pub mod name_replacement;
|
pub mod name_replacement;
|
||||||
pub mod struct_pack;
|
pub mod struct_pack;
|
||||||
|
|||||||
Reference in New Issue
Block a user