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.