implement Img; include "common.m"; # headers for png supportinclude "filter.m";include "crc.m"; # big tables in separate filesinclude "rgb.inc";include "ycbcr.inc"; include "xxx.inc"; # local copies from CUsys: 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 descriptionsCRGB: con 0; # three channels, R, G, B, no mapCY: con 1; # one channel, luminanceCRGB1: con 2; # one channel, map presentCYCbCr: 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 handlesupported(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 exceptionimgerror(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 cannotgetc(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 failsnewimage(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 debuggingprintarray(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 flagsTRANSP: 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 ExtensionLines 1–500 of 3607. Load more
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