diff --git a/src/Context.zig b/src/Context.zig index 3cd35c0..86cc9e7 100644 --- a/src/Context.zig +++ b/src/Context.zig @@ -28,7 +28,7 @@ buffer_pool: BufferPool = .{}, /// Holds a pixman.Image (and its raw pixels) for the wallpaper /// (same image on all outputs, but scaled separately) -wallpaper_image: ?*WallpaperImage, +wallpaper_image: ?*Wallpaper.Image, // WM Configuration config: *Config, @@ -238,14 +238,16 @@ pub fn manage(context: *Context) void { var out_it = context.wm.outputs.iterator(.forward); while (out_it.next()) |output| { if (context.wallpaper_image == null) { - output.deinitWallpaperLayerSurface(); - } else if (output.surfaces != null) { - output.renderWallpaper() catch |err| { + if (output.wallpaper) |*wp| wp.deinit(); + output.wallpaper = null; + } else if (output.wallpaper) |*wp| { + wp.render() catch |err| { log.err("Wallpaper re-render failed: {}", .{err}); }; } else { - output.initWallpaperLayerSurface() catch |err| { + output.wallpaper = Wallpaper.init(context, output) catch |err| { log.err("Failed to init wallpaper surface: {}", .{err}); + continue; }; } } @@ -259,10 +261,10 @@ pub fn manage(context: *Context) void { } } -fn loadWallpaperImage(config: *Config) ?*WallpaperImage { +fn loadWallpaperImage(config: *Config) ?*Wallpaper.Image { const image_path = config.wallpaper_image_path orelse return null; if (image_path.len == 0) return null; - return WallpaperImage.create(image_path) catch |e| { + return Wallpaper.Image.create(image_path) catch |e| { log.err("Failed to load wallpaper image from path \"{s}\": {s}", .{ image_path, @errorName(e) }); return null; }; @@ -291,7 +293,7 @@ const Config = @import("Config.zig"); const InputManager = @import("InputManager.zig"); const Output = @import("Output.zig"); const TagOverlay = @import("TagOverlay.zig"); -const WallpaperImage = @import("WallpaperImage.zig"); +const Wallpaper = @import("Wallpaper.zig"); const WindowManager = @import("WindowManager.zig"); const XkbBindings = @import("XkbBindings.zig"); diff --git a/src/Output.zig b/src/Output.zig index 2761afb..aad00c4 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -26,15 +26,7 @@ geometry: Rect = .{}, // work with beansprout. usable_geometry: Rect = .{}, -// Information for this Output's wallpaper -wallpaper_render_scale: u31 = 0, -wallpaper_render_width: u31 = 0, -wallpaper_render_height: u31 = 0, - -surfaces: ?struct { - wl_surface: *wl.Surface, - layer_surface: *zwlr.LayerSurfaceV1, -} = null, +wallpaper: ?Wallpaper = null, bar: ?Bar, tag_overlay: ?TagOverlay, @@ -59,9 +51,6 @@ tags: u32 = 0x0001, /// State consumed in manage() phase, reset at end of manage(). pending_manage: PendingManage = .{}, -/// Used for wallpaper rendering management -configured: bool = false, - windows: wl.list.Head(Window, .link), link: wl.list.Link, @@ -134,7 +123,7 @@ pub fn destroy(output: *Output) void { // Deinit optional surfaces if (output.bar) |*bar| bar.deinit(); if (output.tag_overlay) |*tag_overlay| tag_overlay.deinit(); - output.deinitWallpaperLayerSurface(); + if (output.wallpaper) |*wp| wp.deinit(); // Destroy/deinit other Output fields output.tag_layout_overrides.deinit(utils.gpa); @@ -270,11 +259,13 @@ fn riverOutputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.E fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void { switch (event) { .done => { - output.initWallpaperLayerSurface() catch |err| { - const output_name = output.name orelse "some output"; - log.err("failed to add a surface to {s}: {}", .{ output_name, err }); - return; - }; + if (output.context.wallpaper_image != null and output.wallpaper == null) { + output.wallpaper = Wallpaper.init(output.context, output) catch |err| { + const output_name = output.name orelse "some output"; + log.err("failed to add a wallpaper surface to {s}: {}", .{ output_name, err }); + return; + }; + } if (output.bar) |*bar| { // Trigger a full manage cycle if the scale changed so that // fonts are reloaded and bar geometry is recalculated. @@ -283,10 +274,12 @@ fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void } } // Re-render wallpaper if scale changed - if (output.configured and output.scale != output.wallpaper_render_scale) { - output.renderWallpaper() catch |err| { - log.err("Wallpaper render failed: {}", .{err}); - }; + if (output.wallpaper) |*wp| { + if (wp.configured and output.scale != wp.render_scale) { + wp.render() catch |err| { + log.err("Wallpaper render failed: {}", .{err}); + }; + } } }, .scale => |ev| { @@ -305,189 +298,6 @@ fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void } } -pub fn initWallpaperLayerSurface(output: *Output) !void { - if (output.context.wallpaper_image == null) { - // No wallpaper image, so we don't need any surfaces - return; - } - - if (output.surfaces) |_| { - // This output already has a surface, we can exit early - return; - } - - const context = output.context; - - const wl_surface = try context.wl_compositor.createSurface(); - errdefer wl_surface.destroy(); - - const layer_surface = try context.zwlr_layer_shell_v1.getLayerSurface(wl_surface, output.wl_output, .background, "beansprout-wallpaper"); - errdefer layer_surface.destroy(); - - // We don't want our surface to have any input region (default is infinite) - const empty_region = try context.wl_compositor.createRegion(); - defer empty_region.destroy(); - wl_surface.setInputRegion(empty_region); - - // Full surface should be opaque - const opaque_region = try context.wl_compositor.createRegion(); - opaque_region.add(0, 0, output.geometry.width, output.geometry.height); - defer opaque_region.destroy(); - wl_surface.setOpaqueRegion(opaque_region); - - layer_surface.setExclusiveZone(-1); - layer_surface.setAnchor(.{ .top = true, .right = true, .bottom = true, .left = true }); - - output.surfaces = .{ - .wl_surface = wl_surface, - .layer_surface = layer_surface, - }; - context.buffer_pool.surface_count += 1; - - layer_surface.setListener(*Output, wallpaperLayerSurfaceListener, output); - wl_surface.commit(); -} - -pub fn deinitWallpaperLayerSurface(output: *Output) void { - if (output.surfaces) |surfaces| { - surfaces.layer_surface.destroy(); - surfaces.wl_surface.destroy(); - output.context.buffer_pool.surface_count -= 1; - } - - output.surfaces = null; - output.configured = false; -} - -fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV1.Event, output: *Output) void { - switch (event) { - .configure => |ev| { - layer_surface.ackConfigure(ev.serial); - - const width: u31 = @intCast(ev.width); - const height: u31 = @intCast(ev.height); - - if (output.configured and - output.wallpaper_render_width == width and - output.wallpaper_render_height == height and - output.scale == output.wallpaper_render_scale) - { - if (output.surfaces) |surfaces| { - surfaces.wl_surface.commit(); - } else { - log.warn("Output is marked as configured but is missing its surfaces.", .{}); - } - return; - } - - log.debug("configuring wallpaper surface with width {} and height {}", .{ width, height }); - output.wallpaper_render_width = width; - output.wallpaper_render_height = height; - output.configured = true; - - output.renderWallpaper() catch |err| { - log.err("Wallpaper render failed: {}", .{err}); - }; - }, - .closed => { - output.deinitWallpaperLayerSurface(); - }, - } -} - -/// Calculates image_dimension / (output_dimension * scale) -fn calculateScale(image_dimension: c_int, output_dimension: u31, scale: u31) f64 { - const numerator: f64 = @floatFromInt(image_dimension); - const denominator: f64 = @floatFromInt(output_dimension * scale); - - return numerator / denominator; -} - -/// Calculates (image_dimension / dimension_scale - output_dimension) / 2 / dimension_scale -fn calculateTransform(image_dimension: c_int, output_dimension: u31, dimension_scale: f64) f64 { - const numerator1: f64 = @floatFromInt(image_dimension); - const denominator1: f64 = dimension_scale; - const subtrahend: f64 = @floatFromInt(output_dimension); - const numerator2: f64 = numerator1 / denominator1 - subtrahend; - - return numerator2 / 2 / dimension_scale; -} - -/// Render the wallpaper image onto the layer surface -pub fn renderWallpaper(output: *Output) !void { - const context = output.context; - const width = output.wallpaper_render_width; - const height = output.wallpaper_render_height; - const scale = output.scale; - - // Don't have anything to render - if (width == 0 or height == 0 or scale == 0) { - return; - } - // 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.pix_image; - const image_data = image.getData(); - const image_width = image.getWidth(); - const image_height = image.getHeight(); - const image_stride = image.getStride(); - const image_format = image.getFormat(); - - const buffer = try context.buffer_pool.nextBuffer(context.wl_shm, width * scale, height * scale); - - const pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride) orelse { - log.err("Failed to copy the wallpaper image for rendering", .{}); - return error.FailedToCreatePixmanImage; - }; - defer _ = pix.unref(); - - // Calculate image scale - var sx: f64 = @as(f64, @floatFromInt(image_width)) / @as(f64, @floatFromInt(width * scale)); - var sy: f64 = calculateScale(image_height, height, scale); - - const s = if (sx > sy) sy else sx; - sx = s; - sy = s; - - // Calculate translation offsets to center the image on the output. - // If the scaled image is larger than the output, the offset crops equally from both sides. - const tx: f64 = calculateTransform(image_width, width * scale, sx); - const ty: f64 = calculateTransform(image_height, height * scale, sy); - - // Build a combined source-to-destination transform matrix. - // Pixman transforms map destination pixels back to source pixels, so: - // t_scale: maps a destination pixel to the corresponding source pixel (scaling) - // t_trans: shifts the sampling point to center the image - // t = t_trans * t_scale: first scale, then translate (in source space) - var t_scale: pixman.FTransform = undefined; - var t_trans: pixman.FTransform = undefined; - var t: pixman.FTransform = undefined; - // t2 is the fixed-point version of t, which is what pixman actually uses internally - var t2: pixman.Transform = undefined; - - pixman.FTransform.initScale(&t_scale, sx, sy); - pixman.FTransform.initTranslate(&t_trans, tx, ty); - pixman.FTransform.multiply(&t, &t_trans, &t_scale); - _ = pixman.Transform.fromFTransform(&t2, &t); - _ = pix.setTransform(&t2); - _ = pix.setFilter(.best, &[_]pixman.Fixed{}, 0); - - // Combine the transformed source image into the buffer. - pixman.Image.composite32(.src, pix, null, buffer.pixman_image, 0, 0, 0, 0, 0, 0, width * scale, height * scale); - - log.info("render: {}x{} (scaled from {}x{})", .{ width * scale, height * scale, image_width, image_height }); - - // Attach the buffer to the surface - const surfaces = output.surfaces orelse return error.NoSurfaces; - const wl_surface = surfaces.wl_surface; - wl_surface.setBufferScale(scale); - wl_surface.attach(buffer.wl_buffer, 0, 0); - wl_surface.damageBuffer(0, 0, width * scale, height * scale); - wl_surface.commit(); - - output.wallpaper_render_scale = scale; -} - pub fn manage(output: *Output) void { defer output.pending_manage = .{}; @@ -840,15 +650,14 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const river = wayland.client.river; const zwlr = wayland.client.zwlr; -const pixman = @import("pixman"); const utils = @import("utils.zig"); const Rect = utils.Rect; const Bar = @import("Bar.zig"); -const Buffer = @import("Buffer.zig"); const Config = @import("Config.zig"); const Context = @import("Context.zig"); const TagOverlay = @import("TagOverlay.zig"); +const Wallpaper = @import("Wallpaper.zig"); const Window = @import("Window.zig"); const log = std.log.scoped(.Output); diff --git a/src/Wallpaper.zig b/src/Wallpaper.zig new file mode 100644 index 0000000..5fae8a9 --- /dev/null +++ b/src/Wallpaper.zig @@ -0,0 +1,279 @@ +// SPDX-FileCopyrightText: 2026 Ben Buhse +// +// SPDX-License-Identifier: GPL-3.0-only + +const Wallpaper = @This(); + +context: *Context, +output: *Output, + +wl_surface: *wl.Surface, +layer_surface: *zwlr.LayerSurfaceV1, + +render_scale: u31 = 0, +render_width: u31 = 0, +render_height: u31 = 0, +configured: bool = false, + +/// Decoded image data shared across all outputs. +/// Stored on Context; each output's Wallpaper references it for rendering. +pub const Image = struct { + // 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, + + pub fn create(image_path: []const u8) !*Image { + var image = try utils.gpa.create(Image); + errdefer utils.gpa.destroy(image); + + var read_buf: [zigimg.io.DEFAULT_BUFFER_SIZE]u8 = undefined; + image.zigimg_image = try zigimg.Image.fromFilePath(utils.gpa, image_path, &read_buf); + errdefer 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.zigimg_image.pixelFormat() != .rgba32) { + try image.zigimg_image.convert(utils.gpa, .rgba32); + } + + log.debug("image loaded ({}x{})", .{ image.zigimg_image.width, image.zigimg_image.height }); + + const pixels = image.zigimg_image.pixels.rgba32; + const width: c_int = @intCast(image.zigimg_image.width); + const height: c_int = @intCast(image.zigimg_image.height); + const stride: c_int = @intCast(image.zigimg_image.width * 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 => { + image.pix_image = pixman.Image.createBits( + .a8b8g8r8, + width, + height, + @ptrCast(@alignCast(pixels.ptr)), + stride, + ) orelse return error.FailedToCreatePixmanImage; + }, + .big => { + image.argb_pixels = try std.ArrayList(u32).initCapacity(utils.gpa, pixels.len); + errdefer 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; + image.argb_pixels.appendAssumeCapacity((a << 24) | (r << 16) | (g << 8) | b); + } + image.pix_image = pixman.Image.createBits( + .a8r8g8b8, + width, + height, + @ptrCast(@alignCast(image.argb_pixels.items.ptr)), + stride, + ) orelse return error.FailedToCreatePixmanImage; + }, + } + + return image; + } + + pub fn destroy(image: *Image) void { + _ = image.pix_image.unref(); + if (native_endian == .big) image.argb_pixels.deinit(utils.gpa); + image.zigimg_image.deinit(utils.gpa); + + utils.gpa.destroy(image); + } +}; + +pub fn init(context: *Context, output: *Output) !Wallpaper { + const wl_surface = try context.wl_compositor.createSurface(); + errdefer wl_surface.destroy(); + + const layer_surface = try context.zwlr_layer_shell_v1.getLayerSurface(wl_surface, output.wl_output, .background, "beansprout-wallpaper"); + errdefer layer_surface.destroy(); + + // We don't want our surface to have any input region (default is infinite) + const empty_region = try context.wl_compositor.createRegion(); + defer empty_region.destroy(); + wl_surface.setInputRegion(empty_region); + + // Full surface should be opaque + const opaque_region = try context.wl_compositor.createRegion(); + opaque_region.add(0, 0, output.geometry.width, output.geometry.height); + defer opaque_region.destroy(); + wl_surface.setOpaqueRegion(opaque_region); + + layer_surface.setExclusiveZone(-1); + layer_surface.setAnchor(.{ .top = true, .right = true, .bottom = true, .left = true }); + + context.buffer_pool.surface_count += 1; + + layer_surface.setListener(*Output, layerSurfaceListener, output); + wl_surface.commit(); + + return .{ + .context = context, + .output = output, + .wl_surface = wl_surface, + .layer_surface = layer_surface, + }; +} + +pub fn deinit(wallpaper: *Wallpaper) void { + wallpaper.layer_surface.destroy(); + wallpaper.wl_surface.destroy(); + wallpaper.context.buffer_pool.surface_count -= 1; +} + +fn layerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV1.Event, output: *Output) void { + switch (event) { + .configure => |ev| { + layer_surface.ackConfigure(ev.serial); + + const wallpaper = &(output.wallpaper orelse return); + + const width: u31 = @intCast(ev.width); + const height: u31 = @intCast(ev.height); + + if (wallpaper.configured and + wallpaper.render_width == width and + wallpaper.render_height == height and + output.scale == wallpaper.render_scale) + { + wallpaper.wl_surface.commit(); + return; + } + + log.debug("configuring wallpaper surface with width {} and height {}", .{ width, height }); + wallpaper.render_width = width; + wallpaper.render_height = height; + wallpaper.configured = true; + + wallpaper.render() catch |err| { + log.err("Wallpaper render failed: {}", .{err}); + }; + }, + .closed => { + if (output.wallpaper) |*wp| wp.deinit(); + output.wallpaper = null; + }, + } +} + +/// Calculates image_dimension / (output_dimension * scale) +fn calculateScale(image_dimension: c_int, output_dimension: u31, scale: u31) f64 { + const numerator: f64 = @floatFromInt(image_dimension); + const denominator: f64 = @floatFromInt(output_dimension * scale); + + return numerator / denominator; +} + +/// Calculates (image_dimension / dimension_scale - output_dimension) / 2 / dimension_scale +fn calculateTransform(image_dimension: c_int, output_dimension: u31, dimension_scale: f64) f64 { + const numerator1: f64 = @floatFromInt(image_dimension); + const denominator1: f64 = dimension_scale; + const subtrahend: f64 = @floatFromInt(output_dimension); + const numerator2: f64 = numerator1 / denominator1 - subtrahend; + + return numerator2 / 2 / dimension_scale; +} + +/// Render the wallpaper image onto the layer surface +pub fn render(wallpaper: *Wallpaper) !void { + const context = wallpaper.context; + const output = wallpaper.output; + const width = wallpaper.render_width; + const height = wallpaper.render_height; + const scale = output.scale; + + // Don't have anything to render + if (width == 0 or height == 0 or scale == 0) { + return; + } + // Scale our loaded image and then copy it into the Buffer's pixman.Image + const wp_image = context.wallpaper_image orelse return error.MissingWallpaperImage; + const image = wp_image.pix_image; + const image_data = image.getData(); + const image_width = image.getWidth(); + const image_height = image.getHeight(); + const image_stride = image.getStride(); + const image_format = image.getFormat(); + + const buffer = try context.buffer_pool.nextBuffer(context.wl_shm, width * scale, height * scale); + + const pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride) orelse { + log.err("Failed to copy the wallpaper image for rendering", .{}); + return error.FailedToCreatePixmanImage; + }; + defer _ = pix.unref(); + + // Calculate image scale + var sx: f64 = @as(f64, @floatFromInt(image_width)) / @as(f64, @floatFromInt(width * scale)); + var sy: f64 = calculateScale(image_height, height, scale); + + const s = if (sx > sy) sy else sx; + sx = s; + sy = s; + + // Calculate translation offsets to center the image on the output. + // If the scaled image is larger than the output, the offset crops equally from both sides. + const tx: f64 = calculateTransform(image_width, width * scale, sx); + const ty: f64 = calculateTransform(image_height, height * scale, sy); + + // Build a combined source-to-destination transform matrix. + // Pixman transforms map destination pixels back to source pixels, so: + // t_scale: maps a destination pixel to the corresponding source pixel (scaling) + // t_trans: shifts the sampling point to center the image + // t = t_trans * t_scale: first scale, then translate (in source space) + var t_scale: pixman.FTransform = undefined; + var t_trans: pixman.FTransform = undefined; + var t: pixman.FTransform = undefined; + // t2 is the fixed-point version of t, which is what pixman actually uses internally + var t2: pixman.Transform = undefined; + + pixman.FTransform.initScale(&t_scale, sx, sy); + pixman.FTransform.initTranslate(&t_trans, tx, ty); + pixman.FTransform.multiply(&t, &t_trans, &t_scale); + _ = pixman.Transform.fromFTransform(&t2, &t); + _ = pix.setTransform(&t2); + _ = pix.setFilter(.best, &[_]pixman.Fixed{}, 0); + + // Combine the transformed source image into the buffer. + pixman.Image.composite32(.src, pix, null, buffer.pixman_image, 0, 0, 0, 0, 0, 0, width * scale, height * scale); + + log.info("render: {}x{} (scaled from {}x{})", .{ width * scale, height * scale, image_width, image_height }); + + // Attach the buffer to the surface + const wl_surface = wallpaper.wl_surface; + wl_surface.setBufferScale(scale); + wl_surface.attach(buffer.wl_buffer, 0, 0); + wl_surface.damageBuffer(0, 0, width * scale, height * scale); + wl_surface.commit(); + + wallpaper.render_scale = scale; +} + +const std = @import("std"); +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + +const wayland = @import("wayland"); +const wl = wayland.client.wl; +const zwlr = wayland.client.zwlr; +const pixman = @import("pixman"); +const zigimg = @import("zigimg"); + +const utils = @import("utils.zig"); +const Context = @import("Context.zig"); +const Output = @import("Output.zig"); + +const log = std.log.scoped(.Wallpaper); diff --git a/src/WallpaperImage.zig b/src/WallpaperImage.zig deleted file mode 100644 index 921b252..0000000 --- a/src/WallpaperImage.zig +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Ben Buhse -// -// SPDX-License-Identifier: GPL-3.0-only - -const WallpaperImage = @This(); - -// 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, - -pub fn create(image_path: []const u8) !*WallpaperImage { - var wallpaper_image = try utils.gpa.create(WallpaperImage); - errdefer utils.gpa.destroy(wallpaper_image); - - var read_buf: [zigimg.io.DEFAULT_BUFFER_SIZE]u8 = undefined; - 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 (wallpaper_image.zigimg_image.pixelFormat() != .rgba32) { - try wallpaper_image.zigimg_image.convert(utils.gpa, .rgba32); - } - - log.debug("image loaded ({}x{})", .{ wallpaper_image.zigimg_image.width, wallpaper_image.zigimg_image.height }); - - 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; - }, - } - - return wallpaper_image; -} - -pub fn destroy(wallpaper_image: *WallpaperImage) void { - _ = 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"); - -const utils = @import("utils.zig"); - -const log = std.log.scoped(.WallpaperImage);