The Image family, and 22 shapes

Image first and deliberately: it is CPU-side, so it is the only large piece of
raylib that can be asserted headlessly rather than looked at. gen-image-color,
the pixel reads, both flips, a PNG round trip through export and load, and the
resize and crop dimensions and contents are all in the table at -O2 and -O0.
The shapes, text and timing calls are observed only, by running sand under Xvfb
and looking, and the program and NEXT.md both say which is which.

Five permutations were run red and restored: Image's width against height and
mipmaps against format, GetImageColor's two indices, the two flip wrappers
bound to each other, and the crop rectangle's width against height. The third
of those also broke the export and load lines, which is what makes the PNG
round trip verified rather than merely plausible.

Two corrections to the brief it was given. MeasureText is not headless material
- it measures with the default font, which only InitWindow loads, and a C probe
returns 0 - and the same is true of the frame-time and screen-size calls. And
the raylib.h on this machine is 5.1-dev while the linked library is 5.5, so
every signature was checked against nm -D instead: IsImageValid rather than
IsImageReady, and DrawRectangleRoundedLines takes no thickness.

Font loading is refused by name. A Font carries a Texture2D, a Rectangle* and a
GlyphInfo*, and a GlyphInfo carries an Image - two more aggregates and two owned
arrays, for something with no headless test.
This commit is contained in:
Joseph Ferano 2026-09-11 19:48:03 +07:00
commit e4586b55c7
6 changed files with 1047 additions and 6 deletions

26
NEXT.md
View File

@ -295,10 +295,36 @@ turned out to check nothing.
computes four numbers from four different field pairs, and the point/rect
predicates turn the wrong way when width and height are exchanged.
- **Scalars in, fields out is the strongest shape there is**, and the Image
family is where it was finally available. `GenImageColor(4, 2, colour)` is
handed two integers and answers with a struct reading 4, 2, 1, 7 — four
distinct values in four adjacent `i32` slots, with no input struct for a
permutation to cancel against. That pins `Image` completely, including that
`data` is present and first; `Texture2D` could never be pinned that way
because nothing without a GPU reads its width, height or mipmaps at all.
- **A non-square image is an axis discriminator.** `GetImageColor` indexes
`y*width + x`, so on a 4-wide, 2-tall image `(3,0)` exists and its transpose
does not: exchange the wrapper's `x` and `y` and the read goes out of bounds
and answers transparent black. `ImageFlipHorizontal` against
`ImageFlipVertical` says the same thing twice more.
- **A file is external ground truth**, so `ExportImage` then `LoadImage` is not
the symmetric round trip the rest of the package has to avoid — stb's encoder
and decoder agree with each other, not with Flan's field order. Verified
red by the `x`/`y` permutation above.
The rule that falls out: make raylib **compute** something whose answer differs
per axis, then verify the test can fail by permuting the fields and watching it
go red. A case not verified that way is decoration.
One correction to an assumption that has now cost two lanes a guess:
**`MeasureText` is not headless material.** It measures with the default font,
`LoadFontDefault` is not exported, and nothing but `InitWindow` loads it — so
with no window it answers 0 for every string. Measured against
`libraylib.so.550`, not reasoned about. `GetFrameTime`, `GetTime` and
`GetScreenWidth`/`Height` are all 0 headless for the same kind of reason. All
five are bound, and all five are exercised by running `sand.flan` and looking,
which is the whole of what can be claimed for them.
## Packages
`lib/load.ml` resolves `(import rl "vendor:raylib")` before the checker runs.

212
sand.flan
View File

