lib/gui/src/paint/cpu/sample.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const gui = @import("../../root.zig");
4 const paint_root = @import("../root.zig");
5
6 const pixel = @import("pixel.zig");
7
8 const Color = gui.model.UiColor;
9 const Command = paint_root.command.Command;
10 const Image = paint_root.command.Image;
11 const ImageSet = paint_root.command.ImageSet;
12 const Rect = gui.layout.Rect;
13
14 pub fn imageAt(images: ImageSet, index: u32) ?Image {
15 const image_index: usize = index;
16 if (image_index >= images.images.len) return null;
17 const image = images.images[image_index];
18 image.validate() catch return null;
19 return image;
20 }
21
22 pub fn sourceRect(source: Rect, image: Image) Rect {
23 if (source.width > 0 and source.height > 0) return source;
24 return .{
25 .x = 0,
26 .y = 0,
27 .width = @floatFromInt(image.width),
28 .height = @floatFromInt(image.height),
29 };
30 }
31
32 pub fn sampleCoord(value: f32, limit: u32) u32 {
33 if (!std.math.isFinite(value) or value <= 0) return 0;
34 const floored = @floor(value);
35 const max_value = @as(f32, @floatFromInt(limit - 1));
36 if (floored >= max_value) return limit - 1;
37 return @intFromFloat(floored);
38 }
39
40 pub fn sampleColumn(paint: Command, source: Rect, image: Image, x: f32) u32 {
41 const u = std.math.clamp((x - paint.rect.x) / paint.rect.width, @as(f32, 0), @as(f32, 0.999999));
42 return sampleCoord(source.x + u * source.width, image.width);
43 }
44
45 pub fn sampleRow(paint: Command, source: Rect, image: Image, y: f32) u32 {
46 const v = std.math.clamp((y - paint.rect.y) / paint.rect.height, @as(f32, 0), @as(f32, 0.999999));
47 return sampleCoord(source.y + v * source.height, image.height);
48 }
49
50 pub fn imageSampleKnown(paint: Command, image: Image, source: Rect, x: f32, y: f32) Color {
51 const sx = sampleColumn(paint, source, image, x);
52 const sy = sampleRow(paint, source, image, y);
53 return pixel.unpackRgba(image.pixels[@as(usize, sy) * image.width + sx]);
54 }
55
56 pub fn imageSampleAt(paint: Command, images: ImageSet, x: f32, y: f32) ?Color {
57 const image = imageAt(images, paint.image_index) orelse return null;
58 const source = sourceRect(paint.source, image);
59 return imageSampleKnown(paint, image, source, x, y);
60 }
61
62 test "sampleCoord clamps non-finite, negative, and overflow values" {
63 try std.testing.expectEqual(@as(u32, 0), sampleCoord(-2, 8));
64 try std.testing.expectEqual(@as(u32, 0), sampleCoord(std.math.nan(f32), 8));
65 try std.testing.expectEqual(@as(u32, 7), sampleCoord(9.5, 8));
66 try std.testing.expectEqual(@as(u32, 3), sampleCoord(3.7, 8));
67 }
68
69 test "sampleColumn and sampleRow match the combined sample" {
70 const image = Image{ .width = 4, .height = 4, .pixels = &(@as([16]u32, @splat(0))) };
71 const paint = Command{
72 .kind = .image,
73 .rect = .{ .x = 1, .y = 2, .width = 8, .height = 8 },
74 .clip = .{ .x = 0, .y = 0, .width = 16, .height = 16 },
75 .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 },
76 };
77 const source = sourceRect(.{}, image);
78 var y: f32 = 2.5;
79 while (y < 10) : (y += 1) {
80 var x: f32 = 1.5;
81 while (x < 9) : (x += 1) {
82 const combined = imageSampleKnown(paint, image, source, x, y);
83 _ = combined;
84 const sx = sampleColumn(paint, source, image, x);
85 const sy = sampleRow(paint, source, image, y);
86 try std.testing.expect(sx < image.width);
87 try std.testing.expect(sy < image.height);
88 }
89 }
90 }