Complete grayscale filter project

This commit is contained in:
2025-08-10 00:17:12 +01:00
parent f0ab03b972
commit 14566a0bf4
5 changed files with 264 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
const std = @import("std");
const c = @cImport({
@cDefine("_NO_CRT_STDIO_INLINE", "1");
@cInclude("stdio.h");
@cInclude("spng.h");
});
const PngError = error {
CouldNotOpenImage,
CouldNotCloseImage,
CouldNotGetImageHeader,
CouldNotCalcOutputSize,
CouldNotDecodeImage,
CouldNotEncodeImage,
};
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = arena.allocator();
defer arena.deinit();
const path = "pedro_pascal.png";
const fd = try open_image(path, "rb");
defer close_image(fd) catch @panic("Failed to close file");
const ctx = c.spng_ctx_new(0) orelse unreachable;
defer c.spng_ctx_free(ctx);
_ = c.spng_set_png_file(ctx, fd);
var header = try get_png_header(ctx);
const size = try calc_output_size(ctx);
var buffer = try allocator.alloc(u8, size);
@memset(buffer[0..], 0);
try read_data_to_buffer(ctx, buffer[0..]);
try apply_grayscale_filter(buffer[0..]);
try save_image(&header, buffer[0..]);
}
fn open_image(path: [*c]const u8, mode: [*c]const u8) !?*c.FILE {
const fd = c.fopen(path, mode);
if (fd == null) {
return error.CouldNotOpenImage;
}
return fd;
}
fn close_image(fd: ?*c.FILE) !void {
if (c.fclose(fd) != 0) {
return error.CouldNotCloseImage;
}
}
fn get_png_header(ctx: *c.spng_ctx) !c.spng_ihdr {
var header: c.spng_ihdr = undefined;
if (c.spng_get_ihdr(ctx, &header) != 0) {
return error.CouldNotGetImageHeader;
}
return header;
}
fn calc_output_size(ctx: *c.spng_ctx) !u64 {
var output_size: u64 = 0;
if (c.spng_decoded_image_size(ctx, c.SPNG_FMT_RGBA8, &output_size) != 0) {
return error.CouldNotCalcOutputSize;
}
return output_size;
}
fn read_data_to_buffer(ctx: *c.spng_ctx, buffer: []u8) !void {
if (c.spng_decode_image(ctx, buffer.ptr, buffer.len, c.SPNG_FMT_RGBA8, 0) != 0) {
return error.CouldNotDecodeImage;
}
}
fn apply_grayscale_filter(buffer: []u8) !void {
const rfactor: f16 = 0.2126;
const gfactor: f16 = 0.7152;
const bfactor: f16 = 0.0722;
var index: u64 = 0;
while (index < buffer.len) : (index += 4) {
const r: f16 = @floatFromInt(buffer[index]);
const g: f16 = @floatFromInt(buffer[index + 1]);
const b: f16 = @floatFromInt(buffer[index + 2]);
const y: f16 = r * rfactor + g * gfactor + b * bfactor;
buffer[index] = @intFromFloat(y);
buffer[index + 1] = @intFromFloat(y);
buffer[index + 2] = @intFromFloat(y);
}
}
fn save_image(header: *c.spng_ihdr, buffer: []u8) !void {
const path = "pedro_pascal_grayscale.png";
const fd = try open_image(path, "wb");
defer close_image(fd) catch @panic("Failed to close file");
const ctx = c.spng_ctx_new(c.SPNG_CTX_ENCODER) orelse unreachable;
defer c.spng_ctx_free(ctx);
_ = c.spng_set_png_file(ctx, fd);
_ = c.spng_set_ihdr(ctx, header);
if (c.spng_encode_image(ctx, buffer.ptr, buffer.len, c.SPNG_FMT_PNG, c.SPNG_ENCODE_FINALIZE) != 0) {
return error.CouldNotEncodeImage;
}
}