@ -35,6 +35,13 @@
(defvar brush rl/Texture2D)
(defvar brush-ok bool)
;; The same sheet a second time, mirrored on the CPU before the GPU ever sees
;; it. That is what the Image family is for, and this is its only call site:
;; nothing headless can make a texture, so load-texture-from-image would
;; otherwise be bound and never called, which is the same as not bound.
(defvar brush-mirrored rl/Texture2D)
(defvar brush-mirrored-ok bool)
;; A missing file is not a crash and not silence: LoadTexture hands back a
;; texture with an id of 0, every draw with it is a no-op, and the program
;; looks like it has a drawing bug. So it is asked and said once, here.
@ -42,7 +49,16 @@
(set brush (rl/load-texture "brush.png"))
(set brush-ok (rl/texture-valid? brush))
(unless brush-ok
(print-line "sand: cannot load brush.png — drawing the cursor is off")))
(print-line "sand: cannot load brush.png — drawing the cursor is off"))
;; The other route to a texture: the file into RAM, changed there, and only
;; then uploaded. An Image that failed to load has a null buffer and
;; unloading it is still safe, so there is one unload and not two.
(let [sheet (rl/load-image "brush.png")]
(when (rl/image-valid? sheet)
(rl/image-flip-horizontal (addr sheet))
(set brush-mirrored (rl/load-texture-from-image sheet)))
(rl/unload-image sheet))
(set brush-mirrored-ok (rl/texture-valid? brush-mirrored)))
;; Four draws, one per shape the call comes in, because none of them can be in
;; the acceptance table. The cursor picks one frame out of the sheet and so
@ -60,11 +76,69 @@
(rl/draw-texture brush 20 50 rl/white)
(rl/draw-texture-v brush (rl/Vector2 {:x 44.0 :y 50.0})
(rl/get-color (nth sim/colors sim/current-color)))
(rl/draw-texture-ex brush (rl/Vector2 {:x 72.0 :y 46.0}) 0.0 2.0 rl/white))))
(rl/draw-texture-ex brush (rl/Vector2 {:x 72.0 :y 46.0}) 0.0 2.0 rl/white)))
;; The mirrored one beside them, scaled up so the flip is visible rather
;; than eight pixels wide. If the two badges look the same, either the flip
;; did nothing or the upload took the unedited buffer.
(when brush-mirrored-ok
(rl/draw-texture-ex brush-mirrored (rl/Vector2 {:x 110.0 :y 46.0})
0.0 2.0 rl/white)))
;; ── The view ────────────────────────────────────────────────────────
;;
;; begin-mode-2d and end-mode-2d were bound along with Camera2D and then
;; called by nothing at all, which is the same as not having bound them. They
;; are load-bearing here now: the grid is drawn through this camera, the arrow
;; keys pan it, comma and period zoom, and `paint` has to undo the transform
;; with get-screen-to-world-2d or the sand lands somewhere other than the
;; cursor. That last part is what makes this a check rather than decoration —
;; a camera plumbed in wrongly shows up as grains appearing in the wrong
;; place, at once, while zoomed.
(defvar view rl/Camera2D)
;; A fresh (Camera2D {}) has a zoom of 0, which is singular: both conversions
;; hand back NaN and nothing draws. 1.0 is the identity.
(defn reset-view []
(set view (rl/Camera2D {:offset (rl/Vector2 {:x 0.0 :y 0.0})
:target (rl/Vector2 {:x 0.0 :y 0.0})
:rotation 0.0
:zoom 1.0})))
(defn set-view [target-x f32 target-y f32 zoom f32]
(set view (rl/Camera2D {:offset (.offset view)
:target (rl/Vector2 {:x target-x :y target-y})
:rotation (.rotation view)
:zoom zoom})))
;; Panning is world units per second and zooming is a factor per second, so
;; neither changes with the frame rate. That is the whole of what
;; get-frame-time is for, and a loop that assumed it hit its target fps would
;; be a loop that moves differently on a slower machine.
(defn move-view []
(let [dt (rl/get-frame-time)
pan (* (f32 600.0) dt)
tx (.x (.target view))
ty (.y (.target view))
zoom (.zoom view)]
(when (rl/key-down? :left) (set tx (- tx pan)))
(when (rl/key-down? :right) (set tx (+ tx pan)))
(when (rl/key-down? :up) (set ty (- ty pan)))
(when (rl/key-down? :down) (set ty (+ ty pan)))
(when (rl/key-down? :comma) (set zoom (- zoom (* zoom dt))))
(when (rl/key-down? :period) (set zoom (+ zoom (* zoom dt))))
;; Clamped away from 0 for the reason above, and away from the far end
;; because a cell is 5 pixels and there is no point past a screenful of
;; one of them.
(set-view tx ty (min (f32 8.0) (max (f32 0.125) zoom)))
(when (rl/key-pressed? :zero) (reset-view))))
;; Locals are assignable places (spec-memory.md); parameters are not.
;;
;; The mouse is in screen pixels and the grid is in world cells, and with a
;; camera in the way those stopped being the same thing — so this is the one
;; place get-screen-to-world-2d is not a test case but a requirement.
(defn paint []
(let [m (rl/get-mouse-position)
(let [m (rl/get-screen-to-world-2d (rl/get-mouse-position) view)
row (/ (i32 (.y m)) sim/cell-size)
col (/ (i32 (.x m)) sim/cell-size)]
(sim/paint-at row col)))
@ -81,12 +155,12 @@
;; reload rejects it. See plan.org "What redefinition cannot do".
(defn game-update []
(when (rl/key-pressed? :r) (sim/clear-grid))
(move-view)
(when (rl/mouse-button-down? :left) (paint))
(when (rl/mouse-button-released? :left) (sim/next-color))
(sim/step))
(defn game-draw []
(rl/clear-background rl/black)
(defn draw-grid []
(dotimes [row sim/rows]
(dotimes [col sim/cols]
(let [c (at sim/grid row col)]
@ -94,8 +168,131 @@
(rl/draw-rectangle (i32 (* col sim/cell-size))
(i32 (* row sim/cell-size))
sim/cell-size sim/cell-size
(rl/get-color c))))))
(rl/get-color c)))))))
;; Drawn inside the camera, in world units, so every one of these moves and
;; scales with the grid. That is the point: a shape binding that is subtly
;; wrong is easiest to see when it is supposed to sit exactly on the cursor
;; and does not.
(defn draw-world-cursor []
(let [p (rl/get-screen-to-world-2d (rl/get-mouse-position) view)
tint (rl/get-color (nth sim/colors sim/current-color))
x (.x p)
y (.y p)
r (f32 (* sim/brush-size sim/cell-size))]
;; The brush's actual reach, as a ring, plus a thinner circle outside it.
(rl/draw-ring p (* r (f32 0.9)) r (f32 0.0) (f32 360.0) 48 tint)
(rl/draw-circle-lines-v p (+ r (f32 6.0)) tint)
;; A crosshair: two thin lines and one thick one, which is three separate
;; raylib calls with three different shapes of argument.
(rl/draw-line-v (rl/Vector2 {:x (- x r) :y y})
(rl/Vector2 {:x (+ x r) :y y}) tint)
(rl/draw-line-v (rl/Vector2 {:x x :y (- y r)})
(rl/Vector2 {:x x :y (+ y r)}) tint)
(rl/draw-line-ex (rl/Vector2 {:x (- x (f32 4.0)) :y y})
(rl/Vector2 {:x (+ x (f32 4.0)) :y y}) (f32 3.0) rl/white)
;; The exact world point: a filled dot, and one pixel of white on top of
;; it. Both are the Vector2 forms, so they land where the ring's centre
;; is and not somewhere an integer cast put them.
(rl/draw-circle-v p (f32 5.0) tint)
(rl/draw-pixel-v p rl/white)
;; A pointer above the cursor. Counter-clockwise, because raylib culls the
;; other winding and draws nothing — which looks exactly like a broken
;; binding and is why the outline is drawn over it as a control.
(let [tip (rl/Vector2 {:x x :y (- y (+ r (f32 26.0)))})
left (rl/Vector2 {:x (- x (f32 12.0)) :y (- y (+ r (f32 6.0)))})
rght (rl/Vector2 {:x (+ x (f32 12.0)) :y (- y (+ r (f32 6.0)))})]
(rl/draw-triangle tip left rght tint)
(rl/draw-triangle-lines tip left rght rl/white))
;; And the world's own edge, so panning has something to pan against.
(rl/draw-rectangle-lines-ex
(rl/Rectangle {:x 0.0 :y 0.0
:width (f32 sim/screen-width)
:height (f32 sim/screen-height)})
(f32 2.0) (rl/get-color 0x303030FF))))
;; Drawn outside the camera, in screen pixels, so it stays put while the world
;; moves underneath it. Nothing here can be asserted — it needs a GL context —
;; so it is built to be *looked* at: every shape binding appears once, and each
;; one is asymmetric enough that a wrapper with its arguments crossed is
;; visible rather than merely different.
(defn draw-hud []
(let [title "SAND"
keys "arrows pan , . zoom 0 reset r clear"
;; measure-text is what sizes the panel, so the box fits the string
;; rather than a number somebody guessed. Headless it answers 0 for
;; everything, which is why it is not in the acceptance table.
w (max (rl/measure-text title 30) (rl/measure-text keys 20))
h 72
x 24
y (- (rl/get-screen-height) (+ h 24))
panel (rl/Rectangle {:x (f32 (- x 12)) :y (f32 (- y 12))
:width (f32 (+ w 24)) :height (f32 (+ h 24))})]
(rl/draw-rectangle-rounded panel (f32 0.2) 8 (rl/get-color 0x101018E0))
;; Both outline forms, one inside the other: the plain one has no
;; thickness in raylib 5.5 and the -ex one is where thickness went.
(rl/draw-rectangle-rounded-lines panel (f32 0.2) 8 (rl/get-color 0x404060FF))
(rl/draw-rectangle-rounded-lines-ex panel (f32 0.2) 8 (f32 2.0)
(rl/get-color 0x6060A0FF))
(rl/draw-text title x y 30 rl/white)
(rl/draw-text keys x (+ y 40) 20 (rl/get-color 0xA0A0B0FF))
;; The palette, along the bottom right. The selected colour is the one
;; with a ring round it, so `current-color` is readable off the screen.
(let [sw (- (rl/get-screen-width) 40)
sh (- (rl/get-screen-height) 40)]
(dotimes [i (len sim/colors)]
(let [cx (- sw (* (- (len sim/colors) (+ i 1)) 46))
c (rl/get-color (nth sim/colors i))]
(rl/draw-circle cx sh (f32 16.0) c)
(when (= i sim/current-color)
(rl/draw-circle-lines cx sh (f32 22.0) rl/white))))
;; A zoom read-out with no number in it, because there is no string
;; formatting yet: the bar's length is the zoom. draw-rectangle-rec,
;; draw-rectangle-v and draw-rectangle-lines are the three remaining
;; rectangle shapes, so all three are here rather than invented
;; elsewhere.
(let [bx (f32 (- sw 220))
by (f32 (- sh 60))
fill (* (f32 200.0) (min (f32 1.0) (/ (.zoom view) (f32 8.0))))]
(rl/draw-rectangle-rec (rl/Rectangle {:x bx :y by :width (f32 200.0)
:height (f32 10.0)})
(rl/get-color 0x202028FF))
(rl/draw-rectangle-v (rl/Vector2 {:x bx :y by})
(rl/Vector2 {:x fill :y (f32 10.0)})
(rl/get-color 0x6060A0FF))
(rl/draw-rectangle-lines (- sw 220) (- sh 60) 200 10
(rl/get-color 0x8080C0FF))
;; The tick at the identity zoom, and a single pixel marking its left
;; end — the integer forms of the line and pixel calls.
(rl/draw-line (+ (- sw 220) 25) (- sh 66) (+ (- sw 220) 25) (- sh 46)
rl/white)
(rl/draw-pixel (- sw 220) (- sh 66) rl/white))
;; An ellipse, deliberately wider than it is tall so that exchanging its
;; two radii would be obvious, and a ring whose sweep is driven by
;; get-time so that something on screen proves the clock is running.
(let [ex (- sw 320)
ey (- sh 20)
spin (f32 (* 60.0 (rl/get-time)))]
(rl/draw-ellipse ex ey (f32 26.0) (f32 12.0) (rl/get-color 0x303040FF))
(rl/draw-ellipse-lines ex ey (f32 26.0) (f32 12.0)
(rl/get-color 0x8080C0FF))
(rl/draw-ring-lines (rl/Vector2 {:x (f32 ex) :y (f32 ey)})
(f32 30.0) (f32 34.0) spin (+ spin (f32 270.0)) 32
rl/white)))))
(defn game-draw []
(rl/clear-background rl/black)
;; Everything between these two is in world space and moves with the camera.
(rl/begin-mode-2d view)
(draw-grid)
(draw-world-cursor)
(rl/end-mode-2d)
;; And everything after it is in screen pixels again.
(draw-brush)
(draw-hud)
(rl/draw-fps 20 20))
(defn main []
@ -103,10 +300,13 @@
(rl/init-window sim/screen-width sim/screen-height "SAND")
(defer (rl/close-window))
(rl/set-target-fps 120)
;; Before anything draws: a zero zoom is singular and nothing would appear.
(reset-view)
;; After the window, never before: LoadTexture uploads to the GPU and there
;; is no GPU to upload to until InitWindow has made a context.
(load-brush)
(defer (rl/unload-texture brush))
(defer (rl/unload-texture brush-mirrored))
;; The dev agent listens on a socket for redefinitions and hands them over;
;; (agent/poll) below is where they are installed. Building without --dev is
;; fine — nothing has cells to install into, so a module is refused on the

View File

@ -0,0 +1,160 @@
(import rl "vendor:raylib")
;; raylib's Image family, headless. An Image is pixels in RAM: no window, no
;; GL context, and — unlike every other struct in the package — raylib will
;; *compute* with it. That is what makes this the strongest FFI case in the
;; project rather than another link check.
;;
;; Two things are being pinned here and they are different things:
;;
;; 1. **Flan's Image layout.** gen-image-color is handed two scalars and
;; answers with a struct whose four ints are 4, 2, 1 and 7 — all
;; distinct, so exchanging any two of width/height/mipmaps/format shows
;; up immediately. Scalars in, fields out: a permuted layout cannot
;; cancel itself the way store-and-return does, which is why this pins
;; more than the shapes texture ever could. It also pins `data`: drop it
;; from the defstruct and `width` reads the low half of raylib's pointer.
;;
;; 2. **The shim's argument order and raylib's own index arithmetic.**
;; get-image-color reads pixel y*width + x out of the buffer, so on a
;; 4-wide, 2-tall image the pixel at (3,0) exists and (0,3) does not.
;; Exchange x and y in the wrapper and the answer is transparent black.
;; This is the axis discriminator that no axis-aligned geometry can be:
;; the image is not square, so a reflection has nowhere to hide.
;;
;; The image is deliberately 4 x 2 throughout. A square one would let a
;; transposed read pass, and that is exactly the trap the collision cases fell
;; into.
(defconst bg (rl/Color {:r 10 :g 20 :b 30 :a 255}))
(defconst mark-a (rl/Color {:r 200 :g 0 :b 0 :a 255}))
(defconst mark-b (rl/Color {:r 0 :g 200 :b 0 :a 255}))
;; Where the export goes and comes back from. The two optimisation levels
;; write identical bytes, so sharing one path between runs is harmless.
(defconst png-path "/tmp/flan-raylib-image.png")
(defn show-image [name string i rl/Image]
(print-str name)
(print-str " ") (print-i64 (i64 (.width i)))
(print-str " ") (print-i64 (i64 (.height i)))
(print-str " ") (print-i64 (i64 (.mipmaps i)))
(print-str " ") (print-i64 (i64 (.format i)))
(newline))
(defn show-color [name string c rl/Color]
(print-str name)
(print-str " ") (print-i64 (i64 (.r c)))
(print-str " ") (print-i64 (i64 (.g c)))
(print-str " ") (print-i64 (i64 (.b c)))
(print-str " ") (print-i64 (i64 (.a c)))
(newline))
(defn show-bool [name string b bool]
(print-str name) (print-str " ")
(print-line (if b "yes" "no")))
;; Every pixel read names its coordinates in the label, so a failure says
;; which one moved rather than only that something did.
(defn show-pixel [name string i rl/Image x i32 y i32]
(show-color name (rl/get-image-color i x y)))
(defn main [] i32
(rl/set-trace-log-level :warning)
;; ── The layout, from a struct raylib built ──────────────────────────
;;
;; 4 wide, 2 tall, 1 mipmap level, format 7 (uncompressed R8G8B8A8). Four
;; different numbers in four adjacent i32 slots is the case Texture2D never
;; got: there, nothing without a GPU read width, height or mipmaps at all.
(let [img (rl/gen-image-color 4 2 bg)]
(show-image "generated" img)
;; ── The axes, from raylib's own indexing ───────────────────────────
;;
;; (3,0) is the last pixel of the first row and (0,1) the first of the
;; second. On a 4 x 2 image neither coordinate pair is valid with x and y
;; exchanged, so a wrapper with its arguments the wrong way round reads out
;; of bounds and answers 0 0 0 0.
(rl/image-draw-pixel (addr img) 3 0 mark-a)
(rl/image-draw-pixel (addr img) 0 1 mark-b)
(show-pixel "at 3,0" img 3 0)
(show-pixel "at 0,1" img 0 1)
;; And the two corners nothing was written to, because a get that ignored
;; its coordinates and returned the last-written colour would pass above.
(show-pixel "at 0,0" img 0 0)
(show-pixel "at 3,1" img 3 1)
;; ── Flip horizontal, then vertical ─────────────────────────────────
;;
;; Horizontal moves x and leaves y: (3,0) becomes (0,0) and (0,1) becomes
;; (3,1). Bind the two flips to each other's wrappers and this reads
;; unchanged at (3,0) instead, because a vertical flip of a 2-row image
;; would put the marks on the other rows entirely.
(rl/image-flip-horizontal (addr img))
(show-pixel "flipped-h at 0,0" img 0 0)
(show-pixel "flipped-h at 3,1" img 3 1)
(show-pixel "flipped-h at 3,0" img 3 0)
;; Vertical moves y and leaves x, so the two marks swap rows: (0,0) goes to
;; (0,1) and (3,1) to (3,0).
(rl/image-flip-vertical (addr img))
(show-pixel "flipped-v at 0,1" img 0 1)
(show-pixel "flipped-v at 3,0" img 3 0)
(show-pixel "flipped-v at 0,0" img 0 0)
;; ── Out to a PNG and back ──────────────────────────────────────────
;;
;; The file is external ground truth, which is what stops this being the
;; symmetric round trip the rest of the package has to avoid: the encoder
;; and the decoder are stb's, they agree with each other and not with
;; whatever field order Flan believes in. A path crosses as ptr+len and
;; the shim NUL-terminates a copy, so this exercises the string half of
;; the boundary too.
(show-bool "exported" (rl/export-image img png-path))
(let [back (rl/load-image png-path)]
(show-bool "loaded valid" (rl/image-valid? back))
(show-image "loaded" back)
(show-pixel "loaded at 0,1" back 0 1)
(show-pixel "loaded at 3,0" back 3 0)
(show-pixel "loaded at 0,0" back 0 0)
;; ── Nearest-neighbour resize ─────────────────────────────────────
;;
;; 4 x 2 to 8 x 2 doubles each pixel across, and leaves the rows alone.
;; The colours survive exactly, which bicubic's would not, so this is
;; the resize that can be asserted on content: the mark at (0,1) spreads
;; to (0,1) and (1,1), the one at (3,0) to (6,0) and (7,0), and (2,1) is
;; background between them. New width and height are 8 and 2 — distinct,
;; so a wrapper that swapped them answers 2 and 8.
(rl/image-resize-nn (addr back) 8 2)
(show-image "resized-nn" back)
(show-pixel "nn at 0,1" back 0 1)
(show-pixel "nn at 1,1" back 1 1)
(show-pixel "nn at 6,0" back 6 0)
(show-pixel "nn at 7,0" back 7 0)
(show-pixel "nn at 2,1" back 2 1)
;; Bicubic. Its pixels are interpolated and not worth asserting, but the
;; dimensions are, and 2 x 6 is asymmetric in both directions at once.
(rl/image-resize (addr back) 2 6)
(show-image "resized" back)
(rl/unload-image back))
(rl/unload-image img))
;; ── Crop, which is where Rectangle meets Image ──────────────────────
;;
;; A 6 x 3 image with one mark at (5,0), cropped to (x 4, y 0, w 2, h 1).
;; The result is 2 x 1 and the mark has moved to (1,0) — it survived, so the
;; crop's x really is 4 and not its width, and the region really is two wide
;; and one tall and not the other way about. Exchange width and height in
;; the Rectangle and the result is 1 x 2 with the mark gone.
(let [img (rl/gen-image-color 6 3 bg)]
(rl/image-draw-pixel (addr img) 5 0 mark-a)
(rl/image-crop (addr img) (rl/Rectangle {:x 4.0 :y 0.0 :width 2.0 :height 1.0}))
(show-image "cropped" img)
(show-pixel "cropped at 1,0" img 1 0)
(show-pixel "cropped at 0,0" img 0 0)
(rl/unload-image img))
0)

View File

@ -282,6 +282,73 @@ let () =
else
print_endline "acceptance: skipping the raylib FFI case (no libraylib)";
(* raylib's Image family, headless, and the strongest FFI case here: an
Image is pixels in RAM, so raylib *computes* with it rather than
storing and returning it.
Two separate things are pinned. gen-image-color is handed two scalars
and answers with a struct reading 4, 2, 1, 7 four distinct values in
four adjacent i32 slots, so exchanging any two of width, height,
mipmaps and format is visible, and dropping `data` makes width the low
half of raylib's pointer. Scalars in and fields out is what makes that
work: a permuted layout has nothing to cancel against, unlike the
shapes texture, where nothing without a GPU read width, height or
mipmaps at all.
The other is the axis, which the collision cases could not get. raylib
indexes a pixel as y*width + x, and the image is 4 wide by 2 tall, so
(3,0) exists and its transpose does not exchange x and y in the shim
and the read is out of bounds and answers transparent black. The
horizontal and vertical flips are the same argument twice more: on two
rows, one of them moves a mark that the other leaves alone.
The PNG round trip is not the symmetric trap either: stb's encoder and
decoder are external ground truth and agree with each other rather than
with whatever field order Flan believes in. It also crosses a path as
ptr+len. /tmp is written to, and both optimisation levels write the
same bytes, so the shared name is harmless.
Trace logging stays at :warning and no read here is out of bounds, so
a warning appearing in this output is a real failure [run] folds
stderr in. *)
let raylib_image_out =
"generated 4 2 1 7\n\
at 3,0 200 0 0 255\n\
at 0,1 0 200 0 255\n\
at 0,0 10 20 30 255\n\
at 3,1 10 20 30 255\n\
flipped-h at 0,0 200 0 0 255\n\
flipped-h at 3,1 0 200 0 255\n\
flipped-h at 3,0 10 20 30 255\n\
flipped-v at 0,1 200 0 0 255\n\
flipped-v at 3,0 0 200 0 255\n\
flipped-v at 0,0 10 20 30 255\n\
exported yes\n\
loaded valid yes\n\
loaded 4 2 1 7\n\
loaded at 0,1 200 0 0 255\n\
loaded at 3,0 0 200 0 255\n\
loaded at 0,0 10 20 30 255\n\
resized-nn 8 2 1 7\n\
nn at 0,1 200 0 0 255\n\
nn at 1,1 200 0 0 255\n\
nn at 6,0 0 200 0 255\n\
nn at 7,0 0 200 0 255\n\
nn at 2,1 10 20 30 255\n\
resized 2 6 1 7\n\
cropped 2 1 1 7\n\
cropped at 1,0 200 0 0 255\n\
cropped at 0,0 10 20 30 255\n"
in
if Sys.command "ldconfig -p 2>/dev/null | grep -q libraylib" = 0 then begin
outputs "raylib images, headless" "programs/raylib-image.flan"
raylib_image_out;
outputs ~opt:"-O0" "raylib images, headless, -O0" "programs/raylib-image.flan"
raylib_image_out
end
else
print_endline "acceptance: skipping the raylib Image case (no libraylib)";
(* Again at -O0. Everything above runs through mem2reg, which launders a
sloppy alloca; -O0 tests the IR actually emitted, so a disagreement
between the two points at undefined behaviour rather than a typo. *)

View File

@ -392,3 +392,349 @@
p position
c tint]
(draw-texture-rec-raw (addr t) (addr s) (addr p) (addr c))))
;; ── Images ──────────────────────────────────────────────────────────
;;
;; An Image is pixels in RAM. Nothing here touches the GPU, which makes it the
;; one corner of the 2D surface a headless test can assert properly — raylib
;; *computes* with these, and a wrong answer is a wrong number rather than the
;; struct handed back unchanged.
;;
;; `data` is raylib's buffer and Flan never reads through it; it is here so
;; the struct is the right size and the four ints that follow are at the right
;; offsets. `format` is a PixelFormat code — GenImageColor makes 7, which is
;; uncompressed R8G8B8A8, one byte per channel.
;;
;; The split between by-value and by-pointer here is raylib's own and worth
;; keeping: a call that *mutates* the image takes (Ptr Image) at the Flan
;; level too, so a caller can see which ones change what they are given.
(defstruct Image [data (Ptr u8) width i32 height i32 mipmaps i32 format i32])
(declare load-image-raw [path string out (Ptr Image)] "flan_rl_load_image")
(defn load-image [path string] Image
(let [i (Image {})]
(load-image-raw path (addr i))
i))
;; raylib 5.5 spells this IsImageValid. IsImageReady, which older code calls,
;; does not exist here — the same rename that took IsTextureReady.
(declare image-valid?-raw [image (Ptr Image)] bool "flan_rl_is_image_valid")
(defn image-valid? [image Image] bool
(let [i image]
(image-valid?-raw (addr i))))
;; By value, as raylib has it. The caller's copy is dangling afterwards —
;; `data` pointed at the buffer this just freed — so an Image is used or
;; unloaded, never both.
(declare unload-image-raw [image (Ptr Image)] "flan_rl_unload_image")
(defn unload-image [image Image]
(let [i image]
(unload-image-raw (addr i))))
;; The format is taken from the path's extension, so ".png" writes a PNG.
;; False means it could not be written.
(declare export-image-raw [image (Ptr Image) path string] bool
"flan_rl_export_image")
(defn export-image [image Image path string] bool
(let [i image]
(export-image-raw (addr i) path)))
(declare gen-image-color-raw
[width i32 height i32 color (Ptr Color) out (Ptr Image)]
"flan_rl_gen_image_color")
(defn gen-image-color [width i32 height i32 color Color] Image
(let [c color
i (Image {})]
(gen-image-color-raw width height (addr c) (addr i))
i))
;; Bicubic, so the pixels that come out are interpolated and only the new
;; width and height are exactly predictable. image-resize-nn is the
;; nearest-neighbour one, and it is the one to reach for when the colours
;; have to survive.
(declare image-resize [image (Ptr Image) width i32 height i32]
"flan_rl_image_resize")
(declare image-resize-nn [image (Ptr Image) width i32 height i32]
"flan_rl_image_resize_nn")
(declare image-crop-raw [image (Ptr Image) crop (Ptr Rectangle)]
"flan_rl_image_crop")
(defn image-crop [image (Ptr Image) crop Rectangle]
(let [r crop]
(image-crop-raw image (addr r))))
(declare image-flip-horizontal [image (Ptr Image)]
"flan_rl_image_flip_horizontal")
(declare image-flip-vertical [image (Ptr Image)]
"flan_rl_image_flip_vertical")
(declare image-draw-pixel-raw
[image (Ptr Image) x i32 y i32 color (Ptr Color)]
"flan_rl_image_draw_pixel")
(defn image-draw-pixel [image (Ptr Image) x i32 y i32 color Color]
(let [c color]
(image-draw-pixel-raw image x y (addr c))))
;; Out of bounds is not an error: raylib logs a warning and hands back a
;; transparent black, so a caller that is off by one gets zeroes rather than
;; somebody else's memory.
(declare get-image-color-raw
[image (Ptr Image) x i32 y i32 out (Ptr Color)]
"flan_rl_get_image_color")
(defn get-image-color [image Image x i32 y i32] Color
(let [i image
c (Color {})]
(get-image-color-raw (addr i) x y (addr c))
c))
;; The one call in this section that does need a GL context — it uploads. An
;; image loaded and edited on the CPU becomes something draw-texture can use.
(declare load-texture-from-image-raw [image (Ptr Image) out (Ptr Texture2D)]
"flan_rl_load_texture_from_image")
(defn load-texture-from-image [image Image] Texture2D
(let [i image
t (Texture2D {})]
(load-texture-from-image-raw (addr i) (addr t))
t))
;; ── Shapes ──────────────────────────────────────────────────────────
;;
;; Immediate-mode drawing: each of these needs a GL context, so a window has
;; to be open and none of them can be in the acceptance table. They are
;; exercised by running sand.flan and looking at it, which is the honest
;; description — "it links" is not "it draws the right thing".
;;
;; raylib's own naming is kept: a plain name fills, `-lines` outlines, and a
;; `-v` suffix takes Vector2s where the plain form takes integers.
;;
;; The one signature worth calling out is draw-rectangle-rounded-lines, which
;; in raylib 5.5 has NO thickness — it moved to the `-ex` form. The 5.1 header
;; still shows the five-argument version, and getting it wrong links cleanly
;; and draws nonsense, so this was read off the library with nm rather than
;; remembered.
(declare draw-pixel-raw [x i32 y i32 color (Ptr Color)] "flan_rl_draw_pixel")
(defn draw-pixel [x i32 y i32 color Color]
(let [c color] (draw-pixel-raw x y (addr c))))
(declare draw-pixel-v-raw [position (Ptr Vector2) color (Ptr Color)]
"flan_rl_draw_pixel_v")
(defn draw-pixel-v [position Vector2 color Color]
(let [p position c color] (draw-pixel-v-raw (addr p) (addr c))))
(declare draw-line-raw [x1 i32 y1 i32 x2 i32 y2 i32 color (Ptr Color)]
"flan_rl_draw_line")
(defn draw-line [x1 i32 y1 i32 x2 i32 y2 i32 color Color]
(let [c color] (draw-line-raw x1 y1 x2 y2 (addr c))))
(declare draw-line-v-raw
[start (Ptr Vector2) end (Ptr Vector2) color (Ptr Color)]
"flan_rl_draw_line_v")
(defn draw-line-v [start Vector2 end Vector2 color Color]
(let [a start b end c color] (draw-line-v-raw (addr a) (addr b) (addr c))))
;; The thick one is built from triangles rather than GL lines, which is why it
;; is a separate call and not a parameter on the one above.
(declare draw-line-ex-raw
[start (Ptr Vector2) end (Ptr Vector2) thick f32 color (Ptr Color)]
"flan_rl_draw_line_ex")
(defn draw-line-ex [start Vector2 end Vector2 thick f32 color Color]
(let [a start b end c color]
(draw-line-ex-raw (addr a) (addr b) thick (addr c))))
(declare draw-circle-raw [x i32 y i32 radius f32 color (Ptr Color)]
"flan_rl_draw_circle")
(defn draw-circle [x i32 y i32 radius f32 color Color]
(let [c color] (draw-circle-raw x y radius (addr c))))
(declare draw-circle-v-raw
[center (Ptr Vector2) radius f32 color (Ptr Color)] "flan_rl_draw_circle_v")
(defn draw-circle-v [center Vector2 radius f32 color Color]
(let [p center c color] (draw-circle-v-raw (addr p) radius (addr c))))
(declare draw-circle-lines-raw [x i32 y i32 radius f32 color (Ptr Color)]
"flan_rl_draw_circle_lines")
(defn draw-circle-lines [x i32 y i32 radius f32 color Color]
(let [c color] (draw-circle-lines-raw x y radius (addr c))))
(declare draw-circle-lines-v-raw
[center (Ptr Vector2) radius f32 color (Ptr Color)]
"flan_rl_draw_circle_lines_v")
(defn draw-circle-lines-v [center Vector2 radius f32 color Color]
(let [p center c color] (draw-circle-lines-v-raw (addr p) radius (addr c))))
;; Two radii, horizontal then vertical. Equal radii is a circle, so a wrapper
;; that exchanged them would be invisible unless they differ — which is why
;; sand.flan's ellipse is deliberately wider than it is tall.
(declare draw-ellipse-raw
[x i32 y i32 radius-h f32 radius-v f32 color (Ptr Color)]
"flan_rl_draw_ellipse")
(defn draw-ellipse [x i32 y i32 radius-h f32 radius-v f32 color Color]
(let [c color] (draw-ellipse-raw x y radius-h radius-v (addr c))))
(declare draw-ellipse-lines-raw
[x i32 y i32 radius-h f32 radius-v f32 color (Ptr Color)]
"flan_rl_draw_ellipse_lines")
(defn draw-ellipse-lines [x i32 y i32 radius-h f32 radius-v f32 color Color]
(let [c color] (draw-ellipse-lines-raw x y radius-h radius-v (addr c))))
;; Angles are degrees, clockwise from the +x axis, and `segments` is how many
;; straight pieces the arc is made of — 0 lets raylib pick from the radius.
(declare draw-ring-raw
[center (Ptr Vector2) inner f32 outer f32 start f32 end f32
segments i32 color (Ptr Color)]
"flan_rl_draw_ring")
(defn draw-ring [center Vector2 inner f32 outer f32 start f32 end f32
segments i32 color Color]
(let [p center c color]
(draw-ring-raw (addr p) inner outer start end segments (addr c))))
(declare draw-ring-lines-raw
[center (Ptr Vector2) inner f32 outer f32 start f32 end f32
segments i32 color (Ptr Color)]
"flan_rl_draw_ring_lines")
(defn draw-ring-lines [center Vector2 inner f32 outer f32 start f32 end f32
segments i32 color Color]
(let [p center c color]
(draw-ring-lines-raw (addr p) inner outer start end segments (addr c))))
;; Counter-clockwise, and raylib means it: the clockwise winding is culled and
;; draws nothing at all, which looks exactly like a broken binding.
(declare draw-triangle-raw
[v1 (Ptr Vector2) v2 (Ptr Vector2) v3 (Ptr Vector2) color (Ptr Color)]
"flan_rl_draw_triangle")
(defn draw-triangle [v1 Vector2 v2 Vector2 v3 Vector2 color Color]
(let [a v1 b v2 d v3 c color]
(draw-triangle-raw (addr a) (addr b) (addr d) (addr c))))
(declare draw-triangle-lines-raw
[v1 (Ptr Vector2) v2 (Ptr Vector2) v3 (Ptr Vector2) color (Ptr Color)]
"flan_rl_draw_triangle_lines")
(defn draw-triangle-lines [v1 Vector2 v2 Vector2 v3 Vector2 color Color]
(let [a v1 b v2 d v3 c color]
(draw-triangle-lines-raw (addr a) (addr b) (addr d) (addr c))))
(declare draw-rectangle-v-raw
[position (Ptr Vector2) size (Ptr Vector2) color (Ptr Color)]
"flan_rl_draw_rectangle_v")
(defn draw-rectangle-v [position Vector2 size Vector2 color Color]
(let [p position s size c color]
(draw-rectangle-v-raw (addr p) (addr s) (addr c))))
(declare draw-rectangle-rec-raw [rec (Ptr Rectangle) color (Ptr Color)]
"flan_rl_draw_rectangle_rec")
(defn draw-rectangle-rec [rec Rectangle color Color]
(let [r rec c color] (draw-rectangle-rec-raw (addr r) (addr c))))
(declare draw-rectangle-lines-raw
[x i32 y i32 width i32 height i32 color (Ptr Color)]
"flan_rl_draw_rectangle_lines")
(defn draw-rectangle-lines [x i32 y i32 width i32 height i32 color Color]
(let [c color] (draw-rectangle-lines-raw x y width height (addr c))))
;; The one-pixel outline above is drawn with GL lines and sits *on* the
;; rectangle's edge; this one is drawn with quads and sits inside it, so the
;; two do not agree at thickness 1 and that is raylib's doing, not a bug here.
(declare draw-rectangle-lines-ex-raw
[rec (Ptr Rectangle) thick f32 color (Ptr Color)]
"flan_rl_draw_rectangle_lines_ex")
(defn draw-rectangle-lines-ex [rec Rectangle thick f32 color Color]
(let [r rec c color] (draw-rectangle-lines-ex-raw (addr r) thick (addr c))))
;; `roundness` is 0 to 1 as a fraction of the shorter side, so 0 is a plain
;; rectangle and 1 is a stadium.
(declare draw-rectangle-rounded-raw
[rec (Ptr Rectangle) roundness f32 segments i32 color (Ptr Color)]
"flan_rl_draw_rectangle_rounded")
(defn draw-rectangle-rounded [rec Rectangle roundness f32 segments i32
color Color]
(let [r rec c color]
(draw-rectangle-rounded-raw (addr r) roundness segments (addr c))))
;; No thickness here — see the section note. The `-ex` form below is the one
;; that takes it.
(declare draw-rectangle-rounded-lines-raw
[rec (Ptr Rectangle) roundness f32 segments i32 color (Ptr Color)]
"flan_rl_draw_rectangle_rounded_lines")
(defn draw-rectangle-rounded-lines [rec Rectangle roundness f32 segments i32
color Color]
(let [r rec c color]
(draw-rectangle-rounded-lines-raw (addr r) roundness segments (addr c))))
(declare draw-rectangle-rounded-lines-ex-raw
[rec (Ptr Rectangle) roundness f32 segments i32 thick f32
color (Ptr Color)]
"flan_rl_draw_rectangle_rounded_lines_ex")
(defn draw-rectangle-rounded-lines-ex [rec Rectangle roundness f32
segments i32 thick f32 color Color]
(let [r rec c color]
(draw-rectangle-rounded-lines-ex-raw (addr r) roundness segments thick
(addr c))))
;; ── Text ────────────────────────────────────────────────────────────
;;
;; Both of these use raylib's built-in font, and both therefore need
;; init-window — not for the GPU in measure-text's case, but because the
;; default font is only loaded as part of opening a window. Called headless,
;; measure-text answers 0 for every string, which was measured against
;; libraylib.so.550 and is why it is NOT in the acceptance table despite
;; looking like exactly the kind of call that could be.
;;
;; Font loading is not bound, deliberately. A Font is baseSize, glyphCount and
;; glyphPadding beside a Texture2D, a Rectangle* and a GlyphInfo* — and a
;; GlyphInfo embeds an Image. Binding it means binding two more aggregates and
;; two owned arrays for something with no headless test at the end of it, so
;; load-font, load-font-ex, unload-font, get-font-default, draw-text-ex and
;; measure-text-ex are all absent rather than half-done.
(declare draw-text-raw
[text string x i32 y i32 font-size i32 color (Ptr Color)]
"flan_rl_draw_text")
(defn draw-text [text string x i32 y i32 font-size i32 color Color]
(let [c color] (draw-text-raw text x y font-size (addr c))))
(declare measure-text [text string font-size i32] i32 "flan_rl_measure_text")
;; ── Timing and window state ─────────────────────────────────────────
;;
;; All four read state that init-window creates, so all four answer 0 before
;; there is a window — again measured, not assumed. get-frame-time is the
;; delta the last frame took, in seconds, which is what a simulation should
;; scale by instead of assuming the target fps was met.
(declare get-frame-time [] f32 "flan_rl_get_frame_time")
(declare get-time [] f64 "flan_rl_get_time")
(declare get-screen-width [] i32 "flan_rl_get_screen_width")
(declare get-screen-height [] i32 "flan_rl_get_screen_height")

