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}