summaryrefslogtreecommitdiff
path: root/appl/charon/img.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/charon/img.b')
-rw-r--r--appl/charon/img.b3607
1 files changed, 3607 insertions, 0 deletions
diff --git a/appl/charon/img.b b/appl/charon/img.b
new file mode 100644
index 00000000..fd8b625d
--- /dev/null
+++ b/appl/charon/img.b
@@ -0,0 +1,3607 @@
+implement Img;
+
+include "common.m";
+
+# headers for png support
+include "filter.m";
+include "crc.m";
+
+# big tables in separate files
+include "rgb.inc";
+include "ycbcr.inc";
+
+include "xxx.inc";
+
+# local copies from CU
+sys: Sys;
+CU: CharonUtils;
+ Header, ByteSource, MaskedImage, ImageCache, ResourceState: import CU;
+D: Draw;
+ Chans, Point, Rect, Image, Display: import D;
+E: Events;
+ Event: import E;
+G: Gui;
+
+# channel descriptions
+CRGB: con 0; # three channels, R, G, B, no map
+CY: con 1; # one channel, luminance
+CRGB1: con 2; # one channel, map present
+CYCbCr: con 3; # three channels, Y, Cb, Cr, no map
+
+dbg := 0;
+dbgev := 0;
+warn := 0;
+progressive := 0;
+display: ref D->Display;
+
+inflate: Filter;
+crc: Crc;
+CRCstate: import crc;
+
+init(cu: CharonUtils)
+{
+ sys = load Sys Sys->PATH;
+ CU = cu;
+ D = load Draw Draw->PATH;
+ G = cu->G;
+ crc = load Crc Crc->PATH;
+ inflate = load Filter "/dis/lib/inflate.dis";
+ inflate->init();
+ init_tabs();
+}
+
+# Return true if mtype is an image type we can handle
+supported(mtype: int) : int
+{
+ case mtype {
+ CU->ImageJpeg or
+ CU->ImageGif or
+ CU->ImageXXBitmap or
+ CU->ImageXInfernoBit or
+ CU->ImagePng =>
+ return 1;
+ }
+ return 0;
+}
+
+# w,h passed in are specified width and height.
+# Result will be resampled if they don't match the dimensions
+# in the decoded picture (if only one of w,h is specified, the other
+# dimension is scaled by the same factor).
+ImageSource.new(bs: ref ByteSource, w, h: int) : ref ImageSource
+{
+ dbg = int (CU->config).dbg['i'];
+ warn = (int (CU->config).dbg['w']) || dbg;
+ dbgev = int (CU->config).dbg['e'];
+ display = G->display;
+ mtype := CU->UnknownType;
+ if(bs.hdr != nil)
+ mtype = bs.hdr.mtype;
+ is := ref ImageSource(
+ w,h, # width, height
+ 0,0, # origw, origh
+ mtype, # mtype
+ 0, # i
+ 0, # curframe
+ bs, # bs
+ nil, # ghdr
+ nil, # jhdr
+ "" # err
+ );
+ return is;
+}
+
+ImageSource.free(is: self ref ImageSource)
+{
+ is.bs = nil;
+ is.gstate = nil;
+ is.jstate = nil;
+}
+
+ImageSource.getmim(is: self ref ImageSource) : (int, ref MaskedImage)
+{
+ if(dbg)
+ sys->print("img: getmim\n");
+ if(dbgev)
+ CU->event("IMAGE_GETMIM", is.width*is.height);
+ ans : ref MaskedImage = nil;
+ ret := Mimnone;
+prtype := 0;
+ {
+ if(is.bs.hdr == nil)
+ return (Mimnone, nil);
+ # temporary hack: wait until whole file is here first
+ if(is.bs.eof) {
+ if(is.mtype == CU->UnknownType) {
+ u := is.bs.req.url;
+ h := is.bs.hdr;
+ h.setmediatype(u.path, is.bs.data);
+ is.mtype = h.mtype;
+ }
+ case is.mtype {
+ CU->ImageJpeg =>
+ ans = getjpegmim(is);
+ CU->ImageGif =>
+ ans = getgifmim(is);
+ CU->ImageXXBitmap =>
+ ans = getxbitmapmim(is);
+ CU->ImageXInfernoBit =>
+ ans = getbitmim(is);
+ CU->ImagePng =>
+ ans = getpngmim(is);
+ * =>
+ is.err = sys->sprint("unsupported image type %s", (CU->mnames)[is.mtype]);
+ ret = Mimerror;
+ ans = nil;
+ }
+ if(ans != nil)
+ ret = Mimdone;
+ }
+ else {
+ # slow down the spin-waiting for this image
+ sys->sleep(100);
+ }
+ }exception ex{
+ "exImageerror*" =>
+ ret = Mimerror;
+ if(dbg)
+ sys->print("getmim got err: %s\n", is.err);
+ }
+ if(dbg)
+ sys->print("img: getmim returns (%d,%x)\n", ret, ans);
+ if(dbgev)
+ CU->event("IMAGE_GETMIM_END", 0);
+ is.bs.lim = is.i;
+ return (ret, ans);
+}
+
+# Raise exImagerror exception
+imgerror(is: ref ImageSource, msg: string)
+{
+ is.err = msg;
+ if(dbg)
+ sys->print("Image error: %s\n", msg);
+ CU->raisex("exImageerror:");
+}
+
+# Get next char or raise exception if cannot
+getc(is: ref ImageSource) : int
+{
+ if(is.i >= len is.bs.data) {
+ imgerror(is, "premature eof");
+ }
+ return int is.bs.data[is.i++];
+}
+
+# Unget the last character.
+# When called before any other getting routines, we
+# know the buffer still has that character in it.
+ungetc(is: ref ImageSource)
+{
+ if(is.i == 0)
+ CU->raisex("EXInternal: ungetc past beginning of buffer");
+ is.i--;
+}
+
+# Like ungetc, but ungets two bytes (gotten in order b1, another char).
+# Now the bytes could have spanned a boundary, if we were unlucky,
+# so we have to be prepared to put b1 in front of current buffer.
+ungetc2(is: ref ImageSource, nil: byte)
+{
+ if(is.i < 2) {
+ if(is.i != 1)
+ CU->raisex("EXInternal: ungetc2 past beginning of buffer");
+ is.i = 0;
+ }
+ else
+ is.i -= 2;
+}
+
+# Get 2 bytes and return the 16-bit value, little-endian order.
+getlew(is: ref ImageSource) : int
+{
+ c0 := getc(is);
+ c1 := getc(is);
+ return c0 + (c1<<8);
+}
+
+# Get 2 bytes and return the 16-bit value, big-endian order.
+getbew(is: ref ImageSource) : int
+{
+ c0 := getc(is);
+ c1 := getc(is);
+ return (c0<<8) + c1;
+}
+
+# Copy next n bytes of input into buf
+# or raise exception if cannot.
+read(is: ref ImageSource, buf: array of byte, n: int)
+{
+ ok := 0;
+ if(is.i +n < len is.bs.data) {
+ buf[0:] = is.bs.data[is.i:is.i+n];
+ is.i += n;
+ }
+ else
+ imgerror(is, "premature eof");
+}
+
+# Caller needs n bytes.
+# Return an (array, index into array) where at least
+# the next n bytes can be found.
+# There might be a "premature eof" exception.
+getn(is: ref ImageSource, n: int) : (array of byte, int)
+{
+ a := is.bs.data;
+ i := is.i;
+ if(i + n <= len a)
+ is.i += n;
+ else
+ imgerror(is, "premature eof");
+ return (a, i);
+}
+
+# display.newimage with some defaults; throw exception if fails
+newimage(is: ref ImageSource, w, h: int) : ref Image
+{
+ if(!(CU->imcache).need(w*h))
+ imgerror(is, "out of memory");
+ im := display.newimage(((0,0),(w,h)), D->CMAP8, 0, D->White);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ return im;
+}
+
+newimage24(is: ref ImageSource, w, h: int) : ref Image
+{
+ if(!(CU->imcache).need(w*h*3))
+ imgerror(is, "out of memory");
+ im := display.newimage(((0,0),(w,h)), D->RGB24, 0, D->White);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ return im;
+}
+
+newimagegrey(is: ref ImageSource, w, h: int) : ref Image
+{
+ if(!(CU->imcache).need(w*h))
+ imgerror(is, "out of memory");
+ im := display.newimage(((0,0),(w,h)), D->GREY8, 0, D->White);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ return im;
+}
+
+
+newmi(im: ref Image) : ref MaskedImage
+{
+ return ref MaskedImage(im, nil, 0, 0, -1, Point(0,0));
+}
+
+# Call this after origw and origh are set to set the width and height
+# to our desired (rescaled) answer dimensions.
+# If only one of the dimensions is specified, the other is scaled by
+# the same factor.
+setdims(is: ref ImageSource)
+{
+ sw := is.origw;
+ sh := is.origh;
+ dw := is.width;
+ dh := is.height;
+ if(dw == 0 && dh == 0) {
+ dw = sw;
+ dh = sh;
+ }
+ else if(dw == 0 || dh == 0) {
+ if(dw == 0) {
+ dw = int ((real sw) * (real dh/real sh));
+ if(dw == 0)
+ dw = 1;
+ }
+ else {
+ dh = int ((real sh) * (real dw/real sw));
+ if(dh == 0)
+ dh = 1;
+ }
+ }
+ is.width = dw;
+ is.height = dh;
+}
+
+# for debugging
+printarray(a: array of int, name: string)
+{
+ sys->print("%s:", name);
+ for(i := 0; i < len a; i++) {
+ if((i%10)==0)
+ sys->print("\n%5d: ", i);
+ sys->print("%6d", a[i]);
+ }
+ sys->print("\n");
+}
+
+################# XBitmap ###################
+
+getxbitmaphdr(is: ref ImageSource)
+{
+ fnd: int;
+ (fnd, is.origw) = getxbitmapdefine(is);
+ if(fnd)
+ (fnd, is.origh) = getxbitmapdefine(is);
+ if(!fnd)
+ imgerror(is, "xbitmap starts badly");
+ # now, optional x_hot, y_hot
+ (fnd, nil) = getxbitmapdefine(is);
+ if(fnd)
+ (fnd, nil) = getxbitmapdefine(is);
+ # now expect 'static char x...x_bits[] = {'
+ get_to_char(is, '{');
+}
+
+getxbitmapmim(is: ref ImageSource) : ref MaskedImage
+{
+ getxbitmaphdr(is);
+ setdims(is);
+ bytesperline := (is.origw+7) / 8;
+ pixels := array[is.origw*is.origh] of byte;
+ pixi := 0;
+ for(i := 0; i < is.origh; i++) {
+ for(j := 0; j < bytesperline; j++) {
+ v := get_hexbyte(is);
+ kend := 7;
+ if(j == bytesperline-1)
+ kend = (is.origw-1)%8;
+ for(k := 0; k <= kend; k++) {
+ if(v & (1<<k))
+ pixels[pixi] = byte D->Black;
+ else
+ pixels[pixi] = byte D->White;
+ pixi++;
+ }
+ }
+ }
+ if(is.width != is.origw || is.height != is.origh)
+ pixels = resample(pixels, is.origw, is.origh, is.width, is.height);
+ im := newimage(is, is.width, is.height);
+ im.writepixels(im.r, pixels);
+ return newmi(im);
+}
+
+# get a line, which should be of form
+# '#define fieldname val'
+# and return (found, integer rep of val)
+getxbitmapdefine(is: ref ImageSource) : (int, int)
+{
+ fnd := 0;
+ n := 0;
+ c := getc(is);
+ if(c == '#') {
+ get_to_char(is, ' ');
+ get_to_char(is, ' ');
+ c = getc(is);
+ while(c >= '0' && c <= '9') {
+ fnd = 1;
+ n = n*10 + c - '0';
+ c = getc(is);
+ }
+ }
+ else
+ ungetc(is);
+ get_to_char(is, '\n');
+ return (fnd, n);
+}
+
+# read fd until get char cterm
+# (raise exception if eof hit first)
+get_to_char(is: ref ImageSource, cterm: int)
+{
+ for(;;) {
+ if(getc(is) == cterm)
+ return;
+ }
+}
+
+# read fd until get xDD, were DD are hex digits.
+# (raise exception if not hex digits or if eof hit first)
+get_hexbyte(is: ref ImageSource) : int
+{
+ get_to_char(is, 'x');
+ n1 := hexdig(getc(is));
+ n2 := hexdig(getc(is));
+ if(n1 < 0 || n2 < 0)
+ imgerror(is, "X Bitmap expected hex digits");
+ return (n1<<4) + n2;
+}
+
+hexdig(c: int) : int
+{
+ if('0' <= c && c <= '9')
+ c -= '0';
+ else if('a' <= c && c <= 'f')
+ c += 10 - 'a';
+ else if('A' <= c && c <= 'F')
+ c += 10 - 'A';
+ else
+ c = -1;
+ return c;
+}
+
+################# GIF ###################
+
+# GIF flags
+TRANSP: con 1;
+INPUT: con 2;
+DISPMASK: con 7<<2;
+HASCMAP: con 16r80;
+INTERLACED: con 16r40;
+
+Entry: adt
+{
+ prefix: int;
+ exten: int;
+};
+
+getgifhdr(is: ref ImageSource)
+{
+ if(dbg)
+ sys->print("img: getgifhdr\n");
+ h := ref Gifstate;
+ (buf, i) := getn(is, 6);
+ vers := string buf[i:i+6];
+ if(vers!="GIF87a" && vers!="GIF89a")
+ imgerror(is, "unknown GIF version " + vers);
+ is.origw = getlew(is);
+ is.origh = getlew(is);
+ h.fields = getc(is);
+ h.bgrnd = getc(is);
+ h.aspect = getc(is);
+ setdims(is);
+ if(dbg)
+ sys->print("img: getgifhdr has vers=%s, origw=%d, origh=%d, w=%d, h=%d, fields=16r%x, bgrnd=%d, aspect=%d\n",
+ vers, is.origw, is.origh, is.width, is.height, h.fields, h.bgrnd, h.aspect);
+ h.flags = 0;
+ h.delay = 0;
+ h.trindex = byte 0;
+ h.tbl = array[4096] of GifEntry;
+ for(i = 0; i < 258; i++) {
+ h.tbl[i].prefix = -1;
+ h.tbl[i].exten = i;
+ }
+ h.globalcmap = nil;
+ h.cmap = nil;
+ if(h.fields & HASCMAP)
+ h.globalcmap = gifreadcmap(is, (h.fields&7)+1);
+ is.gstate = h;
+ if(warn && h.aspect != 0)
+ sys->print("warning: non-standard aspect ratio in GIF image ignored\n");
+ if(!gifgettoimage(is))
+ imgerror(is, "GIF file has no image");
+}
+
+gifgettoimage(is: ref ImageSource) : int
+{
+ if(dbg)
+ sys->print("img: gifgettoimage\n");
+ h := is.gstate;
+loop:
+ for(;;) {
+ # some GIFs omit Trailer
+ if(is.i >= len is.bs.data)
+ break;
+ case c := getc(is) {
+ 16r2C => # Image Descriptor
+ return 1;
+
+ 16r21 => # Extension
+ hsize := 0;
+ hasdata := 0;
+
+ case getc(is){
+ 16r01 => # Plain Text Extension
+ hsize = 14;
+ hasdata = 1;
+ if(dbg)
+ sys->print("gifgettoimage: text extension\n");
+ 16rF9 => # Graphic Control Extension
+ getc(is); # blocksize (should be 4)
+ h.flags = getc(is);
+ h.delay = getlew(is);
+ h.trindex = byte getc(is);
+ getc(is); # block terminator (should be 0)
+ # set minimum delay
+ if (h.delay < 20)
+ h.delay = 20;
+ if(dbg)
+ sys->print("gifgettoimage: graphic control flags=16r%x, delay=%d, trindex=%d\n",
+ h.flags, h.delay, int h.trindex);
+ 16rFE => # Comment Extension
+ if(dbg)
+ sys->print("gifgettoimage: comment extension\n");
+ hasdata = 1;
+ 16rFF => # Application Extension
+ if(dbg)
+ sys->print("gifgettoimage: application extension\n");
+ hsize = getc(is);
+ # standard says this must be 11, but Adobe likes to put out 10-byte ones,
+ # so we pay attention to the field.
+ hasdata = 1;
+ * =>
+ imgerror(is, "GIF unknown extension");
+ }
+ if(hsize > 0)
+ getn(is, hsize);
+ if(hasdata) {
+ for(;;) {
+ if((nbytes := getc(is)) == 0)
+ break;
+ (a, i) := getn(is, nbytes);
+ if(dbg)
+ sys->print("extension data: '%s'\n", string a[i:i+nbytes]);
+ }
+ }
+
+ 16r3B => # Trailer
+ # read to end of data
+ getn(is, len is.bs.data - is.i);
+ break loop;
+
+ * =>
+ if(c == 0)
+ continue; # FIX for some buggy gifs
+ imgerror(is, "GIF unknown block type " + string c);
+ }
+ }
+ return 0;
+}
+
+getgifmim(is: ref ImageSource) : ref MaskedImage
+{
+ if(is.gstate == nil)
+ getgifhdr(is);
+
+ # At this point, should just have read Image Descriptor marker byte
+ h := is.gstate;
+ left :=getlew(is);
+ top := getlew(is);
+ width := getlew(is);
+ height := getlew(is);
+ h.fields = getc(is);
+ totpix := width*height;
+ h.cmap = h.globalcmap;
+ if(dbg)
+ sys->print("getgifmim, left=%d, top=%d, width=%d, height=%d, fields=16r%x\n",
+ left, top, width, height, h.fields);
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM", 0);
+ if(h.fields & HASCMAP)
+ h.cmap = gifreadcmap(is, (h.fields&7)+1);
+ if(h.cmap == nil)
+ imgerror(is, "GIF needs colormap");
+
+ # now decode the image
+ c, incode: int;
+
+ codesize := getc(is);
+ if(codesize > 8)
+ imgerror(is, "GIF bad codesize");
+ if(len h.cmap!=3*(1<<codesize)
+ && len h.cmap != 3*(1<<(codesize-1)) # peculiar GIF bitmap files
+ && (codesize!=2 || len h.cmap!=3*2)){ # peculiar GIF bitmap files II
+ if (warn)
+ sys->print("warning: GIF codesize = %d doesn't match cmap len = %d\n", codesize, len h.cmap);
+ #imgerror(is, "GIF codesize doesn't match color map");
+ }
+
+ CTM :=1<<codesize;
+ EOD := CTM+1;
+
+ pic := array[totpix] of byte;
+ pici := 0;
+ data : array of byte = nil;
+ datai := 0;
+ dataend := 0;
+
+ nbits := 0;
+ sreg := 0;
+ stack := array[4096] of byte;
+ stacki: int;
+ fc := 0;
+ tbl := h.tbl;
+
+Decode:
+ for(;;) {
+ csize := codesize+1;
+ csmask := ((1<<csize) - 1);
+ nentry := EOD+1;
+ maxentry := csmask;
+ first := 1;
+ ocode := -1;
+
+ for(;; ocode = incode) {
+ while(nbits < csize) {
+ if(datai == dataend) {
+ nbytes := getc(is);
+ if (nbytes == 0)
+ # Block Terminator
+ break Decode;
+ (data, datai) = getn(is, nbytes);
+ dataend = datai+nbytes;
+ }
+ c = int data[datai++];
+ sreg |= c<<nbits;
+ nbits += 8;
+ }
+ code := sreg & csmask;
+ sreg >>= csize;
+ nbits -= csize;
+
+ if(code == EOD) {
+ nbytes := getc(is);
+ if(nbytes != 0 && warn)
+ sys->print("warning: unexpected data past EOD\n");
+ break Decode;
+ }
+
+ if(code == CTM)
+ continue Decode;
+
+ stacki = len stack-1;
+
+ incode = code;
+
+ # special case for KwKwK
+ if(code == nentry) {
+ stack[stacki--] = byte fc;
+ code = ocode;
+ }
+
+ if(code > nentry)
+ imgerror(is, "GIF bad code");
+
+ for(c=code; c>=0; c=tbl[c].prefix)
+ stack[stacki--] = byte tbl[c].exten;
+
+ nb := len stack-(stacki+1);
+ if(pici+nb > len pic) {
+ # this common error is harmless
+ # we have to keep reading to keep the blocks in sync
+ ;
+ }
+ else {
+ pic[pici:] = stack[stacki+1:];
+ pici += nb;
+ }
+
+ fc = int stack[stacki+1];
+
+ if(first) {
+ first = 0;
+ continue;
+ }
+ early:=0; # peculiar tiff feature here for reference
+ if(nentry == maxentry-early) {
+ if(csize >= 12)
+ continue;
+ csize++;
+ maxentry = (1<<csize);
+ csmask = maxentry - 1;
+ if(csize < 12)
+ maxentry--;
+ }
+ tbl[nentry].prefix = ocode;
+ tbl[nentry].exten = fc;
+ nentry++;
+ }
+ }
+ while(pici < len pic) {
+ # shouldn't happen, but sometimes get buggy gifs
+ pic[pici++] = byte 0;
+ }
+
+ if(h.fields & INTERLACED) {
+ if(dbg)
+ sys->print("getgifmim uninterlacing\n");
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_INTERLACE_START", 0);
+ # (TODO: Could un-interlace in place.
+ # Decompose permutation into cycles,
+ # then need one double-copy of a line
+ # per cycle).
+ ipic := array[totpix] of byte;
+ # Group 1: every 8th row, starting with row 0
+ pici = 0;
+ ipici := 0;
+ ipiclim := totpix-width;
+ w2 := width+width;
+ w4 := w2+w2;
+ w8 := w4+w4;
+ startandby := array[4] of {(0,w8), (w4,w8), (w2,w4), (width,w2)};
+ for(k := 0; k < 4; k++) {
+ (start, by) := startandby[k];
+ for(ipici=start; ipici <= ipiclim; ipici += by) {
+ ipic[ipici:] = pic[pici:pici+width];
+ pici += width;
+ }
+ }
+ pic = ipic;
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_INTERLACE_END", 0);
+ }
+ if(is.width != is.origw || is.height != is.origh) {
+ if (is.width < 0)
+ is.width = 0;
+ if (is.height < 0)
+ is.height = 0;
+ # need to resample, using same factors as original image
+ wscale := real is.width / real is.origw;
+ hscale := real is.height / real is.origh;
+ owidth := width;
+ oheight := height;
+ width = int (wscale * real width);
+ if(width == 0)
+ width = 1;
+ height = int (hscale * real height);
+ if(height == 0)
+ height = 1;
+ left = int (wscale * real left);
+ top = int (hscale * real top);
+ pic = resample(pic, owidth, oheight, width, height);
+ }
+ mask : ref Image;
+ if(h.flags & TRANSP) {
+ if(dbg)
+ sys->print("getgifmim making mask, trindex=%d\n", int h.trindex);
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_MASK_START", 0);
+ # make a 1-bit deep bitmap for mask
+ # expect most mask bits will be 1
+ bytesperrow := (width+7)/8;
+ trpix := h.trindex;
+ mpic := array[bytesperrow*height] of byte;
+ mpici := 0;
+ pici = 0;
+ for(y := 0; y < height; y++) {
+ v := byte 16rFF;
+ k := 0;
+ for(x := 0; x < width; x++) {
+ if(pic[pici++] == trpix)
+ v &= ~(byte 16r80>>k);
+ if(++k == 8) {
+ k = 0;
+ mpic[mpici++] = v;
+ v = byte 16rFF;
+ }
+ }
+ if(k != 0)
+ mpic[mpici++] = v;
+ }
+ if(!(CU->imcache).need(bytesperrow*height))
+ imgerror(is, "out of memory");
+ mask = display.newimage(((0,0),(width,height)), D->GREY1, 0, D->Opaque);
+ if(mask == nil)
+ imgerror(is, "out of memory");
+ mask.writepixels(mask.r, mpic);
+ mpic = nil;
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_MASK_END", 0);
+ }
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_REMAP_START", 0);
+ pic24 := remap24(pic, h.cmap);
+# remap1(pic, width, height, h.cmap);
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_REMAP_END", 0);
+ bgcolor := -1;
+ i := h.bgrnd;
+ if(i >= 0 && 3*i+2 < len h.cmap) {
+ bgcolor = ((int h.cmap[3*i])<<16)
+ | ((int h.cmap[3*i+1])<<8)
+ | (int h.cmap[3*i+2]);
+ }
+ im := newimage24(is, width, height);
+ im.writepixels(im.r, pic24);
+ if(is.curframe == 0) {
+ # make sure first frame fills up whole rectangle
+ if(is.width != width || is.height != height || left != 0 || top != 0) {
+ r := Rect((left,top),(left+width,top+height));
+ pix := D->White;
+ if(bgcolor != -1)
+ pix = (bgcolor<<8) | 16rFF;
+ newim := display.newimage(((0,0),(is.width,is.height)), D->RGB24, 0, pix);
+ if(newim == nil)
+ imgerror(is, "out of memory");
+ newim.draw(r, im, mask, (0,0));
+ im = newim;
+ if(mask != nil) {
+ newmask := display.newimage(((0,0),(is.width,is.height)), D->GREY1, 0, D->Opaque);
+ if(newmask == nil)
+ imgerror(is, "out of memory");
+ newmask.draw(r, mask, nil, (0,0));
+ mask = newmask;
+ }
+ left = 0;
+ top = 0;
+ }
+ }
+ pic = nil;
+ mi := newmi(im);
+ mi.mask = mask;
+ mi.delay = h.delay*10; # convert centiseconds to milliseconds
+ mi.origin = Point(left, top);
+ dispmeth := (h.flags>>2)&7;
+ if(dispmeth == 2) {
+ # reset to background color after displaying this frame
+ mi.bgcolor = bgcolor;
+ }
+ else if(dispmeth == 3) {
+ # Supposed to "reset to previous", which appears to
+ # mean the previous frame that didn't have a "reset to previous".
+ # Signal this special case to layout by setting bgcolor to -2.
+ mi.bgcolor = -2;
+ }
+ if(gifgettoimage(is)) {
+ mi.more = 1;
+ is.curframe++;
+ # have to reinitialize table for next time
+ for(i = 0; i < 258; i++) {
+ h.tbl[i].prefix = -1;
+ h.tbl[i].exten = i;
+ }
+ }
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_END", 0);
+ return mi;
+}
+
+# Read a GIF colormap, where bpe is number of bits in an entry.
+# Raises a 'premature eof' exception if can't get the whole map.
+gifreadcmap(is: ref ImageSource, bpe: int) : array of byte
+{
+ size := 3*(1<<bpe);
+ map := array[size] of byte;
+ if(dbg > 1)
+ sys->print("gifreadcmap wants %d bytes\n", size);
+ read(is, map, size);
+ return map;
+}
+
+################# JPG ###################
+
+# Constants, all preceded by byte 16rFF
+SOF: con 16rC0; # Start of Frame
+SOF2: con 16rC2; # Start of Frame; progressive Huffman
+JPG: con 16rC8; # Reserved for JPEG extensions
+DHT: con 16rC4; # Define Huffman Tables
+DAC: con 16rCC; # Arithmetic coding conditioning
+RST: con 16rD0; # Restart interval termination
+RST7: con 16rD7; # Restart interval termination (highest value)
+SOI: con 16rD8; # Start of Image
+EOI: con 16rD9; # End of Image
+SOS: con 16rDA; # Start of Scan
+DQT: con 16rDB; # Define quantization tables
+DNL: con 16rDC; # Define number of lines
+DRI: con 16rDD; # Define restart interval
+DHP: con 16rDE; # Define hierarchical progression
+EXP: con 16rDF; # Expand reference components
+APPn: con 16rE0; # Reserved for application segments
+JPGn: con 16rF0; # Reserved for JPEG extensions
+COM: con 16rFE; # Comment
+
+NBUF: con 16*1024;
+
+
+jpegcolorspace: con CYCbCr;
+
+zerobytes := array[64] of { * => byte 0 };
+zeroints := array[64] of { * => 0 };
+
+getjpeghdr(is: ref ImageSource)
+{
+ if(dbg)
+ sys->print("getjpeghdr\n");
+ h := ref Jpegstate(
+ 0, 0, # sr, cnt
+ 0, # Nf
+ nil, # comp
+ byte 0, # mode,
+ 0, 0, # X, Y
+ nil, # qt
+ nil, nil, # dcht, acht
+ 0, # Ns
+ nil, # scomp
+ 0, 0, # Ss, Se
+ 0, 0, # Ah, Al
+ 0, 0, # ri, nseg
+ nil, # nblock
+ nil, nil, # dccoeff, accoeff
+ 0, 0, 0, 0 # nacross, ndown, Hmax, Vmax
+ );
+ is.jstate = h;
+ if(jpegmarker(is) != SOI)
+ imgerror(is, "Jpeg expected SOI marker");
+ (m, n) := jpegtabmisc(is);
+ if(!(m == SOF || m == SOF2))
+ imgerror(is, "Jpeg expected Frame marker");
+ nil = getc(is); # sample precision
+ h.Y = getbew(is);
+ h.X = getbew(is);
+ h.Nf = getc(is);
+ if(dbg)
+ sys->print("start of frame, Y=%d, X=%d, Nf=%d\n", h.Y, h.X, h.Nf);
+ h.comp = array[h.Nf] of Framecomp;
+ h.nblock = array[h.Nf] of int;
+ for(i:=0; i<h.Nf; i++) {
+ h.comp[i].C = getc(is);
+ (H, V) := nibbles(getc(is));
+ h.comp[i].H = H;
+ h.comp[i].V = V;
+ h.comp[i].Tq = getc(is);
+ h.nblock[i] =H*V;
+ if(dbg)
+ sys->print("comp[%d]: C=%d, H=%d, V=%d, Tq=%d\n",
+ i, h.comp[i].C, H, V, h.comp[i].Tq);
+ }
+ h.mode = byte m;
+ is.origw = h.X;
+ is.origh = h.Y;
+ setdims(is);
+ if(n != 6+3*h.Nf)
+ imgerror(is, "Jpeg bad SOF length");
+}
+
+jpegmarker(is: ref ImageSource) : int
+{
+ if(getc(is) != 16rFF)
+ imgerror(is, "Jpeg expected marker");
+ return getc(is);
+}
+
+# Consume tables and miscellaneous marker segments,
+# returning the marker id and length of the first non-such-segment
+# (after having consumed the marker).
+# May raise "premature eof" or other exception.
+jpegtabmisc(is: ref ImageSource) : (int, int)
+{
+ h := is.jstate;
+ m, n : int;
+Loop:
+ for(;;) {
+ h.nseg++;
+ m = jpegmarker(is);
+ n = 0;
+ if(m != EOI)
+ n = getbew(is) - 2;
+ if(dbg > 1)
+ sys->print("jpegtabmisc reading segment, got m=%x, n=%d\n", m, n);
+ case m {
+ SOF or SOF2 or SOS or EOI =>
+ break Loop;
+
+ APPn+0 =>
+ if(h.nseg==1 && n >= 6) {
+ (buf, i) := getn(is, 6);
+ n -= 6;
+ if(string buf[i:i+4]=="JFIF") {
+ vers0 := int buf[i+5];
+ vers1 := int buf[i+6];
+ if(vers0>1 || vers1>2)
+ imgerror(is, "Jpeg unimplemented version");
+ }
+ }
+
+ APPn+1 to APPn+15 =>
+ ;
+
+ DQT =>
+ jpegquanttables(is, n);
+ n = 0;
+
+ DHT =>
+ jpeghuffmantables(is, n);
+ n = 0;
+
+ DRI =>
+ h.ri =getbew(is);
+ n -= 2;
+
+ COM =>
+ ;
+
+ * =>
+ imgerror(is, "Jpeg unexpected marker");
+ }
+ if(n > 0)
+ getn(is, n);
+ }
+ return (m, n);
+}
+
+# Consume huffman tables, raising exception on error.
+jpeghuffmantables(is: ref ImageSource, n: int)
+{
+ if(dbg)
+ sys->print("jpeghuffmantables\n");
+ h := is.jstate;
+ if(h.dcht == nil) {
+ h.dcht = array[4] of ref Huffman;
+ h.acht = array[4] of ref Huffman;
+ }
+ for(l:= 0; l < n; )
+ l += jpeghuffmantable(is);
+ if(l != n)
+ imgerror(is, "Jpeg huffman table bad length");
+}
+
+jpeghuffmantable(is: ref ImageSource) : int
+{
+ t := ref Huffman;
+ h := is.jstate;
+ (Tc, th) := nibbles(getc(is));
+ if(dbg > 1)
+ sys->print("jpeghuffmantable, Tc=%d, th=%d\n", Tc, th);
+ if(Tc > 1)
+ imgerror(is, "Jpeg unknown Huffman table class");
+ if(th>3 || (h.mode==byte SOF && th>1))
+ imgerror(is, "Jpeg unknown Huffman table index");
+ if(Tc == 0)
+ h.dcht[th] = t;
+ else
+ h.acht[th] = t;
+
+ # flow chart C-2
+ (b, bi) := getn(is, 16);
+ numcodes := array[16] of int;
+ nsize := 0;
+ for(i:=0; i<16; i++)
+ nsize += (numcodes[i] = int b[bi+i]);
+ t.size = array[nsize+1] of int;
+ k := 0;
+ for(i=1; i<=16; i++) {
+ n :=numcodes[i-1];
+ for(j:=0; j<n; j++)
+ t.size[k++] = i;
+ }
+ t.size[k] = 0;
+
+ # initialize HUFFVAL
+ t.val = array[nsize] of int;
+ (b, bi) = getn(is, nsize);
+ for(i=0; i<nsize; i++)
+ t.val[i] = int b[bi++];
+
+ # flow chart C-3
+ t.code = array[nsize+1] of int;
+ k = 0;
+ code := 0;
+ si := t.size[0];
+ for(;;) {
+ do
+ t.code[k++] = code++;
+ while(t.size[k] == si);
+ if(t.size[k] == 0)
+ break;
+ do {
+ code <<= 1;
+ si++;
+ } while(t.size[k] != si);
+ }
+
+ # flow chart F-25
+ t.mincode = array[17] of int;
+ t.maxcode = array[17] of int;
+ t.valptr = array[17] of int;
+ i = 0;
+ j := 0;
+ F25:
+ for(;;) {
+ for(;;) {
+ i++;
+ if(i > 16)
+ break F25;
+ if(numcodes[i-1] != 0)
+ break;
+ t.maxcode[i] = -1;
+ }
+ t.valptr[i] = j;
+ t.mincode[i] = t.code[j];
+ j += int numcodes[i-1]-1;
+ t.maxcode[i] = t.code[j];
+ j++;
+ }
+
+ # create byte-indexed fast path tables
+ t.value = array[256] of int;
+ t.shift = array[256] of int;
+ maxcode := t.maxcode;
+ # stupid startup algorithm: just run machine for each byte value
+ Bytes:
+ for(v:=0; v<256; v++){
+ cnt := 7;
+ m := 1<<7;
+ code = 0;
+ sr := v;
+ i = 1;
+ for(;;i++){
+ if(sr & m)
+ code |= 1;
+ if(code <= maxcode[i])
+ break;
+ code <<= 1;
+ m >>= 1;
+ if(m == 0){
+ t.shift[v] = 0;
+ t.value[v] = -1;
+ continue Bytes;
+ }
+ cnt--;
+ }
+ t.shift[v] = 8-cnt;
+ t.value[v] = t.val[t.valptr[i]+(code-t.mincode[i])];
+ }
+ if(dbg > 2) {
+ sys->print("Huffman table %d:\n", th);
+ printarray(t.size, "size");
+ printarray(t.code, "code");
+ printarray(t.val, "val");
+ printarray(t.mincode, "mincode");
+ printarray(t.maxcode, "maxcode");
+ printarray(t.value, "value");
+ printarray(t.shift, "shift");
+ }
+
+ return nsize+17;
+}
+
+jpegquanttables(is: ref ImageSource, n: int)
+{
+ if(dbg)
+ sys->print("jpegquanttables\n");
+ h := is.jstate;
+ if(h.qt == nil)
+ h.qt = array[4] of array of int;
+ for(l:=0; l<n; )
+ l += jpegquanttable(is);
+ if(l != n)
+ imgerror(is, "Jpeg quant table bad length");
+}
+
+jpegquanttable(is: ref ImageSource): int
+{
+ (pq, tq) := nibbles(getc(is));
+ if(dbg)
+ sys->print("jpegquanttable pq=%d tq=%d\n", pq, tq);
+ if(pq > 1)
+ imgerror(is, "Jpeg unknown quantization table class");
+ if(tq > 3)
+ imgerror(is, "Jpeg bad quantization table index");
+ q := array[64] of int;
+ is.jstate.qt[tq] = q;
+ for(i:=0; i<64; i++) {
+ if(pq == 0)
+ q[i] =getc(is);
+ else
+ q[i] = getbew(is);
+ }
+ if(dbg > 2)
+ printarray(q, "quant table");
+ return 1+(64*(1+pq));;
+}
+
+# Have just read Frame header.
+# Now expect:
+# ((tabl/misc segment(s))* (scan header) (entropy coded segment)+)+ EOI
+getjpegmim(is: ref ImageSource) : ref MaskedImage
+{
+ if(dbg)
+ sys->print("getjpegmim\n");
+ if(dbgev)
+ CU->event("IMAGE_GETJPGMIM", is.width*is.height);
+ getjpeghdr(is);
+ h := is.jstate;
+ chans: array of array of byte = nil;
+ for(;;) {
+ (m, n) := jpegtabmisc(is);
+ if(m == EOI)
+ break;
+ if(m != SOS)
+ imgerror(is, "Jpeg expected start of scan");
+ h.Ns = getc(is);
+ if(dbg)
+ sys->print("start of scan, Ns=%d\n", h.Ns);
+ scomp := array[h.Ns] of Scancomp;
+ for(i := 0; i < h.Ns; i++) {
+ scomp[i].C = getc(is);
+ (scomp[i].tdc, scomp[i].tac) = nibbles(getc(is));
+ }
+ h.scomp = scomp;
+ h.Ss = getc(is);
+ h.Se = getc(is);
+ (h.Ah, h.Al) = nibbles(getc(is));
+ if(n != 4+h.Ns*2)
+ imgerror(is, "Jpeg SOS header wrong length");
+
+ if(h.mode == byte SOF) {
+ if(chans != nil)
+ imgerror(is, "Jpeg baseline has > 1 scan");
+ chans = jpegbaselinescan(is);
+ }
+ else
+ jpegprogressivescan(is);
+ }
+ if(h.mode == byte SOF2)
+ chans = jprogressiveIDCT(is);
+ if(chans == nil)
+ imgerror(is, "jpeg has no image");
+ width := is.width;
+ height := is.height;
+ if(width != h.X || height != h.Y) {
+ for(k := 0; k < len chans; k++)
+ chans[k] = resample(chans[k], h.X, h.Y, width, height);
+ }
+ if(dbgev)
+ CU->event("IMAGE_JPG_REMAP", 0);
+ if(len chans == 1) {
+ im := newimagegrey(is, width, height);
+ im.writepixels(im.r, chans[0]);
+ return newmi(im);
+# remapgrey(chans[0], width, height);
+ } else {
+ if (len chans == 3) {
+ r := remapYCbCr(chans);
+ im := newimage24(is, width, height);
+ im.writepixels(im.r, r);
+ return newmi(im);
+ }
+ remaprgb(chans, width, height, jpegcolorspace);
+ }
+ if(dbgev)
+ CU->event("IMAGE_JPG_REMAP_END", 0);
+ im := newimage(is, width, height);
+ im.writepixels(im.r, chans[0]);
+ if(dbgev)
+ CU->event("IMAGE_GETJPGMIM_END", 0);
+ return newmi(im);
+}
+
+remapYCbCr(chans: array of array of byte): array of byte
+{
+ Y := chans[0];
+ Cb := chans[1];
+ Cr := chans[2];
+
+ rgb := array [3*len Y] of byte;
+ bix := 0;
+ for (i := 0; i < len Y; i++) {
+ y := int Y[i];
+ cb := int Cb[i];
+ cr := int Cr[i];
+ r := y + Cr2r[cr];
+ g := y - Cr2g[cr] - Cb2g[cb];
+ b := y + Cb2b[cb];
+
+ rgb[bix++] = clampb[b+CLAMPBOFF];
+ rgb[bix++] = clampb[g+CLAMPBOFF];
+ rgb[bix++] = clampb[r+CLAMPBOFF];
+ }
+ return rgb;
+}
+
+zig := array[64] of {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, # 0-7
+ 24, 32, 25, 18, 11, 4, 5, # 8-15
+ 12, 19, 26, 33, 40, 48, 41, 34, # 16-23
+ 27, 20, 13, 6, 7, 14, 21, 28, # 24-31
+ 35, 42, 49, 56, 57, 50, 43, 36, # 32-39
+ 29, 22, 15, 23, 30, 37, 44, 51, # 40-47
+ 58, 59, 52, 45, 38, 31, 39, 46, # 48-55
+ 53, 60, 61, 54, 47, 55, 62, 63 # 56-63
+};
+
+jpegbaselinescan(is: ref ImageSource) : array of array of byte
+{
+ if(dbg)
+ sys->print("jpegbaselinescan\n");
+ if(dbgev)
+ CU->event("IMAGE_JPGBASELINESCAN", 0);
+ h := is.jstate;
+ Ns := h.Ns;
+ if(Ns != h.Nf)
+ imgerror(is, "Jpeg baseline needs Ns==Nf");
+ if(!(Ns==3 || Ns==1))
+ imgerror(is, "Jpeg baseline needs Ns==1 or 3");
+
+ res := ResourceState.cur();
+ heapavail := res.heaplim - res.heap;
+
+ # check heap availability for
+ # chans: (3+Ns)*4 + (Ns*(3*4+h.X*h.Y)) bytes
+ # Td, Ta, data, H, V, DC: 6 arrays of (3+Ns)*4 bytes
+ #
+ heapavail -= (3+Ns)*28 + (Ns*(12 + h.X * h.Y));
+ if(heapavail <= 0) {
+ if(dbg)
+ sys->print("jpegbaselinescan: no memory for chans et al.\n");
+ imgerror(is, "not enough memory");
+ }
+
+ chans := array[h.Nf] of array of byte;
+ for(k:=0; k<h.Nf; k++)
+ chans[k] = array[h.X*h.Y] of byte;
+
+ # build per-component arrays
+ Td := array[Ns] of int;
+ Ta := array[Ns] of int;
+ data := array[Ns] of array of array of int;
+ H := array[Ns] of int;
+ V := array[Ns] of int;
+ DC := array[Ns] of int;
+
+ # compute maximum H and V
+ Hmax := 0;
+ Vmax := 0;
+ for(comp:=0; comp<Ns; comp++) {
+ if(h.comp[comp].H > Hmax)
+ Hmax = h.comp[comp].H;
+ if(h.comp[comp].V > Vmax)
+ Vmax = h.comp[comp].V;
+ }
+ if(dbg > 1)
+ sys->print("Hmax=%d, Vmax=%d\n", Hmax, Vmax);
+
+ # initialize data structures
+ allHV1 := 1;
+ for(comp=0; comp<Ns; comp++) {
+ # JPEG requires scan components to be in same order as in frame,
+ # so if both have 3 we know scan is Y Cb Cr and there's no need to
+ # reorder
+ Td[comp] = h.scomp[comp].tdc;
+ Ta[comp] = h.scomp[comp].tac;
+ H[comp] = h.comp[comp].H;
+ V[comp] = h.comp[comp].V;
+ nblock := H[comp]*V[comp];
+ if(nblock != 1)
+ allHV1 = 0;
+
+ # data[comp]: needs (3+nblock)*4 + nblock*(3+8*8)*4 bytes
+ heapavail -= 272*nblock + 12;
+ if(heapavail <= 0){
+ if(dbg)
+ sys->print("jpegbaselinescan: no memory for data\n");
+ imgerror(is, "not enough memory");
+ }
+
+ data[comp] = array[nblock] of array of int;
+ DC[comp] = 0;
+ for(m:=0; m<nblock; m++)
+ data[comp][m] = array[8*8] of int;
+ if(dbg > 2)
+ sys->print("scan comp %d: H=%d, V=%d, nblock=%d, Td=%d, Ta=%d\n",
+ comp, H[comp], V[comp], nblock, Td[comp], Ta[comp]);
+ }
+
+ ri := h.ri;
+
+ h.cnt = 0;
+ h.sr = 0;
+ nacross := ((h.X+(8*Hmax-1))/(8*Hmax));
+ nmcu := ((h.Y+(8*Vmax-1))/(8*Vmax))*nacross;
+ if(dbg)
+ sys->print("nacross=%d, nmcu=%d\n", nacross, nmcu);
+ for(mcu:=0; mcu<nmcu; ) {
+ if(dbg > 2)
+ sys->print("mcu %d\n", mcu);
+ for(comp=0; comp<Ns; comp++) {
+ if(dbg > 2)
+ sys->print("comp %d\n", comp);
+ dcht := h.dcht[Td[comp]];
+ acht := h.acht[Ta[comp]];
+ qt := h.qt[h.comp[comp].Tq];
+
+ for(block:=0; block<H[comp]*V[comp]; block++) {
+ if(dbg > 2)
+ sys->print("block %d\n", block);
+ # F-22
+ t := jdecode(is, dcht);
+ diff := jreceive(is, t);
+ DC[comp] += diff;
+ if(dbg > 2)
+ sys->print("t=%d, diff=%d, DC=%d\n", t, diff, DC[comp]);
+
+ # F-23
+ zz := data[comp][block];
+ zz[0:] = zeroints;
+ zz[0] = qt[0]*DC[comp];
+ k = 1;
+
+ for(;;) {
+ rs := jdecode(is, acht);
+ (rrrr, ssss) := nibbles(rs);
+ if(ssss == 0){
+ if(rrrr != 15)
+ break;
+ k += 16;
+ }else{
+ k += rrrr;
+ z := jreceive(is, ssss);
+ zz[zig[k]] = z*qt[k];
+ if(k == 63)
+ break;
+ k++;
+ }
+ }
+
+ idct(zz);
+ }
+ }
+
+ # rotate colors to RGB and assign to bytes
+ if(Ns == 1) # very easy
+ colormap1(h, chans[0], data[0][0], mcu, nacross);
+ else if(allHV1) # fairly easy
+ colormapall1(h, chans, data[0][0], data[1][0], data[2][0], mcu, nacross);
+ else # miserable general case
+ colormap(h, chans, data[0], data[1], data[2], mcu, nacross, Hmax, Vmax, H, V);
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0){
+ jrestart(is, mcu);
+ for(comp=0; comp<Ns; comp++)
+ DC[comp] = 0;
+ }
+ }
+ if(dbgev)
+ CU->event("IMAGE_JPGBASELINESCAN_END", 0);
+ return chans;
+}
+
+jrestart(is: ref ImageSource, mcu: int)
+{
+ h := is.jstate;
+ ri := h.ri;
+ restart := mcu/ri-1;
+ rst, nskip: int;
+ nskip = 0;
+ do {
+ do{
+ rst = jnextborm(is);
+ nskip++;
+ }while(rst>=0 && rst!=16rFF);
+ if(rst == 16rFF){
+ rst = jnextborm(is);
+ nskip++;
+ }
+ } while(rst>=0 && (rst&~7)!= RST);
+ if(nskip != 2 || rst < 0 || ((rst&7) != (restart&7)))
+ imgerror(is, "Jpeg restart problem");
+ h.cnt = 0;
+ h.sr = 0;
+}
+
+jpegprogressivescan(is: ref ImageSource)
+{
+ if(dbgev)
+ CU->event("IMAGE_JPGPROGSCAN", 0);
+ h := is.jstate;
+ if(h.dccoeff == nil)
+ jprogressiveinit(is, h);
+
+ c := h.scomp[0].C;
+ comp := -1;
+ for(i:=0; i<h.Nf; i++)
+ if(h.comp[i].C == c)
+ comp = i;
+ if(comp == -1)
+ imgerror(is, "Jpeg bad component index in scan header");
+
+ if(h.Ss == 0)
+ jprogressivedc(is, comp);
+ else if(h.Ah == 0)
+ jprogressiveac(is, comp);
+ else
+ jprogressiveacinc(is, comp);
+ if(dbgev)
+ CU->event("IMAGE_JPGPROGSCAN_END", 0);
+}
+
+jprogressiveIDCT(is: ref ImageSource): array of array of byte
+{
+ if(dbgev)
+ CU->event("IMAGE_JPGPROGIDCT", 0);
+ h := is.jstate;
+ Nf := h.Nf;
+
+ res := ResourceState.cur();
+ heapavail := res.heaplim - res.heap;
+
+ # check heap availability for
+ # H, V, data, blockno: 4 arrays of (3+Nf)*4 bytes
+ # chans: (3+Nf)*4 + (Nf*(3*4+h.X*h.Y)) bytes
+ #
+ heapavail -= (3+Nf)*20 + (Nf*(12 + h.X * h.Y));
+ if(heapavail <= 0) {
+ if(dbg)
+ sys->print("jprogressiveIDCT: no memory for chans et al.\n");
+ imgerror(is, "not enough memory");
+ }
+ H := array[Nf] of int;
+ V := array[Nf] of int;
+
+ allHV1 := 1;
+
+ data := array[Nf] of array of array of int;
+ for(comp:=0; comp<Nf; comp++){
+ H[comp] = h.comp[comp].H;
+ V[comp] = h.comp[comp].V;
+ nblock := h.nblock[comp];
+ if(nblock != 1)
+ allHV1 = 0;
+
+ # data[comp]: needs (3+nblock)*4 + nblock*(3+8*8)*4 bytes
+ heapavail -= 272*nblock + 12;
+ if(heapavail <= 0){
+ if(dbg)
+ sys->print("jprogressiveIDCT: no memory for data\n");
+ imgerror(is, "not enough memory");
+ }
+
+ data[comp] = array[nblock] of array of int;
+ for(m:=0; m<nblock; m++)
+ data[comp][m] = array[8*8] of int;
+ }
+
+ chans := array[h.Nf] of array of byte;
+ for(k:=0; k<h.Nf; k++)
+ chans[k] = array[h.X*h.Y] of byte;
+
+ blockno := array[Nf] of {* => 0};
+ nmcu := h.nacross*h.ndown;
+ for(mcu:=0; mcu<nmcu; mcu++){
+ for(comp=0; comp<Nf; comp++){
+ dccoeff := h.dccoeff[comp];
+ accoeff := h.accoeff[comp];
+ bn := blockno[comp];
+ for(block:=0; block<h.nblock[comp]; block++){
+ zz := data[comp][block];
+ zz[0:] = zeroints;
+ zz[0] = dccoeff[bn];
+
+ for(k=1; k<64; k++)
+ zz[zig[k]] = accoeff[bn][k];
+
+ idct(zz);
+ bn++;
+ }
+ blockno[comp] = bn;
+ }
+
+ # rotate colors to RGB and assign to bytes
+ if(Nf == 1) # very easy
+ colormap1(h, chans[0], data[0][0], mcu, h.nacross);
+ else if(allHV1) # fairly easy
+ colormapall1(h, chans, data[0][0], data[1][0], data[2][0], mcu, h.nacross);
+ else # miserable general case
+ colormap(h, chans, data[0], data[1], data[2], mcu, h.nacross, h.Hmax, h.Vmax, H, V);
+ }
+ return chans;
+}
+
+jprogressiveinit(is: ref ImageSource, h: ref Jpegstate)
+{
+ Ns := h.Ns;
+ Nf := h.Nf;
+ if((Ns!=3 && Ns!=1) || Ns!=Nf)
+ imgerror(is, "Jpeg image must have 1 or 3 components");
+
+ # compute maximum H and V
+ h.Hmax = 0;
+ h.Vmax = 0;
+ for(comp:=0; comp<Nf; comp++){
+ if(h.comp[comp].H > h.Hmax)
+ h.Hmax = h.comp[comp].H;
+ if(h.comp[comp].V > h.Vmax)
+ h.Vmax = h.comp[comp].V;
+ }
+ h.nacross = ((h.X+(8*h.Hmax-1))/(8*h.Hmax));
+ h.ndown = ((h.Y+(8*h.Vmax-1))/(8*h.Vmax));
+ nmcu := h.nacross*h.ndown;
+
+ res := ResourceState.cur();
+ heapavail := res.heaplim - res.heap;
+
+ # check heap availability for
+ # h.dccoeff: (3+Nf)*4 bytes
+ # h.accoeff: (3+Nf)*4 bytes
+ heapavail -= (3+Nf)*8;
+ if(heapavail <= 0) {
+ if(dbg)
+ sys->print("jprogressiveinit: no memory for coeffs\n");
+ imgerror(is, "not enough memory");
+ }
+
+ h.dccoeff = array[Nf] of array of int;
+ h.accoeff = array[Nf] of array of array of int;
+ for(k:=0; k<Nf; k++){
+ n := h.nblock[k]*nmcu;
+
+ # check heap availability for
+ # h.dccoeff[k]: (3+n)*4 bytes
+ # h.accoeff[k]: (3+n)*4 + n*(3+64)*4 bytes
+ heapavail -= 276*n + 24;
+ if(heapavail <= 0){
+ if(dbg)
+ sys->print("jprogressiveinit: no memory for coeff arrays\n");
+ imgerror(is, "not enough memory");
+ }
+
+ h.dccoeff[k] = array[n] of {* => 0};
+ h.accoeff[k] = array[n] of array of int;
+ for(j:=0; j<n; j++)
+ h.accoeff[k][j] = array[64] of {* => 0};
+ }
+}
+
+jprogressivedc(is: ref ImageSource, comp: int)
+{
+ h := is.jstate;
+ Ns := h.Ns;
+ Ah := h.Ah;
+ Al := h.Al;
+ if(Ns!=h.Nf)
+ imgerror(is, "Jpeg progressive with Nf!=Ns in DC scan");
+
+ # build per-component arrays
+ Td := array[Ns] of int;
+ DC := array[Ns] of int;
+
+ # initialize data structures
+ h.cnt = 0;
+ h.sr = 0;
+ for(comp=0; comp<Ns; comp++) {
+ # JPEG requires scan components to be in same order as in frame,
+ # so if both have 3 we know scan is Y Cb Cr and there's no need to
+ # reorder
+ Td[comp] = h.scomp[comp].tdc;
+ DC[comp] = 0;
+ }
+
+ ri := h.ri;
+
+ nmcu := h.nacross*h.ndown;
+ blockno := array[Ns] of {* => 0};
+ for(mcu:=0; mcu<nmcu; ){
+ for(comp=0; comp<Ns; comp++){
+ dcht := h.dcht[Td[comp]];
+ qt := h.qt[h.comp[comp].Tq][0];
+ dc := h.dccoeff[comp];
+ bn := blockno[comp];
+
+ for(block:=0; block<h.nblock[comp]; block++) {
+ if(Ah == 0) {
+ t := jdecode(is, dcht);
+ diff := jreceive(is, t);
+ DC[comp] += diff;
+ dc[bn] = qt*DC[comp]<<Al;
+ } else
+ dc[bn] |= qt*jreceivebit(is)<<Al;
+ bn++;
+ }
+ blockno[comp] = bn;
+ }
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0){
+ jrestart(is, mcu);
+ for(comp=0; comp<Ns; comp++)
+ DC[comp] = 0;
+ }
+ }
+}
+
+jprogressiveac(is: ref ImageSource, comp: int)
+{
+ h := is.jstate;
+ Ns := h.Ns;
+ Al := h.Al;
+ if(Ns != 1)
+ imgerror(is, "Jpeg illegal Ns>1 in progressive AC scan");
+ Ss := h.Ss;
+ Se := h.Se;
+ H := h.comp[comp].H;
+ V := h.comp[comp].V;
+
+ nacross := h.nacross*H;
+ ndown := h.ndown*V;
+ q := 8*h.Hmax/H;
+ nhor := (h.X+q-1)/q;
+ q = 8*h.Vmax/V;
+ nver := (h.Y+q-1)/q;
+
+ # initialize data structures
+ h.cnt = 0;
+ h.sr = 0;
+ Ta := h.scomp[0].tac;
+
+ ri := h.ri;
+
+ eobrun := 0;
+ acht := h.acht[Ta];
+ qt := h.qt[h.comp[comp].Tq];
+ nmcu := nacross*ndown;
+ mcu := 0;
+ for(y:=0; y<nver; y++) {
+ for(x:=0; x<nhor; x++) {
+ # Figure G-3
+ if(eobrun > 0){
+ --eobrun;
+ continue;
+ }
+
+ # arrange blockno to be in same sequence as
+ # original scan calculation.
+ tmcu := x/H + (nacross/H)*(y/V);
+ blockno := tmcu*H*V + H*(y%V) + x%H;
+ acc := h.accoeff[comp][blockno];
+ k := Ss;
+ for(;;) {
+ rs := jdecode(is, acht);
+ (rrrr, ssss) := nibbles(rs);
+ if(ssss == 0) {
+ if(rrrr < 15) {
+ eobrun = 0;
+ if(rrrr > 0)
+ eobrun = jreceiveEOB(is, rrrr)-1;
+ break;
+ }
+ k += 16;
+ }
+ else {
+ k += rrrr;
+ z := jreceive(is, ssss);
+ acc[k] = z*qt[k]<<Al;
+ if(k == Se)
+ break;
+ k++;
+ }
+ }
+ }
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0) {
+ jrestart(is, mcu);
+ eobrun = 0;
+ }
+ }
+}
+
+jprogressiveacinc(is: ref ImageSource, comp: int)
+{
+ h := is.jstate;
+ Ns := h.Ns;
+ if(Ns != 1)
+ imgerror(is, "Jpeg illegal Ns>1 in progressive AC scan");
+ Ss := h.Ss;
+ Se := h.Se;
+ H := h.comp[comp].H;
+ V := h.comp[comp].V;
+ Al := h.Al;
+
+ nacross := h.nacross*H;
+ ndown := h.ndown*V;
+ q := 8*h.Hmax/H;
+ nhor := (h.X+q-1)/q;
+ q = 8*h.Vmax/V;
+ nver := (h.Y+q-1)/q;
+
+ # initialize data structures
+ h.cnt = 0;
+ h.sr = 0;
+ Ta := h.scomp[0].tac;
+ ri := h.ri;
+
+ eobrun := 0;
+ ac := h.accoeff[comp];
+ acht := h.acht[Ta];
+ qt := h.qt[h.comp[comp].Tq];
+ nmcu := nacross*ndown;
+ mcu := 0;
+ pending := 0;
+ nzeros := -1;
+ for(y:=0; y<nver; y++){
+ for(x:=0; x<nhor; x++){
+ # Figure G-7
+
+ # arrange blockno to be in same sequence as
+ # original scan calculation.
+ tmcu := x/H + (nacross/H)*(y/V);
+ blockno := tmcu*H*V + H*(y%V) + x%H;
+ acc := ac[blockno];
+ if(eobrun > 0){
+ if(nzeros > 0)
+ imgerror(is, "Jpeg zeros pending at block start");
+ for(k:=Ss; k<=Se; k++)
+ jincrement(is, acc, k, qt[k]<<Al);
+ --eobrun;
+ continue;
+ }
+
+ for(k:=Ss; k<=Se; ){
+ if(nzeros >= 0){
+ if(acc[k] != 0)
+ jincrement(is, acc, k, qt[k]<<Al);
+ else if(nzeros-- == 0)
+ acc[k] = pending;
+ k++;
+ continue;
+ }
+ rs := jdecode(is, acht);
+ (rrrr, ssss) := nibbles(rs);
+ if(ssss == 0){
+ if(rrrr < 15){
+ eobrun = 0;
+ if(rrrr > 0)
+ eobrun = jreceiveEOB(is, rrrr)-1;
+ while(k <= Se){
+ jincrement(is, acc, k, qt[k]<<Al);
+ k++;
+ }
+ break;
+ }
+ for(i:=0; i<16; k++){
+ jincrement(is, acc, k, qt[k]<<Al);
+ if(acc[k] == 0)
+ i++;
+ }
+ continue;
+ }else if(ssss != 1)
+ imgerror(is, "Jpeg ssss!=1 in progressive increment");
+ nzeros = rrrr;
+ pending = jreceivebit(is);
+ if(pending == 0)
+ pending = -1;
+ pending *= qt[k]<<Al;
+ }
+ }
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0){
+ jrestart(is, mcu);
+ eobrun = 0;
+ nzeros = -1;
+ }
+ }
+}
+
+jincrement(is: ref ImageSource, acc: array of int, k, Pt: int)
+{
+ if(acc[k] == 0)
+ return;
+ b := jreceivebit(is);
+ if(b != 0)
+ if(acc[k] < 0)
+ acc[k] -= Pt;
+ else
+ acc[k] += Pt;
+}
+
+jc1: con 2871; # 1.402 * 2048
+jc2: con 705; # 0.34414 * 2048
+jc3: con 1463; # 0.71414 * 2048
+jc4: con 3629; # 1.772 * 2048
+
+# Fills in pixels (x,y) for x = minx=8*(mcu%nacross), minx+1, ..., minx+7 (or h.X-1, if less)
+# and for y = miny=8*(mcu/nacross), miny+1, ..., miny+7 (or h.Y-1, if less)
+colormap1(h: ref Jpegstate, pic: array of byte, data: array of int, mcu, nacross: int)
+{
+ minx := 8*(mcu%nacross);
+ dx := 8;
+ if(minx+dx > h.X)
+ dx = h.X-minx;
+ miny := 8*(mcu/nacross);
+ dy := 8;
+ if(miny+dy > h.Y)
+ dy = h.Y-miny;
+ pici := miny*h.X+minx;
+ k := 0;
+ for(y:=0; y<dy; y++) {
+ for(x:=0; x<dx; x++)
+ pic[pici+x] = clampb[(data[k+x]+128)+CLAMPBOFF];
+ pici += h.X;
+ k += 8;
+ }
+}
+
+# Fills in same pixels as colormap1
+colormapall1(h: ref Jpegstate, chans: array of array of byte, data0, data1, data2: array of int, mcu, nacross: int)
+{
+ rpic := chans[0];
+ gpic := chans[1];
+ bpic := chans[2];
+ minx := 8*(mcu%nacross);
+ dx := 8;
+ if(minx+dx > h.X)
+ dx = h.X-minx;
+ miny := 8*(mcu/nacross);
+ dy := 8;
+ if(miny+dy > h.Y)
+ dy = h.Y-miny;
+ pici := miny*h.X+minx;
+ k := 0;
+ for(y:=0; y<dy; y++) {
+ for(x:=0; x<dx; x++){
+ if(jpegcolorspace == CYCbCr) {
+ rpic[pici+x] = clampb[data0[k+x]+128+CLAMPBOFF];
+ gpic[pici+x] = clampb[data1[k+x]+128+CLAMPBOFF];
+ bpic[pici+x] = clampb[data2[k+x]+128+CLAMPBOFF];
+ }
+ else { # RGB
+ Y := (data0[k+x]+128) << 11;
+ Cb := data1[k+x];
+ Cr := data2[k+x];
+ r := Y+jc1*Cr;
+ g := Y-jc2*Cb-jc3*Cr;
+ b := Y+jc4*Cb;
+ rpic[pici+x] = clampb[(r>>11)+CLAMPBOFF];
+ gpic[pici+x] = clampb[(g>>11)+CLAMPBOFF];
+ bpic[pici+x] = clampb[(b>>11)+CLAMPBOFF];
+ }
+ }
+ pici += h.X;
+ k += 8;
+ }
+}
+
+# Fills in pixels (x,y) for x = minx=8*Hmax*(mcu%nacross), minx+1, ..., minx+8*Hmax-1 (or h.X-1, if less)
+# and for y = miny=8*Vmax*(mcu/nacross), miny+1, ..., miny+8*Vmax-1 (or h.Y-1, if less)
+colormap(h: ref Jpegstate, chans: array of array of byte, data0, data1, data2: array of array of int, mcu, nacross, Hmax, Vmax: int, H, V: array of int)
+{
+ rpic := chans[0];
+ gpic := chans[1];
+ bpic := chans[2];
+ minx := 8*Hmax*(mcu%nacross);
+ dx := 8*Hmax;
+ if(minx+dx > h.X)
+ dx = h.X-minx;
+ miny := 8*Vmax*(mcu/nacross);
+ dy := 8*Vmax;
+ if(miny+dy > h.Y)
+ dy = h.Y-miny;
+ pici := miny*h.X+minx;
+ H0 := H[0];
+ H1 := H[1];
+ H2 := H[2];
+ if(dbg > 2)
+ sys->print("colormap, minx=%d, miny=%d, dx=%d, dy=%d, pici=%d, H0=%d, H1=%d, H2=%d\n",
+ minx, miny, dx, dy, pici, H0, H1, H2);
+ for(y:=0; y<dy; y++) {
+ t := y*V[0];
+ b0 := H0*(t/(8*Vmax));
+ y0 := 8*((t/Vmax)&7);
+ t = y*V[1];
+ b1 := H1*(t/(8*Vmax));
+ y1 := 8*((t/Vmax)&7);
+ t = y*V[2];
+ b2 := H2*(t/(8*Vmax));
+ y2 := 8*((t/Vmax)&7);
+ x0 := 0;
+ x1 := 0;
+ x2 := 0;
+ for(x:=0; x<dx; x++) {
+ if(jpegcolorspace == CYCbCr) {
+ rpic[pici+x] = clampb[data0[b0][y0+x0++*H0/Hmax] + 128 + CLAMPBOFF];
+ gpic[pici+x] = clampb[data1[b1][y1+x1++*H1/Hmax] + 128 + CLAMPBOFF];
+ bpic[pici+x] = clampb[data2[b2][y2+x2++*H2/Hmax] + 128 + CLAMPBOFF];
+ }
+ else { # RGB
+ Y := (data0[b0][y0+x0++*H0/Hmax]+128) << 11;
+ Cb := data1[b1][y1+x1++*H1/Hmax];
+ Cr := data2[b2][y2+x2++*H2/Hmax];
+ r := Y+jc1*Cr;
+ g := Y-jc2*Cb-jc3*Cr;
+ b := Y+jc4*Cb;
+ rpic[pici+x] = clampb[(r>>11)+CLAMPBOFF];
+ gpic[pici+x] = clampb[(g>>11)+CLAMPBOFF];
+ bpic[pici+x] = clampb[(b>>11)+CLAMPBOFF];
+ }
+ if(x0*H0/Hmax >= 8){
+ x0 = 0;
+ b0++;
+ }
+ if(x1*H1/Hmax >= 8){
+ x1 = 0;
+ b1++;
+ }
+ if(x2*H2/Hmax >= 8){
+ x2 = 0;
+ b2++;
+ }
+ }
+ pici += h.X;
+ }
+}
+
+# decode next 8-bit value from entropy-coded input. chart F-26
+jdecode(is: ref ImageSource, t: ref Huffman): int
+{
+ h := is.jstate;
+ maxcode := t.maxcode;
+ if(h.cnt < 8)
+ jnextbyte(is);
+ # fast lookup
+ code := (h.sr>>(h.cnt-8))&16rFF;
+ v := t.value[code];
+ if(v >= 0){
+ h.cnt -= t.shift[code];
+ return v;
+ }
+
+ h.cnt -= 8;
+ if(h.cnt == 0)
+ jnextbyte(is);
+ h.cnt--;
+ cnt := h.cnt;
+ m := 1<<cnt;
+ sr := h.sr;
+ code <<= 1;
+ i := 9;
+ for(;;i++){
+ if(sr & m)
+ code |= 1;
+ if(code <= maxcode[i])
+ break;
+ code <<= 1;
+ m >>= 1;
+ if(m == 0){
+ sr = jnextbyte(is);
+ m = 16r80;
+ cnt = 8;
+ }
+ cnt--;
+ }
+ h.cnt = cnt;
+ return t.val[t.valptr[i]+(code-t.mincode[i])];
+}
+
+# load next byte of input
+jnextbyte(is: ref ImageSource): int
+{
+ b :=getc(is);
+
+ if(b == 16rFF) {
+ b2 :=getc(is);
+ if(b2 != 0) {
+ if(b2 == int DNL)
+ imgerror(is, "Jpeg DNL marker unimplemented");
+ # decoder is reading into marker; satisfy it and restore state
+ ungetc2(is, byte b);
+ }
+ }
+ h := is.jstate;
+ h.cnt += 8;
+ h.sr = (h.sr<<8)| b;
+ return b;
+}
+
+# like jnextbyte, but look for marker too
+jnextborm(is: ref ImageSource): int
+{
+ b :=getc(is);
+
+ if(b == 16rFF)
+ return b;
+ h := is.jstate;
+ h.cnt += 8;
+ h.sr = (h.sr<<8)| b;
+ return b;
+}
+
+# return next s bits of input, MSB first, and level shift it
+jreceive(is: ref ImageSource, s: int): int
+{
+ h := is.jstate;
+ while(h.cnt < s)
+ jnextbyte(is);
+ h.cnt -= s;
+ v := h.sr >> h.cnt;
+ m := (1<<s);
+ v &= m-1;
+ # level shift
+ if(v < (m>>1))
+ v += ~(m-1)+1;
+ return v;
+}
+
+# return next s bits of input, decode as EOB
+jreceiveEOB(is: ref ImageSource, s: int): int
+{
+ h := is.jstate;
+ while(h.cnt < s)
+ jnextbyte(is);
+ h.cnt -= s;
+ v := h.sr >> h.cnt;
+ m := (1<<s);
+ v &= m-1;
+ # level shift
+ v += m;
+ return v;
+}
+
+# return next bit of input
+jreceivebit(is: ref ImageSource): int
+{
+ h := is.jstate;
+ if(h.cnt < 1)
+ jnextbyte(is);
+ h.cnt--;
+ return (h.sr >> h.cnt) & 1;
+}
+
+
+nibbles(c: int) : (int, int)
+{
+ return (c>>4, c&15);
+
+}
+
+# Scaled integer implementation.
+# inverse two dimensional DCT, Chen-Wang algorithm
+# (IEEE ASSP-32, pp. 803-816, Aug. 1984)
+# 32-bit integer arithmetic (8 bit coefficients)
+# 11 mults, 29 adds per DCT
+#
+# coefficients extended to 12 bit for IEEE1180-1990
+# compliance
+
+W1: con 2841; # 2048*sqrt(2)*cos(1*pi/16)
+W2: con 2676; # 2048*sqrt(2)*cos(2*pi/16)
+W3: con 2408; # 2048*sqrt(2)*cos(3*pi/16)
+W5: con 1609; # 2048*sqrt(2)*cos(5*pi/16)
+W6: con 1108; # 2048*sqrt(2)*cos(6*pi/16)
+W7: con 565; # 2048*sqrt(2)*cos(7*pi/16)
+
+W1pW7: con 3406; # W1+W7
+W1mW7: con 2276; # W1-W7
+W3pW5: con 4017; # W3+W5
+W3mW5: con 799; # W3-W5
+W2pW6: con 3784; # W2+W6
+W2mW6: con 1567; # W2-W6
+
+R2: con 181; # 256/sqrt(2)
+
+idct(b: array of int)
+{
+ # transform horizontally
+ for(y:=0; y<8; y++){
+ eighty := y<<3;
+ # if all non-DC components are zero, just propagate the DC term
+ if(b[eighty+1]==0)
+ if(b[eighty+2]==0 && b[eighty+3]==0)
+ if(b[eighty+4]==0 && b[eighty+5]==0)
+ if(b[eighty+6]==0 && b[eighty+7]==0){
+ v := b[eighty]<<3;
+ b[eighty+0] = v;
+ b[eighty+1] = v;
+ b[eighty+2] = v;
+ b[eighty+3] = v;
+ b[eighty+4] = v;
+ b[eighty+5] = v;
+ b[eighty+6] = v;
+ b[eighty+7] = v;
+ continue;
+ }
+ # prescale
+ x0 := (b[eighty+0]<<11)+128;
+ x1 := b[eighty+4]<<11;
+ x2 := b[eighty+6];
+ x3 := b[eighty+2];
+ x4 := b[eighty+1];
+ x5 := b[eighty+7];
+ x6 := b[eighty+5];
+ x7 := b[eighty+3];
+ # first stage
+ x8 := W7*(x4+x5);
+ x4 = x8 + W1mW7*x4;
+ x5 = x8 - W1pW7*x5;
+ x8 = W3*(x6+x7);
+ x6 = x8 - W3mW5*x6;
+ x7 = x8 - W3pW5*x7;
+ # second stage
+ x8 = x0 + x1;
+ x0 -= x1;
+ x1 = W6*(x3+x2);
+ x2 = x1 - W2pW6*x2;
+ x3 = x1 + W2mW6*x3;
+ x1 = x4 + x6;
+ x4 -= x6;
+ x6 = x5 + x7;
+ x5 -= x7;
+ # third stage
+ x7 = x8 + x3;
+ x8 -= x3;
+ x3 = x0 + x2;
+ x0 -= x2;
+ x2 = (R2*(x4+x5)+128)>>8;
+ x4 = (R2*(x4-x5)+128)>>8;
+ # fourth stage
+ b[eighty+0] = (x7+x1)>>8;
+ b[eighty+1] = (x3+x2)>>8;
+ b[eighty+2] = (x0+x4)>>8;
+ b[eighty+3] = (x8+x6)>>8;
+ b[eighty+4] = (x8-x6)>>8;
+ b[eighty+5] = (x0-x4)>>8;
+ b[eighty+6] = (x3-x2)>>8;
+ b[eighty+7] = (x7-x1)>>8;
+ }
+ # transform vertically
+ for(x:=0; x<8; x++){
+ # if all non-DC components are zero, just propagate the DC term
+ if(b[x+8*1]==0)
+ if(b[x+8*2]==0 && b[x+8*3]==0)
+ if(b[x+8*4]==0 && b[x+8*5]==0)
+ if(b[x+8*6]==0 && b[x+8*7]==0){
+ v := (b[x+8*0]+32)>>6;
+ b[x+8*0] = v;
+ b[x+8*1] = v;
+ b[x+8*2] = v;
+ b[x+8*3] = v;
+ b[x+8*4] = v;
+ b[x+8*5] = v;
+ b[x+8*6] = v;
+ b[x+8*7] = v;
+ continue;
+ }
+ # prescale
+ x0 := (b[x+8*0]<<8)+8192;
+ x1 := b[x+8*4]<<8;
+ x2 := b[x+8*6];
+ x3 := b[x+8*2];
+ x4 := b[x+8*1];
+ x5 := b[x+8*7];
+ x6 := b[x+8*5];
+ x7 := b[x+8*3];
+ # first stage
+ x8 := W7*(x4+x5) + 4;
+ x4 = (x8+W1mW7*x4)>>3;
+ x5 = (x8-W1pW7*x5)>>3;
+ x8 = W3*(x6+x7) + 4;
+ x6 = (x8-W3mW5*x6)>>3;
+ x7 = (x8-W3pW5*x7)>>3;
+ # second stage
+ x8 = x0 + x1;
+ x0 -= x1;
+ x1 = W6*(x3+x2) + 4;
+ x2 = (x1-W2pW6*x2)>>3;
+ x3 = (x1+W2mW6*x3)>>3;
+ x1 = x4 + x6;
+ x4 -= x6;
+ x6 = x5 + x7;
+ x5 -= x7;
+ # third stage
+ x7 = x8 + x3;
+ x8 -= x3;
+ x3 = x0 + x2;
+ x0 -= x2;
+ x2 = (R2*(x4+x5)+128)>>8;
+ x4 = (R2*(x4-x5)+128)>>8;
+ # fourth stage
+ b[x+8*0] = (x7+x1)>>14;
+ b[x+8*1] = (x3+x2)>>14;
+ b[x+8*2] = (x0+x4)>>14;
+ b[x+8*3] = (x8+x6)>>14;
+ b[x+8*4] = (x8-x6)>>14;
+ b[x+8*5] = (x0-x4)>>14;
+ b[x+8*6] = (x3-x2)>>14;
+ b[x+8*7] = (x7-x1)>>14;
+ }
+}
+
+################# Remap colors and Dither ##############
+
+closest_rgbpix(r, g, b: int) : int
+{
+ pix := int closestrgb[((r>>4)<<8)+((g>>4)<<4)+(b>>4)];
+ # If white is the closest but original r,g,b wasn't white,
+ # look for another color, because web page designer probably
+ # cares more about contrast than actual color
+ if(pix == 0 && !(r == 255 && g ==255 && b == 255)) {
+ bestdist := 1000000;
+ for(i := 1; i < 256; i++) {
+ dr := r-rgbvmap_r[i];
+ dg := g-rgbvmap_g[i];
+ db := b-rgbvmap_b[i];
+ d := dr*dr + dg*dg + db*db;
+ if(d < bestdist) {
+ bestdist = d;
+ pix = i;
+ }
+ }
+ }
+ return pix;
+}
+
+CLAMPBOFF: con 300;
+NCLAMPB: con CLAMPBOFF+256+CLAMPBOFF;
+CLAMPNOFF: con 64;
+NCLAMPN: con CLAMPNOFF+256+CLAMPNOFF;
+
+clampb: array of byte; # clamps byte values
+clampn_b: array of int; # clamps byte values, then shifts >> 4
+clampn_g: array of int; # clamps byte values, then masks off lower 4 bits
+clampn_r: array of int; # clamps byte values, masks off lower 4 bits, then shifts <<4
+
+init_tabs()
+{
+ clampn_b = array[NCLAMPN] of int;
+ clampn_g = array[NCLAMPN] of int;
+ clampn_r = array[NCLAMPN] of int;
+ for(j:=0; j<CLAMPNOFF; j++) {
+ clampn_b[j] = 0;
+ clampn_g[j] = 0;
+ clampn_r[j] = 0;
+ }
+ for(j=0; j<256; j++) {
+ t := j>>4;
+ clampn_b[CLAMPNOFF+j] = t;
+ clampn_g[CLAMPNOFF+j] = t<<4;
+ clampn_r[CLAMPNOFF+j] = t<<8;
+ }
+ for(j=0; j<CLAMPNOFF; j++) {
+ clampn_b[CLAMPNOFF+256+j] = 16r0F;
+ clampn_g[CLAMPNOFF+256+j] = 16rF0;
+ clampn_r[CLAMPNOFF+256+j] = 16rF00;
+ }
+ clampb = array[NCLAMPB] of byte;
+ for(j=0; j<CLAMPBOFF; j++)
+ clampb[j] = byte 0;
+ for(j=0; j<256; j++)
+ clampb[CLAMPBOFF+j] = byte j;
+ for(j=0; j<CLAMPBOFF; j++)
+ clampb[CLAMPBOFF+256+j] = byte 16rFF;
+}
+
+# could account for mask in alpha rather than having separate mask
+remap24(pic: array of byte, cmap: array of byte): array of byte
+{
+ cmap_r := array[256] of byte;
+ cmap_g := array[256] of byte;
+ cmap_b := array[256] of byte;
+ i := 0;
+ for(j := 0; j < 256 && i < len cmap; j++) {
+ cmap_r[j] = cmap[i++];
+ cmap_g[j] = cmap[i++];
+ cmap_b[j] = cmap[i++];
+ }
+ # in case input has bad indices
+ for( ; j < 256; j++) {
+ cmap_r[j] = byte 0;
+ cmap_g[j] = byte 0;
+ cmap_b[j] = byte 0;
+ }
+ pic24 := array [3 * len pic] of byte;
+ ix24 := 0;
+ for (i = 0; i < len pic; i++) {
+ c := int pic[i];
+ pic24[ix24++] = cmap_b[c];
+ pic24[ix24++] = cmap_g[c];
+ pic24[ix24++] = cmap_r[c];
+ }
+ return pic24;
+}
+
+# Remap pixels of pic[] into the closest colors in the rgbv map,
+# and do error diffusion of the result.
+# pic is a one-channel image whose rgb values are given by looking
+# up values in cmap.
+remap1(pic: array of byte, dx, dy: int, cmap: array of byte)
+{
+ if(dbg)
+ sys->print("remap1, pic len %d, dx=%d, dy=%d\n", len pic, dx, dy);
+ cmap_r := array[256] of int;
+ cmap_g := array[256] of int;
+ cmap_b := array[256] of int;
+ i := 0;
+ for(j := 0; j < 256 && i < len cmap; j++) {
+ cmap_r[j] = int cmap[i++];
+ cmap_g[j] = int cmap[i++];
+ cmap_b[j] = int cmap[i++];
+ }
+ # in case input has bad indices
+ for( ; j < 256; j++) {
+ cmap_r[j] = 0;
+ cmap_g[j] = 0;
+ cmap_b[j] = 0;
+ }
+ # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
+ ered := array[dx+1] of { * => 0 };
+ egrn := array[dx+1] of int;
+ eblu := array[dx+1] of int;
+ egrn[0:] = ered;
+ eblu[0:] = ered;
+ p := 0;
+ for(y:=0; y<dy; y++) {
+ er := 0;
+ eg := 0;
+ eb := 0;
+ for(x:=0; x<dx; ) {
+ x1 := x+1;
+ in := int pic[p];
+ r := cmap_r[in]+ered[x];
+ g := cmap_g[in]+egrn[x];
+ b := cmap_b[in]+eblu[x];
+ col := int (closestrgb[clampn_r[r+CLAMPNOFF]
+ +clampn_g[g+CLAMPNOFF]
+ +clampn_b[b+CLAMPNOFF]]);
+ pic[p++] = byte 255 - byte col;
+
+ r -= rgbvmap_r[col];
+ t := (3*r)>>4;
+ ered[x] = t+er;
+ ered[x1] += t;
+ er = r-3*t;
+
+ g -= rgbvmap_g[col];
+ t = (3*g)>>4;
+ egrn[x] = t+eg;
+ egrn[x1] += t;
+ eg = g-3*t;
+
+ b -= rgbvmap_b[col];
+ t = (3*b)>>4;
+ eblu[x] = t+eb;
+ eblu[x1] += t;
+ eb = b-3*t;
+
+ x = x1;
+ }
+ }
+}
+
+# Remap pixels of pic[] into the closest greyscale colors in the rgbv map,
+# and do error diffusion of the result.
+# pic is a one-channel greyscale image.
+remapgrey(pic: array of byte, dx, dy: int)
+{
+ if(dbg)
+ sys->print("remapgrey, pic len %d, dx=%d, dy=%d\n", len pic, dx, dy);
+ # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
+ e := array[dx+1] of {* => 0 };
+ p := 0;
+ for(y:=0; y<dy; y++){
+ eb := 0;
+ for(x:=0; x<dx; ) {
+ x1 := x+1;
+ b := int pic[p]+e[x];
+ b1 := clampn_b[b+CLAMPNOFF];
+ col := 255-17*b1;
+ pic[p++] = byte col;
+
+ b -= rgbvmap_b[col];
+ t := (3*b)>>4;
+ e[x] = t+eb;
+ e[x1] += t;
+ eb = b-3*t;
+ x = x1;
+ }
+ }
+}
+
+# Remap pixels of chans into the closest colors in the rgbv map,
+# and do error diffusion of the result.
+# chans is a 3-channel image whose channels are either (y,cb,cr) or
+# (r,g,b), depending on whether colorspace is CYCbCr or CRGB.
+# Variable names use r,g,b (historical).
+remaprgb(chans: array of array of byte, dx, dy, colorspace: int)
+{
+ if(dbg)
+ sys->print("remaprgb, pic len %d, dx=%d, dy=%d\n", len chans[0], dx, dy);
+ rpic := chans[0];
+ gpic := chans[1];
+ bpic := chans[2];
+ pic := chans[0];
+ # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
+ ered := array[dx+1] of { * => 0 };
+ egrn := array[dx+1] of int;
+ eblu := array[dx+1] of int;
+ egrn[0:] = ered;
+ eblu[0:] = ered;
+ closest: array of byte;
+ map0, map1, map2: array of int;
+ if(colorspace == CRGB) {
+ closest = closestrgb;
+ map0 = rgbvmap_r;
+ map1 = rgbvmap_g;
+ map2 = rgbvmap_b;
+ }
+ else {
+ closest = closestycbcr;
+ map0 = rgbvmap_y;
+ map1 = rgbvmap_cb;
+ map2 = rgbvmap_cr;
+ }
+ p := 0;
+ for(y:=0; y<dy; y++ ) {
+ er := 0;
+ eg := 0;
+ eb := 0;
+ for(x:=0; x<dx; ) {
+ x1 := x + 1;
+ r := int rpic[p]+ered[x];
+ g := int gpic[p]+egrn[x];
+ b := int bpic[p]+eblu[x];
+ # Errors can be uncorrectable if converting from YCbCr,
+ # since we can't guarantee that an extremal value of one of
+ # the components selects a color with an extremal value.
+ # If we don't, the errors accumulate without bound. This
+ # doesn't happen in RGB because the closest table can guarantee
+ # a color on the edge of the gamut, producing a zero error in
+ # that component. For the rotation YCbCr space, there may be
+ # no color that can guarantee zero error at the edge.
+ # Therefore we must clamp explicitly rather than by assuming
+ # an upper error bound of CLAMPOFF. The performance difference
+ # is miniscule anyway.
+ if(r < 0)
+ r = 0;
+ else if(r > 255)
+ r = 255;
+ if(g < 0)
+ g = 0;
+ else if(g > 255)
+ g = 255;
+ if(b < 0)
+ b = 0;
+ else if(b > 255)
+ b = 255;
+ col := int (closest[(b>>4)+16*((g>>4)+(r&16rF0))]);
+ pic[p++] = byte (255-col);
+# col := int (pic[p++] = closest[(b>>4)+16*((g>>4)+16*(r>>4))]);
+
+ r -= map0[col];
+ t := (3*r)>>4;
+ ered[x] = t+er;
+ ered[x1] += t;
+ er = r-3*t;
+
+ g -= map1[col];
+ t = (3*g)>>4;
+ egrn[x] = t+eg;
+ egrn[x1] += t;
+ eg = g-3*t;
+
+ b -= map2[col];
+ t = (3*b)>>4;
+ eblu[x] = t+eb;
+ eblu[x1] += t;
+ eb = b-3*t;
+
+ x = x1;
+ }
+ }
+}
+
+# Given src array, representing sw*sh pixel values, resample them into
+# the returned array, with dimensions dw*dh.
+#
+# Quick and dirty resampling: just interpolate.
+# This lets us resample arrays of pixels indices (e.g., result of gif decoding).
+# The filter-based resampling methods need conversion to rgb or grayscale.
+# Also, although the results won't look good, people really shouldn't be
+# asking the browser to resample except for special purposes (like the common
+# case of resizing a 1x1 image to make a spacer).
+resample(src: array of byte, sw, sh: int, dw, dh: int) : array of byte
+{
+ if(dbgev)
+ CU->event("IMAGE_RESAMPLE_START", 0);
+ if(src == nil || sw == 0 || sh == 0 || dw == 0 || dh == 0)
+ return src;
+ xfac := real sw / real dw;
+ yfac := real sh / real dh;
+ totpix := dw*dh;
+ dst := array[totpix] of byte;
+ dindex := 0;
+
+ # precompute index in src row corresponding to each index in dst row
+ sindices := array[dw] of int;
+ dx := 0.0;
+ for(x := 0; x < dw; x++) {
+ sx := int dx;
+ dx += xfac;
+ if(sx >= sw)
+ sx = sw-1;
+ sindices[x] = sx;
+ }
+ dy := 0.0;
+ for(y := 0; y < dh; y++) {
+ sy := int dy;
+ dy += yfac;
+ if(sy >= sh)
+ sy = sh-1;
+ soffset := sy * sw;
+ for(x = 0; x < dw; x++)
+ dst[dindex++] = src[soffset + sindices[x]];
+ }
+ if(dbgev)
+ CU->event("IMAGE_RESAMPLE_END", 0);
+ return dst;
+}
+
+################# BIT ###################
+
+getbitmim(is: ref ImageSource) : ref MaskedImage
+{
+ if(dbg)
+ sys->print("img getbitmim: w=%d h=%d len=%d\n",
+ is.width, is.height, len is.bs.data);
+
+ im := getbitimage(is, display, is.bs.data);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ is.i = is.bs.edata; # getbitimage should do this too!
+ is.width = im.r.max.x;
+ is.height = im.r.max.y;
+ return newmi(im);
+}
+
+
+NMATCH: con 3; # shortest match possible
+NCBLOCK: con 6000; # size of compressed blocks
+drawld2chan := array[] of {
+0 => Draw->GREY1,
+1 => Draw->GREY2,
+2 => Draw->GREY4,
+3 => Draw->CMAP8
+};
+
+getbitimage(is: ref ImageSource, disp: ref Display, d: array of byte): ref Image
+{
+ compressed := 0;
+
+ if(len d < 5*12)
+ imgerror(is, "bad bit format");
+
+ if(string d[:11] == "compressed\n"){
+ if(dbg)
+ sys->print("img: bit compressed\n");
+ compressed = 1;
+ d = d[11:];
+ }
+
+ #
+ # distinguish new channel descriptor from old ldepth.
+ # channel descriptors have letters as well as numbers,
+ # while ldepths are a single digit formatted as %-11d
+ #
+ new := 0;
+ for(m := 0; m < 10; m++){
+ if(d[m] != byte ' '){
+ new = 1;
+ break;
+ }
+ }
+ if(d[11] != byte ' ')
+ imgerror(is, "bad bit format");
+ chans: Chans;
+ if(new){
+ s := string d[0:11];
+ chans = Chans.mk(s);
+ if(chans.desc == 0)
+ imgerror(is, sys->sprint("bad channel string %s", s));
+ }else{
+ ld := int( d[10] - byte '0' );
+ if(ld < 0 || ld > 3)
+ imgerror(is, "bad bit ldepth");
+ chans = drawld2chan[ld];
+ }
+
+ xmin := int string d[ 1*12 : 2*12 ];
+ ymin := int string d[ 2*12 : 3*12 ];
+ xmax := int string d[ 3*12 : 4*12 ];
+ ymax := int string d[ 4*12 : 5*12 ];
+ if( (xmin > xmax) || (ymin > ymax) )
+ imgerror(is, "bad bit rectangle");
+
+ if(dbg)
+ sys->print("img: bit: chans=%s, xmin=%d, ymin=%d, xmax=%d, ymax=%d\n",
+ chans.text(), xmin, ymin, xmax, ymax);
+
+ r := Rect( (xmin, ymin), (xmax, ymax) );
+ im := disp.newimage(r, chans, 0, D->Black);
+ if(im == nil)
+ return nil;
+
+ if (!compressed){
+ if(!new)
+ for(j:=5*12; j<len d; j++)
+ d[j] ^= byte 16rFF;
+ im.writepixels(im.r, d[5*12:]);
+ return im;
+ }
+
+ # see /libdraw/readimage.c, /libdraw/creadimage.c, and
+ # /libmemdraw/cload.c for reference implementation
+ # of bit compression
+
+ bpl := D->bytesperline(r, im.depth);
+ a := array[(ymax-ymin)*bpl] of byte;
+ ai := 0; #index into uncompressed data array a
+ di := 5*12; #index into compressed data
+ while(ymin < ymax){
+ y := int string d[ di : di + 1*12 ];
+ n := int string d[ di + 1*12 : di + 2*12 ];
+ di += 2*12;
+
+ if (y <= ymin || ymax < y)
+ imgerror(is, "bad compressed bit y-max");
+ if (n <= 0 || NCBLOCK < n)
+ imgerror(is, "bad compressed bit count");
+
+ # no input-stream error checking :-(
+ u := di;
+ while(di < u+n){
+ c := int d[di++];
+ if (c >= 128){
+ # copy as is
+ cnt := c-128 + 1;
+
+ # check for overrun of index di within d?
+
+ a[ai:] = d[di:di+cnt];
+ if(!new)
+ for(j:=0; j<cnt; j++)
+ a[ai+j] ^= byte 16rFF;
+ di += cnt;
+ ai += cnt;
+ }
+ else {
+ # copy a run/match
+ offs := int(d[di++]) + ((c&3)<<8) + 1;
+ cnt := (c>>2) + NMATCH;
+
+ # simply: a[ai:ai+cnt] = a[ai-offs:ai-offs+cnt];
+ for(i:=0; i<cnt; i++)
+ a[ai+i] = a[ai-offs+i];
+ ai += cnt;
+ }
+ }
+ ymin = y;
+ }
+ im.writepixels(im.r, a);
+ return im;
+}
+
+################# PNG ###################
+
+Rawimage: adt {
+ r: Draw->Rect;
+ cmap: array of byte;
+ transp: int; # transparency flag (only for nchans=1)
+ trindex: byte; # transparency index
+ nchans: int;
+ chans: array of array of byte;
+ chandesc:int;
+
+ fields: int; # defined by format
+};
+
+Chunk: adt {
+ size : int;
+ typ: string;
+ crc_state: ref CRCstate;
+};
+
+Png: adt {
+ depth: int;
+ filterbpp: int;
+ colortype: int;
+ compressionmethod: int;
+ filtermethod: int;
+ interlacemethod: int;
+ # tRNS
+ PLTEsize: int;
+ tRNS: array of byte;
+ # state for managing unpacking
+ alpha: int;
+ done: int;
+ error: string;
+ row, rowstep, colstart, colstep: int;
+ phase: int;
+ phasecols: int;
+ phaserows: int;
+ rowsize: int;
+ rowbytessofar: int;
+ thisrow: array of byte;
+ lastrow: array of byte;
+};
+
+# currently do not support transparency
+# hence no mask is set
+#
+# need to re-jig this code
+# for example there is no point in mapping up a 2 or 4 bit greyscale image
+# to 8 bit luminance to then remap it to the inferno palette when
+# the draw device will do that for us anyway!
+
+getpngmim(is: ref ImageSource) : ref MaskedImage
+{
+ chunk := ref Chunk;
+ png := ref Png;
+ raw := ref Rawimage;
+
+ chunk.crc_state = crc->init(0, int 16rffffffff);
+# Check it's a PNG
+ if (!png_signature(is))
+ imgerror(is, "PNG not a PNG");
+# Get the IHDR
+ if (!png_chunk_header(is, chunk))
+ imgerror(is, "PNG duff header");
+ if (chunk.typ != "IHDR")
+ imgerror(is, "PNG IHDR must come first");
+ if (chunk.size != 13)
+ imgerror(is, "PNG IHDR wrong size");
+ raw.r.max.x = png_int(is, chunk.crc_state);
+ if (raw.r.max.x <= 0)
+ imgerror(is, "PNG invalid width");
+ raw.r.max.y = png_int(is, chunk.crc_state);
+ if (raw.r.max.y <= 0)
+ imgerror(is, "PNG invalid height");
+ png.depth = png_byte(is, chunk.crc_state);
+ case png.depth {
+ 1 or 2 or 4 or 8 or 16 =>
+ ;
+ * =>
+ imgerror(is, "PNG invalid depth");
+ }
+ png.colortype = png_byte(is, chunk.crc_state);
+
+ okcombo : int;
+
+ case png.colortype {
+ 0 =>
+ okcombo = 1;
+ raw.nchans = 1;
+ raw.chandesc = CY;
+ png.alpha = 0;
+ 2 =>
+ okcombo = (png.depth == 8 || png.depth == 16);
+ raw.nchans = 3;
+ raw.chandesc = CRGB;
+ png.alpha = 0;
+ 3 =>
+ okcombo = (png.depth != 16);
+ raw.nchans = 1;
+ raw.chandesc = CRGB1;
+ png.alpha = 0;
+ 4 =>
+ okcombo = (png.depth == 8 || png.depth == 16);
+ raw.nchans = 1;
+ raw.chandesc = CY;
+ png.alpha = 1;
+ 6 =>
+ okcombo = (png.depth == 8 || png.depth == 16);
+ raw.nchans = 3;
+ raw.chandesc = CRGB;
+ png.alpha = 1;
+ * =>
+ imgerror(is, "PNG invalid colortype");
+ }
+ if (!okcombo)
+ imgerror(is, "PNG invalid depth/colortype combination");
+ png.compressionmethod = png_byte(is, chunk.crc_state);
+ if (png.compressionmethod != 0)
+ imgerror(is, "PNG invalid compression method " + string png.compressionmethod);
+ png.filtermethod = png_byte(is, chunk.crc_state);
+ if (png.filtermethod != 0)
+ imgerror(is, "PNG invalid filter method");
+ png.interlacemethod = png_byte(is, chunk.crc_state);
+ if (png.interlacemethod != 0 && png.interlacemethod != 1)
+ imgerror(is, "PNG invalid interlace method");
+# sys->print("width %d height %d depth %d colortype %d interlace %d\n",
+# raw.r.max.x, raw.r.max.y, png.depth, png.colortype, png.interlacemethod);
+ if (!png_crc_and_check(is, chunk))
+ imgerror(is, "PNG invalid CRC");
+# Stash some detail in raw
+ raw.r.min = Point(0, 0);
+ raw.transp = 0;
+ raw.chans = array[raw.nchans] of array of byte;
+ {
+ for (r:= 0; r < raw.nchans; r++)
+ raw.chans[r] = array[raw.r.max.x * raw.r.max.y] of byte;
+ }
+# Get the next chunk
+ seenPLTE := 0;
+ seenIDAT := 0;
+ seenLastIDAT := 0;
+ inflateFinished := 0;
+ seenIEND := 0;
+ seentRNS := 0;
+ rq: chan of ref Filter->Rq;
+
+ png.error = nil;
+ rq = nil;
+ while (png.error == nil) {
+ if (!png_chunk_header(is, chunk)) {
+ if (!seenIEND)
+ png.error = "duff header";
+ break;
+ }
+ if (seenIEND) {
+ png.error = "rubbish at eof";
+ break;
+ }
+ case (chunk.typ) {
+ "IEND" =>
+ seenIEND = 1;
+ "PLTE" =>
+ if (seenPLTE) {
+ png.error = "too many PLTEs";
+ break;
+ }
+ if (seentRNS) {
+ png.error = "tRNS before PLTE";
+ break;
+ }
+ if (seenIDAT) {
+ png.error = "PLTE too late";
+ break;
+ }
+ if (chunk.size % 3 || chunk.size < 1 * 3 || chunk.size > 256 * 3) {
+ png.error = "PLTE strange size";
+ break;
+ }
+ if (png.colortype == 0 || png.colortype == 4) {
+ png.error = "superfluous PLTE";
+ break;
+ }
+ raw.cmap = array[256 * 3] of byte;
+ png.PLTEsize = chunk.size / 3;
+ if (!png_bytes(is, chunk.crc_state, raw.cmap, chunk.size)) {
+ png.error = "eof in PLTE";
+ break;
+ }
+# {
+# x: int;
+# sys->print("Palette:\n");
+# for (x = 0; x < chunk.size; x += 3)
+# sys->print("%3d: (%3d, %3d, %3d)\n",
+# x / 3, int raw.cmap[x], int raw.cmap[x + 1], int raw.cmap[x + 2]);
+# }
+ seenPLTE = 1;
+ "tRNS" =>
+ if (seenIDAT) {
+ png.error = "tRNS too late";
+ break;
+ }
+ case png.colortype {
+ 0 =>
+ if (chunk.size != 2) {
+ png.error = "tRNS wrong size";
+ break;
+ }
+ level := png_ushort(is, chunk.crc_state);
+ if (level < 0) {
+ png.error = "eof in tRNS";
+ break;
+ }
+ if (png.depth != 16) {
+ raw.transp = 1;
+ raw.trindex = byte level;
+ }
+ 2 =>
+ # a legitimate coding, but we can't use the information
+ if (!png_skip_bytes(is, chunk.crc_state, chunk.size))
+ png.error = "eof in skipped tRNS chunk";
+ break;
+ 3 =>
+ if (!seenPLTE) {
+ png.error = "tRNS too early";
+ break;
+ }
+ if (chunk.size > png.PLTEsize) {
+ png.error = "tRNS too big";
+ break;
+ }
+ png.tRNS = array[png.PLTEsize] of byte;
+ for (x := chunk.size; x < png.PLTEsize; x++)
+ png.tRNS[x] = byte 255;
+ if (!png_bytes(is, chunk.crc_state, png.tRNS, chunk.size)) {
+ png.error = "eof in tRNS";
+ break;
+ }
+# {
+# sys->print("tRNS:\n");
+# for (x = 0; x < chunk.size; x++)
+# sys->print("%3d: (%3d)\n", x, int png.tRNS[x]);
+# }
+ if (png.error == nil) {
+ # analyse the tRNS chunk to see if it contains a single transparent index
+ # translucent entries are treated as opaque
+ for (x = 0; x < chunk.size; x++)
+ if (png.tRNS[x] == byte 0) {
+ raw.trindex = byte x;
+ if (raw.transp) {
+ raw.transp = 0;
+ break;
+ }
+ raw.transp = 1;
+ }
+# if (raw.transp)
+# sys->print("selected index %d\n", int raw.trindex);
+ }
+ 4 or 6 =>
+ png.error = "tRNS invalid when alpha present";
+ }
+ seentRNS = 1;
+ "IDAT" =>
+ if (seenLastIDAT) {
+ png.error = "non contiguous IDATs";
+ break;
+ }
+ if (inflateFinished) {
+ png.error = "too many IDATs";
+ break;
+ }
+ remaining := 0;
+ if (!seenIDAT) {
+ # open channel to inflate filter
+ if (!processdatainit(png, raw))
+ break;
+ rq = inflate->start(nil);
+ png_skip_bytes(is, chunk.crc_state, 2);
+ remaining = chunk.size - 2;
+ }
+ else
+ remaining = chunk.size;
+ while (remaining && png.error == nil) {
+ pick m := <- rq {
+ Fill =>
+# sys->print("Fill(%d) remaining %d\n", len m.buf, remaining);
+ toget := len m.buf;
+ if (toget > remaining)
+ toget = remaining;
+ if (!png_bytes(is, chunk.crc_state, m.buf, toget)) {
+ m.reply <-= -1;
+ png.error = "eof during IDAT";
+ break;
+ }
+ m.reply <-= toget;
+ remaining -= toget;
+ Result =>
+# sys->print("Result(%d)\n", len m.buf);
+ m.reply <-= 0;
+ processdata(png, raw, m.buf);
+ Info =>
+# sys->print("Info(%s)\n", m.msg);
+ Finished =>
+ inflateFinished = 1;
+# sys->print("Finished\n");
+ Error =>
+ imgerror(is, "PNG inflate error\n");
+ }
+ }
+ seenIDAT = 1;
+ * =>
+ # skip the blighter
+ if (!png_skip_bytes(is, chunk.crc_state, chunk.size))
+ png.error = "eof in skipped chunk";
+ }
+ if (png.error != nil)
+ break;
+ if (!png_crc_and_check(is, chunk))
+ imgerror(is, "PNG invalid CRC");
+ if (chunk.typ != "IDAT" && seenIDAT)
+ seenLastIDAT = 1;
+ }
+ # can only get here if IEND was last chunk, or png.error set
+
+ if (png.error == nil && !seenIDAT) {
+ png.error = "no IDAT!";
+ inflateFinished = 1;
+ }
+ while (rq != nil && !inflateFinished) {
+ pick m := <-rq {
+ Fill =>
+# sys->print("Fill(%d)\n", len m.buf);
+ png.error = "eof in zlib stream";
+ m.reply <-= -1;
+ inflateFinished = 1;
+ Result =>
+# sys->print("Result(%d)\n", len m.buf);
+ if (png.error != nil) {
+ m.reply <-= -1;
+ inflateFinished = 1;
+ }
+ else {
+ m.reply <-= 0;
+ processdata(png, raw, m.buf);
+ }
+ Info =>
+# sys->print("Info(%s)\n", m.msg);
+ Finished =>
+# sys->print("Finished\n");
+ inflateFinished = 1;
+ break;
+ Error =>
+ png.error = "inflate error\n";
+ inflateFinished = 1;
+ }
+
+ }
+ if (png.error == nil && !png.done)
+ png.error = "insufficient data";
+ if (png.error != nil)
+ imgerror(is, "PNG " + png.error);
+
+ width := raw.r.dx();
+ height := raw.r.dy();
+ case raw.chandesc {
+ CY =>
+ remapgrey(raw.chans[0], width, height);
+ CRGB =>
+ remaprgb(raw.chans, width, height, CRGB);
+ CRGB1 =>
+ remap1(raw.chans[0], width, height, raw.cmap);
+ }
+ pixels := raw.chans[0];
+ is.origw = width;
+ is.origh = height;
+ setdims(is);
+ if(is.width != is.origw || is.height != is.origh)
+ pixels = resample(pixels, is.origw, is.origh, is.width, is.height);
+ im := newimage(is, is.width, is.height);
+ im.writepixels(im.r, pixels);
+ mi := newmi(im);
+# mi.mask = display.newimage(im.r, D->GREY1, 0, D->Black);
+ return mi;
+}
+
+phase2stepping(phase: int): (int, int, int, int)
+{
+ case phase {
+ 0 =>
+ return (0, 1, 0, 1);
+ 1 =>
+ return (0, 8, 0, 8);
+ 2 =>
+ return (0, 8, 4, 8);
+ 3 =>
+ return (4, 8, 0, 4);
+ 4 =>
+ return (0, 4, 2, 4);
+ 5 =>
+ return (2, 4, 0, 2);
+ 6 =>
+ return (0, 2, 1, 2);
+ 7 =>
+ return (1, 2, 0, 1);
+ * =>
+ return (-1, -1, -1, -1);
+ }
+}
+
+processdatainitphase(png: ref Png, raw: ref Rawimage)
+{
+ (png.row, png.rowstep, png.colstart, png.colstep) = phase2stepping(png.phase);
+ if (raw.r.max.x > png.colstart)
+ png.phasecols = (raw.r.max.x - png.colstart + png.colstep - 1) / png.colstep;
+ else
+ png.phasecols = 0;
+ if (raw.r.max.y > png.row)
+ png.phaserows = (raw.r.max.y - png.row + png.rowstep - 1) / png.rowstep;
+ else
+ png.phaserows = 0;
+ png.rowsize = png.phasecols * (raw.nchans + png.alpha) * png.depth;
+ png.rowsize = (png.rowsize + 7) / 8;
+ png.rowsize++; # for the filter byte
+ png.rowbytessofar = 0;
+ png.thisrow = array[png.rowsize] of byte;
+ png.lastrow = array[png.rowsize] of byte;
+# sys->print("init phase %d: r (%d, %d, %d) c (%d, %d, %d) (%d)\n",
+# png.phase, png.row, png.rowstep, png.phaserows,
+# png.colstart, png.colstep, png.phasecols, png.rowsize);
+}
+
+processdatainit(png: ref Png, raw: ref Rawimage): int
+{
+ if (raw.nchans != 1&& raw.nchans != 3) {
+ png.error = "only 1 or 3 channels supported";
+ return 0;
+ }
+# if (png.interlacemethod != 0) {
+# png.error = "only progressive supported";
+# return 0;
+# }
+ if (png.colortype == 3 && raw.cmap == nil) {
+ png.error = "PLTE chunk missing";
+ return 0;
+ }
+ png.done = 0;
+ png.filterbpp = (png.depth * (raw.nchans + png.alpha) + 7) / 8;
+ png.phase = png.interlacemethod;
+
+ processdatainitphase(png, raw);
+
+ return 1;
+}
+
+upconvert(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
+{
+ b: byte;
+ bits := pixels * bpp;
+ lim := bits / 8;
+ mask := byte ((1 << bpp) - 1);
+ outx := 0;
+ inx := 0;
+ for (x := 0; x < lim; x++) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ pixel := (b >> s) & mask;
+ ucp := pixel;
+ for (y := bpp; y < 8; y += bpp)
+ ucp |= pixel << y;
+ out[outx] = ucp;
+ outx += outstride;
+ }
+ inx++;
+ }
+ residue := (bits % 8) / bpp;
+ if (residue) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ pixel := (b >> s) & mask;
+ ucp := pixel;
+ for (y := bpp; y < 8; y += bpp)
+ ucp |= pixel << y;
+ out[outx] = ucp;
+ outx += outstride;
+ if (--residue <= 0)
+ break;
+ }
+ }
+}
+
+# expand (1 or 2 or 4) bit to 8 bit without scaling (for palletized stuff)
+
+expand(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
+{
+ b: byte;
+ bits := pixels * bpp;
+ lim := bits / 8;
+ mask := byte ((1 << bpp) - 1);
+ outx := 0;
+ inx := 0;
+ for (x := 0; x < lim; x++) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ out[outx] = (b >> s) & mask;
+ outx += outstride;
+ }
+ inx++;
+ }
+ residue := (bits % 8) / bpp;
+ if (residue) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ out[outx] = (b >> s) & mask;
+ outx += outstride;
+ if (--residue <= 0)
+ break;
+ }
+ }
+}
+
+copybytes(out: array of byte, outstride: int, in: array of byte, instride: int, pixels: int)
+{
+ inx := 0;
+ outx := 0;
+ for (x := 0; x < pixels; x++) {
+ out[outx] = in[inx];
+ inx += instride;
+ outx += outstride;
+ }
+}
+
+outputrow(png: ref Png, raw: ref Rawimage, row: array of byte)
+{
+ offset := png.row * raw.r.max.x;
+ case raw.nchans {
+ 1 =>
+ case (png.depth) {
+ * =>
+ png.error = "depth not supported";
+ return;
+ 1 or 2 or 4 =>
+ if (raw.chandesc == CRGB1)
+ expand(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
+ else
+ upconvert(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
+ 8 or 16 =>
+ # might have an Alpha channel to ignore!
+ stride := (png.alpha + 1) * png.depth / 8;
+ copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
+ }
+ 3 =>
+ case (png.depth) {
+ * =>
+ png.error = "depth not supported (2)";
+ return;
+ 8 or 16 =>
+ # split rgb into three channels
+ bytespc := png.depth / 8;
+ stride := (3 + png.alpha) * bytespc;
+ copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
+ copybytes(raw.chans[1][offset + png.colstart:], png.colstep, row[bytespc:], stride, png.phasecols);
+ copybytes(raw.chans[2][offset + png.colstart:], png.colstep, row[bytespc * 2:], stride, png.phasecols);
+ }
+ }
+}
+
+filtersub(png: ref Png)
+{
+ subx := 1;
+ for (x := int png.filterbpp + 1; x < png.rowsize; x++) {
+ png.thisrow[x] += png.thisrow[subx];
+ subx++;
+ }
+}
+
+filterup(png: ref Png)
+{
+ if (png.row == 0)
+ return;
+ for (x := 1; x < png.rowsize; x++)
+ png.thisrow[x] += png.lastrow[x];
+}
+
+filteraverage(png: ref Png)
+{
+ for (x := 1; x < png.rowsize; x++) {
+ a: int;
+ if (x > png.filterbpp)
+ a = int png.thisrow[x - png.filterbpp];
+ else
+ a = 0;
+ if (png.row != 0)
+ a += int png.lastrow[x];
+ png.thisrow[x] += byte (a / 2);
+ }
+}
+
+filterpaeth(png: ref Png)
+{
+ a, b, c: byte;
+ p, pa, pb, pc: int;
+ for (x := 1; x < png.rowsize; x++) {
+ if (x > png.filterbpp)
+ a = png.thisrow[x - png.filterbpp];
+ else
+ a = byte 0;
+ if (png.row == 0) {
+ b = byte 0;
+ c = byte 0;
+ } else {
+ b = png.lastrow[x];
+ if (x > png.filterbpp)
+ c = png.lastrow[x - png.filterbpp];
+ else
+ c = byte 0;
+ }
+ p = int a + int b - int c;
+ pa = p - int a;
+ if (pa < 0)
+ pa = -pa;
+ pb = p - int b;
+ if (pb < 0)
+ pb = -pb;
+ pc = p - int c;
+ if (pc < 0)
+ pc = -pc;
+ if (pa <= pb && pa <= pc)
+ png.thisrow[x] += a;
+ else if (pb <= pc)
+ png.thisrow[x] += b;
+ else
+ png.thisrow[x] += c;
+ }
+}
+
+phaseendcheck(png: ref Png, raw: ref Rawimage): int
+{
+ if (png.row >= raw.r.max.y || png.rowsize <= 1) {
+ # this phase is over
+ if (png.phase == 0) {
+ png.done = 1;
+ }
+ else {
+ png.phase++;
+ if (png.phase > 7)
+ png.done = 1;
+ else
+ processdatainitphase(png, raw);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+processdata(png: ref Png, raw: ref Rawimage, buf: array of byte)
+{
+#sys->print("processdata(%d)\n", len buf);
+ if (png.error != nil)
+ return;
+ i := 0;
+ while (i < len buf) {
+ if (png.done) {
+ png.error = "too much data";
+ return;
+ }
+ if (phaseendcheck(png, raw))
+ continue;
+ tocopy := (png.rowsize - png.rowbytessofar);
+ if (tocopy > (len buf - i))
+ tocopy = len buf - i;
+ png.thisrow[png.rowbytessofar :] = buf[i : i + tocopy];
+ i += tocopy;
+ png.rowbytessofar += tocopy;
+ if (png.rowbytessofar >= png.rowsize) {
+ # a new row has arrived
+ # apply filter here
+#sys->print("phase %d row %d\n", png.phase, png.row);
+ case int png.thisrow[0] {
+ 0 =>
+ ;
+ 1 =>
+ filtersub(png);
+ 2 =>
+ filterup(png);
+ 3 =>
+ filteraverage(png);
+ 4 =>
+ filterpaeth(png);
+ * =>
+# sys->print("implement filter method %d\n", int png.thisrow[0]);
+ png.error = "filter method unsupported";
+ return;
+ }
+ # output row
+ if (png.row >= raw.r.max.y) {
+ png.error = "too much data";
+ return;
+ }
+ outputrow(png, raw, png.thisrow[1 :]);
+ png.row += png.rowstep;
+ save := png.lastrow;
+ png.lastrow = png.thisrow;
+ png.thisrow = save;
+ png.rowbytessofar = 0;
+ }
+ }
+ phaseendcheck(png, raw);
+}
+
+png_signature(is: ref ImageSource): int
+{
+ sig := array[8] of { byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10 };
+ x: int;
+ for (x = 0; x < 8; x++)
+ if (png_getb(is) != int sig[x])
+ return 0;
+ return 1;
+}
+
+png_getb(is: ref ImageSource) : int
+{
+ if(is.i >= len is.bs.data)
+ return -1;
+ return int is.bs.data[is.i++];
+}
+
+png_bytes(is: ref ImageSource, crc_state: ref CRCstate, buf: array of byte, n: int): int
+{
+ if (is.i +n > len is.bs.data) {
+ is.i = len is.bs.data;
+ return 0;
+ }
+ if (buf == nil) {
+ is.i += n;
+ return 1;
+ }
+ buf[0:] = is.bs.data[is.i:is.i+n];
+ is.i += n;
+ if (crc_state != nil)
+ crc->crc(crc_state, buf, n);
+ return 1;
+}
+
+png_skip_bytes(is: ref ImageSource, crc_state: ref CRCstate, n: int): int
+{
+ buf := array[1024] of byte;
+ while (n) {
+ thistime: int = 1024;
+ if (thistime > n)
+ thistime = n;
+ if (!png_bytes(is, crc_state, buf, thistime))
+ return 0;
+ n -= thistime;
+ }
+ return 1;
+}
+
+png_get_4(is: ref ImageSource, crc_state: ref CRCstate, signed: int): (int, int)
+{
+ buf := array[4] of byte;
+ if (!png_bytes(is, crc_state, buf, 4))
+ return (0, 0);
+ if (signed && int buf[0] & 16r80)
+ return (0, 0);
+ r:int = (int buf[0] << 24) | (int buf[1] << 16) | (int buf[2] << 8) | (int buf[3]);
+# sys->print("got int %d\n", r);
+ return (1, r);
+}
+
+png_int(is: ref ImageSource, crc_state: ref CRCstate): int
+{
+ ok, r: int;
+ (ok, r) = png_get_4(is, crc_state, 1);
+ if (ok)
+ return r;
+ return -1;
+}
+
+png_ushort(is: ref ImageSource, crc_state: ref CRCstate): int
+{
+ buf := array[2] of byte;
+ if (!png_bytes(is, crc_state, buf, 2))
+ return -1;
+ return (int buf[0] << 8) | int buf[1];
+}
+
+png_crc_and_check(is: ref ImageSource, chunk: ref Chunk): int
+{
+ crc, ok: int;
+ (ok, crc) = png_get_4(is, nil, 0);
+ if (!ok)
+ return 0;
+# sys->print("crc: computed %.8ux expected %.8ux\n", chunk.crc_state.crc, crc);
+ if (chunk.crc_state.crc != crc)
+ return 1;
+ return 1;
+}
+
+png_byte(is: ref ImageSource, crc_state: ref CRCstate): int
+{
+ buf := array[1] of byte;
+ if (!png_bytes(is, crc_state, buf, 1))
+ return -1;
+# sys->print("got byte %d\n", int buf[0]);
+ return int buf[0];
+}
+
+png_type(is: ref ImageSource, crc_state: ref CRCstate): string
+{
+ x: int;
+ buf := array[4] of byte;
+ if (!png_bytes(is, crc_state, buf, 4))
+ return nil;
+ for (x = 0; x < 4; x++) {
+ c: int;
+ c = int buf[x];
+ if ((c < 65 || c > 90 && c < 97) || c > 122)
+ return nil;
+ }
+ return string buf;
+}
+
+png_chunk_header(is: ref ImageSource, chunk: ref Chunk): int
+{
+ chunk.size = png_int(is, nil);
+ if (chunk.size < 0)
+ return 0;
+ crc->reset(chunk.crc_state);
+ chunk.typ = png_type(is, chunk.crc_state);
+ if (chunk.typ == nil)
+ return 0;
+# sys->print("%s(%d)\n", chunk.typ, chunk.size);
+ return 1;
+}