Remove manual pixel conversion in WallpaperImage

I used to manually convert pixels from RGBA=>ARGB because Wayland
compositors are only guaranteed to support XRGB and ARGB, but zigimg
doesn't include either of those. This was a bit slow, especially on
debug builds (though not *super* noticeable on release builds).

I realized, though, that zigimg's Rgba32 format is the same as pixman's
a8b8g8r8... on little-endian. I kept the old code just in case someone
out there happens to be running beansprout on MIPS, but I have not
tested it.
This commit is contained in:
Ben Buhse 2026-02-22 18:15:04 -06:00
commit 006bae3532
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
2 changed files with 56 additions and 24 deletions

View file

@ -457,7 +457,7 @@ pub fn renderWallpaper(output: *Output) !void {
}
// Scale our loaded image and then copy it into the Buffer's pixman.Image
const wallpaper_image = context.wallpaper_image orelse return error.MissingWallpaperImage;
const image = wallpaper_image.image;
const image = wallpaper_image.pix_image;
const image_data = image.getData();
const image_width = image.getWidth();
const image_height = image.getHeight();

View file

@ -4,8 +4,15 @@
const WallpaperImage = @This();
image: *pixman.Image,
pixels: std.ArrayList(u32),
// This is used as the backing store for the pixman image
// It's the actual image (png, jpeg, etc.) decoded into pixels.
zigimg_image: zigimg.Image,
// Only used on big-endian; holds manually converted ARGB pixel data.
// On BE: std.ArrayList(u32), on LE: void
argb_pixels: if (native_endian == .big) std.ArrayList(u32) else void = if (native_endian == .big) .empty else {},
// This is the actual scaled, transformed, and rendered image
pix_image: *pixman.Image,
// TODO: Make image_path nullable, if null, do a single color with a single_pixel_buffer instead(?)
pub fn create(image_path: []const u8) !*WallpaperImage {
@ -13,44 +20,69 @@ pub fn create(image_path: []const u8) !*WallpaperImage {
errdefer utils.gpa.destroy(wallpaper_image);
var read_buf: [zigimg.io.DEFAULT_BUFFER_SIZE]u8 = undefined;
var image = try zigimg.Image.fromFilePath(utils.gpa, image_path, &read_buf);
defer image.deinit(utils.gpa);
wallpaper_image.zigimg_image = try zigimg.Image.fromFilePath(utils.gpa, image_path, &read_buf);
errdefer wallpaper_image.zigimg_image.deinit(utils.gpa);
// We don't want to deal with all the possible formats,
// so let's just convert to one we can use with pixman.
if (image.pixelFormat() != .rgba32) {
try image.convert(utils.gpa, .rgba32);
if (wallpaper_image.zigimg_image.pixelFormat() != .rgba32) {
try wallpaper_image.zigimg_image.convert(utils.gpa, .rgba32);
}
log.debug("image loaded ({}x{})", .{ image.width, image.height });
log.debug("image loaded ({}x{})", .{ wallpaper_image.zigimg_image.width, wallpaper_image.zigimg_image.height });
const pixels = image.pixels.rgba32;
// We have to manually convert to argb --
// It's only guaranteed that Wayland compositors will have xrgb and argb support but zigimg doesn't have either of those.
wallpaper_image.pixels = try std.ArrayList(u32).initCapacity(utils.gpa, pixels.len);
errdefer wallpaper_image.pixels.deinit(utils.gpa);
for (0..pixels.len) |i| {
const a: u32 = @intCast(pixels[i].a);
const r: u32 = @intCast(pixels[i].r);
const g: u32 = @intCast(pixels[i].g);
const b: u32 = @intCast(pixels[i].b);
const new_val: u32 = (a << 24) + (r << 16) + (g << 8) + b;
wallpaper_image.pixels.appendAssumeCapacity(new_val);
const pixels = wallpaper_image.zigimg_image.pixels.rgba32;
const width: c_int = @intCast(wallpaper_image.zigimg_image.width);
const height: c_int = @intCast(wallpaper_image.zigimg_image.height);
const stride: c_int = @intCast(wallpaper_image.zigimg_image.width * wallpaper_image.zigimg_image.pixelFormat().pixelStride());
// zigimg's Rgba32 is an extern struct {r, g, b, a}, which actually matches pixman's a8b8g8r8
// (u32 with R at bits 0-7, A at bits 24-31) on little endian machines. That means we can actually
// use zigimg's pixel data directly. On big-endian we keep the manual conversion I used to use.
switch (native_endian) {
.little => {
wallpaper_image.pix_image = pixman.Image.createBits(
.a8b8g8r8,
width,
height,
@ptrCast(@alignCast(pixels.ptr)),
stride,
) orelse return error.FailedToCreatePixmanImage;
},
.big => {
wallpaper_image.argb_pixels = try std.ArrayList(u32).initCapacity(utils.gpa, pixels.len);
errdefer wallpaper_image.argb_pixels.deinit(utils.gpa);
for (pixels) |px| {
const a: u32 = px.a;
const r: u32 = px.r;
const g: u32 = px.g;
const b: u32 = px.b;
wallpaper_image.argb_pixels.appendAssumeCapacity((a << 24) | (r << 16) | (g << 8) | b);
}
wallpaper_image.pix_image = pixman.Image.createBits(
.a8r8g8b8,
width,
height,
@ptrCast(@alignCast(wallpaper_image.argb_pixels.items.ptr)),
stride,
) orelse return error.FailedToCreatePixmanImage;
},
}
wallpaper_image.image = pixman.Image.createBits(.a8r8g8b8, @intCast(image.width), @intCast(image.height), @ptrCast(@alignCast(wallpaper_image.pixels.items.ptr)), @intCast(image.width * image.pixelFormat().pixelStride())) orelse return error.FailedToCreatePixmanImage;
return wallpaper_image;
}
pub fn destroy(wallpaper_image: *WallpaperImage) void {
_ = wallpaper_image.image.unref();
wallpaper_image.pixels.deinit(utils.gpa);
_ = wallpaper_image.pix_image.unref();
if (native_endian == .big) wallpaper_image.argb_pixels.deinit(utils.gpa);
wallpaper_image.zigimg_image.deinit(utils.gpa);
utils.gpa.destroy(wallpaper_image);
}
const std = @import("std");
const builtin = @import("builtin");
const native_endian = builtin.cpu.arch.endian();
const pixman = @import("pixman");
const zigimg = @import("zigimg");