/*
 * Read a Microsoft/IBM BMP file.
 *
 * Based on bmp.c for xli by Graeme Gill,
 *
 * Based on tga.c, and guided by the Microsoft file format
 * description, and bmptoppm.c by David W. Sanderson.
 *
 */
 
#include <stdio.h>
#include <stdlib.h>

#include <X11/Intrinsic.h>
#include <X11/xpm.h>

#include "xpaint.h"
#include "image.h"
#include "libpnmrw.h"

#define C_WIN   1		/* Image class */
#define C_OS2   2

#define BI_RGB  0		/* Compression type */
#define BI_RLE8 1
#define BI_RLE4 2
#define BI_BITFIELDS 3
#define BI_JPEG      4   /* BMP version 5 (not yet supported) */
#define BI_PNG       5   /* BMP version 5 (not yet supported) */

#define BMP_FILEHEADER_LEN 14

#define WIN_INFOHEADER_LEN 108
#define OS2_INFOHEADER_LEN 12

extern void *xmalloc(size_t n);

/* Header structure definition. */
typedef struct {
	int class;			/* Windows or OS/2 */
	unsigned long FileSize;	/* Size of file in bytes */
	unsigned int  xHotSpot;		/* Not used */
	unsigned int  yHotSpot;		/* Not used */
	unsigned long Offset;	/* Offset of image bits from start of header */

	unsigned long HeaderSize;		/* Size of info header in bytes */
	unsigned long Width;		/* Image width in pixels */
	unsigned long Height;		/* Image height in pixels */
	unsigned int  Planes;		/* Planes. Must == 1 */
	unsigned int  BitCount;	/* Bits per pixels = 1,4,8,16,24,32 */
	unsigned long Compression;	/* Compression type */
	unsigned long SizeImage;	/* Size of image in bytes */
	unsigned long XPelsPerMeter;	/* X pixels per meter */
	unsigned long YPelsPerMeter;	/* Y pixels per meter */
	unsigned long ColorUsed;	/* Number of colormap entries (0 == max) */
	unsigned long ColorImportant;	/* Number of important colors */
        unsigned int  RedMask;
	unsigned int  GreenMask;
	unsigned int  BlueMask;
	unsigned int  AlphaMask;
	unsigned int  CsType;
	unsigned int  Endpoints[9];
	unsigned int  GammaRed;
	unsigned int  GammaGreen;
	unsigned int  GammaBlue;
} bmpHeader;



#define GULONG4(bp) ((unsigned long)(bp)[0] + 256 * (unsigned long)(bp)[1] \
	+ 65536 * (unsigned long)(bp)[2] + 16777216 * (unsigned long)(bp)[3])
#define GULONG2(bp) ((unsigned long)(bp)[0] + 256 * (unsigned long)(bp)[1])
#define GUINT2(bp) ((unsigned int)(bp)[0] + 256 * (unsigned int)(bp)[1])

/* A block fill routine for BSD style systems */
void 
bfill(char *s, int n, int c)
{

        int b;
                
        /* bytes to next word */
        b = (0 - (long) s) & (sizeof(unsigned long) - 1);
        if (n < b)
                b = n;
        while (n != 0) {
                n -= b;
                while (b-- > 0)
                        *s++ = c;
                if (n == 0)
                        return;
                /* words to fill */
                b = n & ~(sizeof(unsigned long) - 1);
                if (b != 0) {
                        unsigned long f;
                        int i;
                        f = c & (((unsigned long) -1) >> ((sizeof(unsigned long) - sizeof(char)) * 8));
                        for (i = sizeof(char); i < sizeof(unsigned long); i *= 2)
                                f |= (f << (8 * i));
                        n -= b; /* remaining count */
                        while (b > 0) {
                                *((unsigned long *) s) = f;
                                s += sizeof(unsigned long);
                                b -= sizeof(unsigned long);
                        }
                }
                b = n;
	}
}

