mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
Merge pull request #1359 from mitchellh/kitty-ga
kitty images: support pngs with greyscale/alpha (bpp=2)
This commit is contained in:
@ -723,8 +723,10 @@ pub fn updateFrame(
|
|||||||
switch (kv.value_ptr.image) {
|
switch (kv.value_ptr.image) {
|
||||||
.ready => {},
|
.ready => {},
|
||||||
|
|
||||||
|
.pending_grey_alpha,
|
||||||
.pending_rgb,
|
.pending_rgb,
|
||||||
.pending_rgba,
|
.pending_rgba,
|
||||||
|
.replace_grey_alpha,
|
||||||
.replace_rgb,
|
.replace_rgb,
|
||||||
.replace_rgba,
|
.replace_rgba,
|
||||||
=> try kv.value_ptr.image.upload(self.alloc, self.device),
|
=> try kv.value_ptr.image.upload(self.alloc, self.device),
|
||||||
@ -1280,6 +1282,7 @@ fn prepKittyGraphics(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const new_image: Image = switch (image.format) {
|
const new_image: Image = switch (image.format) {
|
||||||
|
.grey_alpha => .{ .pending_grey_alpha = pending },
|
||||||
.rgb => .{ .pending_rgb = pending },
|
.rgb => .{ .pending_rgb = pending },
|
||||||
.rgba => .{ .pending_rgba = pending },
|
.rgba => .{ .pending_rgba = pending },
|
||||||
.png => unreachable, // should be decoded by now
|
.png => unreachable, // should be decoded by now
|
||||||
|
@ -853,6 +853,7 @@ fn prepKittyGraphics(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const new_image: Image = switch (image.format) {
|
const new_image: Image = switch (image.format) {
|
||||||
|
.grey_alpha => .{ .pending_grey_alpha = pending },
|
||||||
.rgb => .{ .pending_rgb = pending },
|
.rgb => .{ .pending_rgb = pending },
|
||||||
.rgba => .{ .pending_rgba = pending },
|
.rgba => .{ .pending_rgba = pending },
|
||||||
.png => unreachable, // should be decoded by now
|
.png => unreachable, // should be decoded by now
|
||||||
@ -1802,8 +1803,10 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
|
|||||||
switch (kv.value_ptr.image) {
|
switch (kv.value_ptr.image) {
|
||||||
.ready => {},
|
.ready => {},
|
||||||
|
|
||||||
|
.pending_grey_alpha,
|
||||||
.pending_rgb,
|
.pending_rgb,
|
||||||
.pending_rgba,
|
.pending_rgba,
|
||||||
|
.replace_grey_alpha,
|
||||||
.replace_rgb,
|
.replace_rgb,
|
||||||
.replace_rgba,
|
.replace_rgba,
|
||||||
=> try kv.value_ptr.image.upload(self.alloc),
|
=> try kv.value_ptr.image.upload(self.alloc),
|
||||||
|
@ -47,11 +47,13 @@ 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_grey_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_grey_alpha: Replace,
|
||||||
replace_rgb: Replace,
|
replace_rgb: Replace,
|
||||||
replace_rgba: Replace,
|
replace_rgba: Replace,
|
||||||
|
|
||||||
@ -88,10 +90,16 @@ 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_grey_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_grey_alpha => |r| {
|
||||||
|
alloc.free(r.pending.dataSlice(2));
|
||||||
|
r.texture.msgSend(void, objc.sel("release"), .{});
|
||||||
|
},
|
||||||
|
|
||||||
.replace_rgb => |r| {
|
.replace_rgb => |r| {
|
||||||
alloc.free(r.pending.dataSlice(3));
|
alloc.free(r.pending.dataSlice(3));
|
||||||
r.texture.msgSend(void, objc.sel("release"), .{});
|
r.texture.msgSend(void, objc.sel("release"), .{});
|
||||||
@ -122,8 +130,12 @@ pub const Image = union(enum) {
|
|||||||
=> return,
|
=> return,
|
||||||
|
|
||||||
.ready => |obj| .{ .unload_ready = obj },
|
.ready => |obj| .{ .unload_ready = obj },
|
||||||
|
.pending_grey_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_grey_alpha => |r| .{ .unload_replace = .{
|
||||||
|
r.pending.dataSlice(2), r.texture,
|
||||||
|
} },
|
||||||
.replace_rgb => |r| .{ .unload_replace = .{
|
.replace_rgb => |r| .{ .unload_replace = .{
|
||||||
r.pending.dataSlice(3), r.texture,
|
r.pending.dataSlice(3), r.texture,
|
||||||
} },
|
} },
|
||||||
@ -146,7 +158,14 @@ pub const Image = union(enum) {
|
|||||||
// scenarios where there is no existing texture and we can modify
|
// scenarios where there is no existing texture and we can modify
|
||||||
// the self pointer directly.
|
// the self pointer directly.
|
||||||
const existing: objc.Object = switch (self.*) {
|
const existing: objc.Object = 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_grey_alpha => |p| {
|
||||||
|
alloc.free(p.dataSlice(2));
|
||||||
|
self.* = img;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
.pending_rgb => |p| {
|
.pending_rgb => |p| {
|
||||||
alloc.free(p.dataSlice(3));
|
alloc.free(p.dataSlice(3));
|
||||||
self.* = img;
|
self.* = img;
|
||||||
@ -175,6 +194,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_grey_alpha => |r| existing: {
|
||||||
|
alloc.free(r.pending.dataSlice(2));
|
||||||
|
break :existing r.texture;
|
||||||
|
},
|
||||||
|
|
||||||
.replace_rgb => |r| existing: {
|
.replace_rgb => |r| existing: {
|
||||||
alloc.free(r.pending.dataSlice(3));
|
alloc.free(r.pending.dataSlice(3));
|
||||||
break :existing r.texture;
|
break :existing r.texture;
|
||||||
@ -193,6 +217,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_grey_alpha => |p| .{ .replace_grey_alpha = .{
|
||||||
|
.texture = existing,
|
||||||
|
.pending = p,
|
||||||
|
} },
|
||||||
|
|
||||||
.pending_rgb => |p| .{ .replace_rgb = .{
|
.pending_rgb => |p| .{ .replace_rgb = .{
|
||||||
.texture = existing,
|
.texture = existing,
|
||||||
.pending = p,
|
.pending = p,
|
||||||
@ -259,9 +288,43 @@ pub const Image = union(enum) {
|
|||||||
r.pending.data = rgba.ptr;
|
r.pending.data = rgba.ptr;
|
||||||
self.* = .{ .replace_rgba = r.* };
|
self.* = .{ .replace_rgba = r.* };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// GA needs to be converted to RGBA, too.
|
||||||
|
.pending_grey_alpha => |*p| {
|
||||||
|
const data = p.dataSlice(2);
|
||||||
|
const rgba = try gaToRgba(alloc, data);
|
||||||
|
alloc.free(data);
|
||||||
|
p.data = rgba.ptr;
|
||||||
|
self.* = .{ .pending_rgba = p.* };
|
||||||
|
},
|
||||||
|
|
||||||
|
.replace_grey_alpha => |*r| {
|
||||||
|
const data = r.pending.dataSlice(2);
|
||||||
|
const rgba = try gaToRgba(alloc, data);
|
||||||
|
alloc.free(data);
|
||||||
|
r.pending.data = rgba.ptr;
|
||||||
|
self.* = .{ .replace_rgba = r.* };
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gaToRgba(alloc: Allocator, data: []const u8) ![]u8 {
|
||||||
|
const pixels = data.len / 2;
|
||||||
|
var rgba = try alloc.alloc(u8, pixels * 4);
|
||||||
|
errdefer alloc.free(rgba);
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < pixels) : (i += 1) {
|
||||||
|
const data_i = i * 2;
|
||||||
|
const rgba_i = i * 4;
|
||||||
|
rgba[rgba_i] = data[data_i];
|
||||||
|
rgba[rgba_i + 1] = data[data_i];
|
||||||
|
rgba[rgba_i + 2] = data[data_i];
|
||||||
|
rgba[rgba_i + 3] = data[data_i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgba;
|
||||||
|
}
|
||||||
|
|
||||||
fn rgbToRgba(alloc: Allocator, data: []const u8) ![]u8 {
|
fn rgbToRgba(alloc: Allocator, data: []const u8) ![]u8 {
|
||||||
const pixels = data.len / 3;
|
const pixels = data.len / 3;
|
||||||
var rgba = try alloc.alloc(u8, pixels * 4);
|
var rgba = try alloc.alloc(u8, pixels * 4);
|
||||||
|
@ -45,11 +45,13 @@ 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_grey_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_grey_alpha: Replace,
|
||||||
replace_rgb: Replace,
|
replace_rgb: Replace,
|
||||||
replace_rgba: Replace,
|
replace_rgba: Replace,
|
||||||
|
|
||||||
@ -86,10 +88,16 @@ 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_grey_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_grey_alpha => |r| {
|
||||||
|
alloc.free(r.pending.dataSlice(2));
|
||||||
|
r.texture.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
.replace_rgb => |r| {
|
.replace_rgb => |r| {
|
||||||
alloc.free(r.pending.dataSlice(3));
|
alloc.free(r.pending.dataSlice(3));
|
||||||
r.texture.destroy();
|
r.texture.destroy();
|
||||||
@ -120,8 +128,12 @@ pub const Image = union(enum) {
|
|||||||
=> return,
|
=> return,
|
||||||
|
|
||||||
.ready => |obj| .{ .unload_ready = obj },
|
.ready => |obj| .{ .unload_ready = obj },
|
||||||
|
.pending_grey_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_grey_alpha => |r| .{ .unload_replace = .{
|
||||||
|
r.pending.dataSlice(2), r.texture,
|
||||||
|
} },
|
||||||
.replace_rgb => |r| .{ .unload_replace = .{
|
.replace_rgb => |r| .{ .unload_replace = .{
|
||||||
r.pending.dataSlice(3), r.texture,
|
r.pending.dataSlice(3), r.texture,
|
||||||
} },
|
} },
|
||||||
@ -145,6 +157,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_grey_alpha => |p| {
|
||||||
|
alloc.free(p.dataSlice(2));
|
||||||
|
self.* = img;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
.pending_rgb => |p| {
|
.pending_rgb => |p| {
|
||||||
alloc.free(p.dataSlice(3));
|
alloc.free(p.dataSlice(3));
|
||||||
self.* = img;
|
self.* = img;
|
||||||
@ -173,6 +191,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_grey_alpha => |r| existing: {
|
||||||
|
alloc.free(r.pending.dataSlice(2));
|
||||||
|
break :existing r.texture;
|
||||||
|
},
|
||||||
|
|
||||||
.replace_rgb => |r| existing: {
|
.replace_rgb => |r| existing: {
|
||||||
alloc.free(r.pending.dataSlice(3));
|
alloc.free(r.pending.dataSlice(3));
|
||||||
break :existing r.texture;
|
break :existing r.texture;
|
||||||
@ -191,6 +214,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_grey_alpha => |p| .{ .replace_grey_alpha = .{
|
||||||
|
.texture = existing,
|
||||||
|
.pending = p,
|
||||||
|
} },
|
||||||
|
|
||||||
.pending_rgb => |p| .{ .replace_rgb = .{
|
.pending_rgb => |p| .{ .replace_rgb = .{
|
||||||
.texture = existing,
|
.texture = existing,
|
||||||
.pending = p,
|
.pending = p,
|
||||||
@ -218,6 +246,7 @@ pub const Image = union(enum) {
|
|||||||
=> true,
|
=> true,
|
||||||
|
|
||||||
.ready,
|
.ready,
|
||||||
|
.pending_grey_alpha,
|
||||||
.pending_rgb,
|
.pending_rgb,
|
||||||
.pending_rgba,
|
.pending_rgba,
|
||||||
=> false,
|
=> false,
|
||||||
@ -257,9 +286,43 @@ pub const Image = union(enum) {
|
|||||||
r.pending.data = rgba.ptr;
|
r.pending.data = rgba.ptr;
|
||||||
self.* = .{ .replace_rgba = r.* };
|
self.* = .{ .replace_rgba = r.* };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// GA needs to be converted to RGBA, too.
|
||||||
|
.pending_grey_alpha => |*p| {
|
||||||
|
const data = p.dataSlice(2);
|
||||||
|
const rgba = try gaToRgba(alloc, data);
|
||||||
|
alloc.free(data);
|
||||||
|
p.data = rgba.ptr;
|
||||||
|
self.* = .{ .pending_rgba = p.* };
|
||||||
|
},
|
||||||
|
|
||||||
|
.replace_grey_alpha => |*r| {
|
||||||
|
const data = r.pending.dataSlice(2);
|
||||||
|
const rgba = try gaToRgba(alloc, data);
|
||||||
|
alloc.free(data);
|
||||||
|
r.pending.data = rgba.ptr;
|
||||||
|
self.* = .{ .replace_rgba = r.* };
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gaToRgba(alloc: Allocator, data: []const u8) ![]u8 {
|
||||||
|
const pixels = data.len / 2;
|
||||||
|
var rgba = try alloc.alloc(u8, pixels * 4);
|
||||||
|
errdefer alloc.free(rgba);
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < pixels) : (i += 1) {
|
||||||
|
const data_i = i * 2;
|
||||||
|
const rgba_i = i * 4;
|
||||||
|
rgba[rgba_i] = data[data_i];
|
||||||
|
rgba[rgba_i + 1] = data[data_i];
|
||||||
|
rgba[rgba_i + 2] = data[data_i];
|
||||||
|
rgba[rgba_i + 3] = data[data_i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgba;
|
||||||
|
}
|
||||||
|
|
||||||
fn rgbToRgba(alloc: Allocator, data: []const u8) ![]u8 {
|
fn rgbToRgba(alloc: Allocator, data: []const u8) ![]u8 {
|
||||||
const pixels = data.len / 3;
|
const pixels = data.len / 3;
|
||||||
var rgba = try alloc.alloc(u8, pixels * 4);
|
var rgba = try alloc.alloc(u8, pixels * 4);
|
||||||
|
@ -332,6 +332,11 @@ pub const Transmission = struct {
|
|||||||
rgb, // 24
|
rgb, // 24
|
||||||
rgba, // 32
|
rgba, // 32
|
||||||
png, // 100
|
png, // 100
|
||||||
|
|
||||||
|
// The following are not supported directly via the protocol
|
||||||
|
// but they are formats that a png may decode to that we
|
||||||
|
// support.
|
||||||
|
grey_alpha,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Medium = enum {
|
pub const Medium = enum {
|
||||||
|
@ -270,6 +270,7 @@ pub const LoadingImage = struct {
|
|||||||
|
|
||||||
// Data length must be what we expect
|
// Data length must be what we expect
|
||||||
const bpp: u32 = switch (img.format) {
|
const bpp: u32 = switch (img.format) {
|
||||||
|
.grey_alpha => 2,
|
||||||
.rgb => 3,
|
.rgb => 3,
|
||||||
.rgba => 4,
|
.rgba => 4,
|
||||||
.png => unreachable, // png should be decoded by here
|
.png => unreachable, // png should be decoded by here
|
||||||
@ -380,7 +381,10 @@ pub const LoadingImage = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate our bpp
|
// Validate our bpp
|
||||||
if (bpp != 3 and bpp != 4) return error.UnsupportedDepth;
|
if (bpp < 2 or bpp > 4) {
|
||||||
|
log.warn("png with unsupported bpp={}", .{bpp});
|
||||||
|
return error.UnsupportedDepth;
|
||||||
|
}
|
||||||
|
|
||||||
// Replace our data
|
// Replace our data
|
||||||
self.data.deinit(alloc);
|
self.data.deinit(alloc);
|
||||||
@ -392,6 +396,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) {
|
||||||
|
2 => .grey_alpha,
|
||||||
3 => .rgb,
|
3 => .rgb,
|
||||||
4 => .rgba,
|
4 => .rgba,
|
||||||
else => unreachable, // validated above
|
else => unreachable, // validated above
|
||||||
|
Reference in New Issue
Block a user