This is NXBitmapGraphicRep.m in view mode; [Download] [Up]
/* ------------------------------------------------------------------- NXBitmapGraphicRep 1. Definition This class is designed to simply and effeciently implement simple bitmap graphics operations and provide a method to get them on the screen quickly. Advanced bitmap stuff (lines, circles, polygon fills, etc.) are left as an exercise for the programmer. 2. Usage bitmap characteristics: - initialization initWithSize: aSize depth:aDepth andColor:aColor initializes the bitmap and allocates image data for an image with a size of aSize, a pixel depth of aDepth and clears the background to aColor. initWithSize: depth: same as above, but initializes background to black. - drawing: plotPoint:x :y; erasePoint:x :y; plots a point in the current foreColor erases a point to the current backColor the rest of the methods are fairly self explanatory (and are mostly documented within themselves. 3. Theory When the bitmap is initialized, it's superclass allocates enough data to hold an entire image at the initialized depth. The image data is stored in an array of unsigned char's. The array is arranged thusly: samp1=red, samp2=green, samp3=blu. 24 bit color: 8 bits/sample, 3 samples/pixel, 3 bytes/pixel [ pixel1 ][ pixel2 ]... [samp1][samp2][samp3][samp1][samp2][samp3]... 12 bit color: 4 bits/sample, 3 samples/pixel, 1.5(!) bytes/pixel [ pixel1 ][ pixel2 ][ pixel1 ]... [samp1][samp2][samp3][samp1][samp2][samp3][samp1][samp2][samp3]... [ data[0] ][ data[1] ][ data[2] ][ data[3] ][ data[4] ]... 2 bit gray scale: 2 bits/pixel, 4 pixels/byte [pix1][pix2][pix3][pix4][pix5][pix6][pix7][pix8]... [ data[0] ][ data[1] ]... NOTE: pix1 is in bits 7&8 pix2 is in bits 5&6 pix3 is in bits 3&4 pix4 is in bits 1&2 etc... Because of this arrangement, both plotPoint and erasePoint use bit manipulation to deal with 12 bit and 2 bit color models. Because of this, Color management is a bit strange: Internally, all colors are represented as NXColor structures: this gives full support for CMYK, RGB, and HSB color models (just make sure and use NeXT's color conversion routines). The colors are ALSO stored in an optimal form for doing bit level pixel manipulation: 24 bit: cur_red, cur_grn, cur_blu all contain a full 8 bit byte with the RGB intensity level for the corresponding color (0 is off, 255 is full-on) 12 bit: same as 24 bit, but the bottom four bits are masked off. Effectively giving 16 levels of color for each channel (4096 colors). 2 bit: gray_level contains the top 2 bits of the color level that has been converted to a gray level. By doing this, it ensures maximum throughput while avoiding any of postscripts dithering muck. Also, since internally, colors are represented in the full NXColor way, you DO NOT lose color informaton even if you are plotting in 2 bit gray-- as soon as you switch to a deeper color model, this object will take advantage of it. This object is designed to be hacked. There are some fairly obvious optimizations that could be done when implementing line-drawing or poly-fills. Remember: bcopy(src, dest, #bytes) is a hell of a lot faster than a fore-loop that does the same thing (check out the eraseFrame method). ------------------------------------------------------------------- */ #import "NXBitmapGraphicRep.h" #import <appkit/color.h> #import <appkit/graphics.h> #import <string.h> #import <stdio.h> #import <math.h> #import <c.h> #import "miscutil.h" #import "CmdVertex.h" #import "CmdBgnpoly.h" #import "CmdEndpoly.h" extern void exit(); extern void *malloc(); extern void free(); @implementation NXBitmapGraphicRep : NXBitmapImageRep #define RED24 0 #define GRN24 1 #define BLU24 2 // macro to convert x,y coordinates to offset from byte 0 of the data array #define xyto24index(x,y) (((yh-y)*bytes_per_row)+(x*3)) #define xyto12index(x,y) (((yh-y)*bytes_per_row)+((3*x)/2)) #define xyto2index(x,y) (((yh-y)*bytes_per_row)+(x/4)) - initBitmap { int i; unsigned char *top_line; bytes_per_row=[self bytesPerRow]; num_bytes=[self pixelsHigh]*bytes_per_row; data=[self data]; yh=[self pixelsHigh]-1; // erase first line to back color for(i=0;i<[self pixelsWide];i++) [self erasePoint:i:0]; switch(imageDepth){ case NX_TwoBitGrayDepth: top_line=&data[xyto2index(0,0)]; for(i=0;i<[self pixelsHigh];i++) bcopy(top_line, &(data[xyto2index(0,i)]), bytes_per_row); break; case NX_TwelveBitRGBDepth: top_line=&data[xyto12index(0,0)]; for(i=0;i<[self pixelsHigh];i++) bcopy(top_line, &(data[xyto12index(0,i)]), bytes_per_row); break; case NX_TwentyFourBitRGBDepth: top_line=&data[xyto24index(0,0)]; for(i=0;i<[self pixelsHigh];i++) bcopy(top_line, &(data[xyto24index(0,i)]), bytes_per_row); break; } return self; } // NX_DefaultDepth = 0, // NX_TwoBitGrayDepth = 258, // NX_EightBitGrayDepth = 264, // NX_TwelveBitRGBDepth = 516, // NX_TwentyFourBitRGBDepth = 520 - initWithSize:(NXSize *) aSize depth:(int) aDepth andColor:(NXColor)c; { imageDepth=aDepth; [self setBackColor:c]; switch(aDepth) { case NX_TwoBitGrayDepth: // this will yield a bitmap w/4 pixels per byte at 2 bits per pixel. [super initData:NULL pixelsWide:aSize->width pixelsHigh:aSize->height bitsPerSample:2 samplesPerPixel:1 hasAlpha:NO isPlanar:NO colorSpace:NX_OneIsWhiteColorSpace bytesPerRow:0 bitsPerPixel:0]; break; case NX_TwelveBitRGBDepth: // this will yield a bitmap w/4 bits per sample and 3 samples per pixel. [super initData:NULL pixelsWide:aSize->width pixelsHigh:aSize->height bitsPerSample:4 samplesPerPixel:3 hasAlpha:NO isPlanar:NO colorSpace:NX_RGBColorSpace bytesPerRow:0 bitsPerPixel:0]; break; case NX_TwentyFourBitRGBDepth: [super initData:NULL // force NXBitmapImageRep to allocate data pixelsWide:aSize->width // set the width pixelsHigh:aSize->height // set the height bitsPerSample:8 // set the # of bits per sample samplesPerPixel:3 // set the # of samples per pixel hasAlpha:NO // Do not want alpha channel isPlanar:NO // Data should be interleaved in one array colorSpace:NX_RGBColorSpace // standard RGB color space bytesPerRow:0 // allocate the data automatically bitsPerPixel:0]; // no filler bits anywhere break; default: fprintf(stderr, "GraphicsWrap: attempted to create bitmap for unsupported \ pixel depth. Bye!\n"); exit(1); } [self initBitmap]; // initialize instance variables & // clear data return self; } - initWithSize:(NXSize *) aSize andDepth:(int) aDepth { [self initWithSize:aSize depth:aDepth andColor:NXConvertRGBToColor(0.0, 0.0, 0.0)]; return self; } // erases the current frame to aColor - eraseFrameToColor:(NXColor) aColor { [self setBackColor:aColor]; [self eraseFrame]; return self; } // erases the current frame to backColor - eraseFrame { int i; unsigned char *top_line; // erase first line to back color for(i=0;i<[self pixelsWide];i++) [self erasePoint:i:0]; switch(imageDepth){ case NX_TwoBitGrayDepth: top_line=&data[xyto2index(0,0)]; for(i=0;i<[self pixelsHigh];i++) bcopy(top_line, &(data[xyto2index(0,i)]), bytes_per_row); break; case NX_TwelveBitRGBDepth: top_line=&data[xyto12index(0,0)]; for(i=0;i<[self pixelsHigh];i++) bcopy(top_line, &(data[xyto12index(0,i)]), bytes_per_row); break; case NX_TwentyFourBitRGBDepth: top_line=&data[xyto24index(0,0)]; for(i=0;i<[self pixelsHigh];i++) bcopy(top_line, &(data[xyto24index(0,i)]), bytes_per_row); break; } return self; } - plotPoint:(long)x :(long)y { long index; if(x<0 || x>=[self pixelsWide] || y<0 || y>=[self pixelsHigh]) { fprintf(stderr, "Coordinate out of bound: %d,%d\n", x, y); return self; } switch(imageDepth){ case NX_TwentyFourBitRGBDepth: index=xyto24index(x,y); data[index+RED24]=cur_red; data[index+GRN24]=cur_grn; data[index+BLU24]=cur_blu; break; case NX_TwelveBitRGBDepth: index=xyto12index(x,y); // these only work because of the bit munging done in setColor // RED data[index]= // if x is odd (x & 0x01) ? // set the bottom 4 bits of index to the top 4 bits of // the current red in a most peculiar fashion ((cur_red >> 4) | (data[index] & 0xf0)) : // else, x is even // set the top 4 bits of index to the top 4 bits of red (cur_red | (data[index] & 0x0f)); // GREEN // if x is odd if (x & 0x01) data[index+1]= // set the top 4 bits of (index+1) to the top 4 bits of // the current green in an even more peculiar fashion (cur_grn | (data[index+1] & 0x0f)); else data[index]= // set the bottom 4 bits of (index) to the top 4 bits of // the current green ((cur_grn >> 4) | (data[index] & 0xf0)); // BLUE data[index+1]= (x & 0x01) ? // set the bottom 4 bits of (index+1) to the top 4 bits of // the current blue ((cur_blu >> 4) | (data[index+1] & 0xf0)) : // else, x is even and we need to set the top 4 bits of (index+1) // to the top 4 bits of the current blue (cur_blu | (data[index+1] & 0x0f)); break; case NX_TwoBitGrayDepth: index=xyto2index(x,y); switch(x%4){ // 63 is binary 00111111 case 0:data[index]=(data[index] & 63) | gray_level; break; // 207 is binary 11001111 case 1:data[index]=(data[index] & 207) | (gray_level>>2); break; // 243 is binary 11110011 case 2:data[index]=(data[index] & 243) | (gray_level>>4); break; // 252 is binary 11111100 case 3:data[index]=(data[index] & 252) | (gray_level>>6); break; } } return self; } - erasePoint:(long)x :(long)y { long index; if(x<0 || x>=[self pixelsWide] || y<0 || y>=[self pixelsHigh]) { fprintf(stderr, "Coordinate out of bound: %d,%d\n", x, y); return self; } switch(imageDepth){ case NX_TwentyFourBitRGBDepth: index=xyto24index(x,y); data[index+RED24]=bck_red; data[index+GRN24]=bck_grn; data[index+BLU24]=bck_blu; break; case NX_TwelveBitRGBDepth: index=xyto12index(x,y); // these only work because of the bit munging done in setColor // RED data[index]= // if x is odd (x & 0x01) ? // set the bottom 4 bits of index to the top 4 bits of // the current red in a most peculiar fashion ((bck_red >> 4) | (data[index] & 0xf0)) : // else, x is even // set the top 4 bits of index to the top 4 bits of red (bck_red | (data[index] & 0x0f)); // GREEN // if x is odd if (x & 0x01) data[index+1]= // set the top 4 bits of (index+1) to the top 4 bits of // the current green in an even more peculiar fashion (bck_grn | (data[index+1] & 0x0f)); else data[index]= // set the bottom 4 bits of (index) to the top 4 bits of // the current green ((bck_grn >> 4) | (data[index] & 0xf0)); // BLUE data[index+1]= (x & 0x01) ? // set the bottom 4 bits of (index+1) to the top 4 bits of // the current blue ((bck_blu >> 4) | (data[index+1] & 0xf0)) : // else, x is even and we need to set the top 4 bits of (index+1) // to the top 4 bits of the current blue (bck_blu | (data[index+1] & 0x0f)); break; case NX_TwoBitGrayDepth: index=xyto2index(x,y); switch(x%4){ // 63 is binary 00111111 case 0:data[index]=(data[index] & 63) | bck_gray_level; break; // 207 is binary 11001111 case 1:data[index]=(data[index] & 207) | (bck_gray_level>>2); break; // 243 is binary 11110011 case 2:data[index]=(data[index] & 243) | (bck_gray_level>>4); break; // 252 is binary 11111100 case 3:data[index]=(data[index] & 252) | (bck_gray_level>>6); break; } } return self; } - setColor:(NXColor)aColor { float red,green,blue,gray; int r,g,b; NXConvertColorToRGB(aColor, &red, &green, &blue); r=floattobyte(red); g=floattobyte(green); b=floattobyte(blue); foreColor=aColor; switch(imageDepth){ case NX_TwelveBitRGBDepth: // strip bottom four bits off all colors since they are not used // speeds up the point plotting stuff cur_red=r & 0xf0; cur_grn=g & 0xf0; cur_blu=b & 0xf0; break; case NX_TwentyFourBitRGBDepth: cur_red=r; cur_grn=g; cur_blu=b; break; case NX_TwoBitGrayDepth: NXConvertColorToGray(aColor, &gray); gray_level=floattobyte(gray) & 0xc0; break; } return self; } - setBackColor:(NXColor)aColor { float red,green,blue,gray; int r,g,b; NXConvertColorToRGB(aColor, &red, &green, &blue); r=floattobyte(red); g=floattobyte(green); b=floattobyte(blue); backColor=aColor; switch(imageDepth){ case NX_TwelveBitRGBDepth: // strip bottom four bits off all colors since they are not used // speeds up the point plotting stuff by eliminating this // operation from the plotPoint/erasePoint routines. bck_red=r & 0xf0; bck_grn=g & 0xf0; bck_blu=b & 0xf0; break; case NX_TwentyFourBitRGBDepth: bck_red=r; bck_grn=g; bck_blu=b; break; case NX_TwoBitGrayDepth: NXConvertColorToGray(aColor, &gray); bck_gray_level=floattobyte(gray) & 0xc0; break; } [self eraseFrame]; return self; } - (NXColor)color { return foreColor; } -(NXColor)backColor { return backColor; } - (int)imageDepth { return imageDepth; } // modelled after the routine on page 78 of _Computer Graphics: // Princliples and Practice // this line method actually special cases for 8 quadrants for the // line. By doing this, the line ALWAYS starts at x0,y0 to x1,y1. This // is a GOOD THING when doing patterns and such that need to flow from // the start point to the end point only. // QUADRANTS: 1|2 // -+- // 3|4 // Each quadrant is divided into halves at the 45 degree mark. int curX, curY; - line:(int)x0 :(int)y0 :(int)x1 :(int)y1 { long dx, dy, incD, incDD, d, x, y; long xadd, yadd; dx=ABS(x1-x0); dy=ABS(y1-y0); if(x0>x1) { if(y0>y1) { // down and to the left xadd=-1; yadd=-1; } else { // up and to the left xadd=-1; yadd=1; } } else { if(y0>y1) { xadd=1; yadd=-1; } else { xadd=1; yadd=1; } } x=x0; y=y0; [self plotPoint:x :y]; if(dx<dy){ d=2*dx-dy; incD=2*dx; incDD=2*(dx-dy); while(y!=y1){ if(d<=0) d+=incD; else { d+=incDD; x+=xadd; } y+=yadd; [self plotPoint:x :y]; } } else { d=2*dy-dx; incD=2*dy; incDD=2*(dy-dx); while(x!=x1){ if (d<=0) d+=incD; else { d+=incDD; y+=yadd; } x+=xadd; [self plotPoint:x :y]; } } return self; } // TGIF commands - cmdDraw:(int)x :(int)y { [self line:curX :curY :x :y]; [self cmdMove:x :y]; return self; } - cmdMove:(int)x :(int)y { curX=x; curY=y; return self; } - cmdCircle:(int)r { // draw circle of radius r at current cursor location return self; } - cmdFps:(int)f { // set frames per second return self; } - cmdHold:(int)seconds { // set the hold value return self; } - cmdNewframe { // hold for hold value and erase the image [self eraseFrame]; return self; } typedef struct _ETelem { int xmin, ymax, xmax; // only need these coordinates. ymin is curLine. // xmax is only used for right-edge collision detection. int num, den, inc; // state variables of integer line calculation int xinc; BOOL leftEdge; // left or right edge-- approximation direction. struct _ETelem *next; } ETelem; void addtoedgetable(ETelem **etable, ETelem *edge, int curLine) { ETelem *list; // if nothing on this scan line yet, add edge to this line. if(!etable[curLine]) etable[curLine]=edge; else { // something on this line. Deal. if(edge->xmin < etable[curLine]->xmin) { // add as first element edge->next=etable[curLine]; etable[curLine]=edge; } else { // traverse list at the current ymin list=etable[curLine]; while(list->next && (list->next->xmin < edge->xmin)) list=list->next; edge->next=list->next; list->next=edge; } } } - cmdDrawPoly:vertexList { ETelem **etable, *edge; int s, e, numVerts, count, i, j; id sV, eV; int sX=0, sY, eX, eY; ETelem *aet=(ETelem *)nil, *list, *aeTmp, *array[256]; int curLine=0, topLine=0; BOOL drawing=NO; if (!([[vertexList removeObjectAt:0] isMemberOf:[CmdBgnpoly class]])){ fprintf(stderr, "Polygon rejected: No bgnpoly command found.\n"); return nil; } if (!([[vertexList removeLastObject] isMemberOf:[CmdEndpoly class]])){ fprintf(stderr, "Polygon rejected: No endpoly command found.\n"); return nil; } // allocate edgetable-- currently allocated to be the number of scanlines // in the entire image. Ineffecient use of memory. etable=(ETelem **) malloc(sizeof(ETelem *) * [self pixelsHigh]); // set pointers to null bzero(etable, sizeof(ETelem *)*[self pixelsHigh]); // fill edge table for(numVerts=[vertexList count],s=0,e=1;s<numVerts;s++, e++){ if(e==numVerts) e=0; // last vertice is end to beginning sV=[vertexList objectAt:s]; eV=[vertexList objectAt:e]; sX=[sV x]; sY=[sV y]; eX=[eV x]; eY=[eV y]; edge=(ETelem *) malloc(sizeof(ETelem)); edge->next=(ETelem *)nil; if(sY<eY){ edge->xmin=sX; edge->ymax=eY; edge->xmax=eX; edge->num=eX-sX; edge->den=eY-sY; if (eY>topLine) topLine=eY; curLine=sY; } else { edge->xmin=eX; edge->ymax=sY; edge->xmax=sX; edge->num=sX-eX; edge->den=sY-eY; if (sY>topLine) topLine=sY; curLine=eY; } edge->inc=edge->den; if (edge->num > 0) edge->xinc=1; else edge->xinc=-1; addtoedgetable(etable, edge, curLine); } // build AET and process curLine=0; aet=(ETelem *)nil; // traverse to first scanline w/a vertex (no sense walking through everything // before there is anything to // process) while(!etable[curLine])curLine++; while(curLine<=topLine) { // add this scan-lines vertices to the AET (if any) if(etable[curLine]){ if(!aet) aet=etable[curLine]; else { // add to the end of the current AET aeTmp=aet; while(aeTmp && aeTmp->next) aeTmp=aeTmp->next; aeTmp->next=etable[curLine]; } } // and sort the AET count=0; aeTmp=aet; array[count++]=aeTmp; // convert the linked list into an array to do the sort // because the programmer hates sorting algorithms w/a passion // and would rather use a pre-written one from K&R while(aeTmp && aeTmp->next) { aeTmp=aeTmp->next; array[count++]=aeTmp; } for(i=1; i<count; i++){ aeTmp=array[i]; for(j=i-1; (j>=0) && (array[j]->xmin > aeTmp->xmin); j--) array[j+1] = array[j]; array[j+1]=aeTmp; } aet=array[0]; for(i=1;i<count;i++){ array[i-1]->next=array[i]; } if(array[count-1]) array[count-1]->next=(ETelem *)nil; array[count]=(ETelem *)nil; // fill pixels. drawing=NO; for(aeTmp=aet; aeTmp; aeTmp=aeTmp->next){ // avoid ymax==curLines points-- they do not count. (3.3) if(aeTmp->ymax>curLine) if(drawing){ // draw span [self line:sX :curLine :aeTmp->xmin :curLine]; #ifdef DEBUG fprintf(stderr,"Span: %d %d %d %d\n", sX, curLine, aeTmp->xmin, curLine); #endif // toggle flag drawing=NO; aeTmp->leftEdge=NO; // this is a right edge } else { // starting new span-- set start x sX=aeTmp->xmin; // toggle flag drawing=YES; aeTmp->leftEdge=YES; // this is a left edge } } // remove y=ymax // strip from the head, if needed while(aet && aet->ymax==curLine) { aeTmp=aet; aet=aet->next; free(aeTmp); } list=aet; while(list && list->next){ if(list->next->ymax==curLine) // cut out the edge list->next=list->next->next; list=list->next; } // update the yvalue curLine++; // update the xmin values on all edges that are left for(list=aet; list; list=list->next){ // ignore vertical lines if(list->den){ // increase the numerator list->inc+=ABS(list->num); while(list->inc > list->den){ list->xmin+=list->xinc; list->inc-=list->den; } } if((list->xmax == list->xmin) && !list->inc) list->xmin-=1; } } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.