242
vendor/raylib/shim.c vendored
View File

@ -232,3 +232,245 @@ bool flan_rl_check_collision_lines(const Vector2 *a1, const Vector2 *a2,
Vector2 *out) {
return CheckCollisionLines(*a1, *a2, *b1, *b2, out);
}
/* ── Images ─────────────────────────────────────────────────────────
*
* An Image is pixels in RAM, so all of this runs with no window and no GL
* context which is why it is the part of the package that a headless test
* can actually assert rather than merely link. `data` is the pixel buffer
* raylib owns; `format` is a PixelFormat enum and GenImageColor makes 7
* (uncompressed R8G8B8A8).
*
* raylib 5.5 spells the predicate IsImageValid. IsImageReady, which the 5.1
* header still had, is gone checked with nm -D, not remembered.
*/
typedef struct { void *data; int width, height, mipmaps, format; } Image;
extern Image LoadImage(const char *fileName);
extern bool IsImageValid(Image image);
extern void UnloadImage(Image image);
extern bool ExportImage(Image image, const char *fileName);
extern Image GenImageColor(int width, int height, Color color);
extern void ImageResize(Image *image, int newWidth, int newHeight);
extern void ImageResizeNN(Image *image, int newWidth, int newHeight);
extern void ImageCrop(Image *image, Rectangle crop);
extern void ImageFlipHorizontal(Image *image);
extern void ImageFlipVertical(Image *image);
extern void ImageDrawPixel(Image *dst, int posX, int posY, Color color);
extern Color GetImageColor(Image image, int x, int y);
extern Texture2D LoadTextureFromImage(Image image);
void flan_rl_load_image(const char *path, long long n, Image *out) {
char buf[PATH_MAX];
*out = LoadImage(cstr(path, n, buf, sizeof buf));
}
bool flan_rl_is_image_valid(const Image *image) { return IsImageValid(*image); }
void flan_rl_unload_image(const Image *image) { UnloadImage(*image); }
bool flan_rl_export_image(const Image *image, const char *path, long long n) {
char buf[PATH_MAX];
return ExportImage(*image, cstr(path, n, buf, sizeof buf));
}
void flan_rl_gen_image_color(int width, int height, const Color *color,
Image *out) {
*out = GenImageColor(width, height, *color);
}
void flan_rl_image_resize(Image *image, int w, int h) { ImageResize(image, w, h); }
void flan_rl_image_resize_nn(Image *image, int w, int h) { ImageResizeNN(image, w, h); }
void flan_rl_image_crop(Image *image, const Rectangle *crop) {
ImageCrop(image, *crop);
}
void flan_rl_image_flip_horizontal(Image *image) { ImageFlipHorizontal(image); }
void flan_rl_image_flip_vertical(Image *image) { ImageFlipVertical(image); }
void flan_rl_image_draw_pixel(Image *dst, int x, int y, const Color *color) {
ImageDrawPixel(dst, x, y, *color);
}
void flan_rl_get_image_color(const Image *image, int x, int y, Color *out) {
*out = GetImageColor(*image, x, y);
}
void flan_rl_load_texture_from_image(const Image *image, Texture2D *out) {
*out = LoadTextureFromImage(*image);
}
/* ── Shapes, text and timing ─────────────────────────────────────────
*
* All of the drawing below needs a GL context and therefore a window, so none
* of it can be in the acceptance table it is exercised by running sand.flan
* and looking. The three prototypes most likely to be remembered wrong were
* checked against nm -D on libraylib.so.550 rather than against a header:
* raylib 5.5 moved the line thickness off DrawRectangleRoundedLines onto
* DrawRectangleRoundedLinesEx, so the four-argument form here is the 5.5 one
* and not the 5.1 one.
*
* MeasureText, GetFrameTime, GetTime and GetScreenWidth/Height need no GL
* context but do need InitWindow: the first reads the default font, which
* only InitWindow loads, and the others read window state. Headless they all
* answer 0 measured, not assumed so they are no more assertable than the
* drawing is.
*/
extern void DrawPixel(int posX, int posY, Color color);
extern void DrawPixelV(Vector2 position, Color color);
extern void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY,
Color color);
extern void DrawLineV(Vector2 startPos, Vector2 endPos, Color color);
extern void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color);
extern void DrawCircle(int centerX, int centerY, float radius, Color color);
extern void DrawCircleV(Vector2 center, float radius, Color color);
extern void DrawCircleLines(int centerX, int centerY, float radius, Color color);
extern void DrawCircleLinesV(Vector2 center, float radius, Color color);
extern void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV,
Color color);
extern void DrawEllipseLines(int centerX, int centerY, float radiusH,
float radiusV, Color color);
extern void DrawRing(Vector2 center, float innerRadius, float outerRadius,
float startAngle, float endAngle, int segments, Color color);
extern void DrawRingLines(Vector2 center, float innerRadius, float outerRadius,
float startAngle, float endAngle, int segments,
Color color);
extern void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color);
extern void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color);
extern void DrawRectangleV(Vector2 position, Vector2 size, Color color);
extern void DrawRectangleRec(Rectangle rec, Color color);
extern void DrawRectangleLines(int posX, int posY, int width, int height,
Color color);
extern void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color);
extern void DrawRectangleRounded(Rectangle rec, float roundness, int segments,
Color color);
extern void DrawRectangleRoundedLines(Rectangle rec, float roundness,
int segments, Color color);
extern void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness,
int segments, float lineThick,
Color color);
extern void DrawText(const char *text, int posX, int posY, int fontSize,
Color color);
extern int MeasureText(const char *text, int fontSize);
extern float GetFrameTime(void);
extern double GetTime(void);
extern int GetScreenWidth(void);
extern int GetScreenHeight(void);
void flan_rl_draw_pixel(int x, int y, const Color *c) { DrawPixel(x, y, *c); }
void flan_rl_draw_pixel_v(const Vector2 *p, const Color *c) {
DrawPixelV(*p, *c);
}
void flan_rl_draw_line(int x1, int y1, int x2, int y2, const Color *c) {
DrawLine(x1, y1, x2, y2, *c);
}
void flan_rl_draw_line_v(const Vector2 *a, const Vector2 *b, const Color *c) {
DrawLineV(*a, *b, *c);
}
void flan_rl_draw_line_ex(const Vector2 *a, const Vector2 *b, float thick,
const Color *c) {
DrawLineEx(*a, *b, thick, *c);
}
void flan_rl_draw_circle(int x, int y, float radius, const Color *c) {
DrawCircle(x, y, radius, *c);
}
void flan_rl_draw_circle_v(const Vector2 *center, float radius, const Color *c) {
DrawCircleV(*center, radius, *c);
}
void flan_rl_draw_circle_lines(int x, int y, float radius, const Color *c) {
DrawCircleLines(x, y, radius, *c);
}
void flan_rl_draw_circle_lines_v(const Vector2 *center, float radius,
const Color *c) {
DrawCircleLinesV(*center, radius, *c);
}
void flan_rl_draw_ellipse(int x, int y, float rh, float rv, const Color *c) {
DrawEllipse(x, y, rh, rv, *c);
}
void flan_rl_draw_ellipse_lines(int x, int y, float rh, float rv, const Color *c) {
DrawEllipseLines(x, y, rh, rv, *c);
}
void flan_rl_draw_ring(const Vector2 *center, float inner, float outer,
float start, float end, int segments, const Color *c) {
DrawRing(*center, inner, outer, start, end, segments, *c);
}
void flan_rl_draw_ring_lines(const Vector2 *center, float inner, float outer,
float start, float end, int segments,
const Color *c) {
DrawRingLines(*center, inner, outer, start, end, segments, *c);
}
void flan_rl_draw_triangle(const Vector2 *a, const Vector2 *b, const Vector2 *d,
const Color *c) {
DrawTriangle(*a, *b, *d, *c);
}
void flan_rl_draw_triangle_lines(const Vector2 *a, const Vector2 *b,
const Vector2 *d, const Color *c) {
DrawTriangleLines(*a, *b, *d, *c);
}
void flan_rl_draw_rectangle_v(const Vector2 *position, const Vector2 *size,
const Color *c) {
DrawRectangleV(*position, *size, *c);
}
void flan_rl_draw_rectangle_rec(const Rectangle *rec, const Color *c) {
DrawRectangleRec(*rec, *c);
}
void flan_rl_draw_rectangle_lines(int x, int y, int w, int h, const Color *c) {
DrawRectangleLines(x, y, w, h, *c);
}
void flan_rl_draw_rectangle_lines_ex(const Rectangle *rec, float thick,
const Color *c) {
DrawRectangleLinesEx(*rec, thick, *c);
}
void flan_rl_draw_rectangle_rounded(const Rectangle *rec, float roundness,
int segments, const Color *c) {
DrawRectangleRounded(*rec, roundness, segments, *c);
}
/* Four arguments, not five: 5.5's DrawRectangleRoundedLines has no thickness
* and the Ex variant below is where it went. Getting this wrong links fine. */
void flan_rl_draw_rectangle_rounded_lines(const Rectangle *rec, float roundness,
int segments, const Color *c) {
DrawRectangleRoundedLines(*rec, roundness, segments, *c);
}
void flan_rl_draw_rectangle_rounded_lines_ex(const Rectangle *rec,
float roundness, int segments,
float thick, const Color *c) {
DrawRectangleRoundedLinesEx(*rec, roundness, segments, thick, *c);
}
void flan_rl_draw_text(const char *text, long long n, int x, int y,
int font_size, const Color *c) {
char buf[512];
DrawText(cstr(text, n, buf, sizeof buf), x, y, font_size, *c);
}
int flan_rl_measure_text(const char *text, long long n, int font_size) {
char buf[512];
return MeasureText(cstr(text, n, buf, sizeof buf), font_size);
}
float flan_rl_get_frame_time(void) { return GetFrameTime(); }
double flan_rl_get_time(void) { return GetTime(); }
int flan_rl_get_screen_width(void) { return GetScreenWidth(); }
int flan_rl_get_screen_height(void) { return GetScreenHeight(); }