The big dummies guide to graphics programmingC-Scene Issue #09The big dummies guide to graphics programming (Part 1)Andreas BeckThe big dummies guide to graphics programming---------------------------------------------1. Scope and introductary remarksThis Document tries to give some enlightenment about pixel oriented graphicsprogramming with lowlevel graphics libraries.It is intended for people that have never ever used a pixel orientedgraphics library before. If you have, it might be a bit boring.For the examples presented here, we use the LibGGI library which isavailable at http://www.ggi-project.org/. That's also, what links it to"C", as LibGGI is written entirely in C, and intended to be used by CPrograms, though wrappers for a bunch of languages exist.We also use the GGI terminology, as LibGGI covers a very wide range ofpossible hard- and software to draw on, which causes it to have a verybroad set of features that are not supported in many other libraries.If you are using anything but LibGGI, you will probably find, that LibGGIat times has abstraction layers for stuff that you have to access directlyon your library. This at times simplifies the task, at times makes itharder, depending on how much you give about portability.But let's get started ...2. VisualsA visual is something you can draw on. Imagine it as a piece of paper.This is actually a pretty good comparision, as pieces of paper tend to berectangular in shape. When we are talking about visuals in this document wemean a rectangular area that can be drawn to.Most simple graphics libraries (as compared to those connected to windowingsystems) have no notion of a "visual handle" - that is a "name" for avisual. This comes from the fact, that most graphics libraries can onlyhandle a single such visual at a time.LibGGI was designed to be capable of handling multi-headed applications,like a CAD system showing the model on one high-res screen while displayingthe menus on another one.For this reason you need to allocate a visual handle when working withLibGGI. Even if you do not use the multihead capabilities.Example 2.1: Getting at a visual and getting rid of it againggi_visual_t vis; /* a place to store the visual handle */if (ggiInit()) { /* Initialize LibGGI */ printf("Ouch - ggiInit failed.\n"); /* oh - we failed ??? */ return 1;}/* Allocate a visual handle for the default visual (NULL) */if (NULL==(vis=ggiOpen(NULL))) { ggiExit(); /* Deinitialize the library properly */ printf("Ouch - ggiOpen failed.\n"); return 2;}/* Now here we can actually start drawing - or ? * No. We can't. Read on, if you said "yes" :-). *//* Now we close it down again */ggiClose(vis);ggiExit();3. Coordinate systemsO.K. - we now have a visual, so we can draw on it - right ?No. We can't. We first need to think about the way a computer can storesomething drawn on a visual.One way would be something like:- go to the top-left corner- draw a line to the middle of the sheet- draw a line straight up to the top border- ...This is called "vector oriented" drawing, as you basically give a list ofmotion vectors you would follow with a pencil.You might have noticed, that giving positions with phrases like "top-leftcorner" isn't very flexible.So the first thing we need is a coordinate system.That is, we take a point on our visual and call that the "origin".Then we draw two perpendicular lines through that origin and call thatthe axes x and y.To keep things simple, we use a corner of our rectangular drawing areafor the origin and the edges that join there for the axes.In computer graphics, the chosen corner is usually the top-left one,and the top edge, spanning left->right, is called the x, the rightedge, spanning top->bottom is called the y axis.If you now apply some kind of ruler-marks to the axis you can describe anypoint on the visual with a pair of values (x,y), which you get by goingstraight up from the point in question and reading the x-ruler andas well going to the right and reading the y-ruler.Up to now we can describe an infinite number of points. That's not good, ascomputers have only a finite amount of memory, so we make our "points"a little bigger.We draw a rectangular grid on our visual that divides it into many smallrectangles and renumber our rulers, so that each row and column of thegrid gets a number ranging from 0-maxx for the columns and from 0-maxyfor the rows.Beginning at 0 instead of 1 has mathematical advantages later, though itbrings the at first sight strange property of a 320x200 grid being addressedwith pairs that use the ranges 0-319 and 0-199.In LibGGI, we can set that coordinate System, which is commonly called a"mode" using something like:if (ggiSetSimpleMode(vis, GGI_AUTO, GGI_AUTO, GGI_AUTO, GT_AUTO) != 0) { ggiPanic("Cannot set default mode!\n");}Now - what's all that "AUTO"-stuff ? Well, the prototype for the function is:int ggiSetSimpleMode(ggi_visual_t visual, int xsize, int ysize, int frames, ggi_graphtype type);What does that mean ?O.K. - first of all we have the visual, we want to operate on. (Remember ?LibGGI allows multiple visuals to be active at the same time, so we have togive that parameter for every call).The xsize and ysize members are the sizes for the grid we just talked about.Now what the heck is "GGI_AUTO" ? It is a special value, that is used to specify that you don't really careand the library may pick a value based on user preferences or systemconstraints.We use that, because LibGGI can run on such a variety of differentplatforms. On a PC, 320x200 might be a commonly accessible mode, but onhigher-end hardware, it might not even be possible to set such a tiny mode.Thus, you are advised to try not to rely on a given resolution to bepresent. You can of course insist on 320x200 by calling with(vis,320,200,...), but should be prepared that it could fail.And what are frames ?Many graphics cards have the ability to store multiple images at the sametime (while only displaying one of them). Imagine that as a stack of sheets,all the same size and with the same grid. The advantage is, that you candraw on the "invisible" sheets that are not on top of the stack, without theuser seeing the drawing going on (as he is always looking at the top of thestack). When you are done drawing, you can (the video hardware can actually) quickly swap the sheets and the user will see the final result.This is what is called doublebuffering. It makes for a much steadier look.But actually that's an advanced topic, we will pick up again later in theseries.And a graphtype ?You have probably heard about those: It's about how different pixels can be,how many different colors you can use and such. You might have heard aboutterms like "TrueColor" and "HighColor". That's what it is about, but tounderstand that concept fully, we'll have to look closer at the individual pixels:4. PixelsThe little rectangles we just made by introducing the coordinate grid is what we call "pixels".Usually pixels can be given attributes like a color, being bright, blinkingbeing transparent etc. individually [there are a few cases where hardware limitations cause pixels to depend on each other, but these are very rarenowadays].Now if you can give each such pixel a color, you can form an image from it.A simple example: 1 01234567890120 #######1 ## ##2 # # # #3 # # #4 # # # # #5 # # # #6 # ##### #7 ## ##8 #######I only used two "colors" here : ' ' and '#'.You can see, that the image is quite crude. This is due to the verylow resolution of 13x9 I was using. (Note the rulers on the sides ...)As you can probably imagine, the image quality gets better, if you increaseresolution. Of course this will also need more memory and thus as well beslower, if you change the picture.5. AttributesAs said, pixels can have attributes. Attributes tell pixels how they shouldlook like. As computers always just work with numbers, there is normallya number for each pixel that describes the attribute.For a red pixel, this might for example be the number 0xff0000 (hex) or16711680 (dez), or it might be 0x4 or whatever.Many graphics libraries assume that you understand the inner workings ofthe hardware to determine that number, which we will call the "pixelvalue". If you are lucky, they provide a few #defines for some common colors.LibGGI does not do so, as when writing a LibGGI program, you cannot know,what sick hardware someone out there might have. LibGGI thus has a way of determining the pixelvalue from an abstract description of the wantedattributes.Now how does abstractly but still precisely describe a color ?("white" is not enough. The eskimo people are said to have about 200 different words for different shades of white ...)6. Color and colorspaces.The most common attribute of a pixel is it's color.Speaking from the standpoint of physics, the color of a light source isdetermined by its wavelength. One could in theory specify a color bygiving it's wavelength and the intensity.This is however not what hardware can easily do, as wavelength-tuneabledevices are very complex stuff.What is done in reality, is to have a set of basic colors - usually red, green and blue - which can be mixed to get almost any color impression.The read/green/blue model (RGB) is used in systems that emit light, likemonitors. This is called additive color mixing, as the three colored lightsred, green and blue add up to white when turned on simultaneously.Systems that can only absorb light, like printed colors on a white paper,use the so called CMYK model, where the colors Cyan, Magenta and Yellowmixed together absorb all light thus causing a black impression.The K in CMYK stands for black, which is used in addition to the CMY, as existing colors usually do not give a perfect black, which is often a very wanted color.There are a few other color spaces the most important of which I'lldescribe briefly here, just that you know what the terms mean, when youstumble across them:a) HSV: Hue Saturation ValueThis is a very convenient model for photo-retouching and such, as itseparates the brightness (Value) from the color information, and thecolor-contrast (Saturation) from the color (Hue) itself.b) YUV: This one is used for video and similar devices, as it againseparates the brightness (Y) info from the rest, which is useful, as theeye is much more sensitive to the brightness information than it is to the color. So for lossy compression, you start with reducing the datastored in the UV components.7. The RGB model and ColormappingAs said, computer graphics almost always uses the RGB model, as it is howtheir hardware works.Thus most programmers are used to it and most image formats are based on itas well. This is why LibGGI uses it by default as well.First of all, you need to know what color you want to display and how todescribe it as an (R,G,B) triplet. To give you a place to start, I'll list afew common colors as such triplets:( 0%, 0%, 0%) - Black( 0%, 0%,100%) - Blue( 0%,100%, 0%) - Green( 0%,100%,100%) - Cyan(100%, 0%, 0%) - Red(100%, 0%,100%) - Purple(100%,100%, 0%) - Yellow(100%,100%,100%) - WhiteYou can scale down a color in its brightness by multiplying all of itsvalues with a given factor (note, that you cannot exceed 100% ... :-),so e.g. (0%,50%,50%) gives dark cyan. And you can move from one color toanother by adjusting the differing values smoothly. E.g. to get a greenishCyan, you use something like (0%,100%,50%).Now percentile values are a bit inaccurate unless you are using fixedpoint,as the eye can distinguish more than 100 levels of brightness. You can see that on older VGAs with 6 Bit palette entries which only give 64 different shades. You can't get a really smooth shading on them.Computers work with bits and bytes, so the most common thing to do, is tojust use one byte per color giving 256 shades. However there are a fewspecial purpose cards that can do more then 256 shades. For that reasonLibGGI always uses _two_ bytes per color to be safe.To ensure you do not have to know how many shades the underlying hardwarecan do, LibGGI always uses the range 0-65535 for 0-100%. The value will berounded appropriately as the hardware permits.LibGGI provides a call to make a pixelvalue from such an abstract colordescription: ggi_pixel white; ggi_color map; map.r=0xFFFF; /* this is the highest intensity value possible */ map.g=0xFFFF; map.b=0xFFFF; white=ggiMapColor(vis, &map); printf("white=%d\n", white);This might look inconvenient to people used to other graphics libs thatassume you know how the hardware encodes pixels. You'll love it, once youare trying to make your application portable. Other people have otherhardware, and the assumption often doesn't hold there ... you'll get a BIGbunch of #ifdefs or simply wrong colors without it.8. Drawing a pixelYes, we are finally there. We draw the first pixel: ggiPutPixel(vis, 0, 0, white);O.K. - that example is basically to avoid boring you with too much theorybefore you can see the first result. It draws a white pixel (you rememberwhat a pixel is, right ?) in the top left corner (remember that talk aboutcoordinate systems ? 0, 0, are the two coordinates in the order x,y orto say it more simply [left],[down]) of the visual.9. Conclusion on Part #1Now we can basically do everything we need. opening a visual, setting up acoordinate system (also known as "mode"), and drawing colored pixels is all you really need to do computerized graphics.Anything else is just convenience. We will talk about the convenient thingslike drawing lines and boxes, copying areas and rendering text as well assome other advanced topics like palettes(see Footnote 1) in upcoming continuations of the series.If you have any problems, the GGI people are usually pretty glad to help youout. Instructions on how to get onto their discussion lists are also on thewebsite. But note, that LibGGI has very powerful internal debugging mechanisms. Try them first. Often setting the environment variable GGI_DEBUG to 255 and some common sense is all you need to set things straight.---------------------Footnote 1:We have not yet covered palettes, which might give you strange results, whenexecuting your programs on palettized modes (often called 8-bit modes).You need to include the line:ggiSetColorfulPalette(vis);somewhere after the mode setup to make LibGGI work properly on such modes. |
|