mirror of
https://github.com/fairyglade/ly.git
synced 2025-12-20 19:24:53 +00:00
Only support dur format v7, set -1 color to black
Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
@@ -17,14 +17,14 @@ fn read_decompress_file(allocator: Allocator, file_path: []const u8) ![]u8 {
|
|||||||
|
|
||||||
var file_reader_buffer: [4096]u8 = undefined;
|
var file_reader_buffer: [4096]u8 = undefined;
|
||||||
var decompress_buffer: [flate.max_window_len]u8 = undefined;
|
var decompress_buffer: [flate.max_window_len]u8 = undefined;
|
||||||
|
|
||||||
var file_reader = file_buffer.reader(&file_reader_buffer);
|
var file_reader = file_buffer.reader(&file_reader_buffer);
|
||||||
var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer);
|
var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer);
|
||||||
|
|
||||||
const file_decompressed = decompress.reader.allocRemaining(allocator, .unlimited) catch {
|
const file_decompressed = decompress.reader.allocRemaining(allocator, .unlimited) catch {
|
||||||
return error.NotValidFile;
|
return error.NotValidFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
return file_decompressed;
|
return file_decompressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ const Frame = struct {
|
|||||||
|
|
||||||
// allocator must be outside of struct as it will fail the json parser
|
// allocator must be outside of struct as it will fail the json parser
|
||||||
pub fn deinit(self: *const Frame, allocator: Allocator) void {
|
pub fn deinit(self: *const Frame, allocator: Allocator) void {
|
||||||
for (self.contents) |con| {
|
for (self.contents) |con| {
|
||||||
allocator.free(con);
|
allocator.free(con);
|
||||||
}
|
}
|
||||||
allocator.free(self.contents);
|
allocator.free(self.contents);
|
||||||
@@ -63,22 +63,20 @@ const DurFormat = struct {
|
|||||||
frames: std.ArrayList(Frame) = undefined,
|
frames: std.ArrayList(Frame) = undefined,
|
||||||
|
|
||||||
pub fn valid(self: *DurFormat) bool {
|
pub fn valid(self: *DurFormat) bool {
|
||||||
if (self.formatVersion != null and
|
if (self.formatVersion != null and
|
||||||
self.colorFormat != null and
|
self.colorFormat != null and
|
||||||
self.encoding != null and
|
self.encoding != null and
|
||||||
self.framerate != null and
|
self.framerate != null and
|
||||||
self.columns != null and
|
self.columns != null and
|
||||||
self.lines != null and
|
self.lines != null and
|
||||||
self.frames.items.len >= 1) {
|
self.frames.items.len >= 1)
|
||||||
|
{
|
||||||
// Oldest example in dur repo was 5 so unsure if older changes json layout
|
// v8 may have breaking changes like changing the colormap xy direction
|
||||||
if (self.formatVersion.? < 5) return false;
|
|
||||||
// v8 may have breaking changes like changing the colormap xy direction
|
|
||||||
// (https://github.com/cmang/durdraw/issues/24)
|
// (https://github.com/cmang/durdraw/issues/24)
|
||||||
if (self.formatVersion.? > 7) return false;
|
if (self.formatVersion.? != 7) return false;
|
||||||
|
|
||||||
// Code currently only supports 16 and 256 color format only
|
// Code currently only supports 16 and 256 color format only
|
||||||
if (!(eql(u8, "16", self.colorFormat.?) or eql(u8, "256", self.colorFormat.?)))
|
if (!(eql(u8, "16", self.colorFormat.?) or eql(u8, "256", self.colorFormat.?)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Code currently supports only utf-8 encoding
|
// Code currently supports only utf-8 encoding
|
||||||
@@ -96,18 +94,16 @@ const DurFormat = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_dur_from_json(self: *DurFormat, allocator: Allocator, dur_json_root: Json.Value) !void {
|
fn parse_dur_from_json(self: *DurFormat, allocator: Allocator, dur_json_root: Json.Value) !void {
|
||||||
var dur_movie = if (dur_json_root.object.get("DurMovie")) |dm| dm.object else return error.NotValidFile;
|
var dur_movie = if (dur_json_root.object.get("DurMovie")) |dm| dm.object else return error.NotValidFile;
|
||||||
|
|
||||||
// Depending on the version, a dur file can have different json object names (ie: columns vs sizeX)
|
// Depending on the version, a dur file can have different json object names (ie: columns vs sizeX)
|
||||||
self.formatVersion = if (dur_movie.get("formatVersion"))|x| x.integer else null;
|
self.formatVersion = if (dur_movie.get("formatVersion")) |x| x.integer else null;
|
||||||
self.colorFormat = if (dur_movie.get("colorFormat")) |x| try allocator.dupe(u8, x.string) else null;
|
self.colorFormat = if (dur_movie.get("colorFormat")) |x| try allocator.dupe(u8, x.string) else null;
|
||||||
self.encoding = if (dur_movie.get("encoding")) |x| try allocator.dupe(u8, x.string) else null;
|
self.encoding = if (dur_movie.get("encoding")) |x| try allocator.dupe(u8, x.string) else null;
|
||||||
self.framerate = if (dur_movie.get("framerate")) |x| x.float else null;
|
self.framerate = if (dur_movie.get("framerate")) |x| x.float else null;
|
||||||
self.columns = if (dur_movie.get("columns")) |x| x.integer
|
self.columns = if (dur_movie.get("columns")) |x| x.integer else if (dur_movie.get("sizeX")) |x| x.integer else null;
|
||||||
else if (dur_movie.get("sizeX")) |x| x.integer else null;
|
|
||||||
|
|
||||||
self.lines = if (dur_movie.get("lines")) |x| x.integer
|
self.lines = if (dur_movie.get("lines")) |x| x.integer else if (dur_movie.get("sizeY")) |x| x.integer else null;
|
||||||
else if (dur_movie.get("sizeY")) |x| x.integer else null;
|
|
||||||
|
|
||||||
const frames = dur_movie.get("frames") orelse return error.NotValidFile;
|
const frames = dur_movie.get("frames") orelse return error.NotValidFile;
|
||||||
|
|
||||||
@@ -117,21 +113,16 @@ const DurFormat = struct {
|
|||||||
var parsed_frame = try Json.parseFromValue(Frame, allocator, json_frame, .{});
|
var parsed_frame = try Json.parseFromValue(Frame, allocator, json_frame, .{});
|
||||||
defer parsed_frame.deinit();
|
defer parsed_frame.deinit();
|
||||||
|
|
||||||
const frame_val = parsed_frame.value;
|
const frame_val = parsed_frame.value;
|
||||||
|
|
||||||
// copy all fields to own the ptrs for deallocation, the parsed_frame has some other
|
// copy all fields to own the ptrs for deallocation, the parsed_frame has some other
|
||||||
// allocated memory making it difficult to deallocate without leaks
|
// allocated memory making it difficult to deallocate without leaks
|
||||||
const frame: Frame = .{
|
const frame: Frame = .{ .frameNumber = frame_val.frameNumber, .delay = frame_val.delay, .contents = try allocator.alloc([]u8, frame_val.contents.len), .colorMap = try allocator.alloc([][]i32, frame_val.colorMap.len) };
|
||||||
.frameNumber = frame_val.frameNumber,
|
|
||||||
.delay = frame_val.delay,
|
|
||||||
.contents = try allocator.alloc([]u8, frame_val.contents.len),
|
|
||||||
.colorMap = try allocator.alloc([][]i32, frame_val.colorMap.len)
|
|
||||||
};
|
|
||||||
|
|
||||||
for (0..frame.contents.len) |i| {
|
for (0..frame.contents.len) |i| {
|
||||||
frame.contents[i] = try allocator.dupe(u8, frame_val.contents[i]);
|
frame.contents[i] = try allocator.dupe(u8, frame_val.contents[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// colorMap is stored as an 3d array where:
|
// colorMap is stored as an 3d array where:
|
||||||
// the outer (i) most array is the horizontal position of the color
|
// the outer (i) most array is the horizontal position of the color
|
||||||
// the middle (j) is the vertical position of the color
|
// the middle (j) is the vertical position of the color
|
||||||
@@ -143,22 +134,24 @@ const DurFormat = struct {
|
|||||||
frame.colorMap[i][j][0] = frame_val.colorMap[i][j][0];
|
frame.colorMap[i][j][0] = frame_val.colorMap[i][j][0];
|
||||||
frame.colorMap[i][j][1] = frame_val.colorMap[i][j][1];
|
frame.colorMap[i][j][1] = frame_val.colorMap[i][j][1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.frames.append(allocator, frame);
|
try self.frames.append(allocator, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_from_file(self: *DurFormat, allocator: Allocator, file_path: [] const u8) !void {
|
pub fn create_from_file(self: *DurFormat, allocator: Allocator, file_path: []const u8) !void {
|
||||||
const file_decompressed = try read_decompress_file(allocator, file_path);
|
const file_decompressed = try read_decompress_file(allocator, file_path);
|
||||||
defer allocator.free(file_decompressed);
|
defer allocator.free(file_decompressed);
|
||||||
|
|
||||||
const parsed = try Json.parseFromSlice(Json.Value, allocator, file_decompressed, .{});
|
const parsed = try Json.parseFromSlice(Json.Value, allocator, file_decompressed, .{});
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
|
|
||||||
try parse_dur_from_json(self, allocator, parsed.value);
|
try parse_dur_from_json(self, allocator, parsed.value);
|
||||||
|
|
||||||
if (!self.valid()) { return error.NotValidFile; }
|
if (!self.valid()) {
|
||||||
|
return error.NotValidFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) DurFormat {
|
pub fn init(allocator: Allocator) DurFormat {
|
||||||
@@ -169,8 +162,8 @@ const DurFormat = struct {
|
|||||||
if (self.colorFormat) |str| self.allocator.free(str);
|
if (self.colorFormat) |str| self.allocator.free(str);
|
||||||
if (self.encoding) |str| self.allocator.free(str);
|
if (self.encoding) |str| self.allocator.free(str);
|
||||||
|
|
||||||
for (self.frames.items) |frame| {
|
for (self.frames.items) |frame| {
|
||||||
frame.deinit(self.allocator);
|
frame.deinit(self.allocator);
|
||||||
}
|
}
|
||||||
self.frames.deinit(self.allocator);
|
self.frames.deinit(self.allocator);
|
||||||
}
|
}
|
||||||
@@ -218,30 +211,30 @@ const rgb_color_16 = [16]u32{
|
|||||||
// Made this table from looking at colormapping in dur source, not sure whats going on with the mapping logic
|
// Made this table from looking at colormapping in dur source, not sure whats going on with the mapping logic
|
||||||
// Array indexes are dur colormappings which value maps to indexes in table above. Only needed for dur 16 color
|
// Array indexes are dur colormappings which value maps to indexes in table above. Only needed for dur 16 color
|
||||||
const durcolor_table_to_color16 = [17]u32{
|
const durcolor_table_to_color16 = [17]u32{
|
||||||
0, // 0 black
|
0, // 0 black
|
||||||
0, // 1 nothing?? dur source did not say why 1 is unused
|
0, // 1 nothing?? dur source did not say why 1 is unused
|
||||||
4, // 2 blue
|
4, // 2 blue
|
||||||
2, // 3 green
|
2, // 3 green
|
||||||
6, // 4 cyan
|
6, // 4 cyan
|
||||||
1, // 5 red
|
1, // 5 red
|
||||||
5, // 6 magenta
|
5, // 6 magenta
|
||||||
3, // 7 yellow
|
3, // 7 yellow
|
||||||
7, // 8 light gray
|
7, // 8 light gray
|
||||||
8, // 9 gray
|
8, // 9 gray
|
||||||
12, // 10 bright blue
|
12, // 10 bright blue
|
||||||
10, // 11 bright green
|
10, // 11 bright green
|
||||||
14, // 12 bright cyan
|
14, // 12 bright cyan
|
||||||
9, // 13 bright red
|
9, // 13 bright red
|
||||||
13, // 14 bright magenta
|
13, // 14 bright magenta
|
||||||
11, // 15 bright yellow
|
11, // 15 bright yellow
|
||||||
15, // 16 bright white
|
15, // 16 bright white
|
||||||
};
|
};
|
||||||
|
|
||||||
fn sixcube_to_channel(sixcube: u32) u32 {
|
fn sixcube_to_channel(sixcube: u32) u32 {
|
||||||
// Although the range top for the extended range is 0xFF, 6 is not divisible into 0xFF,
|
// Although the range top for the extended range is 0xFF, 6 is not divisible into 0xFF,
|
||||||
// so we use 0xF0 instead with a scaler
|
// so we use 0xF0 instead with a scaler
|
||||||
const equal_divisions = 0xF0 / 6;
|
const equal_divisions = 0xF0 / 6;
|
||||||
|
|
||||||
// Since the range is to 0xFF but 6 isn't divisible, we must add a scaler to get it to 0xFF at the last index (5)
|
// Since the range is to 0xFF but 6 isn't divisible, we must add a scaler to get it to 0xFF at the last index (5)
|
||||||
const scaler = 0xFF - (equal_divisions * 5);
|
const scaler = 0xFF - (equal_divisions * 5);
|
||||||
|
|
||||||
@@ -285,7 +278,7 @@ fn convert_256_to_rgb(color_256: u32) u32 {
|
|||||||
const equal_divisions = (0xEE - scaler) / 23; // evals to 0x0A
|
const equal_divisions = (0xEE - scaler) / 23; // evals to 0x0A
|
||||||
|
|
||||||
const channel = scaler + equal_divisions * (color_256 - 232);
|
const channel = scaler + equal_divisions * (color_256 - 232);
|
||||||
|
|
||||||
// gray is equal value of same channel color in rgb
|
// gray is equal value of same channel color in rgb
|
||||||
rgb_color = channel | (channel << 8) | (channel << 16);
|
rgb_color = channel | (channel << 8) | (channel << 16);
|
||||||
}
|
}
|
||||||
@@ -293,7 +286,6 @@ fn convert_256_to_rgb(color_256: u32) u32 {
|
|||||||
return rgb_color;
|
return rgb_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const DurFile = @This();
|
const DurFile = @This();
|
||||||
|
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
@@ -307,20 +299,14 @@ dur_movie: DurFormat,
|
|||||||
frame_width: u32,
|
frame_width: u32,
|
||||||
frame_height: u32,
|
frame_height: u32,
|
||||||
frame_time: u32,
|
frame_time: u32,
|
||||||
is_color_format_16 : bool,
|
is_color_format_16: bool,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator,
|
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_writer: *std.io.Writer, file_path: []const u8, x_offset: u32, y_offset: u32, full_color: bool) !DurFile {
|
||||||
terminal_buffer: *TerminalBuffer,
|
|
||||||
log_writer: *std.io.Writer,
|
|
||||||
file_path: []const u8,
|
|
||||||
x_offset: u32,
|
|
||||||
y_offset: u32,
|
|
||||||
full_color: bool) !DurFile {
|
|
||||||
var dur_movie: DurFormat = .init(allocator);
|
var dur_movie: DurFormat = .init(allocator);
|
||||||
|
|
||||||
// error state is recoverable when thrown to main and results in no background with Dummy in main
|
// error state is recoverable when thrown to main and results in no background with Dummy in main
|
||||||
dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) {
|
dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) {
|
||||||
error.FileNotFound => {
|
error.FileNotFound => {
|
||||||
try log_writer.print("error: dur_file was not found at: {s}\n", .{file_path});
|
try log_writer.print("error: dur_file was not found at: {s}\n", .{file_path});
|
||||||
return err;
|
return err;
|
||||||
},
|
},
|
||||||
@@ -340,18 +326,18 @@ pub fn init(allocator: Allocator,
|
|||||||
|
|
||||||
const buf_width: u32 = @intCast(terminal_buffer.width);
|
const buf_width: u32 = @intCast(terminal_buffer.width);
|
||||||
const buf_height: u32 = @intCast(terminal_buffer.height);
|
const buf_height: u32 = @intCast(terminal_buffer.height);
|
||||||
|
|
||||||
const movie_width: u32 = @intCast(dur_movie.columns.?);
|
const movie_width: u32 = @intCast(dur_movie.columns.?);
|
||||||
const movie_height: u32 = @intCast(dur_movie.lines.?);
|
const movie_height: u32 = @intCast(dur_movie.lines.?);
|
||||||
|
|
||||||
// Clamp to prevent user from exceeding draw window
|
// Clamp to prevent user from exceeding draw window
|
||||||
const x_offset_clamped = std.math.clamp(x_offset, 0, buf_width - 1);
|
const x_offset_clamped = std.math.clamp(x_offset, 0, buf_width - 1);
|
||||||
const y_offset_clamped = std.math.clamp(y_offset, 0, buf_height - 1);
|
const y_offset_clamped = std.math.clamp(y_offset, 0, buf_height - 1);
|
||||||
|
|
||||||
// Ensure if user offsets and frame goes offscreen, it will not overflow draw
|
// Ensure if user offsets and frame goes offscreen, it will not overflow draw
|
||||||
const frame_width = if ((movie_width + x_offset_clamped) < buf_width) movie_width else buf_width - x_offset_clamped;
|
const frame_width = if ((movie_width + x_offset_clamped) < buf_width) movie_width else buf_width - x_offset_clamped;
|
||||||
const frame_height = if ((movie_height + y_offset_clamped) < buf_height) movie_height else buf_height - y_offset_clamped;
|
const frame_height = if ((movie_height + y_offset_clamped) < buf_height) movie_height else buf_height - y_offset_clamped;
|
||||||
|
|
||||||
// Convert dur fps to frames per ms
|
// Convert dur fps to frames per ms
|
||||||
const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?);
|
const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?);
|
||||||
|
|
||||||
@@ -367,7 +353,7 @@ pub fn init(allocator: Allocator,
|
|||||||
.frame_width = frame_width,
|
.frame_width = frame_width,
|
||||||
.frame_height = frame_height,
|
.frame_height = frame_height,
|
||||||
.frame_time = frame_time,
|
.frame_time = frame_time,
|
||||||
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16")
|
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,37 +369,34 @@ fn realloc(_: *DurFile) anyerror!void {}
|
|||||||
|
|
||||||
fn draw(self: *DurFile) void {
|
fn draw(self: *DurFile) void {
|
||||||
const current_frame = self.dur_movie.frames.items[self.frames];
|
const current_frame = self.dur_movie.frames.items[self.frames];
|
||||||
|
|
||||||
for (0..self.frame_height) |y| {
|
for (0..self.frame_height) |y| {
|
||||||
var iter = std.unicode.Utf8View.initUnchecked(current_frame.contents[y]).iterator();
|
var iter = std.unicode.Utf8View.initUnchecked(current_frame.contents[y]).iterator();
|
||||||
|
|
||||||
for (0..self.frame_width) |x| {
|
for (0..self.frame_width) |x| {
|
||||||
const codepoint: u21 = iter.nextCodepoint().?;
|
const codepoint: u21 = iter.nextCodepoint().?;
|
||||||
|
const color_map = current_frame.colorMap[x][y];
|
||||||
|
|
||||||
var color_map_0: u32 = @intCast(current_frame.colorMap[x][y][0]);
|
var color_map_0: u32 = @intCast(if (color_map[0] == -1) 0 else color_map[0]);
|
||||||
var color_map_1: u32 = @intCast(current_frame.colorMap[x][y][1]);
|
var color_map_1: u32 = @intCast(if (color_map[1] == -1) 0 else color_map[1]);
|
||||||
|
|
||||||
if (self.is_color_format_16) {
|
if (self.is_color_format_16) {
|
||||||
color_map_0 = durcolor_table_to_color16[color_map_0];
|
color_map_0 = durcolor_table_to_color16[color_map_0];
|
||||||
color_map_1 = durcolor_table_to_color16[color_map_1 + 1]; // Add 1, dur source stores it like this for some reason
|
color_map_1 = durcolor_table_to_color16[color_map_1 + 1]; // Add 1, dur source stores it like this for some reason
|
||||||
}
|
}
|
||||||
|
|
||||||
const fg_color = if (self.full_color) convert_256_to_rgb(color_map_0) else tb_color_16[color_map_0];
|
const fg_color = if (self.full_color) convert_256_to_rgb(color_map_0) else tb_color_16[color_map_0];
|
||||||
const bg_color = if (self.full_color) convert_256_to_rgb(color_map_1) else tb_color_16[color_map_1];
|
const bg_color = if (self.full_color) convert_256_to_rgb(color_map_1) else tb_color_16[color_map_1];
|
||||||
|
|
||||||
const cell = Cell {
|
const cell = Cell{ .ch = @intCast(codepoint), .fg = fg_color, .bg = bg_color };
|
||||||
.ch = @intCast(codepoint),
|
|
||||||
.fg = fg_color,
|
|
||||||
.bg = bg_color
|
|
||||||
};
|
|
||||||
|
|
||||||
cell.put(x + self.x_offset, y + self.y_offset);
|
cell.put(x + self.x_offset, y + self.y_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const time_current = std.time.milliTimestamp();
|
const time_current = std.time.milliTimestamp();
|
||||||
const delta_time = time_current - self.time_previous;
|
const delta_time = time_current - self.time_previous;
|
||||||
|
|
||||||
// Convert delay from sec to ms
|
// Convert delay from sec to ms
|
||||||
const delay_time: u32 = @intCast(current_frame.delay * 1000);
|
const delay_time: u32 = @intCast(current_frame.delay * 1000);
|
||||||
if (delta_time > (self.frame_time + delay_time)) {
|
if (delta_time > (self.frame_time + delay_time)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user