code wiki / (root) / nx_gif_decode_t139.nx

nx_gif_decode_t139.nx source

↩ module page · 299 lines · 16476 B

1// nx_gif_decode.nx -- bounded GIF87a/89a image and sequential animation decoder. 2// Checked rectangle RGBA, composed first-canvas RGBA, and sequential disposal-aware 3// RGBA share one parser/LZW core. Iterator retains timing/loop metadata; it is NOT 4// a GUI playback scheduler. Legacy opaque RGB/gray return their historical 5// sys_mmap(logical_bytes+16) allocation; checked owned buffers use gif_release. 6import "syscalls.nx" 7const GIF_DICT: i64 = 4096 8const GIF_WORK_BYTES: i64 = 40960 9const GIF_OK_FIRST_IMAGE: i64 = 1 10const GIF_BAD_FORMAT: i64 = 2 11const GIF_UNSUPPORTED: i64 = 3 12const GIF_BAD_LZW: i64 = 4 13const GIF_NO_MEMORY: i64 = 5 14const GIF_NEEDS_ALPHA: i64 = 6 15const GIF_RELEASE_FAILED: i64 = 7 16const GIF_DONE: i64 = 8 17const GIF_BAD_ARGUMENT: i64 = 9 18const GIF_ANIM_WORDS: i64 = 32 19func gif_u16le(buf: *u8, off: i64) -> i64 { return (buf[off] as i64) | ((buf[off+1] as i64)<<8) } 20func gif_own_size(n: i64) -> i64 { if n<=NXA_SMALL_MAX { return NXA_SMALL_MAX+1 } return n } 21func gif_own(n: i64) -> *u8 { if n<=0 { return 0 as *u8 } let p: *u8=sys_mmap_shared(gif_own_size(n)); if (p as i64)<=0 { return 0 as *u8 } return p } 22// Checked owned buffers only: release with logical bytes. Legacy outputs instead 23// retain sys_mmap(logical_bytes+16) and its paired sys_munmap contract. 24func gif_release(p: *u8, n: i64) -> i64 { if p==(0 as *u8) { return 0 } return sys_munmap(p,gif_own_size(n)) } 25// info[16]: w,h,palette offset,count,interlace,mincode,data start,end, 26// transparent index (-1 none),delay centiseconds,left,top,logical w,h,disposal,status. 27func gif_scan_from(raw: *u8, n: i64, info: *i64, start: i64, animation: *i64) -> i64 { 28 var z: i64=0; while z<16 { info[z]=0; z=z+1 } info[8]=0-1 29 if raw==(0 as *u8) { return GIF_BAD_FORMAT } if n<13 { return GIF_BAD_FORMAT } 30 if raw[0]!=(0x47 as u8) || raw[1]!=(0x49 as u8) || raw[2]!=(0x46 as u8) { return GIF_BAD_FORMAT } 31 if raw[3]!=(0x38 as u8) || raw[5]!=(0x61 as u8) { return GIF_BAD_FORMAT } 32 if raw[4]!=(0x37 as u8) && raw[4]!=(0x39 as u8) { return GIF_BAD_FORMAT } 33 info[12]=gif_u16le(raw,6); info[13]=gif_u16le(raw,8) 34 if info[12]==0 || info[13]==0 { return GIF_BAD_FORMAT } 35 var pos: i64=13; var cto: i64=0; var ctn: i64=0 36 if ((raw[10] as i64)&128)!=0 { ctn=1<<(((raw[10] as i64)&7)+1); cto=pos; if ctn*3>n-pos { return GIF_BAD_FORMAT } pos=pos+ctn*3 } 37 if start<0 || start>n { return GIF_BAD_ARGUMENT } 38 if start>0 { if start<pos { return GIF_BAD_ARGUMENT } pos=start } 39 var gce: i64=0 40 while pos<n { 41 let tag: i64=raw[pos] as i64; pos=pos+1 42 if tag==0x21 { 43 if pos>=n { return GIF_BAD_FORMAT } let label: i64=raw[pos] as i64; pos=pos+1 44 if label==0xff && animation!=(0 as *i64) { 45 if n-pos<12 { return GIF_BAD_FORMAT } 46 if raw[pos]!=(11 as u8) { return GIF_BAD_FORMAT } 47 var known: i64=1; var a: i64=0; let ns: *u8="NETSCAPE2.0" as *u8; let an: *u8="ANIMEXTS1.0" as *u8 48 var nk: i64=1; var ak: i64=1; while a<11 { if raw[pos+1+a]!=ns[a] { nk=0 } if raw[pos+1+a]!=an[a] { ak=0 } a=a+1 } 49 if nk==0 && ak==0 { known=0 } 50 if known==1 { if n-pos<17 { return GIF_BAD_FORMAT } if raw[pos+12]!=(3 as u8) || raw[pos+13]!=(1 as u8) || raw[pos+16]!=(0 as u8) { return GIF_BAD_FORMAT } 51 let loops: i64=gif_u16le(raw,pos+14); if animation[17]>=0 && animation[17]!=loops { return GIF_BAD_FORMAT } animation[17]=loops 52 } 53 } 54 if label==0xf9 { 55 if gce==1 { return GIF_BAD_FORMAT } if n-pos<6 { return GIF_BAD_FORMAT } 56 if raw[pos]!=(4 as u8) || raw[pos+5]!=(0 as u8) { return GIF_BAD_FORMAT } 57 let flags: i64=raw[pos+1] as i64 58 if animation!=(0 as *i64) && (flags&2)!=0 { return GIF_UNSUPPORTED } 59 if (flags&224)!=0 { return GIF_BAD_FORMAT } info[14]=(flags>>2)&7 60 if info[14]>3 { return GIF_UNSUPPORTED } 61 if (flags&1)!=0 { info[8]=raw[pos+4] as i64 } 62 info[9]=gif_u16le(raw,pos+2); pos=pos+6; gce=1 63 } else { 64 if label==1 { return GIF_UNSUPPORTED } 65 var done: i64=0 66 while done==0 { if pos>=n { return GIF_BAD_FORMAT } let count: i64=raw[pos] as i64; pos=pos+1; if count==0 { done=1 } else { if count>n-pos { return GIF_BAD_FORMAT } pos=pos+count } } 67 } 68 } else { 69 if tag==0x3b && animation!=(0 as *i64) { if pos!=n { return GIF_BAD_FORMAT } info[7]=pos; return GIF_DONE } 70 if tag!=0x2c { return GIF_BAD_FORMAT } if n-pos<9 { return GIF_BAD_FORMAT } 71 info[10]=gif_u16le(raw,pos); info[11]=gif_u16le(raw,pos+2) 72 info[0]=gif_u16le(raw,pos+4); info[1]=gif_u16le(raw,pos+6) 73 if info[0]==0 || info[1]==0 { return GIF_BAD_FORMAT } 74 if info[10]+info[0]>info[12] || info[11]+info[1]>info[13] { return GIF_BAD_FORMAT } 75 let flags: i64=raw[pos+8] as i64; if (flags&24)!=0 { return GIF_BAD_FORMAT } 76 info[4]=(flags>>6)&1; pos=pos+9 77 if (flags&128)!=0 { ctn=1<<((flags&7)+1); cto=pos; if ctn*3>n-pos { return GIF_BAD_FORMAT } pos=pos+ctn*3 } 78 if ctn==0 { return GIF_BAD_FORMAT } if info[8]>=ctn { return GIF_BAD_FORMAT } 79 info[2]=cto; info[3]=ctn 80 if pos>=n { return GIF_BAD_FORMAT } info[5]=raw[pos] as i64; pos=pos+1 81 if info[5]<2 || info[5]>8 { return GIF_BAD_LZW } 82 info[6]=pos; var done: i64=0 83 while done==0 { if pos>=n { return GIF_BAD_FORMAT } let count: i64=raw[pos] as i64; pos=pos+1; if count==0 { done=1 } else { if count>n-pos { return GIF_BAD_FORMAT } pos=pos+count } } 84 info[7]=pos; return GIF_OK_FIRST_IMAGE 85 } 86 } 87 return GIF_BAD_FORMAT 88} 89func gif_scan(raw: *u8, n: i64, info: *i64) -> i64 { return gif_scan_from(raw,n,info,0,0 as *i64) } 90// Sub-block reader has already been validated by gif_scan; checks remain local to each fetch. 91func gif_lzw(raw: *u8, info: *i64, idx: *u8, work: *u8) -> i64 { 92 let prefix: *i64=work as *i64 93 let suffix: *u8=((work as i64)+GIF_DICT*8) as *u8 94 let stack: *u8=((work as i64)+GIF_DICT*9) as *u8 95 let clear: i64=1<<info[5]; let eoi: i64=clear+1 96 var i: i64=0; while i<clear { prefix[i]=0-1; suffix[i]=i as u8; i=i+1 } 97 var next: i64=eoi+1; var width: i64=info[5]+1; var prev: i64=0-1 98 var pos: i64=info[6]; var remain: i64=0; var bits: i64=0; var bitcount: i64=0 99 var count: i64=0; let pixels: i64=info[0]*info[1] 100 var row: i64=0; var col: i64=0; var pass: i64=0; var step: i64=8 101 while 1==1 { 102 while bitcount<width { 103 if remain==0 { if pos>=info[7] { return GIF_BAD_LZW } remain=raw[pos] as i64; pos=pos+1; if remain==0 { return GIF_BAD_LZW } } 104 if pos>=info[7] { return GIF_BAD_LZW } 105 bits=bits|((raw[pos] as i64)<<bitcount); pos=pos+1; remain=remain-1; bitcount=bitcount+8 106 } 107 let code: i64=bits&((1<<width)-1); bits=bits>>width; bitcount=bitcount-width 108 if code==eoi { if count!=pixels { return GIF_BAD_LZW } return GIF_OK_FIRST_IMAGE } 109 if code==clear { next=eoi+1; width=info[5]+1; prev=0-1 } else { 110 if code>next || code>=GIF_DICT { return GIF_BAD_LZW } 111 var k: i64=code; var extra: i64=0 112 if code==next { if prev<0 { return GIF_BAD_LZW } k=prev; extra=1 } 113 var sp: i64=0 114 while k>=clear { 115 if k<eoi+1 || k>=next || sp>=GIF_DICT { return GIF_BAD_LZW } 116 stack[sp]=suffix[k]; sp=sp+1 117 let parent: i64=prefix[k]; if parent<0 || parent>=k { return GIF_BAD_LZW } k=parent 118 } 119 if k<0 || k>=clear || sp>=GIF_DICT { return GIF_BAD_LZW } 120 stack[sp]=k as u8; sp=sp+1; let first: i64=k 121 if sp+extra>pixels-count { return GIF_BAD_LZW } 122 var emit: i64=sp+extra 123 while emit>0 { 124 var value: i64=first 125 if sp>0 { sp=sp-1; value=stack[sp] as i64 } 126 if value>=info[3] { return GIF_BAD_LZW } 127 if row>=info[1] { return GIF_BAD_LZW } 128 idx[row*info[0]+col]=value as u8; count=count+1; col=col+1 129 if col==info[0] { 130 col=0 131 if info[4]==0 { row=row+1 } else { 132 row=row+step 133 while row>=info[1] && pass<3 { pass=pass+1; if pass==1 { row=4; step=8 } if pass==2 { row=2; step=4 } if pass==3 { row=1; step=2 } } 134 } 135 } 136 emit=emit-1 137 } 138 if prev>=0 && next<GIF_DICT { prefix[next]=prev; suffix[next]=first as u8; next=next+1; if next==(1<<width) && width<12 { width=width+1 } } 139 prev=code 140 } 141 } 142 return GIF_BAD_LZW 143} 144func gif_indices_from_info(raw: *u8, info: *i64) -> *u8 { 145 let pixels: i64=info[0]*info[1] 146 let idx: *u8=gif_own(pixels); if idx==(0 as *u8) { info[15]=GIF_NO_MEMORY; return 0 as *u8 } 147 let work: *u8=gif_own(GIF_WORK_BYTES) 148 if work==(0 as *u8) { gif_release(idx,pixels); info[15]=GIF_NO_MEMORY; return 0 as *u8 } 149 let code: i64=gif_lzw(raw,info,idx,work) 150 let freecode: i64=gif_release(work,GIF_WORK_BYTES) 151 info[15]=code; if freecode!=0 { info[15]=GIF_RELEASE_FAILED } 152 if info[15]!=GIF_OK_FIRST_IMAGE { gif_release(idx,pixels); return 0 as *u8 } 153 return idx 154} 155func gif_indices_checked(raw: *u8, n: i64, info: *i64) -> *u8 { 156 let rc: i64=gif_scan(raw,n,info); info[15]=rc; if rc!=GIF_OK_FIRST_IMAGE { return 0 as *u8 } 157 return gif_indices_from_info(raw,info) 158} 159// Legacy five-word core metadata preserved; first rectangle indices, no compositing. 160func gif_decode_core(raw: *u8, n: i64, meta: *i64) -> *u8 { 161 let info: *i64=gif_own(128) as *i64; if info==(0 as *i64) { return 0 as *u8 } 162 let out: *u8=gif_indices_checked(raw,n,info); var i: i64=0 163 if out!=(0 as *u8) { while i<5 { meta[i]=info[i]; i=i+1 } } 164 let pixels: i64=info[0]*info[1] 165 if gif_release(info as *u8,128)!=0 { gif_release(out,pixels); return 0 as *u8 } 166 return gif_to_legacy(out,pixels) 167} 168func gif_convert_checked(raw: *u8, n: i64, wh: *i64, info: *i64, channels: i64) -> *u8 { 169 wh[0]=0; wh[1]=0 170 if channels!=1 && channels!=3 && channels!=4 { info[15]=GIF_BAD_ARGUMENT; return 0 as *u8 } 171 let idx: *u8=gif_indices_checked(raw,n,info); if idx==(0 as *u8) { return 0 as *u8 } 172 let pixels: i64=info[0]*info[1] 173 if channels!=4 { 174 if info[10]!=0 || info[11]!=0 || info[0]!=info[12] || info[1]!=info[13] { gif_release(idx,pixels); info[15]=GIF_UNSUPPORTED; return 0 as *u8 } 175 if info[8]>=0 { var k: i64=0; while k<pixels { if (idx[k] as i64)==info[8] { gif_release(idx,pixels); info[15]=GIF_NEEDS_ALPHA; return 0 as *u8 } k=k+1 } } 176 } 177 let out: *u8=gif_own(pixels*channels); if out==(0 as *u8) { gif_release(idx,pixels); info[15]=GIF_NO_MEMORY; return 0 as *u8 } 178 var p: i64=0 179 while p<pixels { 180 let ci: i64=idx[p] as i64; let off: i64=info[2]+ci*3 181 let r: i64=raw[off] as i64; let g: i64=raw[off+1] as i64; let b: i64=raw[off+2] as i64 182 if channels==1 { out[p]=((54*r+183*g+19*b)>>8) as u8 } else { 183 out[p*channels]=r as u8; out[p*channels+1]=g as u8; out[p*channels+2]=b as u8 184 if channels==4 { if ci==info[8] { out[p*4+3]=0 as u8 } else { out[p*4+3]=255 as u8 } } 185 } 186 p=p+1 187 } 188 if gif_release(idx,pixels)!=0 { gif_release(out,pixels*channels); info[15]=GIF_RELEASE_FAILED; return 0 as *u8 } 189 wh[0]=info[0]; wh[1]=info[1]; return out 190} 191func gif_decode_rgba_checked(raw: *u8, n: i64, wh: *i64, info: *i64) -> *u8 { return gif_convert_checked(raw,n,wh,info,4) } 192func gif_decode_opaque_owned(raw: *u8, n: i64, wh: *i64, channels: i64) -> *u8 { 193 wh[0]=0; wh[1]=0 194 let info: *i64=gif_own(128) as *i64; if info==(0 as *i64) { return 0 as *u8 } 195 let out: *u8=gif_convert_checked(raw,n,wh,info,channels) 196 let bytes: i64=wh[0]*wh[1]*channels 197 if gif_release(info as *u8,128)!=0 { gif_release(out,bytes); wh[0]=0; wh[1]=0; return 0 as *u8 } 198 return out 199} 200func gif_decode(raw: *u8, n: i64, wh: *i64) -> *u8 { return gif_decode_opaque(raw,n,wh,1) } 201func gif_decode_rgb(raw: *u8, n: i64, wh: *i64) -> *u8 { return gif_decode_opaque(raw,n,wh,3) } 202 203// Sequential animation: caller owns zero-initialized state[GIF_ANIM_WORDS] and 204// keeps encoded raw bytes alive until close. Canvas is BORROWED until next/close. 205// State: raw,n,cursor,canvas,restore,w,h,bytes,previous x/y/w/h/disposal, 206// frame count,elapsed cs,last delay,status,loop count(-1 absent;0 infinite), 207// background policy(0 transparent,1 logical-screen colour),RGBA,open; remaining reserved. 208// Policy is explicit because RGBA transparency cannot select a viewer's matte. 209func gif_anim_close(st: *i64) -> i64 { 210 var rc: i64=0 211 if st[3]!=0 { if gif_release(st[3] as *u8,st[7])!=0 { rc=GIF_RELEASE_FAILED } } 212 if st[4]!=0 { if gif_release(st[4] as *u8,st[7])!=0 { rc=GIF_RELEASE_FAILED } } 213 var i: i64=0; while i<GIF_ANIM_WORDS { st[i]=0; i=i+1 } return rc 214} 215func gif_anim_init(raw: *u8,n: i64,st: *i64,background_policy: i64) -> i64 { 216 if st[23]!=0 { return GIF_BAD_ARGUMENT } 217 if background_policy!=0 && background_policy!=1 { return GIF_BAD_ARGUMENT } 218 var i: i64=0; while i<GIF_ANIM_WORDS { st[i]=0; i=i+1 } 219 let info: *i64=gif_own(128) as *i64; if info==(0 as *i64) { return GIF_NO_MEMORY } 220 let rc: i64=gif_scan(raw,n,info) 221 if rc!=GIF_OK_FIRST_IMAGE { gif_release(info as *u8,128); return rc } 222 st[5]=info[12]; st[6]=info[13]; st[7]=st[5]*st[6]*4 223 if gif_release(info as *u8,128)!=0 { return GIF_RELEASE_FAILED } 224 if background_policy==1 { 225 if ((raw[10] as i64)&128)==0 { return GIF_BAD_FORMAT } 226 let colours: i64=1<<(((raw[10] as i64)&7)+1); let bg: i64=raw[11] as i64 227 if bg>=colours { return GIF_BAD_FORMAT } 228 st[19]=raw[13+bg*3] as i64; st[20]=raw[14+bg*3] as i64; st[21]=raw[15+bg*3] as i64; st[22]=255 229 } 230 let canvas: *u8=gif_own(st[7]); if canvas==(0 as *u8) { return GIF_NO_MEMORY } 231 st[0]=raw as i64; st[1]=n; st[3]=canvas as i64; st[17]=0-1; st[18]=background_policy; st[23]=1 232 var p: i64=0; while p<st[5]*st[6] { var c: i64=0; while c<4 { canvas[p*4+c]=st[19+c] as u8; c=c+1 } p=p+1 } 233 return GIF_OK_FIRST_IMAGE 234} 235func gif_anim_next(st: *i64,info: *i64) -> i64 { 236 if st[23]!=1 { return GIF_BAD_ARGUMENT } 237 if st[16]!=0 && st[16]!=GIF_OK_FIRST_IMAGE { return st[16] } 238 let raw: *u8=st[0] as *u8 239 let rc: i64=gif_scan_from(raw,st[1],info,st[2],st); info[15]=rc 240 if rc!=GIF_OK_FIRST_IMAGE { st[16]=rc; return rc } 241 let idx: *u8=gif_indices_from_info(raw,info) 242 if idx==(0 as *u8) { st[16]=info[15]; return st[16] } 243 let pixels: i64=info[0]*info[1] 244 if info[14]==3 && st[4]==0 { 245 let backup: *u8=gif_own(st[7]); if backup==(0 as *u8) { gif_release(idx,pixels); st[16]=GIF_NO_MEMORY; return st[16] } st[4]=backup as i64 246 } 247 let canvas: *u8=st[3] as *u8 248 // Previous disposal applies before the next descriptor is composited. 249 if st[12]==2 { 250 var y: i64=0; while y<st[11] { var x: i64=0; while x<st[10] { 251 let at: i64=((st[9]+y)*st[5]+st[8]+x)*4; var c: i64=0 252 while c<4 { canvas[at+c]=st[19+c] as u8; c=c+1 } x=x+1 253 } y=y+1 } 254 } 255 if st[12]==3 { let old: *u8=st[4] as *u8; var i: i64=0; while i<st[7] { canvas[i]=old[i]; i=i+1 } } 256 if info[14]==3 { let backup: *u8=st[4] as *u8; var i: i64=0; while i<st[7] { backup[i]=canvas[i]; i=i+1 } } 257 var y: i64=0; while y<info[1] { var x: i64=0; while x<info[0] { 258 let ci: i64=idx[y*info[0]+x] as i64 259 if ci!=info[8] { 260 let at: i64=((info[11]+y)*st[5]+info[10]+x)*4; let po: i64=info[2]+ci*3 261 canvas[at]=raw[po]; canvas[at+1]=raw[po+1]; canvas[at+2]=raw[po+2]; canvas[at+3]=255 as u8 262 } x=x+1 263 } y=y+1 } 264 if gif_release(idx,pixels)!=0 { st[16]=GIF_RELEASE_FAILED; return st[16] } 265 st[2]=info[7]; st[8]=info[10]; st[9]=info[11]; st[10]=info[0]; st[11]=info[1]; st[12]=info[14] 266 st[13]=st[13]+1; st[14]=st[14]+info[9]; st[15]=info[9]; st[16]=GIF_OK_FIRST_IMAGE 267 return GIF_OK_FIRST_IMAGE 268} 269 270// Owned, composited first-frame RGBA for static image consumers. This supported 271// path retains alpha and logical-screen offsets without selecting an opaque matte. 272func gif_decode_canvas_rgba(raw: *u8,n: i64,wh: *i64,info: *i64) -> *u8 { 273 wh[0]=0; wh[1]=0 274 let st: *i64=gif_own(GIF_ANIM_WORDS*8) as *i64 275 if st==(0 as *i64) { info[15]=GIF_NO_MEMORY; return 0 as *u8 } 276 var rc: i64=gif_anim_init(raw,n,st,0) 277 if rc==GIF_OK_FIRST_IMAGE { rc=gif_anim_next(st,info) } 278 var out: *u8=0 as *u8; var bytes: i64=0 279 if rc==GIF_OK_FIRST_IMAGE { out=st[3] as *u8; bytes=st[7];wh[0]=st[5];wh[1]=st[6];st[3]=0 } 280 if gif_anim_close(st)!=0 { rc=GIF_RELEASE_FAILED } 281 if gif_release(st as *u8,GIF_ANIM_WORDS*8)!=0 { rc=GIF_RELEASE_FAILED } 282 info[15]=rc 283 if rc!=GIF_OK_FIRST_IMAGE { gif_release(out,bytes);wh[0]=0;wh[1]=0;return 0 as *u8 } 284 return out 285} 286 287// Compatibility bridge: old callers may release logical+16 or depend on small 288// arena ownership. Keep that exact allocation shape; only the checked API has 289// graceful owned-allocation failure. Existing sys_mmap fatal-OOM semantics remain. 290func gif_to_legacy(owned: *u8,bytes: i64) -> *u8 { 291 if owned==(0 as *u8) {return 0 as *u8} 292 let legacy: *u8=sys_mmap(bytes+16);var i: i64=0;while i<bytes{legacy[i]=owned[i];i=i+1} 293 if gif_release(owned,bytes)!=0 {sys_munmap(legacy,bytes+16);return 0 as *u8}return legacy 294} 295func gif_decode_opaque(raw: *u8,n: i64,wh: *i64,channels: i64) -> *u8 { 296 let owned: *u8=gif_decode_opaque_owned(raw,n,wh,channels) 297 let legacy: *u8=gif_to_legacy(owned,wh[0]*wh[1]*channels) 298 if legacy==(0 as *u8){wh[0]=0;wh[1]=0}return legacy 299}