# HG changeset patch # User Link Mauve # Date 1764605148 -3600 # Node ID 7e940ebeb5fd79cf0307896928d739a91b9ea3e5 # Parent f73e8524c045a6933e381938502dc5ef9af58ca9 Replace SDL2_image with the image crate diff --git a/README b/README --- a/README +++ b/README @@ -17,7 +17,7 @@ * A working OpenGL driver * libepoxy * SDL2 - * SDL2_image, SDL2_ttf + * SDL2_ttf * A TTF font file, placed as “font.ttf” in the game directory. diff --git a/interpreters/src/lib.rs b/interpreters/src/lib.rs --- a/interpreters/src/lib.rs +++ b/interpreters/src/lib.rs @@ -1,5 +1,4 @@ #![deny(missing_docs)] -#![feature(concat_idents)] //! Crate implementing interpreters for various Touhou formats. diff --git a/python/Cargo.toml b/python/Cargo.toml --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -16,10 +16,10 @@ touhou-formats = "*" touhou-utils = { version = "*", path = "../utils" } pyo3 = "0.27" -image = { version = "0.25", default-features = false, features = ["png"], optional = true } +image = { version = "0.25", default-features = false, features = ["png"] } glob = "0.3.3" kira = "0.11.0" [features] default = [] -glide = ["image"] +glide = [] diff --git a/python/src/lib.rs b/python/src/lib.rs --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,3 +1,4 @@ +use image::{DynamicImage, GenericImageView, ImageFormat}; use pyo3::exceptions::{PyIOError, PyKeyError}; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyTuple}; @@ -176,6 +177,11 @@ /// Return the given file as a Vec. fn get_file_internal(&self, name: &str) -> PyResult> { + let name = if let Some((_, name)) = name.rsplit_once('/') { + name + } else { + name + }; if let Some(archive) = self.known_files.get(name) { let mut archive = archive.lock().unwrap(); let bytes = archive.get_file(name, true)?; @@ -206,6 +212,19 @@ let (_, inner) = msg::Msg::from_slice(&vec).unwrap(); Ok(Py::new(py, PyMsg { inner })?) } + + fn get_image(&self, py: Python, name: &str) -> PyResult> { + let vec = self.get_file_internal(name)?; + let inner = Image::from_rgb_png(&vec); + Py::new(py, Image { inner }) + } + + fn get_image_with_alpha(&self, py: Python, name: &str, alpha: &str) -> PyResult> { + let rgb = self.get_file_internal(name)?; + let alpha = self.get_file_internal(alpha)?; + let inner = Image::from_rgb_and_alpha_png(&rgb, &alpha); + Py::new(py, Image { inner }) + } } impl Loader { @@ -216,6 +235,49 @@ } } +#[pyclass(module = "libtouhou")] +#[derive(Debug)] +struct Image { + inner: DynamicImage, +} + +#[pymethods] +impl Image { + #[getter] + fn dimensions(&self) -> (u32, u32) { + self.inner.dimensions() + } + + #[getter] + fn pixels(&self) -> &[u8] { + self.inner.as_bytes() + } +} + +impl Image { + fn from_rgb_png(rgb: &[u8]) -> DynamicImage { + let rgb = image::load_from_memory_with_format(rgb, ImageFormat::Png).unwrap(); + rgb.into_rgba8().into() + } + + fn from_rgb_and_alpha_png(rgb: &[u8], alpha: &[u8]) -> DynamicImage { + let rgb = image::load_from_memory_with_format(rgb, ImageFormat::Png).unwrap(); + let alpha = image::load_from_memory_with_format(alpha, ImageFormat::Png).unwrap(); + + let (width, height) = rgb.dimensions(); + let DynamicImage::ImageLuma8(alpha) = alpha.grayscale() else { + panic!("Alpha couldn’t be converted to grayscale!"); + }; + let pixels = rgb + .pixels() + .zip(alpha.pixels()) + .map(|((_x, _y, rgb), luma)| [rgb[0], rgb[1], rgb[2], luma[0]]) + .flatten() + .collect::>(); + image::RgbaImage::from_vec(width, height, pixels).unwrap().into() + } +} + /// A loader for Touhou files. #[pyclass(module = "libtouhou")] struct Prng { @@ -250,6 +312,9 @@ use super::Loader; #[pymodule_export] + use super::Image; + + #[pymodule_export] use super::Prng; #[pymodule_export] diff --git a/pytouhou/lib/_sdl.pxd b/pytouhou/lib/_sdl.pxd --- a/pytouhou/lib/_sdl.pxd +++ b/pytouhou/lib/_sdl.pxd @@ -146,7 +146,7 @@ void SDL_FreeSurface(SDL_Surface *surface) int SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) - SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) + SDL_Surface *SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 fmt) cdef extern from "SDL_rwops.h" nogil: @@ -157,18 +157,13 @@ int SDL_RWclose(SDL_RWops *context) -cdef extern from "SDL_image.h" nogil: - int IMG_INIT_PNG - - int IMG_Init(int flags) - void IMG_Quit() - SDL_Surface *IMG_LoadPNG_RW(SDL_RWops *src) - - cdef extern from "SDL_pixels.h" nogil: ctypedef struct SDL_Color: Uint8 r, g, b, a + ctypedef enum SDL_PixelFormatEnum: + SDL_PIXELFORMAT_ABGR8888 + cdef extern from "SDL_ttf.h" nogil: ctypedef struct TTF_Font: diff --git a/pytouhou/lib/sdl.pxd b/pytouhou/lib/sdl.pxd --- a/pytouhou/lib/sdl.pxd +++ b/pytouhou/lib/sdl.pxd @@ -89,6 +89,7 @@ cdef class Surface: + cdef bytes data cdef SDL_Surface *surface cdef bint blit(self, Surface other) except True @@ -102,11 +103,9 @@ cdef bint init(Uint32 flags) except True -cdef bint img_init(int flags) except True cdef bint ttf_init() except True cdef bint gl_set_attribute(SDL_GLattr attr, int value) except True -cdef Surface load_png(file_) -cdef Surface create_rgb_surface(int width, int height, int depth, Uint32 rmask=*, Uint32 gmask=*, Uint32 bmask=*, Uint32 amask=*) +cdef Surface create_rgba_surface(bytes pixels, int width, int height) cdef Uint32 get_ticks() nogil cdef void delay(Uint32 ms) nogil cpdef bint show_simple_message_box(unicode message) except True diff --git a/pytouhou/lib/sdl.pyx b/pytouhou/lib/sdl.pyx --- a/pytouhou/lib/sdl.pyx +++ b/pytouhou/lib/sdl.pyx @@ -71,14 +71,12 @@ IF UNAME_SYSNAME == "Windows": SDL_SetMainReady() init(SDL_INIT_VIDEO if self.video else 0) - img_init(IMG_INIT_PNG) ttf_init() keyboard_state = SDL_GetKeyboardState(NULL) def __exit__(self, *args): TTF_Quit() - IMG_Quit() SDL_Quit() @@ -239,6 +237,9 @@ cdef class Surface: + def __init__(self, bytes data=None): + self.data = data + def __dealloc__(self): if self.surface != NULL: SDL_FreeSurface(self.surface) @@ -288,11 +289,6 @@ raise SDLError() -cdef bint img_init(int flags) except True: - if IMG_Init(flags) != flags: - raise SDLError() - - cdef bint ttf_init() except True: if TTF_Init() < 0: raise SDLError() @@ -303,20 +299,13 @@ raise SDLError() -cdef Surface load_png(file_): - data = file_.read() - rwops = SDL_RWFromConstMem(data, len(data)) - surface = Surface() - surface.surface = IMG_LoadPNG_RW(rwops) - SDL_RWclose(rwops) - if surface.surface == NULL: - raise SDLError() - return surface - - -cdef Surface create_rgb_surface(int width, int height, int depth, Uint32 rmask=0, Uint32 gmask=0, Uint32 bmask=0, Uint32 amask=0): - surface = Surface() - surface.surface = SDL_CreateRGBSurface(0, width, height, depth, rmask, gmask, bmask, amask) +cdef Surface create_rgba_surface(bytes data, int width, int height): + surface = Surface(data) + cdef char *pixels = data + depth = 32 + pitch = width * 4 + fmt = SDL_PIXELFORMAT_ABGR8888 + surface.surface = SDL_CreateRGBSurfaceWithFormatFrom(pixels, width, height, depth, pitch, fmt) if surface.surface == NULL: raise SDLError() return surface diff --git a/pytouhou/ui/opengl/texture.pyx b/pytouhou/ui/opengl/texture.pyx --- a/pytouhou/ui/opengl/texture.pyx +++ b/pytouhou/ui/opengl/texture.pyx @@ -19,7 +19,6 @@ glGenTextures, glBindTexture, glTexImage2D, GL_TEXTURE_2D, GLuint, glPushDebugGroup, GL_DEBUG_SOURCE_APPLICATION, glPopDebugGroup) -from pytouhou.lib.sdl cimport load_png, create_rgb_surface from pytouhou.lib.sdl import SDLError from pytouhou.formats.thtx import Texture #TODO: perhaps define that elsewhere? from pytouhou.game.text cimport NativeText @@ -105,24 +104,12 @@ cdef decode_png(loader, first_name, secondary_name): - image_file = load_png(loader.get_file(os.path.basename(first_name))) - width, height = image_file.surface.w, image_file.surface.h - - # Support only 32 bits RGBA. Paletted surfaces are awful to work with. - #TODO: verify it doesn’t blow up on big-endian systems. - new_image = create_rgb_surface(width, height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000) - new_image.blit(image_file) - - if secondary_name: - alpha_file = load_png(loader.get_file(os.path.basename(secondary_name))) - assert (width == alpha_file.surface.w and height == alpha_file.surface.h) - - new_alpha_file = create_rgb_surface(width, height, 24) - new_alpha_file.blit(alpha_file) - - new_image.set_alpha(new_alpha_file) - - return Texture(width, height, -4, new_image.pixels) + if secondary_name is not None: + image = loader.get_image_with_alpha(first_name, secondary_name) + else: + image = loader.get_image(first_name) + width, height = image.dimensions + return Texture(width, height, -4, image.pixels) cdef GLuint load_texture(thtx) except? 65535: diff --git a/pytouhou/ui/sdl/texture.pyx b/pytouhou/ui/sdl/texture.pyx --- a/pytouhou/ui/sdl/texture.pyx +++ b/pytouhou/ui/sdl/texture.pyx @@ -12,7 +12,7 @@ ## GNU General Public License for more details. ## -from pytouhou.lib.sdl cimport load_png, create_rgb_surface +from pytouhou.lib.sdl cimport create_rgba_surface from pytouhou.lib.sdl import SDLError from pytouhou.game.text cimport NativeText @@ -78,21 +78,9 @@ cdef Surface decode_png(loader, first_name, secondary_name): - image_file = load_png(loader.get_file(os.path.basename(first_name))) - width, height = image_file.surface.w, image_file.surface.h - - # Support only 32 bits RGBA. Paletted surfaces are awful to work with. - #TODO: verify it doesn’t blow up on big-endian systems. - new_image = create_rgb_surface(width, height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000) - new_image.blit(image_file) - if secondary_name: - alpha_file = load_png(loader.get_file(os.path.basename(secondary_name))) - assert (width == alpha_file.surface.w and height == alpha_file.surface.h) - - new_alpha_file = create_rgb_surface(width, height, 24) - new_alpha_file.blit(alpha_file) - - new_image.set_alpha(new_alpha_file) - - return new_image + image = loader.get_image_with_alpha(first_name, secondary_name) + else: + image = loader.get_image(first_name) + width, height = image.dimensions + return create_rgba_surface(image.pixels, width, height) diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ COMMAND = 'pkg-config' GLFW_LIBRARIES = ['glfw3'] -SDL_LIBRARIES = ['sdl2', 'SDL2_image', 'SDL2_ttf'] +SDL_LIBRARIES = ['sdl2', 'SDL2_ttf'] GL_LIBRARIES = ['epoxy'] debug = False # True to generate HTML annotations and display infered types. @@ -67,7 +67,6 @@ default_libs = { 'glfw3': '-lglfw', 'sdl2': '-lSDL2', - 'SDL2_image': '-lSDL2_image', 'SDL2_ttf': '-lSDL2_ttf', 'epoxy': '-lepoxy' }