package tiny_libs
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/tiny_libs.graphics_core/Blit.ml.html
Source file Blit.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) (* See Blit.mli for the ideas, with pictures *) type image = { width : int; height : int; rgba : (int, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t; } type color = { rgb : int; a : float } (*****************************************************************************) (* Filtering *) (*****************************************************************************) (* The 4 bytes of pixel (i, j), as a color *) let pixel (image : image) i j : color = let o = ((j * image.width) + i) * 4 in let byte k = image.rgba.{o + k} in { rgb = (byte 0 lsl 16) lor (byte 1 lsl 8) lor byte 2; a = float (byte 3) /. 255. } let clamp lo hi v = max lo (min hi v) let sample_nearest (image : image) (u, v) : color = let i = clamp 0 (image.width - 1) (int_of_float (Float.floor u)) in let j = clamp 0 (image.height - 1) (int_of_float (Float.floor v)) in pixel image i j (* Mixing colors channel by channel, the alpha too; e.g. * lerp red blue 0.25 = 75% red + 25% blue. (Mixing a transparent pixel * would really need premultiplied alpha, see Porter & Duff, to not * darken the edges of sprites: a known simplification here.) *) let lerp (c0 : color) (c1 : color) (t : float) : color = let mix shift = let x0 = (c0.rgb lsr shift) land 0xFF and x1 = (c1.rgb lsr shift) land 0xFF in int_of_float ((float x0 *. (1. -. t)) +. (float x1 *. t) +. 0.5) in { rgb = (mix 16 lsl 16) lor (mix 8 lsl 8) lor mix 0; a = (c0.a *. (1. -. t)) +. (c1.a *. t) } let sample_bilinear (image : image) (u, v) : color = (* the centers of pixels i and i+1 are at i + 0.5 and i + 1.5, so * the pixel whose center is just left of u is floor (u - 0.5), and * tx how far u is towards the next center *) let x = u -. 0.5 and y = v -. 0.5 in let i = int_of_float (Float.floor x) and j = int_of_float (Float.floor y) in let tx = x -. float i and ty = y -. float j in let at i j = pixel image (clamp 0 (image.width - 1) i) (clamp 0 (image.height - 1) j) in let top = lerp (at i j) (at (i + 1) j) tx in let bottom = lerp (at i (j + 1)) (at (i + 1) (j + 1)) tx in lerp top bottom ty (*****************************************************************************) (* Inverse mapping *) (*****************************************************************************) type filter = Nearest | Bilinear (* The framebuffer pixels the image may cover: the box around its * transformed corners, clipped to the framebuffer, as * (x0, x1, y0, y1), x1 and y1 excluded *) let covered_box (fb : Framebuffer.t) (image : image) (m : Affine.t) = let w = float image.width and h = float image.height in let corners = List.map (Affine.apply m) [ (0., 0.); (w, 0.); (w, h); (0., h) ] in let xs = List.map fst corners and ys = List.map snd corners in let first_pixel v = int_of_float (Float.ceil (v -. 0.5)) in ( max 0 (first_pixel (List.fold_left min infinity xs)), min fb.width (first_pixel (List.fold_left max neg_infinity xs)), max 0 (first_pixel (List.fold_left min infinity ys)), min fb.height (first_pixel (List.fold_left max neg_infinity ys)) ) (* The original, simple version: for each covered pixel, where does its * center come from in the image? (a matrix product), then the color * there (the filter, as a function returning a color) *) let draw_simple (fb : Framebuffer.t) (image : image) (m : Affine.t) ~filter ~alpha = let sample = match filter with Nearest -> sample_nearest | Bilinear -> sample_bilinear in let w = float image.width and h = float image.height in let x0, x1, y0, y1 = covered_box fb image m in let inverse = Affine.invert m in for y = y0 to y1 - 1 do for x = x0 to x1 - 1 do let ((u, v) as p) = Affine.apply inverse (float x +. 0.5, float y +. 0.5) in (* from inside the image (not just its box): take its color *) if u >= 0. && u < w && v >= 0. && v < h then begin let c = sample image p in Framebuffer.plot fb ~x ~y ~rgb:c.rgb ~alpha:(alpha *. c.a) end done done (* claude: optimization (Opti.enabled), the same pixels as draw_simple * but without its per-pixel overhead, about 10 small allocations per * pixel (the (u, v) pair from Affine.apply, a [color] record per texel * read and per lerp): * * - forward differencing: one pixel to the right on screen, (x+1, y), * is always the same step in the image, the inverse matrix's first * column (inverse.a, inverse.b), so compute (u, v) once per row and * then just add that step -- the same idea as Fill's edge coherence; * - the filter inlined, on plain local numbers (which OCaml keeps * unboxed), instead of a function returning records. *) let draw_fast (fb : Framebuffer.t) (image : image) (m : Affine.t) ~filter ~alpha = let w = float image.width and h = float image.height in let x0, x1, y0, y1 = covered_box fb image m in let inverse = Affine.invert m in let data = image.rgba and iw = image.width and ih = image.height in let byte i j k = Bigarray.Array1.unsafe_get data ((((j * iw) + i) * 4) + k) in for y = y0 to y1 - 1 do (* (u, v) for the first pixel of the row, then one step per pixel *) let u0, v0 = Affine.apply inverse (float x0 +. 0.5, float y +. 0.5) in let u = ref u0 and v = ref v0 in for x = x0 to x1 - 1 do let u' = !u and v' = !v in if u' >= 0. && u' < w && v' >= 0. && v' < h then begin match filter with | Nearest -> let i = int_of_float u' and j = int_of_float v' in let a = byte i j 3 in if a > 0 then Framebuffer.plot fb ~x ~y ~rgb:((byte i j 0 lsl 16) lor (byte i j 1 lsl 8) lor byte i j 2) ~alpha:(alpha *. float a /. 255.) | Bilinear -> (* as sample_bilinear: the 4 texels around (u, v), weighted *) let fx = u' -. 0.5 and fy = v' -. 0.5 in let i = int_of_float (Float.floor fx) and j = int_of_float (Float.floor fy) in let tx = fx -. float i and ty = fy -. float j in let i0 = max 0 i and i1 = min (iw - 1) (i + 1) in let j0 = max 0 j and j1 = min (ih - 1) (j + 1) in let mix k = let top = (float (byte i0 j0 k) *. (1. -. tx)) +. (float (byte i1 j0 k) *. tx) in let bottom = (float (byte i0 j1 k) *. (1. -. tx)) +. (float (byte i1 j1 k) *. tx) in (top *. (1. -. ty)) +. (bottom *. ty) in let a = mix 3 in if a > 0. then begin let channel k = int_of_float (mix k +. 0.5) in Framebuffer.plot fb ~x ~y ~rgb:((channel 0 lsl 16) lor (channel 1 lsl 8) lor channel 2) ~alpha:(alpha *. a /. 255.) end end; u := u' +. inverse.a; v := v' +. inverse.b done done let draw fb image m ~filter ~alpha = if !Opti.enabled then draw_fast fb image m ~filter ~alpha else draw_simple fb image m ~filter ~alpha
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>