/* Read the header of the file, and */
/* Return TRUE if this looks like a bmp file */
static Boolean read_bmpHeader(FILE * fd, bmpHeader * hp)
{
	unsigned char buf[WIN_INFOHEADER_LEN];	/* largest we'll need */
        int l;

        /* Reset data to zero */
        memset(buf, 0, WIN_INFOHEADER_LEN);

        /* Read header */
	if (fread(buf, 1, BMP_FILEHEADER_LEN, fd) != BMP_FILEHEADER_LEN)
		return FALSE;

	if (buf[0] != 'B' || buf[1] != 'M')
		return FALSE;	/* bad magic number */

	hp->FileSize = GULONG4(&buf[2]);
	hp->xHotSpot = GUINT2(&buf[6]);
	hp->yHotSpot = GUINT2(&buf[8]);
	hp->Offset = GULONG4(&buf[10]);

	/* Read image header info */
	if (fread(buf, 1, 4, fd) != 4)
		return FALSE;

	hp->HeaderSize = GULONG4(&buf[0]);

        if (hp->HeaderSize == OS2_INFOHEADER_LEN) {
		hp->class = C_OS2;
                l = OS2_INFOHEADER_LEN - 4;
        } else {
		hp->class = C_WIN;
                l = WIN_INFOHEADER_LEN - 4;
	}

	if (l>0 && fread(buf+4, 1, l, fd) != l)
		return FALSE;

        if (hp->class == C_WIN) {
        	hp->Width = GULONG4(&buf[4]);
		hp->Height = GULONG4(&buf[8]);
		hp->Planes = GUINT2(&buf[12]);
		hp->BitCount = GUINT2(&buf[14]);
		hp->Compression = GULONG4(&buf[16]);
		hp->SizeImage = GULONG4(&buf[20]);
		hp->XPelsPerMeter = GULONG4(&buf[24]);
		hp->YPelsPerMeter = GULONG4(&buf[28]);
		hp->ColorUsed = GULONG4(&buf[32]);
		hp->ColorImportant = GULONG4(&buf[36]);
        	hp->RedMask =  GULONG4(&buf[40]);
        	hp->GreenMask =  GULONG4(&buf[44]);
        	hp->BlueMask =  GULONG4(&buf[48]);
        	hp->AlphaMask =  GULONG4(&buf[52]);
        	hp->CsType =  GULONG4(&buf[56]);
        	for (l=0; l<9; l++) hp->Endpoints[l] = GULONG4(&buf[60+4*l]);
        	hp->GammaRed =  GULONG4(&buf[96]);
        	hp->GammaGreen =  GULONG4(&buf[100]);
        	hp->GammaBlue =  GULONG4(&buf[104]);
	} else {
		hp->Width = GULONG2(&buf[4]);
		hp->Height = GULONG2(&buf[6]);
		hp->Planes = GUINT2(&buf[8]);;
		hp->BitCount = GUINT2(&buf[10]);;
		hp->Compression = BI_RGB;
		hp->SizeImage = 0;
		hp->XPelsPerMeter = 0;
		hp->YPelsPerMeter = 0;
		hp->ColorUsed = 0;
		hp->ColorImportant = 0;
	}

	/* Check for file corruption */

	if (hp->BitCount != 1
	    && hp->BitCount != 4
	    && hp->BitCount != 8
	    && hp->BitCount != 16
	    && hp->BitCount != 24
	    && hp->BitCount != 32) {
	    fprintf(stderr, "ReadBMP: Illegal image BitCount %d\n", 
                    hp->BitCount);
		return FALSE;
	}

	if ((hp->Compression != BI_RGB
	     && hp->Compression != BI_RLE8
	     && hp->Compression != BI_RLE4
	     && hp->Compression != BI_BITFIELDS)
	    || (hp->Compression == BI_RLE8 && hp->BitCount != 8)
	    || (hp->Compression == BI_RLE4 && hp->BitCount != 4)) {
		fprintf(stderr,
			"ReadBMP: Illegal image compression type %ld\n",
                        hp->Compression);
		return FALSE;
	}
	if (hp->Planes != 1) {
	    fprintf(stderr, "ReadBMP: Illegal image Planes value %d\n", hp->Planes);
		return FALSE;
	}
	/* Fix up a few things */
	if (hp->BitCount < 24) {
		if (hp->ColorUsed == 0
		    || hp->ColorUsed > (1 << hp->BitCount))
			hp->ColorUsed = (1 << hp->BitCount);
	} else
		hp->ColorUsed = 0;

	return TRUE;
}

int
TestBMP(char *fullname)
{
	FILE *fd;
	bmpHeader hdr;

	if (!(fd = fopen(fullname, "r"))) {
		return FALSE;
	}
	if (!read_bmpHeader(fd, &hdr)) {
		fclose(fd);
		return FALSE;	/* Nope, not a BMP file */
	}
	fclose(fd);
	return TRUE;
}

