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 }