123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119(* 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 Yuv.mli *)typerange=Full|Studio(* claude: compared as ints, and rounded by adding 0.5; it was
*
* max 0 (min 255 (int_of_float (Float.round v)))
*
* Stdlib's min and max being polymorphic, the runtime's generic compare
* twice per component, and Float.round a call into C (caml_round). The
* same integers: for v >= 0, int_of_float (v +. 0.5) rounds half away
* from zero as Float.round does, and below 0 both clamp to 0. Inlined:
* a float crossing a call that isn't is boxed, an allocation per call
* (notes_opti_ocaml.md) *)let[@inline]clamp(v:float):int=leti=int_of_float(v+.0.5)inifi<0then0elseifi>255then255elsei(* the studio range squeezes the full one: 256 levels of Y into 219
* (16-235), of Cb and Cr into 224 (16-240), around 128 *)lety_scale=219./.255.letc_scale=224./.255.letof_rgb(range:range)((r,g,b):int*int*int):int*int*int=letr=float_of_intrandg=float_of_intgandb=float_of_intbinlety=(0.299*.r)+.(0.587*.g)+.(0.114*.b)inletcb=128.-.(0.168736*.r)-.(0.331264*.g)+.(0.5*.b)inletcr=128.+.(0.5*.r)-.(0.418688*.g)-.(0.081312*.b)inmatchrangewith|Full->(clampy,clampcb,clampcr)|Studio->(clamp(16.+.(y*.y_scale)),clamp(128.+.((cb-.128.)*.c_scale)),clamp(128.+.((cr-.128.)*.c_scale)))letto_rgb(range:range)((y,cb,cr):int*int*int):int*int*int=lety=float_of_intyandcb=float_of_intcb-.128.andcr=float_of_intcr-.128.inlety,cb,cr=matchrangewithFull->(y,cb,cr)|Studio->((y-.16.)/.y_scale,cb/.c_scale,cr/.c_scale)in(clamp(y+.(1.402*.cr)),clamp(y-.(0.344136*.cb)-.(0.714136*.cr)),clamp(y+.(1.772*.cb)))typechroma=C420|C444typeplanes={width:int;height:int;chroma:chroma;y:Bytes.t;cb:Bytes.t;cr:Bytes.t}letchroma_size(chroma:chroma)~(width:int)~(height:int):int*int=matchchromawithC444->(width,height)|C420->((width+1)/2,(height+1)/2)letof_image(range:range)(chroma:chroma)(img:Rgba_image.t):planes=letwidth=img.widthandheight=img.heightinletfull=Array.init(width*height)(funi->of_rgbrange(img.rgba.{4*i},img.rgba.{(4*i)+1},img.rgba.{(4*i)+2}))inlety=Bytes.init(width*height)(funi->letv,_,_=full.(i)inChar.chrv)inletcw,ch=chroma_sizechroma~width~heightinletside=matchchromawithC444->1|C420->2in(* a color sample: the rounded average of the pixels it stands for,
* side x side of them, fewer at an odd edge *)letsample(pick:int*int*int->int)=Bytes.init(cw*ch)(funi->letcx=imodcwandcy=i/cwinletsum=ref0andn=ref0inforpy=cy*sidetomin(height-1)((cy*side)+side-1)doforpx=cx*sidetomin(width-1)((cx*side)+side-1)dosum:=!sum+pickfull.((py*width)+px);incrndonedone;Char.chr((!sum+(!n/2))/!n))in{width;height;chroma;y;cb=sample(fun(_,cb,_)->cb);cr=sample(fun(_,_,cr)->cr)}(* claude: to_rgb's terms looked up. Each depends on one byte, Y, Cb or
* Cr, so its 256 values are computed once per range; a pixel is then 3
* additions and 3 clamps, no product, no division, no allocation --
* the classic trick (libjpeg's jdcolor.c). The additions in to_rgb's
* order: the same colors, bit for bit. to_image was
*
* let r, g, b = to_rgb range (Char.code (Bytes.get p.y ...), ...) in
*
* per pixel: a tuple allocated for the argument and one for the result,
* 2 divisions and 6 products (notes_opti_ocaml.md) *)typetables={luma:floatarray;r_cr:floatarray;g_cb:floatarray;g_cr:floatarray;b_cb:floatarray}lettables_of(range:range):tables=letyv=matchrangewithFull->float_of_intv|Studio->(float_of_intv-.16.)/.y_scaleinletcv=matchrangewithFull->float_of_intv-.128.|Studio->(float_of_intv-.128.)/.c_scaleinlettablef=Array.init256fin{luma=tabley;r_cr=table(funv->1.402*.cv);g_cb=table(funv->0.344136*.cv);g_cr=table(funv->0.714136*.cv);b_cb=table(funv->1.772*.cv)}letfull_tables=lazy(tables_ofFull)letstudio_tables=lazy(tables_ofStudio)letto_image(range:range)(p:planes):Rgba_image.t=lett=Lazy.force(matchrangewithFull->full_tables|Studio->studio_tables)inletimg=Rgba_image.create~width:p.width~height:p.heightinletcw,_=chroma_sizep.chroma~width:p.width~height:p.heightin(* a pixel's color sample column is px / side, a shift: side is 1 or 2
* (claude: not a division per pixel) *)letshift=matchp.chromawithC444->0|C420->1inforpy=0top.height-1doletchroma_row=(pylsrshift)*cwinforpx=0top.width-1do(* the nearest color sample: the one this pixel's square shares *)letc=chroma_row+(pxlsrshift)inlety=t.luma.(Char.code(Bytes.getp.y((py*p.width)+px)))inletcb=Char.code(Bytes.getp.cbc)andcr=Char.code(Bytes.getp.crc)inleto=4*((py*p.width)+px)inimg.rgba.{o}<-clamp(y+.t.r_cr.(cr));img.rgba.{o+1}<-clamp(y-.t.g_cb.(cb)-.t.g_cr.(cr));img.rgba.{o+2}<-clamp(y+.t.b_cb.(cb));img.rgba.{o+3}<-255donedone;img