use premultiplied alpha to avoid black fringes on edges

This commit is contained in:
Rokas Puzonas 2026-09-16 02:53:04 +03:00
parent e317e21cfe
commit 1b20016642
2 changed files with 76 additions and 12 deletions

View File

@ -334,6 +334,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
const clear_color = Color.rgb(40, 40, 40);
Gfx.setClearColor(clear_color);
Gfx.setDefaultSampler(.linear);
const tile_size = Vec2.init(48, 48);
var canvas = CanvasScaler{

View File

@ -96,9 +96,11 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
s[0] = .{
.blend = .{
.enabled = true,
.src_factor_rgb = .SRC_ALPHA,
.src_factor_rgb = .ONE,
.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
.op_rgb = .ADD,
.src_factor_alpha = .ONE,
.dst_factor_alpha = .ONE_MINUS_SRC_ALPHA,
.op_alpha = .ADD,
@ -646,13 +648,19 @@ pub fn deinitTexture(id: Texture.Id) void {
}
}
pub fn setTexture(id: Texture.Id, texture_data: ImageData) void {
const SetTextureOptions = struct {
data: ImageData,
premultiplied: bool = false
};
pub fn setTexture(id: Texture.Id, opts: SetTextureOptions) void {
const self = &g_state;
const texture = self.textures.get(id) orelse {
log.warn("Attempt to set texture that doesn't exist: {}", .{id});
return;
};
const texture_data = opts.data;
const pixel_count = texture_data.width*texture_data.height;
var image_data: sg.ImageData = .{};
@ -671,6 +679,9 @@ pub fn setTexture(id: Texture.Id, texture_data: ImageData) void {
}
}
// TODO: Apply premultiply, will need scratch buffer
assert(opts.premultiplied);
var new_image_desc = sg.ImageDesc{
.type = ._2D,
.width = @intCast(texture_data.width),
@ -874,7 +885,8 @@ pub fn deinitSprite(id: Sprite.Id) void {
const SetSpriteOptions = struct {
image: ImageData,
rect: ?Rect = null
rect: ?Rect = null,
premultiplied: bool = false
};
pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
@ -925,6 +937,10 @@ pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
};
}
if (!opts.premultiplied) {
sprite_data.premultiplyAlpha();
}
var old_size: ?Vec2 = null;
if (sprite.data) |old_sprite_data| {
old_size = Vec2.initFromInt(u32, old_sprite_data.width, old_sprite_data.height);
@ -1026,6 +1042,8 @@ fn repackSpritesheetIfNeeded(id: Spritesheet.Id) void {
repackSpritesheet(id);
}
// TODO: This function is a bit large, there are some easy things that could be moved to separate function.
// e.g. extruding padding.
fn rebuildSpritesheetTextureIfNeeded(id: Spritesheet.Id) void {
const self = &g_state;
const spritesheet = self.spritesheets.get(id) orelse return;
@ -1072,9 +1090,43 @@ fn rebuildSpritesheetTextureIfNeeded(id: Spritesheet.Id) void {
sprite_data.width,
sprite_data.height
);
if (sprite.padding > 0) {
for (0..sprite_data.width) |ox| {
const pixel = image_data.getPixel(x+ox, y);
for (0..sprite.padding) |i| {
@memcpy(image_data.getPixel(x+ox, y-(i+1)), pixel);
}
}
setTexture(spritesheet.texture, image_data);
for (0..sprite_data.width) |ox| {
const pixel = image_data.getPixel(x+ox, y+sprite_data.height-1);
for (0..sprite.padding) |i| {
@memcpy(image_data.getPixel(x+ox, y+sprite_data.height+i), pixel);
}
}
for (0..sprite_data.height) |oy| {
const pixel = image_data.getPixel(x, y+oy);
for (0..sprite.padding) |i| {
@memcpy(image_data.getPixel(x-(i+1), y+oy), pixel);
}
}
for (0..sprite_data.height) |oy| {
const pixel = image_data.getPixel(x+sprite_data.width-1, y+oy);
for (0..sprite.padding) |i| {
@memcpy(image_data.getPixel(x+sprite_data.width+i, y+oy), pixel);
}
}
// TODO: Add padding to corners.
}
}
setTexture(spritesheet.texture, .{
.data = image_data,
.premultiplied = true
});
spritesheet.needs_texture_rebuild = false;
}
@ -1125,17 +1177,11 @@ pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
assert(sprite.position != null);
const sprite_size = Vec2.initFromInt(u32, sprite_data.width, sprite_data.height);
var uv_rect = Rect{
return Rect{
.pos = sprite.position.?.divide(spritesheet.size),
.size = sprite_size.divide(spritesheet.size),
};
const texel_size = Vec2.init(1, 1).divide(spritesheet.size);
uv_rect = uv_rect.shrink(texel_size.divideScalar(8));
return uv_rect;
}
pub fn drawSpriteQuad(id: Sprite.Id, points: Quad, color: Color) void {
@ -1283,7 +1329,7 @@ pub const ImageData = struct {
}
}
pub fn getPixel(self: ImageData, x: u32, y: u32) []u8 {
pub fn getPixel(self: ImageData, x: usize, y: usize) []u8 {
const pixel_index = y * self.width + x;
if (self.pixels) |pixels| {
return switch (pixels) {
@ -1330,6 +1376,23 @@ pub const ImageData = struct {
self.setRect(x, y, width, height, pixel);
}
pub fn premultiplyAlpha(self: ImageData) void {
const pixels = self.pixels orelse return;
if (pixels != .rgba8) return;
const rgba8_pixels = pixels.rgba8;
for (0..self.height) |y| {
for (0..self.width) |x| {
const pixel_index = y * self.width + x;
const rgba8_pixel = rgba8_pixels[(4*pixel_index)..][0..4];
inline for (0..3) |i| {
rgba8_pixel[i] = @intCast(@as(u16, rgba8_pixel[i]) * rgba8_pixel[3] / 255);
}
}
}
}
pub fn copyFrom(
self: ImageData,
source: ImageData,