Image *
ReadBMP(char *fullname)
{
	FILE *fd;
	bmpHeader hdr;
	Image *image;
        unsigned char * line;
	Boolean data_bounds = FALSE;

	if (!(fd = fopen(fullname, "r"))) {
		perror("bmpIdent");
		return (0);
	}
	if (!read_bmpHeader(fd, &hdr)) {
		fclose(fd);
		return NULL;	/* Nope, not a BMP file */
	}
#if 0
        /* Print a brief description of the image */
        printf( "%s :\nis a %lux%lu %d bit deep %s BMP image, Compression Mode %d,\n", fullname,
		hdr.Width, hdr.Height, hdr.BitCount,
		hdr.class == C_WIN ? "Windows" : "OS/2",
                hdr.Compression);       
        printf( "File Size: %d Header Size: %d Offset: %d\n", 
                hdr.FileSize, hdr.HeaderSize, hdr.Offset);
        printf( "Masks : R=%d G=%d B=%d A=%d\n", 
                hdr.RedMask, hdr.GreenMask, hdr.BlueMask, hdr.AlphaMask);
        printf( "CsType : %d\n", hdr.CsType);
#endif

	/* Skip to offset specified in file header for image data */
        fseek(fd, BMP_FILEHEADER_LEN+hdr.HeaderSize, SEEK_SET);

	/* Create the appropriate image and colormap */
	if (hdr.BitCount < 16) {
		/* must be 1, 4 or 8 bit mapped type */
		int i, j, n = 3;
		unsigned char buf[4];
		/* maximum number of colors */
		int used = (1 << hdr.BitCount);

		if (hdr.BitCount == 1)	/* bitmap */
			image = ImageNewBW(hdr.Width, hdr.Height);
		else
     		if (hdr.BitCount <= 8)	/* colormap image */
			image = ImageNewCmap(hdr.Width, hdr.Height, used);
	        else
			image = ImageNew(hdr.Width, hdr.Height);

		if (hdr.class == C_WIN)
			n++;
	        j = 0;
		for (i = 0; i < hdr.ColorUsed; i++) {
			if (fread(buf, 1, n, fd) != n) {
				fprintf(stderr, "ReadBMP: Short read within Colormap\n");
				ImageDelete(image);
				fclose(fd);
				return NULL;
			}
                        image->cmapData[j++] = buf[2];
                        image->cmapData[j++] = buf[1];
                        image->cmapData[j++] = buf[0];
		}

		/* init rest of colormap (if any) */
		for (; i < used; i++) {
			image->cmapData[j++] = 0;
  			image->cmapData[j++] = 0;
			image->cmapData[j++] = 0;
		}
	} else {		/* else must be a true color image */
                image = ImageNew(hdr.Width, hdr.Height);
	}

	/* Skip to offset specified in file header for image data */
        fseek(fd, hdr.Offset, SEEK_SET);

	/* Read the pixel data */
	if (hdr.BitCount == 1) {
		unsigned char *data, *buf, pad[4];
		int i, illen, padlen, y;

		/* round bits up to byte */
		illen = (image->width + 7) / 8;
		/* extra bytes to word boundary */	
		padlen = (((image->width + 31) / 32) * 4) - illen;
		/* start at bottom */
		data = image->data + (image->height - 1) * image->width;
		buf = (unsigned char *)xmalloc(illen+1);
		if (!buf) goto data_short;
		for (y = image->height; y > 0; y--) {
			/* BMP files are left bit == ms bit,
			 * so read straight in.
			 */
			if (fread(buf, 1, illen, fd) != illen
			    || fread(pad, 1, padlen, fd) != padlen)
				goto data_short;
			/* convert 8-pixel per byte to 1-pixel per byte */
			for (i=0; i<image->width; i++)
			    data[i] = (buf[i>>3]>>(7-(i&7)))&1;
                        data -= image->width;
		}
		free(buf);
	} else if (hdr.BitCount == 4) {
		unsigned char *data;
		int illen, x, y;

		illen = image->width;
		/* start at bottom */
		data = image->data + (image->height - 1) * illen;

		if (hdr.Compression == BI_RLE4) {
			int d, e;
			bzero((char *) image->data,
				image->width * image->height);
			for (x = y = 0;;) {
				int i, f;
				if ((d = fgetc(fd)) == EOF)
					goto data_short;
				if (d != 0) {	/* run of pixels */
					x += d;
					if (x > image->width ||
							y > image->height) {
						/* don't run off buffer */
						data_bounds = TRUE;
						/* ignore this run */
						x -= d;	
						if ((e = fgetc(fd)) == EOF)
							goto data_short;
						continue;
					}
					if ((e = fgetc(fd)) == EOF)
						goto data_short;
					f = e & 0xf;
					e >>= 4;
					for (i = d / 2; i > 0; i--) {
						*(data++) = e;
						*(data++) = f;
					}
					if (d & 1)
						*(data++) = e;
					continue;
				}
				/* else code */
				if ((d = fgetc(fd)) == EOF)
					goto data_short;
				if (d == 0) {	/* end of line */
					data -= (x + illen);
					x = 0;
					y++;
					continue;
				}
				/* else */
				if (d == 1)	/* end of bitmap */
					break;
				/* else */
				if (d == 2) {	/* delta */
					if ((d = fgetc(fd)) == EOF ||
							(e = fgetc(fd)) == EOF)
						goto data_short;
					x += d;
					data += d;
					y += e;
					data -= (e * illen);
					continue;
				}
				/* else run of literals */
				x += d;
				if (x > image->width || y > image->height) {
					int btr;
					/* don't run off buffer */
					data_bounds = TRUE;
					x -= d;		/* ignore this run */
					btr = d / 2 + (d & 1)
						+ (((d + 1) & 2) >> 1);
					for (; btr > 0; btr--) {
						if ((e = fgetc(fd)) == EOF)
							goto data_short;
					}
					continue;
				}
				for (i = d / 2; i > 0; i--) {
					if ((e = fgetc(fd)) == EOF)
						goto data_short;
					*(data++) = e >> 4;
					*(data++) = e & 0xf;
				}
				if (d & 1) {
					if ((e = fgetc(fd)) == EOF)
						goto data_short;
					*(data++) = e >> 4;
				}
				if ((d + 1) & 2)	/* read pad byte */
					if (fgetc(fd) == EOF)
						goto data_short;
			}
		} else {	/* No 4 bit rle compression */
			int d, s, p;
			int i, e;
			d = image->width / 2;	/* double pixel count */
			s = image->width & 1;	/* single pixel */
			p = (4 - (d + s)) & 0x3;	/* byte pad */
			for (y = image->height; y > 0; y--, data -= (2 * illen)) {
				for (i = d; i > 0; i--) {
					if ((e = fgetc(fd)) == EOF)
						goto data_short;
					*(data++) = e >> 4;
					*(data++) = e & 0xf;
				}
				if (s) {
					if ((e = fgetc(fd)) == EOF)
						goto data_short;
					*(data++) = e >> 4;
				}
				for (i = p; i > 0; i--)
					if (fgetc(fd) == EOF)
						goto data_short;
			}
		}
	} else if (hdr.BitCount == 8) {
		unsigned char *data;
		int illen, x, y;

		illen = image->width;
		/* start at bottom */
		data = image->data + (image->height - 1) * illen;

		if (hdr.Compression == BI_RLE8) {
			int d, e;

			bzero((char *) image->data,
			      image->width * image->height);
			for (x = y = 0;;) {
				if ((d = fgetc(fd)) == EOF)
					goto data_short;
				if (d != 0) {	/* run of pixels */
					x += d;
					if (x > image->width ||
							y > image->height) {
						/* don't run off buffer */
						data_bounds = TRUE;
						/* ignore this run */
						x -= d;
						if ((e = fgetc(fd)) == EOF)
							goto data_short;
						continue;
					}
					if ((e = fgetc(fd)) == EOF)
						goto data_short;
					bfill( (char*) data, d, e);
					data += d;
					continue;
				}
				/* else code */
				if ((d = fgetc(fd)) == EOF)
					goto data_short;
				if (d == 0) {	/* end of line */
					data -= (x + illen);
					x = 0;
					y++;
					continue;
				}
				/* else */
				if (d == 1)	/* end of bitmap */
					break;
				/* else */
				if (d == 2) {	/* delta */
					if ((d = fgetc(fd)) == EOF ||
							(e = fgetc(fd)) == EOF)
						goto data_short;
					x += d;
					data += d;
					y += e;
					data -= (e * illen);
					continue;
				}
				/* else run of literals */
				x += d;
				if (x > image->width || y > image->height) {
					int btr;
					/* don't run off buffer */
					data_bounds = TRUE;
					/* ignore this run */
					x -= d;	
					btr = d + (d & 1);
					for (; btr > 0; btr--) {
						if ((e = fgetc(fd)) == EOF)
							goto data_short;
					}
					continue;
				}
				if (fread(data, 1, d, fd) != d)
					goto data_short;
				data += d;
				if (d & 1)	/* read pad byte */
					if (fgetc(fd) == EOF)
						goto data_short;
			}
		} else {	/* No 8 bit rle compression */
			unsigned char pad[4];
			int padlen;

		        /* extra bytes to word boundary */
			padlen = ((image->width + 3) & ~3) - illen;
			for (y = image->height; y > 0; y--, data -= illen) {
				if (fread(data, 1, illen, fd) != illen
				    || fread(pad, 1, padlen, fd) != padlen)
					goto data_short;
			}
		}
	} else if (hdr.BitCount == 16) {
	        unsigned char *data, *ptr;
	        unsigned char u, v;
		int illen, y;
		illen = 4 * ((image->width+1)>>1);
		/* start at bottom */	
                line = (unsigned char *)xmalloc(illen); 
		for (y = image->height-1; y >= 0; y--) {
		        int i;
		        data = image->data + 3 * y * image->width;
			/* BMP files are RGB, so read straight in. */
			if (fread(line, 1, illen, fd) != illen)
				goto data_short;
			/* Oh, no they're not */
                        ptr = line;
                        if (hdr.GreenMask == 2016)
			for (i = 0; i < image->width; i++) {
		                u = *ptr++;
                                v = *ptr++;
				*data++ = v&0xf8;
				*data++ = ((v&7)<<5) | ((u&0xe0)>>3);
                                *data++ = (u&0x1f)<<3;
			} else
			for (i = 0; i < image->width; i++) {
		                u = *ptr++;
                                v = *ptr++;
				*data++ = (v&0x7c)<<1;
				*data++ = ((v&3)<<6) | ((u&0xe0)>>2);
                                *data++ = (u&0x1f)<<3;
			} 
		}
                free(line);
	} else if (hdr.BitCount == 24) {  /* hdr.BitCount == 24 */
		unsigned char *data, pad[4];
		int illen, padlen, y;
		illen = 3 * image->width;
	        /* extra bytes to word boundary */
	        padlen = ((illen + 3) & ~3) - illen;
		/* start at bottom */	
		data = image->data + (image->height - 1) * illen;	
		for (y = image->height; y > 0; y--, data -= illen) {
			int i;

			/* BMP files are RGB, so read straight in. */
			if (fread(data, 1, illen, fd) != illen
                            || fread(pad, 1, padlen, fd) != padlen)
				goto data_short;
			/* Oh, no they're not */
			for (i = 3 * image->width - 1; i > 0; i -= 3) {
				int t = data[i];
				data[i] = data[i - 2];
				data[i - 2] = t;
			}
		}
	} else {
	        unsigned char *data, *alpha = NULL;
		int illen, y;                
		illen = 4 * image->width;
                if (hdr.Compression > BI_RGB && hdr.AlphaMask>>24)
                    image->alpha = (unsigned char *) 
		        xmalloc(image->width * image->height);
		/* start at bottom */	
                line = (unsigned char *)xmalloc(illen); 
		for (y = image->height - 1; y >= 0; y--) {
		        int i, j=0, k=0;
		        data = image->data + 3 * y * image->width;
                        if (image->alpha)
                            alpha = image->alpha + y * image->width;
			if (fread(line, 1, illen, fd) != illen)
			    goto data_short;
			for (i = 0; i< image->width; i++) {
			    data[j++] = line[2+k++];
			    data[j++] = line[k++];
			    data[j++] = line[k++-2];
                            if (alpha)
                                alpha[i] = line[k++];
                            else
			        k++;
			}
		}
                free(line);
	}
	if (data_bounds)
		fprintf(stderr, "ReadBMP: Data outside image area\n");

	fclose(fd);
	return image;

      data_short:
	fprintf(stderr, "ReadBMP: Short read within Data\n");
	fclose(fd);
	return image;
}

static 
void putshort(fp, i)
     FILE *fp;
     int i;
{
   int c, c1;

   c = ((unsigned int ) i) & 0xff;  
   c1 = (((unsigned int) i)>>8) & 0xff;
   putc(c, fp);   
   putc(c1,fp);
}

static 
void putint(fp, i)
     FILE *fp;
     int i;
{
   int c, c1, c2, c3;
   c  = ((unsigned int ) i)      & 0xff;
   c1 = (((unsigned int) i)>>8)  & 0xff;
   c2 = (((unsigned int) i)>>16) & 0xff;
   c3 = (((unsigned int) i)>>24) & 0xff;

   putc(c, fp);   
   putc(c1,fp);  
   putc(c2,fp);  
   putc(c3,fp);
}

int
WriteBMP(char *file, Image * image)
{
    FILE *fp;
    int x, y, i, j, colsize, bpl, w, h, nbits, sh;
    unsigned char *ip;
    Image *cmapImage = NULL;
    unsigned char buf[3];

    if (!(fp = fopen(file, "wb")))
        return 1;
   
    /* Write BMP header */
    putc('B', fp);  putc('M', fp);           /* BMP file magic number */

    w = image->width;
    h = image->height;
    if (image->isBW) {
        colsize = 2;
        nbits = 1;
        bpl = ((w+31)/32)*4;
    } else {
        /* try compressing image to palette mode, but don't force if too big */
        if (!image->alpha)   /* can't store alpha mask with palette image */
            cmapImage = ImageCompress(image, 256, 1);
        if (cmapImage) {
            image = cmapImage;  /* original was deleted in ImageCompress() */
        }
	if (image->scale==1) {
            colsize = image->cmapSize;
            nbits = 8;
            bpl = (w+3) & ~3 ;
	} else {
            colsize = 0;
            if (image->alpha) {
	        nbits = 32;
                bpl = 4*w;
	    } else {
	        nbits = 24;
                bpl = (3*w+3) & ~3;
	    }
	}
    }

    if (image->alpha) 
        sh = 108;
    else
        sh = 40;
    /* compute filesize and write it */
    i = 14 +                /* size of BMP file header */
      sh +                  /* size of BMP info header */
      (colsize * 4) +       /* size of colormap */
      bpl * h;              /* size of image data */
   
    putint(fp, i);
    putshort(fp, 0);        /* reserved1 */
    putshort(fp, 0);        /* reserved2 */
    putint(fp, 14 + sh + (colsize * 4));  /* offset from BOfile to BObitmap */
    putint(fp, sh);          /* HeaderSize: size of bitmap info header */
    putint(fp, w);          /* Width */
    putint(fp, h);          /* Height */
    putshort(fp, 1);        /* Planes:  must be '1' */
    putshort(fp, nbits);    /* BitCount: 1,4,8,24 or 32*/
    putint(fp, (image->alpha)? BI_BITFIELDS : BI_RGB);     /* Compression:  BI_RGB, BI_RLE8 or BI_RLE4 */
    putint(fp, bpl*h);      /* SizeImage:  size of raw image data */
    putint(fp, 75 * 39);    /* XPelsPerMeter: (75dpi * 39" per meter) */
    putint(fp, 75 * 39);    /* YPelsPerMeter: (75dpi * 39" per meter) */
    putint(fp, colsize);    /* ColorUsed: # of colors used in cmap */
    putint(fp, colsize);    /* ColorImportant: same as above */
    if (image->alpha) {
        putint(fp, 0xff0000); 
        putint(fp, 0xff00);
        putint(fp, 0xff);
        putint(fp, 0xff000000); 
        for (i=0; i<13; i++) putint(fp, 0);
    }

    /* Write colormap */
    for (i=0; i<colsize; i++) {
        j = 3*i;
        putc(image->cmapData[j+2],fp);
        putc(image->cmapData[j+1],fp);
        putc(image->cmapData[j],fp);
        putc('\0',fp);
    }

    if (image->isBW) {
        for (y = h-1; y>=0; y--) {
	   *buf = 0;
           for (x = 0; x < w; x++) {
	       if (image->data[x+y*w]) *buf |= (128>>(x&7));
	       if ((x&7)==7) {
                   putc(*buf, fp);
                   *buf = 0;
	       }
	   }
	   j = bpl - w/8;
	   for (i=0; i<j; i++) {
               putc(*buf, fp);
               *buf = 0;
	   }
	}
    } else
    if (image->scale==1) {
        for (y = h-1; y>=0; y--) {
	    for (x = 0; x < w; x++)
	        putc(image->data[x+y*w], fp);
            j = bpl-w;
	    for (i=0; i<j; i++) putc('\0', fp);
	}
    } else {
        for (y = h-1; y>=0; y--) {
	    for (x = 0; x < w; x++) {
	        j = x+y*w;
	        ip = &image->data[3*j];
                buf[0] = ip[2]; buf[1] = ip[1]; buf[2] = ip[0];
	        fwrite(buf, 1, 3, fp);
                if (image->alpha)
		    putc(image->alpha[j], fp);
	    }
	    if (!image->alpha) {
                j = bpl-3*w ;
	        for (i=0; i<j; i++) putc('\0', fp);
	    }
	}
    }

    fclose(fp);
    return 0;
}
