diff options
Diffstat (limited to 'appl/charon/img.b')
| -rw-r--r-- | appl/charon/img.b | 3607 |
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; +} |
