refactor: split kra (krita) logic into image hook

may further split this into its own crate;
seems quite useful! (albeit somewhat short and easy)

since there aren't any runtime dependancies,
it could even go into image-extras maybe
This commit is contained in:
electria 2026-07-30 23:40:14 -07:00
commit 101adc7a89
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
3 changed files with 75 additions and 105 deletions

41
src/kra.rs Normal file
View file

@ -0,0 +1,41 @@
use std::{
ffi::OsString,
io::{self, Read},
};
use image::{
ImageDecoder, ImageError, ImageReader, ImageResult,
error::DecodingError,
hooks::{self, GenericReader},
};
use zip::ZipArchive;
pub fn register() -> bool {
hooks::register_decoding_hook(OsString::from("kra"), Box::new(hook))
}
fn hook<'a>(reader: GenericReader<'a>) -> ImageResult<Box<dyn ImageDecoder + 'a>> {
let mut zip = ZipArchive::new(reader).map_err(|e| {
ImageError::Decoding(DecodingError::new(
image::error::ImageFormatHint::PathExtension("kra".into()),
e,
))
})?;
let mut reader = zip.by_name("mergedimage.png").map_err(|e| {
ImageError::Decoding(DecodingError::new(
image::error::ImageFormatHint::PathExtension("kra".into()),
e,
))
})?;
// reading it all and wrapping it with a Cursor
// is the only way I know to give it Seek
// (which is required by ImageReader)
let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
let image_reader = ImageReader::with_format(io::Cursor::new(buf), image::ImageFormat::Png);
Ok(Box::new(image_reader.into_decoder()?))
}

View file

@ -7,6 +7,7 @@ use iced::{
};
use image::{EncodableLayout, RgbaImage, imageops};
mod kra;
mod utils;
#[derive(Clone, Debug)]
@ -209,6 +210,7 @@ impl State {
fn main() -> Result<(), iced::Error> {
jxl_oxide::integration::register_image_decoding_hook();
kra::register();
iced::application(State::new, State::update, State::view)
.subscription(State::subscription)

View file

@ -1,124 +1,51 @@
use std::{
fs,
io::{self, Read},
path::{Path, PathBuf},
};
use std::path::{Path, PathBuf};
use iced::widget;
use image::{DynamicImage, ImageDecoder, ImageReader, RgbaImage};
use rfd::FileDialog;
use zip::ZipArchive;
pub fn load_image(path: impl AsRef<Path>) -> Result<RgbaImage, String> {
let path = path.as_ref();
let decoded_image = if path.extension().is_some_and(|ext| ext == "kra") {
let reader = match fs::File::open(path).map(io::BufReader::new) {
Ok(r) => r,
Err(e) => {
return Err(format!(
"failed to create reader for '{}': {e}",
path.display()
));
}
};
let mut zip = match ZipArchive::new(reader) {
Ok(z) => z,
Err(e) => {
return Err(format!(
"failed to open zip archive '{}': {e}",
path.display()
));
}
};
let mut reader = match zip.by_name("mergedimage.png") {
Ok(r) => r,
Err(e) => {
return Err(format!(
"failed to open file in zip archive '{}': {e}",
path.display()
));
}
};
// reading it all and wrapping it with a Cursor
// is the only way I know to give it Seek
// (which is required by ImageReader)
let mut buf = Vec::new();
if let Err(e) = reader.read_to_end(&mut buf) {
let image_reader = match ImageReader::open(path) {
Ok(r) => r,
Err(e) => {
return Err(format!(
"error when reading file in zip archive '{}': {e}",
"failed to create image reader for '{}': {e}",
path.display()
));
};
let image_reader = ImageReader::with_format(io::Cursor::new(buf), image::ImageFormat::Png);
let decoder = match image_reader.into_decoder() {
Ok(d) => d,
Err(e) => {
return Err(format!(
"failed to create decoder for '{}': {e}",
path.display()
));
}
};
let decoded_image = match DynamicImage::from_decoder(decoder) {
Ok(i) => i,
Err(e) => {
return Err(format!("failed to decode image '{}': {e}", path.display()));
}
};
decoded_image
} else {
let image_reader = match ImageReader::open(path) {
Ok(r) => r,
Err(e) => {
return Err(format!(
"failed to create image reader for '{}': {e}",
path.display()
));
}
};
let mut decoder = match image_reader.into_decoder() {
Ok(d) => d,
Err(e) => {
return Err(format!(
"failed to create decoder for '{}': {e}",
path.display()
));
}
};
// we don't need to check this for the kra branch
// since PNGs don't have metadata oreintation
let oreintation = match decoder.orientation() {
Ok(o) => o,
Err(e) => {
return Err(format!(
"failed to get oreintation of '{}': {e}",
path.display()
));
}
};
let mut decoded_image = match DynamicImage::from_decoder(decoder) {
Ok(i) => i,
Err(e) => {
return Err(format!("failed to decode image '{}': {e}", path.display()));
}
};
decoded_image.apply_orientation(oreintation);
decoded_image
}
};
let mut decoder = match image_reader.into_decoder() {
Ok(d) => d,
Err(e) => {
return Err(format!(
"failed to create decoder for '{}': {e}",
path.display()
));
}
};
let oreintation = match decoder.orientation() {
Ok(o) => o,
Err(e) => {
return Err(format!(
"failed to get oreintation of '{}': {e}",
path.display()
));
}
};
let mut decoded_image = match DynamicImage::from_decoder(decoder) {
Ok(i) => i,
Err(e) => {
return Err(format!("failed to decode image '{}': {e}", path.display()));
}
};
decoded_image.apply_orientation(oreintation);
Ok(decoded_image.into_rgba8())
}
pub fn pick_image() -> Result<PathBuf, String> {