home *** CD-ROM | disk | FTP | other *** search
- /***************************************************************************
- * *
- * Program: PCH3, Load & display a 256-colour, 320x200 picture. *
- * Description: Example program 3 for the PC Home series on PC *
- * graphics for C programmers. *
- * Notes: This program ONLY displays 256-colour VGA images. *
- * Don't ask it to display any other kind of picture *
- * because it will fall over. I have written this *
- * program to show the effectiveness of screen buffers. *
- * If you pass parameter 2 as "N", the program will load*
- * the file directly to screen; if you pass it as "Y", *
- * the program will load the file to an off-screen *
- * buffer, and then copy it to the screen. If you've *
- * got a very fast machine (486/33+) with a fast SCSI *
- * hard drive or very large cache, you might not notice *
- * any difference - lucky old you. Everyone else will *
- * immediately see one reason for buffering screens. *
- * *
- * NOTE!! LARGE MODEL ONLY *
- * *
- * NOTE!! We're doing some quite involved stuff now, so this *
- * source file is quite big. Starting next month, I'll *
- * provide all the "support" functions in a separate *
- * .OBJ file, so you can concentrate on the new stuff. *
- * So you don't feel cheated, I'll continue to provide *
- * the source to these functions, but in a different .C *
- * file. Later in the series, I'll bundle all these *
- * functions into a proper .LIB file, so you can use all*
- * these lovely facilities we've developed as if they *
- * were a part of the base functionality of your *
- * compiler. *
- * *
- * LAST NOTE!! Unless it's been excluded for reasons of space or *
- * copyright, you'll find a file called ROSE.PCX on the *
- * coverdisk. This is useful for two reasons: firstly, *
- * it's a lovely picture; secondly, it's an example of *
- * of a PCX file that is larger in it's "compressed" *
- * form than it would be as a straightforward bitmap. *
- * See, I told you... *
- * *
- ***************************************************************************/
-
- #include "stdio.h" /* For standard disk file functions */
- #include "alloc.h" /* We need to allocate a few memory blocks */
- #include "dos.h" /* For PC-specific things like MK_FP */
- #include "string.h" /* String functions to understand the parameters */
-
- #define OK 0
- #define VGA_mem (char *)0xA0000000 // The start seg/off of VGA memory
-
-
- /* This is the standard PCX header. As I mentioned in the article, most of
- this stuff is useless to us, because we're only interested in 256-colour
- files. */
-
- typedef struct {
- char PCX_id; /* ALWAYS 0Ah (10 decimal), for some reason */
- char version; /* If this is not 5, you're not interested */
- char encoding; /* Ignore */
- char bits_per_pixel; /* Either 1 or 8, or you're not interested */
- int xmin, ymin; /* The top-left co-ords of the original image */
- int xmax, ymax; /* The bottom-right co-ords of the original
- image. XMAX-XMIN=Height, YMAX-YMIN=Width.
- BUT!! You must add 1 to the result, because
- PCX will store, for example, 0-319, which
- is a total of 320 pixels. Sorry: I didn't
- design the bloody thing! */
- int hscreen; // If these values aren't 320 and 200, you aren't
- int vscreen; // interested in this file. Throw it out.
- char palette[48]; /* For 16-colour images. Ignore it */
- char filler1; /* Ignore */
- char colour_planes; /* If "bits_per_pixel"=1, this must be 8. If
- it's not, you don't want it. */
- int bytes_per_ln; /* Always 320, or you aren't interested. */
- int pal_type; /* Ignore */
- char filler2[58]; /* Ignore */
- } PCXHDR;
-
- PCXHDR hdr; /* Define a PCX header structure called hdr */
- unsigned int width, depth; /* ... of the image */
- unsigned int bytes, bits;
- char *palette; /* Pointer to some memory to store our palette */
- char pcxfnam[13]; /* PCX file name (XXXXXXXX.XXX\0 = 13 chars) */
- char far *sbptr; /* If you're using a buffer, this its pointer */
-
- char far *g_FPtr(char far *ptr, unsigned int l);
-
- main(int argc, char *argv[])
- {
-
- FILE *fp; /* Pointer to FILE structure for PCX file */
- char pcxfnam[13], *s1, buffer_flag;
- unsigned ss;
-
- if (argc <= 2)
- {
- printf("\nUsage: PCH3 {PCX-file-name} {Buffer-screen-flag}\n");
- exit(0);
- }
-
- strcpy(pcxfnam, argv[1]); /* String pcxfnam = 1st argument */
-
- buffer_flag = *argv[2]; /* Convert the string "argv[2]" to a char */
-
- if (buffer_flag == 'Y' || buffer_flag == 'y')
- {
- sbptr = calloc(64000, 1); /* If you've no calloc, use malloc & clear it*/
- sbptr = g_FPtr(sbptr, 0);
- }
-
- palette = malloc(768); /* These should work with all C compilers, but if
- you've got a funny 'un, use whatever function
- your compiler provides to allocate the correct
- amount of memory, and then assign the pointers
- to point to the start of this memory */
-
- fp = fopen(pcxfnam, "rb"); /* Open PCX file */
- if (fp == NULL)
- {
- printf("\n%s: File does not exist, or cannot be opened\n", pcxfnam);
- exit(0);
- }
-
- if (fread( (char *)&hdr, 1, sizeof(PCXHDR), fp) != sizeof(PCXHDR))
- {
- printf("\n%s: File is not a PCX file\n", pcxfnam);
- exit(0);
- }
-
- /* A note on the above, for less experienced C programmers. The bit that
- says "(char *)&hdr" is rather opaque. It says "Take structure 'hdr',
- find its address in memory (that's the &), and then "typecast" this
- address into a 'char *', that is, a character pointer." Clear as mud?
- Never mind, just use it as is if you don't follow. */
-
- if (hdr.PCX_id != 0x0a) /* The famous check - byte 1 must be 0Ah! */
- {
- printf("\n%s: File is not a PCX file\n", pcxfnam);
- exit(0);
- }
-
- /* OK. If you've got this far, you've either got a PCX file, or you've got
- some other bloody file that is longer than 128 bytes and has 0Ah as its
- first byte. Not much of a check, is it? Let's check out some of the
- other values. */
-
- if (hdr.version != 5) /* I don't know why 5, but there you go. If
- it's not version 5, then either it's not 256-
- colour, or it's been written by a package
- that isn't following the rules... */
- {
- printf("\n%s: File is not a 256-colour PCX file\n");
- exit(0);
- }
-
- if (hdr.bits_per_pixel == 1)
- bits = hdr.colour_planes;
- else
- bits = hdr.bits_per_pixel;
-
- if (bits != 8) /* Because PCX 256-colour has 8 colour bits per pixel */
- {
- printf("\n%s: File is not a 256-colour PCX file\n");
- exit(0);
- }
-
-
- /* The last check we can do before unpacking the file is check that it has a
- valid palette chunk on the end. The PCX palette data for 256-colour
- images is simply a lump of data tagged on the end of the file, separated
- from the image data by - for some reason - the character 0Ch. You read
- the 769th character from the end of file; if it's 12 (0Ch), you can carry
- on. */
-
-
- if ( !fseek(fp, -769L, SEEK_END) )
-
- /* So, in English, if you can read from the end of file (SEEK_END), backwards
- 769 positions, you can carry on. If your compiler doesn't have SEEK_END
- defined, use 2 (it's #defined in the Turbo C include files). */
- {
- if ( fgetc(fp) != 0x0c || fread(palette, 1, 768, fp) != 768 )
- {
- printf("\n%s: Invalid palette information\n", pcxfnam);
- exit(0);
- }
- }
- else
- {
- printf("\n%s: Could not find palette information\n", pcxfnam);
- exit(0);
- }
-
- /* Got this far? Good! You can now be pretty sure that you've got a valid
- 256-colour PCX file (or someone extremely determined to see you fail).
- Let's get back to the start of the image data. */
-
- fseek(fp, 128L, SEEK_SET); /* Point to just after the header block */
-
- width = (hdr.xmax - hdr.xmin) + 1; /* REMEMBER TO ADD 1! */
- depth = (hdr.ymax - hdr.ymin) + 1; /* " " " " */
- bytes = hdr.bytes_per_ln;
-
- /* We've checked everything we can check, and we've read the palette. Let's
- go the whole hog and unpack the data. Pass argument 2 along, so that the
- function knows whether to unpack direct to screen or to a buffer. */
-
-
- unpackPCXfile(fp, buffer_flag);
-
- getch(); /* Press any key when you're bored with the picture... */
-
- /* Close the file, switch back to text mode and end the program */
- fclose(fp);
- g_SetTxt();
- return(0);
-
- }
-
-
- unpackPCXfile(FILE *fp, char buffer_flag)
- {
-
- int i;
-
- g_SetVGA(); /* Switch the hardware over, same as last month */
-
- g_SetVGAPalette(palette); /* We have to use a DOS service to set the VGA
- palette to that stored with the image. If
- we don't, we'll probably end up with a very
- strange-looking picture indeed. */
-
- for (i=0; i<depth; i++) /* Outer loop - do "depth" (height) times */
- {
- if (i >= 200) // A quick check - shouldn't be necessary - to
- break; // ensure we don't run off the bottom of the
- // screen - this is an almost foolproof way of
- // crashing the computer.
-
- if (buffer_flag == 'Y' || buffer_flag == 'y')
- readPCXline(g_FPtr(sbptr, i*320) , fp); // Unpack to buffer
- else
- readPCXline(g_FPtr( VGA_mem, i*320), fp); // Unpack direct to screen
- }
-
- if (buffer_flag == 'Y' || buffer_flag == 'y') // If you have written to
- g_SwapScr(); // a buffer, you now need
- // to copy it to the
- // screen.
-
- return(OK);
-
- }
-
-
- readPCXline(char far *x, FILE *fp)
- {
-
- int n=0, i;
- unsigned char c;
- char far *p;
-
- p = x;
-
- memset(p, 0, width); /* Set the current line to colour 0 */
-
- do
- {
- c = fgetc(fp); /* Read a character from file */
-
-
- /* OK. The unpacking algorithm, in case my article is too boring for you to
- bother reading, is this:-
- 1) If the character is less than 192, great. Simply copy it to the
- screen and read the next character.
- 2) If the character is greater than or equal to 192, subtract 192 from
- it, and store the result in "repeat value".
- 3) Read the next character from file.
- 4) Place this character "repeat value" times into the screen.
- 5) Read the next character.
- */
- if ( c >= 192 ) /* The check in step 1 */
- {
- i = c - 192; /* Step 2 */
- c = fgetc(fp); /* Step 3 */
- while (i--) /* Step 4 */
- p[n++] = c; /* " " */
- }
- else
- p[n++] = c; /* Ordinary character copy, stick it on screen */
-
- } while (n < width); /* Until the last byte in this line */
-
- return(OK);
-
- }
-
- g_SetVGA()
- {
- union REGS regs;
- regs.h.al = 0x13; /* 256-colour VGA */
- regs.h.ah = 0x00; /* Set graphics mode */
- int86(0x10, ®s, ®s); /* Request DOS service 10h */
-
- return(OK);
-
- }
-
-
- g_SetTxt()
- {
- union REGS regs;
- regs.h.al = 0x03; /* Text mode */
- regs.h.ah = 0x00; /* Set graphics mode */
- int86(0x10, ®s, ®s); /* Request DOS service 10h */
-
- return(0);
-
- }
-
-
- g_SetVGAPalette(char *p)
- {
-
- union REGS regs;
- struct SREGS sr;
- int i;
-
- for (i=1; i<=768; i++)
- p[i] = p[i] >> 2; /* Although VGA-256 palette values are held
- in whole bytes, they are really only 6
- bits long. Therefore, to get them to
- their proper values, we have to right-
- shift them by two bits (that is, divide
- them by four). */
-
- regs.x.ax = 0x1012; /* Interrupt 10h, service 12h */
- regs.x.bx = 0;
- regs.x.cx = 256;
- regs.x.dx = FP_OFF(p);
- sr.es = FP_SEG(p);
- int86x(0x10, ®s, ®s, &sr);
-
- return(OK);
-
- }
-
- g_SwapScr()
- {
-
- unsigned s1, s2, o1, o2;
-
- sbptr = g_FPtr(sbptr, 0); // This rationalises the pointer - see the notes
- // at the top of function g_FPtr.
-
- s1 = FP_SEG(sbptr); /* Buffer segment... */
- o1 = FP_OFF(sbptr); /* ... and offset */
- s2 = 0xA000; /* VGA display memory starts at A000h */
- o2 = 0;
-
-
- movedata(s1, o1, s2, o2, 64000);
-
- return(OK);
- }
-
- /* Function g_FPtr() REALLY needs some explanation! The purpose of it is to
- "rationalise" a pointer and a fudging value into the most efficient
- possible configuration.
-
- Say you've got a pointer set to 93CB:C350. This is the same as A000:0000.
- They both point to the top of the VGA screen. If you try to copy 30000
- bytes to the first pointer, however, you'll only get the first 15535; the
- "offset" portion of the pointer will wrap around to 0 when it reaches FFFF.
- The upshot is that the latter 14465 bytes will overwrite the memory area
- immediately after 93CB:0000 - in English, you'll crash the machine.
-
- Function g_FPtr() exists to turn 93CB:C350 into A000:0000; therefore, it
- allows you to copy blocks upto 64K in size without worries. It works by
- loading as much of the offset value as it can onto the segment value. It
- really needs an entire article in itself, so if you don't understand it,
- do one of the following things:-
- 1) Buy a book on assembler/advanced C.
- 2) Just use the function and don't worry about it (simplest by far).
- 3) Use the HUGE memory model for everything (this is slow, however).
- 4) Write to Matthew McGrath, PC Home's editor, and beg him for an article
- on the subject. If he gets enough letters, he might agree, and I'm all
- for that because then I get extra money! Great, isn't it? */
-
-
-
- char *g_FPtr(char far *ptr, unsigned int l)
- {
-
- unsigned int seg, off;
-
- seg = FP_SEG(ptr);
- off = FP_OFF(ptr);
-
- seg += (off/16);
- off &= 0x000f;
- off += (unsigned int)(l & 0x000fL);
- seg += (l / 16L);
- ptr = MK_FP(seg, off);
-
- return(ptr);
-
- }