Merge pull request #2104 from qwerasd205/grayscale-kitty-images

kitty graphics: support loading 1 channel grayscale images
This commit is contained in:
Mitchell Hashimoto
2024-08-15 19:50:48 -07:00
committed by GitHub
6 changed files with 134 additions and 4 deletions

View File

@ -1051,9 +1051,11 @@ pub fn updateFrame(
switch (kv.value_ptr.image) { switch (kv.value_ptr.image) {
.ready => {}, .ready => {},
.pending_gray,
.pending_gray_alpha, .pending_gray_alpha,
.pending_rgb, .pending_rgb,
.pending_rgba, .pending_rgba,
.replace_gray,
.replace_gray_alpha, .replace_gray_alpha,
.replace_rgb, .replace_rgb,
.replace_rgba, .replace_rgba,
@ -1870,6 +1872,7 @@ fn prepKittyImage(
}; };
const new_image: Image = switch (image.format) { const new_image: Image = switch (image.format) {
.gray => .{ .pending_gray = pending },
.gray_alpha => .{ .pending_gray_alpha = pending }, .gray_alpha => .{ .pending_gray_alpha = pending },
.rgb => .{ .pending_rgb = pending }, .rgb => .{ .pending_rgb = pending },
.rgba => .{ .pending_rgba = pending }, .rgba => .{ .pending_rgba = pending },

View File

@ -1147,6 +1147,7 @@ fn prepKittyImage(
}; };
const new_image: Image = switch (image.format) { const new_image: Image = switch (image.format) {
.gray => .{ .pending_gray = pending },
.gray_alpha => .{ .pending_gray_alpha = pending }, .gray_alpha => .{ .pending_gray_alpha = pending },
.rgb => .{ .pending_rgb = pending }, .rgb => .{ .pending_rgb = pending },
.rgba => .{ .pending_rgba = pending }, .rgba => .{ .pending_rgba = pending },
@ -2011,9 +2012,11 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
switch (kv.value_ptr.image) { switch (kv.value_ptr.image) {
.ready => {}, .ready => {},
.pending_gray,
.pending_gray_alpha, .pending_gray_alpha,
.pending_rgb, .pending_rgb,
.pending_rgba, .pending_rgba,
.replace_gray,
.replace_gray_alpha, .replace_gray_alpha,
.replace_rgb, .replace_rgb,
.replace_rgba, .replace_rgba,

View File

@ -47,12 +47,14 @@ pub const Image = union(enum) {
/// ///
/// This data is owned by this union so it must be freed once the /// This data is owned by this union so it must be freed once the
/// image is uploaded. /// image is uploaded.
pending_gray: Pending,
pending_gray_alpha: Pending, pending_gray_alpha: Pending,
pending_rgb: Pending, pending_rgb: Pending,
pending_rgba: Pending, pending_rgba: Pending,
/// This is the same as the pending states but there is a texture /// This is the same as the pending states but there is a texture
/// already allocated that we want to replace. /// already allocated that we want to replace.
replace_gray: Replace,
replace_gray_alpha: Replace, replace_gray_alpha: Replace,
replace_rgb: Replace, replace_rgb: Replace,
replace_rgba: Replace, replace_rgba: Replace,
@ -90,11 +92,17 @@ pub const Image = union(enum) {
pub fn deinit(self: Image, alloc: Allocator) void { pub fn deinit(self: Image, alloc: Allocator) void {
switch (self) { switch (self) {
.pending_gray => |p| alloc.free(p.dataSlice(1)),
.pending_gray_alpha => |p| alloc.free(p.dataSlice(2)), .pending_gray_alpha => |p| alloc.free(p.dataSlice(2)),
.pending_rgb => |p| alloc.free(p.dataSlice(3)), .pending_rgb => |p| alloc.free(p.dataSlice(3)),
.pending_rgba => |p| alloc.free(p.dataSlice(4)), .pending_rgba => |p| alloc.free(p.dataSlice(4)),
.unload_pending => |data| alloc.free(data), .unload_pending => |data| alloc.free(data),
.replace_gray => |r| {
alloc.free(r.pending.dataSlice(1));
r.texture.msgSend(void, objc.sel("release"), .{});
},
.replace_gray_alpha => |r| { .replace_gray_alpha => |r| {
alloc.free(r.pending.dataSlice(2)); alloc.free(r.pending.dataSlice(2));
r.texture.msgSend(void, objc.sel("release"), .{}); r.texture.msgSend(void, objc.sel("release"), .{});
@ -130,9 +138,13 @@ pub const Image = union(enum) {
=> return, => return,
.ready => |obj| .{ .unload_ready = obj }, .ready => |obj| .{ .unload_ready = obj },
.pending_gray => |p| .{ .unload_pending = p.dataSlice(1) },
.pending_gray_alpha => |p| .{ .unload_pending = p.dataSlice(2) }, .pending_gray_alpha => |p| .{ .unload_pending = p.dataSlice(2) },
.pending_rgb => |p| .{ .unload_pending = p.dataSlice(3) }, .pending_rgb => |p| .{ .unload_pending = p.dataSlice(3) },
.pending_rgba => |p| .{ .unload_pending = p.dataSlice(4) }, .pending_rgba => |p| .{ .unload_pending = p.dataSlice(4) },
.replace_gray => |r| .{ .unload_replace = .{
r.pending.dataSlice(1), r.texture,
} },
.replace_gray_alpha => |r| .{ .unload_replace = .{ .replace_gray_alpha => |r| .{ .unload_replace = .{
r.pending.dataSlice(2), r.texture, r.pending.dataSlice(2), r.texture,
} }, } },
@ -160,6 +172,12 @@ pub const Image = union(enum) {
const existing: objc.Object = switch (self.*) { const existing: objc.Object = switch (self.*) {
// For pending, we can free the old data and become pending // For pending, we can free the old data and become pending
// ourselves. // ourselves.
.pending_gray => |p| {
alloc.free(p.dataSlice(1));
self.* = img;
return;
},
.pending_gray_alpha => |p| { .pending_gray_alpha => |p| {
alloc.free(p.dataSlice(2)); alloc.free(p.dataSlice(2));
self.* = img; self.* = img;
@ -194,6 +212,11 @@ pub const Image = union(enum) {
// If we were already pending a replacement, then we free our // If we were already pending a replacement, then we free our
// existing pending data and use the same texture. // existing pending data and use the same texture.
.replace_gray => |r| existing: {
alloc.free(r.pending.dataSlice(1));
break :existing r.texture;
},
.replace_gray_alpha => |r| existing: { .replace_gray_alpha => |r| existing: {
alloc.free(r.pending.dataSlice(2)); alloc.free(r.pending.dataSlice(2));
break :existing r.texture; break :existing r.texture;
@ -217,6 +240,11 @@ pub const Image = union(enum) {
// We now have an existing texture, so set the proper replace key. // We now have an existing texture, so set the proper replace key.
self.* = switch (img) { self.* = switch (img) {
.pending_gray => |p| .{ .replace_gray = .{
.texture = existing,
.pending = p,
} },
.pending_gray_alpha => |p| .{ .replace_gray_alpha = .{ .pending_gray_alpha => |p| .{ .replace_gray_alpha = .{
.texture = existing, .texture = existing,
.pending = p, .pending = p,
@ -289,7 +317,23 @@ pub const Image = union(enum) {
self.* = .{ .replace_rgba = r.* }; self.* = .{ .replace_rgba = r.* };
}, },
// GA needs to be converted to RGBA, too. // Gray and Gray+Alpha need to be converted to RGBA, too.
.pending_gray => |*p| {
const data = p.dataSlice(1);
const rgba = try grayToRgba(alloc, data);
alloc.free(data);
p.data = rgba.ptr;
self.* = .{ .pending_rgba = p.* };
},
.replace_gray => |*r| {
const data = r.pending.dataSlice(2);
const rgba = try grayToRgba(alloc, data);
alloc.free(data);
r.pending.data = rgba.ptr;
self.* = .{ .replace_rgba = r.* };
},
.pending_gray_alpha => |*p| { .pending_gray_alpha => |*p| {
const data = p.dataSlice(2); const data = p.dataSlice(2);
const rgba = try gaToRgba(alloc, data); const rgba = try gaToRgba(alloc, data);
@ -308,6 +352,22 @@ pub const Image = union(enum) {
} }
} }
fn grayToRgba(alloc: Allocator, data: []const u8) ![]u8 {
const pixels = data.len;
var rgba = try alloc.alloc(u8, pixels * 4);
errdefer alloc.free(rgba);
var i: usize = 0;
while (i < pixels) : (i += 1) {
const rgba_i = i * 4;
rgba[rgba_i] = data[i];
rgba[rgba_i + 1] = data[i];
rgba[rgba_i + 2] = data[i];
rgba[rgba_i + 3] = 255;
}
return rgba;
}
fn gaToRgba(alloc: Allocator, data: []const u8) ![]u8 { fn gaToRgba(alloc: Allocator, data: []const u8) ![]u8 {
const pixels = data.len / 2; const pixels = data.len / 2;
var rgba = try alloc.alloc(u8, pixels * 4); var rgba = try alloc.alloc(u8, pixels * 4);

View File

@ -45,12 +45,14 @@ pub const Image = union(enum) {
/// ///
/// This data is owned by this union so it must be freed once the /// This data is owned by this union so it must be freed once the
/// image is uploaded. /// image is uploaded.
pending_gray: Pending,
pending_gray_alpha: Pending, pending_gray_alpha: Pending,
pending_rgb: Pending, pending_rgb: Pending,
pending_rgba: Pending, pending_rgba: Pending,
/// This is the same as the pending states but there is a texture /// This is the same as the pending states but there is a texture
/// already allocated that we want to replace. /// already allocated that we want to replace.
replace_gray: Replace,
replace_gray_alpha: Replace, replace_gray_alpha: Replace,
replace_rgb: Replace, replace_rgb: Replace,
replace_rgba: Replace, replace_rgba: Replace,
@ -88,11 +90,17 @@ pub const Image = union(enum) {
pub fn deinit(self: Image, alloc: Allocator) void { pub fn deinit(self: Image, alloc: Allocator) void {
switch (self) { switch (self) {
.pending_gray => |p| alloc.free(p.dataSlice(1)),
.pending_gray_alpha => |p| alloc.free(p.dataSlice(2)), .pending_gray_alpha => |p| alloc.free(p.dataSlice(2)),
.pending_rgb => |p| alloc.free(p.dataSlice(3)), .pending_rgb => |p| alloc.free(p.dataSlice(3)),
.pending_rgba => |p| alloc.free(p.dataSlice(4)), .pending_rgba => |p| alloc.free(p.dataSlice(4)),
.unload_pending => |data| alloc.free(data), .unload_pending => |data| alloc.free(data),
.replace_gray => |r| {
alloc.free(r.pending.dataSlice(1));
r.texture.destroy();
},
.replace_gray_alpha => |r| { .replace_gray_alpha => |r| {
alloc.free(r.pending.dataSlice(2)); alloc.free(r.pending.dataSlice(2));
r.texture.destroy(); r.texture.destroy();
@ -128,9 +136,13 @@ pub const Image = union(enum) {
=> return, => return,
.ready => |obj| .{ .unload_ready = obj }, .ready => |obj| .{ .unload_ready = obj },
.pending_gray => |p| .{ .unload_pending = p.dataSlice(1) },
.pending_gray_alpha => |p| .{ .unload_pending = p.dataSlice(2) }, .pending_gray_alpha => |p| .{ .unload_pending = p.dataSlice(2) },
.pending_rgb => |p| .{ .unload_pending = p.dataSlice(3) }, .pending_rgb => |p| .{ .unload_pending = p.dataSlice(3) },
.pending_rgba => |p| .{ .unload_pending = p.dataSlice(4) }, .pending_rgba => |p| .{ .unload_pending = p.dataSlice(4) },
.replace_gray => |r| .{ .unload_replace = .{
r.pending.dataSlice(1), r.texture,
} },
.replace_gray_alpha => |r| .{ .unload_replace = .{ .replace_gray_alpha => |r| .{ .unload_replace = .{
r.pending.dataSlice(2), r.texture, r.pending.dataSlice(2), r.texture,
} }, } },
@ -157,6 +169,12 @@ pub const Image = union(enum) {
// the self pointer directly. // the self pointer directly.
const existing: gl.Texture = switch (self.*) { const existing: gl.Texture = switch (self.*) {
// For pending, we can free the old data and become pending ourselves. // For pending, we can free the old data and become pending ourselves.
.pending_gray => |p| {
alloc.free(p.dataSlice(1));
self.* = img;
return;
},
.pending_gray_alpha => |p| { .pending_gray_alpha => |p| {
alloc.free(p.dataSlice(2)); alloc.free(p.dataSlice(2));
self.* = img; self.* = img;
@ -191,6 +209,11 @@ pub const Image = union(enum) {
// If we were already pending a replacement, then we free our // If we were already pending a replacement, then we free our
// existing pending data and use the same texture. // existing pending data and use the same texture.
.replace_gray => |r| existing: {
alloc.free(r.pending.dataSlice(1));
break :existing r.texture;
},
.replace_gray_alpha => |r| existing: { .replace_gray_alpha => |r| existing: {
alloc.free(r.pending.dataSlice(2)); alloc.free(r.pending.dataSlice(2));
break :existing r.texture; break :existing r.texture;
@ -214,6 +237,11 @@ pub const Image = union(enum) {
// We now have an existing texture, so set the proper replace key. // We now have an existing texture, so set the proper replace key.
self.* = switch (img) { self.* = switch (img) {
.pending_gray => |p| .{ .replace_gray = .{
.texture = existing,
.pending = p,
} },
.pending_gray_alpha => |p| .{ .replace_gray_alpha = .{ .pending_gray_alpha => |p| .{ .replace_gray_alpha = .{
.texture = existing, .texture = existing,
.pending = p, .pending = p,
@ -246,6 +274,7 @@ pub const Image = union(enum) {
=> true, => true,
.ready, .ready,
.pending_gray,
.pending_gray_alpha, .pending_gray_alpha,
.pending_rgb, .pending_rgb,
.pending_rgba, .pending_rgba,
@ -287,7 +316,23 @@ pub const Image = union(enum) {
self.* = .{ .replace_rgba = r.* }; self.* = .{ .replace_rgba = r.* };
}, },
// GA needs to be converted to RGBA, too. // Gray and Gray+Alpha need to be converted to RGBA, too.
.pending_gray => |*p| {
const data = p.dataSlice(1);
const rgba = try grayToRgba(alloc, data);
alloc.free(data);
p.data = rgba.ptr;
self.* = .{ .pending_rgba = p.* };
},
.replace_gray => |*r| {
const data = r.pending.dataSlice(2);
const rgba = try grayToRgba(alloc, data);
alloc.free(data);
r.pending.data = rgba.ptr;
self.* = .{ .replace_rgba = r.* };
},
.pending_gray_alpha => |*p| { .pending_gray_alpha => |*p| {
const data = p.dataSlice(2); const data = p.dataSlice(2);
const rgba = try gaToRgba(alloc, data); const rgba = try gaToRgba(alloc, data);
@ -306,6 +351,22 @@ pub const Image = union(enum) {
} }
} }
fn grayToRgba(alloc: Allocator, data: []const u8) ![]u8 {
const pixels = data.len;
var rgba = try alloc.alloc(u8, pixels * 4);
errdefer alloc.free(rgba);
var i: usize = 0;
while (i < pixels) : (i += 1) {
const rgba_i = i * 4;
rgba[rgba_i] = data[i];
rgba[rgba_i + 1] = data[i];
rgba[rgba_i + 2] = data[i];
rgba[rgba_i + 3] = 255;
}
return rgba;
}
fn gaToRgba(alloc: Allocator, data: []const u8) ![]u8 { fn gaToRgba(alloc: Allocator, data: []const u8) ![]u8 {
const pixels = data.len / 2; const pixels = data.len / 2;
var rgba = try alloc.alloc(u8, pixels * 4); var rgba = try alloc.alloc(u8, pixels * 4);

View File

@ -368,9 +368,11 @@ pub const Transmission = struct {
// but they are formats that a png may decode to that we // but they are formats that a png may decode to that we
// support. // support.
gray_alpha, gray_alpha,
gray,
pub fn bpp(self: Format) u8 { pub fn bpp(self: Format) u8 {
return switch (self) { return switch (self) {
.gray => 1,
.gray_alpha => 2, .gray_alpha => 2,
.rgb => 3, .rgb => 3,
.rgba => 4, .rgba => 4,

View File

@ -145,7 +145,7 @@ pub const LoadingImage = struct {
.png => stat_size, .png => stat_size,
// For these formats we have a size we must have. // For these formats we have a size we must have.
.gray_alpha, .rgb, .rgba => |f| size: { .gray, .gray_alpha, .rgb, .rgba => |f| size: {
const bpp = f.bpp(); const bpp = f.bpp();
break :size self.image.width * self.image.height * bpp; break :size self.image.width * self.image.height * bpp;
}, },
@ -432,7 +432,7 @@ pub const LoadingImage = struct {
} }
// Validate our bpp // Validate our bpp
if (bpp < 2 or bpp > 4) { if (bpp < 1 or bpp > 4) {
log.warn("png with unsupported bpp={}", .{bpp}); log.warn("png with unsupported bpp={}", .{bpp});
return error.UnsupportedDepth; return error.UnsupportedDepth;
} }
@ -447,6 +447,7 @@ pub const LoadingImage = struct {
self.image.width = @intCast(width); self.image.width = @intCast(width);
self.image.height = @intCast(height); self.image.height = @intCast(height);
self.image.format = switch (bpp) { self.image.format = switch (bpp) {
1 => .gray,
2 => .gray_alpha, 2 => .gray_alpha,
3 => .rgb, 3 => .rgb,
4 => .rgba, 4 => .